Skip to content

Commit

Permalink
Add debug renderer for boat areas as a setting (writing the same boil…
Browse files Browse the repository at this point in the history
…erplate for the 7th time is enough)

This also adds a new setting "type", which will be used more in the future
  • Loading branch information
platz1de committed Oct 15, 2024
1 parent a26f63f commit 7d5c42b
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/renderer/GameRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {backgroundLayer} from "./layer/BackgroundLayer";
import {territoryRenderer} from "./layer/TerritoryRenderer";
import {nameRenderer} from "./layer/NameRenderer";
import {boatRenderer} from "./layer/BoatRenderer";
import {debugRenderer} from "./layer/debug/DebugRenderer";
import {gameStartRegistry} from "../game/Game";

/**
Expand Down Expand Up @@ -42,6 +43,7 @@ export class GameRenderer {
this.registerLayer(territoryRenderer);
this.registerLayer(nameRenderer);
this.registerLayer(boatRenderer);
this.registerLayer(debugRenderer);
}

/**
Expand Down
30 changes: 30 additions & 0 deletions src/renderer/layer/debug/BoatMeshDebugRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {areaCalculator} from "../../../map/area/AreaCalculator";
import {DebugRendererLayer} from "./DebugRendererRegistry";
import {mapNavigationHandler} from "../../../game/action/MapNavigationHandler";

export class BoatMeshDebugRenderer implements DebugRendererLayer {
readonly useCache = false;

render(context: CanvasRenderingContext2D): void {
context.strokeStyle = "red";
context.fillStyle = "orange";
for (const nodes of areaCalculator.nodeIndex) {
for (const node of nodes) {
context.beginPath();
context.arc((node.x + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.x, (node.y + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.y, mapNavigationHandler.zoom / 2, 0, 2 * Math.PI);
context.fill();
}
}
for (const nodes of areaCalculator.nodeIndex) {
for (const node of nodes) {
for (const neighbor of node.edges) {
if (neighbor.node.x < node.x || (neighbor.node.x === node.x && neighbor.node.y < node.y)) continue; // Only draw each edge once
context.beginPath();
context.moveTo((node.x + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.x, (node.y + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.y);
context.lineTo((neighbor.node.x + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.x, (neighbor.node.y + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.y);
context.stroke();
}
}
}
}
}
60 changes: 60 additions & 0 deletions src/renderer/layer/debug/DebugRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {CachedLayer} from "../CachedLayer";
import {mapTransformHandler} from "../../../event/MapTransformHandler";
import {gameMap} from "../../../game/GameData";
import {registerSettingListener} from "../../../util/UserSettingManager";
import {RendererLayer} from "../RendererLayer";
import {DebugRendererLayer} from "./DebugRendererRegistry";
import {gameStartRegistry} from "../../../game/Game";

/**
* Debug renderer.
* Renders debug information on the map if toggled.
* @internal
*/
class DebugRenderer extends CachedLayer {
private readonly mapLayers: RendererLayer[] = [];
private readonly liveLayers: RendererLayer[] = [];

/**
* Updates the layers to be rendered by the debug renderer.
* @param layers layers to be rendered
*/
updateLayers(layers: DebugRendererLayer[]): void {
this.mapLayers.length = 0;
this.liveLayers.length = 0;
for (const layer of layers) {
if (layer.useCache) {
this.mapLayers.push(layer);
} else {
this.liveLayers.push(layer);
}
}
}

render(context: CanvasRenderingContext2D) {
super.render(context);
this.liveLayers.forEach(layer => layer.render(context));
}

init(): void {
this.resizeCanvas(gameMap.width, gameMap.height);
this.mapLayers.forEach(layer => layer.render(this.context));
}

onMapMove(this: void, x: number, y: number): void {
debugRenderer.dx = x;
debugRenderer.dy = y;
}

onMapScale(this: void, scale: number): void {
debugRenderer.scale = scale;
}
}

export const debugRenderer = new DebugRenderer();

mapTransformHandler.scale.register(debugRenderer.onMapScale);
mapTransformHandler.move.register(debugRenderer.onMapMove);
gameStartRegistry.register(debugRenderer.init.bind(debugRenderer));

registerSettingListener("debug-renderer", value => debugRenderer.updateLayers(value.getEnabledOptions()), true);
10 changes: 10 additions & 0 deletions src/renderer/layer/debug/DebugRendererRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {MultiSelectSetting} from "../../../util/MultiSelectSetting";
import {BoatMeshDebugRenderer} from "./BoatMeshDebugRenderer";
import {RendererLayer} from "../RendererLayer";

export const debugRendererLayers = MultiSelectSetting.init()
.option("boat-navigation-mesh", new BoatMeshDebugRenderer(), "Boat Navigation Mesh", false);

export interface DebugRendererLayer extends Omit<RendererLayer, "invalidateCaches"> {
useCache: boolean;
}
11 changes: 11 additions & 0 deletions src/util/ManagedSetting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ManagedSetting {
/**
* Returns a string representation of this settings value.
*/
toString(): string;

/**
* Parses a string to set the value of this setting.
*/
fromString(value: string): void;
}
80 changes: 80 additions & 0 deletions src/util/MultiSelectSetting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {InvalidArgumentException} from "./Exceptions";
import {ManagedSetting} from "./ManagedSetting";

export class MultiSelectSetting<T extends Record<string, Option<unknown>>> implements ManagedSetting {
private options: T = {} as T;

static init() {
return new MultiSelectSetting<{}>();
}

/**
* Registers a new option with the given key and value.
* @param key The key of the option. Must be unique, not contain ',' and shouldn't change in the future
* @param value The value of the option
* @param label The label of the option, displayed in the UI
* @param defaultStatus The default status of the option
* @throws InvalidArgumentException if the key contains ','
*/
option<K extends string, V>(key: K & Exclude<K, keyof T>, value: V, label: string, defaultStatus: boolean) {
if (key.includes(",")) throw new InvalidArgumentException("Key cannot contain ','");
(this.options as unknown as Record<K, Option<V>>)[key] = {value, label, status: defaultStatus};
return this as unknown as MultiSelectSetting<T & Record<K, Option<V>>>;
}

/**
* Checks if the option with the given key is selected.
* @param key The key of the option
* @returns true if the option is selected, false otherwise
*/
isSelected(key: keyof T) {
return this.options[key].status;
}

/**
* Selects the option with the given key.
* @param key The key of the option
* @param status The status to set
*/
select(key: keyof T, status: boolean) {
this.options[key].status = status;
}

/**
* Returns the enabled options.
* @returns An array of the values of the enabled options
*/
getEnabledOptions(): AnyValue<T>[] {
return Object.keys(this.options).filter(key => this.options[key].status).map(key => this.options[key].value);
}

/**
* Returns all options.
* @returns An array of the values of all options
*/
getAllOptions(): AnyValue<T>[] {
return Object.keys(this.options).map(key => this.options[key].value);
}

toString() {
return Object.keys(this.options).filter(key => this.options[key].status).join(",");
}

fromString(value: string) {
const selected = value.split(",");
for (const key in this.options) {
this.options[key].status = selected.includes(key);
}
return this;
}
}

type Option<T> = {
value: T;
label: string;
status: boolean;
}

type AnyValue<T extends Record<string, Option<unknown>>> = {
[K in keyof T]: T[K]["value"];
}[keyof T];
9 changes: 9 additions & 0 deletions src/util/SettingRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {EventHandlerRegistry} from "../event/EventHandlerRegistry";
import {InvalidArgumentException, UnsupportedDataException} from "./Exceptions";
import {ManagedSetting} from "./ManagedSetting";

/**
* Important Note: For types to work correctly, all register calls must be chained together.
Expand Down Expand Up @@ -94,6 +95,7 @@ export class SettingRegistry<T extends Record<string, Setting<unknown>>> {
return value;
}

//TODO: Turn all of these into managed settings, so we can differentiate between the different types
registerString<K extends string>(key: K & Exclude<K, keyof T>, defaultValue: string, version: number = 0) {
return this.registerUpdatable<K, string>(key, defaultValue, String, version);
}
Expand All @@ -110,6 +112,13 @@ export class SettingRegistry<T extends Record<string, Setting<unknown>>> {
return this.registerUpdatable<K, boolean>(key, defaultValue, value => value === "true", version);
}

registerManaged<K extends string, S extends ManagedSetting>(key: K & Exclude<K, keyof T>, setting: S, version: number = 0) {
return this.registerUpdatable<K, S>(key, setting, value => {
setting.fromString(value);
return setting;
}, version);
}

/**
* Register an updater for a setting
* @param key the key of the setting
Expand Down
2 changes: 2 additions & 0 deletions src/util/UserSettingManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {SettingRegistry} from "./SettingRegistry";
import {getTheme} from "../renderer/GameTheme";
import {debugRendererLayers} from "../renderer/layer/debug/DebugRendererRegistry";

/**
* Setting registry, all register calls need to be chained together
Expand All @@ -10,6 +11,7 @@ const registry = SettingRegistry.init("wf")
.registerBoolean("hud-clock", true)
.registerString("api-location", "https://warfront.io/api") //This needs to enforce no trailing slash, no query parameters and a protocol
.registerString("game-server", "warfront.io")
.registerManaged("debug-renderer", debugRendererLayers);

registry.load();

Expand Down

0 comments on commit 7d5c42b

Please sign in to comment.