Browse Source

Support for loading custom bonuses, slightly less hardcoded Skeleton
Transformer

Ivan Savenko 6 months ago
parent
commit
a305ed28bb

+ 1 - 3
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;

+ 18 - 8
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":

+ 9 - 7
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
 

+ 2 - 0
include/vcmi/FactionMember.h

@@ -61,6 +61,8 @@ public:
 	*/
 	int moraleValAndBonusList(std::shared_ptr<const BonusList> & bonusList) const;
 	int luckValAndBonusList(std::shared_ptr<const BonusList> & bonusList) const;
+
+	bool unaffectedByMorale() const;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 10 - 6
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<const BonusList>();

+ 22 - 6
lib/CBonusTypeHandler.cpp

@@ -122,12 +122,22 @@ std::vector<JsonNode> 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<int>(bonus));
 }
 
+bool CBonusTypeHandler::isCreatureNatureBonus(BonusType bonus) const
+{
+	return bonusTypes.at(static_cast<int>(bonus)).creatureNature;
+}
+
 std::vector<BonusType> CBonusTypeHandler::getAllObjets() const
 {
 	std::vector<BonusType> ret;

+ 3 - 0
lib/CBonusTypeHandler.h

@@ -36,6 +36,7 @@ private:
 	std::map<int, std::string> 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<BonusType> getAllObjets() const;
 private:
 	void loadItem(const JsonNode & source, CBonusType & dest, const std::string & name) const;

+ 4 - 25
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;
+				hasCreatureNatureBonus |= LIBRARY->bth->isCreatureNatureBonus(b->type);
 				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;
-				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<Bonus>(BonusDuration::PERMANENT, BonusType::LIVING, BonusSource::CREATURE_ABILITY, 0, BonusSourceID(creature->getId())));
 
 	LIBRARY->identifiers()->requestIdentifier("faction", config["faction"], [=](si32 faction)

+ 1 - 1
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) \

+ 0 - 2
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

+ 5 - 5
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;
 }

+ 1 - 1
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
 {