From 697d63d2b83a9353788870ced82ff1281548a83d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 3 Nov 2024 18:50:47 +0000 Subject: [PATCH] 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));