diff --git a/ASSETS.md b/assets/ASSETS.md
similarity index 100%
rename from ASSETS.md
rename to assets/ASSETS.md
diff --git a/client/about.ts b/client/about.ts
new file mode 100644
index 0000000..13f595c
--- /dev/null
+++ b/client/about.ts
@@ -0,0 +1,97 @@
+import { marked } from "marked";
+import { AssetManager } from "./assets.ts";
+
+export async function open_about() {
+ const popup = self.open(
+ "",
+ "popupWindow",
+ "width=500,height=400",
+ );
+
+ if (!popup) {
+ alert("oh no");
+ return;
+ }
+
+ popup.document.body.innerHTML = await marked.parse(AssetManager.instance.get("bworld:assets_text"));
+ popup.document.head.innerHTML = ``;
+}
diff --git a/client/assets.ts b/client/assets.ts
index b04cbfd..40cb99b 100644
--- a/client/assets.ts
+++ b/client/assets.ts
@@ -1,4 +1,4 @@
-type Asset = HTMLImageElement | HTMLAudioElement;
+type Asset = HTMLImageElement | HTMLAudioElement | string;
export class AssetManager {
static instance = new AssetManager();
@@ -41,6 +41,12 @@ export class AssetManager {
audio.load();
this.assets[key] = audio;
this.loading_promises.push(promise);
+ } else if (src.endsWith(".txt") || src.endsWith(".md")) {
+ const promise = async () => {
+ const resp = await fetch(src);
+ this.assets[key] = await resp.text();
+ };
+ this.loading_promises.push(promise());
}
}
diff --git a/client/client_world.ts b/client/client_world.ts
index 7c87a3a..a140ae8 100644
--- a/client/client_world.ts
+++ b/client/client_world.ts
@@ -9,11 +9,17 @@ import { Tilemap } from "$/client/components/tilemap.ts";
import { AssetManager } from "$/client/assets.ts";
import { TileEditorSystem } from "$/client/systems/tile_editor_system.ts";
import { InventorySystem } from "$/client/systems/inventory_system.ts";
+import { UIInteractionSystem } from "$/client/systems/ui_interaction_system.ts";
+import { UIRenderSystem } from "$/client/systems/ui_render_system.ts";
+import { UIButton } from "./components/ui_components.ts";
+import { Position } from "../common/components/position.ts";
+import { open_about } from "./about.ts";
export class ClientWorld extends World {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
+ paused = false;
debugging = false;
constructor(canvas: HTMLCanvasElement) {
@@ -61,6 +67,19 @@ export class ClientWorld extends World {
const player = create_player();
this.add_entity(player);
+ // UI !
+ const unpause_button = new Entity("unpausebutton");
+ unpause_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 - 80));
+ unpause_button.add(new UIButton("Unpause", 320, 64, () => this.paused = false));
+ this.add_entity(unpause_button);
+
+ const about_button = new Entity("aboutbutton");
+ about_button.add(new Position(canvas.width / 2 - 150, canvas.height / 2 + 80));
+ about_button.add(new UIButton("About", 320, 64, () => open_about()));
+ this.add_entity(about_button);
+
+ // Logic systems
+ this.add_system(new UIInteractionSystem());
this.add_system(new InventorySystem());
this.add_system(new PlayerControlsSystem(player));
this.add_system(new MovementSystem());
@@ -69,5 +88,6 @@ export class ClientWorld extends World {
this.add_system(new RenderSystem(this.ctx));
this.add_system(new DebugSystem());
this.add_system(new TileEditorSystem());
+ this.add_system(new UIRenderSystem());
}
}
diff --git a/client/components/ui_components.ts b/client/components/ui_components.ts
new file mode 100644
index 0000000..807b1e4
--- /dev/null
+++ b/client/components/ui_components.ts
@@ -0,0 +1,18 @@
+import { Component } from "$/common/ecs/mod.ts";
+
+export class UIButton extends Component {
+ text: string;
+ width: number;
+ height: number;
+ on_click: () => void;
+
+ hovered = false;
+
+ constructor(text: string, width: number, height: number, on_click: () => void) {
+ super();
+ this.text = text;
+ this.width = width;
+ this.height = height;
+ this.on_click = on_click;
+ }
+}
diff --git a/client/main.ts b/client/main.ts
index 7d63f5b..6f005a4 100644
--- a/client/main.ts
+++ b/client/main.ts
@@ -61,6 +61,7 @@ if (!canvas) {
InputManager.initialize(canvas);
+AssetManager.instance.load("bworld:assets_text", "/assets/ASSETS.md");
AssetManager.instance.load("bworld:player", "/assets/sprites/player.png");
AssetManager.instance.load("bworld:roguelike", "/assets/sprites/roguelike.png");
AssetManager.instance.load("bworld:tiny_town", "/assets/sprites/tiny_town.png");
diff --git a/client/systems/inventory_system.ts b/client/systems/inventory_system.ts
index 8130038..ba70689 100644
--- a/client/systems/inventory_system.ts
+++ b/client/systems/inventory_system.ts
@@ -1,11 +1,16 @@
-import { System, World } from "$/common/ecs/mod.ts";
+import { System } from "$/common/ecs/mod.ts";
import { point_inside_rec } from "$/common/utils.ts";
import { SLOT_SIZE } from "$/common/constants.ts";
import { Container, PlayerInventory } from "$/client/components/inventory.ts";
import { InputManager } from "$/client/input_manager.ts";
+import { ClientWorld } from "$/client/client_world.ts";
export class InventorySystem extends System {
- update(world: World, _delta: number): void {
+ update(world: ClientWorld, _delta: number): void {
+ if (world.paused) {
+ return;
+ }
+
for (const entity of world.get_entities()) {
const player_inventory = entity.get(PlayerInventory);
diff --git a/client/systems/player_controls.ts b/client/systems/player_controls.ts
index 1c0b949..cda5f4f 100644
--- a/client/systems/player_controls.ts
+++ b/client/systems/player_controls.ts
@@ -17,6 +17,10 @@ export class PlayerControlsSystem extends System {
}
update(world: ClientWorld, _delta: number): void {
+ if (world.paused) {
+ return;
+ }
+
const velocity = this.player.get(Velocity)!;
const controls = this.player.get(PlayerControls)!;
diff --git a/client/systems/ui_interaction_system.ts b/client/systems/ui_interaction_system.ts
new file mode 100644
index 0000000..4ee71a7
--- /dev/null
+++ b/client/systems/ui_interaction_system.ts
@@ -0,0 +1,35 @@
+import { System } from "$/common/ecs/mod.ts";
+import { Position } from "$/common/components/position.ts";
+import { point_inside_rec } from "$/common/utils.ts";
+import { ClientWorld } from "$/client/client_world.ts";
+import { UIButton } from "$/client/components/ui_components.ts";
+import { InputManager } from "$/client/input_manager.ts";
+
+export class UIInteractionSystem implements System {
+ update(world: ClientWorld, _delta: number) {
+ // TODO: dont like
+ if (InputManager.is_key_pressed("Escape")) {
+ world.paused = !world.paused;
+ }
+
+ if (!world.paused) {
+ return;
+ }
+
+ const mouse = InputManager.get_mouse_position();
+
+ for (const entity of world.get_entities()) {
+ const position = entity.get(Position);
+ const button = entity.get(UIButton);
+
+ if (position && button) {
+ const hovered = point_inside_rec(mouse.x, mouse.y, position.x, position.y, button.width, button.height);
+ button.hovered = hovered;
+ if (hovered && InputManager.is_mouse_pressed(0)) {
+ InputManager.consume_mouse(0);
+ button.on_click();
+ }
+ }
+ }
+ }
+}
diff --git a/client/systems/ui_render_system.ts b/client/systems/ui_render_system.ts
new file mode 100644
index 0000000..a30d331
--- /dev/null
+++ b/client/systems/ui_render_system.ts
@@ -0,0 +1,67 @@
+import { System } from "$/common/ecs/mod.ts";
+import { Position } from "$/common/components/position.ts";
+import { ClientWorld } from "$/client/client_world.ts";
+import { UIButton } from "$/client/components/ui_components.ts";
+import { draw_text, measure_text } from "../text_rendering.ts";
+import { draw_nine_slice } from "./rendering/render_utils.ts";
+import { AssetManager } from "../assets.ts";
+
+const SCALE = 4;
+
+export class UIRenderSystem implements System {
+ constructor() {}
+
+ update(world: ClientWorld, _delta: number) {
+ if (!world.paused) {
+ return;
+ }
+
+ const ctx = world.ctx;
+
+ ctx.save();
+ ctx.resetTransform();
+
+ ctx.scale(SCALE, SCALE);
+
+ // dark background
+ ctx.fillStyle = "rgba(0,0,0,0.6)";
+ ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+
+ const ui = AssetManager.instance.get("bworld:ui");
+
+ for (const entity of world.get_entities()) {
+ const position = entity.get(Position);
+ const button = entity.get(UIButton);
+
+ if (position && button) {
+ draw_nine_slice(
+ ctx,
+ ui,
+ 16 * (button.hovered ? 14 : 11),
+ 16 * (button.hovered ? 1 : 0),
+ 16,
+ 16,
+ 3,
+ 3,
+ 3,
+ 3,
+ position.x / SCALE,
+ position.y / SCALE,
+ button.width / SCALE,
+ button.height / SCALE,
+ );
+ const measure = measure_text(ctx, button.text, 1.25);
+ draw_text(
+ ctx,
+ button.text,
+ (position.x / SCALE) + (button.width / SCALE / 2) - (measure / 2),
+ position.y / SCALE + (button.height / SCALE / 2),
+ 1.5,
+ "white",
+ "middle",
+ );
+ }
+ }
+ ctx.restore();
+ }
+}
diff --git a/deno.json b/deno.json
index f535d5a..9e2729c 100644
--- a/deno.json
+++ b/deno.json
@@ -15,6 +15,7 @@
},
"imports": {
"$/": "./",
- "@std/fs": "jsr:@std/fs@^1.0.23"
+ "@std/fs": "jsr:@std/fs@^1.0.23",
+ "marked": "npm:marked@^17.0.3"
}
}
diff --git a/deno.lock b/deno.lock
index 0ca6179..4e69697 100644
--- a/deno.lock
+++ b/deno.lock
@@ -3,7 +3,8 @@
"specifiers": {
"jsr:@std/fs@^1.0.23": "1.0.23",
"jsr:@std/internal@^1.0.12": "1.0.12",
- "jsr:@std/path@^1.1.4": "1.1.4"
+ "jsr:@std/path@^1.1.4": "1.1.4",
+ "npm:marked@^17.0.3": "17.0.3"
},
"jsr": {
"@std/fs@1.0.23": {
@@ -23,9 +24,16 @@
]
}
},
+ "npm": {
+ "marked@17.0.3": {
+ "integrity": "sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==",
+ "bin": true
+ }
+ },
"workspace": {
"dependencies": [
- "jsr:@std/fs@^1.0.23"
+ "jsr:@std/fs@^1.0.23",
+ "npm:marked@^17.0.3"
]
}
}