Fix chasing after Motion Manager & Pushing.
The motion manager introduced inbae258f9a1
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 in6a66fb8205
(and refined in7b88b1a0f9
). Differential Revision: https://code.wildfiregames.com/D3785 This was SVN commit r25185.
This commit is contained in:
parent
fb498d97d9
commit
52e8a0c5fb
BIN
binaries/data/mods/public/maps/scenarios/unit_chasing_test.xml
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/maps/scenarios/unit_chasing_test.xml
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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", {});
|
@ -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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user