/* * Limiters.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 "Limiters.h" #include "../VCMI_Lib.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 "../CArtHandler.h" #include "../TerrainHandler.h" #include "../constants/StringConstants.h" #include "../battle/BattleInfo.h" #include "../json/JsonUtils.h" VCMI_LIB_NAMESPACE_BEGIN const std::map bonusLimiterMap = { {"SHOOTER_ONLY", std::make_shared(BonusType::SHOOTER)}, {"DRAGON_NATURE", std::make_shared(BonusType::DRAGON_NATURE)}, {"IS_UNDEAD", std::make_shared(BonusType::UNDEAD)}, {"CREATURE_NATIVE_TERRAIN", std::make_shared()}, {"CREATURE_FACTION", std::make_shared(std::initializer_list{std::make_shared(), std::make_shared()})}, {"SAME_FACTION", std::make_shared()}, {"CREATURES_ONLY", std::make_shared()}, {"OPPOSITE_SIDE", std::make_shared()}, }; static const CStack * retrieveStackBattle(const CBonusSystemNode * node) { switch(node->getNodeType()) { case CBonusSystemNode::STACK_BATTLE: return dynamic_cast(node); default: return nullptr; } } static const CStackInstance * retrieveStackInstance(const CBonusSystemNode * node) { switch(node->getNodeType()) { case CBonusSystemNode::STACK_INSTANCE: return (dynamic_cast(node)); case CBonusSystemNode::STACK_BATTLE: return (dynamic_cast(node))->base; default: return nullptr; } } static const CCreature * retrieveCreature(const CBonusSystemNode *node) { switch(node->getNodeType()) { case CBonusSystemNode::CREATURE: return (dynamic_cast(node)); case CBonusSystemNode::STACK_BATTLE: return (dynamic_cast(node))->unitType(); default: const CStackInstance * csi = retrieveStackInstance(node); if(csi) return csi->getCreature(); return nullptr; } } ILimiter::EDecision ILimiter::limit(const BonusLimitationContext &context) const /*return true to drop the bonus */ { return ILimiter::EDecision::ACCEPT; } std::string ILimiter::toString() const { return typeid(*this).name(); } JsonNode ILimiter::toJsonNode() const { JsonNode root; root["type"].String() = toString(); return root; } ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &context) const { const CCreature *c = retrieveCreature(&context.node); if(!c) return ILimiter::EDecision::DISCARD; auto accept = c->getId() == creatureID || (includeUpgrades && creatureID.toCreature()->isMyUpgrade(c)); return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade) } CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades) : creatureID(creature_.getId()), includeUpgrades(IncludeUpgrades) { } void CCreatureTypeLimiter::setCreature(const CreatureID & id) { creatureID = id; } std::string CCreatureTypeLimiter::toString() const { boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)"); fmt % creatureID.toEntity(VLC)->getJsonKey() % (includeUpgrades ? "true" : "false"); return fmt.str(); } JsonNode CCreatureTypeLimiter::toJsonNode() const { JsonNode root; root["type"].String() = "CREATURE_TYPE_LIMITER"; root["parameters"].Vector().emplace_back(creatureID.toEntity(VLC)->getJsonKey()); root["parameters"].Vector().emplace_back(includeUpgrades); return root; } HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus ) : type(bonus), isSubtypeRelevant(false), isSourceRelevant(false), isSourceIDRelevant(false) { } HasAnotherBonusLimiter::HasAnotherBonusLimiter( BonusType bonus, BonusSubtypeID _subtype ) : type(bonus), subtype(_subtype), isSubtypeRelevant(true), isSourceRelevant(false), isSourceIDRelevant(false) { } HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, BonusSource src) : type(bonus), source(src), isSubtypeRelevant(false), isSourceRelevant(true), isSourceIDRelevant(false) { } HasAnotherBonusLimiter::HasAnotherBonusLimiter(BonusType bonus, BonusSubtypeID _subtype, BonusSource src) : type(bonus), subtype(_subtype), isSubtypeRelevant(true), source(src), isSourceRelevant(true), isSourceIDRelevant(false) { } ILimiter::EDecision HasAnotherBonusLimiter::limit(const BonusLimitationContext &context) const { //TODO: proper selector config with parsing of JSON auto mySelector = Selector::type()(type); if(isSubtypeRelevant) mySelector = mySelector.And(Selector::subtype()(subtype)); if(isSourceRelevant && isSourceIDRelevant) mySelector = mySelector.And(Selector::source(source, sid)); else if (isSourceRelevant) mySelector = mySelector.And(Selector::sourceTypeSel(source)); //if we have a bonus of required type accepted, limiter should accept also this bonus if(context.alreadyAccepted.getFirst(mySelector)) return ILimiter::EDecision::ACCEPT; //if there are no matching bonuses pending, we can (and must) reject right away if(!context.stillUndecided.getFirst(mySelector)) return ILimiter::EDecision::DISCARD; //do not accept for now but it may change if more bonuses gets included return ILimiter::EDecision::NOT_SURE; } std::string HasAnotherBonusLimiter::toString() const { std::string typeName = vstd::findKey(bonusNameMap, type); if(isSubtypeRelevant) { boost::format fmt("HasAnotherBonusLimiter(type=%s, subtype=%s)"); fmt % typeName % subtype.toString(); return fmt.str(); } else { boost::format fmt("HasAnotherBonusLimiter(type=%s)"); fmt % typeName; return fmt.str(); } } JsonNode HasAnotherBonusLimiter::toJsonNode() const { JsonNode root; std::string typeName = vstd::findKey(bonusNameMap, type); auto sourceTypeName = vstd::findKey(bonusSourceMap, source); root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER"; root["parameters"].Vector().emplace_back(typeName); if(isSubtypeRelevant) root["parameters"].Vector().emplace_back(subtype.toString()); if(isSourceRelevant) root["parameters"].Vector().emplace_back(sourceTypeName); return root; } ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &context) const { const auto * stack = retrieveStackBattle(&context.node); if(!stack) return ILimiter::EDecision::DISCARD; auto accept = false; for (const auto & hex : stack->getHexes()) accept |= !!applicableHexes.count(hex); return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; } UnitOnHexLimiter::UnitOnHexLimiter(const std::set & applicableHexes): applicableHexes(applicableHexes) { } JsonNode UnitOnHexLimiter::toJsonNode() const { JsonNode root; root["type"].String() = "UNIT_ON_HEXES"; for(const auto & hex : applicableHexes) root["parameters"].Vector().emplace_back(hex); return root; } CreatureTerrainLimiter::CreatureTerrainLimiter() : terrainType(ETerrainId::NATIVE_TERRAIN) { } CreatureTerrainLimiter::CreatureTerrainLimiter(TerrainId terrain): terrainType(terrain) { } ILimiter::EDecision CreatureTerrainLimiter::limit(const BonusLimitationContext &context) const { const CStack *stack = retrieveStackBattle(&context.node); if(stack) { if (terrainType == ETerrainId::NATIVE_TERRAIN && stack->isOnNativeTerrain())//terrainType not specified = native return ILimiter::EDecision::ACCEPT; if(terrainType != ETerrainId::NATIVE_TERRAIN && stack->isOnTerrain(terrainType)) return ILimiter::EDecision::ACCEPT; } return ILimiter::EDecision::DISCARD; //TODO neutral creatues } std::string CreatureTerrainLimiter::toString() const { boost::format fmt("CreatureTerrainLimiter(terrainType=%s)"); auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey(); fmt % (terrainType == ETerrainId::NATIVE_TERRAIN ? "native" : terrainName); return fmt.str(); } JsonNode CreatureTerrainLimiter::toJsonNode() const { JsonNode root; root["type"].String() = "CREATURE_TERRAIN_LIMITER"; auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey(); root["parameters"].Vector().emplace_back(terrainName); return root; } FactionLimiter::FactionLimiter(FactionID creatureFaction) : faction(creatureFaction) { } ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context) const { const auto * bearer = dynamic_cast(&context.node); if(bearer) { if(faction != FactionID::DEFAULT) return bearer->getFactionID() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; switch(context.b.source) { case BonusSource::CREATURE_ABILITY: return bearer->getFactionID() == context.b.sid.as().toCreature()->getFactionID() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; case BonusSource::TOWN_STRUCTURE: return bearer->getFactionID() == context.b.sid.as().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //TODO: other sources of bonuses } } return ILimiter::EDecision::DISCARD; //Discard by default } std::string FactionLimiter::toString() const { boost::format fmt("FactionLimiter(faction=%s)"); fmt % VLC->factions()->getById(faction)->getJsonKey(); return fmt.str(); } JsonNode FactionLimiter::toJsonNode() const { JsonNode root; root["type"].String() = "FACTION_LIMITER"; root["parameters"].Vector().emplace_back(VLC->factions()->getById(faction)->getJsonKey()); return root; } CreatureLevelLimiter::CreatureLevelLimiter(uint32_t minLevel, uint32_t maxLevel) : minLevel(minLevel), maxLevel(maxLevel) { } ILimiter::EDecision CreatureLevelLimiter::limit(const BonusLimitationContext &context) const { const auto *c = retrieveCreature(&context.node); auto accept = c && (c->getLevel() < maxLevel && c->getLevel() >= minLevel); return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //drop bonus for non-creatures or non-native residents } std::string CreatureLevelLimiter::toString() const { boost::format fmt("CreatureLevelLimiter(minLevel=%d,maxLevel=%d)"); fmt % minLevel % maxLevel; return fmt.str(); } JsonNode CreatureLevelLimiter::toJsonNode() const { JsonNode root; root["type"].String() = "CREATURE_LEVEL_LIMITER"; root["parameters"].Vector().emplace_back(minLevel); root["parameters"].Vector().emplace_back(maxLevel); return root; } CreatureAlignmentLimiter::CreatureAlignmentLimiter(EAlignment Alignment) : alignment(Alignment) { } ILimiter::EDecision CreatureAlignmentLimiter::limit(const BonusLimitationContext &context) const { const auto * c = retrieveCreature(&context.node); if(c) { if(alignment == EAlignment::GOOD && c->isGood()) return ILimiter::EDecision::ACCEPT; if(alignment == EAlignment::EVIL && c->isEvil()) return ILimiter::EDecision::ACCEPT; if(alignment == EAlignment::NEUTRAL && !c->isEvil() && !c->isGood()) return ILimiter::EDecision::ACCEPT; } return ILimiter::EDecision::DISCARD; } std::string CreatureAlignmentLimiter::toString() const { boost::format fmt("CreatureAlignmentLimiter(alignment=%s)"); fmt % GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]; return fmt.str(); } JsonNode CreatureAlignmentLimiter::toJsonNode() const { JsonNode root; root["type"].String() = "CREATURE_ALIGNMENT_LIMITER"; root["parameters"].Vector().emplace_back(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]); return root; } RankRangeLimiter::RankRangeLimiter(ui8 Min, ui8 Max) :minRank(Min), maxRank(Max) { } RankRangeLimiter::RankRangeLimiter() { minRank = maxRank = -1; } ILimiter::EDecision RankRangeLimiter::limit(const BonusLimitationContext &context) const { const CStackInstance * csi = retrieveStackInstance(&context.node); if(csi) { if (csi->getNodeType() == CBonusSystemNode::COMMANDER) //no stack exp bonuses for commander creatures return ILimiter::EDecision::DISCARD; if (csi->getExpRank() > minRank && csi->getExpRank() < maxRank) return ILimiter::EDecision::ACCEPT; } return ILimiter::EDecision::DISCARD; } OppositeSideLimiter::OppositeSideLimiter(PlayerColor Owner): owner(std::move(Owner)) { } ILimiter::EDecision OppositeSideLimiter::limit(const BonusLimitationContext & context) const { auto contextOwner = context.node.getOwner(); auto decision = (owner == contextOwner || owner == PlayerColor::CANNOT_DETERMINE) ? ILimiter::EDecision::DISCARD : ILimiter::EDecision::ACCEPT; return decision; } // Aggregate/Boolean Limiters AggregateLimiter::AggregateLimiter(std::vector limiters): limiters(std::move(limiters)) { } void AggregateLimiter::add(const TLimiterPtr & limiter) { if(limiter) limiters.push_back(limiter); } JsonNode AggregateLimiter::toJsonNode() const { JsonNode result; result.Vector().emplace_back(getAggregator()); for(const auto & l : limiters) result.Vector().push_back(l->toJsonNode()); return result; } const std::string AllOfLimiter::aggregator = "allOf"; const std::string & AllOfLimiter::getAggregator() const { return aggregator; } AllOfLimiter::AllOfLimiter(std::vector limiters): AggregateLimiter(limiters) { } ILimiter::EDecision AllOfLimiter::limit(const BonusLimitationContext & context) const { bool wasntSure = false; for(const auto & limiter : limiters) { auto result = limiter->limit(context); if(result == ILimiter::EDecision::DISCARD) return result; if(result == ILimiter::EDecision::NOT_SURE) wasntSure = true; } return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT; } const std::string AnyOfLimiter::aggregator = "anyOf"; const std::string & AnyOfLimiter::getAggregator() const { return aggregator; } AnyOfLimiter::AnyOfLimiter(std::vector limiters): AggregateLimiter(limiters) { } ILimiter::EDecision AnyOfLimiter::limit(const BonusLimitationContext & context) const { bool wasntSure = false; for(const auto & limiter : limiters) { auto result = limiter->limit(context); if(result == ILimiter::EDecision::ACCEPT) return result; if(result == ILimiter::EDecision::NOT_SURE) wasntSure = true; } return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::DISCARD; } const std::string NoneOfLimiter::aggregator = "noneOf"; const std::string & NoneOfLimiter::getAggregator() const { return aggregator; } NoneOfLimiter::NoneOfLimiter(std::vector limiters): AggregateLimiter(limiters) { } ILimiter::EDecision NoneOfLimiter::limit(const BonusLimitationContext & context) const { bool wasntSure = false; for(const auto & limiter : limiters) { auto result = limiter->limit(context); if(result == ILimiter::EDecision::ACCEPT) return ILimiter::EDecision::DISCARD; if(result == ILimiter::EDecision::NOT_SURE) wasntSure = true; } return wasntSure ? ILimiter::EDecision::NOT_SURE : ILimiter::EDecision::ACCEPT; } VCMI_LIB_NAMESPACE_END