Server authority
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
// behavior lives in server/game/blocks.ts
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:chest", {
|
||||
id: "bworld:chest",
|
||||
textures: "bworld:planks",
|
||||
toughness: 8,
|
||||
requires_tool: false,
|
||||
tool_to_break: "axe",
|
||||
drop_table: "bworld:chest",
|
||||
has_collision: false,
|
||||
interactive: true,
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:coal_ore", {
|
||||
id: "bworld:coal_ore",
|
||||
textures: "bworld:stone_coal",
|
||||
has_collision: true,
|
||||
drop_table: "bworld:coal_ore",
|
||||
toughness: 5,
|
||||
requires_tool: true,
|
||||
tool_to_break: "pickaxe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:copper_ore", {
|
||||
id: "bworld:copper_ore",
|
||||
textures: "bworld:stone_copper",
|
||||
has_collision: true,
|
||||
drop_table: "bworld:copper_ore",
|
||||
toughness: 5,
|
||||
requires_tool: true,
|
||||
tool_to_break: "pickaxe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,145 @@
|
||||
import { EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
interface CropsRegistry {
|
||||
total_stages: number;
|
||||
time_to_grow: number[];
|
||||
item_drop: string;
|
||||
sprite_ids: string[];
|
||||
regrowable: boolean;
|
||||
}
|
||||
|
||||
EverythingRegistry.register<CropsRegistry>("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<CropsRegistry>("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<CropsRegistry>("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<CropsRegistry>("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<CropsRegistry>("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(PlayerComponent)!.player_inventory;
|
||||
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(PlayerComponent)!.player_inventory;
|
||||
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 BlockRegistry<TileCropData>;
|
||||
}
|
||||
|
||||
EverythingRegistry.register<BlockRegistry<TileCropData>>(
|
||||
"blocks",
|
||||
"bworld:tomato_crop",
|
||||
create_crop_tile("bworld:tomato"),
|
||||
);
|
||||
EverythingRegistry.register<BlockRegistry<TileCropData>>(
|
||||
"blocks",
|
||||
"bworld:carrot_crop",
|
||||
create_crop_tile("bworld:carrot"),
|
||||
);
|
||||
EverythingRegistry.register<BlockRegistry<TileCropData>>(
|
||||
"blocks",
|
||||
"bworld:potato_crop",
|
||||
create_crop_tile("bworld:potato"),
|
||||
);
|
||||
EverythingRegistry.register<BlockRegistry<TileCropData>>(
|
||||
"blocks",
|
||||
"bworld:pumpkin_crop",
|
||||
create_crop_tile("bworld:pumpkin"),
|
||||
);
|
||||
*/
|
||||
@@ -0,0 +1,15 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
// hoeing it is handled in server/game/blocks.ts
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:dirt", {
|
||||
id: "bworld:dirt",
|
||||
textures: "bworld:dirt",
|
||||
has_collision: false,
|
||||
drop_table: "bworld:dirt",
|
||||
toughness: 2,
|
||||
requires_tool: false,
|
||||
tool_to_break: "shovel",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,16 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
// behavior lives in server/game/blocks.ts
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:furnace", {
|
||||
id: "bworld:furnace",
|
||||
textures: { front: "bworld:furnace", side: "bworld:stone" },
|
||||
has_collision: true,
|
||||
drop_table: "bworld:furnace",
|
||||
toughness: 5,
|
||||
requires_tool: true,
|
||||
tool_to_break: "pickaxe",
|
||||
interactive: true,
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:glass", {
|
||||
id: "bworld:glass",
|
||||
textures: "bworld:glass",
|
||||
has_collision: true,
|
||||
transparent: true,
|
||||
toughness: 3,
|
||||
requires_tool: false,
|
||||
tool_to_break: "pickaxe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:gold_ore", {
|
||||
id: "bworld:gold_ore",
|
||||
textures: "bworld:stone_gold",
|
||||
has_collision: true,
|
||||
drop_table: "bworld:gold_ore",
|
||||
toughness: 5,
|
||||
requires_tool: true,
|
||||
tool_to_break: "pickaxe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,12 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
// hoeing it is handled in server/game/blocks.ts
|
||||
EverythingRegistry.register<BlockRegistry>("blocks", "bworld:grass", {
|
||||
id: "bworld:grass",
|
||||
textures: { top: "bworld:grass_top", bottom: "bworld:dirt", "side": "bworld:grass_side" },
|
||||
has_collision: false,
|
||||
drop_table: "bworld:dirt",
|
||||
toughness: 2,
|
||||
requires_tool: false,
|
||||
tool_to_break: "shovel",
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
// TODO: watered state
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:hoed_dirt", {
|
||||
id: "bworld:hoed_dirt",
|
||||
textures: { side: "bworld:dirt", top: "bworld:hoed_dirt", bottom: "bworld:dirt" },
|
||||
has_collision: false,
|
||||
drop_table: "bworld:dirt",
|
||||
toughness: 5,
|
||||
requires_tool: false,
|
||||
tool_to_break: "shovel",
|
||||
states: [
|
||||
{ name: "watered", bits: 1, default: 0 },
|
||||
],
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:iron_ore", {
|
||||
id: "bworld:iron_ore",
|
||||
textures: "bworld:stone_iron",
|
||||
has_collision: true,
|
||||
drop_table: "bworld:iron_ore",
|
||||
toughness: 5,
|
||||
requires_tool: true,
|
||||
tool_to_break: "pickaxe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:leaves", {
|
||||
id: "bworld:leaves",
|
||||
textures: "bworld:leaves",
|
||||
has_collision: true,
|
||||
transparent: true,
|
||||
toughness: 3,
|
||||
requires_tool: false,
|
||||
tool_to_break: "hoe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:log", {
|
||||
id: "bworld:log",
|
||||
textures: { side: "bworld:log_side", top: "bworld:log_top", bottom: "bworld:log_top" },
|
||||
has_collision: true,
|
||||
drop_table: "bworld:log",
|
||||
toughness: 3,
|
||||
requires_tool: false,
|
||||
tool_to_break: "axe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,19 @@
|
||||
import "./grass.ts";
|
||||
import "./dirt.ts";
|
||||
import "./crops.ts";
|
||||
import "./water.ts";
|
||||
import "./chest.ts";
|
||||
import "./furnace.ts";
|
||||
import "./stone.ts";
|
||||
import "./log.ts";
|
||||
import "./sand.ts";
|
||||
import "./snow.ts";
|
||||
import "./glass.ts";
|
||||
import "./hoed_dirt.ts";
|
||||
import "./leaves.ts";
|
||||
import "./coal_ore.ts";
|
||||
import "./copper_ore.ts";
|
||||
import "./iron_ore.ts";
|
||||
import "./tin_ore.ts";
|
||||
import "./gold_ore.ts";
|
||||
import "./planks.ts";
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:planks", {
|
||||
id: "bworld:planks",
|
||||
textures: "bworld:planks",
|
||||
has_collision: true,
|
||||
drop_table: "bworld:log",
|
||||
toughness: 3,
|
||||
requires_tool: false,
|
||||
tool_to_break: "axe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:sand", {
|
||||
id: "bworld:sand",
|
||||
textures: "bworld:sand",
|
||||
has_collision: true,
|
||||
drop_table: "bworld:sand",
|
||||
toughness: 3,
|
||||
requires_tool: false,
|
||||
tool_to_break: "shovel",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,13 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:snow", {
|
||||
id: "bworld:snow",
|
||||
textures: "bworld:snow",
|
||||
has_collision: true,
|
||||
toughness: 2,
|
||||
requires_tool: true,
|
||||
tool_to_break: "shovel",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:stone", {
|
||||
id: "bworld:stone",
|
||||
textures: "bworld:stone",
|
||||
has_collision: true,
|
||||
drop_table: "bworld:stone",
|
||||
toughness: 3,
|
||||
requires_tool: true,
|
||||
tool_to_break: "pickaxe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
import { register_block_item } from "$/common/utils.ts";
|
||||
|
||||
const block = EverythingRegistry.register<BlockRegistry>("blocks", "bworld:tin_ore", {
|
||||
id: "bworld:tin_ore",
|
||||
textures: "bworld:stone_tin",
|
||||
has_collision: true,
|
||||
drop_table: "bworld:tin_ore",
|
||||
toughness: 5,
|
||||
requires_tool: true,
|
||||
tool_to_break: "pickaxe",
|
||||
});
|
||||
|
||||
register_block_item(block);
|
||||
@@ -0,0 +1,9 @@
|
||||
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<BlockRegistry>("blocks", "bworld:water", {
|
||||
id: "bworld:water",
|
||||
textures: "bworld:water",
|
||||
has_collision: false,
|
||||
transparent: true,
|
||||
alpha: 0.8,
|
||||
});
|
||||
@@ -22,3 +22,13 @@ export const STATE_SHIFT = 16;
|
||||
export const CHUNK_SIZE = 16;
|
||||
export const CHUNK_HEIGHT = 128;
|
||||
export const CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE;
|
||||
|
||||
// where a block placed against each face of another block goes
|
||||
export const FACE_OFFSETS: Record<Faces, { x: number; y: number; z: number }> = {
|
||||
top: { x: 0, y: 1, z: 0 },
|
||||
bottom: { x: 0, y: -1, z: 0 },
|
||||
north: { x: 0, y: 0, z: -1 },
|
||||
south: { x: 0, y: 0, z: 1 },
|
||||
west: { x: -1, y: 0, z: 0 },
|
||||
east: { x: 1, y: 0, z: 0 },
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Block, Dimension } from "$/client/components/dimension.ts";
|
||||
import { ItemStack } from "$/client/inventory.ts";
|
||||
import type { ItemStack } from "./inventory.ts";
|
||||
|
||||
export class EverythingRegistry {
|
||||
static #key_to_id = new Map<string, Map<string, number>>();
|
||||
@@ -94,12 +93,9 @@ export interface BlockRegistry {
|
||||
states?: BlockStateDefinition[];
|
||||
variants?: Record<string, BlockStateVariant>;
|
||||
|
||||
on_create?(dimension: Dimension, block: Block): void;
|
||||
on_break?(dimension: Dimension, block: Block): void;
|
||||
on_click?(dimension: Dimension, block: Block): void;
|
||||
on_interact?(dimension: Dimension, block: Block): boolean;
|
||||
on_tick?(dimension: Dimension, block: Block, tick_delta: number): void;
|
||||
on_second?(dimension: Dimension, block: Block, second_delta: number): void;
|
||||
// right clicking it does something instead of placing a block, clients don't predict placing against it.
|
||||
// behavior runs on the server, see server/game/blocks.ts
|
||||
interactive?: boolean;
|
||||
|
||||
compiled_states?: CompiledStateDefinition[];
|
||||
}
|
||||
@@ -109,8 +105,6 @@ export interface ItemRegistry<T = unknown | undefined> {
|
||||
block_id?: string;
|
||||
tool_type?: string;
|
||||
|
||||
place?(dimension: Dimension, block: Block): void;
|
||||
|
||||
on_create?(item: ItemStack<T>): void;
|
||||
|
||||
get_lore?(item: ItemStack<T>): string;
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
import { Alea, create_noise_2d, create_noise_3d, NoiseFunction2D, NoiseFunction3D } from "@paulaboks/rng";
|
||||
import { CHUNK_HEIGHT, CHUNK_SIZE } from "$/common/constants.ts";
|
||||
|
||||
// generation runs in a worker now, so it only needs somewhere to put blocks
|
||||
export interface BlockSink {
|
||||
add_block(block: { x: number; y: number; z: number; id: string }): void;
|
||||
}
|
||||
|
||||
type Biome =
|
||||
| "desert"
|
||||
| "plains"
|
||||
| "forest"
|
||||
| "jungle"
|
||||
| "tundra"
|
||||
| "taiga"
|
||||
| "snow"
|
||||
| "savanna"
|
||||
| "swamp";
|
||||
|
||||
type OreDef = {
|
||||
id: string;
|
||||
min_y: number;
|
||||
max_y: number;
|
||||
scale: number;
|
||||
threshold: number;
|
||||
};
|
||||
|
||||
const ORES: OreDef[] = [
|
||||
{ id: "bworld:coal_ore", min_y: 20, max_y: 120, scale: 0.05, threshold: 0.55 },
|
||||
{ id: "bworld:copper_ore", min_y: 10, max_y: 80, scale: 0.06, threshold: 0.6 },
|
||||
{ id: "bworld:tin_ore", min_y: 5, max_y: 60, scale: 0.06, threshold: 0.62 },
|
||||
{ id: "bworld:iron_ore", min_y: 5, max_y: 50, scale: 0.05, threshold: 0.65 },
|
||||
{ id: "bworld:gold_ore", min_y: 0, max_y: 30, scale: 0.04, threshold: 0.7 },
|
||||
];
|
||||
|
||||
function get_biome(temp: number, moisture: number): Biome {
|
||||
if (temp > 0.6) {
|
||||
if (moisture < -0.2) {
|
||||
return "desert";
|
||||
}
|
||||
if (moisture > 0.4) {
|
||||
return "jungle";
|
||||
}
|
||||
return "savanna";
|
||||
}
|
||||
if (temp > 0) {
|
||||
if (moisture > 0.5) {
|
||||
return "swamp";
|
||||
}
|
||||
if (moisture > 0) {
|
||||
return "forest";
|
||||
}
|
||||
return "plains";
|
||||
}
|
||||
if (temp > -0.5) {
|
||||
return "taiga";
|
||||
}
|
||||
return "tundra";
|
||||
}
|
||||
|
||||
function get_surface_block(biome: Biome) {
|
||||
if (biome === "desert") {
|
||||
return "bworld:sand";
|
||||
} else if (biome === "tundra") {
|
||||
return "bworld:snow";
|
||||
}
|
||||
return "bworld:grass";
|
||||
}
|
||||
|
||||
function biome_height_modifier(biome: Biome) {
|
||||
if (biome === "desert") {
|
||||
return 0.2;
|
||||
}
|
||||
if (biome === "plains") {
|
||||
return 0.4;
|
||||
}
|
||||
if (biome === "forest") {
|
||||
return 0.5;
|
||||
}
|
||||
if (biome === "jungle") {
|
||||
return 0.45;
|
||||
}
|
||||
if (biome === "taiga") {
|
||||
return 0.55;
|
||||
}
|
||||
if (biome === "tundra") {
|
||||
return 0.35;
|
||||
}
|
||||
if (biome === "savanna") {
|
||||
return 0.4;
|
||||
}
|
||||
if (biome === "swamp") {
|
||||
return 0.35;
|
||||
}
|
||||
|
||||
return 0.4;
|
||||
}
|
||||
|
||||
function fractal_noise(noise: NoiseFunction2D, x: number, y: number, octaves = 2) {
|
||||
let value = 0;
|
||||
let amp = 1;
|
||||
let freq = 1;
|
||||
let max = 0;
|
||||
for (let i = 0; i < octaves; i++) {
|
||||
value += noise(x * freq, y * freq) * amp;
|
||||
max += amp;
|
||||
amp *= 0.5;
|
||||
freq *= 2;
|
||||
}
|
||||
return value / max;
|
||||
}
|
||||
|
||||
function get_terrain_height(base: number, biome: Biome, x: number, z: number, noise: NoiseFunction2D) {
|
||||
const biomeMod = biome_height_modifier(biome);
|
||||
|
||||
const main = fractal_noise(noise, x * 0.003, z * 0.003) * 15;
|
||||
|
||||
const detail = fractal_noise(noise, x * 0.01, z * 0.01) * 3;
|
||||
|
||||
return Math.floor(base + biomeMod * 20 + main + detail);
|
||||
}
|
||||
|
||||
function can_place_tree(tree_map: boolean[][], local_x: number, local_z: number) {
|
||||
const TREE_SPACING = 4;
|
||||
for (let dx = -TREE_SPACING; dx <= TREE_SPACING; dx++) {
|
||||
for (let dz = -TREE_SPACING; dz <= TREE_SPACING; dz++) {
|
||||
const nx = local_x + dx;
|
||||
const nz = local_z + dz;
|
||||
if (nx >= 0 && nx < CHUNK_SIZE && nz >= 0 && nz < CHUNK_SIZE && tree_map[nx][nz]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function place_tree(dimension: BlockSink, rng: Alea, x: number, y: number, z: number, biome: Biome) {
|
||||
const height = Math.floor(rng.next() * 3) + (biome === "jungle" ? 8 : 4);
|
||||
const trunk_block = "bworld:log";
|
||||
const leaves_block = "bworld:leaves";
|
||||
|
||||
for (let i = 0; i < height; i++) {
|
||||
dimension.add_block({ x, y: y + i, z, id: trunk_block });
|
||||
}
|
||||
|
||||
for (let dx = -2; dx <= 2; dx++) {
|
||||
for (let dz = -2; dz <= 2; dz++) {
|
||||
for (let dy = -1; dy <= 1; dy++) {
|
||||
if (Math.abs(dx) + Math.abs(dz) + Math.abs(dy) <= 3) {
|
||||
dimension.add_block({
|
||||
x: x + dx,
|
||||
y: y + height + dy,
|
||||
z: z + dz,
|
||||
id: leaves_block,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TREE_THRESHOLD: Record<Biome, number> = {
|
||||
forest: 0.5,
|
||||
jungle: 0.3,
|
||||
taiga: 0.6,
|
||||
plains: 0.95,
|
||||
desert: 1,
|
||||
tundra: 1,
|
||||
savanna: 0.65,
|
||||
swamp: 0.5,
|
||||
snow: 0.8,
|
||||
};
|
||||
|
||||
function should_place_tree(feature_noise: NoiseFunction2D, biome: Biome, x: number, z: number) {
|
||||
const n = feature_noise(x * 0.1, z * 0.1);
|
||||
return n > (TREE_THRESHOLD[biome] ?? 0.8);
|
||||
}
|
||||
|
||||
interface SeedNoises {
|
||||
height_noise: NoiseFunction2D;
|
||||
temp_noise: NoiseFunction2D;
|
||||
moisture_noise: NoiseFunction2D;
|
||||
feature_noise: NoiseFunction2D;
|
||||
ore_noises: NoiseFunction3D[];
|
||||
}
|
||||
|
||||
// building the permutation tables is expensive, only do it once per seed
|
||||
const noise_cache = new Map<string, SeedNoises>();
|
||||
|
||||
function get_noises(seed: string): SeedNoises {
|
||||
let noises = noise_cache.get(seed);
|
||||
if (!noises) {
|
||||
noises = {
|
||||
height_noise: create_noise_2d(new Alea(seed + "_height")),
|
||||
temp_noise: create_noise_2d(new Alea(seed + "_temp")),
|
||||
moisture_noise: create_noise_2d(new Alea(seed + "_moisture")),
|
||||
feature_noise: create_noise_2d(new Alea(seed + "_feature")),
|
||||
ore_noises: ORES.map((ore) => create_noise_3d(new Alea(seed + "_" + ore.id))),
|
||||
};
|
||||
noise_cache.set(seed, noises);
|
||||
}
|
||||
return noises;
|
||||
}
|
||||
|
||||
export function generate_chunk(dimension: BlockSink, cx: number, cz: number, seed = "seed") {
|
||||
const { height_noise, temp_noise, moisture_noise, feature_noise, ore_noises } = get_noises(seed);
|
||||
// seeded per chunk so every client generates the exact same terrain
|
||||
const rng = new Alea(`${seed}_chunk_${cx}_${cz}`);
|
||||
|
||||
const biome_scale = 0.003;
|
||||
const terrain_scale = 0.01;
|
||||
|
||||
const tree_map: boolean[][] = Array.from({ length: CHUNK_SIZE }, () => Array(CHUNK_SIZE).fill(false));
|
||||
|
||||
for (let x = 0; x < CHUNK_SIZE; x++) {
|
||||
for (let z = 0; z < CHUNK_SIZE; z++) {
|
||||
const wx = cx * CHUNK_SIZE + x;
|
||||
const wz = cz * CHUNK_SIZE + z;
|
||||
|
||||
const temp = temp_noise(wx * biome_scale, wz * biome_scale);
|
||||
const moisture = moisture_noise(wx * biome_scale, wz * biome_scale);
|
||||
const biome = get_biome(temp, moisture);
|
||||
|
||||
const height_noise_value = fractal_noise(height_noise, wx * terrain_scale, wz * terrain_scale);
|
||||
const base_height = (height_noise_value + 1) * 15 + 50;
|
||||
const height = get_terrain_height(base_height, biome, wx, wz, height_noise);
|
||||
|
||||
const surface_block = get_surface_block(biome);
|
||||
|
||||
for (let y = 0; y <= height; y++) {
|
||||
let block = "bworld:stone";
|
||||
|
||||
if (y < height - 3) {
|
||||
for (let i = 0; i < ORES.length; i++) {
|
||||
const ore = ORES[i];
|
||||
|
||||
if (y >= ore.min_y && y <= ore.max_y) {
|
||||
const noise = ore_noises[i](
|
||||
wx * ore.scale,
|
||||
y * ore.scale,
|
||||
wz * ore.scale,
|
||||
);
|
||||
|
||||
if (noise > ore.threshold) {
|
||||
block = ore.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (y === height) {
|
||||
block = surface_block;
|
||||
} else if (y > height - 4) {
|
||||
block = "bworld:dirt";
|
||||
}
|
||||
|
||||
if (biome === "swamp" && y === height && rng.next() < 0.2) {
|
||||
block = "bworld:water";
|
||||
}
|
||||
|
||||
dimension.add_block({ x: wx, y, z: wz, id: block });
|
||||
}
|
||||
|
||||
if (should_place_tree(feature_noise, biome, wx, wz) && can_place_tree(tree_map, x, z)) {
|
||||
place_tree(dimension, rng, wx, height + 1, wz, biome);
|
||||
tree_map[x][z] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface RawChunk {
|
||||
// numeric block ids, only what this chunk generated itself
|
||||
blocks: Uint32Array;
|
||||
// blocks it generated in other chunks (tree leaves), flattened as x, y, z, numeric id
|
||||
spills: Int32Array;
|
||||
}
|
||||
|
||||
// generates one chunk on its own. neighbors' spills get merged in by whoever assembles the world:
|
||||
// a chunk's own blocks always win and spills only fill air, so the result doesn't depend on load order
|
||||
export function generate_raw_chunk(
|
||||
chunk_x: number,
|
||||
chunk_z: number,
|
||||
seed: string,
|
||||
block_ids: Record<string, number>,
|
||||
): RawChunk {
|
||||
const blocks = new Uint32Array(CHUNK_SIZE * CHUNK_SIZE * CHUNK_HEIGHT);
|
||||
const spills: number[] = [];
|
||||
|
||||
generate_chunk(
|
||||
{
|
||||
add_block(block) {
|
||||
const nid = block_ids[block.id];
|
||||
if (nid === undefined || block.y < 0 || block.y >= CHUNK_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
const block_chunk_x = Math.floor(block.x / CHUNK_SIZE);
|
||||
const block_chunk_z = Math.floor(block.z / CHUNK_SIZE);
|
||||
if (block_chunk_x !== chunk_x || block_chunk_z !== chunk_z) {
|
||||
spills.push(block.x, block.y, block.z, nid);
|
||||
return;
|
||||
}
|
||||
const lx = block.x - chunk_x * CHUNK_SIZE;
|
||||
const lz = block.z - chunk_z * CHUNK_SIZE;
|
||||
blocks[block.y * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx] = nid;
|
||||
},
|
||||
},
|
||||
chunk_x,
|
||||
chunk_z,
|
||||
seed,
|
||||
);
|
||||
|
||||
return { blocks, spills: new Int32Array(spills) };
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
// how item stacks are saved and sent over the network
|
||||
export interface ItemData {
|
||||
id: string;
|
||||
count: number;
|
||||
data?: unknown;
|
||||
}
|
||||
|
||||
export class ItemStack<T = unknown | undefined> {
|
||||
type_id: string;
|
||||
amount: number;
|
||||
max_amount: number;
|
||||
data?: T;
|
||||
|
||||
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;
|
||||
this.data = undefined;
|
||||
const item_info = EverythingRegistry.get<ItemRegistry>("items", type_id);
|
||||
if (item_info?.on_create) {
|
||||
item_info.on_create(this);
|
||||
}
|
||||
}
|
||||
|
||||
clone(): ItemStack {
|
||||
const item = new ItemStack(this.type_id, this.amount, this.max_amount);
|
||||
item.data = structuredClone(this.data);
|
||||
return item;
|
||||
}
|
||||
|
||||
to_data(): ItemData {
|
||||
return this.data === undefined
|
||||
? { id: this.type_id, count: this.amount }
|
||||
: { id: this.type_id, count: this.amount, data: this.data };
|
||||
}
|
||||
|
||||
static from_data(data: ItemData): ItemStack {
|
||||
const item = new ItemStack(data.id, data.count);
|
||||
if (data.data !== undefined) {
|
||||
item.data = data.data;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
export class ContainerSlot {
|
||||
#item_stack: ItemStack | undefined;
|
||||
|
||||
has_item() {
|
||||
return this.#item_stack !== undefined;
|
||||
}
|
||||
|
||||
set_item(item_stack: ItemStack | undefined) {
|
||||
if ((item_stack?.amount ?? 0) <= 0) {
|
||||
item_stack = undefined;
|
||||
}
|
||||
this.#item_stack = item_stack;
|
||||
}
|
||||
|
||||
get_item() {
|
||||
return this.#item_stack;
|
||||
}
|
||||
|
||||
get type_id() {
|
||||
return this.#item_stack?.type_id;
|
||||
}
|
||||
|
||||
set amount(new_amount: number) {
|
||||
if (this.#item_stack) {
|
||||
this.#item_stack.amount = new_amount;
|
||||
if (this.#item_stack.amount <= 0) {
|
||||
this.#item_stack = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get amount(): number | undefined {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// returns how many items didn't fit
|
||||
add_item(item_stack: ItemStack): number {
|
||||
for (const slot of this.#slots.filter((slot) => slot.has_item())) {
|
||||
const slot_item = slot.get_item()!;
|
||||
if (slot_item.type_id === item_stack.type_id) {
|
||||
const missing = slot_item.max_amount - slot_item.amount;
|
||||
const adding = Math.min(missing, item_stack.amount);
|
||||
slot_item.amount += adding;
|
||||
item_stack.amount -= adding;
|
||||
if (item_stack.amount === 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const slot of this.#slots) {
|
||||
if (!slot.has_item()) {
|
||||
slot.set_item(item_stack);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// TODO: drop item on ground
|
||||
return item_stack.amount;
|
||||
}
|
||||
|
||||
get_item(slot: number): ItemStack | undefined {
|
||||
return this.#slots[slot]?.get_item();
|
||||
}
|
||||
|
||||
get_slot(slot: number): ContainerSlot {
|
||||
return this.#slots[slot];
|
||||
}
|
||||
|
||||
set_item(slot: number, item: ItemStack | undefined) {
|
||||
this.#slots[slot].set_item(item);
|
||||
}
|
||||
|
||||
to_data(): (ItemData | null)[] {
|
||||
return this.#slots.map((slot) => slot.get_item()?.to_data() ?? null);
|
||||
}
|
||||
|
||||
load(data: (ItemData | null)[]) {
|
||||
for (let i = 0; i < this.size; i += 1) {
|
||||
const item = data[i];
|
||||
this.#slots[i].set_item(item ? ItemStack.from_data(item) : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
for (const slot of this.#slots) {
|
||||
slot.set_item(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the item a player is carrying around with the mouse in an inventory screen
|
||||
export interface Cursor {
|
||||
item: ItemStack | undefined;
|
||||
}
|
||||
|
||||
export const LEFT_CLICK = 0;
|
||||
export const RIGHT_CLICK = 2;
|
||||
|
||||
// what clicking a normal slot does, the server runs this for real and clients run it to predict
|
||||
export function click_slot(container: Container, index: number, cursor: Cursor, button: number) {
|
||||
if (button === LEFT_CLICK) {
|
||||
left_click(container, index, cursor);
|
||||
} else if (button === RIGHT_CLICK) {
|
||||
right_click(container, index, cursor);
|
||||
}
|
||||
if (cursor.item && cursor.item.amount <= 0) {
|
||||
cursor.item = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function swap_with_cursor(container: Container, index: number, cursor: Cursor) {
|
||||
const slot = container.get_slot(index);
|
||||
const original = cursor.item;
|
||||
cursor.item = slot.get_item();
|
||||
slot.set_item(original);
|
||||
}
|
||||
|
||||
function left_click(container: Container, index: number, cursor: Cursor) {
|
||||
const holding = cursor.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) {
|
||||
swap_with_cursor(container, index, cursor);
|
||||
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
|
||||
swap_with_cursor(container, index, cursor);
|
||||
}
|
||||
|
||||
function right_click(container: Container, index: number, cursor: Cursor) {
|
||||
const holding = cursor.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;
|
||||
}
|
||||
|
||||
// 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
|
||||
swap_with_cursor(container, index, cursor);
|
||||
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);
|
||||
|
||||
const picked_up = slot_item.clone();
|
||||
picked_up.amount = original_amount - half;
|
||||
cursor.item = picked_up;
|
||||
|
||||
slot.amount = half;
|
||||
}
|
||||
|
||||
// output slots (furnace result, crafting result) can only be taken from, all at once
|
||||
// returns whether it was taken
|
||||
export function take_output(item: ItemStack, cursor: Cursor): boolean {
|
||||
const holding = cursor.item;
|
||||
if (!holding) {
|
||||
cursor.item = item.clone();
|
||||
return true;
|
||||
}
|
||||
if (holding.type_id === item.type_id && holding.max_amount - holding.amount >= item.amount) {
|
||||
holding.amount += item.amount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:axe", {
|
||||
texture_id: "bworld:axe",
|
||||
tool_type: "axe",
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:coal", {
|
||||
texture_id: "bworld:coal",
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:copper_ingot", {
|
||||
texture_id: "bworld:copper_ingot",
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:gold_ingot", {
|
||||
texture_id: "bworld:gold_ingot",
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:hoe", {
|
||||
texture_id: "bworld:hoe",
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:iron_ingot", {
|
||||
texture_id: "bworld:iron_ingot",
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import "./watering_can.ts";
|
||||
import "./axe.ts";
|
||||
import "./pickaxe.ts";
|
||||
import "./hoe.ts";
|
||||
import "./coal.ts";
|
||||
import "./tin_ingot.ts";
|
||||
import "./iron_ingot.ts";
|
||||
import "./copper_ingot.ts";
|
||||
import "./gold_ingot.ts";
|
||||
import "./stick.ts";
|
||||
import "./wood_pickaxe.ts";
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:pickaxe", {
|
||||
texture_id: "bworld:pickaxe",
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:stick", {
|
||||
texture_id: "bworld:stick",
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:tin_ingot", {
|
||||
texture_id: "bworld:tin_ingot",
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
export interface WateringCanData {
|
||||
water: number;
|
||||
max_water: number;
|
||||
}
|
||||
|
||||
EverythingRegistry.register<ItemRegistry<WateringCanData>>("items", "bworld:watering_can", {
|
||||
texture_id: "bworld:watering_can",
|
||||
on_create(item) {
|
||||
item.data = { water: 0, max_water: 32 };
|
||||
},
|
||||
get_lore(item) {
|
||||
return `Water: ${item.data?.water}/${item.data?.max_water}`;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
|
||||
EverythingRegistry.register<ItemRegistry>("items", "bworld:wood_pickaxe", {
|
||||
texture_id: "bworld:wood_pickaxe",
|
||||
tool_type: "pickaxe",
|
||||
});
|
||||
+50
-3
@@ -1,4 +1,6 @@
|
||||
// messages sent between the client and the server, as json over a websocket
|
||||
import type { Faces } from "./constants.ts";
|
||||
import type { ItemData } from "./inventory.ts";
|
||||
|
||||
export const AIR_ID = "bworld:air";
|
||||
|
||||
@@ -15,19 +17,64 @@ export interface PlayerInfo {
|
||||
// x, y, z, block id
|
||||
export type BlockChange = [number, number, number, string];
|
||||
|
||||
// the containers a client can see and click. "screen" is whatever the open server screen shows
|
||||
export type ContainerKey = "inventory" | "crafting" | "screen";
|
||||
|
||||
export const CRAFTING_RESULT_SLOT = 9;
|
||||
|
||||
// a screen the server opens, drawn below the player's inventory and hotbar
|
||||
export interface ScreenLayout {
|
||||
// height of the screen's own area, in slots
|
||||
rows: number;
|
||||
// x and y in slots, can be fractional
|
||||
// output slots can only be taken from, like the furnace result
|
||||
slots: { index: number; x: number; y: number; output?: boolean }[];
|
||||
// progress bars filled from properties[value] / properties[max]
|
||||
bars: {
|
||||
x: number;
|
||||
y: number;
|
||||
value: string;
|
||||
max: string;
|
||||
direction: "up" | "right";
|
||||
empty_texture: string;
|
||||
full_texture: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
// clients send what the player is trying to do, the server decides what happens
|
||||
export type ClientMessage =
|
||||
| { type: "hello"; name: string }
|
||||
| { type: "move"; x: number; y: number; z: number; yaw: number; pitch: number }
|
||||
| { type: "set_block"; x: number; y: number; z: number; id: string }
|
||||
| { type: "break_block"; x: number; y: number; z: number }
|
||||
// right click on a block: interact with it, or place the held block against `face`
|
||||
| { type: "use_block"; x: number; y: number; z: number; face: Faces }
|
||||
| { type: "select_slot"; slot: number }
|
||||
| { type: "click"; container: ContainerKey; index: number; button: number }
|
||||
// closes the open screen, including the player's own inventory screen
|
||||
| { type: "close_screen" }
|
||||
| { type: "chat"; text: string };
|
||||
|
||||
export type ServerMessage =
|
||||
| { type: "welcome"; id: string; seed: string; players: PlayerInfo[]; changes: BlockChange[] }
|
||||
| {
|
||||
type: "welcome";
|
||||
id: string;
|
||||
seed: string;
|
||||
players: PlayerInfo[];
|
||||
changes: BlockChange[];
|
||||
spawn: { x: number; y: number; z: number; yaw: number; pitch: number };
|
||||
selected_slot: number;
|
||||
}
|
||||
| { type: "player_join"; player: PlayerInfo }
|
||||
| { type: "player_leave"; id: string }
|
||||
| { type: "player_move"; id: string; x: number; y: number; z: number; yaw: number; pitch: number }
|
||||
// also sent to the player who caused it, which corrects anything their client predicted wrong
|
||||
| { type: "set_block"; x: number; y: number; z: number; id: string }
|
||||
| { type: "chat"; from?: string; text: string };
|
||||
| { type: "chat"; from?: string; text: string }
|
||||
| { type: "container"; container: ContainerKey; items: (ItemData | null)[] }
|
||||
| { type: "cursor"; item: ItemData | null }
|
||||
| { type: "open_screen"; layout: ScreenLayout; properties: Record<string, number> }
|
||||
| { type: "screen_properties"; properties: Record<string, number> }
|
||||
| { type: "close_screen" };
|
||||
|
||||
export const MAX_NAME_LENGTH = 16;
|
||||
export const MAX_CHAT_LENGTH = 256;
|
||||
|
||||
+6
-9
@@ -1,5 +1,4 @@
|
||||
import { AssetManager } from "../client/assets.ts";
|
||||
import { ID_MASK, SpriteRegion, STATE_SHIFT } from "./constants.ts";
|
||||
import { ID_MASK, STATE_SHIFT } from "./constants.ts";
|
||||
import { BlockRegistry, EverythingRegistry, ItemRegistry } from "./everything_registry.ts";
|
||||
|
||||
export function point_inside_rec(
|
||||
@@ -16,13 +15,6 @@ export function point_inside_rec(
|
||||
point_y < rec_y + rec_h;
|
||||
}
|
||||
|
||||
type TexturesInfo = Record<string, SpriteRegion>;
|
||||
|
||||
export function get_sprite_region(id: string): SpriteRegion {
|
||||
const textures_info = AssetManager.instance.get<TexturesInfo>("bworld:textures_info");
|
||||
return textures_info?.[id] ?? { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
export function distance_point_rectangle(px: number, py: number, sqx: number, sqy: number, sqw: number, sqh: number) {
|
||||
const x0 = sqx;
|
||||
const y0 = sqy;
|
||||
@@ -106,3 +98,8 @@ function compile_block_states(block_info: BlockRegistry) {
|
||||
return { name: s.name, mask, shift };
|
||||
});
|
||||
}
|
||||
|
||||
// numeric so looking chunks up doesnt allocate a string every time, fine for |x|, |z| < 32768
|
||||
export function chunk_key(x: number, z: number) {
|
||||
return (x + 32768) * 65536 + (z + 32768);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user