mirror of
https://github.com/vcmi/vcmi.git
synced 2025-09-16 09:26:28 +02:00
Merge pull request #5743 from SoundSSGood/num-of-charges-per-spell
Charge cost for charge based spells
This commit is contained in:
@@ -128,6 +128,21 @@ Parameters:
|
||||
|
||||
For reference on tiles indexes see image below:
|
||||
|
||||
### HAS_CHARGES_LIMITER
|
||||
|
||||
Currently works only with spells. Sets the cost of use in charges
|
||||
|
||||
Parameters:
|
||||
|
||||
- use cost (charges)
|
||||
|
||||
```json
|
||||
"limiters" : [ {
|
||||
"type" : "HAS_CHARGES_LIMITER",
|
||||
"parameters" : [2]
|
||||
} ]
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Aggregate Limiters
|
||||
|
@@ -236,6 +236,12 @@ Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32
|
||||
targetSourceType = BonusSource::OTHER;
|
||||
}
|
||||
|
||||
Bonus::Bonus(const Bonus & inst, const BonusSourceID & sourceId)
|
||||
: Bonus(inst)
|
||||
{
|
||||
sid = sourceId;
|
||||
}
|
||||
|
||||
std::shared_ptr<Bonus> Bonus::addPropagator(const TPropagatorPtr & Propagator)
|
||||
{
|
||||
propagator = Propagator;
|
||||
|
@@ -11,29 +11,21 @@
|
||||
|
||||
#include "BonusEnum.h"
|
||||
#include "BonusCustomTypes.h"
|
||||
#include "../constants/VariantIdentifier.h"
|
||||
#include "../constants/EntityIdentifiers.h"
|
||||
#include "Limiters.h"
|
||||
#include "../serializer/Serializeable.h"
|
||||
#include "../texts/MetaString.h"
|
||||
#include "../filesystem/ResourcePath.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct Bonus;
|
||||
class IBonusBearer;
|
||||
class CBonusSystemNode;
|
||||
class ILimiter;
|
||||
class IPropagator;
|
||||
class IUpdater;
|
||||
class BonusList;
|
||||
class CSelector;
|
||||
class IGameInfoCallback;
|
||||
|
||||
using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool>;
|
||||
using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField>;
|
||||
using TBonusListPtr = std::shared_ptr<BonusList>;
|
||||
using TConstBonusListPtr = std::shared_ptr<const BonusList>;
|
||||
using TLimiterPtr = std::shared_ptr<const ILimiter>;
|
||||
using TPropagatorPtr = std::shared_ptr<const IPropagator>;
|
||||
using TUpdaterPtr = std::shared_ptr<const IUpdater>;
|
||||
|
||||
@@ -86,6 +78,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>, public Se
|
||||
Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID);
|
||||
Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype);
|
||||
Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype, BonusValueType ValType);
|
||||
Bonus(const Bonus & inst, const BonusSourceID & sourceId);
|
||||
Bonus() = default;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../constants/EntityIdentifiers.h"
|
||||
#include "../constants/VariantIdentifier.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@@ -76,4 +77,7 @@ public:
|
||||
static BonusCustomSubtype creatureLevel(int level);
|
||||
};
|
||||
|
||||
using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool>;
|
||||
using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, ArtifactInstanceID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField>;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@@ -14,13 +14,9 @@
|
||||
|
||||
#include "../CBonusTypeHandler.h"
|
||||
#include "../GameLibrary.h"
|
||||
#include "../entities/faction/CFaction.h"
|
||||
#include "../entities/faction/CTownHandler.h"
|
||||
#include "../spells/CSpellHandler.h"
|
||||
#include "../CCreatureHandler.h"
|
||||
#include "../CCreatureSet.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../CSkillHandler.h"
|
||||
#include "../CStack.h"
|
||||
#include "../TerrainHandler.h"
|
||||
#include "../constants/StringConstants.h"
|
||||
@@ -81,7 +77,7 @@ static const CCreature * retrieveCreature(const CBonusSystemNode *node)
|
||||
}
|
||||
}
|
||||
|
||||
ILimiter::EDecision ILimiter::limit(const BonusLimitationContext &context) const /*return true to drop the bonus */
|
||||
ILimiter::EDecision ILimiter::limit(const BonusLimitationContext &context) const
|
||||
{
|
||||
return ILimiter::EDecision::ACCEPT;
|
||||
}
|
||||
@@ -591,4 +587,25 @@ ILimiter::EDecision NoneOfLimiter::limit(const BonusLimitationContext & context)
|
||||
return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT;
|
||||
}
|
||||
|
||||
HasChargesLimiter::HasChargesLimiter(const uint16_t cost)
|
||||
: chargeCost(cost)
|
||||
{
|
||||
}
|
||||
|
||||
ILimiter::EDecision HasChargesLimiter::limit(const BonusLimitationContext & context) const
|
||||
{
|
||||
for(const auto & bonus : context.stillUndecided)
|
||||
{
|
||||
if(bonus->type == BonusType::ARTIFACT_CHARGE && bonus->sid == context.b.sid)
|
||||
return ILimiter::EDecision::NOT_SURE;
|
||||
}
|
||||
|
||||
for(const auto & bonus : context.alreadyAccepted)
|
||||
{
|
||||
if(bonus->type == BonusType::ARTIFACT_CHARGE && bonus->sid == context.b.sid)
|
||||
return bonus->val >= chargeCost ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
|
||||
}
|
||||
return ILimiter::EDecision::DISCARD;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@@ -7,18 +7,21 @@
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "Bonus.h"
|
||||
|
||||
#include "BonusCustomTypes.h"
|
||||
#include "BonusEnum.h"
|
||||
#include "../battle/BattleHexArray.h"
|
||||
#include "../serializer/Serializeable.h"
|
||||
#include "../constants/Enumerations.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct Bonus;
|
||||
class BonusList;
|
||||
class CBonusSystemNode;
|
||||
class CCreature;
|
||||
|
||||
extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
|
||||
class JsonNode;
|
||||
|
||||
struct BonusLimitationContext
|
||||
{
|
||||
@@ -41,7 +44,7 @@ public:
|
||||
|
||||
virtual ~ILimiter() = default;
|
||||
|
||||
virtual EDecision limit(const BonusLimitationContext &context) const; //0 - accept bonus; 1 - drop bonus; 2 - delay (drops eventually)
|
||||
virtual EDecision limit(const BonusLimitationContext &context) const;
|
||||
virtual std::string toString() const;
|
||||
virtual JsonNode toJsonNode() const;
|
||||
|
||||
@@ -50,6 +53,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
using TLimiterPtr = std::shared_ptr<const ILimiter>;
|
||||
extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
|
||||
|
||||
class DLL_LINKAGE AggregateLimiter : public ILimiter
|
||||
{
|
||||
protected:
|
||||
@@ -276,4 +282,19 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class DLL_LINKAGE HasChargesLimiter : public ILimiter // works with bonuses that consume charges
|
||||
{
|
||||
public:
|
||||
uint16_t chargeCost;
|
||||
|
||||
HasChargesLimiter(const uint16_t cost = 1);
|
||||
EDecision limit(const BonusLimitationContext & context) const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<ILimiter&>(*this);
|
||||
h & chargeCost;
|
||||
}
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@@ -134,6 +134,11 @@ BuildingTypeUniqueID::BuildingTypeUniqueID(FactionID factionID, BuildingID build
|
||||
assert(buildingID.getNum() < 0x10000);
|
||||
}
|
||||
|
||||
std::string ArtifactInstanceID::encode(const si32 index)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
BuildingID BuildingTypeUniqueID::getBuilding() const
|
||||
{
|
||||
return BuildingID(getNum() % 0x10000);
|
||||
|
@@ -51,6 +51,8 @@ class ArtifactInstanceID : public StaticIdentifier<ArtifactInstanceID>
|
||||
{
|
||||
public:
|
||||
using StaticIdentifier<ArtifactInstanceID>::StaticIdentifier;
|
||||
|
||||
DLL_LINKAGE static std::string encode(const si32 index);
|
||||
};
|
||||
|
||||
class QueryID : public StaticIdentifier<QueryID>
|
||||
|
@@ -181,6 +181,7 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
|
||||
for(const auto & b : node["bonuses"].Vector())
|
||||
{
|
||||
auto bonus = JsonUtils::parseBonus(b);
|
||||
bonus->sid = art->getId();
|
||||
art->addNewBonus(bonus);
|
||||
}
|
||||
}
|
||||
@@ -191,6 +192,7 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
|
||||
if (b.second.isNull())
|
||||
continue;
|
||||
auto bonus = JsonUtils::parseBonus(b.second, art->getBonusTextID(b.first));
|
||||
bonus->sid = art->getId();
|
||||
art->addNewBonus(bonus);
|
||||
}
|
||||
}
|
||||
@@ -200,6 +202,7 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
|
||||
if (b.second.isNull())
|
||||
continue;
|
||||
auto bonus = JsonUtils::parseBonus(b.second, art->getBonusTextID(b.first));
|
||||
bonus->sid = art->getId();
|
||||
bonus->source = BonusSource::ARTIFACT;
|
||||
bonus->duration = BonusDuration::PERMANENT;
|
||||
bonus->description.appendTextID(art->getNameTextID());
|
||||
@@ -254,8 +257,20 @@ std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope,
|
||||
else
|
||||
art->setDefaultStartCharges(charges);
|
||||
}
|
||||
if(art->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST && art->getBonusesOfType(BonusType::SPELL)->size() == 0)
|
||||
logMod->warn("Warning! %s condition of discharge is \"SPELLCAST\", but there is not a single spell.", art->getNameTranslated());
|
||||
}
|
||||
|
||||
// Some bonuses must be located in the instance.
|
||||
for(const auto & b : art->getExportedBonusList())
|
||||
{
|
||||
if(std::dynamic_pointer_cast<const HasChargesLimiter>(b->limiter))
|
||||
{
|
||||
b->source = BonusSource::ARTIFACT;
|
||||
b->duration = BonusDuration::PERMANENT;
|
||||
b->description.appendTextID(art->getNameTextID());
|
||||
b->description.appendRawString(" %+d");
|
||||
art->instanceBonuses.push_back(b);
|
||||
art->removeBonus(b);
|
||||
}
|
||||
}
|
||||
|
||||
return art;
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include "ArtifactUtils.h"
|
||||
#include "CArtifactFittingSet.h"
|
||||
|
||||
#include "../../bonuses/Limiters.h"
|
||||
#include "../../texts/CGeneralTextHandler.h"
|
||||
#include "../../GameLibrary.h"
|
||||
|
||||
@@ -292,6 +293,21 @@ bool CChargedArtifact::getRemoveOnDepletion() const
|
||||
return removeOnDepletion;
|
||||
}
|
||||
|
||||
std::optional<uint16_t> CChargedArtifact::getChargeCost(const SpellID & id) const
|
||||
{
|
||||
auto art = static_cast<const CArtifact*>(this);
|
||||
|
||||
for(const auto & bonus : art->instanceBonuses)
|
||||
{
|
||||
if(bonus->type == BonusType::SPELL && bonus->subtype.as<SpellID>() == id)
|
||||
{
|
||||
if(const auto chargesLimiter = std::static_pointer_cast<const HasChargesLimiter>(bonus->limiter))
|
||||
return chargesLimiter->chargeCost;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CArtifact::CArtifact()
|
||||
: CBonusSystemNode(BonusNodeType::ARTIFACT),
|
||||
iconIndex(ArtifactID::NONE),
|
||||
|
@@ -83,6 +83,7 @@ public:
|
||||
uint16_t getDefaultStartCharges() const;
|
||||
DischargeArtifactCondition getDischargeCondition() const;
|
||||
bool getRemoveOnDepletion() const;
|
||||
std::optional<uint16_t> getChargeCost(const SpellID & id) const;
|
||||
};
|
||||
|
||||
// Container for artifacts. Not for instances.
|
||||
|
@@ -120,44 +120,6 @@ void CGrowingArtifactInstance::growingUp()
|
||||
artInst->addNewBonus(std::make_shared<Bonus>(*bonus.second));
|
||||
}
|
||||
}
|
||||
|
||||
if(artType->isCharged())
|
||||
artInst->onChargesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void CChargedArtifactInstance::onChargesChanged()
|
||||
{
|
||||
auto artInst = static_cast<CArtifactInstance*>(this);
|
||||
const auto artType = artInst->getType();
|
||||
|
||||
if(!artType->isCharged())
|
||||
return;
|
||||
|
||||
const auto bonusSelector = artType->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST ?
|
||||
Selector::type()(BonusType::SPELL) : Selector::all;
|
||||
|
||||
auto instBonuses = artInst->getAllBonuses(bonusSelector, nullptr);
|
||||
|
||||
if(artInst->getCharges() == 0)
|
||||
{
|
||||
for(const auto & bonus : *instBonuses)
|
||||
if(bonus->type != BonusType::ARTIFACT_GROWING && bonus->type != BonusType::ARTIFACT_CHARGE)
|
||||
artInst->removeBonus(bonus);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(const auto & refBonus : *artType->getAllBonuses(bonusSelector, nullptr))
|
||||
{
|
||||
if(const auto bonusFound = std::find_if(instBonuses->begin(), instBonuses->end(),
|
||||
[refBonus](const auto & instBonus)
|
||||
{
|
||||
return refBonus->type == instBonus->type;
|
||||
}); bonusFound == instBonuses->end())
|
||||
{
|
||||
artInst->accumulateBonus(refBonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +133,6 @@ void CChargedArtifactInstance::discharge(const uint16_t charges)
|
||||
chargedBonus->front()->val -= charges;
|
||||
else
|
||||
chargedBonus->front()->val = 0;
|
||||
onChargesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +145,6 @@ void CChargedArtifactInstance::addCharges(const uint16_t charges)
|
||||
const auto chargedBonus = artInst->getBonusesOfType(BonusType::ARTIFACT_CHARGE);
|
||||
assert(!chargedBonus->empty());
|
||||
chargedBonus->front()->val += charges;
|
||||
onChargesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,21 +159,7 @@ void CArtifactInstance::init()
|
||||
{
|
||||
const auto art = artTypeID.toArtifact();
|
||||
assert(art);
|
||||
|
||||
if(art->isCharged())
|
||||
{
|
||||
// Charged artifacts contain all bonuses inside instance bonus node
|
||||
if(art->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST)
|
||||
{
|
||||
for(const auto & bonus : *art->getAllBonuses(Selector::all, nullptr))
|
||||
if(bonus->type != BonusType::SPELL)
|
||||
accumulateBonus(bonus);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
attachToSource(*art);
|
||||
}
|
||||
attachToSource(*art);
|
||||
}
|
||||
|
||||
CArtifactInstance::CArtifactInstance(IGameInfoCallback *cb, const CArtifact * art)
|
||||
|
@@ -80,7 +80,6 @@ class DLL_LINKAGE CChargedArtifactInstance
|
||||
protected:
|
||||
CChargedArtifactInstance() = default;
|
||||
public:
|
||||
void onChargesChanged();
|
||||
void discharge(const uint16_t charges);
|
||||
void addCharges(const uint16_t charges);
|
||||
uint16_t getCharges() const;
|
||||
@@ -122,7 +121,6 @@ public:
|
||||
if(!h.saving && h.loadingGamestate)
|
||||
{
|
||||
init();
|
||||
onChargesChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -946,6 +946,9 @@ void GameStatePackVisitor::visitDischargeArtifact(DischargeArtifact & pack)
|
||||
ePack.posPack.push_back(pack.artLoc.value().slot);
|
||||
ePack.visit(*this);
|
||||
}
|
||||
// Workaround to inform hero bonus node about changes. Obviously this has to be done somehow differently.
|
||||
if(pack.artLoc.has_value())
|
||||
gs.getHero(pack.artLoc.value().artHolder)->nodeHasChanged();
|
||||
}
|
||||
|
||||
void GameStatePackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
|
||||
|
@@ -641,6 +641,16 @@ std::shared_ptr<const ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter
|
||||
}
|
||||
return hexLimiter;
|
||||
}
|
||||
else if(limiterType == "HAS_CHARGES_LIMITER")
|
||||
{
|
||||
auto hasChargesLimiter = std::make_shared<HasChargesLimiter>();
|
||||
if(!parameters.Vector().empty())
|
||||
{
|
||||
if(parameters.Vector().size() == 1 && parameters.Vector().front().isNumber())
|
||||
hasChargesLimiter->chargeCost = parameters.Vector().front().Integer();
|
||||
}
|
||||
return hasChargesLimiter;
|
||||
}
|
||||
else
|
||||
{
|
||||
logMod->error("Error: invalid customizable limiter type %s.", limiterType);
|
||||
|
@@ -902,22 +902,7 @@ void CGHeroInstance::spendMana(ServerCallback * server, const int spellCost) con
|
||||
|
||||
bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const
|
||||
{
|
||||
const bool isAllowed = cb->isAllowed(spell->getId());
|
||||
|
||||
const bool inSpellBook = vstd::contains(spells, spell->getId()) && hasSpellbook();
|
||||
const bool specificBonus = hasBonusOfType(BonusType::SPELL, BonusSubtypeID(spell->getId()));
|
||||
|
||||
bool schoolBonus = false;
|
||||
|
||||
spell->forEachSchool([this, &schoolBonus](const SpellSchool & cnf, bool & stop)
|
||||
{
|
||||
if(hasBonusOfType(BonusType::SPELLS_OF_SCHOOL, BonusSubtypeID(cnf)))
|
||||
{
|
||||
schoolBonus = stop = true;
|
||||
}
|
||||
});
|
||||
|
||||
const bool levelBonus = hasBonusOfType(BonusType::SPELLS_OF_LEVEL, BonusCustomSubtype::spellLevel(spell->getLevel()));
|
||||
const bool inSpellBook = spellbookContainsSpell(spell->getId()) && hasSpellbook();
|
||||
|
||||
if(spell->isSpecial())
|
||||
{
|
||||
@@ -925,9 +910,9 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const
|
||||
{//hero has this spell in spellbook
|
||||
logGlobal->error("Special spell %s in spellbook.", spell->getNameTranslated());
|
||||
}
|
||||
return specificBonus;
|
||||
return hasBonusOfType(BonusType::SPELL, BonusSubtypeID(spell->getId()));
|
||||
}
|
||||
else if(!isAllowed)
|
||||
else if(!cb->isAllowed(spell->getId()))
|
||||
{
|
||||
if(inSpellBook)
|
||||
{
|
||||
@@ -935,12 +920,8 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const
|
||||
//it is normal if set in map editor, but trace it to possible debug of magic guild
|
||||
logGlobal->trace("Banned spell %s in spellbook.", spell->getNameTranslated());
|
||||
}
|
||||
return inSpellBook || specificBonus || schoolBonus || levelBonus;
|
||||
}
|
||||
else
|
||||
{
|
||||
return inSpellBook || schoolBonus || specificBonus || levelBonus;
|
||||
}
|
||||
return !getSourcesForSpell(spell->getId()).empty();
|
||||
}
|
||||
|
||||
bool CGHeroInstance::canLearnSpell(const spells::Spell * spell, bool allowBanned) const
|
||||
@@ -1254,6 +1235,29 @@ bool CGHeroInstance::spellbookContainsSpell(const SpellID & spell) const
|
||||
return vstd::contains(spells, spell);
|
||||
}
|
||||
|
||||
std::vector<BonusSourceID> CGHeroInstance::getSourcesForSpell(const SpellID & spellId) const
|
||||
{
|
||||
std::vector<BonusSourceID> sources;
|
||||
|
||||
if(hasSpellbook() && spellbookContainsSpell(spellId))
|
||||
sources.emplace_back(getArt(ArtifactPosition::SPELLBOOK)->getId());
|
||||
|
||||
for(const auto & bonus : *getBonusesOfType(BonusType::SPELL, spellId))
|
||||
sources.emplace_back(bonus->sid);
|
||||
|
||||
const auto spell = spellId.toSpell();
|
||||
spell->forEachSchool([this, &sources](const SpellSchool & cnf, bool & stop)
|
||||
{
|
||||
for(const auto & bonus : *getBonusesOfType(BonusType::SPELLS_OF_SCHOOL, cnf))
|
||||
sources.emplace_back(bonus->sid);
|
||||
});
|
||||
|
||||
for(const auto & bonus : *getBonusesOfType(BonusType::SPELLS_OF_LEVEL, BonusCustomSubtype::spellLevel(spell->getLevel())))
|
||||
sources.emplace_back(bonus->sid);
|
||||
|
||||
return sources;
|
||||
}
|
||||
|
||||
void CGHeroInstance::removeSpellbook()
|
||||
{
|
||||
spells.clear();
|
||||
|
@@ -153,6 +153,7 @@ public:
|
||||
void addSpellToSpellbook(const SpellID & spell);
|
||||
void removeSpellFromSpellbook(const SpellID & spell);
|
||||
bool spellbookContainsSpell(const SpellID & spell) const;
|
||||
std::vector<BonusSourceID> getSourcesForSpell(const SpellID & spell) const;
|
||||
void removeSpellbook();
|
||||
const std::set<SpellID> & getSpellsInSpellbook() const;
|
||||
EAlignment getAlignment() const;
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include "../RoadHandler.h"
|
||||
#include "../TerrainHandler.h"
|
||||
|
||||
#include "../bonuses/Limiters.h"
|
||||
#include "../callback/IGameInfoCallback.h"
|
||||
#include "../entities/artifact/CArtHandler.h"
|
||||
#include "../entities/hero/CHeroHandler.h"
|
||||
@@ -863,13 +864,14 @@ CArtifactInstance * CMap::createArtifact(const ArtifactID & artID, const SpellID
|
||||
{
|
||||
auto bonus = std::make_shared<Bonus>();
|
||||
bonus->type = BonusType::ARTIFACT_CHARGE;
|
||||
bonus->sid = artInst->getId();
|
||||
bonus->val = 0;
|
||||
artInst->addNewBonus(bonus);
|
||||
artInst->addCharges(art->getDefaultStartCharges());
|
||||
}
|
||||
|
||||
for (const auto & bonus : art->instanceBonuses)
|
||||
artInst->addNewBonus(std::make_shared<Bonus>(*bonus));
|
||||
artInst->addNewBonus(std::make_shared<Bonus>(*bonus, artInst->getId()));
|
||||
|
||||
return artInst;
|
||||
}
|
||||
|
@@ -111,6 +111,7 @@ void registerTypes(Serializer &s)
|
||||
s.template registerType<OppositeSideLimiter>(51);
|
||||
s.template registerType<TownBuildingInstance>(52);
|
||||
s.template registerType<TownRewardableBuildingInstance>(53);
|
||||
s.template registerType<HasChargesLimiter>(55);
|
||||
s.template registerType<CRewardableObject>(56);
|
||||
s.template registerType<CTeamVisited>(57);
|
||||
s.template registerType<CGObelisk>(58);
|
||||
|
@@ -3938,7 +3938,7 @@ void CGameHandler::castSpell(const spells::Caster * caster, SpellID spellID, con
|
||||
s->adventureCast(spellEnv.get(), p);
|
||||
|
||||
if(const auto * hero = caster->getHeroCaster())
|
||||
useChargedArtifactUsed(hero->id, spellID);
|
||||
useChargeBasedSpell(hero->id, spellID);
|
||||
}
|
||||
|
||||
bool CGameHandler::swapStacks(const StackLocation & sl1, const StackLocation & sl2)
|
||||
@@ -4331,33 +4331,38 @@ void CGameHandler::startBattle(const CArmedInstance *army1, const CArmedInstance
|
||||
battles->startBattle(army1, army2);
|
||||
}
|
||||
|
||||
void CGameHandler::useChargedArtifactUsed(const ObjectInstanceID & heroObjectID, const SpellID & spellID)
|
||||
void CGameHandler::useChargeBasedSpell(const ObjectInstanceID & heroObjectID, const SpellID & spellID)
|
||||
{
|
||||
const auto * hero = gameInfo().getHero(heroObjectID);
|
||||
assert(hero);
|
||||
assert(hero->canCastThisSpell(spellID.toSpell()));
|
||||
|
||||
if(vstd::contains(hero->getSpellsInSpellbook(), spellID))
|
||||
return;
|
||||
|
||||
std::vector<std::pair<ArtifactPosition, ArtifactInstanceID>> chargedArts;
|
||||
for(const auto & [slot, slotInfo] : hero->artifactsWorn)
|
||||
// Check if hero used charge based spell
|
||||
// Try to find other sources of the spell besides the charged artifacts. If there are any, we use them.
|
||||
std::optional<std::tuple<ArtifactPosition, ArtifactInstanceID, uint16_t>> chargedArt;
|
||||
for(const auto & source : hero->getSourcesForSpell(spellID))
|
||||
{
|
||||
const auto * artInst = slotInfo.getArt();
|
||||
const auto * artType = artInst->getType();
|
||||
if(artType->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST)
|
||||
if(const auto * artInst = hero->getArtByInstanceId(source.as<ArtifactInstanceID>()))
|
||||
{
|
||||
chargedArts.emplace_back(slot, artInst->getId());
|
||||
const auto * artType = artInst->getType();
|
||||
const auto spellCost = artType->getChargeCost(spellID);
|
||||
if(spellCost.has_value() && spellCost.value() <= artInst->getCharges() && artType->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST)
|
||||
{
|
||||
chargedArt.emplace(hero->getArtPos(artInst), artInst->getId(), spellCost.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(const auto bonuses = artInst->getBonusesOfType(BonusType::SPELL, spellID); !bonuses->empty())
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assert(!chargedArts.empty());
|
||||
DischargeArtifact msg(chargedArts.front().second, 1);
|
||||
msg.artLoc.emplace(hero->id, chargedArts.front().first);
|
||||
assert(chargedArt.has_value());
|
||||
DischargeArtifact msg(std::get<1>(chargedArt.value()), std::get<2>(chargedArt.value()));
|
||||
msg.artLoc.emplace(hero->id, std::get<0>(chargedArt.value()));
|
||||
sendAndApply(msg);
|
||||
}
|
||||
|
@@ -176,7 +176,7 @@ public:
|
||||
void changeFogOfWar(const std::unordered_set<int3> &tiles, PlayerColor player,ETileVisibility mode) override;
|
||||
|
||||
void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override;
|
||||
void useChargedArtifactUsed(const ObjectInstanceID & heroObjectID, const SpellID & spellID);
|
||||
void useChargeBasedSpell(const ObjectInstanceID & heroObjectID, const SpellID & spellID);
|
||||
|
||||
/// Returns hero that is currently visiting this object, or nullptr if no visit is active
|
||||
const CGHeroInstance * getVisitingHero(const CGObjectInstance *obj);
|
||||
|
@@ -124,7 +124,7 @@ bool BattleActionProcessor::doHeroSpellAction(const CBattleInfoCallback & battle
|
||||
}
|
||||
|
||||
parameters.cast(gameHandler->spellcastEnvironment(), ba.getTarget(&battle));
|
||||
gameHandler->useChargedArtifactUsed(h->id, ba.spell);
|
||||
gameHandler->useChargeBasedSpell(h->id, ba.spell);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -436,7 +436,7 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt
|
||||
pack.artsPack0.emplace_back(MoveArtifactInfo(srcSlot, dstSlot));
|
||||
if(ArtifactUtils::isSlotEquipment(dstSlot))
|
||||
pack.artsPack0.back().askAssemble = true;
|
||||
artFittingSet.putArtifact(dstSlot, const_cast<CArtifactInstance*>(art));
|
||||
artFittingSet.putArtifact(dstSlot, art);
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user