README
¶
bevi-df
A Bevi plugin that bridges Dragonfly with the Bevi runtime.
- Fast, typed, cancellable events for most Dragonfly
world.Handlerandplayer.Handlercallbacks - Automatic cancellation bridging: if any ECS system cancels a typed event, the underlying Dragonfly context is cancelled
- ECS resource for the running Dragonfly server and an ECS component wrapper for players
What you get
-
A Bevi Plugin that:
- Boots a Dragonfly server in PreStartup
- Bridges Dragonfly world/player events into a Bevi EventBus
- Exposes the running server as an ECS resource
- Tracks connected players, creating an ECS entity per player and emitting join/quit events
-
Typed events for:
- World: liquid flow/decay/harden, sounds, fire spread, block burn, crop trample, leaves decay, entity spawn/despawn, explosions, world close
- Player: movement, teleport, change world, toggle sneak/sprint, chat, hunger, heal/hurt/death/respawn, skin change, block break/place/pick, item use/release/consume, attacks, XP gain, punch air, sign edit, lectern page turn, item damage/pickup/drop, held slot change, transfer, command execution, diagnostics, join/quit
Quick start (example server)
Clone this repository and run the example server:
# From the repository root
cd example/server
# Generate Bevi glue (systems registration, queries, access metadata)
go generate ./...
# Run the server
go run .
Connect using a Minecraft Bedrock client to see:
- New players receive a welcome message; other players get a join broadcast
- Saying "count" responds with the number of online players
- Simple profanity filter blocks messages containing a placeholder “badword”
- Periodic “Players online: N” broadcast every 10s
- Attempts to break blocks are denied and cancelled
Install in your own module
Add Bevi and Bevi-DF:
go get github.com/oriumgames/bevi@latest
go get github.com/oriumgames/bevi-df@latest
Create an app, add the Dragonfly plugin, and wire your systems:
package main
//go:generate go run github.com/oriumgames/bevi/cmd/gen@latest
import (
"log/slog"
"github.com/df-mc/dragonfly/server"
"github.com/oriumgames/bevi"
"github.com/oriumgames/bevi-df/dragonfly"
)
func main() {
cfg, err := server.DefaultConfig().Config(slog.Default())
if err != nil {
panic(err)
}
bevi.NewApp().
AddPlugin(dragonfly.NewPlugin(cfg)). // start dragonfly + bridge events
AddSystems(Systems). // generated by the bevi generator
Run()
}
//bevi:system Update
func HelloOnJoin(r bevi.EventReader[dragonfly.PlayerJoin]) {
for ev := range r.Iter() {
ev.Player.Message("Welcome to the server!")
}
}
Generate and run:
go generate ./...
go run .
How it works
At a glance:
- The Bevi Plugin boots Dragonfly in the PreStartup stage and wires two handlers:
- A
worldHandlerthat translates world callbacks into typed Bevi events and cancels Dragonfly when ECS cancels the event - A
playerHandlerdoing the same for player callbacks; it also emits internal create/remove notifications
- A
- A PreUpdate system creates an ECS entity for each newly accepted player, maps it with
ecs.Map1[dragonfly.Player], and emitsPlayerJoin; it also handlesPlayerQuitby removing the entity - All user gameplay is modeled as Bevi systems:
- You consume typed events via
bevi.EventReader[T] - You can cancel them by calling
reader.Cancel() - You can also emit your own events with
bevi.EventWriter[T]
- You consume typed events via
- The Bevi scheduler runs systems in ordered, parallelizable batches and advances the event bus frame-by-frame
Cancellation bridging:
- Many Dragonfly callbacks accept a
*Contextthat can be cancelled to veto an action - The bridge emits a typed event and waits: if any ECS system calls
Cancel()on the reader during that frame, Bevi marks the event as cancelled - The bridge observes cancellation and calls
ctx.Cancel()on the Dragonfly side for that callback
Events reference (overview)
Events live under github.com/oriumgames/bevi-df/dragonfly and mirror Dragonfly handlers:
World (cancellable when noted):
- WorldLiquidFlow, WorldLiquidDecay, WorldLiquidHarden (cancellable)
- WorldSound (cancellable)
- WorldFireSpread, WorldBlockBurn, WorldCropTrample, WorldLeavesDecay (cancellable)
- WorldEntitySpawn, WorldEntityDespawn
- WorldExplosion (cancellable)
- WorldClose
Player (cancellable when noted):
- PlayerMove (cancellable), PlayerJump, PlayerTeleport (cancellable), PlayerChangeWorld
- PlayerToggleSprint (cancellable), PlayerToggleSneak (cancellable)
- PlayerChat (cancellable)
- PlayerFoodLoss (cancellable)
- PlayerHeal (cancellable), PlayerHurt (cancellable), PlayerDeath, PlayerRespawn
- PlayerSkinChange (cancellable), PlayerFireExtinguish
- PlayerStartBreak (cancellable), PlayerBlockBreak (cancellable), PlayerBlockPlace (cancellable), PlayerBlockPick (cancellable)
- PlayerItemUse (cancellable), PlayerItemUseOnBlock (cancellable), PlayerItemUseOnEntity (cancellable), PlayerItemRelease, PlayerItemConsume (cancellable)
- PlayerAttackEntity (cancellable), PlayerExperienceGain (cancellable), PlayerPunchAir (cancellable)
- PlayerSignEdit (cancellable), PlayerLecternPageTurn (cancellable)
- PlayerItemDamage (cancellable), PlayerItemPickup (cancellable), PlayerItemDrop (cancellable)
- PlayerHeldSlotChange
- PlayerTransfer (cancellable), PlayerCommandExecution (cancellable)
- PlayerDiagnostics
- PlayerJoin (emitted by the plugin on accept), PlayerQuit
Cancellation:
- To veto, call
reader.Cancel()when iterating a cancellable event type - The bridge propagates this to Dragonfly’s
*Contextfor the originating callback
Server resource and player wrapper
Resource:
- The plugin registers an ECS resource of type
dragonfly.Serverthat wraps Dragonfly’s*server.Serverand tracks players
Convenience methods:
Player(uuid.UUID) (*dragonfly.Player, bool)PlayerByName(string) (*dragonfly.Player, bool)PlayerByXUID(string) (*dragonfly.Player, bool)
Player component:
dragonfly.Playerwraps*player.Playerand stores itsecs.Entity- You can fetch all players via
ecs.Query1[dragonfly.Player]orecs.Filter1[dragonfly.Player]
License
MIT — see license.md.
Click to show internal directories.
Click to hide internal directories.