Dragonfly Documentation
repository·master·Indexed 21 days ago
https://github.com/df-mc/dragonflyAn 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.
What's inside dragonfly
- 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.
Run Dragonfly from the latest source commit
masterIf you want to run the latest development version of Dragonfly directly, clone the repository and use
go runon themain.gofile.git clone https://github.com/df-mc/dragonfly cd dragonfly go run main.goInstall Dragonfly as a library
masterTo use Dragonfly as a library within your own Go project, initialize your module and use
go getto 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/dragonflyUse Target selectors to reference entities and players
masterCommands can use special selector syntax to target entities or players. These are parsed into a
[]Targetslice. 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.Define optional parameters in commands
masterDragonfly 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() }Initialize and start a Dragonfly server
masterTo create and run a Dragonfly server, use
server.New()to instantiate a server with a default configuration, then callListen()to start the server's listeners. OnceListen()has been called, you can useAccept()to begin accepting player connections.To ensure the server saves data properly when the application exits, call
CloseOnProgramEnd()to register signal handlers forSIGINTandSIGTERM.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 } }Configure parameter names and suffixes using `cmd` tags
masterYou can customize how command parameters appear in the CLI by using the
cmdstruct tag on your command struct fields. The tag format iscmd:"name,suffix".- Name: The name used in the command usage/help text.
- Suffix: An optional string appended to the parameter name.
If the
cmdtag 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"` }Retrieve errors and messages from Output
masterTo inspect the results of a command execution, use these methods on the
Outputobject: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.
Implement custom command parameters with the Parameter interface
masterIf you need a parameter type that isn't a primitive (like
int,string, orbool), you can implement theParameterinterface. This allows you to define custom parsing logic and a human-readable type name for command usage documentation.To implement
Parameter, you must provide:Parse(line *Line, v reflect.Value) error: Logic to extract and parse arguments from the command line into the provided reflect value.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" }List all registered commands
masterUse
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) }Look up a command by its alias
masterUse
ByAlias(alias string)to retrieve a registered command using its primary name or any of its defined aliases. It returns theCommandand a boolean indicating whether the command was found.cmd, ok := ByAlias("mc") if !ok { // Handle command not found }Accept incoming players with Accept()
masterThe
Accept()method returns an iterator (iter.Seq[*player.Player]) that yields players as they join the server.Important Concurrency Rules:
- Thread Safety: The loop body runs on the player's world owner. Blocking inside this loop will stall that specific world.
- Deadlocks: Calling
world.Call,world.CallEntity,world.CallRef, orTask.Waitfor the same owner inside the loop can cause deadlocks. - Lifetime: A player object
pis only guaranteed to be valid within the scope of theforloop iteration. If you need to reference a player outside the loop, usep.H()withplayer.Doto 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 }() }