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) {