Index: binaries/data/mods/public/globalscripts/Technologies.js
===================================================================
--- binaries/data/mods/public/globalscripts/Technologies.js
+++ binaries/data/mods/public/globalscripts/Technologies.js
@@ -9,17 +9,13 @@
* Returns modified property value modified by the applicable tech
* modifications.
*
- * @param currentTechModifications Object with mapping of property names to
- * modification arrays, retrieved from the intended player's TechnologyManager.
- * @param classes Array contianing the class list of the template.
- * @param propertyName String encoding the name of the value.
- * @param propertyValue Number storing the original value. Can also be
+ * @param currentTechModifications array of modificiations
+ * @param classes Array containing the class list of the template.
+ * @param originalValue Number storing the original value. Can also be
* non-numberic, but then only "replace" techs can be supported.
*/
-function GetTechModifiedProperty(currentTechModifications, classes, propertyName, propertyValue)
+function GetTechModifiedProperty(modifications, classes, originalValue)
{
- let modifications = currentTechModifications[propertyName] || [];
-
let multiply = 1;
let add = 0;
@@ -34,13 +30,13 @@
else if (modification.add)
add += modification.add;
else
- warn("GetTechModifiedProperty: modification format not recognised (modifying " + propertyName + "): " + uneval(modification));
+ warn("GetTechModifiedProperty: modification format not recognised : " + uneval(modification));
}
// Note, some components pass non-numeric values (for which only the "replace" modification makes sense)
- if (typeof propertyValue == "number")
- return propertyValue * multiply + add;
- return propertyValue;
+ if (typeof originalValue == "number")
+ return originalValue * multiply + add;
+ return originalValue;
}
/**
Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js
+++ binaries/data/mods/public/globalscripts/Templates.js
@@ -128,8 +128,8 @@
if (player)
current_value = ApplyValueModificationsToTemplate(mod_key, current_value, player, template);
- else if (modifiers)
- current_value = GetTechModifiedProperty(modifiers, GetIdentityClasses(template.Identity), mod_key, current_value);
+ else if (modifiers && modifiers[mod_key])
+ current_value = GetTechModifiedProperty(modifiers[mod_key], GetIdentityClasses(template.Identity), current_value);
// Using .toFixed() to get around spidermonkey's treatment of numbers (3 * 1.1 = 3.3000000000000003 for instance).
return +current_value.toFixed(8);
Index: binaries/data/mods/public/simulation/components/AuraManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/AuraManager.js
+++ /dev/null
@@ -1,275 +0,0 @@
-function AuraManager() {}
-
-AuraManager.prototype.Schema =
- "";
-
-AuraManager.prototype.Init = function()
-{
- this.modificationsCache = new Map();
- this.modifications = new Map();
- this.templateModificationsCache = new Map();
- this.templateModifications = new Map();
-
- this.globalAuraSources = [];
-};
-
-AuraManager.prototype.RegisterGlobalAuraSource = function(ent)
-{
- if (this.globalAuraSources.indexOf(ent) == -1)
- this.globalAuraSources.push(ent);
-};
-
-AuraManager.prototype.UnregisterGlobalAuraSource = function(ent)
-{
- let idx = this.globalAuraSources.indexOf(ent);
- if (idx != -1)
- this.globalAuraSources.splice(idx, 1);
-};
-
-AuraManager.prototype.ensureExists = function(name, value, id, key, defaultData)
-{
- var cacheName = name + "Cache";
- var v = this[name].get(value);
- if (!v)
- {
- v = new Map();
- this[name].set(value, v);
- this[cacheName].set(value, new Map());
- }
-
- var i = v.get(id);
- if (!i)
- {
- i = new Map();
- v.set(id, i);
- this[cacheName].get(value).set(id, defaultData);
- }
-
- var k = i.get(key);
- if (!k)
- {
- k = {};
- i.set(key, k);
- }
- return k;
-};
-
-AuraManager.prototype.ApplyBonus = function(value, ents, newData, key)
-{
- for (let ent of ents)
- {
- var data = this.ensureExists("modifications", value, ent, key, { "add":0, "multiply":1 });
-
- if (data.count)
- {
- // this aura is already applied and the bonus shouldn't be given twice,
- // just count the number of times it is applied
- data.count++;
- continue;
- }
-
- // first time added this aura
- data.multiply = newData.multiply;
- data.add = newData.add;
- data.count = 1;
-
- if (data.add)
- this.modificationsCache.get(value).get(ent).add += data.add;
- if (data.multiply)
- this.modificationsCache.get(value).get(ent).multiply *= data.multiply;
-
- // post message to the entity to notify it about the change
- Engine.PostMessage(ent, MT_ValueModification, {
- "entities": [ent],
- "component": value.split("/")[0],
- "valueNames": [value]
- });
- }
-};
-
-AuraManager.prototype.ApplyTemplateBonus = function(value, player, classes, newData, key)
-{
- var data = this.ensureExists("templateModifications", value, player, key, new Map());
-
- if (data.count)
- {
- // this aura is already applied and the bonus shouldn't be given twice,
- // just count the number of times it is applied
- data.count++;
- return;
- }
-
- // first time added this aura
- data.multiply = newData.multiply;
- data.add = newData.add;
- data.count = 1;
-
- let cache = this.templateModificationsCache.get(value).get(player);
-
- // Do not use the classes array from the JSON file directly, since that is not synchronized
- // See MatchesClassList for supported classes formats
- for (let className of classes)
- {
- if (Array.isArray(className))
- className = className.join("+");
-
- if (!cache.get(className))
- cache.set(className, new Map());
-
- if (!cache.get(className).get(key))
- cache.get(className).set(key, { "add": 0, "multiply": 1 });
-
- if (data.add)
- cache.get(className).get(key).add += data.add;
- if (data.multiply)
- cache.get(className).get(key).multiply *= data.multiply;
- }
-
- Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, {
- "player": player,
- "component": value.split("/")[0],
- "valueNames": [value]
- });
-};
-
-AuraManager.prototype.RemoveBonus = function(value, ents, key)
-{
- var v = this.modifications.get(value);
- if (!v)
- return;
-
- for (let ent of ents)
- {
- var e = v.get(ent);
- if (!e)
- continue;
- var data = e.get(key);
- if (!data || !data.count)
- continue;
-
- data.count--;
-
- if (data.count > 0)
- continue;
-
- // out of last aura of this kind, remove modifications
- if (data.add)
- this.modificationsCache.get(value).get(ent).add -= data.add;
-
- if (data.multiply)
- this.modificationsCache.get(value).get(ent).multiply /= data.multiply;
-
- // clean up the object
- e.delete(key);
- if (e.size == 0)
- v.delete(ent);
-
- // post message to the entity to notify it about the change
- Engine.PostMessage(ent, MT_ValueModification, {
- "entities": [ent],
- "component": value.split("/")[0],
- "valueNames": [value]
- });
- }
-};
-
-AuraManager.prototype.RemoveTemplateBonus = function(value, player, classes, key)
-{
- var v = this.templateModifications.get(value);
- if (!v)
- return;
- var p = v.get(player);
- if (!p)
- return;
- var data = p.get(key);
- if (!data || !data.count)
- return;
-
- data.count--;
-
- if (data.count > 0)
- return;
-
- for (let className of classes)
- {
- if (Array.isArray(className))
- className = className.join("+");
-
- this.templateModificationsCache.get(value).get(player).get(className).delete(key);
-
- if (this.templateModificationsCache.get(value).get(player).get(className).size == 0)
- this.templateModificationsCache.get(value).get(player).delete(className);
- }
-
- // clean up the object
- p.delete(key);
- if (p.size == 0)
- v.delete(player);
-
- Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, {
- "player": player,
- "component": value.split("/")[0],
- "valueNames": [value]
- });
-};
-
-AuraManager.prototype.ApplyModifications = function(valueName, value, ent)
-{
- var v = this.modificationsCache.get(valueName);
- if (!v)
- return value;
- var cache = v.get(ent);
- if (!cache)
- return value;
-
- value *= cache.multiply;
- value += cache.add;
- return value;
-};
-
-AuraManager.prototype.ApplyTemplateModifications = function(valueName, value, player, template)
-{
- var v = this.templateModificationsCache.get(valueName);
- if (!v)
- return value;
- var cache = v.get(player);
- if (!cache)
- return value;
-
- if (!template || !template.Identity)
- return value;
- var classes = GetIdentityClasses(template.Identity);
-
- var usedKeys = new Set();
- var add = 0;
- var multiply = 1;
-
- for (let [className, mods] of cache)
- {
- if (!MatchesClassList(classes, [className]))
- continue;
-
- for (let [key, mod] of mods)
- {
- // don't add an aura with the same key twice
- if (usedKeys.has(key))
- continue;
- add += mod.add;
- multiply *= mod.multiply;
- usedKeys.add(key);
- }
- }
- return value * multiply + add;
-};
-
-AuraManager.prototype.OnGlobalOwnershipChanged = function(msg)
-{
- for (let ent of this.globalAuraSources)
- {
- let cmpAuras = Engine.QueryInterface(ent, IID_Auras);
- if (cmpAuras)
- cmpAuras.RegisterGlobalOwnershipChanged(msg);
- }
-};
-
-Engine.RegisterSystemComponentType(IID_AuraManager, "AuraManager", AuraManager);
Index: binaries/data/mods/public/simulation/components/Auras.js
===================================================================
--- binaries/data/mods/public/simulation/components/Auras.js
+++ binaries/data/mods/public/simulation/components/Auras.js
@@ -22,8 +22,8 @@
Auras.prototype.GetModifierIdentifier = function(name)
{
if (AuraTemplates.Get(name).stackable)
- return name + this.entity;
- return name;
+ return "aura/" + name + this.entity;
+ return "aura/" + name;
};
Auras.prototype.GetDescriptions = function()
@@ -218,9 +218,9 @@
targetUnitsClone[name] = this[name].targetUnits.slice();
if (this.IsGlobalAura(name))
- this.RemoveTemplateBonus(name);
+ this.RemoveTemplateAura(name);
- this.RemoveBonus(name, this[name].targetUnits);
+ this.RemoveAura(name, this[name].targetUnits, this.IsGlobalAura(name));
if (this[name].rangeQuery)
cmpRangeManager.DestroyActiveQuery(this[name].rangeQuery);
@@ -242,36 +242,38 @@
if (this.IsGlobalAura(name))
{
- this.ApplyTemplateBonus(name, affectedPlayers);
- for (let player of affectedPlayers)
- this.ApplyBonus(name, cmpRangeManager.GetEntitiesByPlayer(player));
+ this.ApplyTemplateAura(name, affectedPlayers);
+ if (this.GetOverlayIcon(name))
+ for (let player of affectedPlayers)
+ // Only apply icons since modifications are applied in ApplyTemplateAura above.
+ this.ApplyAura(name, cmpRangeManager.GetEntitiesByPlayer(player), true);
continue;
}
if (this.IsPlayerAura(name))
{
let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
- this.ApplyBonus(name, affectedPlayers.map(p => cmpPlayerManager.GetPlayerByID(p)));
+ this.ApplyAura(name, affectedPlayers.map(p => cmpPlayerManager.GetPlayerByID(p)));
continue;
}
if (!this.IsRangeAura(name))
{
- this.ApplyBonus(name, targetUnitsClone[name]);
+ this.ApplyAura(name, targetUnitsClone[name]);
continue;
}
needVisualizationUpdate = true;
- if (this[name].isApplied)
+ if (this[name].isApplied && (this.IsRangeAura(name) || this.IsGlobalAura(name) && !!this.GetOverlayIcon(name)))
{
this[name].rangeQuery = cmpRangeManager.CreateActiveQuery(
- this.entity,
- 0,
- this.GetRange(name),
- affectedPlayers,
- IID_Identity,
- cmpRangeManager.GetEntityFlagMask("normal")
+ this.entity,
+ 0,
+ this.GetRange(name),
+ affectedPlayers,
+ IID_Identity,
+ cmpRangeManager.GetEntityFlagMask("normal")
);
cmpRangeManager.EnableActiveQuery(this[name].rangeQuery);
}
@@ -301,8 +303,8 @@
{
for (let name of this.GetAuraNames().filter(n => this[n] && msg.tag == this[n].rangeQuery))
{
- this.ApplyBonus(name, msg.added);
- this.RemoveBonus(name, msg.removed);
+ this.ApplyAura(name, msg.added);
+ this.RemoveAura(name, msg.removed);
}
};
@@ -310,87 +312,86 @@
{
for (let name of this.GetAuraNames().filter(n => this.IsGarrisonedUnitsAura(n)))
{
- this.ApplyBonus(name, msg.added);
- this.RemoveBonus(name, msg.removed);
+ this.ApplyAura(name, msg.added);
+ this.RemoveAura(name, msg.removed);
}
};
-Auras.prototype.RegisterGlobalOwnershipChanged = function(msg)
-{
- for (let name of this.GetAuraNames().filter(n => this.IsGlobalAura(n)))
- {
- let affectedPlayers = this.GetAffectedPlayers(name);
- let wasApplied = affectedPlayers.indexOf(msg.from) != -1;
- let willBeApplied = affectedPlayers.indexOf(msg.to) != -1;
- if (wasApplied && !willBeApplied)
- this.RemoveBonus(name, [msg.entity]);
- if (willBeApplied && !wasApplied)
- this.ApplyBonus(name, [msg.entity]);
- }
-};
-
-Auras.prototype.ApplyFormationBonus = function(memberList)
+Auras.prototype.ApplyFormationAura = function(memberList)
{
for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n)))
- this.ApplyBonus(name, memberList);
+ this.ApplyAura(name, memberList);
};
-Auras.prototype.ApplyGarrisonBonus = function(structure)
+Auras.prototype.ApplyGarrisonAura = function(structure)
{
for (let name of this.GetAuraNames().filter(n => this.IsGarrisonAura(n)))
- this.ApplyBonus(name, [structure]);
+ this.ApplyAura(name, [structure]);
};
-Auras.prototype.ApplyTemplateBonus = function(name, players)
+Auras.prototype.ApplyTemplateAura = function(name, players)
{
if (!this[name].isApplied)
return;
if (!this.IsGlobalAura(name))
return;
- var modifications = this.GetModifications(name);
- var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager);
- var classes = this.GetClasses(name);
- cmpAuraManager.RegisterGlobalAuraSource(this.entity);
+ let derivedModifiers = DeriveModificationsFromTech({
+ "modifications": this.GetModifications(name),
+ "affects": this.GetClasses(name)
+ });
+ let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager);
+ let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
- for (let mod of modifications)
- for (let player of players)
- cmpAuraManager.ApplyTemplateBonus(mod.value, player, classes, mod, this.GetModifierIdentifier(name));
+ let modifName = this.GetModifierIdentifier(name);
+ for (let player of players)
+ {
+ let playerId = cmpPlayerManager.GetPlayerByID(player);
+ for (let modifierPath in derivedModifiers)
+ for (let modifier of derivedModifiers[modifierPath])
+ cmpModificationsManager.AddModif(modifierPath, modifName, modifier, playerId);
+ }
};
-Auras.prototype.RemoveFormationBonus = function(memberList)
+Auras.prototype.RemoveFormationAura = function(memberList)
{
for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n)))
- this.RemoveBonus(name, memberList);
+ this.RemoveAura(name, memberList);
};
-Auras.prototype.RemoveGarrisonBonus = function(structure)
+Auras.prototype.RemoveGarrisonAura = function(structure)
{
for (let name of this.GetAuraNames().filter(n => this.IsGarrisonAura(n)))
- this.RemoveBonus(name, [structure]);
+ this.RemoveAura(name, [structure]);
};
-Auras.prototype.RemoveTemplateBonus = function(name)
+Auras.prototype.RemoveTemplateAura = function(name)
{
if (!this[name].isApplied)
return;
+
if (!this.IsGlobalAura(name))
return;
- var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager);
- cmpAuraManager.UnregisterGlobalAuraSource(this.entity);
-
- var modifications = this.GetModifications(name);
- var classes = this.GetClasses(name);
- var players = this.GetAffectedPlayers(name);
+ let derivedModifiers = DeriveModificationsFromTech({
+ "modifications": this.GetModifications(name),
+ "affects": this.GetClasses(name)
+ });
+ let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager);
+ let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
- for (let mod of modifications)
- for (let player of players)
- cmpAuraManager.RemoveTemplateBonus(mod.value, player, classes, this.GetModifierIdentifier(name));
+ let modifName = this.GetModifierIdentifier(name);
+ for (let player of this.GetAffectedPlayers(name))
+ {
+ let playerId = cmpPlayerManager.GetPlayerByID(player);
+ for (let modifierPath in derivedModifiers)
+ for (let modifier of derivedModifiers[modifierPath])
+ cmpModificationsManager.RemoveModif(modifierPath, modifName, playerId);
+ }
};
-Auras.prototype.ApplyBonus = function(name, ents)
+Auras.prototype.ApplyAura = function(name, ents, skipModifications = false)
{
var validEnts = this.GiveMembersWithValidClass(name, ents);
if (!validEnts.length)
@@ -401,24 +402,35 @@
if (!this[name].isApplied)
return;
- var modifications = this.GetModifications(name);
- var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager);
-
- for (let mod of modifications)
- cmpAuraManager.ApplyBonus(mod.value, validEnts, mod, this.GetModifierIdentifier(name));
// update status bars if this has an icon
- if (!this.GetOverlayIcon(name))
+ if (this.GetOverlayIcon(name))
+ for (let ent of validEnts)
+ {
+ let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
+ if (cmpStatusBars)
+ cmpStatusBars.AddAuraSource(this.entity, name);
+ }
+
+ // Global aura modifications are handled at the player level by the modification manager.
+ if (skipModifications)
return;
+ let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager);
+
+ let derivedModifiers = DeriveModificationsFromTech({
+ "modifications": this.GetModifications(name),
+ "affects": this.GetClasses(name)
+ });
+
+ let modifName = this.GetModifierIdentifier(name);
for (let ent of validEnts)
- {
- var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
- if (cmpStatusBars)
- cmpStatusBars.AddAuraSource(this.entity, name);
- }
+ for (let modifierPath in derivedModifiers)
+ for (let modifier of derivedModifiers[modifierPath])
+ cmpModificationsManager.AddModif(modifierPath, modifName, modifier, ent);
+
};
-Auras.prototype.RemoveBonus = function(name, ents)
+Auras.prototype.RemoveAura = function(name, ents, skipModifications = false)
{
var validEnts = this.GiveMembersWithValidClass(name, ents);
if (!validEnts.length)
@@ -429,22 +441,31 @@
if (!this[name].isApplied)
return;
- var modifications = this.GetModifications(name);
- var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager);
-
- for (let mod of modifications)
- cmpAuraManager.RemoveBonus(mod.value, validEnts, this.GetModifierIdentifier(name));
-
// update status bars if this has an icon
- if (!this.GetOverlayIcon(name))
+ if (this.GetOverlayIcon(name))
+ for (let ent of validEnts)
+ {
+ let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
+ if (cmpStatusBars)
+ cmpStatusBars.RemoveAuraSource(this.entity, name);
+ }
+
+ // Global aura modifications are handled at the player level by the modification manager.
+ if (skipModifications)
return;
- for (let ent of validEnts)
- {
- var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
- if (cmpStatusBars)
- cmpStatusBars.RemoveAuraSource(this.entity, name);
- }
+ let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager);
+
+ let derivedModifiers = DeriveModificationsFromTech({
+ "modifications": this.GetModifications(name),
+ "affects": this.GetClasses(name)
+ });
+
+ let modifName = this.GetModifierIdentifier(name);
+ for (let ent of ents)
+ for (let modifierPath in derivedModifiers)
+ for (let modifier of derivedModifiers[modifierPath])
+ cmpModificationsManager.RemoveModif(modifierPath, modifName, ent);
};
Auras.prototype.OnOwnershipChanged = function(msg)
@@ -484,7 +505,7 @@
{
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
if (cmpPlayer && cmpPlayer.GetPlayerID() == msg.playerId ||
- this.GetAuraNames().some(name => this.GetAffectedPlayers(name).indexOf(msg.playerId) != -1))
+ this.GetAuraNames().some(name => this.GetAffectedPlayers(name).indexOf(msg.playerId) != -1))
this.Clean();
};
Index: binaries/data/mods/public/simulation/components/Formation.js
===================================================================
--- binaries/data/mods/public/simulation/components/Formation.js
+++ binaries/data/mods/public/simulation/components/Formation.js
@@ -294,7 +294,7 @@
if (cmpAuras && cmpAuras.HasFormationAura())
{
this.formationMembersWithAura.push(ent);
- cmpAuras.ApplyFormationBonus(ents);
+ cmpAuras.ApplyFormationAura(ents);
}
}
@@ -326,11 +326,11 @@
for (var ent of this.formationMembersWithAura)
{
var cmpAuras = Engine.QueryInterface(ent, IID_Auras);
- cmpAuras.RemoveFormationBonus(ents);
+ cmpAuras.RemoveFormationAura(ents);
// the unit with the aura is also removed from the formation
if (ents.indexOf(ent) !== -1)
- cmpAuras.RemoveFormationBonus(this.members);
+ cmpAuras.RemoveFormationAura(this.members);
}
this.formationMembersWithAura = this.formationMembersWithAura.filter(function(e) { return ents.indexOf(e) == -1; });
@@ -359,7 +359,7 @@
for (let ent of this.formationMembersWithAura)
{
let cmpAuras = Engine.QueryInterface(ent, IID_Auras);
- cmpAuras.ApplyFormationBonus(ents);
+ cmpAuras.ApplyFormationAura(ents);
}
this.members = this.members.concat(ents);
@@ -373,7 +373,7 @@
if (cmpAuras && cmpAuras.HasFormationAura())
{
this.formationMembersWithAura.push(ent);
- cmpAuras.ApplyFormationBonus(this.members);
+ cmpAuras.ApplyFormationAura(this.members);
}
}
@@ -413,7 +413,7 @@
for (var ent of this.formationMembersWithAura)
{
var cmpAuras = Engine.QueryInterface(ent, IID_Auras);
- cmpAuras.RemoveFormationBonus(this.members);
+ cmpAuras.RemoveFormationAura(this.members);
}
Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js
@@ -266,7 +266,7 @@
let cmpAura = Engine.QueryInterface(entity, IID_Auras);
if (cmpAura && cmpAura.HasGarrisonAura())
- cmpAura.ApplyGarrisonBonus(this.entity);
+ cmpAura.ApplyGarrisonAura(this.entity);
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added": [entity], "removed": [] });
return true;
@@ -336,7 +336,7 @@
let cmpEntAura = Engine.QueryInterface(entity, IID_Auras);
if (cmpEntAura && cmpEntAura.HasGarrisonAura())
- cmpEntAura.RemoveGarrisonBonus(this.entity);
+ cmpEntAura.RemoveGarrisonAura(this.entity);
cmpEntPosition.JumpTo(pos.x, pos.z);
cmpEntPosition.SetHeightOffset(0);
Index: binaries/data/mods/public/simulation/components/ModificationsManager.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/ModificationsManager.js
@@ -0,0 +1,304 @@
+function ModificationsManager() {}
+
+ModificationsManager.prototype.Schema =
+ "";
+
+ModificationsManager.prototype.Init = function()
+{
+ // TODO:
+ // - add a way to show an icon for a given modification ID
+ // > Note that aura code shows icons when the source is selected, so that's specific to them.
+ // - support stacking modifiers (MultiKeyMap handles it but not this manager).
+
+ // 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.
+
+ // When changing global modifications, all entity-local caches are invalidated. This helps with that.
+ // TODO: it might be worth keying by classes here.
+ this.playerEntitiesCached = new Map(); // Keyed by player ID, property name, entity ID.
+
+ this.modifsStorage = new MultiKeyMap(); // Keyed by property name, entity.
+
+ this.modifsStorage._OnItemModified = (prim, sec, itemID) => this.ModificationsChanged.apply(this, [prim, sec, itemID]);
+};
+
+ModificationsManager.prototype.Serialize = function()
+{
+ // The value cache will be affected by property reads from the GUI and other places so we shouldn't serialize it.
+ // Furthermore it is cyclically self-referencing.
+ // We need to store the player for the Player-Entities cache.
+ let players = [];
+ this.playerEntitiesCached.forEach((_, player) => players.push(player));
+ return {
+ "modifsStorage": this.modifsStorage.Serialize(),
+ "players": players
+ };
+};
+
+ModificationsManager.prototype.Deserialize = function(data)
+{
+ this.Init();
+ this.modifsStorage.Deserialize(data.modifsStorage);
+ data.players.forEach(player => this.playerEntitiesCached.set(player, new Map()));
+};
+
+/**
+ * Inform entities that we have changed possibly all values affected by that property.
+ * It's not hugely efficient and would be nice to batch.
+ * Invalidate caches where relevant.
+ */
+ModificationsManager.prototype.ModificationsChanged = function(propertyName, entity)
+{
+ this.InvalidateCache(propertyName, entity);
+
+ let cmpPlayer = Engine.QueryInterface(entity, IID_Player);
+ if (cmpPlayer)
+ this.SendPlayerModificationMessages(propertyName, cmpPlayer.GetPlayerID());
+ else
+ this.SendEntityModificationMessages(propertyName, entity);
+};
+
+ModificationsManager.prototype.SendEntityModificationMessages = function(propertyName, entity)
+{
+ Engine.PostMessage(entity, MT_ValueModification, { "entities": [entity], "component": propertyName.split("/")[0], "valueNames": [propertyName] });
+};
+
+ModificationsManager.prototype.SendPlayerModificationMessages = function(propertyName, player)
+{
+ // 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": player, "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(player);
+ Engine.BroadcastMessage(MT_ValueModification, { "entities": ents, "component": propertyName.split("/")[0], "valueNames": [propertyName] });
+};
+
+ModificationsManager.prototype.InvalidatePlayerEntCache = function(propertyName, entity)
+{
+ if (!this.cachedValues.has(propertyName))
+ return;
+
+ if (!this.playerEntitiesCached.has(entity))
+ return;
+
+ if (!this.playerEntitiesCached.get(entity).has(propertyName))
+ return;
+
+ // Invalidate all local caches directly (for simplicity in ApplyModifications).
+ let entsMap = this.playerEntitiesCached.get(entity).get(propertyName);
+ entsMap.forEach(ent => this.cachedValues.get(propertyName).delete(ent));
+ entsMap.clear();
+};
+
+ModificationsManager.prototype.InvalidateCache = function(propertyName, entity)
+{
+ this.InvalidatePlayerEntCache(propertyName, entity);
+
+ if (!this.cachedValues.has(propertyName))
+ return;
+ if (this.cachedValues.get(propertyName).delete(entity))
+ if (!this.cachedValues.get(propertyName).size)
+ this.cachedValues.delete(propertyName);
+};
+
+/**
+ * @returns originalValue after modifications.
+ */
+ModificationsManager.prototype.FetchModifiedProperty = function(classesList, propertyName, originalValue, target)
+{
+ return GetTechModifiedProperty(this.modifsStorage.GetItems(propertyName, target), classesList, originalValue);
+};
+
+ModificationsManager.prototype.GetCached = function(propertyName, originalValue, entity)
+{
+ if (!this.cachedValues.has(propertyName))
+ return null;
+ if (!this.cachedValues.get(propertyName).has(entity))
+ return null;
+ if (!this.cachedValues.get(propertyName).get(entity).has(originalValue))
+ return null;
+
+ return this.cachedValues.get(propertyName).get(entity).get(originalValue);
+};
+
+/**
+ * @returns originalValue after modifications
+ */
+ModificationsManager.prototype.Cache = function(classesList, propertyName, originalValue, entity)
+{
+ // Initialise the cache if necessary.
+ if (!this.cachedValues.has(propertyName))
+ this.cachedValues.set(propertyName, new Map());
+ if (!this.cachedValues.get(propertyName).has(entity))
+ this.cachedValues.get(propertyName).set(entity, new Map());
+
+ let value = this.FetchModifiedProperty(classesList, propertyName, originalValue, entity);
+ this.cachedValues.get(propertyName).get(entity).set(originalValue, value);
+ return value;
+};
+
+/**
+ * Caching system in front of FetchModifiedProperty(), as calling that every time is quite slow.
+ * This recomputes lazily.
+ * Applies per-player modifications before per-entity modifications, so the latter take priority;
+ * @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;
+ }
+
+ let newValue = this.GetCached(propertyName, originalValue, entity);
+ if (newValue !== null)
+ return newValue;
+
+ // Get the entity ID of the player / owner of the entity, since we use that to store per-player modifications
+ // (this prevents conflicts between player ID and entity ID).
+ let ownerEntity = QueryOwnerEntityID(entity);
+ if (ownerEntity == entity)
+ ownerEntity = null;
+
+ newValue = originalValue;
+
+ // Apply player-wide modifications before entity-local modifications.
+ if (ownerEntity)
+ {
+ if (!this.playerEntitiesCached.get(ownerEntity).has(propertyName))
+ this.playerEntitiesCached.get(ownerEntity).set(propertyName, new Set());
+ this.playerEntitiesCached.get(ownerEntity).get(propertyName).add(entity);
+ newValue = this.FetchModifiedProperty(cmpIdentity.GetClassesList(), propertyName, newValue, ownerEntity);
+ }
+ newValue = this.Cache(cmpIdentity.GetClassesList(), propertyName, newValue, entity);
+
+ 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, player)
+{
+ if (!template || !template.Identity)
+ return originalValue;
+
+ let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ return this.FetchModifiedProperty(GetIdentityClasses(template.Identity), propertyName, originalValue, cmpPlayerManager.GetPlayerByID(player));
+};
+
+/**
+ * For efficiency in InvalidateCache, keep playerEntitiesCached updated.
+ */
+ModificationsManager.prototype.OnGlobalPlayerEntityChanged = function(msg)
+{
+ if (msg.to != INVALID_PLAYER && !this.playerEntitiesCached.has(msg.to))
+ this.playerEntitiesCached.set(msg.to, new Map());
+
+ if (msg.from != INVALID_PLAYER && this.playerEntitiesCached.has(msg.from))
+ {
+ this.playerEntitiesCached.get(msg.from).forEach(propName => this.InvalidateCache(propName, msg.from));
+ this.playerEntitiesCached.delete(msg.from);
+ }
+};
+
+/**
+ * Handle modifications when an entity changes owner.
+ * We do not retain the original modifications for now.
+ */
+ModificationsManager.prototype.OnGlobalOwnershipChanged = function(msg)
+{
+ if (msg.to == INVALID_PLAYER)
+ return;
+
+ // Invalidate all caches.
+ for (let propName of this.cachedValues.keys())
+ this.InvalidateCache(propName, msg.entity);
+
+ let owner = QueryOwnerEntityID(msg.entity);
+ if (!owner)
+ return;
+
+ let cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
+ if (!cmpIdentity)
+ return;
+
+ let classes = cmpIdentity.GetClassesList();
+
+ // Warn entities that our values have changed.
+ // Local modifications will be added by the relevant components, so no need to check for them here.
+ let modifiedComponents = {};
+ let playerModifs = this.modifsStorage.GetAllItems(owner);
+ for (let propertyName in playerModifs)
+ {
+ // We only need to find one one tech per component for a match.
+ let component = propertyName.split("/")[0];
+ // Only inform if the modification actually applies to the entity as an optimisation.
+ // TODO: would it be better to call FetchModifiedProperty here and compare values?
+ playerModifs[propertyName].forEach(modif => {
+ if (!DoesModificationApply(modif, classes))
+ return;
+ if (!modifiedComponents[component])
+ modifiedComponents[component] = [];
+ modifiedComponents[component].push(propertyName);
+ });
+ }
+
+ for (let component in modifiedComponents)
+ Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] });
+};
+
+/**
+ * The following functions simply proxy MultiKeyMap's interface.
+ */
+ModificationsManager.prototype.AddModif = function(primaryKey, ModifID, Modif, secondaryKey, stackable = false) {
+ return this.modifsStorage.AddItem(primaryKey, ModifID, Modif, secondaryKey, stackable);
+};
+
+ModificationsManager.prototype.AddModifs = function(ModifID, Modifs, secondaryKey, stackable = false) {
+ return this.modifsStorage.AddItems(ModifID, Modifs, secondaryKey, stackable);
+};
+
+ModificationsManager.prototype.RemoveModif = function(primaryKey, ModifID, secondaryKey, stackable = false) {
+ return this.modifsStorage.RemoveItem(primaryKey, ModifID, secondaryKey, stackable);
+};
+
+ModificationsManager.prototype.RemoveAllModifs = function(ModifID, secondaryKey, stackable = false) {
+ return this.modifsStorage.RemoveAllItems(ModifID, secondaryKey, stackable);
+};
+
+ModificationsManager.prototype.HasModif = function(primaryKey, ModifID, secondaryKey) {
+ return this.modifsStorage.HasItem(primaryKey, ModifID, secondaryKey);
+};
+
+ModificationsManager.prototype.HasAnyModif = function(ModifID, secondaryKey) {
+ return this.modifsStorage.HasAnyItem(ModifID, secondaryKey);
+};
+
+ModificationsManager.prototype.GetModifs = function(primaryKey, secondaryKey, stackable = false) {
+ return this.modifsStorage.GetItems(primaryKey, secondaryKey, stackable);
+};
+
+ModificationsManager.prototype.GetAllModifs = function(secondaryKey, stackable = false) {
+ return this.modifsStorage.GetAllItems(secondaryKey, stackable);
+};
+
+Engine.RegisterSystemComponentType(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/Damage/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(SYSTEM_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, this.entity);
}
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/AuraManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/AuraManager.js
+++ /dev/null
@@ -1 +0,0 @@
-Engine.RegisterInterface("AuraManager");
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
@@ -3,9 +3,9 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
-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_AuraManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_AuraManager.js
+++ /dev/null
@@ -1,43 +0,0 @@
-Engine.LoadComponentScript("interfaces/AuraManager.js");
-Engine.LoadComponentScript("AuraManager.js");
-
-let value = "Component/Value";
-let player1 = 1;
-let player2 = 2;
-let ents1 = [25, 26, 27];
-let ents2 = [28, 29, 30];
-let ents3 = [31];
-let classes = ["class1", "class2"];
-let template = { "Identity" : { "Classes" : { "_string" : "class1 class3" } } };
-
-let cmpAuraManager = ConstructComponent(SYSTEM_ENTITY, "AuraManager", {});
-
-// Apply and remove a bonus
-cmpAuraManager.ApplyBonus(value, ents1, { "add": 8 }, "key1");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 25), 18);
-// It isn't apply to wrong entity
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 28), 10);
-cmpAuraManager.RemoveBonus(value, ents1, "key1");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 25), 10);
-
-// Apply 2 bonus with two different keys. Bonus should stack
-cmpAuraManager.ApplyBonus(value, ents2, { "add": 8 }, "key1");
-cmpAuraManager.ApplyBonus(value, ents2, { "multiply": 3 }, "key2");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 28), 38);
-
-// With another operation ordering, the result must be the same
-cmpAuraManager.ApplyBonus(value, ents3, { "multiply": 3 }, "key2");
-cmpAuraManager.ApplyBonus(value, ents3, { "add": 8 }, "key1");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 31), 38);
-
-// Apply bonus to templates
-cmpAuraManager.ApplyTemplateBonus(value, player1, classes, { "add": 10 }, "key3");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player1, template), 310);
-cmpAuraManager.RemoveTemplateBonus(value, player1, classes, "key3");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player1, template), 300);
-cmpAuraManager.ApplyTemplateBonus(value, player2, classes, { "add": 10 }, "key3");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player2, template), 310);
-cmpAuraManager.ApplyTemplateBonus(value, player1, classes, { "add": 10 }, "key3");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player1, template), 310);
-cmpAuraManager.RemoveTemplateBonus(value, player2, classes, "key3");
-TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player2, template), 300);
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
@@ -1,11 +1,12 @@
+Engine.LoadHelperScript("MultiKeyMap.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Auras.js");
-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");
+Engine.LoadComponentScript("ModificationsManager.js");
var playerID = [0, 1, 2];
var playerEnt = [10, 11, 12];
@@ -89,7 +90,9 @@
"GetOwner": () => playerID[1]
});
- ConstructComponent(SYSTEM_ENTITY, "AuraManager", {});
+ let cmpModificationsManager = ConstructComponent(SYSTEM_ENTITY, "ModificationsManager", {});
+ cmpModificationsManager.OnGlobalPlayerEntityChanged({ player: playerID[1], from: -1, to: playerEnt[1] });
+ cmpModificationsManager.OnGlobalPlayerEntityChanged({ player: playerID[2], from: -1, to: playerEnt[2] });
let cmpAuras = ConstructComponent(sourceEnt, "Auras", { "_string": name });
test_function(name, cmpAuras);
}
@@ -125,17 +128,17 @@
testAuras("garrison", (name, cmpAuras) => {
TS_ASSERT_EQUALS(cmpAuras.HasGarrisonAura(), true);
- cmpAuras.ApplyGarrisonBonus(targetEnt);
+ cmpAuras.ApplyGarrisonAura(targetEnt);
TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15);
- cmpAuras.RemoveGarrisonBonus(targetEnt);
+ cmpAuras.RemoveGarrisonAura(targetEnt);
TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 5);
});
testAuras("formation", (name, cmpAuras) => {
TS_ASSERT_EQUALS(cmpAuras.HasFormationAura(), true);
- cmpAuras.ApplyFormationBonus([targetEnt]);
+ cmpAuras.ApplyFormationAura([targetEnt]);
TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 15);
- cmpAuras.RemoveFormationBonus([targetEnt]);
+ cmpAuras.RemoveFormationAura([targetEnt]);
TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Component/Value", 5, targetEnt), 5);
});
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
@@ -1,11 +1,10 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
-Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Auras.js");
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
@@ -5,7 +5,6 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/AttackDetection.js");
-Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Damage.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
Engine.LoadComponentScript("interfaces/Health.js");
@@ -13,7 +12,7 @@
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.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_DeathDamage.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
+++ binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
@@ -1,7 +1,6 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("DamageTypes.js");
Engine.LoadHelperScript("ValueModification.js");
-Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Damage.js");
Engine.LoadComponentScript("interfaces/DeathDamage.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.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
@@ -3,11 +3,10 @@
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Garrisonable.js");
Engine.LoadComponentScript("GarrisonHolder.js");
-Engine.LoadComponentScript("interfaces/AuraManager.js");
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_Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Health.js
+++ binaries/data/mods/public/simulation/components/tests/test_Health.js
@@ -1,5 +1,4 @@
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
-Engine.LoadComponentScript("interfaces/AuraManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.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,141 @@
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
+Engine.LoadComponentScript("ModificationsManager.js");
+Engine.LoadHelperScript("MultiKeyMap.js");
+Engine.LoadHelperScript("Player.js");
+Engine.LoadHelperScript("ValueModification.js");
+
+let cmpModificationsManager = ConstructComponent(SYSTEM_ENTITY, "ModificationsManager", {});
+cmpModificationsManager.Init();
+
+// These should be different as that is the general case.
+const PLAYER_ID_FOR_TEST = 2;
+const PLAYER_ENTITY_ID = 3;
+
+AddMock(SYSTEM_ENTITY, IID_RangeManager, {
+ "GetEntitiesByPlayer": function(a) { return []; }
+});
+
+AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
+ "GetPlayerByID": (a) => PLAYER_ENTITY_ID
+});
+
+AddMock(PLAYER_ENTITY_ID, IID_Player, {
+ "GetPlayerID": () => PLAYER_ID_FOR_TEST
+});
+
+let entitiesToTest = [5, 6, 7, 8];
+for (let ent of entitiesToTest)
+ AddMock(ent, IID_Ownership, {
+ "GetOwner": () => PLAYER_ID_FOR_TEST
+ });
+
+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";}
+});
+
+// Sprinkle random serialisation cycles.
+function SerializationCycle()
+{
+ let data = cmpModificationsManager.Serialize();
+ cmpModificationsManager = ConstructComponent(SYSTEM_ENTITY, "ModificationsManager", {});
+ cmpModificationsManager.Deserialize(data);
+}
+
+cmpModificationsManager.OnGlobalPlayerEntityChanged({ player: PLAYER_ID_FOR_TEST, from: -1, to: PLAYER_ENTITY_ID });
+
+cmpModificationsManager.AddModif("Test_A", "Test_A_0", { "affects": ["Structure"], "add": 10 }, 10, "testLol");
+
+cmpModificationsManager.AddModif("Test_A", "Test_A_0", { "affects": ["Structure"], "add": 10 }, PLAYER_ENTITY_ID);
+cmpModificationsManager.AddModif("Test_A", "Test_A_1", { "affects": ["Infantry"], "add": 5 }, PLAYER_ENTITY_ID);
+cmpModificationsManager.AddModif("Test_A", "Test_A_2", { "affects": ["Unit"], "add": 3 }, PLAYER_ENTITY_ID);
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 15);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 6), 10);
+SerializationCycle();
+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", PLAYER_ENTITY_ID);
+
+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 },
+}, PLAYER_ENTITY_ID);
+
+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);
+
+SerializationCycle();
+
+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", PLAYER_ENTITY_ID));
+
+SerializationCycle();
+
+// check that things still work properly if we change global modifications
+cmpModificationsManager.AddModif("Test_C", "Test_C_3", { "affects": ["Structure"], "add": 10 }, PLAYER_ENTITY_ID);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0);
+
+TS_ASSERT(cmpModificationsManager.HasAnyModif("Test_C_3", PLAYER_ENTITY_ID));
+TS_ASSERT(cmpModificationsManager.HasModif("Test_C", "Test_C_3", PLAYER_ENTITY_ID));
+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);
+
+SerializationCycle();
+
+TS_ASSERT(cmpModificationsManager.HasModif("Test_C", "Test_C_3", PLAYER_ENTITY_ID));
+TS_ASSERT(!cmpModificationsManager.HasModif("Test_C", "Test_C_2", 5));
+
+//////////////////////////////////////////
+// Test that entities keep local modifications but not global ones when changing owner.
+AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
+ "GetPlayerByID": (a) => a == PLAYER_ID_FOR_TEST ? PLAYER_ENTITY_ID : PLAYER_ENTITY_ID + 1
+});
+
+AddMock(PLAYER_ENTITY_ID + 1, IID_Player, {
+ "GetPlayerID": () => PLAYER_ID_FOR_TEST + 1
+});
+
+cmpModificationsManager = ConstructComponent(SYSTEM_ENTITY, "ModificationsManager", {});
+cmpModificationsManager.Init();
+
+cmpModificationsManager.AddModif("Test_D", "Test_D_0", { "affects": ["Structure"], "add": 10 }, PLAYER_ENTITY_ID);
+cmpModificationsManager.AddModif("Test_D", "Test_D_1", { "affects": ["Structure"], "add": 1 }, PLAYER_ENTITY_ID + 1);
+cmpModificationsManager.AddModif("Test_D", "Test_D_2", { "affects": ["Structure"], "add": 5 }, 5);
+
+cmpModificationsManager.OnGlobalPlayerEntityChanged({ player: PLAYER_ID_FOR_TEST, from: -1, to: PLAYER_ENTITY_ID });
+cmpModificationsManager.OnGlobalPlayerEntityChanged({ player: PLAYER_ID_FOR_TEST + 1, from: -1, to: PLAYER_ENTITY_ID + 1 });
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_D", 10, 5), 25);
+cmpModificationsManager.OnGlobalOwnershipChanged({ entity: 5, from: PLAYER_ID_FOR_TEST, to: PLAYER_ID_FOR_TEST + 1 });
+AddMock(5, IID_Ownership, {
+ "GetOwner": () => PLAYER_ID_FOR_TEST + 1
+});
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_D", 10, 5), 16);
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,8 +2,7 @@
Engine.LoadHelperScript("Sound.js");
Engine.LoadHelperScript("Transform.js");
Engine.LoadHelperScript("ValueModification.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
-Engine.LoadComponentScript("interfaces/AuraManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Guard.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
@@ -15,9 +15,8 @@
};
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,30 @@
+// TODO: Move this to a folder of tests for GlobalScripts (once one is created)
+
+// This tests the GetTechModifiedProperty function.
+let add = [{ "add": 10, "affects": "Unit" }];
+
+let add_add = [{ "add": 10, "affects": "Unit" }, { "add": 5, "affects": "Unit" }];
+
+let add_mul_add = [{ "add": 10, "affects": "Unit" }, { "multiply": 2, "affects": "Unit" }, { "add": 5, "affects": "Unit" }];
+
+let add_replace = [{ "add": 10, "affects": "Unit" }, { "replace": 10, "affects": "Unit" }];
+
+let replace_add = [{ "replace": 10, "affects": "Unit" }, { "add": 10, "affects": "Unit" }];
+
+let replace_replace = [{ "replace": 10, "affects": "Unit" }, { "replace": 30, "affects": "Unit" }];
+
+let replace_nonnum = [{ "replace": "alpha", "affects": "Unit" }];
+
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add, "Unit", 5), 15);
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add_add, "Unit", 5), 20);
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add_add, "Other", 5), 5);
+
+// Technologies work by multiplying then adding all.
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add_mul_add, "Unit", 5), 25);
+
+TS_ASSERT_EQUALS(GetTechModifiedProperty(add_replace, "Unit", 5), 10);
+
+// Only the first replace is taken into account
+TS_ASSERT_EQUALS(GetTechModifiedProperty(replace_replace, "Unit", 5), 10);
+
+TS_ASSERT_EQUALS(GetTechModifiedProperty(replace_nonnum, "Unit", "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
@@ -13,8 +13,7 @@
return "" + schema + "";
}
};
-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:
@@ -89,19 +88,15 @@
"CancelTimer": () => {} // Called in components/Upgrade.js::CancelUpgrade().
});
-// 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).
- "GetTimeMultiplier": () => 1.0, // Called in components/Upgrade.js::GetUpgradeTime().
- "TrySubtractResources": () => true // Called in components/Upgrade.js::Upgrade().
-});
-AddMock(10, IID_TechnologyManager, {
- "ApplyModificationsTemplate": (valueName, curValue, template) => {
+AddMock(SYSTEM_ENTITY, IID_ModificationsManager, {
+ "ApplyModificationsTemplate": (valueName, curValue, template, player) => {
// Called in helpers/ValueModification.js::ApplyValueModificationsToTemplate()
// as part of Tests T2 and T5 below.
let mods = isResearched ? templateTechModifications.with : templateTechModifications.without;
- return GetTechModifiedProperty(mods, GetIdentityClasses(template.Identity), valueName, curValue);
+
+ if (mods[valueName])
+ return GetTechModifiedProperty(mods[valueName], GetIdentityClasses(template.Identity), curValue);
+ return curValue;
},
"ApplyModifications": (valueName, curValue, ent) => {
// Called in helpers/ValueModification.js::ApplyValueModificationsToEntity()
@@ -111,6 +106,14 @@
}
});
+// 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).
+ "GetTimeMultiplier": () => 1.0, // Called in components/Upgrade.js::GetUpgradeTime().
+ "TrySubtractResources": () => true // Called in components/Upgrade.js::Upgrade().
+});
+
// Create an entity with an Upgrade component:
AddMock(20, IID_Ownership, {
"GetOwner": () => playerID // Called in helpers/Player.js::QueryOwnerInterface().
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
@@ -1,15 +1,15 @@
Engine.LoadHelperScript("Player.js");
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";
+let otherKey = "Other/Key";
-AddMock(playerEnt, IID_TechnologyManager, {
+AddMock(SYSTEM_ENTITY, IID_ModificationsManager, {
"ApplyModifications": (key, val, ent) => {
if (key != techKey)
return val;
@@ -21,18 +21,6 @@
}
});
-AddMock(SYSTEM_ENTITY, IID_AuraManager, {
- "ApplyModifications": (key, val, ent) => {
- if (key != techKey)
- return val;
- if (ent == playerEnt)
- return val * 10;
- if (ent == ownedEnt)
- return val * 100;
- return val;
- }
-});
-
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": () => 10
});
@@ -45,6 +33,8 @@
"GetOwner": () => 1
});
-TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, playerEnt), 50.0);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(otherKey, 2.0, playerEnt), 2.0);
-TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, ownedEnt), 900.0);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, playerEnt), 5.0);
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, ownedEnt), 9.0);
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,7 +3,7 @@
Engine.LoadHelperScript("Commands.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
-Engine.LoadComponentScript("interfaces/AuraManager.js");
+Engine.LoadComponentScript("interfaces/ModificationsManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/VisionSharing.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
@@ -118,6 +118,9 @@
AddMock(14, IID_TechnologyManager, {
"CanProduce": entity => false,
+});
+
+AddMock(14, IID_ModificationsManager, {
"ApplyModificationsTemplate": (valueName, curValue, template) => curValue
});
@@ -127,8 +130,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/MultiKeyMap.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/helpers/MultiKeyMap.js
@@ -0,0 +1,241 @@
+// Convenient container abstraction for storing items referenced by a 3-tuple.
+// Used by the itemsManager to store items by (property Name, entity, item ID).
+// Methods starting with an underscore are private to the storage.
+// This supports stackable items as it stores count for each 3-tuple.
+function MultiKeyMap()
+{
+ this.items = new Map();
+ // Keys are referred to as 'primaryKey', 'secondaryKey', 'itemID'.
+}
+
+MultiKeyMap.prototype.Serialize = function()
+{
+ let ret = [];
+ for (let primary of this.items.keys())
+ {
+ // Keys of a Map can be arbitrary types whereas objects only support string, so use a list.
+ let vals = [primary, []];
+ ret.push(vals);
+ for (let secondary of this.items.get(primary).keys())
+ vals[1].push([secondary, this.items.get(primary).get(secondary)]);
+ }
+ return ret;
+};
+
+MultiKeyMap.prototype.Deserialize = function(data)
+{
+ for (let primary in data)
+ {
+ this.items.set(data[primary][0], new Map());
+ for (let secondary in data[primary][1])
+ this.items.get(data[primary][0]).set(data[primary][1][secondary][0], data[primary][1][secondary][1]);
+ }
+};
+
+/**
+ * Add a single item.
+ * NB: if you add an item with a different value but the same itemID, the original value remains.
+ * @param itemID - internal ID of this item, for later removal and/or updating
+ * @param stackable - if stackable, changing the count of items invalides, otherwise not.
+ * @returns true if the items list changed in such a way that cached values are possibly invalidated.
+ */
+MultiKeyMap.prototype.AddItem = function(primaryKey, itemID, item, secondaryKey, stackable = false)
+{
+ if (!this._AddItem(primaryKey, itemID, item, secondaryKey, stackable))
+ return false;
+
+ this._OnItemModified(primaryKey, secondaryKey, itemID);
+ return true;
+};
+
+/**
+ * Add items to multiple properties at once (only one item per property)
+ * @param items - Dictionnary of { primaryKey: item }
+ * @returns true if the items list changed in such a way that cached values are possibly invalidated.
+ */
+MultiKeyMap.prototype.AddItems = function(itemID, items, secondaryKey, stackable = false)
+{
+ let modified = false;
+ for (let primaryKey in items)
+ modified = this.AddItem(primaryKey, itemID, items[primaryKey], secondaryKey, stackable) || modified;
+ return modified;
+};
+
+/**
+ * Removes a item on a property.
+ * @param primaryKey - property to change (e.g. "Health/Max")
+ * @param itemID - internal ID of the item to remove
+ * @param secondaryKey - secondaryKey ID
+ * @returns true if the items list changed in such a way that cached values are possibly invalidated.
+ */
+MultiKeyMap.prototype.RemoveItem = function(primaryKey, itemID, secondaryKey, stackable = false)
+{
+ if (!this._RemoveItem(primaryKey, itemID, secondaryKey, stackable))
+ return false;
+
+ this._OnItemModified(primaryKey, secondaryKey, itemID);
+ return true;
+};
+
+/**
+ * Removes items with this ID for any property name.
+ * Naively iterates all property names.
+ * @returns true if the items list changed in such a way that cached values are possibly invalidated.
+ */
+MultiKeyMap.prototype.RemoveAllItems = function(itemID, secondaryKey, stackable = false)
+{
+ let modified = false;
+ // Map doesn't implement some so use a for-loop here.
+ for (let primaryKey of this.items.keys())
+ modified = this.RemoveItem(primaryKey, itemID, secondaryKey, stackable) || modified;
+ return modified;
+};
+
+/**
+ * @param itemID - internal ID of the item to try and find.
+ * @returns true if there is at least one item with that itemID
+ */
+MultiKeyMap.prototype.HasItem = function(primaryKey, itemID, secondaryKey)
+{
+ // some() returns false for an empty list which is wanted here.
+ return this._getItems(primaryKey, secondaryKey).some(item => item.ID === itemID);
+};
+
+/**
+ * Check if we have a item for any property name.
+ * Naively iterates all property names.
+ * @returns true if there is at least one item with that itemID
+ */
+MultiKeyMap.prototype.HasAnyItem = function(itemID, secondaryKey)
+{
+ // Map doesn't implement some so use for loops instead.
+ for (let primaryKey of this.items.keys())
+ if (this.HasItem(primaryKey, itemID, secondaryKey))
+ return true;
+ return false;
+};
+
+/**
+ * @returns A list of items with storage metadata removed.
+ */
+MultiKeyMap.prototype.GetItems = function(primaryKey, secondaryKey, stackable = false)
+{
+ let items = [];
+
+ if (stackable)
+ this._getItems(primaryKey, secondaryKey).forEach(item => (items = items.concat(Array(item.count).fill(item.value))));
+ else
+ this._getItems(primaryKey, secondaryKey).forEach(item => items.push(item.value));
+
+ return items;
+};
+
+/**
+ * @returns A dictionary of { Property Name: items } for the secondary Key.
+ * Naively iterates all property names.
+ */
+MultiKeyMap.prototype.GetAllItems = function(secondaryKey, stackable = false)
+{
+ let items = {};
+
+ // Map doesn't implement filter so use a for loop.
+ for (let primaryKey of this.items.keys())
+ {
+ if (!this.items.get(primaryKey).has(secondaryKey))
+ continue;
+ items[primaryKey] = this.GetItems(primaryKey, secondaryKey, stackable);
+ }
+ return items;
+};
+
+/**
+ * @returns a list of items.
+ * This does not necessarily return a reference to items' list, use _getItemsOrInit for that.
+ */
+MultiKeyMap.prototype._getItems = function(primaryKey, secondaryKey)
+{
+ if (!this._exists(primaryKey, secondaryKey))
+ return [];
+ return this.items.get(primaryKey).get(secondaryKey);
+};
+
+/**
+ * @returns a reference to the list of items for that property name and secondaryKey.
+ */
+MultiKeyMap.prototype._getItemsOrInit = function(primaryKey, secondaryKey)
+{
+ if (!this._exists(primaryKey, secondaryKey))
+ this._initItemsIfNeeded(primaryKey, secondaryKey);
+ return this.items.get(primaryKey).get(secondaryKey);
+};
+
+MultiKeyMap.prototype._exists = function(primaryKey, secondaryKey)
+{
+ if (!this.items.has(primaryKey))
+ return false;
+ if (!this.items.get(primaryKey).has(secondaryKey))
+ return false;
+ return true;
+};
+
+MultiKeyMap.prototype._initItemsIfNeeded = function(primaryKey, secondaryKey)
+{
+ if (!this.items.get(primaryKey))
+ this.items.set(primaryKey, new Map());
+ if (!this.items.get(primaryKey).get(secondaryKey))
+ this.items.get(primaryKey).set(secondaryKey, []);
+};
+
+/**
+ * @returns true if the items list changed in such a way that cached values are possibly invalidated.
+ */
+MultiKeyMap.prototype._AddItem = function(primaryKey, itemID, item, secondaryKey, stackable)
+{
+ let items = this._getItemsOrInit(primaryKey, secondaryKey);
+ for (let it of items)
+ if (it.ID == itemID)
+ {
+ it.count++;
+ return stackable;
+ }
+
+ items.push({ "ID": itemID, "count": 1, "value": item });
+ return true;
+};
+
+/**
+ * @returns true if the items list changed in such a way that cached values are possibly invalidated.
+ */
+MultiKeyMap.prototype._RemoveItem = function(primaryKey, itemID, secondaryKey, stackable)
+{
+ let items = this._getItems(primaryKey, secondaryKey);
+
+ let existingItem = items.filter(item => { return item.ID == itemID; });
+ if (!existingItem.length)
+ return false;
+
+ if (--existingItem[0].count > 0)
+ return stackable;
+
+ let stilValidItems = items.filter(item => item.count > 0);
+
+ // Delete entries from the map if necessary to clean up.
+ if (!stilValidItems.length)
+ {
+ this.items.get(primaryKey).delete(secondaryKey);
+ if (!this.items.get(primaryKey).size)
+ this.items.delete(primaryKey);
+ return true;
+ }
+
+ this.items.get(primaryKey).set(secondaryKey, stilValidItems);
+
+ return true;
+};
+
+/**
+ * Stub method, to overload.
+ */
+MultiKeyMap.prototype._OnItemModified = function(primaryKey, secondaryKey, itemID) {};
+
+Engine.RegisterGlobal("MultiKeyMap", MultiKeyMap);
Index: binaries/data/mods/public/simulation/helpers/Player.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Player.js
+++ binaries/data/mods/public/simulation/helpers/Player.js
@@ -184,6 +184,31 @@
return path;
}
+/**
+ * @param id An entity's ID
+ * @returns The entity ID of the owner player (not his player ID) or ent if ent is a player entity.
+ */
+function QueryOwnerEntityID(ent)
+{
+ let cmpPlayer = Engine.QueryInterface(ent, IID_Player);
+ if (cmpPlayer)
+ return ent;
+
+ let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
+ if (!cmpOwnership)
+ return null;
+
+ let owner = cmpOwnership.GetOwner();
+ if (owner == INVALID_PLAYER)
+ return null;
+
+ let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ if (!cmpPlayerManager)
+ return null;
+
+ return cmpPlayerManager.GetPlayerByID(owner);
+}
+
/**
* Similar to Engine.QueryInterface but applies to the player entity
* that owns the given entity.
@@ -326,6 +351,7 @@
}
Engine.RegisterGlobal("LoadPlayerSettings", LoadPlayerSettings);
+Engine.RegisterGlobal("QueryOwnerEntityID", QueryOwnerEntityID);
Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface);
Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface);
Engine.RegisterGlobal("QueryMiragedInterface", QueryMiragedInterface);
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,29 +3,21 @@
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 cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager);
- if (!cmpAuraManager)
- return value;
- return cmpAuraManager.ApplyModifications(tech_type, value, entity);
+ let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager);
+ if (cmpModificationsManager)
+ value = cmpModificationsManager.ApplyModifications(tech_type, current_value, entity);
+ return value;
}
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 cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager);
- if (!cmpAuraManager)
- return value;
- return cmpAuraManager.ApplyTemplateModifications(tech_type, value, playerID, template);
+ let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager);
+ if (cmpModificationsManager)
+ value = cmpModificationsManager.ApplyModificationsTemplate(tech_type, current_value, template, playerID);
+ return value;
}
Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity);
Index: binaries/data/mods/public/simulation/helpers/tests/test_MultiKeyMap.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/helpers/tests/test_MultiKeyMap.js
@@ -0,0 +1,132 @@
+Engine.LoadHelperScript("MultiKeyMap.js");
+
+function setup_keys(map)
+{
+ map.AddItem("prim_a", "item_a", 0, "sec_a");
+ map.AddItem("prim_a", "item_b", 0, "sec_a");
+ map.AddItem("prim_a", "item_c", 0, "sec_a");
+ map.AddItem("prim_a", "item_a", 0, "sec_b");
+ map.AddItem("prim_b", "item_a", 0, "sec_a");
+ map.AddItem("prim_c", "item_a", 0, "sec_a");
+ map.AddItem("prim_c", "item_a", 0, 5);
+}
+
+// Check that key-related operations are correct.
+function test_keys(map)
+{
+ TS_ASSERT(map.items.has("prim_a"));
+ TS_ASSERT(map.items.has("prim_b"));
+ TS_ASSERT(map.items.has("prim_c"));
+
+ TS_ASSERT(map.items.get("prim_a").has("sec_a"));
+ TS_ASSERT(map.items.get("prim_a").has("sec_b"));
+ TS_ASSERT(!map.items.get("prim_a").has("sec_c"));
+ TS_ASSERT(map.items.get("prim_b").has("sec_a"));
+ TS_ASSERT(map.items.get("prim_c").has("sec_a"));
+ TS_ASSERT(map.items.get("prim_c").has(5));
+
+ TS_ASSERT(map.items.get("prim_a").get("sec_a").length == 3);
+ TS_ASSERT(map.items.get("prim_a").get("sec_b").length == 1);
+ TS_ASSERT(map.items.get("prim_b").get("sec_a").length == 1);
+ TS_ASSERT(map.items.get("prim_c").get("sec_a").length == 1);
+ TS_ASSERT(map.items.get("prim_c").get(5).length == 1);
+
+ TS_ASSERT(map.GetItems("prim_a", "sec_a").length == 3);
+ TS_ASSERT(map.GetItems("prim_a", "sec_b").length == 1);
+ TS_ASSERT(map.GetItems("prim_b", "sec_a").length == 1);
+ TS_ASSERT(map.GetItems("prim_c", "sec_a").length == 1);
+ TS_ASSERT(map.GetItems("prim_c", 5).length == 1);
+
+ TS_ASSERT(map.HasItem("prim_a", "item_a", "sec_a"));
+ TS_ASSERT(map.HasItem("prim_a", "item_b", "sec_a"));
+ TS_ASSERT(map.HasItem("prim_a", "item_c", "sec_a"));
+ TS_ASSERT(!map.HasItem("prim_a", "item_d", "sec_a"));
+ TS_ASSERT(map.HasItem("prim_a", "item_a", "sec_b"));
+ TS_ASSERT(!map.HasItem("prim_a", "item_b", "sec_b"));
+ TS_ASSERT(!map.HasItem("prim_a", "item_c", "sec_b"));
+ TS_ASSERT(map.HasItem("prim_b", "item_a", "sec_a"));
+ TS_ASSERT(map.HasItem("prim_c", "item_a", "sec_a"));
+ TS_ASSERT(map.HasAnyItem("item_a", "sec_b"));
+ TS_ASSERT(map.HasAnyItem("item_b", "sec_a"));
+ TS_ASSERT(!map.HasAnyItem("item_d", "sec_a"));
+ TS_ASSERT(!map.HasAnyItem("item_b", "sec_b"));
+
+ // Adding the same item increases its count.
+ map.AddItem("prim_a", "item_b", 0, "sec_a");
+ TS_ASSERT(map.items.get("prim_a").get("sec_a").length == 3);
+ TS_ASSERT(map.items.get("prim_a").get("sec_a").filter(item => item.ID == "item_b")[0].count == 2);
+ TS_ASSERT(map.GetItems("prim_a", "sec_a").length == 3);
+ TS_ASSERT(map.GetItems("prim_a", "sec_a", true).length == 4);
+
+ // Adding without stackable doesn't invalidate caches, adding with does.
+ TS_ASSERT(!map.AddItem("prim_a", "item_b", 0, "sec_a"));
+ TS_ASSERT(map.AddItem("prim_a", "item_b", 0, "sec_a", true));
+
+ TS_ASSERT(map.items.get("prim_a").get("sec_a").filter(item => item.ID == "item_b")[0].count == 4);
+
+ // Likewise removing, unless we now reach 0
+ TS_ASSERT(!map.RemoveItem("prim_a", "item_b", "sec_a"));
+ TS_ASSERT(map.RemoveItem("prim_a", "item_b", "sec_a", true));
+ TS_ASSERT(!map.RemoveItem("prim_a", "item_b", "sec_a"));
+ TS_ASSERT(map.RemoveItem("prim_a", "item_b", "sec_a"));
+
+ // Check that cleanup is done
+ TS_ASSERT(map.items.get("prim_a").get("sec_a").length == 2);
+ TS_ASSERT(map.RemoveItem("prim_a", "item_a", "sec_a"));
+ TS_ASSERT(map.RemoveItem("prim_a", "item_c", "sec_a"));
+ TS_ASSERT(!map.items.get("prim_a").has("sec_a"));
+ TS_ASSERT(map.items.get("prim_a").has("sec_b"));
+ TS_ASSERT(map.RemoveItem("prim_a", "item_a", "sec_b"));
+ TS_ASSERT(!map.items.has("prim_a"));
+}
+
+function setup_items(map)
+{
+ map.AddItem("prim_a", "item_a", 1, "sec_a");
+ map.AddItem("prim_a", "item_b", 2, "sec_a");
+ map.AddItem("prim_a", "item_c", 3, "sec_a");
+ map.AddItem("prim_a", "item_c", 1000, "sec_a");
+ map.AddItem("prim_a", "item_a", 5, "sec_b");
+ map.AddItem("prim_b", "item_a", 6, "sec_a");
+ map.AddItem("prim_c", "item_a", 7, "sec_a");
+}
+
+// Check that items returned are correct.
+function test_items(map)
+{
+ let items = map.GetAllItems("sec_a");
+ TS_ASSERT("prim_a" in items);
+ TS_ASSERT("prim_b" in items);
+ TS_ASSERT("prim_c" in items);
+ let sum = 0;
+ for (let key in items)
+ items[key].forEach(item => (sum += item));
+ TS_ASSERT(sum == 19);
+
+ items = map.GetAllItems("sec_a", true);
+ sum = 0;
+ for (let key in items)
+ items[key].forEach(item => (sum += item));
+ // We're adding more of the first item_c, the value wasn't replaced.
+ TS_ASSERT(sum == 22);
+}
+
+// Test items, and test that deserialised versions still pass test (i.e. test serialisation).
+let map = new MultiKeyMap();
+setup_keys(map);
+test_keys(map);
+
+map = new MultiKeyMap();
+let map2 = new MultiKeyMap();
+setup_keys(map);
+map2.Deserialize(map.Serialize());
+test_keys(map2);
+
+map = new MultiKeyMap();
+setup_items(map);
+test_items(map);
+map = new MultiKeyMap();
+map2 = new MultiKeyMap();
+setup_items(map);
+map2.Deserialize(map.Serialize());
+test_items(map2);
Index: source/simulation2/components/tests/test_scripts.h
===================================================================
--- source/simulation2/components/tests/test_scripts.h
+++ source/simulation2/components/tests/test_scripts.h
@@ -66,6 +66,7 @@
VfsPaths paths;
TS_ASSERT_OK(vfs::GetPathnames(g_VFS, L"simulation/components/tests/", L"test_*.js", paths));
+ TS_ASSERT_OK(vfs::GetPathnames(g_VFS, L"simulation/helpers/tests/", L"test_*.js", paths));
paths.push_back(VfsPath(L"simulation/components/tests/setup_test.js"));
for (const VfsPath& path : paths)
{