LCOV - code coverage report
Current view: top level - simulation/components - Gate.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 100 126 79.4 %
Date: 2023-04-02 12:52:40 Functions: 16 17 94.1 %

          Line data    Source code
       1             : function Gate() {}
       2             : 
       3           1 : Gate.prototype.Schema =
       4             :         "<a:help>Controls behavior of wall gates</a:help>" +
       5             :         "<a:example>" +
       6             :                 "<PassRange>20</PassRange>" +
       7             :         "</a:example>" +
       8             :         "<element name='PassRange' a:help='Units must be within this distance (in meters) of the gate for it to open'>" +
       9             :                 "<ref name='nonNegativeDecimal'/>" +
      10             :         "</element>";
      11             : 
      12             : /**
      13             :  * Initialize Gate component
      14             :  */
      15           1 : Gate.prototype.Init = function()
      16             : {
      17           2 :         this.allies = [];
      18           2 :         this.ignoreList = [];
      19           2 :         this.opened = false;
      20           2 :         this.locked = false;
      21             : };
      22             : 
      23           1 : Gate.prototype.OnOwnershipChanged = function(msg)
      24             : {
      25           2 :         if (msg.to != INVALID_PLAYER)
      26             :         {
      27           2 :                 this.SetupRangeQuery(msg.to);
      28             :                 // Set the initial state, but don't play unlocking sound
      29           2 :                 if (!this.locked)
      30           1 :                         this.UnlockGate(true);
      31             :         }
      32             : };
      33             : 
      34           1 : Gate.prototype.OnDiplomacyChanged = function(msg)
      35             : {
      36           1 :         let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
      37           1 :         if (cmpOwnership && cmpOwnership.GetOwner() == msg.player)
      38             :         {
      39           1 :                 this.allies = [];
      40           1 :                 this.ignoreList = [];
      41           1 :                 this.SetupRangeQuery(msg.player);
      42             :         }
      43             : };
      44             : 
      45             : /**
      46             :  * Cleanup on destroy
      47             :  */
      48           1 : Gate.prototype.OnDestroy = function()
      49             : {
      50             :         // Clean up range query
      51           0 :         var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
      52           0 :         if (this.unitsQuery)
      53           0 :                 cmpRangeManager.DestroyActiveQuery(this.unitsQuery);
      54             : 
      55             :         // Cancel the closing-blocked timer if it's running.
      56           0 :         if (this.timer)
      57             :         {
      58           0 :                 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
      59           0 :                 cmpTimer.CancelTimer(this.timer);
      60           0 :                 this.timer = undefined;
      61             :         }
      62             : };
      63             : 
      64             : /**
      65             :  * Setup the range query to detect units coming in & out of range
      66             :  */
      67           1 : Gate.prototype.SetupRangeQuery = function(owner)
      68             : {
      69           3 :         var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
      70             : 
      71           3 :         if (this.unitsQuery)
      72           0 :                 cmpRangeManager.DestroyActiveQuery(this.unitsQuery);
      73             : 
      74             :         // Only allied units can make the gate open.
      75           3 :         var players = QueryPlayerIDInterface(owner).GetAllies();
      76             : 
      77           3 :         var range = this.GetPassRange();
      78           3 :         if (range > 0)
      79             :         {
      80             :                 // Only find entities with IID_UnitAI interface
      81           3 :                 this.unitsQuery = cmpRangeManager.CreateActiveQuery(this.entity, 0, range, players, IID_UnitAI, cmpRangeManager.GetEntityFlagMask("normal"), true);
      82           3 :                 cmpRangeManager.EnableActiveQuery(this.unitsQuery);
      83             :         }
      84             : };
      85             : 
      86             : /**
      87             :  * Called when units enter or leave range
      88             :  */
      89           1 : Gate.prototype.OnRangeUpdate = function(msg)
      90             : {
      91           3 :         if (msg.tag != this.unitsQuery)
      92           0 :                 return;
      93             : 
      94           3 :         if (msg.added.length > 0)
      95           2 :                 for (let entity of msg.added)
      96             :                 {
      97             :                         // Ignore entities that cannot move as those won't be able to go through the gate.
      98           2 :                         let unitAI = Engine.QueryInterface(entity, IID_UnitAI);
      99           2 :                         if (!unitAI || !unitAI.AbleToMove())
     100           0 :                                 this.ignoreList.push(entity);
     101           2 :                         this.allies.push(entity);
     102             :                 }
     103             : 
     104           3 :         if (msg.removed.length > 0)
     105           1 :                 for (let entity of msg.removed)
     106             :                 {
     107           1 :                         let index = this.ignoreList.indexOf(entity);
     108           1 :                         if (index !== -1)
     109           1 :                                 this.ignoreList.splice(index, 1);
     110           1 :                         this.allies.splice(this.allies.indexOf(entity), 1);
     111             :                 }
     112             : 
     113           3 :         this.OperateGate();
     114             : };
     115             : 
     116           1 : Gate.prototype.OnGlobalUnitAbleToMoveChanged = function(msg)
     117             : {
     118           1 :         if (this.allies.indexOf(msg.entity) === -1)
     119           0 :                 return;
     120             : 
     121           1 :         let index = this.ignoreList.indexOf(msg.entity);
     122           1 :         if (msg.ableToMove && index !== -1)
     123           0 :                 this.ignoreList.splice(index, 1);
     124           1 :         else if (!msg.ableToMove && index === -1)
     125           1 :                 this.ignoreList.push(msg.entity);
     126             : 
     127           1 :         this.OperateGate();
     128             : };
     129             : 
     130             : /**
     131             :  * Get the range in which units are detected
     132             :  */
     133           1 : Gate.prototype.GetPassRange = function()
     134             : {
     135           3 :         return +this.template.PassRange;
     136             : };
     137             : 
     138           1 : Gate.prototype.ShouldOpen = function()
     139             : {
     140          10 :         return this.allies.some(ent => this.ignoreList.indexOf(ent) === -1);
     141             : };
     142             : 
     143             : /**
     144             :  * Attempt to open or close the gate.
     145             :  * An ally must be in range to open the gate, but an unlocked gate will only close
     146             :  * if there are no allies in range and no units are inside the gate's obstruction.
     147             :  */
     148           1 : Gate.prototype.OperateGate = function()
     149             : {
     150             :         // Cancel the closing-blocked timer if it's running.
     151           7 :         if (this.timer)
     152             :         {
     153           0 :                 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     154           0 :                 cmpTimer.CancelTimer(this.timer);
     155           0 :                 this.timer = undefined;
     156             :         }
     157           7 :         if (this.opened && (this.locked || !this.ShouldOpen()))
     158           2 :                 this.CloseGate();
     159           5 :         else if (!this.opened && this.ShouldOpen())
     160           3 :                 this.OpenGate();
     161             : };
     162             : 
     163           1 : Gate.prototype.IsLocked = function()
     164             : {
     165           1 :         return this.locked;
     166             : };
     167             : 
     168             : /**
     169             :  * Lock the gate, with sound. It will close at the next opportunity.
     170             :  */
     171           1 : Gate.prototype.LockGate = function()
     172             : {
     173           2 :         this.locked = true;
     174             : 
     175             :         // Delete animal corpses to prevent units trying to gather the unreachable entity
     176           2 :         let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     177           2 :         if (cmpObstruction && cmpObstruction.GetBlockMovementFlag(true))
     178           0 :                 for (let ent of cmpObstruction.GetEntitiesDeletedUponConstruction())
     179           0 :                         Engine.DestroyEntity(ent);
     180             : 
     181             :         // If the door is closed, enable 'block pathfinding'
     182             :         // Else 'block pathfinding' will be enabled the next time the gate close
     183           2 :         if (!this.opened)
     184             :         {
     185           1 :                 if (cmpObstruction)
     186           1 :                         cmpObstruction.SetDisableBlockMovementPathfinding(false, false, 0);
     187             :         }
     188             :         else
     189           1 :                 this.OperateGate();
     190             : 
     191             :         // TODO: Possibly move the lock/unlock sounds to UI? Needs testing
     192           2 :         PlaySound("gate_locked", this.entity);
     193             : };
     194             : 
     195             : /**
     196             :  * Unlock the gate, with sound. May open the gate if allied units are within range.
     197             :  * If quiet is true, no sound will be played (used for initial setup).
     198             :  */
     199           1 : Gate.prototype.UnlockGate = function(quiet)
     200             : {
     201           2 :         var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     202           2 :         if (!cmpObstruction)
     203           0 :                 return;
     204             : 
     205             :         // Disable 'block pathfinding'
     206           2 :         cmpObstruction.SetDisableBlockMovementPathfinding(this.opened, true, 0);
     207           2 :         this.locked = false;
     208             : 
     209             :         // TODO: Possibly move the lock/unlock sounds to UI? Needs testing
     210           2 :         if (!quiet)
     211           1 :                 PlaySound("gate_unlocked", this.entity);
     212             : 
     213             :         // If the gate is closed, open it if necessary
     214           2 :         if (!this.opened)
     215           2 :                 this.OperateGate();
     216             : };
     217             : 
     218             : /**
     219             :  * Open the gate if unlocked, with sound and animation.
     220             :  */
     221           1 : Gate.prototype.OpenGate = function()
     222             : {
     223             :         // Do not open the gate if it has been locked
     224           3 :         if (this.locked)
     225           1 :                 return;
     226             : 
     227           2 :         var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     228           2 :         if (!cmpObstruction)
     229           0 :                 return;
     230             : 
     231             :         // Disable 'block movement'
     232           2 :         cmpObstruction.SetDisableBlockMovementPathfinding(true, true, 0);
     233           2 :         this.opened = true;
     234             : 
     235           2 :         PlaySound("gate_opening", this.entity);
     236           2 :         var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     237           2 :         if (cmpVisual)
     238           0 :                 cmpVisual.SelectAnimation("gate_opening", true, 1.0);
     239             : };
     240             : 
     241             : /**
     242             :  * Close the gate, with sound and animation.
     243             :  *
     244             :  * The gate may fail to close due to unit obstruction. If this occurs, the
     245             :  * gate will start a timer and attempt to close on each simulation update.
     246             :  */
     247           1 : Gate.prototype.CloseGate = function()
     248             : {
     249           2 :         let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     250           2 :         if (!cmpObstruction)
     251           0 :                 return;
     252             : 
     253             :         // The gate can't be closed if there are entities colliding with it.
     254             :         // NB: because walls are overlapping, they requires special care to not break
     255             :         // in particular, walls do not block construction, so walls from skirmish maps
     256             :         // do not appear in this check even if they have different control groups from the gate.
     257             :         // This no longer works if gates are made to check for entities blocking movement.
     258             :         // Fixing that would let us change this code, but it sounds decidedly non-trivial.
     259           2 :         let collisions = cmpObstruction.GetEntitiesBlockingConstruction();
     260           2 :         if (collisions.length)
     261             :         {
     262           0 :                 if (!this.timer)
     263             :                 {
     264             :                         // Set an "instant" timer which will run on the next simulation turn.
     265           0 :                         let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     266           0 :                         this.timer = cmpTimer.SetTimeout(this.entity, IID_Gate, "OperateGate", 0);
     267             :                 }
     268           0 :                 return;
     269             :         }
     270             : 
     271             :         // If we ordered the gate to be locked, enable 'block movement' and 'block pathfinding'
     272           2 :         if (this.locked)
     273           1 :                 cmpObstruction.SetDisableBlockMovementPathfinding(false, false, 0);
     274             :         // Else just enable 'block movement'
     275             :         else
     276           1 :                 cmpObstruction.SetDisableBlockMovementPathfinding(false, true, 0);
     277           2 :         this.opened = false;
     278             : 
     279           2 :         PlaySound("gate_closing", this.entity);
     280           2 :         let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     281           2 :         if (cmpVisual)
     282           0 :                 cmpVisual.SelectAnimation("gate_closing", true, 1.0);
     283             : };
     284             : 
     285           1 : Engine.RegisterComponentType(IID_Gate, "Gate", Gate);

Generated by: LCOV version 1.14