1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

Merge pull request #3597 from IvanSavenko/bugfixing

Fixes for map/save loading
This commit is contained in:
Ivan Savenko
2024-02-07 00:16:43 +02:00
committed by GitHub
23 changed files with 209 additions and 183 deletions

View File

@@ -229,7 +229,7 @@ void InfoCard::changeSelection()
iconsLossCondition->setFrame(header->defeatIconIndex); iconsLossCondition->setFrame(header->defeatIconIndex);
labelLossConditionText->setText(header->defeatMessage.toString()); labelLossConditionText->setText(header->defeatMessage.toString());
flagbox->recreate(); flagbox->recreate();
labelDifficulty->setText(CGI->generaltexth->arraytxt[142 + mapInfo->mapHeader->difficulty]); labelDifficulty->setText(CGI->generaltexth->arraytxt[142 + vstd::to_underlying(mapInfo->mapHeader->difficulty)]);
iconDifficulty->setSelected(SEL->getCurrentDifficulty()); iconDifficulty->setSelected(SEL->getCurrentDifficulty());
if(SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::saveGame) if(SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::saveGame)
for(auto & button : iconDifficulty->buttons) for(auto & button : iconDifficulty->buttons)

View File

@@ -177,7 +177,7 @@ void RandomMapTab::updateMapInfoByHost()
mapInfo->mapHeader->version = EMapFormat::VCMI; mapInfo->mapHeader->version = EMapFormat::VCMI;
mapInfo->mapHeader->name.appendLocalString(EMetaText::GENERAL_TXT, 740); mapInfo->mapHeader->name.appendLocalString(EMetaText::GENERAL_TXT, 740);
mapInfo->mapHeader->description.appendLocalString(EMetaText::GENERAL_TXT, 741); mapInfo->mapHeader->description.appendLocalString(EMetaText::GENERAL_TXT, 741);
mapInfo->mapHeader->difficulty = 1; // Normal mapInfo->mapHeader->difficulty = EMapDifficulty::NORMAL;
mapInfo->mapHeader->height = mapGenOptions->getHeight(); mapInfo->mapHeader->height = mapGenOptions->getHeight();
mapInfo->mapHeader->width = mapGenOptions->getWidth(); mapInfo->mapHeader->width = mapGenOptions->getWidth();
mapInfo->mapHeader->twoLevel = mapGenOptions->getHasTwoLevels(); mapInfo->mapHeader->twoLevel = mapGenOptions->getHasTwoLevels();

View File

@@ -70,6 +70,7 @@
"torosar": "torosar":
{ {
"index": 36, "index": 36,
"compatibilityIdentifiers" : [ "torosar " ],
"class" : "alchemist", "class" : "alchemist",
"female": false, "female": false,
"spellbook": [ "magicArrow" ], "spellbook": [ "magicArrow" ],

View File

@@ -326,6 +326,8 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
{ {
JsonNode classConf = node["mapObject"]; JsonNode classConf = node["mapObject"];
classConf["heroClass"].String() = identifier; classConf["heroClass"].String() = identifier;
if (!node["compatibilityIdentifiers"].isNull())
classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"];
classConf.setMeta(scope); classConf.setMeta(scope);
VLC->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex()); VLC->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex());
}); });
@@ -756,6 +758,9 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod
objects.emplace_back(object); objects.emplace_back(object);
registerObject(scope, "hero", name, object->getIndex()); registerObject(scope, "hero", name, object->getIndex());
for(const auto & compatID : data["compatibilityIdentifiers"].Vector())
registerObject(scope, "hero", compatID.String(), object->getIndex());
} }
void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
@@ -767,6 +772,8 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod
objects[index] = object; objects[index] = object;
registerObject(scope, "hero", name, object->getIndex()); registerObject(scope, "hero", name, object->getIndex());
for(const auto & compatID : data["compatibilityIdentifiers"].Vector())
registerObject(scope, "hero", compatID.String(), object->getIndex());
} }
ui32 CHeroHandler::level (TExpType experience) const ui32 CHeroHandler::level (TExpType experience) const

View File

@@ -31,11 +31,11 @@ class CRandomGenerator;
class JsonSerializeFormat; class JsonSerializeFormat;
class BattleField; class BattleField;
enum class EHeroGender : uint8_t enum class EHeroGender : int8_t
{ {
DEFAULT = -1, // from h3m, instance has same gender as hero type
MALE = 0, MALE = 0,
FEMALE = 1, FEMALE = 1,
DEFAULT = 0xff // from h3m, instance has same gender as hero type
}; };
class DLL_LINKAGE CHero : public HeroType class DLL_LINKAGE CHero : public HeroType

View File

@@ -330,7 +330,7 @@ public:
static BuildingID FORT_LEVEL(unsigned int level) static BuildingID FORT_LEVEL(unsigned int level)
{ {
assert(level < 3); assert(level < 3);
return BuildingID(Type::TOWN_HALL + level); return BuildingID(Type::FORT + level);
} }
static std::string encode(int32_t index); static std::string encode(int32_t index);

View File

@@ -64,10 +64,10 @@ enum class EMarketMode : int8_t
enum class EAiTactic : int8_t enum class EAiTactic : int8_t
{ {
NONE = -1, NONE = -1,
RANDOM, RANDOM = 0,
WARRIOR, WARRIOR = 1,
BUILDER, BUILDER = 2,
EXPLORER EXPLORER = 3
}; };
enum class EBuildingState : int8_t enum class EBuildingState : int8_t

View File

@@ -1111,7 +1111,7 @@ std::string CGHeroInstance::getClassNameTextID() const
{ {
if (isCampaignGem()) if (isCampaignGem())
return "core.genrltxt.735"; return "core.genrltxt.735";
return type->heroClass->getNameTranslated(); return type->heroClass->getNameTextID();
} }
std::string CGHeroInstance::getNameTextID() const std::string CGHeroInstance::getNameTextID() const
@@ -1524,28 +1524,6 @@ std::string CGHeroInstance::getHeroTypeName() const
void CGHeroInstance::afterAddToMap(CMap * map) void CGHeroInstance::afterAddToMap(CMap * map)
{ {
if(ID != Obj::RANDOM_HERO)
{
auto existingHero = std::find_if(map->objects.begin(), map->objects.end(), [&](const CGObjectInstance * o) ->bool
{
return o && (o->ID == Obj::HERO || o->ID == Obj::PRISON) && o->subID == subID && o != this;
});
if(existingHero != map->objects.end())
{
if(settings["session"]["editor"].Bool())
{
logGlobal->warn("Hero is already on the map at %s", (*existingHero)->visitablePos().toString());
}
else
{
logGlobal->error("Hero is already on the map at %s", (*existingHero)->visitablePos().toString());
throw std::runtime_error("Hero is already on the map");
}
}
}
if(ID != Obj::PRISON) if(ID != Obj::PRISON)
{ {
map->heroesOnMap.emplace_back(this); map->heroesOnMap.emplace_back(this);

View File

@@ -23,7 +23,7 @@ class CGTownInstance;
class CMap; class CMap;
struct TerrainTile; struct TerrainTile;
struct TurnInfo; struct TurnInfo;
enum class EHeroGender : uint8_t; enum class EHeroGender : int8_t;
class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance
{ {

View File

@@ -42,8 +42,12 @@ DisposedHero::DisposedHero() : heroId(0), portrait(255)
} }
CMapEvent::CMapEvent() : players(0), humanAffected(0), computerAffected(0), CMapEvent::CMapEvent()
firstOccurence(0), nextOccurence(0) : players(0)
, humanAffected(false)
, computerAffected(false)
, firstOccurence(0)
, nextOccurence(0)
{ {
} }

View File

@@ -37,8 +37,8 @@ public:
MetaString message; MetaString message;
TResources resources; TResources resources;
ui8 players; // affected players, bit field? ui8 players; // affected players, bit field?
ui8 humanAffected; bool humanAffected;
ui8 computerAffected; bool computerAffected;
ui32 firstOccurence; ui32 firstOccurence;
ui32 nextOccurence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time ui32 nextOccurence; /// specifies after how many days the event will occur the next time; 0 if event occurs only one time

View File

@@ -117,7 +117,7 @@ void CMapHeader::setupEvents()
} }
CMapHeader::CMapHeader() : version(EMapFormat::VCMI), height(72), width(72), CMapHeader::CMapHeader() : version(EMapFormat::VCMI), height(72), width(72),
twoLevel(true), difficulty(1), levelLimit(0), howManyTeams(0), areAnyPlayers(false) twoLevel(true), difficulty(EMapDifficulty::NORMAL), levelLimit(0), howManyTeams(0), areAnyPlayers(false)
{ {
setupEvents(); setupEvents();
allowedHeroes = VLC->heroh->getDefaultAllowed(); allowedHeroes = VLC->heroh->getDefaultAllowed();
@@ -149,7 +149,7 @@ void CMapHeader::registerMapStrings()
if(maxStrings == 0 || mapLanguages.empty()) if(maxStrings == 0 || mapLanguages.empty())
{ {
logGlobal->info("Map %s doesn't have any supported translation", name.toString()); logGlobal->trace("Map %s doesn't have any supported translation", name.toString());
return; return;
} }

View File

@@ -191,6 +191,15 @@ struct DLL_LINKAGE TriggeredEvent
} }
}; };
enum class EMapDifficulty : uint8_t
{
EASY = 0,
NORMAL = 1,
HARD = 2,
EXPERT = 3,
IMPOSSIBLE = 4
};
/// The map header holds information about loss/victory condition,map format, version, players, height, width,... /// The map header holds information about loss/victory condition,map format, version, players, height, width,...
class DLL_LINKAGE CMapHeader class DLL_LINKAGE CMapHeader
{ {
@@ -218,7 +227,7 @@ public:
bool twoLevel; /// The default value is true. bool twoLevel; /// The default value is true.
MetaString name; MetaString name;
MetaString description; MetaString description;
ui8 difficulty; /// The default value is 1 representing a normal map difficulty. EMapDifficulty difficulty;
/// Specifies the maximum level to reach for a hero. A value of 0 states that there is no /// Specifies the maximum level to reach for a hero. A value of 0 states that there is no
/// maximum level for heroes. This is the default value. /// maximum level for heroes. This is the default value.
ui8 levelLimit; ui8 levelLimit;

View File

@@ -184,10 +184,10 @@ void CMapLoaderH3M::readHeader()
mapHeader->twoLevel = reader->readBool(); mapHeader->twoLevel = reader->readBool();
mapHeader->name.appendTextID(readLocalizedString("header.name")); mapHeader->name.appendTextID(readLocalizedString("header.name"));
mapHeader->description.appendTextID(readLocalizedString("header.description")); mapHeader->description.appendTextID(readLocalizedString("header.description"));
mapHeader->difficulty = reader->readInt8(); mapHeader->difficulty = static_cast<EMapDifficulty>(reader->readInt8Checked(0, 4));
if(features.levelAB) if(features.levelAB)
mapHeader->levelLimit = reader->readUInt8(); mapHeader->levelLimit = reader->readInt8Checked(0, std::min(100u, VLC->heroh->maxSupportedLevel()));
else else
mapHeader->levelLimit = 0; mapHeader->levelLimit = 0;
@@ -218,7 +218,7 @@ void CMapLoaderH3M::readPlayerInfo()
continue; continue;
} }
playerInfo.aiTactic = static_cast<EAiTactic>(reader->readUInt8()); playerInfo.aiTactic = static_cast<EAiTactic>(reader->readInt8Checked(-1, 3));
if(features.levelSOD) if(features.levelSOD)
reader->skipUnused(1); //TODO: check meaning? reader->skipUnused(1); //TODO: check meaning?
@@ -261,8 +261,8 @@ void CMapLoaderH3M::readPlayerInfo()
if(features.levelAB) if(features.levelAB)
{ {
reader->skipUnused(1); //TODO: check meaning? reader->skipUnused(1); //TODO: check meaning?
uint32_t heroCount = reader->readUInt32(); size_t heroCount = reader->readUInt32();
for(int pp = 0; pp < heroCount; ++pp) for(size_t pp = 0; pp < heroCount; ++pp)
{ {
SHeroName vv; SHeroName vv;
vv.heroId = reader->readHero(); vv.heroId = reader->readHero();
@@ -274,39 +274,13 @@ void CMapLoaderH3M::readPlayerInfo()
} }
} }
enum class EVictoryConditionType : uint8_t
{
ARTIFACT = 0,
GATHERTROOP = 1,
GATHERRESOURCE = 2,
BUILDCITY = 3,
BUILDGRAIL = 4,
BEATHERO = 5,
CAPTURECITY = 6,
BEATMONSTER = 7,
TAKEDWELLINGS = 8,
TAKEMINES = 9,
TRANSPORTITEM = 10,
HOTA_ELIMINATE_ALL_MONSTERS = 11,
HOTA_SURVIVE_FOR_DAYS = 12,
WINSTANDARD = 255
};
enum class ELossConditionType : uint8_t
{
LOSSCASTLE = 0,
LOSSHERO = 1,
TIMEEXPIRES = 2,
LOSSSTANDARD = 255
};
void CMapLoaderH3M::readVictoryLossConditions() void CMapLoaderH3M::readVictoryLossConditions()
{ {
mapHeader->triggeredEvents.clear(); mapHeader->triggeredEvents.clear();
mapHeader->victoryMessage.clear(); mapHeader->victoryMessage.clear();
mapHeader->defeatMessage.clear(); mapHeader->defeatMessage.clear();
auto vicCondition = static_cast<EVictoryConditionType>(reader->readUInt8()); auto vicCondition = static_cast<EVictoryConditionType>(reader->readInt8Checked(-1, 12));
EventCondition victoryCondition(EventCondition::STANDARD_WIN); EventCondition victoryCondition(EventCondition::STANDARD_WIN);
EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN); EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN);
@@ -395,9 +369,9 @@ void CMapLoaderH3M::readVictoryLossConditions()
EventExpression::OperatorAll oper; EventExpression::OperatorAll oper;
EventCondition cond(EventCondition::HAVE_BUILDING); EventCondition cond(EventCondition::HAVE_BUILDING);
cond.position = reader->readInt3(); cond.position = reader->readInt3();
cond.objectType = BuildingID::HALL_LEVEL(reader->readUInt8() + 1); cond.objectType = BuildingID::HALL_LEVEL(reader->readInt8Checked(0,3) + 1);
oper.expressions.emplace_back(cond); oper.expressions.emplace_back(cond);
cond.objectType = BuildingID::FORT_LEVEL(reader->readUInt8()); cond.objectType = BuildingID::FORT_LEVEL(reader->readInt8Checked(0, 2));
oper.expressions.emplace_back(cond); oper.expressions.emplace_back(cond);
specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.283"); specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.283");
@@ -578,7 +552,7 @@ void CMapLoaderH3M::readVictoryLossConditions()
} }
// Read loss conditions // Read loss conditions
auto lossCond = static_cast<ELossConditionType>(reader->readUInt8()); auto lossCond = static_cast<ELossConditionType>(reader->readInt8Checked(-1, 2));
if(lossCond == ELossConditionType::LOSSSTANDARD) if(lossCond == ELossConditionType::LOSSSTANDARD)
{ {
mapHeader->defeatIconIndex = 3; mapHeader->defeatIconIndex = 3;
@@ -685,9 +659,9 @@ void CMapLoaderH3M::readAllowedHeroes()
if(features.levelAB) if(features.levelAB)
{ {
uint32_t placeholdersQty = reader->readUInt32(); size_t placeholdersQty = reader->readUInt32();
for (uint32_t i = 0; i < placeholdersQty; ++i) for (size_t i = 0; i < placeholdersQty; ++i)
{ {
auto heroID = reader->readHero(); auto heroID = reader->readHero();
mapHeader->reservedCampaignHeroes.insert(heroID); mapHeader->reservedCampaignHeroes.insert(heroID);
@@ -700,9 +674,9 @@ void CMapLoaderH3M::readDisposedHeroes()
// Reading disposed heroes (20 bytes) // Reading disposed heroes (20 bytes)
if(features.levelSOD) if(features.levelSOD)
{ {
ui8 disp = reader->readUInt8(); size_t disp = reader->readUInt8();
map->disposedHeroes.resize(disp); map->disposedHeroes.resize(disp);
for(int g = 0; g < disp; ++g) for(size_t g = 0; g < disp; ++g)
{ {
map->disposedHeroes[g].heroId = reader->readHero(); map->disposedHeroes[g].heroId = reader->readHero();
map->disposedHeroes[g].portrait = reader->readHeroPortrait(); map->disposedHeroes[g].portrait = reader->readHeroPortrait();
@@ -801,10 +775,10 @@ void CMapLoaderH3M::readAllowedSpellsAbilities()
void CMapLoaderH3M::readRumors() void CMapLoaderH3M::readRumors()
{ {
uint32_t rumorsCount = reader->readUInt32(); size_t rumorsCount = reader->readUInt32();
assert(rumorsCount < 1000); // sanity check assert(rumorsCount < 1000); // sanity check
for(int it = 0; it < rumorsCount; it++) for(size_t it = 0; it < rumorsCount; it++)
{ {
Rumor ourRumor; Rumor ourRumor;
ourRumor.name = readBasicString(); ourRumor.name = readBasicString();
@@ -853,7 +827,7 @@ void CMapLoaderH3M::readPredefinedHeroes()
for(int yy = 0; yy < howMany; ++yy) for(int yy = 0; yy < howMany; ++yy)
{ {
hero->secSkills[yy].first = reader->readSkill(); hero->secSkills[yy].first = reader->readSkill();
hero->secSkills[yy].second = reader->readUInt8(); hero->secSkills[yy].second = reader->readInt8Checked(1,3);
} }
} }
@@ -864,7 +838,7 @@ void CMapLoaderH3M::readPredefinedHeroes()
hero->biographyCustomTextId = readLocalizedString(TextIdentifier("heroes", heroID, "biography")); hero->biographyCustomTextId = readLocalizedString(TextIdentifier("heroes", heroID, "biography"));
// 0xFF is default, 00 male, 01 female // 0xFF is default, 00 male, 01 female
hero->gender = static_cast<EHeroGender>(reader->readUInt8()); hero->gender = static_cast<EHeroGender>(reader->readInt8Checked(-1, 1));
assert(hero->gender == EHeroGender::MALE || hero->gender == EHeroGender::FEMALE || hero->gender == EHeroGender::DEFAULT); assert(hero->gender == EHeroGender::MALE || hero->gender == EHeroGender::FEMALE || hero->gender == EHeroGender::DEFAULT);
bool hasCustomSpells = reader->readBool(); bool hasCustomSpells = reader->readBool();
@@ -910,8 +884,8 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
// bag artifacts // bag artifacts
// number of artifacts in hero's bag // number of artifacts in hero's bag
int amount = reader->readUInt16(); size_t amount = reader->readUInt16();
for(int i = 0; i < amount; ++i) for(size_t i = 0; i < amount; ++i)
{ {
loadArtifactToSlot(hero, ArtifactPosition::BACKPACK_START + static_cast<int>(hero->artifactsInBackpack.size())); loadArtifactToSlot(hero, ArtifactPosition::BACKPACK_START + static_cast<int>(hero->artifactsInBackpack.size()));
} }
@@ -1038,33 +1012,33 @@ void CMapLoaderH3M::readBoxContent(CGPandoraBox * object, const int3 & mapPositi
reward.heroExperience = reader->readUInt32(); reward.heroExperience = reader->readUInt32();
reward.manaDiff = reader->readInt32(); reward.manaDiff = reader->readInt32();
if(auto val = reader->readUInt8()) if(auto val = reader->readInt8Checked(-3, 3))
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven)); reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven));
if(auto val = reader->readUInt8()) if(auto val = reader->readInt8Checked(-3, 3))
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven)); reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, val, BonusSourceID(idToBeGiven));
reader->readResourses(reward.resources); reader->readResourses(reward.resources);
for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x) for(int x = 0; x < GameConstants::PRIMARY_SKILLS; ++x)
reward.primary.at(x) = reader->readUInt8(); reward.primary.at(x) = reader->readUInt8();
int gabn = reader->readUInt8(); //number of gained abilities size_t gabn = reader->readUInt8(); //number of gained abilities
for(int oo = 0; oo < gabn; ++oo) for(size_t oo = 0; oo < gabn; ++oo)
{ {
auto rId = reader->readSkill(); auto rId = reader->readSkill();
auto rVal = reader->readUInt8(); auto rVal = reader->readInt8Checked(1,3);
reward.secondary[rId] = rVal; reward.secondary[rId] = rVal;
} }
int gart = reader->readUInt8(); //number of gained artifacts size_t gart = reader->readUInt8(); //number of gained artifacts
for(int oo = 0; oo < gart; ++oo) for(size_t oo = 0; oo < gart; ++oo)
reward.artifacts.push_back(reader->readArtifact()); reward.artifacts.push_back(reader->readArtifact());
int gspel = reader->readUInt8(); //number of gained spells size_t gspel = reader->readUInt8(); //number of gained spells
for(int oo = 0; oo < gspel; ++oo) for(size_t oo = 0; oo < gspel; ++oo)
reward.spells.push_back(reader->readSpell()); reward.spells.push_back(reader->readSpell());
int gcre = reader->readUInt8(); //number of gained creatures size_t gcre = reader->readUInt8(); //number of gained creatures
for(int oo = 0; oo < gcre; ++oo) for(size_t oo = 0; oo < gcre; ++oo)
{ {
auto rId = reader->readCreature(); auto rId = reader->readCreature();
auto rVal = reader->readUInt16(); auto rVal = reader->readUInt16();
@@ -1094,7 +1068,7 @@ CGObjectInstance * CMapLoaderH3M::readMonster(const int3 & mapPosition, const Ob
//type will be set during initialization //type will be set during initialization
object->putStack(SlotID(0), hlp); object->putStack(SlotID(0), hlp);
object->character = reader->readInt8(); object->character = reader->readInt8Checked(0, 4);
bool hasMessage = reader->readBool(); bool hasMessage = reader->readBool();
if(hasMessage) if(hasMessage)
@@ -1192,17 +1166,17 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share
CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate) CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate)
{ {
enum class ScholarBonusType : uint8_t { enum class ScholarBonusType : int8_t {
RANDOM = -1,
PRIM_SKILL = 0, PRIM_SKILL = 0,
SECONDARY_SKILL = 1, SECONDARY_SKILL = 1,
SPELL = 2, SPELL = 2,
RANDOM = 255
}; };
auto * object = readGeneric(position, objectTemplate); auto * object = readGeneric(position, objectTemplate);
auto * rewardable = dynamic_cast<CRewardableObject*>(object); auto * rewardable = dynamic_cast<CRewardableObject*>(object);
uint8_t bonusTypeRaw = reader->readUInt8(); uint8_t bonusTypeRaw = reader->readInt8Checked(-1, 2);
auto bonusType = static_cast<ScholarBonusType>(bonusTypeRaw); auto bonusType = static_cast<ScholarBonusType>(bonusTypeRaw);
auto bonusID = reader->readUInt8(); auto bonusID = reader->readUInt8();
@@ -1477,7 +1451,7 @@ CGObjectInstance * CMapLoaderH3M::readBank(const int3 & mapPosition, std::shared
int32_t guardsPresetIndex = reader->readInt32(); int32_t guardsPresetIndex = reader->readInt32();
// presence of upgraded stack: -1 = random, 0 = never, 1 = always // presence of upgraded stack: -1 = random, 0 = never, 1 = always
int8_t upgradedStackPresence = reader->readInt8(); int8_t upgradedStackPresence = reader->readInt8Checked(-1, 1);
assert(vstd::iswithin(guardsPresetIndex, -1, 4)); assert(vstd::iswithin(guardsPresetIndex, -1, 4));
assert(vstd::iswithin(upgradedStackPresence, -1, 1)); assert(vstd::iswithin(upgradedStackPresence, -1, 1));
@@ -1807,7 +1781,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
for(int i = 0; i < skillsCount; ++i) for(int i = 0; i < skillsCount; ++i)
{ {
object->secSkills[i].first = reader->readSkill(); object->secSkills[i].first = reader->readSkill();
object->secSkills[i].second = reader->readUInt8(); object->secSkills[i].second = reader->readInt8Checked(1,3);
} }
} }
@@ -1815,7 +1789,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
if(hasGarison) if(hasGarison)
readCreatureSet(object, 7); readCreatureSet(object, 7);
object->formation = static_cast<EArmyFormation>(reader->readUInt8()); object->formation = static_cast<EArmyFormation>(reader->readInt8Checked(0, 1));
assert(object->formation == EArmyFormation::LOOSE || object->formation == EArmyFormation::TIGHT); assert(object->formation == EArmyFormation::LOOSE || object->formation == EArmyFormation::TIGHT);
loadArtifactsOfHero(object); loadArtifactsOfHero(object);
@@ -1828,7 +1802,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
if(hasCustomBiography) if(hasCustomBiography)
object->biographyCustomTextId = readLocalizedString(TextIdentifier("heroes", object->subID, "biography")); object->biographyCustomTextId = readLocalizedString(TextIdentifier("heroes", object->subID, "biography"));
object->gender = static_cast<EHeroGender>(reader->readUInt8()); object->gender = static_cast<EHeroGender>(reader->readInt8Checked(-1, 1));
assert(object->gender == EHeroGender::MALE || object->gender == EHeroGender::FEMALE || object->gender == EHeroGender::DEFAULT); assert(object->gender == EHeroGender::MALE || object->gender == EHeroGender::FEMALE || object->gender == EHeroGender::DEFAULT);
} }
else else
@@ -1938,30 +1912,12 @@ enum class ESeerHutRewardType : uint8_t
CREATURE = 10, CREATURE = 10,
}; };
enum class EQuestMission {
NONE = 0,
LEVEL = 1,
PRIMARY_SKILL = 2,
KILL_HERO = 3,
KILL_CREATURE = 4,
ARTIFACT = 5,
ARMY = 6,
RESOURCES = 7,
HERO = 8,
PLAYER = 9,
HOTA_MULTI = 10,
// end of H3 missions
KEYMASTER = 100,
HOTA_HERO_CLASS = 101,
HOTA_REACH_DATE = 102
};
void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven) void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven)
{ {
EQuestMission missionType = EQuestMission::NONE; EQuestMission missionType = EQuestMission::NONE;
if(features.levelAB) if(features.levelAB)
{ {
missionType = static_cast<EQuestMission>(readQuest(hut, position)); missionType = readQuest(hut, position);
} }
else else
{ {
@@ -1981,7 +1937,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
if(missionType != EQuestMission::NONE) if(missionType != EQuestMission::NONE)
{ {
auto rewardType = static_cast<ESeerHutRewardType>(reader->readUInt8()); auto rewardType = static_cast<ESeerHutRewardType>(reader->readInt8Checked(0, 10));
Rewardable::VisitInfo vinfo; Rewardable::VisitInfo vinfo;
auto & reward = vinfo.reward; auto & reward = vinfo.reward;
switch(rewardType) switch(rewardType)
@@ -2003,36 +1959,34 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
} }
case ESeerHutRewardType::MORALE: case ESeerHutRewardType::MORALE:
{ {
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, reader->readUInt8(), BonusSourceID(idToBeGiven)); reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven));
break; break;
} }
case ESeerHutRewardType::LUCK: case ESeerHutRewardType::LUCK:
{ {
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, reader->readUInt8(), BonusSourceID(idToBeGiven)); reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, reader->readInt8Checked(-3, 3), BonusSourceID(idToBeGiven));
break; break;
} }
case ESeerHutRewardType::RESOURCES: case ESeerHutRewardType::RESOURCES:
{ {
auto rId = reader->readUInt8(); auto rId = reader->readGameResID();
auto rVal = reader->readUInt32(); auto rVal = reader->readUInt32();
assert(rId < features.resourcesCount);
reward.resources[rId] = rVal; reward.resources[rId] = rVal;
break; break;
} }
case ESeerHutRewardType::PRIMARY_SKILL: case ESeerHutRewardType::PRIMARY_SKILL:
{ {
auto rId = reader->readUInt8(); auto rId = reader->readPrimary();
auto rVal = reader->readUInt8(); auto rVal = reader->readUInt8();
reward.primary.at(rId) = rVal; reward.primary.at(rId.getNum()) = rVal;
break; break;
} }
case ESeerHutRewardType::SECONDARY_SKILL: case ESeerHutRewardType::SECONDARY_SKILL:
{ {
auto rId = reader->readSkill(); auto rId = reader->readSkill();
auto rVal = reader->readUInt8(); auto rVal = reader->readInt8Checked(1,3);
reward.secondary[rId] = rVal; reward.secondary[rId] = rVal;
break; break;
@@ -2071,11 +2025,11 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
} }
} }
int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
{ {
auto missionId = reader->readUInt8(); auto missionId = static_cast<EQuestMission>(reader->readInt8Checked(0, 10));
switch(static_cast<EQuestMission>(missionId)) switch(missionId)
{ {
case EQuestMission::NONE: case EQuestMission::NONE:
return missionId; return missionId;
@@ -2100,8 +2054,8 @@ int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
} }
case EQuestMission::ARTIFACT: case EQuestMission::ARTIFACT:
{ {
int artNumber = reader->readUInt8(); size_t artNumber = reader->readUInt8();
for(int yy = 0; yy < artNumber; ++yy) for(size_t yy = 0; yy < artNumber; ++yy)
{ {
auto artid = reader->readArtifact(); auto artid = reader->readArtifact();
guard->quest->mission.artifacts.push_back(artid); guard->quest->mission.artifacts.push_back(artid);
@@ -2111,9 +2065,9 @@ int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
} }
case EQuestMission::ARMY: case EQuestMission::ARMY:
{ {
int typeNumber = reader->readUInt8(); size_t typeNumber = reader->readUInt8();
guard->quest->mission.creatures.resize(typeNumber); guard->quest->mission.creatures.resize(typeNumber);
for(int hh = 0; hh < typeNumber; ++hh) for(size_t hh = 0; hh < typeNumber; ++hh)
{ {
guard->quest->mission.creatures[hh].type = reader->readCreature().toCreature(); guard->quest->mission.creatures[hh].type = reader->readCreature().toCreature();
guard->quest->mission.creatures[hh].count = reader->readUInt16(); guard->quest->mission.creatures[hh].count = reader->readUInt16();
@@ -2143,7 +2097,7 @@ int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
if(missionSubID == 0) if(missionSubID == 0)
{ {
missionId = int(EQuestMission::HOTA_HERO_CLASS); missionId = EQuestMission::HOTA_HERO_CLASS;
std::set<HeroClassID> heroClasses; std::set<HeroClassID> heroClasses;
reader->readBitmaskHeroClassesSized(heroClasses, false); reader->readBitmaskHeroClassesSized(heroClasses, false);
for(auto & hc : heroClasses) for(auto & hc : heroClasses)
@@ -2152,7 +2106,7 @@ int CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position)
} }
if(missionSubID == 1) if(missionSubID == 1)
{ {
missionId = int(EQuestMission::HOTA_REACH_DATE); missionId = EQuestMission::HOTA_REACH_DATE;
guard->quest->mission.daysPassed = reader->readUInt32() + 1; guard->quest->mission.daysPassed = reader->readUInt32() + 1;
break; break;
} }
@@ -2194,7 +2148,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt
if(hasGarrison) if(hasGarrison)
readCreatureSet(object, 7); readCreatureSet(object, 7);
object->formation = static_cast<EArmyFormation>(reader->readUInt8()); object->formation = static_cast<EArmyFormation>(reader->readInt8Checked(0, 1));
assert(object->formation == EArmyFormation::LOOSE || object->formation == EArmyFormation::TIGHT); assert(object->formation == EArmyFormation::LOOSE || object->formation == EArmyFormation::TIGHT);
bool hasCustomBuildings = reader->readBool(); bool hasCustomBuildings = reader->readBool();
@@ -2252,7 +2206,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt
else else
event.humanAffected = true; event.humanAffected = true;
event.computerAffected = reader->readUInt8(); event.computerAffected = reader->readBool();
event.firstOccurence = reader->readUInt16(); event.firstOccurence = reader->readUInt16();
event.nextOccurence = reader->readUInt8(); event.nextOccurence = reader->readUInt8();

View File

@@ -35,6 +35,50 @@ class SpellID;
class PlayerColor; class PlayerColor;
class int3; class int3;
enum class EQuestMission {
NONE = 0,
LEVEL = 1,
PRIMARY_SKILL = 2,
KILL_HERO = 3,
KILL_CREATURE = 4,
ARTIFACT = 5,
ARMY = 6,
RESOURCES = 7,
HERO = 8,
PLAYER = 9,
HOTA_MULTI = 10,
// end of H3 missions
KEYMASTER = 100,
HOTA_HERO_CLASS = 101,
HOTA_REACH_DATE = 102
};
enum class EVictoryConditionType : int8_t
{
WINSTANDARD = -1,
ARTIFACT = 0,
GATHERTROOP = 1,
GATHERRESOURCE = 2,
BUILDCITY = 3,
BUILDGRAIL = 4,
BEATHERO = 5,
CAPTURECITY = 6,
BEATMONSTER = 7,
TAKEDWELLINGS = 8,
TAKEMINES = 9,
TRANSPORTITEM = 10,
HOTA_ELIMINATE_ALL_MONSTERS = 11,
HOTA_SURVIVE_FOR_DAYS = 12
};
enum class ELossConditionType : int8_t
{
LOSSSTANDARD = -1,
LOSSCASTLE = 0,
LOSSHERO = 1,
TIMEEXPIRES = 2
};
class DLL_LINKAGE CMapLoaderH3M : public IMapLoader class DLL_LINKAGE CMapLoaderH3M : public IMapLoader
{ {
public: public:
@@ -204,7 +248,7 @@ private:
* *
* @param guard the quest guard where that quest should be applied to * @param guard the quest guard where that quest should be applied to
*/ */
int readQuest(IQuestObject * guard, const int3 & position); EQuestMission readQuest(IQuestObject * guard, const int3 & position);
void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven); void readSeerHutQuest(CGSeerHut * hut, const int3 & position, const ObjectInstanceID & idToBeGiven);

View File

@@ -1156,6 +1156,21 @@ void CMapLoaderJson::readObjects()
{ {
return a->getObjTypeIndex() < b->getObjTypeIndex(); return a->getObjTypeIndex() < b->getObjTypeIndex();
}); });
std::set<HeroTypeID> debugHeroesOnMap;
for (auto const & object : map->objects)
{
if(object->ID != Obj::HERO && object->ID != Obj::PRISON)
continue;
auto * hero = dynamic_cast<const CGHeroInstance *>(object.get());
if (debugHeroesOnMap.count(hero->getHeroType()))
logGlobal->error("Hero is already on the map at %s", hero->visitablePos().toString());
debugHeroesOnMap.insert(hero->getHeroType());
}
} }
void CMapLoaderJson::readTranslations() void CMapLoaderJson::readTranslations()

View File

@@ -183,6 +183,13 @@ RiverId MapReaderH3M::readRiver()
return result; return result;
} }
PrimarySkill MapReaderH3M::readPrimary()
{
PrimarySkill result(readUInt8());
assert(result <= PrimarySkill::KNOWLEDGE );
return result;
}
SecondarySkill MapReaderH3M::readSkill() SecondarySkill MapReaderH3M::readSkill()
{ {
SecondarySkill result(readUInt8()); SecondarySkill result(readUInt8());
@@ -400,32 +407,35 @@ bool MapReaderH3M::readBool()
return result != 0; return result != 0;
} }
ui8 MapReaderH3M::readUInt8() int8_t MapReaderH3M::readInt8Checked(int8_t lowerLimit, int8_t upperLimit)
{
int8_t result = readInt8();
assert(result >= lowerLimit);
assert(result <= upperLimit);
return std::clamp(result, lowerLimit, upperLimit);
}
uint8_t MapReaderH3M::readUInt8()
{ {
return reader->readUInt8(); return reader->readUInt8();
} }
si8 MapReaderH3M::readInt8() int8_t MapReaderH3M::readInt8()
{ {
return reader->readInt8(); return reader->readInt8();
} }
ui16 MapReaderH3M::readUInt16() uint16_t MapReaderH3M::readUInt16()
{ {
return reader->readUInt16(); return reader->readUInt16();
} }
si16 MapReaderH3M::readInt16() uint32_t MapReaderH3M::readUInt32()
{
return reader->readInt16();
}
ui32 MapReaderH3M::readUInt32()
{ {
return reader->readUInt32(); return reader->readUInt32();
} }
si32 MapReaderH3M::readInt32() int32_t MapReaderH3M::readInt32()
{ {
return reader->readInt32(); return reader->readInt32();
} }
@@ -435,9 +445,4 @@ std::string MapReaderH3M::readBaseString()
return reader->readBaseString(); return reader->readBaseString();
} }
CBinaryReader & MapReaderH3M::getInternalReader()
{
return *reader;
}
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@@ -40,6 +40,7 @@ public:
TerrainId readTerrain(); TerrainId readTerrain();
RoadId readRoad(); RoadId readRoad();
RiverId readRiver(); RiverId readRiver();
PrimarySkill readPrimary();
SecondarySkill readSkill(); SecondarySkill readSkill();
SpellID readSpell(); SpellID readSpell();
SpellID readSpell32(); SpellID readSpell32();
@@ -70,17 +71,17 @@ public:
bool readBool(); bool readBool();
ui8 readUInt8(); uint8_t readUInt8();
si8 readInt8(); int8_t readInt8();
ui16 readUInt16(); int8_t readInt8Checked(int8_t lowerLimit, int8_t upperLimit);
si16 readInt16();
ui32 readUInt32(); uint16_t readUInt16();
si32 readInt32();
uint32_t readUInt32();
int32_t readInt32();
std::string readBaseString(); std::string readBaseString();
CBinaryReader & getInternalReader();
private: private:
template<class Identifier> template<class Identifier>
Identifier remapIdentifier(const Identifier & identifier); Identifier remapIdentifier(const Identifier & identifier);

View File

@@ -54,7 +54,6 @@ void registerTypesMapObjects(Serializer &s)
s.template registerType<CGObjectInstance, CGMarket>(); s.template registerType<CGObjectInstance, CGMarket>();
s.template registerType<CGMarket, CGBlackMarket>(); s.template registerType<CGMarket, CGBlackMarket>();
s.template registerType<CGMarket, CGUniversity>(); s.template registerType<CGMarket, CGUniversity>();
s.template registerType<CGMarket, CGArtifactsAltar>();
s.template registerType<CGObjectInstance, CGHeroPlaceholder>(); s.template registerType<CGObjectInstance, CGHeroPlaceholder>();
s.template registerType<CGObjectInstance, CArmedInstance>(); s.template registerType<CBonusSystemNode, CArmedInstance>(); s.template registerType<CCreatureSet, CArmedInstance>(); s.template registerType<CGObjectInstance, CArmedInstance>(); s.template registerType<CBonusSystemNode, CArmedInstance>(); s.template registerType<CCreatureSet, CArmedInstance>();
@@ -133,6 +132,8 @@ void registerTypesMapObjects(Serializer &s)
//s.template registerType<CObstacleInstance>(); //s.template registerType<CObstacleInstance>();
s.template registerType<CObstacleInstance, SpellCreatedObstacle>(); s.template registerType<CObstacleInstance, SpellCreatedObstacle>();
s.template registerType<CGMarket, CGArtifactsAltar>();
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@@ -434,7 +434,7 @@ void CMapGenerator::addHeaderInfo()
m.twoLevel = mapGenOptions.getHasTwoLevels(); m.twoLevel = mapGenOptions.getHasTwoLevels();
m.name.appendLocalString(EMetaText::GENERAL_TXT, 740); m.name.appendLocalString(EMetaText::GENERAL_TXT, 740);
m.description.appendRawString(getMapDescription()); m.description.appendRawString(getMapDescription());
m.difficulty = 1; m.difficulty = EMapDifficulty::NORMAL;
addPlayerInfo(); addPlayerInfo();
m.waterMap = (mapGenOptions.getWaterContent() != EWaterContent::EWaterContent::NONE); m.waterMap = (mapGenOptions.getWaterContent() != EWaterContent::EWaterContent::NONE);
m.banWaterContent(); m.banWaterContent();

View File

@@ -135,9 +135,16 @@ void JsonSerializeFormat::readLICPart(const JsonNode & part, const JsonSerialize
{ {
const std::string & identifier = index.String(); const std::string & identifier = index.String();
const si32 rawId = decoder(identifier); try
if(rawId != -1) {
const si32 rawId = decoder(identifier);
value.insert(rawId); value.insert(rawId);
}
catch (const IdentifierResolutionException & e)
{
// downgrade exception to warning (printed as part of exception generation
// this is usually caused by loading allowed heroes / artifacts list from vmap's
}
} }
} }

View File

@@ -35,23 +35,23 @@ void GeneralSettings::initialize(MapController & c)
//set difficulty //set difficulty
switch(controller->map()->difficulty) switch(controller->map()->difficulty)
{ {
case 0: case EMapDifficulty::EASY:
ui->diffRadio1->setChecked(true); ui->diffRadio1->setChecked(true);
break; break;
case 1: case EMapDifficulty::NORMAL:
ui->diffRadio2->setChecked(true); ui->diffRadio2->setChecked(true);
break; break;
case 2: case EMapDifficulty::HARD:
ui->diffRadio3->setChecked(true); ui->diffRadio3->setChecked(true);
break; break;
case 3: case EMapDifficulty::EXPERT:
ui->diffRadio4->setChecked(true); ui->diffRadio4->setChecked(true);
break; break;
case 4: case EMapDifficulty::IMPOSSIBLE:
ui->diffRadio5->setChecked(true); ui->diffRadio5->setChecked(true);
break; break;
}; };
@@ -67,11 +67,11 @@ void GeneralSettings::update()
controller->map()->levelLimit = 0; controller->map()->levelLimit = 0;
//set difficulty //set difficulty
if(ui->diffRadio1->isChecked()) controller->map()->difficulty = 0; if(ui->diffRadio1->isChecked()) controller->map()->difficulty = EMapDifficulty::EASY;
if(ui->diffRadio2->isChecked()) controller->map()->difficulty = 1; if(ui->diffRadio2->isChecked()) controller->map()->difficulty = EMapDifficulty::NORMAL;
if(ui->diffRadio3->isChecked()) controller->map()->difficulty = 2; if(ui->diffRadio3->isChecked()) controller->map()->difficulty = EMapDifficulty::HARD;
if(ui->diffRadio4->isChecked()) controller->map()->difficulty = 3; if(ui->diffRadio4->isChecked()) controller->map()->difficulty = EMapDifficulty::EXPERT;
if(ui->diffRadio5->isChecked()) controller->map()->difficulty = 4; if(ui->diffRadio5->isChecked()) controller->map()->difficulty = EMapDifficulty::IMPOSSIBLE;
} }
void GeneralSettings::on_heroLevelLimitCheck_toggled(bool checked) void GeneralSettings::on_heroLevelLimitCheck_toggled(bool checked)

View File

@@ -584,10 +584,10 @@ void BattleFlowProcessor::stackEnchantedTrigger(const CBattleInfoCallback & batt
auto bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTED))); auto bl = *(st->getBonuses(Selector::type()(BonusType::ENCHANTED)));
for(auto b : bl) for(auto b : bl)
{ {
const CSpell * sp = b->subtype.as<SpellID>().toSpell(); if (!b->subtype.as<SpellID>().hasValue())
if(!sp)
continue; continue;
const CSpell * sp = b->subtype.as<SpellID>().toSpell();
const int32_t val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype)); const int32_t val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype));
const int32_t level = ((val > 3) ? (val - 3) : val); const int32_t level = ((val > 3) ? (val - 3) : val);