浏览代码

Merge pull request #3972 from IvanSavenko/release_151

Release 1.5.1 preparation
Ivan Savenko 1 年之前
父节点
当前提交
1f1e693a5b

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

@@ -384,3 +384,34 @@ jobs:
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
         name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
         path: |
         path: |
           ${{ env.ANDROID_APK_PATH }}
           ${{ env.ANDROID_APK_PATH }}
+
+
+  deploy-src:
+    if: always() && github.ref == 'refs/heads/master'
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: bash
+    steps:
+        - uses: actions/checkout@v4
+          with:
+            submodules: recursive
+
+        - name: Build Number
+          run: |
+            source '${{github.workspace}}/CI/get_package_name.sh'
+            echo VCMI_PACKAGE_FILE_NAME="$VCMI_PACKAGE_FILE_NAME" >> $GITHUB_ENV
+            
+        - name: Create source code archive (including submodules)
+          run: |
+            git archive HEAD -o "release.tar" --worktree-attributes -v
+            git submodule update --init --recursive
+            git submodule --quiet foreach 'cd "$toplevel"; tar -rvf "release.tar" "$sm_path"'
+            gzip release.tar
+            
+        - name: Upload source code archive
+          uses: actions/upload-artifact@v4
+          with:
+            name: ${{ env.VCMI_PACKAGE_FILE_NAME }}
+            path: |
+              ./release.tar.gz

+ 51 - 2
ChangeLog.md

@@ -2,12 +2,61 @@
 
 
 ### Stability
 ### Stability
 * Fixed possible crash on accessing faction description
 * Fixed possible crash on accessing faction description
-* Fixed possible thread race on leaving to main menu
+* Fixed possible thread race on exit to main menu
 * Game will now show error message instead of silent crash on corrupted H3 data
 * Game will now show error message instead of silent crash on corrupted H3 data
 * Fixed possible crash on double-deletion of quest artifacts placed by RMG
 * Fixed possible crash on double-deletion of quest artifacts placed by RMG
+* Fixed crash on loading save made in version 1.4 with removed from map Quest Guards
+* Added workaround for crash on accessing Altar of Sacrifice on saves made in 1.4
+* Fixed possible crash on map restart request
+* Fixed crash on attempt to open scenario list with no save or map selected
+* Fixed crash on host resolving error when connecting to online lobby
+* If json file specified in mod.json is missing, vcmi will now only log an error instead of crashing
 
 
 ### Interface
 ### Interface
-* Fixed possible freeze on attempt to move hero when hero has non-zero movement points but not enough to reach first tile in path
+* Added retaliation damage and kills preview when hovering over units that can be attacked in melee during combat
+* Clicking on combat log would now open a window with full combat log history
+* Removed message length limit in text input fields, such as global lobby chat
+* Tapping on already active text input field will display on-screen keyboard on systems with one
+* Fixed possible freeze when trying to move hero if hero has non-zero movement points but not enough to reach first tile in path
+* Fixed selection of the wrong reward in dialogs such as the level-up window when double-clicking on it
+* Fixed launch of wrong map or save when double-clicking in scenario list screen
+* Right-clicking on a hero in a tavern will now select that hero as well, in line with H3
+* Fixed slow map list parsing when hota map format is enabled
+* MacOS and iOS can now use either Ctrl or Cmd key for all keyboard shortcuts
+* Small windows no longer dim the entire screen by default
+
+### Mechanics
+* Recruiting a hero will now immediately reveal the fog of war around him
+* When both a visiting hero and a garrisoned hero are in town, the garrisoned hero will visit town buildings first.
+
+### Multiplayer
+* Fixed in-game chat text not being visible after switching from achannel with a long history
+* Fixed lag when switching to channel with long history
+* Game now automatically scrolls in-game chat on new messages
+* Game will now only plays chat sound for active channel and for private channels
+* Cheats are now disabled by default in multiplayer
+* Game will now show status of cheats and battle replays on map start
+* It is possible to change cheats or battle replay on game loading
+* It is now possible to join rooms hosted by different hotfix versions, e.g. 1.5.1 can join 1.5.0 games
+* Fixed game rooms remaining visible in the lobby even after they have been closed
+* Fixed possible lag when there is a player in lobby with a very slow (or dying) connection
+* Game will show correctly if player has been invited into a room
+* Fixed overflow in invite window when there are more than 8 players in the lobby
+
+### Random Maps Generator
+* Generator will now prefer to place roads away from zone borders
+
+### AI
+* Fixed possible crash when Nullkiller AI tries to upgrade army
+* Nullkiller AI will now recruit new heroes if he left with 0 heroes
+* AI in combat now knows when an enemy unit has used all of its retaliations.
+
+### Map Editor
+* Fixed setting up hero types of heroes in Prisons placed in map editor
+* Fixed crash on setting up Seer Hut in map editor
+* Added text auto-completion hints for army widget
+* Editor will now automatically add .vmap extensions when saving map
+* Fixed text size in map validation window
 
 
 # 1.4.5 -> 1.5.0
 # 1.4.5 -> 1.5.0
 
 

+ 18 - 15
Mods/vcmi/config/vcmi/spanish.json

@@ -180,16 +180,19 @@
 	"vcmi.adventureMap.revisitObject.help" : "{Revisitar objeto}\n\nSi un héroe se encuentra actualmente en un objeto del mapa, puede volver a visitar la ubicación.",
 	"vcmi.adventureMap.revisitObject.help" : "{Revisitar objeto}\n\nSi un héroe se encuentra actualmente en un objeto del mapa, puede volver a visitar la ubicación.",
 
 
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Presiona cualquier tecla para empezar la batalla inmediatamente",
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Presiona cualquier tecla para empezar la batalla inmediatamente",
-	"vcmi.battleWindow.damageEstimation.melee" : "Atacar %CREATURE (%DAÑO).",
-	"vcmi.battleWindow.damageEstimation.meleeKills" : "Atacar %CREATURE (%DAÑO, %BAJAS).",
-	"vcmi.battleWindow.damageEstimation.ranged" : "Disparar a %CREATURE (%DISPAROS, %DAÑO).",
-	"vcmi.battleWindow.damageEstimation.rangedKills" : "Disparar a %CREATURE (%DISPAROS, %DAÑO, %BAJAS).",
+	"vcmi.battleWindow.damageEstimation.melee" : "Atacar %CREATURE (%DAMAGE).",
+	"vcmi.battleWindow.damageEstimation.meleeKills" : "Atacar %CREATURE (%DAMAGE, %KILLS).",
+	"vcmi.battleWindow.damageEstimation.ranged" : "Disparar a %CREATURE (%SHOTS, %DAMAGE).",
+	"vcmi.battleWindow.damageEstimation.rangedKills" : "Disparar a %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
 	"vcmi.battleWindow.damageEstimation.shots" : "%d disparos restantes",
 	"vcmi.battleWindow.damageEstimation.shots" : "%d disparos restantes",
 	"vcmi.battleWindow.damageEstimation.shots.1" : "%d disparo restante",
 	"vcmi.battleWindow.damageEstimation.shots.1" : "%d disparo restante",
 	"vcmi.battleWindow.damageEstimation.damage" : "%d daño",
 	"vcmi.battleWindow.damageEstimation.damage" : "%d daño",
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d daño",
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d daño",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d perecerán",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d perecerán",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerá",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d perecerá",
+	"vcmi.battleWindow.damageRetaliation.will" : "Contratacará ",
+	"vcmi.battleWindow.damageRetaliation.may" : "Puede contratacar ",
+	"vcmi.battleWindow.damageRetaliation.never" : "No contratacará.",
 	"vcmi.battleWindow.killed" : "Eliminados",
 	"vcmi.battleWindow.killed" : "Eliminados",
 	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s han sido eliminados por disparos certeros",
 	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s han sido eliminados por disparos certeros",
 	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s ha sido eliminado por un disparo certero",
 	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s ha sido eliminado por un disparo certero",
@@ -352,16 +355,16 @@
 
 
 	"core.bonus.ADDITIONAL_ATTACK.name": "Doble Ataque",
 	"core.bonus.ADDITIONAL_ATTACK.name": "Doble Ataque",
 	"core.bonus.ADDITIONAL_ATTACK.description": "Ataca dos veces",
 	"core.bonus.ADDITIONAL_ATTACK.description": "Ataca dos veces",
-	"core.bonus.ADDITIONAL_RETALIATION.name": "Contraataques adicionales",
-	"core.bonus.ADDITIONAL_RETALIATION.description": "Puede contraatacar ${val} veces adicionales",
+	"core.bonus.ADDITIONAL_RETALIATION.name": "Contrataques adicionales",
+	"core.bonus.ADDITIONAL_RETALIATION.description": "Puede contratacar ${val} veces adicionales",
 	"core.bonus.AIR_IMMUNITY.name": "Inmunidad al Aire",
 	"core.bonus.AIR_IMMUNITY.name": "Inmunidad al Aire",
 	"core.bonus.AIR_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de Aire",
 	"core.bonus.AIR_IMMUNITY.description": "Inmune a todos los hechizos de la escuela de Aire",
 	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Ataque en todas las direcciones",
 	"core.bonus.ATTACKS_ALL_ADJACENT.name": "Ataque en todas las direcciones",
 	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Ataca a todos los enemigos adyacentes",
 	"core.bonus.ATTACKS_ALL_ADJACENT.description": "Ataca a todos los enemigos adyacentes",
-	"core.bonus.BLOCKS_RETALIATION.name": "Sin contraataque",
-	"core.bonus.BLOCKS_RETALIATION.description": "El enemigo no puede contraatacar",
-	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Sin contraataque a distancia",
-	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "El enemigo no puede contraatacar disparando",
+	"core.bonus.BLOCKS_RETALIATION.name": "Evita contrataque",
+	"core.bonus.BLOCKS_RETALIATION.description": "El enemigo no puede contratacar",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Evita contrataque a distancia",
+	"core.bonus.BLOCKS_RANGED_RETALIATION.description": "El enemigo no puede contratacar disparando",
 	"core.bonus.CATAPULT.name": "Catapulta",
 	"core.bonus.CATAPULT.name": "Catapulta",
 	"core.bonus.CATAPULT.description": "Ataca a las paredes de asedio",
 	"core.bonus.CATAPULT.description": "Ataca a las paredes de asedio",
 	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reducir coste del conjuro (${val})",
 	"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reducir coste del conjuro (${val})",
@@ -397,7 +400,7 @@
 	"core.bonus.FIRE_SHIELD.name": "Escudo de Fuego (${val}%)",
 	"core.bonus.FIRE_SHIELD.name": "Escudo de Fuego (${val}%)",
 	"core.bonus.FIRE_SHIELD.description": "Refleja una parte del daño cuerpo a cuerpo",
 	"core.bonus.FIRE_SHIELD.description": "Refleja una parte del daño cuerpo a cuerpo",
 	"core.bonus.FIRST_STRIKE.name": "Primer Ataque",
 	"core.bonus.FIRST_STRIKE.name": "Primer Ataque",
-	"core.bonus.FIRST_STRIKE.description": "Esta criatura ataca primero en lugar de contraatacar",
+	"core.bonus.FIRST_STRIKE.description": "Esta criatura ataca primero en lugar de contratacar",
 	"core.bonus.FEAR.name": "Miedo",
 	"core.bonus.FEAR.name": "Miedo",
 	"core.bonus.FEAR.description": "Causa miedo a un grupo enemigo",
 	"core.bonus.FEAR.description": "Causa miedo a un grupo enemigo",
 	"core.bonus.FEARLESS.name": "Inmune al miedo",
 	"core.bonus.FEARLESS.name": "Inmune al miedo",
@@ -450,8 +453,8 @@
 	"core.bonus.NON_LIVING.description": "Inmunidad a muchos efectos",
 	"core.bonus.NON_LIVING.description": "Inmunidad a muchos efectos",
 	"core.bonus.RANDOM_SPELLCASTER.name": "Lanzador de hechizos aleatorio",
 	"core.bonus.RANDOM_SPELLCASTER.name": "Lanzador de hechizos aleatorio",
 	"core.bonus.RANDOM_SPELLCASTER.description": "Puede lanzar hechizos aleatorios",
 	"core.bonus.RANDOM_SPELLCASTER.description": "Puede lanzar hechizos aleatorios",
-	"core.bonus.RANGED_RETALIATION.name": "Contraataque a distancia",
-	"core.bonus.RANGED_RETALIATION.description": "Puede realizar un contraataque a distancia",
+	"core.bonus.RANGED_RETALIATION.name": "Contrataque a distancia",
+	"core.bonus.RANGED_RETALIATION.description": "Puede realizar un contrataque a distancia",
 	"core.bonus.RECEPTIVE.name": "Receptivo",
 	"core.bonus.RECEPTIVE.name": "Receptivo",
 	"core.bonus.RECEPTIVE.description": "No tiene inmunidad a hechizos amistosos",
 	"core.bonus.RECEPTIVE.description": "No tiene inmunidad a hechizos amistosos",
 	"core.bonus.REBIRTH.name": "Renacimiento (${val}%)",
 	"core.bonus.REBIRTH.name": "Renacimiento (${val}%)",
@@ -492,8 +495,8 @@
 	"core.bonus.TRANSMUTATION.description": "${val}% de probabilidad de transformar la unidad atacada en otro tipo",
 	"core.bonus.TRANSMUTATION.description": "${val}% de probabilidad de transformar la unidad atacada en otro tipo",
 	"core.bonus.UNDEAD.name": "No muerto",
 	"core.bonus.UNDEAD.name": "No muerto",
 	"core.bonus.UNDEAD.description": "La criatura es un no muerto",
 	"core.bonus.UNDEAD.description": "La criatura es un no muerto",
-	"core.bonus.UNLIMITED_RETALIATIONS.name": "Contraataques ilimitados",
-	"core.bonus.UNLIMITED_RETALIATIONS.description": "Puede realizar un número ilimitado de contraataques",
+	"core.bonus.UNLIMITED_RETALIATIONS.name": "Contrataques ilimitados",
+	"core.bonus.UNLIMITED_RETALIATIONS.description": "Puede realizar un número ilimitado de contrataques",
 	"core.bonus.WATER_IMMUNITY.name": "Inmunidad al agua",
 	"core.bonus.WATER_IMMUNITY.name": "Inmunidad al agua",
 	"core.bonus.WATER_IMMUNITY.description": "Inmune a todos los hechizos de la escuela del agua",
 	"core.bonus.WATER_IMMUNITY.description": "Inmune a todos los hechizos de la escuela del agua",
 	"core.bonus.WIDE_BREATH.name": "Aliento amplio",
 	"core.bonus.WIDE_BREATH.name": "Aliento amplio",

+ 1 - 1
android/vcmi-app/build.gradle

@@ -10,7 +10,7 @@ android {
 		applicationId "is.xyz.vcmi"
 		applicationId "is.xyz.vcmi"
 		minSdk 19
 		minSdk 19
 		targetSdk 33
 		targetSdk 33
-		versionCode 1511
+		versionCode 1513
 		versionName "1.5.1"
 		versionName "1.5.1"
 		setProperty("archivesBaseName", "vcmi")
 		setProperty("archivesBaseName", "vcmi")
 	}
 	}

+ 1 - 0
docs/Readme.md

@@ -1,5 +1,6 @@
 [![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
 [![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
+[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.1)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
 
 
 # VCMI Project
 # VCMI Project

+ 6 - 0
lib/gameState/CGameStateCampaign.cpp

@@ -392,9 +392,15 @@ void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelO
 
 
 		auto * donorHero = campaignHeroReplacement.hero;
 		auto * donorHero = campaignHeroReplacement.hero;
 
 
+		if (!donorHero)
+			throw std::runtime_error("Failed to find hero to take artifacts from! Scenario: " + gameState->map->name.toString());
+
 		for (auto const & artLocation : campaignHeroReplacement.transferrableArtifacts)
 		for (auto const & artLocation : campaignHeroReplacement.transferrableArtifacts)
 		{
 		{
 			auto * artifact = donorHero->getArt(artLocation);
 			auto * artifact = donorHero->getArt(artLocation);
+			if (!donorHero)
+				throw std::runtime_error("Failed to find artifacts to transfer to travelling hero! Scenario: " + gameState->map->name.toString());
+
 			artifact->removeFrom(*donorHero, artLocation);
 			artifact->removeFrom(*donorHero, artLocation);
 
 
 			if (receiver)
 			if (receiver)

+ 15 - 15
server/GlobalLobbyProcessor.cpp

@@ -40,26 +40,26 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr<INetworkConnecti
 	}
 	}
 	else
 	else
 	{
 	{
-		if (owner.getState() == EServerState::LOBBY)
+		for (auto const & proxy : proxyConnections)
 		{
 		{
-			for (auto const & proxy : proxyConnections)
-			{
-				if (proxy.second == connection)
-				{
-					JsonNode message;
-					message["type"].String() = "leaveGameRoom";
-					message["accountID"].String() = proxy.first;
+			if (proxy.second != connection)
+				continue;
 
 
-					sendMessage(controlConnection, message);
+			if (owner.getState() == EServerState::LOBBY)
+			{
+				JsonNode message;
+				message["type"].String() = "leaveGameRoom";
+				message["accountID"].String() = proxy.first;
 
 
-					proxyConnections.erase(proxy.first);
-					break;
-				}
+				sendMessage(controlConnection, message);
 			}
 			}
-		}
 
 
-		// player disconnected
-		owner.onDisconnected(connection, errorMessage);
+			proxyConnections.erase(proxy.first);
+
+			// player disconnected
+			owner.onDisconnected(connection, errorMessage);
+			return;
+		}
 	}
 	}
 }
 }