mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge beta -> develop
This commit is contained in:
		
							
								
								
									
										114
									
								
								.github/workflows/github.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										114
									
								
								.github/workflows/github.yml
									
									
									
									
										vendored
									
									
								
							| @@ -130,7 +130,7 @@ jobs: | ||||
|             preset: android-conan-ninja-release | ||||
|             conan_profile: android-64 | ||||
|             conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT | ||||
|             artifact_platform: aarch64-v8a | ||||
|             artifact_platform: arm64-v8a | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     defaults: | ||||
|       run: | ||||
| @@ -225,6 +225,7 @@ jobs: | ||||
|         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} | ||||
|         path: | | ||||
|           ${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }} | ||||
|            | ||||
|     - name: Android artifacts | ||||
|       if: ${{ startsWith(matrix.platform, 'android') }} | ||||
|       uses: actions/upload-artifact@v3 | ||||
| @@ -233,6 +234,14 @@ jobs: | ||||
|         path: | | ||||
|           ${{ env.ANDROID_APK_PATH }} | ||||
|  | ||||
|     - name: Android JNI ${{matrix.platform}} | ||||
|       if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }} | ||||
|       uses: actions/upload-artifact@v3 | ||||
|       with: | ||||
|         name: Android JNI ${{matrix.platform}} | ||||
|         path: | | ||||
|           ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs | ||||
|  | ||||
|     - name: Upload build | ||||
|       if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' }} | ||||
|       continue-on-error: true | ||||
| @@ -254,3 +263,106 @@ jobs: | ||||
|       env: | ||||
|         SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} | ||||
|       if: always() | ||||
|    | ||||
|   # copy-pasted mostly | ||||
|   bundle_release: | ||||
|      | ||||
|     needs: build | ||||
|     if: always() && github.ref == 'refs/heads/master' | ||||
|     strategy: | ||||
|       matrix: | ||||
|         include: | ||||
|           - platform: android-32 | ||||
|             os: ubuntu-22.04 | ||||
|             extension: aab | ||||
|             preset: android-conan-ninja-release | ||||
|             conan_profile: android-32 | ||||
|             conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT | ||||
|             artifact_platform: aab | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     defaults: | ||||
|       run: | ||||
|         shell: bash | ||||
|  | ||||
|     steps: | ||||
|     - uses: actions/checkout@v3 | ||||
|       with: | ||||
|         submodules: recursive | ||||
|  | ||||
|     - name: Dependencies | ||||
|       run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh' | ||||
|       env: | ||||
|         VCMI_BUILD_PLATFORM: x64 | ||||
|  | ||||
|     - uses: actions/setup-python@v4 | ||||
|       if: "${{ matrix.conan_profile != '' }}" | ||||
|       with: | ||||
|         python-version: '3.10' | ||||
|     - name: Conan setup | ||||
|       if: "${{ matrix.conan_profile != '' }}" | ||||
|       run: | | ||||
|         pip3 install 'conan<2.0' | ||||
|         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 }} \ | ||||
|           ${{ matrix.conan_options }} | ||||
|       env: | ||||
|         GENERATE_ONLY_BUILT_CONFIG: 1 | ||||
|  | ||||
|     - name: Git branch name | ||||
|       id: git-branch-name | ||||
|       uses: EthanSK/git-branch-name-action@v1 | ||||
|  | ||||
|     - name: Build Number | ||||
|       run: | | ||||
|         source '${{github.workspace}}/CI/get_package_name.sh' | ||||
|         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 | ||||
|         echo VCMI_PACKAGE_GITVERSION="$VCMI_PACKAGE_GITVERSION" >> $GITHUB_ENV | ||||
|       env: | ||||
|         PULL_REQUEST: ${{ github.event.pull_request.number }} | ||||
|  | ||||
|     - name: CMake Preset | ||||
|       run: | | ||||
|         cmake --preset ${{ matrix.preset }} | ||||
|  | ||||
|     - name: Build Preset | ||||
|       run: | | ||||
|         cmake --build --preset ${{matrix.preset}} | ||||
|  | ||||
|     - name: Download libs x64 | ||||
|       uses: actions/download-artifact@v3 | ||||
|       with: | ||||
|         name: Android JNI android-64 | ||||
|         path: ${{ github.workspace }}/android/vcmi-app/src/main/jniLibs/ | ||||
|   | ||||
|     - name: Create android package | ||||
|       run: | | ||||
|         cd android | ||||
|         ./gradlew bundleRelease --info | ||||
|         echo ANDROID_APK_PATH="$(ls ${{ github.workspace }}/android/vcmi-app/build/outputs/bundle/release/*.aab)" >> $GITHUB_ENV | ||||
|       env: | ||||
|         ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} | ||||
|         ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | ||||
|  | ||||
|     - name: Android artifacts | ||||
|       uses: actions/upload-artifact@v3 | ||||
|       with: | ||||
|         name: ${{ env.VCMI_PACKAGE_FILE_NAME }} | ||||
|         path: | | ||||
|           ${{ env.ANDROID_APK_PATH }} | ||||
|  | ||||
|     - uses: act10ns/slack@v1 | ||||
|       with: | ||||
|         status: ${{ job.status }} | ||||
|         channel: '#notifications' | ||||
|       env: | ||||
|         SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} | ||||
|       if: always() | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								CI/android/android-release.jks
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								CI/android/android-release.jks
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								CI/android/releaseSigning.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								CI/android/releaseSigning.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| STORE_FILE=android-release.jks | ||||
| KEY_ALIAS=vcmi | ||||
| @@ -251,6 +251,7 @@ if(MINGW OR MSVC) | ||||
| 		add_definitions(-D_CRT_SECURE_NO_WARNINGS) | ||||
| 		add_definitions(-D_SCL_SECURE_NO_WARNINGS) | ||||
|  | ||||
| 		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") | ||||
| 		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") | ||||
| 		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4250") # 4250: 'class1' : inherits 'class2::member' via dominance | ||||
| 		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # 4251: class 'xxx' needs to have dll-interface to be used by clients of class 'yyy' | ||||
|   | ||||
							
								
								
									
										22
									
								
								ChangeLog.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								ChangeLog.md
									
									
									
									
									
								
							| @@ -1,6 +1,26 @@ | ||||
| # 1.2.0 -> 1.3.0 | ||||
| # 1.2.1 -> 1.3.0 | ||||
| (unreleased) | ||||
|  | ||||
| # 1.2.0 -> 1.2.1 | ||||
|  | ||||
| ### GENERAL: | ||||
| * Implemented spell range overlay for Dimension Door and Scuttle Boat | ||||
| * Fixed movement cost penalty from terrain | ||||
| * Fixed empty Black Market on game start | ||||
| * Fixed bad morale happening after waiting | ||||
| * Fixed good morale happening after defeating last enemy unit | ||||
| * Fixed death animation of Efreeti killed by petrification attack | ||||
| * Fixed crash on leaving to main menu from battle in hotseat mode | ||||
| * Adventure map spells are no longer visible on units in battle | ||||
| * Attempt to cast spell with no valid targets in hotseat will show appropriate error message | ||||
| * RMG settings will now show all existing in game templates and not just those suitable for current settings | ||||
| * RMG settings (map size and two-level maps) that are not compatible with current template will be blocked | ||||
| * Fixed centering of scenario information window | ||||
| * Fixed crash on empty save game list after filtering | ||||
| * Fixed blocked progress in Launcher on language detection failure | ||||
| * Launcher will now correctly handle selection of Ddata directory in H3 install | ||||
| * Map editor will now correctly save message property for events and pandoras | ||||
|  | ||||
| # 1.1.1 -> 1.2.0 | ||||
|  | ||||
| ### GENERAL: | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 96 B | 
							
								
								
									
										
											BIN
										
									
								
								Mods/vcmi/Data/debug/spellRange.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Mods/vcmi/Data/debug/spellRange.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 96 B | 
| @@ -10,14 +10,16 @@ android { | ||||
| 		applicationId "is.xyz.vcmi" | ||||
| 		minSdk 19 | ||||
| 		targetSdk 31 | ||||
| 		versionCode 1103 | ||||
| 		versionName "1.1" | ||||
| 		versionCode 1200 | ||||
| 		versionName "1.2" | ||||
| 		setProperty("archivesBaseName", "vcmi") | ||||
| 	} | ||||
|  | ||||
| 	signingConfigs { | ||||
| 		releaseSigning | ||||
| 		LoadSigningConfig() | ||||
| 		dailySigning | ||||
| 		LoadSigningConfig("releaseSigning") | ||||
| 		LoadSigningConfig("dailySigning") | ||||
| 	} | ||||
|  | ||||
| 	buildTypes { | ||||
| @@ -46,6 +48,7 @@ android { | ||||
| 		daily { | ||||
| 			initWith release | ||||
| 			applicationIdSuffix '.daily' | ||||
| 			signingConfig signingConfigs.dailySigning | ||||
| 			manifestPlaceholders = [ | ||||
| 				applicationLabel: 'VCMI daily', | ||||
| 			] | ||||
| @@ -118,38 +121,48 @@ def ResolveGitInfo() { | ||||
| 		CommandOutput("git", ["describe", "--match=", "--always", "--abbrev=7"], ".") | ||||
| } | ||||
|  | ||||
| def SigningPropertiesPath(final basePath) { | ||||
| 	return file("${basePath}/signing.properties") | ||||
| def SigningPropertiesPath(final basePath, final signingConfigKey) { | ||||
| 	return file("${basePath}/${signingConfigKey}.properties") | ||||
| } | ||||
|  | ||||
| def SigningKeystorePath(final basePath, final keystoreFileName) { | ||||
| 	return file("${basePath}/${keystoreFileName}") | ||||
| } | ||||
|  | ||||
| def LoadSigningConfig() { | ||||
| def LoadSigningConfig(final signingConfigKey) { | ||||
| 	final def projectRoot = "${project.projectDir}/../../CI/android" | ||||
| 	final def props = new Properties() | ||||
| 	final def propFile = SigningPropertiesPath(projectRoot) | ||||
| 	final def propFile = SigningPropertiesPath(projectRoot, signingConfigKey) | ||||
| 	 | ||||
| 	def signingConfig = android.signingConfigs.getAt(signingConfigKey) | ||||
| 	 | ||||
| 	if (propFile.canRead()) { | ||||
| 		props.load(new FileInputStream(propFile)) | ||||
|  | ||||
| 		if (props != null | ||||
| 			&& props.containsKey('STORE_FILE') | ||||
| 			&& props.containsKey('STORE_PASSWORD') | ||||
| 			&& props.containsKey('KEY_ALIAS') | ||||
| 			&& props.containsKey('KEY_PASSWORD')) { | ||||
| 			&& props.containsKey('KEY_ALIAS')) { | ||||
|  | ||||
| 			android.signingConfigs.releaseSigning.storeFile = SigningKeystorePath(projectRoot, props['STORE_FILE']) | ||||
| 			android.signingConfigs.releaseSigning.storePassword = props['STORE_PASSWORD'] | ||||
| 			android.signingConfigs.releaseSigning.keyAlias = props['KEY_ALIAS'] | ||||
| 			android.signingConfigs.releaseSigning.keyPassword = props['KEY_PASSWORD'] | ||||
| 			signingConfig.storeFile = SigningKeystorePath(projectRoot, props['STORE_FILE']) | ||||
| 			signingConfig.storePassword = props['STORE_PASSWORD'] | ||||
| 			signingConfig.keyAlias = props['KEY_ALIAS'] | ||||
| 			 | ||||
| 			if(props.containsKey('STORE_PASSWORD')) | ||||
| 				signingConfig.storePassword = props['STORE_PASSWORD'] | ||||
| 			else | ||||
| 				signingConfig.storePassword = System.getenv("ANDROID_STORE_PASSWORD") | ||||
| 			 | ||||
| 			if(props.containsKey('KEY_PASSWORD')) | ||||
| 				signingConfig.keyPassword = props['KEY_PASSWORD'] | ||||
| 			else | ||||
| 				signingConfig.keyPassword = System.getenv("ANDROID_KEY_PASSWORD") | ||||
| 		} else { | ||||
| 			println("Some props from signing file are missing") | ||||
| 			android.buildTypes.release.signingConfig = null | ||||
| 			android.signingConfigs.putAt(signingConfigKey, null) | ||||
| 		} | ||||
| 	} else { | ||||
| 		println("file with signing properties is missing") | ||||
| 		android.buildTypes.release.signingConfig = null | ||||
| 		android.signingConfigs.putAt(signingConfigKey, null) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -374,6 +374,7 @@ void CClient::endGame() | ||||
| 	//threads cleanup has to be after gs cleanup and before battleints cleanup to stop tacticThread | ||||
| 	cleanThreads(); | ||||
|  | ||||
| 	CPlayerInterface::battleInt.reset(); | ||||
| 	playerint.clear(); | ||||
| 	battleints.clear(); | ||||
| 	battleCallbacks.clear(); | ||||
|   | ||||
| @@ -992,8 +992,11 @@ void CAdventureMapInterface::onTileLeftClicked(const int3 &mapPos) | ||||
| 	const CGObjectInstance *topBlocking = getActiveObject(mapPos); | ||||
|  | ||||
| 	int3 selPos = LOCPLINT->localState->getCurrentArmy()->getSightCenter(); | ||||
| 	if(spellBeingCasted && isInScreenRange(selPos, mapPos)) | ||||
| 	if(spellBeingCasted) | ||||
| 	{ | ||||
| 		if (!isInScreenRange(selPos, mapPos)) | ||||
| 			return; | ||||
|  | ||||
| 		const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos); | ||||
|  | ||||
| 		switch(spellBeingCasted->id) | ||||
| @@ -1099,11 +1102,15 @@ void CAdventureMapInterface::onTileHovered(const int3 &mapPos) | ||||
| 		switch(spellBeingCasted->id) | ||||
| 		{ | ||||
| 		case SpellID::SCUTTLE_BOAT: | ||||
| 			if(objAtTile && objAtTile->ID == Obj::BOAT) | ||||
| 			{ | ||||
| 			int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter(); | ||||
|  | ||||
| 			if(objAtTile && objAtTile->ID == Obj::BOAT && isInScreenRange(hpos, mapPos)) | ||||
| 				CCS->curh->set(Cursor::Map::SCUTTLE_BOAT); | ||||
| 			else | ||||
| 				CCS->curh->set(Cursor::Map::POINTER); | ||||
| 			return; | ||||
| 			} | ||||
| 		case SpellID::DIMENSION_DOOR: | ||||
| 			{ | ||||
| 				const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false); | ||||
| @@ -1264,6 +1271,8 @@ void CAdventureMapInterface::enterCastingMode(const CSpell * sp) | ||||
| { | ||||
| 	assert(sp->id == SpellID::SCUTTLE_BOAT || sp->id == SpellID::DIMENSION_DOOR); | ||||
| 	spellBeingCasted = sp; | ||||
| 	Settings config = settings.write["session"]["showSpellRange"]; | ||||
| 	config->Bool() = true; | ||||
|  | ||||
| 	deactivate(); | ||||
| 	terrain->activate(); | ||||
| @@ -1276,6 +1285,9 @@ void CAdventureMapInterface::exitCastingMode() | ||||
| 	spellBeingCasted = nullptr; | ||||
| 	terrain->deactivate(); | ||||
| 	activate(); | ||||
|  | ||||
| 	Settings config = settings.write["session"]["showSpellRange"]; | ||||
| 	config->Bool() = false; | ||||
| } | ||||
|  | ||||
| void CAdventureMapInterface::abortCastingMode() | ||||
|   | ||||
| @@ -137,7 +137,7 @@ bool StackActionAnimation::init() | ||||
|  | ||||
| StackActionAnimation::~StackActionAnimation() | ||||
| { | ||||
| 	if (stack->isFrozen()) | ||||
| 	if (stack->isFrozen() && currGroup != ECreatureAnimType::DEATH && currGroup != ECreatureAnimType::DEATH_RANGED) | ||||
| 		myAnim->setType(ECreatureAnimType::HOLDING); | ||||
| 	else | ||||
| 		myAnim->setType(nextGroup); | ||||
|   | ||||
| @@ -206,7 +206,7 @@ void CLobbyScreen::updateAfterStateChange() | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	if(curTab == tabRand && CSH->si->mapGenOptions) | ||||
| 	if(curTab && curTab == tabRand && CSH->si->mapGenOptions) | ||||
| 		tabRand->setMapGenOptions(CSH->si->mapGenOptions); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -31,8 +31,6 @@ CSavingScreen::CSavingScreen() | ||||
| { | ||||
| 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; | ||||
| 	center(pos); | ||||
| 	// TODO: we should really use std::shared_ptr for passing StartInfo around. | ||||
| 	localSi = new StartInfo(*LOCPLINT->cb->getStartInfo()); | ||||
| 	localMi = std::make_shared<CMapInfo>(); | ||||
| 	localMi->mapHeader = std::unique_ptr<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader())); | ||||
|  | ||||
| @@ -52,7 +50,9 @@ const CMapInfo * CSavingScreen::getMapInfo() | ||||
|  | ||||
| const StartInfo * CSavingScreen::getStartInfo() | ||||
| { | ||||
| 	return localSi; | ||||
| 	if (localMi) | ||||
| 		return localMi->scenarioOptionsOfSave; | ||||
| 	return LOCPLINT->cb->getStartInfo(); | ||||
| } | ||||
|  | ||||
| void CSavingScreen::changeSelection(std::shared_ptr<CMapInfo> to) | ||||
| @@ -61,7 +61,6 @@ void CSavingScreen::changeSelection(std::shared_ptr<CMapInfo> to) | ||||
| 		return; | ||||
|  | ||||
| 	localMi = to; | ||||
| 	localSi = localMi->scenarioOptionsOfSave; | ||||
| 	card->changeSelection(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,6 @@ class CSelectionBase; | ||||
| class CSavingScreen : public CSelectionBase | ||||
| { | ||||
| public: | ||||
| 	const StartInfo * localSi; | ||||
| 	std::shared_ptr<CMapInfo> localMi; | ||||
|  | ||||
| 	CSavingScreen(); | ||||
|   | ||||
| @@ -26,6 +26,10 @@ | ||||
| CScenarioInfoScreen::CScenarioInfoScreen() | ||||
| { | ||||
| 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; | ||||
| 	pos.w = 800; | ||||
| 	pos.h = 600; | ||||
| 	pos = center(); | ||||
|  | ||||
| 	localSi = new StartInfo(*LOCPLINT->cb->getStartInfo()); | ||||
| 	localMi = new CMapInfo(); | ||||
| 	localMi->mapHeader = std::unique_ptr<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader())); | ||||
|   | ||||
| @@ -242,9 +242,29 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts) | ||||
| 	} | ||||
| 	 | ||||
| 	if(auto w = widget<CToggleGroup>("groupMapSize")) | ||||
| 	{ | ||||
| 		for(auto toggle : w->buttons) | ||||
| 		{ | ||||
| 			if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second)) | ||||
| 			{ | ||||
| 				const auto & mapSizes = getPossibleMapSizes(); | ||||
| 				int3 size( mapSizes[toggle.first], mapSizes[toggle.first], 1 + mapGenOptions->getHasTwoLevels()); | ||||
|  | ||||
| 				bool sizeAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size); | ||||
| 				button->block(!sizeAllowed); | ||||
| 			} | ||||
| 		} | ||||
| 		w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth())); | ||||
| 	} | ||||
| 	if(auto w = widget<CToggleButton>("buttonTwoLevels")) | ||||
| 	{ | ||||
| 		int3 size( opts->getWidth(), opts->getWidth(), 2); | ||||
|  | ||||
| 		bool undergoundAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size); | ||||
|  | ||||
| 		w->setSelected(opts->getHasTwoLevels()); | ||||
| 		w->block(!undergoundAllowed); | ||||
| 	} | ||||
| 	if(auto w = widget<CToggleGroup>("groupMaxPlayers")) | ||||
| 	{ | ||||
| 		w->setSelected(opts->getPlayerCount()); | ||||
| @@ -408,7 +428,11 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size): | ||||
| 	REGISTER_BUILDER("templateListItem", &TemplatesDropBox::buildListItem); | ||||
| 	 | ||||
| 	curItems = VLC->tplh->getTemplates(); | ||||
| 	vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);}); | ||||
|  | ||||
| 	boost::range::sort(curItems, [](const CRmgTemplate * a, const CRmgTemplate * b){ | ||||
| 		return a->getName() < b->getName(); | ||||
| 	}); | ||||
|  | ||||
| 	curItems.insert(curItems.begin(), nullptr); //default template | ||||
| 	 | ||||
| 	const JsonNode config(ResourceID("config/widgets/randomMapTemplateWidget.json")); | ||||
|   | ||||
| @@ -86,4 +86,8 @@ public: | ||||
| 	virtual bool showGrid() const = 0; | ||||
| 	virtual bool showVisitable() const = 0; | ||||
| 	virtual bool showBlocked() const = 0; | ||||
|  | ||||
| 	/// if true, spell range for teleport / scuttle boat will be visible | ||||
| 	virtual bool showSpellRange(const int3 & position) const = 0; | ||||
|  | ||||
| }; | ||||
|   | ||||
| @@ -565,15 +565,16 @@ uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 & | ||||
| 	return 0xff-1; | ||||
| } | ||||
|  | ||||
| MapRendererDebug::MapRendererDebug() | ||||
| MapRendererOverlay::MapRendererOverlay() | ||||
| 	: imageGrid(IImage::createFromFile("debug/grid", EImageBlitMode::ALPHA)) | ||||
| 	, imageBlocked(IImage::createFromFile("debug/blocked", EImageBlitMode::ALPHA)) | ||||
| 	, imageVisitable(IImage::createFromFile("debug/visitable", EImageBlitMode::ALPHA)) | ||||
| 	, imageSpellRange(IImage::createFromFile("debug/spellRange", EImageBlitMode::ALPHA)) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| void MapRendererDebug::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates) | ||||
| void MapRendererOverlay::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates) | ||||
| { | ||||
| 	if(context.showGrid()) | ||||
| 		target.draw(imageGrid, Point(0,0)); | ||||
| @@ -599,9 +600,12 @@ void MapRendererDebug::renderTile(IMapRendererContext & context, Canvas & target | ||||
| 		if (context.showVisitable() && visitable) | ||||
| 			target.draw(imageVisitable, Point(0,0)); | ||||
| 	} | ||||
|  | ||||
| 	if (context.showSpellRange(coordinates)) | ||||
| 		target.draw(imageSpellRange, Point(0,0)); | ||||
| } | ||||
|  | ||||
| uint8_t MapRendererDebug::checksum(IMapRendererContext & context, const int3 & coordinates) | ||||
| uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 & coordinates) | ||||
| { | ||||
| 	uint8_t result = 0; | ||||
|  | ||||
| @@ -614,6 +618,9 @@ uint8_t MapRendererDebug::checksum(IMapRendererContext & context, const int3 & c | ||||
| 	if (context.showGrid()) | ||||
| 		result += 4; | ||||
|  | ||||
| 	if (context.showSpellRange(coordinates)) | ||||
| 		result += 8; | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| @@ -747,7 +754,7 @@ MapRenderer::TileChecksum MapRenderer::getTileChecksum(IMapRendererContext & con | ||||
| 			result[3] = rendererRoad.checksum(context, coordinates); | ||||
| 		result[4] = rendererObjects.checksum(context, coordinates); | ||||
| 		result[5] = rendererPath.checksum(context, coordinates); | ||||
| 		result[6] = rendererDebug.checksum(context, coordinates); | ||||
| 		result[6] = rendererOverlay.checksum(context, coordinates); | ||||
|  | ||||
| 		if(!context.isVisible(coordinates)) | ||||
| 			result[7] = rendererFow.checksum(context, coordinates); | ||||
| @@ -781,7 +788,7 @@ void MapRenderer::renderTile(IMapRendererContext & context, Canvas & target, con | ||||
|  | ||||
| 		rendererObjects.renderTile(context, target, coordinates); | ||||
| 		rendererPath.renderTile(context, target, coordinates); | ||||
| 		rendererDebug.renderTile(context, target, coordinates); | ||||
| 		rendererOverlay.renderTile(context, target, coordinates); | ||||
|  | ||||
| 		if(!context.isVisible(coordinates)) | ||||
| 			rendererFow.renderTile(context, target, coordinates); | ||||
|   | ||||
| @@ -129,21 +129,12 @@ public: | ||||
| 	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates); | ||||
| }; | ||||
|  | ||||
| class MapRendererDebug | ||||
| class MapRendererOverlay | ||||
| { | ||||
| 	std::shared_ptr<IImage> imageGrid; | ||||
| 	std::shared_ptr<IImage> imageVisitable; | ||||
| 	std::shared_ptr<IImage> imageBlocked; | ||||
| public: | ||||
| 	MapRendererDebug(); | ||||
|  | ||||
| 	uint8_t checksum(IMapRendererContext & context, const int3 & coordinates); | ||||
| 	void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates); | ||||
| }; | ||||
|  | ||||
| class MapRendererOverlay | ||||
| { | ||||
| 	std::unique_ptr<CAnimation> iconsStorage; | ||||
| 	std::shared_ptr<IImage> imageSpellRange; | ||||
| public: | ||||
| 	MapRendererOverlay(); | ||||
|  | ||||
| @@ -160,7 +151,7 @@ class MapRenderer | ||||
| 	MapRendererFow rendererFow; | ||||
| 	MapRendererObjects rendererObjects; | ||||
| 	MapRendererPath rendererPath; | ||||
| 	MapRendererDebug rendererDebug; | ||||
| 	MapRendererOverlay rendererOverlay; | ||||
|  | ||||
| public: | ||||
| 	using TileChecksum = std::array<uint8_t, 8>; | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
| #include "../../lib/CPathfinder.h" | ||||
| #include "../../lib/Point.h" | ||||
| #include "../../lib/mapObjects/CGHeroInstance.h" | ||||
| #include "../../lib/spells/CSpellHandler.h" | ||||
| #include "../../lib/mapping/CMap.h" | ||||
|  | ||||
| MapRendererBaseContext::MapRendererBaseContext(const MapRendererContextState & viewState) | ||||
| @@ -199,6 +200,11 @@ bool MapRendererBaseContext::showBlocked() const | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool MapRendererBaseContext::showSpellRange(const int3 & position) const | ||||
| { | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| MapRendererAdventureContext::MapRendererAdventureContext(const MapRendererContextState & viewState) | ||||
| 	: MapRendererBaseContext(viewState) | ||||
| { | ||||
| @@ -266,6 +272,19 @@ bool MapRendererAdventureContext::showBlocked() const | ||||
| 	return settingShowBlocked; | ||||
| } | ||||
|  | ||||
| bool MapRendererAdventureContext::showSpellRange(const int3 & position) const | ||||
| { | ||||
| 	if (!settingSpellRange) | ||||
| 		return false; | ||||
|  | ||||
| 	auto hero = LOCPLINT->localState->getCurrentHero(); | ||||
|  | ||||
| 	if (!hero) | ||||
| 		return false; | ||||
|  | ||||
| 	return !isInScreenRange(hero->getSightCenter(), position); | ||||
| } | ||||
|  | ||||
| MapRendererAdventureTransitionContext::MapRendererAdventureTransitionContext(const MapRendererContextState & viewState) | ||||
| 	: MapRendererAdventureContext(viewState) | ||||
| { | ||||
|   | ||||
| @@ -58,6 +58,7 @@ public: | ||||
| 	bool showGrid() const override; | ||||
| 	bool showVisitable() const override; | ||||
| 	bool showBlocked() const override; | ||||
| 	bool showSpellRange(const int3 & position) const override; | ||||
| }; | ||||
|  | ||||
| class MapRendererAdventureContext : public MapRendererBaseContext | ||||
| @@ -67,6 +68,7 @@ public: | ||||
| 	bool settingShowGrid = false; | ||||
| 	bool settingShowVisitable = false; | ||||
| 	bool settingShowBlocked = false; | ||||
| 	bool settingSpellRange= false; | ||||
| 	bool settingsAdventureObjectAnimation = true; | ||||
| 	bool settingsAdventureTerrainAnimation = true; | ||||
|  | ||||
| @@ -80,6 +82,8 @@ public: | ||||
| 	bool showGrid() const override; | ||||
| 	bool showVisitable() const override; | ||||
| 	bool showBlocked() const override; | ||||
|  | ||||
| 	bool showSpellRange(const int3 & position) const override; | ||||
| }; | ||||
|  | ||||
| class MapRendererAdventureTransitionContext : public MapRendererAdventureContext | ||||
|   | ||||
| @@ -160,6 +160,7 @@ void MapView::onViewMapActivated() | ||||
| PuzzleMapView::PuzzleMapView(const Point & offset, const Point & dimensions, const int3 & tileToCenter) | ||||
| 	: BasicMapView(offset, dimensions) | ||||
| { | ||||
| 	controller->setViewCenter(tileToCenter); | ||||
| 	controller->activatePuzzleMapContext(tileToCenter); | ||||
| 	controller->setViewCenter(tileToCenter); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -63,7 +63,7 @@ void MapViewController::setViewCenter(const Point & position, int level) | ||||
| 	model->setViewCenter(betterPosition); | ||||
| 	model->setLevel(std::clamp(level, 0, context->getMapSize().z)); | ||||
|  | ||||
| 	if(adventureInt) // may be called before adventureInt is initialized | ||||
| 	if(adventureInt && !puzzleMapContext) // may be called before adventureInt is initialized | ||||
| 		adventureInt->onMapViewMoved(model->getTilesTotalRect(), model->getLevel()); | ||||
| } | ||||
|  | ||||
| @@ -154,6 +154,7 @@ void MapViewController::updateBefore(uint32_t timeDelta) | ||||
| 		adventureContext->settingShowGrid = settings["gameTweaks"]["showGrid"].Bool(); | ||||
| 		adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool(); | ||||
| 		adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool(); | ||||
| 		adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -510,7 +510,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) | ||||
| 		auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero); | ||||
| 		if(spellCost > owner->myHero->mana) //insufficient mana | ||||
| 		{ | ||||
| 			owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana)); | ||||
| 			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana)); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| @@ -530,7 +530,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) | ||||
| 		if((combatSpell ^ inCombat) || inCastle) | ||||
| 		{ | ||||
| 			std::vector<std::shared_ptr<CComponent>> hlp(1, std::make_shared<CComponent>(CComponent::spell, mySpell->id, 0)); | ||||
| 			owner->myInt->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp); | ||||
| 			LOCPLINT->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp); | ||||
| 		} | ||||
| 		else if(combatSpell) | ||||
| 		{ | ||||
| @@ -545,9 +545,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) | ||||
| 				std::vector<std::string> texts; | ||||
| 				problem.getAll(texts); | ||||
| 				if(!texts.empty()) | ||||
| 					owner->myInt->showInfoDialog(texts.front()); | ||||
| 					LOCPLINT->showInfoDialog(texts.front()); | ||||
| 				else | ||||
| 					owner->myInt->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.spellUnknownProblem")); | ||||
| 					LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.spellUnknownProblem")); | ||||
| 			} | ||||
| 		} | ||||
| 		else //adventure spell | ||||
|   | ||||
							
								
								
									
										10
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							| @@ -3,13 +3,19 @@ vcmi (1.3.0) jammy; urgency=medium | ||||
|   * New upstream release | ||||
|  | ||||
|  -- Ivan Savenko <saven.ivan@gmail.com>  Sat, 01 Jul 2023 16:00:00 +0200 | ||||
|   | ||||
|  | ||||
| vcmi (1.2.1) jammy; urgency=medium | ||||
|  | ||||
|   * New upstream release | ||||
|  | ||||
|  -- Ivan Savenko <saven.ivan@gmail.com>  Fri, 28 Apr 2023 16:00:00 +0200 | ||||
|  | ||||
| vcmi (1.2.0) jammy; urgency=medium | ||||
|  | ||||
|   * New upstream release | ||||
|  | ||||
|  -- Ivan Savenko <saven.ivan@gmail.com>  Fri, 14 Apr 2023 16:00:00 +0200 | ||||
|   | ||||
|  | ||||
| vcmi (1.1.1) jammy; urgency=medium | ||||
|  | ||||
|   * New upstream release | ||||
|   | ||||
| @@ -52,6 +52,7 @@ | ||||
| 	</categories> | ||||
| 	<releases> | ||||
| 		<release version="1.3.0" date="2023-07-01" type="development" /> | ||||
| 		<release version="1.2.1" date="2023-04-28" /> | ||||
| 		<release version="1.2.0" date="2023-04-14" /> | ||||
| 		<release version="1.1.1" date="2023-02-03" /> | ||||
| 		<release version="1.1.0" date="2022-12-23" /> | ||||
|   | ||||
| @@ -204,6 +204,7 @@ void FirstLaunchView::heroesDataMissing() | ||||
| 	ui->labelDataCopy->setVisible(true); | ||||
|  | ||||
| 	ui->labelDataFound->setVisible(false); | ||||
| 	ui->pushButtonDataNext->setEnabled(false); | ||||
|  | ||||
| 	if(hasVCMIBuilderScript) | ||||
| 	{ | ||||
| @@ -232,6 +233,7 @@ void FirstLaunchView::heroesDataDetected() | ||||
| 	} | ||||
|  | ||||
| 	ui->labelDataFound->setVisible(true); | ||||
| 	ui->pushButtonDataNext->setEnabled(true); | ||||
|  | ||||
| 	heroesLanguageUpdate(); | ||||
| } | ||||
| @@ -261,7 +263,6 @@ void FirstLaunchView::heroesLanguageUpdate() | ||||
|  | ||||
| 	ui->labelDataFailure->setVisible(!success); | ||||
| 	ui->labelDataSuccess->setVisible(success); | ||||
| 	ui->pushButtonDataNext->setEnabled(success); | ||||
| } | ||||
|  | ||||
| void FirstLaunchView::forceHeroesLanguage(const QString & language) | ||||
| @@ -278,6 +279,18 @@ void FirstLaunchView::copyHeroesData() | ||||
| 	if(!sourceRoot.exists()) | ||||
| 		return; | ||||
|  | ||||
| 	if (sourceRoot.dirName().compare("data", Qt::CaseInsensitive) == 0) | ||||
| 	{ | ||||
| 		// We got Data folder. Possibly user selected "Data" folder of Heroes III install. Check whether valid data might exist 1 level above | ||||
|  | ||||
| 		QStringList dirData = sourceRoot.entryList({"data"}, QDir::Filter::Dirs); | ||||
| 		if (dirData.empty()) | ||||
| 		{ | ||||
| 			// This is "Data" folder without any "Data" folders inside. Try to check for data 1 level above | ||||
| 			sourceRoot.cdUp(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	QStringList dirData = sourceRoot.entryList({"data"}, QDir::Filter::Dirs); | ||||
| 	QStringList dirMaps = sourceRoot.entryList({"maps"}, QDir::Filter::Dirs); | ||||
| 	QStringList dirMp3 = sourceRoot.entryList({"mp3"}, QDir::Filter::Dirs); | ||||
|   | ||||
| @@ -96,14 +96,14 @@ JsonNode toJson(QVariant object) | ||||
| { | ||||
| 	JsonNode ret; | ||||
|  | ||||
| 	if(object.canConvert<QVariantMap>()) | ||||
| 		ret.Struct() = VariantToMap(object.toMap()); | ||||
| 	else if(object.canConvert<QVariantList>()) | ||||
| 		ret.Vector() = VariantToList(object.toList()); | ||||
| 	else if(object.userType() == QMetaType::QString) | ||||
| 	if(object.userType() == QMetaType::QString) | ||||
| 		ret.String() = object.toString().toUtf8().data(); | ||||
| 	else if(object.userType() == QMetaType::Bool) | ||||
| 		ret.Bool() = object.toBool(); | ||||
| 	else if(object.canConvert<QVariantMap>()) | ||||
| 		ret.Struct() = VariantToMap(object.toMap()); | ||||
| 	else if(object.canConvert<QVariantList>()) | ||||
| 		ret.Vector() = VariantToList(object.toList()); | ||||
| 	else if(object.canConvert<int>()) | ||||
| 		ret.Integer() = object.toInt(); | ||||
| 	else if(object.canConvert<double>()) | ||||
|   | ||||
| @@ -44,7 +44,7 @@ QString Languages::getHeroesDataLanguage() | ||||
| 	QString language = QString::fromStdString(settings["session"]["language"].String()); | ||||
| 	double deviation = settings["session"]["languageDeviation"].Float(); | ||||
|  | ||||
| 	if(deviation > 0.05) | ||||
| 	if(deviation > 0.1) | ||||
| 		return QString(); | ||||
| 	return language; | ||||
| } | ||||
|   | ||||
| @@ -128,7 +128,7 @@ std::vector<si32> CStack::activeSpells() const | ||||
| 	CSelector selector = Selector::sourceType()(Bonus::SPELL_EFFECT) | ||||
| 						 .And(CSelector([](const Bonus * b)->bool | ||||
| 	{ | ||||
| 		return b->type != Bonus::NONE; | ||||
| 		return b->type != Bonus::NONE && SpellID(b->sid).toSpell() && !SpellID(b->sid).toSpell()->isAdventure(); | ||||
| 	})); | ||||
|  | ||||
| 	TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str()); | ||||
|   | ||||
| @@ -76,7 +76,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f | ||||
| 			!ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->getIndex())) //no special movement bonus | ||||
| 	{ | ||||
|  | ||||
| 		ret = VLC->heroh->terrCosts[from.terType->getId()]; | ||||
| 		ret = VLC->terrainTypeHandler->getById(dest.terType->getId())->moveCost; | ||||
| 		ret -= ti->valOfBonuses(Bonus::ROUGH_TERRAIN_DISCOUNT); | ||||
| 		if(ret < GameConstants::BASE_MOVEMENT_COST) | ||||
| 			ret = GameConstants::BASE_MOVEMENT_COST; | ||||
|   | ||||
| @@ -279,10 +279,10 @@ void CGBlackMarket::newTurn(CRandomGenerator & rand) const | ||||
| { | ||||
| 	int resetPeriod = VLC->settings()->getInteger(EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD); | ||||
|  | ||||
| 	if(resetPeriod == 0) //check if feature changing OH3 behavior is enabled | ||||
| 		return; | ||||
| 	bool isFirstDay = cb->getDate(Date::DAY) == 1; | ||||
| 	bool regularResetTriggered = resetPeriod != 0 && ((cb->getDate(Date::DAY)-1) % resetPeriod) != 0; | ||||
|  | ||||
| 	if (((cb->getDate(Date::DAY)-1) % resetPeriod) != 0) | ||||
| 	if (!isFirstDay && !regularResetTriggered) | ||||
| 		return; | ||||
|  | ||||
| 	SetAvailableArtifacts saa; | ||||
|   | ||||
| @@ -676,12 +676,12 @@ void CGameHandler::endBattleConfirm(const BattleInfo * battleInfo) | ||||
| 					sendMoveArtifact(art, &ma); | ||||
| 				} | ||||
| 			} | ||||
| 			while(!finishingBattle->loserHero->artifactsInBackpack.empty()) | ||||
| 			for(int slotNumber = finishingBattle->loserHero->artifactsInBackpack.size() - 1; slotNumber >= 0; slotNumber--) | ||||
| 			{ | ||||
| 				//we assume that no big artifacts can be found | ||||
| 				MoveArtifact ma; | ||||
| 				ma.src = ArtifactLocation(finishingBattle->loserHero, | ||||
| 										  ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning | ||||
| 					ArtifactPosition(GameConstants::BACKPACK_START + slotNumber)); //backpack automatically shifts arts to beginning | ||||
| 				const CArtifactInstance * art =  ma.src.getArt(); | ||||
| 				if (art->artType->getId() != ArtifactID::GRAIL) //grail may not be won | ||||
| 				{ | ||||
| @@ -6517,9 +6517,9 @@ void CGameHandler::runBattle() | ||||
| 			if(!removeGhosts.changedStacks.empty()) | ||||
| 				sendAndApply(&removeGhosts); | ||||
|  | ||||
| 			//check for bad morale => freeze | ||||
| 			// check for bad morale => freeze | ||||
| 			int nextStackMorale = next->MoraleVal(); | ||||
| 			if (nextStackMorale < 0) | ||||
| 			if(!next->hadMorale && !next->waited() && nextStackMorale < 0) | ||||
| 			{ | ||||
| 				auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE); | ||||
| 				size_t diceIndex = std::min<size_t>(diceSize.size()-1, -nextStackMorale); | ||||
| @@ -6705,12 +6705,13 @@ void CGameHandler::runBattle() | ||||
| 				{ | ||||
| 					//check for good morale | ||||
| 					nextStackMorale = next->MoraleVal(); | ||||
| 					if(!next->hadMorale  //only one extra move per turn possible | ||||
| 					if( !battleResult.get() | ||||
| 						&& !next->hadMorale | ||||
| 						&& !next->defending | ||||
| 						&& !next->waited() | ||||
| 						&& !next->fear | ||||
| 						&&  next->alive() | ||||
| 						&&  nextStackMorale > 0) | ||||
| 						&& next->alive() | ||||
| 						&& nextStackMorale > 0) | ||||
| 					{ | ||||
| 						auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE); | ||||
| 						size_t diceIndex = std::min<size_t>(diceSize.size()-1, nextStackMorale); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user