mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-09 18:08:09 +00:00
f5ef306d99
## Description: Adds a **"Highlight small players"** client setting (in the in-game Settings menu). When it's on, human players holding **0.2% or less of the map** get a soft red glow that breathes (a pulsing aura) around their territory, starting **one minute into the game**. This makes near-eliminated players easy to spot on a busy map, even when their territory is scattered in fragments or sitting under structures. How it works: - **Purely client-side**, no simulation or determinism impact. `SmallPlayerGlowPass` renders a tile-space bloom: extract a sub-tile mask of the small players' tiles, run a separable Gaussian blur, then composite the soft aura over the map additively. It's camera-independent (no shimmer when panning/zooming), so scattered tiles blur into one clean halo. The aura breathes: its intensity fades fully to 0 at the trough for clear contrast. - The set is recomputed each tick in `WebGLFrameBuilder`: alive human players with `tilesOwned / (landTiles - fallout) <= 0.002` (0.2%), suppressed during the spawn phase and the first minute. - Backed by a persisted `UserSettings` flag, toggleable live from the in-game Settings modal (with a new, distinct icon). - Drawn after the structure passes so buildings can't hide it. - Mirrors the existing FalloutBloom pipeline and reuses the shared blur shader and render-target helpers rather than duplicating them. Tunable via `render-settings.json` (`smallPlayerGlow`: color / alpha / pulseSpeed). UI (glow fades in and out): <img width="474" height="334" alt="image" src="https://github.com/user-attachments/assets/2b94f292-2cbb-43d3-82fb-f274d1afdedf" /> <img width="976" height="400" alt="image" src="https://github.com/user-attachments/assets/f1b972d7-e046-4f7a-ab43-da1eb758c7b5" /> ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory ## Please put your Discord username so you can be contacted if a bug or regression is found: zixer._
89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import { EFFECTS_KEY, UserSettings } from "../src/core/game/UserSettings";
|
|
|
|
describe("UserSettings effect selection", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
// UserSettings keeps a static in-memory cache; reset it too so each test
|
|
// reads fresh from the (cleared) localStorage.
|
|
(
|
|
UserSettings as unknown as { cache: Map<string, string | null> }
|
|
).cache.clear();
|
|
});
|
|
|
|
it("sets and reads a per-effectType selection", () => {
|
|
const s = new UserSettings();
|
|
s.setSelectedEffectName("transportShipTrail", "spectrum");
|
|
expect(s.getSelectedEffectName("transportShipTrail")).toBe("spectrum");
|
|
});
|
|
|
|
it("returns null when nothing is selected", () => {
|
|
expect(
|
|
new UserSettings().getSelectedEffectName("transportShipTrail"),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("clearing the last selection removes the storage key", () => {
|
|
const s = new UserSettings();
|
|
s.setSelectedEffectName("transportShipTrail", "spectrum");
|
|
s.setSelectedEffectName("transportShipTrail", undefined);
|
|
expect(s.getSelectedEffectName("transportShipTrail")).toBeNull();
|
|
expect(localStorage.getItem(EFFECTS_KEY)).toBeNull();
|
|
});
|
|
|
|
it("clearing one effectType leaves other types intact", () => {
|
|
const s = new UserSettings();
|
|
// Seed two types directly (only one real effectType exists today).
|
|
localStorage.setItem(
|
|
EFFECTS_KEY,
|
|
JSON.stringify({ transportShipTrail: "spectrum", future: "x" }),
|
|
);
|
|
s.setSelectedEffectName("transportShipTrail", undefined);
|
|
expect(s.getSelectedEffects()).toEqual({ future: "x" });
|
|
});
|
|
|
|
it("returns an empty map for a corrupt blob", () => {
|
|
localStorage.setItem(EFFECTS_KEY, "not json");
|
|
expect(new UserSettings().getSelectedEffects()).toEqual({});
|
|
});
|
|
|
|
it("keeps per-nukeType nuke-explosion slots independent", () => {
|
|
const s = new UserSettings();
|
|
s.setSelectedEffectName("atom", "atom_boom");
|
|
s.setSelectedEffectName("hydro", "hydro_boom");
|
|
expect(s.getSelectedEffectName("atom")).toBe("atom_boom");
|
|
expect(s.getSelectedEffectName("hydro")).toBe("hydro_boom");
|
|
// Clearing one bomb's slot leaves the others intact.
|
|
s.setSelectedEffectName("atom", undefined);
|
|
expect(s.getSelectedEffectName("atom")).toBeNull();
|
|
expect(s.getSelectedEffectName("hydro")).toBe("hydro_boom");
|
|
});
|
|
});
|
|
|
|
describe("UserSettings highlight small players", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
(
|
|
UserSettings as unknown as { cache: Map<string, string | null> }
|
|
).cache.clear();
|
|
});
|
|
|
|
it("defaults to off", () => {
|
|
expect(new UserSettings().highlightSmallPlayers()).toBe(false);
|
|
});
|
|
|
|
it("toggles on and off", () => {
|
|
const s = new UserSettings();
|
|
s.toggleHighlightSmallPlayers();
|
|
expect(s.highlightSmallPlayers()).toBe(true);
|
|
s.toggleHighlightSmallPlayers();
|
|
expect(s.highlightSmallPlayers()).toBe(false);
|
|
});
|
|
|
|
it("shares state across instances via the static cache", () => {
|
|
// The settings modal and the renderer's frame builder each hold their own
|
|
// UserSettings; a toggle in one must be visible to the other.
|
|
new UserSettings().toggleHighlightSmallPlayers();
|
|
expect(new UserSettings().highlightSmallPlayers()).toBe(true);
|
|
});
|
|
});
|