Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -249,11 +249,6 @@ if (cmpUnitAI) cmpUnitAI.SetTurretStance(); - // Remove the unit's obstruction to avoid interfering with pathing. - let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); - if (cmpObstruction) - cmpObstruction.SetDisableBlockMovementPathfinding(true, true, -1); - isVisiblyGarrisoned = true; } else @@ -371,11 +366,6 @@ break; } - // Reset the obstruction flags to template defaults. - let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); - if (cmpObstruction) - cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1); - if (cmpEntUnitAI) cmpEntUnitAI.Ungarrison(); Index: ps/trunk/binaries/data/mods/public/simulation/components/Gate.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Gate.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Gate.js @@ -15,7 +15,6 @@ Gate.prototype.Init = function() { this.allies = []; - this.ignoreList = []; this.opened = false; this.locked = false; }; @@ -33,11 +32,10 @@ Gate.prototype.OnDiplomacyChanged = function(msg) { - let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) { this.allies = []; - this.ignoreList = []; this.SetupRangeQuery(msg.player); } }; @@ -93,36 +91,11 @@ 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(); }; @@ -135,11 +108,6 @@ 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 @@ -154,9 +122,10 @@ cmpTimer.CancelTimer(this.timer); this.timer = undefined; } - if (this.opened && (this.locked || !this.ShouldOpen())) + + if (this.opened && (this.allies.length == 0 || this.locked)) this.CloseGate(); - else if (!this.opened && this.ShouldOpen()) + else if (!this.opened && this.allies.length) this.OpenGate(); }; @@ -246,19 +215,19 @@ */ Gate.prototype.CloseGate = function() { - let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); + var 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(); + var collisions = cmpObstruction.GetEntitiesBlockingConstruction(); 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); + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + this.timer = cmpTimer.SetTimeout(this.entity, IID_Gate, "OperateGate", 0, {}); } return; } @@ -272,7 +241,7 @@ this.opened = false; PlaySound("gate_closing", this.entity); - let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); if (cmpVisual) cmpVisual.SelectAnimation("gate_closing", true, 1.0); }; Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -193,13 +193,6 @@ // ignore }, - "OrderTargetRenamed": function() { - // By default, trigger an exit-reenter - // so that state preconditions are checked against the new entity - // (there is no reason to assume the target is still valid). - this.SetNextState(this.GetCurrentState()); - }, - // Formation handlers: "FormationLeave": function(msg) { @@ -209,7 +202,7 @@ // Called when being told to walk as part of a formation "Order.FormationWalk": function(msg) { // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove()) + if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) { this.FinishOrder(); return; @@ -269,7 +262,7 @@ "Order.Walk": function(msg) { // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove()) + if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) { this.FinishOrder(); return; @@ -295,7 +288,7 @@ "Order.WalkAndFight": function(msg) { // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove()) + if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) { this.FinishOrder(); return; @@ -322,7 +315,7 @@ "Order.WalkToTarget": function(msg) { // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove()) + if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) { this.FinishOrder(); return; @@ -451,7 +444,7 @@ // If we can't reach the target, but are standing ground, then abandon this attack order. // Unless we're hunting, that's a special case where we should continue attacking our target. - if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || !this.AbleToMove()) + if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || this.IsTurret()) { this.FinishOrder(); return; @@ -477,7 +470,7 @@ }, "Order.Patrol": function(msg) { - if (this.IsAnimal() || !this.AbleToMove()) + if (this.IsAnimal() || this.IsTurret()) { this.FinishOrder(); return; @@ -630,7 +623,7 @@ }, "Order.Garrison": function(msg) { - if (!this.AbleToMove()) + if (this.IsTurret()) { this.SetNextState("IDLE"); return; @@ -1985,7 +1978,6 @@ "Timer": function(msg) { let target = this.order.data.target; - let attackType = this.order.data.attackType; // Check the target is still alive and attackable if (!this.CanAttack(target)) @@ -2008,16 +2000,11 @@ if (!cmpBuildingAI) { let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - cmpAttack.PerformAttack(attackType, target); + cmpAttack.PerformAttack(this.order.data.attackType, target); } - // PerformAttack might have triggered messages that moved us to another state. - if (this.GetCurrentState() != "INDIVIDUAL.COMBAT.ATTACKING") - return; - - // Check we can still reach the target for the next attack - if (this.CheckTargetAttackRange(target, attackType)) + if (this.CheckTargetAttackRange(target, this.order.data.attackType)) { if (this.resyncAnimation) { @@ -3055,7 +3042,6 @@ if (cmpGarrisonHolder.Garrison(this.entity)) { this.isGarrisoned = true; - this.SetImmobile(true); if (this.formationController) { @@ -3387,7 +3373,6 @@ this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to this.isGarrisoned = false; this.isIdle = false; - this.isImmobile = false; // True if the unit is currently unable to move (garrisoned,...) this.finishedOrder = false; // used to find if all formation members finished the order this.heldPosition = undefined; @@ -3487,30 +3472,6 @@ return !this.orderQueue.length || this.orderQueue[0].type == "Garrison"; }; -UnitAI.prototype.SetImmobile = function(immobile) -{ - this.isImmobile = immobile; - Engine.PostMessage(this.entity, MT_UnitAbleToMoveChanged, { - "entity": this.entity, - "ableToMove": this.AbleToMove() - }); -}; - -/** - * @param cmpUnitMotion - optionally pass unitMotion to avoid querying it here - * @returns true if the entity can move, i.e. has UnitMotion and isn't immobile. - */ -UnitAI.prototype.AbleToMove = function(cmpUnitMotion) -{ - if (this.isImmobile || this.IsTurret()) - return false; - - if (!cmpUnitMotion) - cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - - return !!cmpUnitMotion; -}; - UnitAI.prototype.IsFleeing = function() { var state = this.GetCurrentState().split(".").pop(); @@ -3947,8 +3908,9 @@ // If foundation is not ally of entity, or if entity is unpacked siege, // ignore the order. if (!IsOwnedByAllyOfEntity(this.entity, target) && - !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || - checkPacking && this.IsPacking() || this.CanPack() || !this.AbleToMove()) + !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || + checkPacking && this.IsPacking() || + this.CanPack() || this.IsTurret()) return false; // Move a tile outside the building. @@ -4182,32 +4144,24 @@ UnitAI.prototype.OnGlobalEntityRenamed = function(msg) { let changed = false; - let currentOrderChanged = false; - for (let i = 0; i < this.orderQueue.length; ++i) + for (let order of this.orderQueue) { - let order = this.orderQueue[i]; if (order.data && order.data.target && order.data.target == msg.entity) { changed = true; - if (i == 0) - currentOrderChanged = true; order.data.target = msg.newentity; } if (order.data && order.data.formationTarget && order.data.formationTarget == msg.entity) { changed = true; - if (i == 0) - currentOrderChanged = true; order.data.formationTarget = msg.newentity; } } - if (!changed) - return; + if (this.repairTarget && this.repairTarget == msg.entity) + this.repairTarget = msg.newentity; - if (currentOrderChanged) - this.UnitFsm.ProcessMessage(this, { "type": "OrderTargetRenamed", "data": msg }); - - Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); + if (changed) + Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); }; UnitAI.prototype.OnAttacked = function(msg) @@ -4538,13 +4492,13 @@ UnitAI.prototype.MoveToPoint = function(x, z) { let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToPointRange(x, z, 0, 0); // For point goals, allow a max range of 0. + return cmpUnitMotion && cmpUnitMotion.MoveToPointRange(x, z, 0, 0); // For point goals, allow a max range of 0. }; UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax) { let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax); + return cmpUnitMotion && cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax); }; UnitAI.prototype.MoveToTarget = function(target) @@ -4553,12 +4507,12 @@ return false; let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, 0, 1); + return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, 0, 1); }; UnitAI.prototype.MoveToTargetRange = function(target, iid, type) { - if (!this.CheckTargetVisible(target)) + if (!this.CheckTargetVisible(target) || this.IsTurret()) return false; let range = this.GetRange(iid, type); @@ -4566,7 +4520,7 @@ return false; let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); + return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); }; /** @@ -4584,10 +4538,6 @@ return false; } - let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - if (!this.AbleToMove(cmpUnitMotion)) - return false; - let cmpFormation = Engine.QueryInterface(target, IID_Formation); if (cmpFormation) target = cmpFormation.GetClosestMember(this.entity); @@ -4624,6 +4574,7 @@ // The parabole changes while walking so be cautious: let guessedMaxRange = parabolicMaxRange > range.max ? (range.max + parabolicMaxRange) / 2 : parabolicMaxRange; + let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange); }; @@ -4633,7 +4584,7 @@ return false; let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, min, max); + return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, min, max); }; /** @@ -4648,7 +4599,7 @@ if (cmpTargetFormation) target = cmpTargetFormation.GetClosestMember(this.entity); - if (!this.CheckTargetVisible(target)) + if (!this.CheckTargetVisible(target) || this.IsTurret()) return false; let cmpFormationAttack = Engine.QueryInterface(this.entity, IID_Attack); @@ -4657,7 +4608,7 @@ let range = cmpFormationAttack.GetRange(target); let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); + return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); }; UnitAI.prototype.MoveToGarrisonRange = function(target) @@ -4671,7 +4622,7 @@ var range = cmpGarrisonHolder.GetLoadingRange(); let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); + return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); }; /** @@ -5056,7 +5007,7 @@ */ UnitAI.prototype.ShouldChaseTargetedEntity = function(target, force) { - if (!this.AbleToMove()) + if (this.IsTurret()) return false; if (this.GetStance().respondChase) @@ -5382,11 +5333,11 @@ { // If we're already being told to leave a foundation, then // ignore this new request so we don't end up being too indecisive - // to ever actually move anywhere. + // to ever actually move anywhere + // Ignore also the request if we are packing if (this.order && (this.order.type == "LeaveFoundation" || (this.order.type == "Flee" && this.order.data.target == target))) return; - // Ignore also the request if we are packing. if (this.orderQueue.length && this.orderQueue[0].type == "Unpack" && this.WillMoveFromFoundation(target, false)) { let cmpPack = Engine.QueryInterface(this.entity, IID_Pack); @@ -5448,10 +5399,7 @@ UnitAI.prototype.Ungarrison = function() { if (this.IsGarrisoned()) - { - this.SetImmobile(false); this.AddOrder("Ungarrison", null, false); - } }; /** Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/UnitAI.js @@ -7,13 +7,6 @@ Engine.RegisterMessageType("UnitIdleChanged"); /** - * Message of the form { "ableToMove": boolean } - * sent from UnitAI whenever the unit's ability to move changes. - */ -Engine.RegisterMessageType("UnitAbleToMoveChanged"); - - -/** * Message of the form { "to": string } * where "to" value is a UnitAI stance, * sent from UnitAI whenever the unit's stance changes. Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -1,10 +1,8 @@ Engine.LoadHelperScript("FSM.js"); Engine.LoadHelperScript("Entity.js"); Engine.LoadHelperScript("Player.js"); -Engine.LoadHelperScript("Sound.js"); Engine.LoadComponentScript("interfaces/Attack.js"); Engine.LoadComponentScript("interfaces/Auras.js"); -Engine.LoadComponentScript("interfaces/Builder.js"); Engine.LoadComponentScript("interfaces/BuildingAI.js"); Engine.LoadComponentScript("interfaces/Capturable.js"); Engine.LoadComponentScript("interfaces/Resistance.js"); @@ -19,87 +17,6 @@ Engine.LoadComponentScript("Formation.js"); Engine.LoadComponentScript("UnitAI.js"); -/** - * Fairly straightforward test that entity renaming is handled - * by unitAI states. These ought to be augmented with integration tests, ideally. - */ -function TestTargetEntityRenaming(init_state, post_state, setup) -{ - ResetState(); - const player_ent = 5; - const target_ent = 6; - - AddMock(SYSTEM_ENTITY, IID_Timer, { - "SetInterval": () => {}, - "SetTimeout": () => {} - }); - AddMock(SYSTEM_ENTITY, IID_ObstructionManager, { - "IsInTargetRange": () => false - }); - - let unitAI = ConstructComponent(player_ent, "UnitAI", { - "FormationController": "false", - "DefaultStance": "aggressive", - "FleeDistance": 10 - }); - unitAI.OnCreate(); - - setup(unitAI, player_ent, target_ent); - - TS_ASSERT_EQUALS(unitAI.GetCurrentState(), init_state); - - unitAI.OnGlobalEntityRenamed({ - "entity": target_ent, - "newentity": target_ent + 1 - }); - - TS_ASSERT_EQUALS(unitAI.GetCurrentState(), post_state); -} - -TestTargetEntityRenaming( - "INDIVIDUAL.GARRISON.APPROACHING", "INDIVIDUAL.IDLE", - (unitAI, player_ent, target_ent) => { - unitAI.CanGarrison = (target) => target == target_ent; - unitAI.MoveToGarrisonRange = (target) => target == target_ent; - unitAI.AbleToMove = () => true; - - AddMock(target_ent, IID_GarrisonHolder, { - "CanPickup": () => false - }); - - unitAI.Garrison(target_ent, false); - } -); - -TestTargetEntityRenaming( - "INDIVIDUAL.REPAIR.REPAIRING", "INDIVIDUAL.IDLE", - (unitAI, player_ent, target_ent) => { - QueryBuilderListInterface = () => {}; - unitAI.CheckTargetRange = () => true; - unitAI.CanRepair = (target) => target == target_ent; - - unitAI.Repair(target_ent, false, false); - } -); - - -TestTargetEntityRenaming( - "INDIVIDUAL.FLEEING", "INDIVIDUAL.FLEEING", - (unitAI, player_ent, target_ent) => { - DistanceBetweenEntities = () => 10; - unitAI.CheckTargetRangeExplicit = () => false; - - AddMock(player_ent, IID_UnitMotion, { - "MoveToTargetRange": () => true, - "GetRunMultiplier": () => 1, - "SetSpeedMultiplier": () => {}, - "StopMoving": () => {} - }); - - unitAI.Flee(target_ent, false); - } -); - /* Regression test. * Tests the FSM behaviour of a unit when walking as part of a formation, * then exiting the formation. Index: ps/trunk/source/simulation2/components/CCmpObstruction.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpObstruction.cpp +++ ps/trunk/source/simulation2/components/CCmpObstruction.cpp @@ -651,11 +651,6 @@ return ret; } - virtual std::vector GetEntitiesBlockingMovement() const - { - return GetEntitiesByFlags(ICmpObstructionManager::FLAG_BLOCK_MOVEMENT); - } - virtual std::vector GetEntitiesBlockingConstruction() const { return GetEntitiesByFlags(ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); Index: ps/trunk/source/simulation2/components/ICmpObstruction.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstruction.h +++ ps/trunk/source/simulation2/components/ICmpObstruction.h @@ -105,12 +105,6 @@ virtual std::vector GetEntitiesByFlags(ICmpObstructionManager::flags_t flags) const = 0; /** - * Returns a list of entities that are blocking movement. - * @return vector of blocking entities - */ - virtual std::vector GetEntitiesBlockingMovement() const = 0; - - /** * Returns a list of entities that are blocking construction of a foundation. * @return vector of blocking entities */ Index: ps/trunk/source/simulation2/components/ICmpObstruction.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstruction.cpp +++ ps/trunk/source/simulation2/components/ICmpObstruction.cpp @@ -50,7 +50,6 @@ DEFINE_INTERFACE_METHOD_CONST_0("CheckShorePlacement", bool, ICmpObstruction, CheckShorePlacement) DEFINE_INTERFACE_METHOD_CONST_2("CheckFoundation", std::string, ICmpObstruction, CheckFoundation_wrapper, std::string, bool) DEFINE_INTERFACE_METHOD_CONST_0("CheckDuplicateFoundation", bool, ICmpObstruction, CheckDuplicateFoundation) -DEFINE_INTERFACE_METHOD_CONST_0("GetEntitiesBlockingMovement", std::vector, ICmpObstruction, GetEntitiesBlockingMovement) DEFINE_INTERFACE_METHOD_CONST_0("GetEntitiesBlockingConstruction", std::vector, ICmpObstruction, GetEntitiesBlockingConstruction) DEFINE_INTERFACE_METHOD_CONST_0("GetEntitiesDeletedUponConstruction", std::vector, ICmpObstruction, GetEntitiesDeletedUponConstruction) DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool) Index: ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h +++ ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h @@ -41,7 +41,6 @@ virtual std::string CheckFoundation_wrapper(const std::string& UNUSED(className), bool UNUSED(onlyCenterPoint)) const { return std::string(); } virtual bool CheckDuplicateFoundation() const { return true; } virtual std::vector GetEntitiesByFlags(ICmpObstructionManager::flags_t UNUSED(flags)) const { return std::vector(); } - virtual std::vector GetEntitiesBlockingMovement() const { return std::vector(); } virtual std::vector GetEntitiesBlockingConstruction() const { return std::vector(); } virtual std::vector GetEntitiesDeletedUponConstruction() const { return std::vector(); } virtual void ResolveFoundationCollisions() const { }