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 @@ -156,6 +156,9 @@ UnitAI.prototype.notifyToCheerInRange = 30; +const ACCEPT_ORDER = true; +const REJECT_ORDER = false; + // See ../helpers/FSM.js for some documentation of this FSM specification syntax UnitAI.prototype.UnitFsmSpec = { @@ -224,18 +227,16 @@ // Called when being told to walk as part of a formation "Order.FormationWalk": function(msg) { if (!this.AbleToMove()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; if (this.CanPack()) { this.PushOrderFront("Pack", { "force": true }); - return; + return ACCEPT_ORDER; } this.SetNextState("FORMATIONMEMBER.WALKING"); + return ACCEPT_ORDER; }, // Special orders: @@ -243,22 +244,18 @@ "Order.LeaveFoundation": function(msg) { if (!this.WillMoveFromFoundation(msg.data.target)) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; + this.order.data.min = g_LeaveFoundationRange; this.SetNextState("INDIVIDUAL.WALKING"); + return ACCEPT_ORDER; }, // Individual orders: "Order.LeaveFormation": function() { if (!this.IsFormationMember()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); if (cmpFormation) @@ -269,32 +266,23 @@ cmpFormation.RemoveMembers([this.entity]); cmpFormation.SetRearrange(true); } + return ACCEPT_ORDER; }, "Order.Stop": function(msg) { this.StopMoving(); this.FinishOrder(); - - if (this.IsAnimal()) - this.SetNextState("ANIMAL.IDLE"); - else if (this.IsFormationMember()) - this.SetNextState("FORMATIONMEMBER.IDLE"); - else - this.SetNextState("INDIVIDUAL.IDLE"); - + return ACCEPT_ORDER; }, "Order.Walk": function(msg) { if (!this.AbleToMove()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; if (this.CanPack()) { this.PushOrderFront("Pack", { "force": true }); - return; + return ACCEPT_ORDER; } this.SetHeldPosition(this.order.data.x, this.order.data.z); @@ -303,19 +291,17 @@ this.SetNextState("ANIMAL.WALKING"); else this.SetNextState("INDIVIDUAL.WALKING"); + return ACCEPT_ORDER; }, "Order.WalkAndFight": function(msg) { if (!this.AbleToMove()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; if (this.CanPack()) { this.PushOrderFront("Pack", { "force": true }); - return; + return ACCEPT_ORDER; } this.SetHeldPosition(this.order.data.x, this.order.data.z); @@ -324,29 +310,22 @@ this.SetNextState("ANIMAL.WALKING"); // WalkAndFight not applicable for animals else this.SetNextState("INDIVIDUAL.WALKINGANDFIGHTING"); + return ACCEPT_ORDER; }, "Order.WalkToTarget": function(msg) { if (!this.AbleToMove()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; if (this.CanPack()) { this.PushOrderFront("Pack", { "force": true }); - return; + return ACCEPT_ORDER; } - if (this.CheckRange(this.order.data)) - { - // We are already at the target, or can't move at all - this.FinishOrder(); - return true; - } + return REJECT_ORDER; this.order.data.relaxed = true; @@ -354,24 +333,20 @@ this.SetNextState("ANIMAL.WALKING"); else this.SetNextState("INDIVIDUAL.WALKING"); + return ACCEPT_ORDER; }, "Order.PickupUnit": function(msg) { let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; let range = cmpGarrisonHolder.GetLoadingRange(); this.order.data.min = range.min; this.order.data.max = range.max; if (this.CheckRange(this.order.data)) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; + // Check if we need to move // If the target can reach us and we are reasonably close, don't move. // TODO: it would be slightly more optimal to check for real, not bird-flight distance. @@ -382,19 +357,18 @@ this.SetNextState("INDIVIDUAL.PICKUP.LOADING"); else this.SetNextState("INDIVIDUAL.PICKUP.APPROACHING"); + return ACCEPT_ORDER; }, "Order.Guard": function(msg) { if (!this.AddGuard(this.order.data.target)) - { - this.FinishOrder(); - return; - } + 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; }, "Order.Flee": function(msg) { @@ -402,16 +376,14 @@ this.SetNextState("ANIMAL.FLEEING"); else this.SetNextState("INDIVIDUAL.FLEEING"); + return ACCEPT_ORDER; }, "Order.Attack": function(msg) { let type = this.GetBestAttackAgainst(this.order.data.target, this.order.data.allowCapture); if (!type) - { - // Oops, we can't attack at all - this.FinishOrder(); - return; - } + return REJECT_ORDER; + this.order.data.attackType = type; this.RememberTargetPosition(); @@ -423,95 +395,83 @@ if (this.CanUnpack()) { this.PushOrderFront("Unpack", { "force": true }); - return; + return ACCEPT_ORDER; } // Cancel any current packing order. if (!this.EnsureCorrectPackStateForAttack(false)) - return; + return ACCEPT_ORDER; if (this.IsAnimal()) this.SetNextState("ANIMAL.COMBAT.ATTACKING"); else this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING"); - return; + return ACCEPT_ORDER; } // If 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()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; if (this.CanPack()) { this.PushOrderFront("Pack", { "force": true }); - return; + return ACCEPT_ORDER; } // If we're currently packing/unpacking, make sure we are packed, so we can move. if (!this.EnsureCorrectPackStateForAttack(true)) - return; + return ACCEPT_ORDER; if (this.IsAnimal()) this.SetNextState("ANIMAL.COMBAT.APPROACHING"); else this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING"); + return ACCEPT_ORDER; }, "Order.Patrol": function(msg) { if (this.IsAnimal() || !this.AbleToMove()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; if (this.CanPack()) { this.PushOrderFront("Pack", { "force": true }); - return; + return ACCEPT_ORDER; } this.order.data.relaxed = true; this.SetNextState("INDIVIDUAL.PATROL.PATROLLING"); + return ACCEPT_ORDER; }, "Order.Heal": function(msg) { if (!this.TargetIsAlive(this.order.data.target)) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; // Healers can't heal themselves. if (this.order.data.target == this.entity) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; if (this.CheckTargetRange(this.order.data.target, IID_Heal)) { this.SetNextState("INDIVIDUAL.HEAL.HEALING"); - return; + return ACCEPT_ORDER; } if (this.GetStance().respondStandGround && !this.order.data.force) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; this.SetNextState("INDIVIDUAL.HEAL.APPROACHING"); + return ACCEPT_ORDER; }, "Order.Gather": function(msg) { if (!this.CanGather(this.order.data.target)) { this.SetNextState("INDIVIDUAL.GATHER.FINDINGNEWTARGET"); - return; + return ACCEPT_ORDER; } // If the unit is full go to the nearest dropsite instead of trying to gather. @@ -530,7 +490,7 @@ else if (!this.FinishOrder()) this.WalkToTarget(msg.data.target, false); - return; + return ACCEPT_ORDER; } if (this.MustKillGatherTarget(this.order.data.target)) @@ -540,8 +500,7 @@ { // Oops, we can't attack at all - give up // TODO: should do something so the player knows why this failed - this.FinishOrder(); - return; + return REJECT_ORDER; } // The target was visible when this order was issued, // but could now be invisible again. @@ -564,11 +523,11 @@ "template": data.template }); } - return; + return ACCEPT_ORDER; } this.PushOrderFront("Attack", { "target": this.order.data.target, "force": !!this.order.data.force, "hunting": true, "allowCapture": false }); - return; + return ACCEPT_ORDER; } this.RememberTargetPosition(); @@ -579,12 +538,14 @@ this.SetNextState("INDIVIDUAL.GATHER.GATHERING"); else this.SetNextState("INDIVIDUAL.GATHER.APPROACHING"); + return ACCEPT_ORDER; }, "Order.GatherNearPosition": function(msg) { this.SetNextState("INDIVIDUAL.GATHER.WALKING"); this.order.data.initPos = { 'x': this.order.data.x, 'z': this.order.data.z }; this.order.data.relaxed = true; + return ACCEPT_ORDER; }, "Order.ReturnResource": function(msg) { @@ -600,22 +561,21 @@ // Our next order should always be a Gather, // so just switch back to that order. this.FinishOrder(); - return; } - this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING"); + else + this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING"); + return ACCEPT_ORDER; }, "Order.Trade": function(msg) { - // We must check if this trader has both markets in case it was a back-to-work order - var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); + // 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()) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; this.waypoints = []; this.SetNextState("TRADE.APPROACHINGMARKET"); + return ACCEPT_ORDER; }, "Order.Repair": function(msg) { @@ -623,52 +583,60 @@ this.SetNextState("INDIVIDUAL.REPAIR.REPAIRING"); else this.SetNextState("INDIVIDUAL.REPAIR.APPROACHING"); + return ACCEPT_ORDER; }, "Order.Garrison": function(msg) { if (!this.AbleToMove()) { + // Garrisoned turrets go to IDLE. this.SetNextState("IDLE"); - return; + return ACCEPT_ORDER; } - else if (this.IsGarrisoned()) + if (this.IsGarrisoned()) { if (this.IsAnimal()) this.SetNextState("ANIMAL.GARRISON.GARRISONED"); else this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED"); - return; + return ACCEPT_ORDER; } if (this.CanPack()) { this.PushOrderFront("Pack", { "force": true }); - return; + return ACCEPT_ORDER; } if (this.IsAnimal()) this.SetNextState("ANIMAL.GARRISON.APPROACHING"); else this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING"); + return ACCEPT_ORDER; }, "Order.Ungarrison": function() { this.FinishOrder(); this.isGarrisoned = false; + return ACCEPT_ORDER; }, "Order.Cheer": function(msg) { - return { "discardOrder": true }; + return REJECT_ORDER; }, "Order.Pack": function(msg) { - if (this.CanPack()) - this.SetNextState("INDIVIDUAL.PACKING"); + if (!this.CanPack()) + return REJECT_ORDER; + this.SetNextState("INDIVIDUAL.PACKING"); + return ACCEPT_ORDER; }, "Order.Unpack": function(msg) { - if (this.CanUnpack()) - this.SetNextState("INDIVIDUAL.UNPACKING"); + if (!this.CanUnpack()) + return REJECT_ORDER; + this.SetNextState("INDIVIDUAL.UNPACKING"); + return ACCEPT_ORDER; }, "Order.CancelPack": function(msg) { @@ -691,49 +659,53 @@ "Order.Walk": function(msg) { this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); this.SetNextState("WALKING"); + return ACCEPT_ORDER; }, "Order.WalkAndFight": function(msg) { this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); this.SetNextState("WALKINGANDFIGHTING"); + return ACCEPT_ORDER; }, "Order.MoveIntoFormation": function(msg) { this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); this.SetNextState("FORMING"); + return ACCEPT_ORDER; }, - // Only used by other orders to walk there in formation + // Only used by other orders to walk there in formation. "Order.WalkToTargetRange": function(msg) { - if (!this.CheckRange(this.order.data)) - this.SetNextState("WALKING"); - else - this.FinishOrder(); + if (this.CheckRange(this.order.data)) + return REJECT_ORDER; + this.SetNextState("WALKING"); + return ACCEPT_ORDER; }, "Order.WalkToTarget": function(msg) { - if (!this.CheckRange(this.order.data)) - this.SetNextState("WALKING"); - else - this.FinishOrder(); + if (this.CheckRange(this.order.data)) + return REJECT_ORDER; + this.SetNextState("WALKING"); + return ACCEPT_ORDER; }, "Order.WalkToPointRange": function(msg) { - if (!this.CheckRange(this.order.data)) - this.SetNextState("WALKING"); - else - this.FinishOrder(); + if (this.CheckRange(this.order.data)) + return REJECT_ORDER; + this.SetNextState("WALKING"); + return ACCEPT_ORDER; }, "Order.Patrol": function(msg) { this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); this.SetNextState("PATROL.PATROLLING"); + return ACCEPT_ORDER; }, "Order.Guard": function(msg) { this.CallMemberFunction("Guard", [msg.data.target, false]); - var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); - cmpFormation.Disband(); + Engine.QueryInterface(this.entity, IID_Formation).Disband(); + return ACCEPT_ORDER; }, "Order.Stop": function(msg) { @@ -743,6 +715,7 @@ this.CallMemberFunction("Stop", [false]); this.StopMoving(); this.FinishOrder(); + return ACCEPT_ORDER; // Don't move the members back into formation, // as the formation then resets and it looks odd when walk-stopping. // TODO: this should be improved in the formation reshaping code. @@ -760,10 +733,9 @@ if (this.CanAttack(target) && this.CheckTargetVisible(target)) { this.SetNextState("COMBAT.APPROACHING"); - return; + return ACCEPT_ORDER; } - this.FinishOrder(); - return; + return REJECT_ORDER; } this.CallMemberFunction("Attack", [target, allowCapture, false]); let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); @@ -771,29 +743,22 @@ this.SetNextState("COMBAT.ATTACKING"); else this.SetNextState("MEMBER"); + return ACCEPT_ORDER; }, "Order.Garrison": function(msg) { if (!Engine.QueryInterface(msg.data.target, IID_GarrisonHolder)) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; if (!this.CheckGarrisonRange(msg.data.target)) { if (!this.CheckTargetVisible(msg.data.target)) - { - this.FinishOrder(); - return; - } - else - { - this.SetNextState("GARRISON.APPROACHING"); - return; - } - } + return REJECT_ORDER; - this.SetNextState("GARRISON.GARRISONING"); + this.SetNextState("GARRISON.APPROACHING"); + } + else + this.SetNextState("GARRISON.GARRISONING"); + return ACCEPT_ORDER; }, "Order.Gather": function(msg) { @@ -820,28 +785,28 @@ "template": data.template }); } - return; + return ACCEPT_ORDER; } this.PushOrderFront("Attack", { "target": msg.data.target, "force": !!msg.data.force, "hunting": true, "allowCapture": false, "min": 0, "max": 10 }); - return; + return ACCEPT_ORDER; } // TODO: on what should we base this range? if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) { if (!this.CanGather(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) - this.FinishOrder(); + return REJECT_ORDER; // TODO: Should we issue a gather-near-position order // if the target isn't gatherable/doesn't exist anymore? - else - // Out of range; move there in formation - this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); - return; + + this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); + return ACCEPT_ORDER; } this.CallMemberFunction("Gather", [msg.data.target, false]); this.SetNextState("MEMBER"); + return ACCEPT_ORDER; }, "Order.GatherNearPosition": function(msg) { @@ -850,12 +815,13 @@ { // Out of range; move there in formation this.PushOrderFront("WalkToPointRange", { "x": msg.data.x, "z": msg.data.z, "min": 0, "max": 20 }); - return; + return ACCEPT_ORDER; } this.CallMemberFunction("GatherNearPosition", [msg.data.x, msg.data.z, msg.data.type, msg.data.template, false]); this.SetNextState("MEMBER"); + return ACCEPT_ORDER; }, "Order.Heal": function(msg) { @@ -863,16 +829,17 @@ if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) { if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) - this.FinishOrder(); - else - // Out of range; move there in formation - this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); - return; + return REJECT_ORDER; + + // Out of range; move there in formation. + this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); + return ACCEPT_ORDER; } this.CallMemberFunction("Heal", [msg.data.target, false]); this.SetNextState("MEMBER"); + return ACCEPT_ORDER; }, "Order.Repair": function(msg) { @@ -880,16 +847,17 @@ if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) { if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) - this.FinishOrder(); - else - // Out of range move there in formation - this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); - return; + return REJECT_ORDER; + + // Out of range move there in formation. + this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); + return ACCEPT_ORDER; } this.CallMemberFunction("Repair", [msg.data.target, msg.data.autocontinue, false]); this.SetNextState("MEMBER"); + return ACCEPT_ORDER; }, "Order.ReturnResource": function(msg) { @@ -897,28 +865,31 @@ if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) { if (!this.CheckTargetVisible(msg.data.target)) - this.FinishOrder(); - else - // Out of range; move there in formation - this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); - return; + return REJECT_ORDER; + + // Out of range; move there in formation. + this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); + return ACCEPT_ORDER; } this.CallMemberFunction("ReturnResource", [msg.data.target, false]); this.SetNextState("MEMBER"); + return ACCEPT_ORDER; }, "Order.Pack": function(msg) { this.CallMemberFunction("Pack", [false]); this.SetNextState("MEMBER"); + return ACCEPT_ORDER; }, "Order.Unpack": function(msg) { this.CallMemberFunction("Unpack", [false]); this.SetNextState("MEMBER"); + return ACCEPT_ORDER; }, "IDLE": { @@ -1355,12 +1326,10 @@ // state forever and need to get out of foundations in that case) "Order.LeaveFoundation": function(msg) { if (!this.WillMoveFromFoundation(msg.data.target)) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; this.order.data.min = g_LeaveFoundationRange; this.SetNextState("WALKINGTOPOINT"); + return ACCEPT_ORDER; }, "enter": function() { @@ -1525,10 +1494,10 @@ "Order.Cheer": function() { // Do not cheer if there is no cheering time and we are not idle yet. if (!this.cheeringTime || !this.isIdle) - return { "discardOrder": true }; + return REJECT_ORDER; this.SetNextState("CHEERING"); - return false; + return ACCEPT_ORDER; }, "enter": function() { @@ -1922,7 +1891,7 @@ "COMBAT": { "Order.LeaveFoundation": function(msg) { // Ignore the order as we're busy. - return { "discardOrder": true }; + return REJECT_ORDER; }, "Attacked": function(msg) { @@ -2164,10 +2133,10 @@ "FINDINGNEWTARGET": { "Order.Cheer": function() { if (!this.cheeringTime) - return { "discardOrder": true }; + return REJECT_ORDER; this.SetNextState("CHEERING"); - return false; + return ACCEPT_ORDER; }, "enter": function() { @@ -2215,14 +2184,11 @@ "CHASING": { "Order.MoveToChasingPoint": function(msg) { if (this.CheckPointRangeExplicit(msg.data.x, msg.data.z, 0, msg.data.max)) - { - this.StopMoving(); - this.FinishOrder(); - return; - } + return REJECT_ORDER; this.order.data.relaxed = true; this.StopTimer(); this.SetNextState("MOVINGTOPOINT"); + return ACCEPT_ORDER; }, "enter": function() { if (!this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType)) @@ -3367,12 +3333,10 @@ "Order.LeaveFoundation": function(msg) { // Move a tile outside the building if (this.CheckTargetRangeExplicit(msg.data.target, g_LeaveFoundationRange, -1)) - { - this.FinishOrder(); - return; - } + return REJECT_ORDER; this.order.data.min = g_LeaveFoundationRange; this.SetNextState("WALKING"); + return ACCEPT_ORDER; }, "IDLE": { @@ -3923,7 +3887,7 @@ // If the order was rejected then immediately take it off // and process the remaining queue - if (ret && ret.discardOrder) + if (ret === REJECT_ORDER) return this.FinishOrder(); // Otherwise we've successfully processed a new order @@ -3975,7 +3939,7 @@ // If the order was rejected then immediately take it off // and process the remaining queue - if (ret && ret.discardOrder) + if (ret === REJECT_ORDER) this.FinishOrder(); } @@ -4007,7 +3971,7 @@ // assume the previous active order is still valid (the short-lived // new order hasn't changed state or anything) so we can carry on // as if nothing had happened - if (ret && ret.discardOrder) + if (ret === REJECT_ORDER) { this.orderQueue.shift(); this.order = this.orderQueue[0];