1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

Identifier remapping support for campaigns

This commit is contained in:
Ivan Savenko
2025-05-29 18:12:54 +03:00
parent 2bf951a4cf
commit 1ea2ce7959
10 changed files with 172 additions and 70 deletions

View File

@@ -172,6 +172,7 @@ set(lib_MAIN_SRCS
mapping/MapFormatH3M.cpp mapping/MapFormatH3M.cpp
mapping/MapReaderH3M.cpp mapping/MapReaderH3M.cpp
mapping/MapFormatJson.cpp mapping/MapFormatJson.cpp
mapping/MapFormatSettings.cpp
mapping/ObstacleProxy.cpp mapping/ObstacleProxy.cpp
modding/ActiveModsInSaveList.cpp modding/ActiveModsInSaveList.cpp
@@ -587,6 +588,7 @@ set(lib_MAIN_HEADERS
mapping/MapFeaturesH3M.h mapping/MapFeaturesH3M.h
mapping/MapFormatH3M.h mapping/MapFormatH3M.h
mapping/MapFormat.h mapping/MapFormat.h
mapping/MapFormatSettings.h
mapping/MapReaderH3M.h mapping/MapReaderH3M.h
mapping/MapFormatJson.h mapping/MapFormatJson.h
mapping/ObstacleProxy.h mapping/ObstacleProxy.h

View File

@@ -26,6 +26,7 @@
#include "entities/hero/CHeroClassHandler.h" #include "entities/hero/CHeroClassHandler.h"
#include "entities/hero/CHeroHandler.h" #include "entities/hero/CHeroHandler.h"
#include "texts/CGeneralTextHandler.h" #include "texts/CGeneralTextHandler.h"
#include "mapping/MapFormatSettings.h"
#include "modding/CModHandler.h" #include "modding/CModHandler.h"
#include "modding/IdentifierStorage.h" #include "modding/IdentifierStorage.h"
#include "modding/CModVersion.h" #include "modding/CModVersion.h"
@@ -192,6 +193,8 @@ void GameLibrary::initializeLibrary()
modh->load(); modh->load();
modh->afterLoad(); modh->afterLoad();
createHandler(mapFormat);
} }
#if SCRIPTING_ENABLED #if SCRIPTING_ENABLED

View File

@@ -41,6 +41,7 @@ class IGameSettings;
class GameSettings; class GameSettings;
class CIdentifierStorage; class CIdentifierStorage;
class SpellSchoolHandler; class SpellSchoolHandler;
class MapFormatSettings;
#if SCRIPTING_ENABLED #if SCRIPTING_ENABLED
namespace scripting namespace scripting
@@ -97,6 +98,7 @@ public:
std::unique_ptr<ObstacleHandler> obstacleHandler; std::unique_ptr<ObstacleHandler> obstacleHandler;
std::unique_ptr<GameSettings> settingsHandler; std::unique_ptr<GameSettings> settingsHandler;
std::unique_ptr<ObstacleSetHandler> biomeHandler; std::unique_ptr<ObstacleSetHandler> biomeHandler;
std::unique_ptr<MapFormatSettings> mapFormat;
#if SCRIPTING_ENABLED #if SCRIPTING_ENABLED
std::unique_ptr<scripting::ScriptHandler> scriptHandler; std::unique_ptr<scripting::ScriptHandler> scriptHandler;

View File

@@ -11,6 +11,7 @@
#include "CampaignBonus.h" #include "CampaignBonus.h"
#include "../filesystem/CBinaryReader.h" #include "../filesystem/CBinaryReader.h"
#include "../mapping/MapIdentifiersH3M.h"
#include "../json/JsonNode.h" #include "../json/JsonNode.h"
#include "../constants/StringConstants.h" #include "../constants/StringConstants.h"
@@ -47,7 +48,7 @@ static const std::map<std::string, ui8> resourceTypeMap = {
{"rare", EGameResID::RARE} {"rare", EGameResID::RARE}
}; };
CampaignBonus::CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode) CampaignBonus::CampaignBonus(CBinaryReader & reader, const MapIdentifiersH3M & remapper, CampaignStartOptions mode)
{ {
switch(mode) switch(mode)
{ {
@@ -65,7 +66,7 @@ CampaignBonus::CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode)
{ {
HeroTypeID hero(reader.readUInt16()); HeroTypeID hero(reader.readUInt16());
SpellID spell(reader.readUInt8()); SpellID spell(reader.readUInt8());
data = CampaignBonusSpell{hero, spell}; data = CampaignBonusSpell{remapper.remap(hero), spell};
break; break;
} }
case CampaignBonusType::MONSTER: case CampaignBonusType::MONSTER:
@@ -73,27 +74,27 @@ CampaignBonus::CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode)
HeroTypeID hero(reader.readUInt16()); HeroTypeID hero(reader.readUInt16());
CreatureID creature(reader.readUInt16()); CreatureID creature(reader.readUInt16());
int32_t amount = reader.readUInt16(); int32_t amount = reader.readUInt16();
data = CampaignBonusCreatures{hero, creature, amount}; data = CampaignBonusCreatures{remapper.remap(hero), remapper.remap(creature), amount};
break; break;
} }
case CampaignBonusType::BUILDING: case CampaignBonusType::BUILDING:
{ {
BuildingID building(reader.readUInt8()); BuildingID building(reader.readUInt8());
data = CampaignBonusBuilding{building}; data = CampaignBonusBuilding{remapper.remapBuilding(std::nullopt, building)};
break; break;
} }
case CampaignBonusType::ARTIFACT: case CampaignBonusType::ARTIFACT:
{ {
HeroTypeID hero(reader.readUInt16()); HeroTypeID hero(reader.readUInt16());
ArtifactID artifact(reader.readUInt16()); ArtifactID artifact(reader.readUInt16());
data = CampaignBonusArtifact{hero, artifact}; data = CampaignBonusArtifact{remapper.remap(hero), remapper.remap(artifact)};
break; break;
} }
case CampaignBonusType::SPELL_SCROLL: case CampaignBonusType::SPELL_SCROLL:
{ {
HeroTypeID hero(reader.readUInt16()); HeroTypeID hero(reader.readUInt16());
SpellID spell(reader.readUInt8()); SpellID spell(reader.readUInt8());
data = CampaignBonusSpellScroll{hero, spell}; data = CampaignBonusSpellScroll{remapper.remap(hero), spell};
break; break;
} }
case CampaignBonusType::PRIMARY_SKILL: case CampaignBonusType::PRIMARY_SKILL:
@@ -103,7 +104,7 @@ CampaignBonus::CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode)
for(auto & value : amounts) for(auto & value : amounts)
value = reader.readUInt8(); value = reader.readUInt8();
data = CampaignBonusPrimarySkill{hero, amounts}; data = CampaignBonusPrimarySkill{remapper.remap(hero), amounts};
break; break;
} }
case CampaignBonusType::SECONDARY_SKILL: case CampaignBonusType::SECONDARY_SKILL:
@@ -111,7 +112,7 @@ CampaignBonus::CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode)
HeroTypeID hero(reader.readUInt16()); HeroTypeID hero(reader.readUInt16());
SecondarySkill skill(reader.readUInt8()); SecondarySkill skill(reader.readUInt8());
int32_t skillMastery(reader.readUInt8()); int32_t skillMastery(reader.readUInt8());
data = CampaignBonusSecondarySkill{hero, skill, skillMastery}; data = CampaignBonusSecondarySkill{remapper.remap(hero), remapper.remap(skill), skillMastery};
break; break;
} }
case CampaignBonusType::RESOURCE: case CampaignBonusType::RESOURCE:
@@ -137,7 +138,7 @@ CampaignBonus::CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode)
{ {
PlayerColor player(reader.readUInt8()); PlayerColor player(reader.readUInt8());
HeroTypeID hero(reader.readInt16()); HeroTypeID hero(reader.readInt16());
data = CampaignBonusStartingHero{player, hero}; data = CampaignBonusStartingHero{player, remapper.remap(hero)};
break; break;
} }
default: default:

View File

@@ -15,6 +15,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class CBinaryReader; class CBinaryReader;
class MapIdentifiersH3M;
class JsonNode; class JsonNode;
struct CampaignBonusSpell struct CampaignBonusSpell
@@ -169,7 +170,7 @@ public:
:data(value) :data(value)
{} {}
DLL_LINKAGE CampaignBonus(CBinaryReader & reader, CampaignStartOptions mode); DLL_LINKAGE CampaignBonus(CBinaryReader & reader, const MapIdentifiersH3M & remapper, CampaignStartOptions mode);
DLL_LINKAGE CampaignBonus(const JsonNode & json, CampaignStartOptions mode); DLL_LINKAGE CampaignBonus(const JsonNode & json, CampaignStartOptions mode);
template<typename T> template<typename T>

View File

@@ -20,6 +20,7 @@
#include "../GameLibrary.h" #include "../GameLibrary.h"
#include "../mapping/CMapHeader.h" #include "../mapping/CMapHeader.h"
#include "../mapping/CMapService.h" #include "../mapping/CMapService.h"
#include "../mapping/MapFormatSettings.h"
#include "../modding/CModHandler.h" #include "../modding/CModHandler.h"
#include "../modding/IdentifierStorage.h" #include "../modding/IdentifierStorage.h"
#include "../modding/ModScope.h" #include "../modding/ModScope.h"
@@ -425,14 +426,14 @@ CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader
} }
template<typename Identifier> template<typename Identifier>
static void readContainer(std::set<Identifier> & container, CBinaryReader & reader, int sizeBytes) static void readContainer(std::set<Identifier> & container, CBinaryReader & reader, const MapIdentifiersH3M & remapper, int sizeBytes)
{ {
for(int iId = 0, byte = 0; iId < sizeBytes * 8; ++iId) for(int iId = 0, byte = 0; iId < sizeBytes * 8; ++iId)
{ {
if(iId % 8 == 0) if(iId % 8 == 0)
byte = reader.readUInt8(); byte = reader.readUInt8();
if(byte & (1 << iId % 8)) if(byte & (1 << iId % 8))
container.insert(Identifier(iId)); container.insert(remapper.remap(Identifier(iId)));
} }
} }
@@ -446,16 +447,18 @@ CampaignTravel CampaignHandler::readScenarioTravelFromMemory(CBinaryReader & rea
ret.whatHeroKeeps.secondarySkills = whatHeroKeeps & 4; ret.whatHeroKeeps.secondarySkills = whatHeroKeeps & 4;
ret.whatHeroKeeps.spells = whatHeroKeeps & 8; ret.whatHeroKeeps.spells = whatHeroKeeps & 8;
ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16; ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16;
const auto & mapping = LIBRARY->mapFormat->getMapping(version);
if (version == CampaignVersion::HotA) if (version == CampaignVersion::HotA)
{ {
readContainer(ret.monstersKeptByHero, reader, 24); readContainer(ret.monstersKeptByHero, reader, mapping, 24);
readContainer(ret.artifactsKeptByHero, reader, 21); readContainer(ret.artifactsKeptByHero, reader, mapping, 21);
} }
else else
{ {
readContainer(ret.monstersKeptByHero, reader, 19); readContainer(ret.monstersKeptByHero, reader, mapping, 19);
readContainer(ret.artifactsKeptByHero, reader, version < CampaignVersion::SoD ? 17 : 18); readContainer(ret.artifactsKeptByHero, reader, mapping, version < CampaignVersion::SoD ? 17 : 18);
} }
ret.startOptions = static_cast<CampaignStartOptions>(reader.readUInt8()); ret.startOptions = static_cast<CampaignStartOptions>(reader.readUInt8());
@@ -467,7 +470,7 @@ CampaignTravel CampaignHandler::readScenarioTravelFromMemory(CBinaryReader & rea
{ {
ui8 numOfBonuses = reader.readUInt8(); ui8 numOfBonuses = reader.readUInt8();
for (int g=0; g<numOfBonuses; ++g) for (int g=0; g<numOfBonuses; ++g)
ret.bonusesToChoose.emplace_back(reader, ret.startOptions); ret.bonusesToChoose.emplace_back(reader, mapping, ret.startOptions);
} }
return ret; return ret;

View File

@@ -16,8 +16,6 @@ enum class EMapFormat : uint8_t;
struct MapFormatFeaturesH3M struct MapFormatFeaturesH3M
{ {
public:
static MapFormatFeaturesH3M find(EMapFormat format, uint32_t hotaVersion);
static MapFormatFeaturesH3M getFeaturesROE(); static MapFormatFeaturesH3M getFeaturesROE();
static MapFormatFeaturesH3M getFeaturesAB(); static MapFormatFeaturesH3M getFeaturesAB();
static MapFormatFeaturesH3M getFeaturesSOD(); static MapFormatFeaturesH3M getFeaturesSOD();
@@ -27,6 +25,10 @@ public:
MapFormatFeaturesH3M() = default; MapFormatFeaturesH3M() = default;
public:
static MapFormatFeaturesH3M find(EMapFormat format, uint32_t hotaVersion);
// number of bytes in bitmask of appropriate type // number of bytes in bitmask of appropriate type
int factionsBytes; int factionsBytes;

View File

@@ -13,7 +13,7 @@
#include "CMap.h" #include "CMap.h"
#include "MapReaderH3M.h" #include "MapReaderH3M.h"
#include "MapFormat.h" #include "MapFormatSettings.h"
#include "../CCreatureHandler.h" #include "../CCreatureHandler.h"
#include "../texts/CGeneralTextHandler.h" #include "../texts/CGeneralTextHandler.h"
@@ -111,52 +111,6 @@ void CMapLoaderH3M::init()
//map->banWaterContent(); //Not sure if force this for custom scenarios //map->banWaterContent(); //Not sure if force this for custom scenarios
} }
static MapIdentifiersH3M generateMapping(EMapFormat format)
{
auto features = MapFormatFeaturesH3M::find(format, 0);
MapIdentifiersH3M identifierMapper;
if(features.levelROE)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA));
if(features.levelAB)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
if(features.levelSOD)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
if(features.levelCHR)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES));
if(features.levelWOG)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
if(features.levelHOTA0)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
return identifierMapper;
}
static std::map<EMapFormat, MapIdentifiersH3M> generateMappings()
{
std::map<EMapFormat, MapIdentifiersH3M> result;
auto addMapping = [&result](EMapFormat format)
{
try
{
result[format] = generateMapping(format);
}
catch(const std::runtime_error &)
{
// unsupported map format - skip
}
};
addMapping(EMapFormat::ROE);
addMapping(EMapFormat::AB);
addMapping(EMapFormat::SOD);
addMapping(EMapFormat::CHR);
addMapping(EMapFormat::HOTA);
addMapping(EMapFormat::WOG);
return result;
}
void CMapLoaderH3M::readHeader() void CMapLoaderH3M::readHeader()
{ {
// Map version // Map version
@@ -233,12 +187,10 @@ void CMapLoaderH3M::readHeader()
reader->setFormatLevel(features); reader->setFormatLevel(features);
} }
// optimization - load mappings only once to avoid slow parsing of map headers for map list if (!LIBRARY->mapFormat->isSupported(mapHeader->version))
static const std::map<EMapFormat, MapIdentifiersH3M> identifierMappers = generateMappings();
if (!identifierMappers.count(mapHeader->version))
throw std::runtime_error("Unsupported map format! Format ID " + std::to_string(static_cast<int>(mapHeader->version))); throw std::runtime_error("Unsupported map format! Format ID " + std::to_string(static_cast<int>(mapHeader->version)));
const MapIdentifiersH3M & identifierMapper = identifierMappers.at(mapHeader->version); const MapIdentifiersH3M & identifierMapper = LIBRARY->mapFormat->getMapping(mapHeader->version);
reader->setIdentifierRemapper(identifierMapper); reader->setIdentifierRemapper(identifierMapper);

View File

@@ -0,0 +1,85 @@
/*
* MapFormatSettings.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "MapFormatSettings.h"
#include "MapFeaturesH3M.h"
#include "../GameLibrary.h"
#include "../IGameSettings.h"
MapIdentifiersH3M MapFormatSettings::generateMapping(EMapFormat format)
{
auto features = MapFormatFeaturesH3M::find(format, 0);
MapIdentifiersH3M identifierMapper;
if(features.levelROE)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA));
if(features.levelAB)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE));
if(features.levelSOD)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH));
if(features.levelCHR)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_CHRONICLES));
if(features.levelWOG)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS));
if(features.levelHOTA0)
identifierMapper.loadMapping(LIBRARY->engineSettings()->getValue(EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS));
return identifierMapper;
}
std::map<CampaignVersion, EMapFormat> MapFormatSettings::generateCampaignMapping()
{
return {
{CampaignVersion::RoE, EMapFormat::ROE },
{CampaignVersion::AB, EMapFormat::AB },
{CampaignVersion::SoD, EMapFormat::SOD },
{CampaignVersion::WoG, EMapFormat::WOG },
{CampaignVersion::Chr, EMapFormat::CHR },
{CampaignVersion::HotA, EMapFormat::HOTA}
};
}
std::map<EMapFormat, MapIdentifiersH3M> MapFormatSettings::generateMappings()
{
std::map<EMapFormat, MapIdentifiersH3M> result;
auto addMapping = [&result](EMapFormat format)
{
try
{
result[format] = generateMapping(format);
logMod->trace("Loaded map format support for %d", static_cast<int>(format));
}
catch(const std::runtime_error &)
{
// unsupported map format - skip
logMod->debug("Failed to load map format support for %d", static_cast<int>(format));
}
};
addMapping(EMapFormat::ROE);
addMapping(EMapFormat::AB);
addMapping(EMapFormat::SOD);
addMapping(EMapFormat::CHR);
addMapping(EMapFormat::HOTA);
addMapping(EMapFormat::WOG);
return result;
}
MapFormatSettings::MapFormatSettings()
: mapping(generateMappings())
, campaignToMap(generateCampaignMapping())
{
}
VCMI_LIB_NAMESPACE_END

View File

@@ -0,0 +1,51 @@
/*
* MapFormatSettings.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "MapIdentifiersH3M.h"
#include "MapFormat.h"
#include "../campaign/CampaignConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
class MapFormatSettings
{
static MapIdentifiersH3M generateMapping(EMapFormat format);
static std::map<EMapFormat, MapIdentifiersH3M> generateMappings();
static std::map<CampaignVersion, EMapFormat> generateCampaignMapping();
std::map<EMapFormat, MapIdentifiersH3M> mapping;
std::map<CampaignVersion, EMapFormat> campaignToMap;
public:
MapFormatSettings();
bool isSupported(EMapFormat format) const
{
return mapping.count(format) != 0;
}
bool isSupported(CampaignVersion format) const
{
return isSupported(campaignToMap.at(format));
}
const MapIdentifiersH3M & getMapping(EMapFormat format) const
{
return mapping.at(format);
}
const MapIdentifiersH3M & getMapping(CampaignVersion format) const
{
return mapping.at(campaignToMap.at(format));
}
};
VCMI_LIB_NAMESPACE_END