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 @@ -177,7 +177,7 @@ UnitAI.prototype.notifyToCheerInRange = 30; -// To reject an order, use 'return this.FinishOrder();' +const REJECT_ORDER = false; const ACCEPT_ORDER = true; // See ../helpers/FSM.js for some documentation of this FSM specification syntax @@ -248,7 +248,7 @@ // Called when being told to walk as part of a formation "Order.FormationWalk": function(msg) { if (!this.AbleToMove()) - return this.FinishOrder(); + return REJECT_ORDER; if (this.CanPack()) { @@ -256,12 +256,12 @@ // In that case we don't actually want to move, as that would unpack us. let cmpControllerAI = Engine.QueryInterface(this.GetFormationController(), IID_UnitAI); if (cmpControllerAI.IsIdle()) - return this.FinishOrder(); + return REJECT_ORDER; this.PushOrderFront("Pack", { "force": true }); + return ACCEPT_ORDER; } - else - this.SetNextState("FORMATIONMEMBER.WALKING"); - return ACCEPT_ORDER; + this.SetNextState("FORMATIONMEMBER.WALKING"); + return this.AcceptOrder(msg); }, // Special orders: @@ -269,17 +269,17 @@ "Order.LeaveFoundation": function(msg) { if (!this.WillMoveFromFoundation(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; msg.data.min = g_LeaveFoundationRange; this.SetNextState("INDIVIDUAL.WALKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, // Individual orders: - "Order.LeaveFormation": function() { + "Order.LeaveFormation": function(msg) { if (!this.IsFormationMember()) - return this.FinishOrder(); + return REJECT_ORDER; let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); if (cmpFormation) @@ -290,17 +290,17 @@ cmpFormation.RemoveMembers([this.entity]); cmpFormation.SetRearrange(true); } - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Stop": function(msg) { - this.FinishOrder(); + this.SetNextState("IDLE"); return ACCEPT_ORDER; }, "Order.Walk": function(msg) { if (!this.AbleToMove()) - return this.FinishOrder(); + return REJECT_ORDER; if (this.CanPack()) { @@ -311,12 +311,12 @@ this.SetHeldPosition(msg.data.x, msg.data.z); msg.data.relaxed = true; this.SetNextState("INDIVIDUAL.WALKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.WalkAndFight": function(msg) { if (!this.AbleToMove()) - return this.FinishOrder(); + return REJECT_ORDER; if (this.CanPack()) { @@ -327,13 +327,13 @@ this.SetHeldPosition(msg.data.x, msg.data.z); msg.data.relaxed = true; this.SetNextState("INDIVIDUAL.WALKINGANDFIGHTING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.WalkToTarget": function(msg) { if (!this.AbleToMove()) - return this.FinishOrder(); + return REJECT_ORDER; if (this.CanPack()) { @@ -341,25 +341,24 @@ return ACCEPT_ORDER; } - if (this.CheckRange(msg.data)) - return this.FinishOrder(); + return REJECT_ORDER; msg.data.relaxed = true; this.SetNextState("INDIVIDUAL.WALKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.PickupUnit": function(msg) { let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) - return this.FinishOrder(); + return REJECT_ORDER; let range = cmpGarrisonHolder.GetLoadingRange(); msg.data.min = range.min; msg.data.max = range.max; if (this.CheckRange(msg.data)) - return this.FinishOrder(); + return REJECT_ORDER; // Check if we need to move // If the target can reach us and we are reasonably close, don't move. @@ -371,33 +370,33 @@ this.SetNextState("INDIVIDUAL.PICKUP.LOADING"); else this.SetNextState("INDIVIDUAL.PICKUP.APPROACHING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Guard": function(msg) { if (!this.AddGuard(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; if (!this.CheckTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) this.SetNextState("INDIVIDUAL.GUARD.ESCORTING"); else this.SetNextState("INDIVIDUAL.GUARD.GUARDING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Flee": function(msg) { this.SetNextState("INDIVIDUAL.FLEEING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Attack": function(msg) { let type = this.GetBestAttackAgainst(msg.data.target, msg.data.allowCapture); if (!type) - return this.FinishOrder(); + return REJECT_ORDER; msg.data.attackType = type; - this.RememberTargetPosition(); + this.RememberTargetPosition(msg); if (msg.data.hunting && this.orderQueue.length > 1 && this.orderQueue[1].type === "Gather") this.RememberTargetPosition(this.orderQueue[1].data); @@ -413,12 +412,12 @@ if (this.EnsureCorrectPackStateForAttack(false)) this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); } // If we're hunting, that's a special case where we should continue attacking our target. if (this.GetStance().respondStandGround && !msg.data.force && !msg.data.hunting || !this.AbleToMove()) - return this.FinishOrder(); + return REJECT_ORDER; if (this.CanPack()) { @@ -429,12 +428,12 @@ // If we're currently packing/unpacking, make sure we are packed, so we can move. if (this.EnsureCorrectPackStateForAttack(true)) this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Patrol": function(msg) { if (!this.AbleToMove()) - return this.FinishOrder(); + return REJECT_ORDER; if (this.CanPack()) { @@ -445,16 +444,16 @@ msg.data.relaxed = true; this.SetNextState("INDIVIDUAL.PATROL.PATROLLING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Heal": function(msg) { if (!this.TargetIsAlive(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; // Healers can't heal themselves. if (msg.data.target == this.entity) - return this.FinishOrder(); + return REJECT_ORDER; if (this.CheckTargetRange(msg.data.target, IID_Heal)) { @@ -463,17 +462,17 @@ } if (this.GetStance().respondStandGround && !msg.data.force) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("INDIVIDUAL.HEAL.APPROACHING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Gather": function(msg) { if (!this.CanGather(msg.data.target)) { this.SetNextState("INDIVIDUAL.GATHER.FINDINGNEWTARGET"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); } // If the unit is full go to the nearest dropsite instead of trying to gather. @@ -488,7 +487,7 @@ "type": msg.data.type }); // Players expect the unit to move, so walk to the target instead of trying to gather. - else if (!this.FinishOrder()) + else if (!this.OrderQueue.length) this.WalkToTarget(msg.data.target, false); return ACCEPT_ORDER; @@ -501,7 +500,7 @@ { // Oops, we can't attack at all - give up // TODO: should do something so the player knows why this failed - return this.FinishOrder(); + return REJECT_ORDER; } // The target was visible when this order was issued, // but could now be invisible again. @@ -513,7 +512,7 @@ this.PushOrderFront("Walk", msg.data.lastPos); } // We couldn't move there, or the target moved away - else if (!this.FinishOrder()) + else if (!this.OrderQueue.length) this.PushOrderFront("GatherNearPosition", { "x": msg.data.lastPos.x, "z": msg.data.lastPos.z, @@ -527,7 +526,7 @@ return ACCEPT_ORDER; } - this.RememberTargetPosition(); + this.RememberTargetPosition(msg); if (!msg.data.initPos) msg.data.initPos = msg.data.lastPos; @@ -535,14 +534,14 @@ this.SetNextState("INDIVIDUAL.GATHER.GATHERING"); else this.SetNextState("INDIVIDUAL.GATHER.APPROACHING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.GatherNearPosition": function(msg) { this.SetNextState("INDIVIDUAL.GATHER.WALKING"); msg.data.initPos = { 'x': msg.data.x, 'z': msg.data.z }; msg.data.relaxed = true; - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.ReturnResource": function(msg) { @@ -555,22 +554,21 @@ // Our next order should always be a Gather, // so just switch back to that order. - this.FinishOrder(); + return ACCEPT_ORDER; } - else - this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING"); - return ACCEPT_ORDER; + this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING"); + return this.AcceptOrder(msg); }, "Order.Trade": function(msg) { // We must check if this trader has both markets in case it was a back-to-work order. let cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); if (!cmpTrader || !cmpTrader.HasBothMarkets()) - return this.FinishOrder(); + return REJECT_ORDER; this.waypoints = []; this.SetNextState("TRADE.APPROACHINGMARKET"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Repair": function(msg) { @@ -578,7 +576,7 @@ this.SetNextState("INDIVIDUAL.REPAIR.REPAIRING"); else this.SetNextState("INDIVIDUAL.REPAIR.APPROACHING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Garrison": function(msg) { @@ -586,13 +584,13 @@ { // Garrisoned turrets (unable to move) go IDLE. this.SetNextState("IDLE"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); } if (this.isGarrisoned) { this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); } // Also pack when we are in range. @@ -606,7 +604,7 @@ this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED"); else this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Ungarrison": function() { @@ -619,37 +617,37 @@ }, "Order.Cheer": function(msg) { - return this.FinishOrder(); + return REJECT_ORDER; }, "Order.Pack": function(msg) { if (!this.CanPack()) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("INDIVIDUAL.PACKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Unpack": function(msg) { if (!this.CanUnpack()) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("INDIVIDUAL.UNPACKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.MoveToChasingPoint": function(msg) { // Overriden by the CHASING state. // Can however happen outside of it when renaming... // TODO: don't use an order for that behaviour. - return this.FinishOrder(); + return REJECT_ORDER; }, "Order.CollectTreasure": function(msg) { let cmpTreasureCollecter = Engine.QueryInterface(this.entity, IID_TreasureCollecter); if (!cmpTreasureCollecter || !cmpTreasureCollecter.CanCollect(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("COLLECTTREASURE"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.CollectTreasureNearPosition": function(msg) { @@ -658,7 +656,7 @@ this.CollectTreasure(nearbyTreasure, oldData.autocontinue, true); else this.SetNextState("COLLECTTREASURE"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, // States for the special entity representing a group of units moving in formation: @@ -667,53 +665,53 @@ "Order.Walk": function(msg) { this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); this.SetNextState("WALKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.WalkAndFight": function(msg) { this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); this.SetNextState("WALKINGANDFIGHTING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.MoveIntoFormation": function(msg) { this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); this.SetNextState("FORMING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, // Only used by other orders to walk there in formation. "Order.WalkToTargetRange": function(msg) { if (this.CheckRange(msg.data)) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("WALKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.WalkToTarget": function(msg) { if (this.CheckRange(msg.data)) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("WALKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.WalkToPointRange": function(msg) { if (this.CheckRange(msg.data)) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("WALKING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Patrol": function(msg) { this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); this.SetNextState("PATROL.PATROLLING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Guard": function(msg) { this.CallMemberFunction("Guard", [msg.data.target, false]); Engine.QueryInterface(this.entity, IID_Formation).Disband(); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Stop": function(msg) { @@ -721,7 +719,7 @@ cmpFormation.ResetOrderVariant(); if (!this.IsAttackingAsFormation()) this.CallMemberFunction("Stop", [false]); - this.FinishOrder(); + this.SetNextState("IDLE"); return ACCEPT_ORDER; // Don't move the members back into formation, // as the formation then resets and it looks odd when walk-stopping. @@ -740,9 +738,9 @@ if (this.CanAttack(target) && this.CheckTargetVisible(target)) { this.SetNextState("COMBAT.APPROACHING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); } - return this.FinishOrder(); + return REJECT_ORDER; } this.CallMemberFunction("Attack", [target, allowCapture, false]); let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); @@ -750,22 +748,22 @@ this.SetNextState("COMBAT.ATTACKING"); else this.SetNextState("MEMBER"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Garrison": function(msg) { if (!Engine.QueryInterface(msg.data.target, IID_GarrisonHolder)) - return this.FinishOrder(); + return REJECT_ORDER; if (!this.CheckGarrisonRange(msg.data.target)) { if (!this.CheckTargetVisible(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("GARRISON.APPROACHING"); } else this.SetNextState("GARRISON.GARRISONING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Gather": function(msg) { @@ -781,17 +779,13 @@ this.PushOrderFront("Walk", msg.data.lastPos); } // We couldn't move there, or the target moved away - else - { - let data = msg.data; - if (!this.FinishOrder()) - this.PushOrderFront("GatherNearPosition", { - "x": data.lastPos.x, - "z": data.lastPos.z, - "type": data.type, - "template": data.template - }); - } + else if (!this.OrderQueue.length) + this.PushOrderFront("GatherNearPosition", { + "x": msg.data.lastPos.x, + "z": msg.data.lastPos.z, + "type": msg.data.type, + "template": msg.data.template + }); return ACCEPT_ORDER; } this.PushOrderFront("Attack", { "target": msg.data.target, "force": !!msg.data.force, "hunting": true, "allowCapture": false, "min": 0, "max": 10 }); @@ -802,7 +796,7 @@ if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) { if (!this.CanGather(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; // TODO: Should we issue a gather-near-position order // if the target isn't gatherable/doesn't exist anymore? if (!msg.data.secondTry) @@ -811,13 +805,13 @@ this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); return ACCEPT_ORDER; } - return this.FinishOrder(); + return REJECT_ORDER; } this.CallMemberFunction("Gather", [msg.data.target, false]); this.SetNextState("MEMBER"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.GatherNearPosition": function(msg) { @@ -832,7 +826,7 @@ this.CallMemberFunction("GatherNearPosition", [msg.data.x, msg.data.z, msg.data.type, msg.data.template, false]); this.SetNextState("MEMBER"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Heal": function(msg) { @@ -840,7 +834,7 @@ if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) { if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; if (!msg.data.secondTry) { @@ -848,13 +842,13 @@ this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); return ACCEPT_ORDER; } - return this.FinishOrder(); + return REJECT_ORDER; } this.CallMemberFunction("Heal", [msg.data.target, false]); this.SetNextState("MEMBER"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Repair": function(msg) { @@ -862,7 +856,7 @@ if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) { if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; if (!msg.data.secondTry) { @@ -870,13 +864,13 @@ this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); return ACCEPT_ORDER; } - return this.FinishOrder(); + return REJECT_ORDER; } this.CallMemberFunction("Repair", [msg.data.target, msg.data.autocontinue, false]); this.SetNextState("MEMBER"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.ReturnResource": function(msg) { @@ -884,7 +878,7 @@ if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) { if (!this.CheckTargetVisible(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; if (!msg.data.secondTry) { @@ -892,27 +886,27 @@ this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); return ACCEPT_ORDER; } - return this.FinishOrder(); + return REJECT_ORDER; } this.CallMemberFunction("ReturnResource", [msg.data.target, false]); this.SetNextState("MEMBER"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Pack": function(msg) { this.CallMemberFunction("Pack", [false]); this.SetNextState("MEMBER"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "Order.Unpack": function(msg) { this.CallMemberFunction("Unpack", [false]); this.SetNextState("MEMBER"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "IDLE": { @@ -1352,10 +1346,10 @@ // state forever and need to get out of foundations in that case) "Order.LeaveFoundation": function(msg) { if (!this.WillMoveFromFoundation(msg.data.target)) - return this.FinishOrder(); + return REJECT_ORDER; msg.data.min = g_LeaveFoundationRange; this.SetNextState("WALKINGTOPOINT"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "enter": function() { @@ -1505,13 +1499,13 @@ }, "IDLE": { - "Order.Cheer": function() { + "Order.Cheer": function(msg) { // Do not cheer if there is no cheering time and we are not idle yet. if (!this.cheeringTime || !this.isIdle) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("CHEERING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "enter": function() { @@ -1954,7 +1948,7 @@ "COMBAT": { "Order.LeaveFoundation": function(msg) { // Ignore the order as we're busy. - return this.FinishOrder(); + return REJECT_ORDER; }, "Attacked": function(msg) { @@ -2201,12 +2195,12 @@ }, "FINDINGNEWTARGET": { - "Order.Cheer": function() { + "Order.Cheer": function(msg) { if (!this.cheeringTime) - return this.FinishOrder(); + return REJECT_ORDER; this.SetNextState("CHEERING"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "enter": function() { @@ -2254,11 +2248,11 @@ "CHASING": { "Order.MoveToChasingPoint": function(msg) { if (this.CheckPointRangeExplicit(msg.data.x, msg.data.z, 0, msg.data.max)) - return this.FinishOrder(); + return REJECT_ORDER; msg.data.relaxed = true; this.StopTimer(); this.SetNextState("MOVINGTOPOINT"); - return ACCEPT_ORDER; + return this.AcceptOrder(msg); }, "enter": function() { if (!this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType)) @@ -3858,7 +3852,6 @@ * next one (if any). Returns false and defaults to IDLE * if there are no remaining orders or if the unit is not * inWorld and not garrisoned (thus usually waiting to be destroyed). - * Must be called from inside the FSM. */ UnitAI.prototype.FinishOrder = function() { @@ -3871,16 +3864,19 @@ } this.orderQueue.shift(); - this.order = this.orderQueue[0]; let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (this.orderQueue.length && (this.isGarrisoned || this.IsFormationController() || cmpPosition && cmpPosition.IsInWorld())) { + let order = this.orderQueue[0]; let ret = this.UnitFsm.ProcessMessage(this, - { "type": "Order."+this.order.type, "data": this.order.data } + { "type": "Order." + order.type, "data": order.data } ); + if (ret === REJECT_ORDER) + return this.FinishOrder(); + Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); return ret; @@ -3914,21 +3910,36 @@ }; /** + * @param {Object} order - The order to accept. + * @return {boolean} - Whether we accepted the order. + */ +UnitAI.prototype.AcceptOrder = function(order) +{ + this.order = order; + return ACCEPT_ORDER; +}; + +/** * Add an order onto the back of the queue, * and execute it if we didn't already have an order. */ UnitAI.prototype.PushOrder = function(type, data) { - var order = { "type": type, "data": data }; - this.orderQueue.push(order); + let order = { "type": type, "data": data }; - if (this.orderQueue.length == 1) + if (!this.orderQueue.length) { - this.order = order; - this.UnitFsm.ProcessMessage(this, - { "type": "Order."+this.order.type, "data": this.order.data } + let ret = this.UnitFsm.ProcessMessage(this, + { "type": "Order." + order.type, "data": order.data } ); + if (ret === REJECT_ORDER) + { + // A player might expect this. + this.Stop(); + return; + } } + this.orderQueue.push(order); Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); }; @@ -3939,24 +3950,28 @@ */ UnitAI.prototype.PushOrderFront = function(type, data, ignorePacking = false) { - var order = { "type": type, "data": data }; + let order = { "type": type, "data": data }; // If current order is packing/unpacking then add new order after it. if (!ignorePacking && this.order && this.IsPacking()) { - var packingOrder = this.orderQueue.shift(); + let packingOrder = this.orderQueue.shift(); this.orderQueue.unshift(packingOrder, order); } else { - this.orderQueue.unshift(order); - this.order = order; - this.UnitFsm.ProcessMessage(this, - { "type": "Order."+this.order.type, "data": this.order.data } + let ret = this.UnitFsm.ProcessMessage(this, + { "type": "Order." + order.type, "data": order.data } ); + if (ret === REJECT_ORDER) + { + // A player might expect this. + this.Stop(); + return; + } + this.orderQueue.unshift(order); } Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); - }; /** Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -328,7 +328,7 @@ units.push(unit + i); - var unitAI = ConstructComponent(unit + i, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive" }); + let unitAI = ConstructComponent(unit + i, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive" }); AddMock(unit + i, IID_Identity, { GetClassesList: function() { return []; }, @@ -344,6 +344,7 @@ GetPosition2D: function() { return new Vector2D(); }, GetRotation: function() { return { "y": 0 }; }, IsInWorld: function() { return true; }, + TurnTo: function() {} }); AddMock(unit + i, IID_UnitMotion, { @@ -428,16 +429,16 @@ controllerAI.Attack(enemy, []); - for (let ent of unitAIs) + for (let unitAI of unitAIs) TS_ASSERT_EQUALS(unitAI.fsmStateName, "INDIVIDUAL.COMBAT.ATTACKING"); controllerAI.MoveIntoFormation({"name": "Circle"}); // let all units be in position - for (let ent of unitAIs) + for (let ent of units) controllerFormation.SetWaitingOnController(ent); - for (let ent of unitAIs) + for (let unitAI of unitAIs) TS_ASSERT_EQUALS(unitAI.fsmStateName, "INDIVIDUAL.COMBAT.ATTACKING"); controllerFormation.Disband();