Index: binaries/data/mods/public/simulation/components/ModificationsManager.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/ModificationsManager.js
@@ -0,0 +1,373 @@
+function ModificationsManager() {}
+
+ModificationsManager.prototype.Schema =
+ "";
+
+ModificationsManager.prototype.Serialize = function()
+{
+ // The cache will be affected by property reads from the GUI and other places so we shouldn't serialize it.
+ let ret = {};
+ for (let i in this)
+ if (this.hasOwnProperty(i))
+ ret[i] = this[i];
+
+ ret.cachedValues = new Map();
+ return ret;
+};
+
+ModificationsManager.prototype.Init = function()
+{
+ // TODO:
+ // - add a way to show an icon for a given modification ID (convenient for auras)
+
+ // The two following variables hold the list of modifications in an array.
+ // See GetTechModifiedProperty for what a Modification must be. It's transparent to this component.
+ this.globalModifs = new Map(); // Keyed by property name.
+ this.localModifs = new Map(); // Keyed by property name and entity ID.
+
+ // The cache computes values lazily when they are needed.
+ // Helper functions remove items that have been changed to ensure we stay up-to-date.
+ this.cachedValues = new Map(); // Keyed by property name, entity ID, original values.
+};
+
+
+
+/**
+ * Convenience wrappers to write local/global agnostic code.
+ */
+ModificationsManager.prototype._modifs = function(entity = undefined)
+{
+ return !entity ? this.globalModifs : this.localModifs;
+}
+
+ModificationsManager.prototype._getModifs = function(propertyName, entity = undefined)
+{
+ return entity ? this.localModifs.get(propertyName).get(entity) : this.globalModifs.get(propertyName);
+}
+
+ModificationsManager.prototype._exists = function(propertyName, entity = undefined)
+{
+ if (entity) {
+ if (!this.localModifs.get(propertyName))
+ return false;
+ if (!this.localModifs.get(propertyName).get(entity))
+ return false;
+ } else {
+ if (!this.globalModifs.get(propertyName))
+ return false;
+ }
+ return true;
+}
+
+ModificationsManager.prototype._initModifsIfNeeded = function(propertyName, entity = undefined)
+{
+ if (entity) {
+ if (!this.localModifs.get(propertyName))
+ this.localModifs.set(propertyName, new Map());
+ if (!this.localModifs.get(propertyName).get(entity))
+ this.localModifs.get(propertyName).set(entity, []);
+ } else {
+ if (!this.globalModifs.get(propertyName))
+ this.globalModifs.set(propertyName, []);
+ }
+}
+
+/**
+ * Add a modification.
+ * @param propertyName - Handle of a technology property (eg "Attack/Ranged/Pierce")
+ * @param modifID - internal ID of this modification, for later removal and/or updating
+ * @param modification - Object describing the change.
+ * @param entity - entity ID. If present, the modification will be locally added to this entity
+ */
+ModificationsManager.prototype.AddModif = function(propertyName, modifID, modification, entity = undefined)
+{
+ this._initModifsIfNeeded(propertyName, entity);
+
+ if (this._AddModif(propertyName, modifID, modification, entity))
+ this._InvalidateCache(propertyName, entity);
+
+ this.SendModificationMessages(propertyName, entity);
+};
+
+ModificationsManager.prototype.AddModifs = function(modifID, modifs, entity = undefined)
+{
+ for (let propertyName in modifs) {
+ this.AddModif(propertyName, modifID, modifs[propertyName], entity);
+ }
+};
+
+/**
+ * Removes a modification on a property.
+ * @param propertyName - property to change (e.g. "Health/Max")
+ * @param modifID - internal ID of the modification to remove
+ * @param entity - entity ID. If present, only remove the local modification on this entity
+ * (this won't remove a global modification locally on an entity)
+ */
+ModificationsManager.prototype.RemoveModif = function(propertyName, modifID, entity = undefined)
+{
+ if (!this._exists(propertyName, entity))
+ return;
+
+ if (this._RemoveModif(propertyName, modifID, entity))
+ this._InvalidateCache(propertyName, entity);
+
+ this.SendModificationMessages(propertyName, entity);
+};
+
+/**
+ * Removes modifications with this ID across all property names.
+ * Naively iterates all property names, which is slower but that should not be an issue.
+ */
+ModificationsManager.prototype.RemoveAllModifs = function(modifID, entity = undefined)
+{
+ for (let propertyName of this._modifs(entity).keys())
+ this.RemoveModif(propertyName, modifID);
+};
+
+/**
+ * Check if we have a modification for a given property
+ * @param propertyName - property to change (eg "Health/Max")
+ * @param modifID - internal ID of the modification to try and find.
+ * @param entity - if present, returns local modifications for that entity
+ * @returns true if there is at least one modification with that modifID
+ */
+ModificationsManager.prototype.HasModif = function(propertyName, modifID, entity = undefined)
+{
+ if (!this._exists(propertyName, entity))
+ return false;
+
+ return this._getModifs(propertyName, entity).some(modification => { return modification.ID === modifID; });
+};
+
+ModificationsManager.prototype.HasAnyModif = function(modifID, entity = undefined)
+{
+ // Map doesn't implement some so use for loops instead.
+ for (let propertyName of this._modifs(entity).keys())
+ if (this.HasModif(propertyName, modifID, entity))
+ return true;
+ return false;
+};
+
+/**
+ * Internal use. Call AddModif() from outside this script.
+ * @returns true if the modifications list changed, i.e. the cache is invalidated. Returns false otherwise.
+ */
+ModificationsManager.prototype._AddModif = function(propertyName, modifID, modification, entity = undefined)
+{
+ // TODO: this was implemented for aura support, is it really necessary?
+ // If this is not the first time we are applying this modification, the cached value won't change
+ // as we don't stack modifications at the moment, so return false
+ let existingModif = this._getModifs(propertyName, entity).filter(modif => { return modif.ID == modifID; });
+ if (existingModif.length)
+ {
+ existingModif[0].count++;
+ return false;
+ }
+
+ this._getModifs(propertyName, entity).push({ "ID": modifID, "count": 1, "effect": modification });
+ return true;
+};
+
+/**
+ * Internal use. Call RemoveModif() from outside this script.
+ * @returns true if the modifications list changed, i.e. the cache is invalidated. Returns false otherwise.
+ */
+ModificationsManager.prototype._RemoveModif = function(propertyName, modifID, entity = undefined)
+{
+ let existingModif = this._getModifs(propertyName, entity).filter(modif => { return modif.ID == modifID; });
+ if (!existingModif.length)
+ return false;
+
+ // TODO: this was implemented for aura support, is it really necessary?
+ // If we still want to apply this modification, no changes to the cache (no stacking) so return false.
+ if (--existingModif[0].count > 0)
+ return false;
+
+ let modificationsList = this._getModifs(propertyName, entity).filter(modif => { return modif.count > 0; });
+
+ // Update the map, deleting entries if necessary
+ if (!entity)
+ {
+ if (modificationsList.length)
+ this.globalModifs.set(propertyName, modificationsList);
+ else
+ this.globalModifs.delete(propertyName);
+ }
+ else
+ {
+ if (modificationsList.length)
+ this.localModifs.get(propertyName).set(entity, modificationsList);
+ else
+ {
+ this.localModifs.get(propertyName).delete(entity);
+ if (!this.localModifs.get(propertyName).length)
+ this.localModifs.delete(propertyName);
+ }
+ }
+
+ return true;
+};
+
+
+ModificationsManager.prototype._InvalidateCache = function(propertyName, entity = undefined)
+{
+ if (entity && this.cachedValues.has(propertyName))
+ this.cachedValues.get(propertyName).delete(entity);
+ else if (!entity)
+ this.cachedValues.delete(propertyName);
+};
+
+/**
+ * Inform entities that we have changed possibly all values affected by that property.
+ * It's not hugely efficient and would be nice to batch.
+ * @param propertyName - handle of a technology property (eg Attack/Ranged/Pierce) that was changed.
+ * @param entityID - if present, inform of changes to one entity only.
+ */
+ModificationsManager.prototype.SendModificationMessages = function(propertyName, entity = undefined)
+{
+ if (entity)
+ {
+ // TODO: if this is slow, it might be more efficient to keep track of who wants our info and post them directly
+ Engine.BroadcastMessage(MT_ValueModification, { "entities": [entity], "component": propertyName.split("/")[0], "valueNames": [propertyName] });
+ return;
+ }
+
+ let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
+ if (!cmpPlayer)
+ return;
+
+ let playerID = cmpPlayer.GetPlayerID();
+ if (playerID === undefined)
+ return;
+
+ // TODO: it would be preferable to be able to batch this (i.e. one message for several properties)
+ Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": playerID, "component": propertyName.split("/")[0], "valueNames": [propertyName] });
+ // AIInterface wants the entities potentially affected.
+ // TODO: improve on this
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let ents = cmpRangeManager.GetEntitiesByPlayer(playerID);
+ Engine.BroadcastMessage(MT_ValueModification, { "entities": ents, "component": propertyName.split("/")[0], "valueNames": [propertyName] });
+};
+
+/**
+ * Caching system in front of GetTechModifiedProperty() and such, as calling that every time is quite slow.
+ * This recomputes lazily.
+ * @param propertyName - Handle of a technology property (eg Attack/Ranged/Pierce) that was changed.
+ * @param originalValue - template/raw/before-modifications value.
+ Note that if this is supposed to be a number (i.e. you call add/multiply on it)
+ You must make sure to pass a number and not a string (by using + if necessary)
+ * @param ent - ID of the target entity
+ * @returns originalValue after the modifications
+ */
+ModificationsManager.prototype.ApplyModifications = function(propertyName, originalValue, entity)
+{
+ let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+ if (!cmpIdentity)
+ return originalValue;
+
+ // Sanitize input.
+ if (Array.isArray(originalValue))
+ // Some code bits pass an array instead of a single value.
+ // Those bits should probably be changed, but it is supported in the meantime.
+ originalValue = originalValue.slice();
+ else if (typeof originalValue == "object")
+ {
+ error("ModificationsManager ApplyModifications() called with an object instead of a number.");
+ return undefined;
+ }
+
+ // Return cached value if present.
+ if (this.cachedValues.has(propertyName) &&
+ this.cachedValues.get(propertyName).has(entity) &&
+ this.cachedValues.get(propertyName).get(entity).has(originalValue))
+ return this.cachedValues.get(propertyName).get(entity).get(originalValue);
+
+ // Else initialise the cache.
+ if (!this.cachedValues.get(propertyName))
+ this.cachedValues.set(propertyName, new Map());
+ if (!this.cachedValues.get(propertyName).get(entity))
+ this.cachedValues.get(propertyName).set(entity, new Map());
+ if (!this.cachedValues.get(propertyName).get(entity).get(originalValue))
+ this.cachedValues.get(propertyName).get(entity).set(originalValue, originalValue);
+
+ let newValue = this._FetchModifiedProperty(cmpIdentity.GetClassesList(), propertyName, originalValue, entity);
+
+ this.cachedValues.get(propertyName).get(entity).set(originalValue, newValue);
+
+ return newValue;
+};
+
+/**
+ * Alternative version of ApplyModifications, applies to templates instead of entities
+ * Only needs to handle global modifications
+ */
+ModificationsManager.prototype.ApplyModificationsTemplate = function(propertyName, originalValue, template)
+{
+ if (!template || !template.Identity || !this._exists(propertyName))
+ return originalValue;
+
+ return this._FetchModifiedProperty(GetIdentityClasses(template.Identity), propertyName, originalValue);
+};
+
+/**
+ * Internal use only.
+ * @returns referenceValue after modifications. Applies global modifications before local ones.
+ */
+ModificationsManager.prototype._FetchModifiedProperty = function(classesList, propertyName, referenceValue, entity = undefined)
+{
+ let flatModifs = { [propertyName]: [] };
+ if (this._exists(propertyName, entity))
+ for (let modif of this._getModifs(propertyName, entity))
+ flatModifs[propertyName].push(modif.effect);
+
+ // Apply global modifications before local ones.
+ let value = !entity ? referenceValue : this._FetchModifiedProperty(classesList, propertyName, referenceValue);
+ return GetTechModifiedProperty(flatModifs, classesList, propertyName, value);
+};
+
+/**
+ * Handle modifications when a unit is created.
+ */
+ModificationsManager.prototype.OnGlobalOwnershipChanged = function(msg)
+{
+ // Only do this for created entities, not captures/conversions as we want
+ // captured/converted entities to retain their former technologies.
+ if (msg.from != -1)
+ return;
+
+ let playerID = Engine.QueryInterface(this.entity, IID_Player).GetPlayerID();
+ if (msg.to != playerID)
+ return;
+
+ let cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
+ if (!cmpIdentity)
+ return;
+
+ let classes = cmpIdentity.GetClassesList();
+
+ let template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetCurrentTemplateName(msg.entity);
+
+ // Local modifications will be added by the relevant components, so no need to check for them here.
+ let modifiedComponents = {};
+ for (let property of this.globalModifs)
+ {
+ // We only need to find one one tech per component for a match
+ let modifications = property[1];
+ let component = property[0].split("/")[0];
+ for (let modif of modifications)
+ {
+ if (DoesModificationApply(modif.effect, classes))
+ {
+ if (!modifiedComponents[component])
+ modifiedComponents[component] = [];
+ modifiedComponents[component].push(property[0]);
+ }
+ }
+ }
+
+ // Send message(s) to the entity so it knows about researched techs
+ for (let component in modifiedComponents)
+ Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] });
+};
+
+Engine.RegisterComponentType(IID_ModificationsManager, "ModificationsManager", ModificationsManager);
Index: binaries/data/mods/public/simulation/components/TechnologyManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/TechnologyManager.js
+++ binaries/data/mods/public/simulation/components/TechnologyManager.js
@@ -3,21 +3,6 @@
TechnologyManager.prototype.Schema =
"";
-TechnologyManager.prototype.Serialize = function()
-{
- // The modifications cache will be affected by property reads from the GUI and other places so we shouldn't
- // serialize it.
-
- var ret = {};
- for (var i in this)
- {
- if (this.hasOwnProperty(i))
- ret[i] = this[i];
- }
- ret.modificationCache = {};
- return ret;
-};
-
TechnologyManager.prototype.Init = function()
{
// Holds names of technologies that have been researched.
@@ -29,16 +14,6 @@
// Holds technologies which are being researched currently (non-queued).
this.researchStarted = new Set();
- // This stores the modifications to unit stats from researched technologies
- // Example data: {"ResourceGatherer/Rates/food.grain": [
- // {"multiply": 1.15, "affects": ["FemaleCitizen", "Infantry Sword"]},
- // {"add": 2}
- // ]}
- this.modifications = {};
- this.modificationCache = {}; // Caches the values after technologies have been applied
- // e.g. { "Attack/Melee/Hack" : {5: {"origValue": 8, "newValue": 10}, 7: {"origValue": 9, "newValue": 12}, ...}, ...}
- // where 5 and 7 are entity id's
-
this.classCounts = {}; // stores the number of entities of each Class
this.typeCountsByClass = {}; // stores the number of entities of each type for each class i.e.
// {"someClass": {"unit/spearman": 2, "unit/cav": 5} "someOtherClass":...}
@@ -204,31 +179,6 @@
this.typeCountsByClass[cls][template] += 1;
}
}
-
- // Newly created entity, check if any researched techs might apply
- // (only do this for new entities because even if an entity is converted or captured,
- // we want it to maintain whatever technologies previously applied)
- if (msg.from == INVALID_PLAYER)
- {
- var modifiedComponents = {};
- for (var name in this.modifications)
- {
- // We only need to find one one tech per component for a match
- var modifications = this.modifications[name];
- var component = name.split("/")[0];
- for (let modif of modifications)
- if (DoesModificationApply(modif, classes))
- {
- if (!modifiedComponents[component])
- modifiedComponents[component] = [];
- modifiedComponents[component].push(name);
- }
- }
-
- // Send mesage(s) to the entity so it knows about researched techs
- for (var component in modifiedComponents)
- Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] });
- }
}
if (msg.from == playerID)
{
@@ -254,8 +204,6 @@
}
}
}
-
- this.clearModificationCache(msg.entity);
}
};
@@ -266,23 +214,16 @@
var modifiedComponents = {};
this.researchedTechs.add(tech);
+
// store the modifications in an easy to access structure
let template = TechnologyTemplates.Get(tech);
if (template.modifications)
{
+ let cmpModificationsManager = Engine.QueryInterface(this.entity, IID_ModificationsManager);
let derivedModifiers = DeriveModificationsFromTech(template);
for (let modifierPath in derivedModifiers)
- {
- if (!this.modifications[modifierPath])
- this.modifications[modifierPath] = [];
- this.modifications[modifierPath] = this.modifications[modifierPath].concat(derivedModifiers[modifierPath]);
-
- let component = modifierPath.split("/")[0];
- if (!modifiedComponents[component])
- modifiedComponents[component] = [];
- modifiedComponents[component].push(modifierPath);
- this.modificationCache[modifierPath] = {};
- }
+ for (let modifier of derivedModifiers[modifierPath])
+ cmpModificationsManager.AddModif(modifierPath, "tech/" + tech, modifier);
}
if (template.replaces && template.replaces.length > 0)
@@ -323,62 +264,6 @@
// always send research finished message
Engine.PostMessage(this.entity, MT_ResearchFinished, {"player": playerID, "tech": tech});
-
- for (var component in modifiedComponents)
- {
- Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": playerID, "component": component, "valueNames": modifiedComponents[component]});
- Engine.BroadcastMessage(MT_ValueModification, { "entities": ents, "component": component, "valueNames": modifiedComponents[component]});
- }
-
- if (tech.startsWith("phase") && !template.autoResearch)
- {
- let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
- cmpGUIInterface.PushNotification({
- "type": "phase",
- "players": [playerID],
- "phaseName": tech,
- "phaseState": "completed"
- });
- }
-};
-
-// Clears the cached data for an entity from the modifications cache
-TechnologyManager.prototype.clearModificationCache = function(ent)
-{
- for (var valueName in this.modificationCache)
- delete this.modificationCache[valueName][ent];
-};
-
-// Caching layer in front of ApplyModificationsWorker
-// Note: be careful with the type of curValue, if it should be a numerical
-// value and is derived from template data, you must convert the string
-// from the template to a number using the + operator, before calling
-// this function!
-TechnologyManager.prototype.ApplyModifications = function(valueName, curValue, ent)
-{
- if (!this.modificationCache[valueName])
- this.modificationCache[valueName] = {};
-
- if (!this.modificationCache[valueName][ent] || this.modificationCache[valueName][ent].origValue != curValue)
- {
- let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
- if (!cmpIdentity)
- return curValue;
- this.modificationCache[valueName][ent] = {
- "origValue": curValue,
- "newValue": GetTechModifiedProperty(this.modifications, cmpIdentity.GetClassesList(), valueName, curValue)
- };
- }
-
- return this.modificationCache[valueName][ent].newValue;
-};
-
-// Alternative version of ApplyModifications, applies to templates instead of entities
-TechnologyManager.prototype.ApplyModificationsTemplate = function(valueName, curValue, template)
-{
- if (!template || !template.Identity)
- return curValue;
- return GetTechModifiedProperty(this.modifications, GetIdentityClasses(template.Identity), valueName, curValue);
};
/**
Index: binaries/data/mods/public/simulation/components/interfaces/ModificationsManager.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/ModificationsManager.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("ModificationsManager");
Index: binaries/data/mods/public/simulation/components/tests/test_Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Attack.js
+++ binaries/data/mods/public/simulation/components/tests/test_Attack.js
@@ -6,6 +6,7 @@
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Index: binaries/data/mods/public/simulation/components/tests/test_Auras.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Auras.js
+++ binaries/data/mods/public/simulation/components/tests/test_Auras.js
@@ -4,6 +4,7 @@
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/RangeOverlayManager.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("Auras.js");
Engine.LoadComponentScript("AuraManager.js");
Index: binaries/data/mods/public/simulation/components/tests/test_Capturable.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Capturable.js
+++ binaries/data/mods/public/simulation/components/tests/test_Capturable.js
@@ -5,7 +5,7 @@
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("interfaces/TerritoryDecay.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Capturable.js");
Index: binaries/data/mods/public/simulation/components/tests/test_Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Damage.js
+++ binaries/data/mods/public/simulation/components/tests/test_Damage.js
@@ -12,7 +12,7 @@
Engine.LoadComponentScript("interfaces/Loot.js");
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Attack.js");
Engine.LoadComponentScript("Damage.js");
Index: binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
@@ -7,7 +7,7 @@
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Index: binaries/data/mods/public/simulation/components/tests/test_ModificationsManager.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/tests/test_ModificationsManager.js
@@ -0,0 +1,89 @@
+Engine.LoadComponentScript("interfaces/AuraManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
+Engine.LoadComponentScript("ModificationsManager.js");
+Engine.LoadHelperScript("Player.js");
+Engine.LoadHelperScript("ValueModification.js");
+
+let cmpModificationsManager = ConstructComponent(2, "ModificationsManager", {});
+
+cmpModificationsManager.Init();
+
+AddMock(2, IID_AuraManager, {
+ "ApplyModifications": function(a, value, b) { return value; },
+ "ApplyTemplateModifications": function(a, value, b) { return value; },
+});
+AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
+ "GetPlayerByID": () => 2,
+});
+
+let entitiesToTest = [5, 6, 7, 8];
+// Create ownership, set to "1", otherwise QueryOwnerInterface fails.
+for (let ent of entitiesToTest)
+ AddMock(ent, IID_Ownership, {
+ "GetOwner": () => 1
+ });
+
+AddMock(5, IID_Identity, {
+ "GetClassesList": function() { return "Structure";}
+});
+AddMock(6, IID_Identity, {
+ "GetClassesList": function() { return "Infantry";}
+});
+AddMock(7, IID_Identity, {
+ "GetClassesList": function() { return "Unit";}
+});
+AddMock(8, IID_Identity, {
+ "GetClassesList": function() { return "Structure Unit";}
+});
+
+cmpModificationsManager.AddModif("Test_A", "Test_A_0", { "affects": ["Structure"], "add": 10 });
+cmpModificationsManager.AddModif("Test_A", "Test_A_1", { "affects": ["Infantry"], "add": 5 });
+cmpModificationsManager.AddModif("Test_A", "Test_A_2", { "affects": ["Unit"], "add": 3 });
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 15);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 6), 10);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 7), 8);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 8), 18);
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_B", 5, 8), 5);
+
+cmpModificationsManager.RemoveAllModifs("Test_A_0");
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 5);
+
+cmpModificationsManager.AddModifs("Test_A_0", {
+ "Test_A": { "affects": ["Structure"], "add": 10 },
+ "Test_B": { "affects": ["Structure"], "add": 8 },
+});
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 15);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_B", 5, 8), 13);
+
+// Add two local modifications, only the first should stick.
+cmpModificationsManager.AddModif("Test_C", "Test_C_0", { "affects": ["Structure"], "add": 10 }, 5);
+cmpModificationsManager.AddModif("Test_C", "Test_C_1", { "affects": ["Unit"], "add": 5 }, 5);
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 15);
+
+// test that local modifications are indeed applied after global managers
+cmpModificationsManager.AddModif("Test_C", "Test_C_2", { "affects": ["Structure"], "replace": 0 }, 5);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0);
+
+TS_ASSERT(!cmpModificationsManager.HasAnyModif("Test_C_3"));
+
+// check that things still work properly if we change global modifications
+cmpModificationsManager.AddModif("Test_C", "Test_C_3", { "affects": ["Structure"], "add": 10 });
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0);
+
+TS_ASSERT(cmpModificationsManager.HasAnyModif("Test_C_3"));
+TS_ASSERT(cmpModificationsManager.HasModif("Test_C", "Test_C_3"));
+TS_ASSERT(cmpModificationsManager.HasModif("Test_C", "Test_C_2", 5));
+
+// test removal
+cmpModificationsManager.RemoveModif("Test_C", "Test_C_2", 5);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 25);
+
+TS_ASSERT(cmpModificationsManager.HasModif("Test_C", "Test_C_3"));
+TS_ASSERT(!cmpModificationsManager.HasModif("Test_C", "Test_C_2", 5));
+
+// TODO: test ownership changes, updating.
Index: binaries/data/mods/public/simulation/components/tests/test_Pack.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Pack.js
+++ binaries/data/mods/public/simulation/components/tests/test_Pack.js
@@ -2,7 +2,7 @@
Engine.LoadHelperScript("Sound.js");
Engine.LoadHelperScript("Transform.js");
Engine.LoadHelperScript("ValueModification.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Index: binaries/data/mods/public/simulation/components/tests/test_Player.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Player.js
+++ binaries/data/mods/public/simulation/components/tests/test_Player.js
@@ -17,7 +17,7 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Player.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("Player.js");
var cmpPlayer = ConstructComponent(10, "Player", {
Index: binaries/data/mods/public/simulation/components/tests/test_Technologies_effects.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/tests/test_Technologies_effects.js
@@ -0,0 +1,45 @@
+// TODO: Move this to a folder of tests for GlobalScripts (once one is created)
+
+// This tests the GetTechModifiedProperty function.
+
+let add = {
+ "Test_A": [{ "add": 10, "affects": "Unit" }]
+};
+
+let add_add = {
+ "Test_A": [{ "add": 10, "affects": "Unit" }, { "add": 5, "affects": "Unit" }]
+};
+
+let add_mul_add = {
+ "Test_A": [{ "add": 10, "affects": "Unit" }, { "multiply": 2, "affects": "Unit" }, { "add": 5, "affects": "Unit" }]
+};
+
+let add_replace = {
+ "Test_A": [{ "add": 10, "affects": "Unit" }, { "replace": 10, "affects": "Unit" }]
+};
+
+let replace_add = {
+ "Test_A": [{ "replace": 10, "affects": "Unit" }, { "add": 10, "affects": "Unit" }]
+};
+
+let replace_replace = {
+ "Test_A": [{ "replace": 10, "affects": "Unit" }, { "replace": 30, "affects": "Unit" }]
+};
+
+let replace_nonnum = {
+ "Test_A": [{ "replace": "alpha", "affects": "Unit" }]
+};
+
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add, "Unit", "Test_A", 5), 15);
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add_add, "Unit", "Test_A", 5), 20);
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add_add, "Other", "Test_A", 5), 5);
+
+// Technologies work by multiplying then adding all.
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add_mul_add, "Unit", "Test_A", 5), 25);
+
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add_replace, "Unit", "Test_A", 5), 10);
+
+// Only the first replace is taken into account
+TS_ASSERT_EQUALS(GetTechModifiedProperty(replace_replace, "Unit", "Test_A", 5), 10);
+
+TS_ASSERT_EQUALS(GetTechModifiedProperty(replace_nonnum, "Unit", "Test_A", "beta"), "alpha");
Index: binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js
+++ binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js
@@ -55,14 +55,14 @@
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]);
// Multiple `civ`s
-template.requirements = { "all": [{ "civ": "civ_A"}, { "civ": "civ_B"}, { "civ": "civ_C"}] };
+template.requirements = { "all": [{ "civ": "civ_A" }, { "civ": "civ_B" }, { "civ": "civ_C" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), false);
// Multiple `notciv`s
-template.requirements = { "all": [{ "notciv": "civ_A"}, { "notciv": "civ_B"}, { "notciv": "civ_C"}] };
+template.requirements = { "all": [{ "notciv": "civ_A" }, { "notciv": "civ_B" }, { "notciv": "civ_C" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), false);
Index: binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
+++ binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
@@ -14,7 +14,7 @@
}
};
Engine.LoadComponentScript("interfaces/AuraManager.js"); // Provides `IID_AuraManager`, tested for in helpers/ValueModification.js.
-Engine.LoadComponentScript("interfaces/TechnologyManager.js"); // Provides `IID_TechnologyManager`, used below.
+Engine.LoadComponentScript("interfaces/ModificationsManager.js"); // Provides `IID_ModificationsManager`, used below.
Engine.LoadComponentScript("interfaces/Timer.js"); // Provides `IID_Timer`, used below.
// What we're testing:
@@ -96,7 +96,7 @@
"GetTimeMultiplier": () => 1.0, // Called in components/Upgrade.js::GetUpgradeTime().
"TrySubtractResources": () => true // Called in components/Upgrade.js::Upgrade().
});
-AddMock(10, IID_TechnologyManager, {
+AddMock(10, IID_ModificationsManager, {
"ApplyModificationsTemplate": (valueName, curValue, template) => {
// Called in helpers/ValueModification.js::ApplyValueModificationsToTemplate()
// as part of Tests T2 and T5 below.
Index: binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js
+++ binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js
@@ -2,14 +2,14 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Player.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
let player = 1;
let playerEnt = 10;
let ownedEnt = 60;
let techKey = "Attack/BigAttack";
-AddMock(playerEnt, IID_TechnologyManager, {
+AddMock(playerEnt, IID_ModificationsManager, {
"ApplyModifications": (key, val, ent) => {
if (key != techKey)
return val;
Index: binaries/data/mods/public/simulation/components/tests/test_VisionSharing.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_VisionSharing.js
+++ binaries/data/mods/public/simulation/components/tests/test_VisionSharing.js
@@ -3,6 +3,7 @@
Engine.LoadHelperScript("Commands.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/VisionSharing.js");
@@ -118,6 +119,9 @@
AddMock(14, IID_TechnologyManager, {
"CanProduce": entity => false,
+});
+
+AddMock(14, IID_ModificationsManager, {
"ApplyModificationsTemplate": (valueName, curValue, template) => curValue
});
@@ -127,8 +131,12 @@
AddMock(14, IID_TechnologyManager, {
"CanProduce": entity => entity == "special/spy",
+});
+
+AddMock(14, IID_ModificationsManager, {
"ApplyModificationsTemplate": (valueName, curValue, template) => curValue
});
+
AddMock(14, IID_Player, {
"GetSpyCostMultiplier": () => 1,
"TrySubtractResources": costs => false
Index: binaries/data/mods/public/simulation/helpers/ValueModification.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/ValueModification.js
+++ binaries/data/mods/public/simulation/helpers/ValueModification.js
@@ -3,11 +3,12 @@
function ApplyValueModificationsToEntity(tech_type, current_value, entity)
{
let value = current_value;
+
// entity can be an owned entity or a player entity.
- let cmpTechnologyManager = Engine.QueryInterface(entity, IID_Player) ?
- Engine.QueryInterface(entity, IID_TechnologyManager) : QueryOwnerInterface(entity, IID_TechnologyManager);
- if (cmpTechnologyManager)
- value = cmpTechnologyManager.ApplyModifications(tech_type, current_value, entity);
+ let cmpModificationsManager = Engine.QueryInterface(entity, IID_Player) ?
+ Engine.QueryInterface(entity, IID_ModificationsManager) : QueryOwnerInterface(entity, IID_ModificationsManager);
+ if (cmpModificationsManager)
+ value = cmpModificationsManager.ApplyModifications(tech_type, current_value, entity);
let cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager);
if (!cmpAuraManager)
@@ -18,9 +19,9 @@
function ApplyValueModificationsToTemplate(tech_type, current_value, playerID, template)
{
let value = current_value;
- let cmpTechnologyManager = QueryPlayerIDInterface(playerID, IID_TechnologyManager);
- if (cmpTechnologyManager)
- value = cmpTechnologyManager.ApplyModificationsTemplate(tech_type, current_value, template);
+ let cmpModificationsManager = QueryPlayerIDInterface(playerID, IID_ModificationsManager);
+ if (cmpModificationsManager)
+ value = cmpModificationsManager.ApplyModificationsTemplate(tech_type, current_value, template);
let cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager);
if (!cmpAuraManager)
Index: binaries/data/mods/public/simulation/templates/special/player.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/special/player.xml
+++ binaries/data/mods/public/simulation/templates/special/player.xml
@@ -34,9 +34,6 @@
2
2
5
- 4
- 2
- 1
@@ -59,8 +56,8 @@
Player
Player
- true
+
Index: binaries/data/mods/public/simulation/templates/special/player/player.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/special/player/player.xml
+++ binaries/data/mods/public/simulation/templates/special/player/player.xml
@@ -61,6 +61,7 @@
Player
true
+