Index: binaries/data/mods/public/simulation/components/ModificationsManager.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/ModificationsManager.js
@@ -0,0 +1,418 @@
+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.globalModifications = new Map(); // Keyed by property name.
+ this.localModifications = 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.
+};
+
+/**
+ * Wrapper for AddGlobalModification, to set multiple modifications with the same ID
+ */
+ModificationsManager.prototype.AddGlobalModifications = function(modificationID, modificationArray)
+{
+ for (let propertyName in modificationArray)
+ this.AddGlobalModification(propertyName, modificationID, modificationArray[propertyName]);
+};
+
+/**
+ * Add a global modification.
+ * @param propertyName - Handle of a technology property (eg "Attack/Ranged/Pierce")
+ * @param modificationID - internal ID of this modification, for later removal and/or updating
+ * @param modification - Object describing the change.
+ */
+ModificationsManager.prototype.AddGlobalModification = function(propertyName, modificationID, modification)
+{
+ if (!this.globalModifications.get(propertyName))
+ this.globalModifications.set(propertyName, []);
+
+ if (this._AddModification(this.globalModifications.get(propertyName), propertyName, modificationID, modification))
+ this._InvalidateCache(propertyName);
+
+ this.SendGlobalModificationMessages(propertyName);
+};
+
+/**
+ * Wrapper for AddLocalModification, to set multiple modifications with the same ID
+ */
+ModificationsManager.prototype.AddLocalModifications = function(entity, modificationID, modificationArray)
+{
+ for (let propertyName in modificationArray)
+ this.AddLocalModification(entity, propertyName, modificationID, modificationArray[propertyName]);
+};
+
+/**
+ * Add a local modification. Local modifications are applied to a specific entity.
+ * @param entity - ID of the target entity
+ * @param propertyName - Handle of a technology property (eg "Attack/Ranged/Pierce")
+ * @param modificationID - internal ID of this modification, for later removal and/or updating
+ * @param modification - Object describing the change.
+ */
+ModificationsManager.prototype.AddLocalModification = function(entity, propertyName, modificationID, modification)
+{
+ if (!this.localModifications.get(propertyName))
+ this.localModifications.set(propertyName, new Map());
+ if (!this.localModifications.get(propertyName).get(entity))
+ this.localModifications.get(propertyName).set(entity, []);
+
+ if (this._AddModification(this.localModifications.get(propertyName).get(entity), propertyName, modificationID, modification))
+ this._InvalidateCache(propertyName, entity);
+
+ this.SendLocalModificationMessages(entity, propertyName);
+};
+
+
+/**
+ * Wrapper for RemoveGlobalModification, to remove any modification with this ID
+ * Not extremely efficient but that should be fine in general.
+ * @param modificationID - internal ID of the modification to remove
+ */
+ModificationsManager.prototype.RemoveGlobalModifications = function(modificationID)
+{
+ for (let propertyName of this.globalModifications.keys())
+ this.RemoveGlobalModification(propertyName, modificationID);
+};
+
+ModificationsManager.prototype.RemoveGlobalModification = function(propertyName, modificationID)
+{
+ if (!this.globalModifications.get(propertyName))
+ return;
+
+ if (this._RemoveModification(propertyName, modificationID))
+ this._InvalidateCache(propertyName);
+
+ this.SendGlobalModificationMessages(propertyName);
+};
+
+ModificationsManager.prototype.RemoveLocalModifications = function(entity, modificationID)
+{
+ for (let propertyName of this.localModifications.keys())
+ this.RemoveLocalModification(entity, propertyName, modificationID);
+};
+
+ModificationsManager.prototype.RemoveLocalModification = function(entity, propertyName, modificationID)
+{
+ if (!this.localModifications.has(propertyName))
+ return;
+ if (!this.localModifications.get(propertyName).has(entity))
+ return;
+
+ if (this._RemoveModification(propertyName, modificationID, entity))
+ this._InvalidateCache(propertyName, entity);
+
+ this.SendLocalModificationMessages(entity, propertyName);
+};
+
+/**
+ * Wrapper for HasGlobalModification. Probably rather inefficient as it checks all property names we have.
+ */
+ModificationsManager.prototype.HasAnyGlobalModificationWithID = function(modificationID)
+{
+ // Map doesn't implement someā¦
+ for (let propertyName of this.globalModifications.keys())
+ if (this.HasGlobalModification(propertyName, modificationID))
+ return true;
+ return false;
+};
+
+/**
+ * Check if we have a global modification.
+ * @param propertyName - Handle of a technology property (eg "Attack/Ranged/Pierce")
+ * @param modificationID - internal ID of the modification to try and find.
+ * @returns true if there is at least one modification with that modificationID
+ */
+ModificationsManager.prototype.HasGlobalModification = function(propertyName, modificationID)
+{
+ if (!this.globalModifications.get(propertyName))
+ return false;
+
+ return this._HasModification(this.globalModifications.get(propertyName), modificationID);
+};
+
+ModificationsManager.prototype.HasLocalModification = function(entity, propertyName, modificationID)
+{
+ if (!this.localModifications.get(propertyName))
+ return false;
+ if (!this.localModifications.get(propertyName).get(entity))
+ return false;
+
+ return this._HasModification(this.localModifications.get(propertyName).get(entity), modificationID);
+};
+
+/**
+ * Internal use. Call HasLocalModification or HasGlobalModification instead of this helper
+ * @param modificationsList - the internal object for the modifications. See callers.
+ * @param modificationID - internal ID of the modification to add.
+ * @returns true if the modificationsList contains the modification ID for this property name.
+ */
+ModificationsManager.prototype._HasModification = function(modificationsList, modificationID)
+{
+ return modificationsList.some(modification => { return modification.ID === modificationID; });
+};
+
+/**
+ * Internal use. Call AddLocalModification or AddGlobalModification instead of this helper
+ * @param modificationsList - the internal object for the modifications. See callers.
+ * @param propertyName - Handle of a technology property (eg Attack/Ranged/Pierce)
+ * @param modificationID - internal ID of the modification to add.
+ * @param modification - new modification
+ * @returns true if the modifications list changed, i.e. the cache is invalidated. Returns false otherwise.
+ */
+ModificationsManager.prototype._AddModification = function(modificationsList, propertyName, modificationID, modification)
+{
+ // Check whether we already have that modification
+ let existing = modificationsList.filter(modif => { return modif.ID == modificationID; });
+
+ if (existing.length > 1)
+ {
+ error("Two modifications of " + propertyName + " have the same modificationID " + modificationID);
+ return false;
+ }
+
+ // 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.
+ if (existing.length)
+ {
+ existing[0].count++;
+ return false;
+ }
+
+ // Create a new modification at the end.
+ modificationsList.push({ "ID": modificationID, "count": 1, "effect": modification });
+ return true;
+};
+
+/**
+ * Internal use. Call RemoveLocalModification or RemoveGlobalModification instead of this helper
+ * @returns true if the modifications list changed, i.e. the cache is invalidated. Returns false otherwise.
+ */
+ModificationsManager.prototype._RemoveModification = function(propertyName, modificationID, entity = undefined)
+{
+ let modificationsList = entity === undefined ? this.globalModifications.get(propertyName) : this.localModifications.get(propertyName).get(entity);
+
+ let modification = modificationsList.filter(modification => { return modification.ID == modificationID; });
+ if (!modification.length)
+ return false;
+
+ // If we still want to apply this modification, no changes to the cache (no stacking) so return false.
+ if (--modification[0].count > 0)
+ return false;
+
+ modificationsList = modificationsList.filter(modification => { return modification.count > 0; });
+
+ // Update the map, deleting entries if necessary
+ if (!entity)
+ {
+ if (modificationsList.length)
+ this.globalModifications.set(propertyName, modificationsList);
+ else
+ this.globalModifications.delete(propertyName);
+ }
+ else
+ {
+ if (modificationsList.length)
+ this.localModifications.get(propertyName).set(entity, modificationsList);
+ else
+ {
+ this.localModifications.get(propertyName).delete(entity);
+ if (!this.localModifications.get(propertyName).length)
+ this.localModifications.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.
+ */
+ModificationsManager.prototype.SendGlobalModificationMessages = function(propertyName)
+{
+ 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] });
+};
+
+/**
+ * Inform entities that we have changed a value from a specific entity
+ * It's not hugely efficient and would be nice to batch.
+ * @param entity - ID of the target entity
+ * @param propertyName - Handle of a technology property (eg "Attack/Ranged/Pierce") that was changed.
+ */
+ModificationsManager.prototype.SendLocalModificationMessages = function(entity, propertyName)
+{
+ // 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] });
+};
+
+/**
+ * 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 origValue - 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 origValue after the modifications
+ */
+ModificationsManager.prototype.ApplyModifications = function(propertyName, origValue, ent)
+{
+ let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
+ if (!cmpIdentity)
+ return origValue;
+
+ let originalValue = origValue;
+ // Some code bits pass an array instead of a single value
+ // Those bits should probably be changed, but it is supported in the meantime.
+ if (Array.isArray(origValue))
+ value = origValue.slice();
+ else if (typeof origValue == "object")
+ {
+ error("ModificationsManager ApplyModifications() called with an object instead of a number.");
+ return undefined;
+ }
+
+ if (this.cachedValues.has(propertyName) &&
+ this.cachedValues.get(propertyName).has(ent) &&
+ this.cachedValues.get(propertyName).get(ent).has(originalValue))
+ return this.cachedValues.get(propertyName).get(ent).get(originalValue);
+
+ // Initialise the cache
+ if (!this.cachedValues.get(propertyName))
+ this.cachedValues.set(propertyName, new Map());
+ if (!this.cachedValues.get(propertyName).get(ent))
+ this.cachedValues.get(propertyName).set(ent, new Map());
+ if (!this.cachedValues.get(propertyName).get(ent).get(originalValue))
+ this.cachedValues.get(propertyName).get(ent).set(originalValue, originalValue);
+
+ let entityCache = this.cachedValues.get(propertyName).get(ent);
+ let temporaryValue = entityCache.get(originalValue);
+
+ // Apply global modifications first, local modifications are treated as higher priority.
+ if (this.globalModifications.has(propertyName))
+ temporaryValue = this._FetchModifiedProperty(this.globalModifications.get(propertyName), cmpIdentity.GetClassesList(), propertyName, temporaryValue);
+
+ if (this.localModifications.has(propertyName) && this.localModifications.get(propertyName).has(ent))
+ temporaryValue = this._FetchModifiedProperty(this.localModifications.get(propertyName).get(ent), cmpIdentity.GetClassesList(), propertyName, temporaryValue);
+
+ entityCache.set(originalValue, temporaryValue);
+ return temporaryValue;
+};
+
+/**
+ * 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.globalModifications.has(propertyName))
+ return originalValue;
+
+ return this._FetchModifiedProperty(this.globalModifications.get(propertyName), GetIdentityClasses(template.Identity), propertyName, originalValue);
+};
+
+/**
+ * Internal use only.
+ * @returns referenceValue after modifications
+ */
+ModificationsManager.prototype._FetchModifiedProperty = function(modificationsList, classesList, propertyName, referenceValue)
+{
+ let modifications = { [propertyName]: [] };
+ for (let modification of modificationsList)
+ modifications[propertyName].push(modification.effect);
+
+ return GetTechModifiedProperty(modifications, classesList, propertyName, referenceValue);
+};
+
+/**
+ * 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.globalModifications)
+ {
+ // 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.AddGlobalModification(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,91 @@
+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,
+});
+// create ownership, set to "1" for any entity used below,
+// otherwise QueryOwnerInterface fails
+let entitiesToTest = [5, 6, 7, 8];
+for (let ent of entitiesToTest)
+ AddMock(ent, IID_Ownership, {
+ "GetOwner": () => 1
+ });
+
+// hack
+
+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.AddGlobalModification("Test_A", "Test_A_0", { "affects": ["Structure"], "add": 10 });
+cmpModificationsManager.AddGlobalModification("Test_A", "Test_A_1", { "affects": ["Infantry"], "add": 5 });
+cmpModificationsManager.AddGlobalModification("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.RemoveGlobalModifications("Test_A_0");
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 5);
+
+cmpModificationsManager.AddGlobalModifications("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.AddLocalModification(5, "Test_C", "Test_C_0", { "affects": ["Structure"], "add": 10 });
+cmpModificationsManager.AddLocalModification(5, "Test_C", "Test_C_1", { "affects": ["Unit"], "add": 5 });
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 15);
+
+// test that local modifications are indeed applied after global managers
+cmpModificationsManager.AddLocalModification(5, "Test_C", "Test_C_2", { "affects": ["Structure"], "replace": 0 });
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0);
+
+TS_ASSERT(!cmpModificationsManager.HasAnyGlobalModificationWithID("Test_C_3"));
+
+// check that things still work properly if we change global modifications
+cmpModificationsManager.AddGlobalModification("Test_C", "Test_C_3", { "affects": ["Structure"], "add": 10 });
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0);
+
+TS_ASSERT(cmpModificationsManager.HasAnyGlobalModificationWithID("Test_C_3"));
+TS_ASSERT(cmpModificationsManager.HasGlobalModification("Test_C", "Test_C_3"));
+TS_ASSERT(cmpModificationsManager.HasLocalModification(5, "Test_C", "Test_C_2"));
+
+// test removal
+cmpModificationsManager.RemoveLocalModification(5, "Test_C", "Test_C_2");
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 25);
+
+TS_ASSERT(cmpModificationsManager.HasGlobalModification("Test_C", "Test_C_3"));
+TS_ASSERT(!cmpModificationsManager.HasLocalModification(5, "Test_C", "Test_C_2"));
+
+// 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
+