2017-05-28 14:09:16 -07:00
|
|
|
Engine.LoadHelperScript("Player.js");
|
2022-11-24 03:20:11 -08:00
|
|
|
Engine.LoadHelperScript("Requirements.js");
|
2017-05-28 14:09:16 -07:00
|
|
|
Engine.LoadHelperScript("ValueModification.js");
|
|
|
|
|
Resources = {
|
2025-12-30 00:57:37 -08:00
|
|
|
"BuildSchema": type =>
|
|
|
|
|
{
|
2017-05-28 14:09:16 -07:00
|
|
|
let schema = "";
|
2025-05-11 01:03:06 -07:00
|
|
|
for (const res of ["food", "metal", "stone", "wood"])
|
2017-05-28 14:09:16 -07:00
|
|
|
schema +=
|
|
|
|
|
"<optional>" +
|
|
|
|
|
"<element name='" + res + "'>" +
|
|
|
|
|
"<ref name='" + type + "'/>" +
|
|
|
|
|
"</element>" +
|
|
|
|
|
"</optional>";
|
|
|
|
|
return "<interleave>" + schema + "</interleave>";
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-10-04 03:20:20 -07:00
|
|
|
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
|
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
|
|
|
Engine.LoadComponentScript("interfaces/ModifiersManager.js"); // Provides `IID_ModifiersManager`, used below.
|
2017-05-28 14:09:16 -07:00
|
|
|
Engine.LoadComponentScript("interfaces/Timer.js"); // Provides `IID_Timer`, used below.
|
|
|
|
|
|
|
|
|
|
// What we're testing:
|
|
|
|
|
Engine.LoadComponentScript("interfaces/Upgrade.js");
|
|
|
|
|
Engine.LoadComponentScript("Upgrade.js");
|
|
|
|
|
|
|
|
|
|
// Input (bare minimum needed for tests):
|
2025-05-11 01:03:06 -07:00
|
|
|
const techs = {
|
2017-05-28 14:09:16 -07:00
|
|
|
"alter_tower_upgrade_cost": {
|
|
|
|
|
"modifications": [
|
|
|
|
|
{ "value": "Upgrade/Cost/stone", "add": 60.0 },
|
|
|
|
|
{ "value": "Upgrade/Cost/wood", "multiply": 0.5 },
|
|
|
|
|
{ "value": "Upgrade/Time", "replace": 90 }
|
|
|
|
|
],
|
|
|
|
|
"affects": ["Tower"]
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-05-11 01:03:06 -07:00
|
|
|
const template = {
|
2017-05-28 14:09:16 -07:00
|
|
|
"Identity": {
|
|
|
|
|
"Classes": { '@datatype': "tokens", "_string": "Tower" },
|
|
|
|
|
"VisibleClasses": { '@datatype': "tokens", "_string": "" }
|
|
|
|
|
},
|
|
|
|
|
"Upgrade": {
|
|
|
|
|
"Tower": {
|
|
|
|
|
"Cost": { "stone": "100", "wood": "50" },
|
2020-11-19 02:20:25 -08:00
|
|
|
"Entity": "structures/{civ}/defense_tower",
|
2017-05-28 14:09:16 -07:00
|
|
|
"Time": "100"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-05-11 01:03:06 -07:00
|
|
|
const civCode = "pony";
|
|
|
|
|
const playerID = 1;
|
2017-05-28 14:09:16 -07:00
|
|
|
|
|
|
|
|
// Usually, the tech modifications would be worked out by the TechnologyManager
|
|
|
|
|
// with assistance from globalscripts. This test is not about testing the
|
|
|
|
|
// TechnologyManager, so the modifications (both with and without the technology
|
|
|
|
|
// researched) are worked out before hand and placed here.
|
|
|
|
|
let isResearched = false;
|
2025-05-11 01:03:06 -07:00
|
|
|
const templateTechModifications = {
|
2017-05-28 14:09:16 -07:00
|
|
|
"without": {},
|
|
|
|
|
"with": {
|
|
|
|
|
"Upgrade/Cost/stone": [{ "affects": [["Tower"]], "add": 60 }],
|
|
|
|
|
"Upgrade/Cost/wood": [{ "affects": [["Tower"]], "multiply": 0.5 }],
|
|
|
|
|
"Upgrade/Time": [{ "affects": [["Tower"]], "replace": 90 }]
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-05-11 01:03:06 -07:00
|
|
|
const entityTechModifications = {
|
2017-05-28 14:09:16 -07:00
|
|
|
"without": {
|
2018-03-03 02:05:04 -08:00
|
|
|
'Upgrade/Cost/stone': { "20": { "origValue": 100, "newValue": 100 } },
|
|
|
|
|
'Upgrade/Cost/wood': { "20": { "origValue": 50, "newValue": 50 } },
|
|
|
|
|
'Upgrade/Time': { "20": { "origValue": 100, "newValue": 100 } }
|
2017-05-28 14:09:16 -07:00
|
|
|
},
|
|
|
|
|
"with": {
|
2018-03-03 02:05:04 -08:00
|
|
|
'Upgrade/Cost/stone': { "20": { "origValue": 100, "newValue": 160 } },
|
|
|
|
|
'Upgrade/Cost/wood': { "20": { "origValue": 50, "newValue": 25 } },
|
|
|
|
|
'Upgrade/Time': { "20": { "origValue": 100, "newValue": 90 } }
|
2017-05-28 14:09:16 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initialise various bits.
|
|
|
|
|
*/
|
|
|
|
|
// System Entities:
|
|
|
|
|
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
|
|
|
|
"GetPlayerByID": pID => 10 // Called in helpers/player.js::QueryPlayerIDInterface(), as part of Tests T2 and T5.
|
|
|
|
|
});
|
|
|
|
|
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
2022-07-15 10:16:00 -07:00
|
|
|
"GetTemplate": () => template, // Called in components/Upgrade.js::ChangeUpgradedEntityCount().
|
|
|
|
|
"TemplateExists": (templ) => true
|
2017-05-28 14:09:16 -07:00
|
|
|
});
|
|
|
|
|
AddMock(SYSTEM_ENTITY, IID_Timer, {
|
|
|
|
|
"SetInterval": () => 1, // Called in components/Upgrade.js::Upgrade().
|
|
|
|
|
"CancelTimer": () => {} // Called in components/Upgrade.js::CancelUpgrade().
|
|
|
|
|
});
|
|
|
|
|
|
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
|
|
|
AddMock(SYSTEM_ENTITY, IID_ModifiersManager, {
|
2025-12-30 00:57:37 -08:00
|
|
|
"ApplyTemplateModifiers": (valueName, curValue, templ, player) =>
|
|
|
|
|
{
|
2017-05-28 14:09:16 -07:00
|
|
|
// Called in helpers/ValueModification.js::ApplyValueModificationsToTemplate()
|
|
|
|
|
// as part of Tests T2 and T5 below.
|
2025-05-11 01:03:06 -07:00
|
|
|
const mods = isResearched ? templateTechModifications.with : templateTechModifications.without;
|
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
|
|
|
|
|
|
|
|
if (mods[valueName])
|
|
|
|
|
return GetTechModifiedProperty(mods[valueName], GetIdentityClasses(template.Identity), curValue);
|
|
|
|
|
return curValue;
|
2017-05-28 14:09:16 -07:00
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"ApplyModifiers": (valueName, curValue, ent) =>
|
|
|
|
|
{
|
2017-05-28 14:09:16 -07:00
|
|
|
// Called in helpers/ValueModification.js::ApplyValueModificationsToEntity()
|
|
|
|
|
// as part of Tests T3, T6 and T7 below.
|
2025-05-11 01:03:06 -07:00
|
|
|
const mods = isResearched ? entityTechModifications.with : entityTechModifications.without;
|
2017-05-28 14:09:16 -07:00
|
|
|
return mods[valueName][ent].newValue;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
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
|
|
|
// Init Player:
|
|
|
|
|
AddMock(10, IID_Player, {
|
|
|
|
|
"AddResources": () => {}, // Called in components/Upgrade.js::CancelUpgrade().
|
|
|
|
|
"GetPlayerID": () => playerID, // Called in helpers/Player.js::QueryOwnerInterface() (and several times below).
|
|
|
|
|
"TrySubtractResources": () => true // Called in components/Upgrade.js::Upgrade().
|
|
|
|
|
});
|
2022-07-15 10:16:00 -07:00
|
|
|
AddMock(10, IID_Identity, {
|
|
|
|
|
"GetCiv": () => civCode
|
|
|
|
|
});
|
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
|
|
|
|
2017-05-28 14:09:16 -07:00
|
|
|
// Create an entity with an Upgrade component:
|
|
|
|
|
AddMock(20, IID_Ownership, {
|
|
|
|
|
"GetOwner": () => playerID // Called in helpers/Player.js::QueryOwnerInterface().
|
|
|
|
|
});
|
|
|
|
|
AddMock(20, IID_Identity, {
|
|
|
|
|
"GetCiv": () => civCode // Called in components/Upgrade.js::init().
|
|
|
|
|
});
|
2020-10-04 03:20:20 -07:00
|
|
|
AddMock(20, IID_ProductionQueue, {
|
|
|
|
|
"HasQueuedProduction": () => false
|
|
|
|
|
});
|
2025-05-11 01:03:06 -07:00
|
|
|
const cmpUpgrade = ConstructComponent(20, "Upgrade", template.Upgrade);
|
2017-05-28 14:09:16 -07:00
|
|
|
cmpUpgrade.owner = playerID;
|
2022-07-15 10:16:00 -07:00
|
|
|
cmpUpgrade.OnOwnershipChanged({ "to": playerID });
|
2017-05-28 14:09:16 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Now to start the test proper
|
|
|
|
|
* To start with, no techs are researched...
|
|
|
|
|
*/
|
|
|
|
|
// T1: Check the cost of the upgrade without a player value being passed (as it would be in the structree).
|
2022-08-10 14:49:15 -07:00
|
|
|
let parsed_template = GetTemplateDataHelper(template, null, {}, Resources);
|
2017-05-28 14:09:16 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
|
|
|
|
|
|
|
|
|
|
// T2: Check the value, with a player ID (as it would be in-session).
|
2022-08-10 14:49:15 -07:00
|
|
|
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources);
|
2017-05-28 14:09:16 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
|
|
|
|
|
|
|
|
|
|
// T3: Check that the value is correct within the Update Component.
|
|
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpUpgrade.GetUpgrades()[0].cost, { "stone": 100, "wood": 50, "time": 100 });
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tell the Upgrade component to start the Upgrade,
|
|
|
|
|
* then mark the technology that alters the upgrade cost as researched.
|
|
|
|
|
*/
|
2020-11-19 02:20:25 -08:00
|
|
|
cmpUpgrade.Upgrade("structures/" + civCode + "/defense_tower");
|
2017-05-28 14:09:16 -07:00
|
|
|
isResearched = true;
|
|
|
|
|
|
|
|
|
|
// T4: Check that the player-less value hasn't increased...
|
2022-08-10 14:49:15 -07:00
|
|
|
parsed_template = GetTemplateDataHelper(template, null, {}, Resources);
|
2017-05-28 14:09:16 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
|
|
|
|
|
|
|
|
|
|
// T5: ...but the player-backed value has.
|
2022-08-10 14:49:15 -07:00
|
|
|
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources);
|
2017-05-28 14:09:16 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 160, "wood": 25, "time": 90 });
|
|
|
|
|
|
|
|
|
|
// T6: The upgrade component should still be using the old resource cost (but new time cost) for the upgrade in progress...
|
|
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpUpgrade.GetUpgrades()[0].cost, { "stone": 100, "wood": 50, "time": 90 });
|
|
|
|
|
|
|
|
|
|
// T7: ...but with the upgrade cancelled, it now uses the modified value.
|
|
|
|
|
cmpUpgrade.CancelUpgrade(playerID);
|
|
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpUpgrade.GetUpgrades()[0].cost, { "stone": 160, "wood": 25, "time": 90 });
|