1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-05 00:49:09 +02:00

Support for loading custom bonuses, slightly less hardcoded Skeleton

Transformer
This commit is contained in:
Ivan Savenko
2025-06-09 12:53:44 +03:00
parent 51832c4fb9
commit a305ed28bb
12 changed files with 76 additions and 64 deletions

View File

@ -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;

View File

@ -98,14 +98,6 @@
}
},
"FEAR":
{
},
"FEARLESS":
{
},
"FEROCITY":
{
},
@ -123,6 +115,7 @@
"GARGOYLE":
{
"creatureNature" : true,
},
"GENERAL_DAMAGE_REDUCTION":
@ -179,6 +172,11 @@
{
},
"LIVING":
{
"creatureNature" : true
},
"MANA_CHANNELING":
{
},
@ -195,6 +193,11 @@
{
},
"MECHANICAL":
{
"creatureNature" : true
},
"MIND_IMMUNITY":
{
},
@ -231,6 +234,7 @@
"NON_LIVING":
{
"creatureNature" : true
},
"MECHANICAL":
@ -270,6 +274,11 @@
{
},
"SIEGE_WEAPON":
{
"creatureNature" : true
},
"SHOOTER":
{
},
@ -348,6 +357,7 @@
"UNDEAD":
{
"creatureNature" : true,
},
"UNLIMITED_RETALIATIONS":

View File

@ -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

View File

@ -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

View File

@ -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>();

View File

@ -122,13 +122,23 @@ std::vector<JsonNode> CBonusTypeHandler::loadLegacyData()
void CBonusTypeHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
{
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;

View File

@ -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;

View File

@ -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<Bonus>(BonusDuration::PERMANENT, BonusType::LIVING, BonusSource::CREATURE_ABILITY, 0, BonusSourceID(creature->getId())));
LIBRARY->identifiers()->requestIdentifier("faction", config["faction"], [=](si32 faction)

View File

@ -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) \

View File

@ -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

View File

@ -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))
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;
}

View File

@ -19,7 +19,7 @@ class CBattleInfoCallback;
class BattleHex;
class CStack;
class PlayerColor;
enum class BonusType : uint8_t;
enum class BonusType : uint16_t;
namespace battle
{