Skip to content

Feat: Name Layer text opacity setting#3450

Open
bijx wants to merge 5 commits intomainfrom
player-text-opacity
Open

Feat: Name Layer text opacity setting#3450
bijx wants to merge 5 commits intomainfrom
player-text-opacity

Conversation

@bijx
Copy link
Contributor

@bijx bijx commented Mar 17, 2026

Description:

It's really hard sometimes to see what buildings or features are under those big text letters of people's nametags and troop counts on the map (also reported by users on the Discord). This PR adds a setting for opacity of the name and troop count in the NameLayer, allowing users to change the opacity via a slider in both lobby settings and in-game settings. Setting the opacity to 0% will also hide the flag, but opacity changes do not change the opacity of a nation/player's flag since it already has transparency applied to it. This does not affect gameplay critical icons such as alliance break, incoming alliance request, alliance notification, nukes, crowns, emojis, etc. (see below)

Video Demo

Recording.2026-03-17.020418.mp4

Setting on Lobby Menu

Screen.Recording.2026-03-17.020514.mp4

Emojis and other icons intact (when hidden)

image image

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

bijx

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

Walkthrough

A new player name opacity control feature is implemented across the codebase. Users can adjust on-map player label visibility via a slider ranging from 0–100%. When opacity is 0, player names and flags become hidden. Changes span translations, UI components, game settings, and rendering layers.

Changes

Cohort / File(s) Summary
Translation Keys
resources/lang/en.json
Added three translation keys: player_name_opacity_label, player_name_opacity_desc, and hidden.
Game Settings
src/core/game/UserSettings.ts
Added playerNameOpacity() getter and setPlayerNameOpacity() setter methods. Both validate, round, and clamp values to [0, 100] range.
Slider Component Enhancement
src/client/components/baseComponents/setting/SettingSlider.ts
Added zeroLabelKey property and computed valueText logic. When value is 0 and zeroLabelKey is set, displays translated label instead of numeric value.
UI Modal Components
src/client/UserSettingModal.ts, src/client/graphics/layers/SettingsModal.ts
Added event handler and UI control for player name opacity slider. Both files introduce setting-slider UI elements bound to the opacity getter/setter. Note: SettingsModal.ts implementation appears duplicated across two render locations.
Name Rendering
src/client/graphics/layers/NameLayer.ts
Applies computed opacity value to player name spans and flag elements. When opacity is 0, flag display is hidden. Same opacity applied to troops display.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

✨ Names fade soft with slider's touch,
At zero, flags vanish and such,
Opacity flows like morning mist,
Opacity controlled, never missed! 👻

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding an opacity setting for name layer text, which is the core feature across all modified files.
Description check ✅ Passed The description is directly related to the changeset, explaining the feature's purpose, implementation details, user benefits, and providing video demos and screenshots as evidence.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can use your project's `biome` configuration to improve the quality of JS/TS/CSS/JSON code reviews.

Add a configuration file to your project to customize how CodeRabbit runs biome.

Copy link
Contributor

@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: 2

🧹 Nitpick comments (1)
src/client/graphics/layers/SettingsModal.ts (1)

462-493: Prefer reusing <setting-slider> here to avoid logic drift.

This block duplicates slider rendering/value-label logic already implemented in src/client/components/baseComponents/setting/SettingSlider.ts (including the zero-value translated label). Reusing the shared component will keep behavior consistent between both settings modals.

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

In `@src/client/graphics/layers/SettingsModal.ts` around lines 462 - 493, This
block duplicates the slider and zero-value label logic; replace it with the
shared SettingSlider component (tag <setting-slider> / component implementation
in SettingSlider.ts) to avoid drift: render <setting-slider> bound to
this.userSettings.playerNameOpacity(), pass the same min/max (0/100), wire its
input/change handler to onPlayerNameOpacityChange (or use the component's change
event), and remove the duplicated percent/hidden label logic since SettingSlider
already handles zero-value translation via translateText; ensure value source
remains this.userSettings.playerNameOpacity() and event handler name matches the
existing method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/graphics/layers/NameLayer.ts`:
- Around line 342-345: Replace the unsafe innerHTML assignment for player names
with a safe text assignment: locate the code in NameLayer.ts where
nameDiv.querySelector(".player-name-span") is stored in span and currently calls
span.innerHTML = render.player.name(); and change it to set span.textContent (or
assign to (span as HTMLElement).textContent) while keeping the opacity logic
that uses nameOpacity intact; ensure you use render.player.name() only as text
(no HTML parsing) so player-controlled input cannot inject markup.

In `@src/core/game/UserSettings.ts`:
- Around line 249-252: setPlayerNameOpacity currently allows NaN through into
clampedOpacity and then setFloat which persists and emits "NaN"; guard against
non-finite inputs by validating the incoming opacity with
Number.isFinite(opacity) (or isFinite) before rounding and clamping, and if it's
not finite either return early (no save/emit) or default to a safe value (e.g.,
0), then compute const clampedOpacity = Math.max(0, Math.min(100,
Math.round(opacity))) and call this.setFloat("settings.playerNameOpacity",
clampedOpacity).

---

Nitpick comments:
In `@src/client/graphics/layers/SettingsModal.ts`:
- Around line 462-493: This block duplicates the slider and zero-value label
logic; replace it with the shared SettingSlider component (tag <setting-slider>
/ component implementation in SettingSlider.ts) to avoid drift: render
<setting-slider> bound to this.userSettings.playerNameOpacity(), pass the same
min/max (0/100), wire its input/change handler to onPlayerNameOpacityChange (or
use the component's change event), and remove the duplicated percent/hidden
label logic since SettingSlider already handles zero-value translation via
translateText; ensure value source remains this.userSettings.playerNameOpacity()
and event handler name matches the existing method.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 229ede9c-496a-4d1e-bee2-868e0c5e57d9

📥 Commits

Reviewing files that changed from the base of the PR and between f356f09 and 0c0c50a.

📒 Files selected for processing (6)
  • resources/lang/en.json
  • src/client/UserSettingModal.ts
  • src/client/components/baseComponents/setting/SettingSlider.ts
  • src/client/graphics/layers/NameLayer.ts
  • src/client/graphics/layers/SettingsModal.ts
  • src/core/game/UserSettings.ts

Comment on lines 342 to +345
const span = nameDiv.querySelector(".player-name-span");
if (span) {
span.innerHTML = render.player.name();
(span as HTMLElement).style.opacity = `${nameOpacity}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid innerHTML for player names (XSS risk).

Line [344] writes player-controlled text via innerHTML. Use textContent to prevent HTML/script injection.

Suggested fix
-      span.innerHTML = render.player.name();
+      span.textContent = render.player.name();
🧰 Tools
🪛 ast-grep (0.41.1)

[warning] 343-343: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: span.innerHTML = render.player.name()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 343-343: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: span.innerHTML = render.player.name()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

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

In `@src/client/graphics/layers/NameLayer.ts` around lines 342 - 345, Replace the
unsafe innerHTML assignment for player names with a safe text assignment: locate
the code in NameLayer.ts where nameDiv.querySelector(".player-name-span") is
stored in span and currently calls span.innerHTML = render.player.name(); and
change it to set span.textContent (or assign to (span as
HTMLElement).textContent) while keeping the opacity logic that uses nameOpacity
intact; ensure you use render.player.name() only as text (no HTML parsing) so
player-controlled input cannot inject markup.

Comment on lines +249 to +252
setPlayerNameOpacity(opacity: number): void {
const clampedOpacity = Math.max(0, Math.min(100, Math.round(opacity)));
this.setFloat("settings.playerNameOpacity", clampedOpacity);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard non-finite opacity before saving.

Line [250] can produce NaN (for example if a malformed event passes NaN), and then setFloat stores "NaN" in storage and emits it.

Suggested fix
  setPlayerNameOpacity(opacity: number): void {
+   if (!Number.isFinite(opacity)) return;
    const clampedOpacity = Math.max(0, Math.min(100, Math.round(opacity)));
    this.setFloat("settings.playerNameOpacity", clampedOpacity);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setPlayerNameOpacity(opacity: number): void {
const clampedOpacity = Math.max(0, Math.min(100, Math.round(opacity)));
this.setFloat("settings.playerNameOpacity", clampedOpacity);
}
setPlayerNameOpacity(opacity: number): void {
if (!Number.isFinite(opacity)) return;
const clampedOpacity = Math.max(0, Math.min(100, Math.round(opacity)));
this.setFloat("settings.playerNameOpacity", clampedOpacity);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/game/UserSettings.ts` around lines 249 - 252, setPlayerNameOpacity
currently allows NaN through into clampedOpacity and then setFloat which
persists and emits "NaN"; guard against non-finite inputs by validating the
incoming opacity with Number.isFinite(opacity) (or isFinite) before rounding and
clamping, and if it's not finite either return early (no save/emit) or default
to a safe value (e.g., 0), then compute const clampedOpacity = Math.max(0,
Math.min(100, Math.round(opacity))) and call
this.setFloat("settings.playerNameOpacity", clampedOpacity).

@github-project-automation github-project-automation bot moved this from Triage to Development in OpenFront Release Management Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

1 participant