Skip to content

Allies intel range rings#142

Open
RutreD wants to merge 17 commits intoFAForever:masterfrom
RutreD:AlliedRangeRings
Open

Allies intel range rings#142
RutreD wants to merge 17 commits intoFAForever:masterfrom
RutreD:AlliedRangeRings

Conversation

@RutreD
Copy link
Copy Markdown

@RutreD RutreD commented Feb 25, 2026

This PR adds the ability to view the intel range(radar, sonar, omni) of allied units. It introduces console command ren_IntelRangeBehavior. Lua patch that adds an option in the game settings fa#7054

Modes:

ren_IntelRangeBehavior 0: The default behavior, as before the patch. Only your own intel rings are visible.
ren_IntelRangeBehavior 1: Displays your own rings and those provided by allied structures (e.g., radar and sonar), excluding T3 sonar, as it is a mobile unit
ren_IntelRangeBehavior 2: Displays both your own Intel rings and those of allied units.

Screen ren_IntelRangeBehavior 2
image

Summary by CodeRabbit

  • Bug Fixes

    • Corrected indexing to prevent occasional incorrect range displays.
  • New Features

    • Added configurable intel/range behavior so range indicators can be limited to own units or include allied buildings/units.
    • Smarter rules for when range indicators are shown to reduce clutter.
  • Refactor

    • Replaced the previous range-ring implementation with a consolidated, streamlined rendering flow.
  • Chores

    • Minor formatting and whitespace cleanups.

@RutreD RutreD changed the title give it a spin Allies intel range rings Feb 25, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removed the old RangeRings implementation and its asm hooks; added a new AlliedRangeRings module with intel-aware range logic and an asm trampoline; fixed pointer precedence and a public typedef in include/moho.h; removed an inline asm include from hooks/RangeRings.cpp; minor newline in include/PatchBase.h.

Changes

Cohort / File(s) Summary
Removed implementation
section/RangeRings.cpp
Deleted the previous RangeRings implementation and its asm entry points (GlobalRings, GetSelectedUnits, SelectionRings, RangeRings).
New allied range-ring logic
section/AlliedRangeRings.cxx, section/AlliedRangeRings.cpp
Added intel-range extraction (Moho__UserUnit__GetIntelRanges), map_has_unit, ShouldAddUnit, RenderRange__Moho__UserUnit__UserUnit, global intelRangeBehavior and conIntelRangeBehavior.
ASM wrapper & hooks
section/AlliedRangeRings.cpp, hooks/AlliedRangeRings.hook, hooks/RangeRings.cpp
Added naked asm wrapper asm__ShouldAddUnit and hook patches invoking it; removed #include "../define.h" and an inline asm section from hooks/RangeRings.cpp; added hook entries to call new APIs.
Headers & minor formatting
include/moho.h, include/PatchBase.h
Fixed operator precedence in moho_set index calculation (&begin[(item >> 5) - baseI]), changed typedef struct mRequest to struct mRequest, and applied a trailing-newline formatting change in PatchBase.h.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Renderer
  participant ASMWrapper as asm__ShouldAddUnit
  participant Decision as ShouldAddUnit
  participant Blueprint
  participant Map
  participant AllyCheck as Army_IsAlly

  Renderer->>ASMWrapper: invoke range-check
  ASMWrapper->>Decision: preserve registers & call ShouldAddUnit
  Decision->>Blueprint: query intel ranges (omni/radar/sonar)
  Decision->>Map: call map_has_unit (unit presence)
  Decision->>AllyCheck: check ally status
  AllyCheck-->>Decision: ally result
  Map-->>Decision: presence result
  Blueprint-->>Decision: intel ranges
  Decision-->>ASMWrapper: return boolean
  ASMWrapper-->>Renderer: branch/return based on result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code and cleared the rings,
Rewrote the checks and taught intel to sing,
Pointers aligned, types made right,
Allies peek in from out of sight,
A rabbit's patch to set things bright.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description lacks important structural elements and completeness required by the template guidelines. Add missing sections: confirm all guides were read, document variable/struct naming clarity, confirm moho.h/global.h/Info.txt updates, add changes summary to README.md, and provide test instructions/hints for validation.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Allies intel range rings' directly reflects the main feature being added: Intel range ring visualization for allied units.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@section/AlliedRangeRings.cpp`:
- Around line 38-56: Add a short explanatory comment above the selectionSize <
67 check in ShouldAddUnit that documents why 67 was chosen (e.g., engine
selection limit, practical performance cutoff, or observed behavior) so future
maintainers understand the constraint; reference the selectionSize variable and
the selection-size optimization branch that avoids double-rendering (the check
using selectionSize < 67 and the subsequent map_has_unit(session + 0x4A0,
userUnit) call) and state the source of the value (engine limit, empirical
threshold, or link to design docs) and any implications if it changes.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5eee9b and 1c6ba97.

📒 Files selected for processing (6)
  • hooks/AlliedRangeRings.cpp
  • hooks/RangeRings.cpp
  • include/PatchBase.h
  • include/moho.h
  • section/AlliedRangeRings.cpp
  • section/RangeRings.cpp
💤 Files with no reviewable changes (2)
  • hooks/RangeRings.cpp
  • section/RangeRings.cpp

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/AlliedRangeRings.hook`:
- Around line 5-6: The patch writes the address of
Moho__UserUnit__GetIntelRanges into 0x00E4D984 but the review warns the original
slot may expect a different ABI; inspect all call sites that read 0x00E4D984
(including any vtable usage or direct jumps) and confirm they invoke a
__thiscall member-style function with the exact parameter/return signature used
in the extern "C" declaration; if they do not, either change the declaration for
Moho__UserUnit__GetIntelRanges to the ABI the callers expect or add a small
thunk wrapper that adapts calling conventions/this-pointer semantics and store
the thunk address at 0x00E4D984 instead, then re-run a build/test to validate no
calling-ABI mismatches remain.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c6ba97 and fd5ea0e.

📒 Files selected for processing (1)
  • hooks/AlliedRangeRings.hook

@Garanas
Copy link
Copy Markdown
Member

Garanas commented Feb 26, 2026

This would be amazing!

Would it be possible to include/exclude mobile units somehow via a toggle (console command)? For example, frigates have a lot of range rings. This may clutter the screen. And personally, I use range rings as an informal indicator that these (mobile) units are mine haha. I may want to disable it for mobile units because of that. But it's not blocking in my point of view.

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Feb 26, 2026

Everything is possible, after all, we are C++ developers
Write down anything you want(with details!). If it doesn't require a lot of effort, I will implement it, otherwise leave it for 4z0t :D
For example, 1. switch to default behavior....., or 2. enable SMD circles (possibly including enemy ones)...... 3. etc....; by configuring some command/lua table, etc.

@Garanas
Copy link
Copy Markdown
Member

Garanas commented Feb 28, 2026

To me, just these two features would be useful:

  • Turn on/off the new behavior as a whole (just your own units).
  • Turn on/off the new behavior for mobile units (your own units + structures of allies)

In Lua I think we should default it to the new behavior, but with mobile units turned off. That way you get 95% of the benefit, without all the clutter in larger and longer teamgames.

Maybe it is separate, maybe it is not: being able to see SMD/TMD range of allies. But I'd rather first merge this in and that you work on that in a separate pull request. As the value of just this change to be able to share intel is huge.

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Feb 28, 2026

I added ren_IntelRangeBehavior = {kOwnUnits = 0, kOwnUnitsAndAlliedBuildings = 1, kOwnUnitsAndAlliedUnits = 2}
By default, it's set to 1 (Own units and allied buildings).
Is that okay?

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@section/AlliedRangeRings.cpp`:
- Line 73: Fix the typo in the inline comment that reads "// MoutionType != None
-> let's assume this is not a building" by changing "MoutionType" to
"MotionType"; search for any other occurrences of "MoutionType" in the same
source (e.g., near the AlliedRangeRings logic) and correct them to "MotionType"
so comments consistently reference the correct symbol.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd5ea0e and ecca354.

📒 Files selected for processing (1)
  • section/AlliedRangeRings.cpp

@4z0t
Copy link
Copy Markdown
Member

4z0t commented Feb 28, 2026

I tihnk checking for unit motion is not enough. For example it will exclude t3 sonars from units that don't move.

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Feb 28, 2026

Yes, from a human perspective, it should be added. But I havent found an easy way to define the T3 sonar. Hardcoding the blueprint is a bad idea, imagine a new fifth faction? :D

@Garanas
Copy link
Copy Markdown
Member

Garanas commented Feb 28, 2026

I tihnk checking for unit motion is not enough. For example it will exclude t3 sonars from units that don't move.

To me as a first version this would suffice. We can always expand and/or refine it further later down the line.

An alternative check that may be trivial is instead of checking the motion type, you take the largest intel of any type (radar/sonar/omni). These fields always exist on the blueprint, even when they are 0. Compare that to some integer. If you can make that configurable with a console command (just an integer as input) then we could introduce a slider in the game options menu. You can then exclude allied units with an intel range of 90 or lower, for example.

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Feb 28, 2026

This can be done, but lets leave everything as is for now. We are approaching the 'customization hell' phase from zero seconds.

Imho, considering how update changelogs are presented (usually just text when you first enter the lobby), not many people will even learn about a noticeable changes and customization options. It would be awesome to show images

Honestly, I would set "all allied units" by default, but I will listen to the team leadership experience of Jip, I dont have community surveys to disagree

.exe in zulip

@Garanas
Copy link
Copy Markdown
Member

Garanas commented Feb 28, 2026

@RutreD I've made the Lua side here: FAForever/fa#7054

@Garanas
Copy link
Copy Markdown
Member

Garanas commented Feb 28, 2026

@4z0t is the pull request technically sound? Then we can merge this in and make it available on FAF Develop.

@4z0t
Copy link
Copy Markdown
Member

4z0t commented Feb 28, 2026

Not really. I highly discourage to put asm and cpp code into same file. C++ code must be in .cxx file and asm hook part in .cpp. Second is some missing structs, that are simple and can be added here, like map node and map itself.
I couldn't look at it yet deeply cuz I got flu, maybe I'll do it tomorrow.

@4z0t
Copy link
Copy Markdown
Member

4z0t commented Feb 28, 2026

One issue I noticed is allied acu has attack ring or something?
image

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Feb 28, 2026

Good find, looks like about 5 more hooks will be...
image

@4z0t
Copy link
Copy Markdown
Member

4z0t commented Feb 28, 2026

I think we should just use option 0 and 1 for now cuz it just breaks like this

2026-03-01_00-38-55.mp4

@Garanas
Copy link
Copy Markdown
Member

Garanas commented Mar 1, 2026

One issue I noticed is allied acu has attack ring or something? image

That's Omni of the ACU.

.int Moho__UserUnit__GetIntelRanges

0x007EF12B:
push esp
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you push stack pointer? Why not use lea to get proper pointer to structure on stack?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

push = 1 byte
lea > 3

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Mar 1, 2026

@4z0t , there is no need to nitpick that much. If we take this approach, then this patch is just a half-measure. You need to understand that the game is not designed for ally ranges.

Although, if you're that inspired, to move forward, you would need to completely rewrite the class RangeExtractor (.rdata:00E3F918; class Moho::RangeExtractor: (#classinformer)). However, the work for this patch is enough to rewrite 00E3F980 (RadarExtractor), 00E3F998 (Sonar), 00E3F978 (Omni), and then you'll be able to remove Moho__UserUnit__GetIntelRanges.

Ideally, you'd completely rewrite 007EEA00 ; void __userpurge RangeRenderer__Render(int wldSession@, int camera@, int rangeRenderer, int device, float deltaFrame)

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
section/AlliedRangeRings.cxx (2)

84-111: MotionType heuristic has known limitations for mobile intel units.

Per PR discussion, using MotionType != 0 (line 104) to identify mobile units is a simple heuristic that may not correctly handle all cases (e.g., T3 sonars that are stationary but classified differently). This is acknowledged in the PR discussion as an acceptable initial approach, with refinements deferred to later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@section/AlliedRangeRings.cxx` around lines 84 - 111, The MotionType heuristic
in ShouldAddUnit (the check using *reinterpret_cast<uint32_t*>(blueprint +
0x290) != 0) can misclassify units; replace the inline comment with a clear TODO
and justification: explicitly document that this is a best-effort heuristic for
distinguishing buildings vs mobile units, mention known edge-cases (e.g., T3
sonars), and add a TODO to refine the check later (suggested approaches: use
blueprint category flags or an explicit MotionType enum) so future readers see
the limitation; keep the logic unchanged for now but update the comment near the
blueprint+0x290 check and reference intelRangeBehavior and ShouldAddUnit in the
TODO.

21-31: Consider limiting initial modes per reviewer feedback.

Per the PR discussion, 4z0t recommended limiting options to modes 0 and 1 initially because other modes cause breakage. Mode 2 (kOwnUnitsAndAlliedUnits) is currently available but may exhibit visual bugs with allied mobile units displaying non-intel rings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@section/AlliedRangeRings.cxx` around lines 21 - 31, Remove the problematic
third mode by deleting the kOwnUnitsAndAlliedUnits enum entry from
IntelRangeBehavior, keep only kOwnUnits and kOwnUnitsAndAlliedBuildings, set the
default intelRangeBehavior to kOwnUnitsAndAlliedBuildings, and update the
ConDescReg string (conIntelRangeBehavior) to list only modes 0 and 1; also
search for and remove or guard any usage of kOwnUnitsAndAlliedUnits elsewhere in
the codebase to avoid references to the removed value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@section/AlliedRangeRings.cxx`:
- Around line 99-100: Army_IsAlly is missing a declaration causing the build
failure; declare it (matching the project's pattern in global.h) with the
correct signature and calling convention and bind it to the executable address
using asm(), then include that declaration before AlliedRangeRings.cxx uses it;
reference the function name Army_IsAlly and the variables focusArmyIndex and
unitArmy so you add a prototype like the other engine functions in global.h
(e.g., bool __cdecl Army_IsAlly(int armyIndex1, int armyIndex2) asm("0x...");)
and ensure the asm address matches the engine symbol.

---

Nitpick comments:
In `@section/AlliedRangeRings.cxx`:
- Around line 84-111: The MotionType heuristic in ShouldAddUnit (the check using
*reinterpret_cast<uint32_t*>(blueprint + 0x290) != 0) can misclassify units;
replace the inline comment with a clear TODO and justification: explicitly
document that this is a best-effort heuristic for distinguishing buildings vs
mobile units, mention known edge-cases (e.g., T3 sonars), and add a TODO to
refine the check later (suggested approaches: use blueprint category flags or an
explicit MotionType enum) so future readers see the limitation; keep the logic
unchanged for now but update the comment near the blueprint+0x290 check and
reference intelRangeBehavior and ShouldAddUnit in the TODO.
- Around line 21-31: Remove the problematic third mode by deleting the
kOwnUnitsAndAlliedUnits enum entry from IntelRangeBehavior, keep only kOwnUnits
and kOwnUnitsAndAlliedBuildings, set the default intelRangeBehavior to
kOwnUnitsAndAlliedBuildings, and update the ConDescReg string
(conIntelRangeBehavior) to list only modes 0 and 1; also search for and remove
or guard any usage of kOwnUnitsAndAlliedUnits elsewhere in the codebase to avoid
references to the removed value.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1285c1 and 70be9bf.

📒 Files selected for processing (2)
  • section/AlliedRangeRings.cpp
  • section/AlliedRangeRings.cxx

@4z0t
Copy link
Copy Markdown
Member

4z0t commented Mar 1, 2026

I'm not being nitpick. I want to know why you did certain things for it to work. I don't have that part reversed either that's why I'm asking.

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Mar 1, 2026

Okay, if you want, you can share the file of your IDA base .i64 in zulip. I think I could help supplement some of these parts

@4z0t
Copy link
Copy Markdown
Member

4z0t commented Mar 1, 2026

Your Moho__UserUnit__GetIntelRanges doesn't respect intel changes for units that dynamically change radar/omni/sonar, it is not acceptable. https://youtu.be/KY_4vqu8b2g

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Mar 1, 2026

You are right. It is fixed. But I dont know how to fix this for allies (and same thing with radar toggle)
image

@Strogoo
Copy link
Copy Markdown

Strogoo commented Mar 1, 2026

Hmm, is it hard to tweak the range extractors functions so they work for allied units too? This will fix all the bugs as I see it

@RutreD
Copy link
Copy Markdown
Author

RutreD commented Mar 1, 2026

@4z0t please check

@hotcheese4444
Copy link
Copy Markdown

Love this, this will be great for replays too, not having to bounce between all players to figure out team intel.

Copy link
Copy Markdown

@M3RT1N99 M3RT1N99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the previous review — the encapsulation of EntityAttributes (private fields + GetIntelRadius/SetIntelRadius member helpers that mirror the engine's bit-packing semantics) is a notably better solution than the call-site masking I had originally suggested. Making the helpers part of the type means future hooks cannot accidentally bypass the 0x80000000 enable bit. Adding the VALIDATE_OFFSETs for Entity, UserEntity, Unit, ReconBlip, etc. closes the offset-trust gap as well, and the live-attribute read in ShouldAddUnit addresses the dynamic intel range concern.

With those in, the only substantive remaining issue I'd like to flag is the < 67 selection-size threshold (inline below) — IDA verification of the actual ring-rendering pipeline shows it is both unnecessary and logically inverted. The other two inline notes are minor: a documentation request for the esp + 0x30 stack offset, and a question about the intentional sync scope of the SyncVisionRange hook.

Marking this as a comment review rather than request-changes since the previously-blocking high-bit issue is resolved. Verified against ForgedAlliance.exe md5 d27b16b5d9b1c0480dd92316b125448e.

@4z0t
Copy link
Copy Markdown
Member

4z0t commented Apr 9, 2026

Thats too many changes for 1 PR tbh. I won't review all of that.

Copy link
Copy Markdown

@M3RT1N99 M3RT1N99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both new commits address the remaining concerns cleanly:

  • 36c9140f removes the < 67 threshold entirely and moves the double-draw prevention into RenderRange — which is a better placement than my suggestion of keeping it in ShouldAddUnit, since it preserves the unit in mArmyUnitsInFrustum for other consumers while only skipping the redundant ring draw.
  • 5c6fca43 scopes the Radar/Sonar/Omni sync to allied units only, keeping own-unit recon blips untouched.

All correctness concerns from the IDA verification are resolved. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants