Skip to content

Commit 7d5c42b

Browse files
committed
Add debug renderer for boat areas as a setting (writing the same boilerplate for the 7th time is enough)
This also adds a new setting "type", which will be used more in the future
1 parent a26f63f commit 7d5c42b

File tree

8 files changed

+204
-0
lines changed

8 files changed

+204
-0
lines changed

src/renderer/GameRenderer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {backgroundLayer} from "./layer/BackgroundLayer";
55
import {territoryRenderer} from "./layer/TerritoryRenderer";
66
import {nameRenderer} from "./layer/NameRenderer";
77
import {boatRenderer} from "./layer/BoatRenderer";
8+
import {debugRenderer} from "./layer/debug/DebugRenderer";
89
import {gameStartRegistry} from "../game/Game";
910

1011
/**
@@ -42,6 +43,7 @@ export class GameRenderer {
4243
this.registerLayer(territoryRenderer);
4344
this.registerLayer(nameRenderer);
4445
this.registerLayer(boatRenderer);
46+
this.registerLayer(debugRenderer);
4547
}
4648

4749
/**
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {areaCalculator} from "../../../map/area/AreaCalculator";
2+
import {DebugRendererLayer} from "./DebugRendererRegistry";
3+
import {mapNavigationHandler} from "../../../game/action/MapNavigationHandler";
4+
5+
export class BoatMeshDebugRenderer implements DebugRendererLayer {
6+
readonly useCache = false;
7+
8+
render(context: CanvasRenderingContext2D): void {
9+
context.strokeStyle = "red";
10+
context.fillStyle = "orange";
11+
for (const nodes of areaCalculator.nodeIndex) {
12+
for (const node of nodes) {
13+
context.beginPath();
14+
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);
15+
context.fill();
16+
}
17+
}
18+
for (const nodes of areaCalculator.nodeIndex) {
19+
for (const node of nodes) {
20+
for (const neighbor of node.edges) {
21+
if (neighbor.node.x < node.x || (neighbor.node.x === node.x && neighbor.node.y < node.y)) continue; // Only draw each edge once
22+
context.beginPath();
23+
context.moveTo((node.x + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.x, (node.y + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.y);
24+
context.lineTo((neighbor.node.x + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.x, (neighbor.node.y + 0.5) * mapNavigationHandler.zoom + mapNavigationHandler.y);
25+
context.stroke();
26+
}
27+
}
28+
}
29+
}
30+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {CachedLayer} from "../CachedLayer";
2+
import {mapTransformHandler} from "../../../event/MapTransformHandler";
3+
import {gameMap} from "../../../game/GameData";
4+
import {registerSettingListener} from "../../../util/UserSettingManager";
5+
import {RendererLayer} from "../RendererLayer";
6+
import {DebugRendererLayer} from "./DebugRendererRegistry";
7+
import {gameStartRegistry} from "../../../game/Game";
8+
9+
/**
10+
* Debug renderer.
11+
* Renders debug information on the map if toggled.
12+
* @internal
13+
*/
14+
class DebugRenderer extends CachedLayer {
15+
private readonly mapLayers: RendererLayer[] = [];
16+
private readonly liveLayers: RendererLayer[] = [];
17+
18+
/**
19+
* Updates the layers to be rendered by the debug renderer.
20+
* @param layers layers to be rendered
21+
*/
22+
updateLayers(layers: DebugRendererLayer[]): void {
23+
this.mapLayers.length = 0;
24+
this.liveLayers.length = 0;
25+
for (const layer of layers) {
26+
if (layer.useCache) {
27+
this.mapLayers.push(layer);
28+
} else {
29+
this.liveLayers.push(layer);
30+
}
31+
}
32+
}
33+
34+
render(context: CanvasRenderingContext2D) {
35+
super.render(context);
36+
this.liveLayers.forEach(layer => layer.render(context));
37+
}
38+
39+
init(): void {
40+
this.resizeCanvas(gameMap.width, gameMap.height);
41+
this.mapLayers.forEach(layer => layer.render(this.context));
42+
}
43+
44+
onMapMove(this: void, x: number, y: number): void {
45+
debugRenderer.dx = x;
46+
debugRenderer.dy = y;
47+
}
48+
49+
onMapScale(this: void, scale: number): void {
50+
debugRenderer.scale = scale;
51+
}
52+
}
53+
54+
export const debugRenderer = new DebugRenderer();
55+
56+
mapTransformHandler.scale.register(debugRenderer.onMapScale);
57+
mapTransformHandler.move.register(debugRenderer.onMapMove);
58+
gameStartRegistry.register(debugRenderer.init.bind(debugRenderer));
59+
60+
registerSettingListener("debug-renderer", value => debugRenderer.updateLayers(value.getEnabledOptions()), true);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {MultiSelectSetting} from "../../../util/MultiSelectSetting";
2+
import {BoatMeshDebugRenderer} from "./BoatMeshDebugRenderer";
3+
import {RendererLayer} from "../RendererLayer";
4+
5+
export const debugRendererLayers = MultiSelectSetting.init()
6+
.option("boat-navigation-mesh", new BoatMeshDebugRenderer(), "Boat Navigation Mesh", false);
7+
8+
export interface DebugRendererLayer extends Omit<RendererLayer, "invalidateCaches"> {
9+
useCache: boolean;
10+
}

src/util/ManagedSetting.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export interface ManagedSetting {
2+
/**
3+
* Returns a string representation of this settings value.
4+
*/
5+
toString(): string;
6+
7+
/**
8+
* Parses a string to set the value of this setting.
9+
*/
10+
fromString(value: string): void;
11+
}

src/util/MultiSelectSetting.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {InvalidArgumentException} from "./Exceptions";
2+
import {ManagedSetting} from "./ManagedSetting";
3+
4+
export class MultiSelectSetting<T extends Record<string, Option<unknown>>> implements ManagedSetting {
5+
private options: T = {} as T;
6+
7+
static init() {
8+
return new MultiSelectSetting<{}>();
9+
}
10+
11+
/**
12+
* Registers a new option with the given key and value.
13+
* @param key The key of the option. Must be unique, not contain ',' and shouldn't change in the future
14+
* @param value The value of the option
15+
* @param label The label of the option, displayed in the UI
16+
* @param defaultStatus The default status of the option
17+
* @throws InvalidArgumentException if the key contains ','
18+
*/
19+
option<K extends string, V>(key: K & Exclude<K, keyof T>, value: V, label: string, defaultStatus: boolean) {
20+
if (key.includes(",")) throw new InvalidArgumentException("Key cannot contain ','");
21+
(this.options as unknown as Record<K, Option<V>>)[key] = {value, label, status: defaultStatus};
22+
return this as unknown as MultiSelectSetting<T & Record<K, Option<V>>>;
23+
}
24+
25+
/**
26+
* Checks if the option with the given key is selected.
27+
* @param key The key of the option
28+
* @returns true if the option is selected, false otherwise
29+
*/
30+
isSelected(key: keyof T) {
31+
return this.options[key].status;
32+
}
33+
34+
/**
35+
* Selects the option with the given key.
36+
* @param key The key of the option
37+
* @param status The status to set
38+
*/
39+
select(key: keyof T, status: boolean) {
40+
this.options[key].status = status;
41+
}
42+
43+
/**
44+
* Returns the enabled options.
45+
* @returns An array of the values of the enabled options
46+
*/
47+
getEnabledOptions(): AnyValue<T>[] {
48+
return Object.keys(this.options).filter(key => this.options[key].status).map(key => this.options[key].value);
49+
}
50+
51+
/**
52+
* Returns all options.
53+
* @returns An array of the values of all options
54+
*/
55+
getAllOptions(): AnyValue<T>[] {
56+
return Object.keys(this.options).map(key => this.options[key].value);
57+
}
58+
59+
toString() {
60+
return Object.keys(this.options).filter(key => this.options[key].status).join(",");
61+
}
62+
63+
fromString(value: string) {
64+
const selected = value.split(",");
65+
for (const key in this.options) {
66+
this.options[key].status = selected.includes(key);
67+
}
68+
return this;
69+
}
70+
}
71+
72+
type Option<T> = {
73+
value: T;
74+
label: string;
75+
status: boolean;
76+
}
77+
78+
type AnyValue<T extends Record<string, Option<unknown>>> = {
79+
[K in keyof T]: T[K]["value"];
80+
}[keyof T];

src/util/SettingRegistry.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {EventHandlerRegistry} from "../event/EventHandlerRegistry";
22
import {InvalidArgumentException, UnsupportedDataException} from "./Exceptions";
3+
import {ManagedSetting} from "./ManagedSetting";
34

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

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

115+
registerManaged<K extends string, S extends ManagedSetting>(key: K & Exclude<K, keyof T>, setting: S, version: number = 0) {
116+
return this.registerUpdatable<K, S>(key, setting, value => {
117+
setting.fromString(value);
118+
return setting;
119+
}, version);
120+
}
121+
113122
/**
114123
* Register an updater for a setting
115124
* @param key the key of the setting

src/util/UserSettingManager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {SettingRegistry} from "./SettingRegistry";
22
import {getTheme} from "../renderer/GameTheme";
3+
import {debugRendererLayers} from "../renderer/layer/debug/DebugRendererRegistry";
34

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

1416
registry.load();
1517

0 commit comments

Comments
 (0)