From d3f6ec50889157ee07677ad17b26cb36f17ba4f5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 3 Nov 2024 14:36:54 +0000 Subject: [PATCH 1/6] Fixed schema to account for lighthouse -> flaggable change --- config/schemas/object.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/schemas/object.json b/config/schemas/object.json index f74dffce3..59d913440 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -21,7 +21,7 @@ "enum" : [ "configurable", "dwelling", "hero", "town", "boat", "market", "hillFort", "shipyard", "monster", "resource", "static", "randomArtifact", "randomHero", "randomResource", "randomTown", "randomMonster", "randomDwelling", "generic", "artifact", "borderGate", "borderGuard", "denOfThieves", - "event", "garrison", "heroPlaceholder", "keymaster", "lighthouse", "magi", "mine", "obelisk", "pandora", "prison", "questGuard", "seerHut", "sign", + "event", "garrison", "heroPlaceholder", "keymaster", "flaggable", "magi", "mine", "obelisk", "pandora", "prison", "questGuard", "seerHut", "sign", "siren", "monolith", "subterraneanGate", "whirlpool", "terrain" ] }, From 8cd19f639f761fc1ff2f8770ba188886ced10e97 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 3 Nov 2024 14:47:13 +0000 Subject: [PATCH 2/6] Added validation of flaggable objects --- config/schemas/flaggable.json | 64 +++++++++++++++++++ .../FlaggableInstanceConstructor.cpp | 9 ++- 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 config/schemas/flaggable.json diff --git a/config/schemas/flaggable.json b/config/schemas/flaggable.json new file mode 100644 index 000000000..534c9297f --- /dev/null +++ b/config/schemas/flaggable.json @@ -0,0 +1,64 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-04/schema", + "title" : "VCMI map object format", + "description" : "Description of map object class", + "required" : [ "message" ], + "anyOf" : [ //NOTE: strictly speaking, not required - buidling can function without it, but won't do anythin + { + "required" : [ "bonuses" ] + }, + { + "required" : [ "dailyIncome" ] + } + ], + "additionalProperties" : false, + + "properties" : { + "bonuses" : { + "type" : "object", + "description" : "List of bonuses provided by this map object. See bonus format for more details", + "additionalProperties" : { "$ref" : "bonus.json" } + }, + + "message" : { + "description" : "Message that will be shown to player on capturing this object", + "anyOf" : [ + { + "type" : "string", + }, + { + "type" : "number", + } + ] + }, + + "dailyIncome" : { + "type" : "object", + "additionalProperties" : false, + "description" : "Daily income that this building provides to owner, if any", + "properties" : { + "gold" : { "type" : "number"}, + "wood" : { "type" : "number"}, + "ore" : { "type" : "number"}, + "mercury" : { "type" : "number"}, + "sulfur" : { "type" : "number"}, + "crystal" : { "type" : "number"}, + "gems" : { "type" : "number"} + } + }, + + // Properties that might appear since this node is shared with object config + "compatibilityIdentifiers" : { }, + "blockedVisitable" : { }, + "removable" : { }, + "aiValue" : { }, + "index" : { }, + "base" : { }, + "name" : { }, + "rmg" : { }, + "templates" : { }, + "battleground" : { }, + "sounds" : { } + } +} diff --git a/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp b/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp index eeac5ee13..ed5eac661 100644 --- a/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp @@ -10,14 +10,19 @@ #include "StdInc.h" #include "FlaggableInstanceConstructor.h" -#include "../json/JsonBonus.h" -#include "../texts/CGeneralTextHandler.h" +#include "../CConfigHandler.h" #include "../VCMI_Lib.h" +#include "../json/JsonBonus.h" +#include "../json/JsonUtils.h" +#include "../texts/CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN void FlaggableInstanceConstructor::initTypeData(const JsonNode & config) { + if (settings["mods"]["validation"].String() != "off") + JsonUtils::validate(config, "vcmi:flaggable", getJsonKey()); + for (const auto & bonusJson : config["bonuses"].Struct()) providedBonuses.push_back(JsonUtils::parseBonus(bonusJson.second)); From 697d63d2b83a9353788870ced82ff1281548a83d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 3 Nov 2024 18:50:47 +0000 Subject: [PATCH 3/6] Reworked and fixed gendered hero sprites on adventure map --- config/schemas/hero.json | 2 +- config/schemas/heroClass.json | 4 +- .../AObjectTypeHandler.h | 2 +- .../CommonConstructors.cpp | 71 ++++++++++++++----- .../CommonConstructors.h | 18 +++-- lib/mapObjects/CGHeroInstance.cpp | 25 +++++-- lib/mapObjects/CGHeroInstance.h | 2 + lib/networkPacks/NetPacksLib.cpp | 3 +- 8 files changed, 93 insertions(+), 34 deletions(-) diff --git a/config/schemas/hero.json b/config/schemas/hero.json index 8d533387b..c1ccc548a 100644 --- a/config/schemas/hero.json +++ b/config/schemas/hero.json @@ -4,7 +4,7 @@ "title" : "VCMI hero format", "description" : "Format used to define new heroes in VCMI", "required" : [ "class", "army", "skills", "texts" ], - "oneOf" : [ + "anyOf" : [ { "required" : [ "images" ] }, diff --git a/config/schemas/heroClass.json b/config/schemas/heroClass.json index b6f5db342..93a8607a8 100644 --- a/config/schemas/heroClass.json +++ b/config/schemas/heroClass.json @@ -60,9 +60,7 @@ "properties" : { "filters" : { "type" : "object", - "additionalProperties" : { - "type" : "array" - } + "additionalProperties" : true } } }, diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.h b/lib/mapObjectConstructors/AObjectTypeHandler.h index 402aa2aec..52a2c7c1d 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.h +++ b/lib/mapObjectConstructors/AObjectTypeHandler.h @@ -90,7 +90,7 @@ public: /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) - std::shared_ptr getOverride(TerrainId terrainType, const CGObjectInstance * object) const; + virtual std::shared_ptr getOverride(TerrainId terrainType, const CGObjectInstance * object) const; BattleField getBattlefield() const; diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index f0434d415..57b52bd43 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -123,34 +123,71 @@ void CHeroInstanceConstructor::initTypeData(const JsonNode & input) input["heroClass"], [&](si32 index) { heroClass = HeroClassID(index).toHeroClass(); }); - filtersJson = input["filters"]; -} - -void CHeroInstanceConstructor::afterLoadFinalization() -{ - for(const auto & entry : filtersJson.Struct()) + for (const auto & [name, config] : input["filters"].Struct()) { - filters[entry.first] = LogicalExpression(entry.second, [](const JsonNode & node) + HeroFilter filter; + filter.allowFemale = config["female"].Bool(); + filter.allowMale = config["male"].Bool(); + filters[name] = filter; + + if (!config["hero"].isNull()) { - return HeroTypeID(VLC->identifiers()->getIdentifier("hero", node.Vector()[0]).value_or(-1)); - }); + VLC->identifiers()->requestIdentifier( "hero", config["hero"], [this, templateName = name](si32 index) { + filters.at(templateName).fixedHero = HeroTypeID(index); + }); + } } } -bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std::shared_ptr templ) const +std::shared_ptr CHeroInstanceConstructor::getOverride(TerrainId terrainType, const CGObjectInstance * object) const { const auto * hero = dynamic_cast(object); - auto heroTest = [&](const HeroTypeID & id) - { - return hero->getHeroTypeID() == id; - }; + std::vector> allTemplates = getTemplates(); + std::shared_ptr candidateFullMatch; + std::shared_ptr candidateGenderMatch; + std::shared_ptr candidateBase; - if(filters.count(templ->stringID)) + assert(hero->gender != EHeroGender::DEFAULT); + + for (const auto & templ : allTemplates) { - return filters.at(templ->stringID).test(heroTest); + if (filters.count(templ->stringID)) + { + const auto & filter = filters.at(templ->stringID); + if (filter.fixedHero.hasValue()) + { + if (filter.fixedHero == hero->getHeroTypeID()) + candidateFullMatch = templ; + } + else if (filter.allowMale) + { + if (hero->gender == EHeroGender::MALE) + candidateGenderMatch = templ; + } + else if (filter.allowFemale) + { + if (hero->gender == EHeroGender::FEMALE) + candidateGenderMatch = templ; + } + else + { + candidateBase = templ; + } + } + else + { + candidateBase = templ; + } } - return false; + + if (candidateFullMatch) + return candidateFullMatch; + + if (candidateGenderMatch) + return candidateGenderMatch; + + return candidateBase; } void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const diff --git a/lib/mapObjectConstructors/CommonConstructors.h b/lib/mapObjectConstructors/CommonConstructors.h index 1a048df20..8d4abcc67 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -72,17 +72,21 @@ public: class CHeroInstanceConstructor : public CDefaultObjectTypeHandler { - JsonNode filtersJson; -protected: - bool objectFilter(const CGObjectInstance * obj, std::shared_ptr tmpl) const override; + struct HeroFilter + { + HeroTypeID fixedHero; + bool allowMale; + bool allowFemale; + }; + + std::map filters; + const CHeroClass * heroClass = nullptr; + + std::shared_ptr getOverride(TerrainId terrainType, const CGObjectInstance * object) const override; void initTypeData(const JsonNode & input) override; public: - const CHeroClass * heroClass = nullptr; - std::map> filters; - void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override; - void afterLoadFinalization() override; bool hasNameTextID() const override; std::string getNameTextID() const override; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 539da4345..be35ae4b4 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -336,6 +336,11 @@ void CGHeroInstance::setHeroType(HeroTypeID heroType) subID = heroType; } +void CGHeroInstance::initObj(vstd::RNG & rand) +{ + updateAppearance(); +} + void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID) { subID = SUBID.getNum(); @@ -350,12 +355,27 @@ TObjectTypeHandler CGHeroInstance::getObjectHandler() const return VLC->objtypeh->getHandlerFor(ID, 0); } +void CGHeroInstance::updateAppearance() +{ + auto handler = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex());; + auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId(); + auto app = handler->getOverride(terrain, this); + if (app) + appearance = app; +} + void CGHeroInstance::initHero(vstd::RNG & rand) { assert(validTypes(true)); + if (gender == EHeroGender::DEFAULT) + gender = getHeroType()->gender; + if (ID == Obj::HERO) - appearance = getObjectHandler()->getTemplates().front(); + { + auto handler = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex());; + appearance = handler->getTemplates().front(); + } if(!vstd::contains(spells, SpellID::PRESET)) { @@ -394,9 +414,6 @@ void CGHeroInstance::initHero(vstd::RNG & rand) if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::NONE, -1)) //set secondary skills to default secSkills = getHeroType()->secSkillsInit; - if (gender == EHeroGender::DEFAULT) - gender = getHeroType()->gender; - setFormation(EArmyFormation::LOOSE); if (!stacksCount()) //standard army//initial army { diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 53a59ec13..c673b1727 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -241,6 +241,7 @@ public: HeroTypeID getHeroTypeID() const; void setHeroType(HeroTypeID type); + void initObj(vstd::RNG & rand) override; void initHero(vstd::RNG & rand); void initHero(vstd::RNG & rand, const HeroTypeID & SUBID); @@ -300,6 +301,7 @@ public: void attachToBoat(CGBoat* newBoat); void boatDeserializationFix(); void deserializationFix(); + void updateAppearance(); void pickRandomObject(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index f8f8e49d6..5317855ed 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1430,6 +1430,7 @@ void HeroRecruited::applyGs(CGameState *gs) h->setOwner(player); h->pos = tile; + h->updateAppearance(); if(h->id == ObjectInstanceID()) { @@ -1469,7 +1470,7 @@ void GiveHero::applyGs(CGameState *gs) auto oldVisitablePos = h->visitablePos(); gs->map->removeBlockVisTiles(h,true); - h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->getHeroClassID().getNum())->getTemplates().front(); + h->updateAppearance(); h->setOwner(player); h->setMovementPoints(h->movementPointsLimit(true)); From b9ff192a9111b2863169aa9759b66c163cbc6d6a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 7 Nov 2024 12:07:45 +0000 Subject: [PATCH 4/6] Fix regressions from previous PR --- lib/CArtifactInstance.cpp | 2 +- lib/CCreatureSet.cpp | 4 ++-- lib/CCreatureSet.h | 13 ++++++++++--- lib/mapObjects/CGHeroInstance.cpp | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/CArtifactInstance.cpp b/lib/CArtifactInstance.cpp index 24e7f21db..e1be208dd 100644 --- a/lib/CArtifactInstance.cpp +++ b/lib/CArtifactInstance.cpp @@ -141,7 +141,7 @@ ArtifactID CArtifactInstance::getTypeId() const const CArtifact * CArtifactInstance::getType() const { - return artTypeID.toArtifact(); + return artTypeID.hasValue() ? artTypeID.toArtifact() : nullptr; } ArtifactInstanceID CArtifactInstance::getId() const diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 401611094..d96f4e79d 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -1005,12 +1005,12 @@ CStackBasicDescriptor::CStackBasicDescriptor(const CCreature *c, TQuantity Count const CCreature * CStackBasicDescriptor::getCreature() const { - return typeID.toCreature(); + return typeID.hasValue() ? typeID.toCreature() : nullptr; } const Creature * CStackBasicDescriptor::getType() const { - return typeID.toEntity(VLC); + return typeID.hasValue() ? typeID.toEntity(VLC) : nullptr; } CreatureID CStackBasicDescriptor::getId() const diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index e40c800a1..97065b597 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -51,9 +51,16 @@ public: template void serialize(Handler &h) { - h & typeID; - if(!h.saving) - setType(typeID.toCreature()); + if(h.saving) + { + h & typeID; + } + else + { + CreatureID creatureID; + h & creatureID; + setType(creatureID.toCreature()); + } h & count; } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index be35ae4b4..182494206 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -358,7 +358,7 @@ TObjectTypeHandler CGHeroInstance::getObjectHandler() const void CGHeroInstance::updateAppearance() { auto handler = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex());; - auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId(); + auto terrain = cb->gameState()->getTile(visitablePos())->getTerrainID(); auto app = handler->getOverride(terrain, this); if (app) appearance = app; From c3c5f73a63f485eb4fc445ab2bbb99829445b9a4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 7 Nov 2024 12:08:08 +0000 Subject: [PATCH 5/6] Restore save compatibility with 1.5 --- lib/bonuses/Limiters.h | 11 ++++++++++- lib/mapObjects/CGMarket.h | 19 ++++++++++++++++++- lib/mapping/CMap.h | 20 +++++++++++++++++++- lib/mapping/CMapDefines.h | 7 +++---- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/bonuses/Limiters.h b/lib/bonuses/Limiters.h index 7a1813bab..9ad0e56d2 100644 --- a/lib/bonuses/Limiters.h +++ b/lib/bonuses/Limiters.h @@ -108,7 +108,16 @@ public: template void serialize(Handler &h) { h & static_cast(*this); - h & creatureID; + + if (h.version < Handler::Version::REMOVE_TOWN_PTR) + { + bool isNull = false; + h & isNull; + if(!isNull) + h & creatureID; + } + else + h & creatureID; h & includeUpgrades; } }; diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index 9f2651028..b28a386bc 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -78,7 +78,24 @@ public: template void serialize(Handler &h) { h & static_cast(*this); - h & artifacts; + if (h.version < Handler::Version::REMOVE_VLC_POINTERS) + { + int32_t size = 0; + h & size; + for (int32_t i = 0; i < size; ++i) + { + bool isNull = false; + ArtifactID artifact; + h & isNull; + if (!isNull) + h & artifact; + artifacts.push_back(artifact); + } + } + else + { + h & artifacts; + } } }; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 7d48dc931..5192f7c1a 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -222,7 +222,25 @@ public: // static members h & obeliskCount; h & obelisksVisited; - h & townMerchantArtifacts; + + if (h.version < Handler::Version::REMOVE_VLC_POINTERS) + { + int32_t size = 0; + h & size; + for (int32_t i = 0; i < size; ++i) + { + bool isNull = false; + ArtifactID artifact; + h & isNull; + if (!isNull) + h & artifact; + townMerchantArtifacts.push_back(artifact); + } + } + else + { + h & townMerchantArtifacts; + } h & townUniversitySkills; h & instanceNames; diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 49f27fa33..b37fcc692 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -146,10 +146,9 @@ struct DLL_LINKAGE TerrainTile { bool isNull = false; h & isNull; - if (isNull) + if (!isNull) h & terrainType; } - h & terrainType; h & terView; if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) { @@ -159,7 +158,7 @@ struct DLL_LINKAGE TerrainTile { bool isNull = false; h & isNull; - if (isNull) + if (!isNull) h & riverType; } h & riverDir; @@ -171,7 +170,7 @@ struct DLL_LINKAGE TerrainTile { bool isNull = false; h & isNull; - if (isNull) + if (!isNull) h & roadType; } h & roadDir; From 733b1b339c4f0d42f9882fefbb68b304669eeb6f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 7 Nov 2024 12:08:28 +0000 Subject: [PATCH 6/6] Remove broken automatic addition of dependency for wog maps --- lib/mapping/MapFormatH3M.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 5340538f7..969e168b2 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -215,10 +215,6 @@ void CMapLoaderH3M::readHeader() reader->setIdentifierRemapper(identifierMapper); - // include basic mod - if(mapHeader->version == EMapFormat::WOG) - mapHeader->mods["wake-of-gods"]; - // Read map name, description, dimensions,... mapHeader->areAnyPlayers = reader->readBool(); mapHeader->height = mapHeader->width = reader->readInt32();