Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -132,6 +132,20 @@ } }; +// These orders always require a packed unit, so if a unit that is unpacking is given one of these orders, +// it can immediately cancel unpacking. +var g_OrdersCancelUnpacking = new Set([ + "FormationWalk", + "Walk", + "WalkAndFight", + "WalkToTarget", + "Patrol", + "Garrison" +]); + +// When leaving a foundation, we want to be clear of it by this distance. +var g_LeaveFoundationRange = 4; + // See ../helpers/FSM.js for some documentation of this FSM specification syntax UnitAI.prototype.UnitFsmSpec = { @@ -212,22 +226,13 @@ "Order.LeaveFoundation": function(msg) { // If foundation is not ally of entity, or if entity is unpacked siege, // ignore the order - if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) && !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || - this.IsPacking() || this.CanPack() || this.IsTurret()) + if (!this.WillMoveFromFoundation(msg.data.target)) { this.FinishOrder(); return; } - - // Move a tile outside the building if necessary. - let range = 4; - if (this.CheckTargetRangeExplicit(msg.data.target, range, -1)) - this.FinishOrder(); - else - { - this.order.data.min = range; - this.SetNextState("INDIVIDUAL.WALKING"); - } + this.order.data.min = g_LeaveFoundationRange; + this.SetNextState("INDIVIDUAL.WALKING"); }, // Individual orders: @@ -424,6 +429,10 @@ return; } + // Cancel any current packing order + if (!this.EnsureCorrectPackStateForAttack(true)) + return; + if (this.IsAnimal()) this.SetNextState("ANIMAL.COMBAT.ATTACKING"); else @@ -448,6 +457,10 @@ return; } + // If we're currently packing/unpacking, make sure we are packed, so we can move. + if (!this.EnsureCorrectPackStateForAttack(false)) + return; + if (this.IsAnimal()) this.SetNextState("ANIMAL.COMBAT.APPROACHING"); else @@ -1260,24 +1273,13 @@ "Order.LeaveFoundation": function(msg) { // If foundation is not ally of entity, or if entity is unpacked siege, // ignore the order - if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) && !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || - this.IsPacking() || this.CanPack() || this.IsTurret()) + if (!this.WillMoveFromFoundation(msg.data.target)) { this.FinishOrder(); return; } - // Move a tile outside the building - let range = 4; - if (this.CheckTargetRangeExplicit(msg.data.target, range, -1)) - { - // We are already at the target, or can't move at all - this.FinishOrder(); - } - else - { - this.order.data.min = range; - this.SetNextState("WALKINGTOPOINT"); - } + this.order.data.min = g_LeaveFoundationRange; + this.SetNextState("WALKINGTOPOINT"); }, "enter": function() { @@ -3115,13 +3117,12 @@ "Order.LeaveFoundation": function(msg) { // Move a tile outside the building - let range = 4; - if (this.CheckTargetRangeExplicit(msg.data.target, range, -1)) + if (this.CheckTargetRangeExplicit(msg.data.target, g_LeaveFoundationRange, -1)) { this.FinishOrder(); return; } - this.order.data.min = range; + this.order.data.min = g_LeaveFoundationRange; this.SetNextState("WALKING"); }, @@ -3649,7 +3650,7 @@ * Add an order onto the front of the queue, * and execute it immediately. */ -UnitAI.prototype.PushOrderFront = function(type, data) +UnitAI.prototype.PushOrderFront = function(type, data, ignorePacking = false) { var order = { "type": type, "data": data }; // If current order is cheering then add new order after it @@ -3659,7 +3660,7 @@ var cheeringOrder = this.orderQueue.shift(); this.orderQueue.unshift(cheeringOrder, order); } - else if (this.order && this.IsPacking()) + else if (!ignorePacking && this.order && this.IsPacking()) { var packingOrder = this.orderQueue.shift(); this.orderQueue.unshift(packingOrder, order); @@ -3713,6 +3714,69 @@ Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); }; +/** + * For a unit that is packing and trying to attack something, + * either cancel packing or continue with packing, as appropriate. + * Precondition: if the unit is packing/unpacking, then orderQueue + * should have the Attack order at index 0, + * and the Pack/Unpack order at index 1. + * This precondition holds because if we are packing while processing "Order.Attack", + * then we must have come from ReplaceOrder, which guarantees it. + * + * @param preferUnpacked - true if the unit needs to be unpacked to continue attacking, + * false if it needs to be packed. + * @return true if the unit can attack now, false if it must continue packing (or unpacking) first. + */ +UnitAI.prototype.EnsureCorrectPackStateForAttack = function(preferUnpacked) +{ + let cmpPack = Engine.QueryInterface(this.entity, IID_Pack); + if (!cmpPack || !cmpPack.IsPacking()) + return true; + if (this.orderQueue.length != 2 || + this.orderQueue[0].type != "Attack" || + (this.orderQueue[1].type != "Pack" && this.orderQueue[1].type != "Unpack")) + return true; // violated precondition; should never happen. + if(cmpPack.IsPacked() ^ preferUnpacked) + { + // The unit is already in the packed/unpacked state we want. + // Delete the packing order. + this.orderQueue.splice(1,1); + cmpPack.CancelPack(); + Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); + // Continue with the attack order. + return true; + } + // Move the attack order behind the unpacking order, to continue unpacking. + let tmp = this.orderQueue[0]; + this.orderQueue[0] = this.orderQueue[1]; + this.orderQueue[1] = tmp; + Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); + return false; +}; + +UnitAI.prototype.WillMoveFromFoundation = function(target, checkPacking = true) +{ + // 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()) + return false; + + if (checkPacking && this.IsPacking()) + return false; + + if (this.CanPack()) + return false; + + if (this.IsTurret()) + return false; + + // Move a tile outside the building. + if (this.CheckTargetRangeExplicit(target, g_LeaveFoundationRange, -1)) + return false; + + return true; +}; + UnitAI.prototype.ReplaceOrder = function(type, data) { // Remember the previous work orders to be able to go back to them later if required @@ -3740,7 +3804,22 @@ { var order = { "type": type, "data": data }; var packingOrder = this.orderQueue.shift(); - this.orderQueue = [packingOrder, order]; + if (type == "Attack") + { + // The Attack order is able to handle a packing unit, while other orders can't. + this.orderQueue = [packingOrder]; + this.PushOrderFront(type, data, true); + } + else if (packingOrder.type == "Unpack" && g_OrdersCancelUnpacking.has(type)) + { + // Immediately cancel unpacking before processing an order that demands a packed unit. + let cmpPack = Engine.QueryInterface(this.entity, IID_Pack); + cmpPack.CancelPack(); + this.orderQueue = []; + this.PushOrder(type, data); + } + else + this.orderQueue = [packingOrder, order]; } else { @@ -5065,9 +5144,25 @@ // ignore this new request so we don't end up being too indecisive // 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) || this.IsPacking())) + if (this.order && (this.order.type == "LeaveFoundation" || (this.order.type == "Flee" && this.order.data.target == target))) return; + if (this.IsPacking() && this.WillMoveFromFoundation(target, false)) + { + let packingOrder = this.orderQueue[0]; + if (packingOrder.type == "Unpack") + { + // Immediately cancel unpacking before processing an order that demands a packed unit. + let cmpPack = Engine.QueryInterface(this.entity, IID_Pack); + cmpPack.CancelPack(); + // Note that the unpacking order remains on the queue, so we will resume unpacking + // after the LeaveFoundation order. + } + } + + if (this.IsPacking()) + return; + this.PushOrderFront("LeaveFoundation", { "target": target, "force": true }); };