Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/components/UnitAI.js
const WALKING_SPEED = 1.0 | |||||
function UnitAI() {} | function UnitAI() {} | ||||
UnitAI.prototype.Schema = | UnitAI.prototype.Schema = | ||||
"<a:help>Controls the unit's movement, attacks, etc, in response to commands from the player.</a:help>" + | "<a:help>Controls the unit's movement, attacks, etc, in response to commands from the player.</a:help>" + | ||||
"<a:example/>" + | "<a:example/>" + | ||||
"<element name='AlertReactiveLevel'>" + | "<element name='AlertReactiveLevel'>" + | ||||
"<data type='nonNegativeInteger'/>" + | "<data type='nonNegativeInteger'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='DefaultStance'>" + | "<element name='DefaultStance'>" + | ||||
"<choice>" + | "<choice>" + | ||||
"<value>violent</value>" + | "<value>violent</value>" + | ||||
"<value>aggressive</value>" + | "<value>aggressive</value>" + | ||||
"<value>defensive</value>" + | "<value>defensive</value>" + | ||||
"<value>passive</value>" + | "<value>passive</value>" + | ||||
"<value>standground</value>" + | "<value>standground</value>" + | ||||
"</choice>" + | "</choice>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='FormationController'>" + | |||||
"<data type='boolean'/>" + | |||||
"</element>" + | |||||
"<element name='FleeDistance'>" + | "<element name='FleeDistance'>" + | ||||
"<ref name='positiveDecimal'/>" + | "<ref name='positiveDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='CanGuard'>" + | "<element name='CanGuard'>" + | ||||
"<data type='boolean'/>" + | "<data type='boolean'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<optional>" + | "<optional>" + | ||||
"<interleave>" + | "<interleave>" + | ||||
▲ Show 20 Lines • Show All 140 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | ||||
"TradingCanceled": function(msg) { | "TradingCanceled": function(msg) { | ||||
// ignore | // ignore | ||||
}, | }, | ||||
"GuardedAttacked": function(msg) { | "GuardedAttacked": function(msg) { | ||||
// ignore | // ignore | ||||
}, | }, | ||||
// Formation handlers: | |||||
"FormationLeave": function(msg) { | |||||
// ignore when we're not in FORMATIONMEMBER | |||||
}, | |||||
// Called when being told to walk as part of a formation | |||||
"Order.FormationWalk": function(msg) { | |||||
// Let players move captured domestic animals around | |||||
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
// For packable units: | |||||
// 1. If packed, we can move. | |||||
// 2. If unpacked, we first need to pack, then follow case 1. | |||||
if (this.CanPack()) | |||||
{ | |||||
// Case 2: pack | |||||
this.PushOrderFront("Pack", { "force": true }); | |||||
return; | |||||
} | |||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
cmpUnitMotion.MoveToFormationOffset(msg.data.target, msg.data.x, msg.data.z); | |||||
this.SetNextStateAlwaysEntering("FORMATIONMEMBER.WALKING"); | |||||
}, | |||||
// Special orders: | // Special orders: | ||||
// (these will be overridden by various states) | // (these will be overridden by various states) | ||||
"Order.LeaveFoundation": function(msg) { | "Order.LeaveFoundation": function(msg) { | ||||
// If foundation is not ally of entity, or if entity is unpacked siege, | // If foundation is not ally of entity, or if entity is unpacked siege, | ||||
// ignore the order | // ignore the order | ||||
if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) && !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || | if (!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. Since INDIVIDUAL.WALKING checks for a distance in [0,1], move to 0.5 | ||||
let range = 4; | let range = 0.5; | ||||
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("INDIVIDUAL.WALKING"); | this.SetNextState("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) | |||||
"Order.Stop": function(msg) { | "Order.Stop": function(msg) { | ||||
// We have no control over non-domestic animals. | // We have no control over non-domestic animals. | ||||
if (this.IsAnimal() && !this.IsDomestic()) | if (this.IsAnimal() && !this.IsDomestic()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// Stop moving immediately. | // Stop moving immediately. | ||||
this.StopMoving(); | this.StopMoving(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
// No orders left, we're an individual now | // No orders left, we're an individual now | ||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
this.SetNextState("ANIMAL.IDLE"); | this.SetNextState("ANIMAL.IDLE"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.IDLE"); | this.SetNextState("INDIVIDUAL.IDLE"); | ||||
}, | }, | ||||
"Order.GroupWalk": function(msg) { | |||||
if (this.IsAnimal() || this.IsTurret()) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
// For packable units: | |||||
// 1. If packed, we can move. | |||||
// 2. If unpacked, we first need to pack, then follow case 1. | |||||
if (this.CanPack()) | |||||
{ | |||||
// Case 2: pack | |||||
this.PushOrderFront("Pack", { "force": true }); | |||||
return; | |||||
} | |||||
let cmpGroupWalkManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_GroupWalkManager); | |||||
let group = cmpGroupWalkManager.GetGroup(this.order.data.groupID); | |||||
if (!group || group.state != "waiting") | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
this.SetNextStateAlwaysEntering("INDIVIDUAL.GROUPWALKING"); | |||||
}, | |||||
"Order.Walk": function(msg) { | "Order.Walk": function(msg) { | ||||
// Let players move captured domestic animals around | // Let players move captured domestic animals around | ||||
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) | if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) | ||||
{ | { | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | return; | ||||
} | } | ||||
// For packable units: | // For packable units: | ||||
// 1. If packed, we can move. | // 1. If packed, we can move. | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
// Case 2: pack | // Case 2: pack | ||||
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.SetNextState("ANIMAL.WALKING"); | this.SetNextState("ANIMAL.WALKING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.WALKING"); | this.SetNextState("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 46 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.SetNextState("ANIMAL.WALKING"); | this.SetNextState("ANIMAL.WALKING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.WALKING"); | this.SetNextState("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.SetNextState("INDIVIDUAL.PICKUP"); | ||||
this.SetNextState("INDIVIDUAL.PICKUP.APPROACHING"); | |||||
} | |||||
else | |||||
{ | |||||
// We are already at the target, or can't move at all | |||||
this.StopMoving(); | |||||
this.SetNextState("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.SetNextState("INDIVIDUAL.GUARD.ESCORTING"); | this.SetNextState("INDIVIDUAL.GUARD.ESCORTING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.GUARD.GUARDING"); | this.SetNextState("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.SetNextState("ANIMAL.FLEEING"); | this.SetNextState("ANIMAL.FLEEING"); | ||||
else | else | ||||
this.SetNextState("INDIVIDUAL.FLEEING"); | this.SetNextState("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 181 Lines • ▼ Show 20 Lines | if (this.MustKillGatherTarget(this.order.data.target)) | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// We couldn't move there, or the target moved away | // We couldn't move there, or the target moved away | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
} | } | ||||
return; | return; | ||||
} | } | ||||
if (this.order.data.triedAttacking) | |||||
this.PushOrderFront("Attack", { "target": this.order.data.target, "force": false, "hunting": true, "allowCapture": false }); | { | ||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
this.order.data.triedAttacking = true; | |||||
this.PushOrderFront("Attack", { "target": this.order.data.target, "force": this.order.data.force, "hunting": true, "allowCapture": false }); | |||||
return; | return; | ||||
} | } | ||||
// 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.SetNextState("INDIVIDUAL.GATHER.APPROACHING"); | this.SetNextState("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 All 9 Lines | if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer) && this.CanReturnResource(this.order.data.target, true)) | ||||
var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite); | var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite); | ||||
if (cmpResourceDropsite) | if (cmpResourceDropsite) | ||||
{ | { | ||||
// Dump any resources we can | // Dump any resources we can | ||||
var dropsiteTypes = cmpResourceDropsite.GetTypes(); | var dropsiteTypes = cmpResourceDropsite.GetTypes(); | ||||
Engine.QueryInterface(this.entity, IID_ResourceGatherer).CommitResources(dropsiteTypes); | Engine.QueryInterface(this.entity, IID_ResourceGatherer).CommitResources(dropsiteTypes); | ||||
// Stop showing the carried resource animation. | // Stop showing the carried resource animation. | ||||
this.SetGathererAnimationOverride(); | this.SetAnimationVariant(); | ||||
// 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; | ||||
} | } | ||||
} | } | ||||
// Try to move to the dropsite | // Try to move to the dropsite | ||||
Show All 34 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.SetNextState("INDIVIDUAL.REPAIR.APPROACHING"); | this.SetNextState("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 All 13 Lines | "Order.Garrison": function(msg) { | ||||
// 2. If unpacked, we first need to pack, then follow case 1. | // 2. If unpacked, we first need to pack, then follow case 1. | ||||
if (this.CanPack()) | if (this.CanPack()) | ||||
{ | { | ||||
// Case 2: pack | // Case 2: pack | ||||
this.PushOrderFront("Pack", { "force": true }); | this.PushOrderFront("Pack", { "force": true }); | ||||
return; | return; | ||||
} | } | ||||
if (this.MoveToGarrisonRange(this.order.data.target)) | this.MoveToGarrisonRange(this.order.data.target, true) | ||||
{ | |||||
this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING"); | this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING"); | ||||
} | |||||
else | |||||
{ | |||||
// We do a range check before actually garrisoning | |||||
this.StopMoving(); | |||||
this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED"); | |||||
} | |||||
}, | }, | ||||
"Order.Autogarrison": function(msg) { | "Order.Autogarrison": function(msg) { | ||||
if (this.IsTurret()) | if (this.IsTurret()) | ||||
{ | { | ||||
this.SetNextState("IDLE"); | this.SetNextState("IDLE"); | ||||
return; | return; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | ||||
"Order.CancelUnpack": function(msg) { | "Order.CancelUnpack": function(msg) { | ||||
var cmpPack = Engine.QueryInterface(this.entity, IID_Pack); | var cmpPack = Engine.QueryInterface(this.entity, IID_Pack); | ||||
if (cmpPack && cmpPack.IsPacking() && cmpPack.IsPacked()) | if (cmpPack && cmpPack.IsPacking() && cmpPack.IsPacked()) | ||||
cmpPack.CancelPack(); | cmpPack.CancelPack(); | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
}, | }, | ||||
// States for the special entity representing a group of units moving in formation: | |||||
"FORMATIONCONTROLLER": { | |||||
"Order.Walk": function(msg) { | |||||
this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); | |||||
this.MoveToPoint(this.order.data.x, this.order.data.z); | |||||
this.SetNextState("WALKING"); | |||||
}, | |||||
"Order.WalkAndFight": function(msg) { | |||||
this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); | |||||
this.MoveToPoint(this.order.data.x, this.order.data.z); | |||||
this.SetNextState("WALKINGANDFIGHTING"); | |||||
}, | |||||
"Order.MoveIntoFormation": function(msg) { | |||||
this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); | |||||
this.MoveToPoint(this.order.data.x, this.order.data.z); | |||||
this.SetNextState("FORMING"); | |||||
}, | |||||
// Only used by other orders to walk there in formation | |||||
"Order.WalkToTargetRange": function(msg) { | |||||
if (this.MoveToTargetRangeExplicit(this.order.data.target, this.order.data.min, this.order.data.max)) | |||||
this.SetNextState("WALKING"); | |||||
else | |||||
this.FinishOrder(); | |||||
}, | |||||
"Order.WalkToTarget": function(msg) { | |||||
if (this.MoveToTarget(this.order.data.target)) | |||||
this.SetNextState("WALKING"); | |||||
else | |||||
this.FinishOrder(); | |||||
}, | |||||
"Order.WalkToPointRange": function(msg) { | |||||
if (this.MoveToPointRange(this.order.data.x, this.order.data.z, this.order.data.min, this.order.data.max)) | |||||
this.SetNextState("WALKING"); | |||||
else | |||||
this.FinishOrder(); | |||||
}, | |||||
"Order.Patrol": function(msg) { | |||||
this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); | |||||
this.MoveToPoint(this.order.data.x, this.order.data.z); | |||||
this.SetNextState("PATROL"); | |||||
}, | |||||
"Order.Guard": function(msg) { | |||||
this.CallMemberFunction("Guard", [msg.data.target, false]); | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.Disband(); | |||||
}, | |||||
"Order.Stop": function(msg) { | |||||
if (!this.IsAttackingAsFormation()) | |||||
this.CallMemberFunction("Stop", [false]); | |||||
this.FinishOrder(); | |||||
}, | |||||
"Order.Attack": function(msg) { | |||||
var target = msg.data.target; | |||||
var allowCapture = msg.data.allowCapture; | |||||
var cmpTargetUnitAI = Engine.QueryInterface(target, IID_UnitAI); | |||||
if (cmpTargetUnitAI && cmpTargetUnitAI.IsFormationMember()) | |||||
target = cmpTargetUnitAI.GetFormationController(); | |||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckTargetAttackRange(target, target)) | |||||
{ | |||||
if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) | |||||
{ | |||||
if (this.MoveToTargetAttackRange(target, target)) | |||||
{ | |||||
this.SetNextState("COMBAT.APPROACHING"); | |||||
return; | |||||
} | |||||
} | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
this.CallMemberFunction("Attack", [target, false, allowCapture]); | |||||
if (cmpAttack.CanAttackAsFormation()) | |||||
this.SetNextState("COMBAT.ATTACKING"); | |||||
else | |||||
this.SetNextState("MEMBER"); | |||||
}, | |||||
"Order.Garrison": function(msg) { | |||||
if (!Engine.QueryInterface(msg.data.target, IID_GarrisonHolder)) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckGarrisonRange(msg.data.target)) | |||||
{ | |||||
if (!this.CheckTargetVisible(msg.data.target)) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
else | |||||
{ | |||||
// Out of range; move there in formation | |||||
if (this.MoveToGarrisonRange(msg.data.target)) | |||||
{ | |||||
this.SetNextState("GARRISON.APPROACHING"); | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
this.SetNextState("GARRISON.GARRISONING"); | |||||
}, | |||||
"Order.Gather": function(msg) { | |||||
if (this.MustKillGatherTarget(msg.data.target)) | |||||
{ | |||||
// The target was visible when this order was given, | |||||
// but could now be invisible. | |||||
if (!this.CheckTargetVisible(msg.data.target)) | |||||
{ | |||||
if (msg.data.secondTry === undefined) | |||||
{ | |||||
msg.data.secondTry = true; | |||||
this.PushOrderFront("Walk", msg.data.lastPos); | |||||
} | |||||
else | |||||
{ | |||||
// We couldn't move there, or the target moved away | |||||
this.FinishOrder(); | |||||
} | |||||
return; | |||||
} | |||||
this.PushOrderFront("Attack", { "target": msg.data.target, "hunting": true, "allowCapture": false }); | |||||
return; | |||||
} | |||||
// TODO: on what should we base this range? | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) | |||||
{ | |||||
if (!this.CanGather(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) | |||||
// The target isn't gatherable or not visible any more. | |||||
this.FinishOrder(); | |||||
// TODO: Should we issue a gather-near-position order | |||||
// if the target isn't gatherable/doesn't exist anymore? | |||||
else | |||||
// Out of range; move there in formation | |||||
this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); | |||||
return; | |||||
} | |||||
this.CallMemberFunction("Gather", [msg.data.target, false]); | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
}, | |||||
"Order.GatherNearPosition": function(msg) { | |||||
// TODO: on what should we base this range? | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckPointRangeExplicit(msg.data.x, msg.data.z, 0, 20)) | |||||
{ | |||||
// Out of range; move there in formation | |||||
this.PushOrderFront("WalkToPointRange", { "x": msg.data.x, "z": msg.data.z, "min": 0, "max": 20 }); | |||||
return; | |||||
} | |||||
this.CallMemberFunction("GatherNearPosition", [msg.data.x, msg.data.z, msg.data.type, msg.data.template, false]); | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
}, | |||||
"Order.Heal": function(msg) { | |||||
// TODO: on what should we base this range? | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) | |||||
{ | |||||
if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) | |||||
// The target was destroyed | |||||
this.FinishOrder(); | |||||
else | |||||
// Out of range; move there in formation | |||||
this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); | |||||
return; | |||||
} | |||||
this.CallMemberFunction("Heal", [msg.data.target, false]); | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
}, | |||||
"Order.Repair": function(msg) { | |||||
// TODO: on what should we base this range? | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) | |||||
{ | |||||
if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) | |||||
// The building was finished or destroyed | |||||
this.FinishOrder(); | |||||
else | |||||
// Out of range move there in formation | |||||
this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); | |||||
return; | |||||
} | |||||
this.CallMemberFunction("Repair", [msg.data.target, msg.data.autocontinue, false]); | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
}, | |||||
"Order.ReturnResource": function(msg) { | |||||
// TODO: on what should we base this range? | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10)) | |||||
{ | |||||
if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target)) | |||||
// The target was destroyed | |||||
this.FinishOrder(); | |||||
else | |||||
// Out of range; move there in formation | |||||
this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 }); | |||||
return; | |||||
} | |||||
this.CallMemberFunction("ReturnResource", [msg.data.target, false]); | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
}, | |||||
"Order.Pack": function(msg) { | |||||
this.CallMemberFunction("Pack", [false]); | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
}, | |||||
"Order.Unpack": function(msg) { | |||||
this.CallMemberFunction("Unpack", [false]); | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
}, | |||||
"IDLE": { | |||||
"enter": function(msg) { | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(false); | |||||
}, | |||||
"MoveStarted": function() { | |||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
} | |||||
}, | |||||
"WALKING": { | |||||
"MoveStarted": function(msg) { | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
}, | |||||
"MoveCompleted": function(msg) { | |||||
if (this.FinishOrder()) | |||||
this.CallMemberFunction("ResetFinishOrder", []); | |||||
}, | |||||
}, | |||||
"WALKINGANDFIGHTING": { | |||||
"enter": function(msg) { | |||||
this.StartTimer(0, 1000); | |||||
}, | |||||
"Timer": function(msg) { | |||||
// check if there are no enemies to attack | |||||
this.FindWalkAndFightTargets(); | |||||
}, | |||||
"leave": function(msg) { | |||||
this.StopTimer(); | |||||
}, | |||||
"MoveStarted": function(msg) { | |||||
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": { | |||||
"enter": function(msg) { | |||||
// Memorize the origin position in case that we want to go back | |||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
if (!this.patrolStartPosOrder) | |||||
{ | |||||
this.patrolStartPosOrder = cmpPosition.GetPosition(); | |||||
this.patrolStartPosOrder.targetClasses = this.order.data.targetClasses; | |||||
} | |||||
this.StartTimer(0, 1000); | |||||
}, | |||||
"Timer": function(msg) { | |||||
// Check if there are no enemies to attack | |||||
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() { | |||||
/** | |||||
* A-B-A-B-..: | |||||
* if the user only commands one patrol order, the patrol will be between | |||||
* the last position and the defined waypoint | |||||
* A-B-C-..-A-B-..: | |||||
* otherwise, the patrol is only between the given patrol commands and the | |||||
* last position is not included (last position = the position where the unit | |||||
* is located at the time of the first patrol order) | |||||
*/ | |||||
if (this.orderQueue.length == 1) | |||||
this.PushOrder("Patrol", this.patrolStartPosOrder); | |||||
this.PushOrder(this.order.type, this.order.data); | |||||
this.FinishOrder(); | |||||
}, | |||||
}, | |||||
"GARRISON":{ | |||||
"enter": function() { | |||||
// If the garrisonholder should pickup, warn it so it can take needed action | |||||
var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); | |||||
if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity)) | |||||
{ | |||||
this.pickup = this.order.data.target; // temporary, deleted in "leave" | |||||
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity }); | |||||
} | |||||
}, | |||||
"leave": function() { | |||||
// If a pickup has been requested and not yet canceled, cancel it | |||||
if (this.pickup) | |||||
{ | |||||
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); | |||||
delete this.pickup; | |||||
} | |||||
}, | |||||
"APPROACHING": { | |||||
"MoveStarted": function(msg) { | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
}, | |||||
"MoveCompleted": function(msg) { | |||||
this.SetNextState("GARRISONING"); | |||||
}, | |||||
}, | |||||
"GARRISONING": { | |||||
"enter": function() { | |||||
// If a pickup has been requested, cancel it as it will be requested by members | |||||
if (this.pickup) | |||||
{ | |||||
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); | |||||
delete this.pickup; | |||||
} | |||||
this.CallMemberFunction("Garrison", [this.order.data.target, false]); | |||||
this.SetNextStateAlwaysEntering("MEMBER"); | |||||
}, | |||||
}, | |||||
}, | |||||
"FORMING": { | |||||
"MoveStarted": function(msg) { | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, false); | |||||
}, | |||||
"MoveCompleted": function(msg) { | |||||
if (this.FinishOrder()) | |||||
{ | |||||
this.CallMemberFunction("ResetFinishOrder", []); | |||||
return; | |||||
} | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.FindInPosition(); | |||||
} | |||||
}, | |||||
"COMBAT": { | |||||
"APPROACHING": { | |||||
"MoveStarted": function(msg) { | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(true); | |||||
cmpFormation.MoveMembersIntoFormation(true, true); | |||||
}, | |||||
"MoveCompleted": function(msg) { | |||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | |||||
this.CallMemberFunction("Attack", [this.order.data.target, false, this.order.data.allowCapture]); | |||||
if (cmpAttack.CanAttackAsFormation()) | |||||
this.SetNextState("COMBAT.ATTACKING"); | |||||
else | |||||
this.SetNextState("MEMBER"); | |||||
}, | |||||
}, | |||||
"ATTACKING": { | |||||
// Wait for individual members to finish | |||||
"enter": function(msg) { | |||||
var target = this.order.data.target; | |||||
var allowCapture = this.order.data.allowCapture; | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckTargetAttackRange(target, target)) | |||||
{ | |||||
if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) | |||||
{ | |||||
this.FinishOrder(); | |||||
this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture }); | |||||
return true; | |||||
} | |||||
this.FinishOrder(); | |||||
return true; | |||||
} | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
// TODO fix the rearranging while attacking as formation | |||||
cmpFormation.SetRearrange(!this.IsAttackingAsFormation()); | |||||
cmpFormation.MoveMembersIntoFormation(false, false); | |||||
this.StartTimer(200, 200); | |||||
return false; | |||||
}, | |||||
"Timer": function(msg) { | |||||
var target = this.order.data.target; | |||||
var allowCapture = this.order.data.allowCapture; | |||||
// Check if we are already in range, otherwise walk there | |||||
if (!this.CheckTargetAttackRange(target, target)) | |||||
{ | |||||
if (this.TargetIsAlive(target) && this.CheckTargetVisible(target)) | |||||
{ | |||||
this.FinishOrder(); | |||||
this.PushOrderFront("Attack", { "target": target, "force": false, "allowCapture": allowCapture }); | |||||
return; | |||||
} | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
}, | |||||
"leave": function(msg) { | |||||
this.StopTimer(); | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
if (cmpFormation) | |||||
cmpFormation.SetRearrange(true); | |||||
}, | |||||
}, | |||||
}, | |||||
"MEMBER": { | |||||
// Wait for individual members to finish | |||||
"enter": function(msg) { | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
cmpFormation.SetRearrange(false); | |||||
this.StartTimer(1000, 1000); | |||||
}, | |||||
"Timer": function(msg) { | |||||
// Have all members finished the task? | |||||
if (!this.TestAllMemberFunction("HasFinishedOrder", [])) | |||||
return; | |||||
this.CallMemberFunction("ResetFinishOrder", []); | |||||
// Execute the next order | |||||
if (this.FinishOrder()) | |||||
{ | |||||
// if WalkAndFight order, look for new target before moving again | |||||
if (this.IsWalkingAndFighting()) | |||||
this.FindWalkAndFightTargets(); | |||||
return; | |||||
} | |||||
}, | |||||
"leave": function(msg) { | |||||
this.StopTimer(); | |||||
}, | |||||
}, | |||||
}, | |||||
// States for entities moving as part of a formation: | |||||
"FORMATIONMEMBER": { | |||||
"FormationLeave": function(msg) { | |||||
// We're not in a formation anymore, so no need to track this. | |||||
this.finishedOrder = false; | |||||
// Stop moving as soon as the formation disbands | |||||
this.StopMoving(); | |||||
// If the controller handled an order but some members rejected it, | |||||
// they will have no orders and be in the FORMATIONMEMBER.IDLE state. | |||||
if (this.orderQueue.length) | |||||
{ | |||||
// We're leaving the formation, so stop our FormationWalk order | |||||
if (this.FinishOrder()) | |||||
return; | |||||
} | |||||
// No orders left, we're an individual now | |||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.IDLE"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.IDLE"); | |||||
}, | |||||
// Override the LeaveFoundation order since we're not doing | |||||
// anything more important (and we might be stuck in the WALKING | |||||
// state forever and need to get out of foundations in that case) | |||||
"Order.LeaveFoundation": function(msg) { | |||||
// If foundation is not ally of entity, or if entity is unpacked siege, | |||||
// ignore the order | |||||
if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) && !Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager).IsCeasefireActive() || | |||||
this.IsPacking() || this.CanPack() || this.IsTurret()) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
// Move a tile outside the building | |||||
let range = 4; | |||||
if (this.MoveToTargetRangeExplicit(msg.data.target, range, range)) | |||||
{ | |||||
// We've started walking to the given point | |||||
this.SetNextState("WALKINGTOPOINT"); | |||||
} | |||||
else | |||||
{ | |||||
// We are already at the target, or can't move at all | |||||
this.FinishOrder(); | |||||
} | |||||
}, | |||||
"IDLE": { | |||||
"enter": function() { | |||||
if (this.IsAnimal()) | |||||
this.SetNextState("ANIMAL.IDLE"); | |||||
else | |||||
this.SetNextState("INDIVIDUAL.IDLE"); | |||||
return true; | |||||
}, | |||||
}, | |||||
"WALKING": { | |||||
"enter": function () { | |||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | |||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (cmpFormation && cmpVisual) | |||||
{ | |||||
cmpVisual.ReplaceMoveAnimation("walk", cmpFormation.GetFormationAnimation(this.entity, "walk")); | |||||
cmpVisual.ReplaceMoveAnimation("run", cmpFormation.GetFormationAnimation(this.entity, "run")); | |||||
} | |||||
this.SelectAnimation("move"); | |||||
}, | |||||
// Occurs when the unit has reached its destination and the controller | |||||
// is done moving. The controller is notified. | |||||
"MoveCompleted": function(msg) { | |||||
// We can only finish this order if the move was really completed. | |||||
if (!msg.data.error && this.FinishOrder()) | |||||
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); | |||||
if (cmpFormation) | |||||
cmpFormation.SetInPosition(this.entity); | |||||
}, | |||||
}, | |||||
// Special case used by Order.LeaveFoundation | |||||
"WALKINGTOPOINT": { | |||||
"enter": function() { | |||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | |||||
if (cmpFormation) | |||||
cmpFormation.UnsetInPosition(this.entity); | |||||
this.SelectAnimation("move"); | |||||
}, | |||||
"MoveCompleted": function() { | |||||
this.FinishOrder(); | |||||
}, | |||||
}, | |||||
}, | |||||
// States for entities not part of a formation: | |||||
"INDIVIDUAL": { | "INDIVIDUAL": { | ||||
"enter": function() { | "enter": function() { | ||||
// Sanity-checking | // Sanity-checking | ||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
error("Animal got moved into INDIVIDUAL.* state"); | error("Animal got moved into INDIVIDUAL.* state"); | ||||
}, | }, | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
// Respond to attack if we always target attackers, or if we target attackers | // Respond to attack if we always target attackers, or if we target attackers | ||||
// during passive orders (e.g. gathering/repairing are never forced) | // during passive orders (e.g. gathering/repairing are never forced) | ||||
// TODO: handle group-walking order. | |||||
if (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && (!this.order || !this.order.data || !this.order.data.force))) | if (this.GetStance().targetAttackersAlways || (this.GetStance().targetAttackersPassive && (!this.order || !this.order.data || !this.order.data.force))) | ||||
{ | { | ||||
this.RespondToTargetedEntities([msg.data.attacker]); | this.RespondToTargetedEntities([msg.data.attacker]); | ||||
} | } | ||||
}, | }, | ||||
"GuardedAttacked": function(msg) { | "GuardedAttacked": function(msg) { | ||||
// do nothing if we have a forced order in queue before the guard order | // do nothing if we have a forced order in queue before the guard order | ||||
▲ Show 20 Lines • Show All 47 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 52 Lines • ▼ Show 20 Lines | "IDLE": { | ||||
} | } | ||||
}, | }, | ||||
"LosHealRangeUpdate": function(msg) { | "LosHealRangeUpdate": function(msg) { | ||||
this.RespondToHealableEntities(msg.data.added); | this.RespondToHealableEntities(msg.data.added); | ||||
}, | }, | ||||
"MoveStarted": function() { | "MoveStarted": function() { | ||||
this.SelectAnimation("move"); | |||||
}, | }, | ||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.SelectAnimation("idle"); | 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 }); | ||||
} | } | ||||
}, | }, | ||||
}, | }, | ||||
"GROUPWALKING": { | |||||
"enter": function() { | |||||
this.group = this.order.data.groupID; | |||||
this.SetNextState("WAITING"); | |||||
}, | |||||
"leave": function() { | |||||
let cmpGroupWalkManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_GroupWalkManager); | |||||
let group = cmpGroupWalkManager.GetGroup(this.group); | |||||
if (group) | |||||
cmpGroupWalkManager.ResignFromGroup(this.group, this.entity); | |||||
this.group = undefined; | |||||
}, | |||||
"WAITING" : { | |||||
"enter": function() { | |||||
let cmpGroupWalkManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_GroupWalkManager); | |||||
let group = cmpGroupWalkManager.GetGroup(this.order.data.groupID); | |||||
if (!group || group.state != "waiting") | |||||
{ | |||||
this.FinishOrder(); | |||||
return true; | |||||
} | |||||
cmpGroupWalkManager.SetReady(this.order.data.groupID, this.entity); | |||||
this.StartTimer(200, 400); | |||||
}, | |||||
"leave": function() { | |||||
this.StopTimer(); | |||||
}, | |||||
"Timer": function() { | |||||
let cmpGroupWalkManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_GroupWalkManager); | |||||
let group = cmpGroupWalkManager.GetGroup(this.order.data.groupID); | |||||
if (!group || group.state == "arrived") | |||||
{ | |||||
this.FinishOrder(); | |||||
return true; | |||||
} | |||||
if (group.state == "walking") | |||||
this.SetNextState("WALKING"); | |||||
}, | |||||
}, | |||||
"WALKING": { | "WALKING" : { | ||||
"enter": function () { | "enter": function() { | ||||
this.SelectAnimation("move"); | let cmpGroupWalkManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_GroupWalkManager); | ||||
let group = cmpGroupWalkManager.GetGroup(this.order.data.groupID); | |||||
if (!group || group.state != "walking") | |||||
{ | |||||
this.FinishOrder(); | |||||
return true; | |||||
} | |||||
let offset = group.offsets[this.entity]; | |||||
this.MoveToPointRange(group.rallyPoint.x + offset.x, group.rallyPoint.z + offset.y, 0, true); | |||||
// TODO: for the first "grouping" order it'd be nice to skip this | |||||
let maxSpeed = cmpGroupWalkManager.GetMaxSpeed(this.order.data.groupID); | |||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
let ratio = maxSpeed / cmpUnitMotion.GetBaseSpeed(); | |||||
cmpUnitMotion.SetSpeed(ratio); | |||||
this.StartTimer(200, 500); | |||||
this.step = group.step; // temporary, deleted in leave | |||||
}, | }, | ||||
"MoveCompleted": function() { | "leave": function() { | ||||
this.StopTimer(); | |||||
this.ready = undefined; | |||||
this.step = undefined; | |||||
this.validatedOffset = undefined; | |||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
cmpUnitMotion.SetSpeed(WALKING_SPEED); | |||||
}, | |||||
"Timer": function() { | |||||
let cmpGroupWalkManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_GroupWalkManager); | |||||
let group = cmpGroupWalkManager.GetGroup(this.order.data.groupID); | |||||
if (!group) | |||||
{ | |||||
this.FinishOrder(); | |||||
return true; | |||||
} | |||||
if (group.state == "arrived") | |||||
{ | |||||
// TODO: should probably handle stances and do different things depending on the order here. | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
let offset = group.offsets[this.entity]; | |||||
if (!cmpUnitMotion.IsActuallyMoving() | |||||
&& cmpObstructionManager.IsInPointRange(this.entity, group.rallyPoint.x + offset.x, group.rallyPoint.z + offset.y, 0, 0)) | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
if (group.step < this.step) | |||||
{ | |||||
// jump straight to the next rallypoint. | |||||
this.SetNextStateAlwaysEntering("WALKING"); | |||||
return; | |||||
} | |||||
if (this.ready) | |||||
return; | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
let range = group.step !== 0 ? 10 : group.range; | |||||
let offset = group.offsets[this.entity]; | |||||
if (cmpObstructionManager.IsInPointRange(this.entity, group.rallyPoint.x + offset.x, group.rallyPoint.z + offset.y, 0, range)) | |||||
{ | |||||
this.ready = true; | |||||
cmpGroupWalkManager.SetReady(this.order.data.groupID, this.entity); | |||||
} | |||||
else if (!this.validatedOffset) | |||||
{ | |||||
this.validatedOffset = true; | |||||
// check whether our reachable goal is close enough to our intended offset. | |||||
// TODO: ideally we'd also check the actual-path-distance to the rallypoint, and figure if we've been placed somewhere wrong. | |||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
let goal = cmpUnitMotion.GetReachableGoalPosition(); | |||||
let distance = (goal.x - group.rallyPoint.x - offset.x) * (goal.x - group.rallyPoint.x - offset.x) | |||||
+ (goal.y - group.rallyPoint.z - offset.y) * (goal.y - group.rallyPoint.z - offset.y) | |||||
if (distance > 3) | |||||
cmpGroupWalkManager.SetBlockedPath(this.order.data.groupID, this.entity); | |||||
} | |||||
}, | |||||
"MoveCompleted": function(msg) { | |||||
let cmpGroupWalkManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_GroupWalkManager); | |||||
let group = cmpGroupWalkManager.GetGroup(this.order.data.groupID); | |||||
if (!group) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
if (!msg.data.error) | |||||
return; | |||||
// UnitMotion has told us we were unlikely to reach our destination. | |||||
// if we're way out of position we should exit the group | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
let offset = group.offsets[this.entity]; | |||||
if (!cmpObstructionManager.IsInPointRange(this.entity, group.rallyPoint.x + offset.x, group.rallyPoint.z + offset.y, 0, 60)) | |||||
{ | |||||
this.FinishOrder(); | |||||
return; | |||||
} | |||||
// tell our group we're ready, it's probably just that our waypoint is impassable right now | |||||
this.ready = true; | |||||
cmpGroupWalkManager.SetReady(this.order.data.groupID, this.entity); | |||||
} | |||||
} | |||||
}, | |||||
"WALKING": { | |||||
"enter": function () { | |||||
}, | |||||
"MoveCompleted": 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) | |||||
|| this.order.data.target && cmpObstructionManager.IsInTargetRange(this.entity, this.order.data.target, 0, 1)) | |||||
{ | |||||
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.SetGathererAnimationOverride(true); | this.SetAnimationVariant("combat"); | ||||
this.StartTimer(0, 1000); | this.StartTimer(0, 1000); | ||||
this.SelectAnimation("move"); | |||||
}, | }, | ||||
"Timer": function(msg) { | "Timer": function(msg) { | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
}, | }, | ||||
"leave": function(msg) { | "leave": function(msg) { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
"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)) | |||||
{ | |||||
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.StartTimer(0, 1000); | this.StartTimer(0, 1000); | ||||
this.SelectAnimation("move"); | |||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
delete this.patrolStartPosOrder; | delete this.patrolStartPosOrder; | ||||
}, | }, | ||||
"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.SetGathererAnimationOverride(true); | 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); | ||||
}, | |||||
"leave": function(msg) { | |||||
this.SetMoveSpeed(this.GetWalkSpeed()); | |||||
this.StopTimer(); | |||||
}, | |||||
"MoveStarted": 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 cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
if (cmpUnitMotion.IsInTargetRange(this.isGuardOf, 0, 3*this.guardRange)) | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
{ | if (cmpObstructionManager.IsInTargetRange(this.entity, this.isGuardOf, 0, 3*this.guardRange)) | ||||
var cmpUnitAI = Engine.QueryInterface(this.isGuardOf, IID_UnitAI); | { | ||||
if (cmpUnitAI) | var cmpOtherMotion = Engine.QueryInterface(this.isGuardOf, IID_UnitMotion); | ||||
{ | if (cmpOtherMotion) | ||||
var speed = cmpUnitAI.GetWalkSpeed(); | { | ||||
if (speed < this.GetWalkSpeed()) | let otherSpeed = cmpOtherMotion.GetSpeed(); | ||||
let mySpeed = cmpUnitMotion.GetSpeed(); | |||||
let speed = otherSpeed / mySpeed; | |||||
if (speed < WALKING_SPEED) | |||||
this.SetMoveSpeed(speed); | this.SetMoveSpeed(speed); | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
"leave": function(msg) { | |||||
this.SetMoveSpeed(WALKING_SPEED); | |||||
this.StopTimer(); | |||||
}, | |||||
"MoveCompleted": function() { | "MoveCompleted": function() { | ||||
this.SetMoveSpeed(this.GetWalkSpeed()); | this.StopMoving(); | ||||
if (!this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) | this.SetMoveSpeed(WALKING_SPEED); | ||||
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); | ||||
Show All 10 Lines | "GUARD": { | ||||
"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)) | ||||
this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); | this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); | ||||
else if (this.CanRepair(this.isGuardOf)) | else if (this.CanRepair(this.isGuardOf)) | ||||
this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); | this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
"leave": function(msg) { | "leave": function(msg) { | ||||
this.StopTimer(); | this.StopTimer(); | ||||
}, | }, | ||||
}, | }, | ||||
}, | }, | ||||
"FLEEING": { | "FLEEING": { | ||||
"enter": function() { | "enter": function() { | ||||
this.PlaySound("panic"); | this.PlaySound("panic"); | ||||
// Run quickly | |||||
var speed = this.GetRunSpeed(); | |||||
this.SelectAnimation("move"); | |||||
this.SetMoveSpeed(speed); | |||||
}, | |||||
"HealthChanged": function() { | |||||
var speed = this.GetRunSpeed(); | |||||
this.SetMoveSpeed(speed); | |||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
// Reset normal speed | |||||
this.SetMoveSpeed(this.GetWalkSpeed()); | |||||
}, | }, | ||||
"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": { | ||||
"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 | // If we're already in combat mode, ignore anyone else | ||||
// who's attacking us | // who's attacking us | ||||
}, | }, | ||||
"APPROACHING": { | "APPROACHING": { | ||||
"enter": function () { | "enter": function () { | ||||
// Show weapons rather than carried resources. | // Show weapons rather than carried resources. | ||||
this.SetGathererAnimationOverride(true); | this.SetAnimationVariant("combat"); | ||||
this.SelectAnimation("move"); | |||||
this.StartTimer(1000, 1000); | this.StartTimer(1000, 1000); | ||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
// Show carried resources when walking. | // Show carried resources when walking. | ||||
this.SetGathererAnimationOverride(); | this.SetAnimationVariant(); | ||||
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.SetNextState("UNPACKING"); | this.SetNextState("UNPACKING"); | ||||
else | else | ||||
this.SetNextState("ATTACKING"); | this.SetNextState("ATTACKING"); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | "COMBAT": { | ||||
"Attacked": function(msg) { | "Attacked": function(msg) { | ||||
// Ignore further attacks while unpacking | // Ignore further attacks while unpacking | ||||
}, | }, | ||||
}, | }, | ||||
"ATTACKING": { | "ATTACKING": { | ||||
"enter": function() { | "enter": function() { | ||||
var target = this.order.data.target; | var target = this.order.data.target; | ||||
var cmpFormation = Engine.QueryInterface(target, IID_Formation); | |||||
// if the target is a formation, save the attacking formation, and pick a member | |||||
if (cmpFormation) | |||||
{ | |||||
this.order.data.formationTarget = target; | |||||
target = cmpFormation.GetClosestMember(this.entity); | |||||
this.order.data.target = target; | |||||
} | |||||
// Check the target is still alive and attackable | // Check the target is still alive and attackable | ||||
if (this.TargetIsAlive(target) && | if (this.TargetIsAlive(target) && | ||||
this.CanAttack(target, this.order.data.forceResponse || null) && | this.CanAttack(target, this.order.data.forceResponse || null) && | ||||
!this.CheckTargetAttackRange(target, this.order.data.attackType)) | !this.CheckTargetAttackRange(target, this.order.data.attackType)) | ||||
{ | { | ||||
// Can't reach it - try to chase after it | // Can't reach it - try to chase after it | ||||
if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) | if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) | ||||
{ | { | ||||
Show All 16 Lines | "COMBAT": { | ||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | ||||
var repeatLeft = this.lastAttacked + this.attackTimers.repeat - cmpTimer.GetTime(); | var repeatLeft = this.lastAttacked + this.attackTimers.repeat - cmpTimer.GetTime(); | ||||
prepare = Math.max(prepare, repeatLeft); | prepare = Math.max(prepare, repeatLeft); | ||||
} | } | ||||
this.oldAttackType = this.order.data.attackType; | this.oldAttackType = this.order.data.attackType; | ||||
// 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()) | |||||
{ | |||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | |||||
if (cmpFormation) | |||||
animationName = cmpFormation.GetFormationAnimation(this.entity, animationName); | |||||
} | |||||
this.SelectAnimation(animationName, false, 1.0, "attack"); | this.SelectAnimation(animationName, false, 1.0, "attack"); | ||||
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.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); | |||||
// if the target is a formation, save the attacking formation, and pick a member | |||||
if (cmpFormation) | |||||
{ | |||||
var thisObject = this; | |||||
var filter = function(t) { | |||||
return thisObject.TargetIsAlive(t) && thisObject.CanAttack(t, thisObject.order.data.forceResponse || null); | |||||
}; | |||||
this.order.data.formationTarget = target; | |||||
target = cmpFormation.GetClosestMember(this.entity, filter); | |||||
this.order.data.target = target; | |||||
} | |||||
// Check the target is still alive and attackable | // Check the target is still alive and attackable | ||||
if (this.TargetIsAlive(target) && this.CanAttack(target, this.order.data.forceResponse || null)) | if (this.TargetIsAlive(target) && this.CanAttack(target, this.order.data.forceResponse || null)) | ||||
{ | { | ||||
// If we are hunting, first update the target position of the gather order so we know where will be the killed animal | // If we are hunting, first update the target position of the gather order so we know where will be the killed animal | ||||
if (this.order.data.hunting && this.orderQueue[1] && this.orderQueue[1].data.lastPos) | if (this.order.data.hunting && this.orderQueue[1] && this.orderQueue[1].data.lastPos) | ||||
{ | { | ||||
var cmpPosition = Engine.QueryInterface(this.order.data.target, IID_Position); | var cmpPosition = Engine.QueryInterface(this.order.data.target, IID_Position); | ||||
if (cmpPosition && cmpPosition.IsInWorld()) | if (cmpPosition && cmpPosition.IsInWorld()) | ||||
Show All 37 Lines | "COMBAT": { | ||||
if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType)) | if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType)) | ||||
{ | { | ||||
this.SetNextState("COMBAT.CHASING"); | this.SetNextState("COMBAT.CHASING"); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// if we're targetting a formation, find a new member of that formation | |||||
var cmpTargetFormation = Engine.QueryInterface(this.order.data.formationTarget || INVALID_ENTITY, IID_Formation); | |||||
// if there is no target, it means previously searching for the target inside the target formation failed, so don't repeat the search | |||||
if (target && cmpTargetFormation) | |||||
{ | |||||
this.order.data.target = this.order.data.formationTarget; | |||||
this.TimerHandler(msg.data, msg.lateness); | |||||
return; | |||||
} | |||||
// Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up | // Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up | ||||
// Except if in WalkAndFight mode where we look for more ennemies around before moving again | // Except if in WalkAndFight mode where we look for more ennemies around before moving again | ||||
if (this.FinishOrder()) | if (this.FinishOrder()) | ||||
{ | { | ||||
if (this.IsWalkingAndFighting()) | if (this.IsWalkingAndFighting()) | ||||
this.FindWalkAndFightTargets(); | this.FindWalkAndFightTargets(); | ||||
return; | return; | ||||
} | } | ||||
Show All 31 Lines | "COMBAT": { | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
}, | }, | ||||
"CHASING": { | "CHASING": { | ||||
"enter": function () { | "enter": function () { | ||||
// Show weapons rather than carried resources. | // Show weapons rather than carried resources. | ||||
this.SetGathererAnimationOverride(true); | 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()) | |||||
{ | |||||
// Run after a fleeing target | |||||
var speed = this.GetRunSpeed(); | |||||
this.SetMoveSpeed(speed); | |||||
} | |||||
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; | ||||
var speed = this.GetRunSpeed(); | |||||
this.SetMoveSpeed(speed); | |||||
}, | }, | ||||
"leave": function() { | "leave": function() { | ||||
// Reset normal speed in case it was changed | |||||
this.SetMoveSpeed(this.GetWalkSpeed()); | |||||
// Show carried resources when walking. | // Show carried resources when walking. | ||||
this.SetGathererAnimationOverride(); | this.SetAnimationVariant(); | ||||
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(); | |||||
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; | ||||
}, | }, | ||||
}, | }, | ||||
// 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; | ||||
} | } | ||||
// Range check: if we are in-range, start the gathering animation and set the timer | |||||
// If we are not in-range, we'll set a different timer to avoid waiting an inordinate amount of time. | |||||
if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer)) | |||||
{ | |||||
this.FaceTowardsTarget(this.gatheringTarget); | |||||
var typename = "gather_" + this.order.data.type.specific; | |||||
this.SelectAnimation(typename, false, 1.0, typename); | |||||
// Scale timing interval based on rate, and start timer | // 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. | // 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 offset = 1000/rate; | ||||
var repeat = offset; | var repeat = offset; | ||||
this.StartTimer(offset, repeat); | 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)) | |||||
{ | |||||
var typename = "gather_" + this.order.data.type.specific; | |||||
this.SelectAnimation(typename, false, 1.0, typename); | |||||
} | } | ||||
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. | // Show the carried resource, if we've gathered anything. | ||||
this.SetGathererAnimationOverride(); | this.SelectAnimation("idle"); | ||||
this.SetAnimationVariant(); | |||||
}, | }, | ||||
"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); | ||||
if (!cmpOwnership) | if (!cmpOwnership) | ||||
▲ Show 20 Lines • Show All 53 Lines • ▼ Show 20 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 34 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.SetGathererAnimationOverride(); | this.SetAnimationVariant(); | ||||
// 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", false, 1.0, "build"); | this.SelectAnimation("build", false, 1.0, "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 | ||||
var oldData = this.order.data; | var oldData = this.order.data; | ||||
// Save the current state so we can continue walking if necessary | // Save the current state so we can continue walking if necessary | ||||
// FinishOrder() below will switch to IDLE if there's no order, which sets the idle animation. | // FinishOrder() below will switch to IDLE if there's no order, which sets the idle animation. | ||||
// Idle animation while moving towards finished construction looks weird (ghosty). | // Idle animation while moving towards finished construction looks weird (ghosty). | ||||
var oldState = this.GetCurrentState(); | var oldState = this.GetCurrentState(); | ||||
// Drop any resource we can if we are in range when the construction finishes | // Drop any resource we can if we are in range when the construction finishes | ||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | ||||
var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, IID_ResourceDropsite); | var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, IID_ResourceDropsite); | ||||
if (cmpResourceGatherer && cmpResourceDropsite && this.CheckTargetRange(msg.data.newentity, IID_Builder) && | if (cmpResourceGatherer && cmpResourceDropsite && this.CheckTargetRange(msg.data.newentity, IID_Builder) && | ||||
this.CanReturnResource(msg.data.newentity, true)) | this.CanReturnResource(msg.data.newentity, true)) | ||||
{ | { | ||||
let dropsiteTypes = cmpResourceDropsite.GetTypes(); | let dropsiteTypes = cmpResourceDropsite.GetTypes(); | ||||
cmpResourceGatherer.CommitResources(dropsiteTypes); | cmpResourceGatherer.CommitResources(dropsiteTypes); | ||||
this.SetGathererAnimationOverride(); | this.SetAnimationVariant(); | ||||
} | } | ||||
// We finished building it. | // We finished building it. | ||||
// Switch to the next order (if any) | // Switch to the next order (if any) | ||||
if (this.FinishOrder()) | if (this.FinishOrder()) | ||||
{ | { | ||||
if (this.CanReturnResource(msg.data.newentity, true)) | if (this.CanReturnResource(msg.data.newentity, true)) | ||||
{ | { | ||||
this.SetGathererAnimationOverride(); | this.SetAnimationVariant(); | ||||
this.PushOrderFront("ReturnResource", { "target": msg.data.newentity, "force": false }); | this.PushOrderFront("ReturnResource", { "target": msg.data.newentity, "force": false }); | ||||
} | } | ||||
return; | return; | ||||
} | } | ||||
// No remaining orders - pick a useful default behaviour | // No remaining orders - pick a useful default behaviour | ||||
// If autocontinue explicitly disabled (e.g. by AI) then | // If autocontinue explicitly disabled (e.g. by AI) then | ||||
// do nothing automatically | // do nothing automatically | ||||
if (!oldData.autocontinue) | if (!oldData.autocontinue) | ||||
return; | return; | ||||
// If this building was e.g. a farm of ours, the entities that recieved | // If this building was e.g. a farm of ours, the entities that recieved | ||||
// the build command should start gathering from it | // the build command should start gathering from it | ||||
if ((oldData.force || oldData.autoharvest) && this.CanGather(msg.data.newentity)) | if ((oldData.force || oldData.autoharvest) && this.CanGather(msg.data.newentity)) | ||||
{ | { | ||||
if (this.CanReturnResource(msg.data.newentity, true)) | if (this.CanReturnResource(msg.data.newentity, true)) | ||||
{ | { | ||||
this.SetGathererAnimationOverride(); | this.SetAnimationVariant(); | ||||
this.PushOrder("ReturnResource", { "target": msg.data.newentity, "force": false }); | this.PushOrder("ReturnResource", { "target": msg.data.newentity, "force": false }); | ||||
} | } | ||||
this.PerformGather(msg.data.newentity, true, false); | this.PerformGather(msg.data.newentity, true, false); | ||||
return; | return; | ||||
} | } | ||||
// If this building was e.g. a farmstead of ours, entities that received | // If this building was e.g. a farmstead of ours, entities that received | ||||
// the build command should look for nearby resources to gather | // the build command should look for nearby resources to gather | ||||
▲ Show 20 Lines • Show All 48 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) | if (this.IsUnderAlert() && this.alertGarrisoningTarget) | ||||
{ | { | ||||
// check that we can garrison in the building we're supposed to garrison in | // check that we can garrison in the building we're supposed to garrison in | ||||
var cmpGarrisonHolder = Engine.QueryInterface(this.alertGarrisoningTarget, IID_GarrisonHolder); | var cmpGarrisonHolder = Engine.QueryInterface(this.alertGarrisoningTarget, IID_GarrisonHolder); | ||||
if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) | ||||
{ | { | ||||
// Try to find another nearby building | // Try to find another nearby building | ||||
var nearby = this.FindNearbyGarrisonHolder(); | var nearby = this.FindNearbyGarrisonHolder(); | ||||
if (nearby) | if (nearby) | ||||
{ | { | ||||
this.alertGarrisoningTarget = nearby; | this.alertGarrisoningTarget = nearby; | ||||
this.ReplaceOrder("Garrison", {"target": this.alertGarrisoningTarget}); | this.ReplaceOrder("Garrison", {"target": this.alertGarrisoningTarget}); | ||||
} | } | ||||
else | else | ||||
this.FinishOrder(); | this.FinishOrder(); | ||||
return; | |||||
} | } | ||||
else | |||||
this.SetNextState("GARRISONED"); | |||||
} | } | ||||
else | 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() { | ||||
// Target is not handled the same way with Alert and direct garrisoning | // Target is not handled the same way with Alert and direct garrisoning | ||||
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 All 14 Lines | "GARRISON": { | ||||
if (this.CheckGarrisonRange(target)) | if (this.CheckGarrisonRange(target)) | ||||
{ | { | ||||
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); | var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); | ||||
// Check that garrisoning succeeds | // Check that garrisoning succeeds | ||||
if (cmpGarrisonHolder.Garrison(this.entity)) | if (cmpGarrisonHolder.Garrison(this.entity)) | ||||
{ | { | ||||
this.isGarrisoned = true; | this.isGarrisoned = true; | ||||
if (this.formationController) | |||||
{ | |||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | |||||
if (cmpFormation) | |||||
{ | |||||
// disable rearrange for this removal, | |||||
// but enable it again for the next | |||||
// move command | |||||
var rearrange = cmpFormation.rearrange; | |||||
cmpFormation.SetRearrange(false); | |||||
cmpFormation.RemoveMembers([this.entity]); | |||||
cmpFormation.SetRearrange(rearrange); | |||||
} | |||||
} | |||||
// Check if we are garrisoned in a dropsite | // Check if we are garrisoned in a dropsite | ||||
var cmpResourceDropsite = Engine.QueryInterface(target, IID_ResourceDropsite); | var cmpResourceDropsite = Engine.QueryInterface(target, IID_ResourceDropsite); | ||||
if (cmpResourceDropsite && this.CanReturnResource(target, true)) | if (cmpResourceDropsite && this.CanReturnResource(target, true)) | ||||
{ | { | ||||
// 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); | ||||
if (cmpResourceGatherer) | if (cmpResourceGatherer) | ||||
{ | { | ||||
cmpResourceGatherer.CommitResources(dropsiteTypes); | cmpResourceGatherer.CommitResources(dropsiteTypes); | ||||
this.SetGathererAnimationOverride(); | this.SetAnimationVariant(); | ||||
} | } | ||||
} | } | ||||
// If a pickup has been requested, remove it | // If a pickup has been requested, remove it | ||||
if (this.pickup) | if (this.pickup) | ||||
{ | { | ||||
var cmpHolderPosition = Engine.QueryInterface(target, IID_Position); | var cmpHolderPosition = Engine.QueryInterface(target, IID_Position); | ||||
var cmpHolderUnitAI = Engine.QueryInterface(target, IID_UnitAI); | var cmpHolderUnitAI = Engine.QueryInterface(target, IID_UnitAI); | ||||
Show All 18 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 24 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. Since INDIVIDUAL.WALKING checks for a distance in [0,1], move to 0.5 | ||||
var range = 4; | var range = 0.5; | ||||
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, this.GetWalkSpeed()); | |||||
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)); | ||||
this.SetFacePointAfterMove(false); | |||||
}, | }, | ||||
"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"); | ||||
Show All 16 Lines | "FEEDING": { | ||||
} | } | ||||
// 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 | ||||
"COMBAT": "INDIVIDUAL.COMBAT", // reuse the same combat behaviour for animals | "COMBAT": "INDIVIDUAL.COMBAT", // reuse the same combat behaviour for animals | ||||
"WALKING": "INDIVIDUAL.WALKING", // reuse the same walking behaviour for animals | "WALKING": "INDIVIDUAL.WALKING", // reuse the same walking behaviour for animals | ||||
// only used for domestic animals | // only used for domestic animals | ||||
}, | }, | ||||
}; | }; | ||||
UnitAI.prototype.Init = function() | UnitAI.prototype.Init = function() | ||||
{ | { | ||||
this.orderQueue = []; // current order is at the front of the list | this.orderQueue = []; // current order is at the front of the list | ||||
this.order = undefined; // always == this.orderQueue[0] | this.order = undefined; // always == this.orderQueue[0] | ||||
this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to | |||||
this.isGarrisoned = false; | this.isGarrisoned = false; | ||||
this.isIdle = false; | this.isIdle = false; | ||||
// For A19, keep no formations as a default to help pathfinding. | |||||
this.lastFormationTemplate = "formations/null"; | |||||
this.finishedOrder = false; // used to find if all formation members finished the order | |||||
this.heldPosition = undefined; | this.heldPosition = undefined; | ||||
// Queue of remembered works | // Queue of remembered works | ||||
this.workOrders = []; | this.workOrders = []; | ||||
this.isGuardOf = undefined; | this.isGuardOf = undefined; | ||||
// "Town Bell" behaviour | // "Town Bell" behaviour | ||||
this.alertRaiser = undefined; | this.alertRaiser = undefined; | ||||
this.alertGarrisoningTarget = undefined; | this.alertGarrisoningTarget = undefined; | ||||
// For preventing increased action rate due to Stop orders or target death. | // For preventing increased action rate due to Stop orders or target death. | ||||
this.lastAttacked = undefined; | this.lastAttacked = undefined; | ||||
this.lastHealed = undefined; | this.lastHealed = undefined; | ||||
this.formationTemplate = "formations/null"; | |||||
this.SetStance(this.template.DefaultStance); | this.SetStance(this.template.DefaultStance); | ||||
}; | }; | ||||
UnitAI.prototype.IsTurret = function() | 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); | ||||
Show All 15 Lines | UnitAI.prototype.ResetAlert = function() | ||||
this.alertGarrisoningTarget = undefined; | this.alertGarrisoningTarget = undefined; | ||||
this.alertRaiser = undefined; | this.alertRaiser = undefined; | ||||
}; | }; | ||||
UnitAI.prototype.GetAlertRaiser = function() | UnitAI.prototype.GetAlertRaiser = function() | ||||
{ | { | ||||
return this.alertRaiser; | return this.alertRaiser; | ||||
}; | }; | ||||
UnitAI.prototype.IsFormationController = function() | |||||
{ | |||||
return (this.template.FormationController == "true"); | |||||
}; | |||||
UnitAI.prototype.IsFormationMember = function() | |||||
{ | |||||
return (this.formationController != INVALID_ENTITY); | |||||
}; | |||||
UnitAI.prototype.HasFinishedOrder = function() | UnitAI.prototype.HasFinishedOrder = function() | ||||
fatherbushido: don't forget it :p | |||||
Not Done Inline Actionsdon't forget it :p fatherbushido: don't forget it :p | |||||
{ | { | ||||
return this.finishedOrder; | return this.finishedOrder; | ||||
}; | }; | ||||
UnitAI.prototype.ResetFinishOrder = function() | UnitAI.prototype.ResetFinishOrder = function() | ||||
{ | { | ||||
this.finishedOrder = false; | this.finishedOrder = false; | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | UnitAI.prototype.IsWalking = function() | ||||
return (state == "WALKING"); | return (state == "WALKING"); | ||||
}; | }; | ||||
/** | /** | ||||
* return true if in WalkAndFight looking for new targets | * return true if in WalkAndFight looking for new targets | ||||
*/ | */ | ||||
UnitAI.prototype.IsWalkingAndFighting = function() | UnitAI.prototype.IsWalkingAndFighting = function() | ||||
{ | { | ||||
if (this.IsFormationMember()) | |||||
{ | |||||
var cmpUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | |||||
return (cmpUnitAI && cmpUnitAI.IsWalkingAndFighting()); | |||||
} | |||||
return (this.orderQueue.length > 0 && this.orderQueue[0].type == "WalkAndFight"); | return (this.orderQueue.length > 0 && this.orderQueue[0].type == "WalkAndFight"); | ||||
}; | }; | ||||
UnitAI.prototype.OnCreate = function() | UnitAI.prototype.OnCreate = function() | ||||
{ | { | ||||
if (this.IsAnimal()) | if (this.IsAnimal()) | ||||
this.UnitFsm.Init(this, "ANIMAL.FEEDING"); | this.UnitFsm.Init(this, "ANIMAL.FEEDING"); | ||||
else if (this.IsFormationController()) | |||||
this.UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE"); | |||||
else | else | ||||
this.UnitFsm.Init(this, "INDIVIDUAL.IDLE"); | this.UnitFsm.Init(this, "INDIVIDUAL.IDLE"); | ||||
this.isIdle = true; | this.isIdle = true; | ||||
}; | }; | ||||
UnitAI.prototype.OnDiplomacyChanged = function(msg) | UnitAI.prototype.OnDiplomacyChanged = function(msg) | ||||
{ | { | ||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
▲ Show 20 Lines • Show All 206 Lines • ▼ Show 20 Lines | UnitAI.prototype.FinishOrder = function() | ||||
if (!this.orderQueue.length) | if (!this.orderQueue.length) | ||||
{ | { | ||||
var stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line | var stack = new Error().stack.trimRight().replace(/^/mg, ' '); // indent each line | ||||
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); | var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); | ||||
var template = cmpTemplateManager.GetCurrentTemplateName(this.entity); | var 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]; | ||||
if (this.orderQueue.length) | if (this.orderQueue.length) | ||||
{ | { | ||||
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 All 9 Lines | if (this.orderQueue.length) | ||||
return true; | return true; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
this.SetNextState("IDLE"); | this.SetNextState("IDLE"); | ||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | ||||
// Check if there are queued formation orders | |||||
if (this.IsFormationMember()) | |||||
{ | |||||
let cmpUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | |||||
if (cmpUnitAI) | |||||
{ | |||||
// Inform the formation controller that we finished this task | |||||
this.finishedOrder = true; | |||||
// We don't want to carry out the default order | |||||
// if there are still queued formation orders left | |||||
if (cmpUnitAI.GetOrders().length > 1) | |||||
return true; | |||||
} | |||||
} | |||||
return false; | return false; | ||||
} | } | ||||
}; | }; | ||||
/** | /** | ||||
* Add an order onto the back of the queue, | * Add an order onto the back of the queue, | ||||
* and execute it if we didn't already have an order. | * and execute it if we didn't already have an order. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 86 Lines • ▼ Show 20 Lines | UnitAI.prototype.PushOrderAfterForced = function(type, data) | ||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | ||||
}; | }; | ||||
UnitAI.prototype.ReplaceOrder = function(type, data) | UnitAI.prototype.ReplaceOrder = function(type, data) | ||||
{ | { | ||||
// Remember the previous work orders to be able to go back to them later if required | // Remember the previous work orders to be able to go back to them later if required | ||||
if (data && data.force) | if (data && data.force) | ||||
{ | |||||
if (this.IsFormationController()) | |||||
this.CallMemberFunction("UpdateWorkOrders", [type]); | |||||
else | |||||
this.UpdateWorkOrders(type); | this.UpdateWorkOrders(type); | ||||
} | |||||
this.StopMoving(); | |||||
// 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") | ||||
{ | { | ||||
var order = { "type": type, "data": data }; | var order = { "type": type, "data": data }; | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | if (isWorkType(type)) | ||||
this.workOrders = []; | this.workOrders = []; | ||||
return; | return; | ||||
} | } | ||||
// Then if we already have work orders, keep them | // Then if we already have work orders, keep them | ||||
if (this.workOrders.length) | if (this.workOrders.length) | ||||
return; | return; | ||||
// First if the unit is in a formation, get its workOrders from it | // Take the unit orders | ||||
if (this.IsFormationMember()) | |||||
{ | |||||
var cmpUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | |||||
if (cmpUnitAI) | |||||
{ | |||||
for (var i = 0; i < cmpUnitAI.orderQueue.length; ++i) | |||||
{ | |||||
if (isWorkType(cmpUnitAI.orderQueue[i].type)) | |||||
{ | |||||
this.workOrders = cmpUnitAI.orderQueue.slice(i); | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// If nothing found, take the unit orders | |||||
for (var i = 0; i < this.orderQueue.length; ++i) | for (var i = 0; i < this.orderQueue.length; ++i) | ||||
{ | { | ||||
if (isWorkType(this.orderQueue[i].type)) | if (isWorkType(this.orderQueue[i].type)) | ||||
{ | { | ||||
this.workOrders = this.orderQueue.slice(i); | this.workOrders = this.orderQueue.slice(i); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
Show All 11 Lines | if (this.order && this.order.type == "Cheering") | ||||
this.orderQueue = [cheeringOrder]; | this.orderQueue = [cheeringOrder]; | ||||
} | } | ||||
else | else | ||||
this.orderQueue = []; | this.orderQueue = []; | ||||
this.AddOrders(this.workOrders); | this.AddOrders(this.workOrders); | ||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | ||||
// And if the unit is in a formation, remove it from the formation | |||||
if (this.IsFormationMember()) | |||||
{ | |||||
var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); | |||||
if (cmpFormation) | |||||
cmpFormation.RemoveMembers([this.entity]); | |||||
} | |||||
this.workOrders = []; | this.workOrders = []; | ||||
return true; | return true; | ||||
}; | }; | ||||
UnitAI.prototype.HasWorkOrders = function() | UnitAI.prototype.HasWorkOrders = function() | ||||
{ | { | ||||
return this.workOrders.length > 0; | return this.workOrders.length > 0; | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 46 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) | ||||
{ | |||||
// 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": "MoveCompleted", "data": { "error" : false }}); | |||||
}; | |||||
UnitAI.prototype.OnMoveFailure = function(msg) | |||||
{ | { | ||||
if (msg.starting && !msg.error) | this.UnitFsm.ProcessMessage(this, { "type": "MoveCompleted", "data": { "error" : true }}); | ||||
Not Done Inline Actionsmissing spaces fatherbushido: missing spaces | |||||
this.UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg}); | |||||
else if (!msg.starting || msg.error) | |||||
this.UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg}); | |||||
}; | }; | ||||
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}); | ||||
}; | }; | ||||
UnitAI.prototype.OnGlobalEntityRenamed = function(msg) | UnitAI.prototype.OnGlobalEntityRenamed = function(msg) | ||||
{ | { | ||||
let changed = false; | let changed = false; | ||||
for (let order of this.orderQueue) | for (let order of this.orderQueue) | ||||
{ | { | ||||
if (order.data && order.data.target && order.data.target == msg.entity) | if (order.data && order.data.target && order.data.target == msg.entity) | ||||
{ | { | ||||
changed = true; | changed = true; | ||||
order.data.target = msg.newentity; | order.data.target = msg.newentity; | ||||
} | } | ||||
if (order.data && order.data.formationTarget && order.data.formationTarget == msg.entity) | |||||
{ | |||||
changed = true; | |||||
order.data.formationTarget = msg.newentity; | |||||
} | |||||
} | } | ||||
if (changed) | if (changed) | ||||
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); | ||||
}; | }; | ||||
UnitAI.prototype.OnAttacked = function(msg) | UnitAI.prototype.OnAttacked = function(msg) | ||||
{ | { | ||||
this.UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg}); | this.UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg}); | ||||
Show All 19 Lines | |||||
UnitAI.prototype.OnPackFinished = function(msg) | UnitAI.prototype.OnPackFinished = function(msg) | ||||
{ | { | ||||
this.UnitFsm.ProcessMessage(this, {"type": "PackFinished", "packed": msg.packed}); | this.UnitFsm.ProcessMessage(this, {"type": "PackFinished", "packed": msg.packed}); | ||||
}; | }; | ||||
//// Helper functions to be called by the FSM //// | //// Helper functions to be called by the FSM //// | ||||
UnitAI.prototype.GetWalkSpeed = function() | |||||
{ | |||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||
return cmpUnitMotion.GetWalkSpeed(); | |||||
}; | |||||
UnitAI.prototype.GetRunSpeed = function() | UnitAI.prototype.GetRunSpeed = function() | ||||
{ | { | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
var runSpeed = cmpUnitMotion.GetRunSpeed(); | return cmpUnitMotion.GetTopSpeedRatio(); | ||||
var walkSpeed = cmpUnitMotion.GetWalkSpeed(); | |||||
if (runSpeed <= walkSpeed) | |||||
return runSpeed; | |||||
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); | |||||
var health = cmpHealth.GetHitpoints()/cmpHealth.GetMaxHitpoints(); | |||||
return (health*runSpeed + (1-health)*walkSpeed); | |||||
}; | }; | ||||
/** | /** | ||||
* Returns true if the target exists and has non-zero hitpoints. | * Returns true if the target exists and has non-zero hitpoints. | ||||
*/ | */ | ||||
UnitAI.prototype.TargetIsAlive = function(ent) | UnitAI.prototype.TargetIsAlive = function(ent) | ||||
{ | { | ||||
var cmpFormation = Engine.QueryInterface(ent, IID_Formation); | |||||
if (cmpFormation) | |||||
return true; | |||||
var cmpHealth = QueryMiragedInterface(ent, IID_Health); | var cmpHealth = QueryMiragedInterface(ent, IID_Health); | ||||
return cmpHealth && cmpHealth.GetHitpoints() != 0; | return cmpHealth && cmpHealth.GetHitpoints() != 0; | ||||
}; | }; | ||||
/** | /** | ||||
* Returns true if the target exists and needs to be killed before | * Returns true if the target exists and needs to be killed before | ||||
* beginning to gather resources from it. | * beginning to gather resources from it. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 143 Lines • ▼ Show 20 Lines | UnitAI.prototype.FindNearbyGarrisonHolder = function() | ||||
}); | }); | ||||
}; | }; | ||||
/** | /** | ||||
* Play a sound appropriate to the current entity. | * Play a sound appropriate to the current entity. | ||||
*/ | */ | ||||
UnitAI.prototype.PlaySound = function(name) | UnitAI.prototype.PlaySound = function(name) | ||||
{ | { | ||||
// If we're a formation controller, use the sounds from our first member | |||||
if (this.IsFormationController()) | |||||
{ | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
var member = cmpFormation.GetPrimaryMember(); | |||||
if (member) | |||||
PlaySound(name, member); | |||||
} | |||||
else | |||||
{ | |||||
// Otherwise use our own sounds | |||||
PlaySound(name, this.entity); | PlaySound(name, this.entity); | ||||
} | |||||
}; | }; | ||||
UnitAI.prototype.SetGathererAnimationOverride = function(disable) | // Select a visual actor variant for the purpose of animation | ||||
// This allows changing the walk animation for normal stance, combat stances, carrying stances… | |||||
// without using a hack of replacing the animation in code like we used to. | |||||
UnitAI.prototype.SetAnimationVariant = function(type) | |||||
{ | { | ||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | ||||
if (!cmpResourceGatherer) | |||||
return; | |||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (!cmpVisual) | if (!cmpVisual) | ||||
return; | return; | ||||
// Remove the animation override, so that weapons are shown again. | if (!type || type == "normal") | ||||
if (disable) | |||||
{ | { | ||||
cmpVisual.ResetMoveAnimation("walk"); | // switch between default and carrying resources depending. | ||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | |||||
if (!cmpResourceGatherer) | |||||
{ | |||||
cmpVisual.SetVariant("animationVariant", ""); | |||||
return; | return; | ||||
} | } | ||||
// Work out what we're carrying, in order to select an appropriate animation | let type = cmpResourceGatherer.GetLastCarriedType(); | ||||
var type = cmpResourceGatherer.GetLastCarriedType(); | |||||
if (type) | if (type) | ||||
{ | { | ||||
var typename = "carry_" + type.generic; | let typename = "carry_" + type.generic; | ||||
// Special case for meat | // Special case for meat | ||||
if (type.specific == "meat") | if (type.specific == "meat") | ||||
typename = "carry_" + type.specific; | typename = "carry_" + type.specific; | ||||
cmpVisual.ReplaceMoveAnimation("walk", typename); | cmpVisual.SetVariant("animationVariant", typename); | ||||
} | } | ||||
else | else | ||||
cmpVisual.ResetMoveAnimation("walk"); | cmpVisual.SetVariant("animationVariant", ""); | ||||
} | |||||
else if (type === "combat") | |||||
cmpVisual.SetVariant("animationVariant", "combat"); | |||||
}; | }; | ||||
UnitAI.prototype.SelectAnimation = function(name, once, speed, sound) | UnitAI.prototype.SelectAnimation = function(name, once, speed, sound) | ||||
{ | { | ||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | ||||
if (!cmpVisual) | if (!cmpVisual) | ||||
return; | return; | ||||
// Special case: the "move" animation gets turned into a special | |||||
// movement mode that deals with speeds and walk/run automatically | |||||
if (name == "move") | |||||
{ | |||||
// Speed to switch from walking to running animations | |||||
var runThreshold = (this.GetWalkSpeed() + this.GetRunSpeed()) / 2; | |||||
cmpVisual.SelectMovementAnimation(runThreshold); | |||||
return; | |||||
} | |||||
var soundgroup; | var soundgroup; | ||||
if (sound) | if (sound) | ||||
{ | { | ||||
var cmpSound = Engine.QueryInterface(this.entity, IID_Sound); | var cmpSound = Engine.QueryInterface(this.entity, IID_Sound); | ||||
if (cmpSound) | if (cmpSound) | ||||
soundgroup = cmpSound.GetSoundGroup(sound); | soundgroup = cmpSound.GetSoundGroup(sound); | ||||
} | } | ||||
Show All 16 Lines | UnitAI.prototype.SetAnimationSync = function(actiontime, repeattime) | ||||
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); | cmpUnitMotion.SetAbortIfStuck(30); | ||||
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); | cmpUnitMotion.SetAbortIfStuck(30); | ||||
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); | cmpUnitMotion.SetAbortIfStuck(5); | ||||
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); | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); | cmpUnitMotion.SetAbortIfStuck(5); | ||||
// 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 | |||||
if (this.IsFormationMember()) | |||||
{ | |||||
var cmpFormationUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | |||||
if (cmpFormationUnitAI && cmpFormationUnitAI.IsAttackingAsFormation()) | |||||
return false; | |||||
} | |||||
var cmpFormation = Engine.QueryInterface(target, IID_Formation); | |||||
if (cmpFormation) | |||||
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; | ||||
// 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)) | cmpUnitMotion.SetAbortIfStuck(9); | ||||
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; | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToTargetRange(target, min, max); | cmpUnitMotion.SetAbortIfStuck(5); | ||||
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(); | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); | cmpUnitMotion.SetAbortIfStuck(5); | ||||
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 cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpUnitMotion.IsInPointRange(x, z, min, max); | return cmpObstructionManager.IsInPointRange(this.entity, x, z, min, max); | ||||
}; | }; | ||||
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); | ||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max); | ||||
return cmpUnitMotion.IsInTargetRange(target, range.min, range.max); | |||||
}; | }; | ||||
/** | /** | ||||
* 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 | ||||
*/ | */ | ||||
UnitAI.prototype.CheckTargetAttackRange = function(target, type) | UnitAI.prototype.CheckTargetAttackRange = function(target, type) | ||||
{ | { | ||||
// for formation members, the formation will take care of the range check | |||||
if (this.IsFormationMember()) | |||||
{ | |||||
var cmpFormationUnitAI = Engine.QueryInterface(this.formationController, IID_UnitAI); | |||||
if (cmpFormationUnitAI && cmpFormationUnitAI.IsAttackingAsFormation() | |||||
&& cmpFormationUnitAI.order.data.target == target) | |||||
return true; | |||||
} | |||||
var cmpFormation = Engine.QueryInterface(target, IID_Formation); | |||||
if (cmpFormation) | |||||
target = cmpFormation.GetClosestMember(this.entity); | |||||
if (type != "Ranged") | if (type != "Ranged") | ||||
return this.CheckTargetRange(target, IID_Attack, type); | return this.CheckTargetRange(target, IID_Attack, type); | ||||
var targetCmpPosition = Engine.QueryInterface(target, IID_Position); | var targetCmpPosition = Engine.QueryInterface(target, IID_Position); | ||||
if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) | if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) | ||||
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); | ||||
if (!thisCmpPosition.IsInWorld()) | if (!thisCmpPosition.IsInWorld()) | ||||
return false; | return false; | ||||
var s = thisCmpPosition.GetPosition(); | var s = thisCmpPosition.GetPosition(); | ||||
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 cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpUnitMotion.IsInTargetRange(target, range.min, Math.sqrt(maxRangeSq)); | return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, Math.sqrt(maxRangeSq)); | ||||
}; | }; | ||||
UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max) | UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max) | ||||
{ | { | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpUnitMotion.IsInTargetRange(target, min, max); | return cmpObstructionManager.IsInTargetRange(this.entity, target, min, max); | ||||
}; | }; | ||||
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 cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | ||||
return cmpUnitMotion.IsInTargetRange(target, range.min, range.max); | return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max); | ||||
}; | }; | ||||
/** | /** | ||||
* 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) | ||||
{ | { | ||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
▲ Show 20 Lines • Show All 235 Lines • ▼ Show 20 Lines | UnitAI.prototype.ShouldChaseTargetedEntity = function(target, force) | ||||
if (force) | if (force) | ||||
return true; | return true; | ||||
return false; | return false; | ||||
}; | }; | ||||
//// External interface functions //// | //// External interface functions //// | ||||
UnitAI.prototype.SetFormationController = function(ent) | |||||
{ | |||||
this.formationController = ent; | |||||
// Set obstruction group, so we can walk through members | |||||
// of our own formation (or ourself if not in formation) | |||||
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); | |||||
if (cmpObstruction) | |||||
{ | |||||
if (ent == INVALID_ENTITY) | |||||
cmpObstruction.SetControlGroup(this.entity); | |||||
else | |||||
cmpObstruction.SetControlGroup(ent); | |||||
} | |||||
// If we were removed from a formation, let the FSM switch back to INDIVIDUAL | |||||
if (ent == INVALID_ENTITY) | |||||
this.UnitFsm.ProcessMessage(this, { "type": "FormationLeave" }); | |||||
}; | |||||
UnitAI.prototype.GetFormationController = function() | |||||
{ | |||||
return this.formationController; | |||||
}; | |||||
UnitAI.prototype.SetLastFormationTemplate = function(template) | |||||
{ | |||||
this.lastFormationTemplate = template; | |||||
}; | |||||
UnitAI.prototype.GetLastFormationTemplate = function() | |||||
{ | |||||
return this.lastFormationTemplate; | |||||
}; | |||||
UnitAI.prototype.MoveIntoFormation = function(cmd) | |||||
{ | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
if (!cmpFormation) | |||||
return; | |||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (!cmpPosition || !cmpPosition.IsInWorld()) | |||||
return; | |||||
var pos = cmpPosition.GetPosition(); | |||||
// Add new order to move into formation at the current position | |||||
this.PushOrderFront("MoveIntoFormation", { "x": pos.x, "z": pos.z, "force": true }); | |||||
}; | |||||
UnitAI.prototype.GetTargetPositions = function() | UnitAI.prototype.GetTargetPositions = function() | ||||
{ | { | ||||
var targetPositions = []; | var targetPositions = []; | ||||
for (var i = 0; i < this.orderQueue.length; ++i) | for (var i = 0; i < this.orderQueue.length; ++i) | ||||
{ | { | ||||
var order = this.orderQueue[i]; | var order = this.orderQueue[i]; | ||||
switch (order.type) | switch (order.type) | ||||
{ | { | ||||
case "Walk": | case "Walk": | ||||
case "WalkAndFight": | case "WalkAndFight": | ||||
case "WalkToPointRange": | case "WalkToPointRange": | ||||
case "MoveIntoFormation": | |||||
case "GatherNearPosition": | case "GatherNearPosition": | ||||
case "Patrol": | case "Patrol": | ||||
targetPositions.push(new Vector2D(order.data.x, order.data.z)); | targetPositions.push(new Vector2D(order.data.x, order.data.z)); | ||||
break; // and continue the loop | break; // and continue the loop | ||||
case "WalkToTarget": | case "WalkToTarget": | ||||
case "WalkToTargetRange": // This doesn't move to the target (just into range), but a later order will. | case "WalkToTargetRange": // This doesn't move to the target (just into range), but a later order will. | ||||
case "Guard": | case "Guard": | ||||
▲ Show 20 Lines • Show All 137 Lines • ▼ Show 20 Lines | |||||
UnitAI.prototype.SetGuardOf = function(entity) | UnitAI.prototype.SetGuardOf = function(entity) | ||||
{ | { | ||||
// entity may be undefined | // entity may be undefined | ||||
this.isGuardOf = entity; | this.isGuardOf = entity; | ||||
}; | }; | ||||
UnitAI.prototype.CanGuard = function() | UnitAI.prototype.CanGuard = function() | ||||
{ | { | ||||
// Formation controllers should always respond to commands | |||||
// (then the individual units can make up their own minds) | |||||
if (this.IsFormationController()) | |||||
return true; | |||||
// Do not let a unit already guarded to guard. This would work in principle, | // Do not let a unit already guarded to guard. This would work in principle, | ||||
// but would clutter the gui with too much buttons to take all cases into account | // but would clutter the gui with too much buttons to take all cases into account | ||||
var cmpGuard = Engine.QueryInterface(this.entity, IID_Guard); | var cmpGuard = Engine.QueryInterface(this.entity, IID_Guard); | ||||
if (cmpGuard && cmpGuard.GetEntities().length) | if (cmpGuard && cmpGuard.GetEntities().length) | ||||
return false; | return false; | ||||
return (this.template.CanGuard == "true"); | return (this.template.CanGuard == "true"); | ||||
}; | }; | ||||
/** | /** | ||||
* Set the preferred formation for this entity. | |||||
*/ | |||||
UnitAI.prototype.SetFormationTemplate = function(template) | |||||
{ | |||||
// TODO: validate this entity accepts this? | |||||
this.formationTemplate = template; | |||||
}; | |||||
UnitAI.prototype.GetFormationTemplate = function() | |||||
{ | |||||
return this.formationTemplate; | |||||
}; | |||||
/** | |||||
* Adds group-walk order to the queue, necessarily in front. | |||||
*/ | |||||
UnitAI.prototype.GroupWalk = function(groupID) | |||||
{ | |||||
this.AddOrder("GroupWalk", { "groupID": groupID }, false); | |||||
}; | |||||
/** | |||||
* Adds walk order to queue, forced by the player. | * Adds walk order to queue, forced by the player. | ||||
*/ | */ | ||||
UnitAI.prototype.Walk = function(x, z, queued) | UnitAI.prototype.Walk = function(x, z, queued) | ||||
{ | { | ||||
if (this.expectedRoute && queued) | if (this.expectedRoute && queued) | ||||
this.expectedRoute.push({ "x": x, "z": z }); | this.expectedRoute.push({ "x": x, "z": z }); | ||||
else | else | ||||
this.AddOrder("Walk", { "x": x, "z": z, "force": true }, queued); | this.AddOrder("Walk", { "x": x, "z": z, "force": true }, queued); | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | |||||
*/ | */ | ||||
UnitAI.prototype.Attack = function(target, queued, allowCapture) | UnitAI.prototype.Attack = function(target, queued, allowCapture) | ||||
{ | { | ||||
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 83 Lines • ▼ Show 20 Lines | |||||
* interrupted by attacks. | * interrupted by attacks. | ||||
*/ | */ | ||||
UnitAI.prototype.GatherNearPosition = function(x, z, type, template, queued) | UnitAI.prototype.GatherNearPosition = function(x, z, type, template, queued) | ||||
{ | { | ||||
// Remove "resource|" prefix from template name, if present. | // Remove "resource|" prefix from template name, if present. | ||||
if (template.indexOf("resource|") != -1) | if (template.indexOf("resource|") != -1) | ||||
template = template.slice(9); | template = template.slice(9); | ||||
if (this.IsFormationController() || Engine.QueryInterface(this.entity, IID_ResourceGatherer)) | if (Engine.QueryInterface(this.entity, IID_ResourceGatherer)) | ||||
this.AddOrder("GatherNearPosition", { "type": type, "template": template, "x": x, "z": z, "force": false }, queued); | this.AddOrder("GatherNearPosition", { "type": type, "template": template, "x": x, "z": z, "force": false }, queued); | ||||
else | else | ||||
this.AddOrder("Walk", { "x": x, "z": z, "force": false }, queued); | this.AddOrder("Walk", { "x": x, "z": z, "force": false }, queued); | ||||
}; | }; | ||||
/** | /** | ||||
* Adds heal order to the queue, forced by the player. | * Adds heal order to the queue, forced by the player. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | if (cmpTrader.HasBothMarkets()) | ||||
if (this.expectedRoute) | if (this.expectedRoute) | ||||
{ | { | ||||
if (!route && this.expectedRoute.length) | if (!route && this.expectedRoute.length) | ||||
data.route = this.expectedRoute.slice(); | data.route = this.expectedRoute.slice(); | ||||
this.expectedRoute = undefined; | this.expectedRoute = undefined; | ||||
} | } | ||||
if (this.IsFormationController()) | |||||
{ | |||||
this.CallMemberFunction("AddOrder", ["Trade", data, queued]); | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
if (cmpFormation) | |||||
cmpFormation.Disband(); | |||||
} | |||||
else | |||||
this.AddOrder("Trade", data, queued); | this.AddOrder("Trade", data, queued); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (this.IsFormationController()) | |||||
this.CallMemberFunction("WalkToTarget", [cmpTrader.GetFirstMarket(), queued]); | |||||
else | |||||
this.WalkToTarget(cmpTrader.GetFirstMarket(), queued); | this.WalkToTarget(cmpTrader.GetFirstMarket(), queued); | ||||
this.expectedRoute = []; | this.expectedRoute = []; | ||||
} | } | ||||
}; | }; | ||||
UnitAI.prototype.SetTargetMarket = function(target, source) | UnitAI.prototype.SetTargetMarket = function(target, source) | ||||
{ | { | ||||
var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); | var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); | ||||
if (!cmpTrader) | if (!cmpTrader) | ||||
return false; | return false; | ||||
var marketsChanged = cmpTrader.SetTargetMarket(target, source); | var marketsChanged = cmpTrader.SetTargetMarket(target, source); | ||||
if (this.IsFormationController()) | |||||
this.CallMemberFunction("SetTargetMarket", [target, source]); | |||||
return marketsChanged; | return marketsChanged; | ||||
}; | }; | ||||
UnitAI.prototype.SwitchMarketOrder = function(oldMarket, newMarket) | UnitAI.prototype.SwitchMarketOrder = function(oldMarket, newMarket) | ||||
{ | { | ||||
if (this.order && this.order.data && this.order.data.target && this.order.data.target == oldMarket) | if (this.order && this.order.data && this.order.data.target && this.order.data.target == oldMarket) | ||||
this.order.data.target = newMarket; | this.order.data.target = newMarket; | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 186 Lines • ▼ Show 20 Lines | if (!this.GetStance().targetVisibleEnemies) | ||||
return false; | return false; | ||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | ||||
return this.AttackEntitiesByPreference(cmpRangeManager.ResetActiveQuery(this.losRangeQuery)); | return this.AttackEntitiesByPreference(cmpRangeManager.ResetActiveQuery(this.losRangeQuery)); | ||||
}; | }; | ||||
UnitAI.prototype.FindWalkAndFightTargets = function() | UnitAI.prototype.FindWalkAndFightTargets = function() | ||||
{ | { | ||||
if (this.IsFormationController()) | |||||
{ | |||||
var cmpUnitAI; | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
for (var ent of cmpFormation.members) | |||||
{ | |||||
if (!(cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI))) | |||||
continue; | |||||
var targets = cmpUnitAI.GetTargetsFromUnit(); | |||||
for (var targ of targets) | |||||
{ | |||||
if (!cmpUnitAI.CanAttack(targ)) | |||||
continue; | |||||
if (this.order.data.targetClasses) | |||||
{ | |||||
var cmpIdentity = Engine.QueryInterface(targ, IID_Identity); | |||||
var targetClasses = this.order.data.targetClasses; | |||||
if (targetClasses.attack && cmpIdentity | |||||
&& !MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack)) | |||||
continue; | |||||
if (targetClasses.avoid && cmpIdentity | |||||
&& MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid)) | |||||
continue; | |||||
// Only used by the AIs to prevent some choices of targets | |||||
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ]) | |||||
continue; | |||||
} | |||||
this.PushOrderFront("Attack", { "target": targ, "force": true, "allowCapture": true }); | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
var targets = this.GetTargetsFromUnit(); | var targets = this.GetTargetsFromUnit(); | ||||
for (var targ of targets) | for (var targ of targets) | ||||
{ | { | ||||
if (!this.CanAttack(targ)) | if (!this.CanAttack(targ)) | ||||
continue; | continue; | ||||
if (this.order.data.targetClasses) | if (this.order.data.targetClasses) | ||||
{ | { | ||||
var cmpIdentity = Engine.QueryInterface(targ, IID_Identity); | var cmpIdentity = Engine.QueryInterface(targ, IID_Identity); | ||||
▲ Show 20 Lines • Show All 119 Lines • ▼ Show 20 Lines | |||||
UnitAI.prototype.GetStanceName = function() | UnitAI.prototype.GetStanceName = function() | ||||
{ | { | ||||
return this.stance; | return this.stance; | ||||
}; | }; | ||||
UnitAI.prototype.SetMoveSpeed = function(speed) | UnitAI.prototype.SetMoveSpeed = function(speed) | ||||
{ | { | ||||
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var 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) | |||||
cmpMotion.SetSpeed(speed); | cmpUnitMotion.SetSpeed(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 All 19 Lines | UnitAI.prototype.WalkToHeldPosition = function() | ||||
} | } | ||||
return false; | return false; | ||||
}; | }; | ||||
//// Helper functions //// | //// Helper functions //// | ||||
UnitAI.prototype.CanAttack = function(target, forceResponse) | UnitAI.prototype.CanAttack = function(target, forceResponse) | ||||
{ | { | ||||
// Formation controllers should always respond to commands | |||||
// (then the individual units can make up their own minds) | |||||
if (this.IsFormationController()) | |||||
return true; | |||||
// Verify that we're able to respond to Attack commands | // Verify that we're able to respond to Attack commands | ||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpAttack) | if (!cmpAttack) | ||||
return false; | return false; | ||||
if (!cmpAttack.CanAttack(target)) | if (!cmpAttack.CanAttack(target)) | ||||
return false; | return false; | ||||
Show All 20 Lines | if (IsOwnedByEnemyOfPlayer(owner, target)) | ||||
return true; | return true; | ||||
if (forceResponse && !IsOwnedByAllyOfPlayer(owner, target)) | if (forceResponse && !IsOwnedByAllyOfPlayer(owner, target)) | ||||
return true; | return true; | ||||
return false; | return false; | ||||
}; | }; | ||||
UnitAI.prototype.CanGarrison = function(target) | UnitAI.prototype.CanGarrison = function(target) | ||||
{ | { | ||||
// Formation controllers should always respond to commands | |||||
// (then the individual units can make up their own minds) | |||||
if (this.IsFormationController()) | |||||
return true; | |||||
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); | var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); | ||||
if (!cmpGarrisonHolder) | if (!cmpGarrisonHolder) | ||||
return false; | return false; | ||||
// Verify that the target is owned by this entity's player or a mutual ally of this player | // Verify that the target is owned by this entity's player or a mutual ally of this player | ||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target))) | if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target))) | ||||
return false; | return false; | ||||
Show All 11 Lines | |||||
{ | { | ||||
if (this.IsTurret()) | if (this.IsTurret()) | ||||
return false; | return false; | ||||
// The target must be a valid resource supply, or the mirage of one. | // The target must be a valid resource supply, or the mirage of one. | ||||
var cmpResourceSupply = QueryMiragedInterface(target, IID_ResourceSupply); | var cmpResourceSupply = QueryMiragedInterface(target, IID_ResourceSupply); | ||||
if (!cmpResourceSupply) | if (!cmpResourceSupply) | ||||
return false; | return false; | ||||
// Formation controllers should always respond to commands | |||||
// (then the individual units can make up their own minds) | |||||
if (this.IsFormationController()) | |||||
return true; | |||||
// Verify that we're able to respond to Gather commands | // Verify that we're able to respond to Gather commands | ||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | ||||
if (!cmpResourceGatherer) | if (!cmpResourceGatherer) | ||||
return false; | return false; | ||||
// Verify that we can gather from this target | // Verify that we can gather from this target | ||||
if (!cmpResourceGatherer.GetTargetGatherRate(target)) | if (!cmpResourceGatherer.GetTargetGatherRate(target)) | ||||
return false; | return false; | ||||
// No need to verify ownership as we should be able to gather from | // No need to verify ownership as we should be able to gather from | ||||
// a target regardless of ownership. | // a target regardless of ownership. | ||||
// No need to call "cmpResourceSupply.IsAvailable()" either because that | // No need to call "cmpResourceSupply.IsAvailable()" either because that | ||||
// would cause units to walk to full entities instead of choosing another one | // would cause units to walk to full entities instead of choosing another one | ||||
// nearby to gather from, which is undesirable. | // nearby to gather from, which is undesirable. | ||||
return true; | return true; | ||||
}; | }; | ||||
UnitAI.prototype.CanHeal = function(target) | UnitAI.prototype.CanHeal = function(target) | ||||
{ | { | ||||
// Formation controllers should always respond to commands | |||||
// (then the individual units can make up their own minds) | |||||
if (this.IsFormationController()) | |||||
return true; | |||||
// Verify that we're able to respond to Heal commands | // Verify that we're able to respond to Heal commands | ||||
var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); | var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); | ||||
if (!cmpHeal) | if (!cmpHeal) | ||||
return false; | return false; | ||||
// Verify that the target is alive | // Verify that the target is alive | ||||
if (!this.TargetIsAlive(target)) | if (!this.TargetIsAlive(target)) | ||||
return false; | return false; | ||||
Show All 22 Lines | UnitAI.prototype.CanHeal = function(target) | ||||
return false; | return false; | ||||
}; | }; | ||||
UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) | UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) | ||||
{ | { | ||||
if (this.IsTurret()) | if (this.IsTurret()) | ||||
return false; | return false; | ||||
// Formation controllers should always respond to commands | |||||
// (then the individual units can make up their own minds) | |||||
if (this.IsFormationController()) | |||||
return true; | |||||
// Verify that we're able to respond to ReturnResource commands | // Verify that we're able to respond to ReturnResource commands | ||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | ||||
if (!cmpResourceGatherer) | if (!cmpResourceGatherer) | ||||
return false; | return false; | ||||
// Verify that the target is a dropsite | // Verify that the target is a dropsite | ||||
var cmpResourceDropsite = Engine.QueryInterface(target, IID_ResourceDropsite); | var cmpResourceDropsite = Engine.QueryInterface(target, IID_ResourceDropsite); | ||||
Show All 17 Lines | UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) | ||||
return cmpPlayer && cmpPlayer.HasSharedDropsites() && cmpResourceDropsite.IsShared() && | return cmpPlayer && cmpPlayer.HasSharedDropsites() && cmpResourceDropsite.IsShared() && | ||||
cmpOwnership && IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target); | cmpOwnership && IsOwnedByMutualAllyOfPlayer(cmpOwnership.GetOwner(), target); | ||||
}; | }; | ||||
UnitAI.prototype.CanTrade = function(target) | UnitAI.prototype.CanTrade = function(target) | ||||
{ | { | ||||
if (this.IsTurret()) | if (this.IsTurret()) | ||||
return false; | return false; | ||||
// Formation controllers should always respond to commands | |||||
// (then the individual units can make up their own minds) | |||||
if (this.IsFormationController()) | |||||
return true; | |||||
// Verify that we're able to respond to Trade commands | // Verify that we're able to respond to Trade commands | ||||
var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); | var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); | ||||
return cmpTrader && cmpTrader.CanTrade(target); | return cmpTrader && cmpTrader.CanTrade(target); | ||||
}; | }; | ||||
UnitAI.prototype.CanRepair = function(target) | UnitAI.prototype.CanRepair = function(target) | ||||
{ | { | ||||
if (this.IsTurret()) | if (this.IsTurret()) | ||||
return false; | return false; | ||||
// Formation controllers should always respond to commands | |||||
// (then the individual units can make up their own minds) | |||||
if (this.IsFormationController()) | |||||
return true; | |||||
// Verify that we're able to respond to Repair (Builder) commands | // Verify that we're able to respond to Repair (Builder) commands | ||||
var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder); | var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder); | ||||
if (!cmpBuilder) | if (!cmpBuilder) | ||||
return false; | return false; | ||||
// Verify that the target can be either built or repaired | // Verify that the target can be either built or repaired | ||||
var cmpFoundation = QueryMiragedInterface(target, IID_Foundation); | var cmpFoundation = QueryMiragedInterface(target, IID_Foundation); | ||||
Show All 19 Lines | |||||
}; | }; | ||||
UnitAI.prototype.IsPacking = function() | UnitAI.prototype.IsPacking = function() | ||||
{ | { | ||||
var cmpPack = Engine.QueryInterface(this.entity, IID_Pack); | var cmpPack = Engine.QueryInterface(this.entity, IID_Pack); | ||||
return (cmpPack && cmpPack.IsPacking()); | return (cmpPack && cmpPack.IsPacking()); | ||||
}; | }; | ||||
//// Formation specific functions //// | |||||
UnitAI.prototype.IsAttackingAsFormation = function() | |||||
{ | |||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | |||||
return cmpAttack && cmpAttack.CanAttackAsFormation() | |||||
&& this.GetCurrentState() == "FORMATIONCONTROLLER.COMBAT.ATTACKING"; | |||||
}; | |||||
//// Animal specific functions //// | //// Animal specific functions //// | ||||
UnitAI.prototype.MoveRandomly = function(distance) | UnitAI.prototype.MoveRandomly = function(distance) | ||||
{ | { | ||||
// We want to walk in a random direction, but avoid getting stuck | // We want to walk in a random direction, but avoid getting stuck | ||||
// in obstacles or narrow spaces. | // in obstacles or narrow spaces. | ||||
// So pick a circular range from approximately our current position, | // So pick a circular range from approximately our current position, | ||||
// and move outwards to the nearest point on that circle, which will | // and move outwards to the nearest point on that circle, which will | ||||
Show All 14 Lines | UnitAI.prototype.MoveRandomly = function(distance) | ||||
var jitter = 0.5; | var jitter = 0.5; | ||||
// Randomly adjust the range's center a bit, so we tend to prefer | // Randomly adjust the range's center a bit, so we tend to prefer | ||||
// moving in random directions (if there's nothing in the way) | // moving in random directions (if there's nothing in the way) | ||||
var tx = pos.x + randFloat(-1, 1) * jitter; | var tx = pos.x + randFloat(-1, 1) * jitter; | ||||
var tz = pos.z + randFloat(-1, 1) * jitter; | var tz = pos.z + randFloat(-1, 1) * jitter; | ||||
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
cmpMotion.MoveToPointRange(tx, tz, distance, distance); | cmpMotion.SetNewDestinationAsPosition(tx, tz, distance, 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); | ||||
}; | }; | ||||
/** | |||||
* Call obj.funcname(args) on UnitAI components of all formation members. | |||||
*/ | |||||
UnitAI.prototype.CallMemberFunction = function(funcname, args) | |||||
{ | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
if (!cmpFormation) | |||||
return; | |||||
cmpFormation.GetMembers().forEach(ent => { | |||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | |||||
cmpUnitAI[funcname].apply(cmpUnitAI, args); | |||||
}); | |||||
}; | |||||
/** | |||||
* Call obj.functname(args) on UnitAI components of all formation members, | |||||
* and return true if all calls return true. | |||||
*/ | |||||
UnitAI.prototype.TestAllMemberFunction = function(funcname, args) | |||||
{ | |||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); | |||||
if (!cmpFormation) | |||||
return false; | |||||
return cmpFormation.GetMembers().every(ent => { | |||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | |||||
return cmpUnitAI[funcname].apply(cmpUnitAI, args); | |||||
}); | |||||
}; | |||||
UnitAI.prototype.UnitFsm = new FSM(UnitAI.prototype.UnitFsmSpec); | UnitAI.prototype.UnitFsm = new FSM(UnitAI.prototype.UnitFsmSpec); | ||||
Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI); | Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI); |
Wildfire Games · Phabricator
don't forget it :p