Bläddra i källkod

Merge remote-tracking branch 'upstream/develop' into develop

Xilmi 11 månader sedan
förälder
incheckning
e60a565942

+ 64 - 0
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" : { }
+	}
+}

+ 1 - 1
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" ]
 		},

+ 1 - 3
config/schemas/heroClass.json

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

+ 1 - 1
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"
 			]
 		},

+ 1 - 1
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

+ 2 - 2
lib/CCreatureSet.cpp

@@ -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

+ 10 - 3
lib/CCreatureSet.h

@@ -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;
 	}

+ 10 - 1
lib/bonuses/Limiters.h

@@ -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;
 	}
 };

+ 1 - 1
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<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;
 

+ 54 - 17
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<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;
+
+	assert(hero->gender != EHeroGender::DEFAULT);
 
-	if(filters.count(templ->stringID))
+	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

+ 11 - 7
lib/mapObjectConstructors/CommonConstructors.h

@@ -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;

+ 6 - 1
lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp

@@ -10,14 +10,19 @@
 #include "StdInc.h"
 #include "FlaggableInstanceConstructor.h"
 
+#include "../CConfigHandler.h"
+#include "../VCMI_Lib.h"
 #include "../json/JsonBonus.h"
+#include "../json/JsonUtils.h"
 #include "../texts/CGeneralTextHandler.h"
-#include "../VCMI_Lib.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));
 

+ 21 - 4
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())->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
 	{

+ 2 - 0
lib/mapObjects/CGHeroInstance.h

@@ -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;

+ 18 - 1
lib/mapObjects/CGMarket.h

@@ -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;
+		}
 	}
 };
 

+ 19 - 1
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;

+ 3 - 4
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;

+ 0 - 4
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();

+ 2 - 1
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));