mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:40:43 +00:00
border test 9000
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
></setting-toggle>
|
||||
|
||||
<!-- 😊 Emojis -->
|
||||
<setting-select
|
||||
label="${translateText("user_setting.territory_border_mode_label")}"
|
||||
description="${translateText(
|
||||
"user_setting.territory_border_mode_desc",
|
||||
)}"
|
||||
id="territory-border-mode-select"
|
||||
.value=${String(this.userSettings.territoryBorderMode())}
|
||||
.options=${[
|
||||
{ value: 0, label: "Off" },
|
||||
{ value: 1, label: "Simple" },
|
||||
{ value: 2, label: "Glow" },
|
||||
]}
|
||||
@change=${this.changeTerritoryBorderMode}
|
||||
></setting-select>
|
||||
|
||||
<setting-toggle
|
||||
label="${translateText("user_setting.emojis_label")}"
|
||||
description="${translateText("user_setting.emojis_desc")}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
type SelectOption = {
|
||||
export type SettingSelectOption = {
|
||||
value: number | string;
|
||||
label: string;
|
||||
};
|
||||
@@ -10,8 +10,10 @@ type SelectOption = {
|
||||
export class SettingSelect extends LitElement {
|
||||
@property() label = "Setting";
|
||||
@property() description = "";
|
||||
@property({ type: Array }) options: SelectOption[] = [];
|
||||
@property() id = "setting-select-input";
|
||||
@property({ type: Array }) options: SettingSelectOption[] = [];
|
||||
@property({ type: String }) value = "";
|
||||
@property({ type: Boolean }) easter = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
@@ -35,14 +37,18 @@ export class SettingSelect extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const rainbowClass = this.easter
|
||||
? "bg-[linear-gradient(270deg,#990033,#996600,#336600,#008080,#1c3f99,#5e0099,#990033)] bg-[length:1400%_1400%] animate-rainbow-bg text-white hover:bg-[linear-gradient(270deg,#990033,#996600,#336600,#008080,#1c3f99,#5e0099,#990033)]"
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="flex flex-col w-full p-4 bg-white/5 border border-white/10 rounded-xl hover:bg-white/10 transition-all gap-3"
|
||||
class="flex flex-col w-full p-4 bg-white/5 border border-white/10 rounded-xl hover:bg-white/10 transition-all gap-3 ${rainbowClass}"
|
||||
>
|
||||
<div class="flex flex-col min-w-0">
|
||||
<label
|
||||
class="text-white font-bold text-base block mb-1"
|
||||
for="setting-select-input"
|
||||
for=${this.id}
|
||||
>${this.label}</label
|
||||
>
|
||||
<div class="text-white/50 text-sm leading-snug">
|
||||
@@ -51,7 +57,7 @@ export class SettingSelect extends LitElement {
|
||||
</div>
|
||||
<div class="relative w-full">
|
||||
<select
|
||||
id="setting-select-input"
|
||||
id=${this.id}
|
||||
class="w-full appearance-none py-2 pl-3 pr-9 border border-white/20 rounded-lg bg-black/40 text-white font-mono text-sm focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all"
|
||||
.value=${String(this.value)}
|
||||
@change=${this.handleChange}
|
||||
|
||||
@@ -149,6 +149,17 @@ export class SettingsModal extends LitElement implements Layer {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onTerritoryBorderModeChange(event: Event) {
|
||||
const value = (event.target as HTMLSelectElement).value;
|
||||
const mode = Number.parseInt(value, 10);
|
||||
if (!Number.isFinite(mode))
|
||||
throw new Error(`Invalid border mode: ${value}`);
|
||||
|
||||
this.userSettings.setInt("settings.territoryBorderMode", mode);
|
||||
this.eventBus.emit(new RefreshGraphicsEvent());
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onToggleRandomNameModeButtonClick() {
|
||||
this.userSettings.toggleRandomName();
|
||||
this.requestUpdate();
|
||||
@@ -299,6 +310,34 @@ export class SettingsModal extends LitElement implements Layer {
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
>
|
||||
<img
|
||||
src=${treeIcon}
|
||||
alt="territoryBorderMode"
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">
|
||||
${translateText("user_setting.territory_border_mode_label")}
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${translateText("user_setting.territory_border_mode_desc")}
|
||||
</div>
|
||||
</div>
|
||||
<select
|
||||
class="shrink-0 bg-slate-900 border border-slate-600 text-white/90 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-600"
|
||||
.value=${String(this.userSettings.territoryBorderMode())}
|
||||
@change=${this.onTerritoryBorderModeChange}
|
||||
>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Simple</option>
|
||||
<option value="2">Glow</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
@click="${this.onToggleEmojisButtonClick}"
|
||||
|
||||
@@ -27,6 +27,7 @@ export class TerritoryLayer implements Layer {
|
||||
|
||||
private lastPaletteSignature: string | null = null;
|
||||
private lastDefensePostsSignature: string | null = null;
|
||||
private lastBorderMode: number | null = null;
|
||||
|
||||
private lastMousePosition: { x: number; y: number } | null = null;
|
||||
private hoveredOwnerSmallId: number | null = null;
|
||||
@@ -68,6 +69,7 @@ export class TerritoryLayer implements Layer {
|
||||
|
||||
this.refreshPaletteIfNeeded();
|
||||
this.refreshDefensePostsIfNeeded();
|
||||
this.refreshBorderModeIfNeeded();
|
||||
|
||||
const updatedTiles = this.game.recentlyUpdatedTiles();
|
||||
for (let i = 0; i < updatedTiles.length; i++) {
|
||||
@@ -98,6 +100,8 @@ export class TerritoryLayer implements Layer {
|
||||
this.territoryRenderer = renderer;
|
||||
this.territoryRenderer.setAlternativeView(this.alternativeView);
|
||||
this.territoryRenderer.setHighlightedOwnerId(this.hoveredOwnerSmallId);
|
||||
this.lastBorderMode = this.userSettings.territoryBorderMode();
|
||||
this.territoryRenderer.setBorderMode(this.lastBorderMode);
|
||||
this.territoryRenderer.markAllDirty();
|
||||
this.territoryRenderer.refreshPalette();
|
||||
this.lastPaletteSignature = this.computePaletteSignature();
|
||||
@@ -282,6 +286,17 @@ export class TerritoryLayer implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private refreshBorderModeIfNeeded() {
|
||||
if (!this.territoryRenderer) {
|
||||
return;
|
||||
}
|
||||
const mode = this.userSettings.territoryBorderMode();
|
||||
if (mode !== this.lastBorderMode) {
|
||||
this.lastBorderMode = mode;
|
||||
this.territoryRenderer.setBorderMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
private computeDefensePostsSignature(): string {
|
||||
// Active + completed posts only.
|
||||
const parts: string[] = [];
|
||||
|
||||
@@ -227,6 +227,13 @@ export class TerritoryRenderer {
|
||||
this.resources.setHighlightedOwnerId(ownerSmallId);
|
||||
}
|
||||
|
||||
setBorderMode(mode: number): void {
|
||||
if (!this.resources) {
|
||||
return;
|
||||
}
|
||||
this.resources.setBorderMode(mode);
|
||||
}
|
||||
|
||||
markTile(tile: TileRef): void {
|
||||
if (this.stateUpdatePass) {
|
||||
this.stateUpdatePass.markTile(tile);
|
||||
|
||||
@@ -78,6 +78,7 @@ export class GroundTruthData {
|
||||
private viewOffsetY = 0;
|
||||
private alternativeView = false;
|
||||
private highlightedOwnerId = -1;
|
||||
private borderMode = 1;
|
||||
|
||||
private constructor(
|
||||
private readonly device: GPUDevice,
|
||||
@@ -223,6 +224,10 @@ export class GroundTruthData {
|
||||
this.highlightedOwnerId = ownerSmallId ?? -1;
|
||||
}
|
||||
|
||||
setBorderMode(mode: number): void {
|
||||
this.borderMode = Math.max(0, Math.min(2, Math.trunc(mode)));
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Upload methods
|
||||
// =====================
|
||||
@@ -608,9 +613,11 @@ export class GroundTruthData {
|
||||
}
|
||||
|
||||
private ensureDefensePostsByOwnerBuffer(capacityPosts: number): void {
|
||||
const requested = Math.max(1, capacityPosts);
|
||||
if (
|
||||
this.defensePostsByOwnerBuffer &&
|
||||
capacityPosts <= this.defensePostsByOwnerCapacity
|
||||
requested <= this.defensePostsByOwnerCapacity &&
|
||||
this.defensePostsByOwnerStaging
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -621,7 +628,7 @@ export class GroundTruthData {
|
||||
|
||||
this.defensePostsByOwnerCapacity = Math.max(
|
||||
8,
|
||||
Math.pow(2, Math.ceil(Math.log2(Math.max(1, capacityPosts)))),
|
||||
Math.pow(2, Math.ceil(Math.log2(requested))),
|
||||
);
|
||||
|
||||
const bytesPerPost = 8; // 2 * u32 (x,y)
|
||||
@@ -846,7 +853,7 @@ export class GroundTruthData {
|
||||
this.uniformData[7] = this.highlightedOwnerId;
|
||||
this.uniformData[8] = this.viewWidth;
|
||||
this.uniformData[9] = this.viewHeight;
|
||||
this.uniformData[10] = 0;
|
||||
this.uniformData[10] = this.borderMode;
|
||||
this.uniformData[11] = 0;
|
||||
|
||||
this.device.queue.writeBuffer(this.uniformBuffer, 0, this.uniformData);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
struct Uniforms {
|
||||
mapResolution_viewScale_time: vec4f, // x=mapW, y=mapH, z=viewScale, w=timeSec
|
||||
viewOffset_alt_highlight: vec4f, // x=offX, y=offY, z=alternativeView, w=highlightOwnerId
|
||||
viewSize_pad: vec4f, // x=viewW, y=viewH, z/w unused
|
||||
viewSize_pad: vec4f, // x=viewW, y=viewH, z=borderMode, w unused
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user