Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/components/UnitAI.js
Show First 20 Lines • Show All 137 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | ||||
// Default event handlers: | // Default event handlers: | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
// 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 | ||||
// as switching states) | // as switching states) | ||||
}, | }, | ||||
"MoveStarted": function() { | "enter": function() { | ||||
// ignore spurious movement messages | // ignore spurious movement messages | ||||
}, | }, | ||||
"ConstructionFinished": function(msg) { | "ConstructionFinished": function(msg) { | ||||
// ignore uninteresting construction messages | // ignore uninteresting construction messages | ||||
}, | }, | ||||
"LosRangeUpdate": function(msg) { | "LosRangeUpdate": function(msg) { | ||||
▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Lines | "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 (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) && !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || | ||||
this.IsPacking() || this.CanPack() || this.IsTurret()) | this.IsPacking() || this.CanPack() || this.IsTurret()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// Move a tile outside the building | // Move outside the building | ||||
let range = 4; | let range = 4; | ||||
if (this.MoveToTargetRangeExplicit(msg.data.target, range, range)) | if (this.MoveToTargetRangeExplicit(msg.data.target, range)) | ||||
{ | { | ||||
// We've started walking to the given point | // We've started walking to the given point | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.WALKING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.WALKING"); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// We are already at the target, or can't move at all | // We can't reach the target | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
} | } | ||||
}, | }, | ||||
// 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) { | ||||
Show All 32 Lines | if (this.CanPack()) | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
this.SetHeldPosition(this.order.data.x, this.order.data.z); | this.SetHeldPosition(this.order.data.x, this.order.data.z); | ||||
if (!this.order.data.max) | if (!this.order.data.max) | ||||
this.MoveToPoint(this.order.data.x, this.order.data.z); | this.MoveToPoint(this.order.data.x, this.order.data.z); | ||||
else | else | ||||
this.MoveToPointRange(this.order.data.x, this.order.data.z, this.order.data.min, this.order.data.max); | this.MoveToPointRange(this.order.data.x, this.order.data.z, (this.order.data.min + this.order.data.max) / 2.0); | ||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
this.SetNextStateAlwaysEntering("ANIMAL.WALKING"); | this.SetNextStateAlwaysEntering("ANIMAL.WALKING"); | ||||
else | else | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.WALKING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.WALKING"); | ||||
}, | }, | ||||
"Order.WalkAndFight": function(msg) { | "Order.WalkAndFight": function(msg) { | ||||
// Let players move captured domestic animals around | // Let players move captured domestic animals around | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | if (ok) | ||||
// We've started walking to the given point | // We've started walking to the given point | ||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
this.SetNextStateAlwaysEntering("ANIMAL.WALKING"); | this.SetNextStateAlwaysEntering("ANIMAL.WALKING"); | ||||
else | else | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.WALKING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.WALKING"); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// We are already at the target, or can't move at all | // We can't reach the target | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
} | } | ||||
}, | }, | ||||
"Order.PickupUnit": function(msg) { | "Order.PickupUnit": function(msg) { | ||||
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); | var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); | ||||
if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | ||||
Show All 10 Lines | "Order.PickupUnit": function(msg) { | ||||
{ | { | ||||
// we were already on the shoreline, and have not moved since | // we were already on the shoreline, and have not moved since | ||||
if (DistanceBetweenEntities(this.entity, this.order.data.target) < 50) | if (DistanceBetweenEntities(this.entity, this.order.data.target) < 50) | ||||
needToMove = false; | needToMove = false; | ||||
} | } | ||||
// TODO: what if the units are on a cliff ? the ship will go below the cliff | // TODO: what if the units are on a cliff ? the ship will go below the cliff | ||||
// and the units won't be able to garrison. Should go to the nearest (accessible) shore | // and the units won't be able to garrison. Should go to the nearest (accessible) shore | ||||
if (needToMove && this.MoveToTarget(this.order.data.target)) | this.MoveToTarget(this.order.data.target, true); | ||||
{ | this.SetNextStateAlwaysEntering("INDIVIDUAL.PICKUP"); | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.PICKUP.APPROACHING"); | |||||
} | |||||
else | |||||
{ | |||||
// We are already at the target, or can't move at all | |||||
this.StopMoving(); | |||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.PICKUP.LOADING"); | |||||
} | |||||
}, | }, | ||||
"Order.Guard": function(msg) { | "Order.Guard": function(msg) { | ||||
if (!this.AddGuard(this.order.data.target)) | if (!this.AddGuard(this.order.data.target)) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
if (this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) | if (this.MoveToTargetRangeExplicit(this.isGuardOf, this.guardRange, true)) | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.GUARD.ESCORTING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.GUARD.ESCORTING"); | ||||
else | else | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.GUARD.GUARDING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.GUARD.GUARDING"); | ||||
}, | }, | ||||
"Order.Flee": function(msg) { | "Order.Flee": function(msg) { | ||||
// We use the distance between the entities to account for ranged attacks | // We use the distance between the entities to account for ranged attacks | ||||
var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance); | var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance); | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1)) | if (cmpUnitMotion.SetNewDestinationAsEntity(this.order.data.target, distance, true)) | ||||
{ | { | ||||
// We've started fleeing from the given target | // We've started fleeing from the given target | ||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
this.SetNextStateAlwaysEntering("ANIMAL.FLEEING"); | this.SetNextStateAlwaysEntering("ANIMAL.FLEEING"); | ||||
else | else | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.FLEEING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.FLEEING"); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// We are already at the target, or can't move at all | // We can't reach the target | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
} | } | ||||
}, | }, | ||||
"Order.Attack": function(msg) { | "Order.Attack": function(msg) { | ||||
// Check the target is alive | // Check the target is alive | ||||
if (!this.TargetIsAlive(this.order.data.target)) | if (!this.TargetIsAlive(this.order.data.target)) | ||||
▲ Show 20 Lines • Show All 172 Lines • ▼ Show 20 Lines | "Order.Gather": function(msg) { | ||||
// Try to move within range | // Try to move within range | ||||
if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer)) | if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer)) | ||||
{ | { | ||||
// We've started walking to the given point | // We've started walking to the given point | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.GATHER.APPROACHING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.GATHER.APPROACHING"); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// We are already at the target, or can't move at all, | // We can't reach the target. | ||||
// so try gathering it from here. | // so try gathering it from here. | ||||
// TODO: need better handling of the can't-reach-target case | // TODO: need better handling of the can't-reach-target case | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.GATHER.GATHERING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.GATHER.GATHERING"); | ||||
} | } | ||||
}, | }, | ||||
"Order.GatherNearPosition": function(msg) { | "Order.GatherNearPosition": function(msg) { | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | "Order.Repair": function(msg) { | ||||
// Try to move within range | // Try to move within range | ||||
if (this.MoveToTargetRange(this.order.data.target, IID_Builder)) | if (this.MoveToTargetRange(this.order.data.target, IID_Builder)) | ||||
{ | { | ||||
// We've started walking to the given point | // We've started walking to the given point | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.REPAIR.APPROACHING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.REPAIR.APPROACHING"); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// We are already at the target, or can't move at all, | // We can't reach the target. | ||||
// so try repairing it from here. | // so try repairing it from here. | ||||
// TODO: need better handling of the can't-reach-target case | // TODO: need better handling of the can't-reach-target case | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.REPAIR.REPAIRING"); | this.SetNextStateAlwaysEntering("INDIVIDUAL.REPAIR.REPAIRING"); | ||||
} | } | ||||
}, | }, | ||||
"Order.Garrison": function(msg) { | "Order.Garrison": function(msg) { | ||||
▲ Show 20 Lines • Show All 89 Lines • ▼ Show 20 Lines | "Order.MoveIntoFormation": function(msg) { | ||||
this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); | this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); | ||||
this.MoveToPoint(this.order.data.x, this.order.data.z); | this.MoveToPoint(this.order.data.x, this.order.data.z); | ||||
this.SetNextStateAlwaysEntering("FORMING"); | this.SetNextStateAlwaysEntering("FORMING"); | ||||
}, | }, | ||||
// Only used by other orders to walk there in formation | // Only used by other orders to walk there in formation | ||||
"Order.WalkToTargetRange": function(msg) { | "Order.WalkToTargetRange": function(msg) { | ||||
if (this.MoveToTargetRangeExplicit(this.order.data.target, this.order.data.min, this.order.data.max)) | if (this.MoveToTargetRangeExplicit(this.order.data.target, (this.order.data.min, this.order.data.max) / 2.0)) | ||||
this.SetNextStateAlwaysEntering("WALKING"); | this.SetNextStateAlwaysEntering("WALKING"); | ||||
else | else | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
"Order.WalkToTarget": function(msg) { | "Order.WalkToTarget": function(msg) { | ||||
if (this.MoveToTarget(this.order.data.target)) | if (this.MoveToTarget(this.order.data.target)) | ||||
this.SetNextStateAlwaysEntering("WALKING"); | this.SetNextStateAlwaysEntering("WALKING"); | ||||
else | else | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
"Order.WalkToPointRange": function(msg) { | "Order.WalkToPointRange": function(msg) { | ||||
if (this.MoveToPointRange(this.order.data.x, this.order.data.z, this.order.data.min, this.order.data.max)) | if (this.MoveToPointRange(this.order.data.x, this.order.data.z, (this.order.data.min, this.order.data.max) / 2.0)) | ||||
this.SetNextStateAlwaysEntering("WALKING"); | this.SetNextStateAlwaysEntering("WALKING"); | ||||
else | else | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
"Order.Patrol": function(msg) { | "Order.Patrol": function(msg) { | ||||
this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); | this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); | ||||
▲ Show 20 Lines • Show All 199 Lines • ▼ Show 20 Lines | "Order.Unpack": function(msg) { | ||||
this.SetNextStateAlwaysEntering("MEMBER"); | this.SetNextStateAlwaysEntering("MEMBER"); | ||||
}, | }, | ||||
"IDLE": { | "IDLE": { | ||||
"enter": function(msg) { | "enter": function(msg) { | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
cmpFormation.SetRearrange(false); | cmpFormation.SetRearrange(false); | ||||
}, | }, | ||||
"MoveStarted": function() { | |||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
} | |||||
}, | }, | ||||
"WALKING": { | "WALKING": { | ||||
"MoveStarted": function(msg) { | "enter": function(msg) { | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
cmpFormation.SetRearrange(true); | cmpFormation.SetRearrange(true); | ||||
cmpFormation.MoveMembersIntoFormation(true, true); | cmpFormation.MoveMembersIntoFormation(true, true); | ||||
this.StartTimer(0, 1000); | |||||
}, | }, | ||||
"leave": function(msg) { | |||||
"MoveCompleted": function(msg) { | this.StopTimer(); | ||||
if (this.FinishOrder()) | }, | ||||
this.CallMemberFunction("ResetFinishOrder", []); | "Timer": function(msg) { | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (msg.data.error | |||||
|| !this.order.data.target && cmpObstructionManager.IsInPointRange(this.entity, this.order.data.x, this.order.data.z, 0, 1, true) | |||||
|| this.order.data.target && cmpObstructionManager.IsInTargetRange(this.entity, this.order.data.target, 0, 1, true)) | |||||
{ | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
} | |||||
}, | }, | ||||
"MoveCompleted": "Timer", | |||||
}, | }, | ||||
"WALKINGANDFIGHTING": { | "WALKINGANDFIGHTING": { | ||||
"enter": function(msg) { | "enter": function(msg) { | ||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
this.StartTimer(0, 1000); | this.StartTimer(0, 1000); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
// check if there are no enemies to attack | // check if there are no enemies to attack | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (msg.data.error | |||||
|| !this.order.data.target && cmpObstructionManager.IsInPointRange(this.entity, this.order.data.x, this.order.data.z, 0, 1, true) | |||||
|| this.order.data.target && cmpObstructionManager.IsInTargetRange(this.entity, this.order.data.target, 0, 1, true)) | |||||
{ | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
} | |||||
}, | }, | ||||
"leave": function(msg) { | "leave": function(msg) { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
"MoveStarted": function(msg) { | "MoveCompleted": "Timer", | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
}, | |||||
"MoveCompleted": function(msg) { | |||||
if (this.FinishOrder()) | |||||
this.CallMemberFunction("ResetFinishOrder", []); | |||||
}, | |||||
}, | }, | ||||
"PATROL": { | "PATROL": { | ||||
"enter": function(msg) { | "enter": function(msg) { | ||||
// Memorize the origin position in case that we want to go back | // Memorize the origin position in case that we want to go back | ||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | if (!cmpPosition || !cmpPosition.IsInWorld()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
if (!this.patrolStartPosOrder) | if (!this.patrolStartPosOrder) | ||||
{ | { | ||||
this.patrolStartPosOrder = cmpPosition.GetPosition(); | this.patrolStartPosOrder = cmpPosition.GetPosition(); | ||||
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | ||||
this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture; | this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture; | ||||
} | } | ||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
this.StartTimer(0, 1000); | this.StartTimer(0, 1000); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
// Check if there are no enemies to attack | // Check if there are no enemies to attack | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
}, | |||||
"leave": function(msg) { | |||||
this.StopTimer(); | |||||
delete this.patrolStartPosOrder; | |||||
}, | |||||
"MoveStarted": function(msg) { | |||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
}, | |||||
"MoveCompleted": function() { | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
if (msg.data.error | |||||
|| !this.order.data.target && cmpObstructionManager.IsInPointRange(this.entity, this.order.data.x, this.order.data.z, 0, 1, true) | |||||
|| this.order.data.target && cmpObstructionManager.IsInTargetRange(this.entity, this.order.data.target, 0, 1, true)) | |||||
{ | |||||
/** | /** | ||||
* A-B-A-B-..: | * A-B-A-B-..: | ||||
* if the user only commands one patrol order, the patrol will be between | * if the user only commands one patrol order, the patrol will be between | ||||
* the last position and the defined waypoint | * the last position and the defined waypoint | ||||
* A-B-C-..-A-B-..: | * A-B-C-..-A-B-..: | ||||
* otherwise, the patrol is only between the given patrol commands and the | * otherwise, the patrol is only between the given patrol commands and the | ||||
* last position is not included (last position = the position where the unit | * last position is not included (last position = the position where the unit | ||||
* is located at the time of the first patrol order) | * is located at the time of the first patrol order) | ||||
*/ | */ | ||||
if (this.orderQueue.length == 1) | if (this.orderQueue.length == 1) | ||||
this.PushOrder("Patrol", this.patrolStartPosOrder); | this.PushOrder("Patrol", this.patrolStartPosOrder); | ||||
this.PushOrder(this.order.type, this.order.data); | this.PushOrder(this.order.type, this.order.data); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
} | |||||
}, | }, | ||||
"leave": function(msg) { | |||||
this.StopTimer(); | |||||
delete this.patrolStartPosOrder; | |||||
}, | |||||
"MoveCompleted": "Timer", | |||||
}, | }, | ||||
"GARRISON":{ | "GARRISON":{ | ||||
"enter": function() { | "enter": function() { | ||||
// If the garrisonholder should pickup, warn it so it can take needed action | // If the garrisonholder should pickup, warn it so it can take needed action | ||||
var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); | var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); | ||||
if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity)) | if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity)) | ||||
{ | { | ||||
this.pickup = this.order.data.target; // temporary, deleted in "leave" | this.pickup = this.order.data.target; // temporary, deleted in "leave" | ||||
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity }); | Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity }); | ||||
} | } | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
// If a pickup has been requested and not yet canceled, cancel it | // If a pickup has been requested and not yet canceled, cancel it | ||||
if (this.pickup) | if (this.pickup) | ||||
{ | { | ||||
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); | Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); | ||||
delete this.pickup; | delete this.pickup; | ||||
} | } | ||||
}, | }, | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"MoveStarted": function(msg) { | "enter": function(msg) { | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
cmpFormation.SetRearrange(true); | cmpFormation.SetRearrange(true); | ||||
cmpFormation.MoveMembersIntoFormation(true, true); | cmpFormation.MoveMembersIntoFormation(true, true); | ||||
this.StartTimer(0, 1000); | |||||
}, | }, | ||||
"leave": function(msg) { | |||||
"MoveCompleted": function(msg) { | this.StopTimer(); | ||||
}, | |||||
"Timer": function(msg) { | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (msg.data.error || this.CheckGarrisonRange(this.order.data.target)) | |||||
this.SetNextState("GARRISONING"); | this.SetNextState("GARRISONING"); | ||||
}, | }, | ||||
"MoveCompleted" : "Timer", | |||||
}, | }, | ||||
"GARRISONING": { | "GARRISONING": { | ||||
"enter": function() { | "enter": function() { | ||||
// If a pickup has been requested, cancel it as it will be requested by members | // If a pickup has been requested, cancel it as it will be requested by members | ||||
if (this.pickup) | if (this.pickup) | ||||
{ | { | ||||
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); | Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); | ||||
delete this.pickup; | delete this.pickup; | ||||
} | } | ||||
this.CallMemberFunction("Garrison", [this.order.data.target, false]); | this.CallMemberFunction("Garrison", [this.order.data.target, false]); | ||||
this.SetNextStateAlwaysEntering("MEMBER"); | this.SetNextStateAlwaysEntering("MEMBER"); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
"FORMING": { | "FORMING": { | ||||
"MoveStarted": function(msg) { | "enter": function(msg) { | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
cmpFormation.SetRearrange(true); | cmpFormation.SetRearrange(true); | ||||
cmpFormation.MoveMembersIntoFormation(true, false); | cmpFormation.MoveMembersIntoFormation(true, false); | ||||
this.StartTimer(0, 1000); | |||||
}, | }, | ||||
"leave": function(msg) { | |||||
"MoveCompleted": function(msg) { | this.StopTimer(); | ||||
}, | |||||
if (this.FinishOrder()) | "Timer": function(msg) { | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (msg.data.error | |||||
|| !this.order.data.target && cmpObstructionManager.IsInPointRange(this.entity, this.order.data.x, this.order.data.z, 0, 1, true)) | |||||
{ | { | ||||
this.CallMemberFunction("ResetFinishOrder", []); | this.SetNextStateAlwaysEntering("MEMBER"); | ||||
return; | |||||
} | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.FindInPosition(); | |||||
} | } | ||||
}, | }, | ||||
"MoveCompleted": "Timer", | |||||
}, | |||||
"COMBAT": { | "COMBAT": { | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"MoveStarted": function(msg) { | "enter": function(msg) { | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
cmpFormation.SetRearrange(true); | cmpFormation.SetRearrange(true); | ||||
cmpFormation.MoveMembersIntoFormation(true, true); | cmpFormation.MoveMembersIntoFormation(true, true); | ||||
this.StartTimer(0, 1000); | |||||
}, | }, | ||||
"leave": function(msg) { | |||||
"MoveCompleted": function(msg) { | this.StopTimer(); | ||||
}, | |||||
"Timer": function(msg) { | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (msg.data.error | |||||
|| !this.order.data.target && cmpObstructionManager.IsInPointRange(this.entity, this.order.data.x, this.order.data.z, this.order.data.min || 0, this.order.data.max || 1, true) | |||||
|| this.order.data.target && cmpObstructionManager.IsInTargetRange(this.entity, this.order.data.target, this.order.data.min || 0, this.order.data.max || 1, true)) | |||||
{ | |||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
this.CallMemberFunction("Attack", [this.order.data.target, this.order.data.allowCapture, false]); | this.CallMemberFunction("Attack", [this.order.data.target, this.order.data.allowCapture, false]); | ||||
if (cmpAttack.CanAttackAsFormation()) | if (cmpAttack.CanAttackAsFormation()) | ||||
this.SetNextState("COMBAT.ATTACKING"); | this.SetNextState("COMBAT.ATTACKING"); | ||||
else | else | ||||
this.SetNextState("MEMBER"); | this.SetNextState("MEMBER"); | ||||
} | |||||
}, | }, | ||||
"MoveCompleted": "Timer", | |||||
}, | }, | ||||
"ATTACKING": { | "ATTACKING": { | ||||
// Wait for individual members to finish | // Wait for individual members to finish | ||||
"enter": function(msg) { | "enter": function(msg) { | ||||
var target = this.order.data.target; | var target = this.order.data.target; | ||||
var allowCapture = this.order.data.allowCapture; | var allowCapture = this.order.data.allowCapture; | ||||
// Check if we are already in range, otherwise walk there | // Check if we are already in range, otherwise walk there | ||||
▲ Show 20 Lines • Show All 82 Lines • ▼ Show 20 Lines | |||||
"FORMATIONMEMBER": { | "FORMATIONMEMBER": { | ||||
"FormationLeave": function(msg) { | "FormationLeave": function(msg) { | ||||
// We're not in a formation anymore, so no need to track this. | // We're not in a formation anymore, so no need to track this. | ||||
this.finishedOrder = false; | this.finishedOrder = false; | ||||
// Stop moving as soon as the formation disbands | // Stop moving as soon as the formation disbands | ||||
this.StopMoving(); | this.StopMoving(); | ||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (cmpVisual) | |||||
this.SetDefaultAnimationVariant(); | |||||
// If the controller handled an order but some members rejected it, | // If the controller handled an order but some members rejected it, | ||||
// they will have no orders and be in the FORMATIONMEMBER.IDLE state. | // they will have no orders and be in the FORMATIONMEMBER.IDLE state. | ||||
if (this.orderQueue.length) | if (this.orderQueue.length) | ||||
{ | { | ||||
// We're leaving the formation, so stop our FormationWalk order | // We're leaving the formation, so stop our FormationWalk order | ||||
if (this.FinishOrder()) | if (this.FinishOrder()) | ||||
return; | return; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | "IDLE": { | ||||
}, | }, | ||||
}, | }, | ||||
"WALKING": { | "WALKING": { | ||||
"enter": function() { | "enter": function() { | ||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | ||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | ||||
if (cmpFormation && cmpVisual) | if (cmpFormation && cmpVisual) | ||||
{ | this.SetAnimationVariant(cmpFormation.GetFormationAnimation(this.entity, "walk")); | ||||
cmpVisual.ReplaceMoveAnimation("walk", cmpFormation.GetFormationAnimation(this.entity, "walk")); | this.StartTimer(0, 500); | ||||
cmpVisual.ReplaceMoveAnimation("run", cmpFormation.GetFormationAnimation(this.entity, "run")); | }, | ||||
} | "leave": function() { | ||||
this.SelectAnimation("move"); | this.StopTimer(); | ||||
}, | }, | ||||
"Timer": function(msg) { | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (!msg.data.error && this.order.data.target && !cmpObstructionManager.IsInTargetRange(this.entity, this.order.data.target, 0, 0, true)) | |||||
return; | |||||
// Occurs when the unit has reached its destination and the controller | let cmpControllerAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | ||||
// is done moving. The controller is notified. | if (!cmpControllerAI.IsControllerWaiting()) | ||||
"MoveCompleted": function(msg) { | |||||
// We can only finish this order if the move was really completed. | |||||
if (!msg.data.error && this.FinishOrder()) | |||||
return; | return; | ||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (cmpVisual) | |||||
{ | |||||
cmpVisual.ResetMoveAnimation("walk"); | |||||
cmpVisual.ResetMoveAnimation("run"); | |||||
} | |||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | this.StopMoving(); | ||||
this.FinishOrder(); | |||||
let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | |||||
if (cmpFormation) | if (cmpFormation) | ||||
cmpFormation.SetInPosition(this.entity); | cmpFormation.SetInPosition(this.entity); | ||||
}, | }, | ||||
"MoveCompleted": "Timer", | |||||
}, | }, | ||||
// Special case used by Order.LeaveFoundation | // Special case used by Order.LeaveFoundation | ||||
"WALKINGTOPOINT": { | "WALKINGTOPOINT": { | ||||
"enter": function() { | "enter": function() { | ||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
cmpFormation.UnsetInPosition(this.entity); | cmpFormation.UnsetInPosition(this.entity); | ||||
this.SelectAnimation("move"); | this.StartTimer(0, 500); | ||||
}, | |||||
"leave": function() { | |||||
this.StopTimer(); | |||||
}, | }, | ||||
"Timer": function(msg) { | |||||
let cmpControllerAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | |||||
if (!cmpControllerAI.IsControllerWaiting()) | |||||
return; | |||||
"MoveCompleted": function() { | |||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
"MoveCompleted": "Timer", | |||||
}, | }, | ||||
}, | }, | ||||
// States for entities not part of a formation: | // States for entities not part of a formation: | ||||
"INDIVIDUAL": { | "INDIVIDUAL": { | ||||
"enter": function() { | "enter": function() { | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | "GuardedAttacked": function(msg) { | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
"IDLE": { | "IDLE": { | ||||
"enter": function() { | "enter": function() { | ||||
// Switch back to idle animation to guarantee we won't | // Switch back to idle animation to guarantee we won't | ||||
// get stuck with an incorrect animation | // get stuck with an incorrect animation | ||||
var animationName = "idle"; | this.SelectAnimation("idle"); | ||||
if (this.IsFormationMember()) | |||||
{ | |||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | |||||
if (cmpFormation) | |||||
animationName = cmpFormation.GetFormationAnimation(this.entity, animationName); | |||||
} | |||||
this.SelectAnimation(animationName); | |||||
// If we have some orders, it is because we are in an intermediary state | // If we have some orders, it is because we are in an intermediary state | ||||
// from FinishOrder (SetNextState("IDLE") is only executed when we get | // from FinishOrder (SetNextState("IDLE") is only executed when we get | ||||
// a ProcessMessage), and thus we should not start another order which could | // a ProcessMessage), and thus we should not start another order which could | ||||
// put us in a weird state | // put us in a weird state | ||||
if (this.orderQueue.length > 0 && !this.IsGarrisoned()) | if (this.orderQueue.length > 0 && !this.IsGarrisoned()) | ||||
return false; | return false; | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | "IDLE": { | ||||
this.AttackEntitiesByPreference(msg.data.added); | this.AttackEntitiesByPreference(msg.data.added); | ||||
} | } | ||||
}, | }, | ||||
"LosHealRangeUpdate": function(msg) { | "LosHealRangeUpdate": function(msg) { | ||||
this.RespondToHealableEntities(msg.data.added); | this.RespondToHealableEntities(msg.data.added); | ||||
}, | }, | ||||
"MoveStarted": function() { | |||||
this.SelectAnimation("move"); | |||||
}, | |||||
"MoveCompleted": function() { | |||||
this.SelectAnimation("idle"); | |||||
}, | |||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
// bit of a sanity check, but this happening would most likely mean a bug somewhere. | |||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
if (cmpUnitMotion && cmpUnitMotion.IsTryingToMove()) | |||||
warn("Entity " + this.entity + " is in the idle state but trying to move"); | |||||
if (!this.isIdle) | if (!this.isIdle) | ||||
{ | { | ||||
this.isIdle = true; | this.isIdle = true; | ||||
Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); | Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); | ||||
} | } | ||||
}, | }, | ||||
}, | }, | ||||
"WALKING": { | "WALKING": { | ||||
"enter": function() { | "enter": function () { | ||||
this.SelectAnimation("move"); | this.StartTimer(0, 1000); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "leave": function () { | ||||
this.StopTimer(); | |||||
}, | |||||
"Timer": function(msg) { | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (!this.order.data.target && cmpObstructionManager.IsInPointRange(this.entity, this.order.data.x, this.order.data.z, this.order.data.min || 0, this.order.data.max || 1, true) | |||||
|| this.order.data.target && cmpObstructionManager.IsInTargetRange(this.entity, this.order.data.target, this.order.data.min || 0, this.order.data.max || 1, true)) | |||||
{ | |||||
this.StopMoving(); | |||||
this.FinishOrder(); | this.FinishOrder(); | ||||
} | |||||
}, | }, | ||||
}, | }, | ||||
"WALKINGANDFIGHTING": { | "WALKINGANDFIGHTING": { | ||||
"enter": function() { | "enter": function() { | ||||
// Show weapons rather than carried resources. | // Show weapons rather than carried resources. | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
this.StartTimer(0, 1000); | this.StartTimer(0, 1000); | ||||
this.SelectAnimation("move"); | |||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (cmpObstructionManager.IsInPointRange(this.entity, this.order.data.x, this.order.data.z, 0, 1, true)) | |||||
{ | |||||
this.StopMoving(); | |||||
this.FinishOrder(); | |||||
} | |||||
}, | }, | ||||
"leave": function(msg) { | "leave": function(msg) { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (cmpObstructionManager.IsInPointRange(this.entity, this.order.data.x, this.order.data.z, 0, 1, true)) | |||||
{ | |||||
this.StopMoving(); | |||||
this.FinishOrder(); | this.FinishOrder(); | ||||
} | |||||
}, | }, | ||||
}, | }, | ||||
"PATROL": { | "PATROL": { | ||||
"enter": function() { | "enter": function() { | ||||
// Memorize the origin position in case that we want to go back | // Memorize the origin position in case that we want to go back | ||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | if (!cmpPosition || !cmpPosition.IsInWorld()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
if (!this.patrolStartPosOrder) | if (!this.patrolStartPosOrder) | ||||
{ | { | ||||
this.patrolStartPosOrder = cmpPosition.GetPosition(); | this.patrolStartPosOrder = cmpPosition.GetPosition(); | ||||
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | ||||
this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture; | this.patrolStartPosOrder.allowCapture = this.order.data.allowCapture; | ||||
} | } | ||||
this.StartTimer(0, 1000); | this.StartTimer(0, 1000); | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
this.SelectAnimation("move"); | |||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
delete this.patrolStartPosOrder; | delete this.patrolStartPosOrder; | ||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.StopMoving(); | |||||
if (this.orderQueue.length == 1) | if (this.orderQueue.length == 1) | ||||
this.PushOrder("Patrol", this.patrolStartPosOrder); | this.PushOrder("Patrol", this.patrolStartPosOrder); | ||||
this.PushOrder(this.order.type, this.order.data); | this.PushOrder(this.order.type, this.order.data); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
}, | }, | ||||
"GUARD": { | "GUARD": { | ||||
"RemoveGuard": function() { | "RemoveGuard": function() { | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
"ESCORTING": { | "ESCORTING": { | ||||
"enter": function() { | "enter": function() { | ||||
// Show weapons rather than carried resources. | // Show weapons rather than carried resources. | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
this.StartTimer(0, 1000); | this.StartTimer(0, 1000); | ||||
this.SelectAnimation("move"); | |||||
this.SetHeldPositionOnEntity(this.isGuardOf); | this.SetHeldPositionOnEntity(this.isGuardOf); | ||||
return false; | return false; | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
// Check the target is alive | // Check the target is alive | ||||
if (!this.TargetIsAlive(this.isGuardOf)) | if (!this.TargetIsAlive(this.isGuardOf)) | ||||
{ | { | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
this.SetHeldPositionOnEntity(this.isGuardOf); | this.SetHeldPositionOnEntity(this.isGuardOf); | ||||
this.ResetMoveSpeed(); | |||||
// Adapt the speed to the one of the target if needed | |||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
if (cmpObstructionManager.IsInTargetRange(this.entity, this.isGuardOf, 0, 3*this.guardRange, true)) | |||||
{ | |||||
var cmpOtherMotion = Engine.QueryInterface(this.isGuardOf, IID_UnitMotion); | |||||
if (cmpOtherMotion) | |||||
{ | |||||
let otherSpeed = cmpOtherMotion.GetSpeed(); | |||||
let mySpeed = cmpUnitMotion.GetSpeed(); | |||||
let speed = otherSpeed / mySpeed; | |||||
if (speed < this.GetWalkSpeed()) | |||||
this.SetMoveSpeedRatio(speed); | |||||
} | |||||
} | |||||
}, | }, | ||||
"leave": function(msg) { | "leave": function(msg) { | ||||
this.ResetMoveSpeed(); | this.ResetMoveSpeed(); | ||||
this.StopTimer(); | this.StopTimer(); | ||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
}, | }, | ||||
"MoveStarted": function(msg) { | "enter": function(msg) { | ||||
// Adapt the speed to the one of the target if needed | // Adapt the speed to the one of the target if needed | ||||
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
if (cmpObstructionManager.IsInTargetRange(this.entity, this.isGuardOf, 0, 3 * this.guardRange, true)) | if (cmpObstructionManager.IsInTargetRange(this.entity, this.isGuardOf, 0, 3 * this.guardRange, true)) | ||||
{ | { | ||||
var cmpUnitAI = Engine.QueryInterface(this.isGuardOf, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(this.isGuardOf, IID_UnitAI); | ||||
if (cmpUnitAI) | if (cmpUnitAI) | ||||
{ | { | ||||
var speed = cmpUnitAI.GetWalkSpeed(); | var speed = cmpUnitAI.GetWalkSpeed(); | ||||
if (speed < this.GetWalkSpeed()) | if (speed < this.GetWalkSpeed()) | ||||
this.SetMoveSpeedRatio(speed / this.GetWalkSpeed()); | this.SetMoveSpeedRatio(speed / this.GetWalkSpeed()); | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.StopMoving(); | |||||
this.ResetMoveSpeed(); | this.ResetMoveSpeed(); | ||||
if (!this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) | if (!this.MoveToTargetRangeExplicit(this.isGuardOf, this.guardRange)) | ||||
this.SetNextState("GUARDING"); | this.SetNextState("GUARDING"); | ||||
}, | }, | ||||
}, | }, | ||||
"GUARDING": { | "GUARDING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.StartTimer(1000, 1000); | this.StartTimer(1000, 1000); | ||||
this.SetHeldPositionOnEntity(this.entity); | this.SetHeldPositionOnEntity(this.entity); | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
this.SelectAnimation("idle"); | |||||
return false; | return false; | ||||
}, | }, | ||||
"LosRangeUpdate": function(msg) { | "LosRangeUpdate": function(msg) { | ||||
// Start attacking one of the newly-seen enemy (if any) | // Start attacking one of the newly-seen enemy (if any) | ||||
if (this.GetStance().targetVisibleEnemies) | if (this.GetStance().targetVisibleEnemies) | ||||
this.AttackEntitiesByPreference(msg.data.added); | this.AttackEntitiesByPreference(msg.data.added); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
// Check the target is alive | // Check the target is alive | ||||
if (!this.TargetIsAlive(this.isGuardOf)) | if (!this.TargetIsAlive(this.isGuardOf)) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// then check is the target has moved | // then check is the target has moved | ||||
if (this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) | // TODO: this should call isInRange, not this. | ||||
if (this.MoveToTargetRangeExplicit(this.isGuardOf, this.guardRange)) | |||||
this.SetNextState("ESCORTING"); | this.SetNextState("ESCORTING"); | ||||
else | else | ||||
{ | { | ||||
// if nothing better to do, check if the guarded needs to be healed or repaired | // if nothing better to do, check if the guarded needs to be healed or repaired | ||||
var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); | var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); | ||||
if (cmpHealth && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())) | if (cmpHealth && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())) | ||||
{ | { | ||||
if (this.CanHeal(this.isGuardOf)) | if (this.CanHeal(this.isGuardOf)) | ||||
Show All 9 Lines | "GUARD": { | ||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
"FLEEING": { | "FLEEING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.PlaySound("panic"); | this.PlaySound("panic"); | ||||
this.ResetMoveSpeed(); | |||||
// Run quickly | |||||
this.SelectAnimation("move"); | |||||
this.SetMoveSpeedRatio(this.GetRunMultiplier()); | |||||
}, | }, | ||||
"HealthChanged": function() { | "HealthChanged": function() { | ||||
this.SetMoveSpeedRatio(this.GetRunMultiplier()); | this.SetMoveSpeedRatio(this.GetRunMultiplier()); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.ResetMoveSpeed(); | this.ResetMoveSpeed(); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
// When we've run far enough, stop fleeing | // When we've run far enough, stop fleeing | ||||
this.StopMoving(); | |||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
// TODO: what if we run into more enemies while fleeing? | // TODO: what if we run into more enemies while fleeing? | ||||
}, | }, | ||||
"COMBAT": { | "COMBAT": { | ||||
"enter": function() { | |||||
// Show weapons rather than carried resources. | |||||
this.SetAnimationVariant("combat"); | |||||
}, | |||||
"leave": function() { | |||||
this.SetDefaultAnimationVariant(); | |||||
}, | |||||
"Order.LeaveFoundation": function(msg) { | "Order.LeaveFoundation": function(msg) { | ||||
// Ignore the order as we're busy. | // Ignore the order as we're busy. | ||||
return { "discardOrder": true }; | return { "discardOrder": true }; | ||||
}, | }, | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
// If we're already in combat mode, ignore anyone else who's attacking us | // If we're already in combat mode, ignore anyone else who's attacking us | ||||
// unless it's a melee attack since they may be blocking our way to the target | // unless it's a melee attack since they may be blocking our way to the target | ||||
if (msg.data.type == "Melee" && (this.GetStance().targetAttackersAlways || !this.order.data.force)) | if (msg.data.type == "Melee" && (this.GetStance().targetAttackersAlways || !this.order.data.force)) | ||||
this.RespondToTargetedEntities([msg.data.attacker]); | this.RespondToTargetedEntities([msg.data.attacker]); | ||||
}, | }, | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"enter": function() { | "enter": function() { | ||||
// Show weapons rather than carried resources. | |||||
this.SetAnimationVariant("combat"); | |||||
this.SelectAnimation("move"); | |||||
this.StartTimer(1000, 1000); | this.StartTimer(1000, 1000); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
// Show carried resources when walking. | |||||
this.SetDefaultAnimationVariant(); | |||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Attack, this.order.data.attackType)) | if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Attack, this.order.data.attackType)) | ||||
{ | { | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
// Return to our original position | // Return to our original position | ||||
if (this.GetStance().respondHoldGround) | if (this.GetStance().respondHoldGround) | ||||
this.WalkToHeldPosition(); | this.WalkToHeldPosition(); | ||||
} | } | ||||
else if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | |||||
{ | |||||
this.StopMoving(); | |||||
// If the unit needs to unpack, do so | |||||
if (this.CanUnpack()) | |||||
this.SetNextState("UNPACKING"); | |||||
else | |||||
this.SetNextState("ATTACKING"); | |||||
} | |||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.StopMoving(); | |||||
if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | ||||
{ | { | ||||
// If the unit needs to unpack, do so | // If the unit needs to unpack, do so | ||||
if (this.CanUnpack()) | if (this.CanUnpack()) | ||||
{ | { | ||||
this.PushOrderFront("Unpack", { "force": true }); | this.PushOrderFront("Unpack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
// add prefix + no capital first letter for attackType | // add prefix + no capital first letter for attackType | ||||
var animationName = "attack_" + this.order.data.attackType.toLowerCase(); | var animationName = "attack_" + this.order.data.attackType.toLowerCase(); | ||||
if (this.IsFormationMember()) | if (this.IsFormationMember()) | ||||
{ | { | ||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
animationName = cmpFormation.GetFormationAnimation(this.entity, animationName); | animationName = cmpFormation.GetFormationAnimation(this.entity, animationName); | ||||
} | } | ||||
this.SetAnimationVariant("combat"); | |||||
this.SelectAnimation(animationName); | this.SelectAnimation(animationName); | ||||
this.SetAnimationSync(prepare, this.attackTimers.repeat); | this.SetAnimationSync(prepare, this.attackTimers.repeat); | ||||
this.StartTimer(prepare, this.attackTimers.repeat); | this.StartTimer(prepare, this.attackTimers.repeat); | ||||
// TODO: we should probably only bother syncing projectile attacks, not melee | // TODO: we should probably only bother syncing projectile attacks, not melee | ||||
// If using a non-default prepare time, re-sync the animation when the timer runs. | // If using a non-default prepare time, re-sync the animation when the timer runs. | ||||
this.resyncAnimation = (prepare != this.attackTimers.prepare) ? true : false; | this.resyncAnimation = (prepare != this.attackTimers.prepare) ? true : false; | ||||
this.FaceTowardsTarget(this.order.data.target); | this.FaceTowardsTarget(this.order.data.target); | ||||
var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | ||||
if (cmpBuildingAI) | if (cmpBuildingAI) | ||||
cmpBuildingAI.SetUnitAITarget(this.order.data.target); | cmpBuildingAI.SetUnitAITarget(this.order.data.target); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); | ||||
if (cmpBuildingAI) | if (cmpBuildingAI) | ||||
cmpBuildingAI.SetUnitAITarget(0); | cmpBuildingAI.SetUnitAITarget(0); | ||||
this.StopTimer(); | this.StopTimer(); | ||||
this.SetDefaultAnimationVariant(); | // Reset combat animation that may have been set. | ||||
this.SelectAnimation("idle"); | |||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
var target = this.order.data.target; | var target = this.order.data.target; | ||||
var cmpFormation = Engine.QueryInterface(target, IID_Formation); | var cmpFormation = Engine.QueryInterface(target, IID_Formation); | ||||
// if the target is a formation, save the attacking formation, and pick a member | // if the target is a formation, save the attacking formation, and pick a member | ||||
if (cmpFormation) | if (cmpFormation) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 109 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
}, | }, | ||||
}, | }, | ||||
"CHASING": { | "CHASING": { | ||||
"enter": function() { | "enter": function() { | ||||
// Show weapons rather than carried resources. | // Show weapons rather than carried resources. | ||||
this.SetAnimationVariant("combat"); | this.SetAnimationVariant("combat"); | ||||
this.SelectAnimation("move"); | |||||
var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); | ||||
if (cmpUnitAI && cmpUnitAI.IsFleeing()) | if (cmpUnitAI && cmpUnitAI.IsFleeing()) | ||||
{ | |||||
// Run after a fleeing target | // Run after a fleeing target | ||||
this.SetMoveSpeedRatio(this.GetRunMultiplier()); | this.SetMoveSpeedRatio(this.GetRunMultiplier()); | ||||
} | |||||
this.StartTimer(1000, 1000); | this.StartTimer(1000, 1000); | ||||
}, | }, | ||||
"HealthChanged": function() { | "HealthChanged": function() { | ||||
var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); | ||||
if (!cmpUnitAI || !cmpUnitAI.IsFleeing()) | if (!cmpUnitAI || !cmpUnitAI.IsFleeing()) | ||||
return; | return; | ||||
this.SetMoveSpeedRatio(this.GetRunMultiplier()); | this.SetMoveSpeedRatio(this.GetRunMultiplier()); | ||||
Show All 13 Lines | "COMBAT": { | ||||
{ | { | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
// Return to our original position | // Return to our original position | ||||
if (this.GetStance().respondHoldGround) | if (this.GetStance().respondHoldGround) | ||||
this.WalkToHeldPosition(); | this.WalkToHeldPosition(); | ||||
} | } | ||||
else if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) | |||||
{ | |||||
this.StopMoving(); | |||||
// If the unit needs to unpack, do so | |||||
if (this.CanUnpack()) | |||||
this.SetNextState("UNPACKING"); | |||||
else | |||||
this.SetNextState("ATTACKING"); | |||||
} | |||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.StopMoving(); | |||||
this.SetNextState("ATTACKING"); | this.SetNextState("ATTACKING"); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
"GATHER": { | "GATHER": { | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("move"); | |||||
this.gatheringTarget = this.order.data.target; // temporary, deleted in "leave". | this.gatheringTarget = this.order.data.target; // temporary, deleted in "leave". | ||||
// check that we can gather from the resource we're supposed to gather from. | // check that we can gather from the resource we're supposed to gather from. | ||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | ||||
var cmpMirage = Engine.QueryInterface(this.gatheringTarget, IID_Mirage); | var cmpMirage = Engine.QueryInterface(this.gatheringTarget, IID_Mirage); | ||||
if ((!cmpMirage || !cmpMirage.Mirages(IID_ResourceSupply)) && | if ((!cmpMirage || !cmpMirage.Mirages(IID_ResourceSupply)) && | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | "GATHER": { | ||||
{ | { | ||||
this.PushOrderFront("ReturnResource", { "target": nearby, "force": false }); | this.PushOrderFront("ReturnResource", { "target": nearby, "force": false }); | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
this.StartTimer(0, 500); | |||||
return false; | return false; | ||||
}, | }, | ||||
"MoveCompleted": function(msg) { | "MoveCompleted": function(msg) { | ||||
if (msg.data.error) | if (msg.data.error) | ||||
{ | { | ||||
this.StopMoving(); | |||||
// We failed to reach the target | // We failed to reach the target | ||||
// remove us from the list of entities gathering from Resource. | // remove us from the list of entities gathering from Resource. | ||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | ||||
if (cmpSupply && cmpOwnership) | if (cmpSupply && cmpOwnership) | ||||
cmpSupply.RemoveGatherer(this.entity, cmpOwnership.GetOwner()); | cmpSupply.RemoveGatherer(this.entity, cmpOwnership.GetOwner()); | ||||
else if (cmpSupply) | else if (cmpSupply) | ||||
Show All 24 Lines | "GATHER": { | ||||
return; | return; | ||||
} | } | ||||
// Couldn't find anything else. Just try this one again, | // Couldn't find anything else. Just try this one again, | ||||
// maybe we'll succeed next time | // maybe we'll succeed next time | ||||
this.PerformGather(oldTarget, false, false); | this.PerformGather(oldTarget, false, false); | ||||
return; | return; | ||||
} | } | ||||
else if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer)) | |||||
{ | |||||
this.StopMoving(); | |||||
// We reached the target - start gathering from it now | // We reached the target - start gathering from it now | ||||
this.SetNextState("GATHERING"); | this.SetNextState("GATHERING"); | ||||
} | |||||
}, | |||||
"Timer": function() { | |||||
if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer)) | |||||
{ | |||||
this.StopMoving(); | |||||
this.SetNextState("GATHERING"); | |||||
} | |||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopTimer(); | |||||
// don't use ownership because this is called after a conversion/resignation | // don't use ownership because this is called after a conversion/resignation | ||||
// and the ownership would be invalid then. | // and the ownership would be invalid then. | ||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | ||||
if (cmpSupply) | if (cmpSupply) | ||||
cmpSupply.RemoveGatherer(this.entity); | cmpSupply.RemoveGatherer(this.entity); | ||||
delete this.gatheringTarget; | delete this.gatheringTarget; | ||||
this.SetDefaultAnimationVariant(); | |||||
}, | }, | ||||
}, | }, | ||||
// Walking to a good place to gather resources near, used by GatherNearPosition | // Walking to a good place to gather resources near, used by GatherNearPosition | ||||
"WALKING": { | "WALKING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("move"); | |||||
}, | }, | ||||
"MoveCompleted": function(msg) { | "MoveCompleted": function(msg) { | ||||
if (msg.data.error) | |||||
this.StopMoving(); | |||||
var resourceType = this.order.data.type; | var resourceType = this.order.data.type; | ||||
var resourceTemplate = this.order.data.template; | var resourceTemplate = this.order.data.template; | ||||
// Try to find another nearby target of the same specific type | // Try to find another nearby target of the same specific type | ||||
// Also don't switch to a different type of huntable animal | // Also don't switch to a different type of huntable animal | ||||
var nearby = this.FindNearbyResource(function(ent, type, template) { | var nearby = this.FindNearbyResource(function(ent, type, template) { | ||||
return ( | return ( | ||||
(type.generic == "treasure" && resourceType.generic == "treasure") | (type.generic == "treasure" && resourceType.generic == "treasure") | ||||
Show All 15 Lines | "GATHER": { | ||||
// Nothing better to do: go back to dropsite | // Nothing better to do: go back to dropsite | ||||
var nearby = this.FindNearestDropsite(resourceType.generic); | var nearby = this.FindNearestDropsite(resourceType.generic); | ||||
if (nearby) | if (nearby) | ||||
{ | { | ||||
this.PushOrderFront("ReturnResource", { "target": nearby, "force": false }); | this.PushOrderFront("ReturnResource", { "target": nearby, "force": false }); | ||||
return; | return; | ||||
} | } | ||||
// No dropsites, just give up | // No dropsites, just give up | ||||
}, | }, | ||||
}, | }, | ||||
"GATHERING": { | "GATHERING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.gatheringTarget = this.order.data.target; // deleted in "leave". | this.gatheringTarget = this.order.data.target; // deleted in "leave". | ||||
Show All 32 Lines | "GATHER": { | ||||
return false; | return false; | ||||
} | } | ||||
// No rate, give up on gathering | // No rate, give up on gathering | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
// Scale timing interval based on rate, and start timer | // Range check: if we are in-range, start the gathering animation and set the timer | ||||
// The offset should be at least as long as the repeat time so we use the same value for both. | // If we are not in-range, we'll set a different timer to avoid waiting an inordinate amount of time. | ||||
var offset = 1000/rate; | |||||
var repeat = offset; | |||||
this.StartTimer(offset, repeat); | |||||
// We want to start the gather animation as soon as possible, | |||||
// but only if we're actually at the target and it's still alive | |||||
// (else it'll look like we're chopping empty air). | |||||
// (If it's not alive, the Timer handler will deal with sending us | |||||
// off to a different target.) | |||||
if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer)) | if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer)) | ||||
{ | { | ||||
// This will show the carried resource if relevant | |||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
this.FaceTowardsTarget(this.gatheringTarget); | |||||
var typename = "gather_" + this.order.data.type.specific; | var typename = "gather_" + this.order.data.type.specific; | ||||
this.SelectAnimation(typename); | this.SelectAnimation(typename); | ||||
// Scale timing interval based on rate, and start timer | |||||
// The offset should be at least as long as the repeat time so we use the same value for both. | |||||
var offset = 1000/rate; | |||||
var repeat = offset; | |||||
this.StartTimer(offset, repeat); | |||||
} | } | ||||
else | |||||
this.StartTimer(0, 1000); | |||||
return false; | return false; | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
// don't use ownership because this is called after a conversion/resignation | // don't use ownership because this is called after a conversion/resignation | ||||
// and the ownership would be invalid then. | // and the ownership would be invalid then. | ||||
var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | ||||
if (cmpSupply) | if (cmpSupply) | ||||
cmpSupply.RemoveGatherer(this.entity); | cmpSupply.RemoveGatherer(this.entity); | ||||
delete this.gatheringTarget; | delete this.gatheringTarget; | ||||
// Show the carried resource, if we've gathered anything. | this.SelectAnimation("idle"); | ||||
// This will show the carried resource if relevant | |||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
var resourceTemplate = this.order.data.template; | var resourceTemplate = this.order.data.template; | ||||
var resourceType = this.order.data.type; | var resourceType = this.order.data.type; | ||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
Show All 17 Lines | "GATHER": { | ||||
// If we've already got some resources but they're the wrong type, | // If we've already got some resources but they're the wrong type, | ||||
// drop them first to ensure we're only ever carrying one type | // drop them first to ensure we're only ever carrying one type | ||||
if (cmpResourceGatherer.IsCarryingAnythingExcept(resourceType.generic)) | if (cmpResourceGatherer.IsCarryingAnythingExcept(resourceType.generic)) | ||||
cmpResourceGatherer.DropResources(); | cmpResourceGatherer.DropResources(); | ||||
// Collect from the target | // Collect from the target | ||||
var status = cmpResourceGatherer.PerformGather(this.gatheringTarget); | var status = cmpResourceGatherer.PerformGather(this.gatheringTarget); | ||||
// This will show the carried resource if relevant | |||||
this.SetDefaultAnimationVariant(); | |||||
// If we've collected as many resources as possible, | // If we've collected as many resources as possible, | ||||
// return to the nearest dropsite | // return to the nearest dropsite | ||||
if (status.filled) | if (status.filled) | ||||
{ | { | ||||
var nearby = this.FindNearestDropsite(resourceType.generic); | var nearby = this.FindNearestDropsite(resourceType.generic); | ||||
if (nearby) | if (nearby) | ||||
{ | { | ||||
// (Keep this Gather order on the stack so we'll | // (Keep this Gather order on the stack so we'll | ||||
Show All 21 Lines | "GATHER": { | ||||
} | } | ||||
// Can't reach the target, or it doesn't exist any more | // Can't reach the target, or it doesn't exist any more | ||||
// We want to carry on gathering resources in the same area as | // We want to carry on gathering resources in the same area as | ||||
// the old one. So try to get close to the old resource's | // the old one. So try to get close to the old resource's | ||||
// last known position | // last known position | ||||
var maxRange = 8; // get close but not too close | var range = 4; // get close but not too close | ||||
if (this.order.data.lastPos && | if (this.order.data.lastPos && | ||||
this.MoveToPointRange(this.order.data.lastPos.x, this.order.data.lastPos.z, | this.MoveToPointRange(this.order.data.lastPos.x, this.order.data.lastPos.z, range)) | ||||
0, maxRange)) | |||||
{ | { | ||||
this.SetNextState("APPROACHING"); | this.SetNextState("APPROACHING"); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// We're already in range, can't get anywhere near it or the target is exhausted. | // We're already in range, can't get anywhere near it or the target is exhausted. | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | "HEAL": { | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
// If we stand ground we will rather die than flee | // If we stand ground we will rather die than flee | ||||
if (!this.GetStance().respondStandGround && !this.order.data.force) | if (!this.GetStance().respondStandGround && !this.order.data.force) | ||||
this.Flee(msg.data.attacker, false); | this.Flee(msg.data.attacker, false); | ||||
}, | }, | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("move"); | |||||
this.StartTimer(1000, 1000); | this.StartTimer(1000, 1000); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Heal, null)) | if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Heal, null)) | ||||
{ | { | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
// Return to our original position | // Return to our original position | ||||
if (this.GetStance().respondHoldGround) | if (this.GetStance().respondHoldGround) | ||||
this.WalkToHeldPosition(); | this.WalkToHeldPosition(); | ||||
} | } | ||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.StopMoving(); | |||||
this.SetNextState("HEALING"); | this.SetNextState("HEALING"); | ||||
}, | }, | ||||
}, | }, | ||||
"HEALING": { | "HEALING": { | ||||
"enter": function() { | "enter": function() { | ||||
var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); | var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); | ||||
this.healTimers = cmpHeal.GetTimers(); | this.healTimers = cmpHeal.GetTimers(); | ||||
Show All 14 Lines | "HEAL": { | ||||
// If using a non-default prepare time, re-sync the animation when the timer runs. | // If using a non-default prepare time, re-sync the animation when the timer runs. | ||||
this.resyncAnimation = (prepare != this.healTimers.prepare) ? true : false; | this.resyncAnimation = (prepare != this.healTimers.prepare) ? true : false; | ||||
this.FaceTowardsTarget(this.order.data.target); | this.FaceTowardsTarget(this.order.data.target); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.SelectAnimation("idle"); | |||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
var target = this.order.data.target; | var target = this.order.data.target; | ||||
// Check the target is still alive and healable | // Check the target is still alive and healable | ||||
if (this.TargetIsAlive(target) && this.CanHeal(target)) | if (this.TargetIsAlive(target) && this.CanHeal(target)) | ||||
{ | { | ||||
Show All 39 Lines | "HEAL": { | ||||
// Return to our original position | // Return to our original position | ||||
if (this.GetStance().respondHoldGround) | if (this.GetStance().respondHoldGround) | ||||
this.WalkToHeldPosition(); | this.WalkToHeldPosition(); | ||||
}, | }, | ||||
}, | }, | ||||
"CHASING": { | "CHASING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("move"); | |||||
this.StartTimer(1000, 1000); | this.StartTimer(1000, 1000); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Heal, null)) | if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Heal, null)) | ||||
{ | { | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
// Return to our original position | // Return to our original position | ||||
if (this.GetStance().respondHoldGround) | if (this.GetStance().respondHoldGround) | ||||
this.WalkToHeldPosition(); | this.WalkToHeldPosition(); | ||||
} | } | ||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.StopMoving(); | |||||
this.SetNextState("HEALING"); | this.SetNextState("HEALING"); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
// Returning to dropsite | // Returning to dropsite | ||||
"RETURNRESOURCE": { | "RETURNRESOURCE": { | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("move"); | this.StartTimer(0, 1000); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "leave": function() { | ||||
// Switch back to idle animation to guarantee we won't | this.StopTimer(); | ||||
// get stuck with the carry animation after stopping moving | }, | ||||
this.SelectAnimation("idle"); | |||||
"Timer": function() { | |||||
// Check the dropsite is in range and we can return our resource there | // Check the dropsite is in range and we can return our resource there | ||||
// (we didn't get stopped before reaching it) | // (we didn't get stopped before reaching it) | ||||
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer) && this.CanReturnResource(this.order.data.target, true)) | if (!this.CanReturnResource(this.order.data.target, true)) | ||||
{ | |||||
this.StopMoving(); | |||||
// The dropsite was destroyed, or we couldn't reach it, or ownership changed | |||||
// Look for a new one. | |||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | |||||
var genericType = cmpResourceGatherer.GetMainCarryingType(); | |||||
var nearby = this.FindNearestDropsite(genericType); | |||||
if (nearby) | |||||
{ | |||||
this.FinishOrder(); | |||||
this.PushOrderFront("ReturnResource", { "target": nearby, "force": false }); | |||||
return; | |||||
} | |||||
// Oh no, couldn't find any drop sites. Give up on returning. | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer)) | |||||
{ | { | ||||
this.StopMoving(); | |||||
var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite); | var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite); | ||||
// this ought to be redundant with the above check. | |||||
if (cmpResourceDropsite) | if (cmpResourceDropsite) | ||||
{ | { | ||||
// Dump any resources we can | // Dump any resources we can | ||||
var dropsiteTypes = cmpResourceDropsite.GetTypes(); | var dropsiteTypes = cmpResourceDropsite.GetTypes(); | ||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | ||||
cmpResourceGatherer.CommitResources(dropsiteTypes); | cmpResourceGatherer.CommitResources(dropsiteTypes); | ||||
// Stop showing the carried resource animation. | // Stop showing the carried resource animation. | ||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | ||||
// Our next order should always be a Gather, | // Our next order should always be a Gather, | ||||
// so just switch back to that order | // so just switch back to that order | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
// The dropsite was destroyed, or we couldn't reach it, or ownership changed | |||||
// Look for a new one. | |||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | |||||
var genericType = cmpResourceGatherer.GetMainCarryingType(); | |||||
var nearby = this.FindNearestDropsite(genericType); | |||||
if (nearby) | |||||
{ | |||||
this.FinishOrder(); | |||||
this.PushOrderFront("ReturnResource", { "target": nearby, "force": false }); | |||||
return; | |||||
} | |||||
// Oh no, couldn't find any drop sites. Give up on returning. | |||||
this.FinishOrder(); | |||||
}, | }, | ||||
"MoveCompleted": "Timer", | |||||
}, | }, | ||||
}, | }, | ||||
"TRADE": { | "TRADE": { | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
// Ignore attack | // Ignore attack | ||||
// TODO: Inform player | // TODO: Inform player | ||||
}, | }, | ||||
"APPROACHINGMARKET": { | "APPROACHINGMARKET": { | ||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("move"); | this.StartTimer(1000, 1000); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "leave": function() { | ||||
this.StopTimer(); | |||||
}, | |||||
"Timer": function() { | |||||
if (this.CheckTargetRange(this.order.data.target, IID_Trader)) | |||||
{ | |||||
this.StopMoving(); | |||||
if (this.waypoints && this.waypoints.length) | if (this.waypoints && this.waypoints.length) | ||||
{ | { | ||||
if (!this.MoveToMarket(this.order.data.target)) | if (!this.MoveToMarket(this.order.data.target)) | ||||
this.StopTrading(); | this.StopTrading(); | ||||
} | } | ||||
else | else | ||||
this.PerformTradeAndMoveToNextMarket(this.order.data.target); | this.PerformTradeAndMoveToNextMarket(this.order.data.target); | ||||
} | |||||
}, | }, | ||||
"MoveCompleted": "Timer", | |||||
}, | }, | ||||
"TradingCanceled": function(msg) { | "TradingCanceled": function(msg) { | ||||
if (msg.market != this.order.data.target) | if (msg.market != this.order.data.target) | ||||
return; | return; | ||||
let cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); | let cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); | ||||
let otherMarket = cmpTrader && cmpTrader.GetFirstMarket(); | let otherMarket = cmpTrader && cmpTrader.GetFirstMarket(); | ||||
this.StopTrading(); | this.StopTrading(); | ||||
if (otherMarket) | if (otherMarket) | ||||
this.WalkToTarget(otherMarket); | this.g(otherMarket); | ||||
}, | }, | ||||
}, | }, | ||||
"REPAIR": { | "REPAIR": { | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("move"); | this.StartTimer(0, 1000); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "leave": function() { | ||||
this.StopTimer(); | |||||
}, | |||||
"Timer": function() { | |||||
if (this.CheckTargetRange(this.order.data.target, IID_Builder)) | |||||
{ | |||||
this.StopMoving(); | |||||
this.SetNextState("REPAIRING"); | |||||
} | |||||
}, | |||||
// TODO: clean this up when MoveCompleted becomes MoveSuccesHint and MoveFailure or something | |||||
"MoveCompleted": function(msg) { | |||||
this.StopMoving(); | |||||
this.SetNextState("REPAIRING"); | this.SetNextState("REPAIRING"); | ||||
}, | }, | ||||
}, | }, | ||||
"REPAIRING": { | "REPAIRING": { | ||||
"enter": function() { | "enter": function() { | ||||
// If this order was forced, the player probably gave it, but now we've reached the target | // If this order was forced, the player probably gave it, but now we've reached the target | ||||
// switch to an unforced order (can be interrupted by attacks) | // switch to an unforced order (can be interrupted by attacks) | ||||
Show All 28 Lines | "REPAIR": { | ||||
this.OnGlobalConstructionFinished({"entity": this.repairTarget, "newentity": this.repairTarget}); | this.OnGlobalConstructionFinished({"entity": this.repairTarget, "newentity": this.repairTarget}); | ||||
return true; | return true; | ||||
} | } | ||||
let cmpBuilderList = QueryBuilderListInterface(this.repairTarget); | let cmpBuilderList = QueryBuilderListInterface(this.repairTarget); | ||||
if (cmpBuilderList) | if (cmpBuilderList) | ||||
cmpBuilderList.AddBuilder(this.entity); | cmpBuilderList.AddBuilder(this.entity); | ||||
this.FaceTowardsTarget(this.repairTarget); | |||||
this.SelectAnimation("build"); | this.SelectAnimation("build"); | ||||
this.StartTimer(1000, 1000); | this.StartTimer(1000, 1000); | ||||
return false; | return false; | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
let cmpBuilderList = QueryBuilderListInterface(this.repairTarget); | let cmpBuilderList = QueryBuilderListInterface(this.repairTarget); | ||||
if (cmpBuilderList) | if (cmpBuilderList) | ||||
cmpBuilderList.RemoveBuilder(this.entity); | cmpBuilderList.RemoveBuilder(this.entity); | ||||
delete this.repairTarget; | delete this.repairTarget; | ||||
this.SelectAnimation("idle"); | |||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
// Check we can still reach and repair the target | // Check we can still reach and repair the target | ||||
if (!this.CanRepair(this.repairTarget)) | if (!this.CanRepair(this.repairTarget)) | ||||
{ | { | ||||
// No longer owned by ally, or it doesn't exist any more | // No longer owned by ally, or it doesn't exist any more | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder); | var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder); | ||||
cmpBuilder.PerformBuilding(this.repairTarget); | cmpBuilder.PerformBuilding(this.repairTarget); | ||||
// if the building is completed, the leave() function will be called | // if the building is completed, the leave() function will be called | ||||
// by the ConstructionFinished message | // by the ConstructionFinished message | ||||
// in that case, the repairTarget is deleted, and we can just return | // in that case, the repairTarget is deleted, and we can just return | ||||
if (!this.repairTarget) | if (!this.repairTarget) | ||||
return; | return; | ||||
if (!this.CheckTargetRange(this.repairTarget, IID_Builder)) | |||||
{ | |||||
if (this.MoveToTargetRange(this.repairTarget, IID_Builder)) | if (this.MoveToTargetRange(this.repairTarget, IID_Builder)) | ||||
this.SetNextState("APPROACHING"); | this.SetNextState("APPROACHING"); | ||||
else if (!this.CheckTargetRange(this.repairTarget, IID_Builder)) | else | ||||
this.FinishOrder(); //can't approach and isn't in reach | this.FinishOrder(); //can't approach and isn't in reach | ||||
} | |||||
}, | }, | ||||
}, | }, | ||||
"ConstructionFinished": function(msg) { | "ConstructionFinished": function(msg) { | ||||
if (msg.data.entity != this.order.data.target) | if (msg.data.entity != this.order.data.target) | ||||
return; // ignore other buildings | return; // ignore other buildings | ||||
// Save the current order's data in case we need it later | // Save the current order's data in case we need it later | ||||
▲ Show 20 Lines • Show All 100 Lines • ▼ Show 20 Lines | "GARRISON": { | ||||
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); | Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); | ||||
delete this.pickup; | delete this.pickup; | ||||
} | } | ||||
}, | }, | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("move"); | this.StartTimer(1000,1000); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "leave": function() { | ||||
this.StopTimer(); | |||||
}, | |||||
"Timer": function() { | |||||
if (this.IsUnderAlert() && this.alertGarrisoningTarget) | |||||
{ | |||||
// check that we can garrison in the building we're supposed to garrison in | |||||
var cmpGarrisonHolder = Engine.QueryInterface(this.alertGarrisoningTarget, IID_GarrisonHolder); | |||||
if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | |||||
{ | |||||
// Try to find another nearby building | |||||
var nearby = this.FindNearbyGarrisonHolder(); | |||||
if (nearby) | |||||
{ | |||||
this.alertGarrisoningTarget = nearby; | |||||
this.ReplaceOrder("Garrison", {"target": this.alertGarrisoningTarget}); | |||||
} | |||||
else | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
} | |||||
if (this.order.data.target && this.CheckGarrisonRange(this.order.data.target)) | |||||
this.SetNextState("GARRISONED"); | |||||
else if (this.alertGarrisoningTarget && this.CheckGarrisonRange(this.alertGarrisoningTarget)) | |||||
this.SetNextState("GARRISONED"); | this.SetNextState("GARRISONED"); | ||||
}, | }, | ||||
"MoveCompleted": "Timer", | |||||
}, | }, | ||||
"GARRISONED": { | "GARRISONED": { | ||||
"enter": function() { | "enter": function() { | ||||
if (this.order.data.target) | if (this.order.data.target) | ||||
var target = this.order.data.target; | var target = this.order.data.target; | ||||
else | else | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | "GARRISON": { | ||||
var cmpUnitAI = Engine.QueryInterface(this.pickup, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(this.pickup, IID_UnitAI); | ||||
if (!cmpUnitAI || !cmpUnitAI.HasPickupOrder(this.entity)) | if (!cmpUnitAI || !cmpUnitAI.HasPickupOrder(this.entity)) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
if (this.MoveToTarget(target)) | if (this.MoveToTarget(target, true)) | ||||
{ | { | ||||
this.SetNextState("APPROACHING"); | this.SetNextState("APPROACHING"); | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// Garrisoning failed for some reason, so finish the order | // Garrisoning failed for some reason, so finish the order | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
Show All 14 Lines | "CHEERING": { | ||||
this.StartTimer(2800, 2800); | this.StartTimer(2800, 2800); | ||||
return false; | return false; | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver); | var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver); | ||||
cmpDamageReceiver.SetInvulnerability(false); | cmpDamageReceiver.SetInvulnerability(false); | ||||
this.SelectAnimation("idle"); | |||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
}, | }, | ||||
"PACKING": { | "PACKING": { | ||||
Show All 28 Lines | "UNPACKING": { | ||||
}, | }, | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
// Ignore attacks while unpacking | // Ignore attacks while unpacking | ||||
}, | }, | ||||
}, | }, | ||||
"PICKUP": { | "PICKUP": { | ||||
"APPROACHING": { | |||||
"enter": function() { | |||||
this.SelectAnimation("move"); | |||||
}, | |||||
"MoveCompleted": function() { | |||||
this.SetNextState("LOADING"); | |||||
}, | |||||
"PickupCanceled": function() { | |||||
this.StopMoving(); | |||||
this.FinishOrder(); | |||||
}, | |||||
}, | |||||
"LOADING": { | |||||
"enter": function() { | "enter": function() { | ||||
this.SelectAnimation("idle"); | |||||
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); | var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); | ||||
if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return true; | return true; | ||||
} | } | ||||
return false; | return false; | ||||
}, | }, | ||||
"PickupCanceled": function() { | "PickupCanceled": function() { | ||||
this.StopMoving(); | |||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
}, | |||||
"ANIMAL": { | "ANIMAL": { | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
if (this.template.NaturalBehaviour == "skittish" || | if (this.template.NaturalBehaviour == "skittish" || | ||||
this.template.NaturalBehaviour == "passive") | this.template.NaturalBehaviour == "passive") | ||||
{ | { | ||||
this.Flee(msg.data.attacker, false); | this.Flee(msg.data.attacker, false); | ||||
} | } | ||||
else if (this.IsDangerousAnimal() || this.template.NaturalBehaviour == "defensive") | else if (this.IsDangerousAnimal() || this.template.NaturalBehaviour == "defensive") | ||||
{ | { | ||||
if (this.CanAttack(msg.data.attacker)) | if (this.CanAttack(msg.data.attacker)) | ||||
this.Attack(msg.data.attacker, false); | this.Attack(msg.data.attacker, false); | ||||
} | } | ||||
else if (this.template.NaturalBehaviour == "domestic") | else if (this.template.NaturalBehaviour == "domestic") | ||||
{ | { | ||||
// 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 outside the building. | ||||
var range = 4; | var range = 4; | ||||
if (this.MoveToTargetRangeExplicit(msg.data.target, range, range)) | if (this.MoveToTargetRangeExplicit(msg.data.target, range)) | ||||
{ | { | ||||
// We've started walking to the given point | // We've started walking to the given point | ||||
this.SetNextState("WALKING"); | this.SetNextState("WALKING"); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// We are already at the target, or can't move at all | // We cannot reach the target. | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
} | } | ||||
}, | }, | ||||
"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 | ||||
this.SetNextState("FEEDING"); | this.SetNextState("FEEDING"); | ||||
return true; | return true; | ||||
}, | }, | ||||
}, | }, | ||||
"ROAMING": { | "ROAMING": { | ||||
"enter": function() { | "enter": function() { | ||||
// Walk in a random direction | // Walk in a random direction | ||||
this.SelectAnimation("walk", false, 1); | |||||
this.SetFacePointAfterMove(false); | |||||
this.MoveRandomly(+this.template.RoamDistance); | this.MoveRandomly(+this.template.RoamDistance); | ||||
// Set a random timer to switch to feeding state | // Set a random timer to switch to feeding state | ||||
this.StartTimer(randIntInclusive(+this.template.RoamTimeMin, +this.template.RoamTimeMax)); | this.StartTimer(randIntInclusive(+this.template.RoamTimeMin, +this.template.RoamTimeMax)); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
this.SetFacePointAfterMove(true); | |||||
}, | }, | ||||
"LosRangeUpdate": function(msg) { | "LosRangeUpdate": function(msg) { | ||||
if (this.template.NaturalBehaviour == "skittish") | if (this.template.NaturalBehaviour == "skittish") | ||||
{ | { | ||||
if (msg.data.added.length > 0) | if (msg.data.added.length > 0) | ||||
{ | { | ||||
this.Flee(msg.data.added[0], false); | this.Flee(msg.data.added[0], false); | ||||
Show All 13 Lines | "ROAMING": { | ||||
// find any units that are already in range. | // find any units that are already in range. | ||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.SetNextState("FEEDING"); | this.SetNextState("FEEDING"); | ||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.StopMoving(); | |||||
this.MoveRandomly(+this.template.RoamDistance); | this.MoveRandomly(+this.template.RoamDistance); | ||||
}, | }, | ||||
}, | }, | ||||
"FEEDING": { | "FEEDING": { | ||||
"enter": function() { | "enter": function() { | ||||
// Stop and eat for a while | // Stop and eat for a while | ||||
this.SelectAnimation("feeding"); | this.SelectAnimation("feeding"); | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.StartTimer(randIntInclusive(+this.template.FeedTimeMin, +this.template.FeedTimeMax)); | this.StartTimer(randIntInclusive(+this.template.FeedTimeMin, +this.template.FeedTimeMax)); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.SelectAnimation("idle"); | |||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
"LosRangeUpdate": function(msg) { | "LosRangeUpdate": function(msg) { | ||||
if (this.template.NaturalBehaviour == "skittish") | if (this.template.NaturalBehaviour == "skittish") | ||||
{ | { | ||||
if (msg.data.added.length > 0) | if (msg.data.added.length > 0) | ||||
{ | { | ||||
this.Flee(msg.data.added[0], false); | this.Flee(msg.data.added[0], false); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
// Start attacking one of the newly-seen enemy (if any) | // Start attacking one of the newly-seen enemy (if any) | ||||
else if (this.template.NaturalBehaviour == "violent") | else if (this.template.NaturalBehaviour == "violent") | ||||
{ | { | ||||
this.AttackVisibleEntity(msg.data.added); | this.AttackVisibleEntity(msg.data.added); | ||||
} | } | ||||
}, | }, | ||||
"MoveCompleted": function() { }, | "MoveCompleted": function() { this.StopMoving(); }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.SetNextState("ROAMING"); | this.SetNextState("ROAMING"); | ||||
}, | }, | ||||
}, | }, | ||||
"FLEEING": "INDIVIDUAL.FLEEING", // reuse the same fleeing behaviour for animals | "FLEEING": "INDIVIDUAL.FLEEING", // reuse the same fleeing behaviour for animals | ||||
Show All 32 Lines | UnitAI.prototype.IsTurret = function() | ||||
if (!this.IsGarrisoned()) | if (!this.IsGarrisoned()) | ||||
return false; | return false; | ||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
return cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY; | return cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY; | ||||
}; | }; | ||||
UnitAI.prototype.IsFormationController = function() | UnitAI.prototype.IsFormationController = function() | ||||
{ | { | ||||
return (this.template.FormationController == "true"); | return (this.template.FormationController == "true"); | ||||
fatherbushido: don't forget it :p | |||||
}; | }; | ||||
UnitAI.prototype.IsFormationMember = function() | UnitAI.prototype.IsFormationMember = function() | ||||
{ | { | ||||
return (this.formationController != INVALID_ENTITY); | return (this.formationController != INVALID_ENTITY); | ||||
Not Done Inline Actionsdon't forget it :p fatherbushido: don't forget it :p | |||||
}; | }; | ||||
UnitAI.prototype.HasFinishedOrder = function() | UnitAI.prototype.HasFinishedOrder = function() | ||||
{ | { | ||||
return this.finishedOrder; | return this.finishedOrder; | ||||
}; | }; | ||||
UnitAI.prototype.ResetFinishOrder = function() | UnitAI.prototype.ResetFinishOrder = function() | ||||
▲ Show 20 Lines • Show All 300 Lines • ▼ Show 20 Lines | UnitAI.prototype.FinishOrder = function() | ||||
if (!this.orderQueue.length) | if (!this.orderQueue.length) | ||||
{ | { | ||||
let stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line | let stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line | ||||
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); | let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); | ||||
let template = cmpTemplateManager.GetCurrentTemplateName(this.entity); | let template = cmpTemplateManager.GetCurrentTemplateName(this.entity); | ||||
error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack); | error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack); | ||||
} | } | ||||
// Safety net, in general it's better if unitAI states handle this properly. | |||||
this.StopMoving(); | |||||
this.orderQueue.shift(); | this.orderQueue.shift(); | ||||
this.order = this.orderQueue[0]; | this.order = this.orderQueue[0]; | ||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
if (this.orderQueue.length && (this.IsGarrisoned() || cmpPosition && cmpPosition.IsInWorld())) | if (this.orderQueue.length && (this.IsGarrisoned() || cmpPosition && cmpPosition.IsInWorld())) | ||||
{ | { | ||||
let ret = this.UnitFsm.ProcessMessage(this, | let ret = this.UnitFsm.ProcessMessage(this, | ||||
{ "type": "Order."+this.order.type, "data": this.order.data } | { "type": "Order."+this.order.type, "data": this.order.data } | ||||
▲ Show 20 Lines • Show All 133 Lines • ▼ Show 20 Lines | UnitAI.prototype.ReplaceOrder = function(type, data) | ||||
if (data && data.force) | if (data && data.force) | ||||
{ | { | ||||
if (this.IsFormationController()) | if (this.IsFormationController()) | ||||
this.CallMemberFunction("UpdateWorkOrders", [type]); | this.CallMemberFunction("UpdateWorkOrders", [type]); | ||||
else | else | ||||
this.UpdateWorkOrders(type); | this.UpdateWorkOrders(type); | ||||
} | } | ||||
this.StopMoving(); | |||||
let garrisonHolder = this.IsGarrisoned() && type != "Ungarrison" ? this.GetGarrisonHolder() : null; | let garrisonHolder = this.IsGarrisoned() && type != "Ungarrison" ? this.GetGarrisonHolder() : null; | ||||
// Special cases of orders that shouldn't be replaced: | // Special cases of orders that shouldn't be replaced: | ||||
// 1. Cheering - we're invulnerable, add order after we finish | // 1. Cheering - we're invulnerable, add order after we finish | ||||
// 2. Packing/unpacking - we're immobile, add order after we finish (unless it's cancel) | // 2. Packing/unpacking - we're immobile, add order after we finish (unless it's cancel) | ||||
// TODO: maybe a better way of doing this would be to use priority levels | // TODO: maybe a better way of doing this would be to use priority levels | ||||
if (this.order && this.order.type == "Cheering") | if (this.order && this.order.type == "Cheering") | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 171 Lines • ▼ Show 20 Lines | UnitAI.prototype.StopTimer = function() | ||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | ||||
cmpTimer.CancelTimer(this.timer); | cmpTimer.CancelTimer(this.timer); | ||||
this.timer = undefined; | this.timer = undefined; | ||||
}; | }; | ||||
//// Message handlers ///// | //// Message handlers ///// | ||||
UnitAI.prototype.OnMotionChanged = function(msg) | UnitAI.prototype.OnMovePaused = function(msg) | ||||
{ | { | ||||
if (msg.starting && !msg.error) | // TODO: change this. Doesn't matter if UnitAI thinks it's completed for now since anyways the states do range checks. | ||||
Not Done Inline Actionsmissing spaces fatherbushido: missing spaces | |||||
this.UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg}); | this.UnitFsm.ProcessMessage(this, { "type": "MoveCompleted", "data": { "error" : false }}); | ||||
else if (!msg.starting || msg.error) | }; | ||||
this.UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg}); | |||||
UnitAI.prototype.OnMoveFailure = function(msg) | |||||
{ | |||||
this.UnitFsm.ProcessMessage(this, { "type": "MoveCompleted", "data": { "error" : true }}); | |||||
Not Done Inline Actionsmissing spaces fatherbushido: missing spaces | |||||
}; | }; | ||||
UnitAI.prototype.OnGlobalConstructionFinished = function(msg) | UnitAI.prototype.OnGlobalConstructionFinished = function(msg) | ||||
{ | { | ||||
// TODO: This is a bit inefficient since every unit listens to every | // TODO: This is a bit inefficient since every unit listens to every | ||||
// construction message - ideally we could scope it to only the one we're building | // construction message - ideally we could scope it to only the one we're building | ||||
this.UnitFsm.ProcessMessage(this, {"type": "ConstructionFinished", "data": msg}); | this.UnitFsm.ProcessMessage(this, {"type": "ConstructionFinished", "data": msg}); | ||||
▲ Show 20 Lines • Show All 289 Lines • ▼ Show 20 Lines | UnitAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0) | ||||
// movement mode that deals with speeds and walk/run automatically | // movement mode that deals with speeds and walk/run automatically | ||||
if (name == "move") | if (name == "move") | ||||
{ | { | ||||
// Speed to switch from walking to running animations | // Speed to switch from walking to running animations | ||||
cmpVisual.SelectMovementAnimation(this.GetWalkSpeed()); | cmpVisual.SelectMovementAnimation(this.GetWalkSpeed()); | ||||
return; | return; | ||||
} | } | ||||
// Set default values if unspecified | |||||
if (once === undefined) | |||||
once = false; | |||||
if (speed === undefined) | |||||
speed = 1.0; | |||||
cmpVisual.SelectAnimation(name, once, speed); | cmpVisual.SelectAnimation(name, once, speed); | ||||
}; | }; | ||||
UnitAI.prototype.SetAnimationSync = function(actiontime, repeattime) | UnitAI.prototype.SetAnimationSync = function(actiontime, repeattime) | ||||
{ | { | ||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | ||||
if (!cmpVisual) | if (!cmpVisual) | ||||
return; | return; | ||||
cmpVisual.SetAnimationSyncRepeat(repeattime); | cmpVisual.SetAnimationSyncRepeat(repeattime); | ||||
cmpVisual.SetAnimationSyncOffset(actiontime); | cmpVisual.SetAnimationSyncOffset(actiontime); | ||||
}; | }; | ||||
UnitAI.prototype.StopMoving = function() | UnitAI.prototype.StopMoving = function() | ||||
{ | { | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
cmpUnitMotion.StopMoving(); | cmpUnitMotion.DiscardMove(); | ||||
}; | }; | ||||
UnitAI.prototype.MoveToPoint = function(x, z) | UnitAI.prototype.MoveToPoint = function(x, z) | ||||
{ | { | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToPointRange(x, z, 0, 0); | return cmpUnitMotion.SetNewDestinationAsPosition(x, z, 0, true); | ||||
}; | }; | ||||
UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax) | UnitAI.prototype.MoveToPointRange = function(x, z, range, evenUnreachable = false) | ||||
{ | { | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax); | return cmpUnitMotion.SetNewDestinationAsPosition(x, z, range, evenUnreachable); | ||||
}; | }; | ||||
UnitAI.prototype.MoveToTarget = function(target) | UnitAI.prototype.MoveToTarget = function(target, evenUnreachable = false) | ||||
{ | { | ||||
if (!this.CheckTargetVisible(target)) | if (!this.CheckTargetVisible(target)) | ||||
return false; | return false; | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToTargetRange(target, 0, 0); | return cmpUnitMotion.SetNewDestinationAsEntity(target, 0, evenUnreachable); | ||||
}; | }; | ||||
UnitAI.prototype.MoveToTargetRange = function(target, iid, type) | UnitAI.prototype.MoveToTargetRange = function(target, iid, type, evenUnreachable = false) | ||||
{ | { | ||||
if (!this.CheckTargetVisible(target) || this.IsTurret()) | if (!this.CheckTargetVisible(target) || this.IsTurret()) | ||||
return false; | return false; | ||||
var cmpRanged = Engine.QueryInterface(this.entity, iid); | var cmpRanged = Engine.QueryInterface(this.entity, iid); | ||||
if (!cmpRanged) | if (!cmpRanged) | ||||
return false; | return false; | ||||
var range = cmpRanged.GetRange(type); | var range = cmpRanged.GetRange(type); | ||||
this.order.data.min = range.min; | |||||
this.order.data.max = range.max; | |||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); | // Generally speaking, try to aim for the middle of a range. | ||||
return cmpUnitMotion.SetNewDestinationAsEntity(target, (range.min + range.max)/2.0, evenUnreachable); | |||||
}; | }; | ||||
/** | /** | ||||
* Move unit so we hope the target is in the attack range | * Move unit so we hope the target is in the attack range | ||||
* for melee attacks, this goes straight to the default range checks | * for melee attacks, this goes straight to the default range checks | ||||
* for ranged attacks, the parabolic range is used | * for ranged attacks, the parabolic range is used | ||||
*/ | */ | ||||
UnitAI.prototype.MoveToTargetAttackRange = function(target, type) | UnitAI.prototype.MoveToTargetAttackRange = function(target, type, evenUnreachable = false) | ||||
{ | { | ||||
// for formation members, the formation will take care of the range check | // for formation members, the formation will take care of the range check | ||||
if (this.IsFormationMember()) | if (this.IsFormationMember()) | ||||
{ | { | ||||
var cmpFormationUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | var cmpFormationUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | ||||
if (cmpFormationUnitAI && cmpFormationUnitAI.IsAttackingAsFormation()) | if (cmpFormationUnitAI && cmpFormationUnitAI.IsAttackingAsFormation()) | ||||
return false; | return false; | ||||
} | } | ||||
var cmpFormation = Engine.QueryInterface(target, IID_Formation); | var cmpFormation = Engine.QueryInterface(target, IID_Formation); | ||||
if (cmpFormation) | if (cmpFormation) | ||||
target = cmpFormation.GetClosestMember(this.entity); | target = cmpFormation.GetClosestMember(this.entity); | ||||
if (type != "Ranged") | if (type != "Ranged") | ||||
return this.MoveToTargetRange(target, IID_Attack, type); | return this.MoveToTargetRange(target, IID_Attack, type, evenUnreachable); | ||||
if (!this.CheckTargetVisible(target)) | if (!this.CheckTargetVisible(target)) | ||||
return false; | return false; | ||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
var range = cmpAttack.GetRange(type); | var range = cmpAttack.GetRange(type); | ||||
var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); | var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
Show All 15 Lines | UnitAI.prototype.MoveToTargetAttackRange = function(target, type, evenUnreachable = false) | ||||
else | else | ||||
// return false? Or hope you come close enough? | // return false? Or hope you come close enough? | ||||
var parabolicMaxRange = 0; | var parabolicMaxRange = 0; | ||||
//return false; | //return false; | ||||
// the parabole changes while walking, take something in the middle | // the parabole changes while walking, take something in the middle | ||||
var guessedMaxRange = (range.max + parabolicMaxRange)/2; | var guessedMaxRange = (range.max + parabolicMaxRange)/2; | ||||
this.order.data.min = range.min; | |||||
this.order.data.max = range.max; | |||||
// TODO: here we should give the desired range based on unit speed, our own desire to walk, and so on. | |||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange)) | if (cmpUnitMotion.SetNewDestinationAsEntity(target, (range.min + guessedMaxRange)/2.0, false)) | ||||
return true; | return true; | ||||
// if that failed, try closer | // if that failed, try closer | ||||
return cmpUnitMotion.MoveToTargetRange(target, range.min, Math.min(range.max, parabolicMaxRange)); | return cmpUnitMotion.SetNewDestinationAsEntity(target, (range.min + Math.min(range.max, parabolicMaxRange))/2.0, evenUnreachable); | ||||
}; | }; | ||||
UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max) | UnitAI.prototype.MoveToTargetRangeExplicit = function(target, range, evenUnreachable = false) | ||||
{ | { | ||||
if (!this.CheckTargetVisible(target)) | if (!this.CheckTargetVisible(target)) | ||||
return false; | return false; | ||||
this.order.data.min = range; | |||||
this.order.data.max = range; | |||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToTargetRange(target, min, max); | return cmpUnitMotion.SetNewDestinationAsEntity(target, range, evenUnreachable); | ||||
}; | }; | ||||
UnitAI.prototype.MoveToGarrisonRange = function(target) | UnitAI.prototype.MoveToGarrisonRange = function(target, evenUnreachable = false) | ||||
{ | { | ||||
if (!this.CheckTargetVisible(target)) | if (!this.CheckTargetVisible(target)) | ||||
return false; | return false; | ||||
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); | var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); | ||||
if (!cmpGarrisonHolder) | if (!cmpGarrisonHolder) | ||||
return false; | return false; | ||||
var range = cmpGarrisonHolder.GetLoadingRange(); | var range = cmpGarrisonHolder.GetLoadingRange(); | ||||
this.order.data.min = range.min; | |||||
this.order.data.max = range.max; | |||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); | return cmpUnitMotion.SetNewDestinationAsEntity(target, (range.min + range.max)/2.0, evenUnreachable); | ||||
}; | }; | ||||
UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max) | UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max) | ||||
{ | { | ||||
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpObstructionManager.IsInPointRange(this.entity, x, z, min, max, true); | return cmpObstructionManager.IsInPointRange(this.entity, x, z, min, max, true); | ||||
}; | }; | ||||
UnitAI.prototype.CheckTargetRange = function(target, iid, type) | UnitAI.prototype.CheckTargetRange = function(target, iid, type) | ||||
{ | { | ||||
var cmpRanged = Engine.QueryInterface(this.entity, iid); | var cmpRanged = Engine.QueryInterface(this.entity, iid); | ||||
if (!cmpRanged) | if (!cmpRanged) | ||||
return false; | return false; | ||||
var range = cmpRanged.GetRange(type); | var range = cmpRanged.GetRange(type); | ||||
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, true); | return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, true); | ||||
}; | }; | ||||
/** | /** | ||||
* Check if the target is inside the attack range | * Check if the target is inside the attack range | ||||
* For melee attacks, this goes straigt to the regular range calculation | * For melee attacks, this goes straigt to the regular range calculation | ||||
* For ranged attacks, the parabolic formula is used to accout for bigger ranges | * For ranged attacks, the parabolic formula is used to accout for bigger ranges | ||||
* when the target is lower, and smaller ranges when the target is higher | * when the target is lower, and smaller ranges when the target is higher | ||||
Show All 32 Lines | UnitAI.prototype.CheckTargetAttackRange = function(target, type) | ||||
var t = targetCmpPosition.GetPosition(); | var t = targetCmpPosition.GetPosition(); | ||||
var h = s.y-t.y+range.elevationBonus; | var h = s.y-t.y+range.elevationBonus; | ||||
var maxRangeSq = 2*range.max*(h + range.max/2); | var maxRangeSq = 2*range.max*(h + range.max/2); | ||||
if (maxRangeSq < 0) | if (maxRangeSq < 0) | ||||
return false; | return false; | ||||
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, Math.sqrt(maxRangeSq), true); | return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, Math.sqrt(maxRangeSq), true); | ||||
}; | }; | ||||
UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max) | UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max) | ||||
{ | { | ||||
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, min, max, true); | return cmpObstructionManager.IsInTargetRange(this.entity, target, min, max, true); | ||||
}; | }; | ||||
UnitAI.prototype.CheckGarrisonRange = function(target) | UnitAI.prototype.CheckGarrisonRange = function(target) | ||||
{ | { | ||||
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); | var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); | ||||
if (!cmpGarrisonHolder) | if (!cmpGarrisonHolder) | ||||
return false; | return false; | ||||
var range = cmpGarrisonHolder.GetLoadingRange(); | var range = cmpGarrisonHolder.GetLoadingRange(); | ||||
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); | var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); | ||||
if (cmpObstruction) | if (cmpObstruction) | ||||
range.max += cmpObstruction.GetUnitRadius()*1.5; // multiply by something larger than sqrt(2) | range.max += cmpObstruction.GetUnitRadius()*1.5; // multiply by something larger than sqrt(2) | ||||
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, true); | return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, true); | ||||
}; | }; | ||||
/** | /** | ||||
* Returns true if the target entity is visible through the FoW/SoD. | * Returns true if the target entity is visible through the FoW/SoD. | ||||
*/ | */ | ||||
UnitAI.prototype.CheckTargetVisible = function(target) | UnitAI.prototype.CheckTargetVisible = function(target) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 539 Lines • ▼ Show 20 Lines | |||||
*/ | */ | ||||
UnitAI.prototype.Attack = function(target, allowCapture = true, queued = false) | UnitAI.prototype.Attack = function(target, allowCapture = true, queued = false) | ||||
{ | { | ||||
if (!this.CanAttack(target)) | if (!this.CanAttack(target)) | ||||
{ | { | ||||
// We don't want to let healers walk to the target unit so they can be easily killed. | // We don't want to let healers walk to the target unit so they can be easily killed. | ||||
// Instead we just let them get into healing range. | // Instead we just let them get into healing range. | ||||
if (this.IsHealer()) | if (this.IsHealer()) | ||||
this.MoveToTargetRange(target, IID_Heal); | this.MoveToTargetRange(target, IID_Heal, true); | ||||
else | else | ||||
this.WalkToTarget(target, queued); | this.WalkToTarget(target, queued); | ||||
return; | return; | ||||
} | } | ||||
this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued); | this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued); | ||||
}; | }; | ||||
/** | /** | ||||
▲ Show 20 Lines • Show All 581 Lines • ▼ Show 20 Lines | |||||
*/ | */ | ||||
UnitAI.prototype.ResetMoveSpeed = function() | UnitAI.prototype.ResetMoveSpeed = function() | ||||
{ | { | ||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
cmpUnitMotion.SetSpeedRatio(1); | cmpUnitMotion.SetSpeedRatio(1); | ||||
}; | }; | ||||
UnitAI.prototype.SetMoveSpeedRatio = function(speed) | UnitAI.prototype.SetMoveSpeedRatio = function(speed) | ||||
{ | { | ||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
Not Done Inline Actions(Formal trivial stuff, perhaps for consistency, namming UnitMotion instead of Motion) fatherbushido: (Formal trivial stuff, perhaps for consistency, namming UnitMotion instead of Motion) | |||||
cmpUnitMotion.SetSpeedRatio(speed); | cmpUnitMotion.SetSpeedRatio(speed); | ||||
Not Done Inline ActionsYou don't use the speed argument. fatherbushido: You don't use the speed argument. | |||||
Not Done Inline ActionsThere is something wrong or I miss something: why that function have a speed argument? Shouldn't it be cmpUnitMotion.SetSpeed(speed); ? fatherbushido: There is something wrong or I miss something: why that function have a speed argument? | |||||
}; | }; | ||||
UnitAI.prototype.SetHeldPosition = function(x, z) | UnitAI.prototype.SetHeldPosition = function(x, z) | ||||
{ | { | ||||
this.heldPosition = {"x": x, "z": z}; | this.heldPosition = {"x": x, "z": z}; | ||||
}; | }; | ||||
UnitAI.prototype.SetHeldPositionOnEntity = function(entity) | UnitAI.prototype.SetHeldPositionOnEntity = function(entity) | ||||
▲ Show 20 Lines • Show All 258 Lines • ▼ Show 20 Lines | UnitAI.prototype.MoveRandomly = function(distance) | ||||
{ | { | ||||
this.roamAngle = (randBool() ? 1 : -1) * Math.PI / 6; | this.roamAngle = (randBool() ? 1 : -1) * Math.PI / 6; | ||||
ang -= this.roamAngle / 2; | ang -= this.roamAngle / 2; | ||||
this.startAngle = ang; | this.startAngle = ang; | ||||
} | } | ||||
else if (Math.abs((ang - this.startAngle + Math.PI) % (2 * Math.PI) - Math.PI) < Math.abs(this.roamAngle / 2)) | else if (Math.abs((ang - this.startAngle + Math.PI) % (2 * Math.PI) - Math.PI) < Math.abs(this.roamAngle / 2)) | ||||
this.roamAngle *= randBool() ? 1 : -1; | this.roamAngle *= randBool() ? 1 : -1; | ||||
let halfDelta = randFloat(this.roamAngle / 4, this.roamAngle * 3 / 4); | ang += randFloat(this.roamAngle / 4, this.roamAngle * 3 / 4) * 2.0; | ||||
// First half rotation to decrease the impression of immediate rotation | |||||
ang += halfDelta; | |||||
cmpUnitMotion.FaceTowardsPoint(pos.x + 0.5 * Math.sin(ang), pos.z + 0.5 * Math.cos(ang)); | |||||
// Then second half of the rotation | |||||
ang += halfDelta; | |||||
let dist = randFloat(0.5, 1.5) * distance; | let dist = randFloat(0.5, 1.5) * distance; | ||||
cmpUnitMotion.MoveToPointRange(pos.x - 0.5 * Math.sin(ang), pos.z - 0.5 * Math.cos(ang), dist, dist); | cmpUnitMotion.SetNewDestinationAsPosition(pos.x - 0.5 * Math.sin(ang), pos.z - 0.5 * Math.cos(ang), dist, true); | ||||
}; | |||||
UnitAI.prototype.SetFacePointAfterMove = function(val) | |||||
{ | |||||
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
if (cmpMotion) | |||||
cmpMotion.SetFacePointAfterMove(val); | |||||
}; | }; | ||||
UnitAI.prototype.AttackEntitiesByPreference = function(ents) | UnitAI.prototype.AttackEntitiesByPreference = function(ents) | ||||
{ | { | ||||
if (!ents.length) | if (!ents.length) | ||||
return false; | return false; | ||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
Show All 33 Lines | if (preferences.length) | ||||
for (let pref of preferences) | for (let pref of preferences) | ||||
if (this.RespondToTargetedEntities(entsByPreferences[pref])) | if (this.RespondToTargetedEntities(entsByPreferences[pref])) | ||||
return true; | return true; | ||||
} | } | ||||
return this.RespondToTargetedEntities(entsWithoutPref); | return this.RespondToTargetedEntities(entsWithoutPref); | ||||
}; | }; | ||||
UnitAI.prototype.IsControllerWaiting = function() | |||||
{ | |||||
if (!this.IsFormationController()) | |||||
return false; | |||||
var state = this.GetCurrentState().split(".").pop(); | |||||
return (state == "MEMBER"); | |||||
}; | |||||
/** | /** | ||||
* Call obj.funcname(args) on UnitAI components of all formation members. | * Call obj.funcname(args) on UnitAI components of all formation members. | ||||
*/ | */ | ||||
UnitAI.prototype.CallMemberFunction = function(funcname, args) | UnitAI.prototype.CallMemberFunction = function(funcname, args) | ||||
{ | { | ||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | ||||
if (!cmpFormation) | if (!cmpFormation) | ||||
return; | return; | ||||
Show All 26 Lines |
Wildfire Games · Phabricator
don't forget it :p