From fe885a9e2486c40a06d23db7cc941c8c7531c8bc Mon Sep 17 00:00:00 2001 From: paulaboks Date: Tue, 24 Feb 2026 15:36:10 -0300 Subject: [PATCH] Initial commit --- .gitignore | 1 + ASSETS.md | 52 +++ assets/fonts/m6x11.ttf | Bin 0 -> 13068 bytes assets/sprites/player.png | Bin 0 -> 3250 bytes assets/sprites/roguelike.png | Bin 0 -> 94579 bytes assets/sprites/tiny_town.png | Bin 0 -> 5042 bytes assets/sprites/ui.png | Bin 0 -> 3656 bytes build.ts | 40 ++ client/assets.ts | 59 +++ client/client_world.ts | 73 +++ client/components/camera.ts | 15 + client/components/inventory.ts | 121 +++++ client/components/player_controls.ts | 16 + client/components/sprite.ts | 86 ++++ client/components/tilemap.ts | 37 ++ client/debug_ui.ts | 542 +++++++++++++++++++++++ client/index.html | 36 ++ client/input_manager.ts | 200 +++++++++ client/main.ts | 76 ++++ client/player.ts | 39 ++ client/systems/debug_system.ts | 61 +++ client/systems/inventory_system.ts | 140 ++++++ client/systems/player_controls.ts | 84 ++++ client/systems/render_system.ts | 75 ++++ client/systems/rendering/player.ts | 62 +++ client/systems/rendering/render_utils.ts | 142 ++++++ client/systems/rendering/sprites.ts | 76 ++++ client/systems/rendering/tilemap.ts | 30 ++ client/systems/tile_editor_system.ts | 99 +++++ client/text_rendering.ts | 23 + common/components/position.ts | 12 + common/components/velocity.ts | 12 + common/constants.ts | 11 + common/ecs/component.ts | 3 + common/ecs/entity.ts | 28 ++ common/ecs/mod.ts | 4 + common/ecs/system.ts | 5 + common/ecs/world.ts | 34 ++ common/systems/movement_system.ts | 19 + common/utils.ts | 19 + deno.json | 20 + deno.lock | 31 ++ 42 files changed, 2383 insertions(+) create mode 100644 .gitignore create mode 100644 ASSETS.md create mode 100644 assets/fonts/m6x11.ttf create mode 100644 assets/sprites/player.png create mode 100644 assets/sprites/roguelike.png create mode 100644 assets/sprites/tiny_town.png create mode 100644 assets/sprites/ui.png create mode 100644 build.ts create mode 100644 client/assets.ts create mode 100644 client/client_world.ts create mode 100644 client/components/camera.ts create mode 100644 client/components/inventory.ts create mode 100644 client/components/player_controls.ts create mode 100644 client/components/sprite.ts create mode 100644 client/components/tilemap.ts create mode 100644 client/debug_ui.ts create mode 100644 client/index.html create mode 100644 client/input_manager.ts create mode 100644 client/main.ts create mode 100644 client/player.ts create mode 100644 client/systems/debug_system.ts create mode 100644 client/systems/inventory_system.ts create mode 100644 client/systems/player_controls.ts create mode 100644 client/systems/render_system.ts create mode 100644 client/systems/rendering/player.ts create mode 100644 client/systems/rendering/render_utils.ts create mode 100644 client/systems/rendering/sprites.ts create mode 100644 client/systems/rendering/tilemap.ts create mode 100644 client/systems/tile_editor_system.ts create mode 100644 client/text_rendering.ts create mode 100644 common/components/position.ts create mode 100644 common/components/velocity.ts create mode 100644 common/constants.ts create mode 100644 common/ecs/component.ts create mode 100644 common/ecs/entity.ts create mode 100644 common/ecs/mod.ts create mode 100644 common/ecs/system.ts create mode 100644 common/ecs/world.ts create mode 100644 common/systems/movement_system.ts create mode 100644 common/utils.ts create mode 100644 deno.json create mode 100644 deno.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/ASSETS.md b/ASSETS.md new file mode 100644 index 0000000..4d06527 --- /dev/null +++ b/ASSETS.md @@ -0,0 +1,52 @@ +shout out to kenney + +# Tiny town + +https://kenney.nl/assets/tiny-town + +license: [Creative Commons CC0](https://creativecommons.org/publicdomain/zero/1.0/) + +# Roguelike + +https://kenney.nl/assets/roguelike-rpg-pack + +license: [Creative Commons CC0](https://creativecommons.org/publicdomain/zero/1.0/) + +# UI + +https://kenney.nl/assets/ui-pack-pixel-adventure + +license: [Creative Commons CC0](https://creativecommons.org/publicdomain/zero/1.0/) + +# m6x11.ttf + +Font by Daniel Linssen (https://managore.itch.io) + +https://managore.itch.io/m6x11 (https://web.archive.org/web/20260217032856/https://managore.itch.io/m6x11) + +license: `free to use with attribution` + +# Tiny wonder farm + +https://butterymilk.itch.io/tiny-wonder-farm-asset-pack +(https://web.archive.org/web/20260204234306/https://butterymilk.itch.io/tiny-wonder-farm-asset-pack) + +file: assets/sprites/player.png + +license: + +``` +There's two versions, free and paid (premium): + +Free: It includes a limited number of items available for free. + +You are free to use sprites included in the pack in any of your non-commercial projects as well as edit the sprites and +add something new! + +Premium: It includes everything shown from the showcase, all tilemaps, sprites, characters and more! + +You are free to use sprites included in the pack in any of your commercial or non-commercial projects as well as edit +the sprites and add something new! + +You can't resell the sprites, even if changes were made. You cannot use it in any projects related to NFT." +``` diff --git a/assets/fonts/m6x11.ttf b/assets/fonts/m6x11.ttf new file mode 100644 index 0000000000000000000000000000000000000000..cb2122489c6d375e5c4ff4a99157e7245b067c83 GIT binary patch literal 13068 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 literal 0 HcmV?d00001 diff --git a/assets/sprites/player.png b/assets/sprites/player.png new file mode 100644 index 0000000000000000000000000000000000000000..ce81e5fa600da1ce563a13ed86b7d239a8f9b404 GIT binary patch literal 3250 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 literal 0 HcmV?d00001 diff --git a/assets/sprites/roguelike.png b/assets/sprites/roguelike.png new file mode 100644 index 0000000000000000000000000000000000000000..79b13326ceb8557a98b2b78aa5e8e7b9269f6693 GIT binary patch literal 94579 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 literal 0 HcmV?d00001 diff --git a/assets/sprites/tiny_town.png b/assets/sprites/tiny_town.png new file mode 100644 index 0000000000000000000000000000000000000000..1655d1dfc918dbd450b192d95d8831b6f6155d89 GIT binary patch literal 5042 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 literal 0 HcmV?d00001 diff --git a/assets/sprites/ui.png b/assets/sprites/ui.png new file mode 100644 index 0000000000000000000000000000000000000000..6c8f7d269c8dbca829d9b7e88fcbff302c450b33 GIT binary patch literal 3656 zcmV-O4!7}%P)L-Qwr-*3{_Gtc9Mke3GTEO-28<0R7wmfS9bX zyvVhK0PJdC@Z-i~uk(xDUud}hxVgFPYF__yX2w}het>|PnVCgWTZ@d0a=1?agmyb< zky3{^V|9h)<>e$REQzDIqQKc)qTr-(I{*Lw&t6lGKR9G|h2mygqi{O^@zQNeCwGmX z#fL{xUu%@F#y`;6x&QzG0d!JMQvg8b*k%9#4NXZzK~#9!?VXEa+PD&ib+#?t-rL<~ z3A7C)p&>5DAiw{^9Z4fuqmg9U#uuQSO@n^~CVZAhGb7o{SkLzO_*iOt1T!1(_?cL8 zhlOLl=8pMV8QL^Y5E*R`>>o9;$t}VA@$(+cJSKVPM`U?HHD4c)tvS`2Oj%<1j2{l1 z;o}i)_x8_Ai@)ql{Fkscx(0YX@Akltjulk%gJYa(b&hcPJeTK#AXthmNlQFia+YfM z_FtlBc*#5B-UzcuYjx(BrwQ*z!nZ^$GTe&mpIE(B5k0v=TFFhZ($aMOWJm;R-AE8>+_2cV1te}44}^>DwFVl!K<{($$1Fqkbg}+)3>Foq#Ct>kIX5?EXNT zPt75h$B9i^ck1BPpI>S5*I+Hw9&{M^OZ8I$x4aqk<3Zq}0Deyj;`gm4AYQ8<+rv!{ zJX7FKtwCw^=OC>PKpqEvc=;0baKxL@aWf2@lxO9H&wW1=5G{b`mkJ#7W{u+Tzy!nu zz%SLMIJ{Y-IQ(Sb7v)lV9yDFvtWg|Z16)vWfJ@!3Je#|~S?&Ut?V|(lF)U2*4u^%b z9FFSaw9DXT!h%;C088!dZ(Z6| zAEk6r<@d@!|E#QB^T`Lz1n>61QBWcSX{za@u39{WN6eDj35!Z=z!E+kgynjW%H-?o z_3&X~YL3z6{m_iJ6NXdZdII#b%2b5b1AzD?fs&h>Dy3dTuvqB=E;-ifzbdlfzaDc~{@v6-e?x`uT!wU@wcJsg0)hqu2s zz^w-0R(A!C)}yDiT?htGQB8O=>Q^EQ@F{Rq56|t&PmzQRZ7W|gDVKm>%kTfXG?}2I z8v$ny@PT`HaWp`yVwK!>v{azOz+b4J3b^IXs2`6^I5@yX_m-;-7hQU|r_T|%+dm8N zA4ja!0occZ?_az?dqBJy9XII}4ng3~g8b2d=a&i`^Jc*~+y{PtKRxhEbtw*S7LLQI zOL2YrmAjOl2ThkZi^SoL2TgIJqd`;F!||Z0f!7V1sd~7qua6GA$FQ&&?{fO1Peu!Hhc_^(v}zaUL14rt)GFN-2pbbWmZ{XVYO-xfewcWRm!lJyNo*--%hzrohVByZ% ztV>yk7V2cX{eGux9)b>}m5JA71UDSl0CJ5Rj&lIH#{E^8k9@O*M?+|{g(Gf>xgBJ) zg(1VXgSPE3#MpMwb->@f`xrR@0Eq~%@t{7a|^uCwB@#D?hxmi zw%pdt9pc|KZMm(PBjQ`KvCaNdmYD{l&CBloRd#3EjW+G8f0V6EyU`BOdf;#0e)M=N z;+VH2j(98Ln71M>f$JWwp*yBXn|1@dP`K@%qmUOaZ=emPkaL$e&<0b;ztsVkpO1lq zw!mc&ih+Z+z-7>ifrAbK4FMP3TWB~YeF8VovCVY@2CjI!r=UYz@pjuC;)=K1?hqdz z_{X>J-Z{hxaM5gD&^K_}!vnwrydB~J-VX5qZ4}f9W?><8sxg3Hb!v}t_gGlyWd47EpH{r0X*})WG~JK$2I6tv zKs?U-f{33|o{bc$&^@I5^J#G4XW+w615VlDLAtI?=pTzwfS*B>a*9ilomtL@nNBSq zW!$fIwrgIV-Hii$ia5N#K~vnW>OGeex?HSY;J$8Ko*iM(oFLN-p3NP@LOC-mqe7S; z_H^6w>~8{mx62|2Aes2Q=O*zHo4#&ap1o4wq(ghaD+iWJ?d3IouoUXO?9zIZ2I_<1wNBq+TopEDRm*mtAT;b9XiAbaP)b}9^O0fYb?+Dz?~jH zJ@9Lrfbd+3>(jAI5rAhF_>&C^!B`XE?aH&)IK?*ZpxLfGd!3WWV}^zEdDF0PKA&uv z#Mr=DJnuG%&#YtOqlkk=!$M;j8)YHTWv2GWhN-CiVMDf`^CX+w!(A=UZFk1U z>Rr%%R1Vyo2im!xx?CO(%Vpf9KzrP!K>Hw_Jk`TeJv4u?t;SZjk#4i{*EfKh4OVV7f*GtLt z;7p=Q&n?GG(e(AJ_yr>;q7Ija;p;M8t?mYSz!xL6(?#*-=H?CFzpHPxg|x=4Z-#p` zxZ$cwJ>_NBr5p_Ib(M|?;3g5Wq^&vpbuC4QCnWJH>>0&FiBCmbiQ+otFnA&6D z_*S#NhWpE6pzU;VBfk&^?rI5`0RJGYKj00m0v;(|;dL;f;uS|JPpEixPQX8Gy&ir$ z8StUYv*!T(_Jh~MrvyIi1jJbYAJm7p0zUi{TL3s6G$~LzNY=Oez{#K~yR-J$TKDV2 zTMe310H zQJ0N(aOmRk4h~&B-oa59A37}L0Gyw20BV8D+4{`A@r=BgOsBiY`~jYHDPpepZ!yQl z|1QlGo6us0P3v9O!X|PlX9m1}(1aO!F=wnpf2vVEJZV!6nr8((85YW(ELK}>RL}M` zs)r|Ss<3cu;3V;ZP5neF@sYk;FK*kIl~+iwTBr6?BtD}9N3|1ES~10yRBGQ~ZQ%u^ z_TvMuY9|_aRUpT}XX=LS71dM58SJVJ3d^N$4!BJ{6TYyN#vgE;U4h%dib1Z=_UJ?4eOr_O0000 = {}; + loading_promises: Promise[] = []; + + constructor() {} + + load(key: string, src: string) { + if (this.assets[key]) { + return; + } + + if (src.endsWith(".png")) { + const img = new Image(); + const promise = new Promise((resolve, reject) => { + img.onload = () => resolve(); + img.onerror = () => reject(new Error(`Failed to load image: ${src}`)); + }); + img.src = src; + this.assets[key] = img; + this.loading_promises.push(promise); + } else if (src.endsWith(".ogg")) { + const audio = new Audio(); + const promise = new Promise((resolve, reject) => { + audio.addEventListener( + "canplaythrough", + () => resolve(), + { once: true }, + ); + audio.addEventListener( + "error", + () => reject(new Error(`Failed to load audio: ${src}`)), + { once: true }, + ); + }); + + audio.src = src; + audio.load(); + this.assets[key] = audio; + this.loading_promises.push(promise); + } + } + + async load_all() { + await Promise.all(this.loading_promises); + this.loading_promises = []; + } + + get(key: string): T { + const asset = this.assets[key]; + if (!asset) { + throw new Error(`Asset not found: ${key}`); + } + return asset as T; + } +} diff --git a/client/client_world.ts b/client/client_world.ts new file mode 100644 index 0000000..7c87a3a --- /dev/null +++ b/client/client_world.ts @@ -0,0 +1,73 @@ +import { Entity, World } from "$/common/ecs/mod.ts"; +import { MovementSystem } from "$/common/systems/movement_system.ts"; +import { RenderSystem } from "$/client/systems/render_system.ts"; +import { PlayerControlsSystem } from "$/client/systems/player_controls.ts"; +import { DebugUI } from "$/client/debug_ui.ts"; +import { DebugSystem } from "$/client/systems/debug_system.ts"; +import { create_player } from "$/client/player.ts"; +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"; + +export class ClientWorld extends World { + canvas: HTMLCanvasElement; + ctx: CanvasRenderingContext2D; + + debugging = false; + + constructor(canvas: HTMLCanvasElement) { + super(); + + this.canvas = canvas; + const ctx = canvas.getContext("2d"); + if (!ctx) { + throw new Error("Could not get CanvasRenderingContext2D"); + } + this.ctx = ctx; + ctx.imageSmoothingEnabled = false; + + const resize = () => { + canvas.width = self.innerWidth; + canvas.height = self.innerHeight; + + canvas.style.width = canvas.width + "px"; + canvas.style.height = canvas.height + "px"; + + this.ctx.imageSmoothingEnabled = false; + }; + + self.addEventListener("resize", resize); + resize(); + + canvas.addEventListener("contextmenu", function (event) { + event.preventDefault(); + }); + + DebugUI.initialize(this.ctx); + + const tilemap = new Entity("backgroundtiles"); + tilemap.add( + new Tilemap( + AssetManager.instance.get("bworld:roguelike"), + 16, + [], + 2, + 1, + ), + ); + this.add_entity(tilemap); + + const player = create_player(); + this.add_entity(player); + + this.add_system(new InventorySystem()); + this.add_system(new PlayerControlsSystem(player)); + this.add_system(new MovementSystem()); + + // render systems + this.add_system(new RenderSystem(this.ctx)); + this.add_system(new DebugSystem()); + this.add_system(new TileEditorSystem()); + } +} diff --git a/client/components/camera.ts b/client/components/camera.ts new file mode 100644 index 0000000..362f401 --- /dev/null +++ b/client/components/camera.ts @@ -0,0 +1,15 @@ +import { Component } from "$/common/ecs/mod.ts"; + +export class Camera extends Component { + active = true; + x: number; + y: number; + zoom: number; + + constructor(x = 0, y = 0, zoom = 1) { + super(); + this.x = x; + this.y = y; + this.zoom = zoom; + } +} diff --git a/client/components/inventory.ts b/client/components/inventory.ts new file mode 100644 index 0000000..c2073cf --- /dev/null +++ b/client/components/inventory.ts @@ -0,0 +1,121 @@ +import { Component } from "$/common/ecs/mod.ts"; +import { SLOT_SIZE } from "$/common/constants.ts"; + +export class ItemStack { + type_id: string; + amount: number; + max_amount: number; + + constructor(type_id: string | string, amount: number = 1, max_amount: number = 64) { + this.type_id = type_id; + this.amount = amount; + this.max_amount = max_amount; + } + + clone(): ItemStack { + return new ItemStack(this.type_id, this.amount, this.max_amount); + } +} + +export class ContainerSlot { + #item_stack: ItemStack | undefined; + + has_item() { + return this.#item_stack !== undefined; + } + + set_item(item_stack: ItemStack | undefined) { + this.#item_stack = item_stack; + } + + get_item() { + return this.#item_stack; + } + + get type_id() { + return this.#item_stack?.type_id; + } + + get amount() { + return this.#item_stack?.amount; + } + + get max_amount() { + return this.#item_stack?.max_amount; + } +} + +export class Container { + #slots: ContainerSlot[] = []; + readonly size: number; + + constructor(size: number) { + this.size = size; + for (let i = 0; i < size; i += 1) { + this.#slots.push(new ContainerSlot()); + } + } + + add_item(item_stack: ItemStack) { + // TODO: merge stacks + for (const slot of this.#slots) { + if (!slot.has_item()) { + slot.set_item(item_stack); + return; + } + } + // TODO: drop item on ground + console.error("Failed to place item in container"); + } + + get_item(slot: number): ItemStack | undefined { + return this.#slots[slot].get_item(); + } + + get_slot(slot: number): ContainerSlot { + return this.#slots[slot]; + } +} + +export interface ContainerLayout { + slots: { type: string; x: number; y: number }[]; + offset_x: number; + offset_y: number; +} + +export class Inventory extends Component { + container: Container; + + is_open: boolean = false; + layout: ContainerLayout; + + hovering_slot: number = -1; + + constructor(container: Container, layout: ContainerLayout) { + super(); + this.container = container; + this.layout = layout; + } +} + +export class PlayerInventory extends Inventory { + owner_id: string; + holding_item: ItemStack | undefined; + + constructor(owner_id: string) { + const container = new Container(9 * 4); + const layout: ContainerLayout = { + slots: [], + offset_x: 10, + offset_y: 10, + }; + + for (let row = 0; row < 4; row += 1) { + for (let column = 0; column < 9; column += 1) { + layout.slots.push({ type: "*", x: column * SLOT_SIZE, y: row * SLOT_SIZE }); + } + } + super(container, layout); + this.owner_id = owner_id; + } +} diff --git a/client/components/player_controls.ts b/client/components/player_controls.ts new file mode 100644 index 0000000..d660034 --- /dev/null +++ b/client/components/player_controls.ts @@ -0,0 +1,16 @@ +import { Component } from "$/common/ecs/mod.ts"; +import { KeyCode } from "$/client/input_manager.ts"; + +export class PlayerControls extends Component { + move_speed: number = 100; + + // Keys + move_up: KeyCode = "KeyW"; + move_down: KeyCode = "KeyS"; + move_left: KeyCode = "KeyA"; + move_right: KeyCode = "KeyD"; + + open_inventory: KeyCode = "KeyE"; + + open_debug: KeyCode = "F3"; +} diff --git a/client/components/sprite.ts b/client/components/sprite.ts new file mode 100644 index 0000000..38910fe --- /dev/null +++ b/client/components/sprite.ts @@ -0,0 +1,86 @@ +import { Component } from "$/common/ecs/mod.ts"; +import { AssetManager } from "$/client/assets.ts"; + +export class Sprite extends Component { + image: HTMLImageElement; + width: number; + height: number; + source_x: number; + source_y: number; + source_width: number; + source_height: number; + flip_x = false; + flip_y = false; + + constructor( + image: HTMLImageElement | string, + width: number, + height: number, + source_x = 0, + source_y = 0, + source_width = width, + source_height = height, + ) { + super(); + if (typeof image === "string") { + this.image = AssetManager.instance.get(image); + } else { + this.image = image; + } + this.width = width; + this.height = height; + this.source_x = source_x; + this.source_y = source_y; + this.source_width = source_width; + this.source_height = source_height; + } +} + +interface AnimatedSpritePiece { + source_x: number[]; + source_y: number[]; + source_width: number; + source_height: number; + duration: number; +} + +export class AnimatedSprite extends Component { + image: HTMLImageElement; + width: number; + height: number; + flip_x = false; + flip_y = false; + + current_state: string; + states: Record; + timer = 0; + animation_frame = 0; + + constructor( + image: HTMLImageElement | string, + width: number, + height: number, + states: Record, + initial_state: string, + ) { + super(); + if (typeof image === "string") { + this.image = AssetManager.instance.get(image); + } else { + this.image = image; + } + this.width = width; + this.height = height; + + this.states = states; + this.current_state = initial_state; + } + + set_state(state: string) { + if (this.current_state !== state) { + this.current_state = state; + this.timer = 0; + this.animation_frame = 0; + } + } +} diff --git a/client/components/tilemap.ts b/client/components/tilemap.ts new file mode 100644 index 0000000..3826007 --- /dev/null +++ b/client/components/tilemap.ts @@ -0,0 +1,37 @@ +import { Component } from "$/common/ecs/mod.ts"; + +interface Tile { + x: number; + y: number; + index: number; +} + +export class Tilemap extends Component { + image: HTMLImageElement; + tile_size: number; + rows: number; + columns: number; + tiles: Tile[]; + scale: number; + margin: number; + + selected_tile = 0; + editing = false; + + constructor( + image: HTMLImageElement, + tile_size: number, + tiles: Tile[], + scale: number = 1, + margin: number = 0, + ) { + super(); + this.image = image; + this.tile_size = tile_size; + this.columns = Math.floor((image.width + margin) / (tile_size + margin)); + this.rows = Math.floor((image.height + margin) / (tile_size + margin)); + this.tiles = tiles; + this.scale = scale; + this.margin = margin; + } +} diff --git a/client/debug_ui.ts b/client/debug_ui.ts new file mode 100644 index 0000000..dfbaf74 --- /dev/null +++ b/client/debug_ui.ts @@ -0,0 +1,542 @@ +import { point_inside_rec } from "$/common/utils.ts"; +import { InputManager } from "$/client/input_manager.ts"; +import { draw_text, measure_text } from "$/client/text_rendering.ts"; + +const HEADER_HEIGHT = 24; +const RESIZE_SIZE = 12; + +const PADDING = 4; + +interface WindowState { + x: number; + y: number; + width: number; + height: number; + dragging: boolean; + resizing: boolean; + scroll_y: number; + content_height: number; +} + +interface TextInputState { + value: string; + caret: number; + focused: boolean; + last_blink: number; + show_caret: boolean; + key_repeat_timer: number; +} + +export class DebugUI { + static ctx: CanvasRenderingContext2D; + + static windows = new Map(); + static current_window: WindowState | undefined = undefined; + + static text_inputs = new Map(); + static active_text_input: string | undefined = undefined; + + static open_sections = new Map(); + + // cursor as in like for drawing + static cursor_x = 0; + static cursor_y = 0; + static content_width = 0; + + static initialize(ctx: CanvasRenderingContext2D) { + this.ctx = ctx; + } + + static begin(title: string, x: number, y: number, width = 300, height = 300) { + let win = this.windows.get(title); + if (!win) { + win = { + x, + y, + width, + height, + dragging: false, + resizing: false, + scroll_y: 0, + content_height: 0, + }; + this.windows.set(title, win); + } + + this.current_window = win; + + // header + const mouse = InputManager.get_mouse_position(); + const delta = InputManager.get_mouse_delta(); + + const hovering_header = point_inside_rec(mouse.x, mouse.y, win.x, win.y, win.width, HEADER_HEIGHT); + + if (hovering_header && InputManager.is_mouse_pressed(0)) { + InputManager.consume_mouse(0); + win.dragging = true; + } + + if (!InputManager.is_mouse_down(0)) { + win.dragging = false; + win.resizing = false; + } + + if (win.dragging) { + win.x += delta.x; + win.y += delta.y; + } + + // resize square + const hovering_resize = point_inside_rec( + mouse.x, + mouse.y, + win.x + win.width - RESIZE_SIZE, + win.y + win.height - RESIZE_SIZE, + RESIZE_SIZE, + RESIZE_SIZE, + ); + + if (hovering_resize && InputManager.is_mouse_pressed(0)) { + InputManager.consume_mouse(0); + win.resizing = true; + } + + if (win.resizing) { + win.width += delta.x; + win.height += delta.y; + + win.width = Math.max(150, win.width); + win.height = Math.max(120, win.height); + } + + // scrolling + const wheel = -InputManager.get_wheel_delta(); + + const hovering_content = point_inside_rec(mouse.x, mouse.y, win.x, win.y, win.width, win.height); + + if (hovering_content) { + win.scroll_y -= wheel * 0.7; + } + + const max_scroll = Math.max(0, win.content_height - (win.height - HEADER_HEIGHT - 10)); + win.scroll_y = Math.max(0, Math.min(win.scroll_y, max_scroll)); + + // render + this.ctx.save(); + + // transparent background + this.ctx.fillStyle = "rgba(25,25,25,0.95)"; + this.ctx.fillRect(win.x, win.y, win.width, win.height); + + this.ctx.strokeStyle = "#aaa"; + this.ctx.strokeRect(win.x, win.y, win.width, win.height); + + // header and title + this.ctx.fillStyle = "#333"; + this.ctx.fillRect(win.x, win.y, win.width, HEADER_HEIGHT); + + draw_text(this.ctx, title, win.x + 8, win.y + 4, 2); + + // resize grip + this.ctx.fillStyle = hovering_resize ? "#888" : "#666"; + this.ctx.fillRect( + win.x + win.width - RESIZE_SIZE, + win.y + win.height - RESIZE_SIZE, + RESIZE_SIZE, + RESIZE_SIZE, + ); + + // clip it ! + this.ctx.beginPath(); + this.ctx.rect( + win.x, + win.y + HEADER_HEIGHT, + win.width, + win.height - HEADER_HEIGHT, + ); + this.ctx.clip(); + + // move cursor_x to match the windows x and add some padding + // same idea for cursor_y but with scrolling + this.cursor_x = win.x + PADDING * 2; + this.cursor_y = win.y + HEADER_HEIGHT + PADDING * 2 - win.scroll_y; + // 20 padding (to count for scrollbar) + this.content_width = win.width - 20; + + win.content_height = 0; + } + + static end() { + if (!this.current_window) { + return; + } + + // see how far we've come to save content height + this.current_window.content_height = this.cursor_y - this.current_window.y + this.current_window.scroll_y; + + this.#draw_scrollbar(); + + this.ctx.restore(); + + this.current_window = undefined; + } + + static advance(height: number) { + if (!this.current_window) { + return; + } + this.cursor_y += height; + // maybe dont reset on every advance? eh whatever + this.cursor_x = this.current_window.x + PADDING * 2; + } + + static separator() { + this.ctx.strokeStyle = "#555"; + this.ctx.beginPath(); + this.ctx.moveTo(this.cursor_x, this.cursor_y); + this.ctx.lineTo(this.cursor_x + this.content_width, this.cursor_y); + this.ctx.stroke(); + + this.cursor_y += PADDING * 2; + } + + static text(content: string) { + draw_text(this.ctx, content, this.cursor_x, this.cursor_y, 2); + this.advance(20); + } + + static button(label: string): boolean { + const height = 24; + const x = this.cursor_x; + const y = this.cursor_y; + const width = this.content_width; + + const mouse = InputManager.get_mouse_position(); + const hovered = point_inside_rec(mouse.x, mouse.y, x, y, width, height); + + this.ctx.fillStyle = hovered ? "#666" : "#444"; + this.ctx.fillRect(x, y, width, height); + + this.ctx.strokeStyle = "white"; + this.ctx.strokeRect(x, y, width, height); + + this.ctx.fillStyle = "white"; + draw_text(this.ctx, label, x + 6, y + 4, 2); + + this.advance(height + PADDING); + + if (hovered && InputManager.is_mouse_pressed(0)) { + InputManager.consume_mouse(0); + return true; + } + + return false; + } + + static checkbox(label: string, value: boolean): boolean { + // shoutout to booleans + const size = 16; + const x = this.cursor_x; + const y = this.cursor_y; + + const mouse = InputManager.get_mouse_position(); + const hovered = point_inside_rec(mouse.x, mouse.y, x, y, size, size); + + this.ctx.fillStyle = "#222"; + this.ctx.fillRect(x, y, size, size); + this.ctx.strokeStyle = "white"; + this.ctx.strokeRect(x, y, size, size); + + if (value) { + this.ctx.fillStyle = "white"; + this.ctx.fillRect(x + 4, y + 4, size - 8, size - 8); + } + + this.ctx.fillStyle = "white"; + draw_text(this.ctx, label, x + size + 6, y, 2); + + this.advance(size + PADDING); + + if (hovered && InputManager.is_mouse_pressed(0)) { + InputManager.consume_mouse(0); + return !value; + } + + return value; + } + + static slider_float( + label: string, + value: number, + min: number, + max: number, + ): number { + const height = 20; + const x = this.cursor_x; + const y = this.cursor_y; + const width = this.content_width; + + const mouse = InputManager.get_mouse_position(); + const hovered = point_inside_rec(mouse.x, mouse.y, x, y, width, height); + + if (hovered && InputManager.is_mouse_down(0)) { + const t = (mouse.x - x) / width; + value = min + (max - min) * Math.min(Math.max(t, 0), 1); + } + + this.ctx.fillStyle = "#333"; + this.ctx.fillRect(x, y, width, height); + + const percent = (value - min) / (max - min); + this.ctx.fillStyle = "#0a84ff"; + this.ctx.fillRect(x, y, width * percent, height); + + this.ctx.strokeStyle = "white"; + this.ctx.strokeRect(x, y, width, height); + + this.ctx.fillStyle = "white"; + draw_text(this.ctx, `${label}: ${value.toFixed(2)}`, x + 5, y + 2, 2); + + this.advance(height + PADDING); + + return value; + } + + static progress_bar(value: number) { + const height = 16; + const x = this.cursor_x; + const y = this.cursor_y; + const width = this.content_width; + + this.ctx.fillStyle = "#333"; + this.ctx.fillRect(x, y, width, height); + + this.ctx.fillStyle = "#366992"; + this.ctx.fillRect(x, y, width * value, height); + + this.ctx.strokeStyle = "white"; + this.ctx.strokeRect(x, y, width, height); + + this.advance(height + PADDING); + } + + static collapsing_header(label: string): boolean { + // i only did the ## for ids on this one cause its the only one that mattered + const open = this.open_sections.get(label) ?? false; + + const clicked = this.button((open ? "- " : "+ ") + label.replace(/##.*/, "")); + if (clicked) { + this.open_sections.set(label, !open); + return !open; + } + + return open; + } + + static combo( + label: string, + index: number, + options: string[], + ): number { + if (this.button(`${label}: ${options[index]}`)) { + index = (index + 1) % options.length; + } + return index; + } + + static text_input(label: string, value: string): string { + // yeah just use the text widget + this.text(label); + + const id = label; + + let state = this.text_inputs.get(id); + if (!state) { + // initial state + state = { + value, + caret: value.length, + focused: false, + last_blink: performance.now(), + show_caret: true, + key_repeat_timer: 0, + }; + this.text_inputs.set(id, state); + } + // update on every call + state.value = value; + + const height = 24; + const x = this.cursor_x; + const y = this.cursor_y; + const width = this.content_width; + + const mouse = InputManager.get_mouse_position(); + const hovered = point_inside_rec(mouse.x, mouse.y, x, y, width, height); + + // set focus and move caret to end + if (hovered && InputManager.is_mouse_pressed(0)) { + InputManager.consume_mouse(0); + this.active_text_input = id; + state.focused = true; + state.caret = value.length; + } else if (InputManager.is_mouse_pressed(0) && !hovered) { + if (this.active_text_input === id) { + state.focused = false; + this.active_text_input = undefined; + } + } + + const is_active = this.active_text_input === id; + + if (is_active) { + this.#handle_text_editing(state); + } + + // overkill? + if (is_active) { + const now = performance.now(); + if (now - state.last_blink > 500) { + state.show_caret = !state.show_caret; + state.last_blink = now; + } + } else { + state.show_caret = false; + } + + // draw box + this.ctx.fillStyle = is_active ? "#555" : "#333"; + this.ctx.fillRect(x, y, width, height); + + // highlight + this.ctx.strokeStyle = is_active ? "#0a84ff" : "#aaa"; + this.ctx.strokeRect(x, y, width, height); + + const text_x = x + 6; + + draw_text(this.ctx, state.value, x + 6, y + 4, 2); + + // caret ! + if (is_active && state.show_caret) { + const before_text = state.value.substring(0, state.caret); + const caret_x = text_x + measure_text(this.ctx, before_text, 2); + + this.ctx.strokeStyle = "white"; + this.ctx.beginPath(); + this.ctx.moveTo(caret_x, y + 4); + this.ctx.lineTo(caret_x, y + height - 4); + this.ctx.stroke(); + } + + this.cursor_y += height + PADDING; + + return state.value; + } + + static float_input(label: string, value: number) { + // TODO: do something better when input needs to be empty + const float_value = parseFloat(this.text_input(label, String(value))); + if (Number.isNaN(float_value)) { + return 0; + } + return float_value; + } + + static is_inside_windows(x: number, y: number) { + if (!this.current_window) { + return false; + } + return point_inside_rec( + x, + y, + this.current_window.x, + this.current_window.y, + this.current_window.width, + this.current_window.height, + ); + } + + static #draw_scrollbar() { + const win = this.current_window!; + const content_visible_height = win.height - HEADER_HEIGHT; + + if (win.content_height <= content_visible_height) { + return; + } + + const scrollbarWidth = 6; + const x = win.x + win.width - scrollbarWidth - 2; + const y = win.y + HEADER_HEIGHT; + const h = content_visible_height - 24; + + const ratio = content_visible_height / win.content_height; + const thumbHeight = h * ratio; + + const maxScroll = win.content_height - content_visible_height; + const thumbY = y + (win.scroll_y / maxScroll) * (h - thumbHeight); + + // track + this.ctx.fillStyle = "#222"; + this.ctx.fillRect(x, y, scrollbarWidth, h); + + // thumb + this.ctx.fillStyle = "#888"; + this.ctx.fillRect(x, thumbY, scrollbarWidth, thumbHeight); + } + + static #handle_text_editing(state: TextInputState) { + const now = performance.now(); + const repeat_delay = 400; + const repeat_rate = 40; + + function allow_repeat(): boolean { + if (InputManager.is_key_pressed("Backspace")) { + state.key_repeat_timer = now; + return true; + } + + if (InputManager.is_key_down("Backspace")) { + if (now - state.key_repeat_timer > repeat_delay) { + state.key_repeat_timer = now - (repeat_delay - repeat_rate); + return true; + } + } + + return false; + } + + if (allow_repeat()) { + if (state.caret > 0) { + state.value = state.value.slice(0, state.caret - 1) + state.value.slice(state.caret); + state.caret -= 1; + } + } + + if (InputManager.is_key_pressed("Delete")) { + state.value = state.value.slice(0, state.caret) + state.value.slice(state.caret + 1); + } + + if (InputManager.is_key_pressed("ArrowLeft")) { + state.caret = Math.max(0, state.caret - 1); + } + + if (InputManager.is_key_pressed("ArrowRight")) { + state.caret = Math.min(state.value.length, state.caret + 1); + } + + if (InputManager.is_key_pressed("Home")) { + state.caret = 0; + } + + if (InputManager.is_key_pressed("End")) { + state.caret = state.value.length; + } + + const typed = InputManager.get_typed_characters(); + + for (const char of typed) { + state.value = state.value.slice(0, state.caret) + char + state.value.slice(state.caret); + state.caret += 1; + } + } +} diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..d67a65b --- /dev/null +++ b/client/index.html @@ -0,0 +1,36 @@ + + + + + + bworld + + + + + + + diff --git a/client/input_manager.ts b/client/input_manager.ts new file mode 100644 index 0000000..adaf0cf --- /dev/null +++ b/client/input_manager.ts @@ -0,0 +1,200 @@ +export type KeyCode = + // letters + | "KeyA" + | "KeyB" + | "KeyC" + | "KeyD" + | "KeyE" + | "KeyF" + | "KeyG" + | "KeyH" + | "KeyI" + | "KeyJ" + | "KeyK" + | "KeyL" + | "KeyM" + | "KeyN" + | "KeyO" + | "KeyP" + | "KeyQ" + | "KeyR" + | "KeyS" + | "KeyT" + | "KeyU" + | "KeyV" + | "KeyW" + | "KeyX" + | "KeyY" + | "KeyZ" + // top numbers + | "Digit0" + | "Digit1" + | "Digit2" + | "Digit3" + | "Digit4" + | "Digit5" + | "Digit6" + | "Digit7" + | "Digit8" + | "Digit9" + // arrows + | "ArrowUp" + | "ArrowDown" + | "ArrowLeft" + | "ArrowRight" + // controls + | "Space" + | "Escape" + | "Enter" + | "Tab" + | "ShiftLeft" + | "ShiftRight" + | "ControlLeft" + | "ControlRight" + | "AltLeft" + | "AltRight" + | "Home" + | "End" + | "Backspace" + | "Delete" + // f + | "F1" + | "F2" + | "F3" + | "F4" + | "F5" + | "F6" + | "F7" + | "F8" + | "F9" + | "F10" + | "F11" + | "F12"; + +export class InputManager { + static keys_down = new Set(); + static keys_pressed = new Set(); + static keys_released = new Set(); + + static mouse_buttons_down = new Set(); + static mouse_buttons_pressed = new Set(); + static mouse_buttons_released = new Set(); + static mouse_buttons_consumed = new Set(); + + static mouse_x = 0; + static mouse_y = 0; + static mouse_delta_x = 0; + static mouse_delta_y = 0; + static wheel_delta = 0; + + static typed_characters = new Set(); + + static initialize(canvas: HTMLCanvasElement) { + self.addEventListener("keydown", (e) => { + e.preventDefault(); + if (!this.keys_down.has(e.code)) { + this.keys_pressed.add(e.code); + } + this.keys_down.add(e.code); + if (e.key.length === 1) { + this.typed_characters.add(e.key); + } + }); + + self.addEventListener("keyup", (e) => { + e.preventDefault(); + this.keys_down.delete(e.code); + this.keys_released.add(e.code); + }); + + self.addEventListener("mousedown", (e) => { + e.preventDefault(); + if (!this.mouse_buttons_down.has(e.button)) { + this.mouse_buttons_pressed.add(e.button); + } + this.mouse_buttons_down.add(e.button); + }); + + self.addEventListener("mouseup", (e) => { + e.preventDefault(); + this.mouse_buttons_down.delete(e.button); + this.mouse_buttons_released.add(e.button); + }); + + self.addEventListener("mousemove", (e) => { + e.preventDefault(); + const rect = canvas.getBoundingClientRect(); + const new_x = e.clientX - rect.left; + const new_y = e.clientY - rect.top; + + this.mouse_delta_x += new_x - this.mouse_x; + this.mouse_delta_y += new_y - this.mouse_y; + + this.mouse_x = new_x; + this.mouse_y = new_y; + }); + + self.addEventListener("wheel", (e) => { + this.wheel_delta += e.deltaY; + }); + } + + static is_key_down(code: KeyCode): boolean { + return this.keys_down.has(code); + } + + static is_key_pressed(code: KeyCode): boolean { + return this.keys_pressed.has(code); + } + + static is_key_released(code: KeyCode): boolean { + return this.keys_released.has(code); + } + + static is_mouse_down(button: number): boolean { + return this.mouse_buttons_down.has(button); + } + + static is_mouse_pressed(button: number): boolean { + return this.mouse_buttons_pressed.has(button) && + !this.mouse_buttons_consumed.has(button); + } + + static is_mouse_released(button: number): boolean { + return this.mouse_buttons_released.has(button); + } + + static get_mouse_position() { + return { x: this.mouse_x, y: this.mouse_y }; + } + + static get_mouse_delta() { + return { x: this.mouse_delta_x, y: this.mouse_delta_y }; + } + + static get_wheel_delta(): number { + return this.wheel_delta; + } + + static consume_mouse(button: number) { + this.mouse_buttons_consumed.add(button); + } + + static get_typed_characters(): string[] { + const chars = [...this.typed_characters]; + this.typed_characters.clear(); + return chars; + } + + static update() { + this.keys_pressed.clear(); + this.keys_released.clear(); + this.mouse_buttons_pressed.clear(); + this.mouse_buttons_released.clear(); + this.mouse_delta_x = 0; + this.mouse_delta_y = 0; + this.wheel_delta = 0; + this.typed_characters.clear(); + this.mouse_buttons_consumed.clear(); + } +} diff --git a/client/main.ts b/client/main.ts new file mode 100644 index 0000000..7d63f5b --- /dev/null +++ b/client/main.ts @@ -0,0 +1,76 @@ +import { AssetManager } from "./assets.ts"; +import { ClientWorld } from "./client_world.ts"; +import { InputManager } from "./input_manager.ts"; + +export class ClientLoop { + running = false; + last_time = 0; + world: ClientWorld; + + frame_count = 0; + last_fps_time = 0; + + constructor(world: ClientWorld) { + this.world = world; + } + + start() { + this.running = true; + const now = performance.now(); + this.last_time = now; + this.frame_count = 0; + this.last_fps_time = this.last_time; + requestAnimationFrame((time) => this.loop(time)); + } + + stop() { + this.running = false; + } + + loop(time: number) { + if (!this.running) { + return; + } + + const delta = (time - this.last_time) / 1000; + + const ctx = this.world.ctx; + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + this.world.update(delta); + + this.frame_count += 1; + const now = performance.now(); + if (now - this.last_fps_time >= 1000) { + const fps = (this.frame_count * 1000) / (now - this.last_fps_time); + console.log(`FPS: ${fps.toFixed(2)}`); + this.frame_count = 0; + this.last_fps_time = now; + } + + InputManager.update(); + + this.last_time = time; + requestAnimationFrame((time) => this.loop(time)); + } +} + +const canvas = document.getElementById("game") as HTMLCanvasElement; +if (!canvas) { + throw Error("Canvas was not found"); +} + +InputManager.initialize(canvas); + +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"); +AssetManager.instance.load("bworld:ui", "/assets/sprites/ui.png"); + +await AssetManager.instance.load_all(); + +const client_world = new ClientWorld(canvas); + +const loop = new ClientLoop(client_world); +loop.start(); + +console.log("Game started"); diff --git a/client/player.ts b/client/player.ts new file mode 100644 index 0000000..139bf07 --- /dev/null +++ b/client/player.ts @@ -0,0 +1,39 @@ +import { Position } from "$/common/components/position.ts"; +import { Velocity } from "$/common/components/velocity.ts"; +import { Entity } from "$/common/ecs/mod.ts"; +import { Camera } from "$/client/components/camera.ts"; +import { ItemStack, PlayerInventory } from "$/client/components/inventory.ts"; +import { PlayerControls } from "$/client/components/player_controls.ts"; +import { AnimatedSprite } from "$/client/components/sprite.ts"; + +export function create_player() { + const player = new Entity("player"); + player.add(new Position(10, 10)); + player.add(new Velocity(0, 0)); + player.add( + new AnimatedSprite("bworld:player", 72, 72, { + "idle": { + source_x: [0, 24], + source_y: [0, 0], + source_width: 24, + source_height: 24, + duration: 60, + }, + "running": { + source_x: [0, 24, 48, 72, 96, 120, 144, 168], + source_y: [24, 24, 24, 24, 24, 24, 24, 24], + source_width: 24, + source_height: 24, + duration: 20, + }, + }, "idle"), + ); + player.add(new PlayerControls()); + // TODO: actual player ids + player.add(new PlayerInventory(player.id)); + player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:pickaxe", 1, 1)); + player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:bomb", 5)); + player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:bomb", 64)); + player.add(new Camera(-100, -100)); + return player; +} diff --git a/client/systems/debug_system.ts b/client/systems/debug_system.ts new file mode 100644 index 0000000..eba9964 --- /dev/null +++ b/client/systems/debug_system.ts @@ -0,0 +1,61 @@ +import { System } from "$/common/ecs/mod.ts"; +import { ClientWorld } from "$/client/client_world.ts"; +import { DebugUI } from "$/client/debug_ui.ts"; + +export class DebugSystem extends System { + constructor() { + super(); + } + + update(world: ClientWorld, _delta: number): void { + if (!world.debugging) { + return; + } + + DebugUI.ctx.save(); + DebugUI.ctx.resetTransform(); + + DebugUI.begin("Entities", 10, 10, 300); + + for (const entity of world.get_entities()) { + if (DebugUI.collapsing_header("Entity - " + entity.id)) { + for (const component of entity.get_all()) { + if (DebugUI.collapsing_header(`${component.constructor.name}##${entity.id}`)) { + this.render_component(component); + } + } + } + } + + DebugUI.end(); + DebugUI.ctx.restore(); + } + + // deno-lint-ignore no-explicit-any + render_component(component: any) { + for (const key in component) { + if (key === "__component") { + continue; + } + if (typeof component[key] === "number") { + component[key] = DebugUI.float_input( + key, + component[key], + ); + } else if (typeof component[key] === "string") { + component[key] = DebugUI.text_input( + key, + component[key], + ); + } else if (typeof component[key] === "boolean") { + component[key] = DebugUI.checkbox( + key, + component[key], + ); + } else { + DebugUI.text(`${key}: ${JSON.stringify(component[key])}`); + } + DebugUI.separator(); + } + } +} diff --git a/client/systems/inventory_system.ts b/client/systems/inventory_system.ts new file mode 100644 index 0000000..8130038 --- /dev/null +++ b/client/systems/inventory_system.ts @@ -0,0 +1,140 @@ +import { System, World } 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"; + +export class InventorySystem extends System { + update(world: World, _delta: number): void { + for (const entity of world.get_entities()) { + const player_inventory = entity.get(PlayerInventory); + + if (player_inventory) { + player_inventory.hovering_slot = -1; + const container = player_inventory.container; + if (player_inventory.is_open) { + for (const [index, slot] of player_inventory.layout.slots.entries()) { + const mouse = InputManager.get_mouse_position(); + const slot_x = slot.x + player_inventory.layout.offset_x; + const slot_y = slot.y + player_inventory.layout.offset_y; + const hovering = point_inside_rec(mouse.x, mouse.y, slot_x, slot_y, SLOT_SIZE, SLOT_SIZE); + if (hovering) { + player_inventory.hovering_slot = index; + + if (InputManager.is_mouse_pressed(0)) { + InputManager.consume_mouse(0); + this.handle_left_click(player_inventory, container, index); + return; + } + + if (InputManager.is_mouse_pressed(2)) { + InputManager.consume_mouse(2); + this.handle_right_click(player_inventory, container, index); + return; + } + } + } + } else { + if (player_inventory.holding_item) { + container.add_item(player_inventory.holding_item); + player_inventory.holding_item = undefined; + } + } + + if (player_inventory.holding_item?.amount === 0) { + player_inventory.holding_item = undefined; + } + } + } + } + + switch_item_with_holding(player_inventory: PlayerInventory, index: number) { + const container_slot = player_inventory.container.get_slot(index); + const original_holding_item = player_inventory.holding_item; + player_inventory.holding_item = container_slot.get_item(); + container_slot.set_item(original_holding_item); + } + + handle_left_click(player_inventory: PlayerInventory, container: Container, index: number) { + const holding = player_inventory.holding_item; + const slot = container.get_slot(index); + const slot_item = slot.get_item(); + + // if you aren't holding anything + // "swap" with nothing on your hand (pick it up) + if (!holding) { + this.switch_item_with_holding(player_inventory, index); + return; + } + + // if you are holding something and slot type equals holding type + // try to add to stack + if (slot_item && slot.type_id === holding.type_id) { + const space_left = slot.max_amount! - slot_item.amount!; + const amount_to_add = Math.min(space_left, holding.amount); + + slot_item.amount += amount_to_add; + holding.amount -= amount_to_add; + + return; + } + + // if something on hand but not the same + // swap + this.switch_item_with_holding(player_inventory, index); + } + + handle_right_click(player_inventory: PlayerInventory, container: Container, index: number) { + const holding = player_inventory.holding_item; + const slot = container.get_slot(index); + const slot_item = slot.get_item(); + + // if holding something + if (holding) { + // and slot type equals holding type + // add 1 to matching stack + if (slot_item && slot.type_id === holding.type_id) { + if (slot_item.amount < slot_item.max_amount!) { + slot_item.amount += 1; + holding.amount -= 1; + } + return; + } + + // (actually the same as the last one but creates a new one techinically) + // place 1 into empty slot + if (!slot_item) { + const new_item = holding.clone(); + new_item.amount = 1; + slot.set_item(new_item); + holding.amount -= 1; + return; + } + + // if something on hand but not the same + // swap + this.switch_item_with_holding(player_inventory, index); + return; + } + + // if player is holding nothing and clicks nothing, nothing happens + if (!slot_item) { + return; + } + + // pick up half of the stack + const original_amount = slot_item.amount; + const half = Math.floor(original_amount / 2); + + slot_item.amount = half; + + const picked_up = slot_item.clone(); + picked_up.amount = original_amount - half; + + player_inventory.holding_item = picked_up; + + if (slot_item.amount === 0) { + slot.set_item(undefined); + } + } +} diff --git a/client/systems/player_controls.ts b/client/systems/player_controls.ts new file mode 100644 index 0000000..1c0b949 --- /dev/null +++ b/client/systems/player_controls.ts @@ -0,0 +1,84 @@ +import { Entity, System } from "$/common/ecs/mod.ts"; +import { Velocity } from "$/common/components/velocity.ts"; +import { InputManager } from "../input_manager.ts"; +import { AnimatedSprite } from "../components/sprite.ts"; +import { ClientWorld } from "../client_world.ts"; +import { PlayerControls } from "../components/player_controls.ts"; +import { PlayerInventory } from "../components/inventory.ts"; +import { Camera } from "../components/camera.ts"; +import { Position } from "../../common/components/position.ts"; + +export class PlayerControlsSystem extends System { + player: Entity; + + constructor(player: Entity) { + super(); + this.player = player; + } + + update(world: ClientWorld, _delta: number): void { + const velocity = this.player.get(Velocity)!; + const controls = this.player.get(PlayerControls)!; + + if (InputManager.is_key_down(controls.move_left)) { + velocity.vx = -controls.move_speed; + } else if (InputManager.is_key_down(controls.move_right)) { + velocity.vx = controls.move_speed; + } else { + velocity.vx = 0; + } + + if (InputManager.is_key_down(controls.move_up)) { + velocity.vy = -controls.move_speed; + } else if (InputManager.is_key_down(controls.move_down)) { + velocity.vy = controls.move_speed; + } else { + velocity.vy = 0; + } + + const animated_sprite = this.player.get(AnimatedSprite)!; + + if ( + InputManager.is_key_down(controls.move_up) || InputManager.is_key_down(controls.move_down) || + InputManager.is_key_down(controls.move_left) || InputManager.is_key_down(controls.move_right) + ) { + animated_sprite.set_state("running"); + } else { + animated_sprite.set_state("idle"); + } + + if (InputManager.is_key_down(controls.move_left)) { + animated_sprite.flip_x = false; + } else if (InputManager.is_key_down(controls.move_right)) { + animated_sprite.flip_x = true; + } + + if (InputManager.is_key_pressed(controls.open_inventory)) { + const inventory = this.player.get(PlayerInventory)!; + inventory.is_open = !inventory.is_open; + } + + if (InputManager.is_key_pressed(controls.open_debug)) { + world.debugging = !world.debugging; + } + + const position = this.player.get(Position)!; + const camera = this.player.get(Camera)!; + + camera.x = Math.round(position.x); + camera.y = Math.round(position.y); + + // --- APPLY BOUNDS --- + /*if (camera.bounds) { + camera.x = Math.max( + camera.bounds.min_x, + Math.min(camera.x, camera.bounds.max_x), + ); + + camera.y = Math.max( + camera.bounds.min_y, + Math.min(camera.y, camera.bounds.max_y), + ); + }*/ + } +} diff --git a/client/systems/render_system.ts b/client/systems/render_system.ts new file mode 100644 index 0000000..f650e17 --- /dev/null +++ b/client/systems/render_system.ts @@ -0,0 +1,75 @@ +import { System } from "$/common/ecs/mod.ts"; +import { World } from "$/common/ecs/world.ts"; +import { Position } from "$/common/components/position.ts"; +import { AnimatedSprite, Sprite } from "$/client/components/sprite.ts"; +import { Tilemap } from "$/client/components/tilemap.ts"; +import { PlayerInventory } from "$/client/components/inventory.ts"; +import { Camera } from "$/client/components/camera.ts"; + +import { render_animated_sprite, render_sprite } from "./rendering/sprites.ts"; +import { render_tilemap } from "./rendering/tilemap.ts"; +import { render_player_inventory } from "./rendering/player.ts"; + +export class RenderSystem extends System { + ctx: CanvasRenderingContext2D; + + constructor(ctx: CanvasRenderingContext2D) { + super(); + this.ctx = ctx; + } + + update(world: World, _delta: number): void { + this.ctx.save(); + this.ctx.setTransform(1, 0, 0, 1, 0, 0); + + const camera_entity = world.get_entities().values().find((e) => e.get(Camera)?.active); + const camera = camera_entity?.get(Camera); + + if (!camera) { + return; + } + + for (const entity of world.get_entities()) { + this.ctx.save(); + this.ctx.translate( + Math.floor(this.ctx.canvas.width / 2), + Math.floor(this.ctx.canvas.height / 2), + ); + + this.ctx.scale(camera.zoom, camera.zoom); + + this.ctx.translate( + -Math.floor(camera.x), + -Math.floor(camera.y), + ); + + const position = entity.get(Position); + const sprite = entity.get(Sprite); + + if (sprite && position) { + render_sprite(this.ctx, sprite, position); + } + + const animated_sprite = entity.get(AnimatedSprite); + + if (animated_sprite && position) { + render_animated_sprite(this.ctx, animated_sprite, position); + } + + const tilemap = entity.get(Tilemap); + + if (tilemap) { + render_tilemap(this.ctx, tilemap); + } + + // Ui + this.ctx.restore(); + + const player_inventory = entity.get(PlayerInventory); + + if (player_inventory && player_inventory.is_open) { + render_player_inventory(this.ctx, player_inventory); + } + } + } +} diff --git a/client/systems/rendering/player.ts b/client/systems/rendering/player.ts new file mode 100644 index 0000000..3bf166a --- /dev/null +++ b/client/systems/rendering/player.ts @@ -0,0 +1,62 @@ +import { SLOT_SIZE } from "$/common/constants.ts"; +import { AssetManager } from "$/client/assets.ts"; +import { PlayerInventory } from "$/client/components/inventory.ts"; +import { InputManager } from "$/client/input_manager.ts"; +import { draw_item, draw_nine_slice } from "./render_utils.ts"; + +export function render_player_inventory(ctx: CanvasRenderingContext2D, player_inventory: PlayerInventory) { + const PADDING = 10; + const ui = AssetManager.instance.get("bworld:ui"); + const layout = player_inventory.layout.slots; + + draw_nine_slice( + ctx, + ui, + 160, + 0, + 16, + 16, + 4, + 4, + 4, + 4, + 0, + 0, + PADDING * 2 + SLOT_SIZE * 9, + PADDING * 2 + SLOT_SIZE * 4, + ); + + for (const [index, slot] of layout.entries()) { + const x = slot.x + PADDING; + const y = slot.y + PADDING; + draw_nine_slice( + ctx, + ui, + player_inventory.hovering_slot === index ? 19 * 16 : 160 + 32, + player_inventory.hovering_slot === index ? 16 : 0, + 16, + 16, + 4, + 4, + 4, + 4, + x, + y, + SLOT_SIZE, + SLOT_SIZE, + ); + } + for (const [index, slot] of layout.entries()) { + const x = slot.x + PADDING; + const y = slot.y + PADDING; + const item = player_inventory.container.get_item(index); + if (item) { + draw_item(ctx, item, x, y); + } + } + + if (player_inventory.holding_item) { + const mouse = InputManager.get_mouse_position(); + draw_item(ctx, player_inventory.holding_item, mouse.x, mouse.y); + } +} diff --git a/client/systems/rendering/render_utils.ts b/client/systems/rendering/render_utils.ts new file mode 100644 index 0000000..d297885 --- /dev/null +++ b/client/systems/rendering/render_utils.ts @@ -0,0 +1,142 @@ +import { SLOT_SIZE } from "$/common/constants.ts"; +import { get_sprite_region } from "$/common/utils.ts"; +import { AssetManager } from "$/client/assets.ts"; +import { ItemStack } from "$/client/components/inventory.ts"; +import { draw_text } from "$/client/text_rendering.ts"; + +export function draw_nine_slice( + ctx: CanvasRenderingContext2D, + image: HTMLImageElement, + source_x: number, + source_y: number, + source_width: number, + source_height: number, + left: number, + right: number, + top: number, + bottom: number, + x: number, + y: number, + width: number, + height: number, +) { + const center_width = source_width - left - right; + const center_height = source_height - top - bottom; + + const dest_center_width = width - left - right; + const dest_center_height = height - top - bottom; + + // top left + ctx.drawImage(image, source_x, source_y, left, top, x, y, left, top); + + // top right + ctx.drawImage(image, source_x + source_width - right, source_y, right, top, x + width - right, y, right, top); + + // bottom left + ctx.drawImage( + image, + source_x, + source_y + source_height - bottom, + left, + bottom, + x, + y + height - bottom, + left, + bottom, + ); + + // bottom right + ctx.drawImage( + image, + source_x + source_width - right, + source_y + source_height - bottom, + right, + bottom, + x + width - right, + y + height - bottom, + right, + bottom, + ); + + // top + ctx.drawImage(image, source_x + left, source_y, center_width, top, x + left, y, dest_center_width, top); + + // bottom + ctx.drawImage( + image, + source_x + left, + source_y + source_height - bottom, + center_width, + bottom, + x + left, + y + height - bottom, + dest_center_width, + bottom, + ); + + // left + ctx.drawImage(image, source_x, source_y + top, left, center_height, x, y + top, left, dest_center_height); + + // right + ctx.drawImage( + image, + source_x + source_width - right, + source_y + top, + right, + center_height, + x + width - right, + y + top, + right, + dest_center_height, + ); + + // center ! + ctx.drawImage( + image, + source_x + left, + source_y + top, + center_width, + center_height, + x + left, + y + top, + dest_center_width, + dest_center_height, + ); +} + +export function draw_item(ctx: CanvasRenderingContext2D, item: ItemStack, x: number, y: number) { + const sprite_region = get_sprite_region(item.type_id); + ctx.drawImage( + AssetManager.instance.get("bworld:tiny_town"), + sprite_region.x * 16, + sprite_region.y * 16, + 16, + 16, + x + 3, + y + 3, + SLOT_SIZE - 6, + SLOT_SIZE - 6, + ); + if (item.max_amount !== 1) { + draw_text( + ctx, + String(item.amount), + x + SLOT_SIZE + 2 - 4, + y + SLOT_SIZE + 2 - 4, + 2, + "#3f3f3f", + "bottom", + "right", + ); + draw_text( + ctx, + String(item.amount), + x + SLOT_SIZE - 4, + y + SLOT_SIZE - 4, + 2, + "white", + "bottom", + "right", + ); + } +} diff --git a/client/systems/rendering/sprites.ts b/client/systems/rendering/sprites.ts new file mode 100644 index 0000000..3853018 --- /dev/null +++ b/client/systems/rendering/sprites.ts @@ -0,0 +1,76 @@ +import { Position } from "$/common/components/position.ts"; +import { AnimatedSprite, Sprite } from "$/client/components/sprite.ts"; + +export function render_sprite(ctx: CanvasRenderingContext2D, sprite: Sprite, position: Position) { + ctx.save(); + + ctx.translate( + Math.floor(sprite.flip_x ? position.x + sprite.width : position.x), + Math.floor(sprite.flip_y ? position.y + sprite.height : position.y), + ); + ctx.scale( + sprite.flip_x ? -1 : 1, + sprite.flip_y ? -1 : 1, + ); + + ctx.drawImage( + sprite.image, + sprite.source_x, + sprite.source_y, + sprite.source_width, + sprite.source_height, + 0, + 0, + sprite.width, + sprite.height, + ); + ctx.restore(); +} + +export function render_animated_sprite( + ctx: CanvasRenderingContext2D, + animated_sprite: AnimatedSprite, + position: Position, +) { + ctx.save(); + + const current_animation = animated_sprite.states[animated_sprite.current_state]; + if (!current_animation) { + console.error(`Missing animation for state ${animated_sprite.current_state}`); + return; + } + + ctx.translate( + Math.floor(animated_sprite.flip_x ? position.x + animated_sprite.width : position.x), + Math.floor(animated_sprite.flip_y ? position.y + animated_sprite.height : position.y), + ); + + ctx.scale( + animated_sprite.flip_x ? -1 : 1, + animated_sprite.flip_y ? -1 : 1, + ); + + ctx.drawImage( + animated_sprite.image, + current_animation.source_x[animated_sprite.animation_frame], + current_animation.source_y[animated_sprite.animation_frame], + current_animation.source_width, + current_animation.source_height, + 0, + 0, + animated_sprite.width, + animated_sprite.height, + ); + + animated_sprite.timer += 1; + if (animated_sprite.timer >= current_animation.duration) { + animated_sprite.timer = 0; + animated_sprite.animation_frame += 1; + + if (animated_sprite.animation_frame >= current_animation.source_x.length) { + animated_sprite.animation_frame = 0; + } + } + + ctx.restore(); +} diff --git a/client/systems/rendering/tilemap.ts b/client/systems/rendering/tilemap.ts new file mode 100644 index 0000000..6770f09 --- /dev/null +++ b/client/systems/rendering/tilemap.ts @@ -0,0 +1,30 @@ +import { Tilemap } from "$/client/components/tilemap.ts"; + +export function render_tilemap(ctx: CanvasRenderingContext2D, tilemap: Tilemap) { + ctx.save(); + ctx.translate( + -Math.floor(ctx.canvas.width / 2), + -Math.floor(ctx.canvas.height / 2), + ); + + for (const tile of tilemap.tiles) { + const tx = tile.index % tilemap.columns; + const ty = Math.floor(tile.index / tilemap.columns); + const sx = tx * tilemap.tile_size + (tx * tilemap.margin); + const sy = ty * tilemap.tile_size + (ty * tilemap.margin); + + ctx.drawImage( + tilemap.image, + sx, + sy, + tilemap.tile_size, + tilemap.tile_size, + tile.x * tilemap.tile_size * tilemap.scale, + tile.y * tilemap.tile_size * tilemap.scale, + tilemap.tile_size * tilemap.scale, + tilemap.tile_size * tilemap.scale, + ); + } + + ctx.restore(); +} diff --git a/client/systems/tile_editor_system.ts b/client/systems/tile_editor_system.ts new file mode 100644 index 0000000..f51f354 --- /dev/null +++ b/client/systems/tile_editor_system.ts @@ -0,0 +1,99 @@ +import { System } from "$/common/ecs/mod.ts"; +import { point_inside_rec } from "$/common/utils.ts"; +import { ClientWorld } from "$/client/client_world.ts"; +import { Camera } from "$/client/components/camera.ts"; +import { Tilemap } from "$/client/components/tilemap.ts"; +import { DebugUI } from "$/client/debug_ui.ts"; +import { InputManager } from "$/client/input_manager.ts"; + +export class TileEditorSystem extends System { + constructor() { + super(); + } + + update(world: ClientWorld, _delta: number): void { + if (!world.debugging) { + return; + } + + DebugUI.ctx.save(); + DebugUI.ctx.setTransform(1, 0, 0, 1, 0, 0); + + const ctx = world.ctx; + + const camera_entity = world.get_entities().values().find((e) => e.get(Camera)?.active); + const camera = camera_entity?.get(Camera); + if (!camera) { + return; + } + + for (const entity of world.get_entities()) { + const tilemap = entity.get(Tilemap); + + if (!tilemap) { + continue; + } + + const scaled_size = tilemap.tile_size * tilemap.scale; + const mouse = InputManager.get_mouse_position(); + + DebugUI.begin("Tile Editor", 500, 10, 300); + DebugUI.progress_bar(0.5); + + if (DebugUI.button("Save")) { + navigator.clipboard.writeText(JSON.stringify(tilemap)); + } + + for (let i = 0; i < tilemap.rows; i += 1) { + for (let j = 0; j < tilemap.columns; j += 1) { + const x = DebugUI.cursor_x + j * scaled_size; + const y = DebugUI.cursor_y; + + const hovered = point_inside_rec(mouse.x, mouse.y, x, y, scaled_size, scaled_size); + + if (DebugUI.is_inside_windows(mouse.x, mouse.y) && hovered && InputManager.is_mouse_pressed(0)) { + InputManager.consume_mouse(0); + tilemap.selected_tile = i * tilemap.columns + j; + } + + ctx.drawImage( + tilemap.image, + j * tilemap.tile_size + (j * tilemap.margin), + i * tilemap.tile_size + (i * tilemap.margin), + tilemap.tile_size, + tilemap.tile_size, + x, + y, + scaled_size, + scaled_size, + ); + } + DebugUI.advance(scaled_size); + } + + DebugUI.end(); + + if (tilemap.editing) { + const world_x = mouse.x + camera.x; + const world_y = mouse.y + camera.y; + + const tx = Math.floor(world_x / (tilemap.tile_size * tilemap.scale)); + const ty = Math.floor(world_y / (tilemap.tile_size * tilemap.scale)); + if (InputManager.is_mouse_pressed(0)) { + const maybe_tile = tilemap.tiles.find((tile) => tile.x === tx && tile.y === ty); + if (maybe_tile?.index !== tilemap.selected_tile) { + tilemap.tiles.push({ x: tx, y: ty, index: tilemap.selected_tile }); + } + } else if (InputManager.is_mouse_pressed(1)) { + InputManager.consume_mouse(1); + const index = tilemap.tiles.findLastIndex((tile) => tile.x === tx && tile.y === ty); + if (index !== -1) { + tilemap.tiles.splice(index, 1); + } + } + } + } + + DebugUI.ctx.restore(); + } +} diff --git a/client/text_rendering.ts b/client/text_rendering.ts new file mode 100644 index 0000000..b3ccfb6 --- /dev/null +++ b/client/text_rendering.ts @@ -0,0 +1,23 @@ +const TEXT_SCALE = 8; + +export function measure_text(ctx: CanvasRenderingContext2D, text: string, scale = 1): number { + ctx.font = `${scale * TEXT_SCALE}px m6x11`; + return ctx.measureText(text).width; +} + +export function draw_text( + ctx: CanvasRenderingContext2D, + text: string, + x: number, + y: number, + scale = 1, + color = "white", + baseline: CanvasTextBaseline = "top", + align: CanvasTextAlign = "left", +) { + ctx.textBaseline = baseline; + ctx.textAlign = align; + ctx.font = `${scale * TEXT_SCALE}px m6x11`; + ctx.fillStyle = color; + ctx.fillText(text, Math.floor(x), Math.floor(y)); +} diff --git a/common/components/position.ts b/common/components/position.ts new file mode 100644 index 0000000..55cbc49 --- /dev/null +++ b/common/components/position.ts @@ -0,0 +1,12 @@ +import { Component } from "$/common/ecs/component.ts"; + +export class Position extends Component { + x: number; + y: number; + + constructor(x: number, y: number) { + super(); + this.x = x; + this.y = y; + } +} diff --git a/common/components/velocity.ts b/common/components/velocity.ts new file mode 100644 index 0000000..6e272f8 --- /dev/null +++ b/common/components/velocity.ts @@ -0,0 +1,12 @@ +import { Component } from "$/common/ecs/component.ts"; + +export class Velocity extends Component { + vx: number; + vy: number; + + constructor(vx: number, vy: number) { + super(); + this.vx = vx; + this.vy = vy; + } +} diff --git a/common/constants.ts b/common/constants.ts new file mode 100644 index 0000000..82ed2e0 --- /dev/null +++ b/common/constants.ts @@ -0,0 +1,11 @@ +export const SLOT_SIZE = 18 * 3; + +export interface SpriteRegion { + x: number; + y: number; +} + +export const ITEM_SPRITES: Record = { + "bworld:pickaxe": { x: 7, y: 9 }, + "bworld:bomb": { x: 9, y: 8 }, +}; diff --git a/common/ecs/component.ts b/common/ecs/component.ts new file mode 100644 index 0000000..b634aaa --- /dev/null +++ b/common/ecs/component.ts @@ -0,0 +1,3 @@ +export abstract class Component { + __component = true; +} diff --git a/common/ecs/entity.ts b/common/ecs/entity.ts new file mode 100644 index 0000000..e4ba0a4 --- /dev/null +++ b/common/ecs/entity.ts @@ -0,0 +1,28 @@ +import type { Component } from "./component.ts"; + +// deno-lint-ignore no-explicit-any +type ComponentConstructor = new (...args: any[]) => T; + +export class Entity { + id: string; + // deno-lint-ignore no-explicit-any + components = new Map, Component>(); + active = true; + + constructor(id: string = crypto.randomUUID()) { + this.id = id; + } + + add(component: T): T { + this.components.set(component.constructor as ComponentConstructor, component); + return component; + } + + get(type: ComponentConstructor): T | undefined { + return this.components.get(type) as T; + } + + get_all(): Iterable { + return this.components.values(); + } +} diff --git a/common/ecs/mod.ts b/common/ecs/mod.ts new file mode 100644 index 0000000..2249d13 --- /dev/null +++ b/common/ecs/mod.ts @@ -0,0 +1,4 @@ +export * from "./component.ts"; +export * from "./entity.ts"; +export * from "./system.ts"; +export * from "./world.ts"; diff --git a/common/ecs/system.ts b/common/ecs/system.ts new file mode 100644 index 0000000..c455073 --- /dev/null +++ b/common/ecs/system.ts @@ -0,0 +1,5 @@ +import type { World } from "./world.ts"; + +export abstract class System { + abstract update(world: World, delta: number): void; +} diff --git a/common/ecs/world.ts b/common/ecs/world.ts new file mode 100644 index 0000000..ffca0e5 --- /dev/null +++ b/common/ecs/world.ts @@ -0,0 +1,34 @@ +import type { Entity } from "./entity.ts"; +import type { System } from "./system.ts"; + +export class World { + entities = new Set(); + systems = new Set(); + tags = new Map(); + + add_entity(entity: Entity) { + this.entities.add(entity); + } + + add_system(system: System) { + this.systems.add(system); + } + + add_tag(tag: string, entities: Entity[]) { + this.tags.set(tag, entities); + } + + get_tag(tag: string) { + return this.tags.get(tag); + } + + update(delta: number) { + for (const system of this.systems) { + system.update(this, delta); + } + } + + get_entities() { + return this.entities; + } +} diff --git a/common/systems/movement_system.ts b/common/systems/movement_system.ts new file mode 100644 index 0000000..78a4cc2 --- /dev/null +++ b/common/systems/movement_system.ts @@ -0,0 +1,19 @@ +import { System, World } from "$/common/ecs/mod.ts"; +import { Position } from "$/common/components/position.ts"; +import { Velocity } from "$/common/components/velocity.ts"; + +export class MovementSystem extends System { + update(world: World, delta: number): void { + for (const entity of world.get_entities()) { + const position = entity.get(Position); + const velocity = entity.get(Velocity); + + if (position && velocity) { + position.x += velocity.vx * delta; + position.y += velocity.vy * delta; + position.x = Math.round(position.x); + position.y = Math.round(position.y); + } + } + } +} diff --git a/common/utils.ts b/common/utils.ts new file mode 100644 index 0000000..e34f0c7 --- /dev/null +++ b/common/utils.ts @@ -0,0 +1,19 @@ +import { ITEM_SPRITES, SpriteRegion } from "./constants.ts"; + +export function point_inside_rec( + point_x: number, + point_y: number, + rec_x: number, + rec_y: number, + rec_w: number, + rec_h: number, +) { + return point_x > rec_x && + point_x < rec_x + rec_w && + point_y > rec_y && + point_y < rec_y + rec_h; +} + +export function get_sprite_region(id: string): SpriteRegion { + return ITEM_SPRITES[id] ?? { x: 0, y: 0 }; +} diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..f535d5a --- /dev/null +++ b/deno.json @@ -0,0 +1,20 @@ +{ + "tasks": { + "build": "deno run -A build.ts", + "serve:client": "deno run --allow-net --allow-read jsr:@std/http/file-server build" + }, + "compilerOptions": { + "lib": ["dom", "dom.asynciterable", "dom.iterable", "deno.ns", "deno.unstable"] + }, + "unstable": ["bundle"], + "fmt": { + "useTabs": true, + "indentWidth": 4, + "lineWidth": 120, + "newLineKind": "lf" + }, + "imports": { + "$/": "./", + "@std/fs": "jsr:@std/fs@^1.0.23" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..0ca6179 --- /dev/null +++ b/deno.lock @@ -0,0 +1,31 @@ +{ + "version": "5", + "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/fs@1.0.23": { + "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", + "dependencies": [ + "jsr:@std/internal", + "jsr:@std/path" + ] + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "dependencies": [ + "jsr:@std/internal" + ] + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/fs@^1.0.23" + ] + } +}