Add a system component to handle stat modifiers, make technologies and auras use this common interface.
The ModifiersManager system component provides an interface to add and
remove modifiers, and get modified stats.
The goal is to merge all the different stat-modifying systems 0 A.D. has
implemented over the years.
This commit makes technologies and auras use ModifiersManager. Some
cheats and AI bonuses also have a similar stat-modifying effect that
have not yet been updated.
Further, this system component makes it possible for e.g. triggers to
easily add modifiers, enabling the writing of Castle Blood Automatic,
RPG or Tower Defense maps without the need for mods or hacks.
The 'Modifier' name was preferred over 'Modification' as it is shorter
and more readable, along with the logic that 'modifiers' store
'modifications' and this stores modifiers. Renaming of other functions
and classes has been left for future work for now.
Internally, this uses a JS data structure. If performance issues arise
with it in the future, this data structure or the whole component could
be moved to C++.
The performance has been tested to be about as fast as the current
implementations (and specifically much faster for global auras with no
icons). Testing showed that sending value modification messages was by
far the slowest part.
Comments by: leper, Stan, elexis
Differential Revision: https://code.wildfiregames.com/D274
This was SVN commit r22767.
2019-08-24 00:37:07 -07:00
|
|
|
function setup_keys(map)
|
|
|
|
|
{
|
|
|
|
|
map.AddItem("prim_a", "item_a", null, "sec_a");
|
|
|
|
|
map.AddItem("prim_a", "item_b", null, "sec_a");
|
|
|
|
|
map.AddItem("prim_a", "item_c", null, "sec_a");
|
|
|
|
|
map.AddItem("prim_a", "item_a", null, "sec_b");
|
|
|
|
|
map.AddItem("prim_b", "item_a", null, "sec_a");
|
|
|
|
|
map.AddItem("prim_c", "item_a", null, "sec_a");
|
|
|
|
|
map.AddItem("prim_c", "item_a", null, 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that key-related operations are correct.
|
|
|
|
|
function test_keys(map)
|
|
|
|
|
{
|
|
|
|
|
TS_ASSERT(map.items.has("prim_a"));
|
|
|
|
|
TS_ASSERT(map.items.has("prim_b"));
|
|
|
|
|
TS_ASSERT(map.items.has("prim_c"));
|
|
|
|
|
|
|
|
|
|
TS_ASSERT(map.items.get("prim_a").has("sec_a"));
|
|
|
|
|
TS_ASSERT(map.items.get("prim_a").has("sec_b"));
|
|
|
|
|
TS_ASSERT(!map.items.get("prim_a").has("sec_c"));
|
|
|
|
|
TS_ASSERT(map.items.get("prim_b").has("sec_a"));
|
|
|
|
|
TS_ASSERT(map.items.get("prim_c").has("sec_a"));
|
|
|
|
|
TS_ASSERT(map.items.get("prim_c").has(5));
|
|
|
|
|
|
|
|
|
|
TS_ASSERT(map.items.get("prim_a").get("sec_a").length == 3);
|
|
|
|
|
TS_ASSERT(map.items.get("prim_a").get("sec_b").length == 1);
|
|
|
|
|
TS_ASSERT(map.items.get("prim_b").get("sec_a").length == 1);
|
|
|
|
|
TS_ASSERT(map.items.get("prim_c").get("sec_a").length == 1);
|
|
|
|
|
TS_ASSERT(map.items.get("prim_c").get(5).length == 1);
|
|
|
|
|
|
|
|
|
|
TS_ASSERT(map.GetItems("prim_a", "sec_a").length == 3);
|
|
|
|
|
TS_ASSERT(map.GetItems("prim_a", "sec_b").length == 1);
|
|
|
|
|
TS_ASSERT(map.GetItems("prim_b", "sec_a").length == 1);
|
|
|
|
|
TS_ASSERT(map.GetItems("prim_c", "sec_a").length == 1);
|
|
|
|
|
TS_ASSERT(map.GetItems("prim_c", 5).length == 1);
|
|
|
|
|
|
|
|
|
|
TS_ASSERT(map.HasItem("prim_a", "item_a", "sec_a"));
|
|
|
|
|
TS_ASSERT(map.HasItem("prim_a", "item_b", "sec_a"));
|
|
|
|
|
TS_ASSERT(map.HasItem("prim_a", "item_c", "sec_a"));
|
|
|
|
|
TS_ASSERT(!map.HasItem("prim_a", "item_d", "sec_a"));
|
|
|
|
|
TS_ASSERT(map.HasItem("prim_a", "item_a", "sec_b"));
|
|
|
|
|
TS_ASSERT(!map.HasItem("prim_a", "item_b", "sec_b"));
|
|
|
|
|
TS_ASSERT(!map.HasItem("prim_a", "item_c", "sec_b"));
|
|
|
|
|
TS_ASSERT(map.HasItem("prim_b", "item_a", "sec_a"));
|
|
|
|
|
TS_ASSERT(map.HasItem("prim_c", "item_a", "sec_a"));
|
|
|
|
|
TS_ASSERT(map.HasAnyItem("item_a", "sec_b"));
|
|
|
|
|
TS_ASSERT(map.HasAnyItem("item_b", "sec_a"));
|
|
|
|
|
TS_ASSERT(!map.HasAnyItem("item_d", "sec_a"));
|
|
|
|
|
TS_ASSERT(!map.HasAnyItem("item_b", "sec_b"));
|
|
|
|
|
|
|
|
|
|
// Adding the same item increases its count.
|
|
|
|
|
map.AddItem("prim_a", "item_b", 0, "sec_a");
|
|
|
|
|
TS_ASSERT_EQUALS(map.items.get("prim_a").get("sec_a").length, 3);
|
|
|
|
|
TS_ASSERT_EQUALS(map.items.get("prim_a").get("sec_a").filter(item => item._ID == "item_b")[0]._count, 2);
|
|
|
|
|
TS_ASSERT_EQUALS(map.GetItems("prim_a", "sec_a").length, 3);
|
|
|
|
|
|
|
|
|
|
// Adding without stackable doesn't invalidate caches, adding with does.
|
|
|
|
|
TS_ASSERT(!map.AddItem("prim_a", "item_b", 0, "sec_a"));
|
|
|
|
|
TS_ASSERT(map.AddItem("prim_a", "item_b", 0, "sec_a", true));
|
|
|
|
|
|
|
|
|
|
TS_ASSERT(map.items.get("prim_a").get("sec_a").filter(item => item._ID == "item_b")[0]._count == 4);
|
|
|
|
|
|
|
|
|
|
// Likewise removing, unless we now reach 0
|
|
|
|
|
TS_ASSERT(!map.RemoveItem("prim_a", "item_b", "sec_a"));
|
|
|
|
|
TS_ASSERT(map.RemoveItem("prim_a", "item_b", "sec_a", true));
|
|
|
|
|
TS_ASSERT(!map.RemoveItem("prim_a", "item_b", "sec_a"));
|
|
|
|
|
TS_ASSERT(map.RemoveItem("prim_a", "item_b", "sec_a"));
|
|
|
|
|
|
|
|
|
|
// Check that cleanup is done
|
|
|
|
|
TS_ASSERT(map.items.get("prim_a").get("sec_a").length == 2);
|
|
|
|
|
TS_ASSERT(map.RemoveItem("prim_a", "item_a", "sec_a"));
|
|
|
|
|
TS_ASSERT(map.RemoveItem("prim_a", "item_c", "sec_a"));
|
|
|
|
|
TS_ASSERT(!map.items.get("prim_a").has("sec_a"));
|
|
|
|
|
TS_ASSERT(map.items.get("prim_a").has("sec_b"));
|
|
|
|
|
TS_ASSERT(map.RemoveItem("prim_a", "item_a", "sec_b"));
|
|
|
|
|
TS_ASSERT(!map.items.has("prim_a"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setup_items(map)
|
|
|
|
|
{
|
|
|
|
|
map.AddItem("prim_a", "item_a", { "value": 1 }, "sec_a");
|
|
|
|
|
map.AddItem("prim_a", "item_b", { "value": 2 }, "sec_a");
|
|
|
|
|
map.AddItem("prim_a", "item_c", { "value": 3 }, "sec_a");
|
|
|
|
|
map.AddItem("prim_a", "item_c", { "value": 1000 }, "sec_a");
|
|
|
|
|
map.AddItem("prim_a", "item_a", { "value": 5 }, "sec_b");
|
|
|
|
|
map.AddItem("prim_b", "item_a", { "value": 6 }, "sec_a");
|
|
|
|
|
map.AddItem("prim_c", "item_a", { "value": 7 }, "sec_a");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that items returned are correct.
|
|
|
|
|
function test_items(map)
|
|
|
|
|
{
|
2025-05-06 05:16:13 -07:00
|
|
|
const items = map.GetAllItems("sec_a");
|
Add a system component to handle stat modifiers, make technologies and auras use this common interface.
The ModifiersManager system component provides an interface to add and
remove modifiers, and get modified stats.
The goal is to merge all the different stat-modifying systems 0 A.D. has
implemented over the years.
This commit makes technologies and auras use ModifiersManager. Some
cheats and AI bonuses also have a similar stat-modifying effect that
have not yet been updated.
Further, this system component makes it possible for e.g. triggers to
easily add modifiers, enabling the writing of Castle Blood Automatic,
RPG or Tower Defense maps without the need for mods or hacks.
The 'Modifier' name was preferred over 'Modification' as it is shorter
and more readable, along with the logic that 'modifiers' store
'modifications' and this stores modifiers. Renaming of other functions
and classes has been left for future work for now.
Internally, this uses a JS data structure. If performance issues arise
with it in the future, this data structure or the whole component could
be moved to C++.
The performance has been tested to be about as fast as the current
implementations (and specifically much faster for global auras with no
icons). Testing showed that sending value modification messages was by
far the slowest part.
Comments by: leper, Stan, elexis
Differential Revision: https://code.wildfiregames.com/D274
This was SVN commit r22767.
2019-08-24 00:37:07 -07:00
|
|
|
TS_ASSERT("prim_a" in items);
|
|
|
|
|
TS_ASSERT("prim_b" in items);
|
|
|
|
|
TS_ASSERT("prim_c" in items);
|
|
|
|
|
let sum = 0;
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const key in items)
|
2019-09-22 05:05:04 -07:00
|
|
|
items[key].forEach(item => { sum += item.value.value * item._count; });
|
Add a system component to handle stat modifiers, make technologies and auras use this common interface.
The ModifiersManager system component provides an interface to add and
remove modifiers, and get modified stats.
The goal is to merge all the different stat-modifying systems 0 A.D. has
implemented over the years.
This commit makes technologies and auras use ModifiersManager. Some
cheats and AI bonuses also have a similar stat-modifying effect that
have not yet been updated.
Further, this system component makes it possible for e.g. triggers to
easily add modifiers, enabling the writing of Castle Blood Automatic,
RPG or Tower Defense maps without the need for mods or hacks.
The 'Modifier' name was preferred over 'Modification' as it is shorter
and more readable, along with the logic that 'modifiers' store
'modifications' and this stores modifiers. Renaming of other functions
and classes has been left for future work for now.
Internally, this uses a JS data structure. If performance issues arise
with it in the future, this data structure or the whole component could
be moved to C++.
The performance has been tested to be about as fast as the current
implementations (and specifically much faster for global auras with no
icons). Testing showed that sending value modification messages was by
far the slowest part.
Comments by: leper, Stan, elexis
Differential Revision: https://code.wildfiregames.com/D274
This was SVN commit r22767.
2019-08-24 00:37:07 -07:00
|
|
|
TS_ASSERT(sum == 22);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test items, and test that deserialised versions still pass test (i.e. test serialisation).
|
|
|
|
|
let map = new MultiKeyMap();
|
|
|
|
|
setup_keys(map);
|
|
|
|
|
test_keys(map);
|
|
|
|
|
|
|
|
|
|
map = new MultiKeyMap();
|
|
|
|
|
let map2 = new MultiKeyMap();
|
|
|
|
|
setup_keys(map);
|
|
|
|
|
map2.Deserialize(map.Serialize());
|
|
|
|
|
test_keys(map2);
|
|
|
|
|
|
|
|
|
|
map = new MultiKeyMap();
|
|
|
|
|
setup_items(map);
|
|
|
|
|
test_items(map);
|
|
|
|
|
map = new MultiKeyMap();
|
|
|
|
|
map2 = new MultiKeyMap();
|
|
|
|
|
setup_items(map);
|
|
|
|
|
map2.Deserialize(map.Serialize());
|
|
|
|
|
test_items(map2);
|