Changeset View
Standalone View
binaries/data/mods/public/simulation/components/UnitAI.js
Show First 20 Lines • Show All 187 Lines • ▼ Show 20 Lines | UnitAI.prototype.UnitFsmSpec = { | |||||||||
"TradingCanceled": function(msg) { | "TradingCanceled": function(msg) { | |||||||||
// ignore | // ignore | |||||||||
}, | }, | |||||||||
"GuardedAttacked": function(msg) { | "GuardedAttacked": function(msg) { | |||||||||
// ignore | // ignore | |||||||||
}, | }, | |||||||||
"GatheringStateChanged": function(msg) { | ||||||||||
// ignore | ||||||||||
}, | ||||||||||
// Formation handlers: | // Formation handlers: | |||||||||
"FormationLeave": function(msg) { | "FormationLeave": function(msg) { | |||||||||
// ignore when we're not in FORMATIONMEMBER | // ignore when we're not in FORMATIONMEMBER | |||||||||
}, | }, | |||||||||
// Called when being told to walk as part of a formation | // Called when being told to walk as part of a formation | |||||||||
"Order.FormationWalk": function(msg) { | "Order.FormationWalk": function(msg) { | |||||||||
▲ Show 20 Lines • Show All 2,018 Lines • ▼ Show 20 Lines | "GATHER": { | |||||||||
// If we failed, the GATHERING timer will handle finding a valid resource. | // If we failed, the GATHERING timer will handle finding a valid resource. | |||||||||
if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, this.DefaultRelaxedMaxRange) || | if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, this.DefaultRelaxedMaxRange) || | |||||||||
this.CheckRange(this.order.data)) | this.CheckRange(this.order.data)) | |||||||||
this.SetNextState("GATHERING"); | this.SetNextState("GATHERING"); | |||||||||
}, | }, | |||||||||
}, | }, | |||||||||
"GATHERING": { | "GATHERING": { | |||||||||
"enter": function() { | "enter": function() { | |||||||||
this.gatheringTarget = this.order.data.target || INVALID_ENTITY; // deleted in "leave". | let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | |||||||||
Freagarach: Probably not needed anymore. | ||||||||||
if (!cmpResourceGatherer) | ||||||||||
// Check if the resource is full. | ||||||||||
// Will only be added if we're not already in. | ||||||||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||||||||
let cmpSupply; | ||||||||||
if (cmpOwnership) | ||||||||||
cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | ||||||||||
if (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity)) | ||||||||||
{ | { | |||||||||
this.SetNextState("FINDINGNEWTARGET"); | this.FinishOrder(); | |||||||||
return true; | return true; | |||||||||
} | } | |||||||||
let gatheringTarget = this.order.data.target || INVALID_ENTITY; | ||||||||||
// 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). | |||||||||
this.order.data.force = false; | this.order.data.force = false; | |||||||||
this.order.data.autoharvest = true; | this.order.data.autoharvest = true; | |||||||||
// Calculate timing based on gather rates | let rate = cmpResourceGatherer.StartGathering(gatheringTarget); | |||||||||
// This allows the gather rate to control how often we gather, instead of how much. | ||||||||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | ||||||||||
let rate = cmpResourceGatherer.GetTargetGatherRate(this.gatheringTarget); | ||||||||||
if (!rate) | if (!rate) | |||||||||
{ | { | |||||||||
// Try to find another target if the current one stopped existing | ||||||||||
if (!Engine.QueryInterface(this.gatheringTarget, IID_Identity)) | ||||||||||
{ | ||||||||||
this.SetNextState("FINDINGNEWTARGET"); | this.SetNextState("FINDINGNEWTARGET"); | |||||||||
Done Inline Actions
Freagarach: | ||||||||||
return true; | return true; | |||||||||
} | } | |||||||||
// No rate, give up on gathering | // Scale timing interval based on rate, and start timer. | |||||||||
this.FinishOrder(); | ||||||||||
return true; | ||||||||||
} | ||||||||||
// 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. | |||||||||
let offset = 1000 / rate; | let offset = 1000 / rate; | |||||||||
this.StartTimer(offset, offset); | this.StartTimer(offset, offset); | |||||||||
// We want to start the gather animation as soon as possible, | // 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 | // but only if we're actually at the target and it's still alive | |||||||||
// (else it'll look like we're chopping empty air). | // (else it'll look like we're chopping empty air). | |||||||||
// (If it's not alive, the Timer handler will deal with sending us | // (If it's not alive, the Timer handler will deal with sending us | |||||||||
// off to a different target.) | // off to a different target.) | |||||||||
if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer)) | if (this.CheckTargetRange(gatheringTarget, IID_ResourceGatherer)) | |||||||||
{ | { | |||||||||
this.StopMoving(); | this.StopMoving(); | |||||||||
this.SetDefaultAnimationVariant(); | this.SetDefaultAnimationVariant(); | |||||||||
this.FaceTowardsTarget(this.order.data.target); | this.FaceTowardsTarget(this.order.data.target); | |||||||||
this.SelectAnimation("gather_" + this.order.data.type.specific); | this.SelectAnimation("gather_" + this.order.data.type.specific); | |||||||||
} | } | |||||||||
return false; | return false; | |||||||||
}, | }, | |||||||||
Done Inline ActionsHere or in cmpResourceGatherer? Freagarach: Here or in `cmpResourceGatherer`? | ||||||||||
"leave": function() { | "leave": function() { | |||||||||
this.StopTimer(); | this.StopTimer(); | |||||||||
// Don't use ownership because this is called after a conversion/resignation | let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | |||||||||
// and the ownership would be invalid then. | if (cmpResourceGatherer) | |||||||||
let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | cmpResourceGatherer.StopGathering(); | |||||||||
if (cmpSupply) | ||||||||||
cmpSupply.RemoveGatherer(this.entity); | ||||||||||
delete this.gatheringTarget; | ||||||||||
this.ResetAnimation(); | this.ResetAnimation(); | |||||||||
}, | }, | |||||||||
"Timer": function(msg) { | "GatheringStateChanged": function(msg) { | |||||||||
let resourceTemplate = this.order.data.template; | // If we've collected as many resources as possible, | |||||||||
let resourceType = this.order.data.type; | // return to the nearest dropsite. | |||||||||
if (msg.data.filled) | ||||||||||
{ | ||||||||||
let nearestDropsite = this.FindNearestDropsite(this.order.data.type.generic); | ||||||||||
if (nearestDropsite) | ||||||||||
{ | ||||||||||
// Keep this Gather order on the stack so we'll | ||||||||||
// continue gathering after returning. | ||||||||||
// However mark our target as invalid if it's exhausted, | ||||||||||
// so we don't waste time trying to gather from it. | ||||||||||
if (msg.data.exhausted) | ||||||||||
this.order.data.target = INVALID_ENTITY; | ||||||||||
bbUnsubmitted Done Inline ActionsThis sounds like useless, we should check whether we can gather from a target anyway, when approaching an old target (the target can exhaust too while returning the resource) bb: This sounds like useless, we should check whether we can gather from a target anyway, when… | ||||||||||
this.PushOrderFront("ReturnResource", { "target": nearestDropsite, "force": false }); | ||||||||||
Done Inline ActionsWe could get it from the order data. Freagarach: We could get it from the order data. | ||||||||||
return; | ||||||||||
} | ||||||||||
Done Inline ActionsCan be removed. Freagarach: Can be removed. | ||||||||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | // Oh no, couldn't find any dropsites. Give up on gathering. | |||||||||
if (!cmpOwnership) | this.FinishOrder(); | |||||||||
return; | return; | |||||||||
} | ||||||||||
if (msg.data.exhausted) | ||||||||||
this.SetNextState("FINDINGNEWTARGET"); | ||||||||||
}, | ||||||||||
"Timer": function(msg) { | ||||||||||
bbUnsubmitted Done Inline ActionsDon't like the fact we have two timers running now. Since this patch will set the defaults for many future patches we have to think about the design. Wouldn't it be much cleaner if the gather components sends a message when it is out of range? Then unitAI can decide how to act upon that. This also allows (in theory) for "multi-actions" (e.g. walking and gathering) since in that case we can simply ignore the out of range messages. bb: Don't like the fact we have two timers running now. Since this patch will set the defaults for… | ||||||||||
wraitiiUnsubmitted Done Inline ActionsIt is also possible to "soft code" a dependency on unitAI, by fetching the component and calling ProcessMessage directly. This is probably better when we want to have a channel between two components only. Can't comment on the rest so far, but I need to look at this patch. wraitii: It is also possible to "soft code" a dependency on unitAI, by fetching the component and… | ||||||||||
let gatheringTarget = this.order.data.target || INVALID_ENTITY; | ||||||||||
// TODO: we are leaking information here - if the target died in FOW, we'll know it's dead | // TODO: we are leaking information here - if the target died in FOW, we'll know it's dead | |||||||||
// straight away. | // straight away. | |||||||||
// Seems one would have to listen to ownership changed messages to make it work correctly | // Seems one would have to listen to ownership changed messages to make it work correctly | |||||||||
// but that's likely prohibitively expansive performance wise. | // but that's likely prohibitively expansive performance wise. | |||||||||
let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply); | ||||||||||
// If we can't gather from the target, find a new one. | // If we can't gather from the target, find a new one. | |||||||||
if (!cmpSupply || !cmpSupply.IsAvailable(cmpOwnership.GetOwner(), this.entity) || | if (!this.CanGather(gatheringTarget)) | |||||||||
!this.CanGather(this.gatheringTarget)) | ||||||||||
{ | { | |||||||||
this.SetNextState("FINDINGNEWTARGET"); | this.SetNextState("FINDINGNEWTARGET"); | |||||||||
return; | return; | |||||||||
} | } | |||||||||
if (!this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer)) | if (!this.CheckTargetRange(gatheringTarget, IID_ResourceGatherer)) | |||||||||
{ | { | |||||||||
// Try to follow the target | // Try to follow the target | |||||||||
if (this.MoveToTargetRange(this.gatheringTarget, IID_ResourceGatherer)) | if (this.MoveToTargetRange(gatheringTarget, IID_ResourceGatherer)) | |||||||||
this.SetNextState("APPROACHING"); | this.SetNextState("APPROACHING"); | |||||||||
// Our target is no longer visible - go to its last known position first | // Our target is no longer visible - go to its last known position first | |||||||||
// and then hopefully it will become visible. | // and then hopefully it will become visible. | |||||||||
else if (!this.CheckTargetVisible(this.gatheringTarget) && this.order.data.lastPos) | else if (!this.CheckTargetVisible(gatheringTarget) && this.order.data.lastPos) | |||||||||
this.PushOrderFront("Walk", { | this.PushOrderFront("Walk", { | |||||||||
"x": this.order.data.lastPos.x, | "x": this.order.data.lastPos.x, | |||||||||
"z": this.order.data.lastPos.z, | "z": this.order.data.lastPos.z, | |||||||||
"force": this.order.data.force | "force": this.order.data.force | |||||||||
}); | }); | |||||||||
else | else | |||||||||
this.SetNextState("FINDINGNEWTARGET"); | this.SetNextState("FINDINGNEWTARGET"); | |||||||||
return; | return; | |||||||||
} | } | |||||||||
// Gather the resources: | ||||||||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); | ||||||||||
// Try to gather treasure | ||||||||||
if (cmpResourceGatherer.TryInstantGather(this.gatheringTarget)) | ||||||||||
return; | ||||||||||
// If we've already got some resources but they're the wrong type, | ||||||||||
// drop them first to ensure we're only ever carrying one type | ||||||||||
if (cmpResourceGatherer.IsCarryingAnythingExcept(resourceType.generic)) | ||||||||||
cmpResourceGatherer.DropResources(); | ||||||||||
this.FaceTowardsTarget(this.order.data.target); | this.FaceTowardsTarget(this.order.data.target); | |||||||||
// Collect from the target | ||||||||||
let status = cmpResourceGatherer.PerformGather(this.gatheringTarget); | ||||||||||
// If we've collected as many resources as possible, | ||||||||||
// return to the nearest dropsite | ||||||||||
if (status.filled) | ||||||||||
{ | ||||||||||
let nearestDropsite = this.FindNearestDropsite(resourceType.generic); | ||||||||||
if (nearestDropsite) | ||||||||||
{ | ||||||||||
// (Keep this Gather order on the stack so we'll | ||||||||||
// continue gathering after returning) | ||||||||||
// However mark our target as invalid if it's exhausted, so we don't waste time | ||||||||||
// trying to gather from it. | ||||||||||
if (status.exhausted) | ||||||||||
this.order.data.target = INVALID_ENTITY; | ||||||||||
this.PushOrderFront("ReturnResource", { "target": nearestDropsite, "force": false }); | ||||||||||
return; | ||||||||||
} | ||||||||||
// Oh no, couldn't find any drop sites. Give up on gathering. | ||||||||||
this.FinishOrder(); | ||||||||||
return; | ||||||||||
} | ||||||||||
// Find a new target if the current one is exhausted | ||||||||||
if (status.exhausted) | ||||||||||
this.SetNextState("FINDINGNEWTARGET"); | ||||||||||
}, | }, | |||||||||
Done Inline ActionsThis is a pain, might be good to have a function in the position component that follows the rotation of a unit (so it faces keeps facing to a target whatever movement is present). Needs some proper design there probably bb: This is a pain, might be good to have a function in the position component that follows the… | ||||||||||
Done Inline ActionsFreagarach: D2870. | ||||||||||
Done Inline ActionsSince all gatherables in vanilla are either static or to be killed before gathering, we could consider just ditching this call? Freagarach: Since all gatherables in vanilla are either static or to be killed before gathering, we could… | ||||||||||
}, | }, | |||||||||
"FINDINGNEWTARGET": { | "FINDINGNEWTARGET": { | |||||||||
"enter": function() { | "enter": function() { | |||||||||
let previousTarget = this.order.data.target; | let previousTarget = this.order.data.target; | |||||||||
let resourceTemplate = this.order.data.template; | let resourceTemplate = this.order.data.template; | |||||||||
let resourceType = this.order.data.type; | let resourceType = this.order.data.type; | |||||||||
▲ Show 20 Lines • Show All 1,737 Lines • ▼ Show 20 Lines | else if (msg.tag == this.losHealRangeQuery) | |||||||||
this.UnitFsm.ProcessMessage(this, {"type": "LosHealRangeUpdate", "data": msg}); | this.UnitFsm.ProcessMessage(this, {"type": "LosHealRangeUpdate", "data": msg}); | |||||||||
}; | }; | |||||||||
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}); | |||||||||
}; | }; | |||||||||
UnitAI.prototype.OnGatheringStateChanged = function(msg) | ||||||||||
{ | ||||||||||
this.UnitFsm.ProcessMessage(this, { "type": "GatheringStateChanged", "data": msg }); | ||||||||||
}; | ||||||||||
//// Helper functions to be called by the FSM //// | //// Helper functions to be called by the FSM //// | |||||||||
UnitAI.prototype.GetWalkSpeed = function() | UnitAI.prototype.GetWalkSpeed = function() | |||||||||
{ | { | |||||||||
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | |||||||||
if (!cmpUnitMotion) | if (!cmpUnitMotion) | |||||||||
return 0; | return 0; | |||||||||
return cmpUnitMotion.GetWalkSpeed(); | return cmpUnitMotion.GetWalkSpeed(); | |||||||||
▲ Show 20 Lines • Show All 2,121 Lines • Show Last 20 Lines |
Probably not needed anymore.