mirror of
https://github.com/vcmi/vcmi.git
synced 2025-06-21 00:19:29 +02:00
Make bonus stacking configurable + fix duplicate propagation/inheritance (#433)
Addresses several related problems: * Propagation / unpropagation of duplicate bonuses is inconsistent, causing bugs * Duplicate bonuses never stack, which is not always intended behaviour (e.g. multiple copies of resource generating artifacts) * Different bonuses always stack, which is not always intended behaviour (e.g. Angel + Archangel morale bonuses) This is addressed as follows: * Duplicate bonuses are never eliminated during propagation/inheritance. * Unpropagation eliminates only a single copy of duplicated bonus * Bonus receives a new field stacking that determines stacking behaviour: * * empty string = no stacking with duplicates (default) * * "ALWAYS" = stacks with duplicates & everything else * * some other value = no stacking with bonuses with same stacking value Also Morale/Luck window now hides non-stacking bonuses.
This commit is contained in:
committed by
ArseniyShestakov
parent
2b64bf29ed
commit
02b5a5e830
@ -794,15 +794,24 @@ void processCommand(const std::string &message)
|
|||||||
}
|
}
|
||||||
else if(cn == "bonuses")
|
else if(cn == "bonuses")
|
||||||
{
|
{
|
||||||
|
bool jsonFormat = (message == "bonuses json");
|
||||||
|
auto format = [jsonFormat](const BonusList & b) -> std::string
|
||||||
|
{
|
||||||
|
if(jsonFormat)
|
||||||
|
return b.toJsonNode().toJson(true);
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << b;
|
||||||
|
return ss.str();
|
||||||
|
};
|
||||||
std::cout << "Bonuses of " << adventureInt->selection->getObjectName() << std::endl
|
std::cout << "Bonuses of " << adventureInt->selection->getObjectName() << std::endl
|
||||||
<< adventureInt->selection->getBonusList() << std::endl;
|
<< format(adventureInt->selection->getBonusList()) << std::endl;
|
||||||
|
|
||||||
std::cout << "\nInherited bonuses:\n";
|
std::cout << "\nInherited bonuses:\n";
|
||||||
TCNodes parents;
|
TCNodes parents;
|
||||||
adventureInt->selection->getParents(parents);
|
adventureInt->selection->getParents(parents);
|
||||||
for(const CBonusSystemNode *parent : parents)
|
for(const CBonusSystemNode *parent : parents)
|
||||||
{
|
{
|
||||||
std::cout << "\nBonuses from " << typeid(*parent).name() << std::endl << parent->getBonusList() << std::endl;
|
std::cout << "\nBonuses from " << typeid(*parent).name() << std::endl << format(*parent->getAllBonuses(Selector::all, Selector::all)) << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(cn == "not dialog")
|
else if(cn == "not dialog")
|
||||||
|
@ -1549,7 +1549,8 @@
|
|||||||
"subtype" : "resource.crystal",
|
"subtype" : "resource.crystal",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 1,
|
"val" : 1,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 109,
|
"index" : 109,
|
||||||
@ -1562,7 +1563,8 @@
|
|||||||
"subtype" : "resource.gems",
|
"subtype" : "resource.gems",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 1,
|
"val" : 1,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 110,
|
"index" : 110,
|
||||||
@ -1575,7 +1577,8 @@
|
|||||||
"subtype" : "resource.mercury",
|
"subtype" : "resource.mercury",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 1,
|
"val" : 1,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 111,
|
"index" : 111,
|
||||||
@ -1588,7 +1591,8 @@
|
|||||||
"subtype" : "resource.ore",
|
"subtype" : "resource.ore",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 1,
|
"val" : 1,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 112,
|
"index" : 112,
|
||||||
@ -1601,7 +1605,8 @@
|
|||||||
"subtype" : "resource.sulfur",
|
"subtype" : "resource.sulfur",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 1,
|
"val" : 1,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 113,
|
"index" : 113,
|
||||||
@ -1614,7 +1619,8 @@
|
|||||||
"subtype" : "resource.wood",
|
"subtype" : "resource.wood",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 1,
|
"val" : 1,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 114,
|
"index" : 114,
|
||||||
@ -1627,7 +1633,8 @@
|
|||||||
"subtype" : "resource.gold",
|
"subtype" : "resource.gold",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 1000,
|
"val" : 1000,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 115,
|
"index" : 115,
|
||||||
@ -1640,7 +1647,8 @@
|
|||||||
"subtype" : "resource.gold",
|
"subtype" : "resource.gold",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 750,
|
"val" : 750,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 116,
|
"index" : 116,
|
||||||
@ -1653,7 +1661,8 @@
|
|||||||
"subtype" : "resource.gold",
|
"subtype" : "resource.gold",
|
||||||
"type" : "GENERATE_RESOURCE",
|
"type" : "GENERATE_RESOURCE",
|
||||||
"val" : 500,
|
"val" : 500,
|
||||||
"valueType" : "BASE_NUMBER"
|
"valueType" : "BASE_NUMBER",
|
||||||
|
"stacking" : "ALWAYS"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index" : 117,
|
"index" : 117,
|
||||||
|
@ -306,6 +306,10 @@
|
|||||||
"type" : "HATE",
|
"type" : "HATE",
|
||||||
"subtype" : "creature.archDevil",
|
"subtype" : "creature.archDevil",
|
||||||
"val" : 50
|
"val" : 50
|
||||||
|
},
|
||||||
|
"const_raises_morale" :
|
||||||
|
{
|
||||||
|
"stacking" : "Angels"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"upgrades": ["archangel"],
|
"upgrades": ["archangel"],
|
||||||
@ -357,6 +361,10 @@
|
|||||||
"type" : "HATE",
|
"type" : "HATE",
|
||||||
"subtype" : "creature.archDevil",
|
"subtype" : "creature.archDevil",
|
||||||
"val" : 50
|
"val" : 50
|
||||||
|
},
|
||||||
|
"const_raises_morale" :
|
||||||
|
{
|
||||||
|
"stacking" : "Angels"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graphics" :
|
"graphics" :
|
||||||
|
@ -361,7 +361,8 @@
|
|||||||
{
|
{
|
||||||
"type" : "LUCK",
|
"type" : "LUCK",
|
||||||
"effectRange" : "ONLY_ENEMY_ARMY",
|
"effectRange" : "ONLY_ENEMY_ARMY",
|
||||||
"val" : -1
|
"val" : -1,
|
||||||
|
"stacking" : "Devils"
|
||||||
},
|
},
|
||||||
"blockRetaliation" :
|
"blockRetaliation" :
|
||||||
{
|
{
|
||||||
@ -412,7 +413,8 @@
|
|||||||
{
|
{
|
||||||
"type" : "LUCK",
|
"type" : "LUCK",
|
||||||
"effectRange" : "ONLY_ENEMY_ARMY",
|
"effectRange" : "ONLY_ENEMY_ARMY",
|
||||||
"val" : -1
|
"val" : -1,
|
||||||
|
"stacking" : "Devils"
|
||||||
},
|
},
|
||||||
"blockRetaliation" :
|
"blockRetaliation" :
|
||||||
{
|
{
|
||||||
|
@ -338,6 +338,10 @@
|
|||||||
"dragon" :
|
"dragon" :
|
||||||
{
|
{
|
||||||
"type" : "DRAGON_NATURE"
|
"type" : "DRAGON_NATURE"
|
||||||
|
},
|
||||||
|
"const_lowers_morale" :
|
||||||
|
{
|
||||||
|
"stacking" : "Undead Dragons"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"upgrades": ["ghostDragon"],
|
"upgrades": ["ghostDragon"],
|
||||||
@ -365,6 +369,10 @@
|
|||||||
{
|
{
|
||||||
"type" : "DRAGON_NATURE"
|
"type" : "DRAGON_NATURE"
|
||||||
},
|
},
|
||||||
|
"const_lowers_morale" :
|
||||||
|
{
|
||||||
|
"stacking" : "Undead Dragons"
|
||||||
|
},
|
||||||
"age" :
|
"age" :
|
||||||
{
|
{
|
||||||
"type" : "SPELL_AFTER_ATTACK",
|
"type" : "SPELL_AFTER_ATTACK",
|
||||||
|
@ -111,6 +111,10 @@
|
|||||||
"type":"string",
|
"type":"string",
|
||||||
"description": "sourceType"
|
"description": "sourceType"
|
||||||
},
|
},
|
||||||
|
"stacking" : {
|
||||||
|
"type" : "string",
|
||||||
|
"description" : "stacking"
|
||||||
|
},
|
||||||
"subtype": {
|
"subtype": {
|
||||||
"anyOf" : [
|
"anyOf" : [
|
||||||
{ "type" : "string" },
|
{ "type" : "string" },
|
||||||
|
@ -139,7 +139,6 @@ TBonusListPtr CBonusProxy::get() const
|
|||||||
{
|
{
|
||||||
//TODO: support limiters
|
//TODO: support limiters
|
||||||
data = target->getAllBonuses(selector, Selector::all);
|
data = target->getAllBonuses(selector, Selector::all);
|
||||||
data->eliminateDuplicates();
|
|
||||||
cachedLast = target->getTreeVersion();
|
cachedLast = target->getTreeVersion();
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
@ -246,6 +245,45 @@ void BonusList::changed()
|
|||||||
CBonusSystemNode::treeHasChanged();
|
CBonusSystemNode::treeHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BonusList::stackBonuses()
|
||||||
|
{
|
||||||
|
boost::sort(bonuses, [](std::shared_ptr<Bonus> b1, std::shared_ptr<Bonus> b2) -> bool
|
||||||
|
{
|
||||||
|
if(b1 == b2)
|
||||||
|
return false;
|
||||||
|
#define COMPARE_ATT(ATT) if(b1->ATT != b2->ATT) return b1->ATT < b2->ATT
|
||||||
|
COMPARE_ATT(stacking);
|
||||||
|
COMPARE_ATT(type);
|
||||||
|
COMPARE_ATT(subtype);
|
||||||
|
COMPARE_ATT(valType);
|
||||||
|
#undef COMPARE_ATT
|
||||||
|
return b1->val > b2->val;
|
||||||
|
});
|
||||||
|
// remove non-stacking
|
||||||
|
size_t next = 1;
|
||||||
|
while(next < bonuses.size())
|
||||||
|
{
|
||||||
|
bool remove;
|
||||||
|
const std::shared_ptr<Bonus> last = bonuses[next-1];
|
||||||
|
const std::shared_ptr<Bonus> current = bonuses[next];
|
||||||
|
|
||||||
|
if(current->stacking.empty())
|
||||||
|
remove = current == last;
|
||||||
|
else if(current->stacking == "ALWAYS")
|
||||||
|
remove = false;
|
||||||
|
else
|
||||||
|
remove = current->stacking == last->stacking
|
||||||
|
&& current->type == last->type
|
||||||
|
&& current->subtype == last->subtype
|
||||||
|
&& current->valType == last->valType;
|
||||||
|
|
||||||
|
if(remove)
|
||||||
|
bonuses.erase(bonuses.begin() + next);
|
||||||
|
else
|
||||||
|
next++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int BonusList::totalValue() const
|
int BonusList::totalValue() const
|
||||||
{
|
{
|
||||||
int base = 0;
|
int base = 0;
|
||||||
@ -257,7 +295,7 @@ int BonusList::totalValue() const
|
|||||||
int indepMin = 0;
|
int indepMin = 0;
|
||||||
bool hasIndepMin = false;
|
bool hasIndepMin = false;
|
||||||
|
|
||||||
for (auto& b : bonuses)
|
for(std::shared_ptr<Bonus> b : bonuses)
|
||||||
{
|
{
|
||||||
switch(b->valType)
|
switch(b->valType)
|
||||||
{
|
{
|
||||||
@ -375,14 +413,15 @@ int BonusList::valOfBonuses(const CSelector &select) const
|
|||||||
BonusList ret;
|
BonusList ret;
|
||||||
CSelector limit = nullptr;
|
CSelector limit = nullptr;
|
||||||
getBonuses(ret, select, limit);
|
getBonuses(ret, select, limit);
|
||||||
ret.eliminateDuplicates();
|
|
||||||
return ret.totalValue();
|
return ret.totalValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BonusList::eliminateDuplicates()
|
JsonNode BonusList::toJsonNode() const
|
||||||
{
|
{
|
||||||
sort( bonuses.begin(), bonuses.end() );
|
JsonNode node(JsonNode::JsonType::DATA_VECTOR);
|
||||||
bonuses.erase( unique( bonuses.begin(), bonuses.end() ), bonuses.end() );
|
for(std::shared_ptr<Bonus> b : bonuses)
|
||||||
|
node.Vector().push_back(b->toJsonNode());
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BonusList::push_back(std::shared_ptr<Bonus> x)
|
void BonusList::push_back(std::shared_ptr<Bonus> x)
|
||||||
@ -688,8 +727,8 @@ const TBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, c
|
|||||||
|
|
||||||
BonusList allBonuses;
|
BonusList allBonuses;
|
||||||
getAllBonusesRec(allBonuses);
|
getAllBonusesRec(allBonuses);
|
||||||
allBonuses.eliminateDuplicates();
|
|
||||||
limitBonuses(allBonuses, cachedBonuses);
|
limitBonuses(allBonuses, cachedBonuses);
|
||||||
|
cachedBonuses.stackBonuses();
|
||||||
|
|
||||||
cachedLast = treeChanged;
|
cachedLast = treeChanged;
|
||||||
}
|
}
|
||||||
@ -730,12 +769,10 @@ const TBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelecto
|
|||||||
// Get bonus results without caching enabled.
|
// Get bonus results without caching enabled.
|
||||||
BonusList beforeLimiting, afterLimiting;
|
BonusList beforeLimiting, afterLimiting;
|
||||||
getAllBonusesRec(beforeLimiting);
|
getAllBonusesRec(beforeLimiting);
|
||||||
beforeLimiting.eliminateDuplicates();
|
|
||||||
|
|
||||||
if(!root || root == this)
|
if(!root || root == this)
|
||||||
{
|
{
|
||||||
limitBonuses(beforeLimiting, afterLimiting);
|
limitBonuses(beforeLimiting, afterLimiting);
|
||||||
afterLimiting.getBonuses(*ret, selector, limit);
|
|
||||||
}
|
}
|
||||||
else if(root)
|
else if(root)
|
||||||
{
|
{
|
||||||
@ -747,15 +784,15 @@ const TBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelecto
|
|||||||
for(auto b : beforeLimiting)
|
for(auto b : beforeLimiting)
|
||||||
rootBonuses.push_back(b);
|
rootBonuses.push_back(b);
|
||||||
|
|
||||||
rootBonuses.eliminateDuplicates();
|
|
||||||
root->limitBonuses(rootBonuses, limitedRootBonuses);
|
root->limitBonuses(rootBonuses, limitedRootBonuses);
|
||||||
|
|
||||||
for(auto b : beforeLimiting)
|
for(auto b : beforeLimiting)
|
||||||
if(vstd::contains(limitedRootBonuses, b))
|
if(vstd::contains(limitedRootBonuses, b))
|
||||||
afterLimiting.push_back(b);
|
afterLimiting.push_back(b);
|
||||||
|
|
||||||
afterLimiting.getBonuses(*ret, selector, limit);
|
|
||||||
}
|
}
|
||||||
|
afterLimiting.getBonuses(*ret, selector, limit);
|
||||||
|
ret->stackBonuses();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -944,11 +981,6 @@ void CBonusSystemNode::unpropagateBonus(std::shared_ptr<Bonus> b)
|
|||||||
if(b->propagator->shouldBeAttached(this))
|
if(b->propagator->shouldBeAttached(this))
|
||||||
{
|
{
|
||||||
bonuses -= b;
|
bonuses -= b;
|
||||||
while(vstd::contains(bonuses, b))
|
|
||||||
{
|
|
||||||
logBonus->error("Bonus was duplicated (%s) at %s", b->Description(), nodeName());
|
|
||||||
bonuses -= b;
|
|
||||||
}
|
|
||||||
logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(), nodeName());
|
logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(), nodeName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1198,6 +1230,9 @@ std::string Bonus::Description() const
|
|||||||
std::ostringstream str;
|
std::ostringstream str;
|
||||||
|
|
||||||
if(description.empty())
|
if(description.empty())
|
||||||
|
{
|
||||||
|
if(stacking.empty() || stacking == "ALWAYS")
|
||||||
|
{
|
||||||
switch(source)
|
switch(source)
|
||||||
{
|
{
|
||||||
case ARTIFACT:
|
case ARTIFACT:
|
||||||
@ -1217,8 +1252,14 @@ std::string Bonus::Description() const
|
|||||||
str << "Unknown";
|
str << "Unknown";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
str << stacking;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
str << description;
|
str << description;
|
||||||
|
}
|
||||||
|
|
||||||
if(val != 0)
|
if(val != 0)
|
||||||
str << " " << std::showpos << val;
|
str << " " << std::showpos << val;
|
||||||
@ -1240,6 +1281,7 @@ JsonNode subtypeToJson(Bonus::BonusType type, int subtype)
|
|||||||
case Bonus::MAXED_SPELL:
|
case Bonus::MAXED_SPELL:
|
||||||
case Bonus::SPECIAL_PECULIAR_ENCHANT:
|
case Bonus::SPECIAL_PECULIAR_ENCHANT:
|
||||||
return JsonUtils::stringNode("spell." + (*VLC->spellh)[SpellID::ESpellID(subtype)]->identifier);
|
return JsonUtils::stringNode("spell." + (*VLC->spellh)[SpellID::ESpellID(subtype)]->identifier);
|
||||||
|
case Bonus::IMPROVED_NECROMANCY:
|
||||||
case Bonus::SPECIAL_UPGRADE:
|
case Bonus::SPECIAL_UPGRADE:
|
||||||
return JsonUtils::stringNode("creature." + CreatureID::encode(subtype));
|
return JsonUtils::stringNode("creature." + CreatureID::encode(subtype));
|
||||||
case Bonus::GENERATE_RESOURCE:
|
case Bonus::GENERATE_RESOURCE:
|
||||||
@ -1256,24 +1298,35 @@ JsonNode additionalInfoToJson(Bonus::BonusType type, CAddInfo addInfo)
|
|||||||
case Bonus::SPECIAL_UPGRADE:
|
case Bonus::SPECIAL_UPGRADE:
|
||||||
return JsonUtils::stringNode("creature." + CreatureID::encode(addInfo[0]));
|
return JsonUtils::stringNode("creature." + CreatureID::encode(addInfo[0]));
|
||||||
default:
|
default:
|
||||||
if(addInfo.size() <= 1)
|
return addInfo.toJsonNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode durationToJson(ui16 duration)
|
||||||
{
|
{
|
||||||
return JsonUtils::intNode(addInfo[0]);
|
std::vector<std::string> durationNames;
|
||||||
|
for(ui16 durBit = 1; durBit; durBit = durBit << 1)
|
||||||
|
{
|
||||||
|
if(duration & durBit)
|
||||||
|
durationNames.push_back(vstd::findKey(bonusDurationMap, durBit));
|
||||||
|
}
|
||||||
|
if(durationNames.size() == 1)
|
||||||
|
{
|
||||||
|
return JsonUtils::stringNode(durationNames[0]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JsonNode vecNode(JsonNode::JsonType::DATA_VECTOR);
|
JsonNode node(JsonNode::JsonType::DATA_VECTOR);
|
||||||
for(si32 value : addInfo)
|
for(std::string dur : durationNames)
|
||||||
vecNode.Vector().push_back(JsonUtils::intNode(value));
|
node.Vector().push_back(JsonUtils::stringNode(dur));
|
||||||
return vecNode;
|
return node;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode Bonus::toJsonNode() const
|
JsonNode Bonus::toJsonNode() const
|
||||||
{
|
{
|
||||||
JsonNode root(JsonNode::JsonType::DATA_STRUCT);
|
JsonNode root(JsonNode::JsonType::DATA_STRUCT);
|
||||||
|
// only add values that might reasonably be found in config files
|
||||||
root["type"].String() = vstd::findKey(bonusNameMap, type);
|
root["type"].String() = vstd::findKey(bonusNameMap, type);
|
||||||
if(subtype != -1)
|
if(subtype != -1)
|
||||||
root["subtype"] = subtypeToJson(type, subtype);
|
root["subtype"] = subtypeToJson(type, subtype);
|
||||||
@ -1283,10 +1336,22 @@ JsonNode Bonus::toJsonNode() const
|
|||||||
root["val"].Integer() = val;
|
root["val"].Integer() = val;
|
||||||
if(valType != ADDITIVE_VALUE)
|
if(valType != ADDITIVE_VALUE)
|
||||||
root["valueType"].String() = vstd::findKey(bonusValueMap, valType);
|
root["valueType"].String() = vstd::findKey(bonusValueMap, valType);
|
||||||
|
if(stacking != "")
|
||||||
|
root["stacking"].String() = stacking;
|
||||||
|
if(description != "")
|
||||||
|
root["description"].String() = description;
|
||||||
|
if(effectRange != NO_LIMIT)
|
||||||
|
root["effectRange"].String() = vstd::findKey(bonusLimitEffect, effectRange);
|
||||||
|
if(duration != PERMANENT)
|
||||||
|
root["duration"] = durationToJson(duration);
|
||||||
|
if(turnsRemain)
|
||||||
|
root["turns"].Integer() = turnsRemain;
|
||||||
if(limiter)
|
if(limiter)
|
||||||
root["limiters"].Vector().push_back(limiter->toJsonNode());
|
root["limiters"].Vector().push_back(limiter->toJsonNode());
|
||||||
if(updater)
|
if(updater)
|
||||||
root["updater"] = updater->toJsonNode();
|
root["updater"] = updater->toJsonNode();
|
||||||
|
if(propagator)
|
||||||
|
root["propagator"].String() = vstd::findKey(bonusPropagatorMap, propagator);
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1474,6 +1539,8 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus)
|
|||||||
out << "\taddInfo: " << bonus.additionalInfo.toString() << "\n";
|
out << "\taddInfo: " << bonus.additionalInfo.toString() << "\n";
|
||||||
printField(turnsRemain);
|
printField(turnsRemain);
|
||||||
printField(valType);
|
printField(valType);
|
||||||
|
if(!bonus.stacking.empty())
|
||||||
|
out << "\tstacking: \"" << bonus.stacking << "\"\n";
|
||||||
printField(effectRange);
|
printField(effectRange);
|
||||||
#undef printField
|
#undef printField
|
||||||
|
|
||||||
|
@ -355,6 +355,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
|
|||||||
si32 val;
|
si32 val;
|
||||||
ui32 sid; //source id: id of object/artifact/spell
|
ui32 sid; //source id: id of object/artifact/spell
|
||||||
ValueType valType;
|
ValueType valType;
|
||||||
|
std::string stacking; // bonuses with the same stacking value don't stack (e.g. Angel/Archangel morale bonus)
|
||||||
|
|
||||||
CAddInfo additionalInfo;
|
CAddInfo additionalInfo;
|
||||||
LimitEffect effectRange; //if not NO_LIMIT, bonus will be omitted by default
|
LimitEffect effectRange; //if not NO_LIMIT, bonus will be omitted by default
|
||||||
@ -389,6 +390,10 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
|
|||||||
}
|
}
|
||||||
h & turnsRemain;
|
h & turnsRemain;
|
||||||
h & valType;
|
h & valType;
|
||||||
|
if(version >= 784)
|
||||||
|
{
|
||||||
|
h & stacking;
|
||||||
|
}
|
||||||
h & effectRange;
|
h & effectRange;
|
||||||
h & limiter;
|
h & limiter;
|
||||||
h & propagator;
|
h & propagator;
|
||||||
@ -506,6 +511,7 @@ public:
|
|||||||
TInternalContainer::size_type operator-=(std::shared_ptr<Bonus> const &i);
|
TInternalContainer::size_type operator-=(std::shared_ptr<Bonus> const &i);
|
||||||
|
|
||||||
// BonusList functions
|
// BonusList functions
|
||||||
|
void stackBonuses();
|
||||||
int totalValue() const;
|
int totalValue() const;
|
||||||
void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit) const;
|
void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit) const;
|
||||||
void getAllBonuses(BonusList &out) const;
|
void getAllBonuses(BonusList &out) const;
|
||||||
@ -517,7 +523,8 @@ public:
|
|||||||
const std::shared_ptr<Bonus> getFirst(const CSelector &select) const;
|
const std::shared_ptr<Bonus> getFirst(const CSelector &select) const;
|
||||||
int valOfBonuses(const CSelector &select) const;
|
int valOfBonuses(const CSelector &select) const;
|
||||||
|
|
||||||
void eliminateDuplicates();
|
// conversion / output
|
||||||
|
JsonNode toJsonNode() const;
|
||||||
|
|
||||||
// remove_if implementation for STL vector types
|
// remove_if implementation for STL vector types
|
||||||
template <class Predicate>
|
template <class Predicate>
|
||||||
|
@ -598,6 +598,8 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
|
|||||||
if (!value->isNull())
|
if (!value->isNull())
|
||||||
b->valType = static_cast<Bonus::ValueType>(parseByMap(bonusValueMap, value, "value type "));
|
b->valType = static_cast<Bonus::ValueType>(parseByMap(bonusValueMap, value, "value type "));
|
||||||
|
|
||||||
|
b->stacking = ability["stacking"].String();
|
||||||
|
|
||||||
resolveAddInfo(b->additionalInfo, ability);
|
resolveAddInfo(b->additionalInfo, ability);
|
||||||
|
|
||||||
b->turnsRemain = ability["turns"].Float();
|
b->turnsRemain = ability["turns"].Float();
|
||||||
@ -742,29 +744,6 @@ Key reverseMapFirst(const Val & val, const std::map<Key, Val> & map)
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonUtils::unparseBonus( JsonNode &node, const std::shared_ptr<Bonus>& bonus )
|
|
||||||
{
|
|
||||||
node["type"].String() = reverseMapFirst<std::string, Bonus::BonusType>(bonus->type, bonusNameMap);
|
|
||||||
node["subtype"].Float() = bonus->subtype;
|
|
||||||
node["val"].Float() = bonus->val;
|
|
||||||
node["valueType"].String() = reverseMapFirst<std::string, Bonus::ValueType>(bonus->valType, bonusValueMap);
|
|
||||||
node["additionalInfo"] = bonus->additionalInfo.toJsonNode();
|
|
||||||
node["turns"].Float() = bonus->turnsRemain;
|
|
||||||
node["sourceID"].Float() = bonus->source;
|
|
||||||
node["description"].String() = bonus->description;
|
|
||||||
node["effectRange"].String() = reverseMapFirst<std::string, Bonus::LimitEffect>(bonus->effectRange, bonusLimitEffect);
|
|
||||||
node["duration"].String() = reverseMapFirst<std::string, ui16>(bonus->duration, bonusDurationMap);
|
|
||||||
node["source"].String() = reverseMapFirst<std::string, Bonus::BonusSource>(bonus->source, bonusSourceMap);
|
|
||||||
if(bonus->limiter)
|
|
||||||
{
|
|
||||||
node["limiter"].String() = reverseMapFirst<std::string, TLimiterPtr>(bonus->limiter, bonusLimiterMap);
|
|
||||||
}
|
|
||||||
if(bonus->propagator)
|
|
||||||
{
|
|
||||||
node["propagator"].String() = reverseMapFirst<std::string, TPropagatorPtr>(bonus->propagator, bonusPropagatorMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void minimizeNode(JsonNode & node, const JsonNode & schema)
|
void minimizeNode(JsonNode & node, const JsonNode & schema)
|
||||||
{
|
{
|
||||||
if (schema["type"].String() == "object")
|
if (schema["type"].String() == "object")
|
||||||
|
@ -168,7 +168,6 @@ namespace JsonUtils
|
|||||||
DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector &ability_vec);
|
DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector &ability_vec);
|
||||||
DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode &ability);
|
DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode &ability);
|
||||||
DLL_LINKAGE bool parseBonus(const JsonNode &ability, Bonus *placement);
|
DLL_LINKAGE bool parseBonus(const JsonNode &ability, Bonus *placement);
|
||||||
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(si32 &var, const JsonNode &node, std::string name);
|
||||||
DLL_LINKAGE void resolveIdentifier(const JsonNode &node, si32 &var);
|
DLL_LINKAGE void resolveIdentifier(const JsonNode &node, si32 &var);
|
||||||
DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node);
|
DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node);
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
#include "../ConstTransitivePtr.h"
|
#include "../ConstTransitivePtr.h"
|
||||||
#include "../GameConstants.h"
|
#include "../GameConstants.h"
|
||||||
|
|
||||||
const ui32 SERIALIZATION_VERSION = 783;
|
const ui32 SERIALIZATION_VERSION = 784;
|
||||||
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
|
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
|
||||||
const std::string SAVEGAME_MAGIC = "VCMISVG";
|
const std::string SAVEGAME_MAGIC = "VCMISVG";
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ const TBonusListPtr BonusBearerMock::getAllBonuses(const CSelector & selector, c
|
|||||||
{
|
{
|
||||||
if(cachedLast != treeVersion)
|
if(cachedLast != treeVersion)
|
||||||
{
|
{
|
||||||
bonuses.eliminateDuplicates();
|
bonuses.stackBonuses();
|
||||||
cachedLast = treeVersion;
|
cachedLast = treeVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user