Browse Source

Implemeted HATES_TRAIT bonus, similar to HATE, but targets any unit with
specific bonus

Ivan Savenko 1 month ago
parent
commit
2191e51d48

+ 18 - 1
docs/modders/Bonus/Bonus_Types.md

@@ -543,9 +543,26 @@ When affected unit is attacked from behind, it will receive more damage when att
 
 Affected unit will deal more damage when attacking specific creature
 
-- subtype - identifier of hated creature, ie. "creature.genie"
+- subtype - identifier of hated creature, ie. `genie`
 - val - additional damage, percentage
 
+### HATES_TRAIT
+
+Affected unit will deal more damage when attacking unit that has specific bonus
+
+- subtype - identifier of hated bonus, ie. `UNDEAD`
+- val - additional damage, percentage
+
+Example: Unit deals 50% more damage to any target that has UNDEAD bonus
+
+```json
+	"hatesUndead" : {
+		"type" : "HATES_TRAIT",
+		"subtype" : "UNDEAD",
+		"val" : 50
+	}
+```
+
 ### SPELL_LIKE_ATTACK
 
 Affected unit ranged attack will use animation and range of specified spell (Magog, Lich)

+ 16 - 2
lib/battle/DamageCalculator.cpp

@@ -285,13 +285,26 @@ double DamageCalculator::getAttackFromBackFactor() const
 	return 0;
 }
 
-double DamageCalculator::getAttackHateFactor() const
+double DamageCalculator::getAttackHateCreatureFactor() const
 {
 	//assume that unit have only few HATE features and cache them all
 	auto allHateEffects = info.attacker->getBonusesOfType(BonusType::HATE);
 	return allHateEffects->valOfBonuses(Selector::subtype()(BonusSubtypeID(info.defender->creatureId()))) / 100.0;
 }
 
+double DamageCalculator::getAttackHateTraitFactor() const
+{
+	//assume that unit have only few HATE features and cache them all
+	auto allHateEffects = info.attacker->getBonusesOfType(BonusType::HATES_TRAIT);
+
+	auto selector = [this](const Bonus* hateBonus) -> bool
+	{
+		return info.defender->hasBonusOfType(hateBonus->subtype.as<BonusTypeID>().toEnum());
+	};
+
+	return allHateEffects->valOfBonuses(selector) / 100.0;
+}
+
 double DamageCalculator::getAttackRevengeFactor() const
 {
 	if(info.attacker->hasBonusOfType(BonusType::REVENGE)) //HotA Haspid ability
@@ -475,7 +488,8 @@ std::vector<double> DamageCalculator::getAttackFactors() const
 		getAttackFromBackFactor(),
 		getAttackDeathBlowFactor(),
 		getAttackDoubleDamageFactor(),
-		getAttackHateFactor(),
+		getAttackHateCreatureFactor(),
+		getAttackHateTraitFactor(),
 		getAttackRevengeFactor()
 	};
 }

+ 2 - 1
lib/battle/DamageCalculator.h

@@ -50,7 +50,8 @@ class DLL_LINKAGE DamageCalculator
 	double getAttackJoustingFactor() const;
 	double getAttackDeathBlowFactor() const;
 	double getAttackDoubleDamageFactor() const;
-	double getAttackHateFactor() const;
+	double getAttackHateCreatureFactor() const;
+	double getAttackHateTraitFactor() const;
 	double getAttackRevengeFactor() const;
 	double getAttackFromBackFactor() const;
 

+ 17 - 0
lib/bonuses/BonusCustomTypes.cpp

@@ -10,6 +10,8 @@
 
 #include "StdInc.h"
 #include "BonusCustomTypes.h"
+#include "CBonusTypeHandler.h"
+#include "GameLibrary.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -75,4 +77,19 @@ std::string BonusCustomSource::encode(const si32 index)
 	return std::to_string(index);
 }
 
+std::string BonusTypeID::encode(int32_t index)
+{
+	if (index == static_cast<int32_t>(BonusType::NONE))
+		return "";
+	return LIBRARY->bth->bonusToString(static_cast<BonusType>(index));
+}
+
+si32 BonusTypeID::decode(const std::string & identifier)
+{
+	if (identifier.empty())
+		return RiverId::NO_RIVER.getNum();
+
+	return resolveIdentifier("bonus", identifier);
+}
+
 VCMI_LIB_NAMESPACE_END

+ 22 - 1
lib/bonuses/BonusCustomTypes.h

@@ -11,6 +11,7 @@
 
 #include "../constants/EntityIdentifiers.h"
 #include "../constants/VariantIdentifier.h"
+#include "BonusEnum.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -77,7 +78,27 @@ public:
 	static BonusCustomSubtype creatureLevel(int level);
 };
 
-using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool>;
+class DLL_LINKAGE BonusTypeID : public EntityIdentifier<BonusTypeID>
+{
+public:
+	using EntityIdentifier<BonusTypeID>::EntityIdentifier;
+	using EnumType = BonusType;
+
+	static std::string encode(int32_t index);
+	static si32 decode(const std::string & identifier);
+
+	constexpr EnumType toEnum() const
+	{
+		return static_cast<EnumType>(EntityIdentifier::num);
+	}
+
+	constexpr BonusTypeID(const EnumType & enumValue)
+	{
+		EntityIdentifier::num = static_cast<int32_t>(enumValue);
+	}
+};
+
+using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool, BonusTypeID>;
 using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, ArtifactInstanceID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField>;
 
 VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/bonuses/BonusEnum.h

@@ -194,6 +194,7 @@ class JsonNode;
 	BONUS_NAME(TRANSMUTATION_IMMUNITY) /*blocks TRANSMUTATION bonus*/\
 	BONUS_NAME(COMBAT_MANA_BONUS) /* Additional mana per combat */ \
 	BONUS_NAME(SPECIFIC_SPELL_RANGE) /* value used for allowed spell range, subtype - spell id */\
+	BONUS_NAME(HATES_TRAIT) /* affected unit deals additional damage to units with specific bonus. subtype - bonus, val - damage bonus percent */ \
 	/* end of list */
 
 

+ 8 - 0
lib/json/JsonBonus.cpp

@@ -91,6 +91,14 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso
 			});
 			break;
 		}
+		case BonusType::HATES_TRAIT:
+		{
+			LIBRARY->identifiers()->requestIdentifier( "bonus", node, [&subtype](int32_t identifier)
+			{
+				subtype = BonusType(identifier);
+			});
+			break;
+		}
 		case BonusType::NO_TERRAIN_PENALTY:
 		{
 			LIBRARY->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier)