diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index 143bcdefa..342b65046 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -585,9 +585,7 @@ void MoraleLuckBox::set(const AFactionMember * node) text = LIBRARY->generaltexth->arraytxt[textId[morale]]; boost::algorithm::replace_first(text,"%s",LIBRARY->generaltexth->arraytxt[neutralDescr[morale]-mrlt]); - if (morale && node && (node->getBonusBearer()->hasBonusOfType(BonusType::UNDEAD) - || node->getBonusBearer()->hasBonusOfType(BonusType::NON_LIVING) - || node->getBonusBearer()->hasBonusOfType(BonusType::MECHANICAL))) + if (morale && node && node->unaffectedByMorale()) { text += LIBRARY->generaltexth->arraytxt[113]; //unaffected by morale component.value = 0; diff --git a/config/bonuses.json b/config/bonuses.json index 646f4089b..2c388eb23 100644 --- a/config/bonuses.json +++ b/config/bonuses.json @@ -98,14 +98,6 @@ } }, - "FEAR": - { - }, - - "FEARLESS": - { - }, - "FEROCITY": { }, @@ -123,6 +115,7 @@ "GARGOYLE": { + "creatureNature" : true, }, "GENERAL_DAMAGE_REDUCTION": @@ -178,6 +171,11 @@ "LIMITED_SHOOTING_RANGE": { }, + + "LIVING": + { + "creatureNature" : true + }, "MANA_CHANNELING": { @@ -194,6 +192,11 @@ "MAGIC_RESISTANCE": { }, + + "MECHANICAL": + { + "creatureNature" : true + }, "MIND_IMMUNITY": { @@ -231,6 +234,7 @@ "NON_LIVING": { + "creatureNature" : true }, "MECHANICAL": @@ -269,6 +273,11 @@ "REVENGE": { }, + + "SIEGE_WEAPON": + { + "creatureNature" : true + }, "SHOOTER": { @@ -348,6 +357,7 @@ "UNDEAD": { + "creatureNature" : true, }, "UNLIMITED_RETALIATIONS": diff --git a/docs/modders/Bonus/Bonus_Types.md b/docs/modders/Bonus/Bonus_Types.md index ea65dff17..71b998d65 100644 --- a/docs/modders/Bonus/Bonus_Types.md +++ b/docs/modders/Bonus/Bonus_Types.md @@ -411,33 +411,35 @@ Increases starting amount of shots that unit has in battle ## Creature abilities -## Static abilities and immunities +## Creature Natures ### LIVING -Affected unit is considered to be alive. Automatically granted to any unit that is not UNDEAD, NON_LIVING, MECHANICAL, GARGOYLE, or SIEGE_WEAPON. +Affected unit is considered to be alive. Automatically granted to any unit that does not have any other creature nature bonus Living units can be affected by TRANSMUTATION, LIFE_DRAIN, and SOUL_STEAL bonuses ### NON_LIVING -Affected unit is considered to not be alive and not affected by morale and certain spells +Creature nature bonus. Affected unit is considered to not be alive and not affected by morale and certain spells ### MECHANICAL -Affected unit is considered to not be alive and not affected by morale and certain spells but should be repairable from engineers (factory). +Creature nature bonus. Affected unit is considered to not be alive and not affected by morale and certain spells but should be repairable from engineers (factory). ### GARGOYLE -Affected unit is considered to be a gargoyle and not affected by certain spells +Creature nature bonus. Affected unit is considered to be a gargoyle and not affected by certain spells ### UNDEAD -Affected unit is considered to be undead, which makes it immune to many effects, and also reduce morale of allied living units. +Creature nature bonus. Affected unit is considered to be undead, which makes it immune to many effects, and also reduce morale of allied living units. ### SIEGE_WEAPON -Affected unit is considered to be a siege machine and can not be raised, healed, have morale or move. All War Machines should have this bonus. +Creature nature bonus. Affected unit is considered to be a siege machine and can not be raised, healed, have morale or move. All War Machines should have this bonus. + +## Static abilities and immunities ### DRAGON_NATURE diff --git a/include/vcmi/FactionMember.h b/include/vcmi/FactionMember.h index 28d86d355..d6e329bee 100644 --- a/include/vcmi/FactionMember.h +++ b/include/vcmi/FactionMember.h @@ -61,6 +61,8 @@ public: */ int moraleValAndBonusList(std::shared_ptr & bonusList) const; int luckValAndBonusList(std::shared_ptr & bonusList) const; + + bool unaffectedByMorale() const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/BasicTypes.cpp b/lib/BasicTypes.cpp index 41e0ec0dd..ee88d3312 100644 --- a/lib/BasicTypes.cpp +++ b/lib/BasicTypes.cpp @@ -69,6 +69,15 @@ int AFactionMember::getMaxDamage(bool ranged) const return getBonusBearer()->valOfBonuses(selector, cachingStr); } +bool AFactionMember::unaffectedByMorale() const +{ + static const auto unaffectedByMoraleSelector = Selector::type()(BonusType::NON_LIVING).Or(Selector::type()(BonusType::MECHANICAL)).Or(Selector::type()(BonusType::UNDEAD)) + .Or(Selector::type()(BonusType::SIEGE_WEAPON)).Or(Selector::type()(BonusType::NO_MORALE)); + + static const std::string cachingStrUn = "AFactionMember::unaffectedByMoraleSelector"; + return getBonusBearer()->hasBonus(unaffectedByMoraleSelector, cachingStrUn); +} + int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const { int32_t maxGoodMorale = LIBRARY->engineSettings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_CHANCE).size(); @@ -81,12 +90,7 @@ int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const return maxGoodMorale; } - static const auto unaffectedByMoraleSelector = Selector::type()(BonusType::NON_LIVING).Or(Selector::type()(BonusType::MECHANICAL)).Or(Selector::type()(BonusType::UNDEAD)) - .Or(Selector::type()(BonusType::SIEGE_WEAPON)).Or(Selector::type()(BonusType::NO_MORALE)); - - static const std::string cachingStrUn = "AFactionMember::unaffectedByMoraleSelector"; - auto unaffected = getBonusBearer()->hasBonus(unaffectedByMoraleSelector, cachingStrUn); - if(unaffected) + if(unaffectedByMorale()) { if(bonusList && !bonusList->empty()) bonusList = std::make_shared(); diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index d727295f1..ec7574d98 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -122,12 +122,22 @@ std::vector CBonusTypeHandler::loadLegacyData() void CBonusTypeHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - BonusType bonus = stringToBonus(name); - - CBonusType & bt = bonusTypes[vstd::to_underlying(bonus)]; - - loadItem(data, bt, name); - logBonus->trace("Loaded bonus type %s", name); + if (vstd::contains(bonusNames, name)) + { + //h3 bonus + BonusType bonus = stringToBonus(name); + CBonusType & bt = bonusTypes[vstd::to_underlying(bonus)]; + loadItem(data, bt, name); + logBonus->trace("Loaded bonus type %s", name); + } + else + { + // new bonus + bonusNames.push_back(name); + bonusTypes.emplace_back(); + loadItem(data, bonusTypes.back(), name); + logBonus->trace("New bonus type %s", name); + } } void CBonusTypeHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) @@ -139,6 +149,7 @@ void CBonusTypeHandler::loadItem(const JsonNode & source, CBonusType & dest, con { dest.identifier = name; dest.hidden = source["hidden"].Bool(); //Null -> false + dest.creatureNature = source["creatureNature"].Bool(); //Null -> false if (!dest.hidden) LIBRARY->generaltexth->registerString( "vcmi", dest.getDescriptionTextID(), source["description"]); @@ -197,6 +208,11 @@ const std::string CBonusTypeHandler::bonusToString(BonusType bonus) const return bonusNames.at(static_cast(bonus)); } +bool CBonusTypeHandler::isCreatureNatureBonus(BonusType bonus) const +{ + return bonusTypes.at(static_cast(bonus)).creatureNature; +} + std::vector CBonusTypeHandler::getAllObjets() const { std::vector ret; diff --git a/lib/CBonusTypeHandler.h b/lib/CBonusTypeHandler.h index affb8817a..05176d721 100644 --- a/lib/CBonusTypeHandler.h +++ b/lib/CBonusTypeHandler.h @@ -36,6 +36,7 @@ private: std::map valueDescriptions; std::string identifier; + bool creatureNature; bool hidden; }; @@ -56,6 +57,8 @@ public: BonusType stringToBonus(const std::string & name) const; const std::string bonusToString(BonusType bonus) const; + bool isCreatureNatureBonus(BonusType bonus) const; + std::vector getAllObjets() const; private: void loadItem(const JsonNode & source, CBonusType & dest, const std::string & name) const; diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 42b94a12a..f4aa71d38 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -8,6 +8,7 @@ * */ #include "StdInc.h" +#include "CBonusTypeHandler.h" #include "CCreatureHandler.h" #include "ResourceSet.h" @@ -900,6 +901,7 @@ void CCreatureHandler::loadJsonAnimation(CCreature * cre, const JsonNode & graph void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & config) const { + bool hasCreatureNatureBonus = false; creature->animDefName = AnimationPath::fromJson(config["graphics"]["animation"]); //FIXME: MOD COMPATIBILITY @@ -913,36 +915,13 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c b->source = BonusSource::CREATURE_ABILITY; b->sid = BonusSourceID(creature->getId()); b->duration = BonusDuration::PERMANENT; - creature->addNewBonus(b); - } - } - } - else - { - for(const JsonNode &ability : config["abilities"].Vector()) - { - if(ability.getType() == JsonNode::JsonType::DATA_VECTOR) - { - logMod->error("Ignored outdated creature ability format in %s", creature->getJsonKey()); - } - else - { - auto b = JsonUtils::parseBonus(ability); - b->source = BonusSource::CREATURE_ABILITY; - b->sid = BonusSourceID(creature->getId()); - b->duration = BonusDuration::PERMANENT; + hasCreatureNatureBonus |= LIBRARY->bth->isCreatureNatureBonus(b->type); creature->addNewBonus(b); } } } - static const CSelector livingSelector = Selector::type()(BonusType::UNDEAD) - .Or(Selector::type()(BonusType::NON_LIVING)) - .Or(Selector::type()(BonusType::MECHANICAL)) - .Or(Selector::type()(BonusType::GARGOYLE)) - .Or(Selector::type()(BonusType::SIEGE_WEAPON)); - - if (!creature->hasBonus(livingSelector)) + if (!hasCreatureNatureBonus) creature->addNewBonus(std::make_shared(BonusDuration::PERMANENT, BonusType::LIVING, BonusSource::CREATURE_ABILITY, 0, BonusSourceID(creature->getId()))); LIBRARY->identifiers()->requestIdentifier("faction", config["faction"], [=](si32 faction) diff --git a/lib/bonuses/BonusEnum.h b/lib/bonuses/BonusEnum.h index caaf512c5..5ff2513b8 100644 --- a/lib/bonuses/BonusEnum.h +++ b/lib/bonuses/BonusEnum.h @@ -148,7 +148,7 @@ class JsonNode; BONUS_NAME(DESTRUCTION) /*kills extra units after hit, subtype = 0 - kill percentage of units, 1 - kill amount, val = chance in percent to trigger, additional info - amount/percentage to kill*/ \ BONUS_NAME(SPECIAL_CRYSTAL_GENERATION) /*crystal dragon crystal generation*/ \ BONUS_NAME(NO_SPELLCAST_BY_DEFAULT) /*spellcast will not be default attack option for this creature*/ \ - BONUS_NAME(GARGOYLE) /* gargoyle is special than NON_LIVING, cannot be rised or healed */ \ + BONUS_NAME(DRACONIC_SKELETON) /* for skeleton transformer */ \ BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty spell like Aenin has, increased effect of spell, additionalInfo = value to add*/\ BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty spell like Melody has, constant spell effect (i.e. 3 luck), additionalInfo = value to fix.*/\ BONUS_NAME(THIEVES_GUILD_ACCESS) \ diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index 286a9704c..56edface1 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -819,8 +819,6 @@ public: BONE_DRAGON = 68, // for Skeleton Transformer TROGLODYTES = 70, // for Abandoned Mine MEDUSA = 76, // for Siege UI workaround - HYDRA = 110, // for Skeleton Transformer - CHAOS_HYDRA = 111, // for Skeleton Transformer AIR_ELEMENTAL = 112, // for tests FIRE_ELEMENTAL = 114, // for tests PSYCHIC_ELEMENTAL = 120, // for hardcoded ability diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c8f600859..c53254e43 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3239,11 +3239,11 @@ bool CGameHandler::transformInUndead(const IMarket *market, const CGHeroInstance //resulting creature - bone dragons or skeletons CreatureID resCreature = CreatureID::SKELETON; - if ((s.hasBonusOfType(BonusType::DRAGON_NATURE) - && !(s.hasBonusOfType(BonusType::UNDEAD))) - || (s.getCreatureID() == CreatureID::HYDRA) - || (s.getCreatureID() == CreatureID::CHAOS_HYDRA)) - resCreature = CreatureID::BONE_DRAGON; + if (!s.hasBonusOfType(BonusType::UNDEAD)) + { + if (s.hasBonusOfType(BonusType::DRAGON_NATURE) || s.hasBonusOfType(BonusType::DRACONIC_SKELETON)) + resCreature = CreatureID::BONE_DRAGON; + } changeStackType(StackLocation(army->id, slot), resCreature.toCreature()); return true; } diff --git a/server/battles/BattleActionProcessor.h b/server/battles/BattleActionProcessor.h index 2d4a8c80b..b819da051 100644 --- a/server/battles/BattleActionProcessor.h +++ b/server/battles/BattleActionProcessor.h @@ -19,7 +19,7 @@ class CBattleInfoCallback; class BattleHex; class CStack; class PlayerColor; -enum class BonusType : uint8_t; +enum class BonusType : uint16_t; namespace battle {