From b52b7f4c4452da14c3047f9b0bf2bfcb683bcca4 Mon Sep 17 00:00:00 2001 From: paulaboks Date: Fri, 27 Feb 2026 11:22:22 -0300 Subject: [PATCH] Crops ! --- client/client_world.ts | 4 +++ client/components/camera.ts | 13 ++++++++ client/components/clickable.ts | 13 ++++++++ client/components/crop.ts | 35 ++++++++++++++++++++ client/game.ts | 46 ++++++++++++++++++++++++++ client/systems/clickable_system.ts | 52 ++++++++++++++++++++++++++++++ client/systems/crop_system.ts | 45 ++++++++++++++++++++++++++ 7 files changed, 208 insertions(+) create mode 100644 client/components/clickable.ts create mode 100644 client/components/crop.ts create mode 100644 client/systems/clickable_system.ts create mode 100644 client/systems/crop_system.ts diff --git a/client/client_world.ts b/client/client_world.ts index 89bdb62..8419d67 100644 --- a/client/client_world.ts +++ b/client/client_world.ts @@ -8,7 +8,9 @@ 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 { CropSystem } from "$/client/systems/crop_system.ts"; import { create_main_menu } from "./main_menu.ts"; +import { ClickableSystem } from "./systems/clickable_system.ts"; export class ClientWorld extends World { canvas: HTMLCanvasElement; @@ -56,9 +58,11 @@ export class ClientWorld extends World { // Logic systems this.add_system(new UIInteractionSystem(), "main_menu"); this.add_system(new UIInteractionSystem(), "paused"); + this.add_system(new ClickableSystem(), "game"); this.add_system(new InventorySystem(), "game"); this.add_system(new PlayerControlsSystem(), "game"); this.add_system(new MovementSystem(), "game"); + this.add_system(new CropSystem(), "game"); // render systems this.add_system(new RenderSystem(this.ctx), "game"); diff --git a/client/components/camera.ts b/client/components/camera.ts index 362f401..caad059 100644 --- a/client/components/camera.ts +++ b/client/components/camera.ts @@ -13,3 +13,16 @@ export class Camera extends Component { this.zoom = zoom; } } + +export function screen_to_world( + screen_x: number, + screen_y: number, + camera: Camera, + screen_width: number, + screen_height: number, +) { + return { + x: (screen_x - screen_width / 2) / camera.zoom + camera.x, + y: (screen_y - screen_height / 2) / camera.zoom + camera.y, + }; +} diff --git a/client/components/clickable.ts b/client/components/clickable.ts new file mode 100644 index 0000000..dc54bae --- /dev/null +++ b/client/components/clickable.ts @@ -0,0 +1,13 @@ +import { Component } from "$/common/ecs/mod.ts"; + +export class ClickableSprite extends Component { + clicked = false; + button: number; + access_range: number; + + constructor(button: number = 0, access_range = 2) { + super(); + this.button = button; + this.access_range = access_range; + } +} diff --git a/client/components/crop.ts b/client/components/crop.ts new file mode 100644 index 0000000..3e0987a --- /dev/null +++ b/client/components/crop.ts @@ -0,0 +1,35 @@ +import { Component, Entity } from "$/common/ecs/mod.ts"; +import { Position } from "$/common/components/position.ts"; +import { AssetManager } from "../assets.ts"; +import { Sprite } from "./sprite.ts"; +import { ClickableSprite } from "./clickable.ts"; + +export class Crop extends Component { + current_stage = 0; + growth_time = 0; + total_stages: number; + time_to_grow: number[]; + item_drop: string; + + // you know ["bworld:carrot_seeds", "bworld:carrot_stage_1", "bworld:carrot_stage_2" ...] + sprite_ids: string[]; + + constructor(total_stages: number, time_to_grow: number[], sprite_ids: string[], item_drop: string) { + super(); + this.total_stages = total_stages; + this.time_to_grow = time_to_grow; + this.sprite_ids = sprite_ids; + this.item_drop = item_drop; + } +} + +export function create_crop_entity(x: number, y: number, crop: Crop) { + const crop_entity = new Entity("crop"); + crop_entity.add(new Position(x, y)); + crop_entity.add(new Sprite(AssetManager.instance.get("bworld:textures"), 32, 32, 0, 0, 16, 16)); + crop_entity.add(crop); + crop_entity.add( + new ClickableSprite(2), + ); + return crop_entity; +} diff --git a/client/game.ts b/client/game.ts index 7a22733..0fdb534 100644 --- a/client/game.ts +++ b/client/game.ts @@ -6,6 +6,7 @@ import { AssetManager } from "./assets.ts"; import { create_player } from "./player.ts"; import { UIButton } from "./components/ui_components.ts"; import { open_about } from "./about.ts"; +import { create_crop_entity, Crop } from "./components/crop.ts"; export function start_game(world: ClientWorld) { world.state = "game"; @@ -35,4 +36,49 @@ export function start_game(world: ClientWorld) { about_button.add(new Position(world.canvas.width / 2 - 150, world.canvas.height / 2 + 80)); about_button.add(new UIButton("About", 320, 64, () => open_about())); world.add_entity(about_button); + + world.add_entity(create_crop_entity( + 0, + 0, + new Crop(5, [4, 4, 4, 4], [ + "bworld:potato_seeds", + "bworld:potato_stage_1", + "bworld:potato_stage_2", + "bworld:potato_stage_3", + "bworld:potato_stage_4", + ], "bworld:potato"), + )); + world.add_entity(create_crop_entity( + 32, + 0, + new Crop(5, [5, 5, 5, 5], [ + "bworld:carrot_seeds", + "bworld:carrot_stage_1", + "bworld:carrot_stage_2", + "bworld:carrot_stage_3", + "bworld:carrot_stage_4", + ], "bworld:carrot"), + )); + world.add_entity(create_crop_entity( + 0, + 32, + new Crop(5, [10, 10, 10, 10], [ + "bworld:pumpkin_seeds", + "bworld:pumpkin_stage_1", + "bworld:pumpkin_stage_2", + "bworld:pumpkin_stage_3", + "bworld:pumpkin_stage_4", + ], "bworld:pumpkin"), + )); + world.add_entity(create_crop_entity( + 32, + 32, + new Crop(5, [7, 7, 7, 7], [ + "bworld:tomato_seeds", + "bworld:tomato_stage_1", + "bworld:tomato_stage_2", + "bworld:tomato_stage_3", + "bworld:tomato_stage_4", + ], "bworld:tomato"), + )); } diff --git a/client/systems/clickable_system.ts b/client/systems/clickable_system.ts new file mode 100644 index 0000000..a7bb80d --- /dev/null +++ b/client/systems/clickable_system.ts @@ -0,0 +1,52 @@ +import { System } from "$/common/ecs/mod.ts"; +import { Position } from "$/common/components/position.ts"; +import { Sprite } from "../components/sprite.ts"; +import { ClientWorld } from "../client_world.ts"; +import { ClickableSprite } from "../components/clickable.ts"; +import { point_inside_rec } from "$/common/utils.ts"; +import { InputManager } from "../input_manager.ts"; +import { Camera, screen_to_world } from "../components/camera.ts"; + +export class ClickableSystem extends System { + update(world: ClientWorld, _delta: number): void { + const [player] = world.get_tag("player")!; + const camera = player.get(Camera)!; + + const mouse = InputManager.get_mouse_position(); + + const world_mouse = screen_to_world( + mouse.x, + mouse.y, + camera, + world.canvas.width, + world.canvas.height, + ); + + for (const entity of world.get_entities()) { + const position = entity.get(Position); + + if (!position) { + continue; + } + + const clickable_sprite = entity.get(ClickableSprite); + const sprite = entity.get(Sprite); + + if (clickable_sprite && sprite) { + clickable_sprite.clicked = false; + const hovered = point_inside_rec( + world_mouse.x, + world_mouse.y, + position.x, + position.y, + sprite.width, + sprite.height, + ); + if (hovered && InputManager.is_mouse_pressed(clickable_sprite.button)) { + InputManager.consume_mouse(clickable_sprite.button); + clickable_sprite.clicked = true; + } + } + } + } +} diff --git a/client/systems/crop_system.ts b/client/systems/crop_system.ts new file mode 100644 index 0000000..0e46b35 --- /dev/null +++ b/client/systems/crop_system.ts @@ -0,0 +1,45 @@ +import { System } from "$/common/ecs/mod.ts"; +import { Sprite } from "../components/sprite.ts"; +import { ClientWorld } from "../client_world.ts"; +import { Crop } from "$/client/components/crop.ts"; +import { get_sprite_region } from "$/common/utils.ts"; +import { ClickableSprite } from "../components/clickable.ts"; +import { ItemStack, PlayerInventory } from "../components/inventory.ts"; + +export class CropSystem extends System { + update(world: ClientWorld, delta: number): void { + for (const entity of world.get_entities()) { + const crop = entity.get(Crop); + + if (!crop) { + continue; + } + + // finished growing, very good + if (crop.total_stages === crop.current_stage + 1) { + const clickable_sprite = entity.get(ClickableSprite); + if (clickable_sprite && clickable_sprite.clicked) { + const [player] = world.get_tag("player")!; + const inventory = player.get(PlayerInventory)!; + inventory.container.add_item(new ItemStack(crop.item_drop)); + world.delete_entity(entity); + } + continue; + } + + crop.growth_time += delta; + if (crop.growth_time >= crop.time_to_grow[crop.current_stage]) { + crop.current_stage += 1; + crop.growth_time = 0; + } + + const sprite = entity.get(Sprite); + + if (sprite) { + const region = get_sprite_region(crop.sprite_ids[crop.current_stage]); + sprite.source_x = region.x * 16; + sprite.source_y = region.y * 16; + } + } + } +}