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,10 @@ } }; +// 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"]); + // See ../helpers/FSM.js for some documentation of this FSM specification syntax UnitAI.prototype.UnitFsmSpec = { @@ -417,6 +421,17 @@ }, "Order.Attack": function(msg) { + // If we're packing, we came from ReplaceOrder and need packing to be the second order for now + // so that if we abort the Attack order we can resume packing. + let cmpPack = Engine.QueryInterface(this.entity, IID_Pack); + if (cmpPack && cmpPack.IsPacking()) + { + if (cmpPack.IsPacked()) + this.orderQueue.push({"type": "Unpack", "data": {force: "true"}}); + else + this.orderQueue.push({"type": "Pack", "data": {force: "true"}}); + } + // Check the target is alive if (!this.TargetIsAlive(this.order.data.target)) { @@ -447,6 +462,10 @@ return; } + // If we're currently packing/unpacking, make sure we are unpacked. + if (!this.packForAttack(cmpPack, true)) + return; + if (this.order.data.attackType == this.oldAttackType) { if (this.IsAnimal()) @@ -481,6 +500,10 @@ return; } + // If we're currently packing/unpacking, make sure we are packed. + if (!this.packForAttack(cmpPack, false)) + return; + // Try to move within attack range if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType)) { @@ -3712,6 +3735,42 @@ 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: orderQueue should have the Attack order at index 0, + * and the Pack/Unpack order at index 1. + * + * @param cmpPack - packing component + * @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. + */ +UnitAI.prototype.packForAttack = function(cmpPack, preferUnpacked) +{ + if (cmpPack && cmpPack.IsPacking()) + { + 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(); + // Continue with the attack order. + return true; + } + else + { + // 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; + 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 @@ -3739,7 +3798,22 @@ { var order = { "type": type, "data": data }; var packingOrder = this.orderQueue.shift(); - this.orderQueue = [packingOrder, order]; + var cmpPack = Engine.QueryInterface(this.entity, IID_Pack); + if (packingOrder.type == "Unpack" && g_OrdersCancelUnpacking.has(type) && cmpPack) + { + // Immediately cancel unpacking before processing an order that demands an unpacked unit. + cmpPack.CancelPack(); + this.orderQueue = []; + this.PushOrder(type, data); + } + else if (type == "Attack") + { + // The Attack order is able to handle a packing unit, when other orders can't. + this.orderQueue = []; + this.PushOrder(type, data); + } + else + this.orderQueue = [packingOrder, order]; } else {