Index: binaries/data/mods/public/simulation/components/Armour.js =================================================================== --- binaries/data/mods/public/simulation/components/Armour.js +++ binaries/data/mods/public/simulation/components/Armour.js @@ -27,6 +27,7 @@ Armour.prototype.Init = function() { this.invulnerable = false; + this.UpdateProperties(); }; Armour.prototype.IsInvulnerable = function() @@ -40,7 +41,10 @@ Engine.PostMessage(this.entity, MT_InvulnerabilityChanged, { "entity": this.entity, "invulnerability": invulnerability }); }; -Armour.prototype.GetArmourStrengths = function(effectType) +/** + * Cache properties that are expensive to compute and are used often. + */ +Armour.prototype.UpdateProperties = function() { // Work out the armour values with technology effects. let applyMods = (type, foundation) => { @@ -58,16 +62,43 @@ let foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation; - let ret = {}; - - if (effectType != "Damage") - return ret; + this.armourStrengths = {}; for (let damageType in this.template) if (damageType != "Foundation") - ret[damageType] = applyMods(damageType, foundation); + this.armourStrengths[damageType] = applyMods(damageType, foundation); +}; + +Armour.prototype.GetArmourStrengths = function(effectType) +{ + if (effectType != "Damage") + return {}; + return this.armourStrengths; +}; + +Armour.prototype.OnValueModification = function(msg) +{ + if (msg.component == "Armour") + this.UpdateProperties(); +}; + +Armour.prototype.OnOwnershipChanged = function(msg) +{ + if (msg.to == INVALID_PLAYER) + return; + + this.UpdateProperties(); +}; - return ret; +Armour.prototype.OnGlobalInitGame = function(msg) +{ + this.UpdateProperties(); +}; + +Armour.prototype.OnMultiplierChanged = function(msg) +{ + if (msg.player == QueryOwnerInterface(this.entity, IID_Player).GetPlayerID()) + this.UpdateProperties(); }; Engine.RegisterComponentType(IID_Resistance, "Armour", Armour); Index: binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/Attack.js +++ binaries/data/mods/public/simulation/components/Attack.js @@ -206,9 +206,67 @@ Attack.prototype.Init = function() { + this.UpdateProperties(); }; -Attack.prototype.Serialize = null; // we have no dynamic state to save +Attack.prototype.UpdateProperties = function() +{ + this.timers = {}; + this.ranges = {}; + this.spreads = {}; + this.attackEffects = {}; + this.splashData = {}; + + for (let type in this.template) + { + let template = this.template[type]; + + this.timers[type] = { + "prepare": ApplyValueModificationsToEntity("Attack/" + type + "/PrepareTime", +(template.PrepareTime || 0), this.entity), + "repeat": ApplyValueModificationsToEntity("Attack/" + type + "/RepeatTime", +(template.RepeatTime || 1000), this.entity) + }; + + this.ranges[type] = { + "max": ApplyValueModificationsToEntity("Attack/" + type + "/MaxRange", +template.MaxRange, this.entity), + "min": ApplyValueModificationsToEntity("Attack/" + type + "/MinRange", +(template.MinRange || 0), this.entity), + "elevationBonus": ApplyValueModificationsToEntity("Attack/" + type + "/ElevationBonus", +(template.ElevationBonus || 0), this.entity) + }; + + if (template.Projectile) + this.spreads[type] = ApplyValueModificationsToEntity("Attack/" + type + "/Spread", +template.Projectile.Spread, this.entity); + + this.attackEffects[type] = Attacking.GetAttackEffectsData("Attack/" + type, template, this.entity); + + if (template.Splash) + this.splashData[type] = { + "attackData": Attacking.GetAttackEffectsData("Attack/" + type + "Splash/", template.Splash, this.entity), + "friendlyFire": template.Splash.FriendlyFire == "true", + "shape": template.Splash.Shape + }; + else + this.splashData[type] = false; + } +}; + +Attack.prototype.Serialize = function() +{ + return { + "timers": this.timers, + "ranges": this.ranges, + "spreads": this.spreads, + "attackEffects": this.attackEffects, + "splashData": this.splashData + }; +}; + +Attack.prototype.Deserialize = function(data) +{ + this.timers = data.timers; + this.ranges = data.ranges; + this.spreads = data.spreads; + this.attackEffects = data.attackEffects; + this.splashData = data.splashData; +}; Attack.prototype.GetAttackTypes = function(wantedTypes) { @@ -341,10 +399,9 @@ Attack.prototype.GetAttackEffectsData = function(type, splash) { - let template = this.template[type]; if (splash) - template = template.Splash; - return Attacking.GetAttackEffectsData("Attack/" + type + (splash ? "/Splash" : ""), template, this.entity); + return this.splashData[type].attackData; + return this.attackEffects[type]; }; /** @@ -403,39 +460,17 @@ Attack.prototype.GetTimers = function(type) { - let prepare = +(this.template[type].PrepareTime || 0); - prepare = ApplyValueModificationsToEntity("Attack/" + type + "/PrepareTime", prepare, this.entity); - - let repeat = +(this.template[type].RepeatTime || 1000); - repeat = ApplyValueModificationsToEntity("Attack/" + type + "/RepeatTime", repeat, this.entity); - - return { "prepare": prepare, "repeat": repeat }; + return this.timers[type]; }; Attack.prototype.GetSplashDamage = function(type) { - if (!this.template[type].Splash) - return false; - - return { - "attackData": this.GetAttackEffectsData(type, true), - "friendlyFire": this.template[type].Splash.FriendlyFire != "false", - "shape": this.template[type].Splash.Shape, - }; + return this.splashData[type]; }; Attack.prototype.GetRange = function(type) { - let max = +this.template[type].MaxRange; - max = ApplyValueModificationsToEntity("Attack/" + type + "/MaxRange", max, this.entity); - - let min = +(this.template[type].MinRange || 0); - min = ApplyValueModificationsToEntity("Attack/" + type + "/MinRange", min, this.entity); - - let elevationBonus = +(this.template[type].ElevationBonus || 0); - elevationBonus = ApplyValueModificationsToEntity("Attack/" + type + "/ElevationBonus", elevationBonus, this.entity); - - return { "max": max, "min": min, "elevationBonus": elevationBonus }; + return this.ranges[type]; }; /** @@ -476,8 +511,7 @@ let predictedPosition = (timeToTarget !== false) ? Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition) : targetPosition; // Add inaccuracy based on spread. - let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template[type].Projectile.Spread, this.entity) * - predictedPosition.horizDistanceTo(selfPosition) / 100; + let distanceModifiedSpread = this.spreads[type] * predictedPosition.horizDistanceTo(selfPosition) / 100; let randNorm = randomNormal2D(); let offsetX = randNorm[0] * distanceModifiedSpread; @@ -585,6 +619,8 @@ if (msg.component != "Attack") return; + this.UpdateProperties(); + let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); if (!cmpUnitAI) return; @@ -594,6 +630,25 @@ cmpUnitAI.UpdateRangeQueries(); }; +Attack.prototype.OnOwnershipChanged = function(msg) +{ + if (msg.to == INVALID_PLAYER) + return; + + this.UpdateProperties(); +}; + +Attack.prototype.OnGlobalInitGame = function(msg) +{ + this.UpdateProperties(); +}; + +Attack.prototype.OnMultiplierChanged = function(msg) +{ + if (msg.player == QueryOwnerInterface(this.entity, IID_Player).GetPlayerID()) + this.UpdateProperties(); +}; + Attack.prototype.GetRangeOverlays = function() { if (!this.template.Ranged || !this.template.Ranged.RangeOverlay) Index: binaries/data/mods/public/simulation/components/Loot.js =================================================================== --- binaries/data/mods/public/simulation/components/Loot.js +++ binaries/data/mods/public/simulation/components/Loot.js @@ -8,19 +8,68 @@ "" + Resources.BuildSchema("nonNegativeInteger", ["xp"]); -Loot.prototype.Serialize = null; // we have no dynamic state to save +Loot.prototype.Init = function() +{ + this.UpdateProperties(); +}; + +// Cache some properties that are expensive to compute. +Loot.prototype.UpdateProperties = function() +{ + this.xp = Math.floor(ApplyValueModificationsToEntity("Loot/xp", +(this.template.xp || 0), this.entity)) + + this.resources = {}; + for (let res of Resources.GetCodes()) + this.resources[res] = Math.floor(ApplyValueModificationsToEntity("Loot/" + res, +(this.template[res] || 0), this.entity)); +}; + +Loot.prototype.Serialize = function() +{ + return { + "xp": this.xp, + "resources": this.resources + }; +}; + +Loot.prototype.Deserialize = function(data) +{ + this.xp = data.xp; + this.resources = data.resources; +}; Loot.prototype.GetXp = function() { - return Math.floor(ApplyValueModificationsToEntity("Loot/xp", +(this.template.xp || 0), this.entity)); + return this.xp; }; Loot.prototype.GetResources = function() { - let ret = {}; - for (let res of Resources.GetCodes()) - ret[res] = Math.floor(ApplyValueModificationsToEntity("Loot/" + res, +(this.template[res] || 0), this.entity)); - return ret; + return this.resources; +}; + +Loot.prototype.OnValueModification = function(msg) +{ + if (msg.component == "Loot") + this.UpdateProperties(); +}; + +Loot.prototype.OnOwnershipChanged = function(msg) +{ + if (msg.to == INVALID_PLAYER) + return; + + this.UpdateProperties(); +}; + +Loot.prototype.OnGlobalInitGame = function(msg) +{ + this.UpdateProperties(); +}; + +Loot.prototype.OnMultiplierChanged = function(msg) +{ + if (msg.player == QueryOwnerInterface(this.entity, IID_Player).GetPlayerID()) + this.UpdateProperties(); }; Engine.RegisterComponentType(IID_Loot, "Loot", Loot); Index: binaries/data/mods/public/simulation/components/ResourceSupply.js =================================================================== --- binaries/data/mods/public/simulation/components/ResourceSupply.js +++ binaries/data/mods/public/simulation/components/ResourceSupply.js @@ -40,6 +40,13 @@ let [type, subtype] = this.template.Type.split('.'); this.cachedType = { "generic": type, "specific": subtype }; + + this.UpdateProperties(); +}; + +ResourceSupply.prototype.UpdateProperties = function() +{ + this.diminishingReturns = ApplyValueModificationsToEntity("ResourceSupply/DiminishingReturns", +(this.template.DiminishingReturns || 1), this.entity); }; ResourceSupply.prototype.IsInfinite = function() @@ -99,13 +106,9 @@ if (!this.template.DiminishingReturns) return null; - let diminishingReturns = ApplyValueModificationsToEntity("ResourceSupply/DiminishingReturns", +this.template.DiminishingReturns, this.entity); - if (!diminishingReturns) - return null; - let numGatherers = this.GetNumGatherers(); if (numGatherers > 1) - return diminishingReturns == 1 ? 1 : (1 - Math.pow(diminishingReturns, numGatherers)) / (1 - diminishingReturns) / numGatherers; + return diminishingReturns == 1 ? 1 : (1 - Math.pow(this.diminishingReturns, numGatherers)) / (1 - this.diminishingReturns) / numGatherers; return null; }; @@ -182,4 +185,20 @@ Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() }); }; +ResourceSupply.prototype.OnValueModification = function(msg) +{ + if (msg.component != "ResourceSupply") + return; + + this.UpdateProperties(); +}; + +ResourceSupply.prototype.OnOwnershipChanged = function(msg) +{ + if (msg.to == INVALID_PLAYER) + return; + + this.UpdateProperties(); +}; + Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply); 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 @@ -107,7 +107,12 @@ "Capture": 8, "MaxRange": 10, }, - "Slaughter": {} + "Slaughter": { + "Damage": { + "Hack": 100 + }, + "MaxRange": 10 + } }); let defender = ++entityID; Index: binaries/data/mods/public/simulation/components/tests/test_ResourceSupply.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_ResourceSupply.js +++ binaries/data/mods/public/simulation/components/tests/test_ResourceSupply.js @@ -11,6 +11,8 @@ } }; +Engine.LoadHelperScript("ValueModification.js"); +Engine.LoadComponentScript("interfaces/ModifiersManager.js"); Engine.LoadComponentScript("interfaces/ResourceSupply.js"); Engine.LoadComponentScript("ResourceSupply.js");