From 5ad41885678b79888f76af337a2c29da1bdd4fab Mon Sep 17 00:00:00 2001 From: Paula Date: Sat, 7 Mar 2026 16:26:12 -0300 Subject: [PATCH] Crops ! --- client/client_world.ts | 2 - client/components/crop.ts | 113 -------------------- client/components/dimension.ts | 29 ++++- client/player.ts | 7 +- client/systems/crop_system.ts | 45 -------- client/systems/dimension_logic.ts | 56 +++++----- client/systems/rendering/dimension.ts | 15 ++- client/tiles/crops.ts | 146 ++++++++++++++++++++++++++ client/tiles/dirt.ts | 14 ++- client/tiles/mod.ts | 1 + common/constants.ts | 3 + common/everything_registry.ts | 11 +- 12 files changed, 245 insertions(+), 197 deletions(-) delete mode 100644 client/components/crop.ts delete mode 100644 client/systems/crop_system.ts create mode 100644 client/tiles/crops.ts diff --git a/client/client_world.ts b/client/client_world.ts index 68a1bdc..f019092 100644 --- a/client/client_world.ts +++ b/client/client_world.ts @@ -7,7 +7,6 @@ import { DebugSystem } from "$/client/systems/debug_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"; import { start_game } from "./game.ts"; @@ -46,7 +45,6 @@ export class ClientWorld extends World { this.add_system(new InventorySystem(), "game"); this.add_system(new PlayerControlsSystem(), "game"); this.add_system(new MovementSystem(), "game"); - this.add_system(new CropSystem(), "game"); this.add_system(new DimensionLogicSystem(), "game"); // render systems diff --git a/client/components/crop.ts b/client/components/crop.ts deleted file mode 100644 index db3f203..0000000 --- a/client/components/crop.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { Entity, SerializableComponent } 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"; -import { EverythingRegistry } from "$/common/everything_registry.ts"; - -interface CropsRegistry { - total_stages: number; - time_to_grow: number[]; - item_drop: string; - sprite_ids: string[]; -} - -EverythingRegistry.add_registry("crops"); -EverythingRegistry.register("crops", "bworld:carrot", { - total_stages: 5, - time_to_grow: [60, 60 * 3, 60 * 3, 60 * 3], - sprite_ids: [ - "bworld:carrot_seeds", - "bworld:carrot_stage_1", - "bworld:carrot_stage_2", - "bworld:carrot_stage_3", - "bworld:carrot_stage_4", - ], - item_drop: "bworld:carrot", -}); -EverythingRegistry.register("crops", "bworld:potato", { - total_stages: 5, - time_to_grow: [45, 60 * 2, 60 * 2, 60 * 2], - sprite_ids: [ - "bworld:potato_seeds", - "bworld:potato_stage_1", - "bworld:potato_stage_2", - "bworld:potato_stage_3", - "bworld:potato_stage_4", - ], - item_drop: "bworld:potato", -}); -EverythingRegistry.register("crops", "bworld:tomato", { - total_stages: 5, - time_to_grow: [90, 60 * 2, 60 * 2, 60 * 3], - sprite_ids: [ - "bworld:tomato_seeds", - "bworld:tomato_stage_1", - "bworld:tomato_stage_2", - "bworld:tomato_stage_3", - "bworld:tomato_stage_4", - ], - item_drop: "bworld:tomato", -}); -EverythingRegistry.register("crops", "bworld:pumpkin", { - total_stages: 5, - time_to_grow: [60 * 2, 60 * 4, 60 * 4, 60 * 4], - sprite_ids: [ - "bworld:pumpkin_seeds", - "bworld:pumpkin_stage_1", - "bworld:pumpkin_stage_2", - "bworld:pumpkin_stage_3", - "bworld:pumpkin_stage_4", - ], - item_drop: "bworld:pumpkin", -}); - -export class Crop extends SerializableComponent { - crop_id: string; - 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( - crop_id: string, - ) { - super(); - this.crop_id = crop_id; - const crop_info = EverythingRegistry.get("crops", crop_id); - this.total_stages = crop_info.total_stages; - this.time_to_grow = crop_info.time_to_grow; - this.sprite_ids = crop_info.sprite_ids; - this.item_drop = crop_info.item_drop; - } - - override serialize() { - return { - crop_id: this.crop_id, - current_stage: this.current_stage, - growth_time: this.growth_time, - }; - } - - static deserialize(data: ReturnType): Crop { - const crop = new Crop(data.crop_id); - crop.current_stage = data.current_stage; - crop.growth_time = data.growth_time; - return crop; - } -} - -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/components/dimension.ts b/client/components/dimension.ts index 3e582af..b98e1e1 100644 --- a/client/components/dimension.ts +++ b/client/components/dimension.ts @@ -1,21 +1,46 @@ import { Component } from "$/common/ecs/mod.ts"; +import { EverythingRegistry, TileRegistry } from "$/common/everything_registry.ts"; import { AssetManager } from "../assets.ts"; +import { ClientWorld } from "../client_world.ts"; import { Texture } from "../renderer.ts"; -export interface Tile { +export interface Tile { x: number; y: number; id: string; - data?: Record | undefined; + data?: T; + tickable?: boolean; } export class Dimension extends Component { image: Texture; tiles: Tile[]; + second_timer = 0; + tick_timer = 0; constructor(tiles: Tile[]) { super(); this.image = AssetManager.instance.get("bworld:textures"); this.tiles = tiles; } + + add_tile(world: ClientWorld, tile: Tile) { + this.tiles.push(tile); + const tile_info = EverythingRegistry.get("tiles", tile.id); + if (tile_info?.on_create) { + tile_info?.on_create(world, tile); + } + } + + delete_tile(_world: ClientWorld, tile: Tile) { + const index = this.tiles.indexOf(tile); + if (index !== -1) { + this.tiles.splice(index, 1); + } + + // const tile_info = EverythingRegistry.get("tiles", tile.id); + // if (tile_info?.on_delete) { + // tile_info?.on_delete(world, tile); + // } + } } diff --git a/client/player.ts b/client/player.ts index de4a407..6467db0 100644 --- a/client/player.ts +++ b/client/player.ts @@ -37,9 +37,12 @@ export function create_player(world: ClientWorld) { player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:hoe", 1, 1)); player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:watering_can", 1, 1)); player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:axe", 1, 1)); - player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:sword", 1, 1)); - player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:bomb", 64)); + // player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:sword", 1, 1)); + // player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:bomb", 64)); player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:tomato_seeds", 64)); + player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:potato_seeds", 64)); + player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:pumpkin_seeds", 64)); + player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:carrot_seeds", 64)); player.add(new Camera(-100, -100)); world.add_entity(player); diff --git a/client/systems/crop_system.ts b/client/systems/crop_system.ts deleted file mode 100644 index 0e46b35..0000000 --- a/client/systems/crop_system.ts +++ /dev/null @@ -1,45 +0,0 @@ -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; - } - } - } -} diff --git a/client/systems/dimension_logic.ts b/client/systems/dimension_logic.ts index 670a3b7..bc58702 100644 --- a/client/systems/dimension_logic.ts +++ b/client/systems/dimension_logic.ts @@ -4,14 +4,24 @@ import { Dimension } from "../components/dimension.ts"; import { Camera, screen_to_world } from "../components/camera.ts"; import { InputManager } from "../input_manager.ts"; import { canvas } from "../renderer.ts"; -import { TILE_SIZE } from "$/common/constants.ts"; +import { TICK_DELTA, TILE_SIZE } from "$/common/constants.ts"; import { EverythingRegistry, TileRegistry } from "$/common/everything_registry.ts"; export class DimensionLogicSystem extends System { - update(world: ClientWorld, _delta: number): void { + update(world: ClientWorld, delta: number): void { const dimension_entity = world.get_entities().values().find((ent) => ent.get(Dimension)); const dimension = dimension_entity!.get(Dimension)!; + dimension.second_timer += delta; + dimension.tick_timer += delta; + + if (dimension.second_timer >= 1) { + this.handle_second(world, dimension); + } + if (dimension.tick_timer >= TICK_DELTA) { + this.handle_tick(world, dimension); + } + const [player] = world.get_tag("player")!; const camera = player.get(Camera)!; @@ -48,32 +58,26 @@ export class DimensionLogicSystem extends System { } } } - - /*for (const tile of dimension.tiles) { - const tile_info = EverythingRegistry.get("tiles", tile.id); - if (!tile_info) { - continue; + } + handle_second(world: ClientWorld, dimension: Dimension) { + const tickables = dimension.tiles.filter((t) => t.tickable); + for (const tickable of tickables) { + const tile_info = EverythingRegistry.get("tiles", tickable.id); + if (tile_info && tile_info.on_second) { + tile_info.on_second(world, tickable, dimension.second_timer); } + } + dimension.second_timer = 0; + } - if (tile_info.on_interact || tile_info.on_click) { - console.log(tile_info); - const hovered = point_inside_rec( - world_mouse.x, - world_mouse.y, - tile.x * TILE_SIZE, - tile.y * TILE_SIZE, - TILE_SIZE, - TILE_SIZE, - ); - if (tile_info.on_interact && hovered && InputManager.is_mouse_pressed(2)) { - InputManager.consume_mouse(2); - tile_info.on_interact(world, tile); - } - if (tile_info.on_click && hovered && InputManager.is_mouse_pressed(0)) { - InputManager.consume_mouse(0); - tile_info.on_click(world, tile); - } + handle_tick(world: ClientWorld, dimension: Dimension) { + const tickables = dimension.tiles.filter((t) => t.tickable); + for (const tickable of tickables) { + const tile_info = EverythingRegistry.get("tiles", tickable.id); + if (tile_info && tile_info.on_tick) { + tile_info.on_tick(world, tickable, dimension.tick_timer); } - }*/ + } + dimension.tick_timer = 0; } } diff --git a/client/systems/rendering/dimension.ts b/client/systems/rendering/dimension.ts index 0f658a5..b457f8c 100644 --- a/client/systems/rendering/dimension.ts +++ b/client/systems/rendering/dimension.ts @@ -3,11 +3,10 @@ import { Dimension } from "$/client/components/dimension.ts"; import { TEXTURE_SIZE, TILE_SIZE } from "$/common/constants.ts"; import { Camera, world_to_screen } from "$/client/components/camera.ts"; import { canvas, draw_texture_region } from "$/client/renderer.ts"; +import { EverythingRegistry, TileRegistry } from "$/common/everything_registry.ts"; export function render_dimension(dimension: Dimension, camera: Camera) { for (const tile of dimension.tiles) { - const region = get_sprite_region(tile.id); - const x = tile.x * TILE_SIZE; const y = tile.y * TILE_SIZE; @@ -19,6 +18,18 @@ export function render_dimension(dimension: Dimension, camera: Camera) { continue; } + let texture_id = tile.id; + const tile_info = EverythingRegistry.get("tiles", tile.id); + if (tile_info?.texture_id) { + if (typeof tile_info.texture_id === "string") { + texture_id = tile_info.texture_id; + } else { + texture_id = tile_info.texture_id(tile); + } + } + + const region = get_sprite_region(texture_id); + draw_texture_region( dimension.image, region.x * TEXTURE_SIZE, diff --git a/client/tiles/crops.ts b/client/tiles/crops.ts new file mode 100644 index 0000000..2354c9f --- /dev/null +++ b/client/tiles/crops.ts @@ -0,0 +1,146 @@ +import { EverythingRegistry, TileRegistry } from "$/common/everything_registry.ts"; +import { ItemStack, PlayerInventory } from "../components/inventory.ts"; + +interface CropsRegistry { + total_stages: number; + time_to_grow: number[]; + item_drop: string; + sprite_ids: string[]; + regrowable: boolean; +} + +EverythingRegistry.add_registry("crops"); +EverythingRegistry.register("crops", "bworld:carrot", { + total_stages: 5, + time_to_grow: [60, 60 * 3, 60 * 3, 60 * 3], + sprite_ids: [ + "bworld:carrot_seeds", + "bworld:carrot_stage_1", + "bworld:carrot_stage_2", + "bworld:carrot_stage_3", + "bworld:carrot_stage_4", + ], + item_drop: "bworld:carrot", + regrowable: false, +}); +EverythingRegistry.register("crops", "bworld:potato", { + total_stages: 5, + time_to_grow: [45, 60 * 2, 60 * 2, 60 * 2], + sprite_ids: [ + "bworld:potato_seeds", + "bworld:potato_stage_1", + "bworld:potato_stage_2", + "bworld:potato_stage_3", + "bworld:potato_stage_4", + ], + item_drop: "bworld:potato", + regrowable: false, +}); +EverythingRegistry.register("crops", "bworld:tomato", { + total_stages: 5, + time_to_grow: [90, 60 * 2, 60 * 2, 60 * 3], + sprite_ids: [ + "bworld:tomato_seeds", + "bworld:tomato_stage_1", + "bworld:tomato_stage_2", + "bworld:tomato_stage_3", + "bworld:tomato_stage_4", + ], + item_drop: "bworld:tomato", + regrowable: true, +}); +EverythingRegistry.register("crops", "bworld:pumpkin", { + total_stages: 5, + time_to_grow: [60 * 2, 60 * 4, 60 * 4, 60 * 4], + sprite_ids: [ + "bworld:pumpkin_seeds", + "bworld:pumpkin_stage_1", + "bworld:pumpkin_stage_2", + "bworld:pumpkin_stage_3", + "bworld:pumpkin_stage_4", + ], + item_drop: "bworld:pumpkin", + regrowable: false, +}); + +interface TileCropData { + current_stage: number; + growth_time: number; +} + +function create_crop_tile(crop_id: string) { + const crop_info = EverythingRegistry.get("crops", crop_id); + + return { + has_collision: false, + on_create(_, tile) { + tile.data = { + current_stage: 0, + growth_time: 0, + }; + }, + texture_id(tile) { + return crop_info.sprite_ids[tile.data!.current_stage]; + }, + on_click(world, tile) { + const [player] = world.get_tag("player")!; + const player_inventory = player.get(PlayerInventory)!; + const item = player_inventory.container.get_item(player_inventory.hotbar_selected); + if (!item) { + return; + } + + if (item.type_id === "bworld:pickaxe") { + world.dimension.delete_tile(world, tile); + } + }, + on_interact(world, tile) { + if (tile.data!.current_stage + 1 !== crop_info.total_stages) { + return; + } + const [player] = world.get_tag("player")!; + const player_inventory = player.get(PlayerInventory)!; + player_inventory.container.add_item(new ItemStack(crop_info.item_drop)); + if (crop_info.regrowable) { + tile.data!.current_stage -= 1; + } else { + world.dimension.delete_tile(world, tile); + } + }, + on_second(_, tile, delta) { + const crop = tile.data!; + // finished growing + if (crop.current_stage + 1 === crop_info.total_stages) { + return; + } + + // grow ! + crop.growth_time += delta; + if (crop.growth_time >= crop_info.time_to_grow[crop.current_stage]) { + crop.current_stage += 1; + crop.growth_time = 0; + } + }, + } as TileRegistry; +} + +EverythingRegistry.register>( + "tiles", + "bworld:tomato_crop", + create_crop_tile("bworld:tomato"), +); +EverythingRegistry.register>( + "tiles", + "bworld:carrot_crop", + create_crop_tile("bworld:carrot"), +); +EverythingRegistry.register>( + "tiles", + "bworld:potato_crop", + create_crop_tile("bworld:potato"), +); +EverythingRegistry.register>( + "tiles", + "bworld:pumpkin_crop", + create_crop_tile("bworld:pumpkin"), +); diff --git a/client/tiles/dirt.ts b/client/tiles/dirt.ts index 5398203..829683f 100644 --- a/client/tiles/dirt.ts +++ b/client/tiles/dirt.ts @@ -70,7 +70,19 @@ EverythingRegistry.register("tiles", "bworld:hoed_watered_dirt", { } if (item.type_id === "bworld:tomato_seeds") { - world.dimension.tiles.push({ x: tile.x, y: tile.y, id: "bworld:tomato_seeds" }); + world.dimension.add_tile(world, { x: tile.x, y: tile.y, id: "bworld:tomato_crop", tickable: true }); + item.amount -= 1; + player_inventory.container.set_item(player_inventory.hotbar_selected, item); + } else if (item.type_id === "bworld:potato_seeds") { + world.dimension.add_tile(world, { x: tile.x, y: tile.y, id: "bworld:potato_crop", tickable: true }); + item.amount -= 1; + player_inventory.container.set_item(player_inventory.hotbar_selected, item); + } else if (item.type_id === "bworld:pumpkin_seeds") { + world.dimension.add_tile(world, { x: tile.x, y: tile.y, id: "bworld:pumpkin_crop", tickable: true }); + item.amount -= 1; + player_inventory.container.set_item(player_inventory.hotbar_selected, item); + } else if (item.type_id === "bworld:carrot_seeds") { + world.dimension.add_tile(world, { x: tile.x, y: tile.y, id: "bworld:carrot_crop", tickable: true }); item.amount -= 1; player_inventory.container.set_item(player_inventory.hotbar_selected, item); } diff --git a/client/tiles/mod.ts b/client/tiles/mod.ts index 5969e51..735fdfe 100644 --- a/client/tiles/mod.ts +++ b/client/tiles/mod.ts @@ -1,3 +1,4 @@ import "./grass.ts"; import "./dirt.ts"; import "./tree.ts"; +import "./crops.ts"; diff --git a/common/constants.ts b/common/constants.ts index 0c6d2f3..6027299 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -1,6 +1,9 @@ export const TEXTURE_SIZE = 16; export const TILE_SIZE = 48; +export const TICKS_PER_SECOND = 20; +export const TICK_DELTA = 1 / TICKS_PER_SECOND; + export const SLOT_SIZE = 18 * 3; export interface SpriteRegion { diff --git a/common/everything_registry.ts b/common/everything_registry.ts index ae4e89a..b823d7e 100644 --- a/common/everything_registry.ts +++ b/common/everything_registry.ts @@ -17,10 +17,13 @@ export class EverythingRegistry { } } -export interface TileRegistry { - texture_id: string; +export interface TileRegistry { + texture_id: string | ((tile: Tile) => string); has_collision: boolean; - on_click?(world: ClientWorld, tile: Tile): void; - on_interact?(world: ClientWorld, tile: Tile): void; + on_create?(world: ClientWorld, tile: Tile): void; + on_click?(world: ClientWorld, tile: Tile): void; + on_interact?(world: ClientWorld, tile: Tile): void; + on_tick?(world: ClientWorld, tile: Tile, tick_delta: number): void; + on_second?(world: ClientWorld, tile: Tile, second_delta: number): void; }