瀏覽代碼

Spell shrines can now be configured in json

Ivan Savenko 2 年之前
父節點
當前提交
fc190b14bb

+ 2 - 0
cmake_modules/VCMI_lib.cmake

@@ -70,6 +70,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/mapObjectConstructors/CObjectClassesHandler.cpp
 		${MAIN_LIB_DIR}/mapObjectConstructors/CommonConstructors.cpp
 		${MAIN_LIB_DIR}/mapObjectConstructors/CRewardableConstructor.cpp
+		${MAIN_LIB_DIR}/mapObjectConstructors/ShrineInstanceConstructor.cpp
 
 		${MAIN_LIB_DIR}/mapObjects/CArmedInstance.cpp
 		${MAIN_LIB_DIR}/mapObjects/CBank.cpp
@@ -377,6 +378,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/mapObjectConstructors/CRewardableConstructor.h
 		${MAIN_LIB_DIR}/mapObjectConstructors/IObjectInfo.h
 		${MAIN_LIB_DIR}/mapObjectConstructors/RandomMapInfo.h
+		${MAIN_LIB_DIR}/mapObjectConstructors/ShrineInstanceConstructor.h
 		${MAIN_LIB_DIR}/mapObjectConstructors/SObjectSounds.h
 
 		${MAIN_LIB_DIR}/mapObjects/CArmedInstance.h

+ 12 - 0
config/objects/generic.json

@@ -311,6 +311,10 @@
 					"value"		: 500,
 					"rarity"	: 100
 				}
+				"visitText" : 127,
+				"spell" : {
+					"level" : 1
+				}
 			}
 		}
 	},
@@ -330,6 +334,10 @@
 				"rmg" : {
 					"value"		: 2000,
 					"rarity"	: 100
+				},
+				"visitText" : 128,
+				"spell" : {
+					"level" : 2
 				}
 			}
 		}
@@ -350,6 +358,10 @@
 				"rmg" : {
 					"value"		: 3000,
 					"rarity"	: 100
+				},
+				"visitText" : 129,
+				"spell" : {
+					"level" : 3
 				}
 			}
 		}

+ 2 - 0
include/vcmi/spells/Spell.h

@@ -15,6 +15,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class SpellID;
+enum class ESpellSchool: int8_t;
 
 namespace spells
 {
@@ -43,6 +44,7 @@ public:
 	virtual bool isSpecial() const = 0;
 	virtual bool isMagical() const = 0; //Should this spell considered as magical effect or as ability (like dendroid's bind)
 
+	virtual bool hasSchool(ESpellSchool school) const = 0;
 	virtual void forEachSchool(const SchoolCallback & cb) const = 0;
 	virtual const std::string & getCastSound() const = 0;
 	virtual int32_t getCost(const int32_t skillLevel) const = 0;

+ 9 - 6
lib/IGameCallback.cpp

@@ -170,16 +170,19 @@ void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact *>
 	out.push_back(VLC->arth->objects[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR)]);
 }
 
-void CPrivilegedInfoCallback::getAllowedSpells(std::vector<SpellID> & out, ui16 level)
+void CPrivilegedInfoCallback::getAllowedSpells(std::vector<SpellID> & out, std::optional<ui16> level)
 {
 	for (ui32 i = 0; i < gs->map->allowedSpell.size(); i++) //spellh size appears to be greater (?)
 	{
-
 		const spells::Spell * spell = SpellID(i).toSpell();
-		if(isAllowed(0, spell->getIndex()) && spell->getLevel() == level)
-		{
-			out.push_back(spell->getId());
-		}
+
+		if (!isAllowed(0, spell->getIndex()))
+			continue;
+
+		if (level.has_value() && spell->getLevel() != level)
+			continue;
+
+		out.push_back(spell->getId());
 	}
 }
 

+ 1 - 1
lib/IGameCallback.h

@@ -71,7 +71,7 @@ public:
 
 	//gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant
 	void pickAllowedArtsSet(std::vector<const CArtifact *> & out, CRandomGenerator & rand) const; 
-	void getAllowedSpells(std::vector<SpellID> &out, ui16 level);
+	void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
 
 	template<typename Saver>
 	void saveCommonState(Saver &out) const; //stores GS and VLC

+ 23 - 8
lib/JsonRandom.cpp

@@ -243,21 +243,36 @@ namespace JsonRandom
 		if (value.getType() == JsonNode::JsonType::DATA_STRING)
 			return SpellID(VLC->modh->identifiers.getIdentifier("spell", value).value());
 
-		vstd::erase_if(spells, [=](const SpellID & spell)
+		if (!value["level"].isNull())
 		{
-			return VLC->spellh->getById(spell)->getLevel() != si32(value["level"].Float());
-		});
+			int32_t spellLevel = value["level"].Float();
+
+			vstd::erase_if(spells, [=](const SpellID & spell)
+			{
+				return VLC->spellh->getById(spell)->getLevel() != spellLevel;
+			});
+		}
+
+		if (!value["school"].isNull())
+		{
+			int32_t schoolID = VLC->modh->identifiers.getIdentifier("spellSchool", value["school"]).value();
 
+			vstd::erase_if(spells, [=](const SpellID & spell)
+			{
+				return !VLC->spellh->getById(spell)->hasSchool(ESpellSchool(schoolID));
+			});
+		}
+
+		if (spells.empty())
+		{
+			logMod->warn("Failed to select suitable random spell!");
+			return SpellID::NONE;
+		}
 		return SpellID(*RandomGeneratorUtil::nextItem(spells, rng));
 	}
 
 	std::vector<SpellID> loadSpells(const JsonNode & value, CRandomGenerator & rng, const std::vector<SpellID> & spells)
 	{
-		// possible extensions: (taken from spell json config)
-		// "type": "adventure",//"adventure", "combat", "ability"
-		// "school": {"air":true, "earth":true, "fire":true, "water":true},
-		// "level": 1,
-
 		std::vector<SpellID> ret;
 		for (const JsonNode & entry : value.Vector())
 		{

+ 2 - 1
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -24,6 +24,7 @@
 #include "../mapObjectConstructors/CRewardableConstructor.h"
 #include "../mapObjectConstructors/CommonConstructors.h"
 #include "../mapObjectConstructors/CBankInstanceConstructor.h"
+#include "../mapObjectConstructors/ShrineInstanceConstructor.h"
 #include "../mapObjects/CQuest.h"
 #include "../mapObjects/CGPandoraBox.h"
 #include "../mapObjects/ObjectTemplate.h"
@@ -44,6 +45,7 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER_CLASS("bank", CBankInstanceConstructor);
 	SET_HANDLER_CLASS("boat", BoatInstanceConstructor);
 	SET_HANDLER_CLASS("market", MarketInstanceConstructor);
+	SET_HANDLER_CLASS("shrine", ShrineInstanceConstructor);
 
 	SET_HANDLER_CLASS("static", CObstacleConstructor);
 	SET_HANDLER_CLASS("", CObstacleConstructor);
@@ -78,7 +80,6 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER("scholar", CGScholar);
 	SET_HANDLER("seerHut", CGSeerHut);
 	SET_HANDLER("shipyard", CGShipyard);
-	SET_HANDLER("shrine", CGShrine);
 	SET_HANDLER("sign", CGSignBottle);
 	SET_HANDLER("siren", CGSirens);
 	SET_HANDLER("monolith", CGMonolith);

+ 65 - 0
lib/mapObjectConstructors/ShrineInstanceConstructor.cpp

@@ -0,0 +1,65 @@
+/*
+* ShrineInstanceConstructor.cpp, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+#include "StdInc.h"
+#include "ShrineInstanceConstructor.h"
+
+#include "IObjectInfo.h"
+#include "../mapObjects/MiscObjects.h"
+#include "../JsonRandom.h"
+#include "../IGameCallback.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void ShrineInstanceConstructor::initTypeData(const JsonNode & config)
+{
+	parameters = config;
+}
+
+CGObjectInstance * ShrineInstanceConstructor::create(std::shared_ptr<const ObjectTemplate> tmpl) const
+{
+	CGShrine * shrine = new CGShrine;
+
+	preInitObject(shrine);
+
+	if(tmpl)
+		shrine->appearance = tmpl;
+
+	return shrine;
+}
+
+void ShrineInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const
+{
+	CGShrine * shrine = dynamic_cast<CGShrine*>(object);
+
+	if (!shrine)
+		throw std::runtime_error("Unexpected object instance in ShrineInstanceConstructor!");
+
+	auto visitTextParameter = parameters["visitText"];
+
+	if (visitTextParameter.isNumber())
+		shrine->visitText.addTxt(MetaString::ADVOB_TXT, static_cast<ui32>(visitTextParameter.Float()));
+	else
+		shrine->visitText << visitTextParameter.String();
+
+	if(shrine->spell == SpellID::NONE) // shrine has no predefined spell
+	{
+		std::vector<SpellID> possibilities;
+		shrine->cb->getAllowedSpells(possibilities);
+
+		shrine->spell =JsonRandom::loadSpell(parameters["spell"], rng, possibilities);
+	}
+}
+
+std::unique_ptr<IObjectInfo> ShrineInstanceConstructor::getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const
+{
+	return nullptr;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 34 - 0
lib/mapObjectConstructors/ShrineInstanceConstructor.h

@@ -0,0 +1,34 @@
+/*
+* ShrineInstanceConstructor.h, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+#pragma once
+
+#include "AObjectTypeHandler.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class ShrineInstanceConstructor final : public AObjectTypeHandler
+{
+	JsonNode parameters;
+
+protected:
+	void initTypeData(const JsonNode & config) override;
+	CGObjectInstance * create(std::shared_ptr<const ObjectTemplate> tmpl = nullptr) const override;
+	void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override;
+	std::unique_ptr<IObjectInfo> getObjectInfo(std::shared_ptr<const ObjectTemplate> tmpl) const override;
+
+public:
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & static_cast<AObjectTypeHandler&>(*this);
+		h & parameters;
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 6 - 18
lib/mapObjects/MiscObjects.cpp

@@ -26,6 +26,8 @@
 #include "../CPlayerState.h"
 #include "../GameSettings.h"
 #include "../serializer/JsonSerializeFormat.h"
+#include "../mapObjectConstructors/AObjectTypeHandler.h"
+#include "../mapObjectConstructors/CObjectClassesHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -1530,7 +1532,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
 	InfoWindow iw;
 	iw.type = EInfoWindowMode::AUTO;
 	iw.player = h->getOwner();
-	iw.text.addTxt(MetaString::ADVOB_TXT,127 + ID - 88);
+	iw.text = visitText;
 	iw.text.addTxt(MetaString::SPELL_NAME,spell);
 	iw.text << ".";
 
@@ -1542,7 +1544,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
 	{
 		iw.text.addTxt(MetaString::ADVOB_TXT,174);
 	}
-	else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT  && h->maxSpellLevel() < 3) //it's third level spell and hero doesn't have wisdom
+	else if(spell.toSpell()->getLevel() > h->maxSpellLevel()) //it's third level spell and hero doesn't have wisdom
 	{
 		iw.text.addTxt(MetaString::ADVOB_TXT,130);
 	}
@@ -1560,20 +1562,7 @@ void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
 
 void CGShrine::initObj(CRandomGenerator & rand)
 {
-	if(spell == SpellID::NONE) //spell not set
-	{
-		int level = ID-87;
-		std::vector<SpellID> possibilities;
-		cb->getAllowedSpells (possibilities, level);
-
-		if(possibilities.empty())
-		{
-			logGlobal->error("Error: cannot init shrine, no allowed spells!");
-			return;
-		}
-
-		spell = *RandomGeneratorUtil::nextItem(possibilities, rand);
-	}
+	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
 }
 
 std::string CGShrine::getHoverText(PlayerColor player) const
@@ -1693,8 +1682,7 @@ void CGScholar::initObj(CRandomGenerator & rand)
 			break;
 		case SPELL:
 			std::vector<SpellID> possibilities;
-			for (int i = 1; i < 6; ++i)
-				cb->getAllowedSpells (possibilities, i);
+			cb->getAllowedSpells (possibilities);
 			bonusID = *RandomGeneratorUtil::nextItem(possibilities, rand);
 			break;
 		}

+ 3 - 0
lib/mapObjects/MiscObjects.h

@@ -246,7 +246,9 @@ protected:
 class DLL_LINKAGE CGShrine : public CTeamVisited
 {
 public:
+	MetaString visitText;
 	SpellID spell; //id of spell or NONE if random
+
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void initObj(CRandomGenerator & rand) override;
 	std::string getHoverText(PlayerColor player) const override;
@@ -256,6 +258,7 @@ public:
 	{
 		h & static_cast<CTeamVisited&>(*this);;
 		h & spell;
+		h & visitText;
 	}
 protected:
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;

+ 2 - 4
lib/rewardable/Info.cpp

@@ -74,8 +74,7 @@ Rewardable::LimitersList Rewardable::Info::configureSublimiters(Rewardable::Conf
 void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRandomGenerator & rng, Rewardable::Limiter & limiter, const JsonNode & source) const
 {
 	std::vector<SpellID> spells;
-	for (size_t i=0; i<6; i++)
-		IObjectInterface::cb->getAllowedSpells(spells, static_cast<ui16>(i));
+	IObjectInterface::cb->getAllowedSpells(spells);
 
 
 	limiter.dayOfWeek = JsonRandom::loadValue(source["dayOfWeek"], rng);
@@ -124,8 +123,7 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, CRand
 	reward.secondary = JsonRandom::loadSecondary(source["secondary"], rng);
 
 	std::vector<SpellID> spells;
-	for (size_t i=0; i<6; i++)
-		IObjectInterface::cb->getAllowedSpells(spells, static_cast<ui16>(i));
+	IObjectInterface::cb->getAllowedSpells(spells);
 
 	reward.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng);
 	reward.spells = JsonRandom::loadSpells(source["spells"], rng, spells);

+ 5 - 0
lib/spells/CSpellHandler.cpp

@@ -127,6 +127,11 @@ int64_t CSpell::calculateDamage(const spells::Caster * caster) const
 	return caster->getSpellBonus(this, rawDamage, nullptr);
 }
 
+bool CSpell::hasSchool(ESpellSchool which) const
+{
+	return school.count(which) && school.at(which);
+}
+
 bool CSpell::canBeCast(const CBattleInfoCallback * cb, spells::Mode mode, const spells::Caster * caster) const
 {
 	//if caller do not interested in description just discard it and do not pollute even debug log

+ 2 - 0
lib/spells/CSpellHandler.h

@@ -209,6 +209,8 @@ public:
 
 	int64_t calculateDamage(const spells::Caster * caster) const override;
 
+	bool hasSchool(ESpellSchool school) const override;
+
 	/**
 	 * Calls cb for each school this spell belongs to
 	 *