Browse Source

Merge pull request #3900 from vcmi/beta

Merge beta -> develop
Ivan Savenko 1 year ago
parent
commit
a1ffe7cc64
100 changed files with 594 additions and 270 deletions
  1. 32 3
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  2. 1 0
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp
  3. 8 0
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  4. 3 2
      AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
  5. 3 0
      CI/linux/before_install.sh
  6. 7 0
      Mods/vcmi/config/vcmi/english.json
  7. 133 6
      Mods/vcmi/config/vcmi/german.json
  8. 26 8
      Mods/vcmi/config/vcmi/portuguese.json
  9. 5 2
      client/GameChatHandler.cpp
  10. 3 0
      client/HeroMovementController.cpp
  11. 2 2
      client/NetPacksClient.cpp
  12. 2 2
      client/NetPacksLobbyClient.cpp
  13. 137 3
      client/lobby/CSelectionBase.cpp
  14. 36 0
      client/lobby/CSelectionBase.h
  15. 1 1
      client/mainmenu/CPrologEpilogVideo.cpp
  16. 4 1
      client/mapView/MapViewController.cpp
  17. 1 1
      client/windows/CMarketWindow.cpp
  18. 3 5
      config/filesystem.json
  19. 1 1
      debian/control
  20. 0 2
      docs/developers/AI.md
  21. 0 2
      docs/developers/Bonus_System.md
  22. 0 2
      docs/developers/Building_Android.md
  23. 0 2
      docs/developers/Building_Linux.md
  24. 0 2
      docs/developers/Building_Windows.md
  25. 0 2
      docs/developers/Building_iOS.md
  26. 0 2
      docs/developers/Building_macOS.md
  27. 0 2
      docs/developers/Code_Structure.md
  28. 0 2
      docs/developers/Coding_Guidelines.md
  29. 0 2
      docs/developers/Conan.md
  30. 0 2
      docs/developers/Development_with_Qt_Creator.md
  31. 0 2
      docs/developers/Logging_API.md
  32. 0 2
      docs/developers/Lua_Scripting_System.md
  33. 0 2
      docs/developers/Networking.md
  34. 0 2
      docs/developers/Serialization.md
  35. 0 2
      docs/maintainers/Project_Infrastructure.md
  36. 0 2
      docs/maintainers/Release_Process.md
  37. 0 2
      docs/maintainers/Ubuntu_PPA.md
  38. 0 2
      docs/modders/Animation_Format.md
  39. 0 2
      docs/modders/Bonus/Bonus_Duration_Types.md
  40. 0 2
      docs/modders/Bonus/Bonus_Limiters.md
  41. 0 2
      docs/modders/Bonus/Bonus_Propagators.md
  42. 0 2
      docs/modders/Bonus/Bonus_Range_Types.md
  43. 0 2
      docs/modders/Bonus/Bonus_Sources.md
  44. 0 2
      docs/modders/Bonus/Bonus_Types.md
  45. 0 2
      docs/modders/Bonus/Bonus_Updaters.md
  46. 0 2
      docs/modders/Bonus/Bonus_Value_Types.md
  47. 0 2
      docs/modders/Bonus_Format.md
  48. 0 2
      docs/modders/Building_Bonuses.md
  49. 0 2
      docs/modders/Campaign_Format.md
  50. 0 2
      docs/modders/Configurable_Widgets.md
  51. 0 3
      docs/modders/Difficulty.md
  52. 0 2
      docs/modders/Entities_Format/Artifact_Format.md
  53. 0 2
      docs/modders/Entities_Format/Battle_Obstacle_Format.md
  54. 0 2
      docs/modders/Entities_Format/Battlefield_Format.md
  55. 0 2
      docs/modders/Entities_Format/Creature_Format.md
  56. 0 2
      docs/modders/Entities_Format/Faction_Format.md
  57. 0 2
      docs/modders/Entities_Format/Hero_Class_Format.md
  58. 0 2
      docs/modders/Entities_Format/Hero_Type_Format.md
  59. 0 2
      docs/modders/Entities_Format/River_Format.md
  60. 0 2
      docs/modders/Entities_Format/Road_Format.md
  61. 0 2
      docs/modders/Entities_Format/Secondary_Skill_Format.md
  62. 0 2
      docs/modders/Entities_Format/Spell_Format.md
  63. 0 2
      docs/modders/Entities_Format/Terrain_Format.md
  64. 0 2
      docs/modders/Game_Identifiers.md
  65. 0 2
      docs/modders/Map_Editor.md
  66. 0 2
      docs/modders/Map_Object_Format.md
  67. 0 2
      docs/modders/Map_Objects/Boat.md
  68. 0 2
      docs/modders/Map_Objects/Creature_Bank.md
  69. 0 2
      docs/modders/Map_Objects/Dwelling.md
  70. 0 2
      docs/modders/Map_Objects/Market.md
  71. 0 2
      docs/modders/Map_Objects/Rewardable.md
  72. 0 2
      docs/modders/Mod_File_Format.md
  73. 0 2
      docs/modders/Random_Map_Template.md
  74. 0 2
      docs/modders/Readme.md
  75. 6 2
      docs/modders/Translations.md
  76. 0 2
      docs/players/Bug_Reporting_Guidelines.md
  77. 0 2
      docs/players/Cheat_Codes.md
  78. 0 2
      docs/players/Game_Mechanics.md
  79. 0 2
      docs/players/Installation_Android.md
  80. 0 2
      docs/players/Installation_Linux.md
  81. 0 2
      docs/players/Installation_Windows.md
  82. 0 2
      docs/players/Installation_iOS.md
  83. 0 2
      docs/players/Installation_macOS.md
  84. 0 2
      docs/players/Privacy_Policy.md
  85. 11 9
      launcher/firstLaunch/firstlaunch_moc.cpp
  86. 28 0
      launcher/firstLaunch/firstlaunch_moc.ui
  87. 1 1
      launcher/lib/innoextract
  88. 64 60
      launcher/translation/german.ts
  89. 11 15
      lib/mapObjects/CGCreature.cpp
  90. 5 5
      lib/mapObjects/CGCreature.h
  91. 24 3
      lib/network/NetworkConnection.cpp
  92. 3 1
      lib/network/NetworkConnection.h
  93. 2 2
      lib/network/NetworkHandler.cpp
  94. 1 1
      lib/network/NetworkServer.cpp
  95. 1 0
      lib/networkPacks/NetPackVisitor.h
  96. 5 0
      lib/networkPacks/NetPacksLib.cpp
  97. 2 2
      lib/networkPacks/PacksForClient.h
  98. 20 2
      lib/networkPacks/PacksForLobby.h
  99. 1 1
      lib/pathfinder/CPathfinder.cpp
  100. 1 0
      lib/pathfinder/PathfinderOptions.cpp

+ 32 - 3
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -104,7 +104,7 @@ AINodeStorage::~AINodeStorage() = default;
 
 void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
 {
-	if(heroChainPass)
+	if(heroChainPass != EHeroChainPass::INITIAL)
 		return;
 
 	AISharedStorage::version++;
@@ -157,6 +157,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 void AINodeStorage::clear()
 {
 	actors.clear();
+	commitedTiles.clear();
 	heroChainPass = EHeroChainPass::INITIAL;
 	heroChainTurn = 0;
 	heroChainMaxTurns = 1;
@@ -169,7 +170,7 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 	const EPathfindingLayer layer, 
 	const ChainActor * actor)
 {
-	int bucketIndex = ((uintptr_t)actor) % AIPathfinding::BUCKET_COUNT;
+	int bucketIndex = ((uintptr_t)actor + static_cast<uint32_t>(layer)) % AIPathfinding::BUCKET_COUNT;
 	int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
 	auto chains = nodes.get(pos);
 
@@ -330,10 +331,38 @@ void AINodeStorage::calculateNeighbours(
 
 	for(auto & neighbour : accessibleNeighbourTiles)
 	{
+		if(getAccessibility(neighbour, layer) == EPathAccessibility::NOT_SET)
+		{
+#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
+			logAi->trace(
+				"Node %s rejected for %s, layer %d because of inaccessibility",
+				neighbour.toString(),
+				source.coord.toString(),
+				static_cast<int32_t>(layer));
+#endif
+			continue;
+		}
+
 		auto nextNode = getOrCreateNode(neighbour, layer, srcNode->actor);
 
-		if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
+		if(!nextNode)
+		{
+#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
+			logAi->trace(
+				"Failed to allocate node at %s[%d]",
+				neighbour.toString(),
+				static_cast<int32_t>(layer));
+#endif
 			continue;
+		}
+
+#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
+		logAi->trace(
+			"Node %s added to neighbors of %s, layer %d",
+			neighbour.toString(),
+			source.coord.toString(),
+			static_cast<int32_t>(layer));
+#endif
 
 		result.push_back(nextNode.value());
 	}

+ 1 - 0
AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp

@@ -47,6 +47,7 @@ namespace AIPathfinding
 		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
 	{
 		options.canUseCast = true;
+		options.allowLayerTransitioningAfterBattle = true;
 	}
 
 	AIPathfinderConfig::~AIPathfinderConfig() = default;

+ 8 - 0
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -34,6 +34,14 @@ namespace AIPathfinding
 	{
 		LayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper);
 
+#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
+		logAi->trace("Layer transitioning %s -> %s, action: %d, blocked: %s",
+			source.coord.toString(),
+			destination.coord.toString(),
+			static_cast<int32_t>(destination.action),
+			destination.blocked ? "true" : "false");
+#endif
+
 		if(!destination.blocked)
 		{
 			if(source.node->layer == EPathfindingLayer::LAND

+ 3 - 2
AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp

@@ -84,10 +84,11 @@ namespace AIPathfinding
 
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 		logAi->trace(
-			"Movement from tile %s is blocked. Try to bypass. Action: %d, blocker: %d",
+			"Movement from tile %s is blocked. Try to bypass. Action: %d, blocker: %d, source: %s",
 			destination.coord.toString(),
 			(int)destination.action,
-			(int)blocker);
+			(int)blocker,
+			source.coord.toString());
 #endif
 
 		auto destGuardians = cb->getGuardingCreatures(destination.coord);

+ 3 - 0
CI/linux/before_install.sh

@@ -3,6 +3,9 @@
 sudo apt-get update
 
 # Dependencies
+# In case of change in dependencies list please also update:
+# - developer docs at docs/developer/Building_Linux.md
+# - debian build settings at debian/control
 sudo apt-get install libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev libboost-iostreams-dev \
 libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
 qtbase5-dev \

+ 7 - 0
Mods/vcmi/config/vcmi/english.json

@@ -125,6 +125,13 @@
 	"vcmi.lobby.mod.state.version" : "Version mismatch",
 	"vcmi.lobby.mod.state.excessive" : "Must be disabled",
 	"vcmi.lobby.mod.state.missing" : "Not installed",
+	"vcmi.lobby.pvp.coin.hover" : "Coin",
+	"vcmi.lobby.pvp.coin.help" : "Flips a coin",
+	"vcmi.lobby.pvp.randomTown.hover" : "Random town",
+	"vcmi.lobby.pvp.randomTown.help" : "Write a random town in the chat",
+	"vcmi.lobby.pvp.randomTownVs.hover" : "Random town vs.",
+	"vcmi.lobby.pvp.randomTownVs.help" : "Write two random towns in the chat",
+	"vcmi.lobby.pvp.versus" : "vs.",
 
 	"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",

+ 133 - 6
Mods/vcmi/config/vcmi/german.json

@@ -63,7 +63,6 @@
 	"vcmi.mainMenu.serverClosing" : "Trenne...",
 	"vcmi.mainMenu.hostTCP" : "Hoste TCP/IP Spiel",
 	"vcmi.mainMenu.joinTCP" : "Trete TCP/IP Spiel bei",
-	"vcmi.mainMenu.playerName" : "Spieler",
 	
 	"vcmi.lobby.filepath" : "Dateipfad",
 	"vcmi.lobby.creationDate" : "Erstellungsdatum",
@@ -72,15 +71,79 @@
 	"vcmi.lobby.noPreview" : "Keine Vorschau",
 	"vcmi.lobby.noUnderground" : "Kein Untergrund",
 	"vcmi.lobby.sortDate" : "Ordnet Karten nach Änderungsdatum",
-
+	"vcmi.lobby.backToLobby" : "Zur Lobby zurückkehren",
+	
+	"vcmi.lobby.login.title" : "VCMI Online Lobby",
+	"vcmi.lobby.login.username" : "Benutzername:",
+	"vcmi.lobby.login.connecting" : "Verbinde...",
+	"vcmi.lobby.login.error" : "Verbindungsfehler: %s",
+	"vcmi.lobby.login.create" : "Neuer Account",
+	"vcmi.lobby.login.login" : "Login",
+	"vcmi.lobby.login.as" : "Login als %s",
+	"vcmi.lobby.header.rooms" : "Spielräume - %d",
+	"vcmi.lobby.header.channels" : "Chat Kanäle",
+	"vcmi.lobby.header.chat.global" : "Globaler Spiele-Chat - %s", // %s -> language name
+	"vcmi.lobby.header.chat.match" : "Chat vom vergangenen Spiel am %s", // %s -> game start date & time
+	"vcmi.lobby.header.chat.player" : "Privater Chat mit %s", // %s -> nickname of another player
+	"vcmi.lobby.header.history" : "Eure vorherigen Spiele",
+	"vcmi.lobby.header.players" : "Spieler online - %d",
+	"vcmi.lobby.match.solo" : "Einzelspieler-Spiel",
+	"vcmi.lobby.match.duel" : "Spiel mit %s", // %s -> nickname of another player
+	"vcmi.lobby.match.multi" : "%d Spieler",
+	"vcmi.lobby.room.create" : "Neuen Spiel-Raum erstellen",
+	"vcmi.lobby.room.players.limit" : "Spieler Limit",
+	"vcmi.lobby.room.description.public" : "Jeder Spieler kann dem öffentlichen Raum beitreten.",
+	"vcmi.lobby.room.description.private" : "Nur eingeladene Spieler können den privaten Raum betreten.",
+	"vcmi.lobby.room.description.new" : "Um das Spiel zu starten, wählen Sie ein Szenario oder eine zufällige Karte aus.",
+	"vcmi.lobby.room.description.load" : "Um das Spiel zu starten, verwenden Sie eines Ihrer gespeicherten Spiele.",
+	"vcmi.lobby.room.description.limit" : "Bis zu %d Spieler können deinen Raum betreten, einschließlich Ihnen.",
+	"vcmi.lobby.invite.header" : "Spieler einladen",
+	"vcmi.lobby.invite.notification" : "Der Spieler hat Euch in seinen Spielraum eingeladen. Ihr könnt nun seinem privaten Raum beitreten.",
+	"vcmi.lobby.preview.title" : "Spiel-Raum beitreten",
+	"vcmi.lobby.preview.subtitle" : "Spiel auf %s, gehostet von %s", //TL Note: 1) name of map or RMG template 2) nickname of game host
+	"vcmi.lobby.preview.version" : "Version des Spiels:",
+	"vcmi.lobby.preview.players" : "Spieler:",
+	"vcmi.lobby.preview.mods" : "Verwendete Mods:",
+	"vcmi.lobby.preview.allowed" : "Spiel-Raum beitreten?",
+	"vcmi.lobby.preview.error.header" : "Kann diesem Raum nicht beitreten.",
+	"vcmi.lobby.preview.error.playing" : "Ihr müsst zuerst euer aktuelles Spiel verlassen.",
+	"vcmi.lobby.preview.error.full" : "Der Raum ist bereits voll.",
+	"vcmi.lobby.preview.error.busy" : "Der Raum nimmt keine neuen Spieler mehr auf.",
+	"vcmi.lobby.preview.error.invite" : "Ihr wurdet nicht in diesen Raum eingeladen.",
+	"vcmi.lobby.preview.error.mods" : "Ihr verwendet andere Mods.",
+	"vcmi.lobby.preview.error.version" : "Ihr verwendet eine andere Version von VCMI.",
+	"vcmi.lobby.room.new" : "Neues Spiel",
+	"vcmi.lobby.room.load" : "Spiel laden",
+	"vcmi.lobby.room.type" : "Raumtyp",
+	"vcmi.lobby.room.mode" : "Spielmodus",
+	"vcmi.lobby.room.state.public" : "Öffentlich",
+	"vcmi.lobby.room.state.private" : "Privat",
+	"vcmi.lobby.room.state.busy" : "Im Spiel",
+	"vcmi.lobby.room.state.invited" : "Eingeladen",
+	"vcmi.lobby.mod.state.compatible" : "Kompatibel",
+	"vcmi.lobby.mod.state.disabled" : "Muss aktiviert sein",
+	"vcmi.lobby.mod.state.version" : "Version stimmt nicht überein",
+	"vcmi.lobby.mod.state.excessive" : "Muss deaktiviert sein",
+	"vcmi.lobby.mod.state.missing" : "Nicht installiert",
+	"vcmi.lobby.pvp.coin.hover" : "Münze",
+	"vcmi.lobby.pvp.coin.help" : "Wirft eine Münze",
+	"vcmi.lobby.pvp.randomTown.hover" : "Zufällige Stadt",
+	"vcmi.lobby.pvp.randomTown.help" : "Schreibe eine beliebige Stadt in den Chat",
+	"vcmi.lobby.pvp.randomTownVs.hover" : "Zufällige Stadt vs.",
+	"vcmi.lobby.pvp.randomTownVs.help" : "Schreibe zwei beliebige Städte in den Chat",
+	"vcmi.lobby.pvp.versus" : "vs.",
+
+	"vcmi.client.errors.invalidMap" : "{Ungültige Karte oder Kampagne}\n\nDas Spiel konnte nicht gestartet werden! Die ausgewählte Karte oder Kampagne ist möglicherweise ungültig oder beschädigt. Grund:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Fehlende Dateien}\n\nEs wurden keine Kampagnendateien gefunden! Möglicherweise verwendest du unvollständige oder beschädigte Heroes 3 Datendateien. Bitte installiere die Spieldaten neu.",
+	"vcmi.server.errors.disconnected" : "{Netzwerkfehler}\n\nDie Verbindung zum Spielserver wurde unterbrochen!",
 	"vcmi.server.errors.existingProcess" : "Es läuft ein weiterer vcmiserver-Prozess, bitte beendet diesen zuerst",
 	"vcmi.server.errors.modsToEnable"    : "{Erforderliche Mods um das Spiel zu laden}",
 	"vcmi.server.errors.modsToDisable"   : "{Folgende Mods müssen deaktiviert werden}",
-	"vcmi.server.confirmReconnect"       : "Mit der letzten Sitzung verbinden?",
 	"vcmi.server.errors.modNoDependency" : "Mod {'%s'} konnte nicht geladen werden!\n Sie hängt von Mod {'%s'} ab, die nicht aktiv ist!\n",
 	"vcmi.server.errors.modConflict" : "Mod {'%s'} konnte nicht geladen werden!\n Konflikte mit aktiver Mod {'%s'}!\n",
 	"vcmi.server.errors.unknownEntity" : "Spielstand konnte nicht geladen werden! Unbekannte Entität '%s' im gespeicherten Spiel gefunden! Der Spielstand ist möglicherweise nicht mit der aktuell installierten Version der Mods kompatibel!",
+	
+	"vcmi.dimensionDoor.seaToLandError" : "Es ist nicht möglich, mit einer Dimensionstür vom Meer zum Land oder umgekehrt zu teleportieren.",
 
 	"vcmi.settingsMainWindow.generalTab.hover" : "Allgemein",
 	"vcmi.settingsMainWindow.generalTab.help"     : "Wechselt zur Registerkarte Allgemeine Optionen, die Einstellungen zum allgemeinen Verhalten des Spielclients enthält.",
@@ -171,10 +234,10 @@
 	"vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Statistikfenster für Helden anzeigen",
 	"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Statistikfenster für Helden anzeigen}\n\nDauerhaftes Einschalten des Statistikfenster für Helden, das die primären Werte und Zauberpunkte anzeigt.",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Intro-Musik überspringen",
-	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Intro-Musik überspringen}\n\n Überspringe die kurze Musik, die zu Beginn eines jeden Kampfes gespielt wird, bevor die Action beginnt. Kann auch durch Drücken der ESC-Taste übersprungen werden.",
+	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Intro-Musik überspringen}\n\n Überspringe die kurze Musik, die zu Beginn eines jeden Kampfes gespielt wird, bevor die Action beginnt. Kann auch durch Drücken der ESC-Taste übersprungen werden.",	
 	"vcmi.battleOptions.endWithAutocombat.hover": "Kampf beenden",
 	"vcmi.battleOptions.endWithAutocombat.help": "{Kampf beenden}\n\nAutokampf spielt den Kampf sofort zu Ende",
-	
+
 	"vcmi.adventureMap.revisitObject.hover" : "Objekt erneut besuchen",
 	"vcmi.adventureMap.revisitObject.help" : "{Objekt erneut besuchen}\n\nSteht ein Held gerade auf einem Kartenobjekt, kann er den Ort erneut aufsuchen.",
 
@@ -319,7 +382,7 @@
 
 	"vcmi.optionsTab.extraOptions.hover" : "Extra Optionen",
 	"vcmi.optionsTab.extraOptions.help" : "Zusätzliche Einstellungen für das Spiel",
-	
+
 	"vcmi.optionsTab.cheatAllowed.hover" : "Cheats erlauben",
 	"vcmi.optionsTab.unlimitedReplay.hover" : "Unbegrenzte Kampfwiederholung",
 	"vcmi.optionsTab.cheatAllowed.help" : "{Cheats erlauben}\nErlaubt die Eingabe von Cheats während des Spiels.",
@@ -333,6 +396,7 @@
 	"vcmi.map.victoryCondition.collectArtifacts.message" : "Sammelt drei Artefakte",
 	"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Herzlichen Glückwunsch! Alle eure Feinde wurden besiegt und ihr habt die Engelsallianz! Der Sieg ist euer!",
 	"vcmi.map.victoryCondition.angelicAlliance.message" : "Besiege alle Feinde und gründe eine Engelsallianz",
+	"vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Leider habt Ihr einen Teil der Engelsallianz verloren. Alles ist verloren.",
 
 	// few strings from WoG used by vcmi
 	"vcmi.stackExperience.description" : "» D e t a i l s   z u r   S t a p e l e r f a h r u n g «\n\nKreatur-Typ ................... : %s\nErfahrungsrang ................. : %s (%i)\nErfahrungspunkte ............... : %i\nErfahrungspunkte für den nächsten Rang .. : %i\nMaximale Erfahrung pro Kampf ... : %i%% (%i)\nAnzahl der Kreaturen im Stapel .... : %i\nMaximale Anzahl neuer Rekruten\n ohne Verlust von aktuellem Rang .... : %i\nErfahrungs-Multiplikator ........... : %.2f\nUpgrade-Multiplikator .............. : %.2f\nErfahrung nach Rang 10 ........ : %i\nMaximale Anzahl der neuen Rekruten, die bei\n Rang 10 bei maximaler Erfahrung übrig sind : %i",
@@ -347,6 +411,69 @@
 	"vcmi.stackExperience.rank.8" : "Elite",
 	"vcmi.stackExperience.rank.9" : "Meister",
 	"vcmi.stackExperience.rank.10" : "Ass",
+	
+	// Strings for HotA Seer Hut / Quest Guards
+	"core.seerhut.quest.heroClass.complete.0" : "Ah, Ihr seid %s.  Hier ist ein Geschenk für Euch.  Nehmt Ihr es an?",
+	"core.seerhut.quest.heroClass.complete.1" : "Ah, Ihr seid %s.  Hier ist ein Geschenk für Euch.  Nehmt Ihr es an?",
+	"core.seerhut.quest.heroClass.complete.2" : "Ah, Ihr seid %s.  Hier ist ein Geschenk für Euch.  Nehmt Ihr es an?",
+	"core.seerhut.quest.heroClass.complete.3" : "Die Wachen bemerken, dass Ihr %s seid und bieten Euch an, Euch passieren zu lassen.  Nehmt Ihr das Angebot an?",
+	"core.seerhut.quest.heroClass.complete.4" : "Die Wachen bemerken, dass Ihr %s seid und bieten Euch an, Euch passieren zu lassen.  Nehmt Ihr das Angebot an?",
+	"core.seerhut.quest.heroClass.complete.5" : "Die Wachen bemerken, dass Ihr %s seid und bieten Euch an, Euch passieren zu lassen.  Nehmt Ihr das Angebot an?",
+	"core.seerhut.quest.heroClass.description.0" : "%s an %s senden",
+	"core.seerhut.quest.heroClass.description.1" : "%s an %s senden",
+	"core.seerhut.quest.heroClass.description.2" : "%s an %s senden",
+	"core.seerhut.quest.heroClass.description.3" : "%s zum Öffnen des Tores senden",
+	"core.seerhut.quest.heroClass.description.4" : "%s zum Öffnen des Tores senden",
+	"core.seerhut.quest.heroClass.description.5" : "%s zum Öffnen des Tores senden",
+	"core.seerhut.quest.heroClass.hover.0" : "(sucht den Helden der Klasse %s)",
+	"core.seerhut.quest.heroClass.hover.1" : "(sucht den Helden der Klasse %s)",
+	"core.seerhut.quest.heroClass.hover.2" : "(sucht den Helden der Klasse %s)",
+	"core.seerhut.quest.heroClass.hover.3" : "(sucht den Helden der Klasse %s)",
+	"core.seerhut.quest.heroClass.hover.4" : "(sucht den Helden der Klasse %s)",
+	"core.seerhut.quest.heroClass.hover.5" : "(sucht den Helden der Klasse %s)",
+	"core.seerhut.quest.heroClass.receive.0" : "Ich habe ein Geschenk für %s.",
+	"core.seerhut.quest.heroClass.receive.1" : "Ich habe ein Geschenk für %s.",
+	"core.seerhut.quest.heroClass.receive.2" : "Ich habe ein Geschenk für %s.",
+	"core.seerhut.quest.heroClass.receive.3" : "Die Wachleute hier sagen, dass sie nur %s passieren lassen werden.",
+	"core.seerhut.quest.heroClass.receive.4" : "Die Wachleute hier sagen, dass sie nur %s passieren lassen werden.",
+	"core.seerhut.quest.heroClass.receive.5" : "Die Wachleute hier sagen, dass sie nur %s passieren lassen werden.",
+	"core.seerhut.quest.heroClass.visit.0" : "Ihr seid nicht %s.  Ich habe nichts für Euch. Fort mit Euch!",
+	"core.seerhut.quest.heroClass.visit.1" : "Ihr seid nicht %s.  Ich habe nichts für Euch. Fort mit Euch!",
+	"core.seerhut.quest.heroClass.visit.2" : "Ihr seid nicht %s.  Ich habe nichts für Euch. Fort mit Euch!",
+	"core.seerhut.quest.heroClass.visit.3" : "Die Wachen hier lassen nur %s passieren.",
+	"core.seerhut.quest.heroClass.visit.4" : "Die Wachen hier lassen nur %s passieren.",
+	"core.seerhut.quest.heroClass.visit.5" : "Die Wachen hier lassen nur %s passieren.",
+	
+	"core.seerhut.quest.reachDate.complete.0" : "Ich bin jetzt frei.  Hier ist das, was ich für Euch habe.  Nehmt Ihr es an?",
+	"core.seerhut.quest.reachDate.complete.1" : "Ich bin jetzt frei.  Hier ist das, was ich für Euch habe.  Nehmt Ihr es an?",
+	"core.seerhut.quest.reachDate.complete.2" : "Ich bin jetzt frei.  Hier ist das, was ich für Euch habe.  Nehmt Ihr es an?",
+	"core.seerhut.quest.reachDate.complete.3" : "Es steht Ihnen frei, jetzt durchzugehen.  Wollt Ihr passieren?",
+	"core.seerhut.quest.reachDate.complete.4" : "Es steht Ihnen frei, jetzt durchzugehen.  Wollt Ihr passieren?",
+	"core.seerhut.quest.reachDate.complete.5" : "Es steht Ihnen frei, jetzt durchzugehen.  Wollt Ihr passieren?",
+	"core.seerhut.quest.reachDate.description.0" : "Wartet bis %s für %s",
+	"core.seerhut.quest.reachDate.description.1" : "Wartet bis %s für %s",
+	"core.seerhut.quest.reachDate.description.2" : "Wartet bis %s für %s",
+	"core.seerhut.quest.reachDate.description.3" : "Wartet bis %s, um das Tor zu öffnen",
+	"core.seerhut.quest.reachDate.description.4" : "Wartet bis %s, um das Tor zu öffnen",
+	"core.seerhut.quest.reachDate.description.5" : "Wartet bis %s, um das Tor zu öffnen",
+	"core.seerhut.quest.reachDate.hover.0" : "(Rückkehr nicht vor %s)",
+	"core.seerhut.quest.reachDate.hover.1" : "(Rückkehr nicht vor %s)",
+	"core.seerhut.quest.reachDate.hover.2" : "(Rückkehr nicht vor %s)",
+	"core.seerhut.quest.reachDate.hover.3" : "(Rückkehr nicht vor %s)",
+	"core.seerhut.quest.reachDate.hover.4" : "(Rückkehr nicht vor %s)",
+	"core.seerhut.quest.reachDate.hover.5" : "(Rückkehr nicht vor %s)",
+	"core.seerhut.quest.reachDate.receive.0" : "Ich bin beschäftigt.  Kommt nicht vor %s zurück",
+	"core.seerhut.quest.reachDate.receive.1" : "Ich bin beschäftigt.  Kommt nicht vor %s zurück",
+	"core.seerhut.quest.reachDate.receive.2" : "Ich bin beschäftigt.  Kommt nicht vor %s zurück",
+	"core.seerhut.quest.reachDate.receive.3" : "Geschlossen bis %s.",
+	"core.seerhut.quest.reachDate.receive.4" : "Geschlossen bis %s.",
+	"core.seerhut.quest.reachDate.receive.5" : "Geschlossen bis %s.",
+	"core.seerhut.quest.reachDate.visit.0" : "Ich bin beschäftigt.  Kommt nicht vor %s zurück.",
+	"core.seerhut.quest.reachDate.visit.1" : "Ich bin beschäftigt.  Kommt nicht vor %s zurück.",
+	"core.seerhut.quest.reachDate.visit.2" : "Ich bin beschäftigt.  Kommt nicht vor %s zurück.",
+	"core.seerhut.quest.reachDate.visit.3" : "Geschlossen bis %s.",
+	"core.seerhut.quest.reachDate.visit.4" : "Geschlossen bis %s.",
+	"core.seerhut.quest.reachDate.visit.5" : "Geschlossen bis %s.",
 
 	"core.bonus.ADDITIONAL_ATTACK.name": "Doppelschlag",
 	"core.bonus.ADDITIONAL_ATTACK.description": "Greift zweimal an",

+ 26 - 8
Mods/vcmi/config/vcmi/portuguese.json

@@ -99,6 +99,19 @@
 	"vcmi.lobby.room.description.limit" : "Até %d jogadores podem entrar na sua sala, incluindo você.",
 	"vcmi.lobby.invite.header" : "Convidar Jogadores",
 	"vcmi.lobby.invite.notification" : "O jogador te convidou para a sala de jogo dele. Agora você pode entrar na sala privada dele.",
+	"vcmi.lobby.preview.title" : "Entrar na Sala de Jogo",
+	"vcmi.lobby.preview.subtitle" : "Jogo em %s, hospedado por %s", //TL Note: 1) name of map or RMG template 2) nickname of game host
+	"vcmi.lobby.preview.version" : "Versão do jogo:",
+	"vcmi.lobby.preview.players" : "Jogadores:",
+	"vcmi.lobby.preview.mods" : "Mods utilizados:",
+	"vcmi.lobby.preview.allowed" : "Deseja entrar na sala de jogo?",
+	"vcmi.lobby.preview.error.header" : "Não foi possível entrar nesta sala.",
+	"vcmi.lobby.preview.error.playing" : "Você precisa sair do seu jogo atual primeiro.",
+	"vcmi.lobby.preview.error.full" : "A sala já está cheia.",
+	"vcmi.lobby.preview.error.busy" : "A sala não aceita mais novos jogadores.",
+	"vcmi.lobby.preview.error.invite" : "Você não foi convidado para esta sala.",
+	"vcmi.lobby.preview.error.mods" : "Você está usando um conjunto diferente de mods.",
+	"vcmi.lobby.preview.error.version" : "Você está usando uma versão diferente do VCMI.",
 	"vcmi.lobby.room.new" : "Novo Jogo",
 	"vcmi.lobby.room.load" : "Carregar Jogo",
 	"vcmi.lobby.room.type" : "Tipo de Sala",
@@ -107,6 +120,11 @@
 	"vcmi.lobby.room.state.private" : "Privada",
 	"vcmi.lobby.room.state.busy" : "Em Jogo",
 	"vcmi.lobby.room.state.invited" : "Convidado",
+	"vcmi.lobby.mod.state.compatible" : "Compatível",
+	"vcmi.lobby.mod.state.disabled" : "Deve estar ativado",
+	"vcmi.lobby.mod.state.version" : "Versão incompatível",
+	"vcmi.lobby.mod.state.excessive" : "Deve estar desativado",
+	"vcmi.lobby.mod.state.missing" : "Não instalado",
 
 	"vcmi.client.errors.invalidMap" : "{Mapa ou campanha inválido}\n\nFalha ao iniciar o jogo! O mapa ou campanha selecionado pode ser inválido ou corrompido. Motivo:\n%s",
 	"vcmi.client.errors.missingCampaigns" : "{Arquivos de dados ausentes}\n\nOs arquivos de dados das campanhas não foram encontrados! Você pode estar usando arquivos de dados incompletos ou corrompidos do Heroes 3. Por favor, reinstale os dados do jogo.",
@@ -335,13 +353,13 @@
 
 	"vcmi.optionsTab.simturns.select" : "Selecionar turnos simultâneos",
 	"vcmi.optionsTab.simturns.none" : "Sem turnos simultâneos",
-	"vcmi.optionsTab.simturns.tillContactMax" : "Turnos simultâneos: Até o contato",
-	"vcmi.optionsTab.simturns.tillContact1" : "Turnos simultâneos: 1 semana, interromper no contato",
-	"vcmi.optionsTab.simturns.tillContact2" : "Turnos simultâneos: 2 semanas, interromper no contato",
-	"vcmi.optionsTab.simturns.tillContact4" : "Turnos simultâneos: 1 mês, interromper no contato",
-	"vcmi.optionsTab.simturns.blocked1" : "Turnos simultâneos: 1 semana, contatos bloqueados",
-	"vcmi.optionsTab.simturns.blocked2" : "Turnos simultâneos: 2 semanas, contatos bloqueados",
-	"vcmi.optionsTab.simturns.blocked4" : "Turnos simultâneos: 1 mês, contatos bloqueados",
+	"vcmi.optionsTab.simturns.tillContactMax" : "Simultâneos até o contato",
+	"vcmi.optionsTab.simturns.tillContact1" : "Por 1 semana e parar no contato",
+	"vcmi.optionsTab.simturns.tillContact2" : "Por 2 semanas e parar no contato",
+	"vcmi.optionsTab.simturns.tillContact4" : "Por 1 mês e parar no contato",
+	"vcmi.optionsTab.simturns.blocked1" : "Por 1 semana, contatos bloqueados",
+	"vcmi.optionsTab.simturns.blocked2" : "Por 2 semanas, contatos bloqueados",
+	"vcmi.optionsTab.simturns.blocked4" : "Por 1 mês, contatos bloqueados",
 	
 	// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
 	// Using this information, VCMI will automatically select correct plural form for every possible amount
@@ -478,7 +496,7 @@
 	"core.bonus.DEFENSIVE_STANCE.description" : "+${val} de defesa ao se defender",
 	"core.bonus.DESTRUCTION.name" : "Destruição",
 	"core.bonus.DESTRUCTION.description" : "Tem ${val}% de chance de matar unidades extras após o ataque",
-	"core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Golpe de Morte",
+	"core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Golpe Mortal",
 	"core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Tem ${val}% de chance de causar o dobro do dano base ao atacar",
 	"core.bonus.DRAGON_NATURE.name" : "Dragão",
 	"core.bonus.DRAGON_NATURE.description" : "A criatura possui Natureza de Dragão",

+ 5 - 2
client/GameChatHandler.cpp

@@ -25,6 +25,8 @@
 #include "../lib/mapObjects/CArmedInstance.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/MetaString.h"
+#include "../lib/VCMI_Lib.h"
+#include "../lib/CGeneralTextHandler.h"
 
 const std::vector<GameChatMessage> & GameChatHandler::getChatHistory() const
 {
@@ -45,7 +47,9 @@ void GameChatHandler::sendMessageGameplay(const std::string & messageText)
 void GameChatHandler::sendMessageLobby(const std::string & senderName, const std::string & messageText)
 {
 	LobbyChatMessage lcm;
-	lcm.message = messageText;
+	MetaString txt;
+	txt.appendRawString(messageText);
+	lcm.message = txt;
 	lcm.playerName = senderName;
 	CSH->sendLobbyPack(lcm);
 	CSH->getGlobalLobby().sendMatchChatMessage(messageText);
@@ -104,4 +108,3 @@ void GameChatHandler::onNewSystemMessageReceived(const std::string & messageText
 	if(LOCPLINT && !settings["session"]["hideSystemMessages"].Bool())
 		LOCPLINT->cingconsole->addMessage(TextOperations::getCurrentFormattedTimeLocal(), "System", messageText);
 }
-

+ 3 - 0
client/HeroMovementController.cpp

@@ -205,6 +205,9 @@ void HeroMovementController::onTryMoveHero(const CGHeroInstance * hero, const Tr
 
 void HeroMovementController::onQueryReplyApplied()
 {
+	if (!waitingForQueryApplyReply)
+		return;
+
 	waitingForQueryApplyReply = false;
 
 	// Server accepted our TeleportDialog query reply and moved hero

+ 2 - 2
client/NetPacksClient.cpp

@@ -915,9 +915,9 @@ void ApplyClientNetPackVisitor::visitPackageApplied(PackageApplied & pack)
 void ApplyClientNetPackVisitor::visitSystemMessage(SystemMessage & pack)
 {
 	// usually used to receive error messages from server
-	logNetwork->error("System message: %s", pack.text);
+	logNetwork->error("System message: %s", pack.text.toString());
 
-	CSH->getGameChat().onNewSystemMessageReceived(pack.text);
+	CSH->getGameChat().onNewSystemMessageReceived(pack.text.toString());
 }
 
 void ApplyClientNetPackVisitor::visitPlayerBlocked(PlayerBlocked & pack)

+ 2 - 2
client/NetPacksLobbyClient.cpp

@@ -99,7 +99,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientD
 
 void ApplyOnLobbyScreenNetPackVisitor::visitLobbyChatMessage(LobbyChatMessage & pack)
 {
-	handler.getGameChat().onNewLobbyMessageReceived(pack.playerName, pack.message);
+	handler.getGameChat().onNewLobbyMessageReceived(pack.playerName, pack.message.toString());
 }
 
 void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack)
@@ -220,5 +220,5 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyShowMessage(LobbyShowMessage &
 		return;
 	
 	lobby->buttonStart->block(false);
-	handler.showServerError(pack.message);
+	handler.showServerError(pack.message.toString());
 }

+ 137 - 3
client/lobby/CSelectionBase.cpp

@@ -31,6 +31,7 @@
 #include "../widgets/Buttons.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/GraphicalPrimitiveCanvas.h"
+#include "../widgets/Images.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
@@ -43,12 +44,14 @@
 
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
+#include "../../lib/CTownHandler.h"
 #include "../../lib/CRandomGenerator.h"
 #include "../../lib/CThreadHelper.h"
 #include "../../lib/MetaString.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/mapping/CMapHeader.h"
 #include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/networkPacks/PacksForLobby.h"
 
 ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType)
 	: screenType(ScreenType)
@@ -137,6 +140,7 @@ InfoCard::InfoCard()
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
 	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
 	chat = std::make_shared<CChatBox>(Rect(18, 126, 335, 143));
+	pvpBox = std::make_shared<PvPBox>(Rect(17, 396, 338, 105));
 
 	buttonInvitePlayers = std::make_shared<CButton>(Point(20, 365), AnimationPath::builtin("pregameInvitePlayers"), CGI->generaltexth->zelp[105], [](){ CSH->getGlobalLobby().activateRoomInviteInterface(); } );
 	buttonOpenGlobalLobby = std::make_shared<CButton>(Point(188, 365), AnimationPath::builtin("pregameReturnToLobby"), CGI->generaltexth->zelp[105], [](){ CSH->getGlobalLobby().activateInterface(); });
@@ -292,15 +296,23 @@ void InfoCard::setChat(bool activateChat)
 			iconsLossCondition->disable();
 			labelLossConditionText->disable();
 			labelGroupPlayers->enable();
+			labelMapDiff->disable();
+			labelPlayerDifficulty->disable();
+			labelRating->disable();
+			labelDifficulty->disable();
+			labelDifficultyPercent->disable();
+			flagbox->disable();
+			iconDifficulty->disable();
+			mapDescription->disable();
+			chat->enable();
+			pvpBox->enable();
+			playerListBg->enable();
 		}
 		if (CSH->inLobbyRoom())
 		{
 			buttonInvitePlayers->enable();
 			buttonOpenGlobalLobby->enable();
 		}
-		mapDescription->disable();
-		chat->enable();
-		playerListBg->enable();
 	}
 	else
 	{
@@ -308,6 +320,7 @@ void InfoCard::setChat(bool activateChat)
 		buttonOpenGlobalLobby->disable();
 		mapDescription->enable();
 		chat->disable();
+		pvpBox->disable();
 		playerListBg->disable();
 
 		if(SEL->screenType == ESelectionScreen::campaignList)
@@ -324,6 +337,13 @@ void InfoCard::setChat(bool activateChat)
 			labelVictoryConditionText->enable();
 			labelLossConditionText->enable();
 			labelGroupPlayers->disable();
+			labelMapDiff->enable();
+			labelPlayerDifficulty->enable();
+			labelRating->enable();
+			labelDifficulty->enable();
+			labelDifficultyPercent->enable();
+			flagbox->enable();
+			iconDifficulty->enable();
 		}
 	}
 
@@ -373,6 +393,120 @@ void CChatBox::addNewMessage(const std::string & text)
 		chatHistory->slider->scrollToMax();
 }
 
+PvPBox::PvPBox(const Rect & rect)
+{
+	OBJ_CONSTRUCTION;
+	pos += rect.topLeft();
+	setRedrawParent(true);
+
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, rect.w, rect.h));
+	backgroundTexture->playerColored(PlayerColor(1));
+	backgroundBorder = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, rect.w, rect.h), ColorRGBA(0, 0, 0, 64), ColorRGBA(96, 96, 96, 255), 1);
+
+	townSelector = std::make_shared<TownSelector>(Point(5, 3));
+
+	auto getBannedTowns = [this](){
+		std::vector<FactionID> bannedTowns;
+		for(auto & town : townSelector->townsEnabled)
+			if(!town.second)
+				bannedTowns.push_back(town.first);
+		return bannedTowns;
+	};
+
+	buttonFlipCoin = std::make_shared<CButton>(Point(190, 6), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.pvp.coin.help")), [](){
+		LobbyPvPAction lpa;
+		lpa.action = LobbyPvPAction::COIN;
+		CSH->sendLobbyPack(lpa);
+	}, EShortcut::NONE);
+	buttonFlipCoin->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.coin.hover"), EFonts::FONT_SMALL, Colors::WHITE);
+
+	buttonRandomTown = std::make_shared<CButton>(Point(190, 31), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.pvp.randomTown.help")), [getBannedTowns](){
+		LobbyPvPAction lpa;
+		lpa.action = LobbyPvPAction::RANDOM_TOWN;
+		lpa.bannedTowns = getBannedTowns();
+		CSH->sendLobbyPack(lpa);
+	}, EShortcut::NONE);
+	buttonRandomTown->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.randomTown.hover"), EFonts::FONT_SMALL, Colors::WHITE);
+
+	buttonRandomTownVs = std::make_shared<CButton>(Point(190, 56), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.pvp.randomTownVs.help")), [getBannedTowns](){
+		LobbyPvPAction lpa;
+		lpa.action = LobbyPvPAction::RANDOM_TOWN_VS;
+		lpa.bannedTowns = getBannedTowns();
+		CSH->sendLobbyPack(lpa);
+	}, EShortcut::NONE);
+	buttonRandomTownVs->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.pvp.randomTownVs.hover"), EFonts::FONT_SMALL, Colors::WHITE);
+}
+
+TownSelector::TownSelector(const Point & loc)
+{
+	OBJ_CONSTRUCTION;
+	pos += loc;
+	setRedrawParent(true);
+
+	int count = 0;
+	for(auto const & factionID : VLC->townh->getDefaultAllowed())
+	{
+		townsEnabled[factionID] = true;
+		count++;
+	};
+
+	auto divisionRoundUp = [](int x, int y){ return (x + (y - 1)) / y; };
+
+	if(count > 9)
+	{
+		slider = std::make_shared<CSlider>(Point(144, 0), 96, std::bind(&TownSelector::sliderMove, this, _1), 3, divisionRoundUp(count, 3), 0, Orientation::VERTICAL, CSlider::BLUE);
+		slider->setPanningStep(24);
+		slider->setScrollBounds(Rect(-144, 0, slider->pos.x - pos.x + slider->pos.w, slider->pos.h));
+	}
+
+	updateListItems();
+}
+
+void TownSelector::updateListItems()
+{
+	OBJ_CONSTRUCTION;
+	int line = slider ? slider->getValue() : 0;
+	int x_offset = slider ? 0 : 8;
+	
+	towns.clear();
+	townsArea.clear();
+
+	int x = 0;
+	int y = 0;
+	CGI->factions()->forEach([this, &x, &y, line, x_offset](const Faction *entity, bool &stop){
+		if(!entity->hasTown())
+			return;
+
+		if(y >= line && (y - line) < 3)
+		{
+			FactionID factionID = entity->getFaction();
+			auto getImageIndex = [](FactionID factionID, bool enabled){ return (*CGI->townh)[factionID]->town->clientInfo.icons[true][!enabled] + 2; }; 
+			towns[factionID] = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), getImageIndex(factionID, townsEnabled[factionID]), 0, x_offset + 48 * x, 32 * (y - line));
+			townsArea[factionID] = std::make_shared<LRClickableArea>(Rect(x_offset + 48 * x, 32 * (y - line), 48, 32), [this, getImageIndex, factionID](){
+				townsEnabled[factionID] = !townsEnabled[factionID];
+				towns[factionID]->setFrame(getImageIndex(factionID, townsEnabled[factionID]));
+				redraw();
+			}, [factionID](){ CRClickPopup::createAndPush((*CGI->townh)[factionID]->town->faction->getNameTranslated()); });
+		}
+
+		if (x < 2)
+			x++;
+		else
+		{
+			x = 0;
+			y++;
+		}
+	});
+}
+
+void TownSelector::sliderMove(int slidPos)
+{
+	if(!slider)
+		return; // ignore spurious call when slider is being created
+	updateListItems();
+	redraw();
+}
+
 CFlagBox::CFlagBox(const Rect & rect)
 	: CIntObject(SHOW_POPUP)
 {

+ 36 - 0
client/lobby/CSelectionBase.h

@@ -31,10 +31,15 @@ class ExtraOptionsTab;
 class SelectionTab;
 class InfoCard;
 class CChatBox;
+class PvPBox;
+class TownSelector;
 class CLabel;
+class CSlider;
 class CFlagBox;
 class CLabelGroup;
 class TransparentFilledRectangle;
+class FilledTexturePlayerColored;
+class LRClickableArea;
 
 class ISelectionScreenInfo
 {
@@ -107,6 +112,8 @@ class InfoCard : public CIntObject
 	std::shared_ptr<CLabelGroup> labelGroupPlayers;
 	std::shared_ptr<CButton> buttonInvitePlayers;
 	std::shared_ptr<CButton> buttonOpenGlobalLobby;
+
+	std::shared_ptr<PvPBox> pvpBox;
 public:
 
 	bool showChat;
@@ -136,6 +143,35 @@ public:
 	void addNewMessage(const std::string & text);
 };
 
+class PvPBox : public CIntObject
+{
+	std::shared_ptr<FilledTexturePlayerColored> backgroundTexture;
+	std::shared_ptr<TransparentFilledRectangle> backgroundBorder;
+	
+	std::shared_ptr<TownSelector> townSelector;
+
+	std::shared_ptr<CButton> buttonFlipCoin;
+	std::shared_ptr<CButton> buttonRandomTown;
+	std::shared_ptr<CButton> buttonRandomTownVs;
+public:
+	PvPBox(const Rect & rect);
+};
+
+class TownSelector : public CIntObject
+{
+	std::map<FactionID, std::shared_ptr<CAnimImage>> towns;
+	std::map<FactionID, std::shared_ptr<LRClickableArea>> townsArea;
+	std::shared_ptr<CSlider> slider;
+
+	void sliderMove(int slidPos);
+	void updateListItems();
+
+public:
+	std::map<FactionID, bool> townsEnabled;
+
+	TownSelector(const Point & loc);
+};
+
 class CFlagBox : public CIntObject
 {
 	std::shared_ptr<CAnimation> iconsTeamFlags;

+ 1 - 1
client/mainmenu/CPrologEpilogVideo.cpp

@@ -30,7 +30,7 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f
 	updateShadow();
 
 	auto audioData = CCS->videoh->getAudio(spe.prologVideo);
-	videoSoundHandle = CCS->soundh->playSound(audioData);
+	videoSoundHandle = CCS->soundh->playSound(audioData, -1);
 	CCS->videoh->open(spe.prologVideo);
 	CCS->musich->playMusic(spe.prologMusic, true, true);
 	voiceDurationMilliseconds = CCS->soundh->getSoundDurationMilliseconds(spe.prologVoice);

+ 4 - 1
client/mapView/MapViewController.cpp

@@ -277,6 +277,9 @@ bool MapViewController::isEventInstant(const CGObjectInstance * obj, const Playe
 	if (!isEventVisible(obj, initiator))
 		return true;
 
+	if (!initiator.isValidPlayer())
+		return true; // skip effects such as new monsters on new month
+
 	if(initiator != LOCPLINT->playerID && settings["adventure"]["enemyMoveTime"].Float() <= 0)
 		return true; // instant movement speed
 
@@ -298,7 +301,7 @@ bool MapViewController::isEventVisible(const CGObjectInstance * obj, const Playe
 		return false;
 
 	// do not focus on actions of other players except for AI with simturns off
-	if (initiator != LOCPLINT->playerID)
+	if (initiator != LOCPLINT->playerID && initiator.isValidPlayer())
 	{
 		if (LOCPLINT->makingTurn)
 			return false;

+ 1 - 1
client/windows/CMarketWindow.cpp

@@ -203,7 +203,7 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI
 	background = createBg(ImagePath::builtin("TPMRKASS.bmp"), PLAYER_COLORED);
 	// Create image that copies part of background containing slot MISC_1 into position of slot MISC_5
 	artSlotBack = std::make_shared<CPicture>(background->getSurface(), Rect(20, 187, 47, 47), 0, 0);
-	artSlotBack->moveTo(Point(358, 443));
+	artSlotBack->moveTo(pos.topLeft() + Point(18, 339));
 	auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero);
 	artSets.clear();
 	addSetAndCallbacks(artsSellingMarket->getAOHset());

+ 3 - 5
config/filesystem.json

@@ -18,8 +18,8 @@
 		"SPRITES/":
 		[
 			{"type" : "lod", "path" : "Data/H3ab_spr.lod"}, // Contains H3:AB data
-			{"type" : "lod", "path" : "Data/H3sprite.lod"}, // Localized versions only, contains H3:AB patch data
-			{"type" : "lod", "path" : "Data/h3abp_sp.lod"}, // Contains H3:SoD data (overrides H3:AB data)
+			{"type" : "lod", "path" : "Data/h3abp_sp.lod"}, // Localized versions only, contains H3:AB patch data
+			{"type" : "lod", "path" : "Data/H3sprite.lod"}, // Contains H3:SoD data (overrides H3:AB data)
 //			{"type" : "lod", "path" : "Data/H3psprit.lod"}, // Localized versions only, contains H3:SoD patch data. Unused? Has corrupted data, e.g. lock icon for artifacts
 			{"type" : "dir",  "path" : "Sprites"}
 		],
@@ -27,9 +27,7 @@
 		[
 			{"type" : "snd", "path" : "Data/H3ab_ahd.snd"},
 			{"type" : "snd", "path" : "Data/Heroes3.snd"},
-			{"type" : "snd", "path" : "Data/Heroes3-cd2.snd"},
-			//WoG have overridden sounds with .82m extension in Data
-			{"type" : "dir",  "path" : "Data", "depth": 0}
+			{"type" : "snd", "path" : "Data/Heroes3-cd2.snd"}
 		],
 		"MUSIC/":
 		[

+ 1 - 1
debian/control

@@ -2,7 +2,7 @@ Source: vcmi
 Section: games
 Priority: optional
 Maintainer: Ivan Savenko <[email protected]>
-Build-Depends: debhelper (>= 8), cmake, libsdl2-dev, libsdl2-image-dev, libsdl2-ttf-dev, libsdl2-mixer-dev, zlib1g-dev, libavformat-dev, libswscale-dev, libboost-dev (>=1.48), libboost-program-options-dev (>=1.48), libboost-filesystem-dev (>=1.48), libboost-system-dev (>=1.48), libboost-locale-dev (>=1.48), libboost-thread-dev (>=1.48), qtbase5-dev, libtbb-dev, libfuzzylite-dev, libminizip-dev, libluajit-5.1-dev, qttools5-dev
+Build-Depends: debhelper (>= 8), cmake, libsdl2-dev, libsdl2-image-dev, libsdl2-ttf-dev, libsdl2-mixer-dev, zlib1g-dev, libavformat-dev, libswscale-dev, libboost-dev (>=1.48), libboost-program-options-dev (>=1.48), libboost-filesystem-dev (>=1.48), libboost-system-dev (>=1.48), libboost-locale-dev (>=1.48), libboost-thread-dev (>=1.48), libboost-iostreams-dev, qtbase5-dev, libtbb-dev, libfuzzylite-dev, libminizip-dev, libluajit-5.1-dev, qttools5-dev, liblzma-dev
 Standards-Version: 3.9.1
 Homepage: http://vcmi.eu
 Vcs-Git: git://github.com/vcmi/vcmi.git

+ 0 - 2
docs/developers/AI.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) /  AI Description
-
 There are two types of AI: adventure and battle.
 
 **Adventure AIs** are responsible for moving heroes across the map and developing towns  

+ 0 - 2
docs/developers/Bonus_System.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Bonus System
-
 The bonus system of VCMI is a set of mechanisms that make handling of different bonuses for heroes, towns, players and units easier. The system consists of a set of nodes representing objects that can be a source or a subject of a bonus and two directed acyclic graphs (DAGs) representing inheritance and propagation of bonuses. Core of bonus system is defined in HeroBonus.h file.
 
 ## Propagation and inheritance

+ 0 - 2
docs/developers/Building_Android.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Building for Android
-
 The following instructions apply to **v1.2 and later**. For earlier versions the best documentation is https://github.com/vcmi/vcmi-android/blob/master/building.txt (and reading scripts in that repo), however very limited to no support will be provided from our side if you wish to go down that rabbit hole.
 
 *Note*: building has been tested only on Linux and macOS. It may or may not work on Windows out of the box.

+ 0 - 2
docs/developers/Building_Linux.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Building on Linux
-
 # Compiling VCMI
 
 - Current baseline requirement for building is Ubuntu 20.04

+ 0 - 2
docs/developers/Building_Windows.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Building on Windows
-
 # Preparations
 Windows builds can be made in more than one way and with more than one tool. This guide focuses on the simplest building process using Microsoft Visual Studio 2022
 

+ 0 - 2
docs/developers/Building_iOS.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Building for iOS
-
 ## Requirements
 
 1. **macOS**

+ 0 - 2
docs/developers/Building_macOS.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Building on macOS
-
 # Requirements
 
 1. C++ toolchain, either of:

+ 0 - 2
docs/developers/Code_Structure.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Code Structure
-
 The code of VCMI is divided into several main parts: client, server, lib and AIs, each one in a separate binary file.
 
 # The big picture

+ 0 - 2
docs/developers/Coding_Guidelines.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Coding Guidelines
-
 ## C++ Standard
 
 VCMI implementation bases on C++17 standard. Any feature is acceptable as long as it's will pass build on our CI, but there is list below on what is already being used.

+ 0 - 2
docs/developers/Conan.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Using Conan
-
 # Using dependencies from Conan
 
 [Conan](https://conan.io/) is a package manager for C/C++. We provide prebuilt binary dependencies for some platforms that are used by our CI, but they can also be consumed by users to build VCMI. However, it's not required to use only the prebuilt binaries: you can build them from source as well.

+ 0 - 2
docs/developers/Development_with_Qt_Creator.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Development with Qt Creator
-
 # Introduction to Qt Creator
 
 Qt Creator is the recommended IDE for VCMI development on Linux distributions, but it may be used on other operating systems as well. It has the following advantages compared to other IDEs:

+ 0 - 2
docs/developers/Logging_API.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) /  Logging System
-
 # Features
 
 -   A logger belongs to a "domain", this enables us to change log level settings more selectively

+ 0 - 2
docs/developers/Lua_Scripting_System.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Lua Scripting System
-
 # Configuration
 
 ``` javascript

+ 0 - 2
docs/developers/Networking.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Networking
-
 # The big picture
 
 For implementation details see files located at `lib/network` directory.

+ 0 - 2
docs/developers/Serialization.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Serialization System
-
 # Introduction
 
 The serializer translates between objects living in our code (like int or CGameState\*) and stream of bytes. Having objects represented as a stream of bytes is useful. Such bytes can send through the network connection (so client and server can communicate) or written to the disk (savegames).

+ 0 - 2
docs/maintainers/Project_Infrastructure.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Project Infrastructure
-
 # Project Infrastructure
 
 This section hold important information about project infrastructure for current and future contributors. At moment it's all maintained by me (SXX), but following information will be useful if someone going to replace me in future.

+ 0 - 2
docs/maintainers/Release_Process.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Release Process
-
 # Versioning
 For releases VCMI uses version numbering in form "1.X.Y", where:
 - 'X' indicates major release. Different major versions are generally not compatible with each other. Save format is different, network protocol is different, mod format likely different.

+ 0 - 2
docs/maintainers/Ubuntu_PPA.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Ubuntu PPA
-
 ## Main links
 - [Team](https://launchpad.net/~vcmi)
 - [Project](https://launchpad.net/vcmi)

+ 0 - 2
docs/modders/Animation_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Animation Format
-
 VCMI allows overriding HoMM3 .def files with .json replacement. Compared to .def this format allows:
 
 -   Overriding individual frames from json file (e.g. icons)

+ 0 - 2
docs/modders/Bonus/Bonus_Duration_Types.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Bonus Format](../Bonus_Format.md) / Bonus Duration Types
-
 Bonus may have any of these durations. They acts in disjunction.
 
 ## List of all bonus duration types

+ 0 - 2
docs/modders/Bonus/Bonus_Limiters.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Bonus Format](../Bonus_Format.md) / Bonus Limiters
-
 ## Predefined Limiters
 
 The limiters take no parameters:

+ 0 - 2
docs/modders/Bonus/Bonus_Propagators.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Bonus Format](../Bonus_Format.md) / Bonus Propagators
-
 ## Available propagators
 
 -   BATTLE_WIDE: Affects both sides during battle

+ 0 - 2
docs/modders/Bonus/Bonus_Range_Types.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Bonus Format](../Bonus_Format.md) / Bonus Range Types
-
 # List of all Bonus range types
 
 - NO_LIMIT

+ 0 - 2
docs/modders/Bonus/Bonus_Sources.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Bonus Format](../Bonus_Format.md) / Bonus Sources
-
 # List of all possible bonus sources
 
 - ARTIFACT

+ 0 - 2
docs/modders/Bonus/Bonus_Types.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Bonus Format](../Bonus_Format.md) / Bonus Types
-
 The bonuses were grouped according to their original purpose. The bonus system allows them to propagate freely betwen the nodes, however they may not be recognized properly beyond the scope of original use.
 
 # General-purpose bonuses

+ 0 - 2
docs/modders/Bonus/Bonus_Updaters.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Bonus Format](../Bonus_Format.md) / Bonus Updaters
-
 TODO: this page may be incorrect or outdated
 
 # List of Bonus Updaters

+ 0 - 2
docs/modders/Bonus/Bonus_Value_Types.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Bonus Format](../Bonus_Format.md) / Bonus Value Types
-
 Total value of Bonus is calculated using the following:
 
 -   For each bonus source type we calculate new source value (for all bonus value types except PERCENT_TO_SOURCE and PERCENT_TO_TARGET_TYPE) using the following:

+ 0 - 2
docs/modders/Bonus_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Bonus Format
-
 ## Full format
 
 All parameters but type are optional.

+ 0 - 2
docs/modders/Building_Bonuses.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Building Bonuses
-
 Work-in-progress page do describe all bonuses provided by town buildings
 for future configuration.
 

+ 0 - 2
docs/modders/Campaign_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Campaign Format
-
 # Introduction
 
 Starting from version 1.3, VCMI supports its own campaign format.

+ 0 - 2
docs/modders/Configurable_Widgets.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Configurable Widgets
-
 # Table of contents
 
 - [Introduction](#introduction)

+ 0 - 3
docs/modders/Difficulty.md

@@ -1,6 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Difficulty
-
-
 Since VCMI 1.4.0 there are more capabilities to configure difficulty parameters.
 It means, that modders can give different bonuses to AI or human players depending on selected difficulty
 

+ 0 - 2
docs/modders/Entities_Format/Artifact_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Artifact Format
-
 Artifact bonuses use [Bonus Format](../Bonus_Format.md)
 
 ## Required data

+ 0 - 2
docs/modders/Entities_Format/Battle_Obstacle_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Battle Obstacle Format
-
 ```jsonc
 	// List of terrains on which this obstacle can be used
 	"allowedTerrains" : []

+ 0 - 2
docs/modders/Entities_Format/Battlefield_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Battlefield Format
-
 ```jsonc
 	// Human-readable name of the battlefield
 	"name" : ""

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

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Creature Format
-
 ## Required data
 
 In order to make functional creature you also need:

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

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Faction Format
-
 ## Required data
 
 In order to make functional town you also need:

+ 0 - 2
docs/modders/Entities_Format/Hero_Class_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Hero Class Format
-
 ## Required data
 
 In order to make functional hero class you also need:

+ 0 - 2
docs/modders/Entities_Format/Hero_Type_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Hero Type Format
-
 ## Required data
 
 In order to make functional hero you also need:

+ 0 - 2
docs/modders/Entities_Format/River_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / River Format
-
 ## Format
 
 ```jsonc

+ 0 - 2
docs/modders/Entities_Format/Road_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Road Format
-
 ## Format
 
 ```jsonc

+ 0 - 2
docs/modders/Entities_Format/Secondary_Skill_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Secondary Skill Format
-
 ## Main format
 
 ```jsonc

+ 0 - 2
docs/modders/Entities_Format/Spell_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Spell Format
-
 # Main format
 
 ``` javascript

+ 0 - 2
docs/modders/Entities_Format/Terrain_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / Entities Format / Terrain Format
-
 ## Format
 
 ```jsonc

+ 0 - 2
docs/modders/Game_Identifiers.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Game Identifiers
-
 ## List of all game identifiers
 
 This is a list of all game identifiers available to modders. Note that only identifiers from base game have been included. For identifiers from mods please look up corresponding mod

+ 0 - 2
docs/modders/Map_Editor.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Map Editor
-
 # Interface
 
 <img width="738" src="https://user-images.githubusercontent.com/9308612/188775648-8551107d-9f0b-4743-8980-56c2c1c58bbc.png">

+ 0 - 2
docs/modders/Map_Object_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Map Object Format
-
 ## Description
 
 Full object consists from 3 parts:

+ 0 - 2
docs/modders/Map_Objects/Boat.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Map Object Format](../Map_Object_Format.md) / Boat
-
 ``` javascript
 {
 	// Layer on which this boat moves. Possible values:

+ 0 - 2
docs/modders/Map_Objects/Creature_Bank.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Map Object Format](../Map_Object_Format.md) / Creature Bank
-
 Reward types for clearing creature bank are limited to resources, creatures, artifacts and spell.
 Format of rewards is same as in [Rewardable Objects](Rewardable.md)
 

+ 0 - 2
docs/modders/Map_Objects/Dwelling.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Map Object Format](../Map_Object_Format.md) / Dwelling
-
 ``` javascript
 {
 	/// List of creatures in this bank. Each list represents one "level" of bank

+ 0 - 2
docs/modders/Map_Objects/Market.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Map Object Format](../Map_Object_Format.md) / Market
-
 # Market schema
 
 Since VCMI-1.3 it's possible to create customizable markets on adventure map.

+ 0 - 2
docs/modders/Map_Objects/Rewardable.md

@@ -1,5 +1,3 @@
-< [Documentation](../../Readme.md) / [Modding](../Readme.md) / [Map Object Format](../Map_Object_Format.md) / Rewardable
-
 ## Table of Contents
 - [Base object definition](#base-object-definition)
 - [Configurable object definition](#configurable-object-definition)

+ 0 - 2
docs/modders/Mod_File_Format.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Mod File Format
-
 ## Fields with description of mod
 
 ``` javascript

+ 0 - 2
docs/modders/Random_Map_Template.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Random Map Template Format
-
 ## Template format
 
 ``` javascript

+ 0 - 2
docs/modders/Readme.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Modding
-
 # Creating mod
 
 To make your own mod you need to create subdirectory in **<data dir>/Mods/** with name that will be used as identifier for your mod.

+ 6 - 2
docs/modders/Translations.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / [Modding](Readme.md) / Translations
-
 # Translation system
 
 ## List of currently supported languages
@@ -24,6 +22,12 @@ This is list of all languages that are currently supported by VCMI. If your lang
 - Ukrainian
 - Vietnamese
 
+## Progress of the translations
+You can see the current progress of the different translations here:
+[Translation progress](https://github.com/vcmi/vcmi-translation-status)
+
+The page will be automatically updated once a week.
+
 ## Translating Heroes III data
 
 VCMI allows translating game data into languages other than English. In order to translate Heroes III in your language easiest approach is to:

+ 0 - 2
docs/players/Bug_Reporting_Guidelines.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Bug Reporting Guidelines
-
 First of all, thanks for your support! If you report a bug we can fix it. But keep in mind that reporting your bugs appropriately makes our (developers') lifes easier. Here are a few guidelines that will help you write good bug reports.
 
 # Github bugtracker

+ 0 - 2
docs/players/Cheat_Codes.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Cheat Codes
-
 # Cheat Codes
 
 Similar to H3, VCMI provides cheat codes to make testing game more convenient.

+ 0 - 2
docs/players/Game_Mechanics.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Game Mechanics
-
 # List of features added in VCMI
 
 ## High resolutions

+ 0 - 2
docs/players/Installation_Android.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Installation on Android
-
 ## Step 1: Download and install VCMI
 
 **This app requires original heroes 3 sod / complete files to operate, they are not supplied with this installer. it is recommended to purchase version from gog.com. Heroes 3 "hd edition" (steam version) files are not supported !!!**  

+ 0 - 2
docs/players/Installation_Linux.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Installation on Linux
-
 VCMI requires data from original Heroes 3: Shadow of Death or Complete editions. Data from native Linux version made by LOKI will not work.
 
 # Step 1: Binaries installation

+ 0 - 2
docs/players/Installation_Windows.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Installation on Windows
-
 # Prerequisites
 
 As of VCMI 1.2 and newer Windows 10 or newer is required since our automated system uses elements incompatible with older Windows.

+ 0 - 2
docs/players/Installation_iOS.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Installation on iOS
-
 You can run VCMI on iOS 12.0 and later, all devices are supported. If you wish to run on iOS 10 or 11, you should build from source, see [How to build VCMI (iOS)](../developers/Building_iOS.md).
 
 ## Step 1: Download and install VCMI

+ 0 - 2
docs/players/Installation_macOS.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Installation on macOS
-
 **For iOS installation look here: (Installation on iOS)[Installation_iOS.md]
 
 # Step 1: Download and install VCMI

+ 0 - 2
docs/players/Privacy_Policy.md

@@ -1,5 +1,3 @@
-< [Documentation](../Readme.md) / Privacy Policy
-
 **Last Updated: 24th December, 2022**
 
 ### Glossary

+ 11 - 9
launcher/firstLaunch/firstlaunch_moc.cpp

@@ -311,13 +311,9 @@ void FirstLaunchView::extractGogData()
 	if(fileBin.isEmpty())
 		return;
 
-	ui->pushButtonGogInstall->setText(tr("Installing... Please wait!"));
-	QPalette pal = ui->pushButtonGogInstall->palette();
-	pal.setColor(QPalette::Button, QColor(Qt::yellow));
-	ui->pushButtonGogInstall->setAutoFillBackground(true);
-	ui->pushButtonGogInstall->setPalette(pal);
-	ui->pushButtonGogInstall->update();
-	ui->pushButtonGogInstall->repaint();
+	ui->progressBarGog->setVisible(true);
+	ui->pushButtonGogInstall->setVisible(false);
+	setEnabled(false);
 
 	QTimer::singleShot(100, this, [this, fileExe, fileBin](){ // background to make sure FileDialog is closed...
 		QTemporaryDir dir;
@@ -340,14 +336,20 @@ void FirstLaunchView::extractGogData()
 			o.filenames.set_expand(true);
 
 			o.preserve_file_times = true; // also correctly closes file -> without it: on Windows the files are not written completly
+			
+			process_file(tmpFileExe.toStdString(), o, [this](float progress) {
+				ui->progressBarGog->setValue(progress * 100);
+				qApp->processEvents();
+			});
 
-			process_file(tmpFileExe.toStdString(), o);
+			ui->progressBarGog->setVisible(false);
+			ui->pushButtonGogInstall->setVisible(true);
+			setEnabled(true);
 
 			QStringList dirData = tempDir.entryList({"data"}, QDir::Filter::Dirs);
 			if(dirData.empty() || QDir(tempDir.filePath(dirData.front())).entryList({"*.lod"}, QDir::Filter::Files).empty())
 			{
 				QMessageBox::critical(this, tr("No Heroes III data!"), tr("Selected files do not contain Heroes III data!"), QMessageBox::Ok, QMessageBox::Ok);
-				ui->pushButtonGogInstall->setText(tr("Install gog.com files"));
 				return;
 			}
 			copyHeroesData(dir.path(), true);

+ 28 - 0
launcher/firstLaunch/firstlaunch_moc.ui

@@ -289,6 +289,34 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
            </property>
           </widget>
          </item>
+         <item row="4" column="3">
+          <widget class="QProgressBar" name="progressBarGog">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="maximum">
+             <number>100</number>
+            </property>
+            <property name="value">
+             <number>0</number>
+            </property>
+            <property name="textVisible">
+             <bool>true</bool>
+            </property>
+            <property name="invertedAppearance">
+             <bool>false</bool>
+            </property>
+            <property name="format">
+             <string>Installing... %p%</string>
+            </property>
+            <property name="visible">
+              <bool>false</bool>
+            </property>
+           </widget>
+         </item>
          <item row="2" column="3">
           <widget class="QPushButton" name="pushButtonDataSearch">
            <property name="sizePolicy">

+ 1 - 1
launcher/lib/innoextract

@@ -1 +1 @@
-Subproject commit 46adb5762331aa3f19113fe09c72fc2d37caf90c
+Subproject commit 9977089412ebafe9f79936aa65a2edf16a84ae3e

+ 64 - 60
launcher/translation/german.ts

@@ -254,7 +254,7 @@
     <message>
         <location filename="../modManager/cmodlistview_moc.ui" line="373"/>
         <source>Install from file</source>
-        <translation type="unfinished"></translation>
+        <translation>Datei installieren</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>bitte Mod upgraden</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>Mod Verzeichnis Index</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="312"/>
         <source>or newer</source>
-        <translation type="unfinished"></translation>
+        <translation>oder neuer</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>Alle unterstützten Dateien</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>Maps</source>
-        <translation type="unfinished">Karten</translation>
+        <translation>Karten</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>Campaigns</source>
-        <translation type="unfinished"></translation>
+        <translation>Kampagnen</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>Configs</source>
-        <translation type="unfinished"></translation>
+        <translation>Konfigurationen</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="627"/>
         <source>Mods</source>
-        <translation type="unfinished">Mods</translation>
+        <translation>Mods</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>Wähle Dateien (Konfigurationen, Mods, Karten, Kampagnen) zum installieren...</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
         <source>Replace config file?</source>
-        <translation type="unfinished"></translation>
+        <translation>Konfigurationsdatei ersetzen?</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="654"/>
         <source>Do you want to replace %1?</source>
-        <translation type="unfinished"></translation>
+        <translation>Soll %1 ersetzt werden?</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="693"/>
@@ -505,7 +505,7 @@ Installation erfolgreich heruntergeladen?</translation>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="960"/>
         <source>screenshots</source>
-        <translation type="unfinished"></translation>
+        <translation>Screenshots</translation>
     </message>
     <message>
         <location filename="../modManager/cmodlistview_moc.cpp" line="966"/>
@@ -523,95 +523,96 @@ Installation erfolgreich heruntergeladen?</translation>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="160"/>
         <source>Can not install submod</source>
-        <translation type="unfinished"></translation>
+        <translation>Submod kann nicht installiert werden</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="163"/>
         <source>Mod is already installed</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod ist bereits installiert</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="172"/>
         <source>Can not uninstall submod</source>
-        <translation type="unfinished"></translation>
+        <translation>Submod kann nicht deinstalliert werden</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="175"/>
         <source>Mod is not installed</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod ist nicht installiert</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="185"/>
         <source>Mod is already enabled</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod ist bereits aktiviert</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 muss zuerst installiert werden</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 ist nicht kompatibel, bitte aktualisieren Sie VCMI und überprüfen Sie die neuesten Mod-Versionen</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="197"/>
         <source>Required mod %1 is missing</source>
-        <translation type="unfinished"></translation>
+        <translation>Der erforderliche Mod %1 fehlt</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="202"/>
         <source>Required mod %1 is not enabled</source>
-        <translation type="unfinished"></translation>
+        <translation>Erforderliche Mod %1 ist nicht aktiviert</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>Diese Mod steht im Konflikt mit %1</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="228"/>
         <source>Mod is already disabled</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod ist bereits deaktiviert</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="238"/>
         <source>This mod is needed to run %1</source>
-        <translation type="unfinished"></translation>
+        <translation>Diese Mod wird benötigt, um %1 auszuführen</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="280"/>
         <source>Mod archive is missing</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod-Archiv fehlt</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 mit diesem Namen ist bereits installiert</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="288"/>
         <source>Mod archive is invalid or corrupted</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod-Archiv ist ungültig oder beschädigt</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="314"/>
         <source>Failed to extract mod data</source>
-        <translation type="unfinished"></translation>
+        <translation>Mod-Daten konnten nicht extrahiert werden</translation>
     </message>
     <message>
         <location filename="../modManager/cmodmanager.cpp" line="342"/>
         <source>Data with this mod was not found</source>
-        <translation type="unfinished"></translation>
+        <translation>Daten mit dieser Mod wurden nicht gefunden</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 befindet sich im geschützten Verzeichnis, bitte entfernen Sie sie manuell:
+</translation>
     </message>
 </context>
 <context>
@@ -716,7 +717,7 @@ Installation erfolgreich heruntergeladen?</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 @@ Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwend
     <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 wird derzeit nicht unterstützt!</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>gog.com Dateien installieren</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="362"/>
@@ -1020,7 +1021,7 @@ Heroes III: HD Edition wird derzeit nicht unterstützt!</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>Wenn Sie kein Exemplar von Heroes III installiert haben, kann VCMI Ihre Heroes III-Daten mit dem Offline-Installationsprogramm von gog.com importieren.</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.ui" line="384"/>
@@ -1071,64 +1072,64 @@ Heroes III: HD Edition wird derzeit nicht unterstützt!</translation>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="148"/>
         <source>Heroes III installation found!</source>
-        <translation type="unfinished"></translation>
+        <translation>Heroes III-Installation gefunden!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="148"/>
         <source>Copy data to VCMI folder?</source>
-        <translation type="unfinished"></translation>
+        <translation>Daten in den VCMI-Ordner kopieren?</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>%1 Datei auswählen...</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>Sie müssen %1 Datei auswählen!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="292"/>
         <source>GOG file (*.*)</source>
-        <translation type="unfinished"></translation>
+        <translation>GOG Datei (*.*)</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="293"/>
         <source>File selection</source>
-        <translation type="unfinished"></translation>
+        <translation>Dateiauswahl</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="300"/>
         <source>Invalid file selected</source>
-        <translation type="unfinished"></translation>
+        <translation>Ungültige Datei ausgewählt</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="307"/>
         <source>GOG installer</source>
-        <translation type="unfinished"></translation>
+        <translation>GOG-Installationsprogramm</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="310"/>
         <source>GOG data</source>
-        <translation type="unfinished"></translation>
+        <translation>GOG-Datendatei</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="314"/>
         <source>Installing... Please wait!</source>
-        <translation type="unfinished"></translation>
+        <translation>Installiert... Bitte warten!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="349"/>
         <source>No Heroes III data!</source>
-        <translation type="unfinished"></translation>
+        <translation>Keine Heroes III-Daten!</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>Die ausgewählten Dateien enthalten keine Heroes III-Daten!</translation>
     </message>
     <message>
         <location filename="../firstLaunch/firstlaunch_moc.cpp" line="387"/>
@@ -1136,26 +1137,29 @@ Heroes III: HD Edition wird derzeit nicht unterstützt!</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>Heroes III Daten nicht gefunden!</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>Es konnten keine gültigen Heroes III-Daten im gewählten Verzeichnis gefunden werden.
+Bitte wählen Sie ein Verzeichnis mit installierten Heroes III-Daten.</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>Heroes III: HD Edition Dateien werden von VCMI nicht unterstützt.
+Bitte wählen Sie ein Verzeichnis mit Heroes III: Complete Edition oder 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>Unbekannte oder nicht unterstützte Heroes III-Version gefunden.
+Bitte wählen Sie ein Verzeichnis mit Heroes III: Complete Edition oder 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">Name</translation>
+        <translation>Name</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">Version</translation>
+        <translation>Version</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>Netzwerkfehler</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>JSON kann nicht von der URL gelesen werden oder die JSON-Daten sind falsch</translation>
     </message>
 </context>
 </TS>

+ 11 - 15
lib/mapObjects/CGCreature.cpp

@@ -327,23 +327,15 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 	else
 		powerFactor = -3;
 
-	std::set<CreatureID> myKindCres; //what creatures are the same kind as we
-	const CCreature * myCreature = getCreature().toCreature();
-	myKindCres.insert(myCreature->getId()); //we
-	myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades
-
-	for(auto const & crea : VLC->creh->objects)
-	{
-		if(vstd::contains(crea->upgrades, myCreature->getId())) //it's our base creatures
-			myKindCres.insert(crea->getId());
-	}
-
 	int count = 0; //how many creatures of similar kind has hero
 	int totalCount = 0;
 
 	for(const auto & elem : h->Slots())
 	{
-		if(vstd::contains(myKindCres,elem.second->type->getId()))
+		bool isOurUpgrade = vstd::contains(getCreature().toCreature()->upgrades, elem.second->getCreatureID());
+		bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreature());
+
+		if(isOurUpgrade || isOurDowngrade)
 			count += elem.second->count;
 		totalCount += elem.second->count;
 	}
@@ -365,8 +357,12 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 		if(diplomacy + sympathy + 1 >= character)
 			return JOIN_FOR_FREE;
 
-		else if(diplomacy * 2  +  sympathy  +  1 >= character)
-			return VLC->creatures()->getById(getCreature())->getRecruitCost(EGameResID::GOLD) * getStackCount(SlotID(0)); //join for gold
+		if(diplomacy * 2 + sympathy + 1 >= character)
+		{
+			int32_t recruitCost = VLC->creatures()->getById(getCreature())->getRecruitCost(EGameResID::GOLD);
+			int32_t stackCount = getStackCount(SlotID(0));
+			return recruitCost * stackCount; //join for gold
+		}
 	}
 
 	//we are still here - creatures have not joined hero, flee or fight
@@ -598,7 +594,7 @@ void CGCreature::giveReward(const CGHeroInstance * h) const
 	if(!resources.empty())
 	{
 		cb->giveResources(h->tempOwner, resources);
-		for(auto const & res : GameResID::ALL_RESOURCES())
+		for(const auto & res : GameResID::ALL_RESOURCES())
 		{
 			if(resources[res] > 0)
 				iw.components.emplace_back(ComponentType::RESOURCE, res, resources[res]);

+ 5 - 5
lib/mapObjects/CGCreature.h

@@ -29,15 +29,15 @@ public:
 	};
 
 	ui32 identifier; //unique code for this monster (used in missions)
-	si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage)
+	si8 character = 0; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage)
 	MetaString message; //message printed for attacking hero
 	TResources resources; // resources given to hero that has won with monsters
 	ArtifactID gainedArtifact; //ID of artifact gained to hero, -1 if none
-	bool neverFlees; //if true, the troops will never flee
-	bool notGrowingTeam; //if true, number of units won't grow
-	ui64 temppower; //used to handle fractional stack growth for tiny stacks
+	bool neverFlees = false; //if true, the troops will never flee
+	bool notGrowingTeam = false; //if true, number of units won't grow
+	ui64 temppower = 0; //used to handle fractional stack growth for tiny stacks
 
-	bool refusedJoining;
+	bool refusedJoining = false;
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	std::string getHoverText(PlayerColor player) const override;

+ 24 - 3
lib/network/NetworkConnection.cpp

@@ -12,12 +12,12 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket)
+NetworkConnection::NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket, const std::shared_ptr<NetworkContext> & context)
 	: 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,12 +43,32 @@ 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)
@@ -71,7 +91,8 @@ void NetworkConnection::onHeaderReceived(const boost::system::error_code & ecHea
 
 	if (messageSize == 0)
 	{
-		listener.onDisconnected(shared_from_this(), "Zero-sized packet!");
+		//heartbeat package with no payload - wait for next packet
+		start();
 		return;
 	}
 

+ 3 - 1
lib/network/NetworkConnection.h

@@ -19,15 +19,17 @@ 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;
 
 	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);
+	NetworkConnection(INetworkConnectionListener & listener, const std::shared_ptr<NetworkSocket> & socket, const std::shared_ptr<NetworkContext> & context);
 
 	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, [socket, &listener](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint)
+	boost::asio::async_connect(*socket, endpoints, [this, 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);
+		auto connection = std::make_shared<NetworkConnection>(listener, socket, io);
 		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);
+	auto connection = std::make_shared<NetworkConnection>(*this, upcomingConnection, io);
 	connections.insert(connection);
 	connection->start();
 	listener.onNewConnection(connection);

+ 1 - 0
lib/networkPacks/NetPackVisitor.h

@@ -172,6 +172,7 @@ public:
 	virtual void visitLobbySetDifficulty(LobbySetDifficulty & pack) {}
 	virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) {}
 	virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {}
+	virtual void visitLobbyPvPAction(LobbyPvPAction & pack) {}
 };
 
 VCMI_LIB_NAMESPACE_END

+ 5 - 0
lib/networkPacks/NetPacksLib.cpp

@@ -813,6 +813,11 @@ void LobbyShowMessage::visitTyped(ICPackVisitor & visitor)
 	visitor.visitLobbyShowMessage(*this);
 }
 
+void LobbyPvPAction::visitTyped(ICPackVisitor & visitor)
+{
+	visitor.visitLobbyPvPAction(*this);
+}
+
 void SetResources::applyGs(CGameState * gs) const
 {
 	assert(player.isValidPlayer());

+ 2 - 2
lib/networkPacks/PacksForClient.h

@@ -73,7 +73,7 @@ struct DLL_LINKAGE PackageApplied : public CPackForClient
 
 struct DLL_LINKAGE SystemMessage : public CPackForClient
 {
-	explicit SystemMessage(std::string Text)
+	explicit SystemMessage(MetaString Text)
 		: text(std::move(Text))
 	{
 	}
@@ -81,7 +81,7 @@ struct DLL_LINKAGE SystemMessage : public CPackForClient
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
-	std::string text;
+	MetaString text;
 
 	template <typename Handler> void serialize(Handler & h)
 	{

+ 20 - 2
lib/networkPacks/PacksForLobby.h

@@ -11,6 +11,7 @@
 
 #include "StartInfo.h"
 #include "NetPacksBase.h"
+#include "../MetaString.h"
 
 class CServerHandler;
 class CVCMIServer;
@@ -73,7 +74,7 @@ struct DLL_LINKAGE LobbyClientDisconnected : public CLobbyPackToPropagate
 struct DLL_LINKAGE LobbyChatMessage : public CLobbyPackToPropagate
 {
 	std::string playerName;
-	std::string message;
+	MetaString message;
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
@@ -333,7 +334,7 @@ struct DLL_LINKAGE LobbyForceSetPlayer : public CLobbyPackToServer
 
 struct DLL_LINKAGE LobbyShowMessage : public CLobbyPackToPropagate
 {
-	std::string message;
+	MetaString message;
 	
 	void visitTyped(ICPackVisitor & visitor) override;
 	
@@ -343,4 +344,21 @@ struct DLL_LINKAGE LobbyShowMessage : public CLobbyPackToPropagate
 	}
 };
 
+struct DLL_LINKAGE LobbyPvPAction : public CLobbyPackToServer
+{
+	enum EAction : ui8 {
+		NONE, COIN, RANDOM_TOWN, RANDOM_TOWN_VS
+	} action = NONE;
+	std::vector<FactionID> bannedTowns;
+
+
+	void visitTyped(ICPackVisitor & visitor) override;
+
+	template <typename Handler> void serialize(Handler &h)
+	{
+		h & action;
+		h & bannedTowns;
+	}
+};
+
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/pathfinder/CPathfinder.cpp

@@ -330,7 +330,7 @@ bool CPathfinder::isLayerTransitionPossible() const
 	ELayer destLayer = destination.node->layer;
 
 	/// No layer transition allowed when previous node action is BATTLE
-	if(source.node->action == EPathNodeAction::BATTLE)
+	if(!config->options.allowLayerTransitioningAfterBattle && source.node->action == EPathNodeAction::BATTLE)
 		return false;
 
 	switch(source.node->layer.toEnum())

+ 1 - 0
lib/pathfinder/PathfinderOptions.cpp

@@ -33,6 +33,7 @@ PathfinderOptions::PathfinderOptions()
 	, oneTurnSpecialLayersLimit(true)
 	, turnLimit(std::numeric_limits<uint8_t>::max())
 	, canUseCast(false)
+	, allowLayerTransitioningAfterBattle(false)
 {
 }
 

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