Jelajahi Sumber

Merge branch 'develop' into handlersAbstraction

# Conflicts:
#	CI/linux/before_install.sh
#	CI/mac/before_install.sh
#	CI/mxe/before_install.sh
#	lib/CModHandler.cpp
#	lib/mapObjects/CObjectClassesHandler.cpp
#	lib/mapObjects/CObjectClassesHandler.h
#	lib/mapObjects/CommonConstructors.cpp
#	server/CGameHandler.cpp
#	test/CMakeLists.txt
#	test/spells/effects/TeleportTest.cpp
Andrii Danylchenko 4 tahun lalu
induk
melakukan
9c8d776398

+ 118 - 0
.github/workflows/github.yml

@@ -0,0 +1,118 @@
+name: CMake
+
+on: [ push ]
+
+env:
+  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
+  BUILD_TYPE: Release
+
+jobs:
+  build:
+    strategy:
+      matrix:
+        include:
+          - platform: linux
+            os: ubuntu-20.04
+            cc: clang-10
+            cxx: clang++-10
+            test: 0
+          - platform: linux
+            os: ubuntu-20.04
+            cc: gcc-9
+            cxx: g++-9
+            test: 0
+          - platform: mac
+            os: macos-latest
+            test: 0
+            pack: 1
+            extension: dmg
+          - platform: mxe
+            os: ubuntu-20.04
+            mxe: i686-w64-mingw32.shared
+            test: 0
+            pack: 1
+            extension: exe
+            
+    runs-on: ${{ matrix.os }}
+
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: recursive
+
+    - name: Dependencies
+      run: source ${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh
+      env:
+        MXE_TARGET: ${{ matrix.mxe }}
+        
+    - name: Git branch name
+      id: git-branch-name
+      uses: EthanSK/git-branch-name-action@v1
+
+    - name: Build Number
+      run: |
+        source ${{github.workspace}}/CI/get_package_name.sh
+        echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV
+        echo VCMI_PACKAGE_NAME_SUFFIX="$VCMI_PACKAGE_NAME_SUFFIX" >> $GITHUB_ENV
+      env:
+        PULL_REQUEST: ${{ github.event.pull_request.number }}
+
+    - name: Configure CMake
+      run: |
+        mkdir ${{github.workspace}}/build
+        cd ${{github.workspace}}/build
+        cmake -G Ninja .. -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
+            -DENABLE_TEST=${{matrix.test}} \
+            -DPACKAGE_NAME_SUFFIX:STRING="$VCMI_PACKAGE_NAME_SUFFIX" \
+            -DPACKAGE_FILE_NAME:STRING="$VCMI_PACKAGE_FILE_NAME"
+      env:
+        CC: ${{ matrix.cc }}
+        CXX: ${{ matrix.cxx }}
+
+    - name: Build
+      run: |
+        cd ${{github.workspace}}/build
+        ninja
+
+    - name: Test
+      if: ${{ matrix.test == 1 }}
+      run: |
+        cd ${{github.workspace}}/build
+        ctest -C Release -V
+  
+    - name: Pack
+      id: cpack
+      if: ${{ matrix.pack == 1 }}
+      run: |
+        cd ${{github.workspace}}/build
+        cpack
+        
+    - name: Additional logs
+      if: ${{ failure() && steps.cpack.outcome == 'failure' && matrix.platform == 'mxe' }}
+      run: |
+        cat ${{github.workspace}}/build/_CPack_Packages/win32/NSIS/project.nsi
+        cat ${{github.workspace}}/build/_CPack_Packages/win32/NSIS/NSISOutput.log
+        
+    - name: Artifacts
+      if: ${{ matrix.pack == 1 }}
+      uses: actions/upload-artifact@v2
+      with:
+        name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
+        path: ${{github.workspace}}/build/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
+  
+    - name: Upload build
+      if: ${{ matrix.pack == 1 && github.ref == 'refs/heads/develop' }}
+      run: |
+        cd ${{github.workspace}}/build
+        source ${{github.workspace}}/CI/upload_package.sh
+      env:
+        DEPLOY_RSA: ${{ secrets.DEPLOY_RSA }}
+        PACKAGE_EXTENSION: ${{ matrix.extension }}
+        
+    - uses: act10ns/slack@v1
+      with:
+        status: ${{ job.status }}
+        channel: '#notifications'
+      env:
+        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+      if: always()

+ 3 - 3
AI/VCAI/VCAI.cpp

@@ -1166,12 +1166,12 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 
 			for(auto location : allArtifacts)
 			{
+				if(location.slot == ArtifactPosition::MACH4 || location.slot == ArtifactPosition::SPELLBOOK)
+					continue; // don't attempt to move catapult and spellbook
+
 				if(location.relatedObj() == target && location.slot < ArtifactPosition::AFTER_LAST)
 					continue; //don't reequip artifact we already wear
 
-				if(location.slot == ArtifactPosition::MACH4) // don't attempt to move catapult
-					continue;
-
 				auto s = location.getSlot();
 				if(!s || s->locked) //we can't move locks
 					continue;

+ 973 - 0
CI/NSIS.template.in

@@ -0,0 +1,973 @@
+; CPack install script designed for a nmake build
+
+;--------------------------------
+; You must define these values
+
+  !define VERSION "@CPACK_PACKAGE_VERSION@"
+  !define PATCH  "@CPACK_PACKAGE_VERSION_PATCH@"
+  !define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@"
+
+;--------------------------------
+;Variables
+
+  Var MUI_TEMP
+  Var STARTMENU_FOLDER
+  Var SV_ALLUSERS
+  Var START_MENU
+  Var DO_NOT_ADD_TO_PATH
+  Var ADD_TO_PATH_ALL_USERS
+  Var ADD_TO_PATH_CURRENT_USER
+  Var INSTALL_DESKTOP
+  Var IS_DEFAULT_INSTALLDIR
+;--------------------------------
+;Include Modern UI
+
+  Unicode True
+  !include "MUI.nsh"
+
+  ;Default installation folder
+  InstallDir "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@"
+
+;--------------------------------
+;General
+
+  ;Name and file
+  Name "@CPACK_NSIS_PACKAGE_NAME@"
+  OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@"
+
+  ;Set compression
+  SetCompressor @CPACK_NSIS_COMPRESSOR@
+
+  ;Require administrator access
+  RequestExecutionLevel admin
+
+@CPACK_NSIS_DEFINES@
+
+  !include Sections.nsh
+
+;--- Component support macros: ---
+; The code for the add/remove functionality is from:
+;   http://nsis.sourceforge.net/Add/Remove_Functionality
+; It has been modified slightly and extended to provide
+; inter-component dependencies.
+Var AR_SecFlags
+Var AR_RegFlags
+@CPACK_NSIS_SECTION_SELECTED_VARS@
+
+; Loads the "selected" flag for the section named SecName into the
+; variable VarName.
+!macro LoadSectionSelectedIntoVar SecName VarName
+ SectionGetFlags ${${SecName}} $${VarName}
+ IntOp $${VarName} $${VarName} & ${SF_SELECTED}  ;Turn off all other bits
+!macroend
+
+; Loads the value of a variable... can we get around this?
+!macro LoadVar VarName
+  IntOp $R0 0 + $${VarName}
+!macroend
+
+; Sets the value of a variable
+!macro StoreVar VarName IntValue
+  IntOp $${VarName} 0 + ${IntValue}
+!macroend
+
+!macro InitSection SecName
+  ;  This macro reads component installed flag from the registry and
+  ;changes checked state of the section on the components page.
+  ;Input: section index constant name specified in Section command.
+
+  ClearErrors
+  ;Reading component status from registry
+  ReadRegDWORD $AR_RegFlags HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" "Installed"
+  IfErrors "default_${SecName}"
+    ;Status will stay default if registry value not found
+    ;(component was never installed)
+  IntOp $AR_RegFlags $AR_RegFlags & ${SF_SELECTED} ;Turn off all other bits
+  SectionGetFlags ${${SecName}} $AR_SecFlags  ;Reading default section flags
+  IntOp $AR_SecFlags $AR_SecFlags & 0xFFFE  ;Turn lowest (enabled) bit off
+  IntOp $AR_SecFlags $AR_RegFlags | $AR_SecFlags      ;Change lowest bit
+
+  ; Note whether this component was installed before
+  !insertmacro StoreVar ${SecName}_was_installed $AR_RegFlags
+  IntOp $R0 $AR_RegFlags & $AR_RegFlags
+
+  ;Writing modified flags
+  SectionSetFlags ${${SecName}} $AR_SecFlags
+
+ "default_${SecName}:"
+ !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected
+!macroend
+
+!macro FinishSection SecName
+  ;  This macro reads section flag set by user and removes the section
+  ;if it is not selected.
+  ;Then it writes component installed flag to registry
+  ;Input: section index constant name specified in Section command.
+
+  SectionGetFlags ${${SecName}} $AR_SecFlags  ;Reading section flags
+  ;Checking lowest bit:
+  IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED}
+  IntCmp $AR_SecFlags 1 "leave_${SecName}"
+    ;Section is not selected:
+    ;Calling Section uninstall macro and writing zero installed flag
+    !insertmacro "Remove_${${SecName}}"
+    WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \
+  "Installed" 0
+    Goto "exit_${SecName}"
+
+ "leave_${SecName}:"
+    ;Section is selected:
+    WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \
+  "Installed" 1
+
+ "exit_${SecName}:"
+!macroend
+
+!macro RemoveSection_CPack SecName
+  ;  This macro is used to call section's Remove_... macro
+  ;from the uninstaller.
+  ;Input: section index constant name specified in Section command.
+
+  !insertmacro "Remove_${${SecName}}"
+!macroend
+
+; Determine whether the selection of SecName changed
+!macro MaybeSelectionChanged SecName
+  !insertmacro LoadVar ${SecName}_selected
+  SectionGetFlags ${${SecName}} $R1
+  IntOp $R1 $R1 & ${SF_SELECTED} ;Turn off all other bits
+
+  ; See if the status has changed:
+  IntCmp $R0 $R1 "${SecName}_unchanged"
+  !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected
+
+  IntCmp $R1 ${SF_SELECTED} "${SecName}_was_selected"
+  !insertmacro "Deselect_required_by_${SecName}"
+  goto "${SecName}_unchanged"
+
+  "${SecName}_was_selected:"
+  !insertmacro "Select_${SecName}_depends"
+
+  "${SecName}_unchanged:"
+!macroend
+;--- End of Add/Remove macros ---
+
+;--------------------------------
+;Interface Settings
+
+  !define MUI_HEADERIMAGE
+  !define MUI_ABORTWARNING
+
+;----------------------------------------
+; based upon a script of "Written by KiCHiK 2003-01-18 05:57:02"
+;----------------------------------------
+!verbose 3
+!include "WinMessages.NSH"
+!verbose 4
+;====================================================
+; get_NT_environment
+;     Returns: the selected environment
+;     Output : head of the stack
+;====================================================
+!macro select_NT_profile UN
+Function ${UN}select_NT_profile
+   StrCmp $ADD_TO_PATH_ALL_USERS "1" 0 environment_single
+      DetailPrint "Selected environment for all users"
+      Push "all"
+      Return
+   environment_single:
+      DetailPrint "Selected environment for current user only."
+      Push "current"
+      Return
+FunctionEnd
+!macroend
+!insertmacro select_NT_profile ""
+!insertmacro select_NT_profile "un."
+;----------------------------------------------------
+!define NT_current_env 'HKCU "Environment"'
+!define NT_all_env     'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
+
+!ifndef WriteEnvStr_RegKey
+  !ifdef ALL_USERS
+    !define WriteEnvStr_RegKey \
+       'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
+  !else
+    !define WriteEnvStr_RegKey 'HKCU "Environment"'
+  !endif
+!endif
+
+; AddToPath - Adds the given dir to the search path.
+;        Input - head of the stack
+;        Note - Win9x systems requires reboot
+
+Function AddToPath
+  Exch $0
+  Push $1
+  Push $2
+  Push $3
+
+  # don't add if the path doesn't exist
+  IfFileExists "$0\*.*" "" AddToPath_done
+
+  ReadEnvStr $1 PATH
+  ; if the path is too long for a NSIS variable NSIS will return a 0
+  ; length string.  If we find that, then warn and skip any path
+  ; modification as it will trash the existing path.
+  StrLen $2 $1
+  IntCmp $2 0 CheckPathLength_ShowPathWarning CheckPathLength_Done CheckPathLength_Done
+    CheckPathLength_ShowPathWarning:
+    Messagebox MB_OK|MB_ICONEXCLAMATION "Warning! PATH too long installer unable to modify PATH!"
+    Goto AddToPath_done
+  CheckPathLength_Done:
+  Push "$1;"
+  Push "$0;"
+  Call StrStr
+  Pop $2
+  StrCmp $2 "" "" AddToPath_done
+  Push "$1;"
+  Push "$0\;"
+  Call StrStr
+  Pop $2
+  StrCmp $2 "" "" AddToPath_done
+  GetFullPathName /SHORT $3 $0
+  Push "$1;"
+  Push "$3;"
+  Call StrStr
+  Pop $2
+  StrCmp $2 "" "" AddToPath_done
+  Push "$1;"
+  Push "$3\;"
+  Call StrStr
+  Pop $2
+  StrCmp $2 "" "" AddToPath_done
+
+  Call IsNT
+  Pop $1
+  StrCmp $1 1 AddToPath_NT
+    ; Not on NT
+    StrCpy $1 $WINDIR 2
+    FileOpen $1 "$1\autoexec.bat" a
+    FileSeek $1 -1 END
+    FileReadByte $1 $2
+    IntCmp $2 26 0 +2 +2 # DOS EOF
+      FileSeek $1 -1 END # write over EOF
+    FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n"
+    FileClose $1
+    SetRebootFlag true
+    Goto AddToPath_done
+
+  AddToPath_NT:
+    StrCmp $ADD_TO_PATH_ALL_USERS "1" ReadAllKey
+      ReadRegStr $1 ${NT_current_env} "PATH"
+      Goto DoTrim
+    ReadAllKey:
+      ReadRegStr $1 ${NT_all_env} "PATH"
+    DoTrim:
+    StrCmp $1 "" AddToPath_NTdoIt
+      Push $1
+      Call Trim
+      Pop $1
+      StrCpy $0 "$1;$0"
+    AddToPath_NTdoIt:
+      StrCmp $ADD_TO_PATH_ALL_USERS "1" WriteAllKey
+        WriteRegExpandStr ${NT_current_env} "PATH" $0
+        Goto DoSend
+      WriteAllKey:
+        WriteRegExpandStr ${NT_all_env} "PATH" $0
+      DoSend:
+      SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+
+  AddToPath_done:
+    Pop $3
+    Pop $2
+    Pop $1
+    Pop $0
+FunctionEnd
+
+
+; RemoveFromPath - Remove a given dir from the path
+;     Input: head of the stack
+
+Function un.RemoveFromPath
+  Exch $0
+  Push $1
+  Push $2
+  Push $3
+  Push $4
+  Push $5
+  Push $6
+
+  IntFmt $6 "%c" 26 # DOS EOF
+
+  Call un.IsNT
+  Pop $1
+  StrCmp $1 1 unRemoveFromPath_NT
+    ; Not on NT
+    StrCpy $1 $WINDIR 2
+    FileOpen $1 "$1\autoexec.bat" r
+    GetTempFileName $4
+    FileOpen $2 $4 w
+    GetFullPathName /SHORT $0 $0
+    StrCpy $0 "SET PATH=%PATH%;$0"
+    Goto unRemoveFromPath_dosLoop
+
+    unRemoveFromPath_dosLoop:
+      FileRead $1 $3
+      StrCpy $5 $3 1 -1 # read last char
+      StrCmp $5 $6 0 +2 # if DOS EOF
+        StrCpy $3 $3 -1 # remove DOS EOF so we can compare
+      StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine
+      StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine
+      StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine
+      StrCmp $3 "" unRemoveFromPath_dosLoopEnd
+      FileWrite $2 $3
+      Goto unRemoveFromPath_dosLoop
+      unRemoveFromPath_dosLoopRemoveLine:
+        SetRebootFlag true
+        Goto unRemoveFromPath_dosLoop
+
+    unRemoveFromPath_dosLoopEnd:
+      FileClose $2
+      FileClose $1
+      StrCpy $1 $WINDIR 2
+      Delete "$1\autoexec.bat"
+      CopyFiles /SILENT $4 "$1\autoexec.bat"
+      Delete $4
+      Goto unRemoveFromPath_done
+
+  unRemoveFromPath_NT:
+    StrCmp $ADD_TO_PATH_ALL_USERS "1" unReadAllKey
+      ReadRegStr $1 ${NT_current_env} "PATH"
+      Goto unDoTrim
+    unReadAllKey:
+      ReadRegStr $1 ${NT_all_env} "PATH"
+    unDoTrim:
+    StrCpy $5 $1 1 -1 # copy last char
+    StrCmp $5 ";" +2 # if last char != ;
+      StrCpy $1 "$1;" # append ;
+    Push $1
+    Push "$0;"
+    Call un.StrStr ; Find `$0;` in $1
+    Pop $2 ; pos of our dir
+    StrCmp $2 "" unRemoveFromPath_done
+      ; else, it is in path
+      # $0 - path to add
+      # $1 - path var
+      StrLen $3 "$0;"
+      StrLen $4 $2
+      StrCpy $5 $1 -$4 # $5 is now the part before the path to remove
+      StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove
+      StrCpy $3 $5$6
+
+      StrCpy $5 $3 1 -1 # copy last char
+      StrCmp $5 ";" 0 +2 # if last char == ;
+        StrCpy $3 $3 -1 # remove last char
+
+      StrCmp $ADD_TO_PATH_ALL_USERS "1" unWriteAllKey
+        WriteRegExpandStr ${NT_current_env} "PATH" $3
+        Goto unDoSend
+      unWriteAllKey:
+        WriteRegExpandStr ${NT_all_env} "PATH" $3
+      unDoSend:
+      SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+
+  unRemoveFromPath_done:
+    Pop $6
+    Pop $5
+    Pop $4
+    Pop $3
+    Pop $2
+    Pop $1
+    Pop $0
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Uninstall sutff
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+###########################################
+#            Utility Functions            #
+###########################################
+
+;====================================================
+; IsNT - Returns 1 if the current system is NT, 0
+;        otherwise.
+;     Output: head of the stack
+;====================================================
+; IsNT
+; no input
+; output, top of the stack = 1 if NT or 0 if not
+;
+; Usage:
+;   Call IsNT
+;   Pop $R0
+;  ($R0 at this point is 1 or 0)
+
+!macro IsNT un
+Function ${un}IsNT
+  Push $0
+  ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion
+  StrCmp $0 "" 0 IsNT_yes
+  ; we are not NT.
+  Pop $0
+  Push 0
+  Return
+
+  IsNT_yes:
+    ; NT!!!
+    Pop $0
+    Push 1
+FunctionEnd
+!macroend
+!insertmacro IsNT ""
+!insertmacro IsNT "un."
+
+; StrStr
+; input, top of stack = string to search for
+;        top of stack-1 = string to search in
+; output, top of stack (replaces with the portion of the string remaining)
+; modifies no other variables.
+;
+; Usage:
+;   Push "this is a long ass string"
+;   Push "ass"
+;   Call StrStr
+;   Pop $R0
+;  ($R0 at this point is "ass string")
+
+!macro StrStr un
+Function ${un}StrStr
+Exch $R1 ; st=haystack,old$R1, $R1=needle
+  Exch    ; st=old$R1,haystack
+  Exch $R2 ; st=old$R1,old$R2, $R2=haystack
+  Push $R3
+  Push $R4
+  Push $R5
+  StrLen $R3 $R1
+  StrCpy $R4 0
+  ; $R1=needle
+  ; $R2=haystack
+  ; $R3=len(needle)
+  ; $R4=cnt
+  ; $R5=tmp
+  loop:
+    StrCpy $R5 $R2 $R3 $R4
+    StrCmp $R5 $R1 done
+    StrCmp $R5 "" done
+    IntOp $R4 $R4 + 1
+    Goto loop
+done:
+  StrCpy $R1 $R2 "" $R4
+  Pop $R5
+  Pop $R4
+  Pop $R3
+  Pop $R2
+  Exch $R1
+FunctionEnd
+!macroend
+!insertmacro StrStr ""
+!insertmacro StrStr "un."
+
+Function Trim ; Added by Pelaca
+	Exch $R1
+	Push $R2
+Loop:
+	StrCpy $R2 "$R1" 1 -1
+	StrCmp "$R2" " " RTrim
+	StrCmp "$R2" "$\n" RTrim
+	StrCmp "$R2" "$\r" RTrim
+	StrCmp "$R2" ";" RTrim
+	GoTo Done
+RTrim:
+	StrCpy $R1 "$R1" -1
+	Goto Loop
+Done:
+	Pop $R2
+	Exch $R1
+FunctionEnd
+
+Function ConditionalAddToRegisty
+  Pop $0
+  Pop $1
+  StrCmp "$0" "" ConditionalAddToRegisty_EmptyString
+    WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \
+    "$1" "$0"
+    ;MessageBox MB_OK "Set Registry: '$1' to '$0'"
+    DetailPrint "Set install registry entry: '$1' to '$0'"
+  ConditionalAddToRegisty_EmptyString:
+FunctionEnd
+
+;--------------------------------
+
+!ifdef CPACK_USES_DOWNLOAD
+Function DownloadFile
+    IfFileExists $INSTDIR\* +2
+    CreateDirectory $INSTDIR
+    Pop $0
+
+    ; Skip if already downloaded
+    IfFileExists $INSTDIR\$0 0 +2
+    Return
+
+    StrCpy $1 "@CPACK_DOWNLOAD_SITE@"
+
+  try_again:
+    NSISdl::download "$1/$0" "$INSTDIR\$0"
+
+    Pop $1
+    StrCmp $1 "success" success
+    StrCmp $1 "Cancelled" cancel
+    MessageBox MB_OK "Download failed: $1"
+  cancel:
+    Return
+  success:
+FunctionEnd
+!endif
+
+;--------------------------------
+; Installation types
+@CPACK_NSIS_INSTALLATION_TYPES@
+
+;--------------------------------
+; Component sections
+@CPACK_NSIS_COMPONENT_SECTIONS@
+
+;--------------------------------
+; Define some macro setting for the gui
+@CPACK_NSIS_INSTALLER_MUI_ICON_CODE@
+@CPACK_NSIS_INSTALLER_ICON_CODE@
+@CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE@
+@CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE@
+@CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC@
+@CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE@
+
+;--------------------------------
+;Pages
+  !insertmacro MUI_PAGE_WELCOME
+
+  !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@"
+  Page custom InstallOptionsPage
+  !insertmacro MUI_PAGE_DIRECTORY
+
+  ;Start Menu Folder Page Configuration
+  !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX"
+  !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
+  !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
+  !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
+
+  @CPACK_NSIS_PAGE_COMPONENTS@
+
+  !insertmacro MUI_PAGE_INSTFILES
+  !insertmacro MUI_PAGE_FINISH
+
+  !insertmacro MUI_UNPAGE_CONFIRM
+  !insertmacro MUI_UNPAGE_INSTFILES
+
+;--------------------------------
+;Languages
+
+  !insertmacro MUI_LANGUAGE "English" ;first language is the default language
+  !insertmacro MUI_LANGUAGE "Albanian"
+  !insertmacro MUI_LANGUAGE "Arabic"
+  !insertmacro MUI_LANGUAGE "Basque"
+  !insertmacro MUI_LANGUAGE "Belarusian"
+  !insertmacro MUI_LANGUAGE "Bosnian"
+  !insertmacro MUI_LANGUAGE "Breton"
+  !insertmacro MUI_LANGUAGE "Bulgarian"
+  !insertmacro MUI_LANGUAGE "Croatian"
+  !insertmacro MUI_LANGUAGE "Czech"
+  !insertmacro MUI_LANGUAGE "Danish"
+  !insertmacro MUI_LANGUAGE "Dutch"
+  !insertmacro MUI_LANGUAGE "Estonian"
+  !insertmacro MUI_LANGUAGE "Farsi"
+  !insertmacro MUI_LANGUAGE "Finnish"
+  !insertmacro MUI_LANGUAGE "French"
+  !insertmacro MUI_LANGUAGE "German"
+  !insertmacro MUI_LANGUAGE "Greek"
+  !insertmacro MUI_LANGUAGE "Hebrew"
+  !insertmacro MUI_LANGUAGE "Hungarian"
+  !insertmacro MUI_LANGUAGE "Icelandic"
+  !insertmacro MUI_LANGUAGE "Indonesian"
+  !insertmacro MUI_LANGUAGE "Irish"
+  !insertmacro MUI_LANGUAGE "Italian"
+  !insertmacro MUI_LANGUAGE "Japanese"
+  !insertmacro MUI_LANGUAGE "Korean"
+  !insertmacro MUI_LANGUAGE "Kurdish"
+  !insertmacro MUI_LANGUAGE "Latvian"
+  !insertmacro MUI_LANGUAGE "Lithuanian"
+  !insertmacro MUI_LANGUAGE "Luxembourgish"
+  !insertmacro MUI_LANGUAGE "Macedonian"
+  !insertmacro MUI_LANGUAGE "Malay"
+  !insertmacro MUI_LANGUAGE "Mongolian"
+  !insertmacro MUI_LANGUAGE "Norwegian"
+  !insertmacro MUI_LANGUAGE "Polish"
+  !insertmacro MUI_LANGUAGE "Portuguese"
+  !insertmacro MUI_LANGUAGE "PortugueseBR"
+  !insertmacro MUI_LANGUAGE "Romanian"
+  !insertmacro MUI_LANGUAGE "Russian"
+  !insertmacro MUI_LANGUAGE "Serbian"
+  !insertmacro MUI_LANGUAGE "SerbianLatin"
+  !insertmacro MUI_LANGUAGE "SimpChinese"
+  !insertmacro MUI_LANGUAGE "Slovak"
+  !insertmacro MUI_LANGUAGE "Slovenian"
+  !insertmacro MUI_LANGUAGE "Spanish"
+  !insertmacro MUI_LANGUAGE "Swedish"
+  !insertmacro MUI_LANGUAGE "Thai"
+  !insertmacro MUI_LANGUAGE "TradChinese"
+  !insertmacro MUI_LANGUAGE "Turkish"
+  !insertmacro MUI_LANGUAGE "Ukrainian"
+  !insertmacro MUI_LANGUAGE "Welsh"
+
+
+;--------------------------------
+;Reserve Files
+
+  ;These files should be inserted before other files in the data block
+  ;Keep these lines before any File command
+  ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA)
+
+  ReserveFile "NSIS.InstallOptions.ini"
+  !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
+
+;--------------------------------
+;Installer Sections
+
+Section "-Core installation"
+  ;Use the entire tree produced by the INSTALL target.  Keep the
+  ;list of directories here in sync with the RMDir commands below.
+  SetOutPath "$INSTDIR"
+  @CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@
+  @CPACK_NSIS_FULL_INSTALL@
+
+  ;Store installation folder
+  WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR
+
+  ;Create uninstaller
+  WriteUninstaller "$INSTDIR\Uninstall.exe"
+  Push "DisplayName"
+  Push "@CPACK_NSIS_DISPLAY_NAME@"
+  Call ConditionalAddToRegisty
+  Push "DisplayVersion"
+  Push "@CPACK_PACKAGE_VERSION@"
+  Call ConditionalAddToRegisty
+  Push "Publisher"
+  Push "@CPACK_PACKAGE_VENDOR@"
+  Call ConditionalAddToRegisty
+  Push "UninstallString"
+  Push "$INSTDIR\Uninstall.exe"
+  Call ConditionalAddToRegisty
+  Push "NoRepair"
+  Push "1"
+  Call ConditionalAddToRegisty
+
+  !ifdef CPACK_NSIS_ADD_REMOVE
+  ;Create add/remove functionality
+  Push "ModifyPath"
+  Push "$INSTDIR\AddRemove.exe"
+  Call ConditionalAddToRegisty
+  !else
+  Push "NoModify"
+  Push "1"
+  Call ConditionalAddToRegisty
+  !endif
+
+  ; Optional registration
+  Push "DisplayIcon"
+  Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@"
+  Call ConditionalAddToRegisty
+  Push "HelpLink"
+  Push "@CPACK_NSIS_HELP_LINK@"
+  Call ConditionalAddToRegisty
+  Push "URLInfoAbout"
+  Push "@CPACK_NSIS_URL_INFO_ABOUT@"
+  Call ConditionalAddToRegisty
+  Push "Contact"
+  Push "@CPACK_NSIS_CONTACT@"
+  Call ConditionalAddToRegisty
+  !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State"
+  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+
+  ;Create shortcuts
+  CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
+@CPACK_NSIS_CREATE_ICONS@
+@CPACK_NSIS_CREATE_ICONS_EXTRA@
+  CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
+
+  ;Read a value from an InstallOptions INI file
+  !insertmacro MUI_INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State"
+  !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_ALL_USERS "NSIS.InstallOptions.ini" "Field 3" "State"
+  !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_CURRENT_USER "NSIS.InstallOptions.ini" "Field 4" "State"
+
+  ; Write special uninstall registry entries
+  Push "StartMenu"
+  Push "$STARTMENU_FOLDER"
+  Call ConditionalAddToRegisty
+  Push "DoNotAddToPath"
+  Push "$DO_NOT_ADD_TO_PATH"
+  Call ConditionalAddToRegisty
+  Push "AddToPathAllUsers"
+  Push "$ADD_TO_PATH_ALL_USERS"
+  Call ConditionalAddToRegisty
+  Push "AddToPathCurrentUser"
+  Push "$ADD_TO_PATH_CURRENT_USER"
+  Call ConditionalAddToRegisty
+  Push "InstallToDesktop"
+  Push "$INSTALL_DESKTOP"
+  Call ConditionalAddToRegisty
+
+  !insertmacro MUI_STARTMENU_WRITE_END
+
+@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@
+
+SectionEnd
+
+Section "-Add to path"
+  Push $INSTDIR\bin
+  StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 doNotAddToPath
+  StrCmp $DO_NOT_ADD_TO_PATH "1" doNotAddToPath 0
+    Call AddToPath
+  doNotAddToPath:
+SectionEnd
+
+;--------------------------------
+; Create custom pages
+Function InstallOptionsPage
+  !insertmacro MUI_HEADER_TEXT "Install Options" "Choose options for installing @CPACK_NSIS_PACKAGE_NAME@"
+  !insertmacro MUI_INSTALLOPTIONS_DISPLAY "NSIS.InstallOptions.ini"
+
+FunctionEnd
+
+;--------------------------------
+; determine admin versus local install
+Function un.onInit
+
+  ClearErrors
+  UserInfo::GetName
+  IfErrors noLM
+  Pop $0
+  UserInfo::GetAccountType
+  Pop $1
+  StrCmp $1 "Admin" 0 +3
+    SetShellVarContext all
+    ;MessageBox MB_OK 'User "$0" is in the Admin group'
+    Goto done
+  StrCmp $1 "Power" 0 +3
+    SetShellVarContext all
+    ;MessageBox MB_OK 'User "$0" is in the Power Users group'
+    Goto done
+
+  noLM:
+    ;Get installation folder from registry if available
+
+  done:
+
+FunctionEnd
+
+;--- Add/Remove callback functions: ---
+!macro SectionList MacroName
+  ;This macro used to perform operation on multiple sections.
+  ;List all of your components in following manner here.
+@CPACK_NSIS_COMPONENT_SECTION_LIST@
+!macroend
+
+Section -FinishComponents
+  ;Removes unselected components and writes component status to registry
+  !insertmacro SectionList "FinishSection"
+
+!ifdef CPACK_NSIS_ADD_REMOVE
+  ; Get the name of the installer executable
+  System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1'
+  StrCpy $R3 $R0
+
+  ; Strip off the last 13 characters, to see if we have AddRemove.exe
+  StrLen $R1 $R0
+  IntOp $R1 $R0 - 13
+  StrCpy $R2 $R0 13 $R1
+  StrCmp $R2 "AddRemove.exe" addremove_installed
+
+  ; We're not running AddRemove.exe, so install it
+  CopyFiles $R3 $INSTDIR\AddRemove.exe
+
+  addremove_installed:
+!endif
+SectionEnd
+;--- End of Add/Remove callback functions ---
+
+;--------------------------------
+; Component dependencies
+Function .onSelChange
+  !insertmacro SectionList MaybeSelectionChanged
+FunctionEnd
+
+;--------------------------------
+;Uninstaller Section
+
+Section "Uninstall"
+  ReadRegStr $START_MENU SHCTX \
+   "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "StartMenu"
+  ;MessageBox MB_OK "Start menu is in: $START_MENU"
+  ReadRegStr $DO_NOT_ADD_TO_PATH SHCTX \
+    "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "DoNotAddToPath"
+  ReadRegStr $ADD_TO_PATH_ALL_USERS SHCTX \
+    "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathAllUsers"
+  ReadRegStr $ADD_TO_PATH_CURRENT_USER SHCTX \
+    "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathCurrentUser"
+  ;MessageBox MB_OK "Add to path: $DO_NOT_ADD_TO_PATH all users: $ADD_TO_PATH_ALL_USERS"
+  ReadRegStr $INSTALL_DESKTOP SHCTX \
+    "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "InstallToDesktop"
+  ;MessageBox MB_OK "Install to desktop: $INSTALL_DESKTOP "
+
+@CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@
+
+  ;Remove files we installed.
+  ;Keep the list of directories here in sync with the File commands above.
+@CPACK_NSIS_DELETE_FILES@
+@CPACK_NSIS_DELETE_DIRECTORIES@
+
+!ifdef CPACK_NSIS_ADD_REMOVE
+  ;Remove the add/remove program
+  Delete "$INSTDIR\AddRemove.exe"
+!endif
+
+  ;Remove the uninstaller itself.
+  Delete "$INSTDIR\Uninstall.exe"
+  DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
+
+  ;Remove the installation directory if it is empty.
+  RMDir "$INSTDIR"
+
+  ; Remove the registry entries.
+  DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
+
+  ; Removes all optional components
+  !insertmacro SectionList "RemoveSection_CPack"
+
+  !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
+
+  Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
+@CPACK_NSIS_DELETE_ICONS@
+@CPACK_NSIS_DELETE_ICONS_EXTRA@
+
+  ;Delete empty start menu parent diretories
+  StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
+
+  startMenuDeleteLoop:
+    ClearErrors
+    RMDir $MUI_TEMP
+    GetFullPathName $MUI_TEMP "$MUI_TEMP\.."
+
+    IfErrors startMenuDeleteLoopDone
+
+    StrCmp "$MUI_TEMP" "$SMPROGRAMS" startMenuDeleteLoopDone startMenuDeleteLoop
+  startMenuDeleteLoopDone:
+
+  ; If the user changed the shortcut, then untinstall may not work. This should
+  ; try to fix it.
+  StrCpy $MUI_TEMP "$START_MENU"
+  Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
+@CPACK_NSIS_DELETE_ICONS_EXTRA@
+
+  ;Delete empty start menu parent diretories
+  StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
+
+  secondStartMenuDeleteLoop:
+    ClearErrors
+    RMDir $MUI_TEMP
+    GetFullPathName $MUI_TEMP "$MUI_TEMP\.."
+
+    IfErrors secondStartMenuDeleteLoopDone
+
+    StrCmp "$MUI_TEMP" "$SMPROGRAMS" secondStartMenuDeleteLoopDone secondStartMenuDeleteLoop
+  secondStartMenuDeleteLoopDone:
+
+  DeleteRegKey /ifempty SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
+
+  Push $INSTDIR\bin
+  StrCmp $DO_NOT_ADD_TO_PATH_ "1" doNotRemoveFromPath 0
+    Call un.RemoveFromPath
+  doNotRemoveFromPath:
+SectionEnd
+
+;--------------------------------
+; determine admin versus local install
+; Is install for "AllUsers" or "JustMe"?
+; Default to "JustMe" - set to "AllUsers" if admin or on Win9x
+; This function is used for the very first "custom page" of the installer.
+; This custom page does not show up visibly, but it executes prior to the
+; first visible page and sets up $INSTDIR properly...
+; Choose different default installation folder based on SV_ALLUSERS...
+; "Program Files" for AllUsers, "My Documents" for JustMe...
+
+Function .onInit
+  StrCmp "@CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL@" "ON" 0 inst
+
+  ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString"
+  StrCmp $0 "" inst
+
+  MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION \
+  "@CPACK_NSIS_PACKAGE_NAME@ is already installed. $\n$\nDo you want to uninstall the old version before installing the new one?" \
+  /SD IDYES IDYES uninst IDNO inst
+  Abort
+
+;Run the uninstaller
+uninst:
+  ClearErrors
+  StrLen $2 "\Uninstall.exe"
+  StrCpy $3 $0 -$2 # remove "\Uninstall.exe" from UninstallString to get path
+  ExecWait '"$0" /S _?=$3' ;Do not copy the uninstaller to a temp file
+
+  IfErrors uninst_failed inst
+uninst_failed:
+  MessageBox MB_OK|MB_ICONSTOP "Uninstall failed."
+  Abort
+
+
+inst:
+  ; Reads components status for registry
+  !insertmacro SectionList "InitSection"
+
+  ; check to see if /D has been used to change
+  ; the install directory by comparing it to the
+  ; install directory that is expected to be the
+  ; default
+  StrCpy $IS_DEFAULT_INSTALLDIR 0
+  StrCmp "$INSTDIR" "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" 0 +2
+    StrCpy $IS_DEFAULT_INSTALLDIR 1
+
+  StrCpy $SV_ALLUSERS "JustMe"
+  ; if default install dir then change the default
+  ; if it is installed for JustMe
+  StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2
+    StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@"
+
+  ClearErrors
+  UserInfo::GetName
+  IfErrors noLM
+  Pop $0
+  UserInfo::GetAccountType
+  Pop $1
+  StrCmp $1 "Admin" 0 +4
+    SetShellVarContext all
+    ;MessageBox MB_OK 'User "$0" is in the Admin group'
+    StrCpy $SV_ALLUSERS "AllUsers"
+    Goto done
+  StrCmp $1 "Power" 0 +4
+    SetShellVarContext all
+    ;MessageBox MB_OK 'User "$0" is in the Power Users group'
+    StrCpy $SV_ALLUSERS "AllUsers"
+    Goto done
+
+  noLM:
+    StrCpy $SV_ALLUSERS "AllUsers"
+    ;Get installation folder from registry if available
+
+  done:
+  StrCmp $SV_ALLUSERS "AllUsers" 0 +3
+    StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2
+      StrCpy $INSTDIR "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@"
+
+  StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage
+    !insertmacro MUI_INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini"
+
+  noOptionsPage:
+FunctionEnd

+ 105 - 105
CI/appveyor.yml

@@ -1,105 +1,105 @@
-# Common configuration for all branches
-version: 1.0.{build}
-max_jobs: 2
-clone_depth: 10
-clone_folder: c:\projects\vcmi\source
-install:
-- bash c:\projects\vcmi\source\CI\msvc\install.sh
-build_script:
-- cmd: c:\projects\vcmi\source\CI\msvc\build_script.bat
-artifacts:
-- path: build_$(VCMI_BUILD_PLATFORM)\*.exe
-notifications:
-  - provider: Slack
-    incoming_webhook:
-      secure: zxT3HTnxL744HiSv7ig7sjGL4LmJ8n3MsY8PEA/kinbVMkmcxrSgVBVkHV79RfSWSyq4oLMSRvMMpG8SuDWnf6oK/qvgaiAWfwwlCIiA7uQ=
-
-# Branch-specific configuration
-for:
-# Extended configuration for toolchain_test branch
--
-  branches:
-    only:
-      - toolchain_test
-  environment:
-    matrix:
-      - NAME: MSVS 2015 x86 - Release
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
-        VCMI_GENERATOR: Visual Studio 14 2015
-        VCMI_BUILD_PLATFORM: x86
-        VCMI_BUILD_CONFIGURATION: Release
-      - NAME: MSVS 2015 x86 - Debug
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
-        VCMI_GENERATOR: Visual Studio 14 2015
-        VCMI_BUILD_PLATFORM: x86
-        VCMI_BUILD_CONFIGURATION: Debug
-
-      - NAME: MSVS 2015 x64 - Release
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
-        VCMI_GENERATOR: Visual Studio 14 2015 Win64
-        VCMI_BUILD_PLATFORM: x64
-        VCMI_BUILD_CONFIGURATION: Release
-      - NAME: MSVS 2015 x64 - Debug
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
-        VCMI_GENERATOR: Visual Studio 14 2015 Win64
-        VCMI_BUILD_PLATFORM: x64
-        VCMI_BUILD_CONFIGURATION: Debug
-
-      - NAME: MSVS 2017 x86 - Release
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-        VCMI_GENERATOR: Visual Studio 15 2017
-        VCMI_BUILD_PLATFORM: x86
-        VCMI_BUILD_CONFIGURATION: Release
-      - NAME: MSVS 2017 x86 - Debug
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-        VCMI_GENERATOR: Visual Studio 15 2017
-        VCMI_BUILD_PLATFORM: x86
-        VCMI_BUILD_CONFIGURATION: Debug
-
-      - NAME: MSVS 2017 x64 - Release
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-        VCMI_GENERATOR: Visual Studio 15 2017 Win64
-        VCMI_BUILD_PLATFORM: x64
-        VCMI_BUILD_CONFIGURATION: Release
-      - NAME: MSVS 2017 x64 - Debug
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-        VCMI_GENERATOR: Visual Studio 15 2017 Win64
-        VCMI_BUILD_PLATFORM: x64
-        VCMI_BUILD_CONFIGURATION: Debug
-
-# Special configuration for coverity_scan branch
--
-  branches:
-    only:
-      - coverity_scan
-  environment:
-    matrix:
-      - NAME: Coverity - MSVS 2017 x86 - Release
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-        VCMI_GENERATOR: Visual Studio 15 2017
-        VCMI_BUILD_PLATFORM: x86
-        VCMI_BUILD_CONFIGURATION: Release
-        environment:
-    coverity_token:
-      secure: XNnpYevnZxGmXW1zLu+3js2S+pqfWPQmL26hVgOTBTI=
-    coverity_email:
-      secure: JDd5yXvYaq/yJEVjoadEhA==
-  build_script:
-    - cmd: c:\projects\vcmi\source\CI\msvc\coverity_build_script.bat
-  after_test:
-    - ps: c:\projects\vcmi\source\CI\msvc\coverity_upload_script.ps
-
-# Default configuration for all other branches
--
-  environment:
-    matrix:
-      - NAME: MSVS 2017 x86 - Release
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-        VCMI_GENERATOR: Visual Studio 15 2017
-        VCMI_BUILD_PLATFORM: x86
-        VCMI_BUILD_CONFIGURATION: Release
-      - NAME: MSVS 2017 x64 - Release
-        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
-        VCMI_GENERATOR: Visual Studio 15 2017 Win64
-        VCMI_BUILD_PLATFORM: x64
-        VCMI_BUILD_CONFIGURATION: Release
+# Common configuration for all branches
+version: 1.0.{build}
+max_jobs: 2
+clone_depth: 10
+clone_folder: c:\projects\vcmi\source
+install:
+- bash c:\projects\vcmi\source\CI\msvc\install.sh
+build_script:
+- cmd: c:\projects\vcmi\source\CI\msvc\build_script.bat
+artifacts:
+- path: build_$(VCMI_BUILD_PLATFORM)\*.exe
+notifications:
+  - provider: Slack
+    incoming_webhook:
+      secure: zxT3HTnxL744HiSv7ig7sjGL4LmJ8n3MsY8PEA/kinbVMkmcxrSgVBVkHV79RfSWSyq4oLMSRvMMpG8SuDWnf6oK/qvgaiAWfwwlCIiA7uQ=
+
+# Branch-specific configuration
+for:
+# Extended configuration for toolchain_test branch
+-
+  branches:
+    only:
+      - toolchain_test
+  environment:
+    matrix:
+      - NAME: MSVS 2015 x86 - Release
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+        VCMI_GENERATOR: Visual Studio 14 2015
+        VCMI_BUILD_PLATFORM: x86
+        VCMI_BUILD_CONFIGURATION: Release
+      - NAME: MSVS 2015 x86 - Debug
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+        VCMI_GENERATOR: Visual Studio 14 2015
+        VCMI_BUILD_PLATFORM: x86
+        VCMI_BUILD_CONFIGURATION: Debug
+
+      - NAME: MSVS 2015 x64 - Release
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+        VCMI_GENERATOR: Visual Studio 14 2015 Win64
+        VCMI_BUILD_PLATFORM: x64
+        VCMI_BUILD_CONFIGURATION: Release
+      - NAME: MSVS 2015 x64 - Debug
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+        VCMI_GENERATOR: Visual Studio 14 2015 Win64
+        VCMI_BUILD_PLATFORM: x64
+        VCMI_BUILD_CONFIGURATION: Debug
+
+      - NAME: MSVS 2017 x86 - Release
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+        VCMI_GENERATOR: Visual Studio 15 2017
+        VCMI_BUILD_PLATFORM: x86
+        VCMI_BUILD_CONFIGURATION: Release
+      - NAME: MSVS 2017 x86 - Debug
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+        VCMI_GENERATOR: Visual Studio 15 2017
+        VCMI_BUILD_PLATFORM: x86
+        VCMI_BUILD_CONFIGURATION: Debug
+
+      - NAME: MSVS 2017 x64 - Release
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+        VCMI_GENERATOR: Visual Studio 15 2017 Win64
+        VCMI_BUILD_PLATFORM: x64
+        VCMI_BUILD_CONFIGURATION: Release
+      - NAME: MSVS 2017 x64 - Debug
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+        VCMI_GENERATOR: Visual Studio 15 2017 Win64
+        VCMI_BUILD_PLATFORM: x64
+        VCMI_BUILD_CONFIGURATION: Debug
+
+# Special configuration for coverity_scan branch
+-
+  branches:
+    only:
+      - coverity_scan
+  environment:
+    matrix:
+      - NAME: Coverity - MSVS 2017 x86 - Release
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+        VCMI_GENERATOR: Visual Studio 15 2017
+        VCMI_BUILD_PLATFORM: x86
+        VCMI_BUILD_CONFIGURATION: Release
+        environment:
+    coverity_token:
+      secure: XNnpYevnZxGmXW1zLu+3js2S+pqfWPQmL26hVgOTBTI=
+    coverity_email:
+      secure: JDd5yXvYaq/yJEVjoadEhA==
+  build_script:
+    - cmd: c:\projects\vcmi\source\CI\msvc\coverity_build_script.bat
+  after_test:
+    - ps: c:\projects\vcmi\source\CI\msvc\coverity_upload_script.ps
+
+# Default configuration for all other branches
+-
+  environment:
+    matrix:
+      - NAME: MSVS 2017 x86 - Release
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+        VCMI_GENERATOR: Visual Studio 15 2017
+        VCMI_BUILD_PLATFORM: x86
+        VCMI_BUILD_CONFIGURATION: Release
+      - NAME: MSVS 2017 x64 - Release
+        APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+        VCMI_GENERATOR: Visual Studio 15 2017 Win64
+        VCMI_BUILD_PLATFORM: x64
+        VCMI_BUILD_CONFIGURATION: Release

+ 7 - 0
CI/get_package_name.sh

@@ -13,6 +13,13 @@ then
 	TMP_BRANCH="$APPVEYOR_REPO_BRANCH"
 	TMP_PRID="$APPVEYOR_PULL_REQUEST_NUMBER"
 	TMP_COMMIT="$APPVEYOR_REPO_COMMIT"
+elif [ ! -z "${GITHUB_RUN_ID}" ];
+then
+	echo "Using Github environment variables!"
+	TMP_JOBID="$GITHUB_RUN_ID"
+	TMP_BRANCH=${GITHUB_REF#refs/heads/}
+	TMP_PRID="$PULL_REQUEST"
+	TMP_COMMIT=$(git rev-parse --short "$GITHUB_SHA")
 else
 	echo "No Travir or AppVeyor environment variables found!"
 	exit

+ 12 - 23
CI/linux/before_install.sh

@@ -1,27 +1,16 @@
 #!/bin/sh
 
-#new Clang
-sudo add-apt-repository --yes ppa:h-rayflood/llvm
+sudo apt-get update
 
-#new SDL2
-sudo add-apt-repository --yes ppa:zoogie/sdl2-snapshots
+# Boost
+wget -nv https://boostorg.jfrog.io/artifactory/main/release/1.66.0/source/boost_1_66_0.tar.gz
+tar xfz boost_1_66_0.tar.gz
+cd boost_1_66_0
+./bootstrap.sh --with-libraries=program_options,filesystem,system,thread,locale,date_time
+./b2
+sudo ./b2 install
 
-#new Qt
-sudo add-apt-repository --yes ppa:beineri/opt-qt571-trusty
-
-#new CMake
-sudo add-apt-repository --yes ppa:george-edison55/cmake-3.x
-
-sudo apt-get update -qq
-
-sudo apt-get install -qq $SUPPORT
-sudo apt-get install -qq $PACKAGE
-sudo apt-get install -qq cmake ninja-build libboost1.54-all-dev zlib1g-dev
-sudo apt-get install -qq libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev
-sudo apt-get install -qq libavformat-dev libswscale-dev
-sudo apt-get install -qq qt57declarative
-sudo apt-get install -qq libluajit-5.1-dev
-
-#setup compiler
-source /opt/qt57/bin/qt57-env.sh
-export CC=${REAL_CC} CXX=${REAL_CXX}
+# Dependencies
+sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev
+sudo apt-get install qtbase5-dev
+sudo apt-get install ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev

+ 3 - 3
CI/mac/before_install.sh

@@ -1,7 +1,7 @@
 #!/bin/sh
 
 brew update
-brew install smpeg2 libpng freetype sdl2 sdl2_ttf sdl2_image qt5 ffmpeg ninja luajit
-brew install sdl2_mixer
+brew install smpeg2 libpng freetype qt5 ffmpeg ninja boost tbb luajit
+brew install sdl2 sdl2_ttf sdl2_image sdl2_mixer
 
-export CMAKE_PREFIX_PATH="/usr/local/opt/qt5:$CMAKE_PREFIX_PATH"
+echo CMAKE_PREFIX_PATH="/usr/local/opt/qt5:$CMAKE_PREFIX_PATH" >> $GITHUB_ENV

+ 0 - 15
CI/mac/upload_package.sh

@@ -1,15 +0,0 @@
-#!/bin/sh
-if [ -z "$encrypted_1d30f79f8582_key" ];
-then
-	# Due to security measures travis not expose encryption keys for PR from forks
-	exit 0
-fi
-cpack
-
-touch /tmp/deploy_rsa
-chmod 600 /tmp/deploy_rsa
-openssl aes-256-cbc -K $encrypted_1d30f79f8582_key -iv $encrypted_1d30f79f8582_iv -in $TRAVIS_BUILD_DIR/CI/deploy_rsa.enc -out /tmp/deploy_rsa -d
-eval "$(ssh-agent -s)"
-ssh-add /tmp/deploy_rsa
-
-sftp -r -o StrictHostKeyChecking=no [email protected] <<< "put $VCMI_PACKAGE_FILE_NAME.dmg /incoming/$VCMI_PACKAGE_FILE_NAME.dmg"

+ 5 - 3
CI/mxe/before_install.sh

@@ -4,7 +4,7 @@
 sudo apt-get install -qq nsis ninja-build
 
 # MXE repository was too slow for Travis far too often
-wget https://github.com/vcmi/vcmi-deps-mxe/releases/download/2019-06-28/mxe-i686-w64-mingw32.shared-2019-06-28.tar
+wget -nv https://github.com/vcmi/vcmi-deps-mxe/releases/download/2019-06-28/mxe-i686-w64-mingw32.shared-2019-06-28.tar
 tar -xvf mxe-i686-w64-mingw32.shared-2019-06-28.tar
 sudo dpkg -i mxe-*.deb
 sudo apt-get install -f --yes
@@ -36,5 +36,7 @@ mxe-i686-w64-mingw32.static-luajit
 fi # Disable
 
 # alias for CMake
-sudo mv /usr/bin/cmake /usr/bin/cmake.orig
-sudo ln -s /usr/lib/mxe/usr/bin/$MXE_TARGET-cmake /usr/bin/cmake
+
+CMAKE_LOCATION=$(which cmake)
+sudo mv $CMAKE_LOCATION $CMAKE_LOCATION.orig
+sudo ln -s /usr/lib/mxe/usr/bin/$MXE_TARGET-cmake $CMAKE_LOCATION

+ 0 - 16
CI/mxe/upload_package.sh

@@ -1,16 +0,0 @@
-#!/bin/sh
-if [ -z "$encrypted_1d30f79f8582_key" ];
-then
-	# Due to security measures travis not expose encryption keys for PR from forks
-	exit 0
-fi
-
-cpack
-
-touch /tmp/deploy_rsa
-chmod 600 /tmp/deploy_rsa
-openssl aes-256-cbc -K $encrypted_1d30f79f8582_key -iv $encrypted_1d30f79f8582_iv -in $TRAVIS_BUILD_DIR/CI/deploy_rsa.enc -out /tmp/deploy_rsa -d
-eval "$(ssh-agent -s)"
-ssh-add /tmp/deploy_rsa
-
-sftp -r -o StrictHostKeyChecking=no [email protected] <<< "put $VCMI_PACKAGE_FILE_NAME.exe /incoming/$VCMI_PACKAGE_FILE_NAME.exe"

+ 15 - 0
CI/upload_package.sh

@@ -0,0 +1,15 @@
+#!/bin/sh
+if [ -z "$DEPLOY_RSA" ];
+then
+	# Due to security measures travis not expose encryption keys for PR from forks
+	echo "Build generation is skipped for forks"
+	exit 0
+fi
+
+echo "$DEPLOY_RSA" > /tmp/deploy_rsa
+chmod 600 /tmp/deploy_rsa
+
+eval "$(ssh-agent -s)"
+ssh-add /tmp/deploy_rsa
+
+sftp -r -o StrictHostKeyChecking=no [email protected] <<< "put $VCMI_PACKAGE_FILE_NAME.$PACKAGE_EXTENSION /incoming/$VCMI_PACKAGE_FILE_NAME.$PACKAGE_EXTENSION"

+ 2 - 2
CMakeLists.txt

@@ -64,7 +64,7 @@ set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename")
 #        Miscellaneous options             #
 ############################################
 
-set(CMAKE_MODULE_PATH ${CMAKE_HOME_DIRECTORY}/cmake_modules)
+set(CMAKE_MODULE_PATH ${CMAKE_HOME_DIRECTORY}/cmake_modules ${PROJECT_SOURCE_DIR}/CI)
 # Contains custom functions and macros, but don't altering any options
 include(VCMIUtils)
 vcmi_print_important_variables()
@@ -88,7 +88,7 @@ define_property(
 # Generate Version.cpp
 if(ENABLE_GITVERSION)
 	add_custom_target(update_version ALL
-		COMMAND ${CMAKE_COMMAND} -DGIT_SHA1="${GIT_SHA1}" -P "${CMAKE_MODULE_PATH}/Version.cmake"
+		COMMAND ${CMAKE_COMMAND} -DGIT_SHA1="${GIT_SHA1}" -P "${PROJECT_SOURCE_DIR}/cmake_modules/Version.cmake"
 	)
 else()
 	add_definitions(-DVCMI_NO_EXTRA_VERSION)

+ 1 - 1
Global.h

@@ -191,7 +191,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <boost/format.hpp>
 #include <boost/functional/hash.hpp>
 #include <boost/lexical_cast.hpp>
-#ifndef VCMI_ANDROID
+#ifdef VCMI_WINDOWS
 #include <boost/locale/generator.hpp>
 #endif
 #include <boost/logic/tribool.hpp>

+ 5 - 4
client/widgets/TextControls.cpp

@@ -656,19 +656,20 @@ CFocusable::~CFocusable()
 
 	focusables -= this;
 }
+
 void CFocusable::giveFocus()
 {
+	focus = true;
+	focusGot();
+	redraw();
+
 	if(inputWithFocus)
 	{
 		inputWithFocus->focus = false;
-		inputWithFocus->focusLost();
 		inputWithFocus->redraw();
 	}
 
-	focus = true;
 	inputWithFocus = this;
-	focusGot();
-	redraw();
 }
 
 void CFocusable::moveFocus()

+ 12 - 10
lib/CGameState.cpp

@@ -1158,7 +1158,7 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 
 	auto campaignState = scenarioOps->campState;
 	auto bonus = campaignState->getBonusForCurrentMap();
-	if (bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO)
+	if(bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO)
 	{
 		std::vector<CGHeroInstance *> heroes;
 		for(auto & node : campaignState->camp->scenarios[bonus->info2].crossoverHeroes)
@@ -1172,12 +1172,8 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 	{
 		if(!campaignState->mapsConquered.empty())
 		{
-			std::vector<CGHeroInstance *> heroes;
-			for(auto & node : campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes)
-			{
-				auto h = CCampaignState::crossoverDeserialize(node);
-				heroes.push_back(h);
-			}
+			std::vector<CGHeroInstance *> heroes = {};
+
 			crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = heroes;
 			crossoverHeroes.heroesFromPreviousScenario = heroes;
 
@@ -1190,7 +1186,7 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 				// remove heroes which didn't reached the end of the scenario, but were available at the start
 				for(auto hero : lostCrossoverHeroes)
 				{
-//					auto hero = CCampaignState::crossoverDeserialize(node);
+					//					auto hero = CCampaignState::crossoverDeserialize(node);
 					vstd::erase_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
 					{
 						return hero->subID == h->subID;
@@ -1202,11 +1198,12 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 				{
 					auto hero = CCampaignState::crossoverDeserialize(node);
 					// add new heroes and replace old heroes with newer ones
-					auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios,  [hero](CGHeroInstance * h)
+					auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
 					{
 						return hero->subID == h->subID;
 					});
-					if (it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
+
+					if(it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
 					{
 						// replace old hero with newer one
 						crossoverHeroes.heroesFromAnyPreviousScenarios[it - crossoverHeroes.heroesFromAnyPreviousScenarios.begin()] = hero;
@@ -1216,6 +1213,11 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
 						// add new hero
 						crossoverHeroes.heroesFromAnyPreviousScenarios.push_back(hero);
 					}
+
+					if(mapNr == campaignState->mapsConquered.back())
+					{
+						crossoverHeroes.heroesFromPreviousScenario.push_back(hero);
+					}
 				}
 			}
 		}

+ 47 - 25
lib/CModHandler.cpp

@@ -719,46 +719,68 @@ bool CModHandler::checkDependencies(const std::vector <TModID> & input) const
 	return true;
 }
 
-std::vector <TModID> CModHandler::resolveDependencies(std::vector <TModID> modsToResolve) const
-{
-	std::vector<TModID> brokenMods;
-
-	auto looksValid = [&](const CModInfo & mod) -> bool
+// Returned vector affects the resource loaders call order (see CFilesystemList::load).
+// The loaders call order matters when dependent mod overrides resources in its dependencies.
+std::vector <TModID> CModHandler::validateAndSortDependencies(std::vector <TModID> modsToResolve) const
+{
+	// Topological sort algorithm.
+	// TODO: Investigate possible ways to improve performance.
+	boost::range::sort(modsToResolve); // Sort mods per name
+	std::vector <TModID> sortedValidMods; // Vector keeps order of elements (LIFO) 
+	sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation
+	std::set <TModID> resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements
+
+	// Mod is resolved if it has not dependencies or all its dependencies are already resolved
+	auto isResolved = [&](const CModInfo & mod) -> CModInfo::EValidationStatus
 	{
-		auto res = true;
+		if(mod.dependencies.size() > resolvedModIDs.size())
+			return CModInfo::PENDING;
+
 		for(const TModID & dependency : mod.dependencies)
 		{
-			if(!vstd::contains(modsToResolve, dependency))
-			{
-				logMod->error("Mod '%s' will not work: it depends on mod '%s', which is not installed.", mod.name, dependency);
-				res = false; //continue iterations, since we should show all errors for the current mod.
-			}
+			if(!vstd::contains(resolvedModIDs, dependency))
+				return CModInfo::PENDING;
 		}
-		return res;
+		return CModInfo::PASSED;
 	};
 
 	while(true)
 	{
-		for(auto mod : modsToResolve)
-		{
-			if(!looksValid(this->allMods.at(mod)))
-				brokenMods.push_back(mod);
-		}
-		if(!brokenMods.empty())
+		std::set <TModID> resolvedOnCurrentTreeLevel;
+		for(auto it = modsToResolve.begin(); it != modsToResolve.end();) // One iteration - one level of mods tree
 		{
-			vstd::erase_if(modsToResolve, [&](TModID mid)
+			if(isResolved(allMods.at(*it)) == CModInfo::PASSED)
 			{
-				return brokenMods.end() != std::find(brokenMods.begin(), brokenMods.end(), mid);
-			});
-			brokenMods.clear();
+				resolvedOnCurrentTreeLevel.insert(*it); // Not to the resolvedModIDs, so current node childs will be resolved on the next iteration
+				sortedValidMods.push_back(*it);
+				it = modsToResolve.erase(it);
 				continue;
+			}
+			it++;
 		}
+		if(resolvedOnCurrentTreeLevel.size())
+		{
+			resolvedModIDs.insert(resolvedOnCurrentTreeLevel.begin(), resolvedOnCurrentTreeLevel.end());
+		            continue;
+		}
+		// If there're no valid mods on the current mods tree level, no more mod can be resolved, should be end.
 		break;
 	}
-	boost::range::sort(modsToResolve);
-	return modsToResolve;
+
+	// Left mods have unresolved dependencies, output all to log.
+	for(const auto & brokenModID : modsToResolve)
+	{
+		const CModInfo & brokenMod = allMods.at(brokenModID);
+		for(const TModID & dependency : brokenMod.dependencies)
+		{
+			if(!vstd::contains(resolvedModIDs, dependency))
+				logMod->error("Mod '%s' will not work: it depends on mod '%s', which is not installed.", brokenMod.name, dependency);
+		}
+	}
+	return sortedValidMods;
 }
 
+
 std::vector<std::string> CModHandler::getModList(std::string path)
 {
 	std::string modDir = boost::to_upper_copy(path + "MODS/");
@@ -898,7 +920,7 @@ static ui32 calculateModChecksum(const std::string modName, ISimpleResourceLoade
 
 void CModHandler::loadModFilesystems()
 {
-	activeMods = resolveDependencies(activeMods);
+	activeMods = validateAndSortDependencies(activeMods);
 
 	coreMod.updateChecksum(calculateModChecksum("core", CResourceHandler::get("core")));
 

+ 9 - 3
lib/CModHandler.h

@@ -242,9 +242,15 @@ class DLL_LINKAGE CModHandler
 	// - circular dependencies
 	bool checkDependencies(const std::vector <TModID> & input) const;
 
-	// returns load order in which all dependencies are resolved, e.g. loaded after required mods
-	// function assumes that input list is valid (checkDependencies returned true)
-	std::vector <TModID> resolveDependencies(std::vector<TModID> input) const;
+	/**
+	* 1. Set apart mods with resolved dependencies from mods which have unresolved dependencies
+	* 2. Sort resolved mods using topological algorithm
+	* 3. Log all problem mods and their unresolved dependencies
+	*
+	* @param modsToResolve list of valid mod IDs (checkDependencies returned true - TODO: Clarify it.)
+	* @return a vector of the topologically sorted resolved mods: child nodes (dependent mods) have greater index than parents
+	*/
+	std::vector <TModID> validateAndSortDependencies(std::vector <TModID> modsToResolve) const;
 
 	std::vector<std::string> getModList(std::string path);
 	void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods);

+ 33 - 1
lib/JsonNode.cpp

@@ -261,6 +261,28 @@ bool JsonNode::isCompact() const
 	}
 }
 
+bool JsonNode::TryBoolFromString(bool & success) const
+{
+	success = true;
+	if(type == JsonNode::JsonType::DATA_BOOL)
+		return Bool();
+
+	success = type == JsonNode::JsonType::DATA_STRING;
+	if(success)
+	{
+		auto boolParamStr = String();
+		boost::algorithm::trim(boolParamStr);
+		boost::algorithm::to_lower(boolParamStr);
+		success = boolParamStr == "true";
+
+		if(success)
+			return true;
+		
+		success = boolParamStr == "false";
+	}
+	return false;
+}
+
 void JsonNode::clear()
 {
 	setType(JsonType::DATA_NULL);
@@ -632,7 +654,17 @@ std::shared_ptr<ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter)
 				{
 					creatureLimiter->setCreature(CreatureID(creature));
 				});
-				creatureLimiter->includeUpgrades = parameters.size() > 1 ? parameters[1].Bool() : false;
+				auto includeUpgrades = false;
+
+				if(parameters.size() > 1)
+				{
+					bool success = true;
+					includeUpgrades = parameters[1].TryBoolFromString(success);
+
+					if(!success)
+						logMod->error("Second parameter of '%s' limiter should be Bool", limiterType);
+				}
+				creatureLimiter->includeUpgrades = includeUpgrades;
 				return creatureLimiter;
 			}
 			else if(limiterType == "HAS_ANOTHER_BONUS_LIMITER")

+ 3 - 0
lib/JsonNode.h

@@ -87,6 +87,9 @@ public:
 	/// removes all data from node and sets type to null
 	void clear();
 
+	/// returns bool or bool equivalent of string value if 'success' is true, or false otherwise
+	bool TryBoolFromString(bool & success) const;
+
 	/// non-const accessors, node will change type on type mismatch
 	bool & Bool();
 	double & Float();

+ 1 - 1
lib/VCMIDirs.cpp

@@ -654,7 +654,7 @@ namespace VCMIDirs
 		static bool initialized = false;
 		if (!initialized)
 		{
-			#ifndef VCMI_ANDROID
+			#ifdef VCMI_WINDOWS
 			std::locale::global(boost::locale::generator().generate("en_US.UTF-8"));
 			#endif
 			boost::filesystem::path::imbue(std::locale());

+ 50 - 29
lib/mapObjects/CObjectClassesHandler.cpp

@@ -148,30 +148,43 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData(size_t dataSize)
 
 /// selects preferred ID (or subID) for new object
 template<typename Map>
-si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID)
+si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 fixedObjectsBound)
 {
-	if (!fixedID.isNull() && (si32)fixedID.Float() < defaultID)
-		return static_cast<si32>(fixedID.Float()); // H3M object with fixed ID
-
-	if (map.empty())
-		return defaultID; // no objects loaded, keep gap for H3M objects
-	if (map.rbegin()->first >= defaultID)
-		return map.rbegin()->first + 1; // some modded objects loaded, return next available
+	assert(fixedObjectsBound > 0);
+	if(fixedID.isNull())
+	{
+		auto lastID = map.empty() ? 0 : map.rbegin()->first;
+		return lastID < fixedObjectsBound ? fixedObjectsBound : lastID + 1;
+	}
+	auto id = static_cast<si32>(fixedID.Float());
+	if(id >= fixedObjectsBound)
+		logGlobal->error("Getting next ID overflowed: %d >= %d", id, fixedObjectsBound);
 
-	return defaultID; // some H3M objects loaded, first modded found
+	return id;
 }
 
-void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj)
+void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj, bool isSubobject)
 {
-	if (!handlerConstructors.count(obj->handlerName))
+	static const si32 fixedObjectsBound = 1000; // legacy value for backward compatibilitty
+	static const si32 fixedSubobjectsBound = 10000000; // large enough arbitrary value to avoid ID-collisions
+	si32 usedBound = fixedObjectsBound;
+
+	if(!handlerConstructors.count(obj->handlerName))
 	{
 		logGlobal->error("Handler with name %s was not found!", obj->handlerName);
 		return;
 	}
+	const auto convertedId = VLC->modh->normalizeIdentifier(entry.meta, "core", identifier);
+	const auto & entryIndex = entry["index"];
+	bool useSelectNextID = !isSubobject || entryIndex.isNull();
 
-	std::string convertedId = VLC->modh->normalizeIdentifier(entry.meta, "core", identifier);
-
-	si32 id = selectNextID(entry["index"], obj->subObjects, 1000);
+	if(useSelectNextID && isSubobject)
+	{
+		usedBound = fixedSubobjectsBound;
+		logGlobal->error("Subobject index is Null. convertedId = '%s' obj->id = %d", convertedId, obj->id);
+	}
+	si32 id = useSelectNextID ? selectNextID(entryIndex, obj->subObjects, usedBound) 
+		: (si32)entryIndex.Float();
 
 	auto handler = handlerConstructors.at(obj->handlerName)();
 	handler->setType(obj->id, id);
@@ -196,46 +209,54 @@ void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, cons
 
 	//some mods redefine content handlers in the decoration.json in such way:
 	//"core:sign" : { "types" : { "forgeSign" : { ...
-	static const std::vector<std::string> knownProblemObjects
+	static const std::vector<std::string> breakersRMG
 	{
 		"hota.hota decorations:hotaPandoraBox"
 		, "hota.hota decorations:hotaSubterreanGate"
 	};
-	bool overrideForce = !obj->subObjects.count(id) ||
-	std::any_of(knownProblemObjects.begin(), knownProblemObjects.end(), [obj, id](const std::string & str)
-	{
-		return str.compare(obj->subObjects[id]->subTypeName) == 0;
-	});
+	const bool isExistingKey = obj->subObjects.count(id) > 0;
+	const bool isBreaker = std::any_of(breakersRMG.begin(), breakersRMG.end(),
+		[&handler](const std::string & str)
+		{
+			return str.compare(handler->subTypeName) == 0;
+		});
+	const bool passedHandler = !isExistingKey && !isBreaker;
 
-	if(overrideForce) // DO NOT override mod handlers by default
+	if(passedHandler)
 	{
 		obj->subObjects[id] = handler;
 		obj->subIds[convertedId] = id;
 	}
+	else if(isExistingKey) //It's supposed that fan mods handlers are not overridden by default handlers
+	{
+		logGlobal->trace("Handler '%s' has not been overridden with handler '%s' in object %s(%d)::%s(%d)",
+			obj->subObjects[id]->subTypeName, obj->handlerName, obj->identifier, obj->id, convertedId, id);
+	}
 	else
 	{
-		logGlobal->warn("Don't override handler %s in object %s(%d)::%s(%d) subTypeName : %s"
-			, obj->handlerName, obj->identifier, obj->id, convertedId, id, obj->subObjects[id]->subTypeName);
+		logGlobal->warn("Handler '%s' for object %s(%d)::%s(%d) has not been activated as RMG breaker",
+			obj->handlerName, obj->identifier, obj->id, convertedId, id);
 	}
 }
 
 CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name)
 {
 	auto obj = new ObjectContainter();
+	static const si32 fixedObjectsBound = 256; //Legacy value for backward compatibility
+
 	obj->identifier = name;
 	obj->name = json["name"].String();
 	obj->handlerName = json["handler"].String();
 	obj->base = json["base"];
-	obj->id = selectNextID(json["index"], objects, 256);
+	obj->id = selectNextID(json["index"], objects, fixedObjectsBound);
+
 	if(json["defaultAiValue"].isNull())
 		obj->groupDefaultAiValue = boost::none;
 	else
 		obj->groupDefaultAiValue = static_cast<boost::optional<si32>>(json["defaultAiValue"].Integer());
 
 	for (auto entry : json["types"].Struct())
-	{
 		loadObjectEntry(entry.first, entry.second, obj);
-	}
 
 	return obj;
 }
@@ -257,6 +278,8 @@ void CObjectClassesHandler::loadObject(std::string scope, std::string name, cons
 
 void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, si32 ID, boost::optional<si32> subID)
 {
+	static const bool isSubObject = true;
+
 	config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not NULL
 	assert(objects.count(ID));
 	if (subID)
@@ -265,10 +288,8 @@ void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNo
 		assert(config["index"].isNull());
 		config["index"].Float() = subID.get();
 	}
-
 	inheritNodeWithMeta(config, objects.at(ID)->base);
-
-	loadObjectEntry(identifier, config, objects[ID]);
+	loadObjectEntry(identifier, config, objects[ID], isSubObject);
 }
 
 void CObjectClassesHandler::removeSubObject(si32 ID, si32 subID)

+ 2 - 1
lib/mapObjects/CObjectClassesHandler.h

@@ -283,8 +283,9 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 	/// format: customNames[primaryID][secondaryID] -> name
 	std::map<si32, std::vector<std::string>> customNames;
 
-	void loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj);
+	void loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj, bool isSubobject = false);
 	ObjectContainter * loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name);
+
 public:
 	CObjectClassesHandler();
 	~CObjectClassesHandler();

+ 12 - 9
lib/mapObjects/CommonConstructors.cpp

@@ -144,21 +144,24 @@ CDwellingInstanceConstructor::CDwellingInstanceConstructor()
 void CDwellingInstanceConstructor::initTypeData(const JsonNode & input)
 {
 	const JsonVector & levels = input["creatures"].Vector();
-	availableCreatures.resize(levels.size());
-	for (size_t i=0; i<levels.size(); i++)
+	const auto totalLevels = levels.size();
+
+	availableCreatures.resize(totalLevels);
+	for(auto currentLevel = 0; currentLevel < totalLevels; currentLevel++)
 	{
-		const JsonVector & creatures = levels[i].Vector();
-		availableCreatures[i].resize(creatures.size());
-		for (size_t j=0; j<creatures.size(); j++)
+		const JsonVector & creaturesOnLevel = levels[currentLevel].Vector();
+		const auto creaturesNumber = creaturesOnLevel.size();
+		availableCreatures[currentLevel].resize(creaturesNumber);
+
+		for(auto currentCreature = 0; currentCreature < creaturesNumber; currentCreature++)
 		{
-			VLC->modh->identifiers.requestIdentifier("creature", creatures[j], [=] (si32 index)
+			VLC->modh->identifiers.requestIdentifier("creature", creaturesOnLevel[currentCreature], [=] (si32 index)
 			{
-				availableCreatures[i][j] = VLC->creh->objects[index];
+				availableCreatures[currentLevel][currentCreature] = VLC->creh->objects[index];
 			});
 		}
-		assert(!availableCreatures[i].empty());
+		assert(!availableCreatures[currentLevel].empty());
 	}
-
 	guards = input["guards"];
 }
 

+ 1 - 1
lib/mapping/CMap.cpp

@@ -30,7 +30,7 @@ SHeroName::SHeroName() : heroId(-1)
 
 PlayerInfo::PlayerInfo(): canHumanPlay(false), canComputerPlay(false),
 	aiTactic(EAiTactic::RANDOM), isFactionRandom(false), hasRandomHero(false), mainCustomHeroPortrait(-1), mainCustomHeroId(-1), hasMainTown(false),
-	generateHeroAtMainTown(false), team(TeamID::NO_TEAM), /* following are unused */ generateHero(false), p7(0), powerPlaceholders(-1)
+	generateHeroAtMainTown(false), posOfMainTown(-1), team(TeamID::NO_TEAM), /* following are unused */ generateHero(false), p7(0), powerPlaceholders(-1)
 {
 	allowedFactions = VLC->townh->getAllowedFactions();
 }

+ 1 - 1
license.txt

@@ -1,4 +1,4 @@
-    GNU GENERAL PUBLIC LICENSE
+    GNU GENERAL PUBLIC LICENSE
 		       Version 2, June 1991
 
  Copyright (C) 1989, 1991 Free Software Foundation, Inc.,

+ 7 - 1
osx/CMakeLists.txt

@@ -11,10 +11,16 @@ if(APPLE)
 		")
 	endif()
 
+	# Manually fix VCMI library links in AI libraries with install_name_tool
 	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/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\")
+		execute_process(COMMAND rm \"\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_BINARY_DIR}/libvcmi.dylib\")
 	" COMPONENT Runtime)
 endif(APPLE)
 
@@ -22,7 +28,7 @@ endif(APPLE)
 if(WIN32)
 	if(ENABLE_LAUNCHER)
 		# Temporary ugly fix for Qt deployment since windeployqt broken in Vcpkg
-		
+
 		#there are some weird issues with variables used in path not evaluated properly when trying to remove code duplication from below lines
 		if(EXISTS ${CMAKE_BINARY_DIR}/../../vcpkg) #current path to vcpkg main folder on appveyor CI
 			if(CMAKE_SIZEOF_VOID_P EQUAL 8) #64 bit build

+ 7 - 4
server/CGameHandler.cpp

@@ -1306,11 +1306,11 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
 {
 	for(auto playerConns : connections)
 	{
-		for(auto conn : playerConns.second)
+		for(auto i = playerConns.second.begin(); i != playerConns.second.end(); )
 		{
-			if(lobby->state != EServerState::SHUTDOWN && conn == c)
+			if(lobby->state != EServerState::SHUTDOWN && *i == c)
 			{
-				vstd::erase_if_present(playerConns.second, conn);
+				i = playerConns.second.erase(i);
 				if(playerConns.second.size())
 					continue;
 				PlayerCheated pc;
@@ -1319,6 +1319,8 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
 				sendAndApply(&pc);
 				checkVictoryLossConditionsForPlayer(playerConns.first);
 			}
+			else
+				++i;
 		}
 	}
 }
@@ -5426,7 +5428,8 @@ void CGameHandler::checkVictoryLossConditionsForAll()
 void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 {
 	const PlayerState * p = getPlayerState(player);
-	if (p->status != EPlayerStatus::INGAME) return;
+
+	if(!p || p->status != EPlayerStatus::INGAME) return;
 
 	auto victoryLossCheckResult = gs->checkForVictoryAndLoss(player);
 

+ 2 - 10
test/CMakeLists.txt

@@ -90,11 +90,7 @@ set(test_SRCS
  		spells/targetConditions/ReceptiveFeatureConditionTest.cpp
  		spells/targetConditions/SpellEffectConditionTest.cpp
  		spells/targetConditions/TargetConditionItemFixture.cpp
-
-		vcai/mock_ResourceManager.cpp
-		vcai/mock_VCAI.cpp
-		vcai/ResurceManagerTest.cpp
-
+		
 		mock/BattleFake.cpp
  		mock/mock_IGameCallback.cpp
  		mock/mock_MapService.cpp
@@ -147,16 +143,12 @@ set(mock_HEADERS
 		mock/mock_UnitInfo.h
 		mock/mock_vstd_RNG.h
 		mock/mock_CPSICallback.h
-
-		vcai/mock_ResourceManager.h
-		vcai/mock_VCAI.h
-		vcai/mock_VCAI_CGoal.h
 )
 
 add_subdirectory_with_folder("3rdparty" googletest EXCLUDE_FROM_ALL)
 
 add_executable(vcmitest ${test_SRCS} ${test_HEADERS} ${mock_HEADERS})
-target_link_libraries(vcmitest PRIVATE gtest gmock vcmi ${SYSTEM_LIBS} VCAI)
+target_link_libraries(vcmitest PRIVATE gtest gmock vcmi ${SYSTEM_LIBS})
 
 target_include_directories(vcmitest
 		PUBLIC	${CMAKE_CURRENT_SOURCE_DIR}

+ 17 - 11
test/map/CMapFormatTest.cpp

@@ -42,6 +42,12 @@ TEST(MapFormat, Random)
 	SCOPED_TRACE("MapFormat_Random start");
 
 	CMapGenOptions opt;
+	CRmgTemplate tmpl;
+
+	const_cast<CRmgTemplate::CPlayerCountRange &>(tmpl.getCpuPlayers()).addRange(1, 4);
+	const_cast<CRmgTemplate::Zones &>(tmpl.getZones())[0] = std::make_shared<rmg::ZoneOptions>();
+
+	opt.setMapTemplate(&tmpl);
 
 	opt.setHeight(CMapHeader::MAP_SIZE_MIDDLE);
 	opt.setWidth(CMapHeader::MAP_SIZE_MIDDLE);
@@ -84,7 +90,7 @@ static JsonNode getFromArchive(CZipLoader & archive, const std::string & archive
 	ResourceID resource(archiveFilename, EResType::TEXT);
 
 	if(!archive.existsResource(resource))
-		throw std::runtime_error(archiveFilename+" not found");
+		throw std::runtime_error(archiveFilename + " not found");
 
 	auto data = archive.load(resource)->readAll();
 
@@ -144,14 +150,14 @@ TEST(MapFormat, Objects)
 {
 	static const std::string MAP_DATA_PATH = "test/ObjectPropertyTest/";
 
-	const JsonNode initialHeader(ResourceID(MAP_DATA_PATH+CMapFormatJson::HEADER_FILE_NAME));
-	const JsonNode expectedHeader(ResourceID(MAP_DATA_PATH+CMapFormatJson::HEADER_FILE_NAME));//same as initial for now
+	const JsonNode initialHeader(ResourceID(MAP_DATA_PATH + CMapFormatJson::HEADER_FILE_NAME));
+	const JsonNode expectedHeader(ResourceID(MAP_DATA_PATH + CMapFormatJson::HEADER_FILE_NAME));//same as initial for now
 
-	const JsonNode initialObjects(ResourceID(MAP_DATA_PATH+CMapFormatJson::OBJECTS_FILE_NAME));
-	const JsonNode expectedObjects(ResourceID(MAP_DATA_PATH+"objects.ex.json"));
+	const JsonNode initialObjects(ResourceID(MAP_DATA_PATH + CMapFormatJson::OBJECTS_FILE_NAME));
+	const JsonNode expectedObjects(ResourceID(MAP_DATA_PATH + "objects.ex.json"));
 
-	const JsonNode expectedSurface(ResourceID(MAP_DATA_PATH+"surface_terrain.json"));
-	const JsonNode expectedUnderground(ResourceID(MAP_DATA_PATH+"underground_terrain.json"));
+	const JsonNode expectedSurface(ResourceID(MAP_DATA_PATH + "surface_terrain.json"));
+	const JsonNode expectedUnderground(ResourceID(MAP_DATA_PATH + "underground_terrain.json"));
 
 	std::unique_ptr<CMap> originalMap = loadOriginal(initialHeader, initialObjects, expectedSurface, expectedUnderground);
 
@@ -183,11 +189,11 @@ TEST(MapFormat, Terrain)
 {
 	static const std::string MAP_DATA_PATH = "test/TerrainTest/";
 
-	const JsonNode expectedHeader(ResourceID(MAP_DATA_PATH+CMapFormatJson::HEADER_FILE_NAME));
-	const JsonNode expectedObjects(ResourceID(MAP_DATA_PATH+CMapFormatJson::OBJECTS_FILE_NAME));
+	const JsonNode expectedHeader(ResourceID(MAP_DATA_PATH + CMapFormatJson::HEADER_FILE_NAME));
+	const JsonNode expectedObjects(ResourceID(MAP_DATA_PATH + CMapFormatJson::OBJECTS_FILE_NAME));
 
-	const JsonNode expectedSurface(ResourceID(MAP_DATA_PATH+"surface_terrain.json"));
-	const JsonNode expectedUnderground(ResourceID(MAP_DATA_PATH+"underground_terrain.json"));
+	const JsonNode expectedSurface(ResourceID(MAP_DATA_PATH + "surface_terrain.json"));
+	const JsonNode expectedUnderground(ResourceID(MAP_DATA_PATH + "underground_terrain.json"));
 
 	std::unique_ptr<CMap> originalMap = loadOriginal(expectedHeader, expectedObjects, expectedSurface, expectedUnderground);
 

+ 1 - 0
test/spells/TargetConditionTest.cpp

@@ -194,6 +194,7 @@ TEST_F(TargetConditionTest, StoresNormalLevelCondition)
 
 TEST_F(TargetConditionTest, StoresReceptiveFeature)
 {
+	redirectFactoryToStub();
 	auto itemStub = std::make_shared<StrictMock<ItemMock>>();
 	EXPECT_CALL(factoryMock, createReceptiveFeature()).WillOnce(Return(itemStub));
 	setupSubject(JsonNode());