浏览代码

Merge branch 'vcmi/master' into 'vcmi/develop'

Ivan Savenko 1 年之前
父节点
当前提交
2ddb41e654
共有 50 个文件被更改,包括 500 次插入294 次删除
  1. 19 0
      ChangeLog.md
  2. 9 0
      Global.h
  3. 1 1
      android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java
  4. 1 1
      client/Client.h
  5. 16 1
      client/eventsSDL/InputSourceKeyboard.cpp
  6. 1 0
      client/eventsSDL/InputSourceKeyboard.h
  7. 3 0
      client/widgets/MiscWidgets.cpp
  8. 3 0
      client/windows/CCastleInterface.cpp
  9. 5 0
      client/windows/CCreatureWindow.cpp
  10. 2 0
      client/windows/CCreatureWindow.h
  11. 3 0
      client/windows/CKingdomInterface.cpp
  12. 4 0
      config/schemas/creature.json
  13. 4 0
      config/schemas/faction.json
  14. 5 0
      config/shortcutsConfig.json
  15. 1 1
      debian/changelog
  16. 1 4
      docs/Readme.md
  17. 5 4
      docs/maintainers/Release_Process.md
  18. 1 0
      docs/modders/Entities_Format/Biome_Format.md
  19. 4 0
      docs/modders/Entities_Format/Creature_Format.md
  20. 3 0
      docs/modders/Entities_Format/Faction_Format.md
  21. 12 1
      launcher/eu.vcmi.VCMI.metainfo.xml
  22. 64 60
      launcher/translation/polish.ts
  23. 11 0
      lib/CCreatureHandler.cpp
  24. 3 0
      lib/CCreatureHandler.h
  25. 11 0
      lib/CTownHandler.cpp
  26. 2 0
      lib/CTownHandler.h
  27. 1 1
      lib/IGameCallback.h
  28. 9 0
      lib/constants/Enumerations.h
  29. 2 2
      lib/mapObjects/MiscObjects.cpp
  30. 56 4
      lib/mapObjects/ObstacleSetHandler.cpp
  31. 15 2
      lib/mapObjects/ObstacleSetHandler.h
  32. 7 24
      lib/network/NetworkConnection.cpp
  33. 2 3
      lib/network/NetworkConnection.h
  34. 2 2
      lib/network/NetworkHandler.cpp
  35. 1 1
      lib/network/NetworkServer.cpp
  36. 1 1
      lib/networkPacks/NetPacksBase.h
  37. 1 0
      lib/rmg/CMapGenerator.cpp
  38. 29 8
      lib/rmg/PenroseTiling.cpp
  39. 3 0
      lib/rmg/PenroseTiling.h
  40. 5 1
      lib/rmg/modificators/ObstaclePlacer.cpp
  41. 2 2
      lib/spells/AdventureSpellMechanics.cpp
  42. 1 1
      lib/spells/ISpellMechanics.h
  43. 154 154
      mapeditor/translation/polish.ts
  44. 8 8
      server/CGameHandler.cpp
  45. 1 1
      server/CGameHandler.h
  46. 1 1
      server/NetPacksServer.cpp
  47. 2 2
      server/ServerSpellCastEnvironment.cpp
  48. 1 1
      server/ServerSpellCastEnvironment.h
  49. 1 1
      test/game/CGameStateTest.cpp
  50. 1 1
      test/mock/mock_IGameCallback.h

+ 19 - 0
ChangeLog.md

@@ -2,8 +2,11 @@
 
 ### General
 * Added Portuguese (Brazilian) translation
+* Added basic support for game controllers
 * Added option to disable cheats in game
 * Game will no longer run vcmiserver as a separate process on desktop systems
+* Game will no longer show server error messages in game chat in release builds
+* Implemented switchable artifact sets from HD Mod
 
 ### Stability
 * Fixed possible crash in Altar of Sacrifice
@@ -19,12 +22,19 @@
 * Fixed crash on moving through whirlpool when hero has no troops other than commander
 * Fixed possible freeze when moving hero over events that give enough experience to cause a level-up
 * Fixed possible crash on movement of double-wide creatures next to gates during siege
+* Fixed possible hanging app on attempt to close game during loading
 
 ### Multiplayer
+* Game map will no longer be locked during turn of other human players, allowing to change hero paths or inspect towns or heroes
+* Game will now correctly block most of player actions outside of their turn
 * Implemented new lobby, available in game with persistent accounts and chat
 * Removed old lobby previously available in launcher
 * Fixed potential crash that could occur if two players act at the very same time
 * Game will no longer pause due to network lag after every tile when instant movement speed is selected in multiplayer
+* Game will now show "X player's turn" dialog on new turn in online multiplayer
+* Fixed loading of turn timers state from saved games
+* Simultaneous turns will now break when players are 1 turn away from each other instead of 2 turns
+* Implemented rolling and banning of towns before game start
 
 ### Interface
 * Implemented configurable keyboard shortcuts, editable in file config/shortcutsConfig.json
@@ -35,6 +45,7 @@
 * It is no longer possible to start single scenario by pressing "Enter", in line with H3 and to prevent interference with game chat
 * Empty treasure banks will no longer ask for confirmation when entering
 * Game will now save last used difficulty settings
+* Opening random map tab or scenario selection tab in pregame will no longer reset starting towns or heroes unless different map was selected
 * Town Portal dialog will now show town icons
 * Town Portal dialog will now show town info on right click
 * Town Portal dialog will center on town on clicking it
@@ -55,10 +66,12 @@
 * Fixed translation of some bonuses using incorrect language
 * Added option to use 'nearest' rounding mode for UI scaling
 * Fixed various minor bugs in trade window interface
+* Removed animation of spawning of every single new monster on new month
 * Game will now correctly reset artifact drag-and-drop cursor if player opens another dialog on top of hero window
 * If player has no valid saves, game will pick "NEWGAME" as proposed save name instead of empty field
 * Fixed incorrect visitation sounds of Crypt, Shipwreck and Abandoned Ship
 * Fixed double sound playback on capturing mines
+* Recruitment costs that consist from 3 different resources should now fit recruitment window UI better
 
 ### Campaigns
 * Game will now correctly track who defeated the hero or wandering monsters for related quests and victory conditions
@@ -88,6 +101,7 @@
 ### Mechanics
 * It is no longer possible to learn spells from Pandora or events if hero can not learn them
 * Fixed behavior of 'Dimension Door' spell to be in line with H3:SoD
+* Fixed behavior of 'Fly' spell to be in line with H3:SoD
 * If it is not possible to cast 'Dimension Door', game will show message immediately on picking spell in spellbook
 * Added options to configure 'Dimension Door' spell to be in line with HotA
 * Casting 'Town Portal' while in boat will now show correct message box instead of server error
@@ -117,6 +131,8 @@
 * Decreased minimal density of obstacles on undergound level of the map
 * Density of objects should now closely resemble H3 RMG
 * Generator will now avoid routing road under guarded objects whenever possible
+* Generator will now avoid placing guards near roads
+* Generator will not place a guard near the road if it's stronger than 1/3 of max guard strength for this zone
 * Interactive objects will now appear on top of static objects
 * Windmill will now appear on top of all other objects
 
@@ -140,6 +156,8 @@
 
 ### AI
 * Fixed possible crash on updating NKAI pathfinding data
+* Fixed possible crash if hero has only commander left without army
+* Fixed possible crash on attempt to build tavern in a town
 * Fixed counting mana usage cost of Fly spell
 * Added estimation of value of Pyramid and Cyclops Stockpile
 * Reduced memory usage and improved performance of AI pathfinding
@@ -153,6 +171,7 @@
 
 ### Modding
 * Added new game setting that allows inviting heroes to taverns
+* It is now possible to add creature or faction description accessible via right-click of the icon
 * Fixed reversed Overlord and Warlock classes mapping
 * Added 'selectAll' mode for configurable objects which grants all potential rewards 
 * It is now possible to use most of json5 format in vcmi json files

+ 9 - 0
Global.h

@@ -523,6 +523,15 @@ namespace vstd
 		}
 	}
 
+	// Removes all duplicate elements from the vector
+	template<typename T>
+	void unique(std::vector<T> &vec)
+	{
+		std::sort(vec.begin(), vec.end());
+		auto newEnd = std::unique(vec.begin(), vec.end());
+		vec.erase(newEnd, vec.end());
+	}
+
 	template<typename InputRange, typename OutputIterator, typename Predicate>
 	OutputIterator copy_if(const InputRange &input, OutputIterator result, Predicate pred)
 	{

+ 1 - 1
android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java

@@ -50,7 +50,7 @@ import eu.vcmi.vcmi.util.ServerResponse;
 public class ActivityMods extends ActivityWithToolbar
 {
     private static final boolean ENABLE_REPO_DOWNLOADING = true;
-    private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.4.json";
+    private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.5.json";
     private VCMIModsRepo mRepo;
     private RecyclerView mRecycler;
 

+ 1 - 1
client/Client.h

@@ -199,7 +199,7 @@ public:
 	void startBattlePrimary(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank = false, const CGTownInstance * town = nullptr) override {}; //use hero=nullptr for no hero
 	void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
 	void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
-	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
+	bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
 	void giveHeroBonus(GiveBonus * bonus) override {};
 	void setMovePoints(SetMovePoints * smp) override {};
 	void setMovePoints(ObjectInstanceID hid, int val, bool absolute) override {};

+ 16 - 1
client/eventsSDL/InputSourceKeyboard.cpp

@@ -32,9 +32,24 @@ InputSourceKeyboard::InputSourceKeyboard()
 #endif
 }
 
+std::string InputSourceKeyboard::getKeyNameWithModifiers(const std::string & keyName) const
+{
+	std::string result;
+
+	if (isKeyboardCtrlDown())
+		result += "Ctrl+";
+	if (isKeyboardAltDown())
+		result += "Alt+";
+	if (isKeyboardShiftDown())
+		result += "Shift+";
+	result += keyName;
+
+	return result;
+}
+
 void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
 {
-	std::string keyName = SDL_GetKeyName(key.keysym.sym);
+	std::string keyName = getKeyNameWithModifiers(SDL_GetKeyName(key.keysym.sym));
 	logGlobal->trace("keyboard: key '%s' pressed", keyName);
 	assert(key.state == SDL_PRESSED);
 

+ 1 - 0
client/eventsSDL/InputSourceKeyboard.h

@@ -15,6 +15,7 @@ struct SDL_KeyboardEvent;
 /// Class that handles keyboard input from SDL events
 class InputSourceKeyboard
 {
+	std::string getKeyNameWithModifiers(const std::string & keyName) const;
 public:
 	InputSourceKeyboard();
 

+ 3 - 0
client/widgets/MiscWidgets.cpp

@@ -466,6 +466,9 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
 			if(town->id == townId && town->builtBuildings.count(BuildingID::TAVERN))
 				LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 		}
+	}, [&]{
+		if(!town->town->faction->getDescriptionTranslated().empty())
+			CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
 	});
 	fastMarket = std::make_shared<LRClickableArea>(Rect(143, 31, 30, 34), []()
 	{

+ 3 - 0
client/windows/CCastleInterface.cpp

@@ -1351,6 +1351,9 @@ void CCastleInterface::recreateIcons()
 	{
 		if(town->builtBuildings.count(BuildingID::TAVERN))
 			LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
+	}, [&]{
+		if(!town->town->faction->getDescriptionTranslated().empty())
+			CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
 	});
 
 	creainfo.clear();

+ 5 - 0
client/windows/CCreatureWindow.cpp

@@ -22,6 +22,7 @@
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/ObjectLists.h"
+#include "../windows/InfoWindows.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 
@@ -517,6 +518,10 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 	};
 
 	animation = std::make_shared<CCreaturePic>(5, 41, parent->info->creature);
+	animationArea = std::make_shared<LRClickableArea>(Rect(5, 41, 100, 130), nullptr, [&]{
+		if(!parent->info->creature->getDescriptionTranslated().empty())
+			CRClickPopup::createAndPush(parent->info->creature->getDescriptionTranslated());
+	});
 
 	if(parent->info->stackNode != nullptr && parent->info->commander == nullptr)
 	{

+ 2 - 0
client/windows/CCreatureWindow.h

@@ -29,6 +29,7 @@ class CButton;
 class CMultiLineLabel;
 class CListBox;
 class CCommanderArtPlace;
+class LRClickableArea;
 
 class CCommanderSkillIcon : public LRClickableAreaWText //TODO: maybe bring commander skill button initialization logic inside?
 {
@@ -132,6 +133,7 @@ class CStackWindow : public CWindowObject
 		};
 
 		std::shared_ptr<CCreaturePic> animation;
+		std::shared_ptr<LRClickableArea> animationArea;
 		std::shared_ptr<CLabel> name;
 		std::shared_ptr<CPicture> icons;
 		std::shared_ptr<MoraleLuckBox> morale;

+ 3 - 0
client/windows/CKingdomInterface.cpp

@@ -811,6 +811,9 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 	{
 		if(town->builtBuildings.count(BuildingID::TAVERN))
 			LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
+	}, [&]{
+		if(!town->town->faction->getDescriptionTranslated().empty())
+			CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
 	});
 	fastMarket = std::make_shared<LRClickableArea>(Rect(153, 6, 65, 64), []()
 	{

+ 4 - 0
config/schemas/creature.json

@@ -41,6 +41,10 @@
 				}
 			}
 		},
+		"description" : {
+			"type" : "string",
+			"description" : "Description of creature"
+		},
 		"faction" : {
 			"type" : "string",
 			"description" : "Faction this creature belongs to. Examples: castle, rampart"

+ 4 - 0
config/schemas/faction.json

@@ -40,6 +40,10 @@
 			"type" : "string",
 			"description" : "Localizable faction name, e.g. Rampart"
 		},
+		"description" : {
+			"type" : "string",
+			"description" : "Description about the faction"
+		},
 		"alignment" : {
 			"type" : "string",
 			"enum" : [ "good", "neutral", "evil" ],

+ 5 - 0
config/shortcutsConfig.json

@@ -4,6 +4,7 @@
 // For players (Linux): create file ~/.config/vcmi/shortcutsConfig.json (or ~/.var/app/eu.vcmi.VCMI/config for Flatpak) to modify this set
 //
 // When creating your own config, you can remove all hotkeys that you have not changed and game will read them from this file
+// It is possible to add modifiers to keys: Ctrl, Shift, or Alt. For example, "Ctrl+Tab" hotkey will only activate if Ctrl is pressed
 {
 	"keyboard" : {
 		"globalAccept":             [ "Return", "Keypad Enter"],
@@ -112,6 +113,10 @@
 		"battleTacticsEnd":         [ "Return", "Keypad Enter"],
 		"battleToggleHeroesStats":  [],
 		"battleSelectAction":       "S",
+		"lobbyActivateInterface":   "Ctrl+Tab",
+		"spectateTrackHero":        "F5",
+		"spectateSkipBattle":       "F7",
+		"spectateSkipBattleResult": "F8",
 		"townOpenTavern":           "T",
 		"townSwapArmies":           "Space",
 		"recruitmentMax":           "End",

+ 1 - 1
debian/changelog

@@ -8,7 +8,7 @@ vcmi (1.5.0) jammy; urgency=medium
 
   * New upstream release
 
- -- Ivan Savenko <[email protected]>  Fri, 1 Mar 2024 12:00:00 +0200
+ -- Ivan Savenko <[email protected]>  Fri, 10 May 2024 12:00:00 +0200
 
 vcmi (1.4.5) jammy; urgency=medium
 

+ 1 - 4
docs/Readme.md

@@ -1,8 +1,5 @@
 [![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.4.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.0)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.1)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.2/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.2)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.5)
+[![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/total)](https://github.com/vcmi/vcmi/releases)
 
 # VCMI Project

+ 5 - 4
docs/maintainers/Release_Process.md

@@ -18,7 +18,7 @@ Should be done immediately after start of stabilization stage for previous relea
 - Add all features and bugs that should be fixed as part of this release into this project
 
 ### Start of stabilization stage (major releases only)
-Should be done 2-4 weeks before planned release date. All major features should be finished at this point.
+Should be done 2 weeks before planned release date. All major features should be finished at this point.
 
 - Create `beta` branch from `develop`
 - Bump vcmi version in CMake on `develop` branch
@@ -37,9 +37,10 @@ Should be done 1 week before release. Release date should be decided at this poi
 - Make sure to announce codebase freeze deadline (1 day before release) to all developers
 - Create pull request for release preparation tasks targeting `beta`:
 - - Update [changelog](https://github.com/vcmi/vcmi/blob/develop/ChangeLog.md)
-- - Update release date for Linux packaging. See [example](https://github.com/vcmi/vcmi/pull/1258)
-- - Update build ID for Android packaging. See [example](https://github.com/vcmi/vcmi/pull/2090)
-- - Update downloads counter in readme.md. See [example](https://github.com/vcmi/vcmi/pull/2091)
+- - Update release date in `debian/changelog`
+- - Update release date in `launcher/eu.vcmi.VCMI.metainfo.xml`
+- - Update build ID `android/vcmi-app/build.gradle`
+- - Update downloads counter in `docs/readme.md`
 
 ### Release preparation stage
 Should be done 1 day before release. At this point beta branch is in full freeze.

+ 1 - 0
docs/modders/Entities_Format/Biome_Format.md

@@ -10,6 +10,7 @@ If not enough biomes are defined for [terrain type](Terrain_Format.md), map gene
 "obstacleSetId" : {
 	"biome" : {
 		"terrain" : "grass", // Id or vector of Ids this obstacle set can spawn at
+		"level" : "underground", // or "surface", by default both
 		"faction" : ["castle", "rampart"], //Id or vector of faction Ids. Set will only be used if zone belongs to this faction
 		"alignment" : ["good", "evil", "neutral"], //Alignment of the zone. Set will only be used if zone has this alignment
 		"objectType": "mountain"

+ 4 - 0
docs/modders/Entities_Format/Creature_Format.md

@@ -29,6 +29,10 @@ In order to make functional creature you also need:
 		"singular" : "Creature",
 		"plural" : "Creatures"
 	},
+
+	// Description of creature
+	"description" : "",
+
 	"level" : 0,
 
 	// Marks this object as special and not available by default

+ 3 - 0
docs/modders/Entities_Format/Faction_Format.md

@@ -57,6 +57,9 @@ Each town requires a set of buildings (Around 30-45 buildings)
 	// Localizable faction name, e.g. "Rampart"
 	"name" : "", 
 
+	// Description of town (e.g. history or story about town)
+	"description" : "",
+
 	// Faction alignment. Can be good, neutral (default) or evil.
 	"alignment" : "",
 

+ 12 - 1
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -5,6 +5,7 @@
 	<summary>Open-source game engine for Heroes of Might and Magic III</summary>
 	<summary xml:lang="cs">Herní engine s otevřeným kódem pro Heroes of Might and Magic III</summary>
 	<summary xml:lang="de">Open-Source-Spielengine für Heroes of Might and Magic III</summary>
+	<summary xml:lang="uk">Ігровий рушій з відкритим початковим кодом для Heroes of Might and Magic III</summary>
 	<developer id="eu.vcmi">
 		<name>VCMI Team</name>
 		<name xml:lang="cs">Tým VCMI</name>
@@ -15,35 +16,45 @@
 		<p>VCMI is an open-source engine for Heroes of Might and Magic III with new possibilities. Years of intensive work resulted in an impressive amount of features. Among the current features are:</p>
 		<p xml:lang="cs">VCMI je engine s otevřeným kódem a novými možnostmi pro Heroes of Might and Magic III. Roky usilovné práce vyústily v úchvatném počtu nových funkcí. Mezi současnými funkcemi jsou:</p>
 		<p xml:lang="de">VCMI ist eine Open-Source-Engine für Heroes of Might and Magic III mit neuen Möglichkeiten. Jahrelange intensive Arbeit führte zu einer beeindruckenden Anzahl von Features. Zu den aktuellen Features gehören:</p>
+		<p xml:lang="uk">VCMI - це рушій з відкритим початковим кодом для Heroes of Might and Magic III з новими можливостями. Роки інтенсивної роботи вилилися у вражаючу кількість функцій. Серед поточних можливостей можна виділити наступні:</p>
 		<ul>
 			<li>Complete gameplay mechanics</li>
 			<li xml:lang="cs">Kompletní herní mechaniky</li>
 			<li xml:lang="de">Vollständige Spielmechanik</li>
+			<li xml:lang="uk">Уся ігрова механіка</li>
 			<li>Almost all objects, abilities, spells and other content</li>
 			<li xml:lang="cs">Skoro všechny předměty, schopnosti, kouzla a ostatní obsah</li>
 			<li xml:lang="de">Fast alle Objekte, Fähigkeiten, Zaubersprüche und andere Inhalte</li>
+			<li xml:lang="uk">Практично всі об'єкти, вміння, закляття та інший вміст</li>
 			<li>Basic battle AI and adventure AI</li>
 			<li xml:lang="cs">Základní AI boje a mapy světa</li>
 			<li xml:lang="de">Grundlegende Kampf- und Abenteuer-KI</li>
+			<li xml:lang="uk">Базовий ШІ для бою та для мапи пригод</li>
 			<li>Many GUI improvements: high resolutions, stack queue, creature window</li>
 			<li xml:lang="cs">Mnoho vylepšení rozhraní: vyšší rozlišení, fronta oddílů a okno bojovníků</li>
 			<li xml:lang="de">Viele GUI-Verbesserungen: Hohe Auflösungen, Truppenwarteschlange, Kreaturenfenster</li>
+			<li xml:lang="uk">Численні покращення графічного інтерфейсу: висока роздільна здатність, черга ходу істот, нове вікно істот</li>
 			<li>Advanced and easy mod support - add new towns, creatures, heroes, artifacts and spells without limits or conflicts</li>
 			<li xml:lang="cs">Pokročilá a jednoduchá podpora modifikací - přidání nových měst, bojovníků, hrdinů, artefaktů a kouzel bez limitů a konfliktů</li>
 			<li xml:lang="de">Erweiterte und einfache Mod-Unterstützung - füge neue Städte, Kreaturen, Helden, Artefakte und Zaubersprüche ohne Einschränkungen oder Konflikte hinzu</li>
+			<li xml:lang="uk">Просунута і проста підтримка модів - додавайте нові міста, істот, героїв, артефакти і закляття без обмежень і конфліктів</li>
 			<li>Launcher for easy configuration - download mods from our server and install them immediately!</li>
 			<li xml:lang="cs">Spouštěč pro jednoduché nastavení - stahujte modifikace z našeho serveru a hned je instalujte!</li>
 			<li xml:lang="de">Launcher für einfache Konfiguration - Mods von unserem Server herunterladen und sofort installieren!</li>
+			<li xml:lang="uk">Лаунчер для легкого налаштування гри - завантажуйте моди з нашого сервера та встановлюйте їх одразу!</li>
 			<li>Random map generator that supports objects added by mods</li>
 			<li xml:lang="cs">Náhodný generátor map, který podporuje předměty přidané modifikacemi</li>
 			<li xml:lang="de">Zufallsgenerator für Karten, der von Mods hinzugefügte Objekte unterstützt</li>
+			<li xml:lang="uk">Генератор випадкових карт, який підтримує об'єкти, додані модами</li>
 		</ul>
 		<p>Note: In order to play the game using VCMI you need to own data files for Heroes of Might and Magic III: The Shadow of Death.</p>
 		<p xml:lang="cs">Poznámka: pokud chcete hrát hru přes VCMI, musíte vlastnit datové soubory Heroes of Might and Magic III: The Shadow of Death.</p>
 		<p xml:lang="de">Hinweis: Um das Spiel mit VCMI spielen zu können, sind die Originaldateien für Heroes of Might and Magic III: The Shadow of Death erforderlich.</p>
+		<p xml:lang="uk">Примітка: Для того, щоб грати в гру за допомогою VCMI, вам потрібно мати файли даних для гри Heroes of Might and Magic III: The Shadow of Death.</p>
 		<p>If you want help, please check our forum, bug tracker or GitHub page.</p>
 		<p xml:lang="cs">Pokud chcete pomoct, prosíme, podívejte se na naše fórum nebo GitHub.</p>
 		<p xml:lang="de">Wird Hilfe benötigt, besucht bitte unser Forum, den Bugtracker oder die GitHub-Seite.</p>
+		<p xml:lang="uk">Якщо вам потрібна допомога, зверніться до нашого форуму, баг-трекера або на сторінку GitHub.</p>
 	</description>
 	<screenshots>
 		<screenshot type="default">
@@ -80,7 +91,7 @@
 	<launchable type="desktop-id">vcmilauncher.desktop</launchable>
 	<releases>
 		<release version="1.6.0" date="2024-08-30" type="development"/>
-		<release version="1.5.0" date="2024-03-01" type="development"/>
+		<release version="1.5.0" date="2024-05-10" type="stable"/>
 		<release version="1.4.5" date="2024-01-23" type="stable"/>
 		<release version="1.4.4" date="2024-01-20" type="stable"/>
 		<release version="1.4.3" date="2024-01-19" type="stable"/>

+ 64 - 60
launcher/translation/polish.ts

@@ -254,7 +254,7 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.ui" line="373"/>
         <source>Install from file</source>
-        <translation type="unfinished"></translation>
+        <translation>Zainstaluj z pliku</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.ui" line="424"/>
@@ -350,18 +350,18 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="310"/>
         <source>please upgrade mod</source>
-        <translation type="unfinished"></translation>
+        <translation>proszę zaktualizować moda</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="182"/>
         <location filename="../modManager/cmodlistview_moc.cpp" line="796"/>
         <source>mods repository index</source>
-        <translation type="unfinished"></translation>
+        <translation>indeks repozytorium modów</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="312"/>
         <source>or newer</source>
-        <translation type="unfinished"></translation>
+        <translation>lub nowsze</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="315"/>
@@ -416,42 +416,42 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>All supported files</source>
-        <translation type="unfinished"></translation>
+        <translation>Wszystkie wspierane pliki</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>Maps</source>
-        <translation type="unfinished">Mapy</translation>
+        <translation>Mapy</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>Campaigns</source>
-        <translation type="unfinished"></translation>
+        <translation>Kampanie</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>Configs</source>
-        <translation type="unfinished"></translation>
+        <translation>Konfiguracje</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>Mods</source>
-        <translation type="unfinished">Mody</translation>
+        <translation>Mody</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="628"/>
         <source>Select files (configs, mods, maps, campaigns) to install...</source>
-        <translation type="unfinished"></translation>
+        <translation>Wybierz pliki (konfiguracyjne, mody, mapy, kampanie) do zainstalowania...</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
         <source>Replace config file?</source>
-        <translation type="unfinished"></translation>
+        <translation>Zastąpić plik konfiguracji?</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
         <source>Do you want to replace %1?</source>
-        <translation type="unfinished"></translation>
+        <translation>Czy chcesz zastąpić %1?</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="693"/>
@@ -505,7 +505,7 @@ Zainstalować pomyślnie pobrane?</translation>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="960"/>
         <source>screenshots</source>
-        <translation type="unfinished"></translation>
+        <translation>zrzuty ekranu</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="966"/>
@@ -523,95 +523,96 @@ Zainstalować pomyślnie pobrane?</translation>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="160"/>
         <source>Can not install submod</source>
-        <translation type="unfinished"></translation>
+        <translation>Nie można zainstalować submoda</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="163"/>
         <source>Mod is already installed</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod jest już zainstalowany</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="172"/>
         <source>Can not uninstall submod</source>
-        <translation type="unfinished"></translation>
+        <translation>Nie można odinstalować submoda</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="175"/>
         <source>Mod is not installed</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod nie jest zainstalowany</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="185"/>
         <source>Mod is already enabled</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod jest już włączony</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="188"/>
         <location filename="../modManager/cmodmanager.cpp" line="231"/>
         <source>Mod must be installed first</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod musi zostać najpierw zainstalowany</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="192"/>
         <source>Mod is not compatible, please update VCMI and checkout latest mod revisions</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod nie jest kompatybilny, proszę zaktualizować VCMI i odświeżyć listę modów</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="197"/>
         <source>Required mod %1 is missing</source>
-        <translation type="unfinished"></translation>
+        <translation>Brakuje wymaganego moda %1</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="202"/>
         <source>Required mod %1 is not enabled</source>
-        <translation type="unfinished"></translation>
+        <translation>Wymagany mod %1 jest wyłączony</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="211"/>
         <location filename="../modManager/cmodmanager.cpp" line="218"/>
         <source>This mod conflicts with %1</source>
-        <translation type="unfinished"></translation>
+        <translation>Ten mod konfliktuje z %1</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="228"/>
         <source>Mod is already disabled</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod jest już wyłączony</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="238"/>
         <source>This mod is needed to run %1</source>
-        <translation type="unfinished"></translation>
+        <translation>Ten mod jest potrzebny do uruchomienia %1</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="280"/>
         <source>Mod archive is missing</source>
-        <translation type="unfinished"></translation>
+        <translation>Brakuje archiwum modyfikacji</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="283"/>
         <source>Mod with such name is already installed</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod z taką nazwą jest już zainstalowany</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="288"/>
         <source>Mod archive is invalid or corrupted</source>
-        <translation type="unfinished"></translation>
+        <translation>Archiwum moda jest niepoprawne lub uszkodzone</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="314"/>
         <source>Failed to extract mod data</source>
-        <translation type="unfinished"></translation>
+        <translation>Nieudane wyodrębnienie danych moda</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="342"/>
         <source>Data with this mod was not found</source>
-        <translation type="unfinished"></translation>
+        <translation>Dane z tym modem nie zostały znalezione</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="346"/>
         <source>Mod is located in protected directory, please remove it manually:
 </source>
-        <translation type="unfinished"></translation>
+        <translation>Mod jest umiejscowiony w chronionym folderze, proszę go usunąć ręcznie:
+</translation>
     </message>
 </context>
 <context>
@@ -716,7 +717,7 @@ Zainstalować pomyślnie pobrane?</translation>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="624"/>
         <source>Renderer</source>
-        <translation type="unfinished"></translation>
+        <translation>Renderer</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="246"/>
@@ -858,27 +859,27 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane
     <message>
         <location filename="../modManager/cmodlist.cpp" line="21"/>
         <source>%1 B</source>
-        <translation type="unfinished"></translation>
+        <translation>%1 B</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlist.cpp" line="22"/>
         <source>%1 KiB</source>
-        <translation type="unfinished"></translation>
+        <translation>%1 KiB</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlist.cpp" line="23"/>
         <source>%1 MiB</source>
-        <translation type="unfinished"></translation>
+        <translation>%1 MiB</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlist.cpp" line="24"/>
         <source>%1 GiB</source>
-        <translation type="unfinished"></translation>
+        <translation>%1 GiB</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlist.cpp" line="25"/>
         <source>%1 TiB</source>
-        <translation type="unfinished"></translation>
+        <translation>%1 TiB</translation>
     </message>
 </context>
 <context>
@@ -934,7 +935,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="288"/>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="350"/>
         <source>Install gog.com files</source>
-        <translation type="unfinished"></translation>
+        <translation>Użyj instalatora z gog.com</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/>
@@ -1020,7 +1021,7 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="346"/>
         <source>If you don&apos;t have a copy of Heroes III installed, VCMI can import your Heroes III data using the offline installer from gog.com.</source>
-        <translation type="unfinished"></translation>
+        <translation>Jeśli nie masz zainstalowanego Heroes III, VCMI może użyć danych z instalatora offline gog.com.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="384"/>
@@ -1071,64 +1072,64 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="148"/>
         <source>Heroes III installation found!</source>
-        <translation type="unfinished"></translation>
+        <translation>Znaleziono zainstalowane Heroes III!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="148"/>
         <source>Copy data to VCMI folder?</source>
-        <translation type="unfinished"></translation>
+        <translation>Skopiować dane do folderu VCMI?</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="289"/>
         <source>Select %1 file...</source>
         <comment>param is file extension</comment>
-        <translation type="unfinished"></translation>
+        <translation>Wybierz plik %1...</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="290"/>
         <source>You have to select %1 file!</source>
         <comment>param is file extension</comment>
-        <translation type="unfinished"></translation>
+        <translation>Musisz wybrać plik %1!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="292"/>
         <source>GOG file (*.*)</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalator GOG (*.*)</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="293"/>
         <source>File selection</source>
-        <translation type="unfinished"></translation>
+        <translation>Wybór pliku</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="300"/>
         <source>Invalid file selected</source>
-        <translation type="unfinished"></translation>
+        <translation>Wybrano nieprawidłowy plik</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="307"/>
         <source>GOG installer</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalator GOG</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="310"/>
         <source>GOG data</source>
-        <translation type="unfinished"></translation>
+        <translation>Dane GOG</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="314"/>
         <source>Installing... Please wait!</source>
-        <translation type="unfinished"></translation>
+        <translation>Instalowanie... Proszę czekać!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="349"/>
         <source>No Heroes III data!</source>
-        <translation type="unfinished"></translation>
+        <translation>Brak danych Heroes III!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="349"/>
         <source>Selected files do not contain Heroes III data!</source>
-        <translation type="unfinished"></translation>
+        <translation>Wybrane pliki nie zawierają danych Heroes III!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="387"/>
@@ -1136,26 +1137,29 @@ Heroes III: HD Edition nie jest obecnie wspierane!</translation>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="408"/>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="413"/>
         <source>Heroes III data not found!</source>
-        <translation type="unfinished"></translation>
+        <translation>Dane Heroes III nie znalezione!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="387"/>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="401"/>
         <source>Failed to detect valid Heroes III data in chosen directory.
 Please select directory with installed Heroes III data.</source>
-        <translation type="unfinished"></translation>
+        <translation>Nieudane znalezienie poprawnych plików Heroes III w podanej lokalizacji.
+Proszę wybrać folder z zainstalowanymi danymi Heroes III.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="408"/>
         <source>Heroes III: HD Edition files are not supported by VCMI.
 Please select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death.</source>
-        <translation type="unfinished"></translation>
+        <translation>Pliki Heroes III HD Edition nie są wspierane przez VCMI.
+Proszę wybrać folder z Heroes III: Complete Edition lub Heroes III: Shadow of Death.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="413"/>
         <source>Unknown or unsupported Heroes III version found.
 Please select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death.</source>
-        <translation type="unfinished"></translation>
+        <translation>Znaleziono nieznaną lub niewspieraną wersję Heroes III.
+Proszę wybrać folder z Heroes III: Complete Edition lub Heroes III: Shadow of Death.</translation>
     </message>
 </context>
 <context>
@@ -1312,17 +1316,17 @@ Please select directory with Heroes III: Complete Edition or Heroes III: Shadow
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="172"/>
         <source>Name</source>
-        <translation type="unfinished">Nazwa</translation>
+        <translation>Nazwa</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="175"/>
         <source>Type</source>
-        <translation type="unfinished">Typ</translation>
+        <translation>Typ</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistmodel_moc.cpp" line="176"/>
         <source>Version</source>
-        <translation type="unfinished">Wersja</translation>
+        <translation>Wersja</translation>
     </message>
 </context>
 <context>
@@ -1345,12 +1349,12 @@ Please select directory with Heroes III: Complete Edition or Heroes III: Shadow
     <message>
         <location filename="../updatedialog_moc.cpp" line="64"/>
         <source>Network error</source>
-        <translation type="unfinished"></translation>
+        <translation>Błąd sieciowy</translation>
     </message>
     <message>
         <location filename="../updatedialog_moc.cpp" line="101"/>
         <source>Cannot read JSON from url or incorrect JSON data</source>
-        <translation type="unfinished"></translation>
+        <translation>Nie można odczytać JSON z url lub JSON ma błędną zawartość</translation>
     </message>
 </context>
 </TS>

+ 11 - 0
lib/CCreatureHandler.cpp

@@ -199,6 +199,11 @@ std::string CCreature::getNameTextID() const
 	return getNameSingularTextID();
 }
 
+std::string CCreature::getDescriptionTranslated() const
+{
+	return VLC->generaltexth->translate(getDescriptionTextID());
+}
+
 std::string CCreature::getNamePluralTextID() const
 {
 	return TextIdentifier("creatures", modScope, identifier, "name", "plural" ).get();
@@ -209,6 +214,11 @@ std::string CCreature::getNameSingularTextID() const
 	return TextIdentifier("creatures", modScope, identifier, "name", "singular" ).get();
 }
 
+std::string CCreature::getDescriptionTextID() const
+{
+	return TextIdentifier("creatures", modScope, identifier, "description").get();
+}
+
 CCreature::CreatureQuantityId CCreature::getQuantityID(const int & quantity)
 {
 	if (quantity<5)
@@ -600,6 +610,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 
 	VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"].String());
 	VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"].String());
+	VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"].String());
 
 	cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH);
 	cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED);

+ 3 - 0
lib/CCreatureHandler.h

@@ -51,6 +51,9 @@ class DLL_LINKAGE CCreature : public Creature, public CBonusSystemNode
 	TResources cost; //cost[res_id] - amount of that resource required to buy creature from dwelling
 
 public:
+	std::string getDescriptionTranslated() const;
+	std::string getDescriptionTextID() const;
+
 	ui32 ammMin; // initial size of stack of these creatures on adventure map (if not set in editor)
 	ui32 ammMax;
 

+ 11 - 0
lib/CTownHandler.cpp

@@ -188,6 +188,16 @@ std::string CFaction::getNameTextID() const
 	return TextIdentifier("faction", modScope, identifier, "name").get();
 }
 
+std::string CFaction::getDescriptionTranslated() const
+{
+	return VLC->generaltexth->translate(getDescriptionTextID());
+}
+
+std::string CFaction::getDescriptionTextID() const
+{
+	return TextIdentifier("faction", modScope, identifier, "description").get();
+}
+
 FactionID CFaction::getId() const
 {
 	return FactionID(index);
@@ -1037,6 +1047,7 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode
 	faction->identifier = identifier;
 
 	VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"].String());
+	VLC->generaltexth->registerString(scope, faction->getDescriptionTranslated(), source["description"].String());
 
 	faction->creatureBg120 = ImagePath::fromJson(source["creatureBackground"]["120px"]);
 	faction->creatureBg130 = ImagePath::fromJson(source["creatureBackground"]["130px"]);

+ 2 - 0
lib/CTownHandler.h

@@ -192,6 +192,8 @@ public:
 
 	std::string getNameTranslated() const override;
 	std::string getNameTextID() const override;
+	std::string getDescriptionTranslated() const;
+	std::string getDescriptionTextID() const;
 
 	bool hasTown() const override;
 	TerrainId getNativeTerrain() const override;

+ 1 - 1
lib/IGameCallback.h

@@ -117,7 +117,7 @@ public:
 	virtual void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr)=0; //use hero=nullptr for no hero
 	virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false)=0; //if any of armies is hero, hero will be used
 	virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
-	virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL)=0;
+	virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveMove, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL)=0;
 	virtual bool swapGarrisonOnSiege(ObjectInstanceID tid)=0;
 	virtual void giveHeroBonus(GiveBonus * bonus)=0;
 	virtual void setMovePoints(SetMovePoints * smp)=0;

+ 9 - 0
lib/constants/Enumerations.h

@@ -253,4 +253,13 @@ enum class EArmyFormation : int8_t
 	TIGHT
 };
 
+enum class EMovementMode : int8_t
+{
+	STANDARD,
+	DIMENSION_DOOR,
+	MONOLITH,
+	CASTLE_GATE,
+	TOWN_PORTAL,
+};
+
 VCMI_LIB_NAMESPACE_END

+ 2 - 2
lib/mapObjects/MiscObjects.cpp

@@ -524,7 +524,7 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer,
 	else
 		dPos = hero->convertFromVisitablePos(cb->getObj(randomExit)->visitablePos());
 
-	cb->moveHero(hero->id, dPos, true);
+	cb->moveHero(hero->id, dPos, EMovementMode::MONOLITH);
 }
 
 void CGMonolith::initObj(CRandomGenerator & rand)
@@ -703,7 +703,7 @@ void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer
 		dPos = hero->convertFromVisitablePos(*RandomGeneratorUtil::nextItem(tiles, CRandomGenerator::getDefault()));
 	}
 
-	cb->moveHero(hero->id, dPos, true);
+	cb->moveHero(hero->id, dPos, EMovementMode::MONOLITH);
 }
 
 bool CGWhirlpool::isProtected(const CGHeroInstance * h)

+ 56 - 4
lib/mapObjects/ObstacleSetHandler.cpp

@@ -19,13 +19,15 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 ObstacleSet::ObstacleSet():
 	type(INVALID),
-	allowedTerrains({TerrainId::NONE})
+	allowedTerrains({TerrainId::NONE}),
+	level(EMapLevel::ANY)
 {
 }
 
 ObstacleSet::ObstacleSet(EObstacleType type, TerrainId terrain):
 	type(type),
-	allowedTerrains({terrain})
+	allowedTerrains({terrain}),
+	level(EMapLevel::ANY)
 {
 }
 
@@ -47,16 +49,26 @@ void ObstacleSet::removeEmptyTemplates()
 	});
 }
 
-ObstacleSetFilter::ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain = TerrainId::ANY_TERRAIN, FactionID faction = FactionID::ANY, EAlignment alignment = EAlignment::ANY):
+ObstacleSetFilter::ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes,
+	TerrainId terrain = TerrainId::ANY_TERRAIN,
+	ObstacleSet::EMapLevel level = ObstacleSet::EMapLevel::ANY,
+	FactionID faction = FactionID::ANY,
+	EAlignment alignment = EAlignment::ANY):
 	allowedTypes(allowedTypes),
+	level(level),
 	faction(faction),
 	alignment(alignment),
 	terrain(terrain)
 {
 }
 
-ObstacleSetFilter::ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain = TerrainId::ANY_TERRAIN, FactionID faction = FactionID::ANY, EAlignment alignment = EAlignment::ANY):
+ObstacleSetFilter::ObstacleSetFilter(ObstacleSet::EObstacleType allowedType,
+	TerrainId terrain = TerrainId::ANY_TERRAIN,
+	ObstacleSet::EMapLevel level = ObstacleSet::EMapLevel::ANY,
+	FactionID faction = FactionID::ANY,
+	EAlignment alignment = EAlignment::ANY):
 	allowedTypes({allowedType}),
+	level(level),
 	faction(faction),
 	alignment(alignment),
 	terrain(terrain)
@@ -70,6 +82,14 @@ bool ObstacleSetFilter::filter(const ObstacleSet &set) const
 		return false;
 	}
 
+	if (level != ObstacleSet::EMapLevel::ANY && set.getLevel() != ObstacleSet::EMapLevel::ANY)
+	{
+		if (level != set.getLevel())
+		{
+			return false;
+		}
+	}
+
 	if (faction != FactionID::ANY)
 	{
 		auto factions = set.getFactions();
@@ -117,6 +137,16 @@ void ObstacleSet::addTerrain(TerrainId terrain)
 	this->allowedTerrains.insert(terrain);
 }
 
+ObstacleSet::EMapLevel ObstacleSet::getLevel() const
+{
+	return level;
+}
+
+void ObstacleSet::setLevel(ObstacleSet::EMapLevel newLevel)
+{
+	level = newLevel;
+}
+
 std::set<FactionID> ObstacleSet::getFactions() const
 {
 	return allowedFactions;
@@ -248,6 +278,22 @@ std::string ObstacleSet::toString() const
 	return OBSTACLE_TYPE_STRINGS.at(type);
 }
 
+ObstacleSet::EMapLevel ObstacleSet::levelFromString(const std::string &str)
+{
+	static const std::map<std::string, EMapLevel> LEVEL_NAMES =
+	{
+		{"surface", SURFACE},
+		{"underground", UNDERGROUND}
+	};
+
+	if (LEVEL_NAMES.find(str) != LEVEL_NAMES.end())
+	{
+		return LEVEL_NAMES.at(str);
+	}
+
+	throw std::runtime_error("Invalid map level: " + str);
+}
+
 std::vector<ObstacleSet::EObstacleType> ObstacleSetFilter::getAllowedTypes() const
 {
 	return allowedTypes;
@@ -325,6 +371,12 @@ std::shared_ptr<ObstacleSet> ObstacleSetHandler::loadFromJson(const std::string
 		logMod->error("No terrain specified for obstacle set %s", name);
 	}
 
+	if (biome["level"].isString())
+	{
+		auto level = biome["level"].String();
+		os->setLevel(ObstacleSet::levelFromString(level));
+	}
+
 	auto handleFaction = [os, scope](const std::string & str)
 	{
 		VLC->identifiers()->requestIdentifier(scope, "faction", str, [os](si32 id)

+ 15 - 2
lib/mapObjects/ObstacleSetHandler.h

@@ -37,6 +37,14 @@ public:
 		ANIMALS, // Living, or bones
 		OTHER // Crystals, shipwrecks, barrels, etc.
 	};
+
+	enum EMapLevel // TODO: Move somewhere to map definitions
+	{
+		ANY = -1,
+		SURFACE = 0,
+		UNDERGROUND = 1
+	};
+
 	ObstacleSet();
 	explicit ObstacleSet(EObstacleType type, TerrainId terrain);
 
@@ -51,6 +59,8 @@ public:
 	void setTerrain(TerrainId terrain);
 	void setTerrains(const std::set<TerrainId> & terrains);
 	void addTerrain(TerrainId terrain);
+	EMapLevel getLevel() const;
+	void setLevel(EMapLevel level);
 	std::set<EAlignment> getAlignments() const;
 	void addAlignment(EAlignment alignment);
 	std::set<FactionID> getFactions() const;
@@ -58,12 +68,14 @@ public:
 
 	static EObstacleType typeFromString(const std::string &str);
 	std::string toString() const;
+	static EMapLevel levelFromString(const std::string &str);
 
 	si32 id;
 
 private:
 
 	EObstacleType type;
+	EMapLevel level;
 	std::set<TerrainId> allowedTerrains; // Empty means all terrains
 	std::set<FactionID> allowedFactions; // Empty means all factions
 	std::set<EAlignment> allowedAlignments; // Empty means all alignments
@@ -75,8 +87,8 @@ using TObstacleTypes = std::vector<std::shared_ptr<ObstacleSet>>;
 class DLL_LINKAGE ObstacleSetFilter
 {
 public:
-	ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain, FactionID faction, EAlignment alignment);
-	ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain, FactionID faction, EAlignment alignment);
+	ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain, ObstacleSet::EMapLevel level, FactionID faction, EAlignment alignment);
+	ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain, ObstacleSet::EMapLevel level, FactionID faction, EAlignment alignment);
 
 	bool filter(const ObstacleSet &set) const;
 
@@ -93,6 +105,7 @@ private:
 	EAlignment alignment;
 // TODO: Filter by faction,  surface/underground, etc.
 	const TerrainId terrain;
+	ObstacleSet::EMapLevel level;
 };
 
 // TODO: Instantiate ObstacleSetHandler

+ 7 - 24
lib/network/NetworkConnection.cpp

@@ -12,12 +12,12 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket, const std::shared_ptr<NetworkContext> & context)
+NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket)
 	: socket(socket)
-	, context(context)
 	, listener(listener)
 {
 	socket->set_option(boost::asio::ip::tcp::no_delay(true));
+	socket->set_option(boost::asio::socket_base::keep_alive(true));
 
 	// iOS throws exception on attempt to set buffer size
 	constexpr auto bufferSize = 4 * 1024 * 1024;
@@ -43,32 +43,12 @@ NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, cons
 
 void NetworkConnection::start()
 {
-	heartbeat();
-
 	boost::asio::async_read(*socket,
 							readBuffer,
 							boost::asio::transfer_exactly(messageHeaderSize),
 							[self = shared_from_this()](const auto & ec, const auto & endpoint) { self->onHeaderReceived(ec); });
 }
 
-void NetworkConnection::heartbeat()
-{
-	constexpr auto heartbeatInterval = std::chrono::seconds(10);
-
-	auto timer = std::make_shared<NetworkTimer>(*context, heartbeatInterval);
-	timer->async_wait( [self = shared_from_this(), timer](const auto & ec)
-	{
-		if (ec)
-			return;
-
-		if (!self->socket->is_open())
-			return;
-
-		self->sendPacket({});
-		self->heartbeat();
-	});
-}
-
 void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHeader)
 {
 	if (ecHeader)
@@ -91,7 +71,7 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHea
 
 	if (messageSize == 0)
 	{
-		//heartbeat package with no payload - wait for next packet
+		// Zero-sized packet. Strange, but safe to ignore. Start reading next packet
 		start();
 		return;
 	}
@@ -124,13 +104,16 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u
 
 void NetworkConnection::sendPacket(const std::vector<std::byte> & message)
 {
+	std::lock_guard<std::mutex> lock(writeMutex);
+
 	boost::system::error_code ec;
 
 	// create array with single element - boost::asio::buffer can be constructed from containers, but not from plain integer
 	std::array<uint32_t, 1> messageSize{static_cast<uint32_t>(message.size())};
 
 	boost::asio::write(*socket, boost::asio::buffer(messageSize), ec );
-	boost::asio::write(*socket, boost::asio::buffer(message), ec );
+	if (message.size() > 0)
+		boost::asio::write(*socket, boost::asio::buffer(message), ec );
 
 	//Note: ignoring error code, intended
 }

+ 2 - 3
lib/network/NetworkConnection.h

@@ -19,17 +19,16 @@ class NetworkConnection : public INetworkConnection, public std::enable_shared_f
 	static const int messageMaxSize = 64 * 1024 * 1024; // arbitrary size to prevent potential massive allocation if we receive garbage input
 
 	std::shared_ptr<NetworkSocket> socket;
-	std::shared_ptr<NetworkContext> context;
+	std::mutex writeMutex;
 
 	NetworkBuffer readBuffer;
 	INetworkConnectionListener & listener;
 
-	void heartbeat();
 	void onHeaderReceived(const boost::system::error_code & ec);
 	void onPacketReceived(const boost::system::error_code & ec, uint32_t expectedPacketSize);
 
 public:
-	NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket, const std::shared_ptr<NetworkContext> & context);
+	NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket);
 
 	void start();
 	void close() override;

+ 2 - 2
lib/network/NetworkHandler.cpp

@@ -34,14 +34,14 @@ void NetworkHandler::connectToRemote(INetworkClientListener & listener, const st
 	auto socket = std::make_shared<NetworkSocket>(*io);
 	boost::asio::ip::tcp::resolver resolver(*io);
 	auto endpoints = resolver.resolve(host, std::to_string(port));
-	boost::asio::async_connect(*socket, endpoints, [this, socket, &listener](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint)
+	boost::asio::async_connect(*socket, endpoints, [socket, &listener](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint)
 	{
 		if (error)
 		{
 			listener.onConnectionFailed(error.message());
 			return;
 		}
-		auto connection = std::make_shared<NetworkConnection>(listener, socket, io);
+		auto connection = std::make_shared<NetworkConnection>(listener, socket);
 		connection->start();
 
 		listener.onConnectionEstablished(connection);

+ 1 - 1
lib/network/NetworkServer.cpp

@@ -39,7 +39,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingCo
 	}
 
 	logNetwork->info("We got a new connection! :)");
-	auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection, io);
+	auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection);
 	connections.insert(connection);
 	connection->start();
 	listener.onNewConnection(connection);

+ 1 - 1
lib/networkPacks/NetPacksBase.h

@@ -30,7 +30,7 @@ struct DLL_LINKAGE CPack
 	template <typename Handler> void serialize(Handler &h)
 	{
 		logNetwork->error("CPack serialized... this should not happen!");
-		assert(false && "CPack serialized");
+		throw std::runtime_error("CPack serialized... this should not happen!");
 	}
 
 	void applyGs(CGameState * gs)

+ 1 - 0
lib/rmg/CMapGenerator.cpp

@@ -130,6 +130,7 @@ std::unique_ptr<CMap> CMapGenerator::generate()
 	catch (rmgException &e)
 	{
 		logGlobal->error("Random map generation received exception: %s", e.what());
+		throw;
 	}
 	Load::Progress::finish();
 	return std::move(map->mapInstance);

+ 29 - 8
lib/rmg/PenroseTiling.cpp

@@ -21,21 +21,32 @@ Point2D Point2D::operator * (float scale) const
 	return Point2D(x() * scale, y() * scale);
 }
 
+Point2D Point2D::operator / (float scale) const
+{
+	return Point2D(x() / scale, y() / scale);
+}
+
 Point2D Point2D::operator + (const Point2D& other) const
 {
 	return Point2D(x() + other.x(), y() + other.y());
 }
 
+Point2D Point2D::operator - (const Point2D& other) const
+{
+	return Point2D(x() - other.x(), y() - other.y());
+}
+
 bool Point2D::operator < (const Point2D& other) const
 {
-	if (x() < other.x())
-	{
-		return true;
-	}
-	else
-	{
-		return y() < other.y();
-	}
+	if (x() != other.x())
+		return x() < other.x();
+
+	return y() < other.y();
+}
+
+bool Point2D::operator == (const Point2D& other) const
+{
+	return vstd::isAlmostEqual(x(), other.x()) && vstd::isAlmostEqual(y(), other.y());
 }
 
 std::string Point2D::toString() const
@@ -163,6 +174,16 @@ std::set<Point2D> PenroseTiling::generatePenroseTiling(size_t numZones, CRandomG
 
 		split(t, points, indices, DEPTH);
 	}
+	// Remove duplicates
+	vstd::unique(points);
+
+	// Shift center to (0.5, 0.5)
+	for (auto & point : points)
+	{
+		point = point + Point2D(0.5f, 0.5f);
+	};
+
+	// For 8XM8 map, only 650 out of 15971 points are in the range
 
 	vstd::copy_if(points, vstd::set_inserter(finalPoints), [](const Point2D point)
 	{

+ 3 - 0
lib/rmg/PenroseTiling.h

@@ -28,10 +28,13 @@ public:
 	using point_xy::point_xy;
 
 	Point2D operator * (float scale) const;
+	Point2D operator / (float scale) const;
 	Point2D operator + (const Point2D& other) const;
+	Point2D operator - (const Point2D& other) const;
 	Point2D rotated(float radians) const;
 
 	bool operator < (const Point2D& other) const;
+	bool operator == (const Point2D& other) const;
 
 	std::string toString() const;
 };

+ 5 - 1
lib/rmg/modificators/ObstaclePlacer.cpp

@@ -38,7 +38,11 @@ void ObstaclePlacer::process()
 
 	auto faction = zone.getTownType().toFaction();
 
-	ObstacleSetFilter filter(ObstacleSet::EObstacleType::INVALID, zone.getTerrainType(), faction->getId(), faction->alignment);
+	ObstacleSetFilter filter(ObstacleSet::EObstacleType::INVALID,
+							zone.getTerrainType(),
+							static_cast<ObstacleSet::EMapLevel>(zone.isUnderground()),
+							faction->getId(),
+							faction->alignment);
 
 	if (!prepareBiome(filter, zone.getRand()))
 	{

+ 2 - 2
lib/spells/AdventureSpellMechanics.cpp

@@ -439,7 +439,7 @@ void DimensionDoorMechanics::endCast(SpellCastEnvironment * env, const Adventure
 	const TerrainTile * curr = env->getCb()->getTile(casterPosition);
 
 	if(dest->isClear(curr))
-		env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), true);
+		env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(parameters.pos), EMovementMode::DIMENSION_DOOR);
 }
 
 ///TownPortalMechanics
@@ -567,7 +567,7 @@ void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpe
 		destination = dynamic_cast<const CGTownInstance*>(topObj);
 	}
 
-	if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(destination->visitablePos()), true))
+	if(env->moveHero(ObjectInstanceID(parameters.caster->getCasterUnitId()), parameters.caster->getHeroCaster()->convertFromVisitablePos(destination->visitablePos()), EMovementMode::TOWN_PORTAL))
 	{
 		SetMovePoints smp;
 		smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId());

+ 1 - 1
lib/spells/ISpellMechanics.h

@@ -59,7 +59,7 @@ public:
 	virtual const CMap * getMap() const = 0;
 	virtual const CGameInfoCallback * getCb() const = 0;
 
-	virtual bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) = 0;	//TODO: remove
+	virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0;	//TODO: remove
 
 	virtual void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented
 };

文件差异内容过多而无法显示
+ 154 - 154
mapeditor/translation/polish.ts


+ 8 - 8
server/CGameHandler.cpp

@@ -543,7 +543,7 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack
 {
 	if (si->seedToBeUsed == 0)
 	{
-		si->seedToBeUsed = static_cast<ui32>(std::time(nullptr));
+		si->seedToBeUsed = CRandomGenerator::getDefault().nextInt();
 	}
 	CMapService mapService;
 	gs = new CGameState();
@@ -1073,11 +1073,11 @@ bool CGameHandler::removeObject(const CGObjectInstance * obj, const PlayerColor
 	return true;
 }
 
-bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit, PlayerColor asker)
+bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit, PlayerColor asker)
 {
 	const CGHeroInstance *h = getHero(hid);
 	// not turn of that hero or player can't simply teleport hero (at least not with this function)
-	if(!h || (asker != PlayerColor::NEUTRAL && teleporting))
+	if(!h || (asker != PlayerColor::NEUTRAL && movementMode != EMovementMode::STANDARD))
 	{
 		if(h && getStartInfo()->turnTimerInfo.isEnabled() && gs->players[h->getOwner()].turnTimer.turnTimer == 0)
 			return true; //timer expired, no error
@@ -1164,13 +1164,13 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 	if(h->boat && h->boat->layer == EPathfindingLayer::SAIL && t.terType->isLand() && t.blocked)
 		return complainRet("Cannot disembark hero, tile is blocked!");
 
-	if(distance(h->pos, dst) >= 1.5 && !teleporting)
+	if(distance(h->pos, dst) >= 1.5 && movementMode == EMovementMode::STANDARD)
 		return complainRet("Tiles are not neighboring!");
 
 	if(h->inTownGarrison)
 		return complainRet("Can not move garrisoned hero!");
 
-	if(h->movementPointsRemaining() < cost && dst != h->pos && !teleporting)
+	if(h->movementPointsRemaining() < cost && dst != h->pos && movementMode == EMovementMode::STANDARD)
 		return complainRet("Hero doesn't have any movement points left!");
 
 	if (transit && !canFly && !(canWalkOnSea && t.terType->isWater()) && !CGTeleport::isTeleport(objectToVisit))
@@ -1259,12 +1259,12 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 		return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
 	}
 
-	if (teleporting)
+	if (movementMode != EMovementMode::STANDARD)
 	{
 		if (blockingVisit()) // e.g. hero on the other side of teleporter
 			return true;
 
-		EGuardLook guardsCheck = VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS)
+		EGuardLook guardsCheck = (VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && movementMode == EMovementMode::DIMENSION_DOOR)
 			? CHECK_FOR_GUARDS
 			: IGNORE_GUARDS;
 
@@ -1337,7 +1337,7 @@ bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui
 			return false;
 
 	int3 pos = h->convertFromVisitablePos(t->visitablePos());
-	moveHero(hid,pos,1);
+	moveHero(hid,pos,EMovementMode::CASTLE_GATE);
 	return true;
 }
 

+ 1 - 1
server/CGameHandler.h

@@ -140,7 +140,7 @@ public:
 	void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override; //use hero=nullptr for no hero
 	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override; //if any of armies is hero, hero will be used
 	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
-	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override;
+	bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override;
 	void giveHeroBonus(GiveBonus * bonus) override;
 	void setMovePoints(SetMovePoints * smp) override;
 	void setMovePoints(ObjectInstanceID hid, int val, bool absolute) override;

+ 1 - 1
server/NetPacksServer.cpp

@@ -65,7 +65,7 @@ void ApplyGhNetPackVisitor::visitMoveHero(MoveHero & pack)
 
 	for (auto const & dest : pack.path)
 	{
-		if (!gh.moveHero(pack.hid, dest, 0, pack.transit, pack.player))
+		if (!gh.moveHero(pack.hid, dest, EMovementMode::STANDARD, pack.transit, pack.player))
 		{
 			result = false;
 			return;

+ 2 - 2
server/ServerSpellCastEnvironment.cpp

@@ -89,9 +89,9 @@ const CMap * ServerSpellCastEnvironment::getMap() const
 	return gh->gameState()->map;
 }
 
-bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, bool teleporting)
+bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode)
 {
-	return gh->moveHero(hid, dst, teleporting, false);
+	return gh->moveHero(hid, dst, mode, false);
 }
 
 void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback)

+ 1 - 1
server/ServerSpellCastEnvironment.h

@@ -36,7 +36,7 @@ public:
 
 	const CMap * getMap() const override;
 	const CGameInfoCallback * getCb() const override;
-	bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) override;
+	bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) override;
 	void genericQuery(Query * request, PlayerColor color, std::function<void(std::optional<int32_t>)> callback) override;
 private:
 	CGameHandler * gh;

+ 1 - 1
test/game/CGameStateTest.cpp

@@ -121,7 +121,7 @@ public:
 		return gameState.get();
 	}
 
-	bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) override
+	bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode) override
 	{
 		return false;
 	}

+ 1 - 1
test/mock/mock_IGameCallback.h

@@ -77,7 +77,7 @@ public:
 	void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override {} //use hero=nullptr for no hero
 	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override {} //if any of armies is hero, hero will be used
 	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override {} //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
-	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;}
+	bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;}
 	bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;}
 	void giveHeroBonus(GiveBonus * bonus) override {}
 	void setMovePoints(SetMovePoints * smp) override {}

部分文件因为文件数量过多而无法显示