UnitMotion - Send messages to UnitAI when obstructed, to allow stopping early when walking and avoiding pathfinding lag.
As reported by #5521, Ordering units to walk to a point in a forest can lag terribly, as units will end up computing long short paths in the forest, which can result in `ComputeShortPath` calls that individually take upwards of 80ms. This can be alleviated by allowing units to stop a bit earlier. A23 handled this in UnitMotion directly, but it's better to handle this in UnitAI to avoid surprises and to make it customisable on a per-state level. This diff further speeds up using the short pathfinder by starting with a smaller search range, limiting the max-range more and moving the range slightly towards the goal. This also refactors UM sending messages to UnitAI so that we may in the future push more information (in particular, the entity_id that a unit was obstructed by could be interesting for COMBAT). This doesn't fix the possibility of lag, but it reduces its occurrence to levels that should be similar to A23 and thus acceptable enough. Tested By: Freagarach Fixes #5521 Differential Revision: https://code.wildfiregames.com/D2105 This was SVN commit r22526.
This commit is contained in:
parent
09315fdc6b
commit
32e8ed51aa
@ -271,6 +271,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
}
|
||||
|
||||
this.SetHeldPosition(this.order.data.x, this.order.data.z);
|
||||
// It's not too bad if we don't arrive at exactly the right position.
|
||||
this.order.data.relaxed = true;
|
||||
if (this.IsAnimal())
|
||||
this.SetNextState("ANIMAL.WALKING");
|
||||
else
|
||||
@ -295,6 +297,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
}
|
||||
|
||||
this.SetHeldPosition(this.order.data.x, this.order.data.z);
|
||||
// It's not too bad if we don't arrive at exactly the right position.
|
||||
this.order.data.relaxed = true;
|
||||
if (this.IsAnimal())
|
||||
this.SetNextState("ANIMAL.WALKING"); // WalkAndFight not applicable for animals
|
||||
else
|
||||
@ -327,6 +331,9 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
return true;
|
||||
}
|
||||
|
||||
// It's not too bad if we don't arrive at exactly the right position.
|
||||
this.order.data.relaxed = true;
|
||||
|
||||
if (this.IsAnimal())
|
||||
this.SetNextState("ANIMAL.WALKING");
|
||||
else
|
||||
@ -458,6 +465,9 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's not too bad if we don't arrive at exactly the right position.
|
||||
this.order.data.relaxed = true;
|
||||
|
||||
this.SetNextState("INDIVIDUAL.PATROL");
|
||||
},
|
||||
|
||||
@ -540,6 +550,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
"Order.GatherNearPosition": function(msg) {
|
||||
this.SetNextState("INDIVIDUAL.GATHER.WALKING");
|
||||
this.order.data.initPos = { 'x': this.order.data.x, 'z': this.order.data.z };
|
||||
this.order.data.relaxed = true;
|
||||
},
|
||||
|
||||
"Order.ReturnResource": function(msg) {
|
||||
@ -904,8 +915,11 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (this.CheckRange(this.order.data) && this.FinishOrder())
|
||||
if (msg.likelyFailure || this.CheckRange(this.order.data))
|
||||
{
|
||||
this.FinishOrder();
|
||||
this.CallMemberFunction("ResetFinishOrder", []);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -933,8 +947,11 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (this.CheckRange(this.order.data) && this.FinishOrder())
|
||||
if (msg.likelyFailure || this.CheckRange(this.order.data))
|
||||
{
|
||||
this.FinishOrder();
|
||||
this.CallMemberFunction("ResetFinishOrder", []);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -978,7 +995,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (!msg.error && !this.CheckRange(this.order.data))
|
||||
if (!msg.likelyFailure && !this.CheckRange(this.order.data))
|
||||
return;
|
||||
/**
|
||||
* A-B-A-B-..:
|
||||
@ -1036,7 +1053,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
this.SetNextState("GARRISONING");
|
||||
if (msg.likelyFailure || msg.likelySuccess)
|
||||
this.SetNextState("GARRISONING");
|
||||
},
|
||||
},
|
||||
|
||||
@ -1071,7 +1089,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (!msg.error && !this.CheckRange(this.order.data))
|
||||
if (!msg.likelyFailure && !this.CheckRange(this.order.data))
|
||||
return;
|
||||
|
||||
if (this.FinishOrder())
|
||||
@ -1290,7 +1308,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
let pos = cmpPosition.GetPosition2D();
|
||||
atDestination = this.CheckPointRangeExplicit(pos.X + this.order.data.x, pos.Y + this.order.data.z, 0, 1);
|
||||
}
|
||||
if (!atDestination && !msg.error)
|
||||
if (!atDestination && !msg.likelyFailure)
|
||||
return;
|
||||
|
||||
if (this.FinishOrder())
|
||||
@ -1487,12 +1505,15 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
}
|
||||
},
|
||||
|
||||
"leave": function () {
|
||||
"leave": function() {
|
||||
this.StopMoving();
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (msg.error || this.CheckRange(this.order.data))
|
||||
// If it looks like the path is failing, and we are close enough (3 tiles)
|
||||
// stop anyways. This avoids pathing for an unreachable goal and reduces lag considerably.
|
||||
if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, 12) ||
|
||||
this.CheckRange(this.order.data))
|
||||
this.FinishOrder();
|
||||
},
|
||||
},
|
||||
@ -1521,7 +1542,10 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (msg.error || this.CheckRange(this.order.data))
|
||||
// If it looks like the path is failing, and we are close enough (3 tiles)
|
||||
// stop anyways. This avoids pathing for an unreachable goal and reduces lag considerably.
|
||||
if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, 12) ||
|
||||
this.CheckRange(this.order.data))
|
||||
this.FinishOrder();
|
||||
},
|
||||
},
|
||||
@ -1560,8 +1584,9 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (!msg.error && !this.CheckRange(this.order.data))
|
||||
return;
|
||||
if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, 12) ||
|
||||
this.CheckRange(this.order.data))
|
||||
this.FinishOrder();
|
||||
|
||||
if (this.orderQueue.length == 1)
|
||||
this.PushOrder("Patrol", this.patrolStartPosOrder);
|
||||
@ -1625,7 +1650,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (msg.error || this.CheckTargetRangeExplicit(this.isGuardOf, 0, this.guardRange))
|
||||
if (msg.likelyFailure || this.CheckTargetRangeExplicit(this.isGuardOf, 0, this.guardRange))
|
||||
this.SetNextState("GUARDING");
|
||||
},
|
||||
},
|
||||
@ -1710,7 +1735,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
// When we've run far enough, stop fleeing
|
||||
if (msg.error || this.CheckTargetRangeExplicit(this.order.data.target, this.order.data.distanceToFlee, -1))
|
||||
if (msg.likelyFailure || this.CheckTargetRangeExplicit(this.order.data.target, this.order.data.distanceToFlee, -1))
|
||||
this.FinishOrder();
|
||||
},
|
||||
|
||||
@ -1766,7 +1791,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (msg.error)
|
||||
if (msg.likelyFailure)
|
||||
{
|
||||
// This also handles hunting.
|
||||
if (this.orderQueue.length > 1)
|
||||
@ -1786,22 +1811,21 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
return;
|
||||
}
|
||||
|
||||
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 (this.CanUnpack())
|
||||
{
|
||||
this.PushOrderFront("Unpack", { "force": true });
|
||||
return;
|
||||
}
|
||||
this.SetNextState("ATTACKING");
|
||||
}
|
||||
else if (msg.likelySuccess)
|
||||
// Try moving again,
|
||||
// attack range uses a height-related formula and our actual max range might have changed.
|
||||
if (!this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
|
||||
this.FinishOrder();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the unit needs to unpack, do so
|
||||
if (this.CanUnpack())
|
||||
{
|
||||
this.PushOrderFront("Unpack", { "force": true });
|
||||
return;
|
||||
}
|
||||
this.SetNextState("ATTACKING");
|
||||
},
|
||||
},
|
||||
|
||||
@ -2026,8 +2050,22 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
}
|
||||
},
|
||||
|
||||
"MovementUpdate": function() {
|
||||
this.SetNextState("ATTACKING");
|
||||
"MovementUpdate": function(msg) {
|
||||
if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType))
|
||||
{
|
||||
// If the unit needs to unpack, do so
|
||||
if (this.CanUnpack())
|
||||
{
|
||||
this.PushOrderFront("Unpack", { "force": true });
|
||||
return;
|
||||
}
|
||||
this.SetNextState("ATTACKING");
|
||||
}
|
||||
else if (msg.likelySuccess)
|
||||
// Try moving again,
|
||||
// attack range uses a height-related formula and our actual max range might have changed.
|
||||
if (!this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
|
||||
this.FinishOrder();
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -2054,7 +2092,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
// The GATHERING timer will handle finding a valid resource.
|
||||
this.SetNextState("GATHERING");
|
||||
if (msg.likelyFailure || this.CheckRange(this.order.data, IID_ResourceGatherer))
|
||||
this.SetNextState("GATHERING");
|
||||
},
|
||||
|
||||
"leave": function() {
|
||||
@ -2088,7 +2127,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
// If we failed, the GATHERING timer will handle finding a valid resource.
|
||||
if (msg.error || this.CheckRange(this.order.data))
|
||||
if (msg.likelyFailure || msg.obstructed && this.RelaxedMaxRangeCheck(this.order.data, 8) ||
|
||||
this.CheckRange(this.order.data))
|
||||
this.SetNextState("GATHERING");
|
||||
},
|
||||
},
|
||||
@ -2329,7 +2369,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
"APPROACHING": {
|
||||
"enter": function() {
|
||||
if (this.CheckTargetRange(this.order.data.target, IID_Heal))
|
||||
if (this.CheckRange(this.order.data, IID_Heal))
|
||||
{
|
||||
this.SetNextState("HEALING");
|
||||
return true;
|
||||
@ -2337,7 +2377,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
if (!this.MoveTo(this.order.data, IID_Heal))
|
||||
{
|
||||
this.FinishOrder();
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2351,21 +2391,31 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
"Timer": function(msg) {
|
||||
if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Heal, null))
|
||||
{
|
||||
// Return to our original position unless we have a better order.
|
||||
if (!this.FinishOrder() && this.GetStance().respondHoldGround)
|
||||
this.WalkToHeldPosition();
|
||||
}
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
},
|
||||
|
||||
"MovementUpdate": function() {
|
||||
this.SetNextState("HEALING");
|
||||
"MovementUpdate": function(msg) {
|
||||
if (msg.likelyFailure || this.CheckRange(this.order.data, IID_Heal))
|
||||
this.SetNextState("HEALING");
|
||||
},
|
||||
},
|
||||
|
||||
"HEALING": {
|
||||
"enter": function() {
|
||||
var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
|
||||
if (!this.CheckRange(this.order.data, IID_Heal))
|
||||
{
|
||||
this.SetNextState("APPROACHING");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.TargetIsAlive(this.order.data.target) ||
|
||||
!this.CanHeal(this.order.data.target))
|
||||
{
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
return true;
|
||||
}
|
||||
|
||||
let cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
|
||||
this.healTimers = cmpHeal.GetTimers();
|
||||
|
||||
// If the repeat time since the last heal hasn't elapsed,
|
||||
@ -2378,14 +2428,12 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
prepare = Math.max(prepare, repeatLeft);
|
||||
}
|
||||
|
||||
this.StopMoving();
|
||||
|
||||
this.SelectAnimation("heal");
|
||||
this.SetAnimationSync(prepare, this.healTimers.repeat);
|
||||
this.StartTimer(prepare, this.healTimers.repeat);
|
||||
|
||||
// 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;
|
||||
|
||||
this.FaceTowardsTarget(this.order.data.target);
|
||||
},
|
||||
@ -2396,30 +2444,19 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"Timer": function(msg) {
|
||||
var target = this.order.data.target;
|
||||
let target = this.order.data.target;
|
||||
// Check the target is still alive and healable
|
||||
if (this.TargetIsAlive(target) && this.CanHeal(target))
|
||||
if (!this.TargetIsAlive(target) || !this.CanHeal(target))
|
||||
{
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
return;
|
||||
}
|
||||
// Check if we can still reach the target
|
||||
if (!this.CheckRange(this.order.data, IID_Heal))
|
||||
{
|
||||
// Check if we can still reach the target
|
||||
if (this.CheckTargetRange(target, IID_Heal))
|
||||
{
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.lastHealed = cmpTimer.GetTime() - msg.lateness;
|
||||
|
||||
this.FaceTowardsTarget(target);
|
||||
var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
|
||||
cmpHeal.PerformHeal(target);
|
||||
|
||||
if (this.resyncAnimation)
|
||||
{
|
||||
this.SetAnimationSync(this.healTimers.repeat, this.healTimers.repeat);
|
||||
this.resyncAnimation = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Can't reach it - try to chase after it
|
||||
if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
|
||||
{
|
||||
// Can't reach it - try to chase after it
|
||||
if (this.CanPack())
|
||||
{
|
||||
this.PushOrderFront("Pack", { "force": true });
|
||||
@ -2427,18 +2464,42 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
}
|
||||
this.SetNextState("HEAL.APPROACHING");
|
||||
}
|
||||
}
|
||||
// Can't reach it, healed to max hp or doesn't exist any more - give up
|
||||
if (this.FinishOrder())
|
||||
else
|
||||
this.SetNextState("FINDINGNEWTARGET");
|
||||
return;
|
||||
}
|
||||
|
||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.lastHealed = cmpTimer.GetTime() - msg.lateness;
|
||||
|
||||
this.FaceTowardsTarget(target);
|
||||
let cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
|
||||
cmpHeal.PerformHeal(target);
|
||||
|
||||
if (this.resyncAnimation)
|
||||
{
|
||||
this.SetAnimationSync(this.healTimers.repeat, this.healTimers.repeat);
|
||||
this.resyncAnimation = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
"FINDINGNEWTARGET": {
|
||||
"enter": function() {
|
||||
// If we have another order, do that instead.
|
||||
if (this.FinishOrder())
|
||||
return true;
|
||||
|
||||
// Heal another one
|
||||
if (this.FindNewHealTargets())
|
||||
return;
|
||||
return true;
|
||||
|
||||
// Return to our original position
|
||||
if (this.GetStance().respondHoldGround)
|
||||
this.WalkToHeldPosition();
|
||||
|
||||
// We quit this state right away.
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -2458,18 +2519,18 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
this.StopMoving();
|
||||
},
|
||||
|
||||
"MovementUpdate": function() {
|
||||
"MovementUpdate": function(msg) {
|
||||
// Check the dropsite is in range and we can return our resource there
|
||||
// (we didn't get stopped before reaching it)
|
||||
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);
|
||||
let cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite);
|
||||
if (cmpResourceDropsite)
|
||||
{
|
||||
// Dump any resources we can
|
||||
var dropsiteTypes = cmpResourceDropsite.GetTypes();
|
||||
let dropsiteTypes = cmpResourceDropsite.GetTypes();
|
||||
|
||||
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
cmpResourceGatherer.CommitResources(dropsiteTypes);
|
||||
|
||||
// Stop showing the carried resource animation.
|
||||
@ -2482,12 +2543,16 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
}
|
||||
}
|
||||
|
||||
// The dropsite was destroyed, or we couldn't reach it, or ownership changed
|
||||
if (msg.obstructed)
|
||||
return;
|
||||
|
||||
// If we are here: we are in range but not carrying the right resources (or resources at all),
|
||||
// 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);
|
||||
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
|
||||
let genericType = cmpResourceGatherer.GetMainCarryingType();
|
||||
let nearby = this.FindNearestDropsite(genericType);
|
||||
if (nearby)
|
||||
{
|
||||
this.FinishOrder();
|
||||
@ -2521,7 +2586,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
if (!msg.error && !this.CheckTargetRange(this.order.data.target, IID_Trader))
|
||||
if (!msg.likelyFailure && !this.CheckTargetRange(this.order.data.target, IID_Trader))
|
||||
return;
|
||||
|
||||
if (this.waypoints && this.waypoints.length)
|
||||
@ -2559,8 +2624,9 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
this.StopMoving();
|
||||
},
|
||||
|
||||
"MovementUpdate": function() {
|
||||
this.SetNextState("REPAIRING");
|
||||
"MovementUpdate": function(msg) {
|
||||
if (msg.likelyFailure || msg.likelySuccess)
|
||||
this.SetNextState("REPAIRING");
|
||||
},
|
||||
},
|
||||
|
||||
@ -2769,8 +2835,9 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
this.StopMoving();
|
||||
},
|
||||
|
||||
"MovementUpdate": function() {
|
||||
this.SetNextState("GARRISONED");
|
||||
"MovementUpdate": function(msg) {
|
||||
if (msg.likelyFailure || msg.likelySuccess)
|
||||
this.SetNextState("GARRISONED");
|
||||
},
|
||||
},
|
||||
|
||||
@ -2949,8 +3016,9 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
this.StopMoving();
|
||||
},
|
||||
|
||||
"MovementUpdate": function() {
|
||||
this.SetNextState("LOADING");
|
||||
"MovementUpdate": function(msg) {
|
||||
if (msg.likelyFailure || msg.likelySuccess)
|
||||
this.SetNextState("LOADING");
|
||||
},
|
||||
|
||||
"PickupCanceled": function() {
|
||||
@ -3793,11 +3861,9 @@ UnitAI.prototype.StopTimer = function()
|
||||
this.timer = undefined;
|
||||
};
|
||||
|
||||
//// Message handlers /////
|
||||
|
||||
UnitAI.prototype.OnMotionChanged = function(msg)
|
||||
UnitAI.prototype.OnMotionUpdate = function(msg)
|
||||
{
|
||||
this.UnitFsm.ProcessMessage(this, { "type": "MovementUpdate", "error": msg.error });
|
||||
this.UnitFsm.ProcessMessage(this, Object.assign({ "type": "MovementUpdate" }, msg));
|
||||
};
|
||||
|
||||
UnitAI.prototype.OnGlobalConstructionFinished = function(msg)
|
||||
@ -4399,6 +4465,20 @@ UnitAI.prototype.CheckTargetVisible = function(target)
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns true if the unit is in the relaxed-range from the target.
|
||||
*/
|
||||
UnitAI.prototype.RelaxedMaxRangeCheck = function(data, relaxedRange)
|
||||
{
|
||||
if (!data.relaxed)
|
||||
return false;
|
||||
|
||||
let ndata = data;
|
||||
ndata.min = 0;
|
||||
ndata.max = relaxedRange;
|
||||
return this.CheckRange(ndata);
|
||||
};
|
||||
|
||||
/**
|
||||
* Let an entity face its target.
|
||||
* @param {number} target - The entity-ID of the target.
|
||||
|
@ -319,16 +319,25 @@ public:
|
||||
/**
|
||||
* Sent by CCmpUnitMotion during Update if an event happened that might interest other components.
|
||||
*/
|
||||
class CMessageMotionChanged : public CMessage
|
||||
class CMessageMotionUpdate : public CMessage
|
||||
{
|
||||
public:
|
||||
DEFAULT_MESSAGE_IMPL(MotionChanged)
|
||||
DEFAULT_MESSAGE_IMPL(MotionUpdate)
|
||||
|
||||
CMessageMotionChanged(bool error) : error(error)
|
||||
enum UpdateType {
|
||||
LIKELY_SUCCESS, // UnitMotion considers it is arrived at destination.
|
||||
LIKELY_FAILURE, // UnitMotion says it cannot reach the destination.
|
||||
OBSTRUCTED, // UitMotion was obstructed. This does not mean stuck, but can be a hint to run range checks.
|
||||
LENGTH
|
||||
};
|
||||
|
||||
static const std::array<const char*, UpdateType::LENGTH> UpdateTypeStr;
|
||||
|
||||
CMessageMotionUpdate(UpdateType ut) : updateType(ut)
|
||||
{
|
||||
}
|
||||
|
||||
bool error; // whether we failed to start moving (couldn't find any path)
|
||||
UpdateType updateType;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2018 Wildfire Games.
|
||||
/* Copyright (C) 2019 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -45,7 +45,7 @@ MESSAGE(OwnershipChanged)
|
||||
MESSAGE(PositionChanged)
|
||||
MESSAGE(InterpolatedPositionChanged)
|
||||
MESSAGE(TerritoryPositionChanged)
|
||||
MESSAGE(MotionChanged)
|
||||
MESSAGE(MotionUpdate)
|
||||
MESSAGE(RangeUpdate)
|
||||
MESSAGE(TerrainChanged)
|
||||
MESSAGE(VisibilityChanged)
|
||||
|
@ -53,10 +53,17 @@ static const entity_pos_t WAYPOINT_ADVANCE_MAX = entity_pos_t::FromInt(TERRAIN_T
|
||||
|
||||
/**
|
||||
* Min/Max range to restrict short path queries to. (Larger ranges are slower,
|
||||
* Min/Max range to restrict short path queries to. (Larger ranges are (much) slower,
|
||||
* smaller ranges might miss some legitimate routes around large obstacles.)
|
||||
*/
|
||||
static const entity_pos_t SHORT_PATH_MIN_SEARCH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*2);
|
||||
static const entity_pos_t SHORT_PATH_MAX_SEARCH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*10);
|
||||
static const entity_pos_t SHORT_PATH_MIN_SEARCH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*3)/2;
|
||||
static const entity_pos_t SHORT_PATH_MAX_SEARCH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*6);
|
||||
static const entity_pos_t SHORT_PATH_SEARCH_RANGE_INCREMENT = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*1);
|
||||
|
||||
/**
|
||||
* When using the short-pathfinder to rejoin a long-path waypoint, aim for a circle of this radius around the waypoint.
|
||||
*/
|
||||
static const entity_pos_t SHORT_PATH_LONG_WAYPOINT_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*1);
|
||||
|
||||
/**
|
||||
* Minimum distance to goal for a long path request
|
||||
@ -461,13 +468,13 @@ private:
|
||||
|
||||
void MoveFailed()
|
||||
{
|
||||
CMessageMotionChanged msg(true);
|
||||
CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_FAILURE);
|
||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
||||
}
|
||||
|
||||
void MoveSucceeded()
|
||||
{
|
||||
CMessageMotionChanged msg(false);
|
||||
CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_SUCCESS);
|
||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
||||
}
|
||||
|
||||
@ -689,6 +696,13 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
|
||||
if (!m_ShortPath.m_Waypoints.empty())
|
||||
return;
|
||||
|
||||
if (m_FailedPathComputations >= 1)
|
||||
{
|
||||
// Inform other components - we might be ordered to stop, and computeGoal will then fail and return early.
|
||||
CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED);
|
||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
||||
}
|
||||
|
||||
// Don't notify if we are a formation member - we can occasionally be stuck for a long time
|
||||
// if our current offset is unreachable.
|
||||
if (!IsFormationMember())
|
||||
@ -702,7 +716,9 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
|
||||
m_LongPath.m_Waypoints.pop_back();
|
||||
if (!m_LongPath.m_Waypoints.empty())
|
||||
{
|
||||
PathGoal goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z };
|
||||
// Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way
|
||||
// we'll easily be able to revert it using a long path.
|
||||
PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, SHORT_PATH_LONG_WAYPOINT_RANGE };
|
||||
RequestShortPath(pos, goal, true);
|
||||
return;
|
||||
}
|
||||
@ -908,6 +924,13 @@ bool CCmpUnitMotion::HandleObstructedMove()
|
||||
if (!cmpPosition || !cmpPosition->IsInWorld())
|
||||
return false;
|
||||
|
||||
if (m_FailedPathComputations >= 1)
|
||||
{
|
||||
// Inform other components - we might be ordered to stop, and computeGoal will then fail and return early.
|
||||
CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED);
|
||||
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
|
||||
}
|
||||
|
||||
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||
|
||||
// Oops, we hit something (very likely another unit).
|
||||
@ -930,7 +953,9 @@ bool CCmpUnitMotion::HandleObstructedMove()
|
||||
m_LongPath.m_Waypoints.pop_back();
|
||||
if (!m_LongPath.m_Waypoints.empty())
|
||||
{
|
||||
PathGoal goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z };
|
||||
// Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way
|
||||
// we'll easily be able to revert it using a long path.
|
||||
PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, SHORT_PATH_LONG_WAYPOINT_RANGE };
|
||||
RequestShortPath(pos, goal, true);
|
||||
return true;
|
||||
}
|
||||
@ -1322,7 +1347,7 @@ void CCmpUnitMotion::RequestShortPath(const CFixedVector2D &from, const PathGoal
|
||||
if (!cmpPathfinder)
|
||||
return;
|
||||
|
||||
fixed searchRange = std::max(SHORT_PATH_MIN_SEARCH_RANGE * (m_FailedPathComputations + 1), goal.DistanceToPoint(from));
|
||||
fixed searchRange = SHORT_PATH_MIN_SEARCH_RANGE + SHORT_PATH_SEARCH_RANGE_INCREMENT * m_FailedPathComputations;
|
||||
if (searchRange > SHORT_PATH_MAX_SEARCH_RANGE)
|
||||
searchRange = SHORT_PATH_MAX_SEARCH_RANGE;
|
||||
|
||||
|
@ -518,11 +518,25 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request,
|
||||
|
||||
// Create impassable edges at the max-range boundary, so we can't escape the region
|
||||
// where we're meant to be searching
|
||||
|
||||
fixed rangeXMin = request.x0 - request.range;
|
||||
fixed rangeXMax = request.x0 + request.range;
|
||||
fixed rangeZMin = request.z0 - request.range;
|
||||
fixed rangeZMax = request.z0 + request.range;
|
||||
|
||||
// If useful, move the center of the search-space so that it's slightly towards the goal,
|
||||
// as the vertex pathfinder tends to be used to get around entities in front of us.
|
||||
CFixedVector2D toGoal = CFixedVector2D(request.goal.x, request.goal.z) - CFixedVector2D(request.x0, request.z0);
|
||||
if (toGoal.CompareLength(request.range) >= 0)
|
||||
{
|
||||
fixed toGoalLength = toGoal.Length();
|
||||
fixed inv = fixed::FromInt(1) / toGoalLength;
|
||||
rangeXMin += (toGoal.Multiply(std::min(toGoalLength / 2, request.range * 3 / 5)).Multiply(inv)).X;
|
||||
rangeXMax += (toGoal.Multiply(std::min(toGoalLength / 2, request.range * 3 / 5)).Multiply(inv)).X;
|
||||
rangeZMin += (toGoal.Multiply(std::min(toGoalLength / 2, request.range * 3 / 5)).Multiply(inv)).Y;
|
||||
rangeZMax += (toGoal.Multiply(std::min(toGoalLength / 2, request.range * 3 / 5)).Multiply(inv)).Y;
|
||||
}
|
||||
|
||||
// Add domain edges
|
||||
// (Inside-out square, so edges are in reverse from the usual direction.)
|
||||
m_Edges.emplace_back(Edge{ CFixedVector2D(rangeXMin, rangeZMin), CFixedVector2D(rangeXMin, rangeZMax) });
|
||||
|
@ -266,18 +266,25 @@ CMessage* CMessageTerritoryPositionChanged::FromJSVal(const ScriptInterface& scr
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
JS::Value CMessageMotionChanged::ToJSVal(const ScriptInterface& scriptInterface) const
|
||||
const std::array<const char*, CMessageMotionUpdate::UpdateType::LENGTH> CMessageMotionUpdate::UpdateTypeStr = { {
|
||||
"likelySuccess", "likelyFailure", "obstructed"
|
||||
} };
|
||||
|
||||
JS::Value CMessageMotionUpdate::ToJSVal(const ScriptInterface& scriptInterface) const
|
||||
{
|
||||
TOJSVAL_SETUP();
|
||||
SET_MSG_PROPERTY(error);
|
||||
JS::RootedValue prop(cx);
|
||||
|
||||
if (!JS_SetProperty(cx, obj, UpdateTypeStr[updateType], JS::TrueHandleValue))
|
||||
return JS::UndefinedValue();
|
||||
|
||||
return JS::ObjectValue(*obj);
|
||||
}
|
||||
|
||||
CMessage* CMessageMotionChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val)
|
||||
CMessage* CMessageMotionUpdate::FromJSVal(const ScriptInterface&, JS::HandleValue)
|
||||
{
|
||||
FROMJSVAL_SETUP();
|
||||
GET_MSG_PROPERTY(bool, error);
|
||||
return new CMessageMotionChanged(error);
|
||||
LOGWARNING("CMessageMotionUpdate::FromJSVal not implemented");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
|
Loading…
Reference in New Issue
Block a user