1
0
forked from 0ad/0ad

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:
wraitii 2019-07-22 18:07:24 +00:00
parent 09315fdc6b
commit 32e8ed51aa
6 changed files with 239 additions and 104 deletions

View File

@ -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.

View File

@ -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;
};
/**

View File

@ -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)

View File

@ -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;

View File

@ -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) });

View File

@ -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;
}
////////////////////////////////