1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Extend Bonus.addInfo to integer vector (#427)

* changed Bonus::additionalInfo to integer vector

* fixed deserialization for old savegames

* removed newline from JsonNode::toJson()

* updated bonus schema; SPELL_AFTER_ATTACK and SPELL_BEFORE_ATTACK use new addInfo format

* removed unnecessary init in Bonus constructor
This commit is contained in:
Henning Koehler 2018-03-12 18:20:18 +13:00 committed by Alexander Shishkin
parent 83c6ffbda0
commit 7f76648a7c
11 changed files with 199 additions and 41 deletions

View File

@ -183,7 +183,7 @@
"type" : "SPELL_AFTER_ATTACK",
"subtype" : "spell.stoneGaze",
"val" : 20,
"addInfo" : 2000 // FIXME: replace with range field?
"addInfo" : [0,2]
}
},
"upgrades": ["medusaQueen"],
@ -217,7 +217,7 @@
"type" : "SPELL_AFTER_ATTACK",
"subtype" : "spell.stoneGaze",
"val" : 20,
"addInfo" : 2000 // FIXME: replace with range?
"addInfo" : [0,2]
}
},
"graphics" :

View File

@ -10,7 +10,16 @@
"addInfo": {
"anyOf" : [
{ "type" : "string" },
{ "type" : "number" }
{ "type" : "number" },
{
"type" : "array",
"items" : {
"anyof" : [
{ "type" : "string" },
{ "type" : "number" }
]
}
}
],
"description": "addInfo"
},

View File

@ -1890,7 +1890,7 @@ UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack)
TBonusListPtr lista = h->getBonuses(Selector::typeSubtype(Bonus::SPECIAL_UPGRADE, base->idNumber));
for(const std::shared_ptr<Bonus> it : *lista)
{
auto nid = CreatureID(it->additionalInfo);
auto nid = CreatureID(it->additionalInfo[0]);
if (nid != base->idNumber) //in very specific case the upgrade is available by default (?)
{
ret.newID.push_back(nid);

View File

@ -150,6 +150,59 @@ const BonusList * CBonusProxy::operator->() const
return get().get();
}
CAddInfo::CAddInfo()
{
}
CAddInfo::CAddInfo(si32 value)
{
if(value != CAddInfo::NONE)
push_back(value);
}
bool CAddInfo::operator==(si32 value) const
{
switch(size())
{
case 0:
return value == CAddInfo::NONE;
case 1:
return operator[](0) == value;
default:
return false;
}
}
bool CAddInfo::operator!=(si32 value) const
{
return !operator==(value);
}
si32 CAddInfo::operator[](size_type pos) const
{
return pos < size() ? vector::operator[](pos) : CAddInfo::NONE;
}
std::string CAddInfo::toString() const
{
return toJsonNode().toJson(true);
}
JsonNode CAddInfo::toJsonNode() const
{
if(size() < 2)
{
return JsonUtils::intNode(operator[](0));
}
else
{
JsonNode node(JsonNode::JsonType::DATA_VECTOR);
for(si32 value : *this)
node.Vector().push_back(JsonUtils::intNode(value));
return node;
}
}
std::atomic<int32_t> CBonusSystemNode::treeChanged(1);
const bool CBonusSystemNode::cachingEnabled = true;
@ -1189,14 +1242,24 @@ JsonNode subtypeToJson(Bonus::BonusType type, int subtype)
}
}
JsonNode additionalInfoToJson(Bonus::BonusType type, int addInfo)
JsonNode additionalInfoToJson(Bonus::BonusType type, CAddInfo addInfo)
{
switch(type)
{
case Bonus::SPECIAL_UPGRADE:
return JsonUtils::stringNode("creature." + CreatureID::encode(addInfo));
return JsonUtils::stringNode("creature." + CreatureID::encode(addInfo[0]));
default:
return JsonUtils::intNode(addInfo);
if(addInfo.size() <= 1)
{
return JsonUtils::intNode(addInfo[0]);
}
else
{
JsonNode vecNode(JsonNode::JsonType::DATA_VECTOR);
for(si32 value : addInfo)
vecNode.Vector().push_back(JsonUtils::intNode(value));
return vecNode;
}
}
}
@ -1207,7 +1270,7 @@ JsonNode Bonus::toJsonNode() const
root["type"].String() = vstd::findKey(bonusNameMap, type);
if(subtype != -1)
root["subtype"] = subtypeToJson(type, subtype);
if(additionalInfo != -1)
if(additionalInfo != CAddInfo::NONE)
root["addInfo"] = additionalInfoToJson(type, additionalInfo);
if(val != 0)
root["val"].Integer() = val;
@ -1235,7 +1298,7 @@ std::string Bonus::nameForBonus() const
case Bonus::SPECIAL_PECULIAR_ENCHANT:
return (*VLC->spellh)[SpellID::ESpellID(subtype)]->identifier;
case Bonus::SPECIAL_UPGRADE:
return CreatureID::encode(subtype) + "2" + CreatureID::encode(additionalInfo);
return CreatureID::encode(subtype) + "2" + CreatureID::encode(additionalInfo[0]);
case Bonus::GENERATE_RESOURCE:
return GameConstants::RESOURCE_NAMES[subtype];
case Bonus::STACKS_SPEED:
@ -1248,7 +1311,6 @@ std::string Bonus::nameForBonus() const
Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype)
: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), description(Desc)
{
additionalInfo = -1;
turnsRemain = 0;
valType = ADDITIVE_VALUE;
effectRange = NO_LIMIT;
@ -1258,7 +1320,6 @@ Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::
Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, ValueType ValType)
: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), valType(ValType)
{
additionalInfo = -1;
turnsRemain = 0;
effectRange = NO_LIMIT;
}
@ -1269,7 +1330,6 @@ Bonus::Bonus()
turnsRemain = 0;
type = NONE;
subtype = -1;
additionalInfo = -1;
valType = ADDITIVE_VALUE;
effectRange = NO_LIMIT;
@ -1288,7 +1348,7 @@ namespace Selector
{
DLL_LINKAGE CSelectFieldEqual<Bonus::BonusType> type(&Bonus::type);
DLL_LINKAGE CSelectFieldEqual<TBonusSubtype> subtype(&Bonus::subtype);
DLL_LINKAGE CSelectFieldEqual<si32> info(&Bonus::additionalInfo);
DLL_LINKAGE CSelectFieldEqual<CAddInfo> info(&Bonus::additionalInfo);
DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> sourceType(&Bonus::source);
DLL_LINKAGE CSelectFieldEqual<Bonus::LimitEffect> effectRange(&Bonus::effectRange);
DLL_LINKAGE CWillLastTurns turns;
@ -1299,11 +1359,11 @@ namespace Selector
return type(Type).And(subtype(Subtype));
}
CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, si32 info)
CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, CAddInfo info)
{
return CSelectFieldEqual<Bonus::BonusType>(&Bonus::type)(type)
.And(CSelectFieldEqual<TBonusSubtype>(&Bonus::subtype)(subtype))
.And(CSelectFieldEqual<si32>(&Bonus::additionalInfo)(info));
.And(CSelectFieldEqual<CAddInfo>(&Bonus::additionalInfo)(info));
}
CSelector DLL_LINKAGE source(Bonus::BonusSource source, ui32 sourceID)
@ -1403,7 +1463,8 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus)
printField(duration);
printField(source);
printField(sid);
printField(additionalInfo);
if(bonus.additionalInfo != CAddInfo::NONE)
out << "\taddInfo: " << bonus.additionalInfo.toString() << "\n";
printField(turnsRemain);
printField(valType);
printField(effectRange);

View File

@ -87,6 +87,24 @@ private:
mutable TBonusListPtr data;
};
class DLL_LINKAGE CAddInfo : public std::vector<si32>
{
public:
static const si32 NONE = -1;
CAddInfo();
CAddInfo(si32 value);
bool operator==(si32 value) const;
bool operator!=(si32 value) const;
using std::vector<si32>::operator[];
si32 operator[](size_type pos) const;
std::string toString() const;
JsonNode toJsonNode() const;
};
#define BONUS_TREE_DESERIALIZATION_FIX if(!h.saving && h.smartPointerSerialization) deserializationFix();
#define BONUS_LIST \
@ -147,8 +165,8 @@ private:
BONUS_NAME(MAGIC_RESISTANCE) /*in % (value)*/ \
BONUS_NAME(CHANGES_SPELL_COST_FOR_ALLY) /*in mana points (value) , eg. mage*/ \
BONUS_NAME(CHANGES_SPELL_COST_FOR_ENEMY) /*in mana points (value) , eg. pegasus */ \
BONUS_NAME(SPELL_AFTER_ATTACK) /* subtype - spell id, value - chance %, additional info % 1000 - level, (additional info)/1000 -> [0 - all attacks, 1 - shot only, 2 - melee only*/ \
BONUS_NAME(SPELL_BEFORE_ATTACK) /* subtype - spell id, value - chance %, additional info % 1000 - level, (additional info)/1000 -> [0 - all attacks, 1 - shot only, 2 - melee only*/ \
BONUS_NAME(SPELL_AFTER_ATTACK) /* subtype - spell id, value - chance %, addInfo[0] - level, addInfo[1] -> [0 - all attacks, 1 - shot only, 2 - melee only] */ \
BONUS_NAME(SPELL_BEFORE_ATTACK) /* subtype - spell id, value - chance %, addInfo[0] - level, addInfo[1] -> [0 - all attacks, 1 - shot only, 2 - melee only] */ \
BONUS_NAME(SPELL_RESISTANCE_AURA) /*eg. unicorns, value - resistance bonus in % for adjacent creatures*/ \
BONUS_NAME(LEVEL_SPELL_IMMUNITY) /*creature is immune to all spell with level below or equal to value of this bonus */ \
BONUS_NAME(BLOCK_MAGIC_ABOVE) /*blocks casting spells of the level > value */ \
@ -338,7 +356,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
ui32 sid; //source id: id of object/artifact/spell
ValueType valType;
si32 additionalInfo;
CAddInfo additionalInfo;
LimitEffect effectRange; //if not NO_LIMIT, bonus will be omitted by default
TLimiterPtr limiter;
@ -360,7 +378,15 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
h & val;
h & sid;
h & description;
h & additionalInfo;
if(version >= 783)
{
h & additionalInfo;
}
else
{
additionalInfo.resize(1, -1);
h & additionalInfo[0];
}
h & turnsRemain;
h & valType;
h & effectRange;
@ -980,14 +1006,14 @@ namespace Selector
{
extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusType> type;
extern DLL_LINKAGE CSelectFieldEqual<TBonusSubtype> subtype;
extern DLL_LINKAGE CSelectFieldEqual<si32> info;
extern DLL_LINKAGE CSelectFieldEqual<CAddInfo> info;
extern DLL_LINKAGE CSelectFieldEqual<Bonus::BonusSource> sourceType;
extern DLL_LINKAGE CSelectFieldEqual<Bonus::LimitEffect> effectRange;
extern DLL_LINKAGE CWillLastTurns turns;
extern DLL_LINKAGE CWillLastDays days;
CSelector DLL_LINKAGE typeSubtype(Bonus::BonusType Type, TBonusSubtype Subtype);
CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, si32 info);
CSelector DLL_LINKAGE typeSubtypeInfo(Bonus::BonusType type, TBonusSubtype subtype, CAddInfo info);
CSelector DLL_LINKAGE source(Bonus::BonusSource source, ui32 sourceID);
CSelector DLL_LINKAGE sourceTypeSel(Bonus::BonusSource source);
CSelector DLL_LINKAGE valueType(Bonus::ValueType valType);

View File

@ -418,7 +418,6 @@ std::string JsonNode::toJson(bool compact) const
std::ostringstream out;
JsonWriter writer(out, compact);
writer.writeNode(*this);
out << "\n";
return out.str();
}
@ -496,6 +495,57 @@ void JsonUtils::resolveIdentifier(si32 &var, const JsonNode &node, std::string n
}
}
void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node)
{
const JsonNode & value = node["addInfo"];
if (!value.isNull())
{
switch (value.getType())
{
case JsonNode::JsonType::DATA_INTEGER:
var = value.Integer();
break;
case JsonNode::JsonType::DATA_FLOAT:
var = value.Float();
break;
case JsonNode::JsonType::DATA_STRING:
VLC->modh->identifiers.requestIdentifier(value, [&](si32 identifier)
{
var = identifier;
});
break;
case JsonNode::JsonType::DATA_VECTOR:
{
const JsonVector & vec = value.Vector();
var.resize(vec.size());
for(int i = 0; i < vec.size(); i++)
{
switch(vec[i].getType())
{
case JsonNode::JsonType::DATA_INTEGER:
var[i] = vec[i].Integer();
break;
case JsonNode::JsonType::DATA_FLOAT:
var[i] = vec[i].Float();
break;
case JsonNode::JsonType::DATA_STRING:
VLC->modh->identifiers.requestIdentifier(vec[i], [&var,i](si32 identifier)
{
var[i] = identifier;
});
break;
default:
logMod->error("Error! Wrong identifier used for value of addInfo[%d].", i);
}
}
break;
}
default:
logMod->error("Error! Wrong identifier used for value of addInfo.");
}
}
}
void JsonUtils::resolveIdentifier(const JsonNode &node, si32 &var)
{
switch (node.getType())
@ -548,7 +598,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
if (!value->isNull())
b->valType = static_cast<Bonus::ValueType>(parseByMap(bonusValueMap, value, "value type "));
resolveIdentifier(b->additionalInfo, ability, "addInfo");
resolveAddInfo(b->additionalInfo, ability);
b->turnsRemain = ability["turns"].Float();
@ -698,7 +748,7 @@ void JsonUtils::unparseBonus( JsonNode &node, const std::shared_ptr<Bonus>& bonu
node["subtype"].Float() = bonus->subtype;
node["val"].Float() = bonus->val;
node["valueType"].String() = reverseMapFirst<std::string, Bonus::ValueType>(bonus->valType, bonusValueMap);
node["additionalInfo"].Float() = bonus->additionalInfo;
node["additionalInfo"] = bonus->additionalInfo.toJsonNode();
node["turns"].Float() = bonus->turnsRemain;
node["sourceID"].Float() = bonus->source;
node["description"].String() = bonus->description;

View File

@ -15,6 +15,7 @@ typedef std::vector <JsonNode> JsonVector;
struct Bonus;
class ResourceID;
class CAddInfo;
class DLL_LINKAGE JsonNode
{
@ -170,6 +171,7 @@ namespace JsonUtils
DLL_LINKAGE void unparseBonus (JsonNode &node, const std::shared_ptr<Bonus>& bonus);
DLL_LINKAGE void resolveIdentifier(si32 &var, const JsonNode &node, std::string name);
DLL_LINKAGE void resolveIdentifier(const JsonNode &node, si32 &var);
DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node);
/**
* @brief recursively merges source into dest, replacing identical fields

View File

@ -797,7 +797,7 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info)
TBonusListPtr blessEffects = attackerBonuses->getBonuses(selectorForcedMaxDamage, cachingStrForcedMaxDamage);
int curseBlessAdditiveModifier = blessEffects->totalValue() - curseEffects->totalValue();
double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo<std::shared_ptr<Bonus>>))->additionalInfo : 0;
double curseMultiplicativePenalty = curseEffects->size() ? (*std::max_element(curseEffects->begin(), curseEffects->end(), &Bonus::compareByAdditionalInfo<std::shared_ptr<Bonus>>))->additionalInfo[0] : 0;
if(curseMultiplicativePenalty) //curse handling (partial, the rest is below)
{
@ -1738,12 +1738,12 @@ SpellID CBattleInfoCallback::getRandomCastedSpell(CRandomGenerator & rand,const
int totalWeight = 0;
for(auto b : *bl)
{
totalWeight += std::max(b->additionalInfo, 1); //minimal chance to cast is 1
totalWeight += std::max(b->additionalInfo[0], 1); //minimal chance to cast is 1
}
int randomPos = rand.nextInt(totalWeight - 1);
for(auto b : *bl)
{
randomPos -= std::max(b->additionalInfo, 1);
randomPos -= std::max(b->additionalInfo[0], 1);
if(randomPos < 0)
{
return SpellID(b->subtype);

View File

@ -12,7 +12,7 @@
#include "../ConstTransitivePtr.h"
#include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 782;
const ui32 SERIALIZATION_VERSION = 783;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG";

View File

@ -181,7 +181,7 @@ void Timed::prepareEffects(SetStackEffect & sse, const Mechanics * m, const Effe
const auto tier = std::max(affected->creatureLevel(), 1); //don't divide by 0 for certain creatures (commanders, war machines)
if(bonus)
{
switch(bonus->additionalInfo)
switch(bonus->additionalInfo[0])
{
case 0: //normal
switch(tier)

View File

@ -4731,9 +4731,9 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
for (auto b : bl)
{
if(b->additionalInfo >= 0)
if(b->additionalInfo != CAddInfo::NONE)
{
const CStack * stack = gs->curB->battleGetStackByID(b->additionalInfo); //binding stack must be alive and adjacent
const CStack * stack = gs->curB->battleGetStackByID(b->additionalInfo[0]); //binding stack must be alive and adjacent
if(stack)
{
if(vstd::contains(adjacent, stack)) //binding stack is still present
@ -4840,7 +4840,7 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
{
cast = true;
int cooldown = bonus->additionalInfo;
int cooldown = bonus->additionalInfo[0];
BattleSetStackProperty ssp;
ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
ssp.absolute = false;
@ -5483,8 +5483,18 @@ void CGameHandler::attackCasting(bool ranged, Bonus::BonusType attackMode, const
TBonusListPtr spellsByType = attacker->getBonuses(Selector::typeSubtype(attackMode, spellID));
for(const std::shared_ptr<Bonus> sf : *spellsByType)
{
vstd::amax(spellLevel, sf->additionalInfo % 1000); //pick highest level
int meleeRanged = sf->additionalInfo / 1000;
int meleeRanged;
if(sf->additionalInfo.size() < 2)
{
// legacy format
vstd::amax(spellLevel, sf->additionalInfo[0] % 1000);
meleeRanged = sf->additionalInfo[0] / 1000;
}
else
{
vstd::amax(spellLevel, sf->additionalInfo[0]);
meleeRanged = sf->additionalInfo[1];
}
if (meleeRanged == 0 || (meleeRanged == 1 && ranged) || (meleeRanged == 2 && !ranged))
castMe = true;
}
@ -5569,7 +5579,7 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
for(const std::shared_ptr<Bonus> b : *acidBreath)
{
if(b->additionalInfo > getRandomGenerator().nextInt(99))
if(b->additionalInfo[0] > getRandomGenerator().nextInt(99))
acidDamage += b->val;
}
@ -5597,10 +5607,10 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
if(getRandomGenerator().getDoubleRange(0, 1)() > chanceToTrigger)
return;
int bonusAdditionalInfo = attacker->getBonus(Selector::type(Bonus::TRANSMUTATION))->additionalInfo;
int bonusAdditionalInfo = attacker->getBonus(Selector::type(Bonus::TRANSMUTATION))->additionalInfo[0];
if(defender->getCreature()->idNumber == bonusAdditionalInfo ||
(bonusAdditionalInfo == -1 && defender->getCreature()->idNumber == attacker->getCreature()->idNumber))
(bonusAdditionalInfo == CAddInfo::NONE && defender->getCreature()->idNumber == attacker->getCreature()->idNumber))
return;
battle::UnitInfo resurrectInfo;
@ -5609,7 +5619,7 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
resurrectInfo.position = defender->getPosition();
resurrectInfo.side = defender->unitSide();
if(bonusAdditionalInfo != -1)
if(bonusAdditionalInfo != CAddInfo::NONE)
resurrectInfo.type = CreatureID(bonusAdditionalInfo);
else
resurrectInfo.type = attacker->creatureId();
@ -5639,13 +5649,13 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker
if(attacker->hasBonusOfType(Bonus::DESTRUCTION, 0)) //killing by percentage
{
chanceToTrigger = attacker->valOfBonuses(Bonus::DESTRUCTION, 0) / 100.0f;
int percentageToDie = attacker->getBonus(Selector::type(Bonus::DESTRUCTION).And(Selector::subtype(0)))->additionalInfo;
int percentageToDie = attacker->getBonus(Selector::type(Bonus::DESTRUCTION).And(Selector::subtype(0)))->additionalInfo[0];
amountToDie = defender->getCount() * percentageToDie * 0.01f;
}
else if(attacker->hasBonusOfType(Bonus::DESTRUCTION, 1)) //killing by count
{
chanceToTrigger = attacker->valOfBonuses(Bonus::DESTRUCTION, 1) / 100.0f;
amountToDie = attacker->getBonus(Selector::type(Bonus::DESTRUCTION).And(Selector::subtype(1)))->additionalInfo;
amountToDie = attacker->getBonus(Selector::type(Bonus::DESTRUCTION).And(Selector::subtype(1)))->additionalInfo[0];
}
vstd::amin(chanceToTrigger, 1); //cap trigger chance at 100%