diff --git a/resources/lang/en.json b/resources/lang/en.json index 5135cc399..e119dae09 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -703,6 +703,8 @@ "coordinate_grid_desc": "Toggle the alphanumeric grid overlay", "attacking_troops_overlay_label": "Attacking Troops Overlay", "attacking_troops_overlay_desc": "Show attacker vs defender troop counts on active front lines.", + "territory_border_mode_label": "Territory Borders", + "territory_border_mode_desc": "Select border rendering style (visual only)", "performance_overlay_label": "Performance Overlay", "performance_overlay_desc": "Toggle the performance overlay. When enabled, the performance overlay will be displayed. Press shift-D during game to toggle.", "easter_writing_speed_label": "Writing Speed Multiplier", diff --git a/src/client/UserSettingModal.ts b/src/client/UserSettingModal.ts index 60d434dbf..6f6ddb698 100644 --- a/src/client/UserSettingModal.ts +++ b/src/client/UserSettingModal.ts @@ -300,6 +300,16 @@ export class UserSettingModal extends BaseModal { this.requestUpdate(); } + private changeTerritoryBorderMode(e: CustomEvent<{ value: number | string }>) { + const rawValue = e.detail?.value; + const value = + typeof rawValue === "number" ? rawValue : parseInt(String(rawValue), 10); + if (!Number.isFinite(value)) return; + + this.userSettings.setInt("settings.territoryBorderMode", Math.round(value)); + this.requestUpdate(); + } + private toggleTerritoryPatterns() { this.userSettings.toggleTerritoryPatterns(); @@ -752,6 +762,21 @@ export class UserSettingModal extends BaseModal { > + + ${this.label} @@ -51,7 +57,7 @@ export class SettingSelect extends LitElement { + + + + + ${translateText("user_setting.territory_border_mode_label")} + + + ${translateText("user_setting.territory_border_mode_desc")} + + + + Off + Simple + Glow + + + u: Uniforms; @@ -30,6 +30,7 @@ fn fsMain(@builtin(position) pos: vec4f) -> @location(0) vec4f { let altView = u.viewOffset_alt_highlight.z; let highlightId = u.viewOffset_alt_highlight.w; let viewSize = u.viewSize_pad.xy; + let borderMode = u.viewSize_pad.z; // WebGPU fragment position is top-left origin and at pixel centers (0.5, 1.5, ...). let viewCoord = vec2f(pos.x - 0.5, pos.y - 0.5); @@ -76,6 +77,62 @@ fn fsMain(@builtin(position) pos: vec4f) -> @location(0) vec4f { outColor = terrain; } + // Borders (purely visual): render a stable-pixel-width line at ownership edges. + if (borderMode > 0.5 && altView <= 0.5 && owner != 0u) { + let fx = fract(mapCoord.x); + let fy = fract(mapCoord.y); + + var dist = 1e9; + + // Only border against other non-zero owners. + if (texCoord.x > 0) { + let o = textureLoad(stateTex, texCoord + vec2i(-1, 0), 0).x & 0xFFFu; + if (o != 0u && o != owner) { + dist = min(dist, fx); + } + } + if (texCoord.x + 1 < i32(mapRes.x)) { + let o = textureLoad(stateTex, texCoord + vec2i(1, 0), 0).x & 0xFFFu; + if (o != 0u && o != owner) { + dist = min(dist, 1.0 - fx); + } + } + if (texCoord.y > 0) { + let o = textureLoad(stateTex, texCoord + vec2i(0, -1), 0).x & 0xFFFu; + if (o != 0u && o != owner) { + dist = min(dist, fy); + } + } + if (texCoord.y + 1 < i32(mapRes.y)) { + let o = textureLoad(stateTex, texCoord + vec2i(0, 1), 0).x & 0xFFFu; + if (o != 0u && o != owner) { + dist = min(dist, 1.0 - fy); + } + } + + if (dist < 1e8) { + let pxPerTile = max(viewScale, 0.001); + let aaTiles = 1.0 / pxPerTile; + let thicknessPx = select(1.0, 2.5, borderMode > 1.5); + let thicknessTiles = thicknessPx / pxPerTile; + + let line = 1.0 - smoothstep(thicknessTiles, thicknessTiles + aaTiles, dist); + outColor = vec4f( + mix(outColor.rgb, vec3f(0.0, 0.0, 0.0), clamp(line * 0.9, 0.0, 0.9)), + outColor.a, + ); + + if (borderMode > 1.5) { + let glowTiles = (thicknessPx * 3.0) / pxPerTile; + let glow = 1.0 - smoothstep(glowTiles, glowTiles + aaTiles * 2.0, dist); + outColor = vec4f( + mix(outColor.rgb, vec3f(1.0, 1.0, 1.0), clamp(glow * 0.12, 0.0, 0.12)), + outColor.a, + ); + } + } + } + // Apply hover highlight if needed if (highlightId > 0.5) { let alpha = select(0.65, 0.0, altView > 0.5); diff --git a/src/core/game/UserSettings.ts b/src/core/game/UserSettings.ts index 5062c7936..44f7695b0 100644 --- a/src/core/game/UserSettings.ts +++ b/src/core/game/UserSettings.ts @@ -123,6 +123,20 @@ export class UserSettings { this.setCached(key, value.toString()); } + getInt(key: string, defaultValue: number): number { + const value = localStorage.getItem(key); + if (!value) return defaultValue; + + const intValue = parseInt(value, 10); + if (!Number.isFinite(intValue)) return defaultValue; + + return intValue; + } + + setInt(key: string, value: number): void { + localStorage.setItem(key, Math.trunc(value).toString()); + } + emojis() { return this.getBool("settings.emojis", true); } @@ -174,6 +188,10 @@ export class UserSettings { ); } + territoryBorderMode(): number { + return this.getInt("settings.territoryBorderMode", 1); + } + cursorCostLabel() { const legacy = this.getBool("settings.ghostPricePill", true); return this.getBool("settings.cursorCostLabel", legacy);