Day night cycle

This commit is contained in:
2026-09-26 11:03:07 -03:00
parent 8f32a59bd6
commit 8b549162c4
16 changed files with 244 additions and 6 deletions
+44
View File
@@ -26,6 +26,7 @@ import {
} from "$/common/protocol.ts";
import type { AtlasListing, ModListing, RecipeBook } from "$/common/mod_loader.ts";
import type { WorldgenSetup } from "$/common/generation.ts";
import { CYCLE_TICKS, format_clock, time_of_day, TIMES_OF_DAY } from "$/common/time.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";
@@ -62,6 +63,8 @@ export interface GameHost {
export interface SavedWorld {
version: 3;
seed: string;
// world time in ticks, 0 in saves from before the day and night cycle
time?: number;
changes: BlockChange[];
tiles: SavedTile[];
// every ctx.containers container, by id
@@ -124,6 +127,7 @@ export class GameServer {
this.world = new ServerWorld(saved?.seed ?? default_seed, mods.worldgen);
this.mods.storage = saved?.mod_storage ?? {};
this.world.load_changes(saved?.changes ?? []);
this.world.time = saved?.time ?? 0;
for (const [id, items] of Object.entries(saved?.containers ?? {})) {
const container = new Container(items.length);
container.load(items);
@@ -188,7 +192,11 @@ export class GameServer {
tick() {
this.#tick += 1;
this.world.time += 1;
const second = this.#tick % TICKS_PER_SECOND === 0;
if (second) {
this.#broadcast({ type: "time", time: this.world.time });
}
for (const tile of [...this.world.tiles.values()]) {
if (tile.mod_data === undefined) {
@@ -236,6 +244,7 @@ export class GameServer {
const saved: SavedWorld = {
version: 3,
seed: this.world.seed,
time: this.world.time,
changes: this.world.all_changes(),
tiles: [...this.world.tiles.values()].map((tile) => ({
id: tile.id,
@@ -342,6 +351,12 @@ export class GameServer {
this.#broadcast({ type: "player_move", id: player.id, x, y, z, yaw: player.yaw, pitch: player.pitch }, player);
}
set_time(time: number) {
this.world.time = time;
this.world.dirty = true;
this.#broadcast({ type: "time", time });
}
// 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);
@@ -511,6 +526,7 @@ export class GameServer {
entities: [...this.#entities.values()].map((entity) => entity.info()),
spawn: { x: player.x, y: player.y, z: player.z, yaw: player.yaw, pitch: player.pitch },
selected_slot: player.selected_slot,
time: this.world.time,
});
this.#players.set(conn, player);
this.#sync(player);
@@ -820,6 +836,10 @@ export class GameServer {
this.give(player, item_id, amount);
return;
}
if (command === "time") {
this.#time_command(player, args);
return;
}
if (command === "tps") {
if (!this.loop) {
this.#send(player, { type: "chat", text: "The game loop isn't running" });
@@ -841,6 +861,30 @@ export class GameServer {
this.#send(player, { type: "chat", text: `Unknown command /${command}` });
}
// /time shows the time, /time set <ticks or day, noon, sunset, night, midnight> changes it, /time add <ticks>
#time_command(player: ServerPlayer, [action, value]: string[]) {
const usage = "Usage: /time [set <ticks|day|noon|sunset|night|midnight> | add <ticks>]";
if (action === undefined) {
this.#send(player, { type: "chat", text: `${format_clock(this.world.time)} (tick ${this.world.time})` });
return;
}
const ticks = Number(value);
if (action === "set" && value !== undefined && value in TIMES_OF_DAY) {
// the next time it's that time of day, so days keep counting up
const now = this.world.time;
const target = now - time_of_day(now) + TIMES_OF_DAY[value];
this.set_time(target >= now ? target : target + CYCLE_TICKS);
} else if (action === "set" && Number.isInteger(ticks) && ticks >= 0) {
this.set_time(ticks);
} else if (action === "add" && Number.isInteger(ticks) && this.world.time + ticks >= 0) {
this.set_time(this.world.time + ticks);
} else {
this.#send(player, { type: "chat", text: usage });
return;
}
this.#broadcast({ type: "chat", text: `${player.name} set the time to ${format_clock(this.world.time)}` });
}
#close_screen(player: ServerPlayer) {
const screen = player.screen;
player.screen = undefined;