From fc190b14bbc55b31b87676ef9ffc567ad4301cfe Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 6 Jun 2023 18:34:04 +0300 Subject: [PATCH] Spell shrines can now be configured in json --- cmake_modules/VCMI_lib.cmake | 2 + config/objects/generic.json | 12 ++++ include/vcmi/spells/Spell.h | 2 + lib/IGameCallback.cpp | 15 +++-- lib/IGameCallback.h | 2 +- lib/JsonRandom.cpp | 31 ++++++--- .../CObjectClassesHandler.cpp | 3 +- .../ShrineInstanceConstructor.cpp | 65 +++++++++++++++++++ .../ShrineInstanceConstructor.h | 34 ++++++++++ lib/mapObjects/MiscObjects.cpp | 24 ++----- lib/mapObjects/MiscObjects.h | 3 + lib/rewardable/Info.cpp | 6 +- lib/spells/CSpellHandler.cpp | 5 ++ lib/spells/CSpellHandler.h | 2 + 14 files changed, 168 insertions(+), 38 deletions(-) create mode 100644 lib/mapObjectConstructors/ShrineInstanceConstructor.cpp create mode 100644 lib/mapObjectConstructors/ShrineInstanceConstructor.h diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 888e01b17..20db1745d 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/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 diff --git a/config/objects/generic.json b/config/objects/generic.json index 04c781969..c01ac5dc7 100644 --- a/config/objects/generic.json +++ b/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 } } } diff --git a/include/vcmi/spells/Spell.h b/include/vcmi/spells/Spell.h index 98547a387..ba16ad105 100644 --- a/include/vcmi/spells/Spell.h +++ b/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; diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index c05494e4f..2b642314b 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -170,16 +170,19 @@ void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector out.push_back(VLC->arth->objects[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR)]); } -void CPrivilegedInfoCallback::getAllowedSpells(std::vector & out, ui16 level) +void CPrivilegedInfoCallback::getAllowedSpells(std::vector & out, std::optional 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()); } } diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index d6c428188..e349b6daa 100644 --- a/lib/IGameCallback.h +++ b/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 & out, CRandomGenerator & rand) const; - void getAllowedSpells(std::vector &out, ui16 level); + void getAllowedSpells(std::vector &out, std::optional level = std::nullopt); template void saveCommonState(Saver &out) const; //stores GS and VLC diff --git a/lib/JsonRandom.cpp b/lib/JsonRandom.cpp index de1433f67..3be169f88 100644 --- a/lib/JsonRandom.cpp +++ b/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 loadSpells(const JsonNode & value, CRandomGenerator & rng, const std::vector & 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 ret; for (const JsonNode & entry : value.Vector()) { diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 57bd7392f..3a92ffc79 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/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); diff --git a/lib/mapObjectConstructors/ShrineInstanceConstructor.cpp b/lib/mapObjectConstructors/ShrineInstanceConstructor.cpp new file mode 100644 index 000000000..1e8c8b72d --- /dev/null +++ b/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 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(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(visitTextParameter.Float())); + else + shrine->visitText << visitTextParameter.String(); + + if(shrine->spell == SpellID::NONE) // shrine has no predefined spell + { + std::vector possibilities; + shrine->cb->getAllowedSpells(possibilities); + + shrine->spell =JsonRandom::loadSpell(parameters["spell"], rng, possibilities); + } +} + +std::unique_ptr ShrineInstanceConstructor::getObjectInfo(std::shared_ptr tmpl) const +{ + return nullptr; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/ShrineInstanceConstructor.h b/lib/mapObjectConstructors/ShrineInstanceConstructor.h new file mode 100644 index 000000000..1350f92f4 --- /dev/null +++ b/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 tmpl = nullptr) const override; + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override; + std::unique_ptr getObjectInfo(std::shared_ptr tmpl) const override; + +public: + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & parameters; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 673a9a39c..5fb0bd92d 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/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 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 possibilities; - for (int i = 1; i < 6; ++i) - cb->getAllowedSpells (possibilities, i); + cb->getAllowedSpells (possibilities); bonusID = *RandomGeneratorUtil::nextItem(possibilities, rand); break; } diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 2a50ca0df..d921a4ac1 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/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(*this);; h & spell; + h & visitText; } protected: void serializeJsonOptions(JsonSerializeFormat & handler) override; diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index 68fced7d7..2d5292718 100644 --- a/lib/rewardable/Info.cpp +++ b/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 spells; - for (size_t i=0; i<6; i++) - IObjectInterface::cb->getAllowedSpells(spells, static_cast(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 spells; - for (size_t i=0; i<6; i++) - IObjectInterface::cb->getAllowedSpells(spells, static_cast(i)); + IObjectInterface::cb->getAllowedSpells(spells); reward.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng); reward.spells = JsonRandom::loadSpells(source["spells"], rng, spells); diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 75da3ad99..aee7303b4 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/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 diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 90966df1f..884017a60 100644 --- a/lib/spells/CSpellHandler.h +++ b/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 *