Browse Source

Support for parsing HotA 1.7.2 h3m's and h3c's

Ivan Savenko 8 months ago
parent
commit
091029e0bb

+ 1 - 0
lib/mapObjects/CQuest.h

@@ -35,6 +35,7 @@ enum class EQuestMission {
 	KEYMASTER = 11,
 	HOTA_HERO_CLASS = 12,
 	HOTA_REACH_DATE = 13,
+	HOTA_GAME_DIFFICULTY = 13,
 };
 
 class DLL_LINKAGE CQuest final : public Serializeable

+ 3 - 1
lib/mapping/MapFeaturesH3M.cpp

@@ -131,15 +131,17 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesHOTA(uint32_t hotaVersion)
 {
 	// even if changes are minimal, we might not be able to parse map header in map selection screen
 	// throw exception - to be caught by map selection screen & excluded as invalid
-	if(hotaVersion > 6)
+	if(hotaVersion > 7)
 		throw std::runtime_error("Invalid map format!");
 
 	MapFormatFeaturesH3M result = getFeaturesSOD();
 	result.levelHOTA0 = true;
 	result.levelHOTA1 = hotaVersion > 0;
+	result.levelHOTA2 = hotaVersion > 1;
 	result.levelHOTA3 = hotaVersion > 2;
 	result.levelHOTA5 = hotaVersion > 4;
 	result.levelHOTA6 = hotaVersion > 5;
+	result.levelHOTA7 = hotaVersion > 6;
 
 	result.artifactsBytes = 21;
 	result.heroesBytes = 23;

+ 3 - 0
lib/mapping/MapFeaturesH3M.h

@@ -69,9 +69,12 @@ public:
 	bool levelWOG = false;
 	bool levelHOTA0 = false;
 	bool levelHOTA1 = false;
+	bool levelHOTA2 = false;
 	bool levelHOTA3 = false; // 1.6.0
+	// level 4 - not released publicly?
 	bool levelHOTA5 = false; // 1.7.0
 	bool levelHOTA6 = false; // 1.7.1
+	bool levelHOTA7 = false; // 1.7.2
 };
 
 VCMI_LIB_NAMESPACE_END

+ 50 - 21
lib/mapping/MapFormatH3M.cpp

@@ -168,7 +168,7 @@ void CMapLoaderH3M::readHeader()
 		features = MapFormatFeaturesH3M::find(mapHeader->version, hotaVersion);
 		reader->setFormatLevel(features);
 
-		if(hotaVersion > 0)
+		if(features.levelHOTA1)
 		{
 			bool isMirrorMap = reader->readBool();
 			bool isArenaMap = reader->readBool();
@@ -181,7 +181,7 @@ void CMapLoaderH3M::readHeader()
 				logGlobal->warn("Map '%s': Arena maps are not supported!", mapName);
 		}
 
-		if(hotaVersion > 1)
+		if(features.levelHOTA2)
 		{
 			int32_t terrainTypesCount = reader->readUInt32();
 			assert(features.terrainsCount == terrainTypesCount);
@@ -190,7 +190,7 @@ void CMapLoaderH3M::readHeader()
 				logGlobal->warn("Map '%s': Expected %d terrains, but %d found!", mapName, features.terrainsCount, terrainTypesCount);
 		}
 
-		if(hotaVersion > 4)
+		if(features.levelHOTA5)
 		{
 			int32_t townTypesCount = reader->readUInt32();
 			int8_t allowedDifficultiesMask = reader->readInt8Checked(0, 31);
@@ -203,6 +203,14 @@ void CMapLoaderH3M::readHeader()
 			if (allowedDifficultiesMask != 0)
 				logGlobal->warn("Map '%s': List of allowed difficulties (%d) is not implemented!", mapName, allowedDifficultiesMask);
 		}
+
+		if(features.levelHOTA7)
+		{
+			bool canHireDefeatedHeroes = reader->readBool();
+
+			if (!canHireDefeatedHeroes)
+				logGlobal->warn("Map '%s': Option to block hiring of defeated heroes is not implemented!", mapName);
+		}
 	}
 	else
 	{
@@ -745,13 +753,15 @@ void CMapLoaderH3M::readMapOptions()
 
 	if(features.levelHOTA1)
 	{
-		// Unknown, may be another "sized bitmap", e.g
-		// 4 bytes - size of bitmap (16)
-		// 2 bytes - bitmap data (16 bits / 2 bytes)
-		// potentially - combo_artifact_count / combo_artifacts
-		[[maybe_unused]] uint8_t unknownConstant = reader->readUInt8();
-		assert(unknownConstant == 16);
-		reader->skipZero(5);
+		int32_t combinedArtifactsCount = reader->readInt32();
+		int32_t combinedArtifactsBytes = (combinedArtifactsCount + 7) / 8;
+
+		for (int i = 0; i < combinedArtifactsBytes; ++i)
+		{
+			uint8_t mask = reader->readUInt8();
+			if (mask != 0)
+				logGlobal->warn("Map '%s': Option to ban specific combined artifacts is not implemented!", mapName);
+		}
 	}
 
 	if(features.levelHOTA3)
@@ -1235,10 +1245,11 @@ CGObjectInstance * CMapLoaderH3M::readMonster(const int3 & mapPosition, const Ob
 
 	if (features.levelHOTA5)
 	{
-		[[maybe_unused]] int8_t unknownA = reader->readInt8();
-		[[maybe_unused]] int32_t unknownB = reader->readInt32();
-		assert(unknownA == 0);
-		assert(unknownB == 0);
+		int8_t unknownA = reader->readInt8();
+		int32_t unknownB = reader->readInt32();
+
+		if (unknownA != 0 || unknownB != 0)
+			logGlobal->warn( "Map '%s': Wandering monsters %s unknown settings %d %d is not implemented!", mapName, mapPosition.toString(), unknownA, unknownB);
 	}
 
 	return object;
@@ -2466,6 +2477,7 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
 		case EQuestMission::HOTA_MULTI:
 		{
 			uint32_t missionSubID = reader->readUInt32();
+			assert(missionSubID < 3);
 
 			if(missionSubID == 0)
 			{
@@ -2482,6 +2494,14 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi
 				guard->quest->mission.daysPassed = reader->readUInt32() + 1;
 				break;
 			}
+			if(missionSubID == 2)
+			{
+				missionId = EQuestMission::HOTA_GAME_DIFFICULTY;
+				int32_t difficultyMask = reader->readUInt32();
+				assert(difficultyMask > 0 && difficultyMask < 32);
+				logGlobal->warn("Map '%s': Seer Hut at %s: Difficulty-specific quest (%d) is not implemented!", mapName, position.toString(), difficultyMask);
+				break;
+			}
 			break;
 		}
 		default:
@@ -2588,16 +2608,22 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt
 
 		event.computerAffected = reader->readBool();
 		event.firstOccurrence = reader->readUInt16();
-		event.nextOccurrence = reader->readUInt8();
+		event.nextOccurrence = reader->readUInt16();
 
-		reader->skipZero(17);
+		reader->skipZero(16);
 
 		if(features.levelHOTA5)
 		{
 			[[maybe_unused]] int32_t allowedDifficulties = reader->readInt32();
 			[[maybe_unused]] int32_t hota_level_7b = reader->readInt32();
 			[[maybe_unused]] int32_t hota_amount = reader->readInt32();
-			[[maybe_unused]] int16_t apply_neutral_towns = reader->readInt16();
+			[[maybe_unused]] int16_t hota_special = reader->readInt16();
+		}
+
+		if(features.levelHOTA7)
+		{
+			[[maybe_unused]] int32_t hota_amount = reader->readInt32();
+			[[maybe_unused]] bool apply_neutral_towns = reader->readBool();
 		}
 
 		// New buildings
@@ -2669,17 +2695,20 @@ void CMapLoaderH3M::readEvents()
 		}
 		event.computerAffected = reader->readBool();
 		event.firstOccurrence = reader->readUInt16();
-		event.nextOccurrence = reader->readUInt8();
+		event.nextOccurrence = reader->readUInt16();
 
-		reader->skipZero(17);
+		reader->skipZero(16);
 
-		if (features.levelHOTA5)
+		if (features.levelHOTA7)
+		{
+			[[maybe_unused]] int32_t difficulties = reader->readInt32();
+		}
+		else if (features.levelHOTA5)
 		{
 			[[maybe_unused]] int32_t difficulties = reader->readInt32();
 			[[maybe_unused]] int32_t unknownA= reader->readInt32();
 			[[maybe_unused]] int32_t unknownB= reader->readInt32();
 			[[maybe_unused]] int16_t unknownC= reader->readInt16();
-
 		}
 
 		map->events.push_back(event);