Add formations paths to UnitMotionDebugOverlay

Formation controllers now display their movement paths when enabling
UnitMotion debug overlay.

Key changes:
- Formation controllers show long paths in blue and short paths in green
- Formation controllers path are rendered if selecting
  some of the formation's members
- Prevent the gui from making redundant calls to SetMotionDebugOverlay

Existing limitations (not regressions):
- Paths are often cleared before entities complete them
- Formation members have short paths that rarely get rendered
This commit is contained in:
Atrik 2025-12-13 11:52:03 +01:00 committed by Atrik
parent 2e450f0f52
commit f856565de9
4 changed files with 54 additions and 27 deletions

View file

@ -24,10 +24,28 @@ function _setStatusBars(ents, enabled)
});
}
function _setMotionOverlay(ents, enabled)
function _setMotionOverlay(ents, enabled, motionDebugOverlay, force = false)
{
if (ents.length)
Engine.GuiInterfaceCall("SetMotionDebugOverlay", { "entities": ents, "enabled": enabled });
if (!force && !motionDebugOverlay)
return;
// Get entities plus their formation controllers (if any)
const resultSet = new Set();
for (const ent of ents)
{
resultSet.add(ent);
const entState = GetEntityState(ent);
if (entState?.unitAI?.formation)
{
resultSet.add(entState.unitAI.formation);
}
}
if (resultSet.size)
Engine.GuiInterfaceCall("SetMotionDebugOverlay", {
"entities": resultSet,
"enabled": enabled
});
}
function _playSound(ent)
@ -255,7 +273,7 @@ EntitySelection.prototype.update = function()
// Disable any highlighting of the disappeared unit
_setHighlight([ent], 0, false);
_setStatusBars([ent], false);
_setMotionOverlay([ent], false);
_setMotionOverlay([ent], false, this.motionDebugOverlay);
this.selected.delete(ent);
this.groups.removeEnt(ent);
@ -263,6 +281,10 @@ EntitySelection.prototype.update = function()
continue;
}
}
// Refresh the motion overlay
if (this.motionDebugOverlay)
_setMotionOverlay([...this.selected], true, this.motionDebugOverlay);
if (changed)
this.onChange();
};
@ -327,7 +349,7 @@ EntitySelection.prototype.addList = function(ents, quiet, force = false, addForm
_setHighlight(added, 1, true);
_setStatusBars(added, true);
_setMotionOverlay(added, this.motionDebugOverlay);
_setMotionOverlay(added, this.motionDebugOverlay, this.motionDebugOverlay);
if (added.length)
{
// Play the sound if the entity is controllable by us or Gaia-owned.
@ -358,7 +380,7 @@ EntitySelection.prototype.removeList = function(ents, addFormationMembers = true
_setHighlight(removed, 0, false);
_setStatusBars(removed, false);
_setMotionOverlay(removed, false);
_setMotionOverlay(removed, false, this.motionDebugOverlay);
this.onChange();
};
@ -367,7 +389,7 @@ EntitySelection.prototype.reset = function()
{
_setHighlight(this.toList(), 0, false);
_setStatusBars(this.toList(), false);
_setMotionOverlay(this.toList(), false);
_setMotionOverlay(this.toList(), false, this.motionDebugOverlay);
this.selected.clear();
this.groups.reset();
this.onChange();
@ -460,7 +482,7 @@ EntitySelection.prototype.setHighlightList = function(entities)
EntitySelection.prototype.SetMotionDebugOverlay = function(enabled)
{
this.motionDebugOverlay = enabled;
_setMotionOverlay(this.toList(), enabled);
_setMotionOverlay(this.toList(), enabled, this.motionDebugOverlay, true);
};
EntitySelection.prototype.onChange = function()

View file

@ -1986,12 +1986,9 @@ GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled)
GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
{
for (const ent of data.entities)
{
const cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
cmpUnitMotion.SetDebugOverlay(data.enabled);
}
data.entities.forEach(ent => {
Engine.QueryInterface(ent, IID_UnitMotion)?.SetDebugOverlay(data.enabled);
});
};
GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)

View file

@ -45,8 +45,8 @@ extern void ColorActivateFastImpl();
struct CColor
{
CColor() : r(-1.f), g(-1.f), b(-1.f), a(1.f) {}
CColor(float cr, float cg, float cb, float ca) : r(cr), g(cg), b(cb), a(ca) {}
constexpr CColor() : r(-1.f), g(-1.f), b(-1.f), a(1.f) {}
constexpr CColor(float cr, float cg, float cb, float ca) : r(cr), g(cg), b(cb), a(ca) {}
/**
* Returns whether this has been set to a valid color.

View file

@ -138,8 +138,14 @@ constexpr u8 BACKUP_HACK_DELAY = 10;
*/
constexpr u8 VERY_OBSTRUCTED_THRESHOLD = 10;
const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1);
const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1);
struct PathColorPalette
{
CColor longPath;
CColor shortPath;
};
constexpr PathColorPalette REGULAR_UNIT_PALETTE{{1, 1, 1, 1}, {1, 0, 0, 1}};
constexpr PathColorPalette FORMATION_CONTROLLER_PALETTE{{0, 0, 1, 1}, {0, 1, 0, 1}};
} // anonymous namespace
class CCmpUnitMotion final : public ICmpUnitMotion
@ -1902,7 +1908,6 @@ bool CCmpUnitMotion::IsTargetRangeReachable(entity_id_t target, entity_pos_t min
return cmpPathfinder->IsGoalReachable(pos.X, pos.Y, goal, m_PassClass);
}
void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector<SOverlayLine>& lines, CColor color)
{
bool floating = false;
@ -1934,17 +1939,20 @@ void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector<SOverlayLi
void CCmpUnitMotion::RenderSubmit(SceneCollector& collector)
{
if (!m_DebugOverlayEnabled)
return;
if (!m_DebugOverlayEnabled)
return;
RenderPath(m_LongPath, m_DebugOverlayLongPathLines, OVERLAY_COLOR_LONG_PATH);
RenderPath(m_ShortPath, m_DebugOverlayShortPathLines, OVERLAY_COLOR_SHORT_PATH);
const auto& palette{m_IsFormationController ?
FORMATION_CONTROLLER_PALETTE : REGULAR_UNIT_PALETTE};
for (size_t i = 0; i < m_DebugOverlayLongPathLines.size(); ++i)
collector.Submit(&m_DebugOverlayLongPathLines[i]);
RenderPath(m_LongPath, m_DebugOverlayLongPathLines, palette.longPath);
RenderPath(m_ShortPath, m_DebugOverlayShortPathLines, palette.shortPath);
for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i)
collector.Submit(&m_DebugOverlayShortPathLines[i]);
for (SOverlayLine& line : m_DebugOverlayLongPathLines)
collector.Submit(&line);
for (SOverlayLine& line : m_DebugOverlayShortPathLines)
collector.Submit(&line);
}
#endif // INCLUDED_CCMPUNITMOTION