From c1e86161b54ea3eaf8989fd6998622c674eade59 Mon Sep 17 00:00:00 2001 From: wraitii Date: Thu, 16 Jan 2014 20:32:44 +0000 Subject: [PATCH] AIs now properly receive aura and technology updates. Fixes #2377, Refs #1520 . Consequently reimplement repairing for AIs. Fix a few style issues and a bug with the gatherer count. Still need to fix the entity.js file to handle properly some things as this uses raw templates values. Cache the AIinterface in AIProxy.js, please report if this works properly. This was SVN commit r14588. --- .../public/simulation/ai/aegis/attack_plan.js | 2 + .../simulation/ai/aegis/base-manager.js | 6 +- .../simulation/ai/aegis/headquarters.js | 1 - .../public/simulation/ai/common-api/entity.js | 359 ++++++++++-------- .../simulation/ai/common-api/gamestate.js | 2 +- .../public/simulation/ai/common-api/shared.js | 39 +- .../simulation/components/AIInterface.js | 160 ++++++-- .../public/simulation/components/AIProxy.js | 37 +- .../simulation/components/AuraManager.js | 8 +- .../simulation/components/GuiInterface.js | 5 +- .../components/TechnologyManager.js | 26 +- source/simulation2/MessageTypes.h | 32 +- source/simulation2/TypeList.h | 1 + .../components/ICmpTemplateManager.cpp | 1 + .../scripting/MessageTypeConversions.cpp | 26 +- 15 files changed, 453 insertions(+), 252 deletions(-) diff --git a/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js b/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js index 59786d666d..6383a415a4 100755 --- a/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js +++ b/binaries/data/mods/public/simulation/ai/aegis/attack_plan.js @@ -544,6 +544,8 @@ m.CityAttack.prototype.updatePreparation = function(gameState, HQ,events) { count++; if (count > 1000){ m.debug("No target with a valid position found"); + Engine.ProfileStop(); + Engine.ProfileStop(); return false; } } diff --git a/binaries/data/mods/public/simulation/ai/aegis/base-manager.js b/binaries/data/mods/public/simulation/ai/aegis/base-manager.js index 1f89cd0ac6..0a814ba874 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/base-manager.js +++ b/binaries/data/mods/public/simulation/ai/aegis/base-manager.js @@ -908,10 +908,6 @@ m.BaseManager.prototype.assignToFoundations = function(gameState, noRepair) { } } - // auras/techs are buggy and the AI tries to repair healthy buildings. - // TODO: reimplement once that's fixed. - return; - // don't repair if we're still under attack, unless it's like a vital (civcentre or wall) building that's getting destroyed. for (var i in damagedBuildings) { var target = damagedBuildings[i]; @@ -955,7 +951,7 @@ m.BaseManager.prototype.update = function(gameState, queues, events) { Engine.ProfileStart("Assign builders"); this.assignToFoundations(gameState); - Engine.ProfileStop() + Engine.ProfileStop(); if (this.constructing && this.anchor) { diff --git a/binaries/data/mods/public/simulation/ai/aegis/headquarters.js b/binaries/data/mods/public/simulation/ai/aegis/headquarters.js index 68cd1770b7..a5fe2db393 100644 --- a/binaries/data/mods/public/simulation/ai/aegis/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/aegis/headquarters.js @@ -1121,7 +1121,6 @@ m.HQ.prototype.update = function(gameState, queues, events) { this.checkEvents(gameState,events,queues); //this.buildMoreHouses(gameState); - //Engine.ProfileStart("Train workers and build farms, houses. Research techs."); this.trainMoreWorkers(gameState, queues); // sandbox doesn't expand. diff --git a/binaries/data/mods/public/simulation/ai/common-api/entity.js b/binaries/data/mods/public/simulation/ai/common-api/entity.js index 6aa76033ca..0749a0786e 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ b/binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -1,56 +1,78 @@ var API3 = function(m) { -m.EntityTemplate = m.Class({ +// defines a template. +// It's completely raw data, except it's slightly cleverer now and then. +m.Template = m.Class({ - // techModifications should be the tech modifications of only one player. - // gamestates handle "GetTemplate" and should push the player's - // while entities should push the owner's - _init: function(template, techModifications) + _init: function(template) { - this._techModifications = techModifications; this._template = template; + this._tpCache = {}; }, - genericName: function() { - if (!this._template.Identity || !this._template.Identity.GenericName) - return undefined; - return this._template.Identity.GenericName; + // helper function to return a template value, optionally adjusting for tech. + // TODO: there's no support for "_string" values here. + get: function(string) + { + var value = this._template; + if (this._auraTemplateModif && this._auraTemplateModif[string]) { + return this._auraTemplateModif[string]; + } else if (this._techModif && this._techModif[string]) { + return this._techModif[string]; + } else { + if (this._tpCache[string] === undefined) + { + var args = string.split("/"); + for (var i = 0; i < args.length; ++i) + if (value[args[i]]) + value = value[args[i]]; + else + { + value = undefined; + break; + } + this._tpCache[string] = value; + } + return this._tpCache[string]; + } }, - - rank: function() { - if (!this._template.Identity) + + genericName: function() { + if (!this.get("Identity") || !this.get("Identity/GenericName")) return undefined; - return this._template.Identity.Rank; + return this.get("Identity/GenericName"); + }, + + rank: function() { + if (!this.get("Identity")) + return undefined; + return this.get("Identity/Rank"); }, classes: function() { - if (!this._template.Identity || !this._template.Identity.Classes || !this._template.Identity.Classes._string) + if (!this.get("Identity") || !this.get("Identity/Classes") || !this.get("Identity/Classes/_string")) return undefined; - return this._template.Identity.Classes._string.split(/\s+/); + return this.get("Identity/Classes/_string").split(/\s+/); }, requiredTech: function() { - if (!this._template.Identity || !this._template.Identity.RequiredTechnology) - return undefined; - return this._template.Identity.RequiredTechnology; + return this.get("Identity/RequiredTechnology"); }, - + available: function(gameState) { - if (!this._template.Identity || !this._template.Identity.RequiredTechnology) - return true; - return gameState.isResearched(this._template.Identity.RequiredTechnology); + return gameState.isResearched(this.get("Identity/RequiredTechnology")); }, - + // specifically phase: function() { - if (!this._template.Identity || !this._template.Identity.RequiredTechnology) + if (!this.get("Identity/RequiredTechnology")) return 0; - if (this.template.Identity.RequiredTechnology == "phase_village") + if (this.get("Identity/RequiredTechnology") == "phase_village") return 1; - if (this.template.Identity.RequiredTechnology == "phase_town") + if (this.get("Identity/RequiredTechnology") == "phase_town") return 2; - if (this.template.Identity.RequiredTechnology == "phase_city") + if (this.get("Identity/RequiredTechnology") == "phase_city") return 3; return 0; }, @@ -72,28 +94,26 @@ m.EntityTemplate = m.Class({ }, civ: function() { - if (!this._template.Identity) - return undefined; - return this._template.Identity.Civ; + return this.get("Identity/Civ"); }, cost: function() { - if (!this._template.Cost) + if (!this.get("Cost")) return undefined; var ret = {}; - for (var type in this._template.Cost.Resources) - ret[type] = GetTechModifiedProperty(this._techModifications, this._template, "Cost/Resources/"+type, +this._template.Cost.Resources[type]); + for (var type in this.get("Cost/Resources")) + ret[type] = +this.get("Cost/Resources/" + type); return ret; }, costSum: function() { - if (!this._template.Cost) + if (!this.get("Cost")) return undefined; var ret = 0; - for (var type in this._template.Cost.Resources) - ret += +this._template.Cost.Resources[type]; + for (var type in this.get("Cost/Resources")) + ret += +this.get("Cost/Resources/" + type); return ret; }, @@ -102,134 +122,134 @@ m.EntityTemplate = m.Class({ * obstruction shape, or undefined if no obstruction. */ obstructionRadius: function() { - if (!this._template.Obstruction) + if (!this.get("Obstruction")) return undefined; - if (this._template.Obstruction.Static) + if (this.get("Obstruction/Static")) { - var w = +this._template.Obstruction.Static["@width"]; - var h = +this._template.Obstruction.Static["@depth"]; + var w = +this.get("Obstruction/Static/@width"); + var h = +this.get("Obstruction/Static/@depth"); return Math.sqrt(w*w + h*h) / 2; } - if (this._template.Obstruction.Unit) - return +this._template.Obstruction.Unit["@radius"]; + if (this.get("Obstruction/Unit")) + return +this.get("Obstruction/Unit/@radius"); return 0; // this should never happen }, - + /** * Returns the radius of a circle surrounding this entity's * footprint. */ footprintRadius: function() { - if (!this._template.Footprint) + if (!this.get("Footprint")) return undefined; - if (this._template.Footprint.Square) + if (this.get("Footprint/Square")) { - var w = +this._template.Footprint.Square["@width"]; - var h = +this._template.Footprint.Square["@depth"]; + var w = +this.get("Footprint/Square/@width"); + var h = +this.get("Footprint/Square/@depth"); return Math.sqrt(w*w + h*h) / 2; } - if (this._template.Footprint.Circle) - return +this._template.Footprint.Circle["@radius"]; + if (this.get("Footprint/Circle")) + return +this.get("Footprint/Circle/@radius"); return 0; // this should never happen }, maxHitpoints: function() { - if (this._template.Health !== undefined) - return GetTechModifiedProperty(this._techModifications, this._template, "Health/Max",+this._template.Health.Max); + if (this.get("Health") !== undefined) + return +this.get("Health/Max"); return 0; }, + isHealable: function() { - if (this._template.Health !== undefined) - return this._template.Health.Unhealable !== "true"; + if (this.get("Health") !== undefined) + return this.get("Health/Unhealable") !== "true"; return false; }, + isRepairable: function() { - if (this._template.Health !== undefined) - return this._template.Health.Repairable === "true"; + if (this.get("Health") !== undefined) + return this.get("Health/Repairable") === "true"; return false; }, getPopulationBonus: function() { - if (!this._template.Cost || !this._template.Cost.PopulationBonus) - return undefined; - return this._template.Cost.PopulationBonus; + return this.get("Cost/PopulationBonus"); }, armourStrengths: function() { - if (!this._template.Armour) + if (!this.get("Armour")) return undefined; return { - hack: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Hack", +this._template.Armour.Hack), - pierce: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Pierce", +this._template.Armour.Pierce), - crush: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Crush", +this._template.Armour.Crush) + hack: +this.get("Armour/Hack"), + pierce: +this.get("Armour/Pierce"), + crush: +this.get("Armour/Crush") }; }, attackTypes: function() { - if (!this._template.Attack) + if (!this.get("Attack")) return undefined; var ret = []; - for (var type in this._template.Attack) + for (var type in this.get("Attack")) ret.push(type); return ret; }, attackRange: function(type) { - if (!this._template.Attack || !this._template.Attack[type]) + if (!this.get("Attack/" + type +"")) return undefined; return { - max: GetTechModifiedProperty(this._techModifications, this._template, "Attack/MaxRange", +this._template.Attack[type].MaxRange), - min: GetTechModifiedProperty(this._techModifications, this._template, "Attack/MinRange", +(this._template.Attack[type].MinRange || 0)) + max: +this.get("Attack/" + type +"/MaxRange"), + min: +(this.get("Attack/" + type +"/MinRange") || 0) }; }, attackStrengths: function(type) { - if (!this._template.Attack || !this._template.Attack[type]) + if (!this.get("Attack/" + type +"")) return undefined; return { - hack: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Hack", +(this._template.Attack[type].Hack || 0)), - pierce: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Pierce", +(this._template.Attack[type].Pierce || 0)), - crush: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Crush", +(this._template.Attack[type].Crush || 0)) + hack: +(this.get("Attack/" + type + "/Hack") || 0), + pierce: +(this.get("Attack/" + type + "/Pierce") || 0), + crush: +(this.get("Attack/" + type + "/Crush") || 0) }; }, attackTimes: function(type) { - if (!this._template.Attack || !this._template.Attack[type]) + if (!this.get("Attack/" + type +"")) return undefined; return { - prepare: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/PrepareTime", +(this._template.Attack[type].PrepareTime || 0)), - repeat: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/RepeatTime", +(this._template.Attack[type].RepeatTime || 1000)) + prepare: +(this.get("Attack/" + type + "/PrepareTime") || 0), + repeat: +(this.get("Attack/" + type + "/RepeatTime") || 1000) }; }, // returns the classes this templates counters: - // Return type is [ [-neededClasses-] , multiplier ]. + // Return type is [ [-neededClasses- , multiplier], … ]. getCounteredClasses: function() { - if (!this._template.Attack) + if (!this.get("Attack")) return undefined; var Classes = []; - for (var i in this._template.Attack) { - if (!this._template.Attack[i].Bonuses) + for (var i in this.get("Attack")) { + if (!this.get("Attack/" + i + "/Bonuses")) continue; - for (var o in this._template.Attack[i].Bonuses) - if (this._template.Attack[i].Bonuses[o].Classes) - Classes.push([this._template.Attack[i].Bonuses[o].Classes.split(" "), +this._template.Attack[i].Bonuses[o].Multiplier]); + for (var o in this.get("Attack/" + i + "/Bonuses")) + if (this.get("Attack/" + i + "/Bonuses/" + o + "/Classes")) + Classes.push([this.get("Attack/" + i +"/Bonuses/" + o +"/Classes").split(" "), +this.get("Attack/" + i +"/Bonuses" +o +"/Multiplier")]); } return Classes; }, @@ -237,15 +257,15 @@ m.EntityTemplate = m.Class({ // returns true if the entity counters those classes. // TODO: refine using the multiplier countersClasses: function(classes) { - if (!this._template.Attack) + if (!this.get("Attack")) return false; var mcounter = []; - for (var i in this._template.Attack) { - if (!this._template.Attack[i].Bonuses) + for (var i in this.get("Attack")) { + if (!this.get("Attack/" + i + "/Bonuses")) continue; - for (var o in this._template.Attack[i].Bonuses) - if (this._template.Attack[i].Bonuses[o].Classes) - mcounter.concat(this._template.Attack[i].Bonuses[o].Classes.split(" ")); + for (var o in this.get("Attack/" + i + "/Bonuses")) + if (this.get("Attack/" + i + "/Bonuses/" + o + "/Classes")) + mcounter.concat(this.get("Attack/" + i + "/Bonuses/" + o + "/Classes").split(" ")); } for (var i in classes) { @@ -257,30 +277,30 @@ m.EntityTemplate = m.Class({ // returns, if it exists, the multiplier from each attack against a given class getMultiplierAgainst: function(type, againstClass) { - if (!this._template.Attack || !this._template.Attack[type]) + if (!this.get("Attack/" + type +"")) return undefined; - if (this._template.Attack[type].Bonuses) - for (var o in this._template.Attack[type].Bonuses) { - if (!this._template.Attack[type].Bonuses[o].Classes) + if (this.get("Attack/" + type + "/Bonuses")) + for (var o in this.get("Attack/" + type + "/Bonuses")) { + if (!this.get("Attack/" + type + "/Bonuses/" + o + "/Classes")) continue; - var total = this._template.Attack[type].Bonuses[o].Classes.split(" "); + var total = this.get("Attack/" + type + "/Bonuses/" + o + "/Classes").split(" "); for (var j in total) if (total[j] === againstClass) - return this._template.Attack[type].Bonuses[o].Multiplier; + return this.get("Attack/" + type + "/Bonuses/" + o + "/Multiplier"); } return 1; }, // returns true if the entity can attack the given class canAttackClass: function(saidClass) { - if (!this._template.Attack) + if (!this.get("Attack")) return false; - for (var i in this._template.Attack) { - if (!this._template.Attack[i].RestrictedClasses || !this._template.Attack[i].RestrictedClasses._string) + for (var i in this.get("Attack")) { + if (!this.get("Attack/" + i + "/RestrictedClasses") || !this.get("Attack/" + i + "/RestrictedClasses/_string")) continue; - var cannotAttack = this._template.Attack[i].RestrictedClasses._string.split(" "); + var cannotAttack = this.get("Attack/" + i + "/RestrictedClasses/_string").split(" "); if (cannotAttack.indexOf(saidClass) !== -1) return false; } @@ -288,86 +308,84 @@ m.EntityTemplate = m.Class({ }, buildableEntities: function() { - if (!this._template.Builder) - return undefined; - if (!this._template.Builder.Entities._string) + if (!this.get("Builder/Entities/_string")) return []; var civ = this.civ(); - var templates = this._template.Builder.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/); + var templates = this.get("Builder/Entities/_string").replace(/\{civ\}/g, civ).split(/\s+/); return templates; // TODO: map to Entity? }, trainableEntities: function() { - if (!this._template.ProductionQueue || !this._template.ProductionQueue.Entities || !this._template.ProductionQueue.Entities._string) + if (!this.get("ProductionQueue/Entities/_string")) return undefined; var civ = this.civ(); - var templates = this._template.ProductionQueue.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/); + var templates = this.get("ProductionQueue/Entities/_string").replace(/\{civ\}/g, civ).split(/\s+/); return templates; }, researchableTechs: function() { - if (!this._template.ProductionQueue || !this._template.ProductionQueue.Technologies || !this._template.ProductionQueue.Technologies._string) + if (!this.get("ProductionQueue/Technologies/_string")) return undefined; - var templates = this._template.ProductionQueue.Technologies._string.split(/\s+/); + var templates = this.get("ProductionQueue/Technologies/_string").split(/\s+/); return templates; }, resourceSupplyType: function() { - if (!this._template.ResourceSupply) + if (!this.get("ResourceSupply")) return undefined; - var [type, subtype] = this._template.ResourceSupply.Type.split('.'); + var [type, subtype] = this.get("ResourceSupply/Type").split('.'); return { "generic": type, "specific": subtype }; }, // will return either "food", "wood", "stone", "metal" and not treasure. getResourceType: function() { - if (!this._template.ResourceSupply) + if (!this.get("ResourceSupply")) return undefined; - var [type, subtype] = this._template.ResourceSupply.Type.split('.'); + var [type, subtype] = this.get("ResourceSupply/Type").split('.'); if (type == "treasure") return subtype; return type; }, resourceSupplyMax: function() { - if (!this._template.ResourceSupply) + if (!this.get("ResourceSupply")) return undefined; - return +this._template.ResourceSupply.Amount; + return +this.get("ResourceSupply/Amount"); }, maxGatherers: function() { - if (this._template.ResourceSupply !== undefined) - return +this._template.ResourceSupply.MaxGatherers; + if (this.get("ResourceSupply") !== undefined) + return +this.get("ResourceSupply/MaxGatherers"); return 0; }, resourceGatherRates: function() { - if (!this._template.ResourceGatherer) + if (!this.get("ResourceGatherer")) return undefined; var ret = {}; - var baseSpeed = GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/BaseSpeed", +this._template.ResourceGatherer.BaseSpeed); - for (var r in this._template.ResourceGatherer.Rates) - ret[r] = GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/Rates/"+r, +this._template.ResourceGatherer.Rates[r]) * baseSpeed; + var baseSpeed = +this.get("ResourceGatherer/BaseSpeed"); + for (var r in this.get("ResourceGatherer/Rates")) + ret[r] = +this.get("ResourceGatherer/Rates/" + r) * baseSpeed; return ret; }, resourceDropsiteTypes: function() { - if (!this._template.ResourceDropsite) + if (!this.get("ResourceDropsite")) return undefined; - return this._template.ResourceDropsite.Types.split(/\s+/); + return this.get("ResourceDropsite/Types").split(/\s+/); }, garrisonableClasses: function() { - if (!this._template.GarrisonHolder || !this._template.GarrisonHolder.List._string) + if (!this.get("GarrisonHolder") || !this.get("GarrisonHolder/List/_string")) return undefined; - return this._template.GarrisonHolder.List._string.split(/\s+/); + return this.get("GarrisonHolder/List/_string").split(/\s+/); }, garrisonMax: function() { - if (!this._template.GarrisonHolder) + if (!this.get("GarrisonHolder")) return undefined; - return this._template.GarrisonHolder.Max; + return this.get("GarrisonHolder/Max"); }, /** @@ -375,47 +393,47 @@ m.EntityTemplate = m.Class({ * (Any non domestic currently.) */ isUnhuntable: function() { - if (!this._template.UnitAI || !this._template.UnitAI.NaturalBehaviour) + if (!this.get("UnitAI") || !this.get("UnitAI/NaturalBehaviour")) return false; // only attack domestic animals since they won't flee nor retaliate. - return this._template.UnitAI.NaturalBehaviour !== "domestic"; + return this.get("UnitAI/NaturalBehaviour") !== "domestic"; }, - + walkSpeed: function() { - if (!this._template.UnitMotion || !this._template.UnitMotion.WalkSpeed) + if (!this.get("UnitMotion") || !this.get("UnitMotion/WalkSpeed")) return undefined; - return this._template.UnitMotion.WalkSpeed; + return this.get("UnitMotion/WalkSpeed"); }, buildCategory: function() { - if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Category) + if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Category")) return undefined; - return this._template.BuildRestrictions.Category; + return this.get("BuildRestrictions/Category"); }, buildTime: function() { - if (!this._template.Cost || !this._template.Cost.BuildTime) + if (!this.get("Cost") || !this.get("Cost/BuildTime")) return undefined; - return this._template.Cost.BuildTime; + return this.get("Cost/BuildTime"); }, buildDistance: function() { - if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Distance) + if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Distance")) return undefined; - return this._template.BuildRestrictions.Distance; + return this.get("BuildRestrictions/Distance"); }, buildPlacementType: function() { - if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.PlacementType) + if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/PlacementType")) return undefined; - return this._template.BuildRestrictions.PlacementType; + return this.get("BuildRestrictions/PlacementType"); }, buildTerritories: function() { - if (!this._template.BuildRestrictions || !this._template.BuildRestrictions.Territory) + if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Territory")) return undefined; - return this._template.BuildRestrictions.Territory.split(/\s+/); + return this.get("BuildRestrictions/Territory").split(/\s+/); }, hasBuildTerritory: function(territory) { @@ -424,42 +442,45 @@ m.EntityTemplate = m.Class({ }, hasTerritoryInfluence: function() { - return (this._template.TerritoryInfluence !== undefined); + return (this.get("TerritoryInfluence") !== undefined); }, territoryInfluenceRadius: function() { - if (this._template.TerritoryInfluence !== undefined) - return (this._template.TerritoryInfluence.Radius); + if (this.get("TerritoryInfluence") !== undefined) + return (this.get("TerritoryInfluence/Radius")); else return -1; }, territoryInfluenceWeight: function() { - if (this._template.TerritoryInfluence !== undefined) - return (this._template.TerritoryInfluence.Weight); + if (this.get("TerritoryInfluence") !== undefined) + return (this.get("TerritoryInfluence/Weight")); else return -1; }, visionRange: function() { - if (!this._template.Vision) - return undefined; - return this._template.Vision.Range; + return this.get("Vision/Range"); } }); - +// defines an entity, with a super Template. +// also redefines several of the template functions where the only change is applying aura and tech modifications. m.Entity = m.Class({ - _super: m.EntityTemplate, + _super: m.Template, _init: function(sharedAI, entity) { - this._super.call(this, sharedAI.GetTemplate(entity.template), sharedAI._techModifications[entity.owner]); + this._super.call(this, sharedAI.GetTemplate(entity.template)); - this._ai = sharedAI; this._templateName = entity.template; this._entity = entity; + this._auraTemplateModif = {}; // template modification from auras. this is only for this entity. + this._ai = sharedAI; + if (!sharedAI._techModifications[entity.owner][this._templateName]) + sharedAI._techModifications[entity.owner][this._templateName] = {}; + this._techModif = sharedAI._techModifications[entity.owner][this._templateName]; // save a reference to the template tech modifications }, toString: function() { @@ -477,7 +498,7 @@ m.Entity = m.Class({ /** * Returns extra data that the AI scripts have associated with this entity, * for arbitrary local annotations. - * (This data is not shared with any other AI scripts.) + * (This data should not be shared with any other AI scripts.) */ getMetadata: function(player, key) { return this._ai.getMetadata(player, this, key); @@ -493,7 +514,7 @@ m.Entity = m.Class({ deleteAllMetadata: function(player) { delete this._ai._entityMetadata[player][this.id()]; }, - + deleteMetadata: function(player, key) { this._ai.deleteMetadata(player, this, key); }, @@ -508,7 +529,8 @@ m.Entity = m.Class({ unitAIState: function() { return this._entity.unitAIState; }, unitAIOrderData: function() { return this._entity.unitAIOrderData; }, - hitpoints: function() { if (this._entity.hitpoints !== undefined) return this._entity.hitpoints; return undefined; }, + + hitpoints: function() {if (this._entity.hitpoints !== undefined) return this._entity.hitpoints; return undefined; }, isHurt: function() { return this.hitpoints() < this.maxHitpoints(); }, healthLevel: function() { return (this.hitpoints() / this.maxHitpoints()); }, needsHeal: function() { return this.isHurt() && this.isHealable(); }, @@ -540,14 +562,17 @@ m.Entity = m.Class({ owner: function() { return this._entity.owner; }, + isOwn: function(player) { if (typeof(this._entity.owner) === "undefined") return false; return this._entity.owner === player; }, + isFriendly: function(player) { return this.isOwn(player); // TODO: diplomacy }, + isEnemy: function(player) { return !this.isOwn(player); // TODO: diplomacy }, @@ -557,18 +582,18 @@ m.Entity = m.Class({ return undefined; return this._entity.resourceSupplyAmount; }, - + resourceSupplyGatherers: function(player) { if (this._entity.resourceSupplyGatherers !== undefined) - return this._entity.resourceSupplyGatherers[player-1]; + return this._entity.resourceSupplyGatherers[player]; return []; }, - + isFull: function(player) { if (this._entity.resourceSupplyGatherers !== undefined) - return (this.maxGatherers() === this._entity.resourceSupplyGatherers[player-1].length); + return (this.maxGatherers() === this._entity.resourceSupplyGatherers[player].length); return undefined; }, @@ -578,10 +603,10 @@ m.Entity = m.Class({ return undefined; return this._entity.resourceCarrying; }, - + currentGatherRate: function() { // returns the gather rate for the current target if applicable. - if (!this._template.ResourceGatherer) + if (!this.get("ResourceGatherer")); return undefined; if (this.unitAIOrderData().length && @@ -599,13 +624,13 @@ m.Entity = m.Class({ var type = ress.resourceSupplyType(); var tstring = type.generic + "." + type.specific; - + if (type.generic == "treasure") return 1000; - var speed = GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/BaseSpeed", +this._template.ResourceGatherer.BaseSpeed); - speed *= GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/Rates/"+tstring, +this._template.ResourceGatherer.Rates[tstring]); - + var speed = +this.get("ResourceGatherer/BaseSpeed"); + speed *= +this.get("ResourceGatherer/Rates/" +tstring); + if (speed) return speed; return 0; @@ -644,7 +669,7 @@ m.Entity = m.Class({ }, unload: function(id) { - if (!this._template.GarrisonHolder) + if (!this.get("GarrisonHolder")) return undefined; Engine.PostCommand(PlayerID,{"type": "unload", "garrisonHolder": this.id(), "entities": [id]}); return this; @@ -652,7 +677,7 @@ m.Entity = m.Class({ // Unloads all owned units, don't unload allies unloadAll: function() { - if (!this._template.GarrisonHolder) + if (!this.get("GarrisonHolder")) return undefined; Engine.PostCommand(PlayerID,{"type": "unload-all-own", "garrisonHolders": [this.id()]}); return this; @@ -751,7 +776,7 @@ m.Entity = m.Class({ }); return this; }, - + research: function(template) { Engine.PostCommand(PlayerID,{ "type": "research", "entity": this.id(), "template": template }); return this; diff --git a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js index a16cf09b5e..9cbacacf0e 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js @@ -126,7 +126,7 @@ m.GameState.prototype.getTemplate = function(type) if (!this.templates[type]) return null; - return new m.EntityTemplate(this.templates[type], this.techModifications); + return new m.Template(this.templates[type], this.techModifications); }; m.GameState.prototype.applyCiv = function(str) { diff --git a/binaries/data/mods/public/simulation/ai/common-api/shared.js b/binaries/data/mods/public/simulation/ai/common-api/shared.js index bad69420fe..b4dc583de5 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/shared.js +++ b/binaries/data/mods/public/simulation/ai/common-api/shared.js @@ -58,7 +58,7 @@ m.SharedScript.prototype.Deserialize = function(data) // Components that will be disabled in foundation entity templates. // (This is a bit yucky and fragile since it's the inverse of // CCmpTemplateManager::CopyFoundationSubset and only includes components -// that our EntityTemplate class currently uses.) +// that our Template class currently uses.) m.g_FoundationForbiddenComponents = { "ProductionQueue": 1, "ResourceSupply": 1, @@ -119,6 +119,8 @@ m.SharedScript.prototype.GetTemplate = function(name) // We need to now the initial state of the game for this, as we will use it. // This is called right at the end of the map generation. m.SharedScript.prototype.init = function(state) { + this.ApplyTemplatesDelta(state); + this.passabilityClasses = state.passabilityClasses; this.passabilityMap = state.passabilityMap; this.players = this._players; @@ -127,9 +129,6 @@ m.SharedScript.prototype.init = function(state) { this.timeElapsed = state.timeElapsed; this.barterPrices = state.barterPrices; - for (var o in state.players) - this._techModifications[o] = state.players[o].techModifications; - this._entities = {}; for (var id in state.entities) this._entities[id] = new m.Entity(this, state.entities[id]); @@ -166,6 +165,7 @@ m.SharedScript.prototype.onUpdate = function(state) return; // deals with updating based on create and destroy messages. this.ApplyEntitiesDelta(state); + this.ApplyTemplatesDelta(state); Engine.ProfileStart("onUpdate"); @@ -178,9 +178,6 @@ m.SharedScript.prototype.onUpdate = function(state) this.timeElapsed = state.timeElapsed; this.barterPrices = state.barterPrices; - for (var o in state.players) - this._techModifications[o] = state.players[o].techModifications; - for (var i in this.gameState) this.gameState[i].update(this,state); @@ -313,6 +310,34 @@ m.SharedScript.prototype.ApplyEntitiesDelta = function(state) this.updateEntityCollections(prop, this._entities[id]); } } + + // apply per-entity aura-related changes. + // this supersedes tech-related changes. + for (var id in state.changedEntityTemplateInfo) + { + var changes = state.changedEntityTemplateInfo[id]; + for each (var change in changes) + this._entities[id]._auraTemplateModif[change.variable] = change.value; + } + Engine.ProfileStop(); +}; + +m.SharedScript.prototype.ApplyTemplatesDelta = function(state) +{ + Engine.ProfileStart("Shared ApplyTemplatesDelta"); + + for (var player in state.changedTemplateInfo) + { + var playerDiff = state.changedTemplateInfo[player]; + for (var template in playerDiff) + { + var changes = playerDiff[template]; + if (!this._techModifications[player][template]) + this._techModifications[player][template] = {}; + for each (var change in changes) + this._techModifications[player][template][change.variable] = change.value; + } + } Engine.ProfileStop(); }; diff --git a/binaries/data/mods/public/simulation/components/AIInterface.js b/binaries/data/mods/public/simulation/components/AIInterface.js index f3bd9f76ee..f7ba2f1a61 100644 --- a/binaries/data/mods/public/simulation/components/AIInterface.js +++ b/binaries/data/mods/public/simulation/components/AIInterface.js @@ -18,15 +18,21 @@ AIInterface.prototype.Init = function() this.events["OwnershipChanged"] = []; this.changedEntities = {}; + + // cache for technology changes; + // this one is PlayerID->TemplateName->{StringForTheValue, ActualValue} + this.changedTemplateInfo = {}; + // this is for auras and is EntityID->{StringForTheValue, ActualValue} + this.changedEntityTemplateInfo = {}; }; -AIInterface.prototype.GetRepresentation = function() +AIInterface.prototype.GetNonEntityRepresentation = function() { var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - + // Return the same game state as the GUI uses var state = cmpGuiInterface.GetExtendedSimulationState(-1); - + // Add some extra AI-specific data state.events = {}; state.events["Create"] = this.events["Create"]; @@ -39,7 +45,6 @@ AIInterface.prototype.GetRepresentation = function() state.events["PlayerDefeated"] = this.events["PlayerDefeated"]; state.events["EntityRenamed"] = this.events["EntityRenamed"]; state.events["OwnershipChanged"] = this.events["OwnershipChanged"]; - // Reset the event list for the next turn this.events["Create"] = []; this.events["Destroy"] = []; @@ -51,7 +56,14 @@ AIInterface.prototype.GetRepresentation = function() this.events["PlayerDefeated"] = []; this.events["EntityRenamed"] = []; this.events["OwnershipChanged"] = []; - + + return state; +}; + +AIInterface.prototype.GetRepresentation = function() +{ + var state = this.GetNonEntityRepresentation(); + // Add entity representations Engine.ProfileStart("proxy representations"); state.entities = {}; @@ -64,29 +76,18 @@ AIInterface.prototype.GetRepresentation = function() this.changedEntities = {}; Engine.ProfileStop(); + state.changedTemplateInfo = this.changedTemplateInfo; + this.changedTemplateInfo = {}; + state.changedEntityTemplateInfo = this.changedEntityTemplateInfo; + this.changedEntityTemplateInfo = {}; + return state; }; + // Intended to be called first, during the map initialization: no caching AIInterface.prototype.GetFullRepresentation = function(flushEvents) -{ - var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - - // Return the same game state as the GUI uses - var state = cmpGuiInterface.GetExtendedSimulationState(-1); - - // Add some extra AI-specific data - state.events = {}; - state.events["Create"] = this.events["Create"]; - state.events["Destroy"] = this.events["Destroy"]; - state.events["Attacked"] = this.events["Attacked"]; - state.events["RangeUpdate"] = this.events["RangeUpdate"]; - state.events["ConstructionFinished"] = this.events["ConstructionFinished"]; - state.events["TrainingFinished"] = this.events["TrainingFinished"]; - state.events["AIMetadata"] = this.events["AIMetadata"]; - state.events["PlayerDefeated"] = this.events["PlayerDefeated"]; - state.events["EntityRenamed"] = this.events["EntityRenamed"]; - state.events["OwnershipChanged"] = this.events["OwnershipChanged"]; - +{ + var state = this.GetNonEntityRepresentation(); if (flushEvents) { @@ -102,19 +103,6 @@ AIInterface.prototype.GetFullRepresentation = function(flushEvents) state.events["OwnershipChanged"] = []; } - // Reset the event list for the next turn - this.events["Create"] = []; - this.events["Destroy"] = []; - this.events["Attacked"] = []; - this.events["RangeUpdate"] = []; - this.events["ConstructionFinished"] = []; - this.events["TrainingFinished"] = []; - this.events["AIMetadata"] = []; - this.events["PlayerDefeated"] = []; - this.events["EntityRenamed"] = []; - this.events["OwnershipChanged"] = []; - - // Add entity representations Engine.ProfileStart("proxy representations"); state.entities = {}; @@ -127,6 +115,11 @@ AIInterface.prototype.GetFullRepresentation = function(flushEvents) } Engine.ProfileStop(); + state.changedTemplateInfo = this.changedTemplateInfo; + this.changedTemplateInfo = {}; + state.changedEntityTemplateInfo = this.changedEntityTemplateInfo; + this.changedEntityTemplateInfo = {}; + return state; }; @@ -156,4 +149,97 @@ AIInterface.prototype.OnGlobalEntityRenamed = function(msg) this.events["EntityRenamed"].push(msg); }; +// When a new technology is researched, check which templates it affects, +// and send the updated values to the AI. +// this relies on the fact that any "value" in a technology can only ever change +// one template value, and that the naming is the same (with / in place of .) +// it's not incredibly fast but it's not incredibly slow. +AIInterface.prototype.OnTemplateModification = function(msg) +{ + var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + if (!this.templates) + this.templates = cmpTemplateManager.FindAllTemplates(false); + + for each (var value in msg.valueNames) + { + for (var o = 0; o < this.templates.length; ++o) + { + var tmp = this.templates[o]; + var template = cmpTemplateManager.GetTemplateWithoutValidation(this.templates[o]); + // remove templates that we obviously don't care about. + if (!template || !template.Identity || ! template.Identity.Civ) + { + this.templates.splice(o--,1); + continue; + } + + // let's get the base template value. + var strings = value.split("/"); + var item = template; + var ended = true; + for (var i = 0; i < strings.length; ++i) + { + if (item !== undefined && item[strings[i]] !== undefined) + item = item[strings[i]]; + else + ended = false; + } + if (!ended) + continue; + // item now contains the template value for this. + + // check for numerals, they need to be handled properly + item = !isNaN(+item) ? +item : item; + var newValue = ApplyValueModificationsToTemplate(value, item, msg.player, template); + // round the value to 5th decimal or so. + newValue = !isNaN(+newValue) ? (Math.abs((+newValue) - Math.round(+newValue)) < 0.0001 ? Math.round(+newValue) : +newValue) : newValue; + + if(item != newValue) + { + if (!this.changedTemplateInfo[msg.player]) + this.changedTemplateInfo[msg.player] = {}; + if (!this.changedTemplateInfo[msg.player][this.templates[o]]) + this.changedTemplateInfo[msg.player][this.templates[o]] = [ { "variable" : value, "value" : newValue} ]; + else + this.changedTemplateInfo[msg.player][this.templates[o]].push({ "variable" : value, "value" : newValue }); + } + } + } +}; + +AIInterface.prototype.OnGlobalValueModification = function(msg) +{ + var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + for each (var ent in msg.entities) + { + var template = cmpTemplateManager.GetTemplateWithoutValidation(cmpTemplateManager.GetCurrentTemplateName(ent)); + for each (var value in msg.valueNames) + { + // let's get the base template value. + var strings = value.split("/"); + var item = template; + var ended = true; + for (var i = 0; i < strings.length; ++i) + { + if (item !== undefined && item[strings[i]] !== undefined) + item = item[strings[i]]; + else + ended = false; + } + if (!ended) + continue; + // "item" now contains the unmodified template value for this. + var newValue = ApplyValueModificationsToEntity(value, +item, ent); + newValue = typeof(newValue) === "Number" ? Math.round(newValue) : newValue; + if(item != newValue) + { + if (!this.changedEntityTemplateInfo[ent]) + this.changedEntityTemplateInfo[ent] = [{ "variable" : value, "value" : newValue }]; + else + this.changedEntityTemplateInfo[ent].push({ "variable" : value, "value" : newValue }); + } + } + } +}; + Engine.RegisterComponentType(IID_AIInterface, "AIInterface", AIInterface); diff --git a/binaries/data/mods/public/simulation/components/AIProxy.js b/binaries/data/mods/public/simulation/components/AIProxy.js index ab46a7a2b2..a00f7b9fac 100644 --- a/binaries/data/mods/public/simulation/components/AIProxy.js +++ b/binaries/data/mods/public/simulation/components/AIProxy.js @@ -36,10 +36,19 @@ AIProxy.prototype.Init = function() this.needsFullGet = true; this.owner = -1; // for convenience now and then. + this.cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); + // Let the AIInterface know that we exist and that it should query us this.NotifyChange(); }; +AIProxy.prototype.Serialize = null; // we have no dynamic state to save + +AIProxy.prototype.Deserialize = function () +{ + this.cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); +}; + AIProxy.prototype.GetRepresentation = function() { // Return the full representation the first time we're called @@ -67,9 +76,7 @@ AIProxy.prototype.NotifyChange = function() if (!this.changes) { this.changes = {}; - - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); - cmpAIInterface.ChangedEntity(this.entity); + this.cmpAIInterface.ChangedEntity(this.entity); } }; @@ -261,55 +268,47 @@ AIProxy.prototype.OnOwnershipChanged = function(msg) if (msg.from === -1) { - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); - cmpAIInterface.PushEvent("Create", {"entity" : msg.entity}); + this.cmpAIInterface.PushEvent("Create", {"entity" : msg.entity}); return; } else if (msg.to === -1) { - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); - cmpAIInterface.PushEvent("Destroy", {"entity" : msg.entity}); + this.cmpAIInterface.PushEvent("Destroy", {"entity" : msg.entity}); return; } this.owner = msg.to; this.changes.owner = msg.to; - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); - cmpAIInterface.PushEvent("OwnershipChanged", msg); + this.cmpAIInterface.PushEvent("OwnershipChanged", msg); }; AIProxy.prototype.OnAttacked = function(msg) { - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); - cmpAIInterface.PushEvent("Attacked", msg); + this.cmpAIInterface.PushEvent("Attacked", msg); }; /* Deactivated for actually not really being practical for most uses. AIProxy.prototype.OnRangeUpdate = function(msg) { - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); msg.owner = this.owner; - cmpAIInterface.PushEvent("RangeUpdate", msg); + this.cmpAIInterface.PushEvent("RangeUpdate", msg); warn(uneval(msg)); };*/ AIProxy.prototype.OnConstructionFinished = function(msg) { - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); - cmpAIInterface.PushEvent("ConstructionFinished", msg); + this.cmpAIInterface.PushEvent("ConstructionFinished", msg); }; AIProxy.prototype.OnTrainingFinished = function(msg) { - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); - cmpAIInterface.PushEvent("TrainingFinished", msg); + this.cmpAIInterface.PushEvent("TrainingFinished", msg); }; AIProxy.prototype.OnAIMetadata = function(msg) { - var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); - cmpAIInterface.PushEvent("AIMetadata", msg); + this.cmpAIInterface.PushEvent("AIMetadata", msg); }; Engine.RegisterComponentType(IID_AIProxy, "AIProxy", AIProxy); diff --git a/binaries/data/mods/public/simulation/components/AuraManager.js b/binaries/data/mods/public/simulation/components/AuraManager.js index 8c025d15ae..89adfc90d8 100644 --- a/binaries/data/mods/public/simulation/components/AuraManager.js +++ b/binaries/data/mods/public/simulation/components/AuraManager.js @@ -46,7 +46,7 @@ AuraManager.prototype.ApplyBonus = function(value, ent, data, key) this.modificationsCache[value][ent].add += data.add; // post message to the entity to notify it about the change - Engine.PostMessage(ent, MT_ValueModification, { "component": value.split("/")[0] }); + Engine.PostMessage(ent, MT_ValueModification, { "entities": [ent], "component": value.split("/")[0], "valueNames": [value] }); }; AuraManager.prototype.ApplyTemplateBonus = function(value, player, classes, data, key) @@ -73,6 +73,7 @@ AuraManager.prototype.ApplyTemplateBonus = function(value, player, classes, data if (data.add) this.templateModificationsCache[value][player][c][key].add += data.add; + Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": player, "component": value.split("/")[0], "valueNames": [value] }); } }; @@ -98,7 +99,9 @@ AuraManager.prototype.RemoveBonus = function(value, ent, key) this.modificationsCache[value][ent].multiply /= data.multiply; // post message to the entity to notify it about the change - Engine.PostMessage(ent, MT_ValueModification, { "component": value.split("/")[0] }); + var effects = {}; + effects[value] = this.modificationsCache[value][ent]; + Engine.PostMessage(ent, MT_ValueModification, { "entities": [ent], "component": value.split("/")[0], "valueNames": [value] }); }; AuraManager.prototype.RemoveTemplateBonus = function(value, player, classes, key) @@ -119,6 +122,7 @@ AuraManager.prototype.RemoveTemplateBonus = function(value, player, classes, key this.templateModificationsCache[value][player][c][key].multiply = 1; this.templateModificationsCache[value][player][c][key].add = 0; } + Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": player, "component": value.split("/")[0], "valueNames": [value] }); }; AuraManager.prototype.ApplyModifications = function(valueName, value, ent) diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 3f25673484..e9121e82b0 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -97,7 +97,6 @@ GuiInterface.prototype.GetSimulationState = function(player) "entityLimits": cmpPlayerEntityLimits.GetLimits(), "entityCounts": cmpPlayerEntityLimits.GetCounts(), "entityLimitChangers": cmpPlayerEntityLimits.GetLimitChangers(), - "techModifications": cmpTechnologyManager.GetTechModifications(), "researchQueued": cmpTechnologyManager.GetQueuedResearch(), "researchStarted": cmpTechnologyManager.GetStartedResearch(), "researchedTechs": cmpTechnologyManager.GetResearchedTechs(), @@ -475,8 +474,8 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName) { ret.armour = { "hack": ApplyValueModificationsToTemplate("Armour/Hack", +template.Armour.Hack, player, template), - "pierce": ApplyValueModificationsToTemplate("Armour/Pierce", +template.Armour.Hack, player, template), - "crush": ApplyValueModificationsToTemplate("Armour/Crush", +template.Armour.Hack, player, template), + "pierce": ApplyValueModificationsToTemplate("Armour/Pierce", +template.Armour.Pierce, player, template), + "crush": ApplyValueModificationsToTemplate("Armour/Crush", +template.Armour.Crush, player, template), }; } diff --git a/binaries/data/mods/public/simulation/components/TechnologyManager.js b/binaries/data/mods/public/simulation/components/TechnologyManager.js index 408b125d13..b24b816353 100644 --- a/binaries/data/mods/public/simulation/components/TechnologyManager.js +++ b/binaries/data/mods/public/simulation/components/TechnologyManager.js @@ -226,13 +226,17 @@ TechnologyManager.prototype.OnGlobalOwnershipChanged = function (msg) var modifications = this.modifications[name]; var component = name.split("/")[0]; for (var i in modifications) - if (!modifiedComponents[component] && DoesModificationApply(modifications[i], classes)) - modifiedComponents[component] = true; + if (DoesModificationApply(modifications[i], 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, { "component": component }); + Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] }); } } if (msg.from == playerID) @@ -322,16 +326,28 @@ TechnologyManager.prototype.ResearchTechnology = function (tech) mod[j] = modification[j]; this.modifications[modification.value].push(mod); - modifiedComponents[modification.value.split("/")[0]] = true; + var component = modification.value.split("/")[0]; + if (!modifiedComponents[component]) + modifiedComponents[component] = []; + modifiedComponents[component].push(modification.value); this.modificationCache[modification.value] = {}; } } this.UpdateAutoResearch(); + var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); + if (!cmpPlayer || ! cmpPlayer.GetPlayerID()) + return; + var playerID = cmpPlayer.GetPlayerID(); + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + var ents = cmpRangeManager.GetEntitiesByPlayer(playerID); // TODO: Handle technology broadcasting for autoresearch properly (some components might not be initialized currently) for (var component in modifiedComponents) - Engine.BroadcastMessage(MT_ValueModification, { "component": component }); + { + Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": playerID, "component": component, "valueNames": modifiedComponents[component]}); + Engine.BroadcastMessage(MT_ValueModification, { "entities": ents, "component": component, "valueNames": modifiedComponents[component]}); + } }; // Clears the cached data for an entity from the modifications cache diff --git a/source/simulation2/MessageTypes.h b/source/simulation2/MessageTypes.h index a097104336..76079afe0c 100644 --- a/source/simulation2/MessageTypes.h +++ b/source/simulation2/MessageTypes.h @@ -366,19 +366,43 @@ public: }; /** - * Sent by value modification manager when a value of a certain component is changed + * Sent by aura manager when a value of a certain entity's component is changed */ class CMessageValueModification : public CMessage { public: DEFAULT_MESSAGE_IMPL(ValueModification) - CMessageValueModification(std::wstring component) : - component(component) + CMessageValueModification(const std::vector& entities, std::wstring component, const std::vector& valueNames) : + entities(entities), + component(component), + valueNames(valueNames) { } - + + std::vector entities; std::wstring component; + std::vector valueNames; +}; + +/** + * Sent by aura and tech managers when a value of a certain template's component is changed + */ +class CMessageTemplateModification : public CMessage +{ +public: + DEFAULT_MESSAGE_IMPL(TemplateModification) + + CMessageTemplateModification(player_id_t player, std::wstring component, const std::vector& valueNames) : + player(player), + component(component), + valueNames(valueNames) + { + } + + player_id_t player; + std::wstring component; + std::vector valueNames; }; /** diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index cde9784fe1..8183c2f00e 100644 --- a/source/simulation2/TypeList.h +++ b/source/simulation2/TypeList.h @@ -48,6 +48,7 @@ MESSAGE(TerrainChanged) MESSAGE(TerritoriesChanged) MESSAGE(PathResult) MESSAGE(ValueModification) +MESSAGE(TemplateModification) MESSAGE(VisionRangeChanged) MESSAGE(MinimapPing) diff --git a/source/simulation2/components/ICmpTemplateManager.cpp b/source/simulation2/components/ICmpTemplateManager.cpp index 6792180706..c0af33ac8f 100644 --- a/source/simulation2/components/ICmpTemplateManager.cpp +++ b/source/simulation2/components/ICmpTemplateManager.cpp @@ -23,6 +23,7 @@ BEGIN_INTERFACE_WRAPPER(TemplateManager) DEFINE_INTERFACE_METHOD_1("GetTemplate", const CParamNode*, ICmpTemplateManager, GetTemplate, std::string) +DEFINE_INTERFACE_METHOD_1("GetTemplateWithoutValidation", const CParamNode*, ICmpTemplateManager, GetTemplateWithoutValidation, std::string) DEFINE_INTERFACE_METHOD_1("GetCurrentTemplateName", std::string, ICmpTemplateManager, GetCurrentTemplateName, entity_id_t) DEFINE_INTERFACE_METHOD_1("FindAllTemplates", std::vector, ICmpTemplateManager, FindAllTemplates, bool) DEFINE_INTERFACE_METHOD_1("GetEntitiesUsingTemplate", std::vector, ICmpTemplateManager, GetEntitiesUsingTemplate, std::string) diff --git a/source/simulation2/scripting/MessageTypeConversions.cpp b/source/simulation2/scripting/MessageTypeConversions.cpp index 781dec975c..a2180de468 100644 --- a/source/simulation2/scripting/MessageTypeConversions.cpp +++ b/source/simulation2/scripting/MessageTypeConversions.cpp @@ -303,15 +303,39 @@ CMessage* CMessagePathResult::FromJSVal(ScriptInterface& UNUSED(scriptInterface) jsval CMessageValueModification::ToJSVal(ScriptInterface& scriptInterface) const { TOJSVAL_SETUP(); + SET_MSG_PROPERTY(entities); SET_MSG_PROPERTY(component); + SET_MSG_PROPERTY(valueNames); return OBJECT_TO_JSVAL(obj); } CMessage* CMessageValueModification::FromJSVal(ScriptInterface& scriptInterface, jsval val) { FROMJSVAL_SETUP(); + GET_MSG_PROPERTY(std::vector, entities); GET_MSG_PROPERTY(std::wstring, component); - return new CMessageValueModification(component); + GET_MSG_PROPERTY(std::vector, valueNames); + return new CMessageValueModification(entities, component, valueNames); +} + +//////////////////////////////// + +jsval CMessageTemplateModification::ToJSVal(ScriptInterface& scriptInterface) const +{ + TOJSVAL_SETUP(); + SET_MSG_PROPERTY(player); + SET_MSG_PROPERTY(component); + SET_MSG_PROPERTY(valueNames); + return OBJECT_TO_JSVAL(obj); +} + +CMessage* CMessageTemplateModification::FromJSVal(ScriptInterface& scriptInterface, jsval val) +{ + FROMJSVAL_SETUP(); + GET_MSG_PROPERTY(player_id_t, player); + GET_MSG_PROPERTY(std::wstring, component); + GET_MSG_PROPERTY(std::vector, valueNames); + return new CMessageTemplateModification(player, component, valueNames); } ////////////////////////////////