Dragonfly Documentation

repository·master·Indexed 21 days ago

https://github.com/df-mc/dragonfly

An asynchronous Minecraft: Bedrock Edition server written in Go, designed primarily as a library for building and extending custom server software. It features a scalable architecture and a robust command system supporting custom parameters, target selectors, and the Runnable interface for command implementation.

Tokens
5.8K
Snippets
20
Records
29
Agent score
74%

What's inside dragonfly

  1. Overview of Dragonfly

    master
    Dragonfly is a heavily asynchronous server software for Minecraft: Bedrock Edition written in Go. It is designed for scalability and simplicity, and unlike many other Minecraft server implementations, it is primarily intended to be used as a library that developers extend rather than just a standalone executable.
  2. Run Dragonfly from the latest source commit

    master

    If you want to run the latest development version of Dragonfly directly, clone the repository and use go run on the main.go file.

    git clone https://github.com/df-mc/dragonfly
    cd dragonfly
    go run main.go
  3. Install Dragonfly as a library

    master

    To use Dragonfly as a library within your own Go project, initialize your module and use go get to fetch the Dragonfly package. This is the recommended way to extend the server with your own custom logic.

    go mod init github.com/user/module
    go get github.com/df-mc/dragonfly
  4. Use Target selectors to reference entities and players

    master

    Commands can use special selector syntax to target entities or players. These are parsed into a []Target slice. Supported selectors include:

    • @p: The nearest player to the command source.
    • @e: All entities.
    • @a: All players.
    • @s: The entity that executed the command (the source).
    • @r: A random player.
    • [name]: A specific player by their name (case-insensitive).

    Note: Selectors starting with @ require a valid context to resolve distances or entity lists.

  5. Define optional parameters in commands

    master

    Dragonfly supports optional parameters using the Optional[T] type. To ensure the command parser works correctly, optional parameters must always be placed at the end of the struct fields. If a non-optional parameter follows an optional one, the command registration will panic.

    Example of a valid struct:

    type Example struct {
        Required string
        Optional string `cmd:"opt"` // This is fine
    }

    Example of an invalid struct:

    type Example struct {
        Optional string
        Required string // This will cause a panic during cmd.New()
    }
  6. Initialize and start a Dragonfly server

    master

    To create and run a Dragonfly server, use server.New() to instantiate a server with a default configuration, then call Listen() to start the server's listeners. Once Listen() has been called, you can use Accept() to begin accepting player connections.

    To ensure the server saves data properly when the application exits, call CloseOnProgramEnd() to register signal handlers for SIGINT and SIGTERM.

    import "github.com/df-mc/dragonfly/server"
    
    func main() {
    	srv := server.New()
    	srv.CloseOnProgramEnd()
    	srv.Listen()
    
    	// Accept players
    	for p := range srv.Accept() {
    		// Handle player p
    	}
    }
  7. Configure parameter names and suffixes using `cmd` tags

    master

    You can customize how command parameters appear in the CLI by using the cmd struct tag on your command struct fields. The tag format is cmd:"name,suffix".

    • Name: The name used in the command usage/help text.
    • Suffix: An optional string appended to the parameter name.

    If the cmd tag is omitted, the system defaults to using the field's name.

    type TeleportCommand struct {
        // Uses field name 'X' in help text
        X int
    
        // Uses 'coord' in help text
        Y int `cmd:"coord"`
    
        // Uses 'pos' with suffix 'val' in help text
        Z int `cmd:"pos,val"` 
    }
  8. Retrieve errors and messages from Output

    master

    To inspect the results of a command execution, use these methods on the Output object:

    • Errors() []error: Returns a slice of all errors added to the output.
    • ErrorCount() int: Returns the number of errors recorded.
    • Messages() []fmt.Stringer: Returns a slice of all success messages recorded.
    • MessageCount() int: Returns the number of success messages recorded.
  9. Implement custom command parameters with the Parameter interface

    master

    If you need a parameter type that isn't a primitive (like int, string, or bool), you can implement the Parameter interface. This allows you to define custom parsing logic and a human-readable type name for command usage documentation.

    To implement Parameter, you must provide:

    1. Parse(line *Line, v reflect.Value) error: Logic to extract and parse arguments from the command line into the provided reflect value.
    2. Type() string: A string representing the type name (e.g., "Coordinate") used in help text and client-side command suggestions.
    type MyCustomParam struct{}
    
    func (p MyCustomParam) Parse(line *Line, v reflect.Value) error {
        // Implement parsing logic here
        return nil
    }
    
    func (p MyCustomParam) Type() string {
        return "MyCustomType"
    }
  10. List all registered commands

    master

    Use Commands() to retrieve a map of all currently registered commands. The map is indexed by the specific alias or name used during registration.

    allCommands := Commands()
    for alias, command := range allCommands {
        fmt.Printf("Registered command: %s\n", alias)
    }
  11. Look up a command by its alias

    master

    Use ByAlias(alias string) to retrieve a registered command using its primary name or any of its defined aliases. It returns the Command and a boolean indicating whether the command was found.

    cmd, ok := ByAlias("mc")
    if !ok {
        // Handle command not found
    }
  12. Accept incoming players with Accept()

    master

    The Accept() method returns an iterator (iter.Seq[*player.Player]) that yields players as they join the server.

    Important Concurrency Rules:

    1. Thread Safety: The loop body runs on the player's world owner. Blocking inside this loop will stall that specific world.
    2. Deadlocks: Calling world.Call, world.CallEntity, world.CallRef, or Task.Wait for the same owner inside the loop can cause deadlocks.
    3. Lifetime: A player object p is only guaranteed to be valid within the scope of the for loop iteration. If you need to reference a player outside the loop, use p.H() with player.Do to schedule work.

    Incorrect Usage (leads to invalid references):

    for p := range srv.Accept() {
        go func() {
            // p is no longer valid here!
        }()
    }

    Correct Usage (for long-running tasks):

    for p := range srv.Accept() {
        handle := p.H()
        go func() {
            // Use player.Do with the handle to work outside the loop
            player.Do(handle, func(tx *world.Tx, p *player.Player) (bool, error) {
                // p is valid here
                return false, nil
            })
        }()
    }
    for p := range srv.Accept() {
    	// p is valid here
    	go func() {
    		// p is no longer valid here
    	}()
    }