Server actually ticks
This commit is contained in:
@@ -25,6 +25,7 @@ import type { AtlasListing, ModListing, RecipeBook } from "$/common/mod_loader.t
|
||||
import type { WorldgenSetup } from "$/common/generation.ts";
|
||||
import { BLOCK_BEHAVIORS } from "./blocks.ts";
|
||||
import { ModRuntime, run_guarded } from "./mod_runtime.ts";
|
||||
import type { GameLoop } from "./game_loop.ts";
|
||||
import { consume_recipe_items, update_crafting_result } from "./crafting.ts";
|
||||
import { OpenScreen, SavedPlayer, ServerPlayer } from "./player.ts";
|
||||
import { ServerWorld, Tile } from "./world.ts";
|
||||
@@ -83,6 +84,8 @@ export class GameServer {
|
||||
#mods: GameMods;
|
||||
mods: ModRuntime;
|
||||
recipes: RecipeBook;
|
||||
// what runs tick(), for /tps. tests call tick() themselves
|
||||
loop?: GameLoop;
|
||||
|
||||
constructor(host: GameHost, save: string | undefined, default_seed: string, mods: GameMods) {
|
||||
this.#host = host;
|
||||
@@ -106,7 +109,8 @@ export class GameServer {
|
||||
this.#saved_players = saved?.players ?? {};
|
||||
this.world.dirty = saved?.version !== 2;
|
||||
|
||||
this.world.on_block_change = (x, y, z, id) => this.#broadcast({ type: "set_block", x, y, z, id });
|
||||
this.world.on_block_change = (x, y, z, id, state) =>
|
||||
this.#broadcast(state ? { type: "set_block", x, y, z, id, state } : { type: "set_block", x, y, z, id });
|
||||
}
|
||||
|
||||
// connections
|
||||
@@ -186,6 +190,7 @@ export class GameServer {
|
||||
}
|
||||
|
||||
this.mods.tick();
|
||||
this.mods.check_watched_containers();
|
||||
|
||||
for (const [conn, pending] of this.#pending) {
|
||||
if (this.#tick - pending.since > READY_TIMEOUT_TICKS) {
|
||||
@@ -257,7 +262,15 @@ export class GameServer {
|
||||
this.#broadcast({ type: "player_move", id: player.id, x, y, z, yaw: player.yaw, pitch: player.pitch }, player);
|
||||
}
|
||||
|
||||
set_block(x: number, y: number, z: number, id: string, player?: ServerPlayer) {
|
||||
// changes a block's state bits without anything else happening, see ServerWorld.get_block_value
|
||||
set_block_state(x: number, y: number, z: number, state: number) {
|
||||
const id = this.world.get_block_id(x, y, z);
|
||||
if (id !== AIR_ID) {
|
||||
this.world.set_block(x, y, z, id, state);
|
||||
}
|
||||
}
|
||||
|
||||
set_block(x: number, y: number, z: number, id: string, player?: ServerPlayer, state?: number) {
|
||||
const old_id = this.world.get_block_id(x, y, z);
|
||||
if (old_id !== AIR_ID) {
|
||||
const components = this.mods.components_of(old_id).filter(({ component }) => component.on_break);
|
||||
@@ -282,7 +295,7 @@ export class GameServer {
|
||||
}
|
||||
}
|
||||
|
||||
this.world.set_block(x, y, z, id);
|
||||
this.world.set_block(x, y, z, id, state);
|
||||
|
||||
if (BLOCK_BEHAVIORS[id]?.create_tile) {
|
||||
this.get_or_create_tile(x, y, z);
|
||||
@@ -407,6 +420,14 @@ export class GameServer {
|
||||
this.#break_block(player, message.x, message.y, message.z);
|
||||
}
|
||||
break;
|
||||
case "hit_block":
|
||||
if (is_block_position(message.x, message.y, message.z)) {
|
||||
this.#hit_block(player, message.x, message.y, message.z);
|
||||
}
|
||||
break;
|
||||
case "use_item":
|
||||
this.#use_item(player);
|
||||
break;
|
||||
case "use_block":
|
||||
if (is_block_position(message.x, message.y, message.z) && faces.includes(message.face)) {
|
||||
this.#use_block(player, message.x, message.y, message.z, message.face);
|
||||
@@ -459,6 +480,40 @@ export class GameServer {
|
||||
this.mods.after.block_break.emit(event);
|
||||
}
|
||||
|
||||
#hit_block(player: ServerPlayer, x: number, y: number, z: number) {
|
||||
const info = this.world.get_block_info(x, y, z);
|
||||
if (!info || !this.#in_reach(player, x, y, z)) {
|
||||
return;
|
||||
}
|
||||
const components = this.mods.components_of(info.id).filter(({ component }) => component.on_click);
|
||||
if (components.length === 0) {
|
||||
return;
|
||||
}
|
||||
const block = this.mods.block_ref(x, y, z, info.id);
|
||||
const player_api = this.mods.player(player);
|
||||
for (const { mod, id, component, params } of components) {
|
||||
run_guarded(mod, `${id} on_click`, () => component.on_click!(block, params, player_api));
|
||||
}
|
||||
}
|
||||
|
||||
// the held item's on_use handlers, returns whether it has any
|
||||
#use_item(player: ServerPlayer): boolean {
|
||||
const held = player.held_item;
|
||||
if (!held) {
|
||||
return false;
|
||||
}
|
||||
const components = this.mods.item_components_of(held.type_id).filter(({ component }) => component.on_use);
|
||||
if (components.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const player_api = this.mods.player(player);
|
||||
const item = player_api.held_item!;
|
||||
for (const { mod, id, component, params } of components) {
|
||||
run_guarded(mod, `${id} on_use`, () => component.on_use!(item, params, player_api));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#use_block(player: ServerPlayer, x: number, y: number, z: number, face: Faces) {
|
||||
const offset = FACE_OFFSETS[face];
|
||||
const [tx, ty, tz] = [x + offset.x, y + offset.y, z + offset.z];
|
||||
@@ -495,6 +550,12 @@ export class GameServer {
|
||||
return;
|
||||
}
|
||||
|
||||
// an item that does something when used does that instead of being placed
|
||||
if (this.#use_item(player)) {
|
||||
this.#correct(player, tx, ty, tz);
|
||||
return;
|
||||
}
|
||||
|
||||
// place the held block against the face
|
||||
const held_slot = player.inventory.get_slot(player.selected_slot);
|
||||
const held = EverythingRegistry.get<ItemRegistry>("items", held_slot.type_id ?? "");
|
||||
@@ -596,6 +657,19 @@ export class GameServer {
|
||||
this.give(player, item_id, amount);
|
||||
return;
|
||||
}
|
||||
if (command === "tps") {
|
||||
if (!this.loop) {
|
||||
this.#send(player, { type: "chat", text: "The game loop isn't running" });
|
||||
return;
|
||||
}
|
||||
const { tps, mspt, skipped } = this.loop.stats;
|
||||
this.#send(player, {
|
||||
type: "chat",
|
||||
text: `${tps} ticks per second (aiming for ${TICKS_PER_SECOND}), ${mspt.toFixed(1)} ms per tick, ` +
|
||||
`${skipped} skipped since starting`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const mod_command = this.mods.commands.get(command);
|
||||
if (mod_command) {
|
||||
run_guarded(mod_command.mod, `/${command}`, () => mod_command.command.run(args, this.mods.player(player)));
|
||||
@@ -672,7 +746,7 @@ export class GameServer {
|
||||
|
||||
#replaceable(x: number, y: number, z: number) {
|
||||
const nid = this.world.get_block_nid(x, y, z);
|
||||
return nid === AIR || EverythingRegistry.get_by_id<BlockRegistry>("blocks", nid)?.id === "bworld:water";
|
||||
return nid === AIR || (EverythingRegistry.get_by_id<BlockRegistry>("blocks", nid)?.replaceable ?? false);
|
||||
}
|
||||
|
||||
#in_reach(player: ServerPlayer, x: number, y: number, z: number) {
|
||||
@@ -723,9 +797,6 @@ export class GameServer {
|
||||
}
|
||||
}
|
||||
|
||||
// ticks happen at a fixed rate
|
||||
export const TICK_MS = TICK_DELTA * 1000;
|
||||
|
||||
function is_number(value: unknown): value is number {
|
||||
return typeof value === "number" && Number.isFinite(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user