فهرست منبع

Somewhat configurable spell schools

Ivan Savenko 4 ماه پیش
والد
کامیت
4e47894e7a

+ 18 - 17
client/windows/CSpellWindow.cpp

@@ -33,7 +33,6 @@
 #include "../widgets/VideoWidget.h"
 #include "../adventureMap/AdventureMapInterface.h"
 
-
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/GameConstants.h"
 #include "../../lib/GameLibrary.h"
@@ -42,11 +41,21 @@
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/spells/ISpellMechanics.h"
 #include "../../lib/spells/Problem.h"
+#include "../../lib/spells/SpellSchoolHandler.h"
 #include "../../lib/texts/CGeneralTextHandler.h"
 #include "../../lib/texts/TextOperations.h"
-
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
+// Ordering of spell school tabs in SpelTab.def
+static const std::array schoolTabOrder =
+{
+	SpellSchool::AIR,
+	SpellSchool::FIRE,
+	SpellSchool::WATER,
+	SpellSchool::EARTH,
+	SpellSchool::ANY
+};
+
 CSpellWindow::InteractiveArea::InteractiveArea(const Rect & myRect, std::function<void()> funcL, int helpTextId, CSpellWindow * _owner)
 {
 	addUsedEvents(LCLICK | SHOW_POPUP | HOVER);
@@ -85,12 +94,11 @@ public:
 		if(A->getLevel() > B->getLevel())
 			return false;
 
-
-		for(auto schoolId = 0; schoolId < GameConstants::DEFAULT_SCHOOLS; schoolId++)
+		for (const auto schoolId : LIBRARY->spellSchoolHandler->getAllObjects())
 		{
-			if(A->school.at(SpellSchool(schoolId)) && !B->school.at(SpellSchool(schoolId)))
+			if(A->schools.count(schoolId) && !B->schools.count(schoolId))
 				return true;
-			if(!A->school.at(SpellSchool(schoolId)) && B->school.at(SpellSchool(schoolId)))
+			if(!A->schools.count(schoolId) && B->schools.count(schoolId))
 				return false;
 		}
 
@@ -423,7 +431,7 @@ void CSpellWindow::computeSpellsPerArea()
 	for(const CSpell * spell : mySpells)
 	{
 		if(spell->isCombat() ^ !battleSpellsOnly
-			&& ((selectedTab == 4) || spell->school.at(SpellSchool(selectedTab)))
+		   && ((selectedTab == 4) || spell->schools.count(schoolTabOrder.at(selectedTab)))
 			)
 		{
 			spellsCurSite.push_back(spell);
@@ -726,7 +734,7 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell)
 	mySpell = spell;
 	if(mySpell)
 	{
-		SpellSchool whichSchool; //0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic,
+		SpellSchool whichSchool;
 		schoolLevel = owner->myHero->getSpellSchoolLevel(mySpell, &whichSchool);
 		auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero);
 
@@ -736,21 +744,14 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell)
 		{
 			OBJECT_CONSTRUCTION;
 
-			static const std::array schoolBorders = {
-				AnimationPath::builtin("SplevA.def"),
-				AnimationPath::builtin("SplevF.def"),
-				AnimationPath::builtin("SplevW.def"),
-				AnimationPath::builtin("SplevE.def")
-			};
-
 			schoolBorder.reset();
 			if (owner->selectedTab >= 4)
 			{
 				if (whichSchool.hasValue())
-					schoolBorder = std::make_shared<CAnimImage>(schoolBorders.at(whichSchool.getNum()), schoolLevel);
+					schoolBorder = std::make_shared<CAnimImage>(LIBRARY->spellSchoolHandler->getById(whichSchool)->getSpellBordersPath(), schoolLevel);
 			}
 			else
-				schoolBorder = std::make_shared<CAnimImage>(schoolBorders.at(owner->selectedTab), schoolLevel);
+				schoolBorder = std::make_shared<CAnimImage>(LIBRARY->spellSchoolHandler->getById(schoolTabOrder.at(owner->selectedTab))->getSpellBordersPath(), schoolLevel);
 		}
 
 		ColorRGBA firstLineColor, secondLineColor;

+ 4 - 0
config/gameConfig.json

@@ -99,6 +99,10 @@
 		"config/spells/vcmiAbility.json",
 		"config/spells/moats.json"
 	],
+	"spellSchools" :
+	[
+		"config/spellSchools.json"
+	],
 	"skills" :
 	[
 		"config/skills.json"

+ 19 - 0
config/schemas/spellSchool.json

@@ -0,0 +1,19 @@
+{
+	"type" : "object",
+	"$schema" : "http://json-schema.org/draft-04/schema",
+	"title" : "VCMI spell school format",
+	"description" : "Format used to define new spell schools in VCMI",
+	"required" : [ "schoolBorders" ],
+	"additionalProperties" : false,
+	"properties" : {
+		"index" : {
+			"type" : "number",
+			"description" : "numeric id of h3 spell school, prohibited for new schools"
+		},
+		"schoolBorders" : {
+			"type" : "string",
+			"description" : "File with frame borders of mastery levels for spells of this spell school in spellbook",
+			"format" : "animationFile"
+		}
+	}
+}

+ 21 - 0
config/spellSchools.json

@@ -0,0 +1,21 @@
+{
+	"air" : {
+		"index" : 0,
+		"schoolBorders" : "SplevA"
+	},
+	
+	"fire" : {
+		"index" : 1,
+		"schoolBorders" : "SplevF"
+	},
+	
+	"earth" : {
+		"index" : 2,
+		"schoolBorders" : "SplevE"
+	},
+	
+	"water" : {
+		"index" : 3,
+		"schoolBorders" : "SplevW"
+	}
+}

+ 2 - 0
lib/CMakeLists.txt

@@ -255,6 +255,7 @@ set(lib_MAIN_SRCS
 	spells/ObstacleCasterProxy.cpp
 	spells/Problem.cpp
 	spells/ProxyCaster.cpp
+	spells/SpellSchoolHandler.cpp
 	spells/TargetCondition.cpp
 	spells/ViewSpellInt.cpp
 
@@ -699,6 +700,7 @@ set(lib_MAIN_HEADERS
 	spells/ObstacleCasterProxy.h
 	spells/Problem.h
 	spells/ProxyCaster.h
+	spells/SpellSchoolHandler.h
 	spells/TargetCondition.h
 	spells/ViewSpellInt.h
 

+ 2 - 0
lib/GameLibrary.cpp

@@ -18,6 +18,7 @@
 #include "RiverHandler.h"
 #include "TerrainHandler.h"
 #include "spells/CSpellHandler.h"
+#include "spells/SpellSchoolHandler.h"
 #include "spells/effects/Registry.h"
 #include "CSkillHandler.h"
 #include "entities/artifact/CArtHandler.h"
@@ -178,6 +179,7 @@ void GameLibrary::initializeLibrary()
 	createHandler(biomeHandler);
 	createHandler(objh);
 	createHandler(objtypeh);
+	createHandler(spellSchoolHandler);
 	createHandler(spellh);
 	createHandler(skillh);
 	createHandler(terviewh);

+ 2 - 0
lib/GameLibrary.h

@@ -40,6 +40,7 @@ class IHandlerBase;
 class IGameSettings;
 class GameSettings;
 class CIdentifierStorage;
+class SpellSchoolHandler;
 
 #if SCRIPTING_ENABLED
 namespace scripting
@@ -78,6 +79,7 @@ public:
 	std::unique_ptr<CHeroClassHandler> heroclassesh;
 	std::unique_ptr<CCreatureHandler> creh;
 	std::unique_ptr<CSpellHandler> spellh;
+	std::unique_ptr<SpellSchoolHandler> spellSchoolHandler;
 	std::unique_ptr<CSkillHandler> skillh;
 	// TODO: Remove ObjectHandler altogether?
 	std::unique_ptr<CObjectHandler> objh;

+ 6 - 9
lib/bonuses/BonusCache.cpp

@@ -17,6 +17,7 @@
 
 #include "../GameLibrary.h"
 #include "../IGameSettings.h"
+#include "spells/SpellSchoolHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -59,22 +60,18 @@ bool BonusValueCache::hasBonus() const
 
 MagicSchoolMasteryCache::MagicSchoolMasteryCache(const IBonusBearer * target)
 	:target(target)
+	,schools(LIBRARY->spellSchoolHandler->getAllObjects().size() + 1)
 {}
 
 void MagicSchoolMasteryCache::update() const
 {
 	static const CSelector allBonusesSelector = Selector::type()(BonusType::MAGIC_SCHOOL_SKILL);
-	static const std::array schoolsSelector = {
-		Selector::subtype()(SpellSchool::ANY),
-		Selector::subtype()(SpellSchool::AIR),
-		Selector::subtype()(SpellSchool::FIRE),
-		Selector::subtype()(SpellSchool::WATER),
-		Selector::subtype()(SpellSchool::EARTH),
-	};
 
 	auto list = target->getBonuses(allBonusesSelector);
-	for (int i = 0; i < schoolsSelector.size(); ++i)
-		schools[i] = list->valOfBonuses(schoolsSelector[i]);
+	schools[0] = list->valOfBonuses(Selector::subtype()(SpellSchool::ANY));
+
+	for (int i = 1; i < schools.size(); ++i)
+		schools[i] = list->valOfBonuses(Selector::subtype()(SpellSchool(i-1)));
 
 	version = target->getTreeVersion();
 }

+ 1 - 1
lib/bonuses/BonusCache.h

@@ -171,7 +171,7 @@ class MagicSchoolMasteryCache
 {
 	const IBonusBearer * target;
 	mutable std::atomic<int32_t> version = 0;
-	mutable std::array<std::atomic<int32_t>, 4+1> schools;
+	mutable std::vector<std::atomic<int32_t>> schools;
 
 	void update() const;
 public:

+ 8 - 7
lib/constants/EntityIdentifiers.cpp

@@ -29,9 +29,10 @@
 #include "modding/IdentifierStorage.h"
 #include "modding/ModScope.h"
 #include "GameLibrary.h"
-#include "CCreatureHandler.h"//todo: remove
-#include "spells/CSpellHandler.h" //todo: remove
-#include "CSkillHandler.h"//todo: remove
+#include "CCreatureHandler.h"
+#include "spells/CSpellHandler.h"
+#include "spells/SpellSchoolHandler.h"
+#include "CSkillHandler.h"
 #include "entities/artifact/CArtifact.h"
 #include "entities/faction/CFaction.h"
 #include "entities/hero/CHero.h"
@@ -39,7 +40,7 @@
 #include "mapObjectConstructors/AObjectTypeHandler.h"
 #include "constants/StringConstants.h"
 #include "texts/CGeneralTextHandler.h"
-#include "TerrainHandler.h" //TODO: remove
+#include "TerrainHandler.h"
 #include "RiverHandler.h"
 #include "RoadHandler.h"
 #include "BattleFieldHandler.h"
@@ -77,8 +78,8 @@ const TeamID TeamID::NO_TEAM(-1);
 const SpellSchool SpellSchool::ANY(-1);
 const SpellSchool SpellSchool::AIR(0);
 const SpellSchool SpellSchool::FIRE(1);
-const SpellSchool SpellSchool::WATER(2);
-const SpellSchool SpellSchool::EARTH(3);
+const SpellSchool SpellSchool::EARTH(2);
+const SpellSchool SpellSchool::WATER(3);
 
 const FactionID FactionID::NONE(-2);
 const FactionID FactionID::DEFAULT(-1);
@@ -595,7 +596,7 @@ std::string SpellSchool::encode(const si32 index)
 	if (index == ANY.getNum())
 		return "any";
 
-	return SpellConfig::SCHOOL[index].jsonName;
+	return LIBRARY->spellSchoolHandler->getById(SpellSchool(index))->getJsonKey();
 }
 
 std::string SpellSchool::entityType()

+ 0 - 1
lib/constants/NumericConstants.h

@@ -25,7 +25,6 @@ namespace GameConstants
 	constexpr int CREATURES_PER_TOWN = 8; //without upgrades
 	constexpr int SPELL_LEVELS = 5;
 	constexpr int SPELL_SCHOOL_LEVELS = 4;
-	constexpr int DEFAULT_SCHOOLS = 4;
 	constexpr int CRE_LEVELS = 10; // number of creature experience levels
 
 	constexpr int HERO_GOLD_COST = 2500;

+ 2 - 0
lib/modding/ContentTypeHandler.cpp

@@ -39,6 +39,7 @@
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../rmg/CRmgTemplateStorage.h"
 #include "../spells/CSpellHandler.h"
+#include "../spells/SpellSchoolHandler.h"
 #include "../GameLibrary.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -248,6 +249,7 @@ void CContentHandler::init()
 	handlers.insert(std::make_pair("objects", ContentTypeHandler(LIBRARY->objtypeh.get(), "object")));
 	handlers.insert(std::make_pair("heroes", ContentTypeHandler(LIBRARY->heroh.get(), "hero")));
 	handlers.insert(std::make_pair("spells", ContentTypeHandler(LIBRARY->spellh.get(), "spell")));
+	handlers.insert(std::make_pair("spellSchools", ContentTypeHandler(LIBRARY->spellSchoolHandler.get(), "spellSchool")));
 	handlers.insert(std::make_pair("skills", ContentTypeHandler(LIBRARY->skillh.get(), "skill")));
 	handlers.insert(std::make_pair("templates", ContentTypeHandler(LIBRARY->tplh.get(), "template")));
 #if SCRIPTING_ENABLED

+ 0 - 4
lib/modding/IdentifierStorage.cpp

@@ -23,10 +23,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 CIdentifierStorage::CIdentifierStorage()
 {
-	//TODO: moddable spell schools
-	for (auto i = 0; i < GameConstants::DEFAULT_SCHOOLS; ++i)
-		registerObject(ModScope::scopeBuiltin(), "spellSchool", SpellConfig::SCHOOL[i].jsonName, SpellConfig::SCHOOL[i].id.getNum());
-
 	registerObject(ModScope::scopeBuiltin(), "spellSchool", "any", SpellSchool::ANY.getNum());
 
 	for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i)

+ 17 - 52
lib/spells/CSpellHandler.cpp

@@ -12,7 +12,6 @@
 
 #include <cctype>
 
-#include "CBonusTypeHandler.h"
 #include "CSpellHandler.h"
 #include "Problem.h"
 
@@ -22,56 +21,23 @@
 
 #include "../constants/StringConstants.h"
 
-#include "../battle/BattleInfo.h"
+#include "../CBonusTypeHandler.h"
 #include "../battle/CBattleInfoCallback.h"
 #include "../battle/Unit.h"
 #include "../json/JsonBonus.h"
 #include "../json/JsonUtils.h"
 #include "../GameLibrary.h"
-#include "../mapObjects/CGHeroInstance.h" //todo: remove
 #include "../modding/IdentifierStorage.h"
-#include "../modding/ModUtility.h"
-#include "../serializer/CSerializer.h"
 #include "../texts/CLegacyConfigParser.h"
 #include "../texts/CGeneralTextHandler.h"
 
 #include "ISpellMechanics.h"
+#include "bonuses/BonusSelector.h"
+#include "spells/SpellSchoolHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-namespace SpellConfig
-{
-static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};
-
-const spells::SchoolInfo SCHOOL[4] =
-{
-	{
-		SpellSchool::AIR,
-		"air"
-	},
-	{
-		SpellSchool::FIRE,
-		"fire"
-	},
-	{
-		SpellSchool::WATER,
-		"water"
-	},
-	{
-		SpellSchool::EARTH,
-		"earth"
-	}
-};
-
-//order as described in http://bugs.vcmi.eu/view.php?id=91
-static const SpellSchool SCHOOL_ORDER[4] =
-{
-	SpellSchool::AIR,  //=0
-	SpellSchool::FIRE, //=1
-	SpellSchool::EARTH,//=3(!)
-	SpellSchool::WATER //=2(!)
-};
-} //namespace SpellConfig
+static constexpr std::array LEVEL_NAMES = {"none", "basic", "advanced", "expert"};
 
 ///CSpell
 CSpell::CSpell():
@@ -133,7 +99,7 @@ int64_t CSpell::calculateDamage(const spells::Caster * caster) const
 
 bool CSpell::hasSchool(SpellSchool which) const
 {
-	return school.count(which) && school.at(which);
+	return schools.count(which);
 }
 
 bool CSpell::canBeCast(const CBattleInfoCallback * cb, spells::Mode mode, const spells::Caster * caster) const
@@ -159,13 +125,11 @@ spells::AimType CSpell::getTargetType() const
 void CSpell::forEachSchool(const std::function<void(const SpellSchool &, bool &)>& cb) const
 {
 	bool stop = false;
-	for(auto iter : SpellConfig::SCHOOL_ORDER)
+	for(auto schoolID : LIBRARY->spellSchoolHandler->getAllObjects())
 	{
-		const spells::SchoolInfo & cnf = SpellConfig::SCHOOL[iter.getNum()];
-		if(school.at(cnf.id))
+		if(schools.count(schoolID))
 		{
-			cb(cnf.id, stop);
-
+			cb(schoolID, stop);
 			if(stop)
 				break;
 		}
@@ -190,7 +154,7 @@ std::string CSpell::getNameTranslated() const
 
 std::string CSpell::getDescriptionTextID(int32_t level) const
 {
-	TextIdentifier id("spell", modScope, identifier, "description", SpellConfig::LEVEL_NAMES[level]);
+	TextIdentifier id("spell", modScope, identifier, "description", LEVEL_NAMES[level]);
 	return id.get();
 }
 
@@ -581,7 +545,6 @@ bool DLL_LINKAGE isInScreenRange(const int3 & center, const int3 & pos)
 ///CSpellHandler
 std::vector<JsonNode> CSpellHandler::loadLegacyData()
 {
-	using namespace SpellConfig;
 	std::vector<JsonNode> legacyData;
 
 	CLegacyConfigParser parser(TextPath::builtin("DATA/SPTRAITS.TXT"));
@@ -757,8 +720,6 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 	assert(identifier.find(':') == std::string::npos);
 	assert(!scope.empty());
 
-	using namespace SpellConfig;
-
 	SpellID id(static_cast<si32>(index));
 
 	auto spell = std::make_shared<CSpell>();
@@ -783,11 +744,15 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 
 	logMod->trace("%s: loading spell %s", __FUNCTION__, spell->getNameTranslated());
 
-	const auto schoolNames = json["school"];
-
-	for(const spells::SchoolInfo & info : SpellConfig::SCHOOL)
+	for(const auto & schoolJson : json["school"].Struct())
 	{
-		spell->school[info.id] = schoolNames[info.jsonName].Bool();
+		if (schoolJson.second.Bool())
+		{
+			LIBRARY->identifiers()->requestIdentifier(schoolJson.second.getModScope(), "spellSchool", schoolJson.first, [=](si32 schoolID)
+			{
+				spell->schools.emplace(schoolID);
+			});
+		}
 	}
 
 	spell->castOnSelf = json["canCastOnSelf"].Bool();

+ 3 - 16
lib/spells/CSpellHandler.h

@@ -35,21 +35,8 @@ namespace test
 
 namespace spells
 {
-
-class ISpellMechanicsFactory;
-class IBattleCast;
-
-struct SchoolInfo
-{
-	SpellSchool id; //backlink
-	std::string jsonName;
-};
-
-}
-
-namespace SpellConfig
-{
-	extern const spells::SchoolInfo SCHOOL[4];
+	class ISpellMechanicsFactory;
+	class IBattleCast;
 }
 
 enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
@@ -148,7 +135,7 @@ public:
 	using BTVector = std::vector<BonusType>;
 
 
-	std::map<SpellSchool, bool> school;
+	std::set<SpellSchool> schools;
 	std::map<FactionID, si32> probabilities; //% chance to gain for castles
 
 	bool onlyOnWaterMap; //Spell will be banned on maps without water

+ 56 - 0
lib/spells/SpellSchoolHandler.cpp

@@ -0,0 +1,56 @@
+/*
+ * SpellSchoolHandler.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 "SpellSchoolHandler.h"
+
+#include "../json/JsonNode.h"
+
+std::vector<JsonNode> SpellSchoolHandler::loadLegacyData()
+{
+	objects.resize(4);
+
+	return std::vector<JsonNode>(4, JsonNode(JsonMap()));
+}
+
+std::shared_ptr<spells::SpellSchoolType> SpellSchoolHandler::loadObjectImpl(std::string scope, std::string name, const JsonNode & data, size_t index)
+{
+	auto ret = std::make_shared<spells::SpellSchoolType>();
+
+	ret->id = SpellSchool(index);
+	ret->jsonName = name;
+	ret->spellBordersPath = AnimationPath::fromJson(data["schoolBorders"]);
+
+	return ret;
+}
+
+/// loads single object into game. Scope is namespace of this object, same as name of source mod
+void SpellSchoolHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
+{
+	objects.push_back(loadObjectImpl(scope, name, data, objects.size()));
+	registerObject(scope, "spellSchool", name, objects.back()->getIndex());
+}
+
+void SpellSchoolHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
+{
+	assert(objects[index] == nullptr); // ensure that this id was not loaded before
+	objects[index] = loadObjectImpl(scope, name, data, index);
+	registerObject(scope, "spellSchool", name, objects[index]->getIndex());
+}
+
+std::vector<SpellSchool> SpellSchoolHandler::getAllObjects() const
+{
+	std::vector<SpellSchool> result;
+
+	for (const auto & school : objects)
+		result.push_back(school->id);
+
+	return result;
+}

+ 72 - 0
lib/spells/SpellSchoolHandler.h

@@ -0,0 +1,72 @@
+/*
+ * SpellSchoolHandler.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 "../constants/EntityIdentifiers.h"
+#include "../IHandlerBase.h"
+#include "../filesystem/ResourcePath.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class SpellSchoolHandler;
+
+namespace spells
+{
+
+class DLL_LINKAGE SpellSchoolType
+{
+	friend class VCMI_LIB_WRAP_NAMESPACE(SpellSchoolHandler);
+
+	SpellSchool id; //backlink
+	std::string jsonName;
+	AnimationPath spellBordersPath;
+
+public:
+	std::string getJsonKey() const
+	{
+		return jsonName;
+	}
+
+	AnimationPath getSpellBordersPath() const
+	{
+		return spellBordersPath;
+	}
+
+	int getIndex()
+	{
+		return id.getNum();
+	}
+};
+
+}
+
+class DLL_LINKAGE SpellSchoolHandler : public IHandlerBase
+{
+	std::shared_ptr<spells::SpellSchoolType> loadObjectImpl(std::string scope, std::string name, const JsonNode & data, size_t index);
+public:
+	std::vector<JsonNode> loadLegacyData() override;
+
+	/// loads single object into game. Scope is namespace of this object, same as name of source mod
+	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
+	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
+
+	std::vector<SpellSchool> getAllObjects() const;
+
+	const spells::SpellSchoolType * getById(SpellSchool index) const
+	{
+		return objects.at(index).get();
+	}
+
+private:
+	std::vector<std::shared_ptr<spells::SpellSchoolType>> objects;
+};
+
+VCMI_LIB_NAMESPACE_END