Index: ps/trunk/binaries/data/mods/public/globalscripts/Technologies.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/Technologies.js
+++ ps/trunk/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: ps/trunk/binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/Templates.js
+++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/AuraManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/AuraManager.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/AuraManager.js
@@ -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: ps/trunk/binaries/data/mods/public/simulation/components/Auras.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Auras.js
+++ ps/trunk/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);
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);
+ // Only need to call ApplyAura for the aura icons, so skip it if there are none.
+ if (this.GetOverlayIcon(name))
+ for (let player of affectedPlayers)
+ this.ApplyAura(name, cmpRangeManager.GetEntitiesByPlayer(player));
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 cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
+ 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])
+ cmpModifiersManager.AddModifier(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 cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
+ 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])
+ cmpModifiersManager.RemoveModifier(modifierPath, modifName, playerId);
+ }
};
-Auras.prototype.ApplyBonus = function(name, ents)
+Auras.prototype.ApplyAura = function(name, ents)
{
var validEnts = this.GiveMembersWithValidClass(name, ents);
if (!validEnts.length)
@@ -401,24 +402,36 @@
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,
+ // so stop after icons have been applied.
+ if (this.IsGlobalAura(name))
return;
+ let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
+
+ 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])
+ cmpModifiersManager.AddModifier(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 +442,32 @@
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,
+ // so stop after icons have been removed.
+ if (this.IsGlobalAura(name))
return;
- for (let ent of validEnts)
- {
- var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
- if (cmpStatusBars)
- cmpStatusBars.RemoveAuraSource(this.entity, name);
- }
+ let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
+
+ 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])
+ cmpModifiersManager.RemoveModifier(modifierPath, modifName, ent);
};
Auras.prototype.OnOwnershipChanged = function(msg)
@@ -484,7 +507,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: ps/trunk/binaries/data/mods/public/simulation/components/Formation.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Formation.js
+++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js
+++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/ModifiersManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/ModifiersManager.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/ModifiersManager.js
@@ -0,0 +1,290 @@
+function ModifiersManager() {}
+
+ModifiersManager.prototype.Schema =
+ "";
+
+ModifiersManager.prototype.Init = function()
+{
+ // TODO:
+ // - add a way to show an icon for a given modifier 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 modifiers, 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.modifiersStorage = new MultiKeyMap(); // Keyed by property name, entity.
+
+ this.modifiersStorage._OnItemModified = (prim, sec, itemID) => this.ModifiersChanged.apply(this, [prim, sec, itemID]);
+};
+
+ModifiersManager.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 {
+ "modifiersStorage": this.modifiersStorage.Serialize(),
+ "players": players
+ };
+};
+
+ModifiersManager.prototype.Deserialize = function(data)
+{
+ this.Init();
+ this.modifiersStorage.Deserialize(data.modifiersStorage);
+ 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.
+ */
+ModifiersManager.prototype.ModifiersChanged = function(propertyName, entity)
+{
+ let playerCache = this.playerEntitiesCached.get(entity);
+ this.InvalidateCache(propertyName, entity, playerCache);
+
+ if (playerCache)
+ {
+ let cmpPlayer = Engine.QueryInterface(entity, IID_Player);
+ if (cmpPlayer)
+ this.SendPlayerModifierMessages(propertyName, cmpPlayer.GetPlayerID());
+ }
+ else
+ Engine.PostMessage(entity, MT_ValueModification, { "entities": [entity], "component": propertyName.split("/")[0], "valueNames": [propertyName] });
+};
+
+ModifiersManager.prototype.SendPlayerModifierMessages = 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] });
+};
+
+ModifiersManager.prototype.InvalidatePlayerEntCache = function(valueCache, propertyName, entsMap)
+{
+ entsMap = entsMap.get(propertyName);
+ if (entsMap)
+ {
+ // Invalidate all local caches directly (for simplicity in ApplyModifiers).
+ entsMap.forEach(ent => valueCache.set(ent, new Map()));
+ entsMap.clear();
+ }
+};
+
+ModifiersManager.prototype.InvalidateCache = function(propertyName, entity, playerCache)
+{
+ let valueCache = this.cachedValues.get(propertyName);
+ if (!valueCache)
+ return;
+
+ if (playerCache)
+ this.InvalidatePlayerEntCache(valueCache, propertyName, playerCache);
+ else
+ valueCache.set(entity, new Map());
+};
+
+/**
+ * @returns originalValue after modifiers.
+ */
+ModifiersManager.prototype.FetchModifiedProperty = function(classesList, propertyName, originalValue, target)
+{
+ let modifs = this.modifiersStorage.GetItems(propertyName, target);
+ if (!modifs.length)
+ return originalValue;
+ return GetTechModifiedProperty(modifs, classesList, originalValue);
+};
+
+/**
+ * @returns originalValue after modifiers
+ */
+ModifiersManager.prototype.Cache = function(classesList, propertyName, originalValue, entity)
+{
+ let cache = this.cachedValues.get(propertyName);
+ if (!cache)
+ cache = this.cachedValues.set(propertyName, new Map()).get(propertyName);
+
+ let cache2 = cache.get(entity);
+ if (!cache2)
+ cache2 = cache.set(entity, new Map()).get(entity);
+
+ let value = this.FetchModifiedProperty(classesList, propertyName, originalValue, entity);
+ cache2.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 modifiers before per-entity modifiers, so the latter take priority;
+ * @param propertyName - Handle of a technology property (eg Attack/Ranged/Pierce) that was changed.
+ * @param originalValue - template/raw/before-modifiers 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 modifiers
+ */
+ModifiersManager.prototype.ApplyModifiers = function(propertyName, originalValue, entity)
+{
+ let newValue = this.cachedValues.get(propertyName);
+ if (newValue)
+ {
+ newValue = newValue.get(entity);
+ if (newValue)
+ {
+ newValue = newValue.get(originalValue);
+ if (newValue)
+ return newValue;
+ }
+ }
+
+ // Get the entity ID of the player / owner of the entity, since we use that to store per-player modifiers
+ // (this prevents conflicts between player ID and entity ID).
+ let ownerEntity = QueryOwnerEntityID(entity);
+ if (ownerEntity == entity)
+ ownerEntity = null;
+
+ newValue = originalValue;
+
+ let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+ if (!cmpIdentity)
+ return originalValue;
+ let classesList = cmpIdentity.GetClassesList();
+
+ // Apply player-wide modifiers before entity-local modifiers.
+ if (ownerEntity)
+ {
+ let pc = this.playerEntitiesCached.get(ownerEntity).get(propertyName);
+ if (!pc)
+ pc = this.playerEntitiesCached.get(ownerEntity).set(propertyName, new Set()).get(propertyName);
+ pc.add(entity);
+ newValue = this.FetchModifiedProperty(classesList, propertyName, newValue, ownerEntity);
+ }
+ newValue = this.Cache(classesList, propertyName, newValue, entity);
+
+ return newValue;
+};
+
+/**
+ * Alternative version of ApplyModifiers, applies to templates instead of entities.
+ * Only needs to handle global modifiers.
+ */
+ModifiersManager.prototype.ApplyTemplateModifiers = 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.
+ */
+ModifiersManager.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 modifiers when an entity changes owner.
+ * We do not retain the original modifiers for now.
+ */
+ModifiersManager.prototype.OnGlobalOwnershipChanged = function(msg)
+{
+ if (msg.from == INVALID_PLAYER || 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 modifiers will be added by the relevant components, so no need to check for them here.
+ let modifiedComponents = {};
+ let playerModifs = this.modifiersStorage.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 modifier 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.
+ */
+ModifiersManager.prototype.AddModifier = function(propName, ModifID, Modif, entity, stackable = false) {
+ return this.modifiersStorage.AddItem(propName, ModifID, Modif, entity, stackable);
+};
+
+ModifiersManager.prototype.AddModifiers = function(ModifID, Modifs, entity, stackable = false) {
+ return this.modifiersStorage.AddItems(ModifID, Modifs, entity, stackable);
+};
+
+ModifiersManager.prototype.RemoveModifier = function(propName, ModifID, entity, stackable = false) {
+ return this.modifiersStorage.RemoveItem(propName, ModifID, entity, stackable);
+};
+
+ModifiersManager.prototype.RemoveAllModifiers = function(ModifID, entity, stackable = false) {
+ return this.modifiersStorage.RemoveAllItems(ModifID, entity, stackable);
+};
+
+ModifiersManager.prototype.HasModifier = function(propName, ModifID, entity) {
+ return this.modifiersStorage.HasItem(propName, ModifID, entity);
+};
+
+ModifiersManager.prototype.HasAnyModifier = function(ModifID, entity) {
+ return this.modifiersStorage.HasAnyItem(ModifID, entity);
+};
+
+ModifiersManager.prototype.GetModifiers = function(propName, entity, stackable = false) {
+ return this.modifiersStorage.GetItems(propName, entity, stackable);
+};
+
+ModifiersManager.prototype.GetAllModifiers = function(entity, stackable = false) {
+ return this.modifiersStorage.GetAllItems(entity, stackable);
+};
+
+Engine.RegisterSystemComponentType(IID_ModifiersManager, "ModifiersManager", ModifiersManager);
Index: ps/trunk/binaries/data/mods/public/simulation/components/PlayerManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/PlayerManager.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/PlayerManager.js
@@ -25,45 +25,57 @@
newDiplo[id] = 1;
cmpPlayer.SetDiplomacy(newDiplo);
+ Engine.BroadcastMessage(MT_PlayerEntityChanged, {
+ "player": id,
+ "from": INVALID_ENTITY,
+ "to": ent
+ });
+
return id;
};
/**
- * To avoid possible problems with cached quantities (as in TechnologyManager),
+ * To avoid possible problems,
* we first remove all entities from this player, and add them back after the replacement.
* Note: This should only be called during setup/init and not during the game
*/
PlayerManager.prototype.ReplacePlayer = function(id, ent)
{
- var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
- var entities = cmpRangeManager.GetEntitiesByPlayer(id);
- for (var e of entities)
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let entities = cmpRangeManager.GetEntitiesByPlayer(id);
+ for (let e of entities)
{
- var cmpOwnership = Engine.QueryInterface(e, IID_Ownership);
+ let cmpOwnership = Engine.QueryInterface(e, IID_Ownership);
if (cmpOwnership)
cmpOwnership.SetOwner(INVALID_PLAYER);
}
- var oldent = this.playerEntities[id];
- var cmpPlayer = Engine.QueryInterface(oldent, IID_Player);
- var diplo = cmpPlayer.GetDiplomacy();
- var color = cmpPlayer.GetColor();
+ let oldent = this.playerEntities[id];
+ let oldCmpPlayer = Engine.QueryInterface(oldent, IID_Player);
+ let diplo = oldCmpPlayer.GetDiplomacy();
+ let color = oldCmpPlayer.GetColor();
- var cmpPlayer = Engine.QueryInterface(ent, IID_Player);
- cmpPlayer.SetPlayerID(id);
+ let newCmpPlayer = Engine.QueryInterface(ent, IID_Player);
+ newCmpPlayer.SetPlayerID(id);
this.playerEntities[id] = ent;
- cmpPlayer.SetColor(color);
- cmpPlayer.SetDiplomacy(diplo);
+ newCmpPlayer.SetColor(color);
+ newCmpPlayer.SetDiplomacy(diplo);
- Engine.DestroyEntity(oldent);
- Engine.FlushDestroyedEntities();
+ Engine.BroadcastMessage(MT_PlayerEntityChanged, {
+ "player": id,
+ "from": oldent,
+ "to": ent
+ });
- for (var e of entities)
+ for (let e of entities)
{
- var cmpOwnership = Engine.QueryInterface(e, IID_Ownership);
+ let cmpOwnership = Engine.QueryInterface(e, IID_Ownership);
if (cmpOwnership)
cmpOwnership.SetOwner(id);
}
+
+ Engine.DestroyEntity(oldent);
+ Engine.FlushDestroyedEntities();
};
/**
@@ -126,8 +138,15 @@
PlayerManager.prototype.RemoveAllPlayers = function()
{
// Destroy existing player entities
- for (var id of this.playerEntities)
- Engine.DestroyEntity(id);
+ for (let player in this.playerEntities)
+ {
+ Engine.BroadcastMessage(MT_PlayerEntityChanged, {
+ "player": player,
+ "from": this.playerEntities[player],
+ "to": INVALID_ENTITY
+ });
+ Engine.DestroyEntity(this.playerEntities[player]);
+ }
this.playerEntities = [];
};
@@ -138,6 +157,11 @@
return;
var lastId = this.playerEntities.pop();
+ Engine.BroadcastMessage(MT_PlayerEntityChanged, {
+ "player": this.playerEntities.length + 1,
+ "from": lastId,
+ "to": INVALID_ENTITY
+ });
Engine.DestroyEntity(lastId);
};
Index: ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js
+++ ps/trunk/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 cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
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])
+ cmpModifiersManager.AddModifier(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: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/AuraManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/AuraManager.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/AuraManager.js
@@ -1 +0,0 @@
-Engine.RegisterInterface("AuraManager");
Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ModifiersManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ModifiersManager.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ModifiersManager.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("ModifiersManager");
Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/PlayerManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/PlayerManager.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/PlayerManager.js
@@ -0,0 +1,6 @@
+/**
+ * Message of the form { player": number, "from": number, "to": number }
+ * sent from PlayerManager component to warn other components when a player changed entities.
+ * This is also sent when the player gets created or destroyed.
+ */
+Engine.RegisterMessageType("PlayerEntityChanged");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js
+++ ps/trunk/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/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_AuraManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_AuraManager.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_AuraManager.js
@@ -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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Auras.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Auras.js
+++ ps/trunk/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/ModifiersManager.js");
Engine.LoadComponentScript("Auras.js");
-Engine.LoadComponentScript("AuraManager.js");
+Engine.LoadComponentScript("ModifiersManager.js");
var playerID = [0, 1, 2];
var playerEnt = [10, 11, 12];
@@ -89,7 +90,9 @@
"GetOwner": () => playerID[1]
});
- ConstructComponent(SYSTEM_ENTITY, "AuraManager", {});
+ let cmpModifiersManager = ConstructComponent(SYSTEM_ENTITY, "ModifiersManager", {});
+ cmpModifiersManager.OnGlobalPlayerEntityChanged({ player: playerID[1], from: -1, to: playerEnt[1] });
+ cmpModifiersManager.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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Capturable.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Capturable.js
+++ ps/trunk/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/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/TerritoryDecay.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Capturable.js");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js
+++ ps/trunk/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/DelayedDamage.js");
Engine.LoadComponentScript("interfaces/Resistance.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/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Attack.js");
Engine.LoadComponentScript("DelayedDamage.js");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
@@ -1,9 +1,8 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("Attacking.js");
Engine.LoadHelperScript("ValueModification.js");
-Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/DeathDamage.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("DeathDamage.js");
let deadEnt = 60;
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
+++ ps/trunk/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/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Health.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Health.js
+++ ps/trunk/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/ModifiersManager.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ModificationsManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ModificationsManager.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ModificationsManager.js
@@ -0,0 +1,141 @@
+Engine.LoadComponentScript("interfaces/ModifiersManager.js");
+Engine.LoadComponentScript("ModifiersManager.js");
+Engine.LoadHelperScript("MultiKeyMap.js");
+Engine.LoadHelperScript("Player.js");
+Engine.LoadHelperScript("ValueModification.js");
+
+let cmpModifiersManager = ConstructComponent(SYSTEM_ENTITY, "ModifiersManager", {});
+cmpModifiersManager.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 = cmpModifiersManager.Serialize();
+ cmpModifiersManager = ConstructComponent(SYSTEM_ENTITY, "ModifiersManager", {});
+ cmpModifiersManager.Deserialize(data);
+}
+
+cmpModifiersManager.OnGlobalPlayerEntityChanged({ player: PLAYER_ID_FOR_TEST, from: -1, to: PLAYER_ENTITY_ID });
+
+cmpModifiersManager.AddModifier("Test_A", "Test_A_0", { "affects": ["Structure"], "add": 10 }, 10, "testLol");
+
+cmpModifiersManager.AddModifier("Test_A", "Test_A_0", { "affects": ["Structure"], "add": 10 }, PLAYER_ENTITY_ID);
+cmpModifiersManager.AddModifier("Test_A", "Test_A_1", { "affects": ["Infantry"], "add": 5 }, PLAYER_ENTITY_ID);
+cmpModifiersManager.AddModifier("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);
+
+cmpModifiersManager.RemoveAllModifiers("Test_A_0", PLAYER_ENTITY_ID);
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 5);
+
+cmpModifiersManager.AddModifiers("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.
+cmpModifiersManager.AddModifier("Test_C", "Test_C_0", { "affects": ["Structure"], "add": 10 }, 5);
+cmpModifiersManager.AddModifier("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
+cmpModifiersManager.AddModifier("Test_C", "Test_C_2", { "affects": ["Structure"], "replace": 0 }, 5);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0);
+
+TS_ASSERT(!cmpModifiersManager.HasAnyModifier("Test_C_3", PLAYER_ENTITY_ID));
+
+SerializationCycle();
+
+// check that things still work properly if we change global modifications
+cmpModifiersManager.AddModifier("Test_C", "Test_C_3", { "affects": ["Structure"], "add": 10 }, PLAYER_ENTITY_ID);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0);
+
+TS_ASSERT(cmpModifiersManager.HasAnyModifier("Test_C_3", PLAYER_ENTITY_ID));
+TS_ASSERT(cmpModifiersManager.HasModifier("Test_C", "Test_C_3", PLAYER_ENTITY_ID));
+TS_ASSERT(cmpModifiersManager.HasModifier("Test_C", "Test_C_2", 5));
+
+// test removal
+cmpModifiersManager.RemoveModifier("Test_C", "Test_C_2", 5);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 25);
+
+SerializationCycle();
+
+TS_ASSERT(cmpModifiersManager.HasModifier("Test_C", "Test_C_3", PLAYER_ENTITY_ID));
+TS_ASSERT(!cmpModifiersManager.HasModifier("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
+});
+
+cmpModifiersManager = ConstructComponent(SYSTEM_ENTITY, "ModifiersManager", {});
+cmpModifiersManager.Init();
+
+cmpModifiersManager.AddModifier("Test_D", "Test_D_0", { "affects": ["Structure"], "add": 10 }, PLAYER_ENTITY_ID);
+cmpModifiersManager.AddModifier("Test_D", "Test_D_1", { "affects": ["Structure"], "add": 1 }, PLAYER_ENTITY_ID + 1);
+cmpModifiersManager.AddModifier("Test_D", "Test_D_2", { "affects": ["Structure"], "add": 5 }, 5);
+
+cmpModifiersManager.OnGlobalPlayerEntityChanged({ player: PLAYER_ID_FOR_TEST, from: -1, to: PLAYER_ENTITY_ID });
+cmpModifiersManager.OnGlobalPlayerEntityChanged({ player: PLAYER_ID_FOR_TEST + 1, from: -1, to: PLAYER_ENTITY_ID + 1 });
+
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_D", 10, 5), 25);
+cmpModifiersManager.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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Pack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Pack.js
+++ ps/trunk/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/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Guard.js");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Player.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Player.js
+++ ps/trunk/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/ModifiersManager.js");
Engine.LoadComponentScript("Player.js");
var cmpPlayer = ConstructComponent(10, "Player", {
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Technologies.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Technologies.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Technologies.js
@@ -1,583 +0,0 @@
-// TODO: Move this to a folder of tests for GlobalScripts (once one is created)
-
-// No requirements set in template
-let template = {};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-
-/**
- * First, the basics:
- */
-
-// Technology Requirement
-template.requirements = { "tech": "expected_tech" };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]);
-
-// Entity Requirement: Count of entities matching given class
-template.requirements = { "entity": { "class": "Village", "number": 5 } };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
-
-// Entity Requirement: Count of entities matching given class
-template.requirements = { "entity": { "class": "Village", "numberOfTypes": 5 } };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
-
-// Single `civ`
-template.requirements = { "civ": "athen" };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
-
-// Single `notciv`
-template.requirements = { "notciv": "athen" };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), []);
-
-
-/**
- * Basic `all`s:
- */
-
-// Multiple techs
-template.requirements = { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C"] }]);
-
-// Multiple entity definitions
-template.requirements = {
- "all": [
- { "entity": { "class": "class_A", "number": 5 } },
- { "entity": { "class": "class_B", "number": 5 } },
- { "entity": { "class": "class_C", "number": 5 } }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"),
- [{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "count" }, { "class": "class_C", "number": 5, "check": "count" }] }]);
-
-// A `tech` and an `entity`
-template.requirements = { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] };
-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"}] };
-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"}] };
-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);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []);
-
-// A `civ` with a tech/entity
-template.requirements = { "all": [{ "civ": "athen" }, { "tech": "expected_tech" }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
-
-template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
-
-template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
-
-// A `notciv` with a tech/entity
-template.requirements = { "all": [{ "notciv": "athen" }, { "tech": "expected_tech" }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
-
-template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
-
-template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
-
-
-/**
- * Basic `any`s:
- */
-
-// Multiple techs
-template.requirements = { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "techs": ["tech_B"] }, { "techs": ["tech_C"] }]);
-
-// Multiple entity definitions
-template.requirements = {
- "any": [
- { "entity": { "class": "class_A", "number": 5 } },
- { "entity": { "class": "class_B", "number": 5 } },
- { "entity": { "class": "class_C", "number": 5 } }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
- { "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
- { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] },
- { "entities": [{ "class": "class_C", "number": 5, "check": "count" }] }
-]);
-
-// A tech or an entity
-template.requirements = { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]);
-
-// Multiple `civ`s
-template.requirements = { "any": [{ "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 = { "any": [{ "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);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []);
-
-// A `civ` or a tech/entity
-template.requirements = { "any": [{ "civ": "athen" }, { "tech": "expected_tech" }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
-
-template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
-
-template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
-
-// A `notciv` or a tech
-template.requirements = { "any": [{ "notciv": "athen" }, { "tech": "expected_tech" }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
-
-template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
-
-template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
-
-
-/**
- * Complicated `all`s, part 1 - an `all` inside an `all`:
- */
-
-// Techs
-template.requirements = {
- "all": [
- { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
- { "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C", "tech_D"] }]);
-
-// Techs and entities
-template.requirements = {
- "all": [
- { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
- { "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{
- "techs": ["tech_A", "tech_B"],
- "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }]
-}]);
-
-// Two `civ`s, without and with a tech
-template.requirements = {
- "all": [
- { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
-
-template.requirements = {
- "all": [
- { "tech": "required_tech" },
- { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
-
-// Two `notciv`s, without and with a tech
-template.requirements = {
- "all": [
- { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
-
-template.requirements = {
- "all": [
- { "tech": "required_tech" },
- { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
-
-// Inner `all` has a tech and a `civ`/`notciv`
-template.requirements = {
- "all": [
- { "all": [{ "tech": "tech_A" }, { "civ": "maur" }] },
- { "tech": "tech_B" }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_B"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_A", "tech_B"] }]);
-
-template.requirements = {
- "all": [
- { "all": [{ "tech": "tech_A" }, { "notciv": "maur" }] },
- { "tech": "tech_B" }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_B"] }]);
-
-
-/**
- * Complicated `all`s, part 2 - an `any` inside an `all`:
- */
-
-// Techs
-template.requirements = {
- "all": [
- { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
- { "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
- { "techs": ["tech_A", "tech_C"] },
- { "techs": ["tech_A", "tech_D"] },
- { "techs": ["tech_B", "tech_C"] },
- { "techs": ["tech_B", "tech_D"] }
-]);
-
-// Techs and entities
-template.requirements = {
- "all": [
- { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
- { "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
- { "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] },
- { "techs": ["tech_A", "tech_B"] },
- { "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }] },
- { "entities": [{ "class": "class_A", "number": 5, "check": "count" }], "techs": ["tech_B"] }
-]);
-
-// Two `civ`s, without and with a tech
-template.requirements = {
- "all": [
- { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
-
-template.requirements = {
- "all": [
- { "tech": "required_tech" },
- { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
-
-// Two `notciv`s, without and with a tech
-template.requirements = {
- "all": [
- { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
-
-template.requirements = {
- "all": [
- { "tech": "required_tech" },
- { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
-
-
-/**
- * Complicated `any`s, part 1 - an `all` inside an `any`:
- */
-
-// Techs
-template.requirements = {
- "any": [
- { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
- { "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
- { "techs": ["tech_A", "tech_B"] },
- { "techs": ["tech_C", "tech_D"] }
-]);
-
-// Techs and entities
-template.requirements = {
- "any": [
- { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
- { "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
- { "techs": ["tech_A"], "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
- { "entities": [{ "class": "class_B", "number": 5, "check": "variants" }], "techs": ["tech_B"] }
-]);
-
-// Two `civ`s, without and with a tech
-template.requirements = {
- "any": [
- { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
-
-template.requirements = {
- "any": [
- { "tech": "required_tech" },
- { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
- ]
-};
-// Note: these requirements don't really make sense, as the `any` makes the `civ`s in the the inner `all` irrelevant.
-// We test it anyway as a precursor to later tests.
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
-
-// Two `notciv`s, without and with a tech
-template.requirements = {
- "any": [
- { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
-
-template.requirements = {
- "any": [
- { "tech": "required_tech" },
- { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
- ]
-};
-// Note: these requirements have a result that might seen unexpected at first glance.
-// This is because the `notciv`s are rendered irrelevant by the `any`, and they have nothing else to operate on.
-// We test it anyway as a precursor for later tests.
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
-
-// Inner `all` has a tech and a `civ`/`notciv`
-template.requirements = {
- "any": [
- { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
- { "tech": "tech2" }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]);
-
-template.requirements = {
- "any": [
- { "all": [{ "notciv": "civA" }, { "tech": "tech1" }] },
- { "tech": "tech2" }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech2"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]);
-
-
-/**
- * Complicated `any`s, part 2 - an `any` inside an `any`:
- */
-
-// Techs
-template.requirements = {
- "any": [
- { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
- { "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
- { "techs": ["tech_A"] },
- { "techs": ["tech_B"] },
- { "techs": ["tech_C"] },
- { "techs": ["tech_D"] }
-]);
-
-// Techs and entities
-template.requirements = {
- "any": [
- { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
- { "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
- { "techs": ["tech_A"] },
- { "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
- { "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] },
- { "techs": ["tech_B"] }
-]);
-
-// Two `civ`s, without and with a tech
-template.requirements = {
- "any": [
- { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
-
-template.requirements = {
- "any": [
- { "tech": "required_tech" },
- { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
- ]
-};
-// These requirements may not make sense, as the `civ`s are unable to restrict the requirements due to the outer `any`
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
-
-// Two `notciv`s, without and with a tech
-template.requirements = {
- "any": [
- { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
-
-template.requirements = {
- "any": [
- { "tech": "required_tech" },
- { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
- ]
-};
-// These requirements may not make sense, as the `notciv`s are made irrelevant by the outer `any`
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
-
-
-/**
- * Further tests
- */
-
-template.requirements = {
- "all": [
- { "tech": "tech1" },
- { "any": [{ "civ": "civA" }, { "civ": "civB" }] },
- { "notciv": "civC" }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false);
-
-template.requirements = {
- "any": [
- { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
- { "all": [{ "civ": "civB" }, { "tech": "tech2" }] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false);
-
-template.requirements = {
- "any": [
- { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
- { "all": [
- { "any": [{ "civ": "civB" }, { "civ": "civC" }] },
- { "tech": "tech2" }
- ] }
- ]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), [{ "techs": ["tech2"] }]);
-TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civD"), false);
-
-// Test DeriveModificationsFromTech
-template = {
- "modifications": [{
- "value": "ResourceGatherer/Rates/food.grain",
- "multiply": 15,
- "affects": "Spear Sword"
- },
- {
- "value": "ResourceGatherer/Rates/food.meat",
- "multiply": 10
- }],
- "affects": ["Female", "CitizenSoldier Melee"]
-};
-let techMods = {
- "ResourceGatherer/Rates/food.grain": [{
- "affects": [
- ["Female", "Spear", "Sword"],
- ["CitizenSoldier", "Melee", "Spear", "Sword"]
- ],
- "multiply": 15
- }],
- "ResourceGatherer/Rates/food.meat": [{
- "affects": [
- ["Female"],
- ["CitizenSoldier", "Melee"]
- ],
- "multiply": 10
- }]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveModificationsFromTech(template), techMods);
-
-template = {
- "modifications": [{
- "value": "ResourceGatherer/Rates/food.grain",
- "multiply": 15,
- "affects": "Spear"
- },
- {
- "value": "ResourceGatherer/Rates/food.grain",
- "multiply": 15,
- "affects": "Sword"
- },
- {
- "value": "ResourceGatherer/Rates/food.meat",
- "multiply": 10
- }],
- "affects": ["Female", "CitizenSoldier Melee"]
-};
-techMods = {
- "ResourceGatherer/Rates/food.grain": [{
- "affects": [
- ["Female", "Spear"],
- ["CitizenSoldier", "Melee", "Spear"]
- ],
- "multiply": 15
- },
- {
- "affects": [
- ["Female", "Sword"],
- ["CitizenSoldier", "Melee", "Sword"]
- ],
- "multiply": 15
- }],
- "ResourceGatherer/Rates/food.meat": [{
- "affects": [
- ["Female"],
- ["CitizenSoldier", "Melee"]
- ],
- "multiply": 10
- }]
-};
-TS_ASSERT_UNEVAL_EQUALS(DeriveModificationsFromTech(template), techMods);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Technologies_effects.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Technologies_effects.js
+++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js
@@ -0,0 +1,583 @@
+// TODO: Move this to a folder of tests for GlobalScripts (once one is created)
+
+// No requirements set in template
+let template = {};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+
+/**
+ * First, the basics:
+ */
+
+// Technology Requirement
+template.requirements = { "tech": "expected_tech" };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]);
+
+// Entity Requirement: Count of entities matching given class
+template.requirements = { "entity": { "class": "Village", "number": 5 } };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
+
+// Entity Requirement: Count of entities matching given class
+template.requirements = { "entity": { "class": "Village", "numberOfTypes": 5 } };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
+
+// Single `civ`
+template.requirements = { "civ": "athen" };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
+
+// Single `notciv`
+template.requirements = { "notciv": "athen" };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), []);
+
+
+/**
+ * Basic `all`s:
+ */
+
+// Multiple techs
+template.requirements = { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C"] }]);
+
+// Multiple entity definitions
+template.requirements = {
+ "all": [
+ { "entity": { "class": "class_A", "number": 5 } },
+ { "entity": { "class": "class_B", "number": 5 } },
+ { "entity": { "class": "class_C", "number": 5 } }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"),
+ [{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "count" }, { "class": "class_C", "number": 5, "check": "count" }] }]);
+
+// A `tech` and an `entity`
+template.requirements = { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] };
+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" }] };
+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" }] };
+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);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []);
+
+// A `civ` with a tech/entity
+template.requirements = { "all": [{ "civ": "athen" }, { "tech": "expected_tech" }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
+
+template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
+
+template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
+
+// A `notciv` with a tech/entity
+template.requirements = { "all": [{ "notciv": "athen" }, { "tech": "expected_tech" }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
+
+template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
+
+template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
+
+
+/**
+ * Basic `any`s:
+ */
+
+// Multiple techs
+template.requirements = { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "techs": ["tech_B"] }, { "techs": ["tech_C"] }]);
+
+// Multiple entity definitions
+template.requirements = {
+ "any": [
+ { "entity": { "class": "class_A", "number": 5 } },
+ { "entity": { "class": "class_B", "number": 5 } },
+ { "entity": { "class": "class_C", "number": 5 } }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
+ { "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
+ { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] },
+ { "entities": [{ "class": "class_C", "number": 5, "check": "count" }] }
+]);
+
+// A tech or an entity
+template.requirements = { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]);
+
+// Multiple `civ`s
+template.requirements = { "any": [{ "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 = { "any": [{ "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);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []);
+
+// A `civ` or a tech/entity
+template.requirements = { "any": [{ "civ": "athen" }, { "tech": "expected_tech" }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
+
+template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
+
+template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
+
+// A `notciv` or a tech
+template.requirements = { "any": [{ "notciv": "athen" }, { "tech": "expected_tech" }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
+
+template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
+
+template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
+
+
+/**
+ * Complicated `all`s, part 1 - an `all` inside an `all`:
+ */
+
+// Techs
+template.requirements = {
+ "all": [
+ { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
+ { "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C", "tech_D"] }]);
+
+// Techs and entities
+template.requirements = {
+ "all": [
+ { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
+ { "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{
+ "techs": ["tech_A", "tech_B"],
+ "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }]
+}]);
+
+// Two `civ`s, without and with a tech
+template.requirements = {
+ "all": [
+ { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
+
+template.requirements = {
+ "all": [
+ { "tech": "required_tech" },
+ { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
+
+// Two `notciv`s, without and with a tech
+template.requirements = {
+ "all": [
+ { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
+
+template.requirements = {
+ "all": [
+ { "tech": "required_tech" },
+ { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
+
+// Inner `all` has a tech and a `civ`/`notciv`
+template.requirements = {
+ "all": [
+ { "all": [{ "tech": "tech_A" }, { "civ": "maur" }] },
+ { "tech": "tech_B" }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_B"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_A", "tech_B"] }]);
+
+template.requirements = {
+ "all": [
+ { "all": [{ "tech": "tech_A" }, { "notciv": "maur" }] },
+ { "tech": "tech_B" }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_B"] }]);
+
+
+/**
+ * Complicated `all`s, part 2 - an `any` inside an `all`:
+ */
+
+// Techs
+template.requirements = {
+ "all": [
+ { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
+ { "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
+ { "techs": ["tech_A", "tech_C"] },
+ { "techs": ["tech_A", "tech_D"] },
+ { "techs": ["tech_B", "tech_C"] },
+ { "techs": ["tech_B", "tech_D"] }
+]);
+
+// Techs and entities
+template.requirements = {
+ "all": [
+ { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
+ { "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
+ { "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] },
+ { "techs": ["tech_A", "tech_B"] },
+ { "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }] },
+ { "entities": [{ "class": "class_A", "number": 5, "check": "count" }], "techs": ["tech_B"] }
+]);
+
+// Two `civ`s, without and with a tech
+template.requirements = {
+ "all": [
+ { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
+
+template.requirements = {
+ "all": [
+ { "tech": "required_tech" },
+ { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
+
+// Two `notciv`s, without and with a tech
+template.requirements = {
+ "all": [
+ { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
+
+template.requirements = {
+ "all": [
+ { "tech": "required_tech" },
+ { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
+
+
+/**
+ * Complicated `any`s, part 1 - an `all` inside an `any`:
+ */
+
+// Techs
+template.requirements = {
+ "any": [
+ { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
+ { "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
+ { "techs": ["tech_A", "tech_B"] },
+ { "techs": ["tech_C", "tech_D"] }
+]);
+
+// Techs and entities
+template.requirements = {
+ "any": [
+ { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
+ { "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
+ { "techs": ["tech_A"], "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
+ { "entities": [{ "class": "class_B", "number": 5, "check": "variants" }], "techs": ["tech_B"] }
+]);
+
+// Two `civ`s, without and with a tech
+template.requirements = {
+ "any": [
+ { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
+
+template.requirements = {
+ "any": [
+ { "tech": "required_tech" },
+ { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
+ ]
+};
+// Note: these requirements don't really make sense, as the `any` makes the `civ`s in the the inner `all` irrelevant.
+// We test it anyway as a precursor to later tests.
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
+
+// Two `notciv`s, without and with a tech
+template.requirements = {
+ "any": [
+ { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
+
+template.requirements = {
+ "any": [
+ { "tech": "required_tech" },
+ { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
+ ]
+};
+// Note: these requirements have a result that might seen unexpected at first glance.
+// This is because the `notciv`s are rendered irrelevant by the `any`, and they have nothing else to operate on.
+// We test it anyway as a precursor for later tests.
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
+
+// Inner `all` has a tech and a `civ`/`notciv`
+template.requirements = {
+ "any": [
+ { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
+ { "tech": "tech2" }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]);
+
+template.requirements = {
+ "any": [
+ { "all": [{ "notciv": "civA" }, { "tech": "tech1" }] },
+ { "tech": "tech2" }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech2"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]);
+
+
+/**
+ * Complicated `any`s, part 2 - an `any` inside an `any`:
+ */
+
+// Techs
+template.requirements = {
+ "any": [
+ { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
+ { "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
+ { "techs": ["tech_A"] },
+ { "techs": ["tech_B"] },
+ { "techs": ["tech_C"] },
+ { "techs": ["tech_D"] }
+]);
+
+// Techs and entities
+template.requirements = {
+ "any": [
+ { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
+ { "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
+ { "techs": ["tech_A"] },
+ { "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
+ { "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] },
+ { "techs": ["tech_B"] }
+]);
+
+// Two `civ`s, without and with a tech
+template.requirements = {
+ "any": [
+ { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
+
+template.requirements = {
+ "any": [
+ { "tech": "required_tech" },
+ { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
+ ]
+};
+// These requirements may not make sense, as the `civ`s are unable to restrict the requirements due to the outer `any`
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
+
+// Two `notciv`s, without and with a tech
+template.requirements = {
+ "any": [
+ { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
+
+template.requirements = {
+ "any": [
+ { "tech": "required_tech" },
+ { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
+ ]
+};
+// These requirements may not make sense, as the `notciv`s are made irrelevant by the outer `any`
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
+
+
+/**
+ * Further tests
+ */
+
+template.requirements = {
+ "all": [
+ { "tech": "tech1" },
+ { "any": [{ "civ": "civA" }, { "civ": "civB" }] },
+ { "notciv": "civC" }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false);
+
+template.requirements = {
+ "any": [
+ { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
+ { "all": [{ "civ": "civB" }, { "tech": "tech2" }] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false);
+
+template.requirements = {
+ "any": [
+ { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
+ { "all": [
+ { "any": [{ "civ": "civB" }, { "civ": "civC" }] },
+ { "tech": "tech2" }
+ ] }
+ ]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), [{ "techs": ["tech2"] }]);
+TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civD"), false);
+
+// Test DeriveModificationsFromTech
+template = {
+ "modifications": [{
+ "value": "ResourceGatherer/Rates/food.grain",
+ "multiply": 15,
+ "affects": "Spear Sword"
+ },
+ {
+ "value": "ResourceGatherer/Rates/food.meat",
+ "multiply": 10
+ }],
+ "affects": ["Female", "CitizenSoldier Melee"]
+};
+let techMods = {
+ "ResourceGatherer/Rates/food.grain": [{
+ "affects": [
+ ["Female", "Spear", "Sword"],
+ ["CitizenSoldier", "Melee", "Spear", "Sword"]
+ ],
+ "multiply": 15
+ }],
+ "ResourceGatherer/Rates/food.meat": [{
+ "affects": [
+ ["Female"],
+ ["CitizenSoldier", "Melee"]
+ ],
+ "multiply": 10
+ }]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveModificationsFromTech(template), techMods);
+
+template = {
+ "modifications": [{
+ "value": "ResourceGatherer/Rates/food.grain",
+ "multiply": 15,
+ "affects": "Spear"
+ },
+ {
+ "value": "ResourceGatherer/Rates/food.grain",
+ "multiply": 15,
+ "affects": "Sword"
+ },
+ {
+ "value": "ResourceGatherer/Rates/food.meat",
+ "multiply": 10
+ }],
+ "affects": ["Female", "CitizenSoldier Melee"]
+};
+techMods = {
+ "ResourceGatherer/Rates/food.grain": [{
+ "affects": [
+ ["Female", "Spear"],
+ ["CitizenSoldier", "Melee", "Spear"]
+ ],
+ "multiply": 15
+ },
+ {
+ "affects": [
+ ["Female", "Sword"],
+ ["CitizenSoldier", "Melee", "Sword"]
+ ],
+ "multiply": 15
+ }],
+ "ResourceGatherer/Rates/food.meat": [{
+ "affects": [
+ ["Female"],
+ ["CitizenSoldier", "Melee"]
+ ],
+ "multiply": 10
+ }]
+};
+TS_ASSERT_UNEVAL_EQUALS(DeriveModificationsFromTech(template), techMods);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
+++ ps/trunk/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/ModifiersManager.js"); // Provides `IID_ModifiersManager`, used below.
Engine.LoadComponentScript("interfaces/Timer.js"); // Provides `IID_Timer`, used below.
// What we're testing:
@@ -89,21 +88,17 @@
"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_ModifiersManager, {
+ "ApplyTemplateModifiers": (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) => {
+ "ApplyModifiers": (valueName, curValue, ent) => {
// Called in helpers/ValueModification.js::ApplyValueModificationsToEntity()
// as part of Tests T3, T6 and T7 below.
let mods = isResearched ? entityTechModifications.with : entityTechModifications.without;
@@ -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: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js
@@ -1,16 +1,16 @@
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/ModifiersManager.js");
let player = 1;
let playerEnt = 10;
let ownedEnt = 60;
let techKey = "Attack/BigAttack";
+let otherKey = "Other/Key";
-AddMock(playerEnt, IID_TechnologyManager, {
- "ApplyModifications": (key, val, ent) => {
+AddMock(SYSTEM_ENTITY, IID_ModifiersManager, {
+ "ApplyModifiers": (key, val, ent) => {
if (key != techKey)
return val;
if (ent == playerEnt)
@@ -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, playerEnt), 5.0);
-TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, ownedEnt), 900.0);
+TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, ownedEnt), 9.0);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_VisionSharing.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_VisionSharing.js
+++ ps/trunk/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/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/VisionSharing.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
@@ -118,7 +118,10 @@
AddMock(14, IID_TechnologyManager, {
"CanProduce": entity => false,
- "ApplyModificationsTemplate": (valueName, curValue, template) => curValue
+});
+
+AddMock(14, IID_ModifiersManager, {
+ "ApplyTemplateModifiers": (valueName, curValue) => curValue
});
TS_ASSERT_UNEVAL_EQUALS(cmpVisionSharing.shared, new Set([1, 2, 5]));
@@ -127,8 +130,12 @@
AddMock(14, IID_TechnologyManager, {
"CanProduce": entity => entity == "special/spy",
- "ApplyModificationsTemplate": (valueName, curValue, template) => curValue
});
+
+AddMock(14, IID_ModifiersManager, {
+ "ApplyTemplateModifiers": (valueName, curValue) => curValue
+});
+
AddMock(14, IID_Player, {
"GetSpyCostMultiplier": () => 1,
"TrySubtractResources": costs => false
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/MultiKeyMap.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/MultiKeyMap.js
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/MultiKeyMap.js
@@ -0,0 +1,225 @@
+// Convenient container abstraction for storing items referenced by a 3-tuple.
+// Used by the ModifiersManager 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.
+// It is designed to be as fast as can be for a JS container.
+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 item - an object.
+ * @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 (references to stored items to avoid copying)
+ * (these need to be treated as constants to not break the map)
+ */
+MultiKeyMap.prototype.GetItems = function(primaryKey, secondaryKey)
+{
+ return this._getItems(primaryKey, secondaryKey);
+};
+
+/**
+ * @returns A dictionary of { Property Name: items } for the secondary Key.
+ * Naively iterates all property names.
+ */
+MultiKeyMap.prototype.GetAllItems = function(secondaryKey)
+{
+ 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);
+ }
+ 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)
+{
+ let cache = this.items.get(primaryKey);
+ if (cache)
+ cache = cache.get(secondaryKey);
+ return cache ? cache : [];
+};
+
+/**
+ * @returns a reference to the list of items for that property name and secondaryKey.
+ */
+MultiKeyMap.prototype._getItemsOrInit = function(primaryKey, secondaryKey)
+{
+ let cache = this.items.get(primaryKey);
+ if (!cache)
+ cache = this.items.set(primaryKey, new Map()).get(primaryKey);
+
+ let cache2 = cache.get(secondaryKey);
+ if (!cache2)
+ cache2 = cache.set(secondaryKey, []).get(secondaryKey);
+ return cache2;
+};
+
+/**
+ * @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(Object.assign({ "_ID": itemID, "_count": 1 }, 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: ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js
@@ -185,6 +185,31 @@
}
/**
+ * @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.
* iid is typically IID_Player.
@@ -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: ps/trunk/binaries/data/mods/public/simulation/helpers/ValueModification.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/ValueModification.js
+++ ps/trunk/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);
+ // entity can be an owned entity or a player entity.
+ let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
+ if (cmpModifiersManager)
+ value = cmpModifiersManager.ApplyModifiers(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 cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
+ if (cmpModifiersManager)
+ value = cmpModifiersManager.ApplyTemplateModifiers(tech_type, current_value, template, playerID);
+ return value;
}
Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/tests/test_MultiKeyMap.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/tests/test_MultiKeyMap.js
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/tests/test_MultiKeyMap.js
@@ -0,0 +1,124 @@
+Engine.LoadHelperScript("MultiKeyMap.js");
+
+function setup_keys(map)
+{
+ map.AddItem("prim_a", "item_a", null, "sec_a");
+ map.AddItem("prim_a", "item_b", null, "sec_a");
+ map.AddItem("prim_a", "item_c", null, "sec_a");
+ map.AddItem("prim_a", "item_a", null, "sec_b");
+ map.AddItem("prim_b", "item_a", null, "sec_a");
+ map.AddItem("prim_c", "item_a", null, "sec_a");
+ map.AddItem("prim_c", "item_a", null, 5);
+}
+
+// Check that key-related operations are correct.
+function test_keys(map)
+{
+ TS_ASSERT(map.items.has("prim_a"));
+ TS_ASSERT(map.items.has("prim_b"));
+ TS_ASSERT(map.items.has("prim_c"));
+
+ TS_ASSERT(map.items.get("prim_a").has("sec_a"));
+ TS_ASSERT(map.items.get("prim_a").has("sec_b"));
+ TS_ASSERT(!map.items.get("prim_a").has("sec_c"));
+ TS_ASSERT(map.items.get("prim_b").has("sec_a"));
+ TS_ASSERT(map.items.get("prim_c").has("sec_a"));
+ TS_ASSERT(map.items.get("prim_c").has(5));
+
+ TS_ASSERT(map.items.get("prim_a").get("sec_a").length == 3);
+ TS_ASSERT(map.items.get("prim_a").get("sec_b").length == 1);
+ TS_ASSERT(map.items.get("prim_b").get("sec_a").length == 1);
+ TS_ASSERT(map.items.get("prim_c").get("sec_a").length == 1);
+ TS_ASSERT(map.items.get("prim_c").get(5).length == 1);
+
+ TS_ASSERT(map.GetItems("prim_a", "sec_a").length == 3);
+ TS_ASSERT(map.GetItems("prim_a", "sec_b").length == 1);
+ TS_ASSERT(map.GetItems("prim_b", "sec_a").length == 1);
+ TS_ASSERT(map.GetItems("prim_c", "sec_a").length == 1);
+ TS_ASSERT(map.GetItems("prim_c", 5).length == 1);
+
+ TS_ASSERT(map.HasItem("prim_a", "item_a", "sec_a"));
+ TS_ASSERT(map.HasItem("prim_a", "item_b", "sec_a"));
+ TS_ASSERT(map.HasItem("prim_a", "item_c", "sec_a"));
+ TS_ASSERT(!map.HasItem("prim_a", "item_d", "sec_a"));
+ TS_ASSERT(map.HasItem("prim_a", "item_a", "sec_b"));
+ TS_ASSERT(!map.HasItem("prim_a", "item_b", "sec_b"));
+ TS_ASSERT(!map.HasItem("prim_a", "item_c", "sec_b"));
+ TS_ASSERT(map.HasItem("prim_b", "item_a", "sec_a"));
+ TS_ASSERT(map.HasItem("prim_c", "item_a", "sec_a"));
+ TS_ASSERT(map.HasAnyItem("item_a", "sec_b"));
+ TS_ASSERT(map.HasAnyItem("item_b", "sec_a"));
+ TS_ASSERT(!map.HasAnyItem("item_d", "sec_a"));
+ TS_ASSERT(!map.HasAnyItem("item_b", "sec_b"));
+
+ // Adding the same item increases its count.
+ map.AddItem("prim_a", "item_b", 0, "sec_a");
+ TS_ASSERT_EQUALS(map.items.get("prim_a").get("sec_a").length, 3);
+ TS_ASSERT_EQUALS(map.items.get("prim_a").get("sec_a").filter(item => item._ID == "item_b")[0]._count, 2);
+ TS_ASSERT_EQUALS(map.GetItems("prim_a", "sec_a").length, 3);
+
+ // Adding without stackable doesn't invalidate caches, adding with does.
+ TS_ASSERT(!map.AddItem("prim_a", "item_b", 0, "sec_a"));
+ TS_ASSERT(map.AddItem("prim_a", "item_b", 0, "sec_a", true));
+
+ TS_ASSERT(map.items.get("prim_a").get("sec_a").filter(item => item._ID == "item_b")[0]._count == 4);
+
+ // Likewise removing, unless we now reach 0
+ TS_ASSERT(!map.RemoveItem("prim_a", "item_b", "sec_a"));
+ TS_ASSERT(map.RemoveItem("prim_a", "item_b", "sec_a", true));
+ TS_ASSERT(!map.RemoveItem("prim_a", "item_b", "sec_a"));
+ TS_ASSERT(map.RemoveItem("prim_a", "item_b", "sec_a"));
+
+ // Check that cleanup is done
+ TS_ASSERT(map.items.get("prim_a").get("sec_a").length == 2);
+ TS_ASSERT(map.RemoveItem("prim_a", "item_a", "sec_a"));
+ TS_ASSERT(map.RemoveItem("prim_a", "item_c", "sec_a"));
+ TS_ASSERT(!map.items.get("prim_a").has("sec_a"));
+ TS_ASSERT(map.items.get("prim_a").has("sec_b"));
+ TS_ASSERT(map.RemoveItem("prim_a", "item_a", "sec_b"));
+ TS_ASSERT(!map.items.has("prim_a"));
+}
+
+function setup_items(map)
+{
+ map.AddItem("prim_a", "item_a", { "value": 1 }, "sec_a");
+ map.AddItem("prim_a", "item_b", { "value": 2 }, "sec_a");
+ map.AddItem("prim_a", "item_c", { "value": 3 }, "sec_a");
+ map.AddItem("prim_a", "item_c", { "value": 1000 }, "sec_a");
+ map.AddItem("prim_a", "item_a", { "value": 5 }, "sec_b");
+ map.AddItem("prim_b", "item_a", { "value": 6 }, "sec_a");
+ map.AddItem("prim_c", "item_a", { "value": 7 }, "sec_a");
+}
+
+// Check that items returned are correct.
+function test_items(map)
+{
+ 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.value * item._count));
+ 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: ps/trunk/source/simulation2/components/tests/test_scripts.h
===================================================================
--- ps/trunk/source/simulation2/components/tests/test_scripts.h
+++ ps/trunk/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)
{