mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-12 02:28:11 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
e60a565942
64
config/schemas/flaggable.json
Normal file
64
config/schemas/flaggable.json
Normal file
@ -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" : { }
|
||||
}
|
||||
}
|
@ -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" ]
|
||||
},
|
||||
|
@ -60,9 +60,7 @@
|
||||
"properties" : {
|
||||
"filters" : {
|
||||
"type" : "object",
|
||||
"additionalProperties" : {
|
||||
"type" : "array"
|
||||
}
|
||||
"additionalProperties" : true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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"
|
||||
]
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -1019,12 +1019,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
|
||||
|
@ -51,9 +51,16 @@ public:
|
||||
|
||||
template <typename Handler> 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;
|
||||
}
|
||||
|
@ -108,7 +108,16 @@ public:
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<ILimiter&>(*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;
|
||||
}
|
||||
};
|
||||
|
@ -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<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;
|
||||
|
||||
|
@ -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<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);
|
||||
|
||||
auto heroTest = [&](const HeroTypeID & id)
|
||||
{
|
||||
return hero->getHeroTypeID() == id;
|
||||
};
|
||||
std::vector<std::shared_ptr<const ObjectTemplate>> allTemplates = getTemplates();
|
||||
std::shared_ptr<const ObjectTemplate> candidateFullMatch;
|
||||
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
|
||||
|
@ -72,17 +72,21 @@ public:
|
||||
|
||||
class CHeroInstanceConstructor : public CDefaultObjectTypeHandler<CGHeroInstance>
|
||||
{
|
||||
JsonNode filtersJson;
|
||||
protected:
|
||||
bool objectFilter(const CGObjectInstance * obj, std::shared_ptr<const ObjectTemplate> tmpl) const override;
|
||||
struct HeroFilter
|
||||
{
|
||||
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;
|
||||
|
||||
public:
|
||||
const CHeroClass * heroClass = nullptr;
|
||||
std::map<std::string, LogicalExpression<HeroTypeID>> filters;
|
||||
|
||||
void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override;
|
||||
void afterLoadFinalization() override;
|
||||
|
||||
bool hasNameTextID() const override;
|
||||
std::string getNameTextID() const override;
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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())->getTerrainID();
|
||||
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,ui8>(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
|
||||
{
|
||||
|
@ -244,6 +244,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);
|
||||
|
||||
@ -303,6 +304,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;
|
||||
|
@ -78,7 +78,24 @@ public:
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<CGMarket&>(*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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user