up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled

This commit is contained in:
2026-07-09 08:33:57 +08:00
commit 26ed99fda6
845 changed files with 75419 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
package playerdb
import "github.com/df-mc/dragonfly/server/entity/effect"
func effectsToData(effects []effect.Effect) []jsonEffect {
data := make([]jsonEffect, len(effects))
for key, eff := range effects {
id, ok := effect.ID(eff.Type())
if !ok {
continue
}
data[key] = jsonEffect{
ID: id,
Duration: eff.Duration(),
Level: eff.Level(),
Ambient: eff.Ambient(),
ParticlesHidden: eff.ParticlesHidden(),
Infinite: eff.Infinite(),
}
}
return data
}
func dataToEffects(data []jsonEffect) []effect.Effect {
effects := make([]effect.Effect, len(data))
for i, d := range data {
e, ok := effect.ByID(d.ID)
if !ok {
continue
}
switch eff := e.(type) {
case effect.LastingType:
switch {
case d.Ambient:
effects[i] = effect.NewAmbient(eff, d.Level, d.Duration)
case d.Infinite:
effects[i] = effect.NewInfinite(eff, d.Level)
default:
effects[i] = effect.New(eff, d.Level, d.Duration)
}
if d.ParticlesHidden {
effects[i] = effects[i].WithoutParticles()
}
default:
effects[i] = effect.NewInstant(eff, d.Level)
}
}
return effects
}
+95
View File
@@ -0,0 +1,95 @@
package playerdb
import (
"bytes"
"github.com/df-mc/dragonfly/server/internal/nbtconv"
"github.com/df-mc/dragonfly/server/item"
"github.com/sandertv/gophertunnel/minecraft/nbt"
)
// InventoryData is a struct that contains all data of the player inventories.
type InventoryData struct {
// Items contains all the items in the player's main inventory.
// This excludes armour and offhand.
Items []item.Stack
// Boots, Leggings, Chestplate, Helmet are armour pieces that belong to the slot corresponding to the name.
Boots item.Stack
Leggings item.Stack
Chestplate item.Stack
Helmet item.Stack
// OffHand is what the player is carrying in their non-main hand, like a shield or arrows.
OffHand item.Stack
// MainHandSlot saves the slot in the hotbar that the player is currently switched to.
// Should be between 0-8.
MainHandSlot uint32
}
func invToData(data InventoryData) jsonInventoryData {
d := jsonInventoryData{
MainHandSlot: data.MainHandSlot,
OffHand: encodeItem(data.OffHand),
}
d.Items = encodeItems(data.Items)
d.Boots = encodeItem(data.Boots)
d.Leggings = encodeItem(data.Leggings)
d.Chestplate = encodeItem(data.Chestplate)
d.Helmet = encodeItem(data.Helmet)
return d
}
func dataToInv(data jsonInventoryData) InventoryData {
d := InventoryData{
MainHandSlot: data.MainHandSlot,
OffHand: decodeItem(data.OffHand),
Items: make([]item.Stack, 36),
}
decodeItems(data.Items, d.Items)
d.Boots = decodeItem(data.Boots)
d.Leggings = decodeItem(data.Leggings)
d.Chestplate = decodeItem(data.Chestplate)
d.Helmet = decodeItem(data.Helmet)
return d
}
func encodeItems(items []item.Stack) (encoded []jsonSlot) {
encoded = make([]jsonSlot, 0, len(items))
for slot, i := range items {
data := encodeItem(i)
if data == nil {
continue
}
encoded = append(encoded, jsonSlot{Slot: slot, Item: data})
}
return
}
func decodeItems(encoded []jsonSlot, items []item.Stack) {
for _, i := range encoded {
items[i.Slot] = decodeItem(i.Item)
}
}
func encodeItem(item item.Stack) []byte {
if item.Empty() {
return nil
}
var b bytes.Buffer
itemNBT := nbtconv.WriteItem(item, true)
encoder := nbt.NewEncoderWithEncoding(&b, nbt.LittleEndian)
err := encoder.Encode(itemNBT)
if err != nil {
return nil
}
return b.Bytes()
}
func decodeItem(data []byte) item.Stack {
var itemNBT map[string]any
decoder := nbt.NewDecoderWithEncoding(bytes.NewBuffer(data), nbt.LittleEndian)
err := decoder.Decode(&itemNBT)
if err != nil {
return item.Stack{}
}
return nbtconv.Item(itemNBT, nil)
}
+143
View File
@@ -0,0 +1,143 @@
package playerdb
import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/item/inventory"
"github.com/df-mc/dragonfly/server/player"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
"github.com/google/uuid"
"time"
)
func (p *Provider) fromJson(d jsonData, lookupWorld func(world.Dimension) *world.World) (player.Config, *world.World) {
dim, _ := world.DimensionByID(int(d.Dimension))
mode, _ := world.GameModeByID(int(d.GameMode))
conf := player.Config{
UUID: uuid.MustParse(d.UUID),
XUID: d.XUID,
Name: d.Username,
Position: d.Position,
Rotation: cube.Rotation{d.Yaw, d.Pitch},
Velocity: d.Velocity,
Health: d.Health,
MaxHealth: d.MaxHealth,
Food: d.Hunger,
FoodTick: d.FoodTick,
Exhaustion: d.ExhaustionLevel,
Saturation: d.SaturationLevel,
Experience: d.Experience,
AirSupply: d.AirSupply,
MaxAirSupply: d.MaxAirSupply,
EnchantmentSeed: d.EnchantmentSeed,
GameMode: mode,
Effects: dataToEffects(d.Effects),
FireTicks: d.FireTicks,
FallDistance: d.FallDistance,
Inventory: inventory.New(36, nil),
EnderChestInventory: inventory.New(27, nil),
OffHand: inventory.New(1, nil),
Armour: inventory.NewArmour(nil),
}
echest := make([]item.Stack, 27)
decodeItems(d.EnderChestInventory, echest)
invData := dataToInv(d.Inventory)
for slot, stack := range invData.Items {
_ = conf.Inventory.SetItem(slot, stack)
}
_ = conf.OffHand.SetItem(0, invData.OffHand)
conf.Armour.Set(invData.Helmet, invData.Chestplate, invData.Leggings, invData.Boots)
conf.HeldSlot = int(invData.MainHandSlot)
for slot, stack := range echest {
_ = conf.EnderChestInventory.SetItem(slot, stack)
}
return conf, lookupWorld(dim)
}
func (p *Provider) toJson(d player.Config, w *world.World) jsonData {
dim, _ := world.DimensionID(w.Dimension())
mode, _ := world.GameModeID(d.GameMode)
offHand, _ := d.OffHand.Item(0)
return jsonData{
UUID: d.UUID.String(),
Username: d.Name,
Position: d.Position,
Velocity: d.Velocity,
Yaw: d.Rotation.Yaw(),
Pitch: d.Rotation.Pitch(),
Health: d.Health,
MaxHealth: d.MaxHealth,
Hunger: d.Food,
FoodTick: d.FoodTick,
ExhaustionLevel: d.Exhaustion,
SaturationLevel: d.Saturation,
Experience: d.Experience,
AirSupply: d.AirSupply,
MaxAirSupply: d.MaxAirSupply,
EnchantmentSeed: d.EnchantmentSeed,
GameMode: uint8(mode),
Effects: effectsToData(d.Effects),
FireTicks: d.FireTicks,
FallDistance: d.FallDistance,
Inventory: invToData(InventoryData{
Items: d.Inventory.Slots(),
Boots: d.Armour.Boots(),
Leggings: d.Armour.Leggings(),
Chestplate: d.Armour.Chestplate(),
Helmet: d.Armour.Helmet(),
OffHand: offHand,
MainHandSlot: uint32(d.HeldSlot),
}),
EnderChestInventory: encodeItems(d.EnderChestInventory.Slots()),
Dimension: uint8(dim),
}
}
type jsonData struct {
UUID string
XUID string
Username string
Position, Velocity mgl64.Vec3
Yaw, Pitch float64
Health, MaxHealth float64
Hunger int
FoodTick int
ExhaustionLevel, SaturationLevel float64
EnchantmentSeed int64
Experience int
AirSupply, MaxAirSupply int
GameMode uint8
Inventory jsonInventoryData
EnderChestInventory []jsonSlot
Effects []jsonEffect
FireTicks int64
FallDistance float64
Dimension uint8
}
type jsonInventoryData struct {
Items []jsonSlot
Boots []byte
Leggings []byte
Chestplate []byte
Helmet []byte
OffHand []byte
MainHandSlot uint32
}
type jsonSlot struct {
Item []byte
Slot int
}
type jsonEffect struct {
ID int
Level int
Duration time.Duration
Ambient bool
ParticlesHidden bool
Infinite bool
}
+61
View File
@@ -0,0 +1,61 @@
package playerdb
import (
"encoding/json"
"github.com/df-mc/dragonfly/server/player"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/goleveldb/leveldb"
"github.com/df-mc/goleveldb/leveldb/opt"
"github.com/google/uuid"
"os"
)
// Provider is a player data provider that uses a LevelDB database to store data. The data passed on
// will first be converted to make sure it can be marshaled into JSON. This JSON (in bytes) will then
// be stored in the database under a key that is the byte representation of the player's UUID.
type Provider struct {
db *leveldb.DB
}
// NewProvider creates a new player data provider that saves and loads data using
// a LevelDB database.
func NewProvider(path string) (*Provider, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
_ = os.Mkdir(path, 0777)
}
db, err := leveldb.OpenFile(path, &opt.Options{Compression: opt.SnappyCompression})
if err != nil {
return nil, err
}
return &Provider{db: db}, nil
}
// Save ...
func (p *Provider) Save(id uuid.UUID, d player.Config, w *world.World) error {
b, err := json.Marshal(p.toJson(d, w))
if err != nil {
return err
}
return p.db.Put(id[:], b, nil)
}
// Load ...
func (p *Provider) Load(id uuid.UUID, world func(world.Dimension) *world.World) (player.Config, *world.World, error) {
b, err := p.db.Get(id[:], nil)
if err != nil {
return player.Config{}, nil, err
}
var d jsonData
err = json.Unmarshal(b, &d)
if err != nil {
return player.Config{}, nil, err
}
conf, w := p.fromJson(d, world)
return conf, w, nil
}
// Close ...
func (p *Provider) Close() error {
return p.db.Close()
}