Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js
Show First 20 Lines • Show All 126 Lines • ▼ Show 20 Lines | "none": { | ||||
"respondChase": false, | "respondChase": false, | ||||
"respondChaseBeyondVision": false, | "respondChaseBeyondVision": false, | ||||
"respondStandGround": false, | "respondStandGround": false, | ||||
"respondHoldGround": false, | "respondHoldGround": false, | ||||
"selectable": false | "selectable": false | ||||
} | } | ||||
}; | }; | ||||
// These orders always require a packed unit, so if a unit that is unpacking is given one of these orders, | |||||
// it will 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 | // See ../helpers/FSM.js for some documentation of this FSM specification syntax | ||||
UnitAI.prototype.UnitFsmSpec = { | UnitAI.prototype.UnitFsmSpec = { | ||||
// Default event handlers: | // Default event handlers: | ||||
"MovementUpdate": function(msg) { | "MovementUpdate": function(msg) { | ||||
// ignore spurious movement messages | // ignore spurious movement messages | ||||
// (these can happen when stopping moving at the same time | // (these can happen when stopping moving at the same time | ||||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | ||||
}, | }, | ||||
// Special orders: | // Special orders: | ||||
// (these will be overridden by various states) | // (these will be overridden by various states) | ||||
"Order.LeaveFoundation": function(msg) { | "Order.LeaveFoundation": function(msg) { | ||||
// If foundation is not ally of entity, or if entity is unpacked siege, | // If foundation is not ally of entity, or if entity is unpacked siege, | ||||
// ignore the order | // ignore the order | ||||
if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) && !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || | if (!this.WillMoveFromFoundation(msg.data.target)) | ||||
this.IsPacking() || this.CanPack() || this.IsTurret()) | |||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
this.order.data.min = g_LeaveFoundationRange; | |||||
// 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.SetNextState("INDIVIDUAL.WALKING"); | ||||
} | |||||
}, | }, | ||||
// Individual orders: | // Individual orders: | ||||
// (these will switch the unit out of formation mode) | // (these will switch the unit out of formation mode) | ||||
"Order.Stop": function(msg) { | "Order.Stop": function(msg) { | ||||
// We have no control over non-domestic animals. | // We have no control over non-domestic animals. | ||||
if (this.IsAnimal() && !this.IsDomestic()) | if (this.IsAnimal() && !this.IsDomestic()) | ||||
▲ Show 20 Lines • Show All 182 Lines • ▼ Show 20 Lines | if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | ||||
// 1. If unpacked, we can attack the target. | // 1. If unpacked, we can attack the target. | ||||
// 2. If packed, we first need to unpack, then follow case 1. | // 2. If packed, we first need to unpack, then follow case 1. | ||||
if (this.CanUnpack()) | if (this.CanUnpack()) | ||||
{ | { | ||||
this.PushOrderFront("Unpack", { "force": true }); | this.PushOrderFront("Unpack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
// Cancel any current packing order. | |||||
if (!this.EnsureCorrectPackStateForAttack(false)) | |||||
return; | |||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
this.SetNextState("ANIMAL.COMBAT.ATTACKING"); | this.SetNextState("ANIMAL.COMBAT.ATTACKING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING"); | this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING"); | ||||
return; | return; | ||||
} | } | ||||
// If we can't reach the target, but are standing ground, then abandon this attack order. | // 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. | // 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.IsTurret()) | if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || this.IsTurret()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// For packable units out of attack range: | // For packable units out of attack range: | ||||
// 1. If packed, we need to move to attack range and then unpack. | // 1. If packed, we need to move to attack range and then unpack. | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
// If we're currently packing/unpacking, make sure we are packed, so we can move. | |||||
if (!this.EnsureCorrectPackStateForAttack(true)) | |||||
return; | |||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
this.SetNextState("ANIMAL.COMBAT.APPROACHING"); | this.SetNextState("ANIMAL.COMBAT.APPROACHING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING"); | this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING"); | ||||
}, | }, | ||||
"Order.Patrol": function(msg) { | "Order.Patrol": function(msg) { | ||||
if (this.IsAnimal() || this.IsTurret()) | if (this.IsAnimal() || this.IsTurret()) | ||||
▲ Show 20 Lines • Show All 788 Lines • ▼ Show 20 Lines | "FORMATIONMEMBER": { | ||||
}, | }, | ||||
// Override the LeaveFoundation order since we're not doing | // Override the LeaveFoundation order since we're not doing | ||||
// anything more important (and we might be stuck in the WALKING | // anything more important (and we might be stuck in the WALKING | ||||
// state forever and need to get out of foundations in that case) | // state forever and need to get out of foundations in that case) | ||||
"Order.LeaveFoundation": function(msg) { | "Order.LeaveFoundation": function(msg) { | ||||
// If foundation is not ally of entity, or if entity is unpacked siege, | // If foundation is not ally of entity, or if entity is unpacked siege, | ||||
// ignore the order | // ignore the order | ||||
if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) && !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || | if (!this.WillMoveFromFoundation(msg.data.target)) | ||||
this.IsPacking() || this.CanPack() || this.IsTurret()) | |||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// Move a tile outside the building | this.order.data.min = g_LeaveFoundationRange; | ||||
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.SetNextState("WALKINGTOPOINT"); | ||||
} | |||||
}, | }, | ||||
"enter": function() { | "enter": function() { | ||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
{ | { | ||||
// Animals can't go in formation. | // Animals can't go in formation. | ||||
warn("Entity " + this.entity + " was put in FORMATIONMEMBER state but is an animal"); | warn("Entity " + this.entity + " was put in FORMATIONMEMBER state but is an animal"); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
▲ Show 20 Lines • Show All 1,849 Lines • ▼ Show 20 Lines | "Attacked": function(msg) { | ||||
{ | { | ||||
// Never flee, stop what we were doing | // Never flee, stop what we were doing | ||||
this.SetNextState("IDLE"); | this.SetNextState("IDLE"); | ||||
} | } | ||||
}, | }, | ||||
"Order.LeaveFoundation": function(msg) { | "Order.LeaveFoundation": function(msg) { | ||||
// Move a tile outside the building | // Move a tile outside the building | ||||
let range = 4; | if (this.CheckTargetRangeExplicit(msg.data.target, g_LeaveFoundationRange, -1)) | ||||
if (this.CheckTargetRangeExplicit(msg.data.target, range, -1)) | |||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
this.order.data.min = range; | this.order.data.min = g_LeaveFoundationRange; | ||||
this.SetNextState("WALKING"); | this.SetNextState("WALKING"); | ||||
}, | }, | ||||
"IDLE": { | "IDLE": { | ||||
// (We need an IDLE state so that FinishOrder works) | // (We need an IDLE state so that FinishOrder works) | ||||
"enter": function() { | "enter": function() { | ||||
// Start feeding immediately | // Start feeding immediately | ||||
▲ Show 20 Lines • Show All 511 Lines • ▼ Show 20 Lines | UnitAI.prototype.PushOrder = function(type, data) | ||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | ||||
}; | }; | ||||
/** | /** | ||||
* Add an order onto the front of the queue, | * Add an order onto the front of the queue, | ||||
* and execute it immediately. | * and execute it immediately. | ||||
*/ | */ | ||||
UnitAI.prototype.PushOrderFront = function(type, data) | UnitAI.prototype.PushOrderFront = function(type, data, ignorePacking = false) | ||||
{ | { | ||||
var order = { "type": type, "data": data }; | var order = { "type": type, "data": data }; | ||||
// If current order is cheering then add new order after it | // If current order is cheering then add new order after it | ||||
// same thing if current order if packing/unpacking | // same thing if current order if packing/unpacking | ||||
if (this.order && this.order.type == "Cheering") | if (this.order && this.order.type == "Cheering") | ||||
{ | { | ||||
var cheeringOrder = this.orderQueue.shift(); | var cheeringOrder = this.orderQueue.shift(); | ||||
this.orderQueue.unshift(cheeringOrder, order); | this.orderQueue.unshift(cheeringOrder, order); | ||||
} | } | ||||
else if (this.order && this.IsPacking()) | else if (!ignorePacking && this.order && this.IsPacking()) | ||||
{ | { | ||||
var packingOrder = this.orderQueue.shift(); | var packingOrder = this.orderQueue.shift(); | ||||
this.orderQueue.unshift(packingOrder, order); | this.orderQueue.unshift(packingOrder, order); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
this.orderQueue.unshift(order); | this.orderQueue.unshift(order); | ||||
this.order = order; | this.order = order; | ||||
Show All 37 Lines | for (let i = 1; i < this.orderQueue.length; ++i) | ||||
return; | return; | ||||
} | } | ||||
this.PushOrder(type, data); | this.PushOrder(type, data); | ||||
} | } | ||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | 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 {boolean} requirePacked - true if the unit needs to be packed to continue attacking, | |||||
* false if it needs to be unpacked. | |||||
* @return {boolean} true if the unit can attack now, false if it must continue packing (or unpacking) first. | |||||
*/ | |||||
UnitAI.prototype.EnsureCorrectPackStateForAttack = function(requirePacked) | |||||
{ | |||||
let cmpPack = Engine.QueryInterface(this.entity, IID_Pack); | |||||
if (!cmpPack || | |||||
!cmpPack.IsPacking() || | |||||
this.orderQueue.length != 2 || | |||||
this.orderQueue[0].type != "Attack" || | |||||
this.orderQueue[1].type != "Pack" && | |||||
this.orderQueue[1].type != "Unpack") | |||||
return true; | |||||
if (cmpPack.IsPacked() == requirePacked) | |||||
{ | |||||
// 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() || | |||||
checkPacking && this.IsPacking() || | |||||
this.CanPack() || this.IsTurret()) | |||||
return false; | |||||
// Move a tile outside the building. | |||||
return !this.CheckTargetRangeExplicit(target, g_LeaveFoundationRange, -1); | |||||
}; | |||||
UnitAI.prototype.ReplaceOrder = function(type, data) | UnitAI.prototype.ReplaceOrder = function(type, data) | ||||
{ | { | ||||
// Remember the previous work orders to be able to go back to them later if required | // Remember the previous work orders to be able to go back to them later if required | ||||
if (data && data.force) | if (data && data.force) | ||||
{ | { | ||||
if (this.IsFormationController()) | if (this.IsFormationController()) | ||||
this.CallMemberFunction("UpdateWorkOrders", [type]); | this.CallMemberFunction("UpdateWorkOrders", [type]); | ||||
else | else | ||||
Show All 11 Lines | if (this.order && this.order.type == "Cheering") | ||||
var order = { "type": type, "data": data }; | var order = { "type": type, "data": data }; | ||||
var cheeringOrder = this.orderQueue.shift(); | var cheeringOrder = this.orderQueue.shift(); | ||||
this.orderQueue = [cheeringOrder, order]; | this.orderQueue = [cheeringOrder, order]; | ||||
} | } | ||||
else if (this.IsPacking() && type != "CancelPack" && type != "CancelUnpack") | else if (this.IsPacking() && type != "CancelPack" && type != "CancelUnpack") | ||||
{ | { | ||||
var order = { "type": type, "data": data }; | var order = { "type": type, "data": data }; | ||||
var packingOrder = this.orderQueue.shift(); | var packingOrder = this.orderQueue.shift(); | ||||
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]; | this.orderQueue = [packingOrder, order]; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
this.orderQueue = []; | this.orderQueue = []; | ||||
this.PushOrder(type, data); | this.PushOrder(type, data); | ||||
} | } | ||||
if (garrisonHolder) | if (garrisonHolder) | ||||
▲ Show 20 Lines • Show All 1,307 Lines • ▼ Show 20 Lines | |||||
* Adds leave foundation order to queue, treated as forced. | * Adds leave foundation order to queue, treated as forced. | ||||
*/ | */ | ||||
UnitAI.prototype.LeaveFoundation = function(target) | UnitAI.prototype.LeaveFoundation = function(target) | ||||
{ | { | ||||
// If we're already being told to leave a foundation, then | // If we're already being told to leave a foundation, then | ||||
// ignore this new request so we don't end up being too indecisive | // 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 | // 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.orderQueue.length && this.orderQueue[0].type == "Unpack" && this.WillMoveFromFoundation(target, false)) | |||||
{ | |||||
let cmpPack = Engine.QueryInterface(this.entity, IID_Pack); | |||||
if (cmpPack) | |||||
cmpPack.CancelPack(); | |||||
} | |||||
if (this.IsPacking()) | |||||
return; | return; | ||||
this.PushOrderFront("LeaveFoundation", { "target": target, "force": true }); | this.PushOrderFront("LeaveFoundation", { "target": target, "force": true }); | ||||
}; | }; | ||||
/** | /** | ||||
* Adds attack order to the queue, forced by the player. | * Adds attack order to the queue, forced by the player. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 1,008 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator