1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-30 04:30:42 +02:00

Merge remote-tracking branch 'upstream/develop' into vlc-obstacles

# Conflicts:
#	.github/workflows/github.yml
#	CI/mac/before_install.sh
#	client/CGameInfo.cpp
#	client/CGameInfo.h
#	client/battle/CBattleInterface.cpp
#	config/gameConfig.json
#	config/obstacles.json
#	config/randomMap.json
#	config/schemas/battlefield.json
#	config/terrains.json
#	include/vcmi/Services.h
#	lib/BattleFieldHandler.cpp
#	lib/BattleFieldHandler.h
#	lib/CHeroHandler.cpp
#	lib/CHeroHandler.h
#	lib/CModHandler.cpp
#	lib/GameConstants.cpp
#	lib/GameConstants.h
#	lib/IGameCallback.cpp
#	lib/Terrain.cpp
#	lib/Terrain.h
#	lib/VCMI_Lib.cpp
#	lib/VCMI_Lib.h
#	lib/battle/BattleInfo.cpp
#	lib/mapObjects/CObjectClassesHandler.cpp
#	lib/mapObjects/CObjectClassesHandler.h
#	lib/mapObjects/ObjectTemplate.cpp
#	lib/mapObjects/ObjectTemplate.h
#	lib/mapping/CCampaignHandler.h
#	lib/rmg/CMapGenerator.cpp
#	lib/rmg/CMapGenerator.h
#	lib/rmg/ConnectionsPlacer.cpp
#	lib/rmg/ObjectManager.cpp
#	lib/rmg/ObjectManager.h
#	lib/rmg/ObstaclePlacer.cpp
#	lib/rmg/RiverPlacer.cpp
#	lib/rmg/RmgObject.cpp
#	lib/rmg/RoadPlacer.cpp
#	lib/rmg/RoadPlacer.h
#	lib/rmg/TownPlacer.cpp
#	lib/rmg/TreasurePlacer.cpp
#	lib/rmg/TreasurePlacer.h
#	lib/rmg/WaterProxy.cpp
#	lib/spells/CSpellHandler.cpp
#	test/mock/mock_Services.h
This commit is contained in:
nordsoft 2022-09-13 03:00:32 +04:00
commit e5e5e78fca
111 changed files with 1930 additions and 1177 deletions

View File

@ -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:

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
/server/vcmiserver
/launcher/vcmilauncher
/launcher/vcmilauncher_automoc.cpp
/conan-generated
build/
.cache/*

View File

@ -92,6 +92,8 @@ int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, cons
TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
{
//Fixme: unused variable hero
auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
auto resources = bankInfo->getPossibleResourcesReward();
@ -114,11 +116,33 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
auto creatures = bankInfo->getPossibleCreaturesReward();
uint64_t result = 0;
for(auto c : creatures)
const auto& slots = hero->Slots();
ui64 weakestStackPower = 0;
if (slots.size() >= GameConstants::ARMY_SIZE)
{
result += c.data.type->AIValue * c.data.count * c.chance / 100;
//No free slot, we might discard our weakest stack
weakestStackPower = std::numeric_limits<ui64>().max();
for (const auto stack : slots)
{
vstd::amin(weakestStackPower, stack.second->getPower());
}
}
for (auto c : creatures)
{
//Only if hero has slot for this creature in the army
if (hero->getSlotFor(c.data.type).validSlot())
{
result += (c.data.type->AIValue * c.data.count) * c.chance;
}
else
{
//we will need to discard the weakest stack
result += (c.data.type->AIValue * c.data.count - weakestStackPower) * c.chance;
}
}
result /= 100; //divide by total chance
return result;
}
@ -168,13 +192,13 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
return 1500;
auto statsValue =
4 * art->valOfBonuses(Bonus::LAND_MOVEMENT)
10 * art->valOfBonuses(Bonus::LAND_MOVEMENT)
+ 1200 * art->valOfBonuses(Bonus::STACKS_SPEED)
+ 700 * art->valOfBonuses(Bonus::MORALE)
+ 700 * art->getAttack(false)
+ 700 * art->getDefense(false)
+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
+ 700 * art->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
+ 700 * art->getDefense(false)
+ 500 * art->valOfBonuses(Bonus::LUCK);
auto classValue = 0;
@ -234,6 +258,8 @@ uint64_t RewardEvaluator::getArmyReward(
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
: 0;
case Obj::PANDORAS_BOX:
return 5000;
default:
return 0;
}
@ -273,7 +299,14 @@ float RewardEvaluator::getEnemyHeroStrategicalValue(const CGHeroInstance * enemy
vstd::amax(objectValue, getStrategicalValue(obj));
}
return objectValue / 2.0f + enemy->level / 15.0f;
/*
1. If an enemy hero can attack nearby object, it's not useful to capture the object on our own.
Killing the hero is almost as important (0.9) as capturing the object itself.
2. The formula quickly approaches 1.0 as hero level increases,
but higher level always means higher value and the minimal value for level 1 hero is 0.5
*/
return std::min(1.0f, objectValue * 0.9f + (1.0f - (1.0f / (1 + enemy->level))));
}
float RewardEvaluator::getResourceRequirementStrength(int resType) const
@ -322,13 +355,28 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
case Obj::RESOURCE:
return target->subID == Res::GOLD ? 0 : 0.1f * getResourceRequirementStrength(target->subID);
case Obj::CREATURE_BANK:
{
auto resourceReward = getCreatureBankResources(target, nullptr);
float sum = 0.0f;
for (TResources::nziterator it (resourceReward); it.valid(); it++)
{
//Evaluate resources used for construction. Gold is evaluated separately.
if (it->resType != Res::GOLD)
{
sum += 0.1f * getResourceRequirementStrength(it->resType);
}
}
return sum;
}
case Obj::TOWN:
if(ai->buildAnalyzer->getDevelopmentInfo().empty())
return 1;
return dynamic_cast<const CGTownInstance *>(target)->hasFort()
? (target->tempOwner == PlayerColor::NEUTRAL ? 0.8f : 1.0f)
: 0.5f;
: 0.7f;
case Obj::HERO:
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
@ -385,6 +433,9 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
return 8;
case Obj::WITCH_HUT:
return evaluateWitchHutSkillScore(dynamic_cast<const CGWitchHut *>(target), hero, role);
case Obj::PANDORAS_BOX:
//Can contains experience, spells, or skills (only on custom maps)
return 2.5f;
case Obj::HERO:
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
@ -464,6 +515,11 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
return 10000;
case Obj::SEA_CHEST:
return 1500;
case Obj::PANDORAS_BOX:
return 5000;
case Obj::PRISON:
//Objectively saves us 2500 to hire hero
return GameConstants::HERO_GOLD_COST;
case Obj::HERO:
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))

View File

@ -80,4 +80,4 @@ Dmitry Orlov, <shubus.corporation@gmail.com>
* special buildings support in fan towns, new features and bug fixes
Andrey Cherkas aka nordsoft, <nordsoft@yahoo.com>
* random map generator features and bug fixes
* new terrain support, random map generator features and various bug fixes

11
CI/conan/macos-arm Normal file
View File

@ -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]

11
CI/conan/macos-intel Normal file
View File

@ -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]

4
CI/mac-arm/before_install.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
DEPS_FILENAME=intel-cross-arm
. CI/mac/before_install.sh

4
CI/mac-intel/before_install.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
DEPS_FILENAME=intel
. CI/mac/before_install.sh

13
CI/mac/before_install.sh Normal file → Executable file
View File

@ -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 -

View File

@ -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
@ -40,8 +37,8 @@ if(NOT CMAKE_BUILD_TYPE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo)
endif()
set(VCMI_VERSION_MAJOR 0)
set(VCMI_VERSION_MINOR 99)
set(VCMI_VERSION_MAJOR 1)
set(VCMI_VERSION_MINOR 0)
set(VCMI_VERSION_PATCH 0)
option(ENABLE_ERM "Enable compilation of ERM scripting module" ON)
@ -90,6 +87,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()
@ -130,10 +128,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)
@ -225,7 +219,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)
@ -290,6 +290,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")
@ -301,7 +302,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()

View File

@ -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",

View File

@ -1,9 +1,12 @@
0.99 -> 1.00
0.99 -> 1.0
GENERAL:
* Spectator mode was implemented through command-line options
* Some main menu settings get saved after returning to main menu - last selected map, save etc.
* Restart scenario button should work correctly now
* Skyship Grail works now immediately after capturing without battle
* Lodestar Grail implemented
* Fixed Gargoyles immunity
* New bonuses:
- SOUL_STEAL - "WoG ghost" ability, should work somewhat same as in H3
- TRANSMUTATION - "WoG werewolf"-like ability
@ -35,17 +38,53 @@ MODS:
* Added bonus updaters for hero specialties
* Added allOf, anyOf and noneOf qualifiers for bonus limiters
* Added bonus limiters: alignment, faction and terrain
* Supported new terrains, new battlefields, custom water and rock terrains
* Following special buildings becomes available in the fan towns:
- attackVisitingBonus
- defenceVisitingBonus
- spellPowerVisitingBonus
- knowledgeVisitingBonus
- experienceVisitingBonus
- lighthouse
- treasury
SOUND:
* Fixed many mising or wrong pickup and visit sounds for map objects
* All map objects now have ambient sounds identical to OH3
RANDOM MAP GENERATOR:
* Random map generator supports water modes (normal, islands)
* Added config randomMap.json with settings for map generator
* Added parameter for template allowedWaterContent
* Extra resource packs appear nearby mines
* All map objects now have ambient sounds identical to OH3
RANDOM MAP GENERATOR:
* Random map generator supports water modes (normal, islands)
* Added config randomMap.json with settings for map generator
* Added parameter for template allowedWaterContent
* Extra resource packs appear nearby mines
* Underground can be player starting place for factions allowed to be placed underground
* Improved obstacles placement aesthetics
* Rivers are generated on the random maps
* RMG works more stable, various crashes have been fixed
* Treasures requiring guards are guaranteed to be protected
VCAI:
* Reworked goal decomposition engine, fixing many loopholes. AI will now pick correct goals faster.
* AI will now use universal pathfinding globally
* AI can use Summon Boat and Town Portal
* AI can gather and save resources on purpose
* AI will only buy army on demand instead of every turn
* AI can distinguish the value of all map objects
* General speed optimizations
BATTLES:
* Towers should block ranged retaliation
* AI can bypass broken wall with moat instead of standing and waiting until gate is destroyed
* Towers do not attack war machines automatically
* Draw is possible now as battle outcome in case the battle ends with only summoned creatures (both sides loose)
ADVENTURE MAP:
* Added buttons and keyboard shortcuts to quickly exchange army and artifacts between heroes
* Fix: Captured town should not be duplicated on the UI
LAUNCHER:
* Implemented notifications about updates
* Supported redirection links for downloading mods
0.98 -> 0.99

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

40
Mods/vcmi/Data/s/std.verm Executable file
View File

@ -0,0 +1,40 @@
VERM
; standard verm file, global engine things should be put here
!?PI;
; example 1 --- Hello World
![print ^Hello world!^]
; example 2 --- simple arithmetics
![defun add [x y] [+ x y]]
![print [add 2 3]]
; example 3 --- semantic macros
![defmacro do-n-times [times body]
`[progn
[setq do-counter 0]
[setq do-max ,times]
[do [< do-counter do-max]
[progn
[setq do-counter [+ do-counter 1]]
,body
]
]
]
]
![do-n-times 4 [print ^tekst\n^]]
; example 4 --- conditional expression
![if [> 2 1] [print ^Wieksze^] [print ^Mniejsze^]]
; example 5 --- lambda expressions
![[lambda [x y] [if [> x y] [print ^wieksze^] [print ^mniejsze^]]] 2 3]
; example 6 --- resursion
![defun factorial [n]
[if [= n 0] 1
[* n [factorial [- n 1]]]
]
]
![print [factorial 8]]

14
Mods/vcmi/Data/s/testy.erm Executable file
View File

@ -0,0 +1,14 @@
ZVSE
!?PI;
!!VRv2777:S4;
!!DO1/0/5/1&v2777<>1:P0;
!?FU1;
!!VRv2778:Sx16%2;
!!IF&x16>3:M^Hello world number %X16! To duza liczba^;
!!IF&v2778==0&x16<=3:M^Hello world number %X16! To mala parzysta liczba^;
!!IF&v2778==1&x16<=3:M^Hello world number %X16! To mala nieparzysta liczba^;
!?PI;
!!VRz10:S^Composed hello ^;
!!IF:M^%Z10%%world%%, v2777=%V2777, v2778=%V2778!^;

Binary file not shown.

View File

@ -10,7 +10,6 @@
// CMT.cpp : Defines the entry point for the console application.
//
#include "StdInc.h"
#include <SDL_mixer.h>
#include <boost/program_options.hpp>
@ -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[])

View File

@ -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

View File

@ -272,13 +272,14 @@ void CSoundHandler::soundFinishedCallback(int channel)
{
std::map<int, std::function<void()> >::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

View File

@ -47,6 +47,10 @@
#include <vcmi/events/EventBus.h>
#ifdef VCMI_WINDOWS
#include <windows.h>
#endif
template<typename T> class CApplyOnLobby;
#ifdef VCMI_ANDROID
@ -694,7 +698,23 @@ void CServerHandler::threadRunServer()
}
comm += " > \"" + logName + '\"';
#ifdef VCMI_WINDOWS
int result = -1;
const auto bufSize = ::MultiByteToWideChar(CP_UTF8, 0, comm.c_str(), comm.size(), nullptr, 0);
if(bufSize > 0)
{
std::wstring wComm(bufSize, {});
const auto convertResult = ::MultiByteToWideChar(CP_UTF8, 0, comm.c_str(), comm.size(), &wComm[0], bufSize);
if(convertResult > 0)
result = ::_wsystem(wComm.c_str());
else
logNetwork->error("Error " + std::to_string(GetLastError()) + ": failed to convert server launch command to wide string: " + comm);
}
else
logNetwork->error("Error " + std::to_string(GetLastError()) + ": failed to obtain buffer length to convert server launch command to wide string : " + comm);
#else
int result = std::system(comm.c_str());
#endif
if (result == 0)
{
logNetwork->info("Server closed correctly");

View File

@ -173,8 +173,8 @@ void Graphics::loadHeroAnimations()
{
for (auto & templ : VLC->objtypeh->getHandlerFor(Obj::HERO, elem->getIndex())->getTemplates())
{
if (!heroAnimations.count(templ.animationFile))
heroAnimations[templ.animationFile] = loadHeroAnimation(templ.animationFile);
if (!heroAnimations.count(templ->animationFile))
heroAnimations[templ->animationFile] = loadHeroAnimation(templ->animationFile);
}
}
@ -381,21 +381,21 @@ std::shared_ptr<CAnimation> Graphics::getAnimation(const CGObjectInstance* obj)
return getAnimation(obj->appearance);
}
std::shared_ptr<CAnimation> Graphics::getAnimation(const ObjectTemplate & info)
std::shared_ptr<CAnimation> Graphics::getAnimation(std::shared_ptr<const ObjectTemplate> info)
{
//the only(?) invisible object
if(info.id == Obj::EVENT)
if(info->id == Obj::EVENT)
{
return std::shared_ptr<CAnimation>();
}
if(info.animationFile.empty())
if(info->animationFile.empty())
{
logGlobal->warn("Def name for obj (%d,%d) is empty!", info.id, info.subid);
logGlobal->warn("Def name for obj (%d,%d) is empty!", info->id, info->subid);
return std::shared_ptr<CAnimation>();
}
std::shared_ptr<CAnimation> ret = mapObjectAnimations[info.animationFile];
std::shared_ptr<CAnimation> ret = mapObjectAnimations[info->animationFile];
//already loaded
if(ret)
@ -404,8 +404,8 @@ std::shared_ptr<CAnimation> Graphics::getAnimation(const ObjectTemplate & info)
return ret;
}
ret = std::make_shared<CAnimation>(info.animationFile);
mapObjectAnimations[info.animationFile] = ret;
ret = std::make_shared<CAnimation>(info->animationFile);
mapObjectAnimations[info->animationFile] = ret;
ret->preload();
return ret;

View File

@ -98,7 +98,7 @@ public:
void blueToPlayersAdv(SDL_Surface * sur, PlayerColor player); //replaces blue interface colour with a color of player
std::shared_ptr<CAnimation> getAnimation(const CGObjectInstance * obj);
std::shared_ptr<CAnimation> getAnimation(const ObjectTemplate & info);
std::shared_ptr<CAnimation> getAnimation(std::shared_ptr<const ObjectTemplate> info);
};
extern Graphics * graphics;

View File

@ -1,16 +0,0 @@
/* SDLMain.m - main entry point for our Cocoa-ized SDL app
Initial Version: Darrell Walisser <dwaliss1@purdue.edu>
Non-NIB-Code & other changes: Max Horn <max@quendi.de>
Feel free to customize this file to suit your needs
*/
#ifndef _SDLMain_h_
#define _SDLMain_h_
#import <Cocoa/Cocoa.h>
@interface SDLMain : NSObject
@end
#endif /* _SDLMain_h_ */

View File

@ -1,383 +0,0 @@
/* SDLMain.m - main entry point for our Cocoa-ized SDL app
Initial Version: Darrell Walisser <dwaliss1@purdue.edu>
Non-NIB-Code & other changes: Max Horn <max@quendi.de>
Feel free to customize this file to suit your needs
*/
#include "SDL.h"
#include "SDLMain.h"
#include <sys/param.h> /* for MAXPATHLEN */
#include <unistd.h>
/* 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;
}

View File

@ -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

View File

@ -118,8 +118,6 @@ public:
class CMovementAnimation : public CBattleStackAnimation
{
private:
bool shouldRotate();
std::vector<BattleHex> destTiles; //full path, includes already passed hexes
ui32 curentMoveIndex; // index of nextHex in destTiles

View File

@ -503,13 +503,14 @@ void CBattleInterface::activate()
bWait->activate();
bDefence->activate();
for (auto hex : bfield)
hex->activate();
if (attackingHero)
attackingHero->activate();
if (defendingHero)
defendingHero->activate();
for (auto hex : bfield)
hex->activate();
if (settings["battle"]["showQueue"].Bool())
queue->activate();
@ -1561,6 +1562,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 +1942,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

View File

@ -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<std::shared_ptr<CClickableHex>> bfield; //11 lines, 17 hexes on each
SDL_Surface *cellBorder, *cellShade;

View File

@ -10,8 +10,6 @@
#include "StdInc.h"
#include "CAnimation.h"
#include <SDL_image.h>
#include "../CBitmapHandler.h"
#include "../Graphics.h"
#include "../gui/SDL_Extensions.h"

View File

@ -16,6 +16,10 @@
#include "../Graphics.h"
#include "../CMT.h"
#ifdef VCMI_APPLE
#include <dispatch/dispatch.h>
#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)

View File

@ -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)
{

View File

@ -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");
}
}

View File

@ -979,7 +979,7 @@ CMapHandler::AnimBitmapHolder CMapHandler::CMapBlitter::findHeroBitmap(const CGH
if (hero->boat)
animation = graphics->boatAnimations[hero->boat->subID];
else
animation = graphics->heroAnimations[hero->appearance.animationFile];
animation = graphics->heroAnimations[hero->appearance->animationFile];
bool moving = !hero->isStanding;
int group = getHeroFrameGroup(hero->moveDir, moving);
@ -1486,8 +1486,8 @@ bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObj
return true;
if (!b)
return false;
if (a->appearance.printPriority != b->appearance.printPriority)
return a->appearance.printPriority > b->appearance.printPriority;
if (a->appearance->printPriority != b->appearance->printPriority)
return a->appearance->printPriority > b->appearance->printPriority;
if(a->pos.y != b->pos.y)
return a->pos.y < b->pos.y;

View File

@ -324,6 +324,11 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
upgradeBtn->addOverlay(std::make_shared<CAnimImage>("CPRSMALL", VLC->creh->objects[upgradeInfo.info.newID[buttonIndex]]->iconIndex));
if(buttonsToCreate == 1) // single upgrade avaialbe
{
upgradeBtn->assignedKeys.insert(SDLK_u);
}
upgrade[buttonIndex] = upgradeBtn;
}
}

View File

@ -29,20 +29,20 @@ void QuickRecruitmentWindow::setButtons()
void QuickRecruitmentWindow::setCancelButton()
{
cancelButton = std::make_shared<CButton>(Point((pos.w / 2) + 48, 418), "ICN6432.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_RETURN);
cancelButton = std::make_shared<CButton>(Point((pos.w / 2) + 48, 418), "ICN6432.DEF", CButton::tooltip(), [&](){ close(); }, SDLK_ESCAPE);
cancelButton->setImageOrder(0, 1, 2, 3);
}
void QuickRecruitmentWindow::setBuyButton()
{
buyButton = std::make_shared<CButton>(Point((pos.w/2)-32, 418), "IBY6432.DEF", CButton::tooltip(), [&](){ purhaseUnits(); });
buyButton = std::make_shared<CButton>(Point((pos.w/2)-32, 418), "IBY6432.DEF", CButton::tooltip(), [&](){ purhaseUnits(); }, SDLK_RETURN);
cancelButton->assignedKeys.insert(SDLK_ESCAPE);
buyButton->setImageOrder(0, 1, 2, 3);
}
void QuickRecruitmentWindow::setMaxButton()
{
maxButton = std::make_shared<CButton>(Point((pos.w/2)-112, 418), "IRCBTNS.DEF", CButton::tooltip(), [&](){ maxAllCards(cards); });
maxButton = std::make_shared<CButton>(Point((pos.w/2)-112, 418), "IRCBTNS.DEF", CButton::tooltip(), [&](){ maxAllCards(cards); }, SDLK_m);
maxButton->setImageOrder(0, 1, 2, 3);
}

197
conanfile.py Normal file
View File

@ -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")

12
config/creatures/conflux.json Normal file → Executable file
View File

@ -31,6 +31,12 @@
"type" : "MORE_DAMAGE_FROM_SPELL",
"subtype" : "spell.chainLightning",
"val" : 100
},
"armageddonVulnerablity" :
{
"type" : "MORE_DAMAGE_FROM_SPELL",
"subtype" : "spell.armageddon",
"val" : 100
}
},
"upgrades": ["stormElemental"],
@ -523,6 +529,12 @@
"type" : "MORE_DAMAGE_FROM_SPELL",
"subtype" : "spell.chainLightning",
"val" : 100
},
"armageddonVulnerablity" :
{
"type" : "MORE_DAMAGE_FROM_SPELL",
"subtype" : "spell.armageddon",
"val" : 100
}
},
"graphics" :

2
config/creatures/inferno.json Normal file → Executable file
View File

@ -315,7 +315,7 @@
"fireShield" :
{
"type" : "FIRE_SHIELD",
"val" : 30
"val" : 20
}
},
"graphics" :

7
config/heroes/conflux.json Normal file → Executable file
View File

@ -46,8 +46,9 @@
]
},
"bonuses" : {
"health" : {
"type" : "STACK_HEALTH",
"damage" : {
"type" : "CREATURE_DAMAGE",
"subtype" : 0,
"val" : 5
},
"attack" : {
@ -173,7 +174,7 @@
]
},
"bonuses" : {
"health" : {
"damage" : {
"type" : "CREATURE_DAMAGE",
"subtype" : 0,
"val" : 5

View File

@ -478,6 +478,9 @@
"object" : {
"index" : 0,
"aiValue" : 500,
"sounds" : {
"removal" : [ "PICKUP01", "PICKUP02", "PICKUP03", "PICKUP04", "PICKUP05", "PICKUP06", "PICKUP07" ]
},
"templates" : {
"normal" : {
"visitableFrom" : [ "+++", "+-+", "+++" ],

View File

@ -22,6 +22,7 @@
},
"minGuardStrength" : 2000,
"defaultRoadType" : "pc", //pd - dirt, pg - gravel, pc - cobblestone
"secondaryRoadType": "pd",
"treasureValueLimit" : 20000, //generate pandora with gold for treasure above this limit
"prisons" :
{

View File

@ -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"
}
}
}

View File

@ -9,7 +9,8 @@
"code" : "dt",
"river" : "rm",
"battleFields" : ["dirt_birches", "dirt_hills", "dirt_pines"],
"terrainViewPatterns" : "dirt"
"terrainViewPatterns" : "dirt",
"horseSoundId" : 0
},
"sand" :
{
@ -22,7 +23,8 @@
"river" : "rm",
"battleFields" : ["sand_mesas"],
"transitionRequired" : true,
"terrainViewPatterns" : "sand"
"terrainViewPatterns" : "sand",
"horseSoundId" : 1
},
"grass" :
{
@ -33,7 +35,8 @@
"tiles" : "GRASTL",
"code" : "gr",
"river" : "rw",
"battleFields" : ["grass_hills", "grass_pines"]
"battleFields" : ["grass_hills", "grass_pines"],
"horseSoundId" : 2
},
"snow" :
{
@ -44,7 +47,8 @@
"tiles" : "SNOWTL",
"code" : "sn",
"river" : "ri",
"battleFields" : ["snow_mountains", "snow_trees"]
"battleFields" : ["snow_mountains", "snow_trees"],
"horseSoundId" : 3
},
"swamp" :
{
@ -55,7 +59,8 @@
"tiles" : "SWMPTL",
"code" : "sw",
"river" : "rw",
"battleFields" : ["swamp_trees"]
"battleFields" : ["swamp_trees"],
"horseSoundId" : 4
},
"rough" :
{
@ -66,7 +71,8 @@
"tiles" : "ROUGTL",
"code" : "rg",
"river" : "rm",
"battleFields" : ["rough"]
"battleFields" : ["rough"],
"horseSoundId" : 5
},
"subterra" :
{
@ -79,7 +85,8 @@
"code" : "sb",
"river" : "rw",
"battleFields" : ["subterranean"],
"rockTerrain" : "rock"
"rockTerrain" : "rock",
"horseSoundId" : 6
},
"lava" :
{
@ -91,7 +98,8 @@
"code" : "lv",
"river" : "rl",
"battleFields" : ["lava"],
"rockTerrain" : "rock"
"rockTerrain" : "rock",
"horseSoundId" : 7
},
"water" :
{
@ -104,7 +112,11 @@
"code" : "wt",
"battleFields" : ["ship"],
"transitionRequired" : true,
"terrainViewPatterns" : "water"
"terrainViewPatterns" : "water",
"horseSoundId" : 8,
"sounds": {
"ambient": ["LOOPOCEA"]
}
},
"rock" :
{
@ -117,6 +129,7 @@
"code" : "rc",
"battleFields" : ["rocklands"],
"transitionRequired" : true,
"terrainViewPatterns" : "rock"
"terrainViewPatterns" : "rock",
"horseSoundId" : 9
}
}

View File

@ -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)

View File

@ -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()

View File

@ -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())
{

View File

@ -35,6 +35,8 @@ class CDownloadManager : public QObject
};
QStringList encounteredErrors;
QString filename;
QNetworkAccessManager manager;

View File

@ -92,6 +92,11 @@ bool CModEntry::isUpdateable() const
return false;
}
bool CModEntry::isEssential() const
{
return getValue("storedLocaly").toBool();
}
bool CModEntry::isInstalled() const
{
return !localData.isEmpty();
@ -152,6 +157,10 @@ QVariantMap CModList::copyField(QVariantMap data, QString from, QString to)
return renamed;
}
void CModList::reloadRepositories()
{
}
void CModList::resetRepositories()
{
repositories.clear();
@ -176,7 +185,7 @@ void CModList::modChanged(QString modID)
{
}
static QVariant getValue(QVariantMap input, QString path)
static QVariant getValue(QVariant input, QString path)
{
if(path.size() > 1)
{
@ -184,7 +193,7 @@ static QVariant getValue(QVariantMap input, QString path)
QString remainder = "/" + path.section('/', 2, -1);
entryName.remove(0, 1);
return getValue(input.value(entryName).toMap(), remainder);
return getValue(input.toMap().value(entryName), remainder);
}
else
{
@ -208,11 +217,29 @@ CModEntry CModList::getMod(QString modname) const
}
else
{
if(conf.canConvert<QVariantMap>())
if(!conf.toMap().isEmpty())
{
settings = conf.toMap();
if(settings.value("active").isNull())
settings["active"] = true; // default
}
else
settings.insert("active", conf);
}
if(settings["active"].toBool())
{
QString rootPath = path.section('/', 0, 1);
if(path != rootPath)
{
conf = getValue(modSettings, rootPath);
const auto confMap = conf.toMap();
if(!conf.isNull() && !confMap["active"].isNull() && !confMap["active"].toBool())
{
settings = confMap;
}
}
}
for(auto entry : repositories)
{

View File

@ -49,6 +49,8 @@ public:
bool isUpdateable() const;
// installed
bool isInstalled() const;
// vcmi essential files
bool isEssential() const;
// see ModStatus enum
int getModStatus() const;
@ -74,6 +76,7 @@ class CModList
public:
virtual void resetRepositories();
virtual void reloadRepositories();
virtual void addRepository(QVariantMap data);
virtual void setLocalModList(QVariantMap data);
virtual void setModSettings(QVariant data);

View File

@ -160,6 +160,12 @@ QVariant CModListModel::headerData(int section, Qt::Orientation orientation, int
return QVariant();
}
void CModListModel::reloadRepositories()
{
beginResetModel();
endResetModel();
}
void CModListModel::resetRepositories()
{
beginResetModel();

View File

@ -61,6 +61,7 @@ public:
/// CModListContainer overrides
void resetRepositories() override;
void reloadRepositories() override;
void addRepository(QVariantMap data) override;
void modChanged(QString modID) override;

View File

@ -310,10 +310,10 @@ void CModListView::selectMod(const QModelIndex & index)
// Block buttons if action is not allowed at this time
// TODO: automate handling of some of these cases instead of forcing player
// to resolve all conflicts manually.
ui->disableButton->setEnabled(!hasDependentMods);
ui->disableButton->setEnabled(!hasDependentMods && !mod.isEssential());
ui->enableButton->setEnabled(!hasBlockingMods && !hasInvalidDeps);
ui->installButton->setEnabled(!hasInvalidDeps);
ui->uninstallButton->setEnabled(!hasDependentMods);
ui->uninstallButton->setEnabled(!hasDependentMods && !mod.isEssential());
ui->updateButton->setEnabled(!hasInvalidDeps && !hasDependentMods);
loadScreenshots();
@ -522,6 +522,9 @@ void CModListView::downloadFile(QString file, QString url, QString description)
connect(dlManager, SIGNAL(finished(QStringList,QStringList,QStringList)),
this, SLOT(downloadFinished(QStringList,QStringList,QStringList)));
connect(modModel, &CModListModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged);
QString progressBarFormat = "Downloading %s%. %p% (%v KB out of %m KB) finished";
@ -658,7 +661,7 @@ void CModListView::installMods(QStringList archives)
auto mod = modModel->getMod(modName);
if(mod.isInstalled() && !mod.getValue("keepDisabled").toBool())
{
if(manager->enableMod(modName))
if(mod.isDisabled() && manager->enableMod(modName))
{
for(QString child : modModel->getChildren(modName))
enableMod(child);

View File

@ -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 <modname> 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)
@ -74,6 +74,7 @@ void CModManager::loadMods()
CModHandler handler;
handler.loadMods();
auto installedMods = handler.getAllMods();
localMods.clear();
for(auto modname : installedMods)
{
@ -82,6 +83,13 @@ void CModManager::loadMods()
{
boost::filesystem::path name = *CResourceHandler::get()->getResourceName(resID);
auto mod = JsonUtils::JsonFromFile(pathToQString(name));
if(!name.is_absolute())
{
auto json = JsonUtils::toJson(mod);
json["storedLocaly"].Bool() = true;
mod = JsonUtils::toVariant(json);
}
localMods.insert(QString::fromUtf8(modname.c_str()).toLower(), mod);
}
}
@ -259,11 +267,20 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
return addError(modname, "Failed to extract mod data");
}
QVariantMap json = JsonUtils::JsonFromFile(destDir + modDirName + "/mod.json").toMap();
localMods.insert(modname, json);
modList->setLocalModList(localMods);
modList->modChanged(modname);
//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);
CResourceHandler::get("initial")->updateFilteredFiles([](const std::string &) { return true; });
loadMods();
modList->reloadRepositories();
return true;
}
@ -277,15 +294,13 @@ bool CModManager::doUninstallMod(QString modname)
if(!QDir(modDir).exists())
return addError(modname, "Data with this mod was not found");
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);
modList->modChanged(modname);
CResourceHandler::get("initial")->updateFilteredFiles([](const std::string &){ return true; });
loadMods();
modList->reloadRepositories();
return true;
}

View File

@ -11,6 +11,8 @@
#include "csettingsview_moc.h"
#include "ui_csettingsview_moc.h"
#include "../updatedialog_moc.h"
#include <QFileInfo>
#include <QGuiApplication>
@ -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);
}

View File

@ -63,6 +63,8 @@ private slots:
void on_comboBoxAutoSave_currentIndexChanged(int index);
void on_updatesButton_clicked();
private:
Ui::CSettingsView * ui;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>738</width>
<height>471</height>
<width>779</width>
<height>619</height>
</rect>
</property>
<property name="windowTitle">
@ -26,40 +26,15 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="3" column="6">
<widget class="QLabel" name="labelTempDir">
<property name="text">
<string>Log files directory</string>
</property>
</widget>
</item>
<item row="17" column="6">
<widget class="QComboBox" name="comboBoxAutoCheck">
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>Off</string>
</property>
</item>
<item>
<property name="text">
<string>On</string>
</property>
</item>
</widget>
</item>
<item row="0" column="6" colspan="4">
<widget class="QLabel" name="labelDataDirs">
<item row="8" column="1" colspan="4">
<widget class="QLabel" name="labelAIMovingOnTheMap">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Data Directories</string>
<string>AI on the map</string>
</property>
</widget>
</item>
@ -67,7 +42,6 @@
<widget class="QLabel" name="labelVideo">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@ -76,68 +50,6 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="labelResolution">
<property name="text">
<string>Resolution</string>
</property>
</widget>
</item>
<item row="3" column="9">
<widget class="QPushButton" name="openTempDir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
<item row="11" column="4">
<widget class="QComboBox" name="comboBoxNeutralAI">
<item>
<property name="text">
<string>BattleAI</string>
</property>
</item>
<item>
<property name="text">
<string>StupidAI</string>
</property>
</item>
</widget>
</item>
<item row="2" column="6">
<widget class="QLabel" name="labelUserDataDir">
<property name="text">
<string>User data directory</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QLabel" name="labelEnemyAI">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Enemy AI</string>
</property>
</widget>
</item>
<item row="17" column="1" colspan="4">
<widget class="QLabel" name="labelAutoCheck">
<property name="text">
<string>Check repositories on startup</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="labelDisplayIndex">
<property name="text">
<string>Display index</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="labelShowIntro">
<property name="text">
@ -145,91 +57,6 @@
</property>
</widget>
</item>
<item row="8" column="6" colspan="4">
<widget class="QLabel" name="labelGeneral">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>General</string>
</property>
</widget>
</item>
<item row="4" column="4">
<widget class="QComboBox" name="comboBoxDisplayIndex">
<item>
<property name="text">
<string>0</string>
</property>
</item>
</widget>
</item>
<item row="1" column="8">
<widget class="QPushButton" name="changeGameDataDir">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Change</string>
</property>
</widget>
</item>
<item row="9" column="4">
<widget class="QComboBox" name="comboBoxPlayerAI">
<item>
<property name="text">
<string>VCAI</string>
</property>
</item>
<item>
<property name="text">
<string>Nullkiller</string>
</property>
</item>
</widget>
</item>
<item row="3" column="7" colspan="2">
<widget class="QLineEdit" name="lineEditTempDir">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>/home/user/.vcmi</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="18" column="1" colspan="4">
<widget class="QLabel" name="labelRepositories">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Repositories</string>
</property>
</widget>
</item>
<item row="1" column="9">
<widget class="QPushButton" name="openGameDataDir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
<item row="9" column="7" colspan="3">
<widget class="QSpinBox" name="spinBoxNetworkPort">
<property name="minimum">
@ -243,86 +70,6 @@
</property>
</widget>
</item>
<item row="20" column="1" colspan="9">
<widget class="QPlainTextEdit" name="plainTextEditRepos">
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="plainText">
<string>http://downloads.vcmi.eu/Mods/repository.json</string>
</property>
</widget>
</item>
<item row="2" column="9">
<widget class="QPushButton" name="openUserDataDir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QLabel" name="labelFriendlyAI">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Friendly AI</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="labelPlayerAI">
<property name="text">
<string>Player AI</string>
</property>
</widget>
</item>
<item row="14" column="4">
<widget class="QComboBox" name="comboBoxEnemyAI">
<property name="editable">
<bool>false</bool>
</property>
<property name="currentText">
<string>BattleAI</string>
</property>
<item>
<property name="text">
<string>BattleAI</string>
</property>
</item>
<item>
<property name="text">
<string>StupidAI</string>
</property>
</item>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelFullScreen">
<property name="text">
<string>Fullscreen</string>
</property>
</widget>
</item>
<item row="7" column="1" colspan="4">
<spacer name="spacerSections">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>56</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="7">
<widget class="QLineEdit" name="lineEditGameDir">
<property name="minimumSize">
@ -336,10 +83,31 @@
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="QLabel" name="labelGameDir">
<item row="10" column="6">
<widget class="QLabel" name="labelEncoding">
<property name="text">
<string>Extra data directory</string>
<string>Heroes III character set</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="checkBoxFullScreen">
<property name="text">
<string>Real fullscreen mode</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="labelResolution">
<property name="text">
<string>Resolution</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelFullScreen">
<property name="text">
<string>Fullscreen</string>
</property>
</widget>
</item>
@ -379,16 +147,72 @@
</item>
</widget>
</item>
<item row="8" column="1" colspan="4">
<widget class="QLabel" name="labelAIMovingOnTheMap">
<item row="8" column="6" colspan="4">
<widget class="QLabel" name="labelGeneral">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>AI on the map</string>
<string>General</string>
</property>
</widget>
</item>
<item row="20" column="1" colspan="9">
<widget class="QPlainTextEdit" name="plainTextEditRepos">
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="plainText">
<string>http://downloads.vcmi.eu/Mods/repository.json</string>
</property>
</widget>
</item>
<item row="1" column="9">
<widget class="QPushButton" name="openGameDataDir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="labelPlayerAI">
<property name="text">
<string>Player AI</string>
</property>
</widget>
</item>
<item row="18" column="1" colspan="4">
<widget class="QLabel" name="labelRepositories">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Repositories</string>
</property>
</widget>
</item>
<item row="9" column="4">
<widget class="QComboBox" name="comboBoxPlayerAI">
<item>
<property name="text">
<string>VCAI</string>
</property>
</item>
<item>
<property name="text">
<string>Nullkiller</string>
</property>
</item>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="labelDisplayIndex">
<property name="text">
<string>Display index</string>
</property>
</widget>
</item>
@ -409,11 +233,67 @@
</item>
</widget>
</item>
<item row="11" column="4">
<widget class="QComboBox" name="comboBoxNeutralAI">
<item>
<property name="text">
<string>BattleAI</string>
</property>
</item>
<item>
<property name="text">
<string>StupidAI</string>
</property>
</item>
</widget>
</item>
<item row="13" column="4">
<widget class="QComboBox" name="comboBoxFriendlyAI">
<property name="editable">
<bool>false</bool>
</property>
<property name="currentText">
<string>BattleAI</string>
</property>
<item>
<property name="text">
<string>BattleAI</string>
</property>
</item>
<item>
<property name="text">
<string>StupidAI</string>
</property>
</item>
</widget>
</item>
<item row="11" column="5">
<spacer name="spacerColumns">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="9">
<widget class="QPushButton" name="openTempDir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
<item row="16" column="1" colspan="4">
<widget class="QLabel" name="LauncherSettings">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@ -422,10 +302,33 @@
</property>
</widget>
</item>
<item row="9" column="6">
<widget class="QLabel" name="labelNetworkPort">
<item row="1" column="6">
<widget class="QLabel" name="labelGameDir">
<property name="text">
<string>Network port</string>
<string>Extra data directory</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QLabel" name="labelEnemyAI">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Enemy AI</string>
</property>
</widget>
</item>
<item row="1" column="8">
<widget class="QPushButton" name="changeGameDataDir">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Change</string>
</property>
</widget>
</item>
@ -449,7 +352,7 @@
<string>1024x768</string>
</property>
</item>
<item>
<item>
<property name="text">
<string>1181x664</string>
</property>
@ -506,8 +409,24 @@
</item>
</widget>
</item>
<item row="15" column="1" colspan="4">
<spacer name="spacerRepos">
<item row="11" column="1">
<widget class="QLabel" name="labelNeutralAI">
<property name="text">
<string>Neutral AI</string>
</property>
</widget>
</item>
<item row="4" column="4">
<widget class="QComboBox" name="comboBoxDisplayIndex">
<item>
<property name="text">
<string>0</string>
</property>
</item>
</widget>
</item>
<item row="7" column="1" colspan="4">
<spacer name="spacerSections">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@ -516,14 +435,67 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<width>56</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item row="13" column="4">
<widget class="QComboBox" name="comboBoxFriendlyAI">
<item row="3" column="7" colspan="2">
<widget class="QLineEdit" name="lineEditTempDir">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>/home/user/.vcmi</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="17" column="6">
<widget class="QComboBox" name="comboBoxAutoCheck">
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>Off</string>
</property>
</item>
<item>
<property name="text">
<string>On</string>
</property>
</item>
</widget>
</item>
<item row="11" column="7">
<widget class="QComboBox" name="comboBoxAutoSave">
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>Off</string>
</property>
</item>
<item>
<property name="text">
<string>On</string>
</property>
</item>
</widget>
</item>
<item row="14" column="4">
<widget class="QComboBox" name="comboBoxEnemyAI">
<property name="editable">
<bool>false</bool>
</property>
@ -542,6 +514,96 @@
</item>
</widget>
</item>
<item row="15" column="1" colspan="4">
<spacer name="spacerRepos">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item row="10" column="1">
<widget class="QLabel" name="labelAIInTheBattlefield">
<property name="font">
<font>
<italic>true</italic>
<bold>true</bold>
<underline>false</underline>
</font>
</property>
<property name="text">
<string>AI in the battlefield</string>
</property>
</widget>
</item>
<item row="17" column="1" colspan="4">
<widget class="QLabel" name="labelAutoCheck">
<property name="text">
<string>Check repositories on startup</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QLabel" name="labelFriendlyAI">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Friendly AI</string>
</property>
</widget>
</item>
<item row="3" column="6">
<widget class="QLabel" name="labelTempDir">
<property name="text">
<string>Log files directory</string>
</property>
</widget>
</item>
<item row="0" column="6" colspan="4">
<widget class="QLabel" name="labelDataDirs">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Data Directories</string>
</property>
</widget>
</item>
<item row="2" column="6">
<widget class="QLabel" name="labelUserDataDir">
<property name="text">
<string>User data directory</string>
</property>
</widget>
</item>
<item row="2" column="9">
<widget class="QPushButton" name="openUserDataDir">
<property name="text">
<string>Open</string>
</property>
</widget>
</item>
<item row="9" column="6">
<widget class="QLabel" name="labelNetworkPort">
<property name="text">
<string>Network port</string>
</property>
</widget>
</item>
<item row="10" column="7" colspan="3">
<widget class="QComboBox" name="comboBoxEncoding">
<item>
@ -576,58 +638,6 @@
</item>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="labelAIInTheBattlefield">
<property name="font">
<font>
<weight>75</weight>
<italic>true</italic>
<bold>true</bold>
<underline>false</underline>
</font>
</property>
<property name="text">
<string>AI in the battlefield</string>
</property>
</widget>
</item>
<item row="11" column="5">
<spacer name="spacerColumns">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="10" column="6">
<widget class="QLabel" name="labelEncoding">
<property name="text">
<string>Heroes III character set</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="labelNeutralAI">
<property name="text">
<string>Neutral AI</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="checkBoxFullScreen">
<property name="text">
<string>Real fullscreen mode</string>
</property>
</widget>
</item>
<item row="11" column="6">
<widget class="QLabel" name="labelAutoSave">
<property name="text">
@ -635,21 +645,11 @@
</property>
</widget>
</item>
<item row="11" column="7">
<widget class="QComboBox" name="comboBoxAutoSave">
<property name="currentIndex">
<number>1</number>
<item row="17" column="7">
<widget class="QPushButton" name="updatesButton">
<property name="text">
<string>Check for updates</string>
</property>
<item>
<property name="text">
<string>Off</string>
</property>
</item>
<item>
<property name="text">
<string>On</string>
</property>
</item>
</widget>
</item>
</layout>

View File

@ -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 <QNetworkReply>
#include <QNetworkRequest>
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{"<a href=\"%1\">Download page</a>"}.arg(downloadLink));
}

View File

@ -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 <QDialog>
#include <QNetworkAccessManager>
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);
};

View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UpdateDialog</class>
<widget class="QDialog" name="UpdateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>371</width>
<height>247</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>371</width>
<height>247</height>
</size>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>12</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>12</number>
</property>
<item row="0" column="0" colspan="5">
<widget class="QLabel" name="versionLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>48</height>
</size>
</property>
<property name="font">
<font>
<pointsize>18</pointsize>
<bold>true</bold>
</font>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">QLabel {background-color: green; color : black} </string>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="text">
<string>You have latest version</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="5">
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string/>
</property>
</widget>
</item>
<item row="3" column="3" colspan="2">
<widget class="QPushButton" name="closeButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="checkOnStartup">
<property name="text">
<string>Check updates on startup</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="downloadLink">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -105,4 +105,4 @@ void BattleFieldInfo::registerIcons(const IconRegistar & cb) const
BattleField BattleFieldInfo::getId() const
{
return battlefield;
}
}

View File

@ -611,9 +611,6 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
if(!node["shots"].isNull())
cre->addBonus(node["shots"].Integer(), Bonus::SHOTS);
if(!node["spellPoints"].isNull())
cre->addBonus(node["spellPoints"].Integer(), Bonus::CASTS);
loadStackExperience(cre, node["stackExperience"]);
loadJsonAnimation(cre, node["graphics"]);
loadCreatureJson(cre, node);

View File

@ -438,7 +438,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
)
@ -446,6 +446,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)

View File

@ -620,10 +620,11 @@ void LayerTransitionRule::process(
destination.blocked = true;
}
}
else if(source.node->accessible != CGPathNode::ACCESSIBLE && destination.node->accessible != CGPathNode::ACCESSIBLE)
else if(destination.node->accessible != CGPathNode::ACCESSIBLE)
{
/// Hero that fly can only land on accessible tiles
destination.blocked = true;
if(!destination.isGuardianTile && destination.nodeObject)
destination.blocked = true;
}
break;

View File

@ -52,9 +52,9 @@ const TeamID TeamID::NO_TEAM = TeamID(255);
namespace GameConstants
{
#ifdef VCMI_NO_EXTRA_VERSION
const std::string VCMI_VERSION = std::string("VCMI 0.99");
const std::string VCMI_VERSION = std::string("VCMI 1.0.0");
#else
const std::string VCMI_VERSION = std::string("VCMI 0.99 ") + GIT_SHA1;
const std::string VCMI_VERSION = std::string("VCMI 1.0.0.") + GIT_SHA1;
#endif
}

View File

@ -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);
@ -957,7 +957,10 @@ void CBonusSystemNode::getAllBonusesRec(BonusList &out) const
auto updated = b->updater
? getUpdatedBonus(b, b->updater)
: b;
out.push_back(updated);
//do not add bonus with same pointer
if(!vstd::contains(out, updated))
out.push_back(updated);
}
}

View File

@ -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<PlayerColor, PlayerSettings>::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");
}
}

View File

@ -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 {};

View File

@ -154,6 +154,11 @@ Terrain::Manager::Manager()
}
terrainInfo[terr.first] = info;
if(!terrainId.count(terr.first))
{
terrainId[terr.first] = terrainVault.size();
terrainVault.push_back(terr.first);
}
}
}
}
@ -164,12 +169,18 @@ Terrain::Manager & Terrain::Manager::get()
return manager;
}
std::vector<Terrain> Terrain::Manager::terrains()
const std::vector<Terrain> & Terrain::Manager::terrains()
{
std::set<Terrain> _terrains; //have to use std::set to have ordered container. Othervise de-sync is possible
for(const auto & info : Terrain::Manager::get().terrainInfo)
_terrains.insert(info.first);
return std::vector<Terrain>(_terrains.begin(), _terrains.end());
return Terrain::Manager::get().terrainVault;
}
int Terrain::Manager::id(const Terrain & terrain)
{
if(terrain.name == "ANY") return -3;
if(terrain.name == "WRONG") return -2;
if(terrain.name == "BORDER") return -1;
return Terrain::Manager::get().terrainId.at(terrain);
}
const Terrain::Info & Terrain::Manager::getInfo(const Terrain & terrain)
@ -219,13 +230,7 @@ bool operator<(const Terrain & l, const Terrain & r)
int Terrain::id() const
{
if(name == "ANY") return -3;
if(name == "WRONG") return -2;
if(name == "BORDER") return -1;
auto _terrains = Terrain::Manager::terrains();
auto iter = std::find(_terrains.begin(), _terrains.end(), *this);
return iter - _terrains.begin();
return Terrain::Manager::id(*this);
}
bool Terrain::isLand() const

View File

@ -48,14 +48,17 @@ public:
class DLL_LINKAGE Manager
{
public:
static std::vector<Terrain> terrains();
static const std::vector<Terrain> & terrains();
static const Info & getInfo(const Terrain &);
static int id(const Terrain &);
private:
static Manager & get();
Manager();
std::unordered_map<std::string, Info> terrainInfo;
std::vector<Terrain> terrainVault;
std::map<Terrain, int> terrainId;
};
/*enum EETerrainType

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "CBinaryReader.h"
//FIXME:library file depends on SDL - make cause troubles
#include <SDL_endian.h>
#include "CInputStream.h"
#include "../CGeneralTextHandler.h"

View File

@ -16,9 +16,10 @@
namespace bfs = boost::filesystem;
CFilesystemLoader::CFilesystemLoader(std::string _mountPoint, bfs::path baseDirectory, size_t depth, bool initial):
baseDirectory(std::move(baseDirectory)),
mountPoint(std::move(_mountPoint)),
fileList(listFiles(mountPoint, depth, initial))
baseDirectory(std::move(baseDirectory)),
mountPoint(std::move(_mountPoint)),
fileList(listFiles(mountPoint, depth, initial)),
recursiveDepth(depth)
{
logGlobal->trace("File system loaded, %d files found", fileList.size());
}
@ -52,7 +53,7 @@ void CFilesystemLoader::updateFilteredFiles(std::function<bool(const std::string
{
if (filter(mountPoint))
{
fileList = listFiles(mountPoint, 1, false);
fileList = listFiles(mountPoint, recursiveDepth, false);
}
}

View File

@ -45,6 +45,8 @@ private:
boost::filesystem::path baseDirectory;
std::string mountPoint;
size_t recursiveDepth;
/** A list of files in the directory
* key = ResourceID for resource loader

View File

@ -450,9 +450,8 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
{
int txt_id;
if (cb->getHeroCount(h->tempOwner, false) < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)//GameConstants::MAX_HEROES_PER_PLAYER) //free hero slot
if (cb->getHeroCount(h->tempOwner, false) < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)//free hero slot
{
cb->changeObjPos(id,pos+int3(1,0,0),0);
//update hero parameters
SetMovePoints smp;
smp.hid = id;
@ -534,7 +533,7 @@ void CGHeroInstance::initObj(CRandomGenerator & rand)
{
auto customApp = VLC->objtypeh->getHandlerFor(ID, type->heroClass->getIndex())->getOverride(cb->gameState()->getTile(visitablePos())->terType, this);
if (customApp)
appearance = customApp.get();
appearance = customApp;
}
//copy active (probably growing) bonuses from hero prototype to hero object
@ -1413,7 +1412,9 @@ void CGHeroInstance::setHeroTypeName(const std::string & identifier)
if(rawId)
subID = rawId.get();
else
subID = 0; //fallback to Orrin, throw error instead?
{
throw std::runtime_error("Couldn't resolve hero identifier " + identifier);
}
}
}

View File

@ -1133,7 +1133,7 @@ void CGTownInstance::updateAppearance()
//FIXME: not the best way to do this
auto app = VLC->objtypeh->getHandlerFor(ID, subID)->getOverride(cb->gameState()->getTile(visitablePos())->terType, this);
if (app)
appearance = app.get();
appearance = app;
}
std::string CGTownInstance::nodeName() const

View File

@ -117,11 +117,11 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData(size_t dataSize)
for (size_t i=0; i<totalNumber; i++)
{
ObjectTemplate templ;
templ.readTxt(parser);
auto templ = new ObjectTemplate;
templ->readTxt(parser);
parser.endLine();
std::pair<si32, si32> key(templ.id.num, templ.subid);
legacyTemplates.insert(std::make_pair(key, templ));
std::pair<si32, si32> key(templ->id.num, templ->subid);
legacyTemplates.insert(std::make_pair(key, std::shared_ptr<const ObjectTemplate>(templ)));
}
std::vector<JsonNode> ret(dataSize);// create storage for 256 objects
@ -448,7 +448,6 @@ AObjectTypeHandler::AObjectTypeHandler():
AObjectTypeHandler::~AObjectTypeHandler()
{
}
void AObjectTypeHandler::setType(si32 type, si32 subtype)
@ -488,12 +487,12 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional<std::strin
entry.second.setType(JsonNode::JsonType::DATA_STRUCT);
JsonUtils::inherit(entry.second, base);
ObjectTemplate tmpl;
tmpl.id = Obj(type);
tmpl.subid = subtype;
tmpl.stringID = entry.first; // FIXME: create "fullID" - type.object.template?
tmpl.readJson(entry.second);
templates.push_back(tmpl);
auto tmpl = new ObjectTemplate;
tmpl->id = Obj(type);
tmpl->subid = subtype;
tmpl->stringID = entry.first; // FIXME: create "fullID" - type.object.template?
tmpl->readJson(entry.second);
templates.push_back(std::shared_ptr<const ObjectTemplate>(tmpl));
}
if (input["name"].isNull())
@ -523,7 +522,7 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional<std::strin
initTypeData(input);
}
bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const
bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, std::shared_ptr<const ObjectTemplate>) const
{
return false; // by default there are no overrides
}
@ -551,26 +550,28 @@ SObjectSounds AObjectTypeHandler::getSounds() const
return sounds;
}
void AObjectTypeHandler::addTemplate(const ObjectTemplate & templ)
void AObjectTypeHandler::addTemplate(std::shared_ptr<const ObjectTemplate> templ)
{
//Otherwise the template remains constant
auto ptr = const_cast<ObjectTemplate*>(templ.get());
ptr->id = Obj(type);
ptr->subid = subtype;
templates.push_back(templ);
templates.back().id = Obj(type);
templates.back().subid = subtype;
}
void AObjectTypeHandler::addTemplate(JsonNode config)
{
config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not null
JsonUtils::inherit(config, base);
ObjectTemplate tmpl;
tmpl.id = Obj(type);
tmpl.subid = subtype;
tmpl.stringID = ""; // TODO?
tmpl.readJson(config);
templates.push_back(tmpl);
auto tmpl = new ObjectTemplate;
tmpl->id = Obj(type);
tmpl->subid = subtype;
tmpl->stringID = ""; // TODO?
tmpl->readJson(config);
templates.emplace_back(tmpl);
}
std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates() const
std::vector<std::shared_ptr<const ObjectTemplate>> AObjectTypeHandler::getTemplates() const
{
return templates;
}
@ -580,14 +581,14 @@ BattleField AObjectTypeHandler::getBattlefield() const
return battlefield ? BattleField::fromString(battlefield.get()) : BattleField::NONE;
}
std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates(const Terrain & terrainType) const
std::vector<std::shared_ptr<const ObjectTemplate>>AObjectTypeHandler::getTemplates(const Terrain & terrainType) const
{
std::vector<ObjectTemplate> templates = getTemplates();
std::vector<ObjectTemplate> filtered;
std::vector<std::shared_ptr<const ObjectTemplate>> templates = getTemplates();
std::vector<std::shared_ptr<const ObjectTemplate>> filtered;
std::copy_if(templates.begin(), templates.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj)
std::copy_if(templates.begin(), templates.end(), std::back_inserter(filtered), [&](std::shared_ptr<const ObjectTemplate> obj)
{
return obj.canBePlacedAt(terrainType);
return obj->canBePlacedAt(terrainType);
});
// H3 defines allowed terrains in a weird way - artifacts, monsters and resources have faulty masks here
// Perhaps we should re-define faulty templates and remove this workaround (already done for resources)
@ -597,15 +598,15 @@ std::vector<ObjectTemplate> AObjectTypeHandler::getTemplates(const Terrain & ter
return filtered;
}
boost::optional<ObjectTemplate> AObjectTypeHandler::getOverride(const Terrain & terrainType, const CGObjectInstance * object) const
std::shared_ptr<const ObjectTemplate> AObjectTypeHandler::getOverride(const Terrain & terrainType, const CGObjectInstance * object) const
{
std::vector<ObjectTemplate> ret = getTemplates(terrainType);
for (auto & tmpl : ret)
std::vector<std::shared_ptr<const ObjectTemplate>> ret = getTemplates(terrainType);
for (const auto & tmpl: ret)
{
if (objectFilter(object, tmpl))
return tmpl;
}
return boost::optional<ObjectTemplate>();
return std::shared_ptr<const ObjectTemplate>(); //empty
}
const RandomMapInfo & AObjectTypeHandler::getRMGInfo()

View File

@ -144,7 +144,7 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
JsonNode base; /// describes base template
std::vector<ObjectTemplate> templates;
std::vector<std::shared_ptr<const ObjectTemplate>> templates;
SObjectSounds sounds;
@ -154,7 +154,7 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
protected:
void preInitObject(CGObjectInstance * obj) const;
virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
virtual bool objectFilter(const CGObjectInstance *, std::shared_ptr<const ObjectTemplate>) const;
/// initialization for classes that inherit this one
virtual void initTypeData(const JsonNode & input);
@ -177,17 +177,21 @@ public:
boost::optional<std::string> getCustomName() const;
SObjectSounds getSounds() const;
void addTemplate(const ObjectTemplate & templ);
void addTemplate(std::shared_ptr<const ObjectTemplate> templ);
void addTemplate(JsonNode config);
/// returns all templates matching parameters
std::vector<ObjectTemplate> getTemplates() const;
std::vector<ObjectTemplate> getTemplates(const Terrain & terrainType) const;
std::vector<std::shared_ptr<const ObjectTemplate>> getTemplates() const;
std::vector<std::shared_ptr<const ObjectTemplate>> getTemplates(const Terrain & terrainType) const;
/// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle)
/// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server)
std::shared_ptr<const ObjectTemplate> getOverride(const Terrain & terrainType, const CGObjectInstance * object) const;
BattleField getBattlefield() const;
/// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle)
/// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server)
boost::optional<ObjectTemplate> getOverride(const Terrain & terrainType, const CGObjectInstance * object) const;
const RandomMapInfo & getRMGInfo();
@ -199,14 +203,14 @@ public:
/// Creates object and set up core properties (like ID/subID). Object is NOT initialized
/// to allow creating objects before game start (e.g. map loading)
virtual CGObjectInstance * create(const ObjectTemplate & tmpl) const = 0;
virtual CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const = 0;
/// Configures object properties. Should be re-entrable, resetting state of the object if necessarily
/// This should set remaining properties, including randomized or depending on map
virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0;
/// Returns object configuration, if available. Otherwise returns NULL
virtual std::unique_ptr<IObjectInfo> getObjectInfo(const ObjectTemplate & tmpl) const = 0;
virtual std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const = 0;
template <typename Handler> void serialize(Handler &h, const int version)
{
@ -264,7 +268,7 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors;
/// container with H3 templates, used only during loading, no need to serialize it
typedef std::multimap<std::pair<si32, si32>, ObjectTemplate> TTemplatesContainer;
typedef std::multimap<std::pair<si32, si32>, std::shared_ptr<const ObjectTemplate>> TTemplatesContainer;
TTemplatesContainer legacyTemplates;
/// contains list of custom names for H3 objects (e.g. Dwellings), used to load H3 data

View File

@ -152,24 +152,24 @@ void CGObjectInstance::setOwner(PlayerColor ow)
}
int CGObjectInstance::getWidth() const//returns width of object graphic in tiles
{
return appearance.getWidth();
return appearance->getWidth();
}
int CGObjectInstance::getHeight() const //returns height of object graphic in tiles
{
return appearance.getHeight();
return appearance->getHeight();
}
bool CGObjectInstance::visitableAt(int x, int y) const //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles)
{
return appearance.isVisitableAt(pos.x - x, pos.y - y);
return appearance->isVisitableAt(pos.x - x, pos.y - y);
}
bool CGObjectInstance::blockingAt(int x, int y) const
{
return appearance.isBlockedAt(pos.x - x, pos.y - y);
return appearance->isBlockedAt(pos.x - x, pos.y - y);
}
bool CGObjectInstance::coveringAt(int x, int y) const
{
return appearance.isVisibleAt(pos.x - x, pos.y - y);
return appearance->isVisibleAt(pos.x - x, pos.y - y);
}
std::set<int3> CGObjectInstance::getBlockedPos() const
@ -179,7 +179,7 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
{
for(int h=0; h<getHeight(); ++h)
{
if(appearance.isBlockedAt(w, h))
if(appearance->isBlockedAt(w, h))
ret.insert(int3(pos.x - w, pos.y - h, pos.z));
}
}
@ -188,7 +188,7 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
std::set<int3> CGObjectInstance::getBlockedOffsets() const
{
return appearance.getBlockedOffsets();
return appearance->getBlockedOffsets();
}
void CGObjectInstance::setType(si32 ID, si32 subID)
@ -210,6 +210,11 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
appearance = handler->getTemplates(tile.terType)[0];
else
appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash
if (ID == Obj::HERO)
{
//adjust for the prison offset
pos = visitablePos();
}
cb->gameState()->map->addBlockVisTiles(this);
}
@ -259,7 +264,7 @@ int CGObjectInstance::getSightRadius() const
int3 CGObjectInstance::getVisitableOffset() const
{
return appearance.getVisitableOffset();
return appearance->getVisitableOffset();
}
void CGObjectInstance::giveDummyBonus(ObjectInstanceID heroID, ui8 duration) const
@ -345,7 +350,7 @@ int3 CGObjectInstance::visitablePos() const
bool CGObjectInstance::isVisitable() const
{
return appearance.isVisitable();
return appearance->isVisitable();
}
bool CGObjectInstance::passableFor(PlayerColor color) const
@ -370,7 +375,7 @@ void CGObjectInstance::serializeJson(JsonSerializeFormat & handler)
handler.serializeInt("y", pos.y);
handler.serializeInt("l", pos.z);
JsonNode app;
appearance.writeJson(app, false);
appearance->writeJson(app, false);
handler.serializeRaw("template",app, boost::none);
}

View File

@ -123,7 +123,7 @@ public:
/// Index of object in map's list of objects
ObjectInstanceID id;
/// Defines appearance of object on map (animation, blocked tiles, blit order, etc)
ObjectTemplate appearance;
std::shared_ptr<const ObjectTemplate> appearance;
/// If true hero can visit this object only from neighbouring tiles and can't stand on this object
bool blockVisit;

View File

@ -183,7 +183,7 @@ void CRewardableConstructor::initTypeData(const JsonNode & config)
objectInfo.init(config);
}
CGObjectInstance * CRewardableConstructor::create(const ObjectTemplate & tmpl) const
CGObjectInstance * CRewardableConstructor::create(std::shared_ptr<const ObjectTemplate> tmpl) const
{
auto ret = new CRewardableObject();
preInitObject(ret);
@ -196,7 +196,7 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomG
objectInfo.configureObject(dynamic_cast<CRewardableObject*>(object), rng);
}
std::unique_ptr<IObjectInfo> CRewardableConstructor::getObjectInfo(const ObjectTemplate & tmpl) const
std::unique_ptr<IObjectInfo> CRewardableConstructor::getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const
{
return std::unique_ptr<IObjectInfo>(new CRandomRewardObjectInfo(objectInfo));
}

View File

@ -49,9 +49,9 @@ class DLL_LINKAGE CRewardableConstructor : public AObjectTypeHandler
public:
CRewardableConstructor();
CGObjectInstance * create(const ObjectTemplate & tmpl) const override;
CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override;
void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override;
std::unique_ptr<IObjectInfo> getObjectInfo(const ObjectTemplate & tmpl) const override;
std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const override;
};

View File

@ -1122,7 +1122,7 @@ std::vector<int3> CGMagicSpring::getVisitableOffsets() const
for(int y = 0; y < 6; y++)
for (int x = 0; x < 8; x++) //starting from left
if (appearance.isVisitableAt(x, y))
if (appearance->isVisitableAt(x, y))
visitableTiles.push_back (int3(x, y , 0));
return visitableTiles;

View File

@ -56,7 +56,7 @@ void CTownInstanceConstructor::afterLoadFinalization()
}
}
bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, const ObjectTemplate & templ) const
bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, std::shared_ptr<const ObjectTemplate> templ) const
{
auto town = dynamic_cast<const CGTownInstance *>(object);
@ -65,10 +65,10 @@ bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, con
return town->hasBuilt(id);
};
return filters.count(templ.stringID) != 0 && filters.at(templ.stringID).test(buildTest);
return filters.count(templ->stringID) != 0 && filters.at(templ->stringID).test(buildTest);
}
CGObjectInstance * CTownInstanceConstructor::create(const ObjectTemplate & tmpl) const
CGObjectInstance * CTownInstanceConstructor::create(std::shared_ptr<const ObjectTemplate> tmpl) const
{
CGTownInstance * obj = createTyped(tmpl);
obj->town = faction->town;
@ -80,7 +80,7 @@ void CTownInstanceConstructor::configureObject(CGObjectInstance * object, CRando
{
auto templ = getOverride(object->cb->getTile(object->pos)->terType, object);
if(templ)
object->appearance = templ.get();
object->appearance = templ;
}
CHeroInstanceConstructor::CHeroInstanceConstructor()
@ -110,7 +110,7 @@ void CHeroInstanceConstructor::afterLoadFinalization()
}
}
bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, const ObjectTemplate & templ) const
bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std::shared_ptr<const ObjectTemplate> templ) const
{
auto hero = dynamic_cast<const CGHeroInstance *>(object);
@ -119,14 +119,14 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, con
return hero->type->ID == id;
};
if(filters.count(templ.stringID))
if(filters.count(templ->stringID))
{
return filters.at(templ.stringID).test(heroTest);
return filters.at(templ->stringID).test(heroTest);
}
return false;
}
CGObjectInstance * CHeroInstanceConstructor::create(const ObjectTemplate & tmpl) const
CGObjectInstance * CHeroInstanceConstructor::create(std::shared_ptr<const ObjectTemplate> tmpl) const
{
CGHeroInstance * obj = createTyped(tmpl);
obj->type = nullptr; //FIXME: set to valid value. somehow.
@ -167,12 +167,12 @@ void CDwellingInstanceConstructor::initTypeData(const JsonNode & input)
guards = input["guards"];
}
bool CDwellingInstanceConstructor::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const
bool CDwellingInstanceConstructor::objectFilter(const CGObjectInstance *, std::shared_ptr<const ObjectTemplate>) const
{
return false;
}
CGObjectInstance * CDwellingInstanceConstructor::create(const ObjectTemplate & tmpl) const
CGObjectInstance * CDwellingInstanceConstructor::create(std::shared_ptr<const ObjectTemplate> tmpl) const
{
CGDwelling * obj = createTyped(tmpl);
@ -272,7 +272,7 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input)
bankResetDuration = static_cast<si32>(input["resetDuration"].Float());
}
CGObjectInstance *CBankInstanceConstructor::create(const ObjectTemplate & tmpl) const
CGObjectInstance *CBankInstanceConstructor::create(std::shared_ptr<const ObjectTemplate> tmpl) const
{
return createTyped(tmpl);
}
@ -494,7 +494,7 @@ bool CBankInfo::givesSpells() const
}
std::unique_ptr<IObjectInfo> CBankInstanceConstructor::getObjectInfo(const ObjectTemplate & tmpl) const
std::unique_ptr<IObjectInfo> CBankInstanceConstructor::getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const
{
return std::unique_ptr<IObjectInfo>(new CBankInfo(levels));
}

View File

@ -26,17 +26,31 @@ template<class ObjectType>
class CDefaultObjectTypeHandler : public AObjectTypeHandler
{
protected:
ObjectType * createTyped(const ObjectTemplate & tmpl) const
ObjectType * createTyped(std::shared_ptr<const ObjectTemplate> tmpl /* = nullptr */) const
{
auto obj = new ObjectType();
preInitObject(obj);
obj->appearance = tmpl;
if (tmpl)
{
obj->appearance = tmpl;
}
else
{
auto templates = getTemplates();
if (templates.empty())
{
throw std::runtime_error("No handler for created object");
}
obj->appearance = templates.front(); //just any template for now, will be initialized later
}
return obj;
}
public:
CDefaultObjectTypeHandler() {}
CGObjectInstance * create(const ObjectTemplate & tmpl) const override
CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override
{
return createTyped(tmpl);
}
@ -45,7 +59,7 @@ public:
{
}
virtual std::unique_ptr<IObjectInfo> getObjectInfo(const ObjectTemplate & tmpl) const override
virtual std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const override
{
return nullptr;
}
@ -62,7 +76,7 @@ class CTownInstanceConstructor : public CDefaultObjectTypeHandler<CGTownInstance
{
JsonNode filtersJson;
protected:
bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const override;
bool objectFilter(const CGObjectInstance *, std::shared_ptr<const ObjectTemplate>) const override;
void initTypeData(const JsonNode & input) override;
public:
@ -70,7 +84,7 @@ public:
std::map<std::string, LogicalExpression<BuildingID>> filters;
CTownInstanceConstructor();
CGObjectInstance * create(const ObjectTemplate & tmpl) const override;
CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override;
void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override;
void afterLoadFinalization() override;
@ -87,7 +101,7 @@ class CHeroInstanceConstructor : public CDefaultObjectTypeHandler<CGHeroInstance
{
JsonNode filtersJson;
protected:
bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const override;
bool objectFilter(const CGObjectInstance *, std::shared_ptr<const ObjectTemplate>) const override;
void initTypeData(const JsonNode & input) override;
public:
@ -95,7 +109,7 @@ public:
std::map<std::string, LogicalExpression<HeroTypeID>> filters;
CHeroInstanceConstructor();
CGObjectInstance * create(const ObjectTemplate & tmpl) const override;
CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override;
void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override;
void afterLoadFinalization() override;
@ -115,13 +129,13 @@ class CDwellingInstanceConstructor : public CDefaultObjectTypeHandler<CGDwelling
JsonNode guards;
protected:
bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const override;
bool objectFilter(const CGObjectInstance *, std::shared_ptr<const ObjectTemplate> tmpl) const override;
void initTypeData(const JsonNode & input) override;
public:
CDwellingInstanceConstructor();
CGObjectInstance * create(const ObjectTemplate & tmpl) const override;
CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override;
void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override;
bool producesCreature(const CCreature * crea) const;
@ -207,10 +221,10 @@ public:
CBankInstanceConstructor();
CGObjectInstance * create(const ObjectTemplate & tmpl) const override;
CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override;
void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override;
std::unique_ptr<IObjectInfo> getObjectInfo(const ObjectTemplate & tmpl) const override;
std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const override;
template <typename Handler> void serialize(Handler &h, const int version)
{

View File

@ -53,7 +53,9 @@ ObjectTemplate::ObjectTemplate():
id(Obj::NO_OBJ),
subid(0),
printPriority(0),
stringID("")
width(0),
height(0),
visitable(false)
{
}
@ -65,7 +67,13 @@ ObjectTemplate::ObjectTemplate(const ObjectTemplate& other):
printPriority(other.printPriority),
animationFile(other.animationFile),
editorAnimationFile(other.editorAnimationFile),
stringID(other.stringID)
stringID(other.stringID),
width(other.width),
height(other.height),
visitable(other.visitable),
blockedOffsets(other.blockedOffsets),
blockMapOffset(other.blockMapOffset),
visitableOffset(other.visitableOffset)
{
//default copy constructor is failing with usedTiles this for unknown reason
@ -84,11 +92,18 @@ ObjectTemplate & ObjectTemplate::operator=(const ObjectTemplate & rhs)
animationFile = rhs.animationFile;
editorAnimationFile = rhs.editorAnimationFile;
stringID = rhs.stringID;
width = rhs.width;
height = rhs.height;
visitable = rhs.visitable;
blockedOffsets = rhs.blockedOffsets;
blockMapOffset = rhs.blockMapOffset;
visitableOffset = rhs.visitableOffset;
usedTiles.clear();
usedTiles.resize(rhs.usedTiles.size());
for(size_t i = 0; i < usedTiles.size(); i++)
std::copy(rhs.usedTiles[i].begin(), rhs.usedTiles[i].end(), std::back_inserter(usedTiles[i]));
return *this;
}
@ -121,9 +136,9 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser)
assert(visitStr.size() == 6*8);
setSize(8, 6);
for (size_t i=0; i<6; i++) // 6 rows
for(size_t i = 0; i < 6; i++) // 6 rows
{
for (size_t j=0; j<8; j++) // 8 columns
for(size_t j = 0; j < 8; j++) // 8 columns
{
auto & tile = usedTiles[i][j];
tile |= VISIBLE; // assume that all tiles are visible
@ -141,7 +156,7 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser)
std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain
assert(terrStr.size() == 9); // all terrains but rock
for (size_t i=0; i<9; i++)
for(size_t i = 0; i < 9; i++)
{
if (terrStr[8-i] == '1')
allowedTerrains.insert(Terrain::createTerrainTypeH3M(i));
@ -168,6 +183,7 @@ void ObjectTemplate::readTxt(CLegacyConfigParser & parser)
visitDir = (8|16|32|64|128);
readMsk();
recalculate();
}
void ObjectTemplate::readMsk()
@ -197,9 +213,9 @@ void ObjectTemplate::readMap(CBinaryReader & reader)
for(auto & byte : visitMask)
byte = reader.readUInt8();
for (size_t i=0; i<6; i++) // 6 rows
for(size_t i = 0; i < 6; i++) // 6 rows
{
for (size_t j=0; j<8; j++) // 8 columns
for(size_t j = 0; j < 8; j++) // 8 columns
{
auto & tile = usedTiles[5 - i][7 - j];
tile |= VISIBLE; // assume that all tiles are visible
@ -213,7 +229,7 @@ void ObjectTemplate::readMap(CBinaryReader & reader)
reader.readUInt16();
ui16 terrMask = reader.readUInt16();
for (size_t i=0; i<9; i++)
for(size_t i = 0; i < 9; i++)
{
if (((terrMask >> i) & 1 ) != 0)
allowedTerrains.insert(Terrain::createTerrainTypeH3M(i));
@ -243,6 +259,7 @@ void ObjectTemplate::readMap(CBinaryReader & reader)
readMsk();
afterLoadFixup();
recalculate();
}
void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
@ -288,16 +305,16 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
{
switch (ch)
{
case ' ' : return 0;
case '0' : return 0;
case 'V' : return VISIBLE;
case 'B' : return VISIBLE | BLOCKED;
case 'H' : return BLOCKED;
case 'A' : return VISIBLE | BLOCKED | VISITABLE;
case 'T' : return BLOCKED | VISITABLE;
default:
logGlobal->error("Unrecognized char %s in template mask", ch);
return 0;
case ' ' : return 0;
case '0' : return 0;
case 'V' : return VISIBLE;
case 'B' : return VISIBLE | BLOCKED;
case 'H' : return BLOCKED;
case 'A' : return VISIBLE | BLOCKED | VISITABLE;
case 'T' : return BLOCKED | VISITABLE;
default:
logGlobal->error("Unrecognized char %s in template mask", ch);
return 0;
}
};
@ -320,6 +337,7 @@ void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
printPriority = static_cast<si32>(node["zIndex"].Float());
afterLoadFixup();
recalculate();
}
void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const
@ -413,38 +431,42 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const
node["zIndex"].Float() = printPriority;
}
ui32 ObjectTemplate::getWidth() const
void ObjectTemplate::calculateWidth()
{
//TODO: Use 2D array
//TODO: better precalculate and store constant value
ui32 ret = 0;
for (const auto &row : usedTiles) //copy is expensive
for(const auto& row : usedTiles) //copy is expensive
{
ret = std::max<ui32>(ret, (ui32)row.size());
width = std::max<ui32>(width, (ui32)row.size());
}
return ret;
}
ui32 ObjectTemplate::getHeight() const
void ObjectTemplate::calculateHeight()
{
//TODO: Use 2D array
return static_cast<ui32>(usedTiles.size());
height = static_cast<ui32>(usedTiles.size());
}
void ObjectTemplate::setSize(ui32 width, ui32 height)
{
usedTiles.resize(height);
for (auto & line : usedTiles)
for(auto & line : usedTiles)
line.resize(width, 0);
}
bool ObjectTemplate::isVisitable() const
void ObjectTemplate::calculateVsitable()
{
for (auto & line : usedTiles)
for (auto & tile : line)
for(auto& line : usedTiles)
{
for(auto& tile : line)
{
if (tile & VISITABLE)
return true;
return false;
{
visitable = true;
return;
}
}
}
visitable = false;
}
bool ObjectTemplate::isWithin(si32 X, si32 Y) const
@ -469,31 +491,33 @@ bool ObjectTemplate::isBlockedAt(si32 X, si32 Y) const
return isWithin(X, Y) && usedTiles[Y][X] & BLOCKED;
}
std::set<int3> ObjectTemplate::getBlockedOffsets() const
void ObjectTemplate::calculateBlockedOffsets()
{
std::set<int3> ret;
blockedOffsets.clear();
for(int w = 0; w < (int)getWidth(); ++w)
{
for(int h = 0; h < (int)getHeight(); ++h)
{
if (isBlockedAt(w, h))
ret.insert(int3(-w, -h, 0));
blockedOffsets.insert(int3(-w, -h, 0));
}
}
return ret;
}
int3 ObjectTemplate::getBlockMapOffset() const
void ObjectTemplate::calculateBlockMapOffset()
{
for(int w = 0; w < (int)getWidth(); ++w)
{
for(int h = 0; h < (int)getHeight(); ++h)
{
if (isBlockedAt(w, h))
return int3(w, h, 0);
{
blockMapOffset = int3(w, h, 0);
return;
}
}
}
return int3(0,0,0);
blockMapOffset = int3(0, 0, 0);
}
bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const
@ -502,6 +526,7 @@ bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const
// 1 2 3
// 8 4
// 7 6 5
//TODO: static? cached?
int dirMap[3][3] =
{
{ visitDir & 1, visitDir & 2, visitDir & 4 },
@ -515,22 +540,20 @@ bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const
return dirMap[dy][dx] != 0;
}
int3 ObjectTemplate::getVisitableOffset() const
void ObjectTemplate::calculateVisitableOffset()
{
for(int y = 0; y < (int)getHeight(); y++)
for (int x = 0; x < (int)getWidth(); x++)
{
for(int x = 0; x < (int)getWidth(); x++)
{
if (isVisitableAt(x, y))
return int3(x,y,0);
//logGlobal->warn("Warning: getVisitableOffset called on non-visitable obj!");
return int3(0,0,0);
}
bool ObjectTemplate::isVisitableFromTop() const
{
return visitDir & 2;
//for some reason the line below is never called :?
//return isVisitableFrom (0, 1);
{
visitableOffset = int3(x, y, 0);
return;
}
}
}
visitableOffset = int3(0, 0, 0);
}
bool ObjectTemplate::canBePlacedAt(Terrain terrain) const
@ -538,3 +561,14 @@ bool ObjectTemplate::canBePlacedAt(Terrain terrain) const
return allowedTerrains.count(terrain) != 0;
}
void ObjectTemplate::recalculate()
{
calculateWidth();
calculateHeight();
calculateVsitable();
//The lines below use width and height
calculateBlockedOffsets();
calculateBlockMapOffset();
calculateVisitableOffset();
}

View File

@ -10,6 +10,7 @@
#pragma once
#include "../GameConstants.h"
#include "../int3.h"
class CBinaryReader;
class CLegacyConfigParser;
@ -50,11 +51,22 @@ public:
/// string ID, equals to def base name for h3m files (lower case, no extension) or specified in mod data
std::string stringID;
ui32 getWidth() const;
ui32 getHeight() const;
inline ui32 getWidth() const
{
return width;
};
inline ui32 getHeight() const
{
return height;
};
void setSize(ui32 width, ui32 height);
bool isVisitable() const;
inline bool isVisitable() const
{
return visitable;
};
// Checks object used tiles
// Position is relative to bottom-right corner of the object, can not be negative
@ -62,13 +74,29 @@ public:
bool isVisitableAt(si32 X, si32 Y) const;
bool isVisibleAt(si32 X, si32 Y) const;
bool isBlockedAt(si32 X, si32 Y) const;
std::set<int3> getBlockedOffsets() const;
int3 getBlockMapOffset() const; //bottom-right corner when firts blocked tile is
inline std::set<int3> getBlockedOffsets() const
{
return blockedOffsets;
};
inline int3 getBlockMapOffset() const
{
return blockMapOffset;
};
// Checks if object is visitable from certain direction. X and Y must be between -1..+1
bool isVisitableFrom(si8 X, si8 Y) const;
int3 getVisitableOffset() const;
bool isVisitableFromTop() const;
inline int3 getVisitableOffset() const
{
//logGlobal->warn("Warning: getVisitableOffset called on non-visitable obj!");
return visitableOffset;
};
inline bool isVisitableFromTop() const
{
return visitDir & 2;
};
// Checks if object can be placed on specific terrain
bool canBePlacedAt(Terrain terrain) const;
@ -87,6 +115,25 @@ public:
bool operator==(const ObjectTemplate& ot) const { return (id == ot.id && subid == ot.subid); }
private:
ui32 width;
ui32 height;
bool visitable;
std::set<int3> blockedOffsets;
int3 blockMapOffset;
int3 visitableOffset;
void recalculate();
void calculateWidth();
void calculateHeight();
void calculateVsitable();
void calculateBlockedOffsets();
void calculateBlockMapOffset();
void calculateVisitableOffset();
public:
template <typename Handler> void serialize(Handler &h, const int version)
{
h & usedTiles;
@ -98,6 +145,11 @@ public:
h & printPriority;
h & visitDir;
h & editorAnimationFile;
if (!h.saving)
{
recalculate();
}
}
};

View File

@ -146,7 +146,7 @@ public:
// FIXME: due to usage of JsonNode I can't make these methods const
const CGHeroInstance * strongestHero(PlayerColor owner);
std::vector<CGHeroInstance *> getLostCrossoverHeroes(); /// returns a list of crossover heroes which started the scenario, but didn't complete it
CCampaignScenario();
template <typename Handler> void serialize(Handler &h, const int formatVersion)

View File

@ -417,7 +417,7 @@ bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile *pom, const
if(!vstd::contains(pom->blockingObjects, obj)) //this visitable object is not blocking, ignore
continue;
if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y))
if (!obj->appearance->isVisitableFrom(src.x - dst.x, src.y - dst.y))
return false;
}
return true;

View File

@ -958,9 +958,9 @@ void CMapLoaderH3M::readDefInfo()
// Read custom defs
for(int idd = 0; idd < defAmount; ++idd)
{
ObjectTemplate tmpl;
tmpl.readMap(reader);
templates.push_back(tmpl);
auto tmpl = new ObjectTemplate;
tmpl->readMap(reader);
templates.push_back(std::shared_ptr<const ObjectTemplate>(tmpl));
}
}
@ -977,10 +977,10 @@ void CMapLoaderH3M::readObjects()
int defnum = reader.readUInt32();
ObjectInstanceID idToBeGiven = ObjectInstanceID((si32)map->objects.size());
ObjectTemplate & objTempl = templates.at(defnum);
std::shared_ptr<const ObjectTemplate> objTempl = templates.at(defnum);
reader.skip(5);
switch(objTempl.id)
switch(objTempl->id)
{
case Obj::EVENT:
{
@ -1212,15 +1212,15 @@ void CMapLoaderH3M::readObjects()
readMessageAndGuards(art->message, art);
if(objTempl.id == Obj::SPELL_SCROLL)
if(objTempl->id == Obj::SPELL_SCROLL)
{
spellID = reader.readUInt32();
artID = ArtifactID::SPELL_SCROLL;
}
else if(objTempl.id == Obj::ARTIFACT)
else if(objTempl->id == Obj::ARTIFACT)
{
//specific artifact
artID = objTempl.subid;
artID = objTempl->subid;
}
art->storedArtifact = CArtifactInstance::createArtifact(map, artID, spellID);
@ -1235,7 +1235,7 @@ void CMapLoaderH3M::readObjects()
readMessageAndGuards(res->message, res);
res->amount = reader.readUInt32();
if(objTempl.subid == Res::GOLD)
if(objTempl->subid == Res::GOLD)
{
// Gold is multiplied by 100.
res->amount *= 100;
@ -1246,7 +1246,7 @@ void CMapLoaderH3M::readObjects()
case Obj::RANDOM_TOWN:
case Obj::TOWN:
{
nobj = readTown(objTempl.subid);
nobj = readTown(objTempl->subid);
break;
}
case Obj::MINE:
@ -1347,7 +1347,7 @@ void CMapLoaderH3M::readObjects()
auto dwelling = new CGDwelling();
nobj = dwelling;
CSpecObjInfo * spec = nullptr;
switch(objTempl.id)
switch(objTempl->id)
{
case Obj::RANDOM_DWELLING:
spec = new CCreGenLeveledCastleInfo();
@ -1450,7 +1450,7 @@ void CMapLoaderH3M::readObjects()
}
case Obj::PYRAMID: //Pyramid of WoG object
{
if(objTempl.subid == 0)
if(objTempl->subid == 0)
{
nobj = new CBank();
}
@ -1470,13 +1470,13 @@ void CMapLoaderH3M::readObjects()
}
default: //any other object
{
if (VLC->objtypeh->knownSubObjects(objTempl.id).count(objTempl.subid))
if (VLC->objtypeh->knownSubObjects(objTempl->id).count(objTempl->subid))
{
nobj = VLC->objtypeh->getHandlerFor(objTempl.id, objTempl.subid)->create(objTempl);
nobj = VLC->objtypeh->getHandlerFor(objTempl->id, objTempl->subid)->create(objTempl);
}
else
{
logGlobal->warn("Unrecognized object: %d:%d at %s on map %s", objTempl.id.toEnum(), objTempl.subid, objPos.toString(), map->name);
logGlobal->warn("Unrecognized object: %d:%d at %s on map %s", objTempl->id.toEnum(), objTempl->subid, objPos.toString(), map->name);
nobj = new CGObjectInstance();
}
break;
@ -1484,11 +1484,11 @@ void CMapLoaderH3M::readObjects()
}
nobj->pos = objPos;
nobj->ID = objTempl.id;
nobj->ID = objTempl->id;
nobj->id = idToBeGiven;
if(nobj->ID != Obj::HERO && nobj->ID != Obj::HERO_PLACEHOLDER && nobj->ID != Obj::PRISON)
{
nobj->subID = objTempl.subid;
nobj->subID = objTempl->subid;
}
nobj->appearance = objTempl;
assert(idToBeGiven == ObjectInstanceID((si32)map->objects.size()));

View File

@ -245,7 +245,7 @@ private:
/** List of templates loaded from the map, used on later stage to create
* objects but not needed for fully functional CMap */
std::vector<ObjectTemplate> templates;
std::vector<std::shared_ptr<const ObjectTemplate>> templates;
/** ptr to the map object which gets filled by data from the buffer */
CMap * map;

View File

@ -1110,13 +1110,14 @@ void CMapLoaderJson::MapObjectLoader::construct()
auto handler = VLC->objtypeh->getHandlerFor(typeName, subtypeName);
ObjectTemplate appearance;
auto appearance = new ObjectTemplate;
appearance.id = Obj(handler->type);
appearance.subid = handler->subtype;
appearance.readJson(configuration["template"], false);
appearance->id = Obj(handler->type);
appearance->subid = handler->subtype;
appearance->readJson(configuration["template"], false);
instance = handler->create(appearance);
// Will be destroyed soon and replaced with shared template
instance = handler->create(std::shared_ptr<const ObjectTemplate>(appearance));
instance->id = ObjectInstanceID((si32)owner->map->objects.size());
instance->instanceName = jsonKey;

View File

@ -64,6 +64,7 @@ void CMapGenerator::loadConfig()
config.mineExtraResources = randomMapJson["mines"]["extraResourcesLimit"].Integer();
config.minGuardStrength = randomMapJson["minGuardStrength"].Integer();
config.defaultRoadType = randomMapJson["defaultRoadType"].String();
config.secondaryRoadType = randomMapJson["secondaryRoadType"].String();
config.treasureValueLimit = randomMapJson["treasureValueLimit"].Integer();
for(auto & i : randomMapJson["prisons"]["experience"].Vector())
config.prisonExperience.push_back(i.Integer());

View File

@ -38,6 +38,7 @@ public:
int mineExtraResources;
int minGuardStrength;
std::string defaultRoadType;
std::string secondaryRoadType;
int treasureValueLimit;
std::vector<int> prisonExperience, prisonValues;
std::vector<int> scrollValues;

View File

@ -204,8 +204,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
auto & managerOther = *otherZone->getModificator<ObjectManager>();
auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0);
auto gate1 = factory->create(ObjectTemplate());
auto gate2 = factory->create(ObjectTemplate());
auto gate1 = factory->create();
auto gate2 = factory->create();
rmg::Object rmgGate1(*gate1), rmgGate2(*gate2);
rmgGate1.setTemplate(zone.getTerrainType());
rmgGate2.setTemplate(otherZone->getTerrainType());
@ -249,8 +249,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
if(!success)
{
auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, generator.getNextMonlithIndex());
auto teleport1 = factory->create(ObjectTemplate());
auto teleport2 = factory->create(ObjectTemplate());
auto teleport1 = factory->create();
auto teleport2 = factory->create();
zone.getModificator<ObjectManager>()->addRequiredObject(teleport1, connection.getGuardStrength());
otherZone->getModificator<ObjectManager>()->addRequiredObject(teleport2, connection.getGuardStrength());

View File

@ -27,7 +27,7 @@
void ObjectManager::process()
{
zone.fractalize();
createRequiredObjects();
createRequiredObjects();
}
void ObjectManager::init()
@ -77,6 +77,21 @@ const rmg::Area & ObjectManager::getVisitableArea() const
return objectsVisitableArea;
}
std::vector<CGObjectInstance*> ObjectManager::getMines() const
{
std::vector<CGObjectInstance*> mines;
for (auto object : objects)
{
if (object->ID == Obj::MINE)
{
mines.push_back(object);
}
}
return mines;
}
int3 ObjectManager::findPlaceForObject(const rmg::Area & searchArea, rmg::Object & obj, std::function<float(const int3)> weightFunction, OptimizeType optimizer) const
{
float bestWeight = 0.f;
@ -220,6 +235,7 @@ bool ObjectManager::createRequiredObjects()
{
logGlobal->trace("Creating required objects");
RandomGeneratorUtil::randomShuffle(requiredObjects, generator.rand);
for(const auto & object : requiredObjects)
{
auto * obj = object.first;
@ -351,7 +367,7 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
objects.push_back(&instance->object());
if(auto * m = zone.getModificator<RoadPlacer>())
{
if(instance->object().appearance.isVisitableFromTop())
if(instance->object().appearance->isVisitableFromTop())
m->areaForRoads().add(instance->getVisitablePosition());
else
{
@ -433,7 +449,7 @@ CGCreature * ObjectManager::chooseGuard(si32 strength, bool zoneGuard)
auto guardFactory = VLC->objtypeh->getHandlerFor(Obj::MONSTER, creId);
auto guard = (CGCreature *) guardFactory->create(ObjectTemplate());
auto guard = (CGCreature *) guardFactory->create();
guard->character = CGCreature::HOSTILE;
auto hlp = new CStackInstance(creId, amount);
//will be set during initialization

View File

@ -63,6 +63,8 @@ public:
void createDistancesPriorityQueue();
const rmg::Area & getVisitableArea() const;
std::vector<CGObjectInstance*> getMines() const;
protected:
//content info

View File

@ -31,7 +31,7 @@ void ObstaclePlacer::process()
auto * riverManager = zone.getModificator<RiverPlacer>();
typedef std::vector<ObjectTemplate> ObstacleVector;
typedef std::vector<std::shared_ptr<const ObjectTemplate>> ObstacleVector;
//obstacleVector possibleObstacles;
std::map<int, ObstacleVector> obstaclesBySize;
@ -48,8 +48,8 @@ void ObstaclePlacer::process()
{
for(auto temp : handler->getTemplates())
{
if(temp.canBePlacedAt(zone.getTerrainType()) && temp.getBlockMapOffset().valid())
obstaclesBySize[temp.getBlockedOffsets().size()].push_back(temp);
if(temp->canBePlacedAt(zone.getTerrainType()) && temp->getBlockMapOffset().valid())
obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp);
}
}
}
@ -93,7 +93,7 @@ void ObstaclePlacer::process()
for(auto & temp : shuffledObstacles)
{
auto handler = VLC->objtypeh->getHandlerFor(temp.id, temp.subid);
auto handler = VLC->objtypeh->getHandlerFor(temp->id, temp->subid);
auto obj = handler->create(temp);
allObjects.emplace_back(*obj);
rmg::Object * rmgObject = &allObjects.back();

View File

@ -374,10 +374,10 @@ void RiverPlacer::connectRiver(const int3 & tile)
auto handler = VLC->objtypeh->getHandlerFor(RIVER_DELTA_ID, RIVER_DELTA_SUBTYPE);
assert(handler->isStaticObject());
std::vector<ObjectTemplate> tmplates;
std::vector<std::shared_ptr<const ObjectTemplate>> tmplates;
for(auto & temp : handler->getTemplates())
{
if(temp.canBePlacedAt(zone.getTerrainType()))
if(temp->canBePlacedAt(zone.getTerrainType()))
tmplates.push_back(temp);
}
@ -389,7 +389,7 @@ void RiverPlacer::connectRiver(const int3 & tile)
std::string targetTemplateName = RIVER_DELTA_TEMPLATE_NAME.at(river) + std::to_string(deltaOrientations[pos]) + ".def";
for(auto & templ : tmplates)
{
if(templ.animationFile == targetTemplateName)
if(templ->animationFile == targetTemplateName)
{
auto obj = handler->create(templ);
rmg::Object deltaObj(*obj, deltaPositions[pos]);

View File

@ -105,7 +105,7 @@ void Object::Instance::setPositionRaw(const int3 & position)
void Object::Instance::setTemplate(const Terrain & terrain)
{
if(dObject.appearance.id == Obj::NO_OBJ)
if(dObject.appearance->id == Obj::NO_OBJ)
{
auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain);
if(templates.empty())
@ -130,7 +130,7 @@ void Object::Instance::clear()
bool Object::Instance::isVisitableFrom(const int3 & position) const
{
auto relPosition = position - getPosition(true);
return dObject.appearance.isVisitableFrom(relPosition.x, relPosition.y);
return dObject.appearance->isVisitableFrom(relPosition.x, relPosition.y);
}
CGObjectInstance & Object::Instance::object()
@ -286,7 +286,7 @@ void Object::Instance::finalize(RmgMap & map)
throw rmgException(boost::to_string(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % dObject.id % dObject.pos.toString()));
}
if (dObject.appearance.id == Obj::NO_OBJ)
if (dObject.appearance->id == Obj::NO_OBJ)
{
auto terrainType = map.map().getTile(getPosition(true)).terType;
auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType);

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "RoadPlacer.h"
#include "ObjectManager.h"
#include "Functions.h"
#include "CMapGenerator.h"
#include "RmgMap.h"
@ -63,12 +64,13 @@ bool RoadPlacer::createRoad(const int3 & dst)
}
void RoadPlacer::drawRoads()
void RoadPlacer::drawRoads(bool secondary)
{
zone.areaPossible().subtract(roads);
zone.freePaths().unite(roads);
map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector());
map.getEditManager()->drawRoad(generator.getConfig().defaultRoadType, &generator.rand);
std::string roadType = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType);
map.getEditManager()->drawRoad(roadType, &generator.rand);
}
void RoadPlacer::addRoadNode(const int3& node)
@ -78,7 +80,22 @@ void RoadPlacer::addRoadNode(const int3& node)
void RoadPlacer::connectRoads()
{
if(roadNodes.empty())
bool noRoadNodes = false;
//Assumes objects are already placed
if (roadNodes.size() < 2)
{
//If there are no nodes, draw roads to mines
noRoadNodes = true;
if (auto* m = zone.getModificator<ObjectManager>())
{
for (auto object : m->getMines())
{
addRoadNode(object->visitablePos());
}
}
}
if(roadNodes.size() < 2)
return;
//take any tile from road nodes as destination zone for all other road nodes
@ -90,7 +107,8 @@ void RoadPlacer::connectRoads()
createRoad(node);
}
drawRoads();
//Draw dirt roads if there are only mines
drawRoads(noRoadNodes);
}
char RoadPlacer::dump(const int3 & t)

View File

@ -28,7 +28,7 @@ public:
protected:
bool createRoad(const int3 & dst);
void drawRoads(); //actually updates tiles
void drawRoads(bool secondary = false); //actually updates tiles
protected:
rmg::Tileset roadNodes; //tiles to be connected with roads

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