Index: ps/trunk/binaries/data/mods/public/simulation/components/Gate.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Gate.js (revision 23710) +++ ps/trunk/binaries/data/mods/public/simulation/components/Gate.js (revision 23711) @@ -1,294 +1,280 @@ function Gate() {} Gate.prototype.Schema = "Controls behavior of wall gates" + "" + "20" + "" + "" + "" + ""; /** * Initialize Gate component */ Gate.prototype.Init = function() { this.allies = []; this.ignoreList = []; this.opened = false; this.locked = false; }; -/** - * Handle the renaming case (from long-wall to gate) - * because that does not trigger ownershipchange or diplomacy change - * and units don't get added to the ignorelist. - */ -Gate.prototype.OnEntityRenamed = function(msg) -{ - let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - if (!cmpOwnership || cmpOwnership.GetOwner() == INVALID_PLAYER) - return; - - this.SetupRangeQuery(); -}; - Gate.prototype.OnOwnershipChanged = function(msg) { if (msg.to != INVALID_PLAYER) { this.SetupRangeQuery(msg.to); // Set the initial state, but don't play unlocking sound if (!this.locked) this.UnlockGate(true); } }; Gate.prototype.OnDiplomacyChanged = function(msg) { let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) { this.allies = []; this.ignoreList = []; this.SetupRangeQuery(msg.player); } }; /** * Cleanup on destroy */ Gate.prototype.OnDestroy = function() { // Clean up range query var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (this.unitsQuery) cmpRangeManager.DestroyActiveQuery(this.unitsQuery); // Cancel the closing-blocked timer if it's running. if (this.timer) { var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(this.timer); this.timer = undefined; } }; /** * Setup the range query to detect units coming in & out of range */ Gate.prototype.SetupRangeQuery = function(owner) { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (this.unitsQuery) cmpRangeManager.DestroyActiveQuery(this.unitsQuery); // Only allied units can make the gate open. var players = QueryPlayerIDInterface(owner).GetAllies(); var range = this.GetPassRange(); if (range > 0) { // Only find entities with IID_UnitAI interface this.unitsQuery = cmpRangeManager.CreateActiveQuery(this.entity, 0, range, players, IID_UnitAI, cmpRangeManager.GetEntityFlagMask("normal")); cmpRangeManager.EnableActiveQuery(this.unitsQuery); } }; /** * Called when units enter or leave range */ Gate.prototype.OnRangeUpdate = function(msg) { if (msg.tag != this.unitsQuery) return; if (msg.added.length > 0) for (let entity of msg.added) { // Ignore entities that cannot move as those won't be able to go through the gate. let unitAI = Engine.QueryInterface(entity, IID_UnitAI); if (!unitAI.AbleToMove()) this.ignoreList.push(entity); this.allies.push(entity); } if (msg.removed.length > 0) for (let entity of msg.removed) { let index = this.ignoreList.indexOf(entity); if (index !== -1) this.ignoreList.splice(index, 1); this.allies.splice(this.allies.indexOf(entity), 1); } this.OperateGate(); }; Gate.prototype.OnGlobalUnitAbleToMoveChanged = function(msg) { if (this.allies.indexOf(msg.entity) === -1) return; let index = this.ignoreList.indexOf(msg.entity); if (msg.ableToMove && index !== -1) this.ignoreList.splice(index, 1); else if (!msg.ableToMove && index === -1) this.ignoreList.push(msg.entity); this.OperateGate(); }; /** * Get the range in which units are detected */ Gate.prototype.GetPassRange = function() { return +this.template.PassRange; }; Gate.prototype.ShouldOpen = function() { return this.allies.some(ent => this.ignoreList.indexOf(ent) === -1); }; /** * Attempt to open or close the gate. * An ally must be in range to open the gate, but an unlocked gate will only close * if there are no allies in range and no units are inside the gate's obstruction. */ Gate.prototype.OperateGate = function() { // Cancel the closing-blocked timer if it's running. if (this.timer) { var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(this.timer); this.timer = undefined; } if (this.opened && (this.locked || !this.ShouldOpen())) this.CloseGate(); else if (!this.opened && this.ShouldOpen()) this.OpenGate(); }; Gate.prototype.IsLocked = function() { return this.locked; }; /** * Lock the gate, with sound. It will close at the next opportunity. */ Gate.prototype.LockGate = function() { this.locked = true; // Delete animal corpses to prevent units trying to gather the unreachable entity let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); if (cmpObstruction && cmpObstruction.GetBlockMovementFlag()) for (let ent of cmpObstruction.GetEntitiesDeletedUponConstruction()) Engine.DestroyEntity(ent); // If the door is closed, enable 'block pathfinding' // Else 'block pathfinding' will be enabled the next time the gate close if (!this.opened) { if (cmpObstruction) cmpObstruction.SetDisableBlockMovementPathfinding(false, false, 0); } else this.OperateGate(); // TODO: Possibly move the lock/unlock sounds to UI? Needs testing PlaySound("gate_locked", this.entity); }; /** * Unlock the gate, with sound. May open the gate if allied units are within range. * If quiet is true, no sound will be played (used for initial setup). */ Gate.prototype.UnlockGate = function(quiet) { var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); if (!cmpObstruction) return; // Disable 'block pathfinding' cmpObstruction.SetDisableBlockMovementPathfinding(this.opened, true, 0); this.locked = false; // TODO: Possibly move the lock/unlock sounds to UI? Needs testing if (!quiet) PlaySound("gate_unlocked", this.entity); // If the gate is closed, open it if necessary if (!this.opened) this.OperateGate(); }; /** * Open the gate if unlocked, with sound and animation. */ Gate.prototype.OpenGate = function() { // Do not open the gate if it has been locked if (this.locked) return; var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); if (!cmpObstruction) return; // Disable 'block movement' cmpObstruction.SetDisableBlockMovementPathfinding(true, true, 0); this.opened = true; PlaySound("gate_opening", this.entity); var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); if (cmpVisual) cmpVisual.SelectAnimation("gate_opening", true, 1.0); }; /** * Close the gate, with sound and animation. * * The gate may fail to close due to unit obstruction. If this occurs, the * gate will start a timer and attempt to close on each simulation update. */ Gate.prototype.CloseGate = function() { let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); if (!cmpObstruction) return; // The gate can't be closed if there are entities colliding with it. let collisions = cmpObstruction.GetEntitiesBlockingMovement(); if (collisions.length) { if (!this.timer) { // Set an "instant" timer which will run on the next simulation turn. let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); this.timer = cmpTimer.SetTimeout(this.entity, IID_Gate, "OperateGate", 0); } return; } // If we ordered the gate to be locked, enable 'block movement' and 'block pathfinding' if (this.locked) cmpObstruction.SetDisableBlockMovementPathfinding(false, false, 0); // Else just enable 'block movement' else cmpObstruction.SetDisableBlockMovementPathfinding(false, true, 0); this.opened = false; PlaySound("gate_closing", this.entity); let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); if (cmpVisual) cmpVisual.SelectAnimation("gate_closing", true, 1.0); }; Engine.RegisterComponentType(IID_Gate, "Gate", Gate);