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
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:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user