LCOV - code coverage report
Current view: top level - simulation/components - GuiInterface.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 246 875 28.1 %
Date: 2023-04-02 12:52:40 Functions: 5 84 6.0 %

          Line data    Source code
       1             : function GuiInterface() {}
       2             : 
       3           1 : GuiInterface.prototype.Schema =
       4             :         "<a:component type='system'/><empty/>";
       5             : 
       6           1 : GuiInterface.prototype.Serialize = function()
       7             : {
       8             :         // This component isn't network-synchronized for the biggest part,
       9             :         // so most of the attributes shouldn't be serialized.
      10             :         // Return an object with a small selection of deterministic data.
      11           0 :         return {
      12             :                 "timeNotifications": this.timeNotifications,
      13             :                 "timeNotificationID": this.timeNotificationID
      14             :         };
      15             : };
      16             : 
      17           1 : GuiInterface.prototype.Deserialize = function(data)
      18             : {
      19           0 :         this.Init();
      20           0 :         this.timeNotifications = data.timeNotifications;
      21           0 :         this.timeNotificationID = data.timeNotificationID;
      22             : };
      23             : 
      24           1 : GuiInterface.prototype.Init = function()
      25             : {
      26           1 :         this.placementEntity = undefined; // = undefined or [templateName, entityID]
      27           1 :         this.placementWallEntities = undefined;
      28           1 :         this.placementWallLastAngle = 0;
      29           1 :         this.notifications = [];
      30           1 :         this.renamedEntities = [];
      31           1 :         this.miragedEntities = [];
      32           1 :         this.timeNotificationID = 1;
      33           1 :         this.timeNotifications = [];
      34           1 :         this.entsRallyPointsDisplayed = [];
      35           1 :         this.entsWithAuraAndStatusBars = new Set();
      36           1 :         this.enabledVisualRangeOverlayTypes = {};
      37           1 :         this.templateModified = {};
      38           1 :         this.selectionDirty = {};
      39           1 :         this.obstructionSnap = new ObstructionSnap();
      40             : };
      41             : 
      42             : /*
      43             :  * All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
      44             :  * from GUI scripts, and executed here with arguments (player, arg).
      45             :  *
      46             :  * CAUTION: The input to the functions in this module is not network-synchronised, so it
      47             :  * mustn't affect the simulation state (i.e. the data that is serialised and can affect
      48             :  * the behaviour of the rest of the simulation) else it'll cause out-of-sync errors.
      49             :  */
      50             : 
      51             : /**
      52             :  * Returns global information about the current game state.
      53             :  * This is used by the GUI and also by AI scripts.
      54             :  */
      55           1 : GuiInterface.prototype.GetSimulationState = function()
      56             : {
      57           2 :         let ret = {
      58             :                 "players": []
      59             :         };
      60             : 
      61           2 :         let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
      62           2 :         let numPlayers = cmpPlayerManager.GetNumPlayers();
      63           2 :         for (let i = 0; i < numPlayers; ++i)
      64             :         {
      65           4 :                 const playerEnt = cmpPlayerManager.GetPlayerByID(i);
      66           4 :                 const cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
      67           4 :                 const cmpPlayerEntityLimits = Engine.QueryInterface(playerEnt, IID_EntityLimits);
      68           4 :                 const cmpIdentity = Engine.QueryInterface(playerEnt, IID_Identity);
      69             : 
      70             :                 // Work out which phase we are in.
      71           4 :                 let phase = "";
      72           4 :                 const cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
      73           4 :                 if (cmpTechnologyManager)
      74             :                 {
      75           4 :                         if (cmpTechnologyManager.IsTechnologyResearched("phase_city"))
      76           0 :                                 phase = "city";
      77           4 :                         else if (cmpTechnologyManager.IsTechnologyResearched("phase_town"))
      78           0 :                                 phase = "town";
      79           4 :                         else if (cmpTechnologyManager.IsTechnologyResearched("phase_village"))
      80           4 :                                 phase = "village";
      81             :                 }
      82             : 
      83           4 :                 let allies = [];
      84           4 :                 let mutualAllies = [];
      85           4 :                 let neutrals = [];
      86           4 :                 let enemies = [];
      87             : 
      88           4 :                 for (let j = 0; j < numPlayers; ++j)
      89             :                 {
      90           8 :                         allies[j] = cmpPlayer.IsAlly(j);
      91           8 :                         mutualAllies[j] = cmpPlayer.IsMutualAlly(j);
      92           8 :                         neutrals[j] = cmpPlayer.IsNeutral(j);
      93           8 :                         enemies[j] = cmpPlayer.IsEnemy(j);
      94             :                 }
      95             : 
      96           4 :                 ret.players.push({
      97             :                         "name": cmpIdentity.GetName(),
      98             :                         "civ": cmpIdentity.GetCiv(),
      99             :                         "color": cmpPlayer.GetColor(),
     100             :                         "entity": cmpPlayer.entity,
     101             :                         "controlsAll": cmpPlayer.CanControlAllUnits(),
     102             :                         "popCount": cmpPlayer.GetPopulationCount(),
     103             :                         "popLimit": cmpPlayer.GetPopulationLimit(),
     104             :                         "popMax": cmpPlayer.GetMaxPopulation(),
     105             :                         "panelEntities": cmpPlayer.GetPanelEntities(),
     106             :                         "resourceCounts": cmpPlayer.GetResourceCounts(),
     107             :                         "resourceGatherers": cmpPlayer.GetResourceGatherers(),
     108             :                         "trainingBlocked": cmpPlayer.IsTrainingBlocked(),
     109             :                         "state": cmpPlayer.GetState(),
     110             :                         "team": cmpPlayer.GetTeam(),
     111             :                         "teamsLocked": cmpPlayer.GetLockTeams(),
     112             :                         "cheatsEnabled": cmpPlayer.GetCheatsEnabled(),
     113             :                         "disabledTemplates": cmpPlayer.GetDisabledTemplates(),
     114             :                         "disabledTechnologies": cmpPlayer.GetDisabledTechnologies(),
     115             :                         "hasSharedDropsites": cmpPlayer.HasSharedDropsites(),
     116             :                         "hasSharedLos": cmpPlayer.HasSharedLos(),
     117             :                         "spyCostMultiplier": cmpPlayer.GetSpyCostMultiplier(),
     118             :                         "phase": phase,
     119             :                         "isAlly": allies,
     120             :                         "isMutualAlly": mutualAllies,
     121             :                         "isNeutral": neutrals,
     122             :                         "isEnemy": enemies,
     123             :                         "entityLimits": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetLimits() : null,
     124             :                         "entityCounts": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetCounts() : null,
     125             :                         "matchEntityCounts": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetMatchCounts() : null,
     126             :                         "entityLimitChangers": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetLimitChangers() : null,
     127             :                         "researchQueued": cmpTechnologyManager ? cmpTechnologyManager.GetQueuedResearch() : null,
     128             :                         "researchedTechs": cmpTechnologyManager ? cmpTechnologyManager.GetResearchedTechs() : null,
     129             :                         "classCounts": cmpTechnologyManager ? cmpTechnologyManager.GetClassCounts() : null,
     130             :                         "typeCountsByClass": cmpTechnologyManager ? cmpTechnologyManager.GetTypeCountsByClass() : null,
     131             :                         "canBarter": cmpPlayer.CanBarter(),
     132             :                         "barterPrices": Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter).GetPrices(cmpPlayer)
     133             :                 });
     134             :         }
     135             : 
     136           2 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     137           2 :         if (cmpRangeManager)
     138           2 :                 ret.circularMap = cmpRangeManager.GetLosCircular();
     139             : 
     140           2 :         let cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
     141           2 :         if (cmpTerrain)
     142           0 :                 ret.mapSize = cmpTerrain.GetMapSize();
     143             : 
     144           2 :         let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     145           2 :         ret.timeElapsed = cmpTimer.GetTime();
     146             : 
     147           2 :         let cmpCeasefireManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager);
     148           2 :         if (cmpCeasefireManager)
     149             :         {
     150           0 :                 ret.ceasefireActive = cmpCeasefireManager.IsCeasefireActive();
     151           0 :                 ret.ceasefireTimeRemaining = ret.ceasefireActive ? cmpCeasefireManager.GetCeasefireStartedTime() + cmpCeasefireManager.GetCeasefireTime() - ret.timeElapsed : 0;
     152             :         }
     153             : 
     154           2 :         let cmpCinemaManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CinemaManager);
     155           2 :         if (cmpCinemaManager)
     156           0 :                 ret.cinemaPlaying = cmpCinemaManager.IsPlaying();
     157             : 
     158           2 :         let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
     159           2 :         ret.victoryConditions = cmpEndGameManager.GetVictoryConditions();
     160           2 :         ret.alliedVictory = cmpEndGameManager.GetAlliedVictory();
     161             : 
     162           2 :         ret.maxWorldPopulation = cmpPlayerManager.GetMaxWorldPopulation();
     163             : 
     164           2 :         for (let i = 0; i < numPlayers; ++i)
     165             :         {
     166           4 :                 let cmpPlayerStatisticsTracker = QueryPlayerIDInterface(i, IID_StatisticsTracker);
     167           4 :                 if (cmpPlayerStatisticsTracker)
     168           4 :                         ret.players[i].statistics = cmpPlayerStatisticsTracker.GetBasicStatistics();
     169             :         }
     170             : 
     171           2 :         return ret;
     172             : };
     173             : 
     174             : /**
     175             :  * Returns global information about the current game state, plus statistics.
     176             :  * This is used by the GUI at the end of a game, in the summary screen.
     177             :  * Note: Amongst statistics, the team exploration map percentage is computed from
     178             :  * scratch, so the extended simulation state should not be requested too often.
     179             :  */
     180           1 : GuiInterface.prototype.GetExtendedSimulationState = function()
     181             : {
     182           1 :         let ret = this.GetSimulationState();
     183             : 
     184           1 :         let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
     185           1 :         for (let i = 0; i < numPlayers; ++i)
     186             :         {
     187           2 :                 let cmpPlayerStatisticsTracker = QueryPlayerIDInterface(i, IID_StatisticsTracker);
     188           2 :                 if (cmpPlayerStatisticsTracker)
     189           2 :                         ret.players[i].sequences = cmpPlayerStatisticsTracker.GetSequences();
     190             :         }
     191             : 
     192           1 :         return ret;
     193             : };
     194             : 
     195             : /**
     196             :  * Returns the gamesettings that were chosen at the time the match started.
     197             :  */
     198           1 : GuiInterface.prototype.GetInitAttributes = function()
     199             : {
     200           0 :         return InitAttributes;
     201             : };
     202             : 
     203             : /**
     204             :  * This data will be stored in the replay metadata file after a match has been finished recording.
     205             :  */
     206           1 : GuiInterface.prototype.GetReplayMetadata = function()
     207             : {
     208           0 :         let extendedSimState = this.GetExtendedSimulationState();
     209           0 :         return {
     210             :                 "timeElapsed": extendedSimState.timeElapsed,
     211             :                 "playerStates": extendedSimState.players,
     212             :                 "mapSettings": InitAttributes.settings
     213             :         };
     214             : };
     215             : 
     216             : /**
     217             :  * Called when the game ends if the current game is part of a campaign run.
     218             :  */
     219           1 : GuiInterface.prototype.GetCampaignGameEndData = function(player)
     220             : {
     221           0 :         let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     222           0 :         if (Trigger.prototype.OnCampaignGameEnd)
     223           0 :                 return Trigger.prototype.OnCampaignGameEnd();
     224           0 :         return {};
     225             : };
     226             : 
     227           1 : GuiInterface.prototype.GetRenamedEntities = function(player)
     228             : {
     229           0 :         if (this.miragedEntities[player])
     230           0 :                 return this.renamedEntities.concat(this.miragedEntities[player]);
     231             : 
     232           0 :         return this.renamedEntities;
     233             : };
     234             : 
     235           1 : GuiInterface.prototype.ClearRenamedEntities = function()
     236             : {
     237           0 :         this.renamedEntities = [];
     238           0 :         this.miragedEntities = [];
     239             : };
     240             : 
     241           1 : GuiInterface.prototype.AddMiragedEntity = function(player, entity, mirage)
     242             : {
     243           0 :         if (!this.miragedEntities[player])
     244           0 :                 this.miragedEntities[player] = [];
     245             : 
     246           0 :         this.miragedEntities[player].push({ "entity": entity, "newentity": mirage });
     247             : };
     248             : 
     249             : /**
     250             :  * Get common entity info, often used in the gui.
     251             :  */
     252           1 : GuiInterface.prototype.GetEntityState = function(player, ent)
     253             : {
     254           1 :         if (!ent)
     255           0 :                 return null;
     256             : 
     257             :         // All units must have a template; if not then it's a nonexistent entity id.
     258           1 :         const template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetCurrentTemplateName(ent);
     259           1 :         if (!template)
     260           0 :                 return null;
     261             : 
     262           1 :         const ret = {
     263             :                 "id": ent,
     264             :                 "player": INVALID_PLAYER,
     265             :                 "template": template
     266             :         };
     267             : 
     268           1 :         const cmpAuras = Engine.QueryInterface(ent, IID_Auras);
     269           1 :         if (cmpAuras)
     270           0 :                 ret.auras = cmpAuras.GetDescriptions();
     271             : 
     272           1 :         let cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
     273           1 :         if (cmpMirage)
     274           0 :                 ret.mirage = true;
     275             : 
     276           1 :         let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
     277           1 :         if (cmpIdentity)
     278           1 :                 ret.identity = {
     279             :                         "rank": cmpIdentity.GetRank(),
     280             :                         "rankTechName": cmpIdentity.GetRankTechName(),
     281             :                         "classes": cmpIdentity.GetClassesList(),
     282             :                         "selectionGroupName": cmpIdentity.GetSelectionGroupName(),
     283             :                         "canDelete": !cmpIdentity.IsUndeletable(),
     284             :                         "controllable": cmpIdentity.IsControllable()
     285             :                 };
     286             : 
     287           1 :         const cmpFormation = Engine.QueryInterface(ent, IID_Formation);
     288           1 :         if (cmpFormation)
     289           0 :                 ret.formation = {
     290             :                         "members": cmpFormation.GetMembers()
     291             :                 };
     292             : 
     293           1 :         let cmpPosition = Engine.QueryInterface(ent, IID_Position);
     294           1 :         if (cmpPosition && cmpPosition.IsInWorld())
     295           1 :                 ret.position = cmpPosition.GetPosition();
     296             : 
     297           1 :         let cmpHealth = QueryMiragedInterface(ent, IID_Health);
     298           1 :         if (cmpHealth)
     299             :         {
     300           1 :                 ret.hitpoints = cmpHealth.GetHitpoints();
     301           1 :                 ret.maxHitpoints = cmpHealth.GetMaxHitpoints();
     302           1 :                 ret.needsRepair = cmpHealth.IsRepairable() && cmpHealth.IsInjured();
     303           1 :                 ret.needsHeal = !cmpHealth.IsUnhealable();
     304             :         }
     305             : 
     306           1 :         let cmpCapturable = QueryMiragedInterface(ent, IID_Capturable);
     307           1 :         if (cmpCapturable)
     308             :         {
     309           0 :                 ret.capturePoints = cmpCapturable.GetCapturePoints();
     310           0 :                 ret.maxCapturePoints = cmpCapturable.GetMaxCapturePoints();
     311             :         }
     312             : 
     313           1 :         let cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
     314           1 :         if (cmpBuilder)
     315           1 :                 ret.builder = true;
     316             : 
     317           1 :         let cmpMarket = QueryMiragedInterface(ent, IID_Market);
     318           1 :         if (cmpMarket)
     319           0 :                 ret.market = {
     320             :                         "land": cmpMarket.HasType("land"),
     321             :                         "naval": cmpMarket.HasType("naval")
     322             :                 };
     323             : 
     324           1 :         let cmpPack = Engine.QueryInterface(ent, IID_Pack);
     325           1 :         if (cmpPack)
     326           0 :                 ret.pack = {
     327             :                         "packed": cmpPack.IsPacked(),
     328             :                         "progress": cmpPack.GetProgress()
     329             :                 };
     330             : 
     331           1 :         let cmpPopulation = Engine.QueryInterface(ent, IID_Population);
     332           1 :         if (cmpPopulation)
     333           0 :                 ret.population = {
     334             :                         "bonus": cmpPopulation.GetPopBonus()
     335             :                 };
     336             : 
     337           1 :         let cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
     338           1 :         if (cmpUpgrade)
     339           0 :                 ret.upgrade = {
     340             :                         "upgrades": cmpUpgrade.GetUpgrades(),
     341             :                         "progress": cmpUpgrade.GetProgress(),
     342             :                         "template": cmpUpgrade.GetUpgradingTo(),
     343             :                         "isUpgrading": cmpUpgrade.IsUpgrading()
     344             :                 };
     345             : 
     346           1 :         const cmpResearcher = Engine.QueryInterface(ent, IID_Researcher);
     347           1 :         if (cmpResearcher)
     348           0 :                 ret.researcher = {
     349             :                         "technologies": cmpResearcher.GetTechnologiesList(),
     350             :                         "techCostMultiplier": cmpResearcher.GetTechCostMultiplier()
     351             :                 };
     352             : 
     353           1 :         let cmpStatusEffects = Engine.QueryInterface(ent, IID_StatusEffectsReceiver);
     354           1 :         if (cmpStatusEffects)
     355           0 :                 ret.statusEffects = cmpStatusEffects.GetActiveStatuses();
     356             : 
     357           1 :         let cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
     358           1 :         if (cmpProductionQueue)
     359           0 :                 ret.production = {
     360             :                         "queue": cmpProductionQueue.GetQueue(),
     361             :                         "autoqueue": cmpProductionQueue.IsAutoQueueing()
     362             :                 };
     363             : 
     364           1 :         const cmpTrainer = Engine.QueryInterface(ent, IID_Trainer);
     365           1 :         if (cmpTrainer)
     366           0 :                 ret.trainer = {
     367             :                         "entities": cmpTrainer.GetEntitiesList()
     368             :                 };
     369             : 
     370           1 :         let cmpTrader = Engine.QueryInterface(ent, IID_Trader);
     371           1 :         if (cmpTrader)
     372           0 :                 ret.trader = {
     373             :                         "goods": cmpTrader.GetGoods()
     374             :                 };
     375             : 
     376           1 :         let cmpFoundation = QueryMiragedInterface(ent, IID_Foundation);
     377           1 :         if (cmpFoundation)
     378           0 :                 ret.foundation = {
     379             :                         "numBuilders": cmpFoundation.GetNumBuilders(),
     380             :                         "buildTime": cmpFoundation.GetBuildTime()
     381             :                 };
     382             : 
     383           1 :         let cmpRepairable = QueryMiragedInterface(ent, IID_Repairable);
     384           1 :         if (cmpRepairable)
     385           0 :                 ret.repairable = {
     386             :                         "numBuilders": cmpRepairable.GetNumBuilders(),
     387             :                         "buildTime": cmpRepairable.GetBuildTime()
     388             :                 };
     389             : 
     390           1 :         let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
     391           1 :         if (cmpOwnership)
     392           0 :                 ret.player = cmpOwnership.GetOwner();
     393             : 
     394           1 :         let cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
     395           1 :         if (cmpRallyPoint)
     396           0 :                 ret.rallyPoint = { "position": cmpRallyPoint.GetPositions()[0] }; // undefined or {x,z} object
     397             : 
     398           1 :         let cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
     399           1 :         if (cmpGarrisonHolder)
     400           0 :                 ret.garrisonHolder = {
     401             :                         "entities": cmpGarrisonHolder.GetEntities(),
     402             :                         "buffHeal": cmpGarrisonHolder.GetHealRate(),
     403             :                         "allowedClasses": cmpGarrisonHolder.GetAllowedClasses(),
     404             :                         "capacity": cmpGarrisonHolder.GetCapacity(),
     405             :                         "occupiedSlots": cmpGarrisonHolder.OccupiedSlots()
     406             :                 };
     407             : 
     408           1 :         let cmpTurretHolder = Engine.QueryInterface(ent, IID_TurretHolder);
     409           1 :         if (cmpTurretHolder)
     410           0 :                 ret.turretHolder = {
     411             :                         "turretPoints": cmpTurretHolder.GetTurretPoints()
     412             :                 };
     413             : 
     414           1 :         let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable);
     415           1 :         if (cmpTurretable)
     416           0 :                 ret.turretable = {
     417             :                         "ejectable": cmpTurretable.IsEjectable(),
     418             :                         "holder": cmpTurretable.HolderID()
     419             :                 };
     420             : 
     421           1 :         let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
     422           1 :         if (cmpGarrisonable)
     423           0 :                 ret.garrisonable = {
     424             :                         "holder": cmpGarrisonable.HolderID(),
     425             :                         "size": cmpGarrisonable.UnitSize()
     426             :                 };
     427             : 
     428           1 :         let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     429           1 :         if (cmpUnitAI)
     430           0 :                 ret.unitAI = {
     431             :                         "state": cmpUnitAI.GetCurrentState(),
     432             :                         "orders": cmpUnitAI.GetOrders(),
     433             :                         "hasWorkOrders": cmpUnitAI.HasWorkOrders(),
     434             :                         "canGuard": cmpUnitAI.CanGuard(),
     435             :                         "isGuarding": cmpUnitAI.IsGuardOf(),
     436             :                         "canPatrol": cmpUnitAI.CanPatrol(),
     437             :                         "selectableStances": cmpUnitAI.GetSelectableStances(),
     438             :                         "isIdle": cmpUnitAI.IsIdle(),
     439             :                         "formations": cmpUnitAI.GetFormationsList(),
     440             :                         "formation": cmpUnitAI.GetFormationController()
     441             :                 };
     442             : 
     443           1 :         let cmpGuard = Engine.QueryInterface(ent, IID_Guard);
     444           1 :         if (cmpGuard)
     445           0 :                 ret.guard = {
     446             :                         "entities": cmpGuard.GetEntities()
     447             :                 };
     448             : 
     449           1 :         let cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
     450           1 :         if (cmpResourceGatherer)
     451             :         {
     452           0 :                 ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
     453           0 :                 ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
     454             :         }
     455             : 
     456           1 :         let cmpGate = Engine.QueryInterface(ent, IID_Gate);
     457           1 :         if (cmpGate)
     458           0 :                 ret.gate = {
     459             :                         "locked": cmpGate.IsLocked()
     460             :                 };
     461             : 
     462           1 :         let cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser);
     463           1 :         if (cmpAlertRaiser)
     464           0 :                 ret.alertRaiser = true;
     465             : 
     466           1 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     467           1 :         ret.visibility = cmpRangeManager.GetLosVisibility(ent, player);
     468             : 
     469           1 :         let cmpAttack = Engine.QueryInterface(ent, IID_Attack);
     470           1 :         if (cmpAttack)
     471             :         {
     472           0 :                 let types = cmpAttack.GetAttackTypes();
     473           0 :                 if (types.length)
     474           0 :                         ret.attack = {};
     475             : 
     476           0 :                 for (let type of types)
     477             :                 {
     478           0 :                         ret.attack[type] = {};
     479             : 
     480           0 :                         Object.assign(ret.attack[type], cmpAttack.GetAttackEffectsData(type));
     481             : 
     482           0 :                         ret.attack[type].attackName = cmpAttack.GetAttackName(type);
     483             : 
     484           0 :                         ret.attack[type].splash = cmpAttack.GetSplashData(type);
     485           0 :                         if (ret.attack[type].splash)
     486           0 :                                 Object.assign(ret.attack[type].splash, cmpAttack.GetAttackEffectsData(type, true));
     487             : 
     488           0 :                         let range = cmpAttack.GetRange(type);
     489           0 :                         ret.attack[type].minRange = range.min;
     490           0 :                         ret.attack[type].maxRange = range.max;
     491           0 :                         ret.attack[type].yOrigin = cmpAttack.GetAttackYOrigin(type);
     492             : 
     493           0 :                         let timers = cmpAttack.GetTimers(type);
     494           0 :                         ret.attack[type].prepareTime = timers.prepare;
     495           0 :                         ret.attack[type].repeatTime = timers.repeat;
     496             : 
     497           0 :                         if (type != "Ranged")
     498             :                         {
     499           0 :                                 ret.attack[type].elevationAdaptedRange = ret.attack.maxRange;
     500           0 :                                 continue;
     501             :                         }
     502             : 
     503           0 :                         if (cmpPosition && cmpPosition.IsInWorld())
     504             :                                 // For units, take the range in front of it, no spread, so angle = 0,
     505             :                                 // else, take the average elevation around it: angle = 2 * pi.
     506           0 :                                 ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, ret.attack[type].yOrigin, cmpUnitAI ? 0 : 2 * Math.PI);
     507             :                         else
     508             :                                 // Not in world, set a default?
     509           0 :                                 ret.attack[type].elevationAdaptedRange = ret.attack.maxRange;
     510             :                 }
     511             :         }
     512             : 
     513           1 :         let cmpResistance = QueryMiragedInterface(ent, IID_Resistance);
     514           1 :         if (cmpResistance)
     515           0 :                 ret.resistance = cmpResistance.GetResistanceOfForm(cmpFoundation ? "Foundation" : "Entity");
     516             : 
     517           1 :         let cmpBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI);
     518           1 :         if (cmpBuildingAI)
     519           0 :                 ret.buildingAI = {
     520             :                         "defaultArrowCount": cmpBuildingAI.GetDefaultArrowCount(),
     521             :                         "maxArrowCount": cmpBuildingAI.GetMaxArrowCount(),
     522             :                         "garrisonArrowMultiplier": cmpBuildingAI.GetGarrisonArrowMultiplier(),
     523             :                         "garrisonArrowClasses": cmpBuildingAI.GetGarrisonArrowClasses(),
     524             :                         "arrowCount": cmpBuildingAI.GetArrowCount()
     525             :                 };
     526             : 
     527           1 :         if (cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY)
     528           0 :                 ret.turretParent = cmpPosition.GetTurretParent();
     529             : 
     530           1 :         let cmpResourceSupply = QueryMiragedInterface(ent, IID_ResourceSupply);
     531           1 :         if (cmpResourceSupply)
     532           0 :                 ret.resourceSupply = {
     533             :                         "isInfinite": cmpResourceSupply.IsInfinite(),
     534             :                         "max": cmpResourceSupply.GetMaxAmount(),
     535             :                         "amount": cmpResourceSupply.GetCurrentAmount(),
     536             :                         "type": cmpResourceSupply.GetType(),
     537             :                         "killBeforeGather": cmpResourceSupply.GetKillBeforeGather(),
     538             :                         "maxGatherers": cmpResourceSupply.GetMaxGatherers(),
     539             :                         "numGatherers": cmpResourceSupply.GetNumGatherers()
     540             :                 };
     541             : 
     542           1 :         let cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
     543           1 :         if (cmpResourceDropsite)
     544           0 :                 ret.resourceDropsite = {
     545             :                         "types": cmpResourceDropsite.GetTypes(),
     546             :                         "sharable": cmpResourceDropsite.IsSharable(),
     547             :                         "shared": cmpResourceDropsite.IsShared()
     548             :                 };
     549             : 
     550           1 :         let cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
     551           1 :         if (cmpPromotion)
     552           0 :                 ret.promotion = {
     553             :                         "curr": cmpPromotion.GetCurrentXp(),
     554             :                         "req": cmpPromotion.GetRequiredXp()
     555             :                 };
     556             : 
     557           1 :         if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("Barter"))
     558           1 :                 ret.isBarterMarket = true;
     559             : 
     560           1 :         let cmpHeal = Engine.QueryInterface(ent, IID_Heal);
     561           1 :         if (cmpHeal)
     562           0 :                 ret.heal = {
     563             :                         "health": cmpHeal.GetHealth(),
     564             :                         "range": cmpHeal.GetRange().max,
     565             :                         "interval": cmpHeal.GetInterval(),
     566             :                         "unhealableClasses": cmpHeal.GetUnhealableClasses(),
     567             :                         "healableClasses": cmpHeal.GetHealableClasses()
     568             :                 };
     569             : 
     570           1 :         let cmpLoot = Engine.QueryInterface(ent, IID_Loot);
     571           1 :         if (cmpLoot)
     572             :         {
     573           0 :                 ret.loot = cmpLoot.GetResources();
     574           0 :                 ret.loot.xp = cmpLoot.GetXp();
     575             :         }
     576             : 
     577           1 :         let cmpResourceTrickle = Engine.QueryInterface(ent, IID_ResourceTrickle);
     578           1 :         if (cmpResourceTrickle)
     579           1 :                 ret.resourceTrickle = {
     580             :                         "interval": cmpResourceTrickle.GetInterval(),
     581             :                         "rates": cmpResourceTrickle.GetRates()
     582             :                 };
     583             : 
     584           1 :         let cmpTreasure = Engine.QueryInterface(ent, IID_Treasure);
     585           1 :         if (cmpTreasure)
     586           0 :                 ret.treasure = {
     587             :                         "collectTime": cmpTreasure.CollectionTime(),
     588             :                         "resources": cmpTreasure.Resources()
     589             :                 };
     590             : 
     591           1 :         let cmpTreasureCollector = Engine.QueryInterface(ent, IID_TreasureCollector);
     592           1 :         if (cmpTreasureCollector)
     593           0 :                 ret.treasureCollector = true;
     594             : 
     595           1 :         let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
     596           1 :         if (cmpUnitMotion)
     597           0 :                 ret.speed = {
     598             :                         "walk": cmpUnitMotion.GetWalkSpeed(),
     599             :                         "run": cmpUnitMotion.GetWalkSpeed() * cmpUnitMotion.GetRunMultiplier(),
     600             :                         "acceleration": cmpUnitMotion.GetAcceleration()
     601             :                 };
     602             : 
     603           1 :         let cmpUpkeep = Engine.QueryInterface(ent, IID_Upkeep);
     604           1 :         if (cmpUpkeep)
     605           0 :                 ret.upkeep = {
     606             :                         "interval": cmpUpkeep.GetInterval(),
     607             :                         "rates": cmpUpkeep.GetRates()
     608             :                 };
     609             : 
     610           1 :         return ret;
     611             : };
     612             : 
     613           1 : GuiInterface.prototype.GetMultipleEntityStates = function(player, ents)
     614             : {
     615           0 :         return ents.map(ent => ({ "entId": ent, "state": this.GetEntityState(player, ent) }));
     616             : };
     617             : 
     618           1 : GuiInterface.prototype.GetAverageRangeForBuildings = function(player, cmd)
     619             : {
     620           0 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     621           0 :         let cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
     622             : 
     623           0 :         let rot = { "x": 0, "y": 0, "z": 0 };
     624           0 :         let pos = {
     625             :                 "x": cmd.x,
     626             :                 "y": cmpTerrain.GetGroundLevel(cmd.x, cmd.z),
     627             :                 "z": cmd.z
     628             :         };
     629             : 
     630           0 :         const yOrigin = cmd.yOrigin || 0;
     631           0 :         let range = cmd.range;
     632             : 
     633           0 :         return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, yOrigin, 2 * Math.PI);
     634             : };
     635             : 
     636           1 : GuiInterface.prototype.GetTemplateData = function(player, data)
     637             : {
     638           0 :         let templateName = data.templateName;
     639           0 :         let owner = data.player !== undefined ? data.player : player;
     640           0 :         let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     641           0 :         let template = cmpTemplateManager.GetTemplate(templateName);
     642             : 
     643           0 :         if (!template)
     644           0 :                 return null;
     645             : 
     646           0 :         let aurasTemplate = {};
     647             : 
     648           0 :         if (!template.Auras)
     649           0 :                 return GetTemplateDataHelper(template, owner, aurasTemplate, Resources);
     650             : 
     651           0 :         let auraNames = template.Auras._string.split(/\s+/);
     652             : 
     653           0 :         for (let name of auraNames)
     654             :         {
     655           0 :                 let auraTemplate = AuraTemplates.Get(name);
     656           0 :                 if (!auraTemplate)
     657           0 :                         error("Template " + templateName + " has undefined aura " + name);
     658             :                 else
     659           0 :                         aurasTemplate[name] = auraTemplate;
     660             :         }
     661             : 
     662           0 :         return GetTemplateDataHelper(template, owner, aurasTemplate, Resources);
     663             : };
     664             : 
     665           1 : GuiInterface.prototype.AreRequirementsMet = function(player, data)
     666             : {
     667           0 :         return !data.requirements || RequirementsHelper.AreRequirementsMet(data.requirements,
     668             :                 data.player !== undefined ? data.player : player);
     669             : };
     670             : 
     671             : /**
     672             :  * Checks whether the requirements for this technology have been met.
     673             :  */
     674           1 : GuiInterface.prototype.CheckTechnologyRequirements = function(player, data)
     675             : {
     676           0 :         let cmpTechnologyManager = QueryPlayerIDInterface(data.player !== undefined ? data.player : player, IID_TechnologyManager);
     677           0 :         if (!cmpTechnologyManager)
     678           0 :                 return false;
     679             : 
     680           0 :         return cmpTechnologyManager.CanResearch(data.tech);
     681             : };
     682             : 
     683             : /**
     684             :  * Returns technologies that are being actively researched, along with
     685             :  * which entity is researching them and how far along the research is.
     686             :  */
     687           1 : GuiInterface.prototype.GetStartedResearch = function(player)
     688             : {
     689           0 :         return QueryPlayerIDInterface(player, IID_TechnologyManager)?.GetBasicInfoOfStartedTechs() || {};
     690             : };
     691             : 
     692             : /**
     693             :  * Returns the battle state of the player.
     694             :  */
     695           1 : GuiInterface.prototype.GetBattleState = function(player)
     696             : {
     697           0 :         let cmpBattleDetection = QueryPlayerIDInterface(player, IID_BattleDetection);
     698           0 :         if (!cmpBattleDetection)
     699           0 :                 return false;
     700             : 
     701           0 :         return cmpBattleDetection.GetState();
     702             : };
     703             : 
     704             : /**
     705             :  * Returns a list of ongoing attacks against the player.
     706             :  */
     707           1 : GuiInterface.prototype.GetIncomingAttacks = function(player)
     708             : {
     709           0 :         let cmpAttackDetection = QueryPlayerIDInterface(player, IID_AttackDetection);
     710           0 :         if (!cmpAttackDetection)
     711           0 :                 return [];
     712             : 
     713           0 :         return cmpAttackDetection.GetIncomingAttacks();
     714             : };
     715             : 
     716             : /**
     717             :  * Used to show a red square over GUI elements you can't yet afford.
     718             :  */
     719           1 : GuiInterface.prototype.GetNeededResources = function(player, data)
     720             : {
     721           0 :         let cmpPlayer = QueryPlayerIDInterface(data.player !== undefined ? data.player : player);
     722           0 :         return cmpPlayer ? cmpPlayer.GetNeededResources(data.cost) : {};
     723             : };
     724             : 
     725             : /**
     726             :  * State of the templateData (player dependent): true when some template values have been modified
     727             :  * and need to be reloaded by the gui.
     728             :  */
     729           1 : GuiInterface.prototype.OnTemplateModification = function(msg)
     730             : {
     731           0 :         this.templateModified[msg.player] = true;
     732           0 :         this.selectionDirty[msg.player] = true;
     733             : };
     734             : 
     735           1 : GuiInterface.prototype.IsTemplateModified = function(player)
     736             : {
     737           0 :         return this.templateModified[player] || false;
     738             : };
     739             : 
     740           1 : GuiInterface.prototype.ResetTemplateModified = function()
     741             : {
     742           0 :         this.templateModified = {};
     743             : };
     744             : 
     745             : /**
     746             :  * Some changes may require an update to the selection panel,
     747             :  * which is cached for efficiency. Inform the GUI it needs reloading.
     748             :  */
     749           1 : GuiInterface.prototype.OnDisabledTemplatesChanged = function(msg)
     750             : {
     751           0 :         this.selectionDirty[msg.player] = true;
     752             : };
     753             : 
     754           1 : GuiInterface.prototype.OnDisabledTechnologiesChanged = function(msg)
     755             : {
     756           0 :         this.selectionDirty[msg.player] = true;
     757             : };
     758             : 
     759           1 : GuiInterface.prototype.SetSelectionDirty = function(player)
     760             : {
     761           0 :         this.selectionDirty[player] = true;
     762             : };
     763             : 
     764           1 : GuiInterface.prototype.IsSelectionDirty = function(player)
     765             : {
     766           0 :         return this.selectionDirty[player] || false;
     767             : };
     768             : 
     769           1 : GuiInterface.prototype.ResetSelectionDirty = function()
     770             : {
     771           0 :         this.selectionDirty = {};
     772             : };
     773             : 
     774             : /**
     775             :  * Add a timed notification.
     776             :  * Warning: timed notifacations are serialised
     777             :  * (to also display them on saved games or after a rejoin)
     778             :  * so they should allways be added and deleted in a deterministic way.
     779             :  */
     780           1 : GuiInterface.prototype.AddTimeNotification = function(notification, duration = 10000)
     781             : {
     782           0 :         let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     783           0 :         notification.endTime = duration + cmpTimer.GetTime();
     784           0 :         notification.id = ++this.timeNotificationID;
     785             : 
     786             :         // Let all players and observers receive the notification by default.
     787           0 :         if (!notification.players)
     788             :         {
     789           0 :                 notification.players = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayers();
     790           0 :                 notification.players[0] = -1;
     791             :         }
     792             : 
     793           0 :         this.timeNotifications.push(notification);
     794           0 :         this.timeNotifications.sort((n1, n2) => n2.endTime - n1.endTime);
     795             : 
     796           0 :         cmpTimer.SetTimeout(this.entity, IID_GuiInterface, "DeleteTimeNotification", duration, this.timeNotificationID);
     797             : 
     798           0 :         return this.timeNotificationID;
     799             : };
     800             : 
     801           1 : GuiInterface.prototype.DeleteTimeNotification = function(notificationID)
     802             : {
     803           0 :         this.timeNotifications = this.timeNotifications.filter(n => n.id != notificationID);
     804             : };
     805             : 
     806           1 : GuiInterface.prototype.GetTimeNotifications = function(player)
     807             : {
     808           0 :         let time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime();
     809             :         // Filter on players and time, since the delete timer might be executed with a delay.
     810           0 :         return this.timeNotifications.filter(n => n.players.indexOf(player) != -1 && n.endTime > time);
     811             : };
     812             : 
     813           1 : GuiInterface.prototype.PushNotification = function(notification)
     814             : {
     815           0 :         if (!notification.type || notification.type == "text")
     816           0 :                 this.AddTimeNotification(notification);
     817             :         else
     818           0 :                 this.notifications.push(notification);
     819             : };
     820             : 
     821           1 : GuiInterface.prototype.GetNotifications = function()
     822             : {
     823           0 :         let n = this.notifications;
     824           0 :         this.notifications = [];
     825           0 :         return n;
     826             : };
     827             : 
     828           1 : GuiInterface.prototype.GetAvailableFormations = function(player, wantedPlayer)
     829             : {
     830           0 :         let cmpPlayer = QueryPlayerIDInterface(wantedPlayer);
     831           0 :         if (!cmpPlayer)
     832           0 :                 return [];
     833             : 
     834           0 :         return cmpPlayer.GetFormations();
     835             : };
     836             : 
     837           1 : GuiInterface.prototype.GetFormationRequirements = function(player, data)
     838             : {
     839           0 :         return GetFormationRequirements(data.formationTemplate);
     840             : };
     841             : 
     842           1 : GuiInterface.prototype.CanMoveEntsIntoFormation = function(player, data)
     843             : {
     844           0 :         return CanMoveEntsIntoFormation(data.ents, data.formationTemplate);
     845             : };
     846             : 
     847           1 : GuiInterface.prototype.GetFormationInfoFromTemplate = function(player, data)
     848             : {
     849           0 :         let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     850           0 :         let template = cmpTemplateManager.GetTemplate(data.templateName);
     851             : 
     852           0 :         if (!template || !template.Formation)
     853           0 :                 return {};
     854             : 
     855           0 :         return {
     856             :                 "name": template.Identity.GenericName,
     857             :                 "tooltip": template.Formation.DisabledTooltip || "",
     858             :                 "icon": template.Identity.Icon
     859             :         };
     860             : };
     861             : 
     862           1 : GuiInterface.prototype.IsFormationSelected = function(player, data)
     863             : {
     864           0 :         return data.ents.some(ent => {
     865           0 :                 let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     866           0 :                 return cmpUnitAI && cmpUnitAI.GetFormationTemplate() == data.formationTemplate;
     867             :         });
     868             : };
     869             : 
     870           1 : GuiInterface.prototype.IsStanceSelected = function(player, data)
     871             : {
     872           0 :         for (let ent of data.ents)
     873             :         {
     874           0 :                 let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     875           0 :                 if (cmpUnitAI && cmpUnitAI.GetStanceName() == data.stance)
     876           0 :                         return true;
     877             :         }
     878           0 :         return false;
     879             : };
     880             : 
     881           1 : GuiInterface.prototype.GetAllBuildableEntities = function(player, cmd)
     882             : {
     883           0 :         let buildableEnts = [];
     884           0 :         for (let ent of cmd.entities)
     885             :         {
     886           0 :                 let cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
     887           0 :                 if (!cmpBuilder)
     888           0 :                         continue;
     889             : 
     890           0 :                 for (let building of cmpBuilder.GetEntitiesList())
     891           0 :                         if (buildableEnts.indexOf(building) == -1)
     892           0 :                                 buildableEnts.push(building);
     893             :         }
     894           0 :         return buildableEnts;
     895             : };
     896             : 
     897           1 : GuiInterface.prototype.UpdateDisplayedPlayerColors = function(player, data)
     898             : {
     899           0 :         let updateEntityColor = (iids, entities) => {
     900           0 :                 for (let ent of entities)
     901           0 :                         for (let iid of iids)
     902             :                         {
     903           0 :                                 let cmp = Engine.QueryInterface(ent, iid);
     904           0 :                                 if (cmp)
     905           0 :                                         cmp.UpdateColor();
     906             :                         }
     907             :         };
     908             : 
     909           0 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     910           0 :         let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
     911           0 :         for (let i = 1; i < numPlayers; ++i)
     912             :         {
     913           0 :                 let cmpPlayer = QueryPlayerIDInterface(i, IID_Player);
     914           0 :                 if (!cmpPlayer)
     915           0 :                         continue;
     916             : 
     917           0 :                 cmpPlayer.SetDisplayDiplomacyColor(data.displayDiplomacyColors);
     918           0 :                 if (data.displayDiplomacyColors)
     919           0 :                         cmpPlayer.SetDiplomacyColor(data.displayedPlayerColors[i]);
     920             : 
     921           0 :                 updateEntityColor(data.showAllStatusBars && (i == player || player == -1) ?
     922             :                         [IID_Minimap, IID_RangeOverlayRenderer, IID_RallyPointRenderer, IID_StatusBars] :
     923             :                         [IID_Minimap, IID_RangeOverlayRenderer, IID_RallyPointRenderer],
     924             :                 cmpRangeManager.GetEntitiesByPlayer(i));
     925             :         }
     926           0 :         updateEntityColor([IID_Selectable, IID_StatusBars], data.selected);
     927           0 :         Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager).UpdateColors();
     928             : };
     929             : 
     930           1 : GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
     931             : {
     932             :         // Cache of owner -> color map
     933           0 :         let playerColors = {};
     934             : 
     935           0 :         for (let ent of cmd.entities)
     936             :         {
     937           0 :                 let cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
     938           0 :                 if (!cmpSelectable)
     939           0 :                         continue;
     940             : 
     941             :                 // Find the entity's owner's color.
     942           0 :                 let owner = INVALID_PLAYER;
     943           0 :                 let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
     944           0 :                 if (cmpOwnership)
     945           0 :                         owner = cmpOwnership.GetOwner();
     946             : 
     947           0 :                 let color = playerColors[owner];
     948           0 :                 if (!color)
     949             :                 {
     950           0 :                         color = { "r": 1, "g": 1, "b": 1 };
     951           0 :                         let cmpPlayer = QueryPlayerIDInterface(owner);
     952           0 :                         if (cmpPlayer)
     953           0 :                                 color = cmpPlayer.GetDisplayedColor();
     954           0 :                         playerColors[owner] = color;
     955             :                 }
     956             : 
     957           0 :                 cmpSelectable.SetSelectionHighlight({ "r": color.r, "g": color.g, "b": color.b, "a": cmd.alpha }, cmd.selected);
     958             : 
     959           0 :                 let cmpRangeOverlayManager = Engine.QueryInterface(ent, IID_RangeOverlayManager);
     960           0 :                 if (!cmpRangeOverlayManager || player != owner && player != INVALID_PLAYER)
     961           0 :                         continue;
     962             : 
     963           0 :                 cmpRangeOverlayManager.SetEnabled(cmd.selected, this.enabledVisualRangeOverlayTypes, false);
     964             :         }
     965             : };
     966             : 
     967           1 : GuiInterface.prototype.EnableVisualRangeOverlayType = function(player, data)
     968             : {
     969           0 :         this.enabledVisualRangeOverlayTypes[data.type] = data.enabled;
     970             : };
     971             : 
     972           1 : GuiInterface.prototype.GetEntitiesWithStatusBars = function()
     973             : {
     974           0 :         return Array.from(this.entsWithAuraAndStatusBars);
     975             : };
     976             : 
     977           1 : GuiInterface.prototype.SetStatusBars = function(player, cmd)
     978             : {
     979           0 :         let affectedEnts = new Set();
     980           0 :         for (let ent of cmd.entities)
     981             :         {
     982           0 :                 let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
     983           0 :                 if (!cmpStatusBars)
     984           0 :                         continue;
     985           0 :                 cmpStatusBars.SetEnabled(cmd.enabled, cmd.showRank, cmd.showExperience);
     986             : 
     987           0 :                 let cmpAuras = Engine.QueryInterface(ent, IID_Auras);
     988           0 :                 if (!cmpAuras)
     989           0 :                         continue;
     990             : 
     991           0 :                 for (let name of cmpAuras.GetAuraNames())
     992             :                 {
     993           0 :                         if (!cmpAuras.GetOverlayIcon(name))
     994           0 :                                 continue;
     995           0 :                         for (let e of cmpAuras.GetAffectedEntities(name))
     996           0 :                                 affectedEnts.add(e);
     997           0 :                         if (cmd.enabled)
     998           0 :                                 this.entsWithAuraAndStatusBars.add(ent);
     999             :                         else
    1000           0 :                                 this.entsWithAuraAndStatusBars.delete(ent);
    1001             :                 }
    1002             :         }
    1003             : 
    1004           0 :         for (let ent of affectedEnts)
    1005             :         {
    1006           0 :                 let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
    1007           0 :                 if (cmpStatusBars)
    1008           0 :                         cmpStatusBars.RegenerateSprites();
    1009             :         }
    1010             : };
    1011             : 
    1012           1 : GuiInterface.prototype.SetRangeOverlays = function(player, cmd)
    1013             : {
    1014           0 :         for (let ent of cmd.entities)
    1015             :         {
    1016           0 :                 let cmpRangeOverlayManager = Engine.QueryInterface(ent, IID_RangeOverlayManager);
    1017           0 :                 if (cmpRangeOverlayManager)
    1018           0 :                         cmpRangeOverlayManager.SetEnabled(cmd.enabled, this.enabledVisualRangeOverlayTypes, true);
    1019             :         }
    1020             : };
    1021             : 
    1022           1 : GuiInterface.prototype.GetPlayerEntities = function(player)
    1023             : {
    1024           0 :         return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetEntitiesByPlayer(player);
    1025             : };
    1026             : 
    1027           1 : GuiInterface.prototype.GetNonGaiaEntities = function()
    1028             : {
    1029           0 :         return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetNonGaiaEntities();
    1030             : };
    1031             : 
    1032             : /**
    1033             :  * Displays the rally points of a given list of entities (carried in cmd.entities).
    1034             :  *
    1035             :  * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
    1036             :  * be rendered, in order to support instantaneously rendering a rally point marker at a specified location
    1037             :  * instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
    1038             :  * If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
    1039             :  * RallyPoint component.
    1040             :  */
    1041           1 : GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
    1042             : {
    1043           0 :         let cmpPlayer = QueryPlayerIDInterface(player);
    1044             : 
    1045             :         // If there are some rally points already displayed, first hide them.
    1046           0 :         for (let ent of this.entsRallyPointsDisplayed)
    1047             :         {
    1048           0 :                 let cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
    1049           0 :                 if (cmpRallyPointRenderer)
    1050           0 :                         cmpRallyPointRenderer.SetDisplayed(false);
    1051             :         }
    1052             : 
    1053           0 :         this.entsRallyPointsDisplayed = [];
    1054             : 
    1055             :         // Show the rally points for the passed entities.
    1056           0 :         for (let ent of cmd.entities)
    1057             :         {
    1058           0 :                 let cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
    1059           0 :                 if (!cmpRallyPointRenderer)
    1060           0 :                         continue;
    1061             : 
    1062             :                 // Entity must have a rally point component to display a rally point marker
    1063             :                 // (regardless of whether cmd specifies a custom location).
    1064           0 :                 let cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
    1065           0 :                 if (!cmpRallyPoint)
    1066           0 :                         continue;
    1067             : 
    1068             :                 // Verify the owner.
    1069           0 :                 let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1070           0 :                 if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
    1071           0 :                         if (!cmpOwnership || cmpOwnership.GetOwner() != player)
    1072           0 :                                 continue;
    1073             : 
    1074             :                 // If the command was passed an explicit position, use that and
    1075             :                 // override the real rally point position; otherwise use the real position.
    1076             :                 let pos;
    1077           0 :                 if (cmd.x && cmd.z)
    1078           0 :                         pos = cmd;
    1079             :                 else
    1080             :                         // May return undefined if no rally point is set.
    1081           0 :                         pos = cmpRallyPoint.GetPositions()[0];
    1082             : 
    1083           0 :                 if (pos)
    1084             :                 {
    1085             :                         // Only update the position if we changed it (cmd.queued is set).
    1086             :                         // Note that Add-/SetPosition take a CFixedVector2D which has X/Y components, not X/Z.
    1087           0 :                         if ("queued" in cmd)
    1088             :                         {
    1089           0 :                                 if (cmd.queued == true)
    1090           0 :                                         cmpRallyPointRenderer.AddPosition(new Vector2D(pos.x, pos.z));
    1091             :                                 else
    1092           0 :                                         cmpRallyPointRenderer.SetPosition(new Vector2D(pos.x, pos.z));
    1093             :                         }
    1094           0 :                         else if (!cmpRallyPointRenderer.IsSet())
    1095             :                                 // Rebuild the renderer when not set (when reading saved game or in case of building update).
    1096           0 :                                 for (let posi of cmpRallyPoint.GetPositions())
    1097           0 :                                         cmpRallyPointRenderer.AddPosition(new Vector2D(posi.x, posi.z));
    1098             : 
    1099           0 :                         cmpRallyPointRenderer.SetDisplayed(true);
    1100             : 
    1101             :                         // Remember which entities have their rally points displayed so we can hide them again.
    1102           0 :                         this.entsRallyPointsDisplayed.push(ent);
    1103             :                 }
    1104             :         }
    1105             : };
    1106             : 
    1107           1 : GuiInterface.prototype.AddTargetMarker = function(player, cmd)
    1108             : {
    1109           0 :         let ent = Engine.AddLocalEntity(cmd.template);
    1110           0 :         if (!ent)
    1111           0 :                 return;
    1112           0 :         let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1113           0 :         if (cmpOwnership)
    1114           0 :                 cmpOwnership.SetOwner(cmd.owner);
    1115           0 :         let cmpPosition = Engine.QueryInterface(ent, IID_Position);
    1116           0 :         cmpPosition.JumpTo(cmd.x, cmd.z);
    1117             : };
    1118             : 
    1119             : /**
    1120             :  * Display the building placement preview.
    1121             :  * cmd.template is the name of the entity template, or "" to disable the preview.
    1122             :  * cmd.x, cmd.z, cmd.angle give the location.
    1123             :  *
    1124             :  * Returns result object from CheckPlacement:
    1125             :  *      {
    1126             :  *              "success":             true iff the placement is valid, else false
    1127             :  *              "message":             message to display in UI for invalid placement, else ""
    1128             :  *              "parameters":          parameters to use in the message
    1129             :  *              "translateMessage":    localisation info
    1130             :  *              "translateParameters": localisation info
    1131             :  *              "pluralMessage":       we might return a plural translation instead (optional)
    1132             :  *              "pluralCount":         localisation info (optional)
    1133             :  *  }
    1134             :  */
    1135           1 : GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
    1136             : {
    1137           0 :         let result = {
    1138             :                 "success": false,
    1139             :                 "message": "",
    1140             :                 "parameters": {},
    1141             :                 "translateMessage": false,
    1142             :                 "translateParameters": []
    1143             :         };
    1144             : 
    1145           0 :         if (!this.placementEntity || this.placementEntity[0] != cmd.template)
    1146             :         {
    1147           0 :                 if (this.placementEntity)
    1148           0 :                         Engine.DestroyEntity(this.placementEntity[1]);
    1149             : 
    1150           0 :                 if (cmd.template == "")
    1151           0 :                         this.placementEntity = undefined;
    1152             :                 else
    1153           0 :                         this.placementEntity = [cmd.template, Engine.AddLocalEntity("preview|" + cmd.template)];
    1154             :         }
    1155             : 
    1156           0 :         if (this.placementEntity)
    1157             :         {
    1158           0 :                 let ent = this.placementEntity[1];
    1159             : 
    1160           0 :                 let pos = Engine.QueryInterface(ent, IID_Position);
    1161           0 :                 if (pos)
    1162             :                 {
    1163           0 :                         pos.JumpTo(cmd.x, cmd.z);
    1164           0 :                         pos.SetYRotation(cmd.angle);
    1165             :                 }
    1166             : 
    1167           0 :                 let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1168           0 :                 cmpOwnership.SetOwner(player);
    1169             : 
    1170           0 :                 let cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
    1171           0 :                 if (!cmpBuildRestrictions)
    1172           0 :                         error("cmpBuildRestrictions not defined");
    1173             :                 else
    1174           0 :                         result = cmpBuildRestrictions.CheckPlacement();
    1175             : 
    1176           0 :                 let cmpRangeOverlayManager = Engine.QueryInterface(ent, IID_RangeOverlayManager);
    1177           0 :                 if (cmpRangeOverlayManager)
    1178           0 :                         cmpRangeOverlayManager.SetEnabled(true, this.enabledVisualRangeOverlayTypes);
    1179             : 
    1180             :                 // Set it to a red shade if this is an invalid location.
    1181           0 :                 let cmpVisual = Engine.QueryInterface(ent, IID_Visual);
    1182           0 :                 if (cmpVisual)
    1183             :                 {
    1184           0 :                         if (cmd.actorSeed !== undefined)
    1185           0 :                                 cmpVisual.SetActorSeed(cmd.actorSeed);
    1186             : 
    1187           0 :                         if (!result.success)
    1188           0 :                                 cmpVisual.SetShadingColor(1.4, 0.4, 0.4, 1);
    1189             :                         else
    1190           0 :                                 cmpVisual.SetShadingColor(1, 1, 1, 1);
    1191             :                 }
    1192             :         }
    1193             : 
    1194           0 :         return result;
    1195             : };
    1196             : 
    1197             : /**
    1198             :  * Previews the placement of a wall between cmd.start and cmd.end, or just the starting piece of a wall if cmd.end is not
    1199             :  * specified. Returns an object with information about the list of entities that need to be newly constructed to complete
    1200             :  * at least a part of the wall, or false if there are entities required to build at least part of the wall but none of
    1201             :  * them can be validly constructed.
    1202             :  *
    1203             :  * It's important to distinguish between three lists of entities that are at play here, because they may be subsets of one
    1204             :  * another depending on things like snapping and whether some of the entities inside them can be validly positioned.
    1205             :  * We have:
    1206             :  *    - The list of entities that previews the wall. This list is usually equal to the entities required to construct the
    1207             :  *      entire wall. However, if there is snapping to an incomplete tower (i.e. a foundation), it includes extra entities
    1208             :  *      to preview the completed tower on top of its foundation.
    1209             :  *
    1210             :  *    - The list of entities that need to be newly constructed to build the entire wall. This list is regardless of whether
    1211             :  *      any of them can be validly positioned. The emphasishere here is on 'newly'; this list does not include any existing
    1212             :  *      towers at either side of the wall that we snapped to. Or, more generally; it does not include any _entities_ that we
    1213             :  *      snapped to; we might still snap to e.g. terrain, in which case the towers on either end will still need to be newly
    1214             :  *      constructed.
    1215             :  *
    1216             :  *    - The list of entities that need to be newly constructed to build at least a part of the wall. This list is the same
    1217             :  *      as the one above, except that it is truncated at the first entity that cannot be validly positioned. This happens
    1218             :  *      e.g. if the player tries to build a wall straight through an obstruction. Note that any entities that can be validly
    1219             :  *      constructed but come after said first invalid entity are also truncated away.
    1220             :  *
    1221             :  * With this in mind, this method will return false if the second list is not empty, but the third one is. That is, if there
    1222             :  * were entities that are needed to build the wall, but none of them can be validly constructed. False is also returned in
    1223             :  * case of unexpected errors (typically missing components), and when clearing the preview by passing an empty wallset
    1224             :  * argument (see below). Otherwise, it will return an object with the following information:
    1225             :  *
    1226             :  * result: {
    1227             :  *   'startSnappedEnt':   ID of the entity that we snapped to at the starting side of the wall. Currently only supports towers.
    1228             :  *   'endSnappedEnt':     ID of the entity that we snapped to at the (possibly truncated) ending side of the wall. Note that this
    1229             :  *                        can only be set if no truncation of the second list occurs; if we snapped to an entity at the ending side
    1230             :  *                        but the wall construction was truncated before we could reach it, it won't be set here. Currently only
    1231             :  *                        supports towers.
    1232             :  *   'pieces':            Array with the following data for each of the entities in the third list:
    1233             :  *    [{
    1234             :  *       'template':      Template name of the entity.
    1235             :  *       'x':             X coordinate of the entity's position.
    1236             :  *       'z':             Z coordinate of the entity's position.
    1237             :  *       'angle':         Rotation around the Y axis of the entity (in radians).
    1238             :  *     },
    1239             :  *     ...]
    1240             :  *   'cost': {            The total cost required for constructing all the pieces as listed above.
    1241             :  *     'food': ...,
    1242             :  *     'wood': ...,
    1243             :  *     'stone': ...,
    1244             :  *     'metal': ...,
    1245             :  *     'population': ...,
    1246             :  *   }
    1247             :  * }
    1248             :  *
    1249             :  * @param cmd.wallSet Object holding the set of wall piece template names. Set to an empty value to clear the preview.
    1250             :  * @param cmd.start Starting point of the wall segment being created.
    1251             :  * @param cmd.end (Optional) Ending point of the wall segment being created. If not defined, it is understood that only
    1252             :  *                 the starting point of the wall is available at this time (e.g. while the player is still in the process
    1253             :  *                 of picking a starting point), and that therefore only the first entity in the wall (a tower) should be
    1254             :  *                 previewed.
    1255             :  * @param cmd.snapEntities List of candidate entities to snap the start and ending positions to.
    1256             :  */
    1257           1 : GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
    1258             : {
    1259           0 :         let wallSet = cmd.wallSet;
    1260             : 
    1261             :         // Did the start position snap to anything?
    1262             :         // If we snapped, was it to an entity? If yes, hold that entity's ID.
    1263           0 :         let start = {
    1264             :                 "pos": cmd.start,
    1265             :                 "angle": 0,
    1266             :                 "snapped": false,
    1267             :                 "snappedEnt": INVALID_ENTITY
    1268             :         };
    1269             : 
    1270             :         // Did the end position snap to anything?
    1271             :         // If we snapped, was it to an entity? If yes, hold that entity's ID.
    1272           0 :         let end = {
    1273             :                 "pos": cmd.end,
    1274             :                 "angle": 0,
    1275             :                 "snapped": false,
    1276             :                 "snappedEnt": INVALID_ENTITY
    1277             :         };
    1278             : 
    1279             :         // --------------------------------------------------------------------------------
    1280             :         // Do some entity cache management and check for snapping.
    1281             : 
    1282           0 :         if (!this.placementWallEntities)
    1283           0 :                 this.placementWallEntities = {};
    1284             : 
    1285           0 :         if (!wallSet)
    1286             :         {
    1287             :                 // We're clearing the preview, clear the entity cache and bail.
    1288           0 :                 for (let tpl in this.placementWallEntities)
    1289             :                 {
    1290           0 :                         for (let ent of this.placementWallEntities[tpl].entities)
    1291           0 :                                 Engine.DestroyEntity(ent);
    1292             : 
    1293           0 :                         this.placementWallEntities[tpl].numUsed = 0;
    1294           0 :                         this.placementWallEntities[tpl].entities = [];
    1295             :                         // Keep template data around.
    1296             :                 }
    1297             : 
    1298           0 :                 return false;
    1299             :         }
    1300             : 
    1301           0 :         for (let tpl in this.placementWallEntities)
    1302             :         {
    1303           0 :                 for (let ent of this.placementWallEntities[tpl].entities)
    1304             :                 {
    1305           0 :                         let pos = Engine.QueryInterface(ent, IID_Position);
    1306           0 :                         if (pos)
    1307           0 :                                 pos.MoveOutOfWorld();
    1308             :                 }
    1309             : 
    1310           0 :                 this.placementWallEntities[tpl].numUsed = 0;
    1311             :         }
    1312             : 
    1313             :         // Create cache entries for templates we haven't seen before.
    1314           0 :         for (let type in wallSet.templates)
    1315             :         {
    1316           0 :                 if (type == "curves")
    1317           0 :                         continue;
    1318             : 
    1319           0 :                 let tpl = wallSet.templates[type];
    1320           0 :                 if (!(tpl in this.placementWallEntities))
    1321             :                 {
    1322           0 :                         this.placementWallEntities[tpl] = {
    1323             :                                 "numUsed": 0,
    1324             :                                 "entities": [],
    1325             :                                 "templateData": this.GetTemplateData(player, { "templateName": tpl }),
    1326             :                         };
    1327             : 
    1328           0 :                         if (!this.placementWallEntities[tpl].templateData.wallPiece)
    1329             :                         {
    1330           0 :                                 error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'");
    1331           0 :                                 return false;
    1332             :                         }
    1333             :                 }
    1334             :         }
    1335             : 
    1336             :         // Prevent division by zero errors further on if the start and end positions are the same.
    1337           0 :         if (end.pos && (start.pos.x === end.pos.x && start.pos.z === end.pos.z))
    1338           0 :                 end.pos = undefined;
    1339             : 
    1340             :         // See if we need to snap the start and/or end coordinates to any of our list of snap entities. Note that, despite the list
    1341             :         // of snapping candidate entities, it might still snap to e.g. terrain features. Use the "ent" key in the returned snapping
    1342             :         // data to determine whether it snapped to an entity (if any), and to which one (see GetFoundationSnapData).
    1343           0 :         if (cmd.snapEntities)
    1344             :         {
    1345             :                 // Value of 0.5 was determined through trial and error.
    1346           0 :                 let snapRadius = this.placementWallEntities[wallSet.templates.tower].templateData.wallPiece.length * 0.5;
    1347           0 :                 let startSnapData = this.GetFoundationSnapData(player, {
    1348             :                         "x": start.pos.x,
    1349             :                         "z": start.pos.z,
    1350             :                         "template": wallSet.templates.tower,
    1351             :                         "snapEntities": cmd.snapEntities,
    1352             :                         "snapRadius": snapRadius,
    1353             :                 });
    1354             : 
    1355           0 :                 if (startSnapData)
    1356             :                 {
    1357           0 :                         start.pos.x = startSnapData.x;
    1358           0 :                         start.pos.z = startSnapData.z;
    1359           0 :                         start.angle = startSnapData.angle;
    1360           0 :                         start.snapped = true;
    1361             : 
    1362           0 :                         if (startSnapData.ent)
    1363           0 :                                 start.snappedEnt = startSnapData.ent;
    1364             :                 }
    1365             : 
    1366           0 :                 if (end.pos)
    1367             :                 {
    1368           0 :                         let endSnapData = this.GetFoundationSnapData(player, {
    1369             :                                 "x": end.pos.x,
    1370             :                                 "z": end.pos.z,
    1371             :                                 "template": wallSet.templates.tower,
    1372             :                                 "snapEntities": cmd.snapEntities,
    1373             :                                 "snapRadius": snapRadius,
    1374             :                         });
    1375             : 
    1376           0 :                         if (endSnapData)
    1377             :                         {
    1378           0 :                                 end.pos.x = endSnapData.x;
    1379           0 :                                 end.pos.z = endSnapData.z;
    1380           0 :                                 end.angle = endSnapData.angle;
    1381           0 :                                 end.snapped = true;
    1382             : 
    1383           0 :                                 if (endSnapData.ent)
    1384           0 :                                         end.snappedEnt = endSnapData.ent;
    1385             :                         }
    1386             :                 }
    1387             :         }
    1388             : 
    1389             :         // Clear the single-building preview entity (we'll be rolling our own).
    1390           0 :         this.SetBuildingPlacementPreview(player, { "template": "" });
    1391             : 
    1392             :         // --------------------------------------------------------------------------------
    1393             :         // Calculate wall placement and position preview entities.
    1394             : 
    1395           0 :         let result = {
    1396             :                 "pieces": [],
    1397             :                 "cost": { "population": 0, "time": 0 }
    1398             :         };
    1399           0 :         for (let res of Resources.GetCodes())
    1400           0 :                 result.cost[res] = 0;
    1401             : 
    1402           0 :         let previewEntities = [];
    1403           0 :         if (end.pos)
    1404             :                 // See helpers/Walls.js.
    1405           0 :                 previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end);
    1406             : 
    1407             :         // For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would
    1408             :         // otherwise be allowed by their obstruction shapes. However, during this preview phase, this is not so much of
    1409             :         // an issue, because all preview entities have their obstruction components deactivated, meaning that their
    1410             :         // obstruction shapes do not register in the simulation and hence cannot affect it. This implies that the preview
    1411             :         // entities cannot be found to obstruct each other, which largely solves the issue of overlap between wall pieces.
    1412             : 
    1413             :         // Note that they will still be obstructed by existing shapes in the simulation (that have the BLOCK_FOUNDATION
    1414             :         // flag set), which is what we want. The only exception to this is when snapping to existing towers (or
    1415             :         // foundations thereof); the wall segments that connect up to these will be found to be obstructed by the
    1416             :         // existing tower/foundation, and be shaded red to indicate that they cannot be placed there. To prevent this,
    1417             :         // we manually set the control group of the outermost wall pieces equal to those of the snapped-to towers, so
    1418             :         // that they are free from mutual obstruction (per definition of obstruction control groups). This is done by
    1419             :         // assigning them an extra "controlGroup" field, which we'll then set during the placement loop below.
    1420             : 
    1421             :         // Additionally, in the situation that we're snapping to merely a foundation of a tower instead of a fully
    1422             :         // constructed one, we'll need an extra preview entity for the starting tower, which also must not be obstructed
    1423             :         // by the foundation it snaps to.
    1424             : 
    1425           0 :         if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
    1426             :         {
    1427           0 :                 let startEntObstruction = Engine.QueryInterface(start.snappedEnt, IID_Obstruction);
    1428           0 :                 if (previewEntities.length && startEntObstruction)
    1429           0 :                         previewEntities[0].controlGroups = [startEntObstruction.GetControlGroup()];
    1430             : 
    1431             :                 // If we're snapping to merely a foundation, add an extra preview tower and also set it to the same control group.
    1432           0 :                 let startEntState = this.GetEntityState(player, start.snappedEnt);
    1433           0 :                 if (startEntState.foundation)
    1434             :                 {
    1435           0 :                         let cmpPosition = Engine.QueryInterface(start.snappedEnt, IID_Position);
    1436           0 :                         if (cmpPosition)
    1437           0 :                                 previewEntities.unshift({
    1438             :                                         "template": wallSet.templates.tower,
    1439             :                                         "pos": start.pos,
    1440             :                                         "angle": cmpPosition.GetRotation().y,
    1441             :                                         "controlGroups": [startEntObstruction ? startEntObstruction.GetControlGroup() : undefined],
    1442             :                                         "excludeFromResult": true // Preview only, must not appear in the result.
    1443             :                                 });
    1444             :                 }
    1445             :         }
    1446             :         else
    1447             :         {
    1448             :                 // Didn't snap to an existing entity, add the starting tower manually. To prevent odd-looking rotation jumps
    1449             :                 // when shift-clicking to build a wall, reuse the placement angle that was last seen on a validly positioned
    1450             :                 // wall piece.
    1451             : 
    1452             :                 // To illustrate the last point, consider what happens if we used some constant instead, say, 0. Issuing the
    1453             :                 // build command for a wall is asynchronous, so when the preview updates after shift-clicking, the wall piece
    1454             :                 // foundations are not registered yet in the simulation. This means they cannot possibly be picked in the list
    1455             :                 // of candidate entities for snapping. In the next preview update, we therefore hit this case, and would rotate
    1456             :                 // the preview to 0 radians. Then, after one or two simulation updates or so, the foundations register and
    1457             :                 // onSimulationUpdate in session.js updates the preview again. It first grabs a new list of snapping candidates,
    1458             :                 // which this time does include the new foundations; so we snap to the entity, and rotate the preview back to
    1459             :                 // the foundation's angle.
    1460             : 
    1461             :                 // The result is a noticeable rotation to 0 and back, which is undesirable. So, for a split second there until
    1462             :                 // the simulation updates, we fake it by reusing the last angle and hope the player doesn't notice.
    1463           0 :                 previewEntities.unshift({
    1464             :                         "template": wallSet.templates.tower,
    1465             :                         "pos": start.pos,
    1466             :                         "angle": previewEntities.length ? previewEntities[0].angle : this.placementWallLastAngle
    1467             :                 });
    1468             :         }
    1469             : 
    1470           0 :         if (end.pos)
    1471             :         {
    1472             :                 // Analogous to the starting side case above.
    1473           0 :                 if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY)
    1474             :                 {
    1475           0 :                         let endEntObstruction = Engine.QueryInterface(end.snappedEnt, IID_Obstruction);
    1476             : 
    1477             :                         // Note that it's possible for the last entity in previewEntities to be the same as the first, i.e. the
    1478             :                         // same wall piece snapping to both a starting and an ending tower. And it might be more common than you would
    1479             :                         // expect; the allowed overlap between wall segments and towers facilitates this to some degree. To deal with
    1480             :                         // the possibility of dual initial control groups, we use a '.controlGroups' array rather than a single
    1481             :                         // '.controlGroup' property. Note that this array can only ever have 0, 1 or 2 elements (checked at a later time).
    1482           0 :                         if (previewEntities.length > 0 && endEntObstruction)
    1483             :                         {
    1484           0 :                                 previewEntities[previewEntities.length - 1].controlGroups = previewEntities[previewEntities.length - 1].controlGroups || [];
    1485           0 :                                 previewEntities[previewEntities.length - 1].controlGroups.push(endEntObstruction.GetControlGroup());
    1486             :                         }
    1487             : 
    1488             :                         // If we're snapping to a foundation, add an extra preview tower and also set it to the same control group.
    1489           0 :                         let endEntState = this.GetEntityState(player, end.snappedEnt);
    1490           0 :                         if (endEntState.foundation)
    1491             :                         {
    1492           0 :                                 let cmpPosition = Engine.QueryInterface(end.snappedEnt, IID_Position);
    1493           0 :                                 if (cmpPosition)
    1494           0 :                                         previewEntities.push({
    1495             :                                                 "template": wallSet.templates.tower,
    1496             :                                                 "pos": end.pos,
    1497             :                                                 "angle": cmpPosition.GetRotation().y,
    1498             :                                                 "controlGroups": [endEntObstruction ? endEntObstruction.GetControlGroup() : undefined],
    1499             :                                                 "excludeFromResult": true
    1500             :                                         });
    1501             :                         }
    1502             :                 }
    1503             :                 else
    1504           0 :                         previewEntities.push({
    1505             :                                 "template": wallSet.templates.tower,
    1506             :                                 "pos": end.pos,
    1507             :                                 "angle": previewEntities.length ? previewEntities[previewEntities.length - 1].angle : this.placementWallLastAngle
    1508             :                         });
    1509             :         }
    1510             : 
    1511           0 :         let cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
    1512           0 :         if (!cmpTerrain)
    1513             :         {
    1514           0 :                 error("[SetWallPlacementPreview] System Terrain component not found");
    1515           0 :                 return false;
    1516             :         }
    1517             : 
    1518           0 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    1519           0 :         if (!cmpRangeManager)
    1520             :         {
    1521           0 :                 error("[SetWallPlacementPreview] System RangeManager component not found");
    1522           0 :                 return false;
    1523             :         }
    1524             : 
    1525             :         // Loop through the preview entities, and construct the subset of them that need to be, and can be, validly constructed
    1526             :         // to build at least a part of the wall (meaning that the subset is truncated after the first entity that needs to be,
    1527             :         // but cannot validly be, constructed). See method-level documentation for more details.
    1528             : 
    1529           0 :         let allPiecesValid = true;
    1530             :         // Number of entities that are required to build the entire wall, regardless of validity.
    1531           0 :         let numRequiredPieces = 0;
    1532             : 
    1533           0 :         for (let i = 0; i < previewEntities.length; ++i)
    1534             :         {
    1535           0 :                 let entInfo = previewEntities[i];
    1536             : 
    1537           0 :                 let ent = null;
    1538           0 :                 let tpl = entInfo.template;
    1539           0 :                 let tplData = this.placementWallEntities[tpl].templateData;
    1540           0 :                 let entPool = this.placementWallEntities[tpl];
    1541             : 
    1542           0 :                 if (entPool.numUsed >= entPool.entities.length)
    1543             :                 {
    1544           0 :                         ent = Engine.AddLocalEntity("preview|" + tpl);
    1545           0 :                         entPool.entities.push(ent);
    1546             :                 }
    1547             :                 else
    1548           0 :                         ent = entPool.entities[entPool.numUsed];
    1549             : 
    1550           0 :                 if (!ent)
    1551             :                 {
    1552           0 :                         error("[SetWallPlacementPreview] Failed to allocate or reuse preview entity of template '" + tpl + "'");
    1553           0 :                         continue;
    1554             :                 }
    1555             : 
    1556             :                 // Move piece to right location.
    1557             :                 // TODO: Consider reusing SetBuildingPlacementReview for this, enhanced to be able to deal with multiple entities.
    1558           0 :                 let cmpPosition = Engine.QueryInterface(ent, IID_Position);
    1559           0 :                 if (cmpPosition)
    1560             :                 {
    1561           0 :                         cmpPosition.JumpTo(entInfo.pos.x, entInfo.pos.z);
    1562           0 :                         cmpPosition.SetYRotation(entInfo.angle);
    1563             : 
    1564             :                         // If this piece is a tower, then it should have a Y position that is at least as high as its surrounding pieces.
    1565           0 :                         if (tpl === wallSet.templates.tower)
    1566             :                         {
    1567           0 :                                 let terrainGroundPrev = null;
    1568           0 :                                 let terrainGroundNext = null;
    1569             : 
    1570           0 :                                 if (i > 0)
    1571           0 :                                         terrainGroundPrev = cmpTerrain.GetGroundLevel(previewEntities[i - 1].pos.x, previewEntities[i - 1].pos.z);
    1572             : 
    1573           0 :                                 if (i < previewEntities.length - 1)
    1574           0 :                                         terrainGroundNext = cmpTerrain.GetGroundLevel(previewEntities[i + 1].pos.x, previewEntities[i + 1].pos.z);
    1575             : 
    1576           0 :                                 if (terrainGroundPrev != null || terrainGroundNext != null)
    1577             :                                 {
    1578           0 :                                         let targetY = Math.max(terrainGroundPrev, terrainGroundNext);
    1579           0 :                                         cmpPosition.SetHeightFixed(targetY);
    1580             :                                 }
    1581             :                         }
    1582             :                 }
    1583             : 
    1584           0 :                 let cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
    1585           0 :                 if (!cmpObstruction)
    1586             :                 {
    1587           0 :                         error("[SetWallPlacementPreview] Preview entity of template '" + tpl + "' does not have an Obstruction component");
    1588           0 :                         continue;
    1589             :                 }
    1590             : 
    1591             :                 // Assign any predefined control groups. Note that there can only be 0, 1 or 2 predefined control groups; if there are
    1592             :                 // more, we've made a programming error. The control groups are assigned from the entInfo.controlGroups array on a
    1593             :                 // first-come first-served basis; the first value in the array is always assigned as the primary control group, and
    1594             :                 // any second value as the secondary control group.
    1595             : 
    1596             :                 // By default, we reset the control groups to their standard values. Remember that we're reusing entities; if we don't
    1597             :                 // reset them, then an ending wall segment that was e.g. at one point snapped to an existing tower, and is subsequently
    1598             :                 // reused as a non-snapped ending wall segment, would no longer be capable of being obstructed by the same tower it was
    1599             :                 // once snapped to.
    1600             : 
    1601           0 :                 let primaryControlGroup = ent;
    1602           0 :                 let secondaryControlGroup = INVALID_ENTITY;
    1603             : 
    1604           0 :                 if (entInfo.controlGroups && entInfo.controlGroups.length > 0)
    1605             :                 {
    1606           0 :                         if (entInfo.controlGroups.length > 2)
    1607             :                         {
    1608           0 :                                 error("[SetWallPlacementPreview] Encountered preview entity of template '" + tpl + "' with more than 2 initial control groups");
    1609           0 :                                 break;
    1610             :                         }
    1611             : 
    1612           0 :                         primaryControlGroup = entInfo.controlGroups[0];
    1613           0 :                         if (entInfo.controlGroups.length > 1)
    1614           0 :                                 secondaryControlGroup = entInfo.controlGroups[1];
    1615             :                 }
    1616             : 
    1617           0 :                 cmpObstruction.SetControlGroup(primaryControlGroup);
    1618           0 :                 cmpObstruction.SetControlGroup2(secondaryControlGroup);
    1619             : 
    1620           0 :                 let validPlacement = false;
    1621             : 
    1622           0 :                 let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
    1623           0 :                 cmpOwnership.SetOwner(player);
    1624             : 
    1625             :                 // Check whether it's in a visible or fogged region.
    1626             :                 // TODO: Should definitely reuse SetBuildingPlacementPreview, this is just straight up copy/pasta.
    1627           0 :                 let visible = cmpRangeManager.GetLosVisibility(ent, player) != "hidden";
    1628           0 :                 if (visible)
    1629             :                 {
    1630           0 :                         let cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
    1631           0 :                         if (!cmpBuildRestrictions)
    1632             :                         {
    1633           0 :                                 error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for preview entity of template '" + tpl + "'");
    1634           0 :                                 continue;
    1635             :                         }
    1636             : 
    1637             :                         // TODO: Handle results of CheckPlacement.
    1638           0 :                         validPlacement = cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success;
    1639             : 
    1640             :                         // If a wall piece has two control groups, it's likely a segment that spans
    1641             :                         // between two existing towers. To avoid placing a duplicate wall segment,
    1642             :                         // check for collisions with entities that share both control groups.
    1643           0 :                         if (validPlacement && entInfo.controlGroups && entInfo.controlGroups.length > 1)
    1644           0 :                                 validPlacement = cmpObstruction.CheckDuplicateFoundation();
    1645             :                 }
    1646             : 
    1647           0 :                 allPiecesValid = allPiecesValid && validPlacement;
    1648             : 
    1649             :                 // The requirement below that all pieces so far have to have valid positions, rather than only this single one,
    1650             :                 // ensures that no more foundations will be placed after a first invalidly-positioned piece. (It is possible
    1651             :                 // for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall
    1652             :                 // through and past an existing building).
    1653             : 
    1654             :                 // Additionally, the excludeFromResult flag is set for preview entities that were manually added to be placed
    1655             :                 // on top of foundations of incompleted towers that we snapped to; they must not be part of the result.
    1656             : 
    1657           0 :                 if (!entInfo.excludeFromResult)
    1658           0 :                         ++numRequiredPieces;
    1659             : 
    1660           0 :                 if (allPiecesValid && !entInfo.excludeFromResult)
    1661             :                 {
    1662           0 :                         result.pieces.push({
    1663             :                                 "template": tpl,
    1664             :                                 "x": entInfo.pos.x,
    1665             :                                 "z": entInfo.pos.z,
    1666             :                                 "angle": entInfo.angle,
    1667             :                         });
    1668           0 :                         this.placementWallLastAngle = entInfo.angle;
    1669             : 
    1670             :                         // Grab the cost of this wall piece and add it up (note; preview entities don't have their Cost components
    1671             :                         // copied over, so we need to fetch it from the template instead).
    1672             :                         // TODO: We should really use a Cost object or at least some utility functions for this, this is mindless
    1673             :                         // boilerplate that's probably duplicated in tons of places.
    1674           0 :                         for (let res of Resources.GetCodes().concat(["population", "time"]))
    1675           0 :                                 result.cost[res] += tplData.cost[res];
    1676             :                 }
    1677             : 
    1678           0 :                 let canAfford = true;
    1679           0 :                 let cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
    1680           0 :                 if (cmpPlayer && cmpPlayer.GetNeededResources(result.cost))
    1681           0 :                         canAfford = false;
    1682             : 
    1683           0 :                 let cmpVisual = Engine.QueryInterface(ent, IID_Visual);
    1684           0 :                 if (cmpVisual)
    1685             :                 {
    1686           0 :                         if (!allPiecesValid || !canAfford)
    1687           0 :                                 cmpVisual.SetShadingColor(1.4, 0.4, 0.4, 1);
    1688             :                         else
    1689           0 :                                 cmpVisual.SetShadingColor(1, 1, 1, 1);
    1690             :                 }
    1691             : 
    1692           0 :                 ++entPool.numUsed;
    1693             :         }
    1694             : 
    1695             :         // If any were entities required to build the wall, but none of them could be validly positioned, return failure
    1696             :         // (see method-level documentation).
    1697           0 :         if (numRequiredPieces > 0 && result.pieces.length == 0)
    1698           0 :                 return false;
    1699             : 
    1700           0 :         if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
    1701           0 :                 result.startSnappedEnt = start.snappedEnt;
    1702             : 
    1703             :         // We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed,
    1704             :         // i.e. are included in result.pieces (see docs for the result object).
    1705           0 :         if (end.pos && end.snappedEnt && end.snappedEnt != INVALID_ENTITY && allPiecesValid)
    1706           0 :                 result.endSnappedEnt = end.snappedEnt;
    1707             : 
    1708           0 :         return result;
    1709             : };
    1710             : 
    1711             : /**
    1712             :  * Given the current position {data.x, data.z} of an foundation of template data.template, returns the position and angle to snap
    1713             :  * it to (if necessary/useful).
    1714             :  *
    1715             :  * @param data.x            The X position of the foundation to snap.
    1716             :  * @param data.z            The Z position of the foundation to snap.
    1717             :  * @param data.template     The template to get the foundation snapping data for.
    1718             :  * @param data.snapEntities Optional; list of entity IDs to snap to if {data.x, data.z} is within a circle of radius data.snapRadius
    1719             :  *                            around the entity. Only takes effect when used in conjunction with data.snapRadius.
    1720             :  *                          When this option is used and the foundation is found to snap to one of the entities passed in this list
    1721             :  *                            (as opposed to e.g. snapping to terrain features), then the result will contain an additional key "ent",
    1722             :  *                            holding the ID of the entity that was snapped to.
    1723             :  * @param data.snapRadius   Optional; when used in conjunction with data.snapEntities, indicates the circle radius around an entity that
    1724             :  *                            {data.x, data.z} must be located within to have it snap to that entity.
    1725             :  */
    1726           1 : GuiInterface.prototype.GetFoundationSnapData = function(player, data)
    1727             : {
    1728           0 :         let template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(data.template);
    1729           0 :         if (!template)
    1730             :         {
    1731           0 :                 warn("[GetFoundationSnapData] Failed to load template '" + data.template + "'");
    1732           0 :                 return false;
    1733             :         }
    1734             : 
    1735           0 :         if (data.snapEntities && data.snapRadius && data.snapRadius > 0)
    1736             :         {
    1737             :                 // See if {data.x, data.z} is inside the snap radius of any of the snap entities; and if so, to which it is closest.
    1738             :                 // (TODO: Break unlikely ties by choosing the lowest entity ID.)
    1739             : 
    1740           0 :                 let minDist2 = -1;
    1741           0 :                 let minDistEntitySnapData = null;
    1742           0 :                 let radius2 = data.snapRadius * data.snapRadius;
    1743             : 
    1744           0 :                 for (let ent of data.snapEntities)
    1745             :                 {
    1746           0 :                         let cmpPosition = Engine.QueryInterface(ent, IID_Position);
    1747           0 :                         if (!cmpPosition || !cmpPosition.IsInWorld())
    1748           0 :                                 continue;
    1749             : 
    1750           0 :                         let pos = cmpPosition.GetPosition();
    1751           0 :                         let dist2 = (data.x - pos.x) * (data.x - pos.x) + (data.z - pos.z) * (data.z - pos.z);
    1752           0 :                         if (dist2 > radius2)
    1753           0 :                                 continue;
    1754             : 
    1755           0 :                         if (minDist2 < 0 || dist2 < minDist2)
    1756             :                         {
    1757           0 :                                 minDist2 = dist2;
    1758           0 :                                 minDistEntitySnapData = {
    1759             :                                         "x": pos.x,
    1760             :                                         "z": pos.z,
    1761             :                                         "angle": cmpPosition.GetRotation().y,
    1762             :                                         "ent": ent
    1763             :                                 };
    1764             :                         }
    1765             :                 }
    1766             : 
    1767           0 :                 if (minDistEntitySnapData != null)
    1768           0 :                         return minDistEntitySnapData;
    1769             :         }
    1770             : 
    1771           0 :         if (data.snapToEdges)
    1772             :         {
    1773           0 :                 let position = this.obstructionSnap.getPosition(data, template);
    1774           0 :                 if (position)
    1775           0 :                         return position;
    1776             :         }
    1777             : 
    1778           0 :         if (template.BuildRestrictions.PlacementType == "shore")
    1779             :         {
    1780           0 :                 let angle = GetDockAngle(template, data.x, data.z);
    1781           0 :                 if (angle !== undefined)
    1782           0 :                         return {
    1783             :                                 "x": data.x,
    1784             :                                 "z": data.z,
    1785             :                                 "angle": angle
    1786             :                         };
    1787             :         }
    1788             : 
    1789           0 :         return false;
    1790             : };
    1791             : 
    1792           1 : GuiInterface.prototype.PlaySoundForPlayer = function(player, data)
    1793             : {
    1794           0 :         let playerEntityID = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(player);
    1795           0 :         let cmpSound = Engine.QueryInterface(playerEntityID, IID_Sound);
    1796           0 :         if (!cmpSound)
    1797           0 :                 return;
    1798             : 
    1799           0 :         let soundGroup = cmpSound.GetSoundGroup(data.name);
    1800           0 :         if (soundGroup)
    1801           0 :                 Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager).PlaySoundGroupForPlayer(soundGroup, player);
    1802             : };
    1803             : 
    1804           1 : GuiInterface.prototype.PlaySound = function(player, data)
    1805             : {
    1806           0 :         if (!data.entity)
    1807           0 :                 return;
    1808             : 
    1809           0 :         PlaySound(data.name, data.entity);
    1810             : };
    1811             : 
    1812             : /**
    1813             :  * Find any idle units.
    1814             :  *
    1815             :  * @param data.idleClasses              Array of class names to include.
    1816             :  * @param data.prevUnit         The previous idle unit, if calling a second time to iterate through units.  May be left undefined.
    1817             :  * @param data.limit                    The number of idle units to return.  May be left undefined (will return all idle units).
    1818             :  * @param data.excludeUnits     Array of units to exclude.
    1819             :  *
    1820             :  * Returns an array of idle units.
    1821             :  * If multiple classes were supplied, and multiple items will be returned, the items will be sorted by class.
    1822             :  */
    1823           1 : GuiInterface.prototype.FindIdleUnits = function(player, data)
    1824             : {
    1825           0 :         let idleUnits = [];
    1826             :         // The general case is that only the 'first' idle unit is required; filtering would examine every unit.
    1827             :         // This loop imitates a grouping/aggregation on the first matching idle class.
    1828           0 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    1829           0 :         for (let entity of cmpRangeManager.GetEntitiesByPlayer(player))
    1830             :         {
    1831           0 :                 let filtered = this.IdleUnitFilter(entity, data.idleClasses, data.excludeUnits);
    1832           0 :                 if (!filtered.idle)
    1833           0 :                         continue;
    1834             : 
    1835             :                 // If the entity is in the 'current' (first, 0) bucket on a resumed search, it must be after the "previous" unit, if any.
    1836             :                 // By adding to the 'end', there is no pause if the series of units loops.
    1837           0 :                 let bucket = filtered.bucket;
    1838           0 :                 if (bucket == 0 && data.prevUnit && entity <= data.prevUnit)
    1839           0 :                         bucket = data.idleClasses.length;
    1840             : 
    1841           0 :                 if (!idleUnits[bucket])
    1842           0 :                         idleUnits[bucket] = [];
    1843           0 :                 idleUnits[bucket].push(entity);
    1844             : 
    1845             :                 // If enough units have been collected in the first bucket, go ahead and return them.
    1846           0 :                 if (data.limit && bucket == 0 && idleUnits[0].length == data.limit)
    1847           0 :                         return idleUnits[0];
    1848             :         }
    1849             : 
    1850           0 :         let reduced = idleUnits.reduce((prev, curr) => prev.concat(curr), []);
    1851           0 :         if (data.limit && reduced.length > data.limit)
    1852           0 :                 return reduced.slice(0, data.limit);
    1853             : 
    1854           0 :         return reduced;
    1855             : };
    1856             : 
    1857             : /**
    1858             :  * Discover if the player has idle units.
    1859             :  *
    1860             :  * @param data.idleClasses      Array of class names to include.
    1861             :  * @param data.excludeUnits     Array of units to exclude.
    1862             :  *
    1863             :  * Returns a boolean of whether the player has any idle units
    1864             :  */
    1865           1 : GuiInterface.prototype.HasIdleUnits = function(player, data)
    1866             : {
    1867           0 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    1868           0 :         return cmpRangeManager.GetEntitiesByPlayer(player).some(unit => this.IdleUnitFilter(unit, data.idleClasses, data.excludeUnits).idle);
    1869             : };
    1870             : 
    1871             : /**
    1872             :  * Whether to filter an idle unit
    1873             :  *
    1874             :  * @param unit                  The unit to filter.
    1875             :  * @param idleclasses   Array of class names to include.
    1876             :  * @param excludeUnits  Array of units to exclude.
    1877             :  *
    1878             :  * Returns an object with the following fields:
    1879             :  *      - idle - true if the unit is considered idle by the filter, false otherwise.
    1880             :  *      - bucket - if idle, set to the index of the first matching idle class, undefined otherwise.
    1881             :  */
    1882           1 : GuiInterface.prototype.IdleUnitFilter = function(unit, idleClasses, excludeUnits)
    1883             : {
    1884           0 :         let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI);
    1885           0 :         if (!cmpUnitAI || !cmpUnitAI.IsIdle())
    1886           0 :                 return { "idle": false };
    1887             : 
    1888           0 :         let cmpGarrisonable = Engine.QueryInterface(unit, IID_Garrisonable);
    1889           0 :         if (cmpGarrisonable && cmpGarrisonable.IsGarrisoned())
    1890           0 :                 return { "idle": false };
    1891             : 
    1892           0 :         const cmpTurretable = Engine.QueryInterface(unit, IID_Turretable);
    1893           0 :         if (cmpTurretable && cmpTurretable.IsTurreted())
    1894           0 :                 return { "idle": false };
    1895             : 
    1896           0 :         let cmpIdentity = Engine.QueryInterface(unit, IID_Identity);
    1897           0 :         if (!cmpIdentity)
    1898           0 :                 return { "idle": false };
    1899             : 
    1900           0 :         let bucket = idleClasses.findIndex(elem => MatchesClassList(cmpIdentity.GetClassesList(), elem));
    1901           0 :         if (bucket == -1 || excludeUnits.indexOf(unit) > -1)
    1902           0 :                 return { "idle": false };
    1903             : 
    1904           0 :         return { "idle": true, "bucket": bucket };
    1905             : };
    1906             : 
    1907           1 : GuiInterface.prototype.GetTradingRouteGain = function(player, data)
    1908             : {
    1909           0 :         if (!data.firstMarket || !data.secondMarket)
    1910           0 :                 return null;
    1911             : 
    1912           0 :         let cmpMarket = QueryMiragedInterface(data.firstMarket, IID_Market);
    1913           0 :         return cmpMarket && cmpMarket.CalculateTraderGain(data.secondMarket, data.template);
    1914             : };
    1915             : 
    1916           1 : GuiInterface.prototype.GetTradingDetails = function(player, data)
    1917             : {
    1918           0 :         let cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader);
    1919           0 :         if (!cmpEntityTrader || !cmpEntityTrader.CanTrade(data.target))
    1920           0 :                 return null;
    1921             : 
    1922           0 :         let firstMarket = cmpEntityTrader.GetFirstMarket();
    1923           0 :         let secondMarket = cmpEntityTrader.GetSecondMarket();
    1924           0 :         let result = null;
    1925           0 :         if (data.target === firstMarket)
    1926             :         {
    1927           0 :                 result = {
    1928             :                         "type": "is first",
    1929             :                         "hasBothMarkets": cmpEntityTrader.HasBothMarkets()
    1930             :                 };
    1931           0 :                 if (cmpEntityTrader.HasBothMarkets())
    1932           0 :                         result.gain = cmpEntityTrader.GetGoods().amount;
    1933             :         }
    1934           0 :         else if (data.target === secondMarket)
    1935           0 :                 result = {
    1936             :                         "type": "is second",
    1937             :                         "gain": cmpEntityTrader.GetGoods().amount,
    1938             :                 };
    1939           0 :         else if (!firstMarket)
    1940           0 :                 result = { "type": "set first" };
    1941           0 :         else if (!secondMarket)
    1942           0 :                 result = {
    1943             :                         "type": "set second",
    1944             :                         "gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
    1945             :                 };
    1946             :         else
    1947           0 :                 result = { "type": "set first" };
    1948             : 
    1949           0 :         return result;
    1950             : };
    1951             : 
    1952           1 : GuiInterface.prototype.CanAttack = function(player, data)
    1953             : {
    1954           0 :         let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
    1955           0 :         return cmpAttack && cmpAttack.CanAttack(data.target, data.types || undefined);
    1956             : };
    1957             : 
    1958             : /*
    1959             :  * Returns batch build time.
    1960             :  */
    1961           1 : GuiInterface.prototype.GetBatchTime = function(player, data)
    1962             : {
    1963           0 :         return Engine.QueryInterface(data.entity, IID_Trainer)?.GetBatchTime(data.batchSize) || 0;
    1964             : };
    1965             : 
    1966           1 : GuiInterface.prototype.IsMapRevealed = function(player)
    1967             : {
    1968           0 :         return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetLosRevealAll(player);
    1969             : };
    1970             : 
    1971           1 : GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
    1972             : {
    1973           0 :         Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder).SetDebugOverlay(enabled);
    1974             : };
    1975             : 
    1976           1 : GuiInterface.prototype.SetPathfinderHierDebugOverlay = function(player, enabled)
    1977             : {
    1978           0 :         Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder).SetHierDebugOverlay(enabled);
    1979             : };
    1980             : 
    1981           1 : GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled)
    1982             : {
    1983           0 :         Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager).SetDebugOverlay(enabled);
    1984             : };
    1985             : 
    1986           1 : GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
    1987             : {
    1988           0 :         for (let ent of data.entities)
    1989             :         {
    1990           0 :                 let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
    1991           0 :                 if (cmpUnitMotion)
    1992           0 :                         cmpUnitMotion.SetDebugOverlay(data.enabled);
    1993             :         }
    1994             : };
    1995             : 
    1996           1 : GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
    1997             : {
    1998           0 :         Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).SetDebugOverlay(enabled);
    1999             : };
    2000             : 
    2001           1 : GuiInterface.prototype.GetTraderNumber = function(player)
    2002             : {
    2003           0 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    2004           0 :         let traders = cmpRangeManager.GetEntitiesByPlayer(player).filter(e => Engine.QueryInterface(e, IID_Trader));
    2005             : 
    2006           0 :         let landTrader = { "total": 0, "trading": 0, "garrisoned": 0 };
    2007           0 :         let shipTrader = { "total": 0, "trading": 0 };
    2008             : 
    2009           0 :         for (let ent of traders)
    2010             :         {
    2011           0 :                 let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
    2012           0 :                 let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
    2013           0 :                 if (!cmpIdentity || !cmpUnitAI)
    2014           0 :                         continue;
    2015             : 
    2016           0 :                 if (cmpIdentity.HasClass("Ship"))
    2017             :                 {
    2018           0 :                         ++shipTrader.total;
    2019           0 :                         if (cmpUnitAI.order && cmpUnitAI.order.type == "Trade")
    2020           0 :                                 ++shipTrader.trading;
    2021             :                 }
    2022             :                 else
    2023             :                 {
    2024           0 :                         ++landTrader.total;
    2025           0 :                         if (cmpUnitAI.order && cmpUnitAI.order.type == "Trade")
    2026           0 :                                 ++landTrader.trading;
    2027           0 :                         if (cmpUnitAI.order && cmpUnitAI.order.type == "Garrison")
    2028             :                         {
    2029           0 :                                 let holder = cmpUnitAI.order.data.target;
    2030           0 :                                 let cmpHolderUnitAI = Engine.QueryInterface(holder, IID_UnitAI);
    2031           0 :                                 if (cmpHolderUnitAI && cmpHolderUnitAI.order && cmpHolderUnitAI.order.type == "Trade")
    2032           0 :                                         ++landTrader.garrisoned;
    2033             :                         }
    2034             :                 }
    2035             :         }
    2036             : 
    2037           0 :         return { "landTrader": landTrader, "shipTrader": shipTrader };
    2038             : };
    2039             : 
    2040           1 : GuiInterface.prototype.GetTradingGoods = function(player)
    2041             : {
    2042           0 :         let cmpPlayer = QueryPlayerIDInterface(player);
    2043           0 :         if (!cmpPlayer)
    2044           0 :                 return [];
    2045             : 
    2046           0 :         return cmpPlayer.GetTradingGoods();
    2047             : };
    2048             : 
    2049           1 : GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
    2050             : {
    2051           0 :         this.renamedEntities.push(msg);
    2052             : };
    2053             : 
    2054             : /**
    2055             :  * List the GuiInterface functions that can be safely called by GUI scripts.
    2056             :  * (GUI scripts are non-deterministic and untrusted, so these functions must be
    2057             :  * appropriately careful. They are called with a first argument "player", which is
    2058             :  * trusted and indicates the player associated with the current client; no data should
    2059             :  * be returned unless this player is meant to be able to see it.)
    2060             :  */
    2061           1 : let exposedFunctions = {
    2062             : 
    2063             :         "GetSimulationState": 1,
    2064             :         "GetExtendedSimulationState": 1,
    2065             :         "GetInitAttributes": 1,
    2066             :         "GetReplayMetadata": 1,
    2067             :         "GetCampaignGameEndData": 1,
    2068             :         "GetRenamedEntities": 1,
    2069             :         "ClearRenamedEntities": 1,
    2070             :         "GetEntityState": 1,
    2071             :         "GetMultipleEntityStates": 1,
    2072             :         "GetAverageRangeForBuildings": 1,
    2073             :         "GetTemplateData": 1,
    2074             :         "AreRequirementsMet": 1,
    2075             :         "CheckTechnologyRequirements": 1,
    2076             :         "GetStartedResearch": 1,
    2077             :         "GetBattleState": 1,
    2078             :         "GetIncomingAttacks": 1,
    2079             :         "GetNeededResources": 1,
    2080             :         "GetNotifications": 1,
    2081             :         "GetTimeNotifications": 1,
    2082             : 
    2083             :         "GetAvailableFormations": 1,
    2084             :         "GetFormationRequirements": 1,
    2085             :         "CanMoveEntsIntoFormation": 1,
    2086             :         "IsFormationSelected": 1,
    2087             :         "GetFormationInfoFromTemplate": 1,
    2088             :         "IsStanceSelected": 1,
    2089             : 
    2090             :         "UpdateDisplayedPlayerColors": 1,
    2091             :         "SetSelectionHighlight": 1,
    2092             :         "GetAllBuildableEntities": 1,
    2093             :         "SetStatusBars": 1,
    2094             :         "GetPlayerEntities": 1,
    2095             :         "GetNonGaiaEntities": 1,
    2096             :         "DisplayRallyPoint": 1,
    2097             :         "AddTargetMarker": 1,
    2098             :         "SetBuildingPlacementPreview": 1,
    2099             :         "SetWallPlacementPreview": 1,
    2100             :         "GetFoundationSnapData": 1,
    2101             :         "PlaySound": 1,
    2102             :         "PlaySoundForPlayer": 1,
    2103             :         "FindIdleUnits": 1,
    2104             :         "HasIdleUnits": 1,
    2105             :         "GetTradingRouteGain": 1,
    2106             :         "GetTradingDetails": 1,
    2107             :         "CanAttack": 1,
    2108             :         "GetBatchTime": 1,
    2109             : 
    2110             :         "IsMapRevealed": 1,
    2111             :         "SetPathfinderDebugOverlay": 1,
    2112             :         "SetPathfinderHierDebugOverlay": 1,
    2113             :         "SetObstructionDebugOverlay": 1,
    2114             :         "SetMotionDebugOverlay": 1,
    2115             :         "SetRangeDebugOverlay": 1,
    2116             :         "EnableVisualRangeOverlayType": 1,
    2117             :         "SetRangeOverlays": 1,
    2118             : 
    2119             :         "GetTraderNumber": 1,
    2120             :         "GetTradingGoods": 1,
    2121             :         "IsTemplateModified": 1,
    2122             :         "ResetTemplateModified": 1,
    2123             :         "IsSelectionDirty": 1,
    2124             :         "ResetSelectionDirty": 1
    2125             : };
    2126             : 
    2127           1 : GuiInterface.prototype.ScriptCall = function(player, name, args)
    2128             : {
    2129           0 :         if (exposedFunctions[name])
    2130           0 :                 return this[name](player, args);
    2131             : 
    2132           0 :         throw new Error("Invalid GuiInterface Call name \"" + name + "\"");
    2133             : };
    2134             : 
    2135           1 : Engine.RegisterSystemComponentType(IID_GuiInterface, "GuiInterface", GuiInterface);

Generated by: LCOV version 1.14