Test mod
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
// what client scripts get, see "Client scripts" and "GUIs" in MODS.md
|
||||
import type { Id, KeyCode, ModInfo, Position } from "./shared.ts";
|
||||
|
||||
export type { Id, ItemStack, KeyCode, ModInfo, Position } from "./shared.ts";
|
||||
|
||||
export interface ClientContext {
|
||||
mod: ModInfo;
|
||||
ui: ClientUi;
|
||||
hud: HudRegistry;
|
||||
input: { bind(id: Id, default_key: KeyCode, on_press: () => void): void };
|
||||
net: ClientNet;
|
||||
player: { readonly name: string; readonly position: Readonly<Position> };
|
||||
// read only, what this client sees
|
||||
world: { get_block(x: number, y: number, z: number): Id | undefined };
|
||||
log(...args: unknown[]): void;
|
||||
}
|
||||
|
||||
export interface ModScreen<Props = unknown> {
|
||||
on_open?(): void;
|
||||
on_tick?(dt: number): void;
|
||||
on_render(g: Graphics): void;
|
||||
// the server sent new props for this screen
|
||||
on_props?(props: Props): void;
|
||||
on_close?(): void;
|
||||
// return true to keep the screen open when escape is pressed
|
||||
on_escape?(): boolean;
|
||||
}
|
||||
|
||||
export type Color = [number, number, number, number];
|
||||
|
||||
// immediate mode drawing, like the debug ui, styled with assets/sprites/ui.png
|
||||
export interface Graphics {
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
readonly mouse: { x: number; y: number; down: boolean; pressed: boolean };
|
||||
rect(x: number, y: number, w: number, h: number, color?: Color): void;
|
||||
panel(x: number, y: number, w: number, h: number): void;
|
||||
text(text: string, x: number, y: number, options?: { scale?: number; color?: Color }): void;
|
||||
measure_text(text: string, scale?: number): number;
|
||||
texture(id: Id, x: number, y: number, w: number, h: number): void;
|
||||
item(id: Id, x: number, y: number, count?: number): void;
|
||||
clip(x: number, y: number, w: number, h: number, draw: () => void): void;
|
||||
button(label: string, x: number, y: number, w: number, h: number): boolean;
|
||||
text_input(id: string, x: number, y: number, w: number): string;
|
||||
slider(id: string, x: number, y: number, w: number, min: number, max: number): number;
|
||||
// server synced slots, same rules as container screens
|
||||
slots(container: string, layout: { slot: number; x: number; y: number }[], x: number, y: number): void;
|
||||
key_pressed(key: KeyCode): boolean;
|
||||
}
|
||||
|
||||
export interface ClientUi {
|
||||
register_screen<Props>(id: Id, create: (props: Props) => ModScreen<Props>): void;
|
||||
// open a screen that doesn't involve the server, like a settings page
|
||||
open<Props>(id: Id, props: Props): void;
|
||||
}
|
||||
|
||||
export interface HudRegistry {
|
||||
register(id: Id, element: { on_render(g: Graphics): void }): void;
|
||||
}
|
||||
|
||||
export interface ClientNet {
|
||||
on<T = unknown>(channel: Id, handler: (data: T) => void): void;
|
||||
send(channel: Id, data: unknown): void;
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// types every side of a mod shares. these are the api mods see, not the engine's own classes:
|
||||
// items are { id, count, data } here, like on the network
|
||||
|
||||
export type Id = string;
|
||||
|
||||
export interface ItemStack {
|
||||
readonly id: Id;
|
||||
count: number;
|
||||
// any json value, set by server scripts
|
||||
data?: unknown;
|
||||
}
|
||||
|
||||
export interface ModInfo {
|
||||
readonly id: string;
|
||||
readonly version: string;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export type Face = "west" | "east" | "bottom" | "top" | "north" | "south";
|
||||
|
||||
// KeyboardEvent.code values, like "KeyE" or "Digit1"
|
||||
export type KeyCode = string;
|
||||
|
||||
export type Unsubscribe = () => void;
|
||||
|
||||
export interface EventSignal<T> {
|
||||
subscribe(handler: (event: T) => void): Unsubscribe;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// what worldgen scripts get, see "World generation" in MODS.md. runs in chunk workers and must be deterministic
|
||||
import type { Id } from "./shared.ts";
|
||||
|
||||
export type { Id } from "./shared.ts";
|
||||
|
||||
export interface WorldgenContext {
|
||||
register_terrain(id: Id, generate: (chunk: TerrainChunk) => void): void;
|
||||
register_feature(id: Id, generate: (chunk: FeatureChunk) => void): void;
|
||||
}
|
||||
|
||||
export interface FeatureChunk {
|
||||
// chunk coordinates
|
||||
readonly x: number;
|
||||
readonly z: number;
|
||||
readonly seed: string;
|
||||
// seeded from the seed, chunk and feature id
|
||||
readonly rng: { next(): number };
|
||||
// create_noise_2d(new Alea(seed + "_" + name)), cached per seed and name
|
||||
noise_2d(name: string): (x: number, z: number) => number;
|
||||
noise_3d(name: string): (x: number, y: number, z: number) => number;
|
||||
// surface height and biome, inside this chunk only
|
||||
height_at(x: number, z: number): number;
|
||||
biome_at(x: number, z: number): Id;
|
||||
get_block(x: number, y: number, z: number): Id | undefined; // inside this chunk only
|
||||
set_block(x: number, y: number, z: number, id: Id): void; // up to one chunk away, like trees
|
||||
}
|
||||
|
||||
export interface TerrainChunk extends FeatureChunk {
|
||||
set_height(x: number, z: number, height: number): void;
|
||||
set_biome(x: number, z: number, biome: Id): void;
|
||||
}
|
||||
Reference in New Issue
Block a user