Index: binaries/data/mods/public/simulation/components/AuraManager.js =================================================================== --- binaries/data/mods/public/simulation/components/AuraManager.js +++ /dev/null @@ -1,275 +0,0 @@ -function AuraManager() {} - -AuraManager.prototype.Schema = - ""; - -AuraManager.prototype.Init = function() -{ - this.modificationsCache = new Map(); - this.modifications = new Map(); - this.templateModificationsCache = new Map(); - this.templateModifications = new Map(); - - this.globalAuraSources = []; -}; - -AuraManager.prototype.RegisterGlobalAuraSource = function(ent) -{ - if (this.globalAuraSources.indexOf(ent) == -1) - this.globalAuraSources.push(ent); -}; - -AuraManager.prototype.UnregisterGlobalAuraSource = function(ent) -{ - let idx = this.globalAuraSources.indexOf(ent); - if (idx != -1) - this.globalAuraSources.splice(idx, 1); -}; - -AuraManager.prototype.ensureExists = function(name, value, id, key, defaultData) -{ - var cacheName = name + "Cache"; - var v = this[name].get(value); - if (!v) - { - v = new Map(); - this[name].set(value, v); - this[cacheName].set(value, new Map()); - } - - var i = v.get(id); - if (!i) - { - i = new Map(); - v.set(id, i); - this[cacheName].get(value).set(id, defaultData); - } - - var k = i.get(key); - if (!k) - { - k = {}; - i.set(key, k); - } - return k; -}; - -AuraManager.prototype.ApplyBonus = function(value, ents, newData, key) -{ - for (let ent of ents) - { - var data = this.ensureExists("modifications", value, ent, key, { "add":0, "multiply":1 }); - - if (data.count) - { - // this aura is already applied and the bonus shouldn't be given twice, - // just count the number of times it is applied - data.count++; - continue; - } - - // first time added this aura - data.multiply = newData.multiply; - data.add = newData.add; - data.count = 1; - - if (data.add) - this.modificationsCache.get(value).get(ent).add += data.add; - if (data.multiply) - this.modificationsCache.get(value).get(ent).multiply *= data.multiply; - - // post message to the entity to notify it about the change - Engine.PostMessage(ent, MT_ValueModification, { - "entities": [ent], - "component": value.split("/")[0], - "valueNames": [value] - }); - } -}; - -AuraManager.prototype.ApplyTemplateBonus = function(value, player, classes, newData, key) -{ - var data = this.ensureExists("templateModifications", value, player, key, new Map()); - - if (data.count) - { - // this aura is already applied and the bonus shouldn't be given twice, - // just count the number of times it is applied - data.count++; - return; - } - - // first time added this aura - data.multiply = newData.multiply; - data.add = newData.add; - data.count = 1; - - let cache = this.templateModificationsCache.get(value).get(player); - - // Do not use the classes array from the JSON file directly, since that is not synchronized - // See MatchesClassList for supported classes formats - for (let className of classes) - { - if (Array.isArray(className)) - className = className.join("+"); - - if (!cache.get(className)) - cache.set(className, new Map()); - - if (!cache.get(className).get(key)) - cache.get(className).set(key, { "add": 0, "multiply": 1 }); - - if (data.add) - cache.get(className).get(key).add += data.add; - if (data.multiply) - cache.get(className).get(key).multiply *= data.multiply; - } - - Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { - "player": player, - "component": value.split("/")[0], - "valueNames": [value] - }); -}; - -AuraManager.prototype.RemoveBonus = function(value, ents, key) -{ - var v = this.modifications.get(value); - if (!v) - return; - - for (let ent of ents) - { - var e = v.get(ent); - if (!e) - continue; - var data = e.get(key); - if (!data || !data.count) - continue; - - data.count--; - - if (data.count > 0) - continue; - - // out of last aura of this kind, remove modifications - if (data.add) - this.modificationsCache.get(value).get(ent).add -= data.add; - - if (data.multiply) - this.modificationsCache.get(value).get(ent).multiply /= data.multiply; - - // clean up the object - e.delete(key); - if (e.size == 0) - v.delete(ent); - - // post message to the entity to notify it about the change - Engine.PostMessage(ent, MT_ValueModification, { - "entities": [ent], - "component": value.split("/")[0], - "valueNames": [value] - }); - } -}; - -AuraManager.prototype.RemoveTemplateBonus = function(value, player, classes, key) -{ - var v = this.templateModifications.get(value); - if (!v) - return; - var p = v.get(player); - if (!p) - return; - var data = p.get(key); - if (!data || !data.count) - return; - - data.count--; - - if (data.count > 0) - return; - - for (let className of classes) - { - if (Array.isArray(className)) - className = className.join("+"); - - this.templateModificationsCache.get(value).get(player).get(className).delete(key); - - if (this.templateModificationsCache.get(value).get(player).get(className).size == 0) - this.templateModificationsCache.get(value).get(player).delete(className); - } - - // clean up the object - p.delete(key); - if (p.size == 0) - v.delete(player); - - Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { - "player": player, - "component": value.split("/")[0], - "valueNames": [value] - }); -}; - -AuraManager.prototype.ApplyModifications = function(valueName, value, ent) -{ - var v = this.modificationsCache.get(valueName); - if (!v) - return value; - var cache = v.get(ent); - if (!cache) - return value; - - value *= cache.multiply; - value += cache.add; - return value; -}; - -AuraManager.prototype.ApplyTemplateModifications = function(valueName, value, player, template) -{ - var v = this.templateModificationsCache.get(valueName); - if (!v) - return value; - var cache = v.get(player); - if (!cache) - return value; - - if (!template || !template.Identity) - return value; - var classes = GetIdentityClasses(template.Identity); - - var usedKeys = new Set(); - var add = 0; - var multiply = 1; - - for (let [className, mods] of cache) - { - if (!MatchesClassList(classes, [className])) - continue; - - for (let [key, mod] of mods) - { - // don't add an aura with the same key twice - if (usedKeys.has(key)) - continue; - add += mod.add; - multiply *= mod.multiply; - usedKeys.add(key); - } - } - return value * multiply + add; -}; - -AuraManager.prototype.OnGlobalOwnershipChanged = function(msg) -{ - for (let ent of this.globalAuraSources) - { - let cmpAuras = Engine.QueryInterface(ent, IID_Auras); - if (cmpAuras) - cmpAuras.RegisterGlobalOwnershipChanged(msg); - } -}; - -Engine.RegisterSystemComponentType(IID_AuraManager, "AuraManager", AuraManager); Index: binaries/data/mods/public/simulation/components/Auras.js =================================================================== --- binaries/data/mods/public/simulation/components/Auras.js +++ binaries/data/mods/public/simulation/components/Auras.js @@ -243,8 +243,10 @@ if (this.IsGlobalAura(name)) { this.ApplyTemplateBonus(name, affectedPlayers); - for (let player of affectedPlayers) - this.ApplyBonus(name, cmpRangeManager.GetEntitiesByPlayer(player)); + // will only apply icons + if (this.GetOverlayIcon(name)) + for (let player of affectedPlayers) + this.ApplyBonus(name, cmpRangeManager.GetEntitiesByPlayer(player)); continue; } @@ -263,15 +265,15 @@ 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); } @@ -315,20 +317,6 @@ } }; -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) { for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n))) @@ -348,15 +336,17 @@ 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 cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager); - for (let mod of modifications) - for (let player of players) - cmpAuraManager.ApplyTemplateBonus(mod.value, player, classes, mod, this.GetModifierIdentifier(name)); + let derivedModifiers = DeriveModificationsFromTech({ + modifications: this.GetModifications(name), + affects: this.GetClasses(name) + }); + for (let player of players) + for (let modifierPath in derivedModifiers) + for (let modifier of derivedModifiers[modifierPath]) + cmpModificationsManager.AddPlayerModif(modifierPath, "aura/" + this.GetModifierIdentifier(name), modifier, player); }; Auras.prototype.RemoveFormationBonus = function(memberList) @@ -378,16 +368,16 @@ if (!this.IsGlobalAura(name)) return; - var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); - cmpAuraManager.UnregisterGlobalAuraSource(this.entity); + let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager); - var modifications = this.GetModifications(name); - var classes = this.GetClasses(name); - var players = this.GetAffectedPlayers(name); - - for (let mod of modifications) - for (let player of players) - cmpAuraManager.RemoveTemplateBonus(mod.value, player, classes, this.GetModifierIdentifier(name)); + let derivedModifiers = DeriveModificationsFromTech({ + modifications: this.GetModifications(name), + affects: this.GetClasses(name) + }); + for (let player of this.GetAffectedPlayers(name)) + for (let modifierPath in derivedModifiers) + for (let modifier of derivedModifiers[modifierPath]) + cmpModificationsManager.RemovePlayerModif(modifierPath, "aura/" + this.GetModifierIdentifier(name), player); }; Auras.prototype.ApplyBonus = function(name, ents) @@ -401,21 +391,31 @@ if (!this[name].isApplied) return; - var modifications = this.GetModifications(name); - var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); - - for (let mod of modifications) - cmpAuraManager.ApplyBonus(mod.value, validEnts, mod, this.GetModifierIdentifier(name)); // update status bars if this has an icon - if (!this.GetOverlayIcon(name)) + if (this.GetOverlayIcon(name)) { + for (let ent of validEnts) + { + let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars); + if (cmpStatusBars) + cmpStatusBars.AddAuraSource(this.entity, name); + } + } + + // Global aura modifications are handled at the player level by the modification manager. + if (this.IsGlobalAura(name)) return; + let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager); + + let derivedModifiers = DeriveModificationsFromTech({ + modifications: this.GetModifications(name), + affects: this.GetClasses(name) + }); for (let ent of validEnts) - { - var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars); - if (cmpStatusBars) - cmpStatusBars.AddAuraSource(this.entity, name); - } + for (let modifierPath in derivedModifiers) + for (let modifier of derivedModifiers[modifierPath]) + cmpModificationsManager.AddEntityModif(modifierPath, "aura/" + this.GetModifierIdentifier(name), modifier, ent); + }; Auras.prototype.RemoveBonus = function(name, ents) @@ -429,22 +429,30 @@ if (!this[name].isApplied) return; - var modifications = this.GetModifications(name); - var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); - - for (let mod of modifications) - cmpAuraManager.RemoveBonus(mod.value, validEnts, this.GetModifierIdentifier(name)); - // update status bars if this has an icon - if (!this.GetOverlayIcon(name)) + if (this.GetOverlayIcon(name)) { + for (let ent of validEnts) + { + let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars); + if (cmpStatusBars) + cmpStatusBars.RemoveAuraSource(this.entity, name); + } + } + + // Global aura modifications are handled at the player level by the modification manager. + if (this.IsGlobalAura(name)) return; - for (let ent of validEnts) - { - var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars); - if (cmpStatusBars) - cmpStatusBars.RemoveAuraSource(this.entity, name); - } + let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager); + + let derivedModifiers = DeriveModificationsFromTech({ + modifications: this.GetModifications(name), + affects: this.GetClasses(name) + }); + for (let ent of ents) + for (let modifierPath in derivedModifiers) + for (let modifier of derivedModifiers[modifierPath]) + cmpModificationsManager.RemoveEntityModif(modifierPath, "aura/" + this.GetModifierIdentifier(name), ent); }; Auras.prototype.OnOwnershipChanged = function(msg) @@ -484,7 +492,7 @@ { let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); if (cmpPlayer && cmpPlayer.GetPlayerID() == msg.playerId || - this.GetAuraNames().some(name => this.GetAffectedPlayers(name).indexOf(msg.playerId) != -1)) + this.GetAuraNames().some(name => this.GetAffectedPlayers(name).indexOf(msg.playerId) != -1)) this.Clean(); }; Index: binaries/data/mods/public/simulation/components/ModificationsManager.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/ModificationsManager.js @@ -0,0 +1,365 @@ +function ModificationsManagerCommon(cache) +{ + // TODO: + // - add a way to show an icon for a given modification ID (convenient for auras) + + // This holds the list of modifications in an array. + // See GetTechModifiedProperty for what a Modification must be. It's transparent to this component. + this.modifs = new Map(); // Keyed by property name and entity/player ID + + this.cachedValues = cache; +}; + +ModificationsManagerCommon.prototype._getModifs = function(propertyName, target) +{ + return this.modifs.get(propertyName).get(target); +} + +ModificationsManagerCommon.prototype._exists = function(propertyName, target) +{ + if (!this.modifs.get(propertyName)) + return false; + if (!this.modifs.get(propertyName).get(target)) + return false; + return true; +} + +ModificationsManagerCommon.prototype._initModifsIfNeeded = function(propertyName, target) +{ + if (!this.modifs.get(propertyName)) + this.modifs.set(propertyName, new Map()); + if (!this.modifs.get(propertyName).get(target)) + this.modifs.get(propertyName).set(target, []); +} + +/** + * Add a modification. + * @param propertyName - Handle of a technology property (eg "Attack/Ranged/Pierce") + * @param modifID - internal ID of this modification, for later removal and/or updating + * @param modification - Object describing the change. + * @param target - target ID - may be a player ID or an entity ID. + */ +ModificationsManagerCommon.prototype.AddModif = function(propertyName, modifID, modification, target) +{ + this._initModifsIfNeeded(propertyName, target); + + if (this._AddModif(propertyName, modifID, modification, target)) + this._InvalidateCache(propertyName, target); + + this._SendModificationMessages(propertyName, target); +}; + +ModificationsManagerCommon.prototype.AddModifs = function(modifID, modifs, target) +{ + for (let propertyName in modifs) { + this.AddModif(propertyName, modifID, modifs[propertyName], target); + } +}; + +/** + * Removes a modification on a property. + * @param propertyName - property to change (e.g. "Health/Max") + * @param modifID - internal ID of the modification to remove + * @param target - target ID - may be a player ID or an entity ID. + */ +ModificationsManagerCommon.prototype.RemoveModif = function(propertyName, modifID, target) +{ + if (!this._exists(propertyName, target)) + return; + + if (this._RemoveModif(propertyName, modifID, target)) + this._InvalidateCache(propertyName, target); + + this._SendModificationMessages(propertyName, target); +}; + +/** + * Removes modifications with this ID across all property names. + * Naively iterates all property names, which is slower but that should not be an issue. + */ +ModificationsManagerCommon.prototype.RemoveAllModifs = function(modifID, target) +{ + for (let propertyName of this.modifs.keys()) + this.RemoveModif(propertyName, modifID, target); +}; + +/** + * Check if we have a modification for a given property + * @param propertyName - property to change (eg "Health/Max") + * @param modifID - internal ID of the modification to try and find. + * @param target - target ID - may be a player ID or an entity ID. + * @returns true if there is at least one modification with that modifID + */ +ModificationsManagerCommon.prototype.HasModif = function(propertyName, modifID, target) +{ + if (!this._exists(propertyName, target)) + return false; + + return this._getModifs(propertyName, target).some(modification => { return modification.ID === modifID; }); +}; + +ModificationsManagerCommon.prototype.HasAnyModif = function(modifID, target) +{ + // Map doesn't implement some so use for loops instead. + for (let propertyName of this.modifs.keys()) + if (this.HasModif(propertyName, modifID, target)) + return true; + return false; +}; + +/** + * Internal use. Call AddModif() from outside this script. + * @returns true if the modifications list changed, i.e. the cache is invalidated. Returns false otherwise. + */ +ModificationsManagerCommon.prototype._AddModif = function(propertyName, modifID, modification, target) +{ + // TODO: this was implemented for aura support, is it really necessary? + // If this is not the first time we are applying this modification, the cached value won't change + // as we don't stack modifications at the moment, so return false + let existingModif = this._getModifs(propertyName, target).filter(modif => { return modif.ID == modifID; }); + if (existingModif.length) + { + existingModif[0].count++; + return false; + } + + this._getModifs(propertyName, target).push({ "ID": modifID, "count": 1, "effect": modification }); + return true; +}; + +/** + * Internal use. Call RemoveModif() from outside this script. + * @returns true if the modifications list changed, i.e. the cache is invalidated. Returns false otherwise. + */ +ModificationsManagerCommon.prototype._RemoveModif = function(propertyName, modifID, target) +{ + let existingModif = this._getModifs(propertyName, target).filter(modif => { return modif.ID == modifID; }); + if (!existingModif.length) + return false; + + // TODO: this was implemented for aura support, is it really necessary? + // If we still want to apply this modification, no changes to the cache (no stacking) so return false. + if (--existingModif[0].count > 0) + return false; + + let modificationsList = this._getModifs(propertyName, target).filter(modif => { return modif.count > 0; }); + + // Update the map, deleting entries if necessary + if (modificationsList.length) + this.modifs.get(propertyName).set(target, modificationsList); + else + { + this.modifs.get(propertyName).delete(target); + if (!this.modifs.get(propertyName).length) + this.modifs.delete(propertyName); + } + + return true; +}; + + +ModificationsManagerCommon.prototype._InvalidateCache = function(propertyName, target) +{ + if (this.cachedValues.has(propertyName)) + { + this.cachedValues.get(propertyName).delete(target); + if (!this.cachedValues.get(propertyName).length) + this.cachedValues.delete(propertyName); + } +}; + +/** + * Inform entities that we have changed possibly all values affected by that property. + * It's not hugely efficient and would be nice to batch. + * @param propertyName - handle of a technology property (eg Attack/Ranged/Pierce) that was changed. + * @param target - target ID - may be a player ID or an entity ID. + */ +ModificationsManagerCommon.prototype._SendModificationMessages = function(propertyName, target) {}; + +/** + * Internal use only. + * @returns referenceValue after modifications. + */ +ModificationsManagerCommon.prototype._FetchModifiedProperty = function(classesList, propertyName, referenceValue, target) +{ + let flatModifs = { [propertyName]: [] }; + if (this._exists(propertyName, target)) + for (let modif of this._getModifs(propertyName, target)) + flatModifs[propertyName].push(modif.effect); + + return GetTechModifiedProperty(flatModifs, classesList, propertyName, referenceValue); +}; + +function ModificationsManager() {} + +ModificationsManager.prototype.Schema = + ""; + +ModificationsManager.prototype.Init = function() +{ + // 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/player ID, original values. + + this.playerModifs = new ModificationsManagerCommon(this.cachedValues); + this.entityModifs = new ModificationsManagerCommon(this.cachedValues); + + this.playerModifs._SendModificationMessages = this.SendPlayerModificationMessages; + this.entityModifs._SendModificationMessages = this.SendEntityModificationMessages; + + // Create Player and Entity variants of all 'public' common methods on the core class. + for (let propName in this.playerModifs) + { + if (typeof this.playerModifs[propName] !== "function" || propName[0] === '_') + continue; + this[propName.replace('Modif','PlayerModif')] = ((a,b) => () => a[b].apply(a, arguments))(this.playerModifs, propName); + this[propName.replace('Modif','EntityModif')] = ((a,b) => () => a[b].apply(a, arguments))(this.entityModifs, propName); + } +} + +ModificationsManager.prototype.Serialize = function() +{ + // The 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. + let ret = {}; + + ret.playerModifs = this.playerModifs.modifs; + ret.entityModifs = this.entityModifs.modifs; + + return ret; +} + +ModificationsManager.prototype.Deserialize = function(data) +{ + this.Init(); + + this.playerModifs.modifs = data.playerModifs; + this.entityModifs.modifs = data.entityModifs; +} + +ModificationsManager.prototype.SendEntityModificationMessages = function(propertyName, entity) +{ + // TODO: if this is slow, it might be more efficient to keep track of who wants our info and post them directly + Engine.BroadcastMessage(MT_ValueModification, { "entities": [entity], "component": propertyName.split("/")[0], "valueNames": [propertyName] }); +} + +ModificationsManager.prototype.SendPlayerModificationMessages = function(propertyName, player) +{ + // TODO: it would be preferable to be able to batch this (i.e. one message for several properties) + Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": player, "component": propertyName.split("/")[0], "valueNames": [propertyName] }); + // AIInterface wants the entities potentially affected. + // TODO: improve on this + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + let ents = cmpRangeManager.GetEntitiesByPlayer(player); + Engine.BroadcastMessage(MT_ValueModification, { "entities": ents, "component": propertyName.split("/")[0], "valueNames": [propertyName] }); +}; + +/** + * Caching system in front of GetTechModifiedProperty() and such, as calling that every time is quite slow. + * This recomputes lazily. + * Applies per-player modifications before per-entity modifications, so the latter take priority; + * @param propertyName - Handle of a technology property (eg Attack/Ranged/Pierce) that was changed. + * @param originalValue - template/raw/before-modifications value. + Note that if this is supposed to be a number (i.e. you call add/multiply on it) + You must make sure to pass a number and not a string (by using + if necessary) + * @param ent - ID of the target entity + * @returns originalValue after the modifications + */ +ModificationsManager.prototype.ApplyModifications = function(propertyName, originalValue, entity) +{ + let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); + if (!cmpIdentity) + return originalValue; + + // Sanitize input. + if (Array.isArray(originalValue)) + // Some code bits pass an array instead of a single value. + // Those bits should probably be changed, but it is supported in the meantime. + originalValue = originalValue.slice(); + else if (typeof originalValue == "object") + { + error("ModificationsManager ApplyModifications() called with an object instead of a number."); + return undefined; + } + + // Return cached value if present. + if (this.cachedValues.has(propertyName) && + this.cachedValues.get(propertyName).has(entity) && + this.cachedValues.get(propertyName).get(entity).has(originalValue)) + return this.cachedValues.get(propertyName).get(entity).get(originalValue); + + // Else initialise the cache. + if (!this.cachedValues.get(propertyName)) + this.cachedValues.set(propertyName, new Map()); + if (!this.cachedValues.get(propertyName).get(entity)) + this.cachedValues.get(propertyName).set(entity, new Map()); + if (!this.cachedValues.get(propertyName).get(entity).get(originalValue)) + this.cachedValues.get(propertyName).get(entity).set(originalValue, originalValue); + + // entity could be a player entity. + let cmpPlayer = Engine.QueryInterface(entity, IID_Player) ? + Engine.QueryInterface(entity, IID_Player) : QueryOwnerInterface(entity); + + let newValue = originalValue; + if (cmpPlayer) + newValue = this.playerModifs._FetchModifiedProperty(cmpIdentity.GetClassesList(), propertyName, originalValue, cmpPlayer.GetPlayerID()); + newValue = this.entityModifs._FetchModifiedProperty(cmpIdentity.GetClassesList(), propertyName, newValue, entity); + + this.cachedValues.get(propertyName).get(entity).set(originalValue, newValue); + + return newValue; +}; + +/** + * Alternative version of ApplyModifications, applies to templates instead of entities + * Only needs to handle global modifications + */ +ModificationsManager.prototype.ApplyModificationsTemplate = function(propertyName, originalValue, template, player) +{ + if (!template || !template.Identity || !this.playerModifs._exists(propertyName, player)) + return originalValue; + return this.playerModifs._FetchModifiedProperty(GetIdentityClasses(template.Identity), propertyName, originalValue, player); +}; + +/** + * Handle modifications when a unit is created. + */ +ModificationsManager.prototype.OnGlobalOwnershipChanged = function(msg) +{ + // Only do this for created entities, not captures/conversions as we want + // captured/converted entities to retain their former technologies. + if (msg.from != -1 || msg.to == -1) + return; + + let cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity); + if (!cmpIdentity) + return; + + let classes = cmpIdentity.GetClassesList(); + + let template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetCurrentTemplateName(msg.entity); + + // Local modifications will be added by the relevant components, so no need to check for them here. + let modifiedComponents = {}; + for (let propertyName in this.playerModifs.modifs.keys()) + { + // We only need to find one one tech per component for a match + let modifications = this.playerModifs._getModifs(propertyName, msg.to); + let component = propertyName.split("/")[0]; + for (let modif of modifications) + { + if (DoesModificationApply(modif.effect, classes)) + { + if (!modifiedComponents[component]) + modifiedComponents[component] = []; + modifiedComponents[component].push(propertyName); + } + } + } + + // Send message(s) to the entity so it knows about researched techs + for (let component in modifiedComponents) + Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] }); +}; + +Engine.RegisterSystemComponentType(IID_ModificationsManager, "ModificationsManager", ModificationsManager); Index: binaries/data/mods/public/simulation/components/TechnologyManager.js =================================================================== --- binaries/data/mods/public/simulation/components/TechnologyManager.js +++ binaries/data/mods/public/simulation/components/TechnologyManager.js @@ -3,21 +3,6 @@ TechnologyManager.prototype.Schema = ""; -TechnologyManager.prototype.Serialize = function() -{ - // The modifications cache will be affected by property reads from the GUI and other places so we shouldn't - // serialize it. - - var ret = {}; - for (var i in this) - { - if (this.hasOwnProperty(i)) - ret[i] = this[i]; - } - ret.modificationCache = {}; - return ret; -}; - TechnologyManager.prototype.Init = function() { // Holds names of technologies that have been researched. @@ -29,16 +14,6 @@ // Holds technologies which are being researched currently (non-queued). this.researchStarted = new Set(); - // This stores the modifications to unit stats from researched technologies - // Example data: {"ResourceGatherer/Rates/food.grain": [ - // {"multiply": 1.15, "affects": ["FemaleCitizen", "Infantry Sword"]}, - // {"add": 2} - // ]} - this.modifications = {}; - this.modificationCache = {}; // Caches the values after technologies have been applied - // e.g. { "Attack/Melee/Hack" : {5: {"origValue": 8, "newValue": 10}, 7: {"origValue": 9, "newValue": 12}, ...}, ...} - // where 5 and 7 are entity id's - this.classCounts = {}; // stores the number of entities of each Class this.typeCountsByClass = {}; // stores the number of entities of each type for each class i.e. // {"someClass": {"unit/spearman": 2, "unit/cav": 5} "someOtherClass":...} @@ -204,31 +179,6 @@ this.typeCountsByClass[cls][template] += 1; } } - - // Newly created entity, check if any researched techs might apply - // (only do this for new entities because even if an entity is converted or captured, - // we want it to maintain whatever technologies previously applied) - if (msg.from == INVALID_PLAYER) - { - var modifiedComponents = {}; - for (var name in this.modifications) - { - // We only need to find one one tech per component for a match - var modifications = this.modifications[name]; - var component = name.split("/")[0]; - for (let modif of modifications) - if (DoesModificationApply(modif, classes)) - { - if (!modifiedComponents[component]) - modifiedComponents[component] = []; - modifiedComponents[component].push(name); - } - } - - // Send mesage(s) to the entity so it knows about researched techs - for (var component in modifiedComponents) - Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] }); - } } if (msg.from == playerID) { @@ -254,8 +204,6 @@ } } } - - this.clearModificationCache(msg.entity); } }; @@ -266,23 +214,16 @@ var modifiedComponents = {}; this.researchedTechs.add(tech); + // store the modifications in an easy to access structure let template = TechnologyTemplates.Get(tech); if (template.modifications) { + let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager); let derivedModifiers = DeriveModificationsFromTech(template); for (let modifierPath in derivedModifiers) - { - if (!this.modifications[modifierPath]) - this.modifications[modifierPath] = []; - this.modifications[modifierPath] = this.modifications[modifierPath].concat(derivedModifiers[modifierPath]); - - let component = modifierPath.split("/")[0]; - if (!modifiedComponents[component]) - modifiedComponents[component] = []; - modifiedComponents[component].push(modifierPath); - this.modificationCache[modifierPath] = {}; - } + for (let modifier of derivedModifiers[modifierPath]) + cmpModificationsManager.AddPlayerModif(modifierPath, "tech/" + tech, modifier, this.entity); } if (template.replaces && template.replaces.length > 0) @@ -323,62 +264,6 @@ // always send research finished message Engine.PostMessage(this.entity, MT_ResearchFinished, {"player": playerID, "tech": tech}); - - for (var component in modifiedComponents) - { - Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": playerID, "component": component, "valueNames": modifiedComponents[component]}); - Engine.BroadcastMessage(MT_ValueModification, { "entities": ents, "component": component, "valueNames": modifiedComponents[component]}); - } - - if (tech.startsWith("phase") && !template.autoResearch) - { - let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGUIInterface.PushNotification({ - "type": "phase", - "players": [playerID], - "phaseName": tech, - "phaseState": "completed" - }); - } -}; - -// Clears the cached data for an entity from the modifications cache -TechnologyManager.prototype.clearModificationCache = function(ent) -{ - for (var valueName in this.modificationCache) - delete this.modificationCache[valueName][ent]; -}; - -// Caching layer in front of ApplyModificationsWorker -// Note: be careful with the type of curValue, if it should be a numerical -// value and is derived from template data, you must convert the string -// from the template to a number using the + operator, before calling -// this function! -TechnologyManager.prototype.ApplyModifications = function(valueName, curValue, ent) -{ - if (!this.modificationCache[valueName]) - this.modificationCache[valueName] = {}; - - if (!this.modificationCache[valueName][ent] || this.modificationCache[valueName][ent].origValue != curValue) - { - let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - if (!cmpIdentity) - return curValue; - this.modificationCache[valueName][ent] = { - "origValue": curValue, - "newValue": GetTechModifiedProperty(this.modifications, cmpIdentity.GetClassesList(), valueName, curValue) - }; - } - - return this.modificationCache[valueName][ent].newValue; -}; - -// Alternative version of ApplyModifications, applies to templates instead of entities -TechnologyManager.prototype.ApplyModificationsTemplate = function(valueName, curValue, template) -{ - if (!template || !template.Identity) - return curValue; - return GetTechModifiedProperty(this.modifications, GetIdentityClasses(template.Identity), valueName, curValue); }; /** Index: binaries/data/mods/public/simulation/components/interfaces/AuraManager.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/AuraManager.js +++ /dev/null @@ -1 +0,0 @@ -Engine.RegisterInterface("AuraManager"); Index: binaries/data/mods/public/simulation/components/interfaces/ModificationsManager.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/interfaces/ModificationsManager.js @@ -0,0 +1 @@ +Engine.RegisterInterface("ModificationsManager"); Index: binaries/data/mods/public/simulation/components/tests/test_Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Attack.js +++ binaries/data/mods/public/simulation/components/tests/test_Attack.js @@ -3,9 +3,9 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("ValueModification.js"); Engine.LoadComponentScript("interfaces/Attack.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Capturable.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); Engine.LoadComponentScript("interfaces/Formation.js"); Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/TechnologyManager.js"); Index: binaries/data/mods/public/simulation/components/tests/test_AuraManager.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_AuraManager.js +++ /dev/null @@ -1,43 +0,0 @@ -Engine.LoadComponentScript("interfaces/AuraManager.js"); -Engine.LoadComponentScript("AuraManager.js"); - -let value = "Component/Value"; -let player1 = 1; -let player2 = 2; -let ents1 = [25, 26, 27]; -let ents2 = [28, 29, 30]; -let ents3 = [31]; -let classes = ["class1", "class2"]; -let template = { "Identity" : { "Classes" : { "_string" : "class1 class3" } } }; - -let cmpAuraManager = ConstructComponent(SYSTEM_ENTITY, "AuraManager", {}); - -// Apply and remove a bonus -cmpAuraManager.ApplyBonus(value, ents1, { "add": 8 }, "key1"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 25), 18); -// It isn't apply to wrong entity -TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 28), 10); -cmpAuraManager.RemoveBonus(value, ents1, "key1"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 25), 10); - -// Apply 2 bonus with two different keys. Bonus should stack -cmpAuraManager.ApplyBonus(value, ents2, { "add": 8 }, "key1"); -cmpAuraManager.ApplyBonus(value, ents2, { "multiply": 3 }, "key2"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 28), 38); - -// With another operation ordering, the result must be the same -cmpAuraManager.ApplyBonus(value, ents3, { "multiply": 3 }, "key2"); -cmpAuraManager.ApplyBonus(value, ents3, { "add": 8 }, "key1"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyModifications(value, 10, 31), 38); - -// Apply bonus to templates -cmpAuraManager.ApplyTemplateBonus(value, player1, classes, { "add": 10 }, "key3"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player1, template), 310); -cmpAuraManager.RemoveTemplateBonus(value, player1, classes, "key3"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player1, template), 300); -cmpAuraManager.ApplyTemplateBonus(value, player2, classes, { "add": 10 }, "key3"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player2, template), 310); -cmpAuraManager.ApplyTemplateBonus(value, player1, classes, { "add": 10 }, "key3"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player1, template), 310); -cmpAuraManager.RemoveTemplateBonus(value, player2, classes, "key3"); -TS_ASSERT_EQUALS(cmpAuraManager.ApplyTemplateModifications(value, 300, player2, template), 300); Index: binaries/data/mods/public/simulation/components/tests/test_Auras.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Auras.js +++ binaries/data/mods/public/simulation/components/tests/test_Auras.js @@ -1,11 +1,11 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("ValueModification.js"); Engine.LoadComponentScript("interfaces/Auras.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/RangeOverlayManager.js"); Engine.LoadComponentScript("interfaces/TechnologyManager.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); Engine.LoadComponentScript("Auras.js"); -Engine.LoadComponentScript("AuraManager.js"); +Engine.LoadComponentScript("ModificationsManager.js"); var playerID = [0, 1, 2]; var playerEnt = [10, 11, 12]; @@ -89,7 +89,7 @@ "GetOwner": () => playerID[1] }); - ConstructComponent(SYSTEM_ENTITY, "AuraManager", {}); + ConstructComponent(SYSTEM_ENTITY, "ModificationsManager", {}); let cmpAuras = ConstructComponent(sourceEnt, "Auras", { "_string": name }); test_function(name, cmpAuras); } Index: binaries/data/mods/public/simulation/components/tests/test_Capturable.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Capturable.js +++ binaries/data/mods/public/simulation/components/tests/test_Capturable.js @@ -1,11 +1,10 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("ValueModification.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Capturable.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); -Engine.LoadComponentScript("interfaces/TechnologyManager.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); Engine.LoadComponentScript("interfaces/TerritoryDecay.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("Capturable.js"); Index: binaries/data/mods/public/simulation/components/tests/test_Damage.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Damage.js +++ binaries/data/mods/public/simulation/components/tests/test_Damage.js @@ -5,14 +5,13 @@ Engine.LoadHelperScript("ValueModification.js"); Engine.LoadComponentScript("interfaces/Attack.js"); Engine.LoadComponentScript("interfaces/AttackDetection.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Damage.js"); Engine.LoadComponentScript("interfaces/DamageReceiver.js"); Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/Loot.js"); Engine.LoadComponentScript("interfaces/Player.js"); Engine.LoadComponentScript("interfaces/Promotion.js"); -Engine.LoadComponentScript("interfaces/TechnologyManager.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("Attack.js"); Engine.LoadComponentScript("Damage.js"); Index: binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js +++ binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js @@ -1,7 +1,6 @@ Engine.LoadHelperScript("DamageBonus.js"); Engine.LoadHelperScript("DamageTypes.js"); Engine.LoadHelperScript("ValueModification.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Damage.js"); Engine.LoadComponentScript("interfaces/DeathDamage.js"); Engine.LoadComponentScript("interfaces/TechnologyManager.js"); Index: binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js +++ binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js @@ -3,11 +3,10 @@ Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Garrisonable.js"); Engine.LoadComponentScript("GarrisonHolder.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/ProductionQueue.js"); -Engine.LoadComponentScript("interfaces/TechnologyManager.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); Index: binaries/data/mods/public/simulation/components/tests/test_ModificationsManager.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/tests/test_ModificationsManager.js @@ -0,0 +1,96 @@ +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); +Engine.LoadComponentScript("ModificationsManager.js"); +Engine.LoadHelperScript("Player.js"); +Engine.LoadHelperScript("ValueModification.js"); + +let cmpModificationsManager = ConstructComponent(SYSTEM_ENTITY, "ModificationsManager", {}); +cmpModificationsManager.Init(); + +const PLAYER_ID_FOR_TEST = 2; + +AddMock(SYSTEM_ENTITY, IID_RangeManager, { + "GetEntitiesByPlayer": function(a) { return []; }, +}); + +AddMock(SYSTEM_ENTITY, IID_PlayerManager, { + "GetPlayerByID": (a) => PLAYER_ID_FOR_TEST, +}); + +AddMock(PLAYER_ID_FOR_TEST, IID_Player, { + "GetPlayerID": () => PLAYER_ID_FOR_TEST, +}); + + +let entitiesToTest = [5, 6, 7, 8]; +// Create ownership, set to "2". +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";} +}); + +cmpModificationsManager.AddEntityModif("Test_A", "Test_A_0", { "affects": ["Structure"], "add": 10 }, 10, "testLol"); + +cmpModificationsManager.AddPlayerModif("Test_A", "Test_A_0", { "affects": ["Structure"], "add": 10 }, PLAYER_ID_FOR_TEST); +cmpModificationsManager.AddPlayerModif("Test_A", "Test_A_1", { "affects": ["Infantry"], "add": 5 }, PLAYER_ID_FOR_TEST); +cmpModificationsManager.AddPlayerModif("Test_A", "Test_A_2", { "affects": ["Unit"], "add": 3 }, PLAYER_ID_FOR_TEST); + +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 15); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 6), 10); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 7), 8); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 8), 18); + +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_B", 5, 8), 5); + +cmpModificationsManager.RemoveAllPlayerModifs("Test_A_0", PLAYER_ID_FOR_TEST); + +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 5); + +cmpModificationsManager.AddPlayerModifs("Test_A_0", { + "Test_A": { "affects": ["Structure"], "add": 10 }, + "Test_B": { "affects": ["Structure"], "add": 8 }, +}, PLAYER_ID_FOR_TEST); + +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_A", 5, 5), 15); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_B", 5, 8), 13); + +// Add two local modifications, only the first should stick. +cmpModificationsManager.AddEntityModif("Test_C", "Test_C_0", { "affects": ["Structure"], "add": 10 }, 5); +cmpModificationsManager.AddEntityModif("Test_C", "Test_C_1", { "affects": ["Unit"], "add": 5 }, 5); + +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 15); + +// test that local modifications are indeed applied after global managers +cmpModificationsManager.AddEntityModif("Test_C", "Test_C_2", { "affects": ["Structure"], "replace": 0 }, 5); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0); + +TS_ASSERT(!cmpModificationsManager.HasAnyPlayerModif("Test_C_3", PLAYER_ID_FOR_TEST)); + +// check that things still work properly if we change global modifications +cmpModificationsManager.AddPlayerModif("Test_C", "Test_C_3", { "affects": ["Structure"], "add": 10 }, PLAYER_ID_FOR_TEST); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 0); + +TS_ASSERT(cmpModificationsManager.HasAnyPlayerModif("Test_C_3", PLAYER_ID_FOR_TEST)); +TS_ASSERT(cmpModificationsManager.HasPlayerModif("Test_C", "Test_C_3", PLAYER_ID_FOR_TEST)); +TS_ASSERT(cmpModificationsManager.HasEntityModif("Test_C", "Test_C_2", 5)); + +// test removal +cmpModificationsManager.RemoveEntityModif("Test_C", "Test_C_2", 5); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity("Test_C", 5, 5), 25); + +TS_ASSERT(cmpModificationsManager.HasPlayerModif("Test_C", "Test_C_3", PLAYER_ID_FOR_TEST)); +TS_ASSERT(!cmpModificationsManager.HasEntityModif("Test_C", "Test_C_2", 5)); + +// TODO: test ownership changes, updating. Index: binaries/data/mods/public/simulation/components/tests/test_Pack.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Pack.js +++ binaries/data/mods/public/simulation/components/tests/test_Pack.js @@ -2,8 +2,7 @@ Engine.LoadHelperScript("Sound.js"); Engine.LoadHelperScript("Transform.js"); Engine.LoadHelperScript("ValueModification.js"); -Engine.LoadComponentScript("interfaces/TechnologyManager.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); Engine.LoadComponentScript("interfaces/Capturable.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Guard.js"); Index: binaries/data/mods/public/simulation/components/tests/test_Player.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Player.js +++ binaries/data/mods/public/simulation/components/tests/test_Player.js @@ -15,9 +15,8 @@ }; Engine.LoadHelperScript("ValueModification.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Player.js"); -Engine.LoadComponentScript("interfaces/TechnologyManager.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); Engine.LoadComponentScript("Player.js"); var cmpPlayer = ConstructComponent(10, "Player", { Index: binaries/data/mods/public/simulation/components/tests/test_Technologies_effects.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/tests/test_Technologies_effects.js @@ -0,0 +1,45 @@ +// TODO: Move this to a folder of tests for GlobalScripts (once one is created) + +// This tests the GetTechModifiedProperty function. + +let add = { + "Test_A": [{ "add": 10, "affects": "Unit" }] +}; + +let add_add = { + "Test_A": [{ "add": 10, "affects": "Unit" }, { "add": 5, "affects": "Unit" }] +}; + +let add_mul_add = { + "Test_A": [{ "add": 10, "affects": "Unit" }, { "multiply": 2, "affects": "Unit" }, { "add": 5, "affects": "Unit" }] +}; + +let add_replace = { + "Test_A": [{ "add": 10, "affects": "Unit" }, { "replace": 10, "affects": "Unit" }] +}; + +let replace_add = { + "Test_A": [{ "replace": 10, "affects": "Unit" }, { "add": 10, "affects": "Unit" }] +}; + +let replace_replace = { + "Test_A": [{ "replace": 10, "affects": "Unit" }, { "replace": 30, "affects": "Unit" }] +}; + +let replace_nonnum = { + "Test_A": [{ "replace": "alpha", "affects": "Unit" }] +}; + +TS_ASSERT_EQUALS(GetTechModifiedProperty(add, "Unit", "Test_A", 5), 15); +TS_ASSERT_EQUALS(GetTechModifiedProperty(add_add, "Unit", "Test_A", 5), 20); +TS_ASSERT_EQUALS(GetTechModifiedProperty(add_add, "Other", "Test_A", 5), 5); + +// Technologies work by multiplying then adding all. +TS_ASSERT_EQUALS(GetTechModifiedProperty(add_mul_add, "Unit", "Test_A", 5), 25); + +TS_ASSERT_EQUALS(GetTechModifiedProperty(add_replace, "Unit", "Test_A", 5), 10); + +// Only the first replace is taken into account +TS_ASSERT_EQUALS(GetTechModifiedProperty(replace_replace, "Unit", "Test_A", 5), 10); + +TS_ASSERT_EQUALS(GetTechModifiedProperty(replace_nonnum, "Unit", "Test_A", "beta"), "alpha"); Index: binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js +++ binaries/data/mods/public/simulation/components/tests/test_Technologies_reqs.js @@ -55,14 +55,14 @@ TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]); // Multiple `civ`s -template.requirements = { "all": [{ "civ": "civ_A"}, { "civ": "civ_B"}, { "civ": "civ_C"}] }; +template.requirements = { "all": [{ "civ": "civ_A" }, { "civ": "civ_B" }, { "civ": "civ_C" }] }; TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), []); TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), []); TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), []); TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), false); // Multiple `notciv`s -template.requirements = { "all": [{ "notciv": "civ_A"}, { "notciv": "civ_B"}, { "notciv": "civ_C"}] }; +template.requirements = { "all": [{ "notciv": "civ_A" }, { "notciv": "civ_B" }, { "notciv": "civ_C" }] }; TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), false); TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), false); TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), false); Index: binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js +++ binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js @@ -13,8 +13,7 @@ return "" + schema + ""; } }; -Engine.LoadComponentScript("interfaces/AuraManager.js"); // Provides `IID_AuraManager`, tested for in helpers/ValueModification.js. -Engine.LoadComponentScript("interfaces/TechnologyManager.js"); // Provides `IID_TechnologyManager`, used below. +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); // Provides `IID_ModificationsManager`, used below. Engine.LoadComponentScript("interfaces/Timer.js"); // Provides `IID_Timer`, used below. // What we're testing: @@ -89,15 +88,8 @@ "CancelTimer": () => {} // Called in components/Upgrade.js::CancelUpgrade(). }); -// Init Player: -AddMock(10, IID_Player, { - "AddResources": () => {}, // Called in components/Upgrade.js::CancelUpgrade(). - "GetPlayerID": () => playerID, // Called in helpers/Player.js::QueryOwnerInterface() (and several times below). - "GetTimeMultiplier": () => 1.0, // Called in components/Upgrade.js::GetUpgradeTime(). - "TrySubtractResources": () => true // Called in components/Upgrade.js::Upgrade(). -}); -AddMock(10, IID_TechnologyManager, { - "ApplyModificationsTemplate": (valueName, curValue, template) => { +AddMock(SYSTEM_ENTITY, IID_ModificationsManager, { + "ApplyModificationsTemplate": (valueName, curValue, template, player) => { // Called in helpers/ValueModification.js::ApplyValueModificationsToTemplate() // as part of Tests T2 and T5 below. let mods = isResearched ? templateTechModifications.with : templateTechModifications.without; @@ -111,6 +103,14 @@ } }); +// Init Player: +AddMock(10, IID_Player, { + "AddResources": () => {}, // Called in components/Upgrade.js::CancelUpgrade(). + "GetPlayerID": () => playerID, // Called in helpers/Player.js::QueryOwnerInterface() (and several times below). + "GetTimeMultiplier": () => 1.0, // Called in components/Upgrade.js::GetUpgradeTime(). + "TrySubtractResources": () => true // Called in components/Upgrade.js::Upgrade(). +}); + // Create an entity with an Upgrade component: AddMock(20, IID_Ownership, { "GetOwner": () => playerID // Called in helpers/Player.js::QueryOwnerInterface(). Index: binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js +++ binaries/data/mods/public/simulation/components/tests/test_ValueModificationHelper.js @@ -1,15 +1,15 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("ValueModification.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Player.js"); -Engine.LoadComponentScript("interfaces/TechnologyManager.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); let player = 1; let playerEnt = 10; let ownedEnt = 60; let techKey = "Attack/BigAttack"; +let otherKey = "Other/Key"; -AddMock(playerEnt, IID_TechnologyManager, { +AddMock(SYSTEM_ENTITY, IID_ModificationsManager, { "ApplyModifications": (key, val, ent) => { if (key != techKey) return val; @@ -21,18 +21,6 @@ } }); -AddMock(SYSTEM_ENTITY, IID_AuraManager, { - "ApplyModifications": (key, val, ent) => { - if (key != techKey) - return val; - if (ent == playerEnt) - return val * 10; - if (ent == ownedEnt) - return val * 100; - return val; - } -}); - AddMock(SYSTEM_ENTITY, IID_PlayerManager, { "GetPlayerByID": () => 10 }); @@ -45,6 +33,8 @@ "GetOwner": () => 1 }); -TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, playerEnt), 50.0); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(otherKey, 2.0, playerEnt), 2.0); + +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, playerEnt), 5.0); -TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, ownedEnt), 900.0); +TS_ASSERT_EQUALS(ApplyValueModificationsToEntity(techKey, 2.0, ownedEnt), 9.0); Index: binaries/data/mods/public/simulation/components/tests/test_VisionSharing.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_VisionSharing.js +++ binaries/data/mods/public/simulation/components/tests/test_VisionSharing.js @@ -3,7 +3,7 @@ Engine.LoadHelperScript("Commands.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/TechnologyManager.js"); -Engine.LoadComponentScript("interfaces/AuraManager.js"); +Engine.LoadComponentScript("interfaces/ModificationsManager.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("interfaces/VisionSharing.js"); Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); @@ -118,6 +118,9 @@ AddMock(14, IID_TechnologyManager, { "CanProduce": entity => false, +}); + +AddMock(14, IID_ModificationsManager, { "ApplyModificationsTemplate": (valueName, curValue, template) => curValue }); @@ -127,8 +130,12 @@ AddMock(14, IID_TechnologyManager, { "CanProduce": entity => entity == "special/spy", +}); + +AddMock(14, IID_ModificationsManager, { "ApplyModificationsTemplate": (valueName, curValue, template) => curValue }); + AddMock(14, IID_Player, { "GetSpyCostMultiplier": () => 1, "TrySubtractResources": costs => false Index: binaries/data/mods/public/simulation/helpers/ValueModification.js =================================================================== --- binaries/data/mods/public/simulation/helpers/ValueModification.js +++ binaries/data/mods/public/simulation/helpers/ValueModification.js @@ -3,29 +3,21 @@ function ApplyValueModificationsToEntity(tech_type, current_value, entity) { let value = current_value; - // entity can be an owned entity or a player entity. - let cmpTechnologyManager = Engine.QueryInterface(entity, IID_Player) ? - Engine.QueryInterface(entity, IID_TechnologyManager) : QueryOwnerInterface(entity, IID_TechnologyManager); - if (cmpTechnologyManager) - value = cmpTechnologyManager.ApplyModifications(tech_type, current_value, entity); - let cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); - if (!cmpAuraManager) - return value; - return cmpAuraManager.ApplyModifications(tech_type, value, entity); + // entity can be an owned entity or a player entity. + let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager); + if (cmpModificationsManager) + value = cmpModificationsManager.ApplyModifications(tech_type, current_value, entity); + return value; } function ApplyValueModificationsToTemplate(tech_type, current_value, playerID, template) { let value = current_value; - let cmpTechnologyManager = QueryPlayerIDInterface(playerID, IID_TechnologyManager); - if (cmpTechnologyManager) - value = cmpTechnologyManager.ApplyModificationsTemplate(tech_type, current_value, template); - - let cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); - if (!cmpAuraManager) - return value; - return cmpAuraManager.ApplyTemplateModifications(tech_type, value, playerID, template); + let cmpModificationsManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModificationsManager); + if (cmpModificationsManager) + value = cmpModificationsManager.ApplyModificationsTemplate(tech_type, current_value, template, playerID); + return value; } Engine.RegisterGlobal("ApplyValueModificationsToEntity", ApplyValueModificationsToEntity);