Introduction

Mcrizzledizzle is an incredibly shitty yet somehow performant take on the classic Minecraft server.

Here you can find information about writing plugins and configuring the server.

Configuration

The config file is a regular toml file that is placed in the same directory that the program is run from. There's an example in the repo where all the available parameters have been set.

The config is split into two parts, one relating to server operations and another relating to world operations.

[server]

Under server you can set the port, motd and name of the server.

name = "server of ire"
motd = "Message of the day" # There's a 64 character limit on these so be careful (including colour escapes)
port = 25565 # default mc port
max_players = 255 # 255 is the maximum number

[world]

For the world you can set the path and size (for generation) of the world.

world = "world.wrld" # This is a custom world format that will not work with other servers

# Generator settings
size_x = 64
size_y = 32
size_z = 64

Installing Extensions

Extensions are distributed as rhai files. As with all software that you install on your computer, it's important to make sure that you trust the program that you're running as they are quite powerful.

Extensions go into the auto-generated extension folder. Some official examples can be found in the extensions folder on the repository:

  • fill.rhai - A worldedit like fill command
  • ping-pong.rhai - A very basic command, responds when you using /ping
  • utils.rhai - A set of utilities used for development of the extension interface

To see what commands are available you can use the /help command. To see what extensions are installed and their versions you can use the /extensions command.

Other extensions

There are some third-party extensions that are officially endorsed by mcrizzledizzle.

Extensions

This guide is intended for experienced programmers, it is not a step by step guide. If in doubt check the official plugins.

The extensions interface uses the rhai programming language with some custom functionality to provide a full set of instructions for making extensions.

Extension Structure

All extensions must provide a metadata() and init(players, world) function. mcrizzledizzle uses these internally to populate information about the extension as well as register any commands or event listeners that the extension might want to use.

Example:

#![allow(unused)]

fn main() {
fn metadata() {
  Metadata("My Awesome Plugin's name!", "My Plugin's (less awesome) author", Version("1.0.0"))
}

fn init(players, world) {
  ...
}
}

The Metadata struct expects a name string, description string and valid Version(). The Version should be a valid semantic version otherwise the plugin will not load correctly.

Types

This contains a big list of structs and their available functions to call from rhai.

Metadata

This type tells mcrizzledizzle important imformation about the plugin.

The struct signature looks like this:

#![allow(unused)]
fn main() {
struct Metadata {
  name: String,
  author: String,
  version: Version,
};
}

It should be used inside the metadata function like so:

#![allow(unused)]
fn main() {
fn metadata() {
  Metadata("Example Name", "Example Description", Version("1.0.0"))
}
}

Version

This type is stored internally as a complete semantic version, an easy constructor is provided that converts a valid semver to a set of parts, providing a display function.

Struct signature:

#![allow(unused)]
fn main() {
struct Version {
  major: u16,
  minor: u16,
  patch: u16,
  prerelease: String,
  build: String,
};
}

Usually it would be used along with Metadata to setup an extension.

Player

The player struct probably shouldn't be modified by plugins, in any case, here's the struct definition:

#![allow(unused)]
fn main() {
struct Player {
  id: u8,
};
}

PlayersWrapper

The PlayersWrapper struct wraps around the server's Player Data mutex. It provides a friendly interface for extensions.

#![allow(unused)]
fn main() {
struct PlayersWrapper(Arc<Mutex<[Player; 255]>>);
}

You cannot instantiate a PlayersWrapper yourself, it is passed as the first argument of init() and can then be called upon from there.

Implementations

send_message

#![allow(unused)]
fn main() {
fn send_message(
  self,
  player: u8,
  message: String,
)
}

This function expects a player id and a message to be passed as arguments. Keep in mind that the length limit for messages is 64 characters.

send_all

#![allow(unused)]
fn main() {
fn send_all(self, message: String)
}

This function is like send_message except it sends the message to all connected players.

username

#![allow(unused)]
fn main() {
fn username(self, player: u8)
}

This function gets the username of a player from their id.

WorldWrapper

The WorldWrapper struct wraps around the server's World Data mutex. It provides a friendly interface for extensions.

#![allow(unused)]
fn main() {
struct WorldWrapper(Arc<Mutex<World>>);
}

You cannot instantiate a WorldWrapper yourself, it is passed as the second argument of init() and can be called upon from there.

Implementations

set_block

#![allow(unused)]
fn main() {
fn set_block(
  self,
  players_wrapper: PlayersWrapper,
  position: Vec3,
  block_type: u8,
)
}

This functions sets a block at the desired position. Since it uses the player data internally it requires the PlayersWrapper.

Context

The context is a struct that you can create to tell the server about what your plugin wants to do. You must create one if you want to register commands or add event listeners.

#![allow(unused)]
fn main() {
struct Context {
  commands: HashMap<String, FnPtr>,
  event_listener: HashMap<EventType, FnPtr>,
};
}

Implementations

register_command

#![allow(unused)]
fn main() {
fn register_command(&mut self, name: String, callback: FnPtr)
}

This is how you can register commands on the server. Your `callback`` likely should be a closure as you almost certainly want to capture information from the environment.

add_event_listener

#![allow(unused)]
fn main() {
fn add_event_listener(&mut self, event: &str, callback: FnPtr)
}

This is how event listeners are created. `callback`` should probably be a closure because you almost certainly want information from the environment. The currently available event types are:

  • "block_break" This is fired when a black is broken. This is interruptible.
  • "player_leave" This is fired when a player leaves the server. This is not interruptible.

Vec3

Vec3s are used to store positions of blocks and players.

#![allow(unused)]
fn main() {
struct Vec3 {
  pub x: i16,
  pub y: i16,
  pub z: i16,
}
}

You can create one using the constructor:

#![allow(unused)]
fn main() {
Vec3(x: i64, y: i64, z: i64)
}

Event

Events are structs that tell the extension about changes in server or player state.

#![allow(unused)]
fn main() {
struct Event {
  player: u8,
  position: Vec3,
  selected_block: u8,
  is_cancelled: bool,
}
}

Only certain parts of the struct are used for certain events. Since events are only returned to their corresponding handler, the event type is not provided in the struct. Available event types are:

  • "block_break" This is fired when a black is broken. This is interruptible.
  • "player_leave" This is fired when a player leaves the server. This is not interruptible.

Implementations

cancel

#![allow(unused)]
fn main() {
fn cancel(&mut self)
}

This sets is_cancelled to true, cancelling interruptible events, like block breaking.

Functions

These functions can be called directly from an extension. They aren't tied to any structure.

Logging

These functions all go directly to the server logs, as you'd expect. They all take a message and handle the rest for you.

info

Signature:

#![allow(unused)]
fn main() {
fn info(msg: String)
}

warn

Signature:

#![allow(unused)]
fn main() {
fn warn(msg: String)
}

error

Signature:

#![allow(unused)]
fn main() {
fn error(msg: String)
}

debug

Signature:

#![allow(unused)]
fn main() {
fn debug(msg: String)
}