253 lines
7.6 KiB
TypeScript
253 lines
7.6 KiB
TypeScript
// what server scripts get, see "Server scripts" in MODS.md
|
|
import type { EventSignal, Face, Id, ItemStack, ModInfo, Position } from "./shared.ts";
|
|
|
|
export type { EventSignal, Face, Id, ItemStack, ModInfo, Position } from "./shared.ts";
|
|
|
|
export interface ServerContext {
|
|
mod: ModInfo;
|
|
components: ComponentRegistry; // only during setup
|
|
commands: CommandRegistry; // only during setup
|
|
events: { before: ServerBeforeEvents; after: ServerAfterEvents };
|
|
system: System;
|
|
world: ServerWorld;
|
|
players: PlayerList;
|
|
containers: ContainerApi;
|
|
recipes: RecipeApi;
|
|
ui: ServerUi;
|
|
net: ServerNet;
|
|
storage: ModStorage;
|
|
log(...args: unknown[]): void;
|
|
}
|
|
|
|
// components
|
|
|
|
export interface BlockRef {
|
|
readonly id: Id;
|
|
readonly x: number;
|
|
readonly y: number;
|
|
readonly z: number;
|
|
// tile data, any json value. saved with the world, never sent to clients
|
|
// deno-lint-ignore no-explicit-any
|
|
data: any;
|
|
}
|
|
|
|
// P is the component's params from the block json
|
|
// deno-lint-ignore no-explicit-any
|
|
export interface BlockComponent<P = any> {
|
|
on_create?(block: BlockRef, params: P): void;
|
|
on_break?(block: BlockRef, params: P, player: Player | undefined): void;
|
|
on_click?(block: BlockRef, params: P, player: Player): void;
|
|
// return true if it did something, so no block gets placed
|
|
on_interact?(block: BlockRef, params: P, player: Player): boolean;
|
|
on_tick?(block: BlockRef, params: P, dt: number): void;
|
|
on_second?(block: BlockRef, params: P, dt: number): void;
|
|
}
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
export interface ItemComponent<P = any> {
|
|
on_create?(item: ItemStack, params: P): void;
|
|
get_lore?(item: ItemStack, params: P): string;
|
|
on_use?(item: ItemStack, params: P, player: Player): void;
|
|
}
|
|
|
|
export interface ComponentRegistry {
|
|
register_block<P>(id: Id, component: BlockComponent<P>): void;
|
|
register_item<P>(id: Id, component: ItemComponent<P>): void;
|
|
}
|
|
|
|
// commands
|
|
|
|
export interface Command {
|
|
description: string;
|
|
usage: string;
|
|
run(args: string[], player: Player): void;
|
|
}
|
|
|
|
export interface CommandRegistry {
|
|
register(name: string, command: Command): void;
|
|
}
|
|
|
|
// events
|
|
|
|
export interface Cancelable {
|
|
cancel: boolean;
|
|
}
|
|
|
|
export interface BlockBreakEvent {
|
|
readonly player: Player;
|
|
readonly block: BlockRef;
|
|
readonly item: ItemStack | undefined;
|
|
}
|
|
|
|
export interface BlockPlaceEvent {
|
|
readonly player: Player;
|
|
readonly block: { readonly id: Id } & Position;
|
|
readonly face: Face;
|
|
readonly item: ItemStack | undefined;
|
|
}
|
|
|
|
export interface BlockInteractEvent {
|
|
readonly player: Player;
|
|
readonly block: BlockRef;
|
|
readonly item: ItemStack | undefined;
|
|
}
|
|
|
|
export interface ChatSendEvent {
|
|
readonly player: Player;
|
|
message: string;
|
|
}
|
|
|
|
export interface ServerBeforeEvents {
|
|
block_break: EventSignal<BlockBreakEvent & Cancelable>;
|
|
block_place: EventSignal<BlockPlaceEvent & Cancelable>;
|
|
block_interact: EventSignal<BlockInteractEvent & Cancelable>;
|
|
chat_send: EventSignal<ChatSendEvent & Cancelable>;
|
|
}
|
|
|
|
export interface ServerAfterEvents {
|
|
block_break: EventSignal<BlockBreakEvent>;
|
|
block_place: EventSignal<BlockPlaceEvent>;
|
|
block_interact: EventSignal<BlockInteractEvent>;
|
|
chat_send: EventSignal<Readonly<ChatSendEvent>>;
|
|
player_join: EventSignal<{ readonly player: Player }>;
|
|
player_leave: EventSignal<{ readonly player: Player }>;
|
|
server_start: EventSignal<Record<never, never>>;
|
|
tick: EventSignal<{ readonly dt: number }>;
|
|
}
|
|
|
|
// world and players
|
|
|
|
export interface ServerWorld {
|
|
get_block(x: number, y: number, z: number): Id | undefined; // undefined when the chunk isn't loaded
|
|
set_block(x: number, y: number, z: number, id: Id): boolean; // runs on_break / on_create, synced to everyone
|
|
get_state(x: number, y: number, z: number, name: string): number | undefined;
|
|
set_state(x: number, y: number, z: number, name: string, value: number): boolean;
|
|
get_block_data<T>(x: number, y: number, z: number): T | undefined;
|
|
is_loaded(x: number, z: number): boolean;
|
|
readonly seed: string;
|
|
}
|
|
|
|
export interface Player {
|
|
readonly id: string;
|
|
readonly name: string;
|
|
readonly position: Readonly<Position>;
|
|
readonly inventory: Container; // 36 slots, hotbar is 0-8
|
|
readonly selected_slot: number;
|
|
readonly held_item: ItemStack | undefined;
|
|
give_item(id: Id, count?: number, data?: unknown): void;
|
|
send_message(text: string): void;
|
|
teleport(x: number, y: number, z: number): void;
|
|
}
|
|
|
|
export interface PlayerList {
|
|
all(): Player[];
|
|
get(id: string): Player | undefined;
|
|
by_name(name: string): Player | undefined;
|
|
}
|
|
|
|
export interface System {
|
|
run_timeout(fn: () => void, ticks: number): number;
|
|
run_interval(fn: () => void, ticks: number): number;
|
|
clear_run(handle: number): void;
|
|
readonly current_tick: number;
|
|
}
|
|
|
|
// small key value store per mod, saved with the world
|
|
export interface ModStorage {
|
|
get<T>(key: string): T | undefined;
|
|
set(key: string, value: unknown): void; // json values only
|
|
delete(key: string): void;
|
|
}
|
|
|
|
// containers and recipes
|
|
|
|
export interface Container {
|
|
readonly id: string;
|
|
readonly size: number;
|
|
get(slot: number): ItemStack | undefined;
|
|
set(slot: number, item: ItemStack | undefined): void;
|
|
add(item: ItemStack): ItemStack | undefined; // returns what didn't fit
|
|
on_change(fn: (slot: number) => void): () => void;
|
|
}
|
|
|
|
export interface ContainerApi {
|
|
create(size: number): Container; // saved with the world
|
|
get(id: string): Container | undefined;
|
|
delete(id: string): void;
|
|
}
|
|
|
|
export interface RecipeApi {
|
|
furnace_result(input: Id): { output: ItemStack; cook_time: number } | undefined;
|
|
fuel_value(item: Id): number; // burn time in ticks, 0 if it isn't fuel
|
|
is_fuel(item: Id): boolean;
|
|
is_smeltable(item: Id): boolean;
|
|
}
|
|
|
|
// guis
|
|
|
|
export interface ActionForm {
|
|
title: string;
|
|
body?: string;
|
|
buttons: { text: string; icon?: Id }[];
|
|
}
|
|
|
|
export interface MessageForm {
|
|
title: string;
|
|
body: string;
|
|
buttons: [string, string];
|
|
}
|
|
|
|
export type FormField =
|
|
| { type: "toggle"; label: string; default?: boolean }
|
|
| { type: "slider"; label: string; min: number; max: number; step?: number; default?: number }
|
|
| { type: "dropdown"; label: string; options: string[]; default?: number }
|
|
| { type: "text"; label: string; placeholder?: string; default?: string; max_length?: number };
|
|
|
|
export interface ModalForm {
|
|
title: string;
|
|
fields: FormField[];
|
|
}
|
|
|
|
export type FormResult<T> = ({ canceled: true } & Partial<T>) | ({ canceled: false } & T);
|
|
|
|
export type SlotFilter = "smeltable" | "fuel" | ((item: ItemStack) => boolean);
|
|
|
|
export interface ContainerScreenOptions {
|
|
title: string;
|
|
container: Container;
|
|
// x and y in slot units
|
|
layout: { slot: number; x: number; y: number; filter?: SlotFilter; output_only?: boolean }[];
|
|
player_inventory?: boolean;
|
|
bars?: { id: string; x: number; y: number; texture: Id }[];
|
|
labels?: { x: number; y: number; property: string }[];
|
|
}
|
|
|
|
export interface ScreenHandle<Props = unknown> {
|
|
readonly player: Player;
|
|
set_property(id: string, value: number): void;
|
|
update(props: Props): void; // custom screens only
|
|
close(): void;
|
|
on_close(fn: () => void): void;
|
|
}
|
|
|
|
export interface ServerUi {
|
|
message_form(player: Player, form: MessageForm): Promise<FormResult<{ selection: 0 | 1 }>>;
|
|
action_form(player: Player, form: ActionForm): Promise<FormResult<{ selection: number }>>;
|
|
modal_form(player: Player, form: ModalForm): Promise<FormResult<{ values: (boolean | number | string)[] }>>;
|
|
open_container(player: Player, options: ContainerScreenOptions): ScreenHandle;
|
|
open_screen<Props>(
|
|
player: Player,
|
|
id: Id,
|
|
props: Props,
|
|
options?: { containers?: Record<string, Container> },
|
|
): ScreenHandle<Props>;
|
|
}
|
|
|
|
// mod channels
|
|
|
|
export interface ServerNet {
|
|
on<T = unknown>(channel: Id, handler: (player: Player, data: T) => void): void;
|
|
send(player: Player, channel: Id, data: unknown): void;
|
|
broadcast(channel: Id, data: unknown): void;
|
|
}
|