1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-18 03:21:27 +02:00

Reworked and fixed gendered hero sprites on adventure map

This commit is contained in:
Ivan Savenko 2024-11-03 18:50:47 +00:00
parent 8cd19f639f
commit 697d63d2b8
8 changed files with 93 additions and 34 deletions

View File

@ -4,7 +4,7 @@
"title" : "VCMI hero format", "title" : "VCMI hero format",
"description" : "Format used to define new heroes in VCMI", "description" : "Format used to define new heroes in VCMI",
"required" : [ "class", "army", "skills", "texts" ], "required" : [ "class", "army", "skills", "texts" ],
"oneOf" : [ "anyOf" : [
{ {
"required" : [ "images" ] "required" : [ "images" ]
}, },

View File

@ -60,9 +60,7 @@
"properties" : { "properties" : {
"filters" : { "filters" : {
"type" : "object", "type" : "object",
"additionalProperties" : { "additionalProperties" : true
"type" : "array"
}
} }
} }
}, },

View File

@ -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) /// 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) /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server)
std::shared_ptr<const ObjectTemplate> getOverride(TerrainId terrainType, const CGObjectInstance * object) const; virtual std::shared_ptr<const ObjectTemplate> getOverride(TerrainId terrainType, const CGObjectInstance * object) const;
BattleField getBattlefield() const; BattleField getBattlefield() const;

View File

@ -123,34 +123,71 @@ void CHeroInstanceConstructor::initTypeData(const JsonNode & input)
input["heroClass"], input["heroClass"],
[&](si32 index) { heroClass = HeroClassID(index).toHeroClass(); }); [&](si32 index) { heroClass = HeroClassID(index).toHeroClass(); });
filtersJson = input["filters"]; for (const auto & [name, config] : input["filters"].Struct())
}
void CHeroInstanceConstructor::afterLoadFinalization()
{
for(const auto & entry : filtersJson.Struct())
{ {
filters[entry.first] = LogicalExpression<HeroTypeID>(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<const ObjectTemplate> templ) const std::shared_ptr<const ObjectTemplate> CHeroInstanceConstructor::getOverride(TerrainId terrainType, const CGObjectInstance * object) const
{ {
const auto * hero = dynamic_cast<const CGHeroInstance *>(object); const auto * hero = dynamic_cast<const CGHeroInstance *>(object);
auto heroTest = [&](const HeroTypeID & id) std::vector<std::shared_ptr<const ObjectTemplate>> allTemplates = getTemplates();
{ std::shared_ptr<const ObjectTemplate> candidateFullMatch;
return hero->getHeroTypeID() == id; std::shared_ptr<const ObjectTemplate> candidateGenderMatch;
}; std::shared_ptr<const ObjectTemplate> 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 void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const

View File

@ -72,17 +72,21 @@ public:
class CHeroInstanceConstructor : public CDefaultObjectTypeHandler<CGHeroInstance> class CHeroInstanceConstructor : public CDefaultObjectTypeHandler<CGHeroInstance>
{ {
JsonNode filtersJson; struct HeroFilter
protected: {
bool objectFilter(const CGObjectInstance * obj, std::shared_ptr<const ObjectTemplate> tmpl) const override; HeroTypeID fixedHero;
bool allowMale;
bool allowFemale;
};
std::map<std::string, HeroFilter> filters;
const CHeroClass * heroClass = nullptr;
std::shared_ptr<const ObjectTemplate> getOverride(TerrainId terrainType, const CGObjectInstance * object) const override;
void initTypeData(const JsonNode & input) override; void initTypeData(const JsonNode & input) override;
public: public:
const CHeroClass * heroClass = nullptr;
std::map<std::string, LogicalExpression<HeroTypeID>> filters;
void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override; void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override;
void afterLoadFinalization() override;
bool hasNameTextID() const override; bool hasNameTextID() const override;
std::string getNameTextID() const override; std::string getNameTextID() const override;

View File

@ -336,6 +336,11 @@ void CGHeroInstance::setHeroType(HeroTypeID heroType)
subID = heroType; subID = heroType;
} }
void CGHeroInstance::initObj(vstd::RNG & rand)
{
updateAppearance();
}
void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID) void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID)
{ {
subID = SUBID.getNum(); subID = SUBID.getNum();
@ -350,12 +355,27 @@ TObjectTypeHandler CGHeroInstance::getObjectHandler() const
return VLC->objtypeh->getHandlerFor(ID, 0); 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) void CGHeroInstance::initHero(vstd::RNG & rand)
{ {
assert(validTypes(true)); assert(validTypes(true));
if (gender == EHeroGender::DEFAULT)
gender = getHeroType()->gender;
if (ID == Obj::HERO) 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)) 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,ui8>(SecondarySkill::NONE, -1)) //set secondary skills to default if(secSkills.size() == 1 && secSkills[0] == std::pair<SecondarySkill,ui8>(SecondarySkill::NONE, -1)) //set secondary skills to default
secSkills = getHeroType()->secSkillsInit; secSkills = getHeroType()->secSkillsInit;
if (gender == EHeroGender::DEFAULT)
gender = getHeroType()->gender;
setFormation(EArmyFormation::LOOSE); setFormation(EArmyFormation::LOOSE);
if (!stacksCount()) //standard army//initial army if (!stacksCount()) //standard army//initial army
{ {

View File

@ -241,6 +241,7 @@ public:
HeroTypeID getHeroTypeID() const; HeroTypeID getHeroTypeID() const;
void setHeroType(HeroTypeID type); void setHeroType(HeroTypeID type);
void initObj(vstd::RNG & rand) override;
void initHero(vstd::RNG & rand); void initHero(vstd::RNG & rand);
void initHero(vstd::RNG & rand, const HeroTypeID & SUBID); void initHero(vstd::RNG & rand, const HeroTypeID & SUBID);
@ -300,6 +301,7 @@ public:
void attachToBoat(CGBoat* newBoat); void attachToBoat(CGBoat* newBoat);
void boatDeserializationFix(); void boatDeserializationFix();
void deserializationFix(); void deserializationFix();
void updateAppearance();
void pickRandomObject(vstd::RNG & rand) override; void pickRandomObject(vstd::RNG & rand) override;
void onHeroVisit(const CGHeroInstance * h) const override; void onHeroVisit(const CGHeroInstance * h) const override;

View File

@ -1430,6 +1430,7 @@ void HeroRecruited::applyGs(CGameState *gs)
h->setOwner(player); h->setOwner(player);
h->pos = tile; h->pos = tile;
h->updateAppearance();
if(h->id == ObjectInstanceID()) if(h->id == ObjectInstanceID())
{ {
@ -1469,7 +1470,7 @@ void GiveHero::applyGs(CGameState *gs)
auto oldVisitablePos = h->visitablePos(); auto oldVisitablePos = h->visitablePos();
gs->map->removeBlockVisTiles(h,true); gs->map->removeBlockVisTiles(h,true);
h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->getHeroClassID().getNum())->getTemplates().front(); h->updateAppearance();
h->setOwner(player); h->setOwner(player);
h->setMovementPoints(h->movementPointsLimit(true)); h->setMovementPoints(h->movementPointsLimit(true));