1
0
forked from 0ad/0ad

Fix chasing after Motion Manager & Pushing.

The motion manager introduced in bae258f9a1 makes 'chasing' (e.g. an
entity targeting another entity, which also applies to fleeing)
behaviour symmetrical: both units see the initial state instead of it
being ID-dependent.
This allows removing hacks introduced in 6a66fb8205 (and refined in
7b88b1a0f9).

Differential Revision: https://code.wildfiregames.com/D3785
This was SVN commit r25185.
This commit is contained in:
wraitii 2021-04-03 07:00:58 +00:00
parent fb498d97d9
commit 52e8a0c5fb
3 changed files with 305 additions and 37 deletions

Binary file not shown.

View File

@ -0,0 +1,277 @@
const ARCHER_TEMPLATE = "units/maur/infantry_archer_b";
const JAV_TEMPLATE = "units/mace/infantry_javelineer_b";
const REG_UNIT_TEMPLATE = "units/athen/infantry_spearman_b";
const FAST_UNIT_TEMPLATE = "units/athen/cavalry_swordsman_b";
const FAST_UNIT_TEMPLATE_2 = "units/athen/cavalry_javelineer_b";
const ATTACKER = 2;
var QuickSpawn = function(x, z, template, owner = 1)
{
let ent = Engine.AddEntity(template);
let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpEntOwnership)
cmpEntOwnership.SetOwner(owner);
let cmpEntPosition = Engine.QueryInterface(ent, IID_Position);
cmpEntPosition.JumpTo(x, z);
return ent;
};
var WalkTo = function(x, z, queued, ent, owner=1)
{
ProcessCommand(owner, {
"type": "walk",
"entities": Array.isArray(ent) ? ent : [ent],
"x": x,
"z": z,
"queued": queued,
"force": false,
});
return ent;
};
var Attack = function(target, ent)
{
let comm = {
"type": "attack",
"entities": Array.isArray(ent) ? ent : [ent],
"target": target,
"queued": true,
"force": true,
};
ProcessCommand(ATTACKER, comm);
return ent;
};
var Garrison = function(target, ent)
{
let comm = {
"type": "garrison",
"entities": Array.isArray(ent) ? ent : [ent],
"target": target,
"queued": true,
"force": true,
};
ProcessCommand(1, comm);
return ent;
};
var gx;
var gy;
var straight_line = function(attacker_first, attacker, target, walk = true)
{
return () => {
let chaser;
let chasee;
if (attacker_first)
{
chaser = QuickSpawn(gx, 80, attacker, ATTACKER);
chasee = QuickSpawn(gx, 80+40, target);
}
else
{
chasee = QuickSpawn(gx, 80+40, target);
chaser = QuickSpawn(gx, 80, attacker, ATTACKER);
}
if (walk)
WalkTo(gx, 900, true, chasee);
Attack(chasee, chaser, ATTACKER);
return [chaser, chasee];
};
};
var straight_line_garrison = function(garrison_first, attacker, target)
{
return () => {
let chaser;
let chasee;
if (garrison_first)
{
chaser = QuickSpawn(gx, gy, attacker);
chasee = QuickSpawn(gx, gy+50, target);
}
else
{
chasee = QuickSpawn(gx, gy+50, target);
chaser = QuickSpawn(gx, gy, attacker);
}
WalkTo(gx, 900, true, chasee);
Garrison(chasee, chaser);
return [chaser, chasee];
};
};
var experiments = {};
experiments.fast_on_fast = {
"spawn": straight_line(false, FAST_UNIT_TEMPLATE, FAST_UNIT_TEMPLATE_2)
};
experiments.fast_on_fast_2 = {
"spawn": straight_line(true, FAST_UNIT_TEMPLATE, FAST_UNIT_TEMPLATE_2)
};
experiments.fast_on_slow = {
"spawn": straight_line(false, FAST_UNIT_TEMPLATE, ARCHER_TEMPLATE)
};
experiments.fast_on_slow_2 = {
"spawn": straight_line(true, FAST_UNIT_TEMPLATE, ARCHER_TEMPLATE)
};
experiments.slow_on_slow = {
"spawn": straight_line(false, "units/athen/infantry_spearman_b", "units/mace/infantry_pikeman_b")
};
experiments.slow_on_slow_2 = {
"spawn": straight_line(true, "units/athen/infantry_spearman_b", "units/mace/infantry_pikeman_b")
};
experiments.fast_on_trader = {
"spawn": straight_line(false, FAST_UNIT_TEMPLATE, "units/mace/support_trader")
};
experiments.fast_on_trader_2 = {
"spawn": straight_line(true, FAST_UNIT_TEMPLATE, "units/mace/support_trader")
};
// Traders are passive, let them flee
experiments.fast_on_trader_flee = {
"spawn": straight_line(false, JAV_TEMPLATE, "units/mace/support_trader", false)
};
experiments.fast_on_trader_flee_2 = {
"spawn": straight_line(true, JAV_TEMPLATE, "units/mace/support_trader", false)
};
experiments.slow_on_trader = {
"spawn": straight_line(false, "units/athen/infantry_spearman_b", "units/mace/support_trader", false)
};
experiments.slow_on_trader_2 = {
"spawn": straight_line(true, "units/athen/infantry_spearman_b", "units/mace/support_trader", false)
};
experiments.fast_on_muskox = {
"spawn": straight_line(false, FAST_UNIT_TEMPLATE, "gaia/fauna_muskox")
};
experiments.fast_on_muskox_2 = {
"spawn": straight_line(true, FAST_UNIT_TEMPLATE, "gaia/fauna_muskox")
};
// Women flee
experiments.fast_on_women_flee = {
"spawn": straight_line(false, FAST_UNIT_TEMPLATE, "units/mace/support_female_citizen", false)
};
experiments.fast_on_women_flee_2 = {
"spawn": straight_line(true, FAST_UNIT_TEMPLATE, "units/mace/support_female_citizen", false)
};
// Women flee
experiments.slow_on_women_flee = {
"spawn": straight_line(false, "units/athen/infantry_spearman_b", "units/mace/support_female_citizen", false)
};
experiments.slow_on_women_flee_2 = {
"spawn": straight_line(true, "units/athen/infantry_spearman_b", "units/mace/support_female_citizen", false)
};
experiments.straight_line_garrison = {
"spawn": straight_line_garrison(false, "units/athen/infantry_spearman_b", "units/mace/siege_ram")
};
experiments.straight_line_garrison_2 = {
"spawn": straight_line_garrison(true, "units/athen/infantry_spearman_b", "units/mace/siege_ram")
};
experiments.archer_on_spearman = {
"spawn": straight_line(false, ARCHER_TEMPLATE, REG_UNIT_TEMPLATE, true)
};
experiments.archer_on_spearman_2 = {
"spawn": straight_line(true, ARCHER_TEMPLATE, REG_UNIT_TEMPLATE, true)
};
experiments.jav_on_spearman = {
"spawn": straight_line(false, JAV_TEMPLATE, REG_UNIT_TEMPLATE, true)
};
experiments.jav_on_spearman_2 = {
"spawn": straight_line(true, JAV_TEMPLATE, REG_UNIT_TEMPLATE, true)
};
experiments.fast_archer_on_spearman = {
"spawn": straight_line(false, FAST_UNIT_TEMPLATE_2, REG_UNIT_TEMPLATE, true)
};
experiments.fast_archer_on_spearman_2 = {
"spawn": straight_line(true, FAST_UNIT_TEMPLATE_2, REG_UNIT_TEMPLATE, true)
};
experiments.fast_on_semi = {
"spawn": straight_line(false, FAST_UNIT_TEMPLATE_2, ARCHER_TEMPLATE, true)
};
experiments.fast_on_semi_2 = {
"spawn": straight_line(true, FAST_UNIT_TEMPLATE_2, ARCHER_TEMPLATE, true)
};
experiments.fast_on_flee = {
"spawn": straight_line(false, FAST_UNIT_TEMPLATE_2, "units/mace/support_female_citizen", false)
};
experiments.fast_on_flee_2 = {
"spawn": straight_line(true, FAST_UNIT_TEMPLATE_2, "units/mace/support_female_citizen", false)
};
var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
var onDelete = {};
Trigger.prototype.SetupUnits = function()
{
gx = 130;
gy = 150;
for (let key in experiments)
{
let ents = experiments[key].spawn();
onDelete[ents[0]] = ents;
onDelete[ents[1]] = ents;
gx += 15;
if (gx >= 620)
{
gx = 120;
gy += 70;
}
}
};
Trigger.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to === -1 && msg.entity in onDelete)
{
Engine.DestroyEntity(onDelete[msg.entity][0]);
Engine.DestroyEntity(onDelete[msg.entity][1]);
}
};
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "OnOwnershipChanged", { "enabled": true });
var cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
// Prevent promotions, messes up things.
cmpModifiersManager.AddModifiers("no_promotion_A", {
"Promotion/RequiredXp": [{ "affects": ["Unit"], "replace": 50000 }],
}, 3);
cmpModifiersManager.AddModifiers("no_promotion_B", {
"Promotion/RequiredXp": [{ "affects": ["Unit"], "replace": 50000 }],
}, 4); // player 2 is ent 4
cmpTrigger.DoAfterDelay(3000, "SetupUnits", {});

View File

@ -716,7 +716,7 @@ private:
* Attempts to replace the current path with a straight line to the target,
* if it's close enough and the route is not obstructed.
*/
bool TryGoingStraightToTarget(const CFixedVector2D& from);
bool TryGoingStraightToTarget(const CFixedVector2D& from, bool updatePaths);
/**
* Returns whether our we need to recompute a path to reach our target.
@ -955,7 +955,7 @@ void CCmpUnitMotion::Move(CCmpUnitMotionManager::MotionState& state, fixed dt)
// If we're chasing a potentially-moving unit and are currently close
// enough to its current position, and we can head in a straight line
// to it, then throw away our current path and go straight to it.
state.wentStraight = TryGoingStraightToTarget(state.initialPos);
state.wentStraight = TryGoingStraightToTarget(state.initialPos, true);
state.wasObstructed = PerformMove(dt, state.cmpPosition->GetTurnRate(), m_ShortPath, m_LongPath, state.pos, state.angle);
}
@ -1274,50 +1274,28 @@ bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out, const MoveReques
else
{
out = cmpTargetPosition->GetPosition2D();
// Because units move one-at-a-time and pathing is asynchronous, we need to account for target movement,
// if we are computing this during the MT_Motion* part of the turn.
// If our entity ID is lower, we move first, and so we need to add a predicted movement to compute a path for next turn.
// If our entity ID is higher, the target has already moved, so we can just use the position directly.
// Position is only updated after all units have moved & pushed.
// Therefore, we may need to interpolate the target position, depending on when this call takes place during the turn:
// - On "Turn Start", we'll check positions directly without interpolation.
// - During movement, we'll call this for direct-pathing & we need to interpolate
// (this way, we move where the unit will end up at the end of _this_ turn, making it match on next turn start).
// - After movement, we'll call this to request paths & we need to interpolate
// (this way, we'll move where the unit ends up in the end of _next_ turn, making it a match in 2 turns).
// TODO: This does not really aim many turns in advance, with orthogonal trajectories it probably should.
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), moveRequest.m_Entity);
CmpPtr<ICmpUnitMotionManager> cmpUnitMotionManager(GetSystemEntity());
bool needInterpolation = cmpUnitMotion && cmpUnitMotion->IsMoveRequested() && cmpUnitMotionManager->ComputingMotion();
if (needInterpolation && GetEntityId() < moveRequest.m_Entity)
if (needInterpolation)
{
// Add predicted movement.
CFixedVector2D tempPos = out + (out - cmpTargetPosition->GetPreviousPosition2D());
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return true; // Still return true since we don't need a position for the target to have one.
// Fleeing fix: if we anticipate the target to go through us, we'll suddenly turn around, which is bad.
// Pretend that the target is still behind us in those cases.
if (m_MoveRequest.m_MinRange > fixed::Zero())
{
if ((out - cmpPosition->GetPosition2D()).RelativeOrientation(tempPos - cmpPosition->GetPosition2D()) >= 0)
out = tempPos;
}
else
out = tempPos;
}
else if (needInterpolation && GetEntityId() > moveRequest.m_Entity)
{
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return true; // Still return true since we don't need a position for the target to have one.
// Fleeing fix: opposite to above, check if our target has travelled through us already this turn.
CFixedVector2D tempPos = out - (out - cmpTargetPosition->GetPreviousPosition2D());
if (m_MoveRequest.m_MinRange > fixed::Zero() &&
(out - cmpPosition->GetPosition2D()).RelativeOrientation(tempPos - cmpPosition->GetPosition2D()) < 0)
out = tempPos;
}
}
return true;
}
bool CCmpUnitMotion::TryGoingStraightToTarget(const CFixedVector2D& from)
bool CCmpUnitMotion::TryGoingStraightToTarget(const CFixedVector2D& from, bool updatePaths)
{
// Assume if we have short paths we want to follow them.
// Exception: offset movement (formations) generally have very short deltas
@ -1368,6 +1346,9 @@ bool CCmpUnitMotion::TryGoingStraightToTarget(const CFixedVector2D& from)
else if (!cmpPathfinder->CheckMovement(GetObstructionFilter(), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass))
return false;
if (!updatePaths)
return true;
// That route is okay, so update our path
m_LongPath.m_Waypoints.clear();
m_ShortPath.m_Waypoints.clear();
@ -1600,10 +1581,17 @@ void CCmpUnitMotion::ComputePathToGoal(const CFixedVector2D& from, const PathGoa
}
#endif
// If the target is close and we can reach it in a straight line,
// then we'll just go along the straight line instead of computing a path.
if (!ShouldAlternatePathfinder() && TryGoingStraightToTarget(from))
// If the target is close enough, hope that we'll be able to go straight next turn.
if (!ShouldAlternatePathfinder() && TryGoingStraightToTarget(from, false))
{
// NB: since we may fail to move straight next turn, we should edge our bets.
// Since the 'go straight' logic currently fires only if there's no short path,
// we'll compute a long path regardless to make sure _that_ stays up to date.
// (it's also extremely likely to be very fast to compute, so no big deal).
m_ShortPath.m_Waypoints.clear();
RequestLongPath(from, goal);
return;
}
// Otherwise we need to compute a path.