diff --git a/binaries/data/mods/public/gui/session/hotkeys/misc.xml b/binaries/data/mods/public/gui/session/hotkeys/misc.xml index 79b628d524..53bb77e78b 100644 --- a/binaries/data/mods/public/gui/session/hotkeys/misc.xml +++ b/binaries/data/mods/public/gui/session/hotkeys/misc.xml @@ -60,7 +60,7 @@ - performCommand(g_Selection.toList().map(ent => GetExtendedEntityState(ent)), "delete"); + performCommand(g_Selection.toList().map(ent => GetEntityState(ent)), "delete"); diff --git a/binaries/data/mods/public/gui/session/selection_details.js b/binaries/data/mods/public/gui/session/selection_details.js index 208189802c..3651dcac32 100644 --- a/binaries/data/mods/public/gui/session/selection_details.js +++ b/binaries/data/mods/public/gui/session/selection_details.js @@ -463,7 +463,7 @@ function updateSelectionDetails() for (let sel of g_Selection.toList()) { - let entState = GetExtendedEntityState(sel); + let entState = GetEntityState(sel); if (!entState) continue; entStates.push(entState); diff --git a/binaries/data/mods/public/gui/session/selection_panels_helpers.js b/binaries/data/mods/public/gui/session/selection_panels_helpers.js index 24d1692490..f970a51560 100644 --- a/binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ b/binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -257,7 +257,7 @@ function performAllyCommand(entity, commandName) if (!entity) return; - let entState = GetExtendedEntityState(entity); + let entState = GetEntityState(entity); let playerState = GetSimState().players[Engine.GetPlayerID()]; if (!playerState.isMutualAlly[entState.player] || g_IsObserver) return; @@ -381,7 +381,7 @@ function unloadSelection() let ents = []; for (let ent in g_Selection.selected) { - let state = GetExtendedEntityState(+ent); + let state = GetEntityState(+ent); if (!state || !state.turretParent) continue; if (!parent) diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index 8926375a94..f07bc50d9c 100644 --- a/binaries/data/mods/public/gui/session/session.js +++ b/binaries/data/mods/public/gui/session/session.js @@ -192,33 +192,20 @@ function GetSimState() return g_SimState; } +function GetMultipleEntityStates(ents) +{ + if (!ents.length) + return null; + let entityStates = Engine.GuiInterfaceCall("GetMultipleEntityStates", ents); + for (let item of entityStates) + g_EntityStates[item.entId] = deepfreeze(item.state); + return entityStates; +} + function GetEntityState(entId) { if (!g_EntityStates[entId]) - { - g_EntityStates[entId] = Engine.GuiInterfaceCall("GetEntityState", entId); - - // Freeze all existing properties, but allow GetExtendedEntityState to extend the object - if (g_EntityStates[entId]) - for (let name of Object.getOwnPropertyNames(g_EntityStates[entId])) - if (typeof prop == 'object' && prop !== null) - deepfreeze(g_EntityStates[entId][name]); - } - - return g_EntityStates[entId]; -} - -function GetExtendedEntityState(entId) -{ - let entState = GetEntityState(entId); - if (entState && !entState.extended) - { - let extension = Engine.GuiInterfaceCall("GetExtendedEntityState", entId); - for (let prop in extension) - entState[prop] = extension[prop]; - entState.extended = true; - g_EntityStates[entId] = deepfreeze(entState); - } + g_EntityStates[entId] = deepfreeze(Engine.GuiInterfaceCall("GetEntityState", entId)); return g_EntityStates[entId]; } @@ -744,6 +731,8 @@ function onTick() if (g_Selection.dirty) { g_Selection.dirty = false; + // When selection changed, get the entityStates of new entities + GetMultipleEntityStates(g_Selection.toList().filter(entId => !g_EntityStates[entId])); updateGUIObjects(); @@ -798,6 +787,8 @@ function onSimulationUpdate() if (!GetSimState()) return; + GetMultipleEntityStates(g_Selection.toList()); + updateCinemaPath(); handleNotifications(); updateGUIObjects(); @@ -960,7 +951,7 @@ function updatePanelEntities() for (let ent of panelEnts) { - let panelEntState = GetExtendedEntityState(ent); + let panelEntState = GetEntityState(ent); let template = GetTemplateData(panelEntState.template); let panelEnt = g_PanelEntities.find(pEnt => ent == pEnt.ent); @@ -1107,7 +1098,7 @@ function updateDebug() let selection = g_Selection.toList(); if (selection.length) { - let entState = GetExtendedEntityState(selection[0]); + let entState = GetEntityState(selection[0]); if (entState) { let template = GetTemplateData(entState.template); diff --git a/binaries/data/mods/public/gui/session/unit_actions.js b/binaries/data/mods/public/gui/session/unit_actions.js index f3238aa687..6fd9afb474 100644 --- a/binaries/data/mods/public/gui/session/unit_actions.js +++ b/binaries/data/mods/public/gui/session/unit_actions.js @@ -1520,12 +1520,12 @@ function getActionInfo(action, target, selection) // Look at the first targeted entity // (TODO: maybe we eventually want to look at more, and be more context-sensitive? // e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse) - let targetState = GetExtendedEntityState(target); + let targetState = GetEntityState(target); // Check if any entities in the selection can do some of the available actions with target for (let entityID of selection) { - let entState = GetExtendedEntityState(entityID); + let entState = GetEntityState(entityID); if (!entState) continue; diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 09445f69c5..babdc5e458 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -194,8 +194,8 @@ GuiInterface.prototype.GetRenamedEntities = function(player) { if (this.miragedEntities[player]) return this.renamedEntities.concat(this.miragedEntities[player]); - else - return this.renamedEntities; + + return this.renamedEntities; }; GuiInterface.prototype.ClearRenamedEntities = function() @@ -229,25 +229,42 @@ GuiInterface.prototype.GetEntityState = function(player, ent) "template": template, "alertRaiser": null, + "armour": null, + "attack": null, "builder": null, + "buildingAI": null, + "buildRate": null, + "buildTime": null, "canGarrison": null, + "deathDamage": null, + "heal": null, "identity": null, + "isBarterMarket": null, "fogging": null, "foundation": null, "garrisonHolder": null, "gate": null, "guard": null, + "loot": null, "market": null, "mirage": null, "pack": null, + "promotion": null, "upgrade" : null, "player": -1, "position": null, "production": null, "rallyPoint": null, + "repairRate": null, "resourceCarrying": null, + "resourceDropsite": null, + "resourceGatherRates": null, + "resourceSupply": null, + "resourceTrickle": null, "rotation": null, + "speed": null, "trader": null, + "turretParent":null, "unitAI": null, "visibility": null, }; @@ -338,14 +355,21 @@ GuiInterface.prototype.GetEntityState = function(player, ent) let cmpFoundation = QueryMiragedInterface(ent, IID_Foundation); if (cmpFoundation) + { ret.foundation = { "progress": cmpFoundation.GetBuildPercentage(), "numBuilders": cmpFoundation.GetNumBuilders() }; + ret.buildRate = cmpFoundation.GetBuildRate(); + ret.buildTime = cmpFoundation.GetBuildTime(); + } let cmpRepairable = QueryMiragedInterface(ent, IID_Repairable); if (cmpRepairable) + { ret.repairable = { "numBuilders": cmpRepairable.GetNumBuilders() }; + ret.repairRate = cmpRepairable.GetRepairRate(); + } let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) @@ -388,7 +412,10 @@ GuiInterface.prototype.GetEntityState = function(player, ent) let cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer); if (cmpResourceGatherer) + { ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus(); + ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates(); + } let cmpGate = Engine.QueryInterface(ent, IID_Gate); if (cmpGate) @@ -407,37 +434,6 @@ GuiInterface.prototype.GetEntityState = function(player, ent) let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); ret.visibility = cmpRangeManager.GetLosVisibility(ent, player); - return ret; -}; - -/** - * Get additionnal entity info, rarely used in the gui - */ -GuiInterface.prototype.GetExtendedEntityState = function(player, ent) -{ - let ret = { - "armour": null, - "attack": null, - "buildingAI": null, - "deathDamage": null, - "heal": null, - "isBarterMarket": null, - "loot": null, - "obstruction": null, - "turretParent":null, - "promotion": null, - "repairRate": null, - "buildRate": null, - "buildTime": null, - "resourceDropsite": null, - "resourceGatherRates": null, - "resourceSupply": null, - "resourceTrickle": null, - "speed": null, - }; - - let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - let cmpAttack = Engine.QueryInterface(ent, IID_Attack); if (cmpAttack) { @@ -468,10 +464,6 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent) ret.attack[type].elevationBonus = range.elevationBonus; - let cmpPosition = Engine.QueryInterface(ent, IID_Position); - let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); - let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld()) { // For units, take the range in front of it, no spread. So angle = 0 @@ -512,28 +504,9 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent) if (cmpDeathDamage) ret.deathDamage = cmpDeathDamage.GetDeathDamageStrengths(); - let cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); - if (cmpObstruction) - ret.obstruction = { - "controlGroup": cmpObstruction.GetControlGroup(), - "controlGroup2": cmpObstruction.GetControlGroup2(), - }; - - let cmpPosition = Engine.QueryInterface(ent, IID_Position); if (cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY) ret.turretParent = cmpPosition.GetTurretParent(); - let cmpRepairable = Engine.QueryInterface(ent, IID_Repairable); - if (cmpRepairable) - ret.repairRate = cmpRepairable.GetRepairRate(); - - let cmpFoundation = Engine.QueryInterface(ent, IID_Foundation); - if (cmpFoundation) - { - ret.buildRate = cmpFoundation.GetBuildRate(); - ret.buildTime = cmpFoundation.GetBuildTime(); - } - let cmpResourceSupply = QueryMiragedInterface(ent, IID_ResourceSupply); if (cmpResourceSupply) ret.resourceSupply = { @@ -546,10 +519,6 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent) "numGatherers": cmpResourceSupply.GetNumGatherers() }; - let cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer); - if (cmpResourceGatherer) - ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates(); - let cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite); if (cmpResourceDropsite) ret.resourceDropsite = { @@ -581,27 +550,17 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent) let cmpLoot = Engine.QueryInterface(ent, IID_Loot); if (cmpLoot) { - let resources = cmpLoot.GetResources(); - ret.loot = { - "xp": cmpLoot.GetXp() - }; - for (let res of Resources.GetCodes()) - ret.loot[res] = resources[res]; + ret.loot = cmpLoot.GetResources(); + ret.loot.xp = cmpLoot.GetXp(); } let cmpResourceTrickle = Engine.QueryInterface(ent, IID_ResourceTrickle); if (cmpResourceTrickle) - { ret.resourceTrickle = { "interval": cmpResourceTrickle.GetTimer(), - "rates": {} + "rates": cmpResourceTrickle.GetRates() }; - let rates = cmpResourceTrickle.GetRates(); - for (let res in rates) - ret.resourceTrickle.rates[res] = rates[res]; - } - let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); if (cmpUnitMotion) ret.speed = { @@ -612,6 +571,11 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent) return ret; }; +GuiInterface.prototype.GetMultipleEntityStates = function(player, ents) +{ + return ents.map(ent => ({ "entId": ent, "state": this.GetEntityState(player, ent) })); +}; + GuiInterface.prototype.GetAverageRangeForBuildings = function(player, cmd) { let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); @@ -630,10 +594,10 @@ GuiInterface.prototype.GetAverageRangeForBuildings = function(player, cmd) return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, elevationBonus, 2*Math.PI); }; -GuiInterface.prototype.GetTemplateData = function(player, name) +GuiInterface.prototype.GetTemplateData = function(player, templateName) { let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); - let template = cmpTemplateManager.GetTemplate(name); + let template = cmpTemplateManager.GetTemplate(templateName); if (!template) return null; @@ -1222,42 +1186,40 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) return false; } - else - { - // Move all existing cached entities outside of the world and reset their use count - for (let tpl in this.placementWallEntities) - { - for (let ent of this.placementWallEntities[tpl].entities) - { - let pos = Engine.QueryInterface(ent, IID_Position); - if (pos) - pos.MoveOutOfWorld(); - } - this.placementWallEntities[tpl].numUsed = 0; + // Move all existing cached entities outside of the world and reset their use count + for (let tpl in this.placementWallEntities) + { + for (let ent of this.placementWallEntities[tpl].entities) + { + let pos = Engine.QueryInterface(ent, IID_Position); + if (pos) + pos.MoveOutOfWorld(); } - // Create cache entries for templates we haven't seen before - for (let type in wallSet.templates) + this.placementWallEntities[tpl].numUsed = 0; + } + + // Create cache entries for templates we haven't seen before + for (let type in wallSet.templates) + { + if (type == "curves") + continue; + + let tpl = wallSet.templates[type]; + if (!(tpl in this.placementWallEntities)) { - if (type == "curves") - continue; + this.placementWallEntities[tpl] = { + "numUsed": 0, + "entities": [], + "templateData": this.GetTemplateData(player, tpl), + }; - let tpl = wallSet.templates[type]; - if (!(tpl in this.placementWallEntities)) + // ensure that the loaded template data contains a wallPiece component + if (!this.placementWallEntities[tpl].templateData.wallPiece) { - this.placementWallEntities[tpl] = { - "numUsed": 0, - "entities": [], - "templateData": this.GetTemplateData(player, tpl), - }; - - // ensure that the loaded template data contains a wallPiece component - if (!this.placementWallEntities[tpl].templateData.wallPiece) - { - error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'"); - return false; - } + error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'"); + return false; } } } @@ -1365,7 +1327,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) "template": wallSet.templates.tower, "pos": start.pos, "angle": cmpPosition.GetRotation().y, - "controlGroups": [(startEntObstruction ? startEntObstruction.GetControlGroup() : undefined)], + "controlGroups": [startEntObstruction ? startEntObstruction.GetControlGroup() : undefined], "excludeFromResult": true, // preview only, must not appear in the result }); } @@ -1390,7 +1352,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) previewEntities.unshift({ "template": wallSet.templates.tower, "pos": start.pos, - "angle": (previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle) + "angle": previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle }); } @@ -1408,7 +1370,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) // '.controlGroup' property. Note that this array can only ever have 0, 1 or 2 elements (checked at a later time). if (previewEntities.length > 0 && endEntObstruction) { - previewEntities[previewEntities.length-1].controlGroups = (previewEntities[previewEntities.length-1].controlGroups || []); + previewEntities[previewEntities.length-1].controlGroups = previewEntities[previewEntities.length-1].controlGroups || []; previewEntities[previewEntities.length-1].controlGroups.push(endEntObstruction.GetControlGroup()); } @@ -1422,7 +1384,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) "template": wallSet.templates.tower, "pos": end.pos, "angle": cmpPosition.GetRotation().y, - "controlGroups": [(endEntObstruction ? endEntObstruction.GetControlGroup() : undefined)], + "controlGroups": [endEntObstruction ? endEntObstruction.GetControlGroup() : undefined], "excludeFromResult": true }); } @@ -1431,7 +1393,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) previewEntities.push({ "template": wallSet.templates.tower, "pos": end.pos, - "angle": (previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle) + "angle": previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle }); } @@ -1553,7 +1515,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) // Check whether it's in a visible or fogged region // TODO: should definitely reuse SetBuildingPlacementPreview, this is just straight up copy/pasta - let visible = (cmpRangeManager.GetLosVisibility(ent, player) != "hidden"); + let visible = cmpRangeManager.GetLosVisibility(ent, player) != "hidden"; if (visible) { let cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions); @@ -1564,7 +1526,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) } // TODO: Handle results of CheckPlacement - validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success); + validPlacement = cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success; // If a wall piece has two control groups, it's likely a segment that spans // between two existing towers. To avoid placing a duplicate wall segment, @@ -1972,7 +1934,7 @@ let exposedFunctions = { "GetRenamedEntities": 1, "ClearRenamedEntities": 1, "GetEntityState": 1, - "GetExtendedEntityState": 1, + "GetMultipleEntityStates": 1, "GetAverageRangeForBuildings": 1, "GetTemplateData": 1, "IsTechnologyResearched": 1, @@ -2027,8 +1989,8 @@ GuiInterface.prototype.ScriptCall = function(player, name, args) { if (exposedFunctions[name]) return this[name](player, args); - else - throw new Error("Invalid GuiInterface Call name \""+name+"\""); + + throw new Error("Invalid GuiInterface Call name \""+name+"\""); }; Engine.RegisterSystemComponentType(IID_GuiInterface, "GuiInterface", GuiInterface); diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js index c635a665ee..5f731914fd 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -561,79 +561,65 @@ AddMock(10, IID_Position, { AddMock(10, IID_ResourceTrickle, { "GetTimer": () => 1250, - "GetRates": () => ({ - "food": 2, - "wood": 3, - "stone": 5, - "metal": 9 - }) + "GetRates": () => ({ "food": 2, "wood": 3, "stone": 5, "metal": 9 }) }); // Note: property order matters when using TS_ASSERT_UNEVAL_EQUALS, // because uneval preserves property order. So make sure this object // matches the ordering in GuiInterface. TS_ASSERT_UNEVAL_EQUALS(cmp.GetEntityState(-1, 10), { - id: 10, - template: "example", - alertRaiser: null, - builder: true, - canGarrison: false, - identity: { - rank: "foo", - classes: ["class1", "class2"], - visibleClasses: ["class3", "class4"], - selectionGroupName: "Selection Group Name", - canDelete: true + "id": 10, + "template": "example", + "alertRaiser": null, + "armour": null, + "attack": null, + "builder": true, + "buildingAI": null, + "buildRate": null, + "buildTime": null, + "canGarrison": false, + "deathDamage": null, + "heal": null, + "identity": { + "rank": "foo", + "classes": ["class1", "class2"], + "visibleClasses": ["class3", "class4"], + "selectionGroupName": "Selection Group Name", + "canDelete": true }, - fogging: null, - foundation: null, - garrisonHolder: null, - gate: null, - guard: null, - market: null, - mirage: null, - pack: null, - upgrade: null, - player: -1, - position: {x:1, y:2, z:3}, - production: null, - rallyPoint: null, - resourceCarrying: null, - rotation: {x:4, y:5, z:6}, - trader: null, - unitAI: null, - visibility: "visible", - hitpoints: 50, - maxHitpoints: 60, - needsRepair: false, - needsHeal: true -}); - -TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedEntityState(-1, 10), { - armour: null, - attack: null, - buildingAI: null, - deathDamage:null, - heal: null, - isBarterMarket: true, - loot: null, - obstruction: null, - turretParent: null, - promotion: null, - repairRate: null, - buildRate: null, - buildTime: null, - resourceDropsite: null, - resourceGatherRates: null, - resourceSupply: null, - resourceTrickle: { + "isBarterMarket": true, + "fogging": null, + "foundation": null, + "garrisonHolder": null, + "gate": null, + "guard": null, + "loot": null, + "market": null, + "mirage": null, + "pack": null, + "promotion": null, + "upgrade" : null, + "player": -1, + "position": {x:1, y:2, z:3}, + "production": null, + "rallyPoint": null, + "repairRate": null, + "resourceCarrying": null, + "resourceDropsite": null, + "resourceGatherRates": null, + "resourceSupply": null, + "resourceTrickle": { "interval": 1250, - "rates": { - "food": 2, - "wood": 3, - "stone": 5, - "metal": 9 - } + "rates": { "food": 2, "wood": 3, "stone": 5, "metal": 9 } }, - speed: null + "rotation": {x:4, y:5, z:6}, + "speed": null, + "trader": null, + "turretParent":null, + "unitAI": null, + "visibility": "visible", + "hitpoints": 50, + "maxHitpoints": 60, + "needsRepair": false, + "needsHeal": true });