1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-02 23:07:36 +02:00

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

This commit is contained in:
Ivan Savenko 2025-02-13 13:46:38 +00:00
parent a12ea45276
commit 091029e0bb
4 changed files with 57 additions and 22 deletions

View File

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

View File

@ -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 // 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 // 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!"); throw std::runtime_error("Invalid map format!");
MapFormatFeaturesH3M result = getFeaturesSOD(); MapFormatFeaturesH3M result = getFeaturesSOD();
result.levelHOTA0 = true; result.levelHOTA0 = true;
result.levelHOTA1 = hotaVersion > 0; result.levelHOTA1 = hotaVersion > 0;
result.levelHOTA2 = hotaVersion > 1;
result.levelHOTA3 = hotaVersion > 2; result.levelHOTA3 = hotaVersion > 2;
result.levelHOTA5 = hotaVersion > 4; result.levelHOTA5 = hotaVersion > 4;
result.levelHOTA6 = hotaVersion > 5; result.levelHOTA6 = hotaVersion > 5;
result.levelHOTA7 = hotaVersion > 6;
result.artifactsBytes = 21; result.artifactsBytes = 21;
result.heroesBytes = 23; result.heroesBytes = 23;

View File

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

View File

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