1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-15 20:03:15 +02:00

Implemented configurable shrine

This commit is contained in:
Ivan Savenko
2023-10-04 16:49:17 +03:00
parent dd841bdaa7
commit bb05c2dea5
17 changed files with 36 additions and 531 deletions

View File

@@ -81,7 +81,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/mapObjectConstructors/DwellingInstanceConstructor.cpp
${MAIN_LIB_DIR}/mapObjectConstructors/HillFortInstanceConstructor.cpp
${MAIN_LIB_DIR}/mapObjectConstructors/ShipyardInstanceConstructor.cpp
${MAIN_LIB_DIR}/mapObjectConstructors/ShrineInstanceConstructor.cpp
${MAIN_LIB_DIR}/mapObjects/CArmedInstance.cpp
${MAIN_LIB_DIR}/mapObjects/CBank.cpp
@@ -425,7 +424,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/mapObjectConstructors/IObjectInfo.h
${MAIN_LIB_DIR}/mapObjectConstructors/RandomMapInfo.h
${MAIN_LIB_DIR}/mapObjectConstructors/ShipyardInstanceConstructor.h
${MAIN_LIB_DIR}/mapObjectConstructors/ShrineInstanceConstructor.h
${MAIN_LIB_DIR}/mapObjectConstructors/SObjectSounds.h
${MAIN_LIB_DIR}/mapObjects/CArmedInstance.h

View File

@@ -52,7 +52,9 @@
"config/objects/moddables.json",
"config/objects/creatureBanks.json",
"config/objects/dwellings.json",
"config/objects/rewardableGeneric.json",
"config/objects/rewardableObservatories.json",
"config/objects/rewardableWitchHut.json",
"config/objects/rewardableShrine.json",
"config/objects/rewardableOncePerWeek.json",
"config/objects/rewardablePickable.json",
"config/objects/rewardableOnceVisitable.json",

View File

@@ -230,78 +230,6 @@
}
}
},
"shrineOfMagicLevel1" : {//incantation
"index" :88,
"handler" : "shrine",
"base" : {
"sounds" : {
"ambient" : ["LOOPSHRIN"],
"visit" : ["TEMPLE"]
}
},
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
"value" : 500,
"rarity" : 100
},
"visitText" : 127,
"spell" : {
"level" : 1
}
}
}
},
"shrineOfMagicLevel2" : {//gesture
"index" :89,
"handler" : "shrine",
"base" : {
"sounds" : {
"ambient" : ["LOOPSHRIN"],
"visit" : ["TEMPLE"]
}
},
"types" : {
"object" : {
"index" : 0,
"aiValue" : 2000,
"rmg" : {
"value" : 2000,
"rarity" : 100
},
"visitText" : 128,
"spell" : {
"level" : 2
}
}
}
},
"shrineOfMagicLevel3" : {//thinking
"index" :90,
"handler" : "shrine",
"base" : {
"sounds" : {
"ambient" : ["LOOPSHRIN"],
"visit" : ["TEMPLE"]
}
},
"types" : {
"object" : {
"index" : 0,
"aiValue" : 3000,
"rmg" : {
"value" : 3000,
"rarity" : 100
},
"visitText" : 129,
"spell" : {
"level" : 3
}
}
}
},
"eyeOfTheMagi" : {
"index" :27,
"handler" : "magi",

View File

@@ -1,277 +0,0 @@
{
"witchHut" : {
"index" :113,
"handler" : "configurable",
"base" : {
"sounds" : {
"visit" : ["GAZEBO"]
}
},
"types" : {
"witchHut" : {
"index" : 0,
"aiValue" : 1500,
"rmg" : {
"zoneLimit" : 3,
"value" : 1500,
"rarity" : 80
},
"compatibilityIdentifiers" : [ "object" ],
"visitMode" : "limiter",
"variables" : {
"secondarySkill" : {
"gainedSkill" : { // Note: this variable name is used by engine for H3M loading and by AI
"noneOf" : [
"leadership",
"necromancy"
]
}
}
},
"visitLimiter" : {
"secondary" : {
"@gainedSkill" : 1
}
},
"rewards" : [
{
"limiter" : {
"canLearnSkills" : true,
"noneOf" : [
{
"secondary" : {
"@gainedSkill" : 1
}
}
]
},
"secondary" : {
"@gainedSkill" : 1
},
"message" : 171 // Witch teaches you skill
}
],
"onVisited" : [
{
"message" : 172 // You already known this skill
}
],
"onEmpty" : [
{
"message" : 173 // You know too much (no free slots)
}
]
}
}
},
"redwoodObservatory" : {
"index" :58,
"handler" : "configurable",
"base" : {
"sounds" : {
"visit" : ["LIGHTHOUSE"]
}
},
"types" : {
"redwoodObservatory" : {
"index" : 0,
"aiValue" : 750,
"templates" :
{
"base" : { "animation" : "avxredw.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "VV", "VA"], "allowedTerrains":["grass", "swamp", "dirt", "sand", "lava", "rough"] },
"snow" : { "animation" : "avxreds0.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "VV", "VA"], "allowedTerrains":["snow"] }
},
"rmg" : {
"zoneLimit" : 1,
"value" : 750,
"rarity" : 100
},
"compatibilityIdentifiers" : [ "object" ],
"visitMode" : "unlimited",
"rewards" : [
{
"message" : 98,
"revealTiles" : {
"radius" : 20,
"surface" : 1,
"subterra" : 1,
"water" : 1,
"rock" : 1
}
}
]
}
}
},
"pillarOfFire" : {
"index" :60,
"handler" : "configurable",
"base" : {
"sounds" : {
"ambient" : ["LOOPFIRE"],
"visit" : ["LIGHTHOUSE"]
}
},
"types" : {
"pillarOfFire" : {
"index" : 0,
"aiValue" : 750,
"rmg" : {
"zoneLimit" : 1,
"value" : 750,
"rarity" : 100
},
"compatibilityIdentifiers" : [ "object" ],
"visitMode" : "unlimited",
"rewards" : [
{
"message" : 99,
"revealTiles" : {
"radius" : 20,
"surface" : 1,
"subterra" : 1,
"water" : 1,
"rock" : 1
}
}
]
}
}
},
"coverOfDarkness" : {
"index" :15,
"handler" : "configurable",
"base" : {
"sounds" : {
"visit" : ["LIGHTHOUSE"]
}
},
"types" : {
"coverOfDarkness" : {
"index" : 0,
"aiValue" : 100,
"rmg" : {
},
"compatibilityIdentifiers" : [ "object" ],
"visitMode" : "unlimited",
"rewards" : [
{
"message" : 31,
"revealTiles" : {
"radius" : 20,
"surface" : 1,
"subterra" : 1,
"water" : 1,
"rock" : 1,
"hide" : true
}
}
]
}
}
},
"cartographer" : {
"index" :13,
"handler": "configurable",
"lastReservedIndex" : 2,
"base" : {
"sounds" : {
"visit" : ["LIGHTHOUSE"]
}
},
"types" : {
"cartographerWater" : {
"index" : 0,
"aiValue" : 5000,
"rmg" : {
"zoneLimit" : 1,
"value" : 5000,
"rarity" : 20
},
"compatibilityIdentifiers" : [ "water" ],
"visitMode" : "unlimited",
"canRefuse" : true,
"rewards" : [
{
"limiter" : { "resources" : { "gold" : 1000 } },
"message" : 25,
"resources" : {
"gold" : -1000
},
"revealTiles" : {
"water" : 1
}
}
],
"onEmptyMessage" : 28,
"onVisitedMessage" : 24
},
"cartographerLand" : {
"index" : 1,
"aiValue": 10000,
"rmg" : {
"zoneLimit" : 1,
"value" : 10000,
"rarity" : 2
},
"compatibilityIdentifiers" : [ "land" ],
"visitMode" : "unlimited",
"canRefuse" : true,
"rewards" : [
{
"limiter" : { "resources" : { "gold" : 1000 } },
"message" : 26,
"resources" : {
"gold" : -1000
},
"revealTiles" : {
"surface" : 1,
"water" : -1,
"rock" : -1
}
}
],
"onEmptyMessage" : 28,
"onVisitedMessage" : 24
},
"cartographerSubterranean" : {
"index" : 2,
"aiValue" : 7500,
"rmg" : {
"zoneLimit" : 1,
"value" : 7500,
"rarity" : 20
},
"compatibilityIdentifiers" : [ "subterra" ],
"visitMode" : "unlimited",
"canRefuse" : true,
"rewards" : [
{
"limiter" : { "resources" : { "gold" : 1000 } },
"message" : 27,
"resources" : {
"gold" : -1000
},
"revealTiles" : {
"subterra" : 1,
"water" : -1,
"rock" : -1,
"surface" : -1
}
}
],
"onEmptyMessage" : 28,
"onVisitedMessage" : 24
}
}
}
}

View File

@@ -26,7 +26,6 @@
#include "../mapObjectConstructors/DwellingInstanceConstructor.h"
#include "../mapObjectConstructors/HillFortInstanceConstructor.h"
#include "../mapObjectConstructors/ShipyardInstanceConstructor.h"
#include "../mapObjectConstructors/ShrineInstanceConstructor.h"
#include "../mapObjects/CGCreature.h"
#include "../mapObjects/CGPandoraBox.h"
#include "../mapObjects/CQuest.h"
@@ -54,7 +53,6 @@ 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("hillFort", HillFortInstanceConstructor);
SET_HANDLER_CLASS("shipyard", ShipyardInstanceConstructor);
SET_HANDLER_CLASS("monster", CreatureInstanceConstructor);

View File

@@ -1,39 +0,0 @@
/*
* 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 "../mapObjects/MiscObjects.h"
#include "../JsonRandom.h"
#include "../IGameCallback.h"
VCMI_LIB_NAMESPACE_BEGIN
void ShrineInstanceConstructor::initTypeData(const JsonNode & config)
{
parameters = config;
}
void ShrineInstanceConstructor::randomizeObject(CGShrine * shrine, CRandomGenerator & rng) const
{
JsonRandom::Variables emptyVariables;
auto visitTextParameter = parameters["visitText"];
if (visitTextParameter.isNumber())
shrine->visitText.appendLocalString(EMetaText::ADVOB_TXT, static_cast<ui32>(visitTextParameter.Float()));
else
shrine->visitText.appendRawString(visitTextParameter.String());
if(shrine->spell == SpellID::NONE) // shrine has no predefined spell
shrine->spell =JsonRandom::loadSpell(parameters["spell"], rng, emptyVariables);
}
VCMI_LIB_NAMESPACE_END

View File

@@ -1,34 +0,0 @@
/*
* 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 "CDefaultObjectTypeHandler.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGShrine;
class ShrineInstanceConstructor final : public CDefaultObjectTypeHandler<CGShrine>
{
JsonNode parameters;
protected:
void initTypeData(const JsonNode & config) override;
void randomizeObject(CGShrine * object, CRandomGenerator & rng) const override;
public:
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<AObjectTypeHandler&>(*this);
h & parameters;
}
};
VCMI_LIB_NAMESPACE_END

View File

@@ -772,7 +772,7 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const
bool CGHeroInstance::canLearnSpell(const spells::Spell * spell) const
{
if(!hasSpellbook())
if(!hasSpellbook())
return false;
if(spell->getLevel() > maxSpellLevel()) //not enough wisdom

View File

@@ -842,77 +842,6 @@ void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler)
}
}
void CGShrine::onHeroVisit( const CGHeroInstance * h ) const
{
if(spell == SpellID::NONE)
{
logGlobal->error("Not initialized shrine visited!");
return;
}
if(!wasVisited(h->tempOwner))
cb->setObjProperty(id, CGShrine::OBJPROP_VISITED, h->tempOwner.getNum());
InfoWindow iw;
iw.type = EInfoWindowMode::AUTO;
iw.player = h->getOwner();
iw.text = visitText;
iw.text.appendLocalString(EMetaText::SPELL_NAME,spell);
iw.text.appendRawString(".");
if(!h->getArt(ArtifactPosition::SPELLBOOK))
{
iw.text.appendLocalString(EMetaText::ADVOB_TXT,131);
}
else if(h->spellbookContainsSpell(spell))//hero already knows the spell
{
iw.text.appendLocalString(EMetaText::ADVOB_TXT,174);
}
else if(spell.toSpell()->getLevel() > h->maxSpellLevel()) //it's third level spell and hero doesn't have wisdom
{
iw.text.appendLocalString(EMetaText::ADVOB_TXT,130);
}
else //give spell
{
std::set<SpellID> spells;
spells.insert(spell);
cb->changeSpells(h, true, spells);
iw.components.emplace_back(Component::EComponentType::SPELL, spell, 0, 0);
}
cb->showInfoDialog(&iw);
}
void CGShrine::initObj(CRandomGenerator & rand)
{
VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
}
std::string CGShrine::getHoverText(PlayerColor player) const
{
std::string hoverName = getObjectName();
if(wasVisited(player))
{
hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s)
boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->getNameTranslated());
}
return hoverName;
}
std::string CGShrine::getHoverText(const CGHeroInstance * hero) const
{
std::string hoverName = getHoverText(hero->tempOwner);
if(wasVisited(hero->tempOwner) && hero->spellbookContainsSpell(spell)) //know what spell there is and hero knows that spell
hoverName += "\n\n" + VLC->generaltexth->allTexts[354]; // (Already learned)
return hoverName;
}
void CGShrine::serializeJsonOptions(JsonSerializeFormat & handler)
{
handler.serializeId("spell", spell, SpellID::NONE);
}
void CGSignBottle::initObj(CRandomGenerator & rand)
{
//if no text is set than we pick random from the predefined ones

View File

@@ -149,27 +149,6 @@ protected:
void serializeJsonOptions(JsonSerializeFormat & handler) override;
};
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;
std::string getHoverText(const CGHeroInstance * hero) const override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CTeamVisited&>(*this);;
h & spell;
h & visitText;
}
protected:
void serializeJsonOptions(JsonSerializeFormat & handler) override;
};
class DLL_LINKAGE CGMine : public CArmedInstance
{
public:

View File

@@ -1325,10 +1325,22 @@ CGObjectInstance * CMapLoaderH3M::readDwellingRandom(const int3 & mapPosition, s
return object;
}
CGObjectInstance * CMapLoaderH3M::readShrine()
CGObjectInstance * CMapLoaderH3M::readShrine(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate)
{
auto * object = new CGShrine();
object->spell = reader->readSpell32();
auto * object = readGeneric(position, objectTemplate);
auto * rewardable = dynamic_cast<CRewardableObject*>(object);
assert(rewardable);
SpellID spell = reader->readSpell32();
if(spell != SpellID::NONE)
{
JsonNode variable;
variable.String() = VLC->spells()->getById(spell)->getJsonKey();
variable.setMeta(ModScope::scopeGame()); // list may include spells from all mods
rewardable->configuration.presetVariable("spell", "gainedSpell", variable);
}
return object;
}
@@ -1514,7 +1526,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr<const ObjectTemplat
case Obj::SHRINE_OF_MAGIC_INCANTATION:
case Obj::SHRINE_OF_MAGIC_GESTURE:
case Obj::SHRINE_OF_MAGIC_THOUGHT:
return readShrine();
return readShrine(mapPosition, objectTemplate);
case Obj::PANDORAS_BOX:
return readPandora(mapPosition, objectInstanceID);

View File

@@ -174,7 +174,7 @@ private:
CGObjectInstance * readPandora(const int3 & position, const ObjectInstanceID & idToBeGiven);
CGObjectInstance * readDwelling(const int3 & position);
CGObjectInstance * readDwellingRandom(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
CGObjectInstance * readShrine();
CGObjectInstance * readShrine(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
CGObjectInstance * readHeroPlaceholder(const int3 & position);
CGObjectInstance * readGrail(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
CGObjectInstance * readPyramid(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);

View File

@@ -23,7 +23,6 @@
#include "../mapObjectConstructors/DwellingInstanceConstructor.h"
#include "../mapObjectConstructors/HillFortInstanceConstructor.h"
#include "../mapObjectConstructors/ShipyardInstanceConstructor.h"
#include "../mapObjectConstructors/ShrineInstanceConstructor.h"
#include "../mapObjects/MapObjects.h"
#include "../mapObjects/CGCreature.h"
#include "../mapObjects/CGTownBuilding.h"
@@ -102,7 +101,6 @@ void registerTypesMapObjectTypes(Serializer &s)
s.template registerType<AObjectTypeHandler, BoatInstanceConstructor>();
s.template registerType<AObjectTypeHandler, MarketInstanceConstructor>();
s.template registerType<AObjectTypeHandler, CObstacleConstructor>();
s.template registerType<AObjectTypeHandler, ShrineInstanceConstructor>();
s.template registerType<AObjectTypeHandler, ShipyardInstanceConstructor>();
s.template registerType<AObjectTypeHandler, HillFortInstanceConstructor>();
s.template registerType<AObjectTypeHandler, CreatureInstanceConstructor>();
@@ -136,7 +134,6 @@ void registerTypesMapObjectTypes(Serializer &s)
REGISTER_GENERIC_HANDLER(CGScholar);
REGISTER_GENERIC_HANDLER(CGSeerHut);
REGISTER_GENERIC_HANDLER(CGShipyard);
REGISTER_GENERIC_HANDLER(CGShrine);
REGISTER_GENERIC_HANDLER(CGSignBottle);
REGISTER_GENERIC_HANDLER(CGSirens);
REGISTER_GENERIC_HANDLER(CGMonolith);
@@ -173,7 +170,6 @@ void registerTypesMapObjects2(Serializer &s)
s.template registerType<CGObjectInstance, CRewardableObject>();
s.template registerType<CGObjectInstance, CTeamVisited>();
s.template registerType<CTeamVisited, CGShrine>();
s.template registerType<CTeamVisited, CGObelisk>();
//s.template registerType<CQuest>();

View File

@@ -119,6 +119,7 @@ void Rewardable::Info::configureLimiter(Rewardable::Configuration & object, CRan
limiter.secondary = JsonRandom::loadSecondaries(source["secondary"], rng, variables);
limiter.artifacts = JsonRandom::loadArtifacts(source["artifacts"], rng, variables);
limiter.spells = JsonRandom::loadSpells(source["spells"], rng, variables);
limiter.canLearnSpells = JsonRandom::loadSpells(source["canLearnSpells"], rng, variables);
limiter.creatures = JsonRandom::loadCreatures(source["creatures"], rng, variables);
limiter.players = JsonRandom::loadColors(source["colors"], rng);

View File

@@ -125,6 +125,12 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
return false;
}
for(const auto & spell : canLearnSpells)
{
if (!hero->canLearnSpell(spell.toSpell(VLC->spells())))
return false;
}
{
std::unordered_map<ArtifactID, unsigned int, ArtifactID::hash> artifactsRequirements; // artifact ID -> required count
for(const auto & art : artifacts)

View File

@@ -61,6 +61,9 @@ struct DLL_LINKAGE Limiter final
/// Spells that hero must have in the spellbook
std::vector<SpellID> spells;
/// Spells that hero must be able to learn
std::vector<SpellID> canLearnSpells;
/// creatures that hero needs to have
std::vector<CStackBasicDescriptor> creatures;
@@ -97,10 +100,13 @@ struct DLL_LINKAGE Limiter final
h & heroLevel;
h & manaPoints;
h & manaPercentage;
h & canLearnSkills;
h & resources;
h & primary;
h & secondary;
h & artifacts;
h & spells;
h & canLearnSpells;
h & creatures;
h & heroes;
h & heroClasses;

View File

@@ -85,8 +85,8 @@ public:
void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override {}
void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {}
void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {} //when two heroes meet on adventure map
void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) override {}
void changeFogOfWar(std::unordered_set<int3> &tiles, PlayerColor player, bool hide) override {}
void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) override {}
void changeFogOfWar(std::unordered_set<int3> &tiles, PlayerColor player, ETileVisibility mode) override {}
void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {}
///useful callback methods