From 861f628f04a3c4c75815313da2dfcec0b1733ff6 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 30 Sep 2025 20:46:12 -0700 Subject: [PATCH 01/41] feat: implement nether portals --- server/block/fire.go | 25 ++- server/block/hash.go | 5 + server/block/model/portal.go | 27 +++ server/block/portal.go | 43 +++++ server/block/register.go | 3 + server/entity/travel.go | 131 +++++++++++++++ server/player/conf.go | 6 +- server/player/player.go | 8 +- server/world/portal/nether.go | 305 ++++++++++++++++++++++++++++++++++ server/world/portal/scan.go | 132 +++++++++++++++ server/world/world.go | 22 ++- 11 files changed, 694 insertions(+), 13 deletions(-) create mode 100644 server/block/model/portal.go create mode 100644 server/block/portal.go create mode 100644 server/entity/travel.go create mode 100644 server/world/portal/nether.go create mode 100644 server/world/portal/scan.go diff --git a/server/block/fire.go b/server/block/fire.go index da9dcf424..ff52408c6 100644 --- a/server/block/fire.go +++ b/server/block/fire.go @@ -3,13 +3,15 @@ package block //lint:file-ignore ST1022 Exported variables in this package have compiler directives. These variables are not otherwise exposed to users. import ( + "math/rand/v2" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/event" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/item/enchantment" "github.com/df-mc/dragonfly/server/world" - "math/rand/v2" - "time" + "github.com/df-mc/dragonfly/server/world/portal" ) // Fire is a non-solid block that can spread to nearby flammable blocks. @@ -272,6 +274,25 @@ func (f Fire) Start(tx *world.Tx, pos cube.Pos) { } } +// Place ... +func (Fire) Place(pos cube.Pos, w *world.World) bool { + portalActivated := false + for _, f := range cube.Faces() { + if portalActivated { + break + } + w.Exec(func(tx *world.Tx) { + if o, ok := tx.Block(pos.Side(f)).(Obsidian); ok && !o.Crying { + if p, ok := portal.NetherPortalFromPos(tx, pos); ok && p.Framed() && !p.Activated() { + p.Activate() + portalActivated = true + } + } + }) + } + return true +} + // allFire ... func allFire() (b []world.Block) { for i := 0; i < 16; i++ { diff --git a/server/block/hash.go b/server/block/hash.go index 1e47df7ce..41a1300c6 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -134,6 +134,7 @@ const ( hashPodzol hashPolishedBlackstoneBrick hashPolishedTuff + hashPortal hashPotato hashPrismarine hashPumpkin @@ -716,6 +717,10 @@ func (PolishedTuff) Hash() (uint64, uint64) { return hashPolishedTuff, 0 } +func (p Portal) Hash() (uint64, uint64) { + return hashPortal, uint64(p.Axis) +} + func (p Potato) Hash() (uint64, uint64) { return hashPotato, uint64(p.Growth) } diff --git a/server/block/model/portal.go b/server/block/model/portal.go new file mode 100644 index 000000000..ab489049d --- /dev/null +++ b/server/block/model/portal.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// Portal is a model used by portal blocks. +type Portal struct { + // Axis is the axis that the portal faces. + Axis cube.Axis +} + +// BBox ... +func (p Portal) BBox(cube.Pos, world.BlockSource) []cube.BBox { + min, max := mgl64.Vec3{0, 0, 0.375}, mgl64.Vec3{1, 1, 0.25} + if p.Axis == cube.Z { + min[0], min[2], max[0], max[2] = 0.375, 0, 0.25, 1 + } + return []cube.BBox{cube.Box(min[0], min[1], min[2], max[0], max[1], max[2])} +} + +// FaceSolid ... +func (Portal) FaceSolid(cube.Pos, cube.Face, world.BlockSource) bool { + return false +} diff --git a/server/block/portal.go b/server/block/portal.go new file mode 100644 index 000000000..0505dfe70 --- /dev/null +++ b/server/block/portal.go @@ -0,0 +1,43 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/portal" +) + +// Portal is the translucent part of the nether portal that teleports the player to and from the Nether. +type Portal struct { + transparent + + // Axis is the axis which the portal faces. + Axis cube.Axis +} + +// Model ... +func (p Portal) Model() world.BlockModel { + return model.Portal{Axis: p.Axis} +} + +// Portal ... +func (Portal) Portal() world.Dimension { + return world.Nether +} + +// HasLiquidDrops ... +func (p Portal) HasLiquidDrops() bool { + return false +} + +// EncodeBlock ... +func (p Portal) EncodeBlock() (string, map[string]interface{}) { + return "minecraft:portal", map[string]interface{}{"portal_axis": p.Axis.String()} +} + +// NeighbourUpdateTick ... +func (p Portal) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if n, ok := portal.NetherPortalFromPos(tx, pos); ok && (!n.Framed() || !n.Activated()) { + n.Deactivate() + } +} diff --git a/server/block/register.go b/server/block/register.go index 48ac9788a..ef290f13d 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -1,6 +1,7 @@ package block import ( + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" ) @@ -79,6 +80,8 @@ func init() { world.RegisterBlock(PackedIce{}) world.RegisterBlock(PackedMud{}) world.RegisterBlock(Podzol{}) + world.RegisterBlock(Portal{Axis: cube.X}) + world.RegisterBlock(Portal{Axis: cube.Z}) world.RegisterBlock(PolishedBlackstoneBrick{Cracked: true}) world.RegisterBlock(PolishedBlackstoneBrick{}) world.RegisterBlock(QuartzBricks{}) diff --git a/server/entity/travel.go b/server/entity/travel.go new file mode 100644 index 000000000..a94f0a490 --- /dev/null +++ b/server/entity/travel.go @@ -0,0 +1,131 @@ +package entity + +import ( + "math" + "sync" + "time" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/portal" + "github.com/go-gl/mathgl/mgl64" +) + +// TravelComputer handles the interdimensional travelling of an entity. +type TravelComputer struct { + // Instantaneous is a function that returns true if the entity given can travel instantly. + Instantaneous func() bool + + mu sync.RWMutex + start time.Time + awaitingTravel bool + travelling bool + timedOut bool +} + +// Traveller represents a world.Entity that can travel between dimensions. +type Traveller interface { + world.Entity + // Teleport teleports the entity to the position given. + Teleport(pos mgl64.Vec3) +} + +// portalBlock represents a block that can be used as a portal to travel between dimensions. +type portalBlock interface { + world.Block + // Portal returns the dimension that the portal leads to. + Portal() world.Dimension +} + +// TickTravelling checks if the player is colliding with a nether portal block. If so, it teleports the player +// to the other dimension after four seconds or instantly if instantaneous is true. +func (t *TravelComputer) TickTravelling(travel Traveller, tx *world.Tx) { + box := travel.H().Type().BBox(travel).Translate(travel.Position()).Grow(0.25) + + min, max := box.Min(), box.Max() + minX, minY, minZ := int(math.Floor(min[0])), int(math.Floor(min[1])), int(math.Floor(min[2])) + maxX, maxY, maxZ := int(math.Ceil(max[0])), int(math.Ceil(max[1])), int(math.Ceil(max[2])) + found, target := false, world.Dimension(nil) + for y := minY; y <= maxY; y++ { + for x := minX; x <= maxX; x++ { + for z := minZ; z <= maxZ; z++ { + pos := cube.Pos{x, y, z} + p, ok := tx.Block(pos).(portalBlock) + if !ok { + continue + } + for _, blockBox := range p.Model().BBox(pos, tx) { + if blockBox.Translate(pos.Vec3()).IntersectsWith(box) { + found, target = true, p.Portal() + break + } + } + } + } + } + + t.mu.Lock() + defer t.mu.Unlock() + if !found { + if t.travelling { + // Don't reset if we're travelling. + return + } + t.timedOut, t.awaitingTravel = false, false + return + } + + switch target { + case world.Nether: + if t.timedOut { + // Timed out, we can't travel through Nether portals. + return + } + if t.Instantaneous() || (t.awaitingTravel && time.Since(t.start) >= time.Second*4) { + t.mu.Unlock() + t.Travel(travel, tx.World(), tx.World().PortalDestination(world.Nether)) + t.mu.Lock() + } else if !t.awaitingTravel { + t.start, t.awaitingTravel = time.Now(), true + } + } +} + +// Travel moves the player to the given Nether or Overworld world and translates the player's current position based +// on the source world. +func (t *TravelComputer) Travel(e Traveller, source *world.World, destination *world.World) { + sourceDimension, targetDimension := source.Dimension(), destination.Dimension() + pos := cube.PosFromVec3(e.Position()) + if sourceDimension == world.Overworld { + pos = cube.Pos{pos.X() / 8, pos.Y() + sourceDimension.Range().Min(), pos.Z() / 8} + } else if sourceDimension == world.Nether { + pos = cube.Pos{pos.X() * 8, pos.Y() - targetDimension.Range().Min(), pos.Z() * 8} + } + + t.mu.Lock() + defer t.mu.Unlock() + t.travelling, t.timedOut, t.awaitingTravel = true, true, false + + go func() { + spawn := pos.Vec3Middle() + + source.Exec(func(tx *world.Tx) { + tx.RemoveEntity(e) + }) + + destination.Exec(func(tx *world.Tx) { + if netherPortal, ok := portal.FindOrCreateNetherPortal(tx, pos, 128); ok { + spawn = netherPortal.Spawn().Vec3Middle() + } + + tx.AddEntity(e.H()) + if ent, ok := e.H().Entity(tx); ok { + ent.(Traveller).Teleport(spawn) + } + }) + + t.mu.Lock() + defer t.mu.Unlock() + t.travelling = false + }() +} diff --git a/server/player/conf.go b/server/player/conf.go index 194156c0d..2b901cde3 100644 --- a/server/player/conf.go +++ b/server/player/conf.go @@ -1,6 +1,9 @@ package player import ( + "math/rand/v2" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/entity" "github.com/df-mc/dragonfly/server/entity/effect" @@ -11,8 +14,6 @@ import ( "github.com/go-gl/mathgl/mgl64" "github.com/google/uuid" "golang.org/x/text/language" - "math/rand/v2" - "time" ) // Config holds options that a Player can be created with. @@ -68,6 +69,7 @@ func (cfg Config) Apply(data *world.EntityData) { locale: conf.Locale, cooldowns: make(map[string]time.Time), mc: &entity.MovementComputer{Gravity: 0.08, Drag: 0.02, DragBeforeGravity: true}, + tc: &entity.TravelComputer{Instantaneous: func() bool { return cfg.GameMode == world.GameModeCreative }}, heldSlot: &slot, gameMode: conf.GameMode, skin: conf.Skin, diff --git a/server/player/player.go b/server/player/player.go index 3b7d61eb6..67fcbe73b 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2,8 +2,6 @@ package player import ( "fmt" - "github.com/df-mc/dragonfly/server/player/debug" - "github.com/df-mc/dragonfly/server/player/hud" "math" "math/rand/v2" "net" @@ -12,6 +10,9 @@ import ( "sync" "time" + "github.com/df-mc/dragonfly/server/player/debug" + "github.com/df-mc/dragonfly/server/player/hud" + "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" @@ -87,6 +88,7 @@ type playerData struct { enchantSeed int64 mc *entity.MovementComputer + tc *entity.TravelComputer collidedVertically, collidedHorizontally bool @@ -2460,6 +2462,8 @@ func (p *Player) Tick(tx *world.Tx, current int64) { } else { p.data.Vel = mgl64.Vec3{} } + + p.tc.TickTravelling(p, tx) } // tickAirSupply tick's the player's air supply, consuming it when underwater, and replenishing it when out of water. diff --git a/server/world/portal/nether.go b/server/world/portal/nether.go new file mode 100644 index 000000000..6b7846a50 --- /dev/null +++ b/server/world/portal/nether.go @@ -0,0 +1,305 @@ +package portal + +import ( + "math" + "math/rand" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/world" +) + +// Nether contains information about a nether portal structure. +type Nether struct { + w, h int + framed bool + axis cube.Axis + tx *world.Tx + spawnPos cube.Pos + positions []cube.Pos +} + +const ( + // minimumNetherPortalWidth, maximumNetherPortalWidth controls the minimum and maximum width of a portal. + minimumNetherPortalWidth, maximumNetherPortalWidth = 2, 21 + // minimumNetherPortalHeight, maximumNetherPortalHeight controls the minimum and maximum height of a portal. + minimumNetherPortalHeight, maximumNetherPortalHeight = 3, 21 + // minimumArea is the minimum area of a portal. + minimumArea = minimumNetherPortalWidth * minimumNetherPortalHeight +) + +// NetherPortalFromPos returns Nether portal information from a given position in the frame. +func NetherPortalFromPos(tx *world.Tx, pos cube.Pos) (Nether, bool) { + if tx.World().Dimension() == world.End { + // Don't waste our time; we can't make a portal in the end. + return Nether{}, false + } + + axis, positions, width, height, completed, ok := multiAxisScan(pos, tx, []string{ + "minecraft:air", + "minecraft:fire", + }) + if !ok { + axis, positions, width, height, completed, ok = multiAxisScan(pos, tx, []string{"minecraft:portal"}) + } + return Nether{ + w: width, h: height, + spawnPos: pos, + positions: positions, + framed: completed, + axis: axis, + tx: tx, + }, ok +} + +// FindOrCreateNetherPortal finds or creates a Nether portal at the given position. +func FindOrCreateNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { + n, ok := FindNetherPortal(tx, pos, radius) + if ok { + return n, true + } + return CreateNetherPortal(tx, pos) +} + +// portalBlock represents a block that can be used as a portal to travel between dimensions. +type portalBlock interface { + // Portal returns the dimension that the portal leads to. + Portal() world.Dimension +} + +// frameBlock represents a block that can be used as a frame for a Nether portal. +type frameBlock interface { + // Frame returns true if the block is used as a frame for the given dimension. + Frame(dimension world.Dimension) bool +} + +// FindNetherPortal searches a provided radius for a Nether portal. +func FindNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { + if tx.World().Dimension() == world.End { + // Don't waste our time - we can't make a portal in the end. + return Nether{}, false + } + + closestPos, closestDist, found := cube.Pos{}, math.MaxFloat64, false + for x := pos.X() - radius; x < pos.X()+radius; x++ { + for z := pos.Z() - radius; z < pos.Z()+radius; z++ { + r := tx.World().Dimension().Range() + for y := r.Max(); y >= r.Min(); y-- { + selectedPos := cube.Pos{x, y, z} + // Just check if it's a portal block, don't check destination dimension + if _, ok := tx.Block(selectedPos).(portalBlock); ok { + // Verify the portal is valid by checking if we can get portal info from it + if portal, portalOk := NetherPortalFromPos(tx, selectedPos); portalOk && portal.Framed() { + dist := selectedPos.Vec3().Sub(pos.Vec3()).Len() + if dist < closestDist { + closestDist, closestPos, found = dist, selectedPos, true + } + } + } + } + } + } + if !found { + // Don't waste our time if the search didn't work out. + return Nether{}, false + } + return NetherPortalFromPos(tx, closestPos) +} + +// CreateNetherPortal creates a Nether portal at the given position. +func CreateNetherPortal(tx *world.Tx, pos cube.Pos) (Nether, bool) { + if tx.World().Dimension() == world.End { + // You can't create a nether portal in the end. + return Nether{}, false + } + + resultPos, random, distance, a, r := pos, rand.Intn(4), -1.0, 0, tx.Range() + searchValidArea := func(directions int, valid func(pos cube.Pos, riv int, coEff1, coEff2 int) bool) { + for tempX := pos.X() - 16; tempX <= pos.X()+16; tempX++ { + offsetX := float64(tempX-pos.X()) + 0.5 + for tempZ := pos.Z() - 16; tempZ <= pos.Z()+16; tempZ++ { + offsetZ := float64(tempZ-pos.Z()) + 0.5 + for tempY := r.Max() - 1; tempY >= r.Min(); tempY-- { + entryPos := cube.Pos{tempX, tempY, tempZ} + if tx.Block(entryPos) != air() { + continue + } + + for tempY > r.Min() && tx.Block(entryPos.Side(cube.FaceDown)) == air() { + tempY-- + entryPos[1]-- + } + + for riv := random; riv < random+directions; riv++ { + coEff1 := riv % 2 + coEff2 := 1 - coEff1 + + if !valid(entryPos, riv, coEff1, coEff2) { + break + } + + offsetY := float64(tempY-pos.Y()) + 0.5 + newDist := offsetX*offsetX + offsetY*offsetY + offsetZ*offsetZ + if distance < 0.0 || newDist < distance { + distance = newDist + a = riv % directions + resultPos = cube.Pos{tempX, tempY, tempZ} + } + } + } + } + } + } + + // Search for a valid area in all four directions, adding some extra space for comfort. + searchValidArea(4, func(pos cube.Pos, riv int, coEff1, coEff2 int) bool { + if riv%4 >= 2 { + coEff1 = -coEff1 + coEff2 = -coEff2 + } + + for safeSpace1 := 0; safeSpace1 < 3; safeSpace1++ { + for safeSpace2 := -1; safeSpace2 < 3; safeSpace2++ { + for height := -1; height < 4; height++ { + b := tx.Block(cube.Pos{ + pos.X() + safeSpace2*coEff1 + safeSpace1*coEff2, + pos.Y() + height, + pos.Z() + safeSpace2*coEff2 - safeSpace1*coEff1, + }) + _, solid := b.Model().(model.Solid) + if height < 0 && !solid || height >= 0 && b != air() { + return false + } + } + } + } + return true + }) + + if distance < 0.0 { + // If we couldn't find a valid area under those specifications, we can search the two main directions instead, + // reducing comfort but at least allowing us to have a portal in the area. + searchValidArea(2, func(pos cube.Pos, riv int, coEff1, coEff2 int) bool { + for safeSpace := 0; safeSpace < 3; safeSpace++ { + for height := -1; height < 4; height++ { + b := tx.Block(cube.Pos{ + pos.X() + safeSpace*coEff1, + pos.Y() + height, + pos.Z() + safeSpace*coEff2, + }) + _, solid := b.Model().(model.Solid) + if height < 0 && !solid || height >= 0 && b != air() { + return false + } + } + } + return true + }) + } + + coEff1 := a % 2 + coEff2 := 1 - coEff1 + if a%4 >= 2 { + coEff1 = -coEff1 + coEff2 = -coEff2 + } + + axis := cube.X + if coEff1 == 0 { + axis = cube.Z + } + + if distance < 0.0 { + // If all else fails, we can simply create a floating platform in the void with the portal on it. + resultPos[1] = int(math.Min(math.Max(float64(resultPos[1]), 70), float64(r.Max()-10))) + for safeBeforeAfter := -1; safeBeforeAfter <= 1; safeBeforeAfter++ { + for safeWidth := 0; safeWidth < 2; safeWidth++ { + for height := -1; height < 3; height++ { + entryPos := cube.Pos{ + resultPos.X() + safeWidth*coEff1 + safeBeforeAfter*coEff2, + resultPos.Y() + height, + resultPos.Z() + safeWidth*coEff2 - safeBeforeAfter*coEff1, + } + + tx.SetBlock(entryPos, nil, nil) + if height < 0 { + tx.SetBlock(entryPos, obsidian(), nil) + } + } + } + } + } + + // Build the portal frame and activate it. + var positions []cube.Pos + for width := -1; width < 3; width++ { + for height := -1; height < 4; height++ { + entryPos := cube.Pos{ + resultPos.X() + width*coEff1, + resultPos.Y() + height, + resultPos.Z() + width*coEff2, + } + + if width == -1 || width == 2 || height == -1 || height == 3 { + tx.SetBlock(entryPos, obsidian(), nil) + continue + } + positions = append(positions, entryPos) + tx.SetBlock(entryPos, portal(axis), nil) + } + } + + return Nether{ + w: minimumNetherPortalWidth, + h: minimumNetherPortalHeight, + framed: true, + spawnPos: resultPos, + positions: positions, + axis: axis, + tx: tx, + }, true +} + +// Bounds ... +func (n Nether) Bounds() (int, int) { + return n.w, n.h +} + +// Activate ... +func (n Nether) Activate() { + for _, pos := range n.Positions() { + n.tx.SetBlock(pos, portal(n.axis), nil) + } +} + +// Deactivate ... +func (n Nether) Deactivate() { + for _, pos := range n.Positions() { + n.tx.SetBlock(pos, nil, nil) + } +} + +// Framed ... +func (n Nether) Framed() bool { + return n.framed +} + +// Activated ... +func (n Nether) Activated() bool { + for _, pos := range n.Positions() { + if n.tx.Block(pos) != portal(n.axis) { + return false + } + } + return true +} + +// Spawn ... +func (n Nether) Spawn() cube.Pos { + return n.spawnPos +} + +// Positions ... +func (n Nether) Positions() []cube.Pos { + return n.positions +} diff --git a/server/world/portal/scan.go b/server/world/portal/scan.go new file mode 100644 index 000000000..882842032 --- /dev/null +++ b/server/world/portal/scan.go @@ -0,0 +1,132 @@ +package portal + +import ( + "container/list" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" +) + +// scanIteration contains data about a nether portal scan iteration. +type scanIteration struct { + lastPos cube.Pos + face cube.Face + first bool +} + +// multiAxisScan performs a scan on the Z and X axis, returning the result that had the most positions, although +// favouring the Z axis. +func multiAxisScan(framePos cube.Pos, tx *world.Tx, matchers []string) (cube.Axis, []cube.Pos, int, int, bool, bool) { + positions, width, height, completed := scan(cube.Z, framePos, tx, matchers) + positionsTwo, widthTwo, heightTwo, completedTwo := scan(cube.X, framePos, tx, matchers) + if len(positions) < minimumArea && len(positionsTwo) >= minimumArea { + return cube.X, positionsTwo, widthTwo, heightTwo, completedTwo, len(positionsTwo) > 0 + } + return cube.Z, positions, width, height, completed, len(positions) > 0 +} + +// scan performs a scan on the given axis for any of the provided matchers using a position and a world. +func scan(axis cube.Axis, framePos cube.Pos, tx *world.Tx, matchers []string) ([]cube.Pos, int, int, bool) { + var width, height int + positionsMap := make(map[cube.Pos]bool) + + completed := true + queue := list.New() + queue.PushBack(scanIteration{lastPos: framePos, first: true}) + + for queue.Len() > 0 { + e := queue.Front() + queue.Remove(e) + + // Parse the latest iteration. + iteration := e.Value.(scanIteration) + pos := iteration.lastPos + if !iteration.first { + pos = pos.Side(iteration.face) + } + + b := tx.Block(pos) + if _, ok := positionsMap[pos]; !ok && satisfiesMatchers(b, matchers) { + // Add the position to the map. + positionsMap[pos] = true + + // If we are on the same X or Z axis as the portal, we can assume that our height is being changed. + if pos.X() == framePos.X() && pos.Z() == framePos.Z() && iteration.face < cube.FaceNorth { + height++ + } + + // If we are on the same Y axis as the portal, we can assume that our width is being changed. + if pos.Y() == framePos.Y() { + width++ + } + + // Make sure we don't exceed the maximum portal width or height. + if width > maximumNetherPortalWidth || height > maximumNetherPortalHeight { + return []cube.Pos{}, 0, 0, false + } + + // Plan new iterations. + if axis == cube.Z { + queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceSouth}) + queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceNorth}) + } else if axis == cube.X { + queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceWest}) + queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceEast}) + } + queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceUp}) + queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceDown}) + } else if !ok && b != obsidian() { + // Found a block that's not in our portal area and not part of the frame + completed = false + } + } + + // Make sure we at least reach the minimum portal width and height. + area, expectedArea := len(positionsMap), width*height + completed = completed && width >= minimumNetherPortalWidth && height >= minimumNetherPortalHeight && area == expectedArea + + // Get the actual positions from the map. + positions := make([]cube.Pos, 0, expectedArea) + for pos := range positionsMap { + positions = append(positions, pos) + } + return positions, width, height, completed +} + +// satisfiesMatchers checks if the given block satisfies all matchers. +func satisfiesMatchers(b world.Block, matchers []string) bool { + name, _ := b.EncodeBlock() + for _, matcher := range matchers { + if name == matcher { + return true + } + } + return false +} + +// air returns an air block. +func air() world.Block { + a, ok := world.BlockByName("minecraft:air", nil) + if !ok { + panic("could not find air block") + } + return a +} + +// portal returns a portal block. +func portal(axis cube.Axis) world.Block { + p, ok := world.BlockByName("minecraft:portal", map[string]interface{}{"portal_axis": axis.String()}) + if !ok { + panic("could not find portal block") + } + return p +} + +// obsidian returns an obsidian block. +func obsidian() world.Block { + o, ok := world.BlockByName("minecraft:obsidian", nil) + if !ok { + panic("could not find obsidian block") + } + return o +} diff --git a/server/world/world.go b/server/world/world.go index 2210dc2a8..6a3d72bd5 100644 --- a/server/world/world.go +++ b/server/world/world.go @@ -4,13 +4,6 @@ import ( "encoding/binary" "errors" "fmt" - "github.com/df-mc/dragonfly/server/block/cube" - "github.com/df-mc/dragonfly/server/event" - "github.com/df-mc/dragonfly/server/internal/sliceutil" - "github.com/df-mc/dragonfly/server/world/chunk" - "github.com/df-mc/goleveldb/leveldb" - "github.com/go-gl/mathgl/mgl64" - "github.com/google/uuid" "iter" "maps" "math/rand/v2" @@ -18,6 +11,14 @@ import ( "sync" "sync/atomic" "time" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/event" + "github.com/df-mc/dragonfly/server/internal/sliceutil" + "github.com/df-mc/dragonfly/server/world/chunk" + "github.com/df-mc/goleveldb/leveldb" + "github.com/go-gl/mathgl/mgl64" + "github.com/google/uuid" ) // World implements a Minecraft world. It manages all aspects of what players @@ -257,6 +258,13 @@ func (w *World) setBlock(pos cube.Pos, b Block, opts *SetOpts) { opts = &SetOpts{} } + if p, ok := b.(interface { + Place(pos cube.Pos, w *World) bool + }); ok && !p.Place(pos, w) { + // Don't place the block. + return + } + x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2]) c := w.chunk(chunkPosFromBlockPos(pos)) From e9fe5537de2da2f3f74ac80b2bff4db5e2390bf9 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 30 Sep 2025 20:51:53 -0700 Subject: [PATCH 02/41] add obsidian and a nether portal frame block --- server/block/obsidian.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/block/obsidian.go b/server/block/obsidian.go index 5d99bd4a0..1e0393b9d 100644 --- a/server/block/obsidian.go +++ b/server/block/obsidian.go @@ -2,6 +2,7 @@ package block import ( "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" ) // Obsidian is a dark purple block known for its high blast resistance and strength, most commonly found when @@ -37,6 +38,11 @@ func (o Obsidian) EncodeBlock() (string, map[string]any) { return "minecraft:obsidian", nil } +// Frame ... +func (o Obsidian) Frame(dimension world.Dimension) bool { + return dimension == world.Nether +} + // BreakInfo ... func (o Obsidian) BreakInfo() BreakInfo { return newBreakInfo(35, func(t item.Tool) bool { From 6b0bdd069a4f3b33ca41c8914769c7539fcf04e8 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 30 Sep 2025 20:57:12 -0700 Subject: [PATCH 03/41] changes --- server/world/portal/nether.go | 7 +++---- server/world/portal/scan.go | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/server/world/portal/nether.go b/server/world/portal/nether.go index 6b7846a50..c5e7c9cc5 100644 --- a/server/world/portal/nether.go +++ b/server/world/portal/nether.go @@ -86,10 +86,9 @@ func FindNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { r := tx.World().Dimension().Range() for y := r.Max(); y >= r.Min(); y-- { selectedPos := cube.Pos{x, y, z} - // Just check if it's a portal block, don't check destination dimension - if _, ok := tx.Block(selectedPos).(portalBlock); ok { - // Verify the portal is valid by checking if we can get portal info from it - if portal, portalOk := NetherPortalFromPos(tx, selectedPos); portalOk && portal.Framed() { + if p, ok := tx.Block(selectedPos).(portalBlock); ok && p.Portal() == world.Nether { + belowPos := selectedPos.Side(cube.FaceDown) + if f, ok := tx.Block(belowPos).(frameBlock); ok && f.Frame(world.Nether) { dist := selectedPos.Vec3().Sub(pos.Vec3()).Len() if dist < closestDist { closestDist, closestPos, found = dist, selectedPos, true diff --git a/server/world/portal/scan.go b/server/world/portal/scan.go index 882842032..f7b867c9c 100644 --- a/server/world/portal/scan.go +++ b/server/world/portal/scan.go @@ -33,7 +33,6 @@ func scan(axis cube.Axis, framePos cube.Pos, tx *world.Tx, matchers []string) ([ completed := true queue := list.New() queue.PushBack(scanIteration{lastPos: framePos, first: true}) - for queue.Len() > 0 { e := queue.Front() queue.Remove(e) @@ -75,8 +74,7 @@ func scan(axis cube.Axis, framePos cube.Pos, tx *world.Tx, matchers []string) ([ } queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceUp}) queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceDown}) - } else if !ok && b != obsidian() { - // Found a block that's not in our portal area and not part of the frame + } else if _, ok = positionsMap[pos]; !(ok || b == obsidian()) { completed = false } } From db14f1e3898e07cf6997a56d6822fb4264ac832f Mon Sep 17 00:00:00 2001 From: didntpot Date: Fri, 13 Feb 2026 18:04:02 +0000 Subject: [PATCH 04/41] Merge remote-tracking branch 'upstream/master' into feature/portal --- README.md | 2 +- cmd/blockhash/main.go | 5 +- go.mod | 25 +- go.sum | 60 ++-- server/block/beacon.go | 2 +- server/block/bed.go | 298 ++++++++++++++++++ server/block/beetroot_seeds.go | 10 +- server/block/blackstone.go | 11 +- server/block/blast_furnace.go | 2 +- server/block/break_info.go | 125 +++++++- server/block/carrot.go | 11 +- server/block/coal_ore.go | 2 +- server/block/copper_bars.go | 96 ++++++ server/block/copper_chain.go | 118 +++++++ server/block/copper_door.go | 21 +- server/block/copper_golem_pose.go | 53 ++++ server/block/copper_golem_statue.go | 144 +++++++++ server/block/copper_grate.go | 21 +- server/block/copper_lantern.go | 149 +++++++++ server/block/copper_ore.go | 3 +- server/block/copper_torch.go | 98 ++++++ server/block/copper_trapdoor.go | 23 +- server/block/crop.go | 8 - server/block/cube/pos.go | 33 +- server/block/diamond_ore.go | 2 +- server/block/double_tall_grass.go | 11 +- server/block/emerald_ore.go | 2 +- server/block/explosion.go | 14 +- server/block/fern.go | 11 +- server/block/fire.go | 8 +- server/block/furnace.go | 2 +- server/block/glowstone.go | 3 +- server/block/gravel.go | 10 +- server/block/hash.go | 45 ++- server/block/hopper.go | 2 +- server/block/{chain.go => iron_chain.go} | 26 +- server/block/lantern.go | 4 +- server/block/lapis_ore.go | 3 +- server/block/leaves.go | 16 +- server/block/melon.go | 3 +- server/block/model/bed.go | 19 ++ server/block/model/brewing_stand.go | 2 +- server/block/model/cocoa_bean.go | 4 +- server/block/model/tilled_grass.go | 2 +- server/block/model/wall.go | 8 +- server/block/nether_gold_ore.go | 3 +- server/block/nether_wart.go | 11 +- server/block/oxidizable.go | 15 +- server/block/potato.go | 18 +- server/block/quartz_ore.go | 2 +- server/block/register.go | 22 +- server/block/short_grass.go | 11 +- server/block/skull.go | 3 - server/block/smoker.go | 2 +- server/block/smooth_basalt.go | 22 ++ server/block/wheat_seeds.go | 10 +- server/cmd/command.go | 15 +- server/cmd/parameter.go | 9 +- server/conf.go | 14 +- server/entity/action.go | 12 +- server/entity/item_behaviour.go | 16 +- server/internal/blockinternal/components.go | 28 +- server/internal/packbuilder/items.go | 2 +- server/item/armour.go | 13 +- server/item/axe.go | 2 + server/item/boots.go | 4 +- server/item/chestplate.go | 4 + server/item/copper_nugget.go | 9 + server/item/creative/creative_items.nbt | Bin 141008 -> 140585 bytes server/item/crossbow.go | 61 +++- server/item/enchantment/fortune.go | 43 +++ server/item/enchantment/multishot.go | 48 +++ server/item/enchantment/register.go | 4 +- server/item/enchantment/silk_touch.go | 5 +- server/item/helmet.go | 4 +- server/item/hoe.go | 2 + server/item/leggings.go | 4 +- server/item/nether_star.go | 5 + server/item/pickaxe.go | 2 + server/item/recipe/crafting_data.nbt | Bin 807793 -> 816411 bytes server/item/recipe/dynamic.go | 120 +++++++ server/item/recipe/furnace_data.nbt | Bin 41570 -> 37423 bytes server/item/recipe/potion_data.nbt | Bin 33442 -> 33442 bytes server/item/recipe/recipe.go | 10 + server/item/recipe/register.go | 13 + server/item/recipe/smithing_data.nbt | Bin 2351 -> 3074 bytes server/item/recipe/vanilla.go | 3 + server/item/register.go | 1 + server/item/shovel.go | 2 + server/item/stack.go | 15 + server/item/sword.go | 2 + server/item/tool.go | 7 +- server/player/chat/translate.go | 9 + server/player/form/element.go | 131 +++++++- server/player/form/form.go | 16 +- server/player/form/menu.go | 95 ++++-- server/player/handler.go | 17 +- server/player/player.go | 203 ++++++++++-- server/player/type.go | 3 + server/server.go | 35 +- server/session/command.go | 116 ++++--- server/session/controllable.go | 3 + server/session/enchantment_texts.go | 2 +- server/session/entity_metadata.go | 25 +- server/session/handler_anvil.go | 19 +- server/session/handler_crafting.go | 77 ++++- server/session/handler_player_action.go | 9 +- server/session/player.go | 73 +++-- server/session/session.go | 26 +- server/session/world.go | 44 ++- server/world/biome/badlands.go | 2 +- server/world/biome/badlands_plateau.go | 2 +- server/world/biome/basalt_deltas.go | 5 - server/world/biome/cold_ocean.go | 2 +- server/world/biome/crimson_forest.go | 5 - server/world/biome/deep_cold_ocean.go | 2 +- server/world/biome/deep_frozen_ocean.go | 4 +- server/world/biome/deep_lukewarm_ocean.go | 2 +- server/world/biome/deep_ocean.go | 2 +- server/world/biome/deep_warm_ocean.go | 2 +- server/world/biome/eroded_badlands.go | 2 +- server/world/biome/frozen_ocean.go | 2 +- server/world/biome/legacy_frozen_ocean.go | 2 +- server/world/biome/lukewarm_ocean.go | 2 +- server/world/biome/mangrove_swamp.go | 2 +- .../world/biome/modified_badlands_plateau.go | 2 +- .../biome/modified_wooded_badlands_plateau.go | 2 +- server/world/biome/ocean.go | 2 +- server/world/biome/old_growth_birch_forest.go | 4 +- server/world/biome/soul_sand_valley.go | 5 - server/world/biome/swamp.go | 6 +- server/world/biome/swamp_hills.go | 4 +- server/world/biome/warm_ocean.go | 2 +- server/world/biome/warped_forest.go | 5 - server/world/biome/windswept_savanna.go | 4 +- server/world/biome/wooded_badlands_plateau.go | 2 +- server/world/biome_definition.go | 24 -- server/world/block_states.nbt | Bin 2091687 -> 2156052 bytes server/world/conf.go | 8 + server/world/entity.go | 7 +- server/world/mcdb/db.go | 2 +- server/world/mcdb/iterator.go | 6 +- server/world/settings.go | 5 +- server/world/sleep.go | 64 ++++ server/world/tick.go | 22 +- server/world/tx.go | 66 +++- server/world/vanilla_items.nbt | Bin 170010 -> 189722 bytes server/world/viewer.go | 9 +- server/world/weather.go | 23 +- server/world/world.go | 43 ++- 150 files changed, 2792 insertions(+), 581 deletions(-) create mode 100644 server/block/bed.go create mode 100644 server/block/copper_bars.go create mode 100644 server/block/copper_chain.go create mode 100644 server/block/copper_golem_pose.go create mode 100644 server/block/copper_golem_statue.go create mode 100644 server/block/copper_lantern.go create mode 100644 server/block/copper_torch.go rename server/block/{chain.go => iron_chain.go} (53%) create mode 100644 server/block/model/bed.go create mode 100644 server/block/smooth_basalt.go create mode 100644 server/item/copper_nugget.go create mode 100644 server/item/enchantment/fortune.go create mode 100644 server/item/enchantment/multishot.go create mode 100644 server/item/recipe/dynamic.go create mode 100644 server/world/sleep.go diff --git a/README.md b/README.md index 49a6350aa..af94708dc 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Minecraft server software, Dragonfly is generally used as a library to extend. [![Discord Banner 2](https://discordapp.com/api/guilds/623638955262345216/widget.png?style=banner2)](https://discord.gg/U4kFWHhTNR) ## Getting started -Running Dragonfly requires at least **Go 1.23**. After starting the server through one of the methods below, +Running Dragonfly requires at least **Go 1.24**. After starting the server through one of the methods below, **ctrl+c** may be used to shut down the server. Also check out the [wiki](https://github.com/df-mc/dragonfly/wiki) for more detailed info. diff --git a/cmd/blockhash/main.go b/cmd/blockhash/main.go index 8244acfdf..25d90a133 100644 --- a/cmd/blockhash/main.go +++ b/cmd/blockhash/main.go @@ -5,13 +5,14 @@ import ( "flag" "fmt" "go/ast" - "golang.org/x/tools/go/packages" "io" "log" "os" "sort" "strconv" "strings" + + "golang.org/x/tools/go/packages" ) func main() { @@ -224,6 +225,8 @@ func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[ name = t.Name case *ast.SelectorExpr: name = t.Sel.Name + case *ast.StarExpr: + return "", 0 // Ignore this field default: log.Fatalf("unknown field type %#v\n", expr) return "", 0 diff --git a/go.mod b/go.mod index 3a532c16d..67cad178a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/df-mc/dragonfly -go 1.24 +go 1.24.0 toolchain go1.24.4 @@ -8,26 +8,29 @@ require ( github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 github.com/cespare/xxhash/v2 v2.3.0 github.com/df-mc/goleveldb v1.1.9 - github.com/df-mc/worldupgrader v1.0.19 + github.com/df-mc/worldupgrader v1.0.20 github.com/go-gl/mathgl v1.2.0 github.com/google/uuid v1.6.0 github.com/pelletier/go-toml v1.9.5 - github.com/sandertv/gophertunnel v1.49.0 + github.com/sandertv/gophertunnel v1.54.0 github.com/segmentio/fasthash v1.0.3 golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 golang.org/x/mod v0.22.0 - golang.org/x/text v0.22.0 + golang.org/x/text v0.23.0 golang.org/x/tools v0.28.0 ) require ( - github.com/go-jose/go-jose/v4 v4.1.0 // indirect + github.com/coreos/go-oidc/v3 v3.17.0 // indirect + github.com/df-mc/go-playfab v1.0.0 // indirect + github.com/df-mc/go-xsapi v1.0.1 // indirect + github.com/df-mc/jsonc v1.0.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/klauspost/compress v1.17.11 // indirect - github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect - github.com/sandertv/go-raknet v1.14.3-0.20250305181847-6af3e95113d6 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sync v0.11.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect + github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sync v0.12.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index f7a7e5442..0f0f10a7a 100644 --- a/go.sum +++ b/go.sum @@ -2,32 +2,36 @@ github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 h1:/G0ghZwrhou0Wq github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= +github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= +github.com/df-mc/go-playfab v1.0.0 h1:6gVukk3aQbJ934GJFdcZJHVIw9lhauK+KHOevbwJA10= +github.com/df-mc/go-playfab v1.0.0/go.mod h1:nGOlE+JFGOH5Z0iidEgJapHhndFi/oNk17RN9pKCF+k= +github.com/df-mc/go-xsapi v1.0.1 h1:H1SbxYr4rXOqZSB8MwiODbDUsHRihxbHf+YOljUWgXw= +github.com/df-mc/go-xsapi v1.0.1/go.mod h1:uKC/a/2/JOamgRDezvgVe7OmXdqERUfmCcIWAOp9hPA= github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU= github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk= -github.com/df-mc/worldupgrader v1.0.19 h1:BnwRe/lVv9IhwRWZK5olvTYNTKWh7rhFdqNPDMnoKFM= -github.com/df-mc/worldupgrader v1.0.19/go.mod h1:tsSOLTRm9mpG7VHvYpAjjZrkRHWmSbKZAm9bOLNnlDk= +github.com/df-mc/jsonc v1.0.5 h1:O7oh07kbS5AYY+l2Fji6l4h0iHcdjKbxCtK5VlZlLMU= +github.com/df-mc/jsonc v1.0.5/go.mod h1:+Q++JuCE9IKiP8v7sWImdf/RjQX0nfXyfX6PdfTTmc4= +github.com/df-mc/worldupgrader v1.0.20 h1:wfJyG3bFeaM/HXy7TCiO4HKVw3Mf3N4gPFmgxMHsKnc= +github.com/df-mc/worldupgrader v1.0.20/go.mod h1:tsSOLTRm9mpG7VHvYpAjjZrkRHWmSbKZAm9bOLNnlDk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-gl/mathgl v1.2.0 h1:v2eOj/y1B2afDxF6URV1qCYmo1KW08lAMtTbOn3KXCY= github.com/go-gl/mathgl v1.2.0/go.mod h1:pf9+b5J3LFP7iZ4XXaVzZrCle0Q/vNpB/vDe5+3ulRE= -github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= -github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs= -github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -35,34 +39,32 @@ github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sandertv/go-raknet v1.14.3-0.20250305181847-6af3e95113d6 h1:ZfK7NCzIDE+dzp5x6NIO4JDLsjsOxi762CNR1Obds2Q= github.com/sandertv/go-raknet v1.14.3-0.20250305181847-6af3e95113d6/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU= -github.com/sandertv/gophertunnel v1.49.0 h1:9FIl4GGYMrFTmBPgkOTUskGWRQHIg4NMpY8JdM6SrCg= -github.com/sandertv/gophertunnel v1.49.0/go.mod h1:lmRarAmn25V/+QeiUbUDXeA26bEaNlX1wGEM/rj39ew= +github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 h1:UZQq2253Q+7co/C9Et62RYPBggzz+L+2yqGlvQhSNM8= +github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU= +github.com/sandertv/gophertunnel v1.54.0 h1:iAHY+0k84GtoRVe3IEDNLScM0WRS0Rxh9YODAGjzjOM= +github.com/sandertv/gophertunnel v1.54.0/go.mod h1:F8+ZPbzxJ0LqunXEaDjqeyUgHVB0rI5ZU+PHnptXGfI= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -73,5 +75,3 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server/block/beacon.go b/server/block/beacon.go index b4d7040c5..c48961174 100644 --- a/server/block/beacon.go +++ b/server/block/beacon.go @@ -35,7 +35,7 @@ type BeaconSource interface { // BreakInfo ... func (b Beacon) BreakInfo() BreakInfo { - return newBreakInfo(3, alwaysHarvestable, nothingEffective, oneOf(b)) + return newBreakInfo(3, alwaysHarvestable, nothingEffective, oneOf(Beacon{})) } // Activate manages the opening of a beacon by activating it. diff --git a/server/block/bed.go b/server/block/bed.go new file mode 100644 index 000000000..503d51125 --- /dev/null +++ b/server/block/bed.go @@ -0,0 +1,298 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/internal/nbtconv" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/player/chat" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// Bed is a dyeable utility block that allows a player in the Overworld to sleep through the night and reset +// their spawn point to within a few blocks of the bed, as long as it is not broken or obstructed. +type Bed struct { + transparent + sourceWaterDisplacer + + // Colour is the colour of the bed. + Colour item.Colour + // Facing is the direction that the bed is Facing. + Facing cube.Direction + // Head is true if the bed is the head side. + Head bool + // Sleeper is the user that is using the bed. It is only set for the Head part of the bed. + Sleeper *world.EntityHandle +} + +// MaxCount always returns 1. +func (Bed) MaxCount() int { + return 1 +} + +// Model ... +func (Bed) Model() world.BlockModel { + return model.Bed{} +} + +// SideClosed ... +func (Bed) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// BreakInfo ... +func (b Bed) BreakInfo() BreakInfo { + return newBreakInfo(0.2, alwaysHarvestable, nothingEffective, oneOf(b)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, _ item.User) { + headSide, _, ok := b.head(pos, tx) + if !ok { + return + } + + s := headSide.Sleeper + if s == nil { + return + } + + ent, ok := s.Entity(tx) + if !ok { + return + } + + sleeper, ok := ent.(world.Sleeper) + if ok { + sleeper.Wake() + } + }) +} + +// UseOnBlock ... +func (b Bed) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + if pos, _, used = firstReplaceable(tx, pos, face, b); !used { + return + } + if !supportedFromBelow(pos, tx) { + return + } + + b.Facing = user.Rotation().Direction() + + side, sidePos := b, pos.Side(b.Facing.Face()) + side.Head = true + + if !replaceableWith(tx, sidePos, side) { + return + } + if !supportedFromBelow(sidePos, tx) { + return + } + + ctx.IgnoreBBox = true + place(tx, sidePos, side, user, ctx) + place(tx, pos, b, user, ctx) + return placed(ctx) +} + +// Activate ... +func (b Bed) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool { + s, ok := u.(world.Sleeper) + if !ok { + return false + } + + w := tx.World() + if w.Dimension() != world.Overworld { + tx.SetBlock(pos, nil, nil) + ExplosionConfig{ + Size: 5, + SpawnFire: true, + }.Explode(tx, pos.Vec3Centre()) + return true + } + + _, sidePos, ok := b.side(pos, tx) + if !ok { + return false + } + + userPos := s.Position() + if sidePos.Vec3Middle().Sub(userPos).Len() > 2 && pos.Vec3Middle().Sub(userPos).Len() > 2 { + s.Messaget(chat.MessageBedTooFar) + return true + } + + headSide, headPos, ok := b.head(pos, tx) + if !ok { + return false + } + + if _, safeSpawn := b.SafeSpawn(pos, tx); !safeSpawn { + s.Messaget(chat.MessageBedObstructed) + return false + } + + if _, ok = tx.Liquid(headPos); ok { + return false + } + + previousSpawn := w.PlayerSpawn(s.UUID()) + if previousSpawn != headPos { + w.SetPlayerSpawn(s.UUID(), headPos) + s.Messaget(chat.MessageRespawnPointSet) + } + + time := w.Time() % world.TimeFull + if !tx.Thundering() { + if !tx.Raining() && (time <= world.TimeSleep || time >= world.TimeWake) { + s.Messaget(chat.MessageNoSleep) + return true + } + if time <= world.TimeSleepWithRain || time >= world.TimeWakeWithRain { + s.Messaget(chat.MessageNoSleep) + return true + } + } + if headSide.Sleeper != nil { + s.Messaget(chat.MessageBedIsOccupied) + return true + } + + // TODO: add a check for when monsters are nearby + + s.Sleep(headPos) + return true +} + +// EntityLand ... +func (b Bed) EntityLand(_ cube.Pos, _ *world.Tx, e world.Entity, distance *float64) { + if _, ok := e.(fallDistanceEntity); ok { + *distance *= 0.5 + } + if v, ok := e.(velocityEntity); ok { + vel := v.Velocity() + vel[1] = vel[1] * -2 / 3 + v.SetVelocity(vel) + } +} + +// velocityEntity represents an entity that can maintain a velocity. +type velocityEntity interface { + // Velocity returns the current velocity of the entity. + Velocity() mgl64.Vec3 + // SetVelocity sets the velocity of the entity. + SetVelocity(mgl64.Vec3) +} + +// NeighbourUpdateTick ... +func (b Bed) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if _, _, ok := b.side(pos, tx); !ok { + breakBlockNoDrops(b, pos, tx) + } +} + +// EncodeItem ... +func (b Bed) EncodeItem() (name string, meta int16) { + return "minecraft:bed", int16(b.Colour.Uint8()) +} + +// EncodeBlock ... +func (b Bed) EncodeBlock() (name string, properties map[string]interface{}) { + return "minecraft:bed", map[string]interface{}{ + "direction": int32(horizontalDirection(b.Facing)), + "occupied_bit": boolByte(b.Sleeper != nil), + "head_piece_bit": boolByte(b.Head), + } +} + +// EncodeNBT ... +func (b Bed) EncodeNBT() map[string]interface{} { + return map[string]interface{}{ + "id": "Bed", + "color": b.Colour.Uint8(), + } +} + +// DecodeNBT ... +func (b Bed) DecodeNBT(data map[string]interface{}) interface{} { + b.Colour = item.Colours()[nbtconv.Uint8(data, "color")] + return b +} + +// head returns the head side of the bed. If neither side is a head side, the third return value is false. +func (b Bed) head(pos cube.Pos, tx *world.Tx) (Bed, cube.Pos, bool) { + headSide, headPos, ok := b.side(pos, tx) + if !ok { + return Bed{}, cube.Pos{}, false + } + if b.Head { + return b, pos, true + } + return headSide, headPos, true +} + +// side returns the other side of the bed. If the other side is not a bed, the third return value is false. +func (b Bed) side(pos cube.Pos, tx *world.Tx) (Bed, cube.Pos, bool) { + face := b.Facing.Face() + if b.Head { + face = face.Opposite() + } + + sidePos := pos.Side(face) + o, ok := tx.Block(sidePos).(Bed) + return o, sidePos, ok +} + +// allBeds returns all possible beds. +func allBeds() (beds []world.Block) { + for _, d := range cube.Directions() { + beds = append(beds, Bed{Facing: d}) + beds = append(beds, Bed{Facing: d, Head: true}) + } + return +} + +// CanRespawnOn ... +func (Bed) CanRespawnOn() bool { + return true +} + +// bedOffsets is a map of offsets for each face of the bed. The offsets are relative to the heel side of the bed. +var bedOffsets = map[cube.Face][]cube.Pos{ + cube.FaceNorth: {{-1, 0, 0}, {-1, 0, 1}, {0, 0, 1}, {1, 0, 1}, {1, 0, 0}, {1, 0, -1}, {1, 0, -2}, {0, 0, -2}, {-1, 0, -2}, {-1, 0, -1}, {0, 1, -1}, {0, 1, 0}}, + cube.FaceEast: {{0, 0, -1}, {-1, 0, -1}, {-1, 0, 0}, {-1, 0, 1}, {-1, 0, 1}, {0, 0, 1}, {1, 0, 1}, {2, 0, 1}, {2, 0, 0}, {2, 0, -1}, {1, 0, -1}, {1, 1, 0}, {0, 1, 0}}, + cube.FaceSouth: {{1, 0, 0}, {1, 0, -1}, {0, 0, -1}, {-1, 0, -1}, {-1, 0, 0}, {-1, 0, 1}, {-1, 0, 2}, {0, 0, 2}, {1, 0, 2}, {1, 0, 1}, {0, 1, 1}, {0, 1, 0}}, + cube.FaceWest: {{0, 0, 1}, {1, 0, 1}, {1, 0, 0}, {1, 0, -1}, {1, 0, -1}, {0, 0, -1}, {-1, 0, -1}, {-2, 0, -1}, {-2, 0, 0}, {-2, 0, 1}, {-1, 0, 1}, {-1, 1, 0}, {0, 1, 0}}, +} + +// SafeSpawn ... +func (b Bed) SafeSpawn(pos cube.Pos, tx *world.Tx) (cube.Pos, bool) { + _, headPos, ok := b.head(pos, tx) + if !ok { + return cube.Pos{}, false + } + + heelPos := headPos.Side(b.Facing.Opposite().Face()) + + for _, offset := range bedOffsets[b.Facing.Face()] { + offsetPos := heelPos.Add(offset) + + if _, solidBlock := tx.Block(offsetPos).Model().(model.Solid); solidBlock { + if diffuser, ok := tx.Block(offsetPos).(LightDiffuser); !ok || diffuser.LightDiffusionLevel() != 0 { + continue + } + } + + if _, emptyBlock := tx.Block(offsetPos.Side(cube.FaceDown)).Model().(model.Empty); emptyBlock { + continue + } + + return heelPos.Add(offset), true + } + return cube.Pos{}, false +} + +// supportedFromBelow ... +func supportedFromBelow(pos cube.Pos, tx *world.Tx) bool { + below := pos.Side(cube.FaceDown) + return tx.Block(below).Model().FaceSolid(below, cube.FaceUp, tx) +} diff --git a/server/block/beetroot_seeds.go b/server/block/beetroot_seeds.go index 5776284ac..b0f6516df 100644 --- a/server/block/beetroot_seeds.go +++ b/server/block/beetroot_seeds.go @@ -1,11 +1,12 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // BeetrootSeeds are a crop that can be harvested to craft soup or red dye. @@ -49,12 +50,7 @@ func (b BeetrootSeeds) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx // BreakInfo ... func (b BeetrootSeeds) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(item.Tool, []item.Enchantment) []item.Stack { - if b.Growth < 7 { - return []item.Stack{item.NewStack(b, 1)} - } - return []item.Stack{item.NewStack(item.Beetroot{}, 1), item.NewStack(b, rand.IntN(4)+1)} - }) + return newBreakInfo(0, alwaysHarvestable, nothingEffective, cropSeedDrops(b, item.Beetroot{}, b.Growth)) } // CompostChance ... diff --git a/server/block/blackstone.go b/server/block/blackstone.go index 37098c233..f8426047c 100644 --- a/server/block/blackstone.go +++ b/server/block/blackstone.go @@ -1,9 +1,10 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" - "math/rand/v2" ) // Blackstone is a naturally generating block in the nether that can be used to craft stone tools, brewing stands and @@ -23,8 +24,12 @@ func (b Blackstone) BreakInfo() BreakInfo { switch b.Type { case GildedBlackstone(): - drops = func(item.Tool, []item.Enchantment) []item.Stack { - if rand.Float64() < 0.1 { + drops = func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if hasSilkTouch(enchantments) { + return []item.Stack{item.NewStack(b, 1)} + } + nuggetChances := []float64{0.1, 1.0 / 7.0, 0.25, 1.0} + if rand.Float64() < nuggetChances[min(fortuneLevel(enchantments), 3)] { return []item.Stack{item.NewStack(item.GoldNugget{}, rand.IntN(4)+2)} } return []item.Stack{item.NewStack(b, 1)} diff --git a/server/block/blast_furnace.go b/server/block/blast_furnace.go index f76f5c8b2..09cc62b2b 100644 --- a/server/block/blast_furnace.go +++ b/server/block/blast_furnace.go @@ -73,7 +73,7 @@ func (b BlastFurnace) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx // BreakInfo ... func (b BlastFurnace) BreakInfo() BreakInfo { xp := b.Experience() - return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(b)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(BlastFurnace{})).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { for _, i := range b.Inventory(tx, pos).Clear() { dropItem(tx, i, pos.Vec3()) } diff --git a/server/block/break_info.go b/server/block/break_info.go index bfe74b641..82c212397 100644 --- a/server/block/break_info.go +++ b/server/block/break_info.go @@ -1,15 +1,16 @@ package block import ( + "math" + "math/rand/v2" + "slices" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/item/enchantment" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/particle" - "math" - "math/rand/v2" - "slices" - "time" ) // Breakable represents a block that may be broken by a player in survival mode. Blocks not include are blocks @@ -236,6 +237,122 @@ func silkTouchOnlyDrop(it world.Item) func(t item.Tool, enchantments []item.Ench } } +// fortuneLevel returns the level of the fortune enchantment in enchantments, or 0 if it isn't present. +func fortuneLevel(enchantments []item.Enchantment) int { + index := slices.IndexFunc(enchantments, func(i item.Enchantment) bool { + return i.Type() == enchantment.Fortune + }) + if index == -1 { + return 0 + } + return enchantments[index].Level() +} + +// fortuneOreCount computes the drop count for an ore after applying the Fortune ore multiplier to a given base +// drop count. The Fortune enchantment has a 2/(level + 2) chance of applying an integer bonus multiplier between +// 2x up to (level + 1)x to the drop count. +func fortuneOreCount(base int, enchantments []item.Enchantment) int { + fortune := fortuneLevel(enchantments) + if fortune == 0 || rand.IntN(fortune+2) < 2 { + return base + } + multiplier := rand.IntN(fortune) + 2 + return base * multiplier +} + +// fortuneDiscreteCount computes the drop count for a block with a discrete uniform distribution. A drop count is +// chosen with equal likelihood between min and max. Every level of Fortune will increase the max by one. The final +// drop count is then limited by the cap count. +func fortuneDiscreteCount(minCount, maxCount, capCount int, enchantments []item.Enchantment) int { + fortune := fortuneLevel(enchantments) + maxWithFortune := maxCount + fortune + return min(capCount, rand.IntN(maxWithFortune-minCount+1)+minCount) +} + +// fortuneBinomial computes the binomial distribution B(n=attempts, p=8/15) for crop seed drops. +func fortuneBinomial(attempts int) int { + count := 0 + for range attempts { + if rand.IntN(15) < 8 { + count++ + } + } + return count +} + +// oreDrops returns a drop function for ores that drop a single item, such as diamond. Silk touch tools will +// cause the ore block itself to always drop. Otherwise, a single item is dropped. The Fortune enchantment has a +// 2/(level + 2) chance of applying an integer bonus multiplier between 2x up to (level + 1)x to the drop count. +func oreDrops(drop, block world.Item) func(item.Tool, []item.Enchantment) []item.Stack { + return func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if hasSilkTouch(enchantments) { + return []item.Stack{item.NewStack(block, 1)} + } + return []item.Stack{item.NewStack(drop, fortuneOreCount(1, enchantments))} + } +} + +// multiOreDrops returns a drop function for ores that drop multiple items, such as copper. Silk touch tools will +// cause the ore block itself to always drop. Otherwise, a drop count is chosen with equal likelihood between min +// and max. The Fortune enchantment has a 2/(level + 2) chance of applying an integer bonus multiplier between 2x +// up to (level + 1)x to the drop count. +func multiOreDrops(drop, block world.Item, minCount, maxCount int) func(item.Tool, []item.Enchantment) []item.Stack { + return func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if hasSilkTouch(enchantments) { + return []item.Stack{item.NewStack(block, 1)} + } + baseCount := rand.IntN(maxCount-minCount+1) + minCount + return []item.Stack{item.NewStack(drop, fortuneOreCount(baseCount, enchantments))} + } +} + +// discreteDrops returns a drop function for blocks with discrete uniform random drops, such as glowstone or melon +// blocks. Silk touch tools will cause the block itself to always drop. Otherwise, a drop count is chosen with equal +// likelihood between min and max. Every level of Fortune will increase the max by one. The final drop count is then +// limited by the cap count. +func discreteDrops(drop, block world.Item, minCount, maxCount, capCount int) func(item.Tool, []item.Enchantment) []item.Stack { + return func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if hasSilkTouch(enchantments) { + return []item.Stack{item.NewStack(block, 1)} + } + return []item.Stack{item.NewStack(drop, fortuneDiscreteCount(minCount, maxCount, capCount, enchantments))} + } +} + +// grassDrops returns a drop function for grass/fern blocks. Shears or silk touch tools will cause the grass block +// itself to always drop. Otherwise, there is a 12.5% chance of dropping a wheat seed. Every level of Fortune will +// increase the max drop count by 2, with each possible drop count being equally likely. +func grassDrops(grass world.Item) func(item.Tool, []item.Enchantment) []item.Stack { + return func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if t.ToolType() == item.TypeShears || hasSilkTouch(enchantments) { + return []item.Stack{item.NewStack(grass, 1)} + } + if rand.Float32() < 0.125 { + count := 1 + if fortune := fortuneLevel(enchantments); fortune > 0 { + count += rand.IntN(fortune*2 + 1) + } + return []item.Stack{item.NewStack(WheatSeeds{}, count)} + } + return nil + } +} + +// cropSeedDrops returns a drop function for wheat/beetroot seeds. +// Uses binomial distribution B(3+fortune, 8/15), seeds may not drop. +func cropSeedDrops(seed, crop world.Item, growth int) func(item.Tool, []item.Enchantment) []item.Stack { + return func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if growth < 7 { + return []item.Stack{item.NewStack(seed, 1)} + } + seedCount := fortuneBinomial(3 + fortuneLevel(enchantments)) + if seedCount == 0 { + return []item.Stack{item.NewStack(crop, 1)} + } + return []item.Stack{item.NewStack(crop, 1), item.NewStack(seed, seedCount)} + } +} + // breakBlock removes a block, shows breaking particles and drops the drops of // the block as items. func breakBlock(b world.Block, pos cube.Pos, tx *world.Tx) { diff --git a/server/block/carrot.go b/server/block/carrot.go index b11763ef8..c7d559e4a 100644 --- a/server/block/carrot.go +++ b/server/block/carrot.go @@ -1,12 +1,13 @@ package block import ( + "math/rand/v2" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" - "time" ) // Carrot is a crop that can be consumed raw. @@ -63,11 +64,13 @@ func (c Carrot) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world // BreakInfo ... func (c Carrot) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(item.Tool, []item.Enchantment) []item.Stack { + return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { if c.Growth < 7 { return []item.Stack{item.NewStack(c, 1)} } - return []item.Stack{item.NewStack(c, rand.IntN(4)+2)} + fortune := fortuneLevel(enchantments) + count := rand.IntN(fortune+1) + 1 + fortuneBinomial(3+fortune) + return []item.Stack{item.NewStack(c, count)} }) } diff --git a/server/block/coal_ore.go b/server/block/coal_ore.go index ad006fa64..13d3d6273 100644 --- a/server/block/coal_ore.go +++ b/server/block/coal_ore.go @@ -13,7 +13,7 @@ type CoalOre struct { // BreakInfo ... func (c CoalOre) BreakInfo() BreakInfo { - return newBreakInfo(c.Type.Hardness(), pickaxeHarvestable, pickaxeEffective, silkTouchOneOf(item.Coal{}, c)).withXPDropRange(0, 2).withBlastResistance(15) + return newBreakInfo(c.Type.Hardness(), pickaxeHarvestable, pickaxeEffective, oreDrops(item.Coal{}, c)).withXPDropRange(0, 2).withBlastResistance(15) } // SmeltInfo ... diff --git a/server/block/copper_bars.go b/server/block/copper_bars.go new file mode 100644 index 000000000..87539cf42 --- /dev/null +++ b/server/block/copper_bars.go @@ -0,0 +1,96 @@ +package block + +import ( + "math/rand/v2" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" +) + +// CopperBars are blocks that serve a similar purpose to glass panes, but made of copper instead of glass. +type CopperBars struct { + transparent + thin + sourceWaterDisplacer + + // Oxidation is the level of oxidation of the copper bars. + Oxidation OxidationType + // Waxed bool is whether the copper bars has been waxed with honeycomb. + Waxed bool +} + +// BreakInfo ... +func (c CopperBars) BreakInfo() BreakInfo { + return newBreakInfo(5, pickaxeHarvestable, pickaxeEffective, oneOf(c)).withBlastResistance(30) +} + +// SideClosed ... +func (c CopperBars) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// Wax waxes the copper bars to stop it from oxidising further. +func (c CopperBars) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if c.Waxed { + return c, false + } + c.Waxed = true + return c, true +} + +// Strip ... +func (c CopperBars) Strip() (world.Block, world.Sound, bool) { + if c.Waxed { + c.Waxed = false + return c, sound.WaxRemoved{}, true + } else if ot, ok := c.Oxidation.Decrease(); ok { + c.Oxidation = ot + return c, sound.CopperScraped{}, true + } + return c, nil, false +} + +// CanOxidate ... +func (c CopperBars) CanOxidate() bool { + return !c.Waxed +} + +// OxidationLevel ... +func (c CopperBars) OxidationLevel() OxidationType { + return c.Oxidation +} + +// WithOxidationLevel ... +func (c CopperBars) WithOxidationLevel(o OxidationType) Oxidisable { + c.Oxidation = o + return c +} + +// RandomTick ... +func (c CopperBars) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + attemptOxidation(pos, tx, r, c) +} + +// EncodeItem ... +func (c CopperBars) EncodeItem() (name string, meta int16) { + return copperBlockName("copper_bars", c.Oxidation, c.Waxed), 0 +} + +// EncodeBlock ... +func (c CopperBars) EncodeBlock() (name string, properties map[string]any) { + return copperBlockName("copper_bars", c.Oxidation, c.Waxed), nil +} + +// allCopperBars ... +func allCopperBars() (bars []world.Block) { + f := func(waxed bool) { + for _, o := range OxidationTypes() { + bars = append(bars, CopperBars{Oxidation: o, Waxed: waxed}) + } + } + f(true) + f(false) + return +} diff --git a/server/block/copper_chain.go b/server/block/copper_chain.go new file mode 100644 index 000000000..5f553ce45 --- /dev/null +++ b/server/block/copper_chain.go @@ -0,0 +1,118 @@ +package block + +import ( + "math/rand/v2" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" +) + +// CopperChain is a metallic decoration block. +type CopperChain struct { + transparent + sourceWaterDisplacer + + // Axis is the axis which the chain faces. + Axis cube.Axis + // Oxidation is the level of oxidation of the copper chain. + Oxidation OxidationType + // Waxed bool is whether the copper chain has been waxed with honeycomb. + Waxed bool +} + +// SideClosed ... +func (CopperChain) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// UseOnBlock ... +func (c CopperChain) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, c) + if !used { + return + } + c.Axis = face.Axis() + + place(tx, pos, c, user, ctx) + return placed(ctx) +} + +// BreakInfo ... +func (c CopperChain) BreakInfo() BreakInfo { + return newBreakInfo(5, pickaxeHarvestable, pickaxeEffective, oneOf(c)).withBlastResistance(30) +} + +// Wax waxes the copper chain to stop it from oxidising further. +func (c CopperChain) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if c.Waxed { + return c, false + } + c.Waxed = true + return c, true +} + +// Strip ... +func (c CopperChain) Strip() (world.Block, world.Sound, bool) { + if c.Waxed { + c.Waxed = false + return c, sound.WaxRemoved{}, true + } else if ot, ok := c.Oxidation.Decrease(); ok { + c.Oxidation = ot + return c, sound.CopperScraped{}, true + } + return c, nil, false +} + +// CanOxidate ... +func (c CopperChain) CanOxidate() bool { + return !c.Waxed +} + +// OxidationLevel ... +func (c CopperChain) OxidationLevel() OxidationType { + return c.Oxidation +} + +// WithOxidationLevel ... +func (c CopperChain) WithOxidationLevel(o OxidationType) Oxidisable { + c.Oxidation = o + return c +} + +// RandomTick ... +func (c CopperChain) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + attemptOxidation(pos, tx, r, c) +} + +// EncodeItem ... +func (c CopperChain) EncodeItem() (name string, meta int16) { + return copperBlockName("copper_chain", c.Oxidation, c.Waxed), 0 +} + +// EncodeBlock ... +func (c CopperChain) EncodeBlock() (name string, properties map[string]any) { + return copperBlockName("copper_chain", c.Oxidation, c.Waxed), map[string]any{"pillar_axis": c.Axis.String()} +} + +// Model ... +func (c CopperChain) Model() world.BlockModel { + return model.Chain{Axis: c.Axis} +} + +// allCopperChains ... +func allCopperChains() (chains []world.Block) { + f := func(waxed bool) { + for _, o := range OxidationTypes() { + for _, axis := range cube.Axes() { + chains = append(chains, CopperChain{Axis: axis, Oxidation: o, Waxed: waxed}) + } + } + } + f(true) + f(false) + return +} diff --git a/server/block/copper_door.go b/server/block/copper_door.go index 66672ac2a..c73f3f312 100644 --- a/server/block/copper_door.go +++ b/server/block/copper_door.go @@ -1,13 +1,14 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // CopperDoor is a block that can be used as an openable 1x2 barrier. @@ -167,26 +168,12 @@ func (d CopperDoor) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { // EncodeItem ... func (d CopperDoor) EncodeItem() (name string, meta int16) { - name = "copper_door" - if d.Oxidation != UnoxidisedOxidation() { - name = d.Oxidation.String() + "_" + name - } - if d.Waxed { - name = "waxed_" + name - } - return "minecraft:" + name, 0 + return copperBlockName("copper_door", d.Oxidation, d.Waxed), 0 } // EncodeBlock ... func (d CopperDoor) EncodeBlock() (name string, properties map[string]any) { - name = "copper_door" - if d.Oxidation != UnoxidisedOxidation() { - name = d.Oxidation.String() + "_" + name - } - if d.Waxed { - name = "waxed_" + name - } - return "minecraft:" + name, map[string]any{"minecraft:cardinal_direction": d.Facing.RotateRight().String(), "door_hinge_bit": d.Right, "open_bit": d.Open, "upper_block_bit": d.Top} + return copperBlockName("copper_door", d.Oxidation, d.Waxed), map[string]any{"minecraft:cardinal_direction": d.Facing.RotateRight().String(), "door_hinge_bit": d.Right, "open_bit": d.Open, "upper_block_bit": d.Top} } // allCopperDoors returns a list of all copper door types diff --git a/server/block/copper_golem_pose.go b/server/block/copper_golem_pose.go new file mode 100644 index 000000000..101571323 --- /dev/null +++ b/server/block/copper_golem_pose.go @@ -0,0 +1,53 @@ +package block + +// CopperGolemPose represents a pose of a copper golem statue. +type CopperGolemPose struct { + pose +} + +type pose uint8 + +// StandingPose is the standing pose. +func StandingPose() CopperGolemPose { + return CopperGolemPose{0} +} + +// SittingPose is the sitting pose. +func SittingPose() CopperGolemPose { + return CopperGolemPose{1} +} + +// RunningPose is the running pose. +func RunningPose() CopperGolemPose { + return CopperGolemPose{2} +} + +// StarPose is the head button pressing pose. +func StarPose() CopperGolemPose { + return CopperGolemPose{3} +} + +// Uint8 returns the pose as a uint8. +func (p pose) Uint8() uint8 { + return uint8(p) +} + +// Name returns the pose as a string. +func (p pose) Name() string { + switch p { + case 0: + return "Standing" + case 1: + return "Sitting" + case 2: + return "Running" + case 3: + return "Star" + } + panic("unknown copper golem pose") +} + +// CopperGolemPoses returns all copper golem poses. +func CopperGolemPoses() []CopperGolemPose { + return []CopperGolemPose{StandingPose(), SittingPose(), RunningPose(), StarPose()} +} diff --git a/server/block/copper_golem_statue.go b/server/block/copper_golem_statue.go new file mode 100644 index 000000000..7198a9b34 --- /dev/null +++ b/server/block/copper_golem_statue.go @@ -0,0 +1,144 @@ +package block + +import ( + "math/rand/v2" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/internal/nbtconv" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" +) + +// CopperGolemStatue is the result of a copper golem fully oxidizing and petrifying into a block. +// It can be posed in four different ways. +type CopperGolemStatue struct { + transparent + sourceWaterDisplacer + solid + + // Facing is the direction the copper golem statue is facing. + Facing cube.Direction + // Pose is the pose of the copper golem statue. + Pose CopperGolemPose + // Oxidation is the level of oxidation of the copper lantern. + Oxidation OxidationType + // Waxed bool is whether the copper lantern has been waxed with honeycomb. + Waxed bool +} + +// BreakInfo ... +func (c CopperGolemStatue) BreakInfo() BreakInfo { + return newBreakInfo(3, alwaysHarvestable, pickaxeEffective, oneOf(c)).withBlastResistance(30) +} + +// Activate ... +func (c CopperGolemStatue) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool { + held, _ := u.HeldItems() + if !held.Empty() { + // copper golems can't be activated while holding an item. + return false + } + poses := CopperGolemPoses() + nextIndex := int(c.Pose.Uint8()) + 1 + if nextIndex >= len(poses) { + nextIndex = 0 + } + c.Pose = poses[nextIndex] + tx.SetBlock(pos, c, nil) + return true +} + +// UseOnBlock ... +func (c CopperGolemStatue) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, _, used = firstReplaceable(tx, pos, face, c) + if !used { + return + } + + c.Facing = user.Rotation().Direction().Opposite() + + place(tx, pos, c, user, ctx) + return placed(ctx) +} + +// Wax waxes the copper lantern to stop it from oxidising further. +func (c CopperGolemStatue) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if c.Waxed { + return c, false + } + c.Waxed = true + return c, true +} + +// Strip ... +func (c CopperGolemStatue) Strip() (world.Block, world.Sound, bool) { + if c.Waxed { + c.Waxed = false + return c, sound.WaxRemoved{}, true + } else if ot, ok := c.Oxidation.Decrease(); ok { + c.Oxidation = ot + return c, sound.CopperScraped{}, true + } + return c, nil, false +} + +// CanOxidate ... +func (c CopperGolemStatue) CanOxidate() bool { + return !c.Waxed +} + +// OxidationLevel ... +func (c CopperGolemStatue) OxidationLevel() OxidationType { + return c.Oxidation +} + +// WithOxidationLevel ... +func (c CopperGolemStatue) WithOxidationLevel(o OxidationType) Oxidisable { + c.Oxidation = o + return c +} + +// RandomTick ... +func (c CopperGolemStatue) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + attemptOxidation(pos, tx, r, c) +} + +// DecodeNBT ... +func (c CopperGolemStatue) DecodeNBT(data map[string]any) any { + c.Pose = CopperGolemPose{pose(nbtconv.Int32(data, "Pose"))} + return c +} + +// EncodeNBT ... +func (c CopperGolemStatue) EncodeNBT() map[string]any { + return map[string]any{ + "Pose": int32(c.Pose.Uint8()), + "id": "CopperGolemStatue", + } +} + +// EncodeItem ... +func (c CopperGolemStatue) EncodeItem() (name string, meta int16) { + return copperBlockName("copper_golem_statue", c.Oxidation, c.Waxed), 0 +} + +// EncodeBlock ... +func (c CopperGolemStatue) EncodeBlock() (string, map[string]any) { + return copperBlockName("copper_golem_statue", c.Oxidation, c.Waxed), map[string]any{"minecraft:cardinal_direction": c.Facing.String()} +} + +// allCopperGolemStatues ... +func allCopperGolemStatues() (golems []world.Block) { + f := func(waxed bool) { + for _, o := range OxidationTypes() { + for _, direction := range cube.Directions() { + golems = append(golems, CopperGolemStatue{Facing: direction, Oxidation: o, Waxed: waxed}) + } + } + } + f(true) + f(false) + return +} diff --git a/server/block/copper_grate.go b/server/block/copper_grate.go index b0f91531e..cfdaea578 100644 --- a/server/block/copper_grate.go +++ b/server/block/copper_grate.go @@ -1,12 +1,13 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // CopperGrate is a solid block commonly found in deserts and beaches underneath sand. @@ -68,26 +69,12 @@ func (c CopperGrate) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { // EncodeItem ... func (c CopperGrate) EncodeItem() (name string, meta int16) { - name = "copper_grate" - if c.Oxidation != UnoxidisedOxidation() { - name = c.Oxidation.String() + "_" + name - } - if c.Waxed { - name = "waxed_" + name - } - return "minecraft:" + name, 0 + return copperBlockName("copper_grate", c.Oxidation, c.Waxed), 0 } // EncodeBlock ... func (c CopperGrate) EncodeBlock() (string, map[string]any) { - name := "copper_grate" - if c.Oxidation != UnoxidisedOxidation() { - name = c.Oxidation.String() + "_" + name - } - if c.Waxed { - name = "waxed_" + name - } - return "minecraft:" + name, nil + return copperBlockName("copper_grate", c.Oxidation, c.Waxed), nil } // allCopperGrates returns a list of all copper grate variants. diff --git a/server/block/copper_lantern.go b/server/block/copper_lantern.go new file mode 100644 index 000000000..3b4558731 --- /dev/null +++ b/server/block/copper_lantern.go @@ -0,0 +1,149 @@ +package block + +import ( + "math/rand/v2" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl64" +) + +// CopperLantern is a light emitting block. +type CopperLantern struct { + transparent + sourceWaterDisplacer + + // Hanging determines if a lantern is hanging off a block. + Hanging bool + // Oxidation is the level of oxidation of the copper lantern. + Oxidation OxidationType + // Waxed bool is whether the copper lantern has been waxed with honeycomb. + Waxed bool +} + +// Model ... +func (c CopperLantern) Model() world.BlockModel { + return model.Lantern{Hanging: c.Hanging} +} + +// NeighbourUpdateTick ... +func (c CopperLantern) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if c.Hanging { + up := pos.Side(cube.FaceUp) + if _, ok := tx.Block(up).(CopperChain); !ok && !tx.Block(up).Model().FaceSolid(up, cube.FaceDown, tx) { + breakBlock(c, pos, tx) + } + } else { + down := pos.Side(cube.FaceDown) + if !tx.Block(down).Model().FaceSolid(down, cube.FaceUp, tx) { + breakBlock(c, pos, tx) + } + } +} + +// LightEmissionLevel ... +func (CopperLantern) LightEmissionLevel() uint8 { + return 15 +} + +// UseOnBlock ... +func (c CopperLantern) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, c) + if !used { + return false + } + if face == cube.FaceDown { + upPos := pos.Side(cube.FaceUp) + if _, ok := tx.Block(upPos).(CopperChain); !ok && !tx.Block(upPos).Model().FaceSolid(upPos, cube.FaceDown, tx) { + face = cube.FaceUp + } + } + if face != cube.FaceDown { + downPos := pos.Side(cube.FaceDown) + if !tx.Block(downPos).Model().FaceSolid(downPos, cube.FaceUp, tx) { + return false + } + } + c.Hanging = face == cube.FaceDown + + place(tx, pos, c, user, ctx) + return placed(ctx) +} + +// SideClosed ... +func (CopperLantern) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { + return false +} + +// BreakInfo ... +func (c CopperLantern) BreakInfo() BreakInfo { + return newBreakInfo(3.5, pickaxeHarvestable, pickaxeEffective, oneOf(c)) +} + +// Wax waxes the copper lantern to stop it from oxidising further. +func (c CopperLantern) Wax(cube.Pos, mgl64.Vec3) (world.Block, bool) { + if c.Waxed { + return c, false + } + c.Waxed = true + return c, true +} + +// Strip ... +func (c CopperLantern) Strip() (world.Block, world.Sound, bool) { + if c.Waxed { + c.Waxed = false + return c, sound.WaxRemoved{}, true + } else if ot, ok := c.Oxidation.Decrease(); ok { + c.Oxidation = ot + return c, sound.CopperScraped{}, true + } + return c, nil, false +} + +// CanOxidate ... +func (c CopperLantern) CanOxidate() bool { + return !c.Waxed +} + +// OxidationLevel ... +func (c CopperLantern) OxidationLevel() OxidationType { + return c.Oxidation +} + +// WithOxidationLevel ... +func (c CopperLantern) WithOxidationLevel(o OxidationType) Oxidisable { + c.Oxidation = o + return c +} + +// RandomTick ... +func (c CopperLantern) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { + attemptOxidation(pos, tx, r, c) +} + +// EncodeItem ... +func (c CopperLantern) EncodeItem() (name string, meta int16) { + return copperBlockName("copper_lantern", c.Oxidation, c.Waxed), 0 +} + +// EncodeBlock ... +func (c CopperLantern) EncodeBlock() (name string, properties map[string]any) { + return copperBlockName("copper_lantern", c.Oxidation, c.Waxed), map[string]any{"hanging": c.Hanging} +} + +// allCopperLanterns ... +func allCopperLanterns() (lanterns []world.Block) { + f := func(waxed bool) { + for _, o := range OxidationTypes() { + lanterns = append(lanterns, CopperLantern{Hanging: false, Oxidation: o, Waxed: waxed}) + lanterns = append(lanterns, CopperLantern{Hanging: true, Oxidation: o, Waxed: waxed}) + } + } + f(true) + f(false) + return +} diff --git a/server/block/copper_ore.go b/server/block/copper_ore.go index fa195f027..e851ad19d 100644 --- a/server/block/copper_ore.go +++ b/server/block/copper_ore.go @@ -2,7 +2,6 @@ package block import ( "github.com/df-mc/dragonfly/server/item" - "math/rand/v2" ) // CopperOre is a rare mineral block found underground. @@ -18,7 +17,7 @@ type CopperOre struct { func (c CopperOre) BreakInfo() BreakInfo { return newBreakInfo(c.Type.Hardness(), func(t item.Tool) bool { return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierStone.HarvestLevel - }, pickaxeEffective, silkTouchDrop(item.NewStack(item.RawCopper{}, rand.IntN(4)+2), item.NewStack(c, 1))).withBlastResistance(15) + }, pickaxeEffective, multiOreDrops(item.RawCopper{}, c, 2, 5)).withBlastResistance(15) } // SmeltInfo ... diff --git a/server/block/copper_torch.go b/server/block/copper_torch.go new file mode 100644 index 000000000..be030c634 --- /dev/null +++ b/server/block/copper_torch.go @@ -0,0 +1,98 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" +) + +// CopperTorch are non-solid blocks that emit light. +type CopperTorch struct { + transparent + empty + + // Facing is the direction from the torch to the block. + Facing cube.Face +} + +// BreakInfo ... +func (t CopperTorch) BreakInfo() BreakInfo { + return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(t)) +} + +// LightEmissionLevel ... +func (t CopperTorch) LightEmissionLevel() uint8 { + return 14 +} + +// UseOnBlock ... +func (t CopperTorch) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) bool { + pos, face, used := firstReplaceable(tx, pos, face, t) + if !used { + return false + } + if face == cube.FaceDown { + return false + } + if !tx.Block(pos.Side(face.Opposite())).Model().FaceSolid(pos.Side(face.Opposite()), face, tx) { + found := false + for _, i := range []cube.Face{cube.FaceSouth, cube.FaceWest, cube.FaceNorth, cube.FaceEast, cube.FaceDown} { + if tx.Block(pos.Side(i)).Model().FaceSolid(pos.Side(i), i.Opposite(), tx) { + found = true + face = i.Opposite() + break + } + } + if !found { + return false + } + } + t.Facing = face.Opposite() + + place(tx, pos, t, user, ctx) + return placed(ctx) +} + +// NeighbourUpdateTick ... +func (t CopperTorch) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { + if !tx.Block(pos.Side(t.Facing)).Model().FaceSolid(pos.Side(t.Facing), t.Facing.Opposite(), tx) { + breakBlock(t, pos, tx) + } +} + +// HasLiquidDrops ... +func (t CopperTorch) HasLiquidDrops() bool { + return true +} + +// EncodeItem ... +func (t CopperTorch) EncodeItem() (name string, meta int16) { + return "minecraft:copper_torch", 0 +} + +// EncodeBlock ... +func (t CopperTorch) EncodeBlock() (name string, properties map[string]any) { + var face string + if t.Facing == cube.FaceDown { + face = "top" + } else if t.Facing == unknownFace { + face = "unknown" + } else { + face = t.Facing.String() + } + + return "minecraft:copper_torch", map[string]any{"torch_facing_direction": face} +} + +// allTorches ... +func allCopperTorches() (torch []world.Block) { + for _, face := range cube.Faces() { + if face == cube.FaceUp { + face = unknownFace + } + + torch = append(torch, CopperTorch{Facing: face}) + } + return +} diff --git a/server/block/copper_trapdoor.go b/server/block/copper_trapdoor.go index 55b82b3c5..919e1a091 100644 --- a/server/block/copper_trapdoor.go +++ b/server/block/copper_trapdoor.go @@ -1,14 +1,15 @@ package block import ( + "math" + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "math" - "math/rand/v2" ) // CopperTrapdoor is a block that can be used as an openable 1x1 barrier. @@ -110,26 +111,12 @@ func (t CopperTrapdoor) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { // EncodeItem ... func (t CopperTrapdoor) EncodeItem() (name string, meta int16) { - name = "copper_trapdoor" - if t.Oxidation != UnoxidisedOxidation() { - name = t.Oxidation.String() + "_" + name - } - if t.Waxed { - name = "waxed_" + name - } - return "minecraft:" + name, 0 + return copperBlockName("copper_trapdoor", t.Oxidation, t.Waxed), 0 } // EncodeBlock ... func (t CopperTrapdoor) EncodeBlock() (name string, properties map[string]any) { - name = "copper_trapdoor" - if t.Oxidation != UnoxidisedOxidation() { - name = t.Oxidation.String() + "_" + name - } - if t.Waxed { - name = "waxed_" + name - } - return "minecraft:" + name, map[string]any{"direction": int32(math.Abs(float64(t.Facing) - 3)), "open_bit": t.Open, "upside_down_bit": t.Top} + return copperBlockName("copper_trapdoor", t.Oxidation, t.Waxed), map[string]any{"direction": int32(math.Abs(float64(t.Facing) - 3)), "open_bit": t.Open, "upside_down_bit": t.Top} } // allCopperTrapdoors returns a list of all copper trapdoor types diff --git a/server/block/crop.go b/server/block/crop.go index dba790e22..e5ec85d3c 100644 --- a/server/block/crop.go +++ b/server/block/crop.go @@ -94,11 +94,3 @@ func sameCrop(blockA, blockB world.Block) bool { } return false } - -// min returns the smaller of the two integers passed. -func min(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/server/block/cube/pos.go b/server/block/cube/pos.go index 063337204..09625d197 100644 --- a/server/block/cube/pos.go +++ b/server/block/cube/pos.go @@ -2,8 +2,10 @@ package cube import ( "fmt" - "github.com/go-gl/mathgl/mgl64" + "iter" "math" + + "github.com/go-gl/mathgl/mgl64" ) // Pos holds the position of a block. The position is represented as an array @@ -136,3 +138,32 @@ func (p Pos) Neighbours(f func(neighbour Pos), r Range) { func PosFromVec3(vec3 mgl64.Vec3) Pos { return Pos{int(math.Floor(vec3[0])), int(math.Floor(vec3[1])), int(math.Floor(vec3[2]))} } + +// Min returns a new position where each coordinate is the minimum +// of input positions p1 and p2. +func Min(p1, p2 Pos) Pos { + return Pos{min(p1[0], p2[0]), min(p1[1], p2[1]), min(p1[2], p2[2])} +} + +// Max returns a new position where each coordinate is the maximum +// of input positions p1 and p2. +func Max(p1, p2 Pos) Pos { + return Pos{max(p1[0], p2[0]), max(p1[1], p2[1]), max(p1[2], p2[2])} +} + +// Range3D returns iterator that iterates all points between minimum and maximum of p1 & p2. +func Range3D(p1, p2 Pos) iter.Seq[Pos] { + max := Max(p1, p2) + min := Min(p1, p2) + return func(yield func(Pos) bool) { + for x := min[0]; x <= max[0]; x++ { + for y := min[1]; y <= max[1]; y++ { + for z := min[2]; z <= min[2]; z++ { + if !yield(min.Add(Pos{x, y, z})) { + return + } + } + } + } + } +} diff --git a/server/block/diamond_ore.go b/server/block/diamond_ore.go index 227523503..f48a46e0e 100644 --- a/server/block/diamond_ore.go +++ b/server/block/diamond_ore.go @@ -17,7 +17,7 @@ type DiamondOre struct { func (d DiamondOre) BreakInfo() BreakInfo { return newBreakInfo(d.Type.Hardness(), func(t item.Tool) bool { return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierIron.HarvestLevel - }, pickaxeEffective, silkTouchOneOf(item.Diamond{}, d)).withXPDropRange(3, 7).withBlastResistance(15) + }, pickaxeEffective, oreDrops(item.Diamond{}, d)).withXPDropRange(3, 7).withBlastResistance(15) } // SmeltInfo ... diff --git a/server/block/double_tall_grass.go b/server/block/double_tall_grass.go index d0675b595..7db5df777 100644 --- a/server/block/double_tall_grass.go +++ b/server/block/double_tall_grass.go @@ -5,7 +5,6 @@ import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // DoubleTallGrass is a two-block high variety of grass. @@ -57,15 +56,7 @@ func (d DoubleTallGrass) FlammabilityInfo() FlammabilityInfo { // BreakInfo ... func (d DoubleTallGrass) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { - if t.ToolType() == item.TypeShears || hasSilkTouch(enchantments) { - return []item.Stack{item.NewStack(d, 1)} - } - if rand.Float32() > 0.57 { - return []item.Stack{item.NewStack(WheatSeeds{}, 1)} - } - return nil - }) + return newBreakInfo(0, alwaysHarvestable, nothingEffective, grassDrops(d)) } // CompostChance ... diff --git a/server/block/emerald_ore.go b/server/block/emerald_ore.go index 6600f3743..dea7f38a9 100644 --- a/server/block/emerald_ore.go +++ b/server/block/emerald_ore.go @@ -17,7 +17,7 @@ type EmeraldOre struct { func (e EmeraldOre) BreakInfo() BreakInfo { i := newBreakInfo(e.Type.Hardness(), func(t item.Tool) bool { return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierIron.HarvestLevel - }, pickaxeEffective, silkTouchOneOf(item.Emerald{}, e)).withXPDropRange(3, 7) + }, pickaxeEffective, oreDrops(item.Emerald{}, e)).withXPDropRange(3, 7) if e.Type == DeepslateOre() { i = i.withBlastResistance(15) } diff --git a/server/block/explosion.go b/server/block/explosion.go index 7d7327139..1070bd961 100644 --- a/server/block/explosion.go +++ b/server/block/explosion.go @@ -140,6 +140,13 @@ func (c ExplosionConfig) Explode(tx *world.Tx, explosionPos mgl64.Vec3) { return } + for _, e := range affectedEntities { + if explodable, ok := e.(ExplodableEntity); ok { + impact := (1 - e.Position().Sub(explosionPos).Len()/d) * exposure(tx, explosionPos, e) + explodable.Explode(explosionPos, impact, c) + } + } + for _, pos := range affectedBlocks { bl := tx.Block(pos) if explodable, ok := bl.(Explodable); ok { @@ -158,13 +165,6 @@ func (c ExplosionConfig) Explode(tx *world.Tx, explosionPos mgl64.Vec3) { } } - for _, e := range affectedEntities { - if explodable, ok := e.(ExplodableEntity); ok { - impact := (1 - e.Position().Sub(explosionPos).Len()/d) * exposure(tx, explosionPos, e) - explodable.Explode(explosionPos, impact, c) - } - } - if spawnFire { for _, pos := range affectedBlocks { if r.IntN(3) == 0 { diff --git a/server/block/fern.go b/server/block/fern.go index 50915910d..4317feb93 100644 --- a/server/block/fern.go +++ b/server/block/fern.go @@ -5,7 +5,6 @@ import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // Fern is a transparent plant block which can be used to obtain seeds and as decoration. @@ -22,15 +21,7 @@ func (g Fern) FlammabilityInfo() FlammabilityInfo { // BreakInfo ... func (g Fern) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { - if t.ToolType() == item.TypeShears || hasSilkTouch(enchantments) { - return []item.Stack{item.NewStack(g, 1)} - } - if rand.Float32() > 0.57 { - return []item.Stack{item.NewStack(WheatSeeds{}, 1)} - } - return nil - }) + return newBreakInfo(0, alwaysHarvestable, nothingEffective, grassDrops(g)) } // BoneMeal attempts to affect the block using a bone meal item. diff --git a/server/block/fire.go b/server/block/fire.go index ff52408c6..ff941a12e 100644 --- a/server/block/fire.go +++ b/server/block/fire.go @@ -58,14 +58,14 @@ func infinitelyBurning(pos cube.Pos, tx *world.Tx) bool { // burn attempts to burn a block. func (f Fire) burn(from, to cube.Pos, tx *world.Tx, r *rand.Rand, chanceBound int) { if flammable, ok := tx.Block(to).(Flammable); ok && r.IntN(chanceBound) < flammable.FlammabilityInfo().Flammability { - if r.IntN(f.Age+10) < 5 && !rainingAround(to, tx) { - f.spread(from, to, tx, r) - return - } if t, ok := flammable.(TNT); ok { t.Ignite(to, tx, nil) return } + if r.IntN(f.Age+10) < 5 && !rainingAround(to, tx) { + f.spread(from, to, tx, r) + return + } tx.SetBlock(to, nil, nil) } } diff --git a/server/block/furnace.go b/server/block/furnace.go index 0399c8d11..48ccced5d 100644 --- a/server/block/furnace.go +++ b/server/block/furnace.go @@ -72,7 +72,7 @@ func (f Furnace) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *worl // BreakInfo ... func (f Furnace) BreakInfo() BreakInfo { xp := f.Experience() - return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(f)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(Furnace{})).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { for _, i := range f.Inventory(tx, pos).Clear() { dropItem(tx, i, pos.Vec3()) } diff --git a/server/block/glowstone.go b/server/block/glowstone.go index 905ff5b58..021a48895 100644 --- a/server/block/glowstone.go +++ b/server/block/glowstone.go @@ -3,7 +3,6 @@ package block import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world/sound" - "math/rand/v2" ) // Glowstone is commonly found on the ceiling of the nether dimension. @@ -18,7 +17,7 @@ func (g Glowstone) Instrument() sound.Instrument { // BreakInfo ... func (g Glowstone) BreakInfo() BreakInfo { - return newBreakInfo(0.3, alwaysHarvestable, nothingEffective, silkTouchDrop(item.NewStack(item.GlowstoneDust{}, rand.IntN(3)+2), item.NewStack(g, 1))) + return newBreakInfo(0.3, alwaysHarvestable, nothingEffective, discreteDrops(item.GlowstoneDust{}, g, 2, 4, 4)) } // EncodeItem ... diff --git a/server/block/gravel.go b/server/block/gravel.go index 6fdf195c1..4326d5988 100644 --- a/server/block/gravel.go +++ b/server/block/gravel.go @@ -1,10 +1,11 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" - "math/rand/v2" ) // Gravel is a block affected by gravity. It has a 10% chance of dropping flint instead of itself on break. @@ -22,7 +23,12 @@ func (g Gravel) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { // BreakInfo ... func (g Gravel) BreakInfo() BreakInfo { return newBreakInfo(0.6, alwaysHarvestable, shovelEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { - if !hasSilkTouch(enchantments) && rand.Float64() < 0.1 { + if hasSilkTouch(enchantments) { + return []item.Stack{item.NewStack(g, 1)} + } + flintChances := []float64{0.1, 1.0 / 7.0, 0.25, 1.0} + flintChance := flintChances[min(fortuneLevel(enchantments), 3)] + if rand.Float64() < flintChance { return []item.Stack{item.NewStack(item.Flint{}, 1)} } return []item.Stack{item.NewStack(g, 1)} diff --git a/server/block/hash.go b/server/block/hash.go index 41a1300c6..50ef12634 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -15,6 +15,7 @@ const ( hashBarrier hashBasalt hashBeacon + hashBed hashBedrock hashBeetrootSeeds hashBlackstone @@ -30,7 +31,6 @@ const ( hashCampfire hashCarpet hashCarrot - hashChain hashChest hashChiseledQuartz hashClay @@ -42,9 +42,14 @@ const ( hashConcrete hashConcretePowder hashCopper + hashCopperBars + hashCopperChain hashCopperDoor + hashCopperGolemStatue hashCopperGrate + hashCopperLantern hashCopperOre + hashCopperTorch hashCopperTrapdoor hashCoral hashCoralBlock @@ -94,6 +99,7 @@ const ( hashInvisibleBedrock hashIron hashIronBars + hashIronChain hashIronOre hashItemFrame hashJukebox @@ -161,6 +167,7 @@ const ( hashSlab hashSmithingTable hashSmoker + hashSmoothBasalt hashSnow hashSoulSand hashSoulSoil @@ -241,6 +248,10 @@ func (Beacon) Hash() (uint64, uint64) { return hashBeacon, 0 } +func (b Bed) Hash() (uint64, uint64) { + return hashBed, uint64(b.Facing) | uint64(boolByte(b.Head))<<2 +} + func (b Bedrock) Hash() (uint64, uint64) { return hashBedrock, uint64(boolByte(b.InfiniteBurning)) } @@ -301,10 +312,6 @@ func (c Carrot) Hash() (uint64, uint64) { return hashCarrot, uint64(c.Growth) } -func (c Chain) Hash() (uint64, uint64) { - return hashChain, uint64(c.Axis) -} - func (c Chest) Hash() (uint64, uint64) { return hashChest, uint64(c.Facing) } @@ -349,18 +356,38 @@ func (c Copper) Hash() (uint64, uint64) { return hashCopper, uint64(c.Type.Uint8()) | uint64(c.Oxidation.Uint8())<<2 | uint64(boolByte(c.Waxed))<<4 } +func (c CopperBars) Hash() (uint64, uint64) { + return hashCopperBars, uint64(c.Oxidation.Uint8()) | uint64(boolByte(c.Waxed))<<2 +} + +func (c CopperChain) Hash() (uint64, uint64) { + return hashCopperChain, uint64(c.Axis) | uint64(c.Oxidation.Uint8())<<2 | uint64(boolByte(c.Waxed))<<4 +} + func (d CopperDoor) Hash() (uint64, uint64) { return hashCopperDoor, uint64(d.Oxidation.Uint8()) | uint64(boolByte(d.Waxed))<<2 | uint64(d.Facing)<<3 | uint64(boolByte(d.Open))<<5 | uint64(boolByte(d.Top))<<6 | uint64(boolByte(d.Right))<<7 } +func (c CopperGolemStatue) Hash() (uint64, uint64) { + return hashCopperGolemStatue, uint64(c.Facing) | uint64(c.Oxidation.Uint8())<<2 | uint64(boolByte(c.Waxed))<<4 +} + func (c CopperGrate) Hash() (uint64, uint64) { return hashCopperGrate, uint64(c.Oxidation.Uint8()) | uint64(boolByte(c.Waxed))<<2 } +func (c CopperLantern) Hash() (uint64, uint64) { + return hashCopperLantern, uint64(boolByte(c.Hanging)) | uint64(c.Oxidation.Uint8())<<1 | uint64(boolByte(c.Waxed))<<3 +} + func (c CopperOre) Hash() (uint64, uint64) { return hashCopperOre, uint64(c.Type.Uint8()) } +func (t CopperTorch) Hash() (uint64, uint64) { + return hashCopperTorch, uint64(t.Facing) +} + func (t CopperTrapdoor) Hash() (uint64, uint64) { return hashCopperTrapdoor, uint64(t.Oxidation.Uint8()) | uint64(boolByte(t.Waxed))<<2 | uint64(t.Facing)<<3 | uint64(boolByte(t.Open))<<5 | uint64(boolByte(t.Top))<<6 } @@ -557,6 +584,10 @@ func (IronBars) Hash() (uint64, uint64) { return hashIronBars, 0 } +func (c IronChain) Hash() (uint64, uint64) { + return hashIronChain, uint64(c.Axis) +} + func (i IronOre) Hash() (uint64, uint64) { return hashIronOre, uint64(i.Type.Uint8()) } @@ -825,6 +856,10 @@ func (s Smoker) Hash() (uint64, uint64) { return hashSmoker, uint64(s.Facing) | uint64(boolByte(s.Lit))<<2 } +func (SmoothBasalt) Hash() (uint64, uint64) { + return hashSmoothBasalt, 0 +} + func (Snow) Hash() (uint64, uint64) { return hashSnow, 0 } diff --git a/server/block/hopper.go b/server/block/hopper.go index 0b19f8bca..e24c24ad6 100644 --- a/server/block/hopper.go +++ b/server/block/hopper.go @@ -69,7 +69,7 @@ func (Hopper) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { // BreakInfo ... func (h Hopper) BreakInfo() BreakInfo { - return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(h)).withBlastResistance(24).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oneOf(Hopper{})).withBlastResistance(24).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { for _, i := range h.Inventory(tx, pos).Clear() { dropItem(tx, i, pos.Vec3()) } diff --git a/server/block/chain.go b/server/block/iron_chain.go similarity index 53% rename from server/block/chain.go rename to server/block/iron_chain.go index 11dca26c7..a5c50a538 100644 --- a/server/block/chain.go +++ b/server/block/iron_chain.go @@ -8,8 +8,8 @@ import ( "github.com/go-gl/mathgl/mgl64" ) -// Chain is a metallic decoration block. -type Chain struct { +// IronChain is a metallic decoration block. +type IronChain struct { transparent sourceWaterDisplacer @@ -18,12 +18,12 @@ type Chain struct { } // SideClosed ... -func (Chain) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { +func (IronChain) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { return false } // UseOnBlock ... -func (c Chain) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { +func (c IronChain) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { pos, face, used = firstReplaceable(tx, pos, face, c) if !used { return @@ -35,29 +35,29 @@ func (c Chain) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world. } // BreakInfo ... -func (c Chain) BreakInfo() BreakInfo { +func (c IronChain) BreakInfo() BreakInfo { return newBreakInfo(5, pickaxeHarvestable, pickaxeEffective, oneOf(c)).withBlastResistance(30) } // EncodeItem ... -func (Chain) EncodeItem() (name string, meta int16) { - return "minecraft:chain", 0 +func (IronChain) EncodeItem() (name string, meta int16) { + return "minecraft:iron_chain", 0 } // EncodeBlock ... -func (c Chain) EncodeBlock() (string, map[string]any) { - return "minecraft:chain", map[string]any{"pillar_axis": c.Axis.String()} +func (c IronChain) EncodeBlock() (string, map[string]any) { + return "minecraft:iron_chain", map[string]any{"pillar_axis": c.Axis.String()} } // Model ... -func (c Chain) Model() world.BlockModel { +func (c IronChain) Model() world.BlockModel { return model.Chain{Axis: c.Axis} } -// allChains ... -func allChains() (chains []world.Block) { +// allIronChains ... +func allIronChains() (chains []world.Block) { for _, axis := range cube.Axes() { - chains = append(chains, Chain{Axis: axis}) + chains = append(chains, IronChain{Axis: axis}) } return } diff --git a/server/block/lantern.go b/server/block/lantern.go index 691053dea..1177ae73b 100644 --- a/server/block/lantern.go +++ b/server/block/lantern.go @@ -28,7 +28,7 @@ func (l Lantern) Model() world.BlockModel { func (l Lantern) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { if l.Hanging { up := pos.Side(cube.FaceUp) - if _, ok := tx.Block(up).(Chain); !ok && !tx.Block(up).Model().FaceSolid(up, cube.FaceDown, tx) { + if _, ok := tx.Block(up).(IronChain); !ok && !tx.Block(up).Model().FaceSolid(up, cube.FaceDown, tx) { breakBlock(l, pos, tx) } } else { @@ -52,7 +52,7 @@ func (l Lantern) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *worl } if face == cube.FaceDown { upPos := pos.Side(cube.FaceUp) - if _, ok := tx.Block(upPos).(Chain); !ok && !tx.Block(upPos).Model().FaceSolid(upPos, cube.FaceDown, tx) { + if _, ok := tx.Block(upPos).(IronChain); !ok && !tx.Block(upPos).Model().FaceSolid(upPos, cube.FaceDown, tx) { face = cube.FaceUp } } diff --git a/server/block/lapis_ore.go b/server/block/lapis_ore.go index 44ca601b1..56b12ab46 100644 --- a/server/block/lapis_ore.go +++ b/server/block/lapis_ore.go @@ -2,7 +2,6 @@ package block import ( "github.com/df-mc/dragonfly/server/item" - "math/rand/v2" ) // LapisOre is an ore block from which lapis lazuli is obtained. @@ -18,7 +17,7 @@ type LapisOre struct { func (l LapisOre) BreakInfo() BreakInfo { return newBreakInfo(l.Type.Hardness(), func(t item.Tool) bool { return t.ToolType() == item.TypePickaxe && t.HarvestLevel() >= item.ToolTierStone.HarvestLevel - }, pickaxeEffective, silkTouchDrop(item.NewStack(item.LapisLazuli{}, rand.IntN(5)+4), item.NewStack(l, 1))).withXPDropRange(2, 5).withBlastResistance(15) + }, pickaxeEffective, multiOreDrops(item.LapisLazuli{}, l, 4, 9)).withXPDropRange(2, 5).withBlastResistance(15) } // SmeltInfo ... diff --git a/server/block/leaves.go b/server/block/leaves.go index 9fa236c22..704850e86 100644 --- a/server/block/leaves.go +++ b/server/block/leaves.go @@ -1,12 +1,13 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/event" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // Leaves are blocks that grow as part of trees which mainly drop saplings and sticks. @@ -103,13 +104,20 @@ func (l Leaves) BreakInfo() BreakInfo { if t.ToolType() == item.TypeShears || hasSilkTouch(enchantments) { return []item.Stack{item.NewStack(l, 1)} } + fortune := fortuneLevel(enchantments) var drops []item.Stack + // TODO: Drop saplings. - if rand.Float64() < 0.02 { + + stickChances := []float64{0.02, 0.022222222, 0.025, 0.033333333} + if rand.Float64() < stickChances[min(fortune, 3)] { drops = append(drops, item.NewStack(item.Stick{}, rand.IntN(2)+1)) } - if (l.Wood == OakWood() || l.Wood == DarkOakWood()) && rand.Float64() < 0.005 { - drops = append(drops, item.NewStack(item.Apple{}, 1)) + if l.Wood == OakWood() || l.Wood == DarkOakWood() { + appleChances := []float64{0.005, 0.005555556, 0.00625, 0.008333333} + if rand.Float64() < appleChances[min(fortune, 3)] { + drops = append(drops, item.NewStack(item.Apple{}, 1)) + } } return drops }) diff --git a/server/block/melon.go b/server/block/melon.go index 142d15f9e..511b7798c 100644 --- a/server/block/melon.go +++ b/server/block/melon.go @@ -2,7 +2,6 @@ package block import ( "github.com/df-mc/dragonfly/server/item" - "math/rand/v2" ) // Melon is a fruit block that grows from melon stems. @@ -12,7 +11,7 @@ type Melon struct { // BreakInfo ... func (m Melon) BreakInfo() BreakInfo { - return newBreakInfo(1, alwaysHarvestable, axeEffective, silkTouchDrop(item.NewStack(item.MelonSlice{}, rand.IntN(5)+3), item.NewStack(m, 1))) + return newBreakInfo(1, alwaysHarvestable, axeEffective, discreteDrops(item.MelonSlice{}, m, 3, 7, 9)) } // CompostChance ... diff --git a/server/block/model/bed.go b/server/block/model/bed.go new file mode 100644 index 000000000..58854ce52 --- /dev/null +++ b/server/block/model/bed.go @@ -0,0 +1,19 @@ +package model + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" +) + +// Bed is a model used for beds. This model works for both parts of the bed. +type Bed struct{} + +// BBox ... +func (b Bed) BBox(cube.Pos, world.BlockSource) []cube.BBox { + return []cube.BBox{cube.Box(0, 0, 0, 1, 0.5625, 1)} +} + +// FaceSolid ... +func (Bed) FaceSolid(cube.Pos, cube.Face, world.BlockSource) bool { + return false +} diff --git a/server/block/model/brewing_stand.go b/server/block/model/brewing_stand.go index f32f83eb0..83f8e6e0e 100644 --- a/server/block/model/brewing_stand.go +++ b/server/block/model/brewing_stand.go @@ -11,7 +11,7 @@ type BrewingStand struct{} // BBox ... func (b BrewingStand) BBox(cube.Pos, world.BlockSource) []cube.BBox { return []cube.BBox{ - full.ExtendTowards(cube.FaceDown, 0.875), + full.ExtendTowards(cube.FaceUp, -0.875), full.Stretch(cube.X, -0.4375).Stretch(cube.Z, -0.4375).ExtendTowards(cube.FaceDown, 0.125), } } diff --git a/server/block/model/cocoa_bean.go b/server/block/model/cocoa_bean.go index d61493ba3..51e3c1730 100644 --- a/server/block/model/cocoa_bean.go +++ b/server/block/model/cocoa_bean.go @@ -18,8 +18,8 @@ type CocoaBean struct { func (c CocoaBean) BBox(cube.Pos, world.BlockSource) []cube.BBox { return []cube.BBox{full. Stretch(c.Facing.RotateRight().Face().Axis(), -(6-float64(c.Age))/16). - ExtendTowards(cube.FaceDown, -0.25). - ExtendTowards(cube.FaceUp, -((7-float64(c.Age)*2)/16)). + ExtendTowards(cube.FaceUp, -0.25). + ExtendTowards(cube.FaceDown, -((7-float64(c.Age)*2)/16)). ExtendTowards(c.Facing.Face(), -0.0625). ExtendTowards(c.Facing.Opposite().Face(), -((11 - float64(c.Age)*2) / 16))} } diff --git a/server/block/model/tilled_grass.go b/server/block/model/tilled_grass.go index 48d735abd..cd609b354 100644 --- a/server/block/model/tilled_grass.go +++ b/server/block/model/tilled_grass.go @@ -10,7 +10,7 @@ type TilledGrass struct{} // BBox returns a physics.BBox that spans an entire block. func (TilledGrass) BBox(cube.Pos, world.BlockSource) []cube.BBox { - return []cube.BBox{full.ExtendTowards(cube.FaceDown, 0.0625)} + return []cube.BBox{full.ExtendTowards(cube.FaceUp, -0.0625)} } // FaceSolid always returns true. diff --git a/server/block/model/wall.go b/server/block/model/wall.go index 884f34c19..284c151a3 100644 --- a/server/block/model/wall.go +++ b/server/block/model/wall.go @@ -27,16 +27,16 @@ func (w Wall) BBox(cube.Pos, world.BlockSource) []cube.BBox { } boxes := []cube.BBox{cube.Box(0.25, 0, 0.25, 0.75, postHeight, 0.75)} if w.NorthConnection > 0 { - boxes = append(boxes, cube.Box(0.25, 0, 0.75, 0.75, w.SouthConnection, 1)) + boxes = append(boxes, cube.Box(0.25, 0, 0, 0.75, w.NorthConnection, 0.25)) } if w.EastConnection > 0 { - boxes = append(boxes, cube.Box(0, 0, 0.25, 0.25, w.WestConnection, 0.75)) + boxes = append(boxes, cube.Box(0.75, 0, 0.25, 1, w.EastConnection, 0.75)) } if w.SouthConnection > 0 { - boxes = append(boxes, cube.Box(0.25, 0, 0, 0.75, w.NorthConnection, 0.25)) + boxes = append(boxes, cube.Box(0.25, 0, 0.75, 0.75, w.SouthConnection, 1)) } if w.WestConnection > 0 { - boxes = append(boxes, cube.Box(0.75, 0, 0.25, 1, w.EastConnection, 0.75)) + boxes = append(boxes, cube.Box(0, 0, 0.25, 0.25, w.WestConnection, 0.75)) } return boxes } diff --git a/server/block/nether_gold_ore.go b/server/block/nether_gold_ore.go index 293680ccc..5725f69f9 100644 --- a/server/block/nether_gold_ore.go +++ b/server/block/nether_gold_ore.go @@ -2,7 +2,6 @@ package block import ( "github.com/df-mc/dragonfly/server/item" - "math/rand/v2" ) // NetherGoldOre is a variant of gold ore found exclusively in The Nether. @@ -12,7 +11,7 @@ type NetherGoldOre struct { // BreakInfo ... func (n NetherGoldOre) BreakInfo() BreakInfo { - return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, silkTouchDrop(item.NewStack(item.GoldNugget{}, rand.IntN(4)+2), item.NewStack(n, 1))).withXPDropRange(0, 1) + return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, multiOreDrops(item.GoldNugget{}, n, 2, 6)).withXPDropRange(0, 1) } // SmeltInfo ... diff --git a/server/block/nether_wart.go b/server/block/nether_wart.go index 40e497fd5..2f9fda24c 100644 --- a/server/block/nether_wart.go +++ b/server/block/nether_wart.go @@ -1,11 +1,12 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // NetherWart is a fungus found in the Nether that is vital in the creation of potions. @@ -53,11 +54,11 @@ func (n NetherWart) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { // BreakInfo ... func (n NetherWart) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(item.Tool, []item.Enchantment) []item.Stack { - if n.Age == 3 { - return []item.Stack{item.NewStack(n, rand.IntN(3)+2)} + return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if n.Age < 3 { + return []item.Stack{item.NewStack(n, 1)} } - return []item.Stack{item.NewStack(n, 1)} + return []item.Stack{item.NewStack(n, fortuneDiscreteCount(2, 4, 7, enchantments))} }) } diff --git a/server/block/oxidizable.go b/server/block/oxidizable.go index 4d3b6c5e1..d9929740a 100644 --- a/server/block/oxidizable.go +++ b/server/block/oxidizable.go @@ -1,9 +1,10 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" - "math/rand/v2" ) // Oxidisable is a block that can naturally oxidise over time, such as copper. @@ -65,3 +66,15 @@ func attemptOxidation(pos cube.Pos, tx *world.Tx, r *rand.Rand, o Oxidisable) { tx.SetBlock(pos, o.WithOxidationLevel(level), nil) } } + +// copperBlockName returns the name of a copper block with the given oxidation and waxed status. +func copperBlockName(blockName string, oxidation OxidationType, waxed bool) string { + name := blockName + if oxidation != UnoxidisedOxidation() { + name = oxidation.String() + "_" + name + } + if waxed { + name = "waxed_" + name + } + return "minecraft:" + name +} diff --git a/server/block/potato.go b/server/block/potato.go index fdf9a871f..ecb9e3b5f 100644 --- a/server/block/potato.go +++ b/server/block/potato.go @@ -1,12 +1,13 @@ package block import ( + "math/rand/v2" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" - "time" ) // Potato is a crop that can be consumed raw or cooked to make baked potatoes. @@ -68,15 +69,16 @@ func (p Potato) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world // BreakInfo ... func (p Potato) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(item.Tool, []item.Enchantment) []item.Stack { - n := 1 - if p.Growth >= 7 { - n += rand.IntN(5) + return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { + if p.Growth < 7 { + return []item.Stack{item.NewStack(p, 1)} } + fortune := fortuneLevel(enchantments) + count := rand.IntN(fortune+1) + 1 + fortuneBinomial(3+fortune) if rand.Float64() < 0.02 { - return []item.Stack{item.NewStack(p, n), item.NewStack(item.PoisonousPotato{}, 1)} + return []item.Stack{item.NewStack(p, count), item.NewStack(item.PoisonousPotato{}, 1)} } - return []item.Stack{item.NewStack(p, n)} + return []item.Stack{item.NewStack(p, count)} }) } diff --git a/server/block/quartz_ore.go b/server/block/quartz_ore.go index 696bdc1db..20ef317a1 100644 --- a/server/block/quartz_ore.go +++ b/server/block/quartz_ore.go @@ -10,7 +10,7 @@ type NetherQuartzOre struct { // BreakInfo ... func (q NetherQuartzOre) BreakInfo() BreakInfo { - return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, silkTouchOneOf(item.NetherQuartz{}, q)).withXPDropRange(0, 3) + return newBreakInfo(3, pickaxeHarvestable, pickaxeEffective, oreDrops(item.NetherQuartz{}, q)).withXPDropRange(0, 3) } // SmeltInfo ... diff --git a/server/block/register.go b/server/block/register.go index ef290f13d..17da8158a 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -97,6 +97,7 @@ func init() { world.RegisterBlock(SeaLantern{}) world.RegisterBlock(Shroomlight{}) world.RegisterBlock(SmithingTable{}) + world.RegisterBlock(SmoothBasalt{}) world.RegisterBlock(Snow{}) world.RegisterBlock(SoulSand{}) world.RegisterBlock(SoulSoil{}) @@ -129,6 +130,7 @@ func init() { registerAll(allBanners()) registerAll(allBarrels()) registerAll(allBasalt()) + registerAll(allBeds()) registerAll(allBeetroot()) registerAll(allBlackstone()) registerAll(allBlastFurnaces()) @@ -139,7 +141,7 @@ func init() { registerAll(allCampfires()) registerAll(allCarpet()) registerAll(allCarrots()) - registerAll(allChains()) + registerAll(allIronChains()) registerAll(allChests()) registerAll(allCocoaBeans()) registerAll(allComposters()) @@ -210,8 +212,13 @@ func init() { registerAll(allWool()) registerAll(allDecoratedPots()) registerAll(allCopper()) + registerAll(allCopperBars()) + registerAll(allCopperChains()) registerAll(allCopperDoors()) + registerAll(allCopperGolemStatues()) registerAll(allCopperGrates()) + registerAll(allCopperLanterns()) + registerAll(allCopperTorches()) registerAll(allCopperTrapdoors()) } @@ -238,7 +245,7 @@ func init() { world.RegisterItem(Cake{}) world.RegisterItem(Calcite{}) world.RegisterItem(Carrot{}) - world.RegisterItem(Chain{}) + world.RegisterItem(IronChain{}) world.RegisterItem(Chest{}) world.RegisterItem(ChiseledQuartz{}) world.RegisterItem(Clay{}) @@ -247,6 +254,7 @@ func init() { world.RegisterItem(Cobblestone{}) world.RegisterItem(CocoaBean{}) world.RegisterItem(Composter{}) + world.RegisterItem(CopperTorch{}) world.RegisterItem(CraftingTable{}) world.RegisterItem(DeadBush{}) world.RegisterItem(DeepslateBricks{Cracked: true}) @@ -344,6 +352,7 @@ func init() { world.RegisterItem(Shroomlight{}) world.RegisterItem(SmithingTable{}) world.RegisterItem(Smoker{}) + world.RegisterItem(SmoothBasalt{}) world.RegisterItem(Snow{}) world.RegisterItem(SoulSand{}) world.RegisterItem(SoulSoil{}) @@ -391,6 +400,7 @@ func init() { } for _, c := range item.Colours() { world.RegisterItem(Banner{Colour: c}) + world.RegisterItem(Bed{Colour: c}) world.RegisterItem(Carpet{Colour: c}) world.RegisterItem(ConcretePowder{Colour: c}) world.RegisterItem(Concrete{Colour: c}) @@ -466,10 +476,18 @@ func init() { world.RegisterItem(Deepslate{Type: t}) } for _, o := range OxidationTypes() { + world.RegisterItem(CopperBars{Oxidation: o}) + world.RegisterItem(CopperBars{Oxidation: o, Waxed: true}) + world.RegisterItem(CopperChain{Oxidation: o}) + world.RegisterItem(CopperChain{Oxidation: o, Waxed: true}) world.RegisterItem(CopperDoor{Oxidation: o}) world.RegisterItem(CopperDoor{Oxidation: o, Waxed: true}) + world.RegisterItem(CopperGolemStatue{Oxidation: o}) + world.RegisterItem(CopperGolemStatue{Oxidation: o, Waxed: true}) world.RegisterItem(CopperGrate{Oxidation: o}) world.RegisterItem(CopperGrate{Oxidation: o, Waxed: true}) + world.RegisterItem(CopperLantern{Oxidation: o}) + world.RegisterItem(CopperLantern{Oxidation: o, Waxed: true}) world.RegisterItem(CopperTrapdoor{Oxidation: o}) world.RegisterItem(CopperTrapdoor{Oxidation: o, Waxed: true}) diff --git a/server/block/short_grass.go b/server/block/short_grass.go index b97260452..6f74cb832 100644 --- a/server/block/short_grass.go +++ b/server/block/short_grass.go @@ -5,7 +5,6 @@ import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // ShortGrass is a transparent plant block which can be used to obtain seeds and as decoration. @@ -24,15 +23,7 @@ func (g ShortGrass) FlammabilityInfo() FlammabilityInfo { // BreakInfo ... func (g ShortGrass) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { - if t.ToolType() == item.TypeShears || hasSilkTouch(enchantments) { - return []item.Stack{item.NewStack(g, 1)} - } - if rand.Float32() > 0.57 { - return []item.Stack{item.NewStack(WheatSeeds{}, 1)} - } - return nil - }) + return newBreakInfo(0, alwaysHarvestable, nothingEffective, grassDrops(g)) } // BoneMeal attempts to affect the block using a bone meal item. diff --git a/server/block/skull.go b/server/block/skull.go index 55fd90980..0f78caa30 100644 --- a/server/block/skull.go +++ b/server/block/skull.go @@ -94,9 +94,6 @@ func (s Skull) DecodeNBT(data map[string]interface{}) interface{} { s.Type = SkullType{t} } s.Attach.o = cube.OrientationFromYaw(float64(nbtconv.Float32(data, "Rotation"))) - if s.Attach.facing >= 0 { - s.Attach.hanging = true - } return s } diff --git a/server/block/smoker.go b/server/block/smoker.go index ecf3ba281..8e5160b29 100644 --- a/server/block/smoker.go +++ b/server/block/smoker.go @@ -73,7 +73,7 @@ func (s Smoker) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world // BreakInfo ... func (s Smoker) BreakInfo() BreakInfo { xp := s.Experience() - return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(s)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(Smoker{})).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { for _, i := range s.Inventory(tx, pos).Clear() { dropItem(tx, i, pos.Vec3()) } diff --git a/server/block/smooth_basalt.go b/server/block/smooth_basalt.go new file mode 100644 index 000000000..6f39d401c --- /dev/null +++ b/server/block/smooth_basalt.go @@ -0,0 +1,22 @@ +package block + +// SmoothBasalt is a decorative solid block obtained by smelting basalt. +type SmoothBasalt struct { + solid + bassDrum +} + +// EncodeBlock ... +func (SmoothBasalt) EncodeBlock() (string, map[string]any) { + return "minecraft:smooth_basalt", nil +} + +// EncodeItem ... +func (SmoothBasalt) EncodeItem() (name string, meta int16) { + return "minecraft:smooth_basalt", 0 +} + +// BreakInfo ... +func (s SmoothBasalt) BreakInfo() BreakInfo { + return newBreakInfo(1.25, pickaxeHarvestable, pickaxeEffective, oneOf(s)).withBlastResistance(21) +} diff --git a/server/block/wheat_seeds.go b/server/block/wheat_seeds.go index 2ebb4659a..4103a306b 100644 --- a/server/block/wheat_seeds.go +++ b/server/block/wheat_seeds.go @@ -1,11 +1,12 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // WheatSeeds are a crop that can be harvested to craft bread, cake, & cookies. @@ -46,12 +47,7 @@ func (s WheatSeeds) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *w // BreakInfo ... func (s WheatSeeds) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(item.Tool, []item.Enchantment) []item.Stack { - if s.Growth < 7 { - return []item.Stack{item.NewStack(s, 1)} - } - return []item.Stack{item.NewStack(item.Wheat{}, 1), item.NewStack(s, rand.IntN(4)+1)} - }) + return newBreakInfo(0, alwaysHarvestable, nothingEffective, cropSeedDrops(s, item.Wheat{}, s.Growth)) } // CompostChance ... diff --git a/server/cmd/command.go b/server/cmd/command.go index 249338912..c32500f60 100644 --- a/server/cmd/command.go +++ b/server/cmd/command.go @@ -3,11 +3,12 @@ package cmd import ( "encoding/csv" "fmt" - "github.com/df-mc/dragonfly/server/world" "go/ast" "reflect" "slices" "strings" + + "github.com/df-mc/dragonfly/server/world" ) // Runnable represents a Command that may be run by a Command source. The Command must be a struct type and @@ -178,14 +179,20 @@ type ParamInfo struct { func (cmd Command) Params(src Source) [][]ParamInfo { params := make([][]ParamInfo, 0, len(cmd.v)) for _, runnable := range cmd.v { - elem := reflect.New(runnable.Type()).Elem() - elem.Set(runnable) - if allower, ok := runnable.Interface().(Allower); ok && !allower.Allow(src) { // This source cannot execute this runnable. continue } + // If the runnable can describe its own parameters, prefer that over reflection. + if d, ok := runnable.Interface().(ParamDescriber); ok { + params = append(params, d.DescribeParams(src)) + continue + } + + elem := reflect.New(runnable.Type()).Elem() + elem.Set(runnable) + var fields []ParamInfo for _, t := range exportedFields(elem) { field := elem.FieldByName(t.Name) diff --git a/server/cmd/parameter.go b/server/cmd/parameter.go index d386a090c..3106a8874 100644 --- a/server/cmd/parameter.go +++ b/server/cmd/parameter.go @@ -1,9 +1,10 @@ package cmd import ( - "github.com/go-gl/mathgl/mgl64" "reflect" "strings" + + "github.com/go-gl/mathgl/mgl64" ) // Parameter is an interface for a generic parameters. Users may have types as command parameters that @@ -39,6 +40,12 @@ type Enum interface { Options(source Source) []string } +// ParamDescriber may be implemented by a Runnable to programmatically describe its parameters +// without relying on struct field reflection. +type ParamDescriber interface { + DescribeParams(src Source) []ParamInfo +} + // SubCommand represents a subcommand that may be added as a static value that must be written. Adding // multiple Runnable implementations to the command in New with different SubCommand fields as the // first parameter allows for commands with subcommands. diff --git a/server/conf.go b/server/conf.go index 4b9ce1e10..d0e5ba87d 100644 --- a/server/conf.go +++ b/server/conf.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "slices" + "time" _ "unsafe" "github.com/df-mc/dragonfly/server/block" @@ -101,6 +102,17 @@ type Config struct { // left as 0, the RandomTickSpeed will default to a speed of 3 blocks per // sub chunk per tick (normal ticking speed). RandomTickSpeed int + // SaveInterval specifies how often a World should be automatically saved to + // disk. This includes chunks, entities and level.dat data. If ReadOnlyWorld + // is set to true, changing SaveInterval will have no effect. + // By default, SaveInterval is set to 10 minutes. Setting SaveInterval to + // a negative number disables automatic saving entirely. + SaveInterval time.Duration + // ChunkUnloadInterval specifies how often unused chunks should be unloaded + // from memory when no longer in use. By default, this is set to 2 minutes. + // ChunkUnloadInterval should not be used to prevent chunks from unloading + // altogether. This should be done using a Loader with a custom Viewer. + ChunkUnloadInterval time.Duration // Entities is a world.EntityRegistry with all entity types registered that // may be added to the Server's worlds. If no entity types are registered, // Entities will be set to entity.DefaultRegistry. @@ -174,8 +186,6 @@ func (conf Config) New() *Server { srv.nether = srv.createWorld(world.Nether, &srv.world, &srv.end) srv.end = srv.createWorld(world.End, &srv.nether, &srv.world) - srv.checkNetIsolation() - return srv } diff --git a/server/entity/action.go b/server/entity/action.go index 4a48a11df..ccfe32d62 100644 --- a/server/entity/action.go +++ b/server/entity/action.go @@ -14,11 +14,19 @@ type HurtAction struct{ action } // CriticalHitAction is a world.EntityAction that makes an entity display critical hit particles. This will show stars // around the entity. -type CriticalHitAction struct{ action } +type CriticalHitAction struct { + action + // Count is the count of particles around the entity. + Count int +} // EnchantedHitAction is a world.Action that makes an entity display enchanted hit particles. This will show circles // around the entity. -type EnchantedHitAction struct{ action } +type EnchantedHitAction struct { + action + // Count is the count of particles around the entity. + Count int +} // DeathAction is a world.EntityAction that makes an entity display the death animation. After this animation, the // entity disappears from viewers watching it. diff --git a/server/entity/item_behaviour.go b/server/entity/item_behaviour.go index f83748601..807506459 100644 --- a/server/entity/item_behaviour.go +++ b/server/entity/item_behaviour.go @@ -1,14 +1,15 @@ package entity import ( + "math" + "time" + "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math" - "time" ) // ItemBehaviourConfig holds optional parameters for an ItemBehaviour. @@ -96,6 +97,17 @@ func (i *ItemBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { return i.passive.Tick(e, tx) } +// Explode reacts to explosions. The item entity is destroyed, unless the item +// type is blast proof. +func (i *ItemBehaviour) Explode(e *Ent, src mgl64.Vec3, impact float64, conf block.ExplosionConfig) { + if impact > 0 { + if expl, ok := i.Item().Item().(interface{ BlastProof() bool }); ok && expl.BlastProof() { + return + } + _ = e.Close() + } +} + // tick checks if the item can be picked up or merged with nearby item stacks. func (i *ItemBehaviour) tick(e *Ent, tx *world.Tx) { if i.pickupDelay == 0 { diff --git a/server/internal/blockinternal/components.go b/server/internal/blockinternal/components.go index c4ee35ccc..86a7557d2 100644 --- a/server/internal/blockinternal/components.go +++ b/server/internal/blockinternal/components.go @@ -55,10 +55,10 @@ func Components(identifier string, b world.CustomBlock, blockID int32) map[strin func componentsFromProperties(props customblock.Properties) map[string]any { components := make(map[string]any) if props.CollisionBox != (cube.BBox{}) { - components["minecraft:collision_box"] = bboxComponent(props.CollisionBox) + components["minecraft:collision_box"] = collisionBoxComponent(props.CollisionBox) } if props.SelectionBox != (cube.BBox{}) { - components["minecraft:selection_box"] = bboxComponent(props.SelectionBox) + components["minecraft:selection_box"] = selectionBoxComponent(props.SelectionBox) } if props.Geometry != "" { components["minecraft:geometry"] = map[string]any{"identifier": props.Geometry} @@ -104,9 +104,27 @@ func componentsFromProperties(props customblock.Properties) map[string]any { return components } -// bboxComponent returns the component data for a bounding box. It translates the coordinates to the origin and size -// format that the client expects. -func bboxComponent(box cube.BBox) map[string]any { +// collisionBoxComponent returns the component data for a collision box, using absolute min/max coordinates in pixels. +func collisionBoxComponent(box cube.BBox) map[string]any { + min, max := box.Min(), box.Max() + return map[string]any{ + "enabled": true, + "boxes": []map[string]any{ + { + "minX": float32(min.X() * 16), + "minY": float32(min.Y() * 16), + "minZ": float32(min.Z() * 16), + "maxX": float32(max.X() * 16), + "maxY": float32(max.Y() * 16), + "maxZ": float32(max.Z() * 16), + }, + }, + } +} + +// selectionBoxComponent returns the component data for a selection box, translating coordinates to the origin/size +// format the client expects. +func selectionBoxComponent(box cube.BBox) map[string]any { min, max := box.Min(), box.Max() originX, originY, originZ := min.X()*16, min.Y()*16, min.Z()*16 sizeX, sizeY, sizeZ := (max.X()-min.X())*16, (max.Y()-min.Y())*16, (max.Z()-min.Z())*16 diff --git a/server/internal/packbuilder/items.go b/server/internal/packbuilder/items.go index e83a34587..c277650a6 100644 --- a/server/internal/packbuilder/items.go +++ b/server/internal/packbuilder/items.go @@ -28,7 +28,7 @@ func buildItems(dir string) (count int, lang []string) { lang = append(lang, fmt.Sprintf("item.%s.name=%s", identifier, item.Name())) name := strings.Split(identifier, ":")[1] - textureData[name] = map[string]string{"textures": fmt.Sprintf("textures/items/%s.png", name)} + textureData[identifier] = map[string]string{"textures": fmt.Sprintf("textures/items/%s.png", name)} buildItemTexture(dir, name, item.Texture()) diff --git a/server/item/armour.go b/server/item/armour.go index 6f454b538..d866d236f 100644 --- a/server/item/armour.go +++ b/server/item/armour.go @@ -67,6 +67,15 @@ func (ArmourTierLeather) KnockBackResistance() float64 { return 0 } func (ArmourTierLeather) EnchantmentValue() int { return 15 } func (ArmourTierLeather) Name() string { return "leather" } +// ArmourTierCopper is the ArmourTier of copper armour. +type ArmourTierCopper struct{} + +func (ArmourTierCopper) BaseDurability() float64 { return 121 } +func (ArmourTierCopper) Toughness() float64 { return 0 } +func (ArmourTierCopper) KnockBackResistance() float64 { return 0 } +func (ArmourTierCopper) EnchantmentValue() int { return 8 } +func (ArmourTierCopper) Name() string { return "copper" } + // ArmourTierGold is the ArmourTier of gold armour. type ArmourTierGold struct{} @@ -114,7 +123,7 @@ func (ArmourTierNetherite) Name() string { return "netherite" } // ArmourTiers returns a list of all armour tiers. func ArmourTiers() []ArmourTier { - return []ArmourTier{ArmourTierLeather{}, ArmourTierGold{}, ArmourTierChain{}, ArmourTierIron{}, ArmourTierDiamond{}, ArmourTierNetherite{}} + return []ArmourTier{ArmourTierLeather{}, ArmourTierCopper{}, ArmourTierGold{}, ArmourTierChain{}, ArmourTierIron{}, ArmourTierDiamond{}, ArmourTierNetherite{}} } // armourTierRepairable returns true if the ArmourTier passed is repairable. @@ -124,6 +133,8 @@ func armourTierRepairable(tier ArmourTier) func(Stack) bool { switch tier.(type) { case ArmourTierLeather: _, ok = stack.Item().(Leather) + case ArmourTierCopper: + _, ok = stack.Item().(CopperIngot) case ArmourTierGold: _, ok = stack.Item().(GoldIngot) case ArmourTierChain, ArmourTierIron: diff --git a/server/item/axe.go b/server/item/axe.go index 87e64f6c6..ffd142f28 100644 --- a/server/item/axe.go +++ b/server/item/axe.go @@ -63,6 +63,8 @@ func (a Axe) SmeltInfo() SmeltInfo { return newOreSmeltInfo(NewStack(IronNugget{}, 1), 0.1) case ToolTierGold: return newOreSmeltInfo(NewStack(GoldNugget{}, 1), 0.1) + case ToolTierCopper: + return newOreSmeltInfo(NewStack(CopperNugget{}, 1), 0.1) } return SmeltInfo{} } diff --git a/server/item/boots.go b/server/item/boots.go index 77fc468c9..55cfc0e34 100644 --- a/server/item/boots.go +++ b/server/item/boots.go @@ -40,6 +40,8 @@ func (b Boots) SmeltInfo() SmeltInfo { return newOreSmeltInfo(NewStack(IronNugget{}, 1), 0.1) case ArmourTierGold: return newOreSmeltInfo(NewStack(GoldNugget{}, 1), 0.1) + case ArmourTierCopper: + return newOreSmeltInfo(NewStack(CopperNugget{}, 1), 0.1) } return SmeltInfo{} } @@ -52,7 +54,7 @@ func (b Boots) RepairableBy(i Stack) bool { // DefencePoints ... func (b Boots) DefencePoints() float64 { switch b.Tier.Name() { - case "leather", "golden", "chainmail": + case "leather", "copper", "golden", "chainmail": return 1 case "iron": return 2 diff --git a/server/item/chestplate.go b/server/item/chestplate.go index d8dd90e49..e3522969a 100644 --- a/server/item/chestplate.go +++ b/server/item/chestplate.go @@ -30,6 +30,8 @@ func (c Chestplate) DefencePoints() float64 { switch c.Tier.Name() { case "leather": return 3 + case "copper": + return 4 case "golden", "chainmail": return 5 case "iron": @@ -70,6 +72,8 @@ func (c Chestplate) SmeltInfo() SmeltInfo { return newOreSmeltInfo(NewStack(IronNugget{}, 1), 0.1) case ArmourTierGold: return newOreSmeltInfo(NewStack(GoldNugget{}, 1), 0.1) + case ArmourTierCopper: + return newOreSmeltInfo(NewStack(CopperNugget{}, 1), 0.1) } return SmeltInfo{} } diff --git a/server/item/copper_nugget.go b/server/item/copper_nugget.go new file mode 100644 index 000000000..655045fb2 --- /dev/null +++ b/server/item/copper_nugget.go @@ -0,0 +1,9 @@ +package item + +// CopperNugget is a piece of copper that can be obtained by smelting copper tools/weapons or armour. +type CopperNugget struct{} + +// EncodeItem ... +func (CopperNugget) EncodeItem() (name string, meta int16) { + return "minecraft:copper_nugget", 0 +} diff --git a/server/item/creative/creative_items.nbt b/server/item/creative/creative_items.nbt index 2c34b9257136b44dd8ac0c6ab254f6209926ce22..85492bd10d4f9bc102332a548d2d4ae675bedb62 100644 GIT binary patch literal 140585 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

IT*`TG8=BCAhT zmiT;$xhUk^@b8RoyFZr2f=eazazZv;p6a@cs8S)`^=-Z+$u%eLf0TbO4nlrS)_(IO z;KL92K4{uaF*9?!)nq5CoG6baQp+ao|ePPbw zEq?n-5!`)dE;&ynyb(r%Wd^cVwalLKVx@~sp*ngTH7(MB$V}kfrY|dzjWBO1ve=Ge z>5?@>xMM6JaIK&tpvAK-fM*iUqMBd!$3H#jQW19;tiWW| z3{`cd7RT+g-uBJyQo^I;HvkRXkTYr2irop^ls7#(pSX$b>=NXd$Ar$=d^v0~MrE_) znxDwL?C#0|6>fH2tHO!8P~N2#F+FK+;&4P@6V}s9nh#+Ms`;>Nbd^Q#8PP)y%rhV% zenudz%F%)nPy66$A=7Uo0@Nux!B@me!W7+jXk)#sM_d zcaiZCY^G)X@V$B3xY-+09af`ytlOsQ#-vMM@@ndKUW2Cpk5f-0{d;(^5ne^}z}%D4 z_{Ln2V&bSZuA&Uv&O;jyKOV~0_Efk1?4tg1$kSp7gw3JAIOd8?UgEQD;6;1bXOi<` zI>Bh8%sOkEJ_I%$LeeT2IV(xWOj=SJkv27VB`?r5&`F}d1MZi@MFm}dWd=h?ny&G2w8`( zZ|IL0{b2UhR8UKp1C}*7v(_dbDjN8q`=p?!ftELnA$@;39JlLSTz?bQ-rCsx*XwPS zubL8YJvUtIc&ZIIcDKM0qciJyO6h{0V2`4J3T+}#uYwDUjd+mMHOmt__9~iK z-Y}hDxPZ!mtExO6m*uND;_6$G9y|co9ssR{dt$gv-&gGuQU5OS#@ zhQEtZ0FT8Q*f+**Ar6RI&MJ*)z#!GJw@ELJV!R^9r1gG_1IVqBb5aGO-|be>5%c1Z z75X>@J)#|Mcb>ou9-R&A)Q1o#^p zaoC?X8f~@9zHmUA8+`(z<~H>B@nCfEXW=xec>1Bxq~a>zL+h-1Y4a}qaT2dPliuCj zT42cmnHvTz%k2E85a%{=X#mY_R59Y9G83KIpd7?r%gA_#Rw?&hM!=l)rY24ykTJ%B z1qWi8W*w2~iG)|iwlz|WbNNg+x5ID+95RgYB`In!cJUP2Dq?ku z-4xu!IWs&`-gsX8Qdia@I;rF7=@|!R#3z~ohMPpQv8F~3-4T}gytG<-PYonHxlMyp5=SJDELr;m=wj*GfE-t0Z(~&5VGj%(!x77c~si%?tJ-j$58_ts&#**u$ zLuPtK*%h6HvF=t7fTJ-jtg2UV8S=J83zOg`D!EYybD@DOx2VlF1g^Ey=9<31C) zP5I*393^mD)oh!9G?e8oTfO)a8)iF+q=8`wV!&IGDiES89X!2;;Xnpa(yXLfooy5F z^6j>GSsb&~)vc5y-p&?e)H{&M00&XjNfT#4!recdva0@tgU0ZL!1B{6zP_iA$0EmQ z0S{%YvoE{t^QK;`^ID_^mlds)d@ZzEI`fK|_1r9mocQoN#1ODK%V?f49lHr;E~pNHhDcV! z(^@>O#+@_^1D}cw*?q8z-?^XGgL~cpnfJ#YAM4R%217_1H3k?6JvadK+*u1Y(@2a9 z;&tMKu}J$&Ax)(lB=N*?ix4L+rVXYU7>0yL5j53|=%`Dek%q(L@7jV$t5V#VY45PL zH^&!k>7%tuB?(^hW8MCK|Eu(U!LSG^q3ltBH6zUl(?0mXCwnHp7mkw#!ThnBKYUFj&=cYuXEpVRbcm?^%ju*s$D43c5IYQvU~pe(y%jb^ zam=k%!N&xtW*W%ula-(EesxP_!?srRyE{#OZk>GKUtmFoQVRfMc0U5(8$5 zW7D0uEmN=;%Vsj<$YEeK0TiyQZBtjYk&!Xu6rP-g4rn!qVy>+}{JP3FU)ELDR9Ii^ za^aiwtg~)i6m=E_eeiZ7FZu2edmIIrCui0)R!rz|BIP3A_8Niw8*xqw(7T8n3>|?8TI(6;KIiu+vj5ra{}uU`e{`uo}zVZ zOV^*1bb39;7?T~K1@J?7w8^3IzuSxY04sU^ei8{qS+y^#g|KkAZKLo_i18p@>J9xI;Iyzs2OheoPYVZfBv<`;TXdmwokk@qzFlE z79Y~F z8qT}Zd}o!~((_c4w-3N6HZe&me6aVFOYv&fHY*}PI$Wm--;W~2@(0F!UWkX^i@Mzu zGI0cfAwUaRvdPm;ZxjG$C~s0My_aac;`aWS^qC~$3w<_nj9w%tb_c@zMC~*;@^ANP zo2l1D*suOA86Lcsa~ww091Qx z+g&0OQGHrsmQj6dw#R(e4101hoCN zijwN-SsZ8U7y$xBppiyJl8H$eB+2pczqO$wVdl*-93FHwfB_Q-GF^%iBPfCCYP;?c z-gaXEW%jc07n}wbwfF8SFw?`GCJ_%j0?c`^H#Gw%U+j{4lA=6sm0ELIiDuxr%5leb z8cR2@^sqoc_pLBIFLRNkS|oFLkjuO8dVh=+#vq-54EW}0pyHV`3kMlh#Mdw;fdBC% z>UuTNckAYcuC5T9qPyIlb|RG~V%iLyI_UrK_)urhV1iP)-i`V*W)?`xc-rOlN}ga- zhZRyk@br2qCD|Y*%mvB>-kU7q!8z~Pu)0*Ve_sP;>g5|;fdqY96|*Ls-N#D@P-qG@ z`$agn7B?yGb8z;p>LmMRI3N0}l3ub?2l8j0cIE4AygFdPEvr2D2g)zytVu-{^K}fO z6t;lB$y`GKd%_U!VH?xYZht>^Y`OnzgC+9J zxJ@K%?9;sn{8Za)ckCK}^JpLu%>dyoOb|l|EZ(P6Rb(q2lr#Cw&LUTI+6E3Ba9vKwC+!gZm#cn?9m2_MF>zAvWiM zr;Y9E;pck$-uxD?Sb=$Su}-i+z$Nf@Op9e%*Cr|SA&@p#gg zA=)e;bti`$j_cuuX3>I7&7%M1ZEzgXPOUhJY6vWLZ`6PM(}Q*@-J6gDaxg{uJx+}n zC^Cu*4~B-p*;vF2gBcCICND$`%#rMH*s&$Nu4`^s;Ltb}dsbd7+Q}Lnnq1{g8HY3B z3J{_y z;OKspRTXcL&S%K6Bf+8vM{ms*EEbZSaew{P$FLs))U(>###{f9J!3h8kdv14aFxv? z$22Pm4FNgKWOp$QROfs=27B2so6UWQV&~)WaJ-(q(wX;X@!9}qvPuCBRselGMkMv@ z71kh2Y0NKRdBG5-Gz`uzgnmVqL9Dov0Fp$UaAzs9Uo2r{G78g=dQYqq<|Pev)lf$P z!%vH)hyPS%^T>(HQO1feOotigZu1u}qMyW>G1W*K@4X{oe#Fvno!4Q?=qIr>i8vi` z#2nA7rLR;mNQe=4)Cy6Po^#F_VMuX%e~kY|Jmuscj2RjRCfq?KbD0`3l8G$2QLNve z4x+45zF3#9g?zQ~hu3^tt;@xFuy-3(BrImWa@cQ)5=#&+J0(G?4vb_3L#(CuW=p}t zL*gp?vUn}a9j8U!GObr9g;l;+lVV_k0Oc|(id4*=d)eOF+n_h8X@XMge%W zvxU7V-6U%HigyZj*-^i)XTZ>rvqXlseXK?ya#oZnSj?b&jw|K`l^`g^ z8*XQ0+yO_d67Z(9H4tpa#t9laju#|OPz*B}apW+1qsVBCX;5TsFSV6`Ak!wR#KCM+ zYHF8#;ea$Z`UFJHZRqji!RUgA9E~|EE`L?oN@Rm)$}&f&;_1)RNe-IeY7sXq3$K^< z=80$Es(l>TRU1QQ7~Q~5e{MlUduvi*GQ_=b{WjgTPB5fdlW*Q%V=#erW5*1OLsP7= z<|xLnyCDZ~DY_JpTxyKr?_x~?N+C*3;`Q021pIA!=@s5-^M8!9!EUYEcWE5g4P198 z#O?9jA{w42_&4FarRWG~CtT3qj2s5m8xeTGn~78&5e?4+lC!bII}vh(bE8iI)!fD& zKOPJ)@Y;sdR3Q(}(_kn-sW>9oWtmG75=bD^SJd3zXX0(zx)d*(9e3H1+qlAg>zi9l zX*>C8#;*#r#WH}pL6jksZ=P9q=~H`!P=1gk)qH>aCKHbLYO~La`cW7d#adi+dieU> zv+|D*kB6##82pQ`pxp&zBzXez2;yqmF;YwJ?Rw>Ct$>(p>Y;Y`SlFqFVSB8QsAu3Wv)_90EH8SNmjeV!Rio2cgVF_!z* zrcea~K)-(O;|YVsdptd7`nlz%u295)Llq9pTQW8kO2Gv#>;^8Ya?d&tm!&lvQ>+HI zPRr_MKUdVT*&|}?H)_(Z(x?R^BvjQy!)IOG^th{DGQcp?50B-HWP@H;5 z>U6ldFg;-ifDFkE`9PCnWDi;?)CmWHH0i)&B%oUCraacODbfoJ&!~ii_!(b{5@RNX z^J>58@&1at9jHglS{ut5cR->Pl*#a3@&!AgiV|U}5Ha1IhC&hl{iF-C+fD+~Y5kr01cE#9htLgpHGmLT3)$f-4h_5#yi5pN#1WfExD(7oY*wFDZ%C~f45LW*4ma!P z+?go33Or$yMAxcqg@2>o=i_h?1JNh_P$btl4^~(92|JQQa9#hTExZS@S51sJ#vwk7 zg$g2P?Q%N5@D-zC7{e5ghlX-;014}u@r07pvsZ%Qts!E68S@KRRv_BfV(084Ty80) z@ctglwJ;r?9){wa3Mqy8GA0lt4#OPnk5?=UyvhbIqZDQ+9s;A#VtEss;dw=r)H!L= zpf660d4e~my2=i~y+gSS9JP*L!Su@e$MPU#mkWk(RiJd)XwHZk3|JMNyV0Bnc4TDBTt0vZqSq4 z^I?#5lExcPSOCC{Fs1zHeOrzdXnb^b6q|HwBcDCR2W(q!TF-cFjrDC)#f!xS0!wPW zJmrOX5Iag|Y(DDQ(4+Nh=I4-&y&ELJP9Ds9gJV*vZht^-nQuw`ZmP5W-2K`fB)!UUQ(6PyO zPoJAB?B%|n>tOG_seLExs7q0LwR&tPbEi9Vk{Ar&LhdeGuX87VT+905ho@}S_>x?q zCMg4_zrX_GtqlOZHbdoFvRwgy>atpGIP2*8=7+HZj-G^S6D8RSr9@E9flH@Bb^n!$ zP^8t;;6VfI>0Nf%9v8dpm(y;mOP#r;8E#ZX-d0QaJ;@0yasXOM@3~P&(`wQ8+Z(J; zqCzqBN8B}sC&BUb>=1(MH6}zesnLaTZ;U*#YQ~a$H`|7)R-esk;=3+qj3EPsf3xTPU74y zIfi&;AKy(p<=G#^>;u-S%K8(fmdaY+9*@4rVf`<)=qH?$w6fwq_?9TKJi%*BTAl-5 zI=vRo4Zci3;-F^hxwjW@>%^aBJh%)&Y{$s3td6-XTNJr#@MiYASsSV9c5p$0Le^T4 z=igKNQTsz}*=S%2H)mtwZa@IG7!ElOYN&jJ}x;UJ1Lg25hA~mI6-E_ZvrchnG5V^ zlY;L9%h>b`^QVMM>J*tFr{kq_qAO?YDkQCF?mI`sF={6|lkBtAEe>9Q3$0S?eP|hz znqm8tNJ*O>GvIWbgrE@vhAw>0-h(VoROw(4G52Qo0v4{yr6}y$zM$b+37AWu|6MN5 zsf_vCDUpMz%;K^4B_F?U+uI|M@7CpOvB)z~5A;=xCW$pBz0C4M?a3e~Z`TyG7BzBD zx%X+v_uGP5C=&KSq+Vb#onRb+80Io73VmtxKUh3{TxWIm@bT~4q#J)oj&QEjau`>b zcv2uK8Gz0Agru*9UNlK7!9*vWlV4zsy>7O3zL4W`m%NnstjJ*)#jfHv|Cpo?9crPN z*LDdb#@F0{tM{2QPzY@ic@9XIqD#iir3M=QF4i;PsW+f!ybnwg@QCQ;43wxdk%*6Q zz_~T;NYrH$h6Q+qgM)?VA} zm2XQ*23ahwY%+r~{6RD!s+RxU=#w!vw+Y0L2jlH8L9&9i_Cn88hFYDcw;X)U%05mU zjDxM`N<4t|{>p84eP_Tni#r@;cL-rCGdfYus`GodQt~8S$ocRLKqj0%hjT=F{@|?rXo# zPaYm-K=EcL{ul=ffp}_5h?*ww2_gLhZy8xgqe~RX+UsdW` z7wAS8N3FTn%@_z?9SrC-b5ahHP2SW|A$aFxx83J&KdJ#TIob)+@eGZo?qRajX><<( zAQ6_iioAm4!gfHu7{*(sok5i1&j*fD3@Oa!NU=r^qc@6B{9tSpS z5hnjnqGsLUv_HJ?rDnnQ&lFR^bMM$xweH{Al=49v!ig}ndYUn*@m<#>z!&4kI=UK6 z^N<~MVzFB^;!41X5`1^5N|{!o8e?6ZTkgFYIxB&TpItqCG- ztIhV9?*!7@%L>Hl@XT<}5IS2YwLg3b^W?XKnKA zr!R|DS;ZuHFRMKNC2!76+=z;2_WR7d@DVCP#M_YIq-dW#=S7`)uMEB;@)-oRe`hzj zLGB2SB|QvMup4PmbOa{K&HaTM7e03IHr_l zoWx?l?IuiQ<)#6ranfwe*zcx6CAQb;=`b9Cr==wsaas~;MPnx&9O}R%^f`BjmoUAx z!v#;V`2WTpp^)PDoT5ZEJr4-hc~M#d1Us(gwqKg&xULzV11Gc5bPiSJ!cD_gx?XuB zCn{;s0^a16AF6<}aILlYa}|jwZv{b7)-DuY0UR<6ozX+kAfj!CN6H&7Lw>0%PXWDu$r)zM851l?~OoQ;T~^nHR%_#YP!TLr;lDTF699x1Qb7p=NCjXW3%Zf@Ea-wZ=<0 zRe6);Qs=Y_A8x1FHvBD2wdtiVSt+xr+j;lf{y$DVjr8wftWlP)=Ur`6Um`*V2s4e2Seb&L0hk&9c1g>s@&rRS_ z6K7uuCfOvY^6nS+9Brfpqi2M~(Fb*mpn+67o@IDX^wyc`fbw$A@tO?gg1kX~v1}dm9ZN_UAis1Le5e zuGn<;8?}4yavYvu;X7X;A6GZ0-~afp|D^H_=!RN_#J}Zbk|Es3)%QN&1SfLL^L6Yq z%S4=28D;ot1eRQco8r#_R=m+FN5StLju8VT~U5#>>7u9>tSW^smwE2ebCIL-rAl+F6vHP1%HcvhV!Klb@rpEk1?MB2bH zQb4ua24Ee*w1stCFH%Y;qM^pn!w;eaV6`soYi@T#xfC|Or!-d@H{?{_cN>fdVC1@p zc+GY@r2#){|FWYY97CDzLqp^7w@r^i8;D2VR`pg`OtpB?t$pY3V^J3QO(RWb1l)htB9B!sD zF+&yC(b(zwPMxek+w`;!)LR*Y7_V@66sZR!Jn(E8v174qjTk}Tjyze3<)dR8C$Yw> zi@iT4eI|+cLZ6KsBSy*ZK-k@)ggh^iE8WvTgM-lW?p836-jAHN9Qzw4#&RWBFt5K^ zaxK%hr?=Hv0Kmi{TfK;j2Ky6HMZ*EmWr(qu!RW=X)vluyJW5L>Nws*BRwEI(cQ1n# zdA-T2w(J-e&H=HOi&0~Sf>Sj)CdpG1=jxmyGQ>kMRhhQTSBBwgV&{qEX%dd^919KD z@la@;8x6)mr==s;x3{BMlg4p{qvs_D;WZ$BKhxc zqv7LPG=$lcq*i(7HhTJU0`GzxOB#eNJCf#Y9$yPB<>@1CAw#Plq#T{(Xy%0|y2c)A z-Kf21vkGuSa4-z(<}31;j5bPKSlbpF*8Mg7oOFxbA3K1Uyh#;)A zA9Z<*$A>z5cIyz2`mn2N4jsJxxFibU=F{e9IP&vjn*egv=^l;ZBo&9*UZzKTg`ls9nT(n-20;2R- z^j>2%mIE$u5+@A(L-V!?b8SM7V{Y^bpqkszH-30FDs#%hy#0_VM^5<@dZvUG-o?7MA( z@dFsqu*_Esg$9e{C)i6@Uow#tm{-D4E9*LoUh+7@9=KDiF!5{vY??N5mr-65f7WP8 z=_v-LKsewkE&!Mt2IkD?0%CTZxx|$PnVDd~hTaH&z&X$el8c07LOQsV8<+&HDYc$g zvG@ORhV#Ws7HzSfA{-Q@hE7azjfff@0|n4Y&~gE|{?bkQOcJqoOi4YBL5v}UzTrCr zCP_7jCOP|6DK#-WwNyZtqQqP#^uMwa(B*G}P;+>kd*3tEay|AA%x|CZfEgdfUpXdU zw}E|?NDyjOl?q4`uj5)Q~zI`}a9&_==aHZDXy}lXdFr-w321h_YJ!Pxr zRB*km+-ee8YTnTAYjI2l@-sq_xZN>LG3kbVuMahCm1;Z_RCCM{HrAF|C5E! zATPI#BRD)6pa~V2=*!j}jVKoe*Ho{L&{agU={SlL>i3W-4ktw&;7 zo`)qTqZaIaSN3dK4|C;uCyOx#VkK&uvS)NOwtOvy!jvkp1WG-WKX2FDU&QXYI@O}g z$l;Jz?UitDlj_wJGl%SCvpwd!W+3e#6pxUr9MP~BtIV0AqskeVO=24y!&*MJ!{^vH zaChKbW4X(?_ufYtY(m6`nKVWPw0{gLB_fxnoi0*<&Xog=G#c(7cA|F8DA5vTcB3%; z;((iGvKSel$|_oC3z0K#9(OR%&{FTl!=yw)ef9PS0c$)pEpJpG+=nMgeqj&*n^WDi z7tQExf+VFu(OcZWWA>)HO;4wNsP^u`ch+wbX%{=i`{@TI7hm|hSn6P#IS4nu79nsW z^T8SBw3p@i5!nbYd7^wW1r-8=s{G)-K`2SHn=>B?m`84zVd1yxC1b-4Foh{$_Zjyzw&Rm%8#sqGAtx zFM$CK5X1eW-y&#)KOc+&@aAP)#)t|FfK%* zCQei-m)s=TbkP@IBbmy;z>AUTR+D#hlYAcIpxWS`HL&1s+e&;=SKhMpdwrLIH8a z!H6*%fqBOsizJadXtc$u`fB2JmL0O%`v;D+j4`hi%aosn~Ju%v+}Q8h&66 zc#~e3PB1Q*i*7$ET!`e)K`y!tFD^V0aFwnFk1xH0rq$q1vLpBy6Ikn4cfk#&{4^`^ z&0lY$5up|Vy4*(7_Ck-bLhUh$(uH7|&+=+p0z&3H6|6(ROCzlgiMZD0Z88)llvnEg z7PnE}8aWr);F&|C-8y0>udx|p&OWmq?!-txU=SH4;21=}roMtZUed%(Pz={KJ*+Ik z0HC*za61E>4(wu42N6)JbXweuE5i-ktZe zR35;zim0Xo^hcYz)V?9X?SpAur%?lhhU)3Wc8u`=`MqtP60i>1C;9HiSE9PjQ9Ms> zw0aU2^8=S5aPU^rA?EYD?&X3}~HO>gRASpH| z;9+gK+lpkW^+FF{En1UMD;KS=g|;mE#ZDcy@B0+TTPENw$L{^H&)532nZ+Q|298k! zsy`esYT7tTb)>-)(NJS(l0*-+cENtN%#PWPeQfh0gqZulQZd|@!={Li7}VqbsddyI}OZf&B?CjlT-8e6!5sr$&%HM>RkSB?1gd*1H)(a z-aMx!FVPrt_`gR?pe=!;m~Ea3mu!u0u4VHGEWQzI0iVTM@8;nB@vP6D)p(}E>NzKj z(!Y&Dsi=I&?Rg0uXrxZE_o?Hgo@|n9@YYzG%ywg7^)AL$l=|XdNRMu=F%^2eeER3q zyL)9|A0&>)l=0@@Ubzi$&aE(1qE=aoMH1CvKp=!+qHaQ_vNm;b(_-@NH8%P2J}`_TZ5Ggbq;iP_$5 z-h#4EQ+d*F<-XNc7$E`0!rPk>4K*O9w8rrCclCCK?J!j5mA!M|%|PZ2#WRy5>;CSO;o^Sqc-p9F}^*TH$t@xg+iT z6!TSPM=$1kcUSL^y*}5g(M*PsICcykmS^yW`tk4SrV z*3&?1csgw{-Ip*({Q8A506LyZXflH#B#j!Qg z%x%N~%w4UbOnnizS|_E%{HKJc1{A+qRRhiO5m#$~SKig&{n5h*PBrvsGJ_!`jT)nc zT6Ib!$*Z;7PGe`v)e0)**Qci^PR0%fzcUqOk)2wup(bo*;D;{3lk3f7-Qh*1~gt) zTan64L(SKs7vhLxQH3}X)%x;nr(zXJ>5c{#sPY;H-XF=24K=J$J3o6`MqM|Yl&g;% zuwlJW>1{bMl4|BWi(4yn!hvSM!w;|dwpy19P{&a`WYxZ(wJu)^;vp-^FnXEEkHzkj zHqSsUD7sU*+PRI=iy7~aeZJPG%`66yHgF6rn0a8fdX;ADHTfeNYAB9r_M2d&0vfoE zv(BoQh22|*hgtf!(pz{TmfqdCEikx1IZY%CFSLTqG_ZQ;xO5GsPDrnDAGzKg@Onp% z*8o^?Ru?TOe_Eq@?#{B54L`5gC1#yzmD&yta}THe;bmL6IvcYW#%g4n#WBy<+J*QS z7>p^DYEz-qMgj^398T3iyr0@UEb9(Pa^rnadp9Fcf^fv5soz-NaFC*@;|R`+(iTj< z(y1Lx18S`x`h`^-gkN*6lh2U8V!XEWiJQ5#%-jxW`BCn(J80$6HFu}#2#B#HKrSbhJXQO zRD-qV+!*TB6jn`A7l8N*CWuu?39pTH@nL6ssJj=nedn^ z*E?B^F&LVZ`YKdn)z@MuOi?%64+pV#t~OvsRe>z`$=%cdF#e zD{57v%3}<%X8OQIjEIaXIYRkj3TGXFrJ-gt6g}o0@(Ts;`1`~(t>GLP^oWIV82M>i zqH}1;(3%l{E4jMJe%>DMzgTy3m{UyV>!Xb{?t4*_m%$#ULt!bkCyw4I3Dfq%llRUm ziZgoh)6EaBSg_2LZlDiHfXtBqd4_<5kkyoAYu4Mm)ccOdJsdb zxjgW$qWH;tG_=OxY=Q%Xsxa<*QC&I<5e|x2WAJA*KVx~@j0t9-+{n_t=N&rtiOwMJ z9ETUIQaw|cAcRa9j#_`*Ze$V8h`le3kC+)Qj>dlPe&YRc>XAta=kAdd>1xMn#XD_CZ>u^Q6Xfg$m!2xPDB zVC1%NBW|A+cC&x2K+a?x7P<#9v{U41pjF9Cha&uw}79gO(*97qYE!B_H2Pg>2JwXvj8wMnd*RdNv0FkZtE3al2<;or0_I=eiP5(E_~_aBxx@b$efA1sa$ffw>7NvWM%>5;B_PFoGG?* z+HWT0DbPV#Qqetb)y+8K&SdS(%7(&vhFVW8Kz* zVCeHq(Vt&SxL;7)>NK@sAk*q7Uex7qb3sxpY;aK5Ec<5EtqX;B2ra%Yv!v*`J_098 z#%!~!RG|p2LzG0||GGP3m>lI**WMo|7~BM(cn4D)m5X>CfeT`tb>!VU1C2C-C)qgF z(gQ^(49vG*r?H=NmUddy==_)VD!1)_PYQ$05F`h2ZV8!z7a zbU1#SHwQ3v|98|)!EcZZXuZ6vS#*-I{VdiyX7k)fd+8;TQKntOx7@UFP^PxCWs+re z%$J&^Z#h_Rdp*SQ{UPQHtcMo=whB&J2QlGYW+;lP}-@iH)FNG-f9`YQ`-6Ly1Do26+^Z<%7x$h3w#Stb7^k{BaAIV22M1N?ME-{Qc7(L|Twqa!e6q%cBWvo;j$dak@jF2T5&{uLQ01FIf|~@6|3Qbb$T6Qf*&+>|+hKVM zIC&aqj*avZ9uCwcvWVGjB`Cc=CVnW9{DPm39w|mmCnyY1BjR?IEhpYtWZotkIg^~~ zsogqy@)9BMf*i}4S?qkmm8XH0acU%=p`Tx_mAj2J^>?C8115o%*)#{C#^Po$PVbrw zu@{XSR(P7qtyKKj7 zz24z|`hde_FNGR615KqS4FT?i(=u7ZH=;6-0Br9b)-_9fZyImdl!= zbotQF>QH);`UDb(`$Zc-qnhi8mv_B*f24XvgGyl#L|#PKqs%wryCY6%R$b%u06ZzP z9=v(mo(5WWlepJv2?2lKDs-kN(X7NSVL4t!G;ni8jEjHcWmnXB$Nr>%eU{iiXUemSbYc^*%g)NR#;jpDx`w zNsrA6a85^oleNtD%d+G)dO&y-g<*^52*T(d{e)h93&!Q|C7STu&tWWrVC8X@Jw1ss zm4bcBnf=TLHUX_h;ltOWxb$+n+ty$1?!PzxEkaKD>Sd^i6R=9blfK6$Wsnm&d^X5F zvn~RoVu(Aomeyn+>NmW8ttOjP(uaJI%(KNVD@3M+E+>iV3_|!?S`A4X7m0A+hX`~2 zPaEbuiYkPng4tY(RRcz?%B85r?9E;S5wSpE_fnJ?G-;ggAMH$ze8YilvZkQ*Tp$6A z`>2%bONm-qK`~$Vfg;TN{-971T<_kT6p>CMW#qb_yE&&H&C0<21 ziqgDN5S&Sr1h3@YXV`#|u;X9Fv5)C(Oqn~b5jbVuXT@_R>ZJ}T^z`oM*@6+36ny%S zneHZ)JlM8H{k`V#)BzWJYUgvKX9OhWojO>ivuUL5KGS;KoOUn5eiXa+ZpW#1kS{MB z?<0HW4R{gg7f% zig1AuDWx!oY{8zq&Z?J1nY{$W`+i$YDQ%!8ja&v1IOzBfr{Z}RGGpHx{ggrm`dZ0i z5D^1S5nj%3o_I=uf-8#+=LeA{Sb)bvbrMViP-20R3a!%Dn>W`MBHe^TkqBvgVx8 z8!_`B=3uPDaDWb6BOdIJyPiNpMSJP#{+YUDg$Lon0}q=7iW+;58Z#;<3-szcfRH?i zBn})3jQTU?8VIx>%|pn|OS`nxS@K@uH^)Nc$;5~fkm3T9i3{4#s=@3fY0n!*mn87t zBTq8s)}psG>4oP2yt}vqYMEM>6G{_U`%$M8^P%r8#ZIncKzK&tXGu}nVvMKJ*@M6Z zuU+}9kLLTg1UBl^sVc-JW_NcVbpXuJvGhPv!f{`UV-vh5YDqkTV+iq-o5+65cJ^NO zr(=1lH{CokDPrdX0g2jhw&x_DnVkG0h-2$5?^x%1m;aJ{$i8Bve(^yv6HZ13Bj5gj z>46NZvHfdZjyViH3t!|7nXHSV~5k;=x)r@15b4oOo(y@z_Z1dC*}p zoh75AKMWXL^+3luNpFA)0nH7VLz5Sz&fNQ|kA7XoBc)&ejEE)Q2kkN$HH_^0gDDy{ zNl*a>uYrm@!S9QdUOmPGrC0Y}oDA9HTZIXD?9#yGY`>##r}1Fv`vosv2JSQ&GW}Hu zqm!aFOJ8e+;+RPs>8oe@c6uIqE;eiilF8^{Fwh@V(dbFqm#EEKrJ?i%OusJU;nJ^v zMy|XG)0+!#6)Kb>Fiuflu=MRT9xi>q;04UUoxWAbfTw$mIb$Hx+mG(uYdmOrf5MBJ zp?kd|*jSS$&BX?n+6{u5th&DXrbmDAsOizU7cay0myDGDb_!$oOlQ8Rq`bZeq0EbL zm+>I!*S{AhgLat=6-MCw5i|}|z?}3-Y0rJ`-}%m&kuOfGHjn$1Q)vpr5yr* zgx_-klh#!{PSX1I!efxG-Ucebv5LrNDC^DQA5K|S|I+JV(987HFQw97^yBe^#jt^@ z3JQQJqDmwM1$xk%R9scn^o>asDFGblJm9U920HboIbuCln-~L2|8cEupD;BNF}mPo z1yO&rBN>&s^s!rv&DX*ePelDIlQ`(?NG@nB3y~%~o12f|D${Z&9<+I5r5=;`u9Q`ecWsot46aZg#e2TK>y8&B z#cL9z<2Iwl$?tM=Kty`AS);z6rdE}UQ16^}9*PWe04#!r1EP%V7jp>!)iowbH8<|R zU5VOijTI%HXuEs(yveuEo4RK`W9O+hukMHUC~ME#e`j{0-LkfrBRowkPrJp_wrcM9 z9>xDOFXibAB;lcTdIdrjvGC(v{~xiKf!QIhYU~y(@sfEnW*YI}=Gf{>RxCteVe#Tu zS=X5scM|x|zta)sqg<+Iir6&<8(tmDEqir4c3>bCJSI78Eb`gI!f~nxk?0_uKtH90 zMOLs$9{WgICR6NMA7>vK zz`Phw(o$ma{ojx=udM_?wlOT!Bf9QQ=;$B&3Yc~%LkgkC(w}8aLC$jwTcI0Dz<1sI z{n}T?A!*VW=po~G<~>qLgyGAq)3mx_WnK!u*Hz9b8r^_0FQCB4w!R@BbG2`}b~nV# z3o+D&626KzO{>0Gp-qyIA#*cXp6cIwU&Me(7h1anE(eX>NTG zLW+Y{H#c<>QYXNVZ6luDI7Yu;Z!#WG9}I99Hk(k@WQ+KkLRA9@7%Wo}ZoF)$idMuS z$-i+$J%Eg;g%9$8F09Z5fn-7<1iYx^_3MFrZc#AVYIH$@4CZAE7tI42YNk zq9YVyOxm~SlE`%JZipEWF(c`i8)C#Q-S1Y_EdpC-fBe$}N&xR&^>)gJvsW|k?6S#} zvX{n8k1zHGO_HNz+^ZfQ(=~x+0BZ&o0Mq$k5fwY9YStGn5}y%wp*y(h0hen!8XUls znM&?be#u|!3m2m>I&cVq=#9f=0PT`i7AiUocggt5xHb{1NK>R2t2|KYhrMWk8CIzRoL#@-ye2a&59)cbMcg5D?6Mj@vkhys8waBSU*E+mfyFB?QWM< zi|ps^@&1d|^ssb){KzgviD3k*!3IUAAl0K9?4L;m2MWWe92EhH;^oLKQ*g(_Jv&M5 zY(Df@2tem5wk*r6PF=t@=F^kg^I=dww8W1;EP!=cR!RU5-IpTw_I{(%Iyur(ktT*;a__+1v{;@pd zMN59JjFo8&Jh?X5vD#$YqSw}y)KhAPrv%QeRe36Eab@ze0K?%_fAhiR%b_~0@`Va# zERNe}9T7nl&IeZcMvGXLuLcKjq1FzWxr67#Eo!ygoG?arJ@~bJtRrbJrxJ|TM|fjQ z%0GOqKe|)yJt^wwM@@<@t=U&plg`SuKd*5zN=wgQ@p-+P{y)xn;&>UjM%i(b@1B&x zLh6?x)=N9e82I*3zS?Ife$;=pUtQE+4*A*9Yz03~7x%IgykJQEQY!sLKOR3=3@x|i zu@=FDBi;--4J)ZNxF&f!NxhX5x%&=&Ay*O@!Z_I5xU{_t0heKX1M3lZ{ z&+eniF(=`(UWYHwyX>e{@qMfr4o*Ssyg%wFetUZSgPp#FQ1_7}_h&pz9Oy9Hh5z`( zoYa1#PHI2Og$-n*FV*;Qp3q>#c~}*y<^lX*b5X!Qk(3rw&IGJUl~(GXM?C$Mvw$|h zZo6+@8r!i?Qo$u4&I?N-a)rkZX5Hoxd|4dYmTGO>cLU=bwN``=Kj5^94@tVuE;Vyf z`DYy+(Amn$A{SXbR6z0uct5X$sLGXK3E=9qo^C+3yhAi?+lMC6Zt8)6pZvx(b}z0M>ioIrcWud&?wlx>~W(lb8DF zU65lrcgoi^ds4f^+5y#onCN6v)|cPjE$+=$^LW3jSk+5T zsX*qEQ~#?qu?$voRN#vwK_1L%JC2>m*!K^cUoBQtp^ex7DEC&qdUdi?tE88fAQzGD zC3SAFlC+6?8mGXO-;es%z#nVnXwgWQ%Y?oL2?)%Ih{wIBdi#F6DD1}qsWZ*2Jj&yg zbl}#v`CESl&9ZBG#QJJo9ta0y%lKnrG5WOn676C-7&0nf2lEYmNKRU~TT&Lp9?L5T zRZxuFjf(fND(PO5be=!Hk)p=vLZYX@HGAnjCA z>dNt6=rW5)juBE+;(D!OJ<4Z@Pw&b%FXlygD*dXwR_r|r%!S3MbL>WLHnRxjYHT^> z3Ak5mOMG&P2Ek~otT7H8(LNL(s<%W0EN0Fc!?v}&z&Kn@5HAkJu6(YI^yIl|(ah4U zmW!tN=Du~Ul$-wo${#ovcUayJ=XDtfWZT_x|5%h!TED!Zwv>2|`ze_^YN{yL;3b(H z*8tb&^S*@$SdOve+M|IVJbmsEa!a)bZg-i1(YU*SjZyilq=z!Sz;8HS*gN1#;0p94 z-Y4Rda~USHV56$J)?ZNj$XC_9pj+H?#|_ z#1>K8n>;?Bx3m#W&Up-UKa>wc80FLbH>>F9-&}mPU2V!JBQN+FocwXJIZWRy_}?vo zuc-IxB^M6{I=xX@3COA(yp%2r;+!Z&*4H?1wENhJw(fg)yFQbm|V*T%JzdS zpp*EZuPI~CH1_Usg(I57)7!U+uUfik&A|o2yETUAX?3iJy=#Stj>hM-EBAJtIx;0@@O8R9q(}3 zbEWGf*EcPCTdQ9`eMvy8)V!;F54PJeMB8e{@qpd@$=_)q(zpk$rNtr{4?PP?V3idd zGx3PVpXeVmC&+Bp<5bBNaSCvn$DXay+gci2D9j}#HUu7ZGp;mljhSIf>t?k#iGU@) zl|DS}4y(m%UtYve&JqnZuws1i=22gfy;4pX2+0*9<->>N$trE*lwEF6`#kOE z?mX>Ri?0UrV-4Wb%u5)jdpn`jCja>GQZ2dWYrtNSq51JxzRYlHR~An02g3%~&Sk%g zk}vh8T5?X*LBBaEs%stKdYaeBs-ET{0CHNPa=F*q1~~Z;)HC&Efm)e8Rm{t*NU+wZFMU?jkDA+G2Ui#5AQdusKZAjdtxeXlH6;nDY^{Bv z>nZhe^K<1u{6f6dpTY2UFZe%eT?1)HoNn zv~1?0MCD%C?Hh@NX>Dbz6@-<9w-Tndv5!KkoJ8GX&1N5T+nicC;3jJp9gu(p3;Emt7aifZ`3+qkpVBQ{DC#ojiZBoH4SeemJfa zQbJuVYx2&@bE)I3@K$`bNm<|*zPXO!8d%lyuq(DufTmB4j2(sBlWN^Bz$#jXJNwOo zsw<3Dsjrt>es%l~`{^*Nep}~wpGRi&Mw0sdICgS`hvj~8{4D`9JypA^a~j1?QbSl! zpCwFzwxtf9%8N#QkC$qlusi)GQ3$d&$AY^U<*TJ%FJjfqHkWz+7s8wdUeVZrIJTA97lfQ@M`Q!4+PwqCy=j1c}W%K?VsPIT}|7kGG?cE5zhJF-w*hck) zZF7IxaLhV>wc!*|n{VI0{`nwr%@62le%4SWr>CV!+9TJuyqpl+g$}NBOzviWlHg6m zFK2bN%xTF#|BNqp^VRZx`E|a7K4Sn5U1X>n7oFX`5rhqWv`DobzRjI<(?RQa>85k^ z?52gyFnrZ~*!bzJu9hp8a@3uu;c5rv9_M$%-p@q&h_}w`Yx&cXyZ(K?;P;E-@NoLE z`^<#;oDn#Dv9WSIc7FdxFgA4AaQJM}YQVU;&u%b8|iR^C9C zJ$~iGxb#$wt6@Q-8%SS0ns2kFo_2n|_qr}6w;*oQP zA+t&pHyQ?T_`OE^CwJE&wg%aC)!Z*^A|j56}1FBcAO0KI=GrK?>1C-yN+eUYZb0O!{855;;` zzDmC<=9}`FxLnUC?x(=F2=4LqkXxI4`Z(G+GJ_!ifNnB;OU~?^0K9kN(IV zxWBqD4#mq?zx=Q*e~T&pv5B&+s4{^rbjC%&qwUiR{_|qDrSH<&>+ylcI`0!=(>FGP z*PyKEL%Nr02hPLT-TjB)(*~DXV|rdr1o+t}Bh~S))iIyPcYj}7S7*#6-lq{Q6l^p=(=nurTd&Pi`7*NjJ`O*pJVgf2 zA|NR+iiZen1w#+H(2-4W2$IF>Xi82&S2ej`>Em#4t!I5nIQCp z8*_J!pJZ=N?i#+>WQPV|=j`OG(OygQ_H6{dXc90Oem`u^h>BH;*nN?=sOFnQNW^qyy=Uw^GaV`huWw*qJO;U%IWI7h>_@R za;|CV0)j^6=eFg0n)#bF=j)sj`r32B+Vi%7H`N`AvJUt1qxZ}E!-G?uw*TufOhG1@ z847QTtgXnd<5%7BZz{}J;m28lDK`&Dx8un$>eeK~`-id+WPi~uRmh`D-mZ{Wy=qq5 zgQwSSf={|#XEJKev=X-I@>m=*8 zzZ|nQZCAb$3e6uDnWQ60?Q8%&lAGhlAj|c1`W-ml4Jt z0GcIx|xSdW%D1v-HnRv*nz-=tOVmdh#s^#A>Dzhhjg+P}PA|7u-4 zE}}c7=pWT#ZGp)9*;}=xzGAy9wM$!rPgJaqFypqurpf-j3ALYPf$}p1pTA!$clVpw z{c^XMAC{YS`AR_UGXbt#a!(t9>fivDu~^^F?snxF{XWGk*U~Q7y|QsMG=;XV%fJWc z`h~>yD8uPX69d!i_*)HCsQuV}&}!UBqLW1}`Aan3w?etex=```bYqc{8LZnc?z`PqC|ygeL$$2fJsF5D~#u`3TN_r?0&e6!}; zoshw6Y-MoELuWhoDY)L@mnJ+EB5pU@#iG}v)2(7GB*)v`a{pND%4bqyp*GgRilLJr z){{8^eIUcY*f7@5#M-c_(u3-Yc_yBifOp|Ilo4K}dfD0grv{uf-WR*_dlBLXgU~L~ zSGxf=Jc!j*xW#3rNv&I#R?-3TECyeM4T(;6?*7RDM-1DiUHRYa6n2=340eIGwD@ms z%kOxL^4-_3miy-)eAZZ21jd-ad_0!GX<>zIICt0E60i_vf3?^hmh)mYdni_KKfWuU z=G#0vr0b*(YRWF7hy3JSOCP{7iw;%{w@geSVm(z>#5xRMDoz7WzlsS)YOCep_-UMn(+bxM&YY@+0l`buOJf%Jfb7-2=K zmz}+TQUG7)_6=q)g;s$~_Ob%$1W9&hlTvBJRc4bz&^APNu20@ zgI9)VtYcs>3yJo(i%9aP1S4yI8VfRDb^FK7=J1f%qDI==b?Y>VbX0W}X@BvFcz6Mi zz(LIOBWf=zW|9XU!P+1NJO*CYD;E3Zq5QzdJ7+O8R@&J`>m*P*40d3yWXS(4C=um` zT3dYjSE~0j37cI=r?Qz{vaV9uUe%~<*7LAjov;VGU!LHIXfx2*if~b7mT#r6Gn831 zvrA4YQy*6EZoC1gxaw@9wfqa?2zfA<6UaU^w~WW2pK9tnWnyDHeE-UXP`BNzmivd} zQ_2yoM%r5;boE9$GKCn8M%uSEkv>jxdPxC@!5qX07gD|K^74|fJGM0u&j2*rPp1N@URKO{+-ZXpP~NWg6Y0HwqK_3u4=0Jvs@@WP z1~VZi3*4d9z*XIdVRlYWD|Y{+0pEZ8EzlTaJi9fr#d=$r$d|+#Gp_VFRSiMBRYk*$ zfHJ}l2pD=d3uOP~fKBXiY1x+-yvBCfhWzRz)LV#!&i{3?*UwOPgc`9~? zubHVD`vEn2jcNHg$C9BzEyZ-GO>_7tx7h7A?=4mpuATlF$`z~f zy6&R9Z~1Pv-5kG~FyF6!x6joCMdeAOX!z=JaVYG|39oa#F}w}y?jessE8iQDR=!W) zSZ1ESk5((+ACXpma5Y-_!HBf-!>iHC4@aaG``g3Qk1IbKkyd{DYHa1VN2HY>UyW9N zJR+_9c?Kf4;O{A@&8`T5mo<>w>P${${hR{n5ATKNlCqm{of zBCY(<)oA69Mx>R$cr{x2izCv?U%DEt{G}0T-5uZzt|s+PesVzIOi`lh%VLYp3@r*Z|)b{@`pN$ z!@|A6@XE{w&pd3)MeXEtrX4(eJl{NSi~U}<ukyBXt6ryXDSB=Sd<~}i`7bpE7{G0_4)_L zL1y#Ky7c46M}B6958K6Tz1bZeUadFlMWDfc^JE+RJL(AU7yH9$fal*j{e8RHA7*#U z!?V|V`$bW;cdlmGCJ)c>lBbhDZp;im(TTLu?5VjJd*NLtwp{%ut0V30DxIcursUt)zDHbJNSZ zcwEF>kE)hfhg_{hVyeE8igb{3X6FS_hN;3wd&rgOoQw$5r}QyJs=~)Q5a``DQ~sLezar2-UbZ&#V}S8-YOy;{7Fow);i;2m#YnfhKf+}y6QU3BcI z^&~gJCywcmBVK&v17lFkrKlzr>tOYrViElCl~3E*KC+8V^|G^1XcLrh1<&+v-uctx~kxj z_Hq@vhX~8T<(4BJExI*_9X9WU7@fdrC?jn=IL5Db7wLUtS-VT z`2A;%Rgv0BPEV`SuC{P>=;fJL9seNdl_NU$NHo2RKsm$ILomxGFT%697t;2=R`Ng& zO_$pG&mWzGa=J{*m!+FJgs#J3VxOK-rB~1Ce3jby!iC^seZSZ*%WL1KA4B~0Z(iD9 zCT{BGDq;-U`!JjClFF{Jq-EZh83d zg5O_nBq?A_l4}0Sm}985m2&9bO!ZO@(pMZ(_*vn*RWbka^z}g6D1Z*Gn`#yY)^K|Q z7LZqM!LzTP#Dq|bVybpx1!-Uh2BX+yJ;|%9DQqp__Jl^wEAHKA1Ag?c3oQI*`rU3h z|FZfq`eYPAJ3IA*pIT?vyw#=_&kpu@PzwOo|~a`wjn-KNP#ApJbSM;5){w zt`>LaU4vEMh?75;Wj}RN&L5Wh@(b|$nc~&>rrS8O%+Bpd3V~z2I6N$NCLX696nr2= zBH{=EB__MIBNqC^8rzmk=m(%Th%4r6;LME~9M3_&$j&U%!K$i*oHHPvpJmzkovphA z1$_Wn>IMFv5R6_Y3^cauP_8Y>#LF!1!#A^*_JP4 zSN_ul;r15V43h9dP2<(ej^*1NhlRfMK;uiS8(e((nnn2;{!wLo z)m~A^gjcm637~F!TEiMOPisc!`#Q`Nzr9^>3%=LoLBicgb>FQv^Doo>25MDKIlgZt zqp+e#U#U1$-!epDOiZTg(@8A$Ybra^@}gpG!sjvad04T2dy9rIs451hWeaQfNmf}C zE6AX%etWz2Zb{it%nV90CB=GJQ5%p})n$Z|Y#F<9FR?FADl!v3Ot?#JdA-r|FeN9u zSpZtlHRmx@`j7TpLs&Ro^?Is;Hj}`(oajWVg1L}jMfb(-%UOA-wSO$icfc}^9PxW= zzppB&S#qLNGc-CE6?n*C@}mhe-DEc_t`cITNoV+CXTKtd!h>q}r-RE34MX6q`b0Dv zXlzC7z(Sjd^nF(oA(0~G++cGnmQ<6X%Ry)^yvtHFcWi7$3>hL{Dr7cn~>KK~3ykBoh#;B-SikYiN;kTGm6ns>do2unmCPRL+yZrjL0% zgUodCRrxk&xgR&Bo;0Ii+elrhgu-i=Vy7v))DWmeDd1kaT#_G-sK}=jVsnPE0_Fv& zx}-frlW?P&Fz^d`x;oSStjNw7f+THGrx+PLQqLl%0f|nRkwin7Yz%h;RBKnbnI6On zm)syENGZw%Q6G_ZG3k}_%z0Ac=QtG}F%$rDhOrFj#y5391JysVxT(>{?`Oqs;Gdvu zRC<+l*5?&aBB}bI4YR6sps`(Y(pEhtsD{g3ZWECp?2t%#l`y}#6)TT`Y1v$QC!A6f zF%@NmcP4?xR>VL~J(!QgRIWHYMY;^7r^n5H|DntvkH0sOSpb6Sw&<-|ho^g!S0=|G z=;1g<{qRDSo!d!NV1J)Li*>hoV!iD;m8W0emk$)2RFk90k4cw7Dq|G8hCEAD0dr&# z#ut-5oe)G7tr?^hX6QDT!Q&=7SqQv_G^UK4TecK%LwD6S17h%Qq?MF8dJI;n#%=RV ziG_W5OINKdU|6!Fl{(L`(xOMK>`WA*de_X%TWkgij$>Fj256cp(;VC)0z!f2@hSgQ z0CFelrka%kZ?G@HnzAibNN!FIC4*8uNER`lNCzuW4`$O)T2&XCnHb9mHn)-nYM?E? z<<-F}QxWrkd>C)lr^7uY3)oS$5rb{42f>@N4A0!M%p@eCpA4EM2HM)yxvi*PH-^tS zd`u$N74KtbR<0aKn{&$xR`MJZMD`buy*U%YRu*Eyz(Y@(dk$U^hu|L8=BwrX^6MJq zeBg-dOSMM5037dQd6hvK9(K+V1X4If5cC~XG&P2q(b0<3Lh;3^QCP`2(!+;ks`Th3 z=s?5BpVieOAU%WPJ~A}#U}%-isxJN7AE&fuc@akVqgXu39sBE#P8Z5!&zGE|S$MeE zY1Vw(QI^s|A33CY(fU2{vxJFTf7++3IwFsQhRQ0V86B-y-5Tn-2xM6tuka7kSkHw* z)v0f4IuIEhEg3T05f4O-Ug3e%gFckC&++I&#Cp_Ny>q7GpudqIPzMl#MC>!!&5G2j zb-w1hi-DT}`QkvPI{s^1m+Vs%K<95NDGv+2tPHZq3WThU&BbGuxviruXEYPo;@ zA;al9i9`+v6`5Y1=^-I%)d&nUS?_5apF&EgwJi{8JR-w&ZDD9&xCVPD`R2Yr0=|2#CC{q3?w~IceWzQCw~-kMDzw>xz$*P^idffXUS* zu(Y{qB&a&q5hOB9LW-ET&6YxQA64g?-WGdV&GqkXrz3d8+hz-?`3%yN>TQ)xWO@** z?mX9}6t7Rzy+zc0Nu*axfl{cm^oW^+ z+gs`k%0{4~j^afOB-X=MAy=aBi0L@2A}XODlFVXsuH%3h0Z&DSLLzV2pQ{c!IN}SIH%um8!A`A9$VKs=C$4D zsdY1FXD=DIuEnkvZe4GmcIAK7W6=KXbmETtR58%e(uSY(%NNPP3s*Cny8*mpYp5D( z9h#3;90Y}%Y(OApw8{CB2uUO$(W#KoeiX@*$bh1Eq$L7oYMfI&S;T1b5Hd>(w57F$ zE`sPjA0)8|se27Oe4f)%5DiU~fGTEIfAUo94qvNgC7{B#Ru*nawCW^OcC_Q~Yz1;` zkk@N%CvVNU*wIhVYWvQm3ltz1q>>vhL3c8#UKW+0N}4zuTo!XST_7Vhp6Pb0b(+@A zk;wBeJ%a*{w3=D-z(yTZ$F|6&Xbpzw3@e#w708tu3rx{a4sh?Qlqm+ zeFIr~5WAc_%BWyDgA#kz2oC45B)rFnu_R-sG1^Cw!6$f+^Q$p0)Y!5iMNT-Q@Ft5` zZ?ub@w*}L6Y!vQ2BsGJG?8xmkLOt!el}BS>^MCve0S_jg9?>u~^)Y%G6ON5j*BnD) zFaYFo(n!c{7krLEIbswABX6>sTwO5Kb2r|r{BY3ei5)088tD_Atn7K-+GA6KDaSPIa(JOyb1Y#B7-h&je*T-xqm3DYKc3{ z1SPndbWzm!!Hnq=G!#BKaTz)H<;$$kJ;U|m?C#Cbs|yMwLi@+f=I~JZldNB7f_*=B zjD+A=j~0dHo*BqjE!5Pc)0Br;;q_EyB=7EAXtaN}#HvQy6B2gxOh`42x6776M2R$7 zTuIEQN*l>D6^q8z5KLIm1}&lBuQ&0$2IjlK-yzr9xeDl>iFQ^9Hig z=P^fxAi@JoS1Lc2|KqVxN2rj1^s0Q7p-LsgAN;DltiVZidyuMvbKS?J)izp@_I8!d zR+HyFQd*TMcH}&Tdmxw`08WPjQ>tJ^SaNO>lt>};Gf_pdtZ^)Uier_Krmo5B< zq?(ZPMs`UlvH;Hhk5sBeG1`vzu`^4_6?ok8GPbUta^vDcw~7qrTc_XcG-)T_tXgq> zNeobdAriJAHUtSViM4LG%6rfv2fLCM;$Ot2H&$$9!^UsrQSJr= zOhc=t*-?gGE{j_2QLqH*#34sx($xSrx#+Q=Ul&}sWC`C1rh_%?i7D-X0k}yUd=s54 z#?FCehw7$cz;`Ha^=c5di>nu7jojf`RCVz@q^WRF21Mc1I74pC71d(hEgE@YsZ%L+ zaAO{{?uS3!e~RQYK1Q0L1LdUo%+;KZ%1>J~RBt_t)=fFo+7iQ;D2o8KSDbJs`oa^P z`dmL?=)Gm3FFe$`&lN)&=dD=vn1;sDE_UtpoNDK6VzQ|YQ$#eala)`YoJh~vhsgxB zq4~>%0*_s+fJ0A8yP=F8dcr4?;r4_hSb6#Yw;QQqabI^$u^v`3$#=yl`Y-`q31R++ z+gs7P&kyQmEPddpXQetp^tKkiK2{4iV@|nn*hsaOcBMmzQyfeED8%?~y{(mIFeb?% z4;!P_(uy;Q7zAfClu)?KKj|z!ahoTMY(_@{`Ul5h%3qjXuNU*fa>q?pel)ZP>-KFDL37uT>BQ`ZVaYgr7aru4mRgGWGgF0W;k$%9c3PA& zo(*CW)G6z&AC9p2`f+1Lzm#X!ST7oxh>)5qp)_&(xUtZ1#RqxZamUif~UQUzQIZNvk>**mCiyys$MlldLS>P zM8B7nzJW2G?PDRc4f$9-O~~kYNx6PMCw4?4X?}kzWe?mvTifB7=rfY5+{)-PG!>2ID~}r7OR;m9k#QQc-XTqDYTh0VLm0 zd2q5qHZWX+X8zO(WW;W-6=cFx@ePyXlA?;=+oI^Z2325g>>`@7UyV*%1AOjatlf=3 zgs$Vua>naPG$?WeMT_iyvb$UTb!+P9s~Uc;30kniqOzo-@V1Ylszs0736TmWi?U;{MK-ZZ}LXY-|PM4Aeb3H{CG;fOS zcCGcy)no)Y^QlhGZBj*{?kr{Ej!Oj~63O%WTDW>J;j@s?Rim0Li`zLpEed@bE2cG0 zzxASYatsnqr7qu)CtN`~GV6Gg145%^F*&EF6{enftCB~I_G}RHj8Biid3`MqJs9rU zXy}SnjhEuBQh&-B#=@dMroqV$S%q>bGW+RUsFVbUNj&n`K#eA5)tEbB1+KpO$_)K2 zx0ASA+_TSf`)XZ0E~biFjB5-F!0nm9&FQvV zu924AH?JAQBlB#DYYLS<7LfSxJ@XOLaKC1*0l9=gEoh;ijVH26|4Ysme ztyjMNF!2n?Coh zk@N;z4%&L16^}Sn5vzSc$9*^87JHw5 z7)ym2yK~YB0~a$@bo~j!1rm`Pa3b+#mf9z9i@7zQWVlxqUhW3mFUoJUY@$?S=Wwfz1b`D1V}UdP-1l8sX|;U! zaG2e#o?5w;G>iZdHP^(ae#lCf%405f#oz%rB7(9#Yv0VXUd8F`65FpR^Spg41_}{y z1A*ekyyNe!h|^WZyuQJsbTo%>MAM6H{QZ0d%O}#LDJkxZdBXrN-)s@Do7vtSzbwfI z4}G2>Ti*d=1kB4%Rr>aMM(39<1mrUh%~s~jE@!yqd57Y#*h8KtqAlBD+?aQKb0e@l zlTMCB->YOEdEV3kHqcPST0uj5L8r4+W%xc1@DfgrCCPn9+hKrbvu^-O__sJvTFALE z4+Dl^9<$T{^Weu?AB%U3^`W?qt4=X0-jLJGO)+8-XX8d}4fe*lwTP=X;cMvTE|T zsaUrY0ppg>g?9fUYB0{RsL`#pTFs6kLWoV-C6OC%gb-=5fU#T74FkEDpM%|(bZov8 zyjiQ~=Ms1QF=Tw~d=A#vyV-q>Dk~9wj-_z7*y?c(tTh!|1IWQW?9HxNziZ{v(=b9S z5kFN`_uU#As-UBK^S?ddMin1KFwL{oP8c_Q;KC#maW)WGuN1hLk7JCx0T(!S6@Kgn z-07zm^tY^land$@JH9av8OKv#w!6Z}%HEN;+pbQ093=u5t;^Y@$R`kDBFM&aHf`V5 zDR9G&s$i4>VIJ{Tar;i&3aF+MJk3&ob z=i`PE95mN%!HoGhmdCMxaRtGF)R)@i1B$}o2^6mYxESLSJ`Qu1Kq>RL-1l^MoAqM$ zxF}ZVKJYyZ7Z<}V#xWLD1D4taZq|~4b$=TCW@V(IH zUhcOTsJD3P9^mkyK@TRvM2~zU3e_TB+kCUDfY>SaQ7p%0zD%=pyV1JE1s+c0SY9`7 zVLO%X?&jZ|e*@bU4vpo#J^Hs=Zq2%(V@BC(v7AK2h>C$sPEsV#W%$!Q!$`n{x&V# zn))}!H-@Da7uv$jYT9K!1C9$R|JEaJM$%k)8m5R+qLH*)?3856~{>P zkX`z>Vc4mHn~0|jH)fN4<5p{%V8v^FpQTA{Cj$GO@Wm)f5Kh$&~M?@!yx zLk4T7B6beTPKSY8rkx6($MW7D_ovp2TsH5us{k_;zATQBhWzyXnYyc}*6$#S-(mIAHkA+(Bf;;ArX%}%e9Zz9GDs5}MSaN8~B$G5zNe`C01 zHZFao~C-#bSFX57P{7T;oa8ryp@irwOAvpV_J;by%Ba7#G-IJY#$cIn^9 zBVRNRQE*G;xNHyKH*RLWk(>jj@?M5zmwX!rZaI8ocFHh{@$C!vuI*}mAr4%gpj#YD zOSh|p&EGBHMM1gg(sv7!g^fZ}jm?6Hwrr>Bp60s21{RwNw(bmYvao3m-G&L@NO4Cc zTwNgLy-L`CVA7c}Os~wh#n=Y0rQF$#ZR&Rmf^D}1q{{REEp8_Z`A!7k62< z*;N2U!ncXNQ*uYvA#6s^BPqBEwlO0Ounj%{eE+aKwDQdMsBjY$yy3Ra$=pPIjhn7D z)2rv32&OmOviK(a8^ev|bJOQrgK)|)((;(;sBH#s)Kt#B5uBGBZApf{> z)KaeOhT1*lY@crC^QY~yjDF>A3Jp@EzP+JF?(?D*5L(4KF;t`>-9-ah??q9I#ZVp% zI5-$%^*nc~evNcjgKMdj*Ih+zA2sVeoR)G(S8~7Aiy(Ft?1m)JNQ6(e575}!orRj& z23?^HMgtL28In^+&2kOMB?2V#XDnX!%Qf;SIfiM4yVNOyaAmh0RmLq;;oL5T+Az3A zt7tKXy02j12FSbJVlkm7kF4>7XA{g~o{jnbm}i?VZkp5bZMRd*cZr6#M&o|t~%h2kye{_^x^37;^$oVetP53;t$!~ z{#ua(&WS*p=jEo1oM&X2rYrm$gVE(9Q${YwrwL$^aChtH?3$g2xWt~zt^zDm7R3F$ zjqOV{qIQcMDJ};vE8wM0Qd548q1i2R1V$|g%Qf}3&yVr&l37VDzzF6GAt!!{0-&zz{WF7w4`e6Si(3jFJcrY;WIxY#l!5fMmkYu^f+~*==kc6>`GQ z-H_94O}Aj&;0p={IQKnQWm*;6<$ks*zJ6LQZ)GO<*5L--+ruyPns91Q)-QC{EL=#> z*@;~!85S`%&((Egu*1lPtpaPrQkMB@-qOa`j-VTUfDI>lGTu7d!F2d)p#vtSd>v!r z)w2$uOZmDRbomjM)lB-0u*2|mHcapXFAfbd(D!Q>2orYEZXp9sPDET_(DrMv!$?kO z6*aEqh?pU8y!tQ^PEJJBxJF;sgmq%`FSZsPvu@nJPN3!K;Oe2P5p@GkwS3nVowiTA z?dnQi<@P-tR_kQAby)&#!#V+O_{JIrPQ=$hV$un4!%l{|7i*RbPZ&6D4`6Kpz)A(v z>tkFdaKfoEM&5vHSX940;JgGZMn6AN;ODJkGvRI^-h7-0qimZR*m&K56Rf%c_o~^? zT`=JK^l>)myPSK#mARCu_6giD65v=ofIKc`syE;Q=}0O8zP{jK9R{!~mFzN*G|t+> z(k-eA#{K0!8R(krHr&cwhbRPenW&M9T+15H$<*IB&5<^w&A+! z%@z2zR7eQG_I-CBX|;U!aG1T@6(8F8jkjT#YWC?VASnpQqJ(>=sx$D+&Tu4;0*hRi z`#hIYVd^_^@Uod#BVxF7`x|)sLvdK_6X4-3h>wrY&74kXI|Gk&p0jlz2U#wwx3^sU z9V@~kyj~!y>kK@?qZW#Nf(J1o<@5RmuNG#6blc$BfdMG7cVNMR0i~5hJ2zXqY!|0; z?6+<3G|}zK08qg^k)041t|;2Z!1H|`?!W{&`%aS0vM-(Id4!1}2{cS?H?AgCiQ(ll zFB)yHlX)@lfNUwB$AU=b0b>|un+cwtzy(LU?b)q6SJ80@?9;*4P4_Z+SPUd8(AN%JjZ6+HdVU{KboRhiuk|+f`+vX z5*@%c47O=PTPT4*A_vY350iz>Lgcdzb2k}K-NrFw**CT==7;5GEdwaS*@yC~VdGJN z875LP@cVI-HPV)R8w(TH%{K2>siM&}uw%Hf?X!nPaX;HG7xTsJZh7Eo+s(J#+vo}j zhzO?)wwsZ5beE~E&6U-uqxU6u3BW9|Er>Hq3o0Fkgj#zl!nj<}h3X)fZ-`C9EhOX_ zDa+w*usPUxy333eGm`Ypx$ScOWgEAchQF2f_fLqD*lVTPc0I7g!bEK&y-L_b47eC< z6dY@X)IK9AuuY`947PN-+ZNk~0h?U~cmEs~fbf;eG;7q`rUjdptXe1mDv<(nSbZp! zbhjxay=ijbSKGsmdM@zf6d+JbHP zYy+f3BxOiln~^GHl3M_OyTBmQg32ucf>j1$p^;yIXE{%fpA~B-1N{46g2Bu|I@x2Ly|GF<%z5yG?Phfml`l(X|d&zXY{LPp69XHnfNs2|rBIldCyHz>kX05d5s|EfQ>-)uid9d7g@$8K$ z(UYya^AM$%b(+EKlgris{=4GwZnK%~inm$tSIrIl$4+(Ohb3-{ZP*s)~dEQ>Wgy%f;$`_E_vot=-S=iuHQ2n{A83VX<3xwQO)z^W9=m3OGBS{90yL z>KASe+gh!^{QPZc8S8hm`_jjpuE?;Q?3u8W-*&>=5&IDvw6*za@uB?bf?~PnVWU#U)KDDFoNhtvE$!2o<&tjj32 zK9q*?ZnL^ytY^ix{0WL$8hqUR)1stnKN|eZA@-|fj#vJD*WqrnyMHR<_OkTOCy~!v zGQ{4+^O_?{KIw_6eO=kxLR+V?Bezget){8XcRtbqrMXt@d%oh)K zV_li&>&R{;H3Y$x%UH$S8u8UrPh68$d5kTLukkUw>BckQMQCV{%$WLIzM6@$DM~tw z#SvHZctN}FsvM+Hx2o8P4Vo4_DHx^-7_$0Q?Ok@8eMY9<#@zlGWhHL6xlgCCeilIW zyGrmENf%M&P{5pcih^0`>dE|$UPvRbbGG^FT-nTBRtOk{Wq@yDUE$QQh`<9CSP;H4qt$X9O7v)I*F_ z#uF5y>_jICTFl>*ldtF+GPg#QRmmyWcT8yWD698Xr^y>o?W)hx*?>37O2A{3jie}0 zR+w*gYg21Y2X4hjQ^3f~Rzh|YipMQX3Bul$rT^TM1I%rX-5wXks*?7TlH=;P733wV z+zj0Iaq2X}kxA1BiLZ^7iX#<^Z5w}vB+U!ZHxwJSKuX~vmGRU(*utfiNyP4hIoT4H#p5?z{V5N(j{TaSaY3SV z8bqdNMqiMFZi@2eN$hG8JbHU<+w0;^M|Qw01#MHG{CpS(R(qWw~G!Jfq5UuvDrJjzd%C4X8>%V|df$ z4XF23r?CS9r^Z#9I+0_ZleIu;O6w#+%Su`*7Yy#|R4l^-c>_E+`gi56wD^t*hO_Li zyhL`T9(EUMZMvUTMFJ&U>)0+2rAsU?2CmBMPckBKPuc=HxnT$vd0*_loF!w?pL2IO zs6+FB1f+4-nyd>q8ie9NulJcM;zKN0lvlY*9WIJp@aP>#S)O{+DO6hJ2*hcc@`7V_ z+^jBpLrrSGT^`q34kD*EutKypHH*im{X=;T>oL>;7`1M<+tpv?A%#ljZovTuLm+`IC3a6^a! zS>BZwQ%ix&6(ErYa9{z6Q^&W*SqM(A1_M%^9Bp|oc~{IgheL5*JOHaq+;E4;`zKFj zBKft1gUbhlm%)1N?zTU-w?%|&sM?(;6f8A6@hm4cKUieF78eE_$f<2+;t6f7P^~AP zw06NbgKxZeWS7-i;;Bxm&tlWM_zGJe(92chquUh%r%JeoerpR{wjp3V@s!Fr=9C7Q z#84$Z@zajV>qB5P_sF7|=lh4!SwwG(LT+X0UpMotx1`hY(D9td?%Twzi3UrGq@7sI zSe2J{?vGhO`C-d`uf=xmt-@=`4u9aiZA7!a(J%w?vxi{afb%icUMn`$Sz9Mua*BJ! z*9M*<9(@46^iB(tjMKI(7JmYbt(mtIfYjwrGy*jU7_Emi;ME8s5Y({^xwC2?sa}@B zE?3Dae!MXb3sSg;(qWZlW&Jxr(;AIvRepX`)^-nv@V5>FY0d)yEUS|FBt;b~%Xk0t z!Z(obVY7{ViwS|p){WB}1=!t~(Py?V?S?Yf3469vHABuVsPIpp*6&v3&5u~+9)fYz zym_qzpp3@{*xH*PRTULGu54H)^w1*(xipvR#kyO|6R{)7psG+FmZnk6pydy?x^0NY z!hnU*Ik5bTdkqEzu8kmO{KVBHk-qW0i$Nu;s{}tQdQ>?lcs5~(W)YAPrIXH}J*2iV zF7hmd z1`eiXFtfY%;Eqv5qf-w(m>K(v^{}=t;ui&yEME``W+Y9cU}hKRM+Md2+jD;CA7 zXiP%26&+h2P!&$c!HNuA8)Gmt^o<8I34qJaM2OP{OlD)%854x6M&-CR0-m**D&Ph0 zFuQ=~+>eYVl5sLCq6u3|Fq*ivxX9KBQW;eQq_PRm_fukIK>8UEJdoyC zP^@#2??;`##Na&vhxDgsEwMpdv~uTXVlI4FWT;c1lptP)3ZE0s4TO*|3n65a4*2|r zgpe+x)^kHC+sbeeE0ZuQ03kU+}Soy0`?_LSR%I1PGz=bPc?~YWWyHih;7) zsckGp)7#px*G_V#t-VoNW*E#eExT4fSF&WqM{V&H5Z zi+AO{r6PKy3?AP$kGZm7b8FD;c(Z^I6Ln}qaJdj9Lte!!^CAtoZT*fbni6Z{K8=Eg z+gqjwv7}DFFIpv!VDo`;Ddfnz@_K{35CTKcJH+-10*662ll-lv+s;+F8Ys6|t(H&9 zh00oM2nl!1pu02m;@PoecF*nQ4Ss^ItkrY?lKalw;<??se&EBb85UNta;Oo%=PhABGe1)v|n)QJ#?3a%;i*4bLgzvh((7SH2h9 zrNa-d%DEy}ORUmLP;UIrzX|>z&;GQNxGTd%T`(VuS^1b=@mR&FV2t2kf)b3k+%j|7%E9Y^1;5n${Z-L65GS2j&fb#fse}8-o8>is{dvxdT9dUVZ0v{I=;mL6y2D_qo9hF5Imb z$A@~3*X{RZB~2+t54i`}+3_j*__t&iZ_Kr+YT@)bT(&;zmc#&qP5DXIJ3kBK3k14V zt@VPKwX=A|%Md9vW-`!LS1)u4ZsoAN69}s*|MbFW2E5VnjUhWA6hcN&4uhYmu#~M` z4NHzeC?-i;6_dcyH{0@?`Z}NTsD#gU5Y+-8FjcT*9zC8nd*z;z#S56_!(w&(h!(4X z+$}+C=fo;Rf;&IjLdmn(!|@Vg?mLIE$+P!wsZ7ZjB<%9um%j{@ajyjyw=I8f;go{h z6IZ;gklK`p?d4iYmRD+q_2%wMw?$b9pvMY?7w4LX?J4zazC8ucmVFvtr(knxt4Bcz9 z9xIK;5}l+5vu@Vrh6<>2rz^lprL}IHfPA(5^})l_ewG_4!jq@tL=~jKqiz?Qb;hrx zg(qzi8Be6xmOllvQa1hEIftjtmKTH4@uTC+B;}l$8}KfDP8MEJS_|^>-S+8m`{i;i zi_69yfh2(!U96ux{VFKr9*cMH#8>PXlgzxc?b5wUk}5D%aDp3*hgQ_MJQm;-17oZX z5n|yr)Y^Keb^@#}L8B0B7v={)HBOEvBDNm0fmb;2w-sQNbx zxK*kakR4|$j{!T>$ah(hon$Mka_58Y+FAAWIPh36)*3qnIqr(%4s5$Q6o*a7c>|f} z#cmgV39EK38ob|pvpKG0SKQq#Gj@>>0!){BHRfU91$xLS#a|4qx&gD91soxWg-S4C z*VMY0cH*S6@J2yW4TpBAe&oDRkZgU0x^(I^R%GxiBM6dE*E!KI9w8+h?j~b=q!OL@ zYgN_03&UgZXKi`Fkp!p8TjsQo$#2V7HZrz*=g$fI>Emz1&F;!`MH#HJWlisIDu{93 z(m7@B^v;mCa%@Y@A;A%$Iyg^;Ex=dF))Ypu4nVAf@klY)vaL%!R zDxV0QFE?eur0{X z{gI*qNoAd15=+7HVx(|D#Jexg*B7hhMycuXNSi|A%}CLJyKdz@p3ju=$ngt|9t;nDIoR_n~$@Eh01m&)lhX!GrCF zJ#01(h&i)L^&-hV{2IUjfz9}q*P;0d9l+>y7;jk^+cyj?Una{ zXl3P%J#pVmV3-62)@2Pet|w-M=!%A)R!UgtM8oAwy3|1pmp!cL&tI;}?Bw}3x10Bi zV@`g`Rn8?mz+Dkw{!oOk4mKkIO7QoSq+FF&E3>a*vsPsb?7;QIXAPuqR@d#bbh&HHsb;TEcl^QS{6`_~`v z&4-&vM=8?50+dINh*L$LkleHVs%Sa$k{b7C(-BDZvM{`cD#))EyTfu`7C9b@)!UDc zKlQYEJR2rR1D)26k!RWVCIqS~l~VzrPV?zdrTba1r1iY~{QY9NyWhCFP^Q<35DR=T z)oD5?3JG`WPsXwG5>ey(6C+sEf=`V^Fo2B3Usea3SCMxpGjcRYHU*ZP@p4nv2o9}p z@sjHfZuN3{TEsfp@&OZ>@;cc1!H-ZFKch_)LmshY;Wx+fGMldwv39q!8|0ee+-c&;(&kGrCf~O-EAHJz|E-|7S7D= z%dn`<2$^I$V=Ec9y$-gNDce)GmKFm)%geLK(|`?_1z+_sW_7aF{Aq$z(Jg0YXA9%G zTTjSD88qE&LBl6|@vW%hg>r|oQ04DW8&C{tC4?jG1=pZnPySVPADLU@om$alOt$m%$rjc@kwm1I z+LRf9WQ~(Vg&Rr?iq`8SX%Un{i4`eb{s?5>0`GE?^E@ygv4c{H@S^IRoHhXutoF6I zM(ZjeoH&iZTIFP|S4%R$36@Lp`Vu&h5a*q`=OPv{y!HzwNyF1BCWcH9GUkCIiRZmT z@<%Eruvg^d!NNtmvHK7C?lv~HZfCWI$Yj(RMU#jPc2G4*+L2l|$#hVsz7{b**KWWs z*u-%qcTzYJ+pxf_oK>D38KiQDvRFYKB?J_A-LReJ>+Gg>61nsnFP<1kvb$SM@(U)O z0z&vj92w;H4ocd%x9h)PYt2(^nvoOC0GQL$Vt2OfsqCT!c2Gfy(8Hfr2PeC`MX2){ z3dC-ORGk!2ejbjEfMMifK#c(`ja>$!-?Rv$+HL6<=BhWdo64sdMU> zzl}#{+XD-(shjo8n+~8^=o0Onr_dumx<=9b@8}>my?e_k(qv= zh;^{aqBn_vS7nm?tA+9l@@D3XG~{oQ;(qnv_>;l&_(Sbx>8V}>O1*qlGS(T~Fc?en zUzEb_F8ravJ z`mCB^8vM+_@n`7v#oStA6$qfgTa$dt*gYWbjGjh z!t$!tv8*f9+Ipii(=PdA-yp%wyWhO~DIJ4Cr=@I8LXzxeNnCSJB%Qi@=iPtG(s&v0 zrqobQZK64MCaP^`dlI?Q{nI-4cTHncXaD?*=gwF6W^UzWwsz&Nq5wXiU2GRQ&mg^f zT9*%X-G|$PLwsAlllgwLE91tY*iqo%lEC2!b=Ym@UoPyEoiCrgd39eLiZ`G9aW&xn zlQ;j-@nzF}`3C3y&5P&Xwtx8J)w1lkd-nWW_W$|iu2`;~J^z&c{b9R!_6_>;f4Etd zZ!$gm2L1hyi~Gg1XU{(_M|$^gD1WdukWjcI(t93TdqwpW1HRiKxE!lD46EAUj}6@K z^$6}auMOPqcL;96=s&HE_1KvEgD$~6*XMqV!9O-|f7l_o+~Rr%xA3M%xA4)mxrI+V1ebYCZsi}>#(HdS;j<3GfQkyS0+!x;C_U?Kp{@;`GHvI z1dUELtK%>_#M=Mu!{RX^GRH7opLzE3oAalA`TYCojO>5?&;HBL>YRl&zzTksaJ76a zf1coj@{1d}V3}(u?q_UO%+BQz2lb7Q+5h%`{eR!+7&G+@y4Dq(x85P`iJb2Du?=b- z5&n~Z@yQ#Xc^rlF53xE14IbA&o$}7^R!^PfJ4Oy} zD?AqO7VAUNhL!$+!RGVr&;INsBiz~BMRE4jF>GL^)vr>w;M;!4P~#xQcY*bflzg)N z*>C=XjzNPk>+Xa$4xYv*{wRUjxrds(yy;{1FaDRWdcJcqn-|{j0m;cXy z-F4UmqPdU0x^-nd*WjK>nqOiA;e*z_D}$ZW@mE1xFwGYUn#E1C!D)(@$NZ1~>es>B z`PyZ|1{)}#*}L+Kf>!+5t2k(!X6K)hc%c0s|NNi5={RIUN;}`oH^uC3QLHmA;D&bT zCecWQ0S;SzzaQNR^biK%E6i1~UG8VA;_Ii?^4zUwDioZXpz+|~d$k@y0c-`fPrGgT z=}QZq?M=c+$+NX{+V+u;+CTrF{^uT(j?>ezmC4t& zK4fT+djMwJW2L(V3;lv&#vx)Bdj6?^r<8yBZ{`hX*6z0-yx4v2HO;0Uwp4t8lstHt zVZP`BHXM`p_nXbio)5kG=x$Yh-4krr;KQ%GQ>o)PiDL}T0x{P$mGc}wb#9>Yd{iJm zKOJx=u+Vqc?o)ZK7cKso_&Qn@cG$Q;3SND_n1_tsZ(7)Z$-`NNf{!t%aC3siqu3<) z1A7sBDiH)I(00P;fR$8-4?%GBrW3{noTVwuDbVQR8*@|pPr{}^GE+-ZkP#TTmO5xt zL+0*uYLJ7BYdK+LAX7OV4FrMF8crA&aI00#Cy0#Rbi&vG6;72M1sPp><9xL44ZRHI zMefNXaDvLlq0_yBNaRkH2nC%$-bc-K^crF$JiY>phhwK0f$~XQb(^U*ybfGWwU{7l z02gXFI$WUE5WY4TAueN#E`OxIG$nwQ_W67VE~jyBN9*6fkZJ$}O}nbsXhLyN49Z$d z9jx&{T}x;g_hCjpFWc@@C8GD8X&*K)eRfboao6WP9c-a@?y zqlr1+Zp#sXe=rZu@VBI+h6i+z+@>}Nu;NfPLM)gX5=m&mlTG*H+ij8VQS<3S1k536 zRUfQ1yb4&T!jOW68jcJWNKn#`G(%%oLu1Hzeng};t{dSbqm#Zd)i;-VTN<2J)6flr z#A$fIXLnVQ(8V1-G~hresxsn;7)1@GHuV^l2aZPxGpXK@rfslys>K@-dMJfcAqqFA z6_bqT0F{Le&D4j11&RwQN`bahhX|dfx!^kI4E$J9ZriqwVk@lk`C$hzuCOB5J6E*AyH%+NHA}keMnL zDHEfHQk=nWFU0p`r?8}c=Oe>5eW9TugaMvf=0YS4k-{ZU5h+t})CqT{=^JFxwV~>v z+!2_)`MIJi8&4)=-KyT~0yh@3y3p&#K}2Dj-X!yrQHDS}&s5G?N9PY$^|^Mi_2R zIC;2E6ExsS94@Wll>$YT6_r;26g6xgC^=$BQ4oB!{owubb(p{=NUtsg8tVu)NYv{g z)I<3tAn+K_0pkf(T|^2+4W&l)6l%oy0N3D*NcBT{)0Q!!2$|>`y+X72~6=U#8P%P-Ot}+dTT#-a|N`R$n*|MFHwGPN9f!tkIQAW%mi2Nac zt@A|W1Jqsa*hY*Gg!67|CL_iOI20+ji4muDmRFnd3<-+y)5RebV9=<1nfY)W= zV*>EFT87FW0MfO*BK8DJ9-eP8J_4|E-3Y7c_#X)KaTl#MBs@F-rvnbSgl>x+Fwv-7 zGmwG`7^>^h8cv-(BB_aQ;KR+^1xk)~>1-Ov5r;@?D8-pp_5(6g-6CaT)Nq90fV`34 zz?DIoKB8gZc%doMji8d@3vvOMfa-a(+LVT-wKUvW)WJrnQZQr;7)m?2UfrZrVTX+InBwWSfK5s@c|QoL|=tR zZ#rpoAfUJcg$j<^@>;NQ1qp@?-bFsB_KcuA6J>(b zvIRBMas6o01~*o~V$6UMuM^m7GtXln3bduAl5A(-V`hTnPl1Hy>B0MejrF!KcOnnn z1A(4glCE+`Z8>S?fW*O?g${tQ0oT7_OqcT{+)u||2e5I)hZJvIODWETJdWqRpB6La z7YlXy4Q;gU4Ot_2=p2(=LBGB@T(dBX2Dc4O<*Lu%jU4QX?4Xum%)2xHxPYry4b<~R_YlzIy_yaOhDMAfaT0;VZ)O?0z z(HIG;(4=R^@zZa*M|KYw$3yolYH2f#4gJh~Iya`K1c!%T$JnInIR+V}om}sPZ~-If zr=CMJ7&>DhX$jYJ3~BVHt@1_=raK*iw_|$}k+{!#4hcDQ4h3p7YPbrtm5QH`_W{G{ zubwj+_CP%EtDZAPY+OA?wJSi;wVbq7K}?T(Wv$_)kpX`Lg$PC)N=uK9Rs?wHM^MFg zR->^gpaYd67&ZnDts#No!8Z|PM7Be_YZaex^GTxww20%QHM|yNLNyCreqnFaeZZCl zd+1aAxqQh22uBf-?;WH9+zw0Y-(Z7P0~l!9)z8doLJb=fSaGOWA+_kDxYk{o5wr?& zMVKYEl=B~1)Gb#atLe5=?@{hI+Jrx}Qs zf${=F#;^n&5;T?KG>=XMd>j#)vfJDS%s*6K;-sGbJ!qlP+QgM|e|Fe%FAD-jR_UDm{R-6A1yO{E4J?l0?L zlP*TcWjPa=Re95K&@<2hjCHqy+li0v+rtKA5wiC_a!F7B1Jh6IL(-b9cAe*>il zMjJzQz3HUU0Xk5nLB&REIB8@=`a=6@hL4VdVN`gKiC7oVB7~1YLTjiaH$7yi5W$!u zkfDa7L*_e=%k|>)jLt8P2mhC^SSZ`~jQy3;i&W8Ica@I{ma$KEyn?%T#*Nf;zRO!2Ve^ zr$Ad;D9NQ)C=?lo%p9<&Zuj0J!v(g8ZjY<-MQu4zwt%dz!jMA6wVXaG!77A_qJW=k zIZ;#~e`ykpwC;`Kkc2lHkUEV+;BCW|2QR=F-st^EvPQTJDX#-%cUN;5Sp*X4SbR4f zZca4uh&3h7gplzBHhc%BQ7;0h5hm1|X1kn!0qLl4poV9yp%i4m87>H5@{7yY7a+L1 zlt<6~)us~vhWqI{*eFg>C5+Neu2<)aDlErE4(6;#9iTy+u7eA-rJ{-ojMh*Ja|SXZ zb9CgA^Yp+N$VIxX!KiBIycrrgA`CaleZzyu2sKKS0yP%ZzyfV0_or!N-Bx8r$sSmA zm$ey~#<|>~`yW`et1vLT(oIKrQrEws*OBuKCZ>nHve+b0Ln+KO{Y|(oVX%Sl4^1bH z4y1UY>ICimj-(JIqFw}3LyMwm5{3lxob!Vw$(@x*RA^7lT#kVx+?*Cn5>CjvfIo43 zw1(G$OsHO=%P&lfx;L~eGay7y7xLu_8t$^dbpsKT{NGDOa7eC>QXPS&nNe=jjC-}KepM1P|I&7Z~Z(c}~yy&x8u<{I_BLXy~ zzs$Idl;P$CjR)prY%EZdP(cc`Z51IoSi|IGjhIODF#YdhI8tTDDC8ub$_FYJA7bZ)!h{|V3Htbh>^~sv~Hx0?hOi3m~cJAP+f0I zYbDvH#2gW!Nwv(fbQlb9;X$V2dB9OU7Vj48L!tdTpg|+hRtIn@0DXSawATW?=>gwWSVRGjPl_QLpUb*S(7dk-IEqXd(|kv;#P9_m^UhYdLM^ zfY*Wggo=*dv`u(CtI4pk?hOkV-(4jGCZXJco0Wt)?wPxXMx-pEZMs~#+iZmgY37;- z&7crKg6Y&yhprhkViw|>oTZ=5DDa-D6U_6-r8(|s-3JQ|1fj~$UjADW-Jx^OV|b&t zmFj88oLcZG#R*0mvu358T(G_s)x{k?G+-0ml`-i%-U@s115r` zYWVCC(U?MnYG2{zQuos`vp|7@p+w1ui&=q=gqMt8W91U88>$fuArV`kDPiKlI30k% zb#+8_|T zR=*>X8rqDj))j7kMJx(Iq(fjFH5eXsZ|Z-@iKTWJw4koH8X#S$v_Xq@*`d}8mdx*e z56Hw|t9^1QXBIa|XayvUXRBiV<>?qvG6%3EC#Ubgw3c!K4GSAJfPtnx{D^4~6kT*- zwA8zZBo^liG12)Lts9}&Ei7RA-Ig*m896j0b0$XJLL(w_JOKz|`pcR`Ri@yp zfGl(tsM?>_uzgmM9lK&>7-gEikBu|PPtiLC6g8xkl6=!mLIEhMsxY8*IKoAlQ8;uc z7qn4?@tBfRk&z&QCWwi8!l|{CN@`emsR0Z$?W*LWi8^dhV8x*Vi`3Q8zX*e(?#;MD zlyuG+)qGZ){v0!pqm0L;8D9RUjVFRAj8gUV8z`P zD`1k5z#{TDS~qhu#{R?2+cYXU)+NI>C^b}+5YCYrN>RoVn@%E}c^gER-k^m+itdO+ zoiKr#^fc5`F5vv2odO2-p*<8LNYr720!BoY1m9iWHiFP#Qd4-6S8wS&Y>Vk@m-jvo!awS7I$vA9h z457EDLd5uh2^JzeHi%TlbtC#T^EA|*=8IcZx5fmF2^Jy*7OtffW-6uxiAg*@X$_|f z3wYGt3K1jmF+hY~3K1iLMdWWi79vIh5ilnN4h0K!>qNcC@idD~#?!-c2tXQ`7LvQ) He)j(Zp=^zf literal 807793 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 diff --git a/server/item/recipe/dynamic.go b/server/item/recipe/dynamic.go new file mode 100644 index 000000000..02f6531df --- /dev/null +++ b/server/item/recipe/dynamic.go @@ -0,0 +1,120 @@ +package recipe + +import ( + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" +) + +// DecoratedPotRecipe is a dynamic recipe for crafting decorated pots. The output depends on which +// pottery sherds or bricks are used in the crafting grid. +type DecoratedPotRecipe struct { + block string +} + +// NewDecoratedPotRecipe creates a new decorated pot recipe. +func NewDecoratedPotRecipe() DecoratedPotRecipe { + return DecoratedPotRecipe{block: "crafting_table"} +} + +// potDecoration is a local interface to check if an item can be used as a pot decoration +// without importing the block package (which would create an import cycle). +type potDecoration interface { + world.Item + PotDecoration() bool +} + +// Match checks if the given input items match the decorated pot recipe pattern. +// The pattern requires exactly 4 PotDecoration items (bricks or pottery sherds) in a diamond/plus shape: +// - Slot 1 (top centre) +// - Slot 3 (middle left) +// - Slot 5 (middle right) +// - Slot 7 (bottom centre) +// All other slots must be empty. +func (r DecoratedPotRecipe) Match(input []Item) (output []item.Stack, ok bool) { + // For a 3x3 crafting grid, we need exactly 9 slots + if len(input) != 9 { + return nil, false + } + + // Define the slots for the diamond pattern (0-indexed) + // Layout: 0 1 2 + // 3 4 5 + // 6 7 8 + // We need items at: 1 (top), 3 (left), 5 (right), 7 (bottom) + // Odd indices should have items, even indices should be empty + + decorations := [4]world.Item{} + decorationIndex := 0 + for i := range input { + it := input[i] + if i%2 == 0 { + // Even slots (0, 2, 4, 6, 8) should be empty + if !it.Empty() { + return nil, false + } + } else { + // Odd slots (1, 3, 5, 7) should have items + if it.Empty() { + return nil, false + } + + // Extract the actual item from the Item interface + var actualItem item.Stack + if v, ok := it.(item.Stack); ok { + actualItem = v + } else { + // ItemTag or other types are not valid for decorated pots + return nil, false + } + + // Check if the item implements PotDecoration + decoration, ok := actualItem.Item().(potDecoration) + if !ok { + return nil, false + } + decorations[decorationIndex] = decoration + decorationIndex++ + } + } + + // Create the decorated pot by encoding the decorations into NBT + // We'll use world.BlockByName to get the DecoratedPot block and set its decorations + // The decorations are ordered: [top, left, right, bottom] in the crafting grid + // For the pot NBT: [back, left, front, right] based on facing direction + + // Get a decorated pot block instance + pot, ok := world.BlockByName("minecraft:decorated_pot", map[string]any{"direction": int32(2)}) + if !ok { + return nil, false + } + + // The pot will be decoded with the decorations through NBT when placed + // For now, we'll create a pot with the decorations in the correct order + // DecoratedPot.DecodeNBT expects sherds in order: [back, left, front, right] + sherds := []any{} + // Order: top -> back, left -> left, bottom -> front, right -> right + for _, idx := range []int{0, 1, 3, 2} { // top, left, bottom, right + name, _ := decorations[idx].EncodeItem() + sherds = append(sherds, name) + } + + // Decode the pot with the sherds NBT data using type assertion + if nbtDecoder, ok := pot.(interface { + DecodeNBT(map[string]any) any + }); ok { + decodedPot := nbtDecoder.DecodeNBT(map[string]any{ + "id": "DecoratedPot", + "sherds": sherds, + }) + if potItem, ok := decodedPot.(world.Item); ok { + return []item.Stack{item.NewStack(potItem, 1)}, true + } + } + + return nil, false +} + +// Block returns the block used to craft this recipe. +func (r DecoratedPotRecipe) Block() string { + return r.block +} diff --git a/server/item/recipe/furnace_data.nbt b/server/item/recipe/furnace_data.nbt index 309399e117cbb112ef8cc317c2598c35a6633f6f..cb855de319a07d9c5d83f6b0317dd4af7cdfb524 100644 GIT binary patch literal 37423 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 literal 41570 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 bNgnD8d2<82Jydmnc_Tk!gi5|>kG%g6bQ#%z diff --git a/server/item/recipe/potion_data.nbt b/server/item/recipe/potion_data.nbt index 8f35c4df3931345004adc5e7dc00d208d9fb1977..7d13e6740bc8765ed71134d4ecc3ac8b7acf46e5 100644 GIT binary patch delta 170 zcmZ3~%CxAJX~RC|$?iO{lYN*aCoAZQPZkiCoGhTiGr54}>g3~GLO_~jvNW&SWCbC) z$@5u@CLiFnnQWk?xH*sQ4dY~2R+-7M{Bj_H$*KC;KoDFgf>6mcn%U2ntXv-Yx0Hi1h5Ggm{}&TXZD_~ zAS5?AK)?q{ENAk8DkrEdX_EsAJfUo1pljqO8?ecu=qX_F2fGlY$Ce`;Y`!grEYuMu LAhnwV1cHhHL-s}M diff --git a/server/item/recipe/recipe.go b/server/item/recipe/recipe.go index 3332b1a96..4ba17bbd2 100644 --- a/server/item/recipe/recipe.go +++ b/server/item/recipe/recipe.go @@ -18,6 +18,16 @@ type Recipe interface { Priority() uint32 } +// DynamicRecipe represents a recipe whose output depends on the specific items used in crafting. +// These recipes are not sent to the client and are validated server-side. +type DynamicRecipe interface { + // Match checks if the given input items match this dynamic recipe pattern. + // It returns true if the pattern matches, along with the computed output items. + Match(input []Item) (output []item.Stack, ok bool) + // Block returns the block that is used to craft the recipe. + Block() string +} + // Shapeless is a recipe that has no particular shape. type Shapeless struct { recipe diff --git a/server/item/recipe/register.go b/server/item/recipe/register.go index 3cebd850a..f8fe0bc08 100644 --- a/server/item/recipe/register.go +++ b/server/item/recipe/register.go @@ -13,6 +13,8 @@ import ( // recipes is a list of each recipe. var ( recipes []Recipe + // dynamicRecipes is a list of each dynamic recipe. + dynamicRecipes []DynamicRecipe // index maps an input hash to output stacks for each PotionContainerChange and Potion recipe. index = make(map[string]map[string]Recipe) // reagent maps the item name and an item.Stack. @@ -24,6 +26,11 @@ func Recipes() []Recipe { return slices.Clone(recipes) } +// DynamicRecipes returns each dynamic recipe in a slice. +func DynamicRecipes() []DynamicRecipe { + return slices.Clone(dynamicRecipes) +} + // Register registers a new recipe. func Register(recipe Recipe) { recipes = append(recipes, recipe) @@ -115,3 +122,9 @@ func ValidBrewingReagent(i world.Item) bool { _, exists := reagent[name] return exists } + +// RegisterDynamic registers a new dynamic recipe. Dynamic recipes are not sent to the client +// and are validated server-side. +func RegisterDynamic(recipe DynamicRecipe) { + dynamicRecipes = append(dynamicRecipes, recipe) +} diff --git a/server/item/recipe/smithing_data.nbt b/server/item/recipe/smithing_data.nbt index 362d1e7903f2b84c97501f1b1dbddf475b5d2649..0c84b8db275e981d892d3d2483c8899f56ac0c67 100644 GIT binary patch delta 279 zcmZ24)Fi>e$-pHsS%O7rqT0SmEb5c3IfN&xG9CgkMVan{C^L59$)?OlCb9eg^5kT5 zGxJiDixSgHtWq))bMy03;xqD#ic{kgi*oaeCOfju(vU|e$xAKCNG-}NNkvw~#G0I6 znpeU&`9HJ9WHr{?Kw}jZ5PD$7<|USvWag9>BTUv-La0KRjH(XBaDBF$5FcCzQPvzH aECs2FU`I}V%OS=C;-ILv=D0eEMFIfwU1ItG delta 151 zcmZpYSTDrF$-pHv`46+wM74cvTr4SxC5e+ia#~N8V?2c5YD{Klx{u7|WxkHg%v") var MessageServerDisconnect = Translate(str("%disconnect.disconnected"), 0, `Disconnected by Server`).Enc("%v") +var MessageBedTooFar = Translate(str("%tile.bed.tooFar"), 0, `Bed is too far away`).Enc("%v") +var MessageBedObstructed = Translate(str("%tile.bed.obstructed"), 0, `Bed is obstructed`).Enc("%v") +var MessageRespawnPointSet = Translate(str("%tile.bed.respawnSet"), 0, `Respawn point set`).Enc("%v") +var MessageNoSleep = Translate(str("%tile.bed.noSleep"), 0, `You can only sleep at night and during thunderstorms`).Enc("%v") +var MessageBedIsOccupied = Translate(str("%tile.bed.occupied"), 0, `This bed is occupied`).Enc("%v") +var MessageSleeping = Translate(str("%chat.type.sleeping"), 2, `%v is sleeping in a bed. To skip to dawn, %v more users need to sleep in beds at the same time.`) +var MessageBedNotValid = Translate(str("%tile.bed.notValid"), 0, `Your home bed was missing or obstructed`) + type str string // Resolve returns the translation identifier as a string. diff --git a/server/player/form/element.go b/server/player/form/element.go index 4ad89e7c0..9e0bd84a0 100644 --- a/server/player/form/element.go +++ b/server/player/form/element.go @@ -12,6 +12,43 @@ type Element interface { elem() } +// MenuElement represents an element that may be added to a Menu form. This includes buttons, dividers, +// headers, and labels. +type MenuElement interface { + json.Marshaler + menuElem() +} + +// Divider represents a visual separator element on a form. It displays a horizontal line. +type Divider struct{} + +// MarshalJSON ... +func (d Divider) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]any{ + "type": "divider", + "text": "", + }) +} + +// Header represents a header element on a form. It displays larger, emphasised text for section titles. +type Header struct { + // Text is the text held by the header. The text may contain Minecraft formatting codes. + Text string +} + +// NewHeader creates and returns a new Header with the text passed. +func NewHeader(text string) Header { + return Header{Text: text} +} + +// MarshalJSON ... +func (h Header) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]any{ + "type": "header", + "text": h.Text, + }) +} + // Label represents a static label on a form. It serves only to display a box of text, and users cannot // submit values to it. type Label struct { @@ -43,6 +80,9 @@ type Input struct { // Placeholder is the text displayed in the input box if it does not contain any text filled out by the // user. The text may contain Minecraft formatting codes. Placeholder string + // Tooltip is an optional text displayed when hovering over the element's info icon. The icon only + // appears when a tooltip is set. + Tooltip string value string } @@ -52,14 +92,24 @@ func NewInput(text, defaultValue, placeholder string) Input { return Input{Text: text, Default: defaultValue, Placeholder: placeholder} } +// WithTooltip returns a copy of the Input with the tooltip set. +func (i Input) WithTooltip(tooltip string) Input { + i.Tooltip = tooltip + return i +} + // MarshalJSON ... func (i Input) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ + m := map[string]any{ "type": "input", "text": i.Text, "default": i.Default, "placeholder": i.Placeholder, - }) + } + if i.Tooltip != "" { + m["tooltip"] = i.Tooltip + } + return json.Marshal(m) } // Value returns the value filled out by the user. @@ -74,6 +124,9 @@ type Toggle struct { Text string // Default determines if the toggle should be on/off by default. Default bool + // Tooltip is an optional text displayed when hovering over the element's info icon. The icon only + // appears when a tooltip is set. + Tooltip string value bool } @@ -83,13 +136,23 @@ func NewToggle(text string, defaultValue bool) Toggle { return Toggle{Text: text, Default: defaultValue} } +// WithTooltip returns a copy of the Toggle with the tooltip set. +func (t Toggle) WithTooltip(tooltip string) Toggle { + t.Tooltip = tooltip + return t +} + // MarshalJSON ... func (t Toggle) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ + m := map[string]any{ "type": "toggle", "text": t.Text, "default": t.Default, - }) + } + if t.Tooltip != "" { + m["tooltip"] = t.Tooltip + } + return json.Marshal(m) } // Value returns the value filled out by the user. @@ -110,6 +173,9 @@ type Slider struct { StepSize float64 // Default is the default value filled out for the slider. Default float64 + // Tooltip is an optional text displayed when hovering over the element's info icon. The icon only + // appears when a tooltip is set. + Tooltip string value float64 } @@ -119,16 +185,26 @@ func NewSlider(text string, min, max, stepSize, defaultValue float64) Slider { return Slider{Text: text, Min: min, Max: max, StepSize: stepSize, Default: defaultValue} } +// WithTooltip returns a copy of the Slider with the tooltip set. +func (s Slider) WithTooltip(tooltip string) Slider { + s.Tooltip = tooltip + return s +} + // MarshalJSON ... func (s Slider) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ + m := map[string]any{ "type": "slider", "text": s.Text, "min": s.Min, "max": s.Max, "step": s.StepSize, "default": s.Default, - }) + } + if s.Tooltip != "" { + m["tooltip"] = s.Tooltip + } + return json.Marshal(m) } // Value returns the value filled out by the user. @@ -147,6 +223,9 @@ type Dropdown struct { // DefaultIndex is the index in the Options slice that is used as default. When sent to a Submitter, the // value at this index in the Options slice will be selected. DefaultIndex int + // Tooltip is an optional text displayed when hovering over the element's info icon. The icon only + // appears when a tooltip is set. + Tooltip string value int } @@ -156,14 +235,24 @@ func NewDropdown(text string, options []string, defaultIndex int) Dropdown { return Dropdown{Text: text, Options: options, DefaultIndex: defaultIndex} } +// WithTooltip returns a copy of the Dropdown with the tooltip set. +func (d Dropdown) WithTooltip(tooltip string) Dropdown { + d.Tooltip = tooltip + return d +} + // MarshalJSON ... func (d Dropdown) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ + m := map[string]any{ "type": "dropdown", "text": d.Text, "default": d.DefaultIndex, "options": d.Options, - }) + } + if d.Tooltip != "" { + m["tooltip"] = d.Tooltip + } + return json.Marshal(m) } // Value returns the value that the Submitter submitted. The value is an index pointing to the selected option @@ -181,14 +270,24 @@ func NewStepSlider(text string, options []string, defaultIndex int) StepSlider { return StepSlider{Text: text, Options: options, DefaultIndex: defaultIndex} } +// WithTooltip returns a copy of the StepSlider with the tooltip set. +func (s StepSlider) WithTooltip(tooltip string) StepSlider { + s.Tooltip = tooltip + return s +} + // MarshalJSON ... func (s StepSlider) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ + m := map[string]any{ "type": "step_slider", "text": s.Text, "default": s.DefaultIndex, "steps": s.Options, - }) + } + if s.Tooltip != "" { + m["tooltip"] = s.Tooltip + } + return json.Marshal(m) } // Value returns the value that the Submitter submitted. The value is an index pointing to the selected option @@ -216,7 +315,10 @@ func NewButton(text, image string) Button { // MarshalJSON ... func (b Button) MarshalJSON() ([]byte, error) { - m := map[string]any{"text": b.Text} + m := map[string]any{ + "type": "button", + "text": b.Text, + } if b.Image != "" { buttonType := "path" if strings.HasPrefix(b.Image, "http:") || strings.HasPrefix(b.Image, "https:") { @@ -227,9 +329,16 @@ func (b Button) MarshalJSON() ([]byte, error) { return json.Marshal(m) } +func (Divider) elem() {} +func (Header) elem() {} func (Label) elem() {} func (Input) elem() {} func (Toggle) elem() {} func (Slider) elem() {} func (Dropdown) elem() {} func (StepSlider) elem() {} + +func (Divider) menuElem() {} +func (Header) menuElem() {} +func (Label) menuElem() {} +func (Button) menuElem() {} diff --git a/server/player/form/form.go b/server/player/form/form.go index 3d66d63cd..d6a556fd2 100644 --- a/server/player/form/form.go +++ b/server/player/form/form.go @@ -105,6 +105,10 @@ func (f Custom) SubmitJSON(b []byte, submitter Submitter, tx *world.Tx) error { if len(data) == 0 { return fmt.Errorf("form JSON data array does not have enough values") } + if elementReadonly(e) { + data = data[1:] + continue + } elem, err := f.parseValue(e, data[0]) if err != nil { return fmt.Errorf("error parsing form response value: %w", err) @@ -118,6 +122,16 @@ func (f Custom) SubmitJSON(b []byte, submitter Submitter, tx *world.Tx) error { return nil } +// elementReadonly returns true if the element is read only. +func elementReadonly(e Element) bool { + switch e.(type) { + case Divider, Header, Label: + return true + default: + return false + } +} + // parseValue parses a value into the Element passed and returns it as a reflection Value. If the value is not // valid for the element, an error is returned. func (f Custom) parseValue(elem Element, s any) (reflect.Value, error) { @@ -125,8 +139,6 @@ func (f Custom) parseValue(elem Element, s any) (reflect.Value, error) { var value reflect.Value switch element := elem.(type) { - case Label: - value = reflect.ValueOf(element) case Input: element.value, ok = s.(string) if !ok { diff --git a/server/player/form/menu.go b/server/player/form/menu.go index 5ee68d8a5..46a690dfc 100644 --- a/server/player/form/menu.go +++ b/server/player/form/menu.go @@ -3,16 +3,17 @@ package form import ( "encoding/json" "fmt" - "github.com/df-mc/dragonfly/server/world" "reflect" + + "github.com/df-mc/dragonfly/server/world" ) -// Menu represents a menu form. These menus are made up of a title and a body, with a number of buttons which -// come below the body. These buttons may also have images on the side of them. +// Menu represents a menu form. These menus are made up of a title and a body, with a number of elements which +// come below the body. These elements can include buttons, dividers, headers, and labels. type Menu struct { title, body string submittable MenuSubmittable - buttons []Button + elements []MenuElement } // NewMenu creates a new Menu form using the MenuSubmittable passed to handle the output of the form. The @@ -30,10 +31,10 @@ func NewMenu(submittable MenuSubmittable, title ...any) Menu { // MarshalJSON ... func (m Menu) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]any{ - "type": "form", - "title": m.title, - "content": m.body, - "buttons": m.Buttons(), + "type": "form", + "title": m.title, + "content": m.body, + "elements": m.Elements(), }) } @@ -44,10 +45,43 @@ func (m Menu) WithBody(body ...any) Menu { return m } -// WithButtons creates a copy of the Menu form and appends the buttons passed to the existing buttons, after +// AddButton appends a button to the menu's element list and returns the updated Menu. +func (m Menu) AddButton(button Button) Menu { + m.elements = append(m.elements, button) + return m +} + +// AddDivider appends a divider to the menu's element list and returns the updated Menu. +func (m Menu) AddDivider(divider Divider) Menu { + m.elements = append(m.elements, divider) + return m +} + +// AddHeader appends a header to the menu's element list and returns the updated Menu. +func (m Menu) AddHeader(header Header) Menu { + m.elements = append(m.elements, header) + return m +} + +// AddLabel appends a label to the menu's element list and returns the updated Menu. +func (m Menu) AddLabel(label Label) Menu { + m.elements = append(m.elements, label) + return m +} + +// WithButtons creates a copy of the Menu form and appends the buttons passed to the existing elements, after // which the new Menu form is returned. func (m Menu) WithButtons(buttons ...Button) Menu { - m.buttons = append(m.buttons, buttons...) + for _, b := range buttons { + m.elements = append(m.elements, b) + } + return m +} + +// WithElements creates a copy of the Menu form and appends the elements passed to the existing elements, after +// which the new Menu form is returned. This allows adding any MenuElement type. +func (m Menu) WithElements(elements ...MenuElement) Menu { + m.elements = append(m.elements, elements...) return m } @@ -61,8 +95,8 @@ func (m Menu) Body() string { return m.body } -// Buttons returns a list of all buttons of the MenuSubmittable. It parses them from the fields using -// reflection and returns them. +// Buttons returns a list of all buttons of the MenuSubmittable. It collects buttons from the MenuSubmittable +// fields and any buttons added via WithButtons(), AddButton(). func (m Menu) Buttons() []Button { v := reflect.New(reflect.TypeOf(m.submittable)).Elem() v.Set(reflect.ValueOf(m.submittable)) @@ -73,13 +107,36 @@ func (m Menu) Buttons() []Button { if !field.CanSet() { continue } - // Each exported field is guaranteed to be of type Button. - buttons = append(buttons, field.Interface().(Button)) + if b, ok := field.Interface().(Button); ok { + buttons = append(buttons, b) + } + } + for _, elem := range m.elements { + if b, ok := elem.(Button); ok { + buttons = append(buttons, b) + } } - buttons = append(buttons, m.buttons...) return buttons } +// Elements returns all elements of this menu form. It collects elements from the MenuSubmittable +// fields and any elements added via WithElements(). +func (m Menu) Elements() []MenuElement { + v := reflect.New(reflect.TypeOf(m.submittable)).Elem() + v.Set(reflect.ValueOf(m.submittable)) + + elements := make([]MenuElement, 0, v.NumField()+len(m.elements)) + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if !field.CanSet() { + continue + } + elements = append(elements, field.Interface().(MenuElement)) + } + elements = append(elements, m.elements...) + return elements +} + // SubmitJSON submits a JSON value to the menu, containing the index of the button clicked. func (m Menu) SubmitJSON(b []byte, submitter Submitter, tx *world.Tx) error { if b == nil { @@ -102,8 +159,8 @@ func (m Menu) SubmitJSON(b []byte, submitter Submitter, tx *world.Tx) error { return nil } -// verify verifies if the form is valid, checking all fields are of the type Button. It panics if the form is -// not valid. +// verify verifies if the form is valid, checking all exported fields implement MenuElement. +// It panics if the form is not valid. func (m Menu) verify() { v := reflect.New(reflect.TypeOf(m.submittable)).Elem() v.Set(reflect.ValueOf(m.submittable)) @@ -111,8 +168,8 @@ func (m Menu) verify() { if !v.Field(i).CanSet() { continue } - if _, ok := v.Field(i).Interface().(Button); !ok { - panic("all exported fields must be of the type form.Button") + if _, ok := v.Field(i).Interface().(MenuElement); !ok { + panic("all exported fields must implement form.MenuElement") } } } diff --git a/server/player/handler.go b/server/player/handler.go index 6631c6cc1..ada7a6546 100644 --- a/server/player/handler.go +++ b/server/player/handler.go @@ -1,6 +1,9 @@ package player import ( + "net" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/cmd" "github.com/df-mc/dragonfly/server/event" @@ -9,8 +12,6 @@ import ( "github.com/df-mc/dragonfly/server/session" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "net" - "time" ) type Context = event.Context[*Player] @@ -121,6 +122,8 @@ type Handler interface { // HandleSignEdit handles the player editing a sign. It is called for every keystroke while editing a sign and // has both the old text passed and the text after the edit. This typically only has a change of one character. HandleSignEdit(ctx *Context, pos cube.Pos, frontSide bool, oldText, newText string) + // HandleSleep handles the player beginning the sleep action. ctx.Cancel() may be called to cancel the action. + HandleSleep(ctx *Context, sendReminder *bool) // HandleLecternPageTurn handles the player turning a page in a lectern. ctx.Cancel() may be called to cancel the // page turn. The page number may be changed by assigning to *page. HandleLecternPageTurn(ctx *Context, pos cube.Pos, oldPage int, newPage *int) @@ -128,15 +131,14 @@ type Handler interface { // damage through usage. // The type of the item may be checked to determine whether it was armour or a tool used. The damage to // the item is passed. - HandleItemDamage(ctx *Context, i item.Stack, damage int) + HandleItemDamage(ctx *Context, i item.Stack, damage *int) // HandleItemPickup handles the player picking up an item from the ground. The item stack laying on the // ground is passed. ctx.Cancel() may be called to prevent the player from picking up the item. HandleItemPickup(ctx *Context, i *item.Stack) // HandleHeldSlotChange handles the player changing the slot they are currently holding. HandleHeldSlotChange(ctx *Context, from, to int) - // HandleItemDrop handles the player dropping an item on the ground. The dropped item entity is passed. - // ctx.Cancel() may be called to prevent the player from dropping the entity.Item passed on the ground. - // e.Item() may be called to obtain the item stack dropped. + // HandleItemDrop handles the player dropping an item on the ground. + // ctx.Cancel() may be called to prevent the player from dropping the item.Stack passed on the ground. HandleItemDrop(ctx *Context, s item.Stack) // HandleTransfer handles a player being transferred to another server. ctx.Cancel() may be called to // cancel the transfer. @@ -179,6 +181,7 @@ func (NopHandler) HandleBlockBreak(*Context, cube.Pos, *[]item.Stack, *int) func (NopHandler) HandleBlockPlace(*Context, cube.Pos, world.Block) {} func (NopHandler) HandleBlockPick(*Context, cube.Pos, world.Block) {} func (NopHandler) HandleSignEdit(*Context, cube.Pos, bool, string, string) {} +func (NopHandler) HandleSleep(*Context, *bool) {} func (NopHandler) HandleLecternPageTurn(*Context, cube.Pos, int, *int) {} func (NopHandler) HandleItemPickup(*Context, *item.Stack) {} func (NopHandler) HandleItemUse(*Context) {} @@ -186,7 +189,7 @@ func (NopHandler) HandleItemUseOnBlock(*Context, cube.Pos, cube.Face, mgl64.Vec3 func (NopHandler) HandleItemUseOnEntity(*Context, world.Entity) {} func (NopHandler) HandleItemRelease(ctx *Context, item item.Stack, dur time.Duration) {} func (NopHandler) HandleItemConsume(*Context, item.Stack) {} -func (NopHandler) HandleItemDamage(*Context, item.Stack, int) {} +func (NopHandler) HandleItemDamage(*Context, item.Stack, *int) {} func (NopHandler) HandleAttackEntity(*Context, world.Entity, *float64, *float64, *bool) {} func (NopHandler) HandleExperienceGain(*Context, *int) {} func (NopHandler) HandlePunchAir(*Context) {} diff --git a/server/player/player.go b/server/player/player.go index 67fcbe73b..7c5fa6f5c 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -57,6 +57,10 @@ type playerData struct { sneaking, sprinting, swimming, gliding, crawling, flying, invisible, immobile, onGround, usingItem bool + + sleeping bool + sleepPos cube.Pos + usingSince time.Time glideTicks int64 @@ -154,7 +158,7 @@ func (p *Player) DeviceID() string { if p.session() == session.Nop { return "" } - return p.session().ClientData().DeviceID + return string(p.session().ClientData().DeviceID) } // DeviceModel returns the device model of the player. If the Player is not connected to a network session, an empty @@ -644,6 +648,8 @@ func (p *Player) Hurt(dmg float64, src world.DamageSource) (float64, bool) { p.tx.PlaySound(pos, sound.Drowning{}) } + p.Wake() + if p.Dead() { p.kill(src) } @@ -885,7 +891,9 @@ func finishDying(_ *world.Tx, e world.Entity) { // position server side so that in the future, the client won't respawn // on the death location when disconnecting. The client should not see // the movement itself yet, though. - p.data.Pos = p.tx.World().Spawn().Vec3() + pos, _, _, _ := p.spawnLocation() + + p.data.Pos = pos.Vec3() } } @@ -936,10 +944,13 @@ func (p *Player) respawn(f func(p *Player)) { if !p.Dead() || p.session() == session.Nop { return } - // We can use the principle here that returning through a portal of a specific dimension inside that dimension will - // always bring us back to the overworld. - w := p.tx.World().PortalDestination(p.tx.World().Dimension()) - pos := w.PlayerSpawn(p.UUID()).Vec3Middle() + + blockPos, w, spawnObstructed, _ := p.spawnLocation() + pos := blockPos.Vec3Middle() + + if spawnObstructed { + p.Messaget(chat.MessageBedNotValid) + } p.addHealth(p.MaxHealth()) p.hunger.Reset() @@ -961,6 +972,26 @@ func (p *Player) respawn(f func(p *Player)) { }) } +// spawnLocation designates a players safe spawn location. +func (p *Player) spawnLocation() (playerSpawn cube.Pos, w *world.World, spawnBlockBroken bool, previousDimension world.Dimension) { + tx := p.tx + w = tx.World() + previousDimension = w.Dimension() + playerSpawn = w.PlayerSpawn(p.UUID()) + if b, ok := tx.Block(playerSpawn).(block.Bed); ok && b.CanRespawnOn() { + pos, ok := b.SafeSpawn(playerSpawn, tx) + if ok { + return pos, w, false, previousDimension + } + } + + // We can use the principle here that returning through a portal of a specific dimension inside that dimension will + // always bring us back to the overworld. + w = w.PortalDestination(w.Dimension()) + worldSpawn := w.Spawn() + return worldSpawn, w, playerSpawn != worldSpawn, previousDimension +} + // StartSprinting makes a player start sprinting, increasing the speed of the player by 30% and making // particles show up under the feet. The player will only start sprinting if its food level is high enough. // If the player is sneaking when calling StartSprinting, it is stopped from sneaking. @@ -1171,6 +1202,81 @@ func (p *Player) Jump() { } } +// Sleep makes the player sleep at the given position. If the position does not map to a bed (specifically the head side), +// the player will not sleep. +func (p *Player) Sleep(pos cube.Pos) { + if p.sleeping { + // The player is already sleeping. + return + } + + tx := p.tx + b, ok := tx.Block(pos).(block.Bed) + if !ok || b.Sleeper != nil { + // The player cannot sleep here. + return + } + + ctx, sendReminder := event.C(p), true + if p.Handler().HandleSleep(ctx, &sendReminder); ctx.Cancelled() { + return + } + + b.Sleeper = p.H() + tx.SetBlock(pos, b, nil) + + tx.World().SetRequiredSleepDuration(time.Millisecond * 5050) + + p.data.Pos = pos.Vec3Middle().Add(mgl64.Vec3{0, 0.5625}) + p.sleeping = true + p.sleepPos = pos + + p.session().SendPlayerSpawn(pos.Vec3()) + + if sendReminder { + tx.BroadcastSleepingReminder(p) + } + + tx.BroadcastSleepingIndicator() + p.updateState() +} + +// Wake forces the player out of bed if they are sleeping. +func (p *Player) Wake() { + if !p.sleeping { + return + } + p.sleeping = false + + tx := p.tx + tx.BroadcastSleepingIndicator() + + for _, v := range p.viewers() { + v.ViewEntityWake(p) + } + p.updateState() + + pos := p.sleepPos + if b, ok := tx.Block(pos).(block.Bed); ok { + b.Sleeper = nil + tx.SetBlock(pos, b, nil) + } +} + +// Sleeping returns true if the player is currently sleeping, along with the position of the bed the player is sleeping +// on. +func (p *Player) Sleeping() (cube.Pos, bool) { + if !p.sleeping { + return cube.Pos{}, false + } + return p.sleepPos, true +} + +// SendSleepingIndicator displays a notification to the player on the amount of sleeping players in the world. +func (p *Player) SendSleepingIndicator(sleeping, max int) { + p.session().ViewSleepingPlayers(sleeping, max) +} + // SetInvisible sets the player invisible, so that other players will not be able to see it. func (p *Player) SetInvisible() { if p.Invisible() { @@ -1561,12 +1667,12 @@ func (p *Player) UseItemOnBlock(pos cube.Pos, face cube.Face, clickPos mgl64.Vec if _, ok := p.tx.Block(pos).(block.Air); ok || !p.canReach(pos.Vec3Centre()) { // The client used its item on a block that does not exist server-side or one it couldn't reach. Stop trying // to use the item immediately. - p.resendBlocks(pos, face) + p.resendNearbyBlocks(pos, face) return } ctx := event.C(p) if p.Handler().HandleItemUseOnBlock(ctx, pos, face, clickPos); ctx.Cancelled() { - p.resendBlocks(pos, face) + p.resendNearbyBlocks(pos, face) return } i, left := p.HeldItems() @@ -1664,13 +1770,19 @@ func (p *Player) AttackEntity(e world.Entity) bool { critical = !p.Sprinting() && !p.Flying() && p.FallDistance() > 0 && !slowFalling && !blind ) + i, _ := p.HeldItems() + if k, ok := i.Enchantment(enchantment.Knockback); ok { + inc := enchantment.Knockback.Force(k.Level()) + force += inc + height += inc + } + ctx := event.C(p) if p.Handler().HandleAttackEntity(ctx, e, &force, &height, &critical); ctx.Cancelled() { return false } p.SwingArm() - i, _ := p.HeldItems() if !isLiving { return false } @@ -1707,11 +1819,6 @@ func (p *Player) AttackEntity(e world.Entity) bool { p.Exhaust(0.1) - if k, ok := i.Enchantment(enchantment.Knockback); ok { - inc := enchantment.Knockback.Force(k.Level()) - force += inc - height += inc - } living.KnockBack(p.Position(), force, height) if f, ok := i.Enchantment(enchantment.FireAspect); ok { @@ -1741,7 +1848,7 @@ func (p *Player) StartBreaking(pos cube.Pos, face cube.Face) { ctx := event.C(p) if p.Handler().HandleFireExtinguish(ctx, pos); ctx.Cancelled() { // Resend the block because on client side that was extinguished - p.resendBlocks(pos, face) + p.resendNearbyBlocks(pos, face) return } @@ -1809,7 +1916,7 @@ func (p *Player) breakTime(pos cube.Pos) time.Duration { // FinishBreaking will stop the animation and break the block. func (p *Player) FinishBreaking() { if !p.breaking { - p.resendBlock(p.breakingPos) + p.resendNearbyBlock(p.breakingPos) return } p.AbortBreaking() @@ -1873,7 +1980,7 @@ func (p *Player) PlaceBlock(pos cube.Pos, b world.Block, ctx *item.UseContext) { // of the player. A bool is returned indicating if a block was placed successfully. func (p *Player) placeBlock(pos cube.Pos, b world.Block, ignoreBBox bool) bool { if !p.canReach(pos.Vec3Centre()) || !p.GameMode().AllowsEditing() { - p.resendBlocks(pos, cube.Faces()...) + p.resendNearbyBlocks(pos, cube.Faces()...) return false } if obstructed, selfOnly := p.obstructedPos(pos, b); obstructed && !ignoreBBox { @@ -1881,14 +1988,14 @@ func (p *Player) placeBlock(pos cube.Pos, b world.Block, ignoreBBox bool) bool { // Only resend blocks if there were other entities blocking the // placement than the player itself. Resending blocks placed inside // the player itself leads to synchronisation issues. - p.resendBlocks(pos, cube.Faces()...) + p.resendNearbyBlocks(pos, cube.Faces()...) } return false } ctx := event.C(p) if p.Handler().HandleBlockPlace(ctx, pos, b); ctx.Cancelled() { - p.resendBlocks(pos, cube.Faces()...) + p.resendNearbyBlocks(pos, cube.Faces()...) return false } p.tx.SetBlock(pos, b, nil) @@ -1912,7 +2019,7 @@ func (p *Player) obstructedPos(pos cube.Pos, b world.Block) (obstructed, selfOnl for e := range p.tx.EntitiesWithin(cube.Box(-3, -3, -3, 3, 3, 3).Translate(pos.Vec3())) { t := e.H().Type() switch t { - case entity.ItemType, entity.ArrowType: + case entity.ItemType, entity.ArrowType, entity.ExperienceOrbType: continue default: if cube.AnyIntersections(blockBoxes, t.BBox(e).Translate(e.Position()).Grow(-1e-4)) { @@ -1936,11 +2043,11 @@ func (p *Player) BreakBlock(pos cube.Pos) { return } if !p.canReach(pos.Vec3Centre()) || !p.GameMode().AllowsEditing() { - p.resendBlocks(pos) + p.resendNearbyBlocks(pos) return } if _, breakable := b.(block.Breakable); !breakable && !p.GameMode().CreativeInventory() { - p.resendBlocks(pos) + p.resendNearbyBlocks(pos) return } held, _ := p.HeldItems() @@ -1948,12 +2055,14 @@ func (p *Player) BreakBlock(pos cube.Pos) { xp := 0 if breakable, ok := b.(block.Breakable); ok && !p.GameMode().CreativeInventory() { - xp = breakable.BreakInfo().XPDrops.RandomValue() + if _, hasSilkTouch := held.Enchantment(enchantment.SilkTouch); !hasSilkTouch { + xp = breakable.BreakInfo().XPDrops.RandomValue() + } } ctx := event.C(p) if p.Handler().HandleBlockBreak(ctx, pos, &drops, &xp); ctx.Cancelled() { - p.resendBlocks(pos) + p.resendNearbyBlocks(pos) return } held, left := p.HeldItems() @@ -2062,6 +2171,7 @@ func (p *Player) Teleport(pos mgl64.Vec3) { if p.Handler().HandleTeleport(ctx, pos); ctx.Cancelled() { return } + p.Wake() p.teleport(pos) } @@ -2445,7 +2555,7 @@ func (p *Player) Tick(tx *world.Tx, current int64) { } } - p.session().SendDebugShapes() + p.session().SendDebugShapes(tx.World().Dimension()) p.session().SendHudUpdates() if p.prevWorld != tx.World() && p.prevWorld != nil { @@ -2793,14 +2903,14 @@ func (p *Player) EditSign(pos cube.Pos, frontText, backText string) error { ctx := event.C(p) if frontText != sign.Front.Text { if p.Handler().HandleSignEdit(ctx, pos, true, sign.Front.Text, frontText); ctx.Cancelled() { - p.resendBlock(pos) + p.resendNearbyBlock(pos) return nil } sign.Front.Text = frontText sign.Front.Owner = p.XUID() } else { if p.Handler().HandleSignEdit(ctx, pos, false, sign.Back.Text, backText); ctx.Cancelled() { - p.resendBlock(pos) + p.resendNearbyBlock(pos) return nil } sign.Back.Text = backText @@ -2918,7 +3028,7 @@ func (p *Player) damageItem(s item.Stack, d int) item.Stack { return s } ctx := event.C(p) - if p.Handler().HandleItemDamage(ctx, s, d); ctx.Cancelled() { + if p.Handler().HandleItemDamage(ctx, s, &d); ctx.Cancelled() || d <= 0 { return s } if e, ok := s.Enchantment(enchantment.Unbreaking); ok { @@ -3138,19 +3248,44 @@ func (p *Player) viewers() []world.Viewer { return viewers } -// resendBlocks resends blocks in a world.World at the cube.Pos passed and the block next to it at the cube.Face passed. -func (p *Player) resendBlocks(pos cube.Pos, faces ...cube.Face) { +// withinChunkRadius checks if the position provided is within the chunk radius of the player. +func (p *Player) withinChunkRadius(pos mgl64.Vec3) bool { + playerChunkX, playerChunkZ := int(p.Position().X())>>4, int(p.Position().Z())>>4 + posChunkX, posChunkZ := int(pos.X())>>4, int(pos.Z())>>4 + dx, dz := playerChunkX-posChunkX, playerChunkZ-posChunkZ + if dx < 0 { + dx = -dx + } + if dz < 0 { + dz = -dz + } + r := int(p.session().ChunkRadius()) + return dx <= r && dz <= r +} + +// resendNearbyBlocks resends the block at cube.Pos and its adjacent blocks (if faces provided), +// but only if they are within the player's render distance. +func (p *Player) resendNearbyBlocks(pos cube.Pos, faces ...cube.Face) { if p.session() == session.Nop { return } - p.resendBlock(pos) + p.resendNearbyBlock(pos) for _, f := range faces { - p.resendBlock(pos.Side(f)) + p.resendNearbyBlock(pos.Side(f)) } } -// resendBlock resends the block at a cube.Pos in the world.World passed. -func (p *Player) resendBlock(pos cube.Pos) { +// resendNearbyBlock resends a block at cube.Pos if it is within the player's render distance. +func (p *Player) resendNearbyBlock(pos cube.Pos) { + if p.session() == session.Nop { + return + } + if !p.withinChunkRadius(pos.Vec3()) { + // This is a safety check. Without it, clients could request block resends for arbitrary world positions + // (including unloaded chunks). A malicious client could repeatedly trigger such requests and force the server + // to allocate memory for chunks, potentially exhausting RAM. + return + } b := p.tx.Block(pos) p.session().ViewBlockUpdate(pos, b, 0) if _, ok := b.(world.LiquidDisplacer); ok { diff --git a/server/player/type.go b/server/player/type.go index cb6c3a56b..49d91f4e5 100644 --- a/server/player/type.go +++ b/server/player/type.go @@ -47,7 +47,10 @@ func (ptype) NetworkOffset() float64 { return 1.621 } func (ptype) BBox(e world.Entity) cube.BBox { p := e.(*Player) s := p.Scale() + _, sleeping := p.Sleeping() switch { + case sleeping: + return cube.Box(-0.1*s, 0, -0.1*s, 0.1*s, 0.2*s, 0.1*s) case p.Gliding(), p.Swimming(), p.Crawling(): return cube.Box(-0.3*s, 0, -0.3*s, 0.3*s, 0.6*s, 0.3*s) case p.Sneaking(): diff --git a/server/server.go b/server/server.go index 8f917bda8..b02676693 100644 --- a/server/server.go +++ b/server/server.go @@ -1,7 +1,6 @@ package server import ( - "bytes" "context" _ "embed" "encoding/base64" @@ -9,9 +8,7 @@ import ( "iter" "maps" "os" - "os/exec" "os/signal" - "runtime" "runtime/debug" "slices" "strings" @@ -503,22 +500,6 @@ func (srv *Server) dimension(dimension world.Dimension) *world.World { } } -// checkNetIsolation checks if a loopback exempt is in place to allow the -// hosting device to join the server. This is only relevant on Windows. It will -// never log anything for anything but Windows. -func (srv *Server) checkNetIsolation() { - if runtime.GOOS != "windows" { - // Only an issue on Windows. - return - } - data, _ := exec.Command("CheckNetIsolation", "LoopbackExempt", "-s", `-n="microsoft.minecraftuwp_8wekyb3d8bbwe"`).CombinedOutput() - if bytes.Contains(data, []byte("microsoft.minecraftuwp_8wekyb3d8bbwe")) { - return - } - const loopbackExemptCmd = `CheckNetIsolation LoopbackExempt -a -n="Microsoft.MinecraftUWP_8wekyb3d8bbwe"` - srv.conf.Log.Info("You are currently unable to join the server on this machine. Run " + loopbackExemptCmd + " in an admin PowerShell session to resolve.") -} - // handleSessionClose handles the closing of a session. It removes the player // of the session from the server. func (srv *Server) handleSessionClose(tx *world.Tx, c session.Controllable) { @@ -573,13 +554,15 @@ func (srv *Server) createWorld(dim world.Dimension, nether, end **world.World) * logger.Debug("Loading dimension...") conf := world.Config{ - Log: logger, - Dim: dim, - Provider: srv.conf.WorldProvider, - Generator: srv.conf.Generator(dim), - RandomTickSpeed: srv.conf.RandomTickSpeed, - ReadOnly: srv.conf.ReadOnlyWorld, - Entities: srv.conf.Entities, + Log: logger, + Dim: dim, + Provider: srv.conf.WorldProvider, + Generator: srv.conf.Generator(dim), + RandomTickSpeed: srv.conf.RandomTickSpeed, + ReadOnly: srv.conf.ReadOnlyWorld, + SaveInterval: srv.conf.SaveInterval, + ChunkUnloadInterval: srv.conf.ChunkUnloadInterval, + Entities: srv.conf.Entities, PortalDestination: func(dim world.Dimension) *world.World { if dim == world.Nether { return *nether diff --git a/server/session/command.go b/server/session/command.go index 644a94d73..498c7b5ef 100644 --- a/server/session/command.go +++ b/server/session/command.go @@ -1,12 +1,13 @@ package session import ( + "math" + "github.com/df-mc/dragonfly/server/cmd" "github.com/go-gl/mathgl/mgl64" "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" "golang.org/x/text/language" - "math" ) // SendCommandOutput sends the output of a command to the player. It will be shown to the caller of the @@ -46,7 +47,7 @@ type translation interface { // sendAvailableCommands sends all available commands of the server. Once sent, they will be visible in the // /help list and will be auto-completed. -func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd.Runnable { +func (s *Session) sendAvailableCommands(co Controllable, softEnums map[string]struct{}) map[string]map[int]cmd.Runnable { commands := cmd.Commands() m := make(map[string]map[int]cmd.Runnable, len(commands)) @@ -83,32 +84,9 @@ func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd. for i, params := range params { for _, paramInfo := range params { t, enum := valueToParamType(paramInfo, co) - t |= protocol.CommandArgValid suffix := paramInfo.Suffix opt := byte(0) - if _, ok := paramInfo.Value.(bool); ok { - opt |= protocol.ParamOptionCollapseEnum - } - if len(enum.Options) > 0 || enum.Type != "" { - if !enum.Dynamic { - index, ok := enumIndices[enum.Type] - if !ok { - index = uint32(len(enums)) - enumIndices[enum.Type] = index - enums = append(enums, enum) - } - t |= protocol.CommandArgEnum | index - } else { - index, ok := dynamicEnumIndices[enum.Type] - if !ok { - index = uint32(len(dynamicEnums)) - dynamicEnumIndices[enum.Type] = index - dynamicEnums = append(dynamicEnums, enum) - } - t |= protocol.CommandArgSoftEnum | index - } - } if suffix != "" { index, ok := suffixIndices[suffix] if !ok { @@ -116,7 +94,29 @@ func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd. suffixIndices[suffix] = index pk.Suffixes = append(pk.Suffixes, suffix) } - t |= protocol.CommandArgSuffixed | index + t = protocol.CommandArgSuffixed | index + } else { + t |= protocol.CommandArgValid + if len(enum.Options) > 0 || enum.Type != "" { + _, dynamic := softEnums[enum.Type] + if !dynamic { + index, ok := enumIndices[enum.Type] + if !ok { + index = uint32(len(enums)) + enumIndices[enum.Type] = index + enums = append(enums, enum) + } + t |= protocol.CommandArgEnum | index + } else { + index, ok := dynamicEnumIndices[enum.Type] + if !ok { + index = uint32(len(dynamicEnums)) + dynamicEnumIndices[enum.Type] = index + dynamicEnums = append(dynamicEnums, enum) + } + t |= protocol.CommandArgSoftEnum | index + } + } } overloads[i].Parameters = append(overloads[i].Parameters, protocol.CommandParameter{ Name: paramInfo.Name, @@ -127,10 +127,11 @@ func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd. } } pk.Commands = append(pk.Commands, protocol.Command{ - Name: c.Name(), - Description: c.Description(), - AliasesOffset: aliasesIndex, - Overloads: overloads, + Name: c.Name(), + Description: c.Description(), + AliasesOffset: aliasesIndex, + PermissionLevel: protocol.CommandPermissionLevelAny, + Overloads: overloads, }) } pk.DynamicEnums = make([]protocol.DynamicEnum, 0, len(dynamicEnums)) @@ -151,7 +152,7 @@ func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd. enumValueIndices[opt] = index pk.EnumValues = append(pk.EnumValues, opt) } - protoEnum.ValueIndices = append(protoEnum.ValueIndices, uint(index)) + protoEnum.ValueIndices = append(protoEnum.ValueIndices, index) } pk.Enums = append(pk.Enums, protoEnum) } @@ -162,7 +163,6 @@ func (s *Session) sendAvailableCommands(co Controllable) map[string]map[int]cmd. type commandEnum struct { Type string Options []string - Dynamic bool } // valueToParamType finds the command argument type of the value passed and returns it, in addition to creating @@ -181,8 +181,8 @@ func valueToParamType(i cmd.ParamInfo, source cmd.Source) (t uint32, enum comman return protocol.CommandArgTypeTarget, enum case bool: return 0, commandEnum{ - Type: "bool", - Options: []string{"true", "1", "false", "0"}, + Type: "Boolean", + Options: []string{"true", "false"}, } case mgl64.Vec3: return protocol.CommandArgTypePosition, enum @@ -196,7 +196,6 @@ func valueToParamType(i cmd.ParamInfo, source cmd.Source) (t uint32, enum comman return 0, commandEnum{ Type: enum.Type(), Options: enum.Options(source), - Dynamic: true, } } return protocol.CommandArgTypeValue, enum @@ -205,7 +204,11 @@ func valueToParamType(i cmd.ParamInfo, source cmd.Source) (t uint32, enum comman // resendCommands resends all commands that a Session has access to if the map of runnable commands passed does not // match with the commands that the Session is currently allowed to execute. // True is returned if the commands were resent. -func (s *Session) resendCommands(before map[string]map[int]cmd.Runnable, co Controllable) (map[string]map[int]cmd.Runnable, bool) { +func (s *Session) resendCommands( + before map[string]map[int]cmd.Runnable, + co Controllable, + softEnums map[string]struct{}, +) (map[string]map[int]cmd.Runnable, bool) { commands := cmd.Commands() m := make(map[string]map[int]cmd.Runnable, len(commands)) @@ -217,13 +220,13 @@ func (s *Session) resendCommands(before map[string]map[int]cmd.Runnable, co Cont } } if len(before) != len(m) { - return s.sendAvailableCommands(co), true + return s.sendAvailableCommands(co, softEnums), true } // First check for commands that were newly added. for name, r := range m { for k := range r { if _, ok := before[name][k]; !ok { - return s.sendAvailableCommands(co), true + return s.sendAvailableCommands(co, softEnums), true } } } @@ -231,7 +234,7 @@ func (s *Session) resendCommands(before map[string]map[int]cmd.Runnable, co Cont for name, r := range before { for k := range r { if _, ok := m[name][k]; !ok { - return s.sendAvailableCommands(co), true + return s.sendAvailableCommands(co, softEnums), true } } } @@ -257,22 +260,41 @@ func (s *Session) enums(co Controllable) (map[string]cmd.Enum, map[string][]stri } // resendEnums checks the options of the enums passed against the values that were previously recorded. If they do not -// match, the enum is resent to the client and the values are updated in the before map. -func (s *Session) resendEnums(enums map[string]cmd.Enum, before map[string][]string, c Controllable) { +// match, and the enum is in softEnums, the enum is resent via UpdateSoftEnum. If the enum is not yet in softEnums, +// it is added and the full AvailableCommands packet is resent. +func (s *Session) resendEnums( + enums map[string]cmd.Enum, + before map[string][]string, + softEnums map[string]struct{}, + r map[string]map[int]cmd.Runnable, + c Controllable, +) map[string]map[int]cmd.Runnable { for name, enum := range enums { valuesBefore := before[name] values := enum.Options(c) before[name] = values + changed := false if len(valuesBefore) != len(values) { - s.writePacket(&packet.UpdateSoftEnum{EnumType: name, Options: values, ActionType: packet.SoftEnumActionSet}) + changed = true + } else { + for k, v := range values { + if valuesBefore[k] != v { + changed = true + break + } + } + } + if !changed { continue } - for k, v := range values { - if valuesBefore[k] != v { - s.writePacket(&packet.UpdateSoftEnum{EnumType: name, Options: values, ActionType: packet.SoftEnumActionSet}) - break - } + + if _, dynamic := softEnums[name]; dynamic { + s.writePacket(&packet.UpdateSoftEnum{EnumType: name, Options: values, ActionType: packet.SoftEnumActionSet}) + } else { + softEnums[name] = struct{}{} + r = s.sendAvailableCommands(c, softEnums) } } + return r } diff --git a/server/session/controllable.go b/server/session/controllable.go index 524c5dbb0..8a924bf60 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -43,6 +43,9 @@ type Controllable interface { FlightSpeed() float64 VerticalFlightSpeed() float64 + Sleep(pos cube.Pos) + Wake() + Chat(msg ...any) ExecuteCommand(commandLine string) GameMode() world.GameMode diff --git a/server/session/enchantment_texts.go b/server/session/enchantment_texts.go index a1ecd5595..6dd403b2f 100644 --- a/server/session/enchantment_texts.go +++ b/server/session/enchantment_texts.go @@ -4,4 +4,4 @@ package session // enchantNames are names translated to the 'Standard Galactic Alphabet' client-side. The names generally have no meaning // on the vanilla server implementation, so we can sneak some easter eggs in here without anyone noticing. -var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "akmal fairuz", "alvin0319", "andreas hgk", "atm85", "blackjack200", "cetfu", "cjmustard", "cooldogedev", "cqdetdev", "da pig guy", "daft0175", "dasciam", "davy craft648", "deniel world", "didntpot", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "game parrot", "gewinum", "hashim the arab", "hochbaum", "im da real ani", "inotflying", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "k4ties", "krivey", "liatoast", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "nope not dark", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "sculas", "sergittos", "sqmatheus", "ssaini123456", "studgi", "superomarking", "t14 raptor", "tadhunt", "theaddonn", "thicksunny", "thunder33345", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x4caa", "xd-pro"} +var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "akmal fairuz", "alvin0319", "andreashgk", "atm85", "azvyl", "blackjack200", "cetfu", "cjmustard", "cooldogedev", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "driftlgtm", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "game parrot", "gewinum", "hashim the arab", "hochbaum", "im da real ani", "inotflying", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "josscoder", "just tal develops", "k4ties", "krivey", "manab-pr", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "nope not dark", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "schphe", "sculas", "sergittos", "sqmatheus", "ssaini123456", "studgi", "superomarking", "t14 raptor", "tadhunt", "theaddonn", "thicksunny", "thunder33345", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x toast-dev", "x4caa", "xd-pro"} diff --git a/server/session/entity_metadata.go b/server/session/entity_metadata.go index b5495da49..c0f811eee 100644 --- a/server/session/entity_metadata.go +++ b/server/session/entity_metadata.go @@ -1,6 +1,10 @@ package session import ( + "math" + "time" + + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/entity" "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/internal/nbtconv" @@ -10,8 +14,6 @@ import ( "github.com/go-gl/mathgl/mgl64" "github.com/google/uuid" "github.com/sandertv/gophertunnel/minecraft/protocol" - "math" - "time" ) // parseEntityMetadata returns an entity metadata object with default values. It is equivalent to setting @@ -57,6 +59,9 @@ func (s *Session) addSpecificMetadata(e any, m protocol.EntityMetadata) { if gl, ok := e.(glider); ok && gl.Gliding() { m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagGliding) } + if bb, ok := e.(baby); ok && bb.Baby() { + m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagBaby) + } if b, ok := e.(breather); ok { m[protocol.EntityDataKeyAirSupply] = int16(b.AirSupply().Milliseconds() / 50) m[protocol.EntityDataKeyAirSupplyMax] = int16(b.MaxAirSupply().Milliseconds() / 50) @@ -111,6 +116,14 @@ func (s *Session) addSpecificMetadata(e any, m protocol.EntityMetadata) { if sc, ok := e.(scoreTag); ok { m[protocol.EntityDataKeyScore] = sc.ScoreTag() } + if sl, ok := e.(sleeper); ok { + if pos, ok := sl.Sleeping(); ok { + m[protocol.EntityDataKeyBedPosition] = protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])} + + // For some reason there is no such flag in gophertunnel. + m.SetFlag(protocol.EntityDataKeyPlayerFlags, 1) + } + } if c, ok := e.(areaEffectCloud); ok { m[protocol.EntityDataKeyDataRadius] = float32(c.Radius()) @@ -188,6 +201,10 @@ type glider interface { Gliding() bool } +type baby interface { + Baby() bool +} + type breather interface { Breathing() bool AirSupply() time.Duration @@ -260,6 +277,10 @@ type gameMode interface { GameMode() world.GameMode } +type sleeper interface { + Sleeping() (cube.Pos, bool) +} + type tnt interface { Fuse() time.Duration } diff --git a/server/session/handler_anvil.go b/server/session/handler_anvil.go index 65f63fe1d..143d4c974 100644 --- a/server/session/handler_anvil.go +++ b/server/session/handler_anvil.go @@ -2,12 +2,13 @@ package session import ( "fmt" + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/sandertv/gophertunnel/minecraft/protocol" - "math/rand/v2" ) const ( @@ -267,19 +268,3 @@ func mergeEnchantments(input item.Stack, material item.Stack, result item.Stack, } return result, hasCompatible, hasIncompatible, cost } - -// max returns the max of two integers. -func max(x, y int) int { - if x > y { - return x - } - return y -} - -// min returns the min of two integers. -func min(x, y int) int { - if x > y { - return y - } - return x -} diff --git a/server/session/handler_crafting.go b/server/session/handler_crafting.go index 64a4d2b08..ae90c902f 100644 --- a/server/session/handler_crafting.go +++ b/server/session/handler_crafting.go @@ -16,7 +16,8 @@ import ( func (h *ItemStackRequestHandler) handleCraft(a *protocol.CraftRecipeStackRequestAction, s *Session, tx *world.Tx) error { craft, ok := s.recipes[a.RecipeNetworkID] if !ok { - return fmt.Errorf("recipe with network id %v does not exist", a.RecipeNetworkID) + // Try dynamic recipes if no static recipe matches + return h.tryDynamicCraft(s, tx, int(a.NumberOfCrafts)) } _, shaped := craft.(recipe.Shaped) _, shapeless := craft.(recipe.Shapeless) @@ -70,7 +71,8 @@ func (h *ItemStackRequestHandler) handleCraft(a *protocol.CraftRecipeStackReques func (h *ItemStackRequestHandler) handleAutoCraft(a *protocol.AutoCraftRecipeStackRequestAction, s *Session, tx *world.Tx) error { craft, ok := s.recipes[a.RecipeNetworkID] if !ok { - return fmt.Errorf("recipe with network id %v does not exist", a.RecipeNetworkID) + // Try dynamic recipes if no static recipe matches + return h.tryDynamicCraft(s, tx, int(a.TimesCrafted)) } _, shaped := craft.(recipe.Shaped) _, shapeless := craft.(recipe.Shapeless) @@ -237,3 +239,74 @@ func grow(i recipe.Item, count int) recipe.Item { } panic(fmt.Errorf("unexpected recipe item %T", i)) } + +// tryDynamicCraft attempts to match the items in the crafting grid with any registered dynamic recipes. +func (h *ItemStackRequestHandler) tryDynamicCraft(s *Session, tx *world.Tx, timesCrafted int) error { + if timesCrafted < 1 { + return fmt.Errorf("times crafted must be at least 1") + } + + size := s.craftingSize() + offset := s.craftingOffset() + + // Collect all items from the crafting grid + input := make([]recipe.Item, size) + for i := uint32(0); i < size; i++ { + slot := offset + i + it, _ := s.ui.Item(int(slot)) + if it.Empty() { + input[i] = item.Stack{} + } else { + input[i] = it + } + } + + // Try to match with any dynamic recipe + for _, dynamicRecipe := range recipe.DynamicRecipes() { + if dynamicRecipe.Block() != "crafting_table" { + continue + } + + output, ok := dynamicRecipe.Match(input) + if !ok { + continue + } + + // Found a matching dynamic recipe! Now validate ingredient counts and consume the items + // For dynamic recipes, we consume all non-empty slots, but we need to ensure each slot + // has enough items to craft timesCrafted times. + minStackCount := math.MaxInt + for i := uint32(0); i < size; i++ { + slot := offset + i + it, _ := s.ui.Item(int(slot)) + if !it.Empty() { + if it.Count() < minStackCount { + minStackCount = it.Count() + } + } + } + + // Cap timesCrafted to the minimum available stack count to prevent item duplication + if minStackCount < timesCrafted { + timesCrafted = minStackCount + } + + // Now consume the validated amount from each non-empty slot + for i := uint32(0); i < size; i++ { + slot := offset + i + it, _ := s.ui.Item(int(slot)) + if !it.Empty() { + // Consume one item from this slot per craft + st := it.Grow(-1 * timesCrafted) + h.setItemInSlot(protocol.StackRequestSlotInfo{ + Container: protocol.FullContainerName{ContainerID: protocol.ContainerCraftingInput}, + Slot: byte(slot), + }, st, s, tx) + } + } + + return h.createResults(s, tx, repeatStacks(output, timesCrafted)...) + } + + return fmt.Errorf("no matching recipe found for crafting grid") +} diff --git a/server/session/handler_player_action.go b/server/session/handler_player_action.go index 1f6d980de..4d38d9906 100644 --- a/server/session/handler_player_action.go +++ b/server/session/handler_player_action.go @@ -2,6 +2,7 @@ package session import ( "fmt" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" "github.com/sandertv/gophertunnel/minecraft/protocol" @@ -24,14 +25,10 @@ func handlePlayerAction(action int32, face int32, pos protocol.BlockPos, entityR return errSelfRuntimeID } switch action { - case protocol.PlayerActionRespawn, protocol.PlayerActionDimensionChangeDone: + case protocol.PlayerActionStartSleeping, protocol.PlayerActionRespawn, protocol.PlayerActionDimensionChangeDone: // Don't do anything for these actions. case protocol.PlayerActionStopSleeping: - if mode := c.GameMode(); !mode.Visible() && !mode.HasCollision() { - // As of v1.19.50, the client sends this packet when switching to spectator mode... even if it wasn't - // sleeping in the first place. This accounts for that. - return nil - } + c.Wake() case protocol.PlayerActionStartBreak, protocol.PlayerActionContinueDestroyBlock: s.swingingArm.Store(true) defer s.swingingArm.Store(false) diff --git a/server/session/player.go b/server/session/player.go index 6bf88cdf4..ea275920e 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -12,6 +12,7 @@ import ( _ "unsafe" // Imported for compiler directives. "github.com/df-mc/dragonfly/server/block" + "github.com/df-mc/dragonfly/server/entity" "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/item" @@ -24,6 +25,7 @@ import ( "github.com/df-mc/dragonfly/server/player/hud" "github.com/df-mc/dragonfly/server/player/skin" "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl32" "github.com/go-gl/mathgl/mgl64" "github.com/google/uuid" @@ -88,6 +90,18 @@ func (s *Session) SendRespawn(pos mgl64.Vec3, c Controllable) { }) } +// SendPlayerSpawn updates the player's spawn point on the client-side. There is currently little reason +// to do so other than to prevent the client-side "Respawn point set" message when sleeping in a bed. +func (s *Session) SendPlayerSpawn(pos mgl64.Vec3) { + blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])} + s.writePacket(&packet.SetSpawnPosition{ + SpawnType: packet.SpawnTypePlayer, + Position: blockPos, + Dimension: packet.DimensionOverworld, + SpawnPosition: blockPos, + }) +} + // sendBiomes sends all the vanilla biomes to the session. func (s *Session) sendBiomes() { definitions, stringList := world.BiomeDefinitions() @@ -505,7 +519,7 @@ func (s *Session) SendAbilities(c Controllable) { s.writePacket(&packet.UpdateAbilities{AbilityData: protocol.AbilityData{ EntityUniqueID: selfEntityRuntimeID, PlayerPermissions: packet.PermissionLevelMember, - CommandPermissions: packet.CommandPermissionLevelNormal, + CommandPermissions: protocol.CommandPermissionLevelAny, Layers: []protocol.AbilityLayer{ { Type: protocol.AbilityLayerTypeBase, @@ -557,6 +571,7 @@ func (s *Session) SendEffect(e effect.Effect) { Amplifier: int32(e.Level() - 1), Particles: !e.ParticlesHidden(), Duration: int32(dur), + Ambient: e.Ambient(), }) } @@ -647,7 +662,8 @@ func (s *Session) broadcastOffHandFunc(tx *world.Tx, c Controllable) inventory.S func (s *Session) broadcastArmourFunc(tx *world.Tx, c Controllable) inventory.SlotFunc { return func(slot int, before, after item.Stack) { - if !s.inTransaction.Load() { + inTransaction := s.inTransaction.Load() + if !inTransaction { s.sendItem(after, slot, protocol.WindowIDArmour) } if before.Comparable(after) && before.Empty() == after.Empty() { @@ -657,6 +673,10 @@ func (s *Session) broadcastArmourFunc(tx *world.Tx, c Controllable) inventory.Sl for _, viewer := range tx.Viewers(c.Position()) { viewer.ViewEntityArmour(c) } + + if !after.Empty() && inTransaction { + tx.PlaySound(entity.EyePosition(c), sound.EquipItem{Item: after.Item()}) + } } } @@ -858,7 +878,7 @@ func (s *Session) RemoveAllDebugShapes() { // SendDebugShapes sends any pending additions/removals of debug shapes to the player. Shapes should be sent // every tick to allow for batching and time-efficient updates. -func (s *Session) SendDebugShapes() { +func (s *Session) SendDebugShapes(dim world.Dimension) { s.debugShapesMu.Lock() defer s.debugShapesMu.Unlock() @@ -866,65 +886,70 @@ func (s *Session) SendDebugShapes() { return } - shapes := make([]packet.DebugDrawerShape, 0, len(s.debugShapesAdd)+len(s.debugShapesRemove)) + shapes := make([]protocol.DebugDrawerShape, 0, len(s.debugShapesAdd)+len(s.debugShapesRemove)) loop: for { select { case shape := <-s.debugShapesAdd: s.debugShapes[shape.ShapeID()] = shape - shapes = append(shapes, s.debugShapeToProtocol(shape)) + shapes = append(shapes, s.debugShapeToProtocol(shape, dim)) case id := <-s.debugShapesRemove: delete(s.debugShapes, id) - shapes = append(shapes, packet.DebugDrawerShape{NetworkID: uint64(id)}) + shapes = append(shapes, protocol.DebugDrawerShape{NetworkID: uint64(id), DimensionID: protocol.Option(s.dimensionID(dim))}) default: break loop } } - s.writePacket(&packet.ServerScriptDebugDrawer{Shapes: shapes}) + s.writePacket(&packet.DebugDrawer{Shapes: shapes}) } // debugShapeToProtocol converts a debug shape to its protocol representation. It also provides defaults // for some fields such as colour, scale and other per-shape properties. -func (s *Session) debugShapeToProtocol(shape debug.Shape) packet.DebugDrawerShape { - ps := packet.DebugDrawerShape{NetworkID: uint64(shape.ShapeID())} +func (s *Session) debugShapeToProtocol(shape debug.Shape, dim world.Dimension) protocol.DebugDrawerShape { + ps := protocol.DebugDrawerShape{ + NetworkID: uint64(shape.ShapeID()), + DimensionID: protocol.Option(s.dimensionID(dim)), + } white := color.RGBA{R: 255, G: 255, B: 255, A: 255} switch shape := shape.(type) { case *debug.Arrow: - ps.Type = protocol.Option(uint8(packet.ScriptDebugShapeArrow)) + ps.Type = protocol.Option(uint8(protocol.DebugDrawerShapeArrow)) ps.Colour = protocol.Option(valueOrDefault(shape.Colour, white)) ps.Location = protocol.Option(vec64To32(shape.Position)) - ps.LineEndLocation = protocol.Option(vec64To32(shape.EndPosition)) - ps.ArrowHeadLength = protocol.Option(valueOrDefault(float32(shape.HeadLength), 1)) - ps.ArrowHeadRadius = protocol.Option(valueOrDefault(float32(shape.HeadRadius), 0.5)) - ps.Segments = protocol.Option(valueOrDefault(uint8(shape.HeadSegments), 4)) + ps.ExtraShapeData = &protocol.ArrowShape{ + ArrowEndLocation: protocol.Option(vec64To32(shape.EndPosition)), + ArrowHeadLength: protocol.Option(valueOrDefault(float32(shape.HeadLength), 1)), + ArrowHeadRadius: protocol.Option(valueOrDefault(float32(shape.HeadRadius), 0.5)), + Segments: protocol.Option(valueOrDefault(uint8(shape.HeadSegments), 4)), + } case *debug.Box: - ps.Type = protocol.Option(uint8(packet.ScriptDebugShapeBox)) + ps.Type = protocol.Option(uint8(protocol.DebugDrawerShapeBox)) ps.Colour = protocol.Option(valueOrDefault(shape.Colour, white)) - ps.BoxBound = protocol.Option(valueOrDefault(vec64To32(shape.Bounds), mgl32.Vec3{1, 1, 1})) ps.Location = protocol.Option(vec64To32(shape.Position)) ps.Scale = protocol.Option(valueOrDefault(float32(shape.Scale), 1)) + ps.ExtraShapeData = &protocol.BoxShape{BoxBound: valueOrDefault(vec64To32(shape.Bounds), mgl32.Vec3{1, 1, 1})} case *debug.Circle: - ps.Type = protocol.Option(uint8(packet.ScriptDebugShapeCircle)) + ps.Type = protocol.Option(uint8(protocol.DebugDrawerShapeCircle)) ps.Colour = protocol.Option(valueOrDefault(shape.Colour, white)) ps.Location = protocol.Option(vec64To32(shape.Position)) ps.Scale = protocol.Option(valueOrDefault(float32(shape.Scale), 1)) - ps.Segments = protocol.Option(valueOrDefault(uint8(shape.Segments), 20)) + ps.ExtraShapeData = &protocol.SphereShape{Segments: valueOrDefault(uint8(shape.Segments), 20)} case *debug.Line: - ps.Type = protocol.Option(uint8(packet.ScriptDebugShapeLine)) + ps.Type = protocol.Option(uint8(protocol.DebugDrawerShapeLine)) ps.Colour = protocol.Option(valueOrDefault(shape.Colour, white)) ps.Location = protocol.Option(vec64To32(shape.Position)) - ps.LineEndLocation = protocol.Option(vec64To32(shape.EndPosition)) + ps.ExtraShapeData = &protocol.LineShape{LineEndLocation: vec64To32(shape.EndPosition)} case *debug.Sphere: - ps.Type = protocol.Option(uint8(packet.ScriptDebugShapeSphere)) + ps.Type = protocol.Option(uint8(protocol.DebugDrawerShapeSphere)) ps.Colour = protocol.Option(valueOrDefault(shape.Colour, white)) ps.Location = protocol.Option(vec64To32(shape.Position)) ps.Scale = protocol.Option(valueOrDefault(float32(shape.Scale), 1)) - ps.Segments = protocol.Option(valueOrDefault(uint8(shape.Segments), 20)) + ps.ExtraShapeData = &protocol.SphereShape{Segments: valueOrDefault(uint8(shape.Segments), 20)} case *debug.Text: - ps.Type = protocol.Option(uint8(packet.ScriptDebugShapeText)) + ps.Type = protocol.Option(uint8(protocol.DebugDrawerShapeText)) ps.Colour = protocol.Option(valueOrDefault(shape.Colour, white)) ps.Location = protocol.Option(vec64To32(shape.Position)) - ps.Text = protocol.Option(shape.Text) + ps.ExtraShapeData = &protocol.TextShape{Text: shape.Text} default: panic(fmt.Sprintf("unknown debug shape type %T", shape)) } diff --git a/server/session/session.go b/server/session/session.go index 4cdf02779..c3518aff2 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -82,6 +82,8 @@ type Session struct { changingDimension atomic.Bool moving bool + lastChunkPos world.ChunkPos + recipes map[uint32]recipe.Recipe blobMu sync.Mutex @@ -368,13 +370,14 @@ func (s *Session) background() { r map[string]map[int]cmd.Runnable enums map[string]cmd.Enum enumValues map[string][]string + softEnums = make(map[string]struct{}) ok bool i int ) s.ent.ExecWorld(func(tx *world.Tx, e world.Entity) { co := e.(Controllable) - r = s.sendAvailableCommands(co) + r = s.sendAvailableCommands(co, softEnums) enums, enumValues = s.enums(co) }) @@ -389,11 +392,11 @@ func (s *Session) background() { if i++; i%20 == 0 { // Enum resending happens relatively often and frequent updates are more important than with full // command changes. Those are generally only related to permission changes, which doesn't happen often. - s.resendEnums(enums, enumValues, c) + r = s.resendEnums(enums, enumValues, softEnums, r, c) } if i%100 == 0 { // Try to resend commands only every 5 seconds. - if r, ok = s.resendCommands(r, c); ok { + if r, ok = s.resendCommands(r, c, softEnums); ok { enums, enumValues = s.enums(c) } } @@ -413,10 +416,14 @@ func (s *Session) sendChunks(tx *world.Tx, c Controllable) { } pos := c.Position() s.chunkLoader.Move(tx, pos) - s.writePacket(&packet.NetworkChunkPublisherUpdate{ - Position: protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}, - Radius: uint32(s.chunkRadius) << 4, - }) + chunkPos := world.ChunkPos{int32(pos[0]) << 4, int32(pos[2]) << 4} + if s.lastChunkPos != chunkPos { + s.lastChunkPos = chunkPos + s.writePacket(&packet.NetworkChunkPublisherUpdate{ + Position: protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}, + Radius: uint32(s.chunkRadius) << 4, + }) + } s.blobMu.Lock() const maxChunkTransactions = 8 @@ -475,6 +482,11 @@ func (s *Session) ChangingDimension() bool { return s.changingDimension.Load() } +// ChunkRadius returns the chunk radius of the session. +func (s *Session) ChunkRadius() int32 { + return s.chunkRadius +} + // handlePacket handles an incoming packet, processing it accordingly. If the packet had invalid data or was // otherwise not valid in its context, an error is returned. func (s *Session) handlePacket(pk packet.Packet, tx *world.Tx, c Controllable) (err error) { diff --git a/server/session/world.go b/server/session/world.go index 2d44129bf..b05ad0bc5 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -1,8 +1,8 @@ package session import ( + "bytes" "fmt" - "github.com/df-mc/dragonfly/server/entity/effect" "image/color" "math/rand/v2" "strings" @@ -11,6 +11,7 @@ import ( "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/entity" + "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/internal/nbtconv" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/item/inventory" @@ -20,6 +21,7 @@ import ( "github.com/go-gl/mathgl/mgl32" "github.com/go-gl/mathgl/mgl64" "github.com/google/uuid" + "github.com/sandertv/gophertunnel/minecraft/nbt" "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" ) @@ -231,6 +233,11 @@ func (s *Session) ViewTime(time int) { s.writePacket(&packet.SetTime{Time: int32(time)}) } +// ViewTimeCycle ... +func (s *Session) ViewTimeCycle(doDayLightCycle bool) { + s.sendGameRules([]protocol.GameRule{{Name: "dodaylightcycle", Value: doDayLightCycle}}) +} + // ViewEntityTeleport ... func (s *Session) ViewEntityTeleport(e world.Entity, position mgl64.Vec3) { id := s.entityRuntimeID(e) @@ -325,6 +332,23 @@ func (s *Session) ViewItemCooldown(item world.Item, duration time.Duration) { }) } +// ViewSleepingPlayers ... +func (s *Session) ViewSleepingPlayers(sleeping, max int) { + buf := bytes.NewBuffer(nil) + _ = nbt.NewEncoderWithEncoding(buf, nbt.NetworkLittleEndian).Encode(map[string]any{ + "ableToSleep": int32(max), + "overworldPlayerCount": int32(max), + "sleepingPlayerCount": int32(sleeping), + }) + + eventData := buf.Bytes() + + s.writePacket(&packet.LevelEventGeneric{ + EventID: packet.LevelEventSleepingPlayers, + SerialisedEventData: eventData[2 : len(eventData)-1], + }) +} + // ViewParticle ... func (s *Session) ViewParticle(pos mgl64.Vec3, p world.Particle) { switch pa := p.(type) { @@ -467,6 +491,8 @@ func tierToSoundEvent(tier item.ArmourTier) uint32 { switch tier.(type) { case item.ArmourTierLeather: return packet.SoundEventEquipLeather + case item.ArmourTierCopper: + return packet.SoundEventEquipCopper case item.ArmourTierGold: return packet.SoundEventEquipGold case item.ArmourTierChain: @@ -971,14 +997,22 @@ func (s *Session) ViewEntityAction(e world.Entity, a world.EntityAction) { EventType: packet.ActorEventHurt, }) case entity.CriticalHitAction: + if act.Count <= 0 { + act.Count = 55 + } s.writePacket(&packet.Animate{ ActionType: packet.AnimateActionCriticalHit, EntityRuntimeID: s.entityRuntimeID(e), + Data: float32(act.Count), }) case entity.EnchantedHitAction: + if act.Count <= 0 { + act.Count = 15 + } s.writePacket(&packet.Animate{ ActionType: packet.AnimateActionMagicCriticalHit, EntityRuntimeID: s.entityRuntimeID(e), + Data: float32(act.Count), }) case entity.DeathAction: s.writePacket(&packet.ActorEvent{ @@ -1258,6 +1292,14 @@ func (s *Session) ViewWeather(raining, thunder bool) { s.writePacket(pk) } +// ViewEntityWake ... +func (s *Session) ViewEntityWake(e world.Entity) { + s.writePacket(&packet.Animate{ + EntityRuntimeID: s.entityRuntimeID(e), + ActionType: packet.AnimateActionStopSleep, + }) +} + // nextWindowID produces the next window ID for a new window. It is an int of 1-99. func (s *Session) nextWindowID() byte { if s.openedWindowID.CompareAndSwap(99, 1) { diff --git a/server/world/biome/badlands.go b/server/world/biome/badlands.go index cc259a908..edbff7d9e 100644 --- a/server/world/biome/badlands.go +++ b/server/world/biome/badlands.go @@ -32,7 +32,7 @@ func (Badlands) WaterColour() color.RGBA { // Tags ... func (Badlands) Tags() []string { - return []string{"animal", "mesa", "monster", "overworld", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals"} + return []string{"animal", "mesa", "monster", "overworld", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals", "surface_mineshaft"} } // String ... diff --git a/server/world/biome/badlands_plateau.go b/server/world/biome/badlands_plateau.go index 9f6b43574..f412c036f 100644 --- a/server/world/biome/badlands_plateau.go +++ b/server/world/biome/badlands_plateau.go @@ -32,7 +32,7 @@ func (BadlandsPlateau) WaterColour() color.RGBA { // Tags ... func (BadlandsPlateau) Tags() []string { - return []string{"animal", "mesa", "monster", "overworld", "plateau", "rare", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals"} + return []string{"animal", "mesa", "monster", "overworld", "plateau", "rare", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals", "surface_mineshaft"} } // String ... diff --git a/server/world/biome/basalt_deltas.go b/server/world/biome/basalt_deltas.go index f180160ea..b28016bc3 100644 --- a/server/world/biome/basalt_deltas.go +++ b/server/world/biome/basalt_deltas.go @@ -15,11 +15,6 @@ func (BasaltDeltas) Rainfall() float64 { return 0 } -// Ash ... -func (BasaltDeltas) Ash() (ash float64, whiteAsh float64) { - return 0, 2 -} - // Depth ... func (BasaltDeltas) Depth() float64 { return 0.1 diff --git a/server/world/biome/cold_ocean.go b/server/world/biome/cold_ocean.go index 9a8869a22..e52b68250 100644 --- a/server/world/biome/cold_ocean.go +++ b/server/world/biome/cold_ocean.go @@ -32,7 +32,7 @@ func (ColdOcean) WaterColour() color.RGBA { // Tags ... func (ColdOcean) Tags() []string { - return []string{"cold", "monster", "ocean", "overworld", "spawns_cold_variant_farm_animals"} + return []string{"cold", "monster", "ocean", "overworld", "spawns_cold_variant_farm_animals", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/crimson_forest.go b/server/world/biome/crimson_forest.go index 212b1566e..5f7da8ac7 100644 --- a/server/world/biome/crimson_forest.go +++ b/server/world/biome/crimson_forest.go @@ -15,11 +15,6 @@ func (CrimsonForest) Rainfall() float64 { return 0 } -// Spores ... -func (CrimsonForest) Spores() (blueSpores float64, redSpores float64) { - return 0, 0.25 -} - // Depth ... func (CrimsonForest) Depth() float64 { return 0.1 diff --git a/server/world/biome/deep_cold_ocean.go b/server/world/biome/deep_cold_ocean.go index 98624191d..8a4893d06 100644 --- a/server/world/biome/deep_cold_ocean.go +++ b/server/world/biome/deep_cold_ocean.go @@ -32,7 +32,7 @@ func (DeepColdOcean) WaterColour() color.RGBA { // Tags ... func (DeepColdOcean) Tags() []string { - return []string{"cold", "deep", "monster", "ocean", "overworld", "spawns_cold_variant_farm_animals"} + return []string{"cold", "deep", "monster", "ocean", "overworld", "spawns_cold_variant_farm_animals", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/deep_frozen_ocean.go b/server/world/biome/deep_frozen_ocean.go index 3a056d1ec..424154a06 100644 --- a/server/world/biome/deep_frozen_ocean.go +++ b/server/world/biome/deep_frozen_ocean.go @@ -7,7 +7,7 @@ type DeepFrozenOcean struct{} // Temperature ... func (DeepFrozenOcean) Temperature() float64 { - return 0 + return 0.5 } // Rainfall ... @@ -32,7 +32,7 @@ func (DeepFrozenOcean) WaterColour() color.RGBA { // Tags ... func (DeepFrozenOcean) Tags() []string { - return []string{"deep", "frozen", "monster", "ocean", "overworld", "spawns_cold_variant_farm_animals", "spawns_cold_variant_frogs", "spawns_polar_bears_on_alternate_blocks"} + return []string{"deep", "frozen", "monster", "ocean", "overworld", "spawns_cold_variant_farm_animals", "spawns_cold_variant_frogs", "spawns_polar_bears_on_alternate_blocks", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/deep_lukewarm_ocean.go b/server/world/biome/deep_lukewarm_ocean.go index dfe4b6ef8..3f18e8da8 100644 --- a/server/world/biome/deep_lukewarm_ocean.go +++ b/server/world/biome/deep_lukewarm_ocean.go @@ -32,7 +32,7 @@ func (DeepLukewarmOcean) WaterColour() color.RGBA { // Tags ... func (DeepLukewarmOcean) Tags() []string { - return []string{"deep", "lukewarm", "monster", "ocean", "overworld", "spawns_warm_variant_farm_animals"} + return []string{"deep", "lukewarm", "monster", "ocean", "overworld", "spawns_warm_variant_farm_animals", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/deep_ocean.go b/server/world/biome/deep_ocean.go index 3120c6e74..a5ee2ee14 100644 --- a/server/world/biome/deep_ocean.go +++ b/server/world/biome/deep_ocean.go @@ -32,7 +32,7 @@ func (DeepOcean) WaterColour() color.RGBA { // Tags ... func (DeepOcean) Tags() []string { - return []string{"deep", "monster", "ocean", "overworld", "spawns_warm_variant_farm_animals"} + return []string{"deep", "monster", "ocean", "overworld", "spawns_warm_variant_farm_animals", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/deep_warm_ocean.go b/server/world/biome/deep_warm_ocean.go index c9a043700..e0efa55e7 100644 --- a/server/world/biome/deep_warm_ocean.go +++ b/server/world/biome/deep_warm_ocean.go @@ -32,7 +32,7 @@ func (DeepWarmOcean) WaterColour() color.RGBA { // Tags ... func (DeepWarmOcean) Tags() []string { - return []string{"deep", "monster", "ocean", "overworld", "warm", "spawns_warm_variant_farm_animals"} + return []string{"deep", "monster", "ocean", "overworld", "warm", "spawns_warm_variant_farm_animals", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/eroded_badlands.go b/server/world/biome/eroded_badlands.go index cbcc653f5..063e45604 100644 --- a/server/world/biome/eroded_badlands.go +++ b/server/world/biome/eroded_badlands.go @@ -32,7 +32,7 @@ func (ErodedBadlands) WaterColour() color.RGBA { // Tags ... func (ErodedBadlands) Tags() []string { - return []string{"animal", "mesa", "monster", "mutated", "overworld", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals"} + return []string{"animal", "mesa", "monster", "mutated", "overworld", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals", "surface_mineshaft"} } // String ... diff --git a/server/world/biome/frozen_ocean.go b/server/world/biome/frozen_ocean.go index 759874551..e6ae7a256 100644 --- a/server/world/biome/frozen_ocean.go +++ b/server/world/biome/frozen_ocean.go @@ -32,7 +32,7 @@ func (FrozenOcean) WaterColour() color.RGBA { // Tags ... func (FrozenOcean) Tags() []string { - return []string{"frozen", "monster", "ocean", "overworld", "spawns_polar_bears_on_alternate_blocks", "spawns_cold_variant_farm_animals", "spawns_cold_variant_frogs", "spawns_snow_foxes", "spawns_white_rabbits"} + return []string{"frozen", "monster", "ocean", "overworld", "spawns_polar_bears_on_alternate_blocks", "spawns_cold_variant_farm_animals", "spawns_cold_variant_frogs", "spawns_snow_foxes", "spawns_white_rabbits", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/legacy_frozen_ocean.go b/server/world/biome/legacy_frozen_ocean.go index 0db5980ea..65fd5a269 100644 --- a/server/world/biome/legacy_frozen_ocean.go +++ b/server/world/biome/legacy_frozen_ocean.go @@ -32,7 +32,7 @@ func (LegacyFrozenOcean) WaterColour() color.RGBA { // Tags ... func (LegacyFrozenOcean) Tags() []string { - return []string{"legacy", "frozen", "ocean", "overworld", "spawns_cold_variant_farm_animals", "spawns_polar_bears_on_alternate_blocks"} + return []string{"legacy", "frozen", "ocean", "overworld", "spawns_cold_variant_farm_animals", "spawns_polar_bears_on_alternate_blocks", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/lukewarm_ocean.go b/server/world/biome/lukewarm_ocean.go index c26a8c0d6..382a0c140 100644 --- a/server/world/biome/lukewarm_ocean.go +++ b/server/world/biome/lukewarm_ocean.go @@ -32,7 +32,7 @@ func (LukewarmOcean) WaterColour() color.RGBA { // Tags ... func (LukewarmOcean) Tags() []string { - return []string{"lukewarm", "monster", "ocean", "overworld", "spawns_warm_variant_farm_animals"} + return []string{"lukewarm", "monster", "ocean", "overworld", "spawns_warm_variant_farm_animals", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/mangrove_swamp.go b/server/world/biome/mangrove_swamp.go index 935abb426..f8525951f 100644 --- a/server/world/biome/mangrove_swamp.go +++ b/server/world/biome/mangrove_swamp.go @@ -32,7 +32,7 @@ func (MangroveSwamp) WaterColour() color.RGBA { // Tags ... func (MangroveSwamp) Tags() []string { - return []string{"mangrove_swamp", "overworld", "monster", "spawns_slimes_on_surface", "spawns_warm_variant_farm_animals", "spawns_warm_variant_frogs"} + return []string{"mangrove_swamp", "overworld", "monster", "spawns_slimes_on_surface", "spawns_warm_variant_farm_animals", "spawns_warm_variant_frogs", "slime"} } // String ... diff --git a/server/world/biome/modified_badlands_plateau.go b/server/world/biome/modified_badlands_plateau.go index 1eafdeb65..a426af553 100644 --- a/server/world/biome/modified_badlands_plateau.go +++ b/server/world/biome/modified_badlands_plateau.go @@ -32,7 +32,7 @@ func (ModifiedBadlandsPlateau) WaterColour() color.RGBA { // Tags ... func (ModifiedBadlandsPlateau) Tags() []string { - return []string{"animal", "mesa", "monster", "mutated", "overworld", "plateau", "stone", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals"} + return []string{"animal", "mesa", "monster", "mutated", "overworld", "plateau", "stone", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals", "surface_mineshaft"} } // String ... diff --git a/server/world/biome/modified_wooded_badlands_plateau.go b/server/world/biome/modified_wooded_badlands_plateau.go index 5a214b539..227d70b42 100644 --- a/server/world/biome/modified_wooded_badlands_plateau.go +++ b/server/world/biome/modified_wooded_badlands_plateau.go @@ -32,7 +32,7 @@ func (ModifiedWoodedBadlandsPlateau) WaterColour() color.RGBA { // Tags ... func (ModifiedWoodedBadlandsPlateau) Tags() []string { - return []string{"animal", "mesa", "monster", "mutated", "overworld", "plateau", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals"} + return []string{"animal", "mesa", "monster", "mutated", "overworld", "plateau", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals", "surface_mineshaft"} } // String ... diff --git a/server/world/biome/ocean.go b/server/world/biome/ocean.go index 5a4c5bbe8..f8aa9b2ec 100644 --- a/server/world/biome/ocean.go +++ b/server/world/biome/ocean.go @@ -32,7 +32,7 @@ func (Ocean) WaterColour() color.RGBA { // Tags ... func (Ocean) Tags() []string { - return []string{"monster", "ocean", "overworld"} + return []string{"monster", "ocean", "overworld", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/old_growth_birch_forest.go b/server/world/biome/old_growth_birch_forest.go index 2eda89c6b..40314e888 100644 --- a/server/world/biome/old_growth_birch_forest.go +++ b/server/world/biome/old_growth_birch_forest.go @@ -7,12 +7,12 @@ type OldGrowthBirchForest struct{} // Temperature ... func (OldGrowthBirchForest) Temperature() float64 { - return 0.7 + return 0.6 } // Rainfall ... func (OldGrowthBirchForest) Rainfall() float64 { - return 0.8 + return 0.6 } // Depth ... diff --git a/server/world/biome/soul_sand_valley.go b/server/world/biome/soul_sand_valley.go index 54e20c84d..102937f15 100644 --- a/server/world/biome/soul_sand_valley.go +++ b/server/world/biome/soul_sand_valley.go @@ -15,11 +15,6 @@ func (SoulSandValley) Rainfall() float64 { return 0 } -// Ash ... -func (SoulSandValley) Ash() (ash float64, whiteAsh float64) { - return 0.05, 0 -} - // Depth ... func (SoulSandValley) Depth() float64 { return 0.1 diff --git a/server/world/biome/swamp.go b/server/world/biome/swamp.go index 0708c5606..1e2f793aa 100644 --- a/server/world/biome/swamp.go +++ b/server/world/biome/swamp.go @@ -12,7 +12,7 @@ func (Swamp) Temperature() float64 { // Rainfall ... func (Swamp) Rainfall() float64 { - return 0.5 + return 0.9 } // Depth ... @@ -27,12 +27,12 @@ func (Swamp) Scale() float64 { // WaterColour ... func (Swamp) WaterColour() color.RGBA { - return color.RGBA{R: 0x4c, G: 0x65, B: 0x59, A: 0xa5} + return color.RGBA{R: 0x61, G: 0x7b, B: 0x64, A: 0xa5} } // Tags ... func (Swamp) Tags() []string { - return []string{"animal", "monster", "overworld", "swamp", "spawns_slimes_on_surface"} + return []string{"animal", "monster", "overworld", "swamp", "spawns_slimes_on_surface", "slime", "swamp_water_huge_mushroom"} } // String ... diff --git a/server/world/biome/swamp_hills.go b/server/world/biome/swamp_hills.go index 198caa1fc..13adefc64 100644 --- a/server/world/biome/swamp_hills.go +++ b/server/world/biome/swamp_hills.go @@ -27,12 +27,12 @@ func (SwampHills) Scale() float64 { // WaterColour ... func (SwampHills) WaterColour() color.RGBA { - return color.RGBA{R: 0x4c, G: 0x65, B: 0x59, A: 0xa5} + return color.RGBA{R: 0x61, G: 0x7b, B: 0x64, A: 0xa5} } // Tags ... func (SwampHills) Tags() []string { - return []string{"animal", "monster", "mutated", "swamp", "overworld_generation", "spawns_slimes_on_surface"} + return []string{"animal", "monster", "mutated", "swamp", "overworld_generation", "spawns_slimes_on_surface", "slime", "swamp_water_huge_mushroom"} } // String ... diff --git a/server/world/biome/warm_ocean.go b/server/world/biome/warm_ocean.go index 93e8a4480..17256962a 100644 --- a/server/world/biome/warm_ocean.go +++ b/server/world/biome/warm_ocean.go @@ -32,7 +32,7 @@ func (WarmOcean) WaterColour() color.RGBA { // Tags ... func (WarmOcean) Tags() []string { - return []string{"monster", "ocean", "overworld", "warm", "spawns_warm_variant_farm_animals", "spawns_warm_variant_frogs"} + return []string{"monster", "ocean", "overworld", "warm", "spawns_warm_variant_farm_animals", "spawns_warm_variant_frogs", "fast_fishing", "high_seas"} } // String ... diff --git a/server/world/biome/warped_forest.go b/server/world/biome/warped_forest.go index 893523f50..eacfe23ea 100644 --- a/server/world/biome/warped_forest.go +++ b/server/world/biome/warped_forest.go @@ -15,11 +15,6 @@ func (WarpedForest) Rainfall() float64 { return 0 } -// Spores ... -func (WarpedForest) Spores() (blueSpores float64, redSpores float64) { - return 0.25, 0 -} - // Depth ... func (WarpedForest) Depth() float64 { return 0.1 diff --git a/server/world/biome/windswept_savanna.go b/server/world/biome/windswept_savanna.go index 38cd73837..9252d8221 100644 --- a/server/world/biome/windswept_savanna.go +++ b/server/world/biome/windswept_savanna.go @@ -7,12 +7,12 @@ type WindsweptSavanna struct{} // Temperature ... func (WindsweptSavanna) Temperature() float64 { - return 1.1 + return 2 } // Rainfall ... func (WindsweptSavanna) Rainfall() float64 { - return 0.5 + return 0 } // Depth ... diff --git a/server/world/biome/wooded_badlands_plateau.go b/server/world/biome/wooded_badlands_plateau.go index 6ecc20157..ad758c509 100644 --- a/server/world/biome/wooded_badlands_plateau.go +++ b/server/world/biome/wooded_badlands_plateau.go @@ -32,7 +32,7 @@ func (WoodedBadlandsPlateau) WaterColour() color.RGBA { // Tags ... func (WoodedBadlandsPlateau) Tags() []string { - return []string{"animal", "mesa", "monster", "overworld", "plateau", "rare", "stone", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals"} + return []string{"animal", "mesa", "monster", "overworld", "plateau", "rare", "stone", "spawns_mesa_mobs", "spawns_warm_variant_farm_animals", "surface_mineshaft"} } // String ... diff --git a/server/world/biome_definition.go b/server/world/biome_definition.go index 8229d13ed..59525210b 100644 --- a/server/world/biome_definition.go +++ b/server/world/biome_definition.go @@ -25,18 +25,6 @@ func finaliseBiomeRegistry() { } } -// ashyBiome represents a biome that has any form of ash. -type ashyBiome interface { - // Ash returns the ash and white ash of the biome. - Ash() (ash float64, whiteAsh float64) -} - -// sporingBiome represents a biome that has blue or red spores. -type sporingBiome interface { - // Spores returns the blue and red spores of the biome. - Spores() (blueSpores float64, redSpores float64) -} - // BiomeDefinitions returns the list of biome definitions along with the associated StringList. func BiomeDefinitions() ([]protocol.BiomeDefinition, []string) { var ( @@ -87,18 +75,6 @@ func BiomeDefinitions() ([]protocol.BiomeDefinition, []string) { Tags: protocol.Option[[]uint16](tagIndices), } - if a, ok := b.(ashyBiome); ok { - ash, whiteAsh := a.Ash() - def.AshDensity = float32(ash) - def.WhiteAshDensity = float32(whiteAsh) - } - - if s, ok := b.(sporingBiome); ok { - blueSpores, redSpores := s.Spores() - def.BlueSporeDensity = float32(blueSpores) - def.RedSporeDensity = float32(redSpores) - } - encodedBiomes = append(encodedBiomes, def) } diff --git a/server/world/block_states.nbt b/server/world/block_states.nbt index 46350120065d81d42ac20714e510defa4899411a..53ba0264b1e99958596d4491a93bda8a9064c172 100644 GIT binary patch delta 24600 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 GY1O}D)r3m` diff --git a/server/world/conf.go b/server/world/conf.go index 015be70dc..02afa8781 100644 --- a/server/world/conf.go +++ b/server/world/conf.go @@ -38,6 +38,11 @@ type Config struct { // By default, SaveInterval is set to 10 minutes. Setting SaveInterval to // a negative number disables automatic saving entirely. SaveInterval time.Duration + // ChunkUnloadInterval specifies how often unused chunks should be unloaded + // from memory when no longer in use. By default, this is set to 2 minutes. + // ChunkUnloadInterval should not be used to prevent chunks from unloading + // altogether. This should be done using a Loader with a custom Viewer. + ChunkUnloadInterval time.Duration // RandomTickSpeed specifies the rate at which blocks should be ticked in // the World. By default, each sub chunk has 3 blocks randomly ticked per // sub chunk, so the default value is 3. Setting this value to -1 or lower @@ -72,6 +77,9 @@ func (conf Config) New() *World { if conf.SaveInterval == 0 { conf.SaveInterval = time.Minute * 10 } + if conf.ChunkUnloadInterval <= 0 { + conf.ChunkUnloadInterval = time.Minute * 2 + } if conf.Generator == nil { conf.Generator = NopGenerator{} } diff --git a/server/world/entity.go b/server/world/entity.go index 69a0b83cb..dcc415ce9 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -2,15 +2,16 @@ package world import ( "encoding/binary" - "github.com/df-mc/dragonfly/server/block/cube" - "github.com/go-gl/mathgl/mgl64" - "github.com/google/uuid" "io" "maps" "slices" "sync" "sync/atomic" "time" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/go-gl/mathgl/mgl64" + "github.com/google/uuid" ) // EntityType is the type of Entity. It specifies the name, encoded Entity diff --git a/server/world/mcdb/db.go b/server/world/mcdb/db.go index 9c92b6fd5..5123d3597 100644 --- a/server/world/mcdb/db.go +++ b/server/world/mcdb/db.go @@ -136,7 +136,7 @@ func (db *DB) LoadColumn(pos world.ChunkPos, dim world.Dimension) (*chunk.Column return col, nil } -const chunkVersion = 41 +const chunkVersion = 42 func (db *DB) column(k dbKey) (*chunk.Column, error) { var cdata chunk.SerialisedData diff --git a/server/world/mcdb/iterator.go b/server/world/mcdb/iterator.go index d5750974d..43653ce1f 100644 --- a/server/world/mcdb/iterator.go +++ b/server/world/mcdb/iterator.go @@ -3,6 +3,7 @@ package mcdb import ( "encoding/binary" "fmt" + "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/chunk" "github.com/df-mc/goleveldb/leveldb/iterator" @@ -50,11 +51,12 @@ func (iter *ColumnIterator) Next() bool { return false } k := iter.dbIter.Key() - if (len(k) != 9 && len(k) != 13) || (k[8] != keyVersion && k[8] != keyVersionOld) { + kLen := len(k) + if (kLen != 9 && kLen != 13) || (k[kLen-1] != keyVersion && k[kLen-1] != keyVersionOld) { return iter.Next() } iter.dim = world.Dimension(world.Overworld) - if len(k) > 9 { + if kLen > 9 { var ok bool id := int(binary.LittleEndian.Uint32(k[8:12])) if iter.dim, ok = world.DimensionByID(id); !ok { diff --git a/server/world/settings.go b/server/world/settings.go index bd029c773..085a5c280 100644 --- a/server/world/settings.go +++ b/server/world/settings.go @@ -1,9 +1,10 @@ package world import ( - "github.com/df-mc/dragonfly/server/block/cube" "sync" "sync/atomic" + + "github.com/df-mc/dragonfly/server/block/cube" ) // Settings holds the settings of a World. These are typically saved to a level.dat file. It is safe to pass the same @@ -30,6 +31,8 @@ type Settings struct { Thundering bool // WeatherCycle specifies if weather should be enabled in this world. If set to false, weather will be disabled. WeatherCycle bool + // RequiredSleepTicks is the number of ticks that players must sleep for in order for the time to change to day. + RequiredSleepTicks int64 // CurrentTick is the current tick of the world. This is similar to the Time, except that it has no visible effect // to the client. It can also not be changed through commands and will only ever go up. CurrentTick int64 diff --git a/server/world/sleep.go b/server/world/sleep.go new file mode 100644 index 000000000..32773450f --- /dev/null +++ b/server/world/sleep.go @@ -0,0 +1,64 @@ +package world + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/player/chat" + "github.com/google/uuid" +) + +// Sleeper represents an entity that can sleep. +type Sleeper interface { + Entity + + Name() string + UUID() uuid.UUID + + Messaget(t chat.Translation, a ...any) + SendSleepingIndicator(sleeping, max int) + + Sleep(pos cube.Pos) + Sleeping() (cube.Pos, bool) + Wake() +} + +// Time constants for sleep usage. +const ( + TimeSleep = 12542 + TimeWake = 23459 + TimeSleepWithRain = 12010 + TimeWakeWithRain = 23991 + TimeFull = 24000 +) + +// tryAdvanceDay attempts to advance the day of the world, by first ensuring that all sleepers are sleeping, and then +// updating the time of day. +func (ticker) tryAdvanceDay(tx *Tx, timeCycle bool) { + sleepers := tx.Sleepers() + time := tx.w.Time() % TimeFull + + for s := range sleepers { + if !tx.Thundering() { + if !tx.Raining() && (time <= TimeSleep || time >= TimeWake) { + return + } + if time <= TimeSleepWithRain || time >= TimeWakeWithRain { + return + } + } + + if _, ok := s.Sleeping(); !ok { + // We can't advance the time - not everyone is sleeping. + return + } + } + + for s := range sleepers { + s.Wake() + } + + totalTime := tx.w.Time() + if timeCycle { + tx.w.SetTime(totalTime + TimeFull - time) + } + tx.w.StopRaining() +} diff --git a/server/world/tick.go b/server/world/tick.go index 9c025d807..a4c29a261 100644 --- a/server/world/tick.go +++ b/server/world/tick.go @@ -1,12 +1,13 @@ package world import ( - "github.com/df-mc/dragonfly/server/block/cube" - "github.com/df-mc/dragonfly/server/internal/sliceutil" "maps" "math/rand/v2" "slices" "time" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/internal/sliceutil" ) // ticker implements World ticking methods. @@ -39,7 +40,7 @@ func (t ticker) tick(tx *Tx) { w := tx.World() w.set.Lock() - if s := w.set.Spawn; s[1] > tx.Range()[1] { + if s := w.set.Spawn; s[1] > tx.Range()[1] && w.Dimension() == Overworld { // Vanilla will set the spawn position's Y value to max to indicate that // the player should spawn at the highest position in the world. w.set.Spawn[1] = w.highestObstructingBlock(s[0], s[2]) + 1 @@ -59,12 +60,23 @@ func (t ticker) tick(tx *Tx) { } } - rain, thunder, tick, tim := w.set.Raining, w.set.Thundering && w.set.Raining, w.set.CurrentTick, int(w.set.Time) + rain, thunder, tick, tim, cycle := w.set.Raining, w.set.Thundering && w.set.Raining, w.set.CurrentTick, int(w.set.Time), w.set.TimeCycle + + tryAdvanceDay := false + if tx.w.set.RequiredSleepTicks > 0 { + tx.w.set.RequiredSleepTicks-- + tryAdvanceDay = tx.w.set.RequiredSleepTicks <= 0 + } + w.set.Unlock() + if tryAdvanceDay { + t.tryAdvanceDay(tx, cycle) + } + if tick%20 == 0 { for _, viewer := range viewers { - if w.Dimension().TimeCycle() { + if w.Dimension().TimeCycle() && cycle { viewer.ViewTime(tim) } if w.Dimension().WeatherCycle() { diff --git a/server/world/tx.go b/server/world/tx.go index 1422bfa1f..4183e02fe 100644 --- a/server/world/tx.go +++ b/server/world/tx.go @@ -1,12 +1,14 @@ package world import ( - "github.com/df-mc/dragonfly/server/block/cube" - "github.com/go-gl/mathgl/mgl64" "iter" "sync" "sync/atomic" "time" + + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/player/chat" + "github.com/go-gl/mathgl/mgl64" ) // Tx represents a synchronised transaction performed on a World. Most @@ -157,6 +159,16 @@ func (tx *Tx) ThunderingAt(pos cube.Pos) bool { return tx.World().thunderingAt(pos) } +// Raining checks if it is raining anywhere in the World. +func (tx *Tx) Raining() bool { + return tx.World().raining() +} + +// Thundering checks if it is thundering anywhere in the World. +func (tx *Tx) Thundering() bool { + return tx.World().thundering() +} + // AddParticle spawns a Particle at a given position in the World. Viewers that // are viewing the chunk will be shown the particle. func (tx *Tx) AddParticle(pos mgl64.Vec3, p Particle) { @@ -215,6 +227,56 @@ func (tx *Tx) Viewers(pos mgl64.Vec3) []Viewer { return tx.World().viewersOf(pos) } +// Sleepers returns an iterator that yields all sleeping entities currently added to the World. +func (tx *Tx) Sleepers() iter.Seq[Sleeper] { + ent := tx.Entities() + return func(yield func(Sleeper) bool) { + for e := range ent { + if sleeper, ok := e.(Sleeper); ok { + if !yield(sleeper) { + return + } + } + } + } +} + +// BroadcastSleepingIndicator broadcasts a sleeping indicator to all sleepers in the world. +func (tx *Tx) BroadcastSleepingIndicator() { + sleepers := tx.Sleepers() + + var sleeping, allSleepers int + for s := range sleepers { + allSleepers++ + if _, ok := s.Sleeping(); ok { + sleeping++ + } + } + + for s := range sleepers { + s.SendSleepingIndicator(sleeping, allSleepers) + } +} + +// BroadcastSleepingReminder broadcasts a sleeping reminder message to all sleepers in the world, excluding the sleeper +// passed. +func (tx *Tx) BroadcastSleepingReminder(sleeper Sleeper) { + sleepers := tx.Sleepers() + + var notSleeping int + for s := range sleepers { + if _, ok := s.Sleeping(); !ok { + notSleeping++ + } + } + + for s := range sleepers { + if _, ok := s.Sleeping(); !ok { + s.Messaget(chat.MessageSleeping, sleeper.Name(), notSleeping) + } + } +} + // World returns the World of the Tx. It panics if the transaction was already // marked complete. func (tx *Tx) World() *World { diff --git a/server/world/vanilla_items.nbt b/server/world/vanilla_items.nbt index 69c6e987ceea0e829de36716f0ce9210101ddcd4..27d9be720773b71031b1b1949765c6348dc763cc 100644 GIT binary patch literal 189722 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 delta 24403 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 F{{YvVA=Ur@ diff --git a/server/world/viewer.go b/server/world/viewer.go index 7ffbbbebf..316f53450 100644 --- a/server/world/viewer.go +++ b/server/world/viewer.go @@ -1,11 +1,12 @@ package world import ( + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world/chunk" "github.com/go-gl/mathgl/mgl64" "github.com/google/uuid" - "time" ) // Viewer is a viewer in the world. It can view changes that are made in the world, such as the addition of @@ -39,6 +40,8 @@ type Viewer interface { // ViewTime views the time of the world. It is called every time the time is changed or otherwise every // second. ViewTime(t int) + // ViewTimeCycle controls the automatic time-of-day cycle (day and night) in the world for this viewer. + ViewTimeCycle(doDayLightCycle bool) // ViewEntityItems views the items currently held by an Entity that is able to equip items. ViewEntityItems(e Entity) // ViewEntityArmour views the items currently equipped as armour by the Entity. @@ -70,6 +73,8 @@ type Viewer interface { ViewWorldSpawn(pos cube.Pos) // ViewWeather views the weather of the world, including rain and thunder. ViewWeather(raining, thunder bool) + // ViewEntityWake views an entity waking up from a bed. + ViewEntityWake(e Entity) } // NopViewer is a Viewer implementation that does not implement any behaviour. It may be embedded by other structs to @@ -87,6 +92,7 @@ func (NopViewer) ViewEntityVelocity(Entity, mgl64.Vec3) func (NopViewer) ViewEntityTeleport(Entity, mgl64.Vec3) {} func (NopViewer) ViewChunk(ChunkPos, Dimension, map[cube.Pos]Block, *chunk.Chunk) {} func (NopViewer) ViewTime(int) {} +func (NopViewer) ViewTimeCycle(bool) {} func (NopViewer) ViewEntityItems(Entity) {} func (NopViewer) ViewEntityArmour(Entity) {} func (NopViewer) ViewEntityAction(Entity, EntityAction) {} @@ -101,5 +107,6 @@ func (NopViewer) ViewSkin(Entity) func (NopViewer) ViewWorldSpawn(cube.Pos) {} func (NopViewer) ViewWeather(bool, bool) {} func (NopViewer) ViewBrewingUpdate(time.Duration, time.Duration, int32, int32, int32, int32) {} +func (NopViewer) ViewEntityWake(Entity) {} func (NopViewer) ViewFurnaceUpdate(time.Duration, time.Duration, time.Duration, time.Duration, time.Duration, time.Duration) { } diff --git a/server/world/weather.go b/server/world/weather.go index efcc7b18a..45e821673 100644 --- a/server/world/weather.go +++ b/server/world/weather.go @@ -1,9 +1,10 @@ package world import ( + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/go-gl/mathgl/mgl64" - "time" ) // weather implements weather related methods for World. World embeds this @@ -64,6 +65,26 @@ func (w weather) thunderingAt(pos cube.Pos) bool { return a && w.w.highestObstructingBlock(pos[0], pos[2]) < pos[1] } +// raining checks if it is raining anywhere in the World. +func (w weather) raining() bool { + if w.w == nil || !w.w.Dimension().WeatherCycle() { + return false + } + w.w.set.Lock() + defer w.w.set.Unlock() + return w.w.set.Raining +} + +// thundering checks if it is thundering anywhere in the World. +func (w weather) thundering() bool { + if w.w == nil || !w.w.Dimension().WeatherCycle() { + return false + } + w.w.set.Lock() + defer w.w.set.Unlock() + return w.w.set.Thundering +} + // StartRaining makes it rain in the World. The time.Duration passed will // determine how long it will rain. func (w weather) StartRaining(dur time.Duration) { diff --git a/server/world/world.go b/server/world/world.go index 6a3d72bd5..7e25110ee 100644 --- a/server/world/world.go +++ b/server/world/world.go @@ -627,6 +627,16 @@ func (w *World) StartTime() { w.enableTimeCycle(true) } +// TimeCycle returns whether time cycle is enabled. +func (w *World) TimeCycle() bool { + if w == nil { + return false + } + w.set.Lock() + defer w.set.Unlock() + return w.set.TimeCycle +} + // enableTimeCycle enables or disables the time cycling of the World. func (w *World) enableTimeCycle(v bool) { if w == nil { @@ -635,6 +645,10 @@ func (w *World) enableTimeCycle(v bool) { w.set.Lock() defer w.set.Unlock() w.set.TimeCycle = v + viewers, _ := w.allViewers() + for _, viewer := range viewers { + viewer.ViewTimeCycle(v) + } } // temperature returns the temperature in the World at a specific position. @@ -772,6 +786,13 @@ func (w *World) Spawn() cube.Pos { if w == nil { return cube.Pos{} } + + if w.Dimension() == End { + return cube.Pos{100, 50} + } else if w.Dimension() == Nether { + return cube.Pos{} + } + w.set.Lock() defer w.set.Unlock() return w.set.Spawn @@ -783,6 +804,12 @@ func (w *World) SetSpawn(pos cube.Pos) { if w == nil { return } + + // nether has no spawn point and end spawn point is always 100 50 0. + if w.Dimension() == Nether || w.Dimension() == End { + return + } + w.set.Lock() w.set.Spawn = pos w.set.Unlock() @@ -821,6 +848,17 @@ func (w *World) SetPlayerSpawn(id uuid.UUID, pos cube.Pos) { } } +// SetRequiredSleepDuration sets the duration of time players in the world must sleep for, in order to advance to the +// next day. +func (w *World) SetRequiredSleepDuration(duration time.Duration) { + if w == nil { + return + } + w.set.Lock() + defer w.set.Unlock() + w.set.RequiredSleepTicks = duration.Milliseconds() / 50 +} + // DefaultGameMode returns the default game mode of the world. When players // join, they are given this game mode. The default game mode may be changed // using SetDefaultGameMode(). @@ -1059,6 +1097,7 @@ func (w *World) addWorldViewer(l *Loader) { w.viewerMu.Unlock() l.viewer.ViewTime(w.Time()) + l.viewer.ViewTimeCycle(w.TimeCycle()) w.set.Lock() raining, thundering := w.set.Raining, w.set.Raining && w.set.Thundering w.set.Unlock() @@ -1204,7 +1243,7 @@ func (w *World) autoSave() { save = time.NewTicker(w.conf.SaveInterval) defer save.Stop() } - closeUnused := time.NewTicker(time.Minute * 2) + closeUnused := time.NewTicker(w.conf.ChunkUnloadInterval) defer closeUnused.Stop() for { @@ -1220,7 +1259,7 @@ func (w *World) autoSave() { } } -// closeUnusedChunk is called every 5 minutes by autoSave. +// closeUnusedChunk closes all chunks currently not in use by any viewer. func (w *World) closeUnusedChunks(tx *Tx) { for pos, c := range w.chunks { if len(c.viewers) == 0 { From 54c7ab9262db506c4227549502fe4d70c7cb550c Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 16:15:57 -0400 Subject: [PATCH 05/41] Use block-inside events for portal travel Portal blocks should not expose collision boxes just to trigger interdimensional travel. Route portal contact through the existing EntityInside path and let player travel state handle per-tick entry/reset. Constraint: Portal blocks are non-colliding, so BBox cannot be the travel trigger. Rejected: Scanning nearby portal blocks through Model().BBox | depends on collision geometry and duplicates EntityInsider handling. Confidence: high Scope-risk: moderate Directive: Keep portal contact detection in block EntityInside unless Dragonfly adds a broader non-collision trigger surface. --- server/block/model/portal.go | 9 ++--- server/block/portal.go | 16 +++++++-- server/entity/travel.go | 64 +++++++++++------------------------- server/player/player.go | 7 +++- 4 files changed, 42 insertions(+), 54 deletions(-) diff --git a/server/block/model/portal.go b/server/block/model/portal.go index ab489049d..a0007aba4 100644 --- a/server/block/model/portal.go +++ b/server/block/model/portal.go @@ -3,7 +3,6 @@ package model import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" - "github.com/go-gl/mathgl/mgl64" ) // Portal is a model used by portal blocks. @@ -13,12 +12,8 @@ type Portal struct { } // BBox ... -func (p Portal) BBox(cube.Pos, world.BlockSource) []cube.BBox { - min, max := mgl64.Vec3{0, 0, 0.375}, mgl64.Vec3{1, 1, 0.25} - if p.Axis == cube.Z { - min[0], min[2], max[0], max[2] = 0.375, 0, 0.25, 1 - } - return []cube.BBox{cube.Box(min[0], min[1], min[2], max[0], max[1], max[2])} +func (Portal) BBox(cube.Pos, world.BlockSource) []cube.BBox { + return nil } // FaceSolid ... diff --git a/server/block/portal.go b/server/block/portal.go index 0505dfe70..e9c0c66e9 100644 --- a/server/block/portal.go +++ b/server/block/portal.go @@ -15,6 +15,11 @@ type Portal struct { Axis cube.Axis } +// portalTraveller represents an entity that can handle touching a portal block. +type portalTraveller interface { + TravelThroughPortal(tx *world.Tx, target world.Dimension) +} + // Model ... func (p Portal) Model() world.BlockModel { return model.Portal{Axis: p.Axis} @@ -31,8 +36,8 @@ func (p Portal) HasLiquidDrops() bool { } // EncodeBlock ... -func (p Portal) EncodeBlock() (string, map[string]interface{}) { - return "minecraft:portal", map[string]interface{}{"portal_axis": p.Axis.String()} +func (p Portal) EncodeBlock() (string, map[string]any) { + return "minecraft:portal", map[string]any{"portal_axis": p.Axis.String()} } // NeighbourUpdateTick ... @@ -41,3 +46,10 @@ func (p Portal) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { n.Deactivate() } } + +// EntityInside ... +func (p Portal) EntityInside(_ cube.Pos, tx *world.Tx, e world.Entity) { + if t, ok := e.(portalTraveller); ok { + t.TravelThroughPortal(tx, p.Portal()) + } +} diff --git a/server/entity/travel.go b/server/entity/travel.go index a94f0a490..bcbc1f6bb 100644 --- a/server/entity/travel.go +++ b/server/entity/travel.go @@ -1,7 +1,6 @@ package entity import ( - "math" "sync" "time" @@ -18,6 +17,7 @@ type TravelComputer struct { mu sync.RWMutex start time.Time + inside bool awaitingTravel bool travelling bool timedOut bool @@ -30,51 +30,12 @@ type Traveller interface { Teleport(pos mgl64.Vec3) } -// portalBlock represents a block that can be used as a portal to travel between dimensions. -type portalBlock interface { - world.Block - // Portal returns the dimension that the portal leads to. - Portal() world.Dimension -} - -// TickTravelling checks if the player is colliding with a nether portal block. If so, it teleports the player -// to the other dimension after four seconds or instantly if instantaneous is true. -func (t *TravelComputer) TickTravelling(travel Traveller, tx *world.Tx) { - box := travel.H().Type().BBox(travel).Translate(travel.Position()).Grow(0.25) - - min, max := box.Min(), box.Max() - minX, minY, minZ := int(math.Floor(min[0])), int(math.Floor(min[1])), int(math.Floor(min[2])) - maxX, maxY, maxZ := int(math.Ceil(max[0])), int(math.Ceil(max[1])), int(math.Ceil(max[2])) - found, target := false, world.Dimension(nil) - for y := minY; y <= maxY; y++ { - for x := minX; x <= maxX; x++ { - for z := minZ; z <= maxZ; z++ { - pos := cube.Pos{x, y, z} - p, ok := tx.Block(pos).(portalBlock) - if !ok { - continue - } - for _, blockBox := range p.Model().BBox(pos, tx) { - if blockBox.Translate(pos.Vec3()).IntersectsWith(box) { - found, target = true, p.Portal() - break - } - } - } - } - } - +// EnterPortal handles an entity touching a portal block. It teleports the entity to the other dimension after four +// seconds or instantly if instantaneous is true. +func (t *TravelComputer) EnterPortal(travel Traveller, tx *world.Tx, target world.Dimension) { t.mu.Lock() defer t.mu.Unlock() - if !found { - if t.travelling { - // Don't reset if we're travelling. - return - } - t.timedOut, t.awaitingTravel = false, false - return - } - + t.inside = true switch target { case world.Nether: if t.timedOut { @@ -91,6 +52,21 @@ func (t *TravelComputer) TickTravelling(travel Traveller, tx *world.Tx) { } } +// StopTravelling resets the travel timer if the entity was not inside a portal this tick. +func (t *TravelComputer) StopTravelling() { + t.mu.Lock() + defer t.mu.Unlock() + if t.inside { + t.inside = false + return + } + t.inside = false + if t.travelling { + return + } + t.timedOut, t.awaitingTravel = false, false +} + // Travel moves the player to the given Nether or Overworld world and translates the player's current position based // on the source world. func (t *TravelComputer) Travel(e Traveller, source *world.World, destination *world.World) { diff --git a/server/player/player.go b/server/player/player.go index 8e1ec60de..000615907 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2581,7 +2581,12 @@ func (p *Player) Tick(tx *world.Tx, current int64) { p.data.Vel = mgl64.Vec3{} } - p.tc.TickTravelling(p, tx) + p.tc.StopTravelling() +} + +// TravelThroughPortal handles the player touching a portal block. +func (p *Player) TravelThroughPortal(tx *world.Tx, target world.Dimension) { + p.tc.EnterPortal(p, tx, target) } // ViewLayer returns the ViewLayer attached to the player's session. From 0609eeb992776505f4a6db28bfbdbcad007550ca Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 16:20:42 -0400 Subject: [PATCH 06/41] NeighbourUpdateTick now skips portal rescans for perpendicular horizontal neighbour updates, while still checking vertical updates and same-axis updates so frame breaks are preserved --- server/block/portal.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/block/portal.go b/server/block/portal.go index e9c0c66e9..7a6794db6 100644 --- a/server/block/portal.go +++ b/server/block/portal.go @@ -41,7 +41,11 @@ func (p Portal) EncodeBlock() (string, map[string]any) { } // NeighbourUpdateTick ... -func (p Portal) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { +func (p Portal) NeighbourUpdateTick(pos, neighbour cube.Pos, tx *world.Tx) { + axis := pos.Face(neighbour).Axis() + if axis != cube.Y && axis != p.Axis { + return + } if n, ok := portal.NetherPortalFromPos(tx, pos); ok && (!n.Framed() || !n.Activated()) { n.Deactivate() } From a76b792e85e1fce693e697f35d898911f9269011 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 17:10:07 -0400 Subject: [PATCH 07/41] server: tighten Nether portal scan, frame and travel handling - Replace string-based portal scan matchers with typed PortalInterior and Frame block interfaces (Air, Fire, Obsidian opt in); crying obsidian no longer counts as frame and soul fire no longer activates a portal. - Rewrite scan as a directed rectangular validator with explicit width/height bounds, replacing the BFS that accepted any connected air/fire region. - Add connectedNetherPortal flood-fill so DeactivateNetherPortal clears every reachable portal block when a frame is broken, avoiding orphan portal blocks left behind by a partial scan. - Switch portal travel to a push model: Portal.EntityInside calls TravelThroughPortal on the entity; TravelComputer.EnterPortal records the contact and StopTravelling only resets the timer when the entity leaves. - Hoist activation into portal.ActivateNetherPortal and use it from flint and steel, fire charge and Fire.Start; remove the racy Fire.Place portal hook. - Add cube.Axis.Faces() returning the negative and positive Face along an Axis, and use it in the portal scan. - Drop the Portal model BBox; EntityInside dispatch handles travel without needing a collision shape. --- server/block/air.go | 7 ++ server/block/cube/axis.go | 14 +++ server/block/fire.go | 30 ++--- server/block/obsidian.go | 2 +- server/block/portal.go | 5 +- server/entity/travel.go | 5 +- server/item/fire_charge.go | 4 + server/item/flint_and_steel.go | 4 + server/world/portal/nether.go | 35 ++++-- server/world/portal/scan.go | 217 +++++++++++++++++++++------------ 10 files changed, 215 insertions(+), 108 deletions(-) diff --git a/server/block/air.go b/server/block/air.go index 895946b6d..d2d2cc809 100644 --- a/server/block/air.go +++ b/server/block/air.go @@ -1,5 +1,7 @@ package block +import "github.com/df-mc/dragonfly/server/world" + // Air is the block present in otherwise empty space. type Air struct { empty @@ -12,6 +14,11 @@ func (Air) HasLiquidDrops() bool { return false } +// PortalInterior returns true if air may occupy the inside of a portal frame before activation. +func (Air) PortalInterior(dimension world.Dimension) bool { + return dimension == world.Nether +} + // EncodeItem ... func (Air) EncodeItem() (name string, meta int16) { return "minecraft:air", 0 diff --git a/server/block/cube/axis.go b/server/block/cube/axis.go index dd94d46de..fa39ca40d 100644 --- a/server/block/cube/axis.go +++ b/server/block/cube/axis.go @@ -44,6 +44,20 @@ func (a Axis) RotateRight() Axis { return a.RotateLeft() } +// Faces returns the negative and positive Face along the Axis. For X it +// returns FaceWest, FaceEast; for Y, FaceDown, FaceUp; for Z, FaceNorth, +// FaceSouth. +func (a Axis) Faces() (negative, positive Face) { + switch a { + case X: + return FaceWest, FaceEast + case Y: + return FaceDown, FaceUp + default: + return FaceNorth, FaceSouth + } +} + // Vec3 returns a unit Vec3 of either (1, 0, 0), (0, 1, 0) or (0, 0, 1), // depending on the Axis. func (a Axis) Vec3() mgl64.Vec3 { diff --git a/server/block/fire.go b/server/block/fire.go index ff941a12e..a56648768 100644 --- a/server/block/fire.go +++ b/server/block/fire.go @@ -187,6 +187,9 @@ func (f Fire) spread(from, to cube.Pos, tx *world.Tx, r *rand.Rand) { return } spread := Fire{Type: f.Type, Age: min(15, f.Age+r.IntN(5)/4)} + if spread.Type == NormalFire() && portal.ActivateNetherPortal(tx, to) { + return + } tx.SetBlock(to, spread, nil) tx.ScheduleBlockUpdate(to, spread, time.Duration(30+r.IntN(10))*time.Second/20) } @@ -241,6 +244,11 @@ func (f Fire) HasLiquidDrops() bool { return false } +// PortalInterior returns true if fire may occupy the inside of a portal frame before activation. +func (f Fire) PortalInterior(dimension world.Dimension) bool { + return dimension == world.Nether && f.Type == NormalFire() +} + // LightEmissionLevel ... func (f Fire) LightEmissionLevel() uint8 { return f.Type.LightLevel() @@ -267,6 +275,9 @@ func (f Fire) Start(tx *world.Tx, pos cube.Pos) { if air || shortGrass || fern { below := tx.Block(pos.Side(cube.FaceDown)) if below.Model().FaceSolid(pos, cube.FaceUp, tx) || neighboursFlammable(pos, tx) { + if portal.ActivateNetherPortal(tx, pos) { + return + } f := Fire{} tx.SetBlock(pos, f, nil) tx.ScheduleBlockUpdate(pos, f, time.Duration(30+rand.IntN(10))*time.Second/20) @@ -274,25 +285,6 @@ func (f Fire) Start(tx *world.Tx, pos cube.Pos) { } } -// Place ... -func (Fire) Place(pos cube.Pos, w *world.World) bool { - portalActivated := false - for _, f := range cube.Faces() { - if portalActivated { - break - } - w.Exec(func(tx *world.Tx) { - if o, ok := tx.Block(pos.Side(f)).(Obsidian); ok && !o.Crying { - if p, ok := portal.NetherPortalFromPos(tx, pos); ok && p.Framed() && !p.Activated() { - p.Activate() - portalActivated = true - } - } - }) - } - return true -} - // allFire ... func allFire() (b []world.Block) { for i := 0; i < 16; i++ { diff --git a/server/block/obsidian.go b/server/block/obsidian.go index 1e0393b9d..0b8abe51a 100644 --- a/server/block/obsidian.go +++ b/server/block/obsidian.go @@ -40,7 +40,7 @@ func (o Obsidian) EncodeBlock() (string, map[string]any) { // Frame ... func (o Obsidian) Frame(dimension world.Dimension) bool { - return dimension == world.Nether + return dimension == world.Nether && !o.Crying } // BreakInfo ... diff --git a/server/block/portal.go b/server/block/portal.go index 7a6794db6..3dfdeab7a 100644 --- a/server/block/portal.go +++ b/server/block/portal.go @@ -46,9 +46,10 @@ func (p Portal) NeighbourUpdateTick(pos, neighbour cube.Pos, tx *world.Tx) { if axis != cube.Y && axis != p.Axis { return } - if n, ok := portal.NetherPortalFromPos(tx, pos); ok && (!n.Framed() || !n.Activated()) { - n.Deactivate() + if n, ok := portal.NetherPortalFromPos(tx, pos); ok && n.Framed() && n.Activated() { + return } + portal.DeactivateNetherPortal(tx, pos) } // EntityInside ... diff --git a/server/entity/travel.go b/server/entity/travel.go index bcbc1f6bb..b92b666a5 100644 --- a/server/entity/travel.go +++ b/server/entity/travel.go @@ -72,9 +72,10 @@ func (t *TravelComputer) StopTravelling() { func (t *TravelComputer) Travel(e Traveller, source *world.World, destination *world.World) { sourceDimension, targetDimension := source.Dimension(), destination.Dimension() pos := cube.PosFromVec3(e.Position()) - if sourceDimension == world.Overworld { + switch sourceDimension { + case world.Overworld: pos = cube.Pos{pos.X() / 8, pos.Y() + sourceDimension.Range().Min(), pos.Z() / 8} - } else if sourceDimension == world.Nether { + case world.Nether: pos = cube.Pos{pos.X() * 8, pos.Y() - targetDimension.Range().Min(), pos.Z() * 8} } diff --git a/server/item/fire_charge.go b/server/item/fire_charge.go index b74653b01..7199650c4 100644 --- a/server/item/fire_charge.go +++ b/server/item/fire_charge.go @@ -3,6 +3,7 @@ package item import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/portal" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" "math/rand/v2" @@ -27,6 +28,9 @@ func (f FireCharge) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *w } else if s := pos.Side(face); tx.Block(s) == air() { ctx.SubtractFromCount(1) tx.PlaySound(s.Vec3Centre(), sound.FireCharge{}) + if portal.ActivateNetherPortal(tx, s) { + return true + } flame := fire() tx.SetBlock(s, flame, nil) diff --git a/server/item/flint_and_steel.go b/server/item/flint_and_steel.go index 1e139dcbe..3be2004e4 100644 --- a/server/item/flint_and_steel.go +++ b/server/item/flint_and_steel.go @@ -3,6 +3,7 @@ package item import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/portal" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" "math/rand/v2" @@ -38,6 +39,9 @@ func (f FlintAndSteel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx return true } else if s := pos.Side(face); tx.Block(s) == air() { tx.PlaySound(s.Vec3Centre(), sound.Ignite{}) + if portal.ActivateNetherPortal(tx, s) { + return true + } flame := fire() tx.SetBlock(s, flame, nil) diff --git a/server/world/portal/nether.go b/server/world/portal/nether.go index c5e7c9cc5..fb88f4460 100644 --- a/server/world/portal/nether.go +++ b/server/world/portal/nether.go @@ -35,12 +35,9 @@ func NetherPortalFromPos(tx *world.Tx, pos cube.Pos) (Nether, bool) { return Nether{}, false } - axis, positions, width, height, completed, ok := multiAxisScan(pos, tx, []string{ - "minecraft:air", - "minecraft:fire", - }) + axis, positions, width, height, completed, ok := multiAxisScan(pos, tx, matchesNetherPortalInterior) if !ok { - axis, positions, width, height, completed, ok = multiAxisScan(pos, tx, []string{"minecraft:portal"}) + axis, positions, width, height, completed, ok = multiAxisScan(pos, tx, matchesNetherPortal) } return Nether{ w: width, h: height, @@ -52,6 +49,26 @@ func NetherPortalFromPos(tx *world.Tx, pos cube.Pos) (Nether, bool) { }, ok } +// ActivateNetherPortal activates an inactive framed Nether portal at the position passed. +func ActivateNetherPortal(tx *world.Tx, pos cube.Pos) bool { + p, ok := NetherPortalFromPos(tx, pos) + if !ok || !p.Framed() || p.Activated() { + return false + } + p.Activate() + return true +} + +// DeactivateNetherPortal deactivates the connected Nether portal at the position passed. +func DeactivateNetherPortal(tx *world.Tx, pos cube.Pos) bool { + _, positions, ok := connectedNetherPortal(tx, pos) + if !ok { + return false + } + deactivate(tx, positions) + return true +} + // FindOrCreateNetherPortal finds or creates a Nether portal at the given position. func FindOrCreateNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { n, ok := FindNetherPortal(tx, pos, radius) @@ -273,8 +290,12 @@ func (n Nether) Activate() { // Deactivate ... func (n Nether) Deactivate() { - for _, pos := range n.Positions() { - n.tx.SetBlock(pos, nil, nil) + deactivate(n.tx, n.Positions()) +} + +func deactivate(tx *world.Tx, positions []cube.Pos) { + for _, pos := range positions { + tx.SetBlock(pos, nil, nil) } } diff --git a/server/world/portal/scan.go b/server/world/portal/scan.go index f7b867c9c..7b3bdabe0 100644 --- a/server/world/portal/scan.go +++ b/server/world/portal/scan.go @@ -1,105 +1,168 @@ package portal import ( - "container/list" - "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/world" ) -// scanIteration contains data about a nether portal scan iteration. -type scanIteration struct { - lastPos cube.Pos - face cube.Face - first bool -} +// blockMatcher reports whether a block belongs to a portal interior on the given axis. +type blockMatcher func(world.Block, cube.Axis) bool -// multiAxisScan performs a scan on the Z and X axis, returning the result that had the most positions, although -// favouring the Z axis. -func multiAxisScan(framePos cube.Pos, tx *world.Tx, matchers []string) (cube.Axis, []cube.Pos, int, int, bool, bool) { - positions, width, height, completed := scan(cube.Z, framePos, tx, matchers) - positionsTwo, widthTwo, heightTwo, completedTwo := scan(cube.X, framePos, tx, matchers) - if len(positions) < minimumArea && len(positionsTwo) >= minimumArea { - return cube.X, positionsTwo, widthTwo, heightTwo, completedTwo, len(positionsTwo) > 0 +// multiAxisScan performs a scan on the Z and X axis, favouring the Z axis unless only the X axis reaches the minimum +// area. The last return value reports whether a portal-like interior was found; use Framed to check completion. +func multiAxisScan(framePos cube.Pos, tx *world.Tx, matches blockMatcher) (cube.Axis, []cube.Pos, int, int, bool, bool) { + zPositions, zWidth, zHeight, zCompleted := scan(cube.Z, framePos, tx, matches) + xPositions, xWidth, xHeight, xCompleted := scan(cube.X, framePos, tx, matches) + if len(zPositions) < minimumArea && len(xPositions) >= minimumArea { + return cube.X, xPositions, xWidth, xHeight, xCompleted, len(xPositions) > 0 } - return cube.Z, positions, width, height, completed, len(positions) > 0 + return cube.Z, zPositions, zWidth, zHeight, zCompleted, len(zPositions) > 0 } -// scan performs a scan on the given axis for any of the provided matchers using a position and a world. -func scan(axis cube.Axis, framePos cube.Pos, tx *world.Tx, matchers []string) ([]cube.Pos, int, int, bool) { - var width, height int - positionsMap := make(map[cube.Pos]bool) - - completed := true - queue := list.New() - queue.PushBack(scanIteration{lastPos: framePos, first: true}) - for queue.Len() > 0 { - e := queue.Front() - queue.Remove(e) - - // Parse the latest iteration. - iteration := e.Value.(scanIteration) - pos := iteration.lastPos - if !iteration.first { - pos = pos.Side(iteration.face) - } +// scan validates a vertical rectangular portal interior on the given horizontal axis. +func scan(axis cube.Axis, pos cube.Pos, tx *world.Tx, matches blockMatcher) ([]cube.Pos, int, int, bool) { + // Return if the starting block isn't part of a portal interior. + if !matches(tx.Block(pos), axis) { + return nil, 0, 0, false + } + negative, positive := axis.Faces() - b := tx.Block(pos) - if _, ok := positionsMap[pos]; !ok && satisfiesMatchers(b, matchers) { - // Add the position to the map. - positionsMap[pos] = true + // Walk down then towards the negative face to land on the bottom-left interior corner. + origin := pos + for down := 0; matches(tx.Block(origin.Side(cube.FaceDown)), axis); down++ { + if down >= maximumNetherPortalHeight { + return nil, 0, 0, false + } + origin = origin.Side(cube.FaceDown) + } + for left := 0; matches(tx.Block(origin.Side(negative)), axis); left++ { + if left >= maximumNetherPortalWidth { + return nil, 0, 0, false + } + origin = origin.Side(negative) + } - // If we are on the same X or Z axis as the portal, we can assume that our height is being changed. - if pos.X() == framePos.X() && pos.Z() == framePos.Z() && iteration.face < cube.FaceNorth { - height++ - } + // Measure the bottom row and the leftmost column from the origin. + width := 0 + for p := origin; matches(tx.Block(p), axis); p = p.Side(positive) { + width++ + if width > maximumNetherPortalWidth { + return nil, 0, 0, false + } + } + height := 0 + for p := origin; matches(tx.Block(p), axis); p = p.Side(cube.FaceUp) { + height++ + if height > maximumNetherPortalHeight { + return nil, 0, 0, false + } + } + // Reject anything smaller than the minimum frame size. + if width < minimumNetherPortalWidth || height < minimumNetherPortalHeight { + return nil, width, height, false + } - // If we are on the same Y axis as the portal, we can assume that our width is being changed. - if pos.Y() == framePos.Y() { - width++ + // Validate each row: side frames intact and every interior block matches. + positions := make([]cube.Pos, 0, width*height) + for y := 0; y < height; y++ { + row := origin.Add(cube.Pos{0, y}) + if !isFrame(tx.Block(row.Side(negative))) || !isFrame(tx.Block(row.Add(widthOffset(axis, width)))) { + return positions, width, height, false + } + for x := 0; x < width; x++ { + p := row.Add(widthOffset(axis, x)) + if !matches(tx.Block(p), axis) { + return positions, width, height, false } + positions = append(positions, p) + } + } + // Validate the top and bottom frames over each column. + for x := 0; x < width; x++ { + p := origin.Add(widthOffset(axis, x)) + if !isFrame(tx.Block(p.Side(cube.FaceDown))) || !isFrame(tx.Block(p.Add(cube.Pos{0, height}))) { + return positions, width, height, false + } + } + return positions, width, height, true +} - // Make sure we don't exceed the maximum portal width or height. - if width > maximumNetherPortalWidth || height > maximumNetherPortalHeight { - return []cube.Pos{}, 0, 0, false - } +// connectedNetherPortal flood-fills the region of portal blocks reachable from pos and returns its axis and positions. +// Used to clean up an entire portal when its frame breaks, where scan would only return a partial rectangle. +func connectedNetherPortal(tx *world.Tx, pos cube.Pos) (cube.Axis, []cube.Pos, bool) { + for _, axis := range []cube.Axis{cube.Z, cube.X} { + if !matchesNetherPortal(tx.Block(pos), axis) { + continue + } + positions := connectedPortalBlocks(tx, pos, axis) + return axis, positions, len(positions) > 0 + } + return 0, nil, false +} - // Plan new iterations. - if axis == cube.Z { - queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceSouth}) - queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceNorth}) - } else if axis == cube.X { - queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceWest}) - queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceEast}) +// connectedPortalBlocks returns every portal block of the given axis reachable from pos via face neighbours. +func connectedPortalBlocks(tx *world.Tx, pos cube.Pos, axis cube.Axis) []cube.Pos { + var positions []cube.Pos + queue := []cube.Pos{pos} + seen := map[cube.Pos]struct{}{pos: {}} + for len(queue) > 0 { + p := queue[0] + queue = queue[1:] + if !matchesNetherPortal(tx.Block(p), axis) { + continue + } + positions = append(positions, p) + for _, face := range portalFaces(axis) { + next := p.Side(face) + if _, ok := seen[next]; ok { + continue } - queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceUp}) - queue.PushBack(scanIteration{lastPos: pos, face: cube.FaceDown}) - } else if _, ok = positionsMap[pos]; !(ok || b == obsidian()) { - completed = false + seen[next] = struct{}{} + queue = append(queue, next) } } + return positions +} - // Make sure we at least reach the minimum portal width and height. - area, expectedArea := len(positionsMap), width*height - completed = completed && width >= minimumNetherPortalWidth && height >= minimumNetherPortalHeight && area == expectedArea +// portalFaces returns the four faces (vertical plus the two horizontal ones on the portal plane) used to flood-fill a portal of the given axis. +func portalFaces(axis cube.Axis) []cube.Face { + if axis == cube.X { + return []cube.Face{cube.FaceDown, cube.FaceUp, cube.FaceWest, cube.FaceEast} + } + return []cube.Face{cube.FaceDown, cube.FaceUp, cube.FaceNorth, cube.FaceSouth} +} - // Get the actual positions from the map. - positions := make([]cube.Pos, 0, expectedArea) - for pos := range positionsMap { - positions = append(positions, pos) +// widthOffset returns the position offset for moving by the given number of blocks along the portal's width axis. +func widthOffset(axis cube.Axis, offset int) cube.Pos { + if axis == cube.X { + return cube.Pos{offset, 0, 0} } - return positions, width, height, completed + return cube.Pos{0, 0, offset} } -// satisfiesMatchers checks if the given block satisfies all matchers. -func satisfiesMatchers(b world.Block, matchers []string) bool { - name, _ := b.EncodeBlock() - for _, matcher := range matchers { - if name == matcher { - return true - } +// isFrame reports whether the block can act as a Nether portal frame block. +func isFrame(b world.Block) bool { + f, ok := b.(frameBlock) + return ok && f.Frame(world.Nether) +} + +// matchesNetherPortalInterior reports whether the block may sit inside an unactivated Nether portal frame. +func matchesNetherPortalInterior(b world.Block, _ cube.Axis) bool { + i, ok := b.(interface { + PortalInterior(dimension world.Dimension) bool + }) + return ok && i.PortalInterior(world.Nether) +} + +// matchesNetherPortal reports whether the block is an active Nether portal block aligned with the given axis. +func matchesNetherPortal(b world.Block, axis cube.Axis) bool { + p, ok := b.(portalBlock) + if !ok || p.Portal() != world.Nether { + return false } - return false + m, ok := b.Model().(model.Portal) + return ok && m.Axis == axis } // air returns an air block. @@ -113,7 +176,7 @@ func air() world.Block { // portal returns a portal block. func portal(axis cube.Axis) world.Block { - p, ok := world.BlockByName("minecraft:portal", map[string]interface{}{"portal_axis": axis.String()}) + p, ok := world.BlockByName("minecraft:portal", map[string]any{"portal_axis": axis.String()}) if !ok { panic("could not find portal block") } From c2ed28f13c87c5bc09f3043bb3794cc9382a4a8b Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 17:11:30 -0400 Subject: [PATCH 08/41] server: add Nether portal and travel tests, run them in CI - Add server/world/portal/nether_portal_test.go covering scan acceptance and rejection (axis, frame size limits, missing side frames, crying obsidian, soul fire) plus axis-preference, ActivateNetherPortal and the FireCharge ignite path. - Add server/entity/travel_test.go for TravelComputer.StopTravelling, asserting it preserves the timer while the entity is inside a portal and resets it once contact ends. - Wire `go test ./...` into the pr.yml and push.yml GitHub Actions workflows so regressions are caught alongside `make lint`. --- .github/workflows/pr.yml | 3 + .github/workflows/push.yml | 3 + server/entity/travel_test.go | 27 +++ server/world/portal/nether_portal_test.go | 229 ++++++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 server/entity/travel_test.go create mode 100644 server/world/portal/nether_portal_test.go diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b9f1cf130..2dae4541e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -17,3 +17,6 @@ jobs: - name: Lint run: make lint + + - name: Test + run: go test ./... diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 2107ea1ff..c0955b250 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -18,6 +18,9 @@ jobs: - name: Lint run: make lint + - name: Test + run: go test ./... + deploy: name: Deploy needs: build diff --git a/server/entity/travel_test.go b/server/entity/travel_test.go new file mode 100644 index 000000000..923840efb --- /dev/null +++ b/server/entity/travel_test.go @@ -0,0 +1,27 @@ +package entity + +import ( + "testing" + "time" +) + +func TestTravelComputerStopTravelling(t *testing.T) { + t.Run("keeps timer after portal contact", func(t *testing.T) { + tc := &TravelComputer{inside: true, awaitingTravel: true, start: time.Now()} + tc.StopTravelling() + if !tc.awaitingTravel { + t.Fatal("StopTravelling() reset travel timer after portal contact") + } + if tc.inside { + t.Fatal("StopTravelling() did not clear portal contact for the next tick") + } + }) + + t.Run("resets timer without portal contact", func(t *testing.T) { + tc := &TravelComputer{awaitingTravel: true, start: time.Now()} + tc.StopTravelling() + if tc.awaitingTravel { + t.Fatal("StopTravelling() kept travel timer without portal contact") + } + }) +} diff --git a/server/world/portal/nether_portal_test.go b/server/world/portal/nether_portal_test.go new file mode 100644 index 000000000..c8e2fe035 --- /dev/null +++ b/server/world/portal/nether_portal_test.go @@ -0,0 +1,229 @@ +package portal_test + +import ( + "testing" + _ "unsafe" + + "github.com/df-mc/dragonfly/server/block" + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/portal" +) + +//go:linkname finaliseBlockRegistry github.com/df-mc/dragonfly/server/world.finaliseBlockRegistry +func finaliseBlockRegistry() + +func init() { + finaliseBlockRegistry() +} + +func TestNetherPortalFromPos(t *testing.T) { + tests := []struct { + name string + build func(tx *world.Tx, origin cube.Pos) + pos cube.Pos + ok bool + }{ + { + name: "valid vertical frame", + build: func(tx *world.Tx, origin cube.Pos) { + buildVerticalFrame(tx, origin, cube.Z, 2, 3) + }, + pos: cube.Pos{}, + ok: true, + }, + { + name: "maximum size frame", + build: func(tx *world.Tx, origin cube.Pos) { + buildVerticalFrame(tx, origin, cube.Z, 21, 21) + }, + pos: cube.Pos{}, + ok: true, + }, + { + name: "too wide frame", + build: func(tx *world.Tx, origin cube.Pos) { + buildVerticalFrame(tx, origin, cube.Z, 22, 3) + }, + pos: cube.Pos{}, + }, + { + name: "too tall frame", + build: func(tx *world.Tx, origin cube.Pos) { + buildVerticalFrame(tx, origin, cube.Z, 2, 22) + }, + pos: cube.Pos{}, + }, + { + name: "horizontal frame", + build: func(tx *world.Tx, origin cube.Pos) { + buildHorizontalFrame(tx, origin) + }, + pos: cube.Pos{1, 0, 1}, + }, + { + name: "crying obsidian does not complete frame", + build: func(tx *world.Tx, origin cube.Pos) { + buildVerticalFrame(tx, origin, cube.Z, 2, 3) + tx.SetBlock(origin.Side(cube.FaceNorth), block.Obsidian{Crying: true}, nil) + }, + pos: cube.Pos{}, + }, + { + name: "missing side frame does not complete frame", + build: func(tx *world.Tx, origin cube.Pos) { + buildVerticalFrame(tx, origin, cube.Z, 2, 3) + tx.SetBlock(origin.Side(cube.FaceNorth), nil, nil) + }, + pos: cube.Pos{}, + }, + { + name: "soul fire does not activate frame", + build: func(tx *world.Tx, origin cube.Pos) { + buildVerticalFrame(tx, origin, cube.Z, 2, 3) + tx.SetBlock(origin, block.Fire{Type: block.SoulFire()}, nil) + }, + pos: cube.Pos{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := world.New() + t.Cleanup(func() { _ = w.Close() }) + + origin := cube.Pos{8, 10, 8} + <-w.Exec(func(tx *world.Tx) { + tt.build(tx, origin) + p, ok := portal.NetherPortalFromPos(tx, origin.Add(tt.pos)) + if ok != tt.ok { + t.Fatalf("NetherPortalFromPos() ok = %v, want %v, portal = %#v", ok, tt.ok, p) + } + if ok && !p.Framed() { + t.Fatal("NetherPortalFromPos() returned an unframed portal") + } + }) + }) + } +} + +func TestPortalModelHasNoCollisionBBox(t *testing.T) { + for _, axis := range []cube.Axis{cube.X, cube.Z} { + if boxes := (model.Portal{Axis: axis}).BBox(cube.Pos{}, nil); len(boxes) != 0 { + t.Fatalf("BBox() returned %d boxes, want 0", len(boxes)) + } + } +} + +func TestActivateNetherPortal(t *testing.T) { + w := world.New() + t.Cleanup(func() { _ = w.Close() }) + + origin := cube.Pos{8, 10, 8} + <-w.Exec(func(tx *world.Tx) { + buildVerticalFrame(tx, origin, cube.Z, 2, 3) + if !portal.ActivateNetherPortal(tx, origin) { + t.Fatal("ActivateNetherPortal() = false, want true") + } + for x := range 2 { + for y := range 3 { + if _, ok := tx.Block(origin.Add(widthOffset(cube.Z, x)).Add(cube.Pos{0, y})).(block.Portal); !ok { + t.Fatalf("portal block not placed at interior offset %d,%d", x, y) + } + } + } + }) +} + +func TestFireChargeActivatesNetherPortal(t *testing.T) { + w := world.New() + t.Cleanup(func() { _ = w.Close() }) + + origin := cube.Pos{8, 10, 8} + <-w.Exec(func(tx *world.Tx) { + buildVerticalFrame(tx, origin, cube.Z, 2, 3) + ctx := &item.UseContext{} + if ok := (item.FireCharge{}).UseOnBlock(origin.Side(cube.FaceDown), cube.FaceUp, cube.Pos{}.Vec3(), tx, nil, ctx); !ok { + t.Fatal("FireCharge.UseOnBlock() = false, want true") + } + if ctx.CountSub != 1 { + t.Fatalf("FireCharge.UseOnBlock() subtracted %d items, want 1", ctx.CountSub) + } + if _, ok := tx.Block(origin).(block.Portal); !ok { + t.Fatal("FireCharge.UseOnBlock() did not activate portal") + } + }) +} + +func TestActivatedPortalCleanupOnBrokenFrame(t *testing.T) { + w := world.New() + t.Cleanup(func() { _ = w.Close() }) + + origin := cube.Pos{8, 10, 8} + <-w.Exec(func(tx *world.Tx) { + buildVerticalFrame(tx, origin, cube.Z, 2, 3) + if !portal.ActivateNetherPortal(tx, origin) { + t.Fatal("ActivateNetherPortal() = false, want true") + } + + broken := origin.Add(widthOffset(cube.Z, 2)).Add(cube.Pos{0, 1}) + tx.SetBlock(broken, nil, nil) + + updated := origin.Add(widthOffset(cube.Z, 1)).Add(cube.Pos{0, 1}) + pb, ok := tx.Block(updated).(block.Portal) + if !ok { + t.Fatalf("block at updated position = %T, want block.Portal", tx.Block(updated)) + } + pb.NeighbourUpdateTick(updated, broken, tx) + + var remaining []cube.Pos + for x := range 2 { + for y := range 3 { + p := origin.Add(widthOffset(cube.Z, x)).Add(cube.Pos{0, y}) + if _, ok := tx.Block(p).(block.Portal); ok { + remaining = append(remaining, p) + } + } + } + if len(remaining) != 0 { + t.Fatalf("after frame break: %d orphan portal blocks remain at %v", len(remaining), remaining) + } + }) +} + +func buildVerticalFrame(tx *world.Tx, origin cube.Pos, axis cube.Axis, width, height int) { + for x := 0; x < width; x++ { + p := origin.Add(widthOffset(axis, x)) + tx.SetBlock(p.Side(cube.FaceDown), block.Obsidian{}, nil) + tx.SetBlock(p.Add(cube.Pos{0, height}), block.Obsidian{}, nil) + } + negative := cube.FaceNorth + if axis == cube.X { + negative = cube.FaceWest + } + for y := 0; y < height; y++ { + p := origin.Add(cube.Pos{0, y}) + tx.SetBlock(p.Side(negative), block.Obsidian{}, nil) + tx.SetBlock(p.Add(widthOffset(axis, width)), block.Obsidian{}, nil) + } +} + +func buildHorizontalFrame(tx *world.Tx, origin cube.Pos) { + for x := 0; x < 3; x++ { + for z := 0; z < 3; z++ { + if x == 1 && z == 1 { + continue + } + tx.SetBlock(origin.Add(cube.Pos{x, 0, z}), block.Obsidian{}, nil) + } + } +} + +func widthOffset(axis cube.Axis, width int) cube.Pos { + if axis == cube.X { + return cube.Pos{width, 0, 0} + } + return cube.Pos{0, 0, width} +} From eaff6f430b604ea5d590583583ddc2caca27d3d1 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 17:15:02 -0400 Subject: [PATCH 09/41] remove useless docs --- server/world/portal/nether.go | 8 +++----- server/world/portal/scan.go | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/server/world/portal/nether.go b/server/world/portal/nether.go index fb88f4460..3f9ec5e90 100644 --- a/server/world/portal/nether.go +++ b/server/world/portal/nether.go @@ -20,12 +20,10 @@ type Nether struct { } const ( - // minimumNetherPortalWidth, maximumNetherPortalWidth controls the minimum and maximum width of a portal. - minimumNetherPortalWidth, maximumNetherPortalWidth = 2, 21 - // minimumNetherPortalHeight, maximumNetherPortalHeight controls the minimum and maximum height of a portal. + minimumNetherPortalWidth, maximumNetherPortalWidth = 2, 21 minimumNetherPortalHeight, maximumNetherPortalHeight = 3, 21 - // minimumArea is the minimum area of a portal. - minimumArea = minimumNetherPortalWidth * minimumNetherPortalHeight + + minimumNetherPortalArea = minimumNetherPortalWidth * minimumNetherPortalHeight ) // NetherPortalFromPos returns Nether portal information from a given position in the frame. diff --git a/server/world/portal/scan.go b/server/world/portal/scan.go index 7b3bdabe0..968113361 100644 --- a/server/world/portal/scan.go +++ b/server/world/portal/scan.go @@ -14,7 +14,7 @@ type blockMatcher func(world.Block, cube.Axis) bool func multiAxisScan(framePos cube.Pos, tx *world.Tx, matches blockMatcher) (cube.Axis, []cube.Pos, int, int, bool, bool) { zPositions, zWidth, zHeight, zCompleted := scan(cube.Z, framePos, tx, matches) xPositions, xWidth, xHeight, xCompleted := scan(cube.X, framePos, tx, matches) - if len(zPositions) < minimumArea && len(xPositions) >= minimumArea { + if len(zPositions) < minimumNetherPortalArea && len(xPositions) >= minimumNetherPortalArea { return cube.X, xPositions, xWidth, xHeight, xCompleted, len(xPositions) > 0 } return cube.Z, zPositions, zWidth, zHeight, zCompleted, len(zPositions) > 0 From 46aa0d9858bf5b9b52c412f1bd61b43873a437c2 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 17:16:08 -0400 Subject: [PATCH 10/41] remove useless docs --- server/world/portal/nether.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/world/portal/nether.go b/server/world/portal/nether.go index 3f9ec5e90..51bf75704 100644 --- a/server/world/portal/nether.go +++ b/server/world/portal/nether.go @@ -29,7 +29,6 @@ const ( // NetherPortalFromPos returns Nether portal information from a given position in the frame. func NetherPortalFromPos(tx *world.Tx, pos cube.Pos) (Nether, bool) { if tx.World().Dimension() == world.End { - // Don't waste our time; we can't make a portal in the end. return Nether{}, false } @@ -91,7 +90,6 @@ type frameBlock interface { // FindNetherPortal searches a provided radius for a Nether portal. func FindNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { if tx.World().Dimension() == world.End { - // Don't waste our time - we can't make a portal in the end. return Nether{}, false } @@ -114,7 +112,6 @@ func FindNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { } } if !found { - // Don't waste our time if the search didn't work out. return Nether{}, false } return NetherPortalFromPos(tx, closestPos) @@ -123,7 +120,6 @@ func FindNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { // CreateNetherPortal creates a Nether portal at the given position. func CreateNetherPortal(tx *world.Tx, pos cube.Pos) (Nether, bool) { if tx.World().Dimension() == world.End { - // You can't create a nether portal in the end. return Nether{}, false } @@ -194,7 +190,7 @@ func CreateNetherPortal(tx *world.Tx, pos cube.Pos) (Nether, bool) { // If we couldn't find a valid area under those specifications, we can search the two main directions instead, // reducing comfort but at least allowing us to have a portal in the area. searchValidArea(2, func(pos cube.Pos, riv int, coEff1, coEff2 int) bool { - for safeSpace := 0; safeSpace < 3; safeSpace++ { + for safeSpace := range 3 { for height := -1; height < 4; height++ { b := tx.Block(cube.Pos{ pos.X() + safeSpace*coEff1, From 3083946157c5238a535ddccd32db3de9b7de8932 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 18:00:03 -0400 Subject: [PATCH 11/41] add light emission to portal block --- server/block/portal.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/block/portal.go b/server/block/portal.go index 3dfdeab7a..665ecbfc9 100644 --- a/server/block/portal.go +++ b/server/block/portal.go @@ -30,6 +30,11 @@ func (Portal) Portal() world.Dimension { return world.Nether } +// LightEmissionLevel returns 11. +func (Portal) LightEmissionLevel() uint8 { + return 11 +} + // HasLiquidDrops ... func (p Portal) HasLiquidDrops() bool { return false From c48fef7c09ec6aa29282423165faf036405b9f1a Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 18:01:40 -0400 Subject: [PATCH 12/41] remove Place interface check: - No remaining implementer of Place(pos cube.Pos, w *World) bool under server/. - Flint and steel does not use that hook now. It activates portals directly in item.FlintAndSteel.UseOnBlock via portal.ActivateNetherPortal(tx, s). - Fire charge does the same. - Fire spread/start also uses portal.ActivateNetherPortal directly. - The hook is on World.setBlock, so keeping it would add a dead implicit interface check to every block placement. --- server/world/world.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/server/world/world.go b/server/world/world.go index 9439ab034..992d4d9ac 100644 --- a/server/world/world.go +++ b/server/world/world.go @@ -258,13 +258,6 @@ func (w *World) setBlock(pos cube.Pos, b Block, opts *SetOpts) { opts = &SetOpts{} } - if p, ok := b.(interface { - Place(pos cube.Pos, w *World) bool - }); ok && !p.Place(pos, w) { - // Don't place the block. - return - } - x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2]) c := w.chunk(chunkPosFromBlockPos(pos)) From 68ee5aa6d058270353ff40aeaf13f52ffdbf872b Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 18:02:11 -0400 Subject: [PATCH 13/41] portal/nether.go: simplify and improve code and clarify doc --- server/world/portal/nether.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/server/world/portal/nether.go b/server/world/portal/nether.go index 51bf75704..66023c597 100644 --- a/server/world/portal/nether.go +++ b/server/world/portal/nether.go @@ -2,14 +2,15 @@ package portal import ( "math" - "math/rand" + "math/rand/v2" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/world" ) -// Nether contains information about a nether portal structure. +// Nether contains information about a nether portal structure. Values returned from this package are tied to the +// transaction that created them and must not be retained after that transaction finishes. type Nether struct { w, h int framed bool @@ -36,6 +37,9 @@ func NetherPortalFromPos(tx *world.Tx, pos cube.Pos) (Nether, bool) { if !ok { axis, positions, width, height, completed, ok = multiAxisScan(pos, tx, matchesNetherPortal) } + if !ok { + return Nether{}, false + } return Nether{ w: width, h: height, spawnPos: pos, @@ -123,7 +127,7 @@ func CreateNetherPortal(tx *world.Tx, pos cube.Pos) (Nether, bool) { return Nether{}, false } - resultPos, random, distance, a, r := pos, rand.Intn(4), -1.0, 0, tx.Range() + resultPos, random, distance, a, r := pos, rand.IntN(4), -1.0, 0, tx.Range() searchValidArea := func(directions int, valid func(pos cube.Pos, riv int, coEff1, coEff2 int) bool) { for tempX := pos.X() - 16; tempX <= pos.X()+16; tempX++ { offsetX := float64(tempX-pos.X()) + 0.5 @@ -168,7 +172,7 @@ func CreateNetherPortal(tx *world.Tx, pos cube.Pos) (Nether, bool) { coEff2 = -coEff2 } - for safeSpace1 := 0; safeSpace1 < 3; safeSpace1++ { + for safeSpace1 := range 3 { for safeSpace2 := -1; safeSpace2 < 3; safeSpace2++ { for height := -1; height < 4; height++ { b := tx.Block(cube.Pos{ @@ -221,9 +225,9 @@ func CreateNetherPortal(tx *world.Tx, pos cube.Pos) (Nether, bool) { if distance < 0.0 { // If all else fails, we can simply create a floating platform in the void with the portal on it. - resultPos[1] = int(math.Min(math.Max(float64(resultPos[1]), 70), float64(r.Max()-10))) + resultPos[1] = min(max(resultPos[1], 70), r.Max()-10) for safeBeforeAfter := -1; safeBeforeAfter <= 1; safeBeforeAfter++ { - for safeWidth := 0; safeWidth < 2; safeWidth++ { + for safeWidth := range 2 { for height := -1; height < 3; height++ { entryPos := cube.Pos{ resultPos.X() + safeWidth*coEff1 + safeBeforeAfter*coEff2, From 961db4517f7d1d19adcc4eb571f8942f3c340506 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 18:13:34 -0400 Subject: [PATCH 14/41] entity: harden TravelComputer and extract portal coord translation - Capture the *EntityHandle returned by RemoveEntity and pass it to AddEntity in the destination world; Tx docs warn that the original entity is unusable after removal. - Await both source.Exec and destination.Exec so removal completes before the destination tries to add or find the entity, fixing the cross-world race. - Bail out of travel if RemoveEntity returns nil and reset travelling. - Restructure EnterPortal to compute travelNow under the lock and call travel() after releasing it, removing the unlock-relock dance. - Short-circuit EnterPortal when the source world has no distinct portal destination, so non-Nether targets no-op cleanly. - Add an instantaneous() helper that tolerates a nil Instantaneous field instead of panicking. - Drop the dead `t.inside = false` reassignment in StopTravelling and switch the mutex to sync.Mutex (no read-locked path exists). - Unexport Travel to travel; the entry point is EnterPortal and an external caller would skip the timed-out / awaiting-travel checks. - Extract translatePortalPosition with godoc covering the 1:8 X/Z scaling and Y clamp to the destination's vertical range, replacing the inline transform that shifted Y by the source's Range().Min(). --- server/entity/travel.go | 94 ++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/server/entity/travel.go b/server/entity/travel.go index b92b666a5..08cec3eb4 100644 --- a/server/entity/travel.go +++ b/server/entity/travel.go @@ -12,10 +12,10 @@ import ( // TravelComputer handles the interdimensional travelling of an entity. type TravelComputer struct { - // Instantaneous is a function that returns true if the entity given can travel instantly. + // Instantaneous returns true if the entity should skip the portal wait timer. Players use this for Creative mode. Instantaneous func() bool - mu sync.RWMutex + mu sync.Mutex start time.Time inside bool awaitingTravel bool @@ -32,26 +32,35 @@ type Traveller interface { // EnterPortal handles an entity touching a portal block. It teleports the entity to the other dimension after four // seconds or instantly if instantaneous is true. -func (t *TravelComputer) EnterPortal(travel Traveller, tx *world.Tx, target world.Dimension) { +func (t *TravelComputer) EnterPortal(e Traveller, tx *world.Tx, target world.Dimension) { + source := tx.World() + destination := source.PortalDestination(target) + if destination == source { + return + } + t.mu.Lock() - defer t.mu.Unlock() t.inside = true - switch target { - case world.Nether: - if t.timedOut { - // Timed out, we can't travel through Nether portals. - return - } - if t.Instantaneous() || (t.awaitingTravel && time.Since(t.start) >= time.Second*4) { - t.mu.Unlock() - t.Travel(travel, tx.World(), tx.World().PortalDestination(world.Nether)) - t.mu.Lock() - } else if !t.awaitingTravel { - t.start, t.awaitingTravel = time.Now(), true - } + if t.timedOut { + // Timed out, we can't travel through portals. + t.mu.Unlock() + return + } + travelNow := t.instantaneous() || (t.awaitingTravel && time.Since(t.start) >= time.Second*4) + if !travelNow && !t.awaitingTravel { + t.start, t.awaitingTravel = time.Now(), true + } + t.mu.Unlock() + + if travelNow { + t.travel(e, source, destination) } } +func (t *TravelComputer) instantaneous() bool { + return t.Instantaneous != nil && t.Instantaneous() +} + // StopTravelling resets the travel timer if the entity was not inside a portal this tick. func (t *TravelComputer) StopTravelling() { t.mu.Lock() @@ -60,25 +69,21 @@ func (t *TravelComputer) StopTravelling() { t.inside = false return } - t.inside = false if t.travelling { return } t.timedOut, t.awaitingTravel = false, false } -// Travel moves the player to the given Nether or Overworld world and translates the player's current position based -// on the source world. -func (t *TravelComputer) Travel(e Traveller, source *world.World, destination *world.World) { - sourceDimension, targetDimension := source.Dimension(), destination.Dimension() - pos := cube.PosFromVec3(e.Position()) - switch sourceDimension { - case world.Overworld: - pos = cube.Pos{pos.X() / 8, pos.Y() + sourceDimension.Range().Min(), pos.Z() / 8} - case world.Nether: - pos = cube.Pos{pos.X() * 8, pos.Y() - targetDimension.Range().Min(), pos.Z() * 8} +// travel moves the entity to the given Nether or Overworld world and translates its current position based on the +// source world. +func (t *TravelComputer) travel(e Traveller, source *world.World, destination *world.World) { + if destination == nil || destination == source { + return } + pos := translatePortalPosition(cube.PosFromVec3(e.Position()), source.Dimension(), destination.Dimension()) + t.mu.Lock() defer t.mu.Unlock() t.travelling, t.timedOut, t.awaitingTravel = true, true, false @@ -86,18 +91,24 @@ func (t *TravelComputer) Travel(e Traveller, source *world.World, destination *w go func() { spawn := pos.Vec3Middle() - source.Exec(func(tx *world.Tx) { - tx.RemoveEntity(e) + var handle *world.EntityHandle + <-source.Exec(func(tx *world.Tx) { + handle = tx.RemoveEntity(e) }) + if handle == nil { + t.mu.Lock() + t.travelling = false + t.mu.Unlock() + return + } - destination.Exec(func(tx *world.Tx) { + <-destination.Exec(func(tx *world.Tx) { if netherPortal, ok := portal.FindOrCreateNetherPortal(tx, pos, 128); ok { spawn = netherPortal.Spawn().Vec3Middle() } - tx.AddEntity(e.H()) - if ent, ok := e.H().Entity(tx); ok { - ent.(Traveller).Teleport(spawn) + if ent, ok := tx.AddEntity(handle).(Traveller); ok { + ent.Teleport(spawn) } }) @@ -106,3 +117,18 @@ func (t *TravelComputer) Travel(e Traveller, source *world.World, destination *w t.travelling = false }() } + +// translatePortalPosition maps a position in the source dimension to the equivalent position in the target dimension. +// Overworld coordinates are divided by 8 when crossing to the Nether and Nether coordinates are multiplied by 8 when +// crossing to the Overworld; the Y coordinate is clamped to the target dimension's vertical range. +func translatePortalPosition(pos cube.Pos, source, target world.Dimension) cube.Pos { + switch source { + case world.Overworld: + pos[0], pos[2] = pos[0]/8, pos[2]/8 + case world.Nether: + pos[0], pos[2] = pos[0]*8, pos[2]*8 + } + r := target.Range() + pos[1] = min(max(pos[1], r.Min()), r.Max()) + return pos +} From 1b75559a84cc59565519d5f4063312d4d059ee83 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 18:28:12 -0400 Subject: [PATCH 15/41] Floor Nether portal scaling for negative Overworld coordinates --- server/entity/travel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/entity/travel.go b/server/entity/travel.go index 08cec3eb4..6c92a63dd 100644 --- a/server/entity/travel.go +++ b/server/entity/travel.go @@ -124,7 +124,7 @@ func (t *TravelComputer) travel(e Traveller, source *world.World, destination *w func translatePortalPosition(pos cube.Pos, source, target world.Dimension) cube.Pos { switch source { case world.Overworld: - pos[0], pos[2] = pos[0]/8, pos[2]/8 + pos[0], pos[2] = pos[0]>>3, pos[2]>>3 case world.Nether: pos[0], pos[2] = pos[0]*8, pos[2]*8 } From 59d0c6f8ca0b790c777c4260badb032fc72d7467 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 19:29:11 -0400 Subject: [PATCH 16/41] rename variable --- server/block/air.go | 6 +++--- server/block/fire.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/block/air.go b/server/block/air.go index d2d2cc809..e1840a294 100644 --- a/server/block/air.go +++ b/server/block/air.go @@ -14,9 +14,9 @@ func (Air) HasLiquidDrops() bool { return false } -// PortalInterior returns true if air may occupy the inside of a portal frame before activation. -func (Air) PortalInterior(dimension world.Dimension) bool { - return dimension == world.Nether +// PortalInterior returns true if air may occupy the inside of a portal frame before activation for the target dimension. +func (Air) PortalInterior(target world.Dimension) bool { + return target == world.Nether } // EncodeItem ... diff --git a/server/block/fire.go b/server/block/fire.go index a56648768..380b32af3 100644 --- a/server/block/fire.go +++ b/server/block/fire.go @@ -244,9 +244,9 @@ func (f Fire) HasLiquidDrops() bool { return false } -// PortalInterior returns true if fire may occupy the inside of a portal frame before activation. -func (f Fire) PortalInterior(dimension world.Dimension) bool { - return dimension == world.Nether && f.Type == NormalFire() +// PortalInterior returns true if fire may occupy the inside of a portal frame before activation for the target dimension. +func (f Fire) PortalInterior(target world.Dimension) bool { + return target == world.Nether && f.Type == NormalFire() } // LightEmissionLevel ... From 48ed0b219bdc0c9e3d731a0d0bb374de24bc5673 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 19:31:45 -0400 Subject: [PATCH 17/41] fix portal blocks being ignored if it didnt have obsidian below it Portal lookup distance includes Y, so tall portals must be matched by any active interior block, not only bottom-row blocks with frame below --- server/world/portal/nether.go | 9 ++++----- .../portal/{nether_portal_test.go => nether_test.go} | 0 2 files changed, 4 insertions(+), 5 deletions(-) rename server/world/portal/{nether_portal_test.go => nether_test.go} (100%) diff --git a/server/world/portal/nether.go b/server/world/portal/nether.go index 66023c597..61bdd6b24 100644 --- a/server/world/portal/nether.go +++ b/server/world/portal/nether.go @@ -97,18 +97,17 @@ func FindNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { return Nether{}, false } - closestPos, closestDist, found := cube.Pos{}, math.MaxFloat64, false + closest, closestDist, found := Nether{}, math.MaxFloat64, false for x := pos.X() - radius; x < pos.X()+radius; x++ { for z := pos.Z() - radius; z < pos.Z()+radius; z++ { r := tx.World().Dimension().Range() for y := r.Max(); y >= r.Min(); y-- { selectedPos := cube.Pos{x, y, z} if p, ok := tx.Block(selectedPos).(portalBlock); ok && p.Portal() == world.Nether { - belowPos := selectedPos.Side(cube.FaceDown) - if f, ok := tx.Block(belowPos).(frameBlock); ok && f.Frame(world.Nether) { + if n, ok := NetherPortalFromPos(tx, selectedPos); ok && n.Framed() && n.Activated() { dist := selectedPos.Vec3().Sub(pos.Vec3()).Len() if dist < closestDist { - closestDist, closestPos, found = dist, selectedPos, true + closestDist, closest, found = dist, n, true } } } @@ -118,7 +117,7 @@ func FindNetherPortal(tx *world.Tx, pos cube.Pos, radius int) (Nether, bool) { if !found { return Nether{}, false } - return NetherPortalFromPos(tx, closestPos) + return closest, true } // CreateNetherPortal creates a Nether portal at the given position. diff --git a/server/world/portal/nether_portal_test.go b/server/world/portal/nether_test.go similarity index 100% rename from server/world/portal/nether_portal_test.go rename to server/world/portal/nether_test.go From 19e963f66607637c44e2ec9f8a58d439b1fe1868 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 19:36:20 -0400 Subject: [PATCH 18/41] fix: Prevent portal-crossed ender pearls from teleporting owners --- server/entity/ender_pearl.go | 6 +++++- server/entity/projectile.go | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/server/entity/ender_pearl.go b/server/entity/ender_pearl.go index 5fd44e374..42b905af1 100644 --- a/server/entity/ender_pearl.go +++ b/server/entity/ender_pearl.go @@ -34,7 +34,11 @@ type teleporter interface { // teleport teleports the owner of an Ent to a trace.Result's position. func teleport(e *Ent, tx *world.Tx, target trace.Result) { - owner, _ := e.Behaviour().(*ProjectileBehaviour).Owner().Entity(tx) + behaviour := e.Behaviour().(*ProjectileBehaviour) + if behaviour.PortalTravel() { + return + } + owner, _ := behaviour.Owner().Entity(tx) if user, ok := owner.(teleporter); ok { tx.PlaySound(user.Position(), sound.Teleport{}) user.Teleport(target.Position()) diff --git a/server/entity/projectile.go b/server/entity/projectile.go index 1d11f6a2c..2997cbd7d 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -109,6 +109,7 @@ type ProjectileBehaviour struct { collisionPos cube.Pos collided bool + portalTravel bool } // Owner returns the owner of the projectile. @@ -134,6 +135,16 @@ func (lt *ProjectileBehaviour) Critical() bool { return lt.conf.Critical && !lt.collided } +// HandlePortalTravel records that this projectile has travelled between dimensions through a portal. +func (lt *ProjectileBehaviour) HandlePortalTravel(world.Dimension, world.Dimension) { + lt.portalTravel = true +} + +// PortalTravel reports whether this projectile has travelled between dimensions through a portal. +func (lt *ProjectileBehaviour) PortalTravel() bool { + return lt.portalTravel +} + // Tick runs the tick-based behaviour of a ProjectileBehaviour and returns the // Movement within the tick. Tick handles the movement, collision and hitting // of a projectile. From 8d145f163b3bfc1cf7ef73b0db3262070a46aea9 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 19:41:07 -0400 Subject: [PATCH 19/41] Tighten Nether portal scan failure semantics Portal scan helpers should return complete validated portal interiors or no positions at all. --- server/world/portal/scan.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/server/world/portal/scan.go b/server/world/portal/scan.go index 968113361..722fad2f1 100644 --- a/server/world/portal/scan.go +++ b/server/world/portal/scan.go @@ -30,17 +30,17 @@ func scan(axis cube.Axis, pos cube.Pos, tx *world.Tx, matches blockMatcher) ([]c // Walk down then towards the negative face to land on the bottom-left interior corner. origin := pos - for down := 0; matches(tx.Block(origin.Side(cube.FaceDown)), axis); down++ { + for down, next := 0, origin.Side(cube.FaceDown); matches(tx.Block(next), axis); down, next = down+1, origin.Side(cube.FaceDown) { if down >= maximumNetherPortalHeight { return nil, 0, 0, false } - origin = origin.Side(cube.FaceDown) + origin = next } - for left := 0; matches(tx.Block(origin.Side(negative)), axis); left++ { + for left, next := 0, origin.Side(negative); matches(tx.Block(next), axis); left, next = left+1, origin.Side(negative) { if left >= maximumNetherPortalWidth { return nil, 0, 0, false } - origin = origin.Side(negative) + origin = next } // Measure the bottom row and the leftmost column from the origin. @@ -68,12 +68,12 @@ func scan(axis cube.Axis, pos cube.Pos, tx *world.Tx, matches blockMatcher) ([]c for y := 0; y < height; y++ { row := origin.Add(cube.Pos{0, y}) if !isFrame(tx.Block(row.Side(negative))) || !isFrame(tx.Block(row.Add(widthOffset(axis, width)))) { - return positions, width, height, false + return nil, width, height, false } for x := 0; x < width; x++ { p := row.Add(widthOffset(axis, x)) if !matches(tx.Block(p), axis) { - return positions, width, height, false + return nil, width, height, false } positions = append(positions, p) } @@ -82,7 +82,7 @@ func scan(axis cube.Axis, pos cube.Pos, tx *world.Tx, matches blockMatcher) ([]c for x := 0; x < width; x++ { p := origin.Add(widthOffset(axis, x)) if !isFrame(tx.Block(p.Side(cube.FaceDown))) || !isFrame(tx.Block(p.Add(cube.Pos{0, height}))) { - return positions, width, height, false + return nil, width, height, false } } return positions, width, height, true @@ -125,12 +125,10 @@ func connectedPortalBlocks(tx *world.Tx, pos cube.Pos, axis cube.Axis) []cube.Po return positions } -// portalFaces returns the four faces (vertical plus the two horizontal ones on the portal plane) used to flood-fill a portal of the given axis. +// portalFaces returns the four neighbouring faces used to flood-fill a portal of the given horizontal axis. func portalFaces(axis cube.Axis) []cube.Face { - if axis == cube.X { - return []cube.Face{cube.FaceDown, cube.FaceUp, cube.FaceWest, cube.FaceEast} - } - return []cube.Face{cube.FaceDown, cube.FaceUp, cube.FaceNorth, cube.FaceSouth} + negative, positive := axis.Faces() + return []cube.Face{cube.FaceDown, cube.FaceUp, negative, positive} } // widthOffset returns the position offset for moving by the given number of blocks along the portal's width axis. @@ -150,7 +148,7 @@ func isFrame(b world.Block) bool { // matchesNetherPortalInterior reports whether the block may sit inside an unactivated Nether portal frame. func matchesNetherPortalInterior(b world.Block, _ cube.Axis) bool { i, ok := b.(interface { - PortalInterior(dimension world.Dimension) bool + PortalInterior(target world.Dimension) bool }) return ok && i.PortalInterior(world.Nether) } From f45ce656d61a392920e3a917fcd1a73a4792347c Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Wed, 6 May 2026 22:57:52 -0400 Subject: [PATCH 20/41] add portal teleport method to avoid calling handler --- server/player/conf.go | 35 +++++++++++++++++++++-------------- server/player/player.go | 9 +++++++-- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/server/player/conf.go b/server/player/conf.go index 2b901cde3..02bab660b 100644 --- a/server/player/conf.go +++ b/server/player/conf.go @@ -56,20 +56,27 @@ func (cfg Config) Apply(data *world.EntityData) { data.Name, data.Pos, data.Rot = conf.Name, conf.Position, conf.Rotation slot := uint32(conf.HeldSlot) pdata := &playerData{ - xuid: conf.XUID, - ui: inventory.New(54, nil), - inv: conf.Inventory, - enderChest: conf.EnderChestInventory, - offHand: conf.OffHand, - armour: conf.Armour, - hunger: newHungerManager(), - health: entity.NewHealthManager(conf.Health, conf.MaxHealth), // 20, 20 - experience: entity.NewExperienceManager(), - effects: entity.NewEffectManager(conf.Effects...), - locale: conf.Locale, - cooldowns: make(map[string]time.Time), - mc: &entity.MovementComputer{Gravity: 0.08, Drag: 0.02, DragBeforeGravity: true}, - tc: &entity.TravelComputer{Instantaneous: func() bool { return cfg.GameMode == world.GameModeCreative }}, + xuid: conf.XUID, + ui: inventory.New(54, nil), + inv: conf.Inventory, + enderChest: conf.EnderChestInventory, + offHand: conf.OffHand, + armour: conf.Armour, + hunger: newHungerManager(), + health: entity.NewHealthManager(conf.Health, conf.MaxHealth), // 20, 20 + experience: entity.NewExperienceManager(), + effects: entity.NewEffectManager(conf.Effects...), + locale: conf.Locale, + cooldowns: make(map[string]time.Time), + mc: &entity.MovementComputer{Gravity: 0.08, Drag: 0.02, DragBeforeGravity: true}, + tc: &entity.TravelComputer{ + Instantaneous: func() bool { + return cfg.GameMode == world.GameModeCreative + }, + Teleport: func(e entity.Traveller, pos mgl64.Vec3) { + e.(*Player).forceTeleport(pos) + }, + }, heldSlot: &slot, gameMode: conf.GameMode, skin: conf.Skin, diff --git a/server/player/player.go b/server/player/player.go index 1921e3479..3bc68b48c 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2169,12 +2169,17 @@ func (p *Player) Teleport(pos mgl64.Vec3) { if p.Handler().HandleTeleport(ctx, pos); ctx.Cancelled() { return } + p.forceTeleport(pos) +} + +// forceTeleport teleports the player without calling the Handler. +// It also wakes up the player from sleep. +func (p *Player) forceTeleport(pos mgl64.Vec3) { p.Wake() p.teleport(pos) } -// teleport teleports the player to a target position in the world. It does not call the Handler of the -// player. +// teleport teleports the player to a target position in the world without updating non-positional state. func (p *Player) teleport(pos mgl64.Vec3) { for _, v := range p.viewers() { v.ViewEntityTeleport(p, pos) From 982e1b3644859a7860bacf3e70e8fd66c4903786 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Sat, 9 May 2026 02:28:56 -0400 Subject: [PATCH 21/41] feat: allow entities to travel through portals --- server/entity/area_effect_cloud_behaviour.go | 10 +- server/entity/base_behaviour.go | 20 ++ server/entity/ent.go | 38 ++- server/entity/ent_portal.go | 74 ++++++ server/entity/experience_orb_behaviour.go | 13 +- server/entity/falling_block_behaviour.go | 5 + server/entity/firework_behaviour.go | 5 + server/entity/item_behaviour.go | 5 + server/entity/passive.go | 17 +- server/entity/projectile.go | 18 +- server/entity/stationary.go | 4 +- server/entity/travel.go | 158 +++++++++++-- server/entity/travel_test.go | 236 ++++++++++++++++++- server/player/conf.go | 42 ++-- server/player/conf_test.go | 27 +++ server/player/player.go | 8 +- server/world/entity.go | 21 +- server/world/handler.go | 2 +- server/world/portal/nether_test.go | 8 - server/world/tx.go | 7 + server/world/world.go | 13 +- 21 files changed, 639 insertions(+), 92 deletions(-) create mode 100644 server/entity/base_behaviour.go create mode 100644 server/entity/ent_portal.go create mode 100644 server/player/conf_test.go diff --git a/server/entity/area_effect_cloud_behaviour.go b/server/entity/area_effect_cloud_behaviour.go index 056b94443..b7cfa604d 100644 --- a/server/entity/area_effect_cloud_behaviour.go +++ b/server/entity/area_effect_cloud_behaviour.go @@ -1,12 +1,13 @@ package entity import ( + "iter" + "time" + "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "iter" - "time" ) // AreaEffectCloudBehaviourConfig contains optional parameters for an area @@ -66,6 +67,11 @@ type AreaEffectCloudBehaviour struct { targets map[*world.EntityHandle]time.Duration } +// PortalTravelComputer returns the interdimensional travel state for the behaviour. +func (a *AreaEffectCloudBehaviour) PortalTravelComputer() *PortalTravelComputer { + return a.stationary.PortalTravelComputer() +} + // Radius returns the current radius of the area effect cloud. func (a *AreaEffectCloudBehaviour) Radius() float64 { return a.radius diff --git a/server/entity/base_behaviour.go b/server/entity/base_behaviour.go new file mode 100644 index 000000000..0f36fe0ab --- /dev/null +++ b/server/entity/base_behaviour.go @@ -0,0 +1,20 @@ +package entity + +// BaseBehaviour provides shared runtime state for Ent behaviours. Embed it +// to inherit common functionality, or forward methods to another instance. +type BaseBehaviour struct { + portalTravel *PortalTravelComputer +} + +// NewBaseBehaviour returns a BaseBehaviour initialised with the default Ent runtime behaviour. +func NewBaseBehaviour() BaseBehaviour { + return BaseBehaviour{portalTravel: NewPortalTravelComputer()} +} + +// PortalTravelComputer returns the portal travel state for a behaviour. +func (b *BaseBehaviour) PortalTravelComputer() *PortalTravelComputer { + if b.portalTravel == nil { + b.portalTravel = NewPortalTravelComputer() + } + return b.portalTravel +} diff --git a/server/entity/ent.go b/server/entity/ent.go index e3bb889fb..b74e8f8df 100644 --- a/server/entity/ent.go +++ b/server/entity/ent.go @@ -1,12 +1,13 @@ package entity import ( + "sync" + "time" + "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "sync" - "time" ) // Behaviour implements the behaviour of an Ent. @@ -21,10 +22,11 @@ type Behaviour interface { // share a lot of code. It is currently under development and is prone to // (breaking) changes. type Ent struct { - tx *world.Tx - handle *world.EntityHandle - data *world.EntityData - once sync.Once + tx *world.Tx + handle *world.EntityHandle + data *world.EntityData + deferPortalTravel bool + once sync.Once } // Open converts a world.EntityHandle to an Ent in a world.Tx. @@ -66,6 +68,15 @@ func (e *Ent) SetVelocity(v mgl64.Vec3) { e.data.Vel = v } +// Teleport teleports the entity to the position given. +func (e *Ent) Teleport(pos mgl64.Vec3) { + viewers := e.tx.Viewers(e.data.Pos) + e.data.Pos = pos + for _, v := range viewers { + v.ViewEntityTeleport(e, pos) + } +} + // Rotation returns the rotation of the entity. func (e *Ent) Rotation() cube.Rotation { return e.data.Rot @@ -118,6 +129,11 @@ func (e *Ent) SetNameTag(s string) { // Tick ticks Ent, progressing its lifetime and closing the entity if it is // in the void. func (e *Ent) Tick(tx *world.Tx, current int64) { + e.deferPortalTravel = true + defer func() { + e.deferPortalTravel = false + }() + y := e.data.Pos[1] if y < float64(tx.Range()[0]) && current%10 == 0 { _ = e.Close() @@ -125,9 +141,17 @@ func (e *Ent) Tick(tx *world.Tx, current int64) { } e.SetOnFire(e.OnFireDuration() - time.Second/20) - if m := e.Behaviour().Tick(e, tx); m != nil { + m := e.Behaviour().Tick(e, tx) + if e.finishPendingPortalTravel(tx) { + return + } + if m != nil { m.Send() } + if e.checkPortalInsiders() && e.finishPendingPortalTravel(tx) { + return + } + e.stopPortalContact() e.data.Age += time.Second / 20 } diff --git a/server/entity/ent_portal.go b/server/entity/ent_portal.go new file mode 100644 index 000000000..392f6d650 --- /dev/null +++ b/server/entity/ent_portal.go @@ -0,0 +1,74 @@ +package entity + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" +) + +// TravelThroughPortal handles the entity touching a portal block. +func (e *Ent) TravelThroughPortal(tx *world.Tx, target world.Dimension) { + if tc := e.portalTravelComputer(); tc != nil { + if e.deferPortalTravel { + tc.queuePortalTravel(tx, target) + return + } + tc.EnterPortal(e, tx, target) + } +} + +// portalTravelComputer returns the behaviour's portal travel state, if any. +func (e *Ent) portalTravelComputer() *PortalTravelComputer { + if b, ok := e.Behaviour().(portalTravelComputerProvider); ok { + return b.PortalTravelComputer() + } + return nil +} + +// stopPortalContact resets portal contact state when no portal was touched. +func (e *Ent) stopPortalContact() { + if tc := e.portalTravelComputer(); tc != nil { + tc.StopPortalContact() + } +} + +// pendingPortalTravel reports whether this tick queued terminal portal travel. +func (e *Ent) pendingPortalTravel() bool { + if tc := e.portalTravelComputer(); tc != nil { + return tc.hasPendingPortalTravel() + } + return false +} + +// finishPendingPortalTravel starts queued terminal portal travel, if present. +func (e *Ent) finishPendingPortalTravel(tx *world.Tx) bool { + if tc := e.portalTravelComputer(); tc != nil { + return tc.finishPendingPortalTravel(e, tx) + } + return false +} + +type portalBlock interface { + Portal() world.Dimension +} + +// checkPortalInsiders checks whether the entity is inside portal blocks. +// Other EntityInsider blocks are intentionally left to entity physics. +func (e *Ent) checkPortalInsiders() bool { + box := e.H().Type().BBox(e).Translate(e.Position()).Grow(-0.0001) + low, high := cube.PosFromVec3(box.Min()), cube.PosFromVec3(box.Max()) + + for y := low[1]; y <= high[1]; y++ { + for x := low[0]; x <= high[0]; x++ { + for z := low[2]; z <= high[2]; z++ { + blockPos := cube.Pos{x, y, z} + if p, ok := e.tx.Block(blockPos).(portalBlock); ok { + e.TravelThroughPortal(e.tx, p.Portal()) + if e.pendingPortalTravel() { + return true + } + } + } + } + } + return false +} diff --git a/server/entity/experience_orb_behaviour.go b/server/entity/experience_orb_behaviour.go index f90453ede..eea158ddc 100644 --- a/server/entity/experience_orb_behaviour.go +++ b/server/entity/experience_orb_behaviour.go @@ -1,11 +1,12 @@ package entity import ( + "math" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math" - "time" ) // ExperienceOrbBehaviourConfig holds optional parameters for the creation of @@ -47,14 +48,18 @@ func (conf ExperienceOrbBehaviourConfig) New() *ExperienceOrbBehaviour { // ExperienceOrbBehaviour implements Behaviour for an experience orb entity. type ExperienceOrbBehaviour struct { - conf ExperienceOrbBehaviourConfig - + conf ExperienceOrbBehaviourConfig passive *PassiveBehaviour lastSearch time.Time target *world.EntityHandle } +// PortalTravelComputer returns the interdimensional travel state for the behaviour. +func (exp *ExperienceOrbBehaviour) PortalTravelComputer() *PortalTravelComputer { + return exp.passive.PortalTravelComputer() +} + // Experience returns the amount of experience the orb carries. func (exp *ExperienceOrbBehaviour) Experience() int { return exp.conf.Experience diff --git a/server/entity/falling_block_behaviour.go b/server/entity/falling_block_behaviour.go index 7ea9ea84f..1b346550a 100644 --- a/server/entity/falling_block_behaviour.go +++ b/server/entity/falling_block_behaviour.go @@ -48,6 +48,11 @@ type FallingBlockBehaviour struct { block world.Block } +// PortalTravelComputer returns the interdimensional travel state for the behaviour. +func (f *FallingBlockBehaviour) PortalTravelComputer() *PortalTravelComputer { + return f.passive.PortalTravelComputer() +} + // Block returns the world.Block of the entity. func (f *FallingBlockBehaviour) Block() world.Block { return f.block diff --git a/server/entity/firework_behaviour.go b/server/entity/firework_behaviour.go index a09a09f55..ae7f68c42 100644 --- a/server/entity/firework_behaviour.go +++ b/server/entity/firework_behaviour.go @@ -51,6 +51,11 @@ type FireworkBehaviour struct { passive *PassiveBehaviour } +// PortalTravelComputer returns the interdimensional travel state for the behaviour. +func (f *FireworkBehaviour) PortalTravelComputer() *PortalTravelComputer { + return f.passive.PortalTravelComputer() +} + // Firework returns the underlying item.Firework of the FireworkBehaviour. func (f *FireworkBehaviour) Firework() item.Firework { return f.conf.Firework diff --git a/server/entity/item_behaviour.go b/server/entity/item_behaviour.go index 807506459..01e2d0f08 100644 --- a/server/entity/item_behaviour.go +++ b/server/entity/item_behaviour.go @@ -66,6 +66,11 @@ type ItemBehaviour struct { pickupDelay time.Duration } +// PortalTravelComputer returns the interdimensional travel state for the behaviour. +func (i *ItemBehaviour) PortalTravelComputer() *PortalTravelComputer { + return i.passive.PortalTravelComputer() +} + // Item returns the item.Stack held by the entity. func (i *ItemBehaviour) Item() item.Stack { return i.i diff --git a/server/entity/passive.go b/server/entity/passive.go index d1d8b500a..3a0f16bef 100644 --- a/server/entity/passive.go +++ b/server/entity/passive.go @@ -36,17 +36,24 @@ func (conf PassiveBehaviourConfig) New() *PassiveBehaviour { if conf.ExistenceDuration == 0 { conf.ExistenceDuration = math.MaxInt64 } - return &PassiveBehaviour{conf: conf, fuse: conf.ExistenceDuration, mc: &MovementComputer{ - Gravity: conf.Gravity, - Drag: conf.Drag, - DragBeforeGravity: true, - }} + return &PassiveBehaviour{ + BaseBehaviour: NewBaseBehaviour(), + conf: conf, + fuse: conf.ExistenceDuration, + mc: &MovementComputer{ + Gravity: conf.Gravity, + Drag: conf.Drag, + DragBeforeGravity: true, + }, + } } // PassiveBehaviour implements Behaviour for entities that act passively. This // means that they can move, but only under influence of the environment, which // includes, for example, falling, and flowing water. type PassiveBehaviour struct { + BaseBehaviour + conf PassiveBehaviourConfig mc *MovementComputer diff --git a/server/entity/projectile.go b/server/entity/projectile.go index 2997cbd7d..4269bf12c 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -92,16 +92,24 @@ func (conf ProjectileBehaviourConfig) New() *ProjectileBehaviour { if conf.ParticleCount == 0 && conf.Particle != nil { conf.ParticleCount = 1 } - return &ProjectileBehaviour{conf: conf, collided: conf.CollisionPosition != cube.Pos{}, collisionPos: conf.CollisionPosition, mc: &MovementComputer{ - Gravity: conf.Gravity, - Drag: conf.Drag, - DragBeforeGravity: true, - }} + return &ProjectileBehaviour{ + BaseBehaviour: NewBaseBehaviour(), + conf: conf, + collided: conf.CollisionPosition != cube.Pos{}, + collisionPos: conf.CollisionPosition, + mc: &MovementComputer{ + Gravity: conf.Gravity, + Drag: conf.Drag, + DragBeforeGravity: true, + }, + } } // ProjectileBehaviour implements the behaviour of projectiles. Its specifics // may be configured using ProjectileBehaviourConfig. type ProjectileBehaviour struct { + BaseBehaviour + conf ProjectileBehaviourConfig mc *MovementComputer ageCollided int diff --git a/server/entity/stationary.go b/server/entity/stationary.go index 3aaecd18c..e399f3184 100644 --- a/server/entity/stationary.go +++ b/server/entity/stationary.go @@ -31,13 +31,15 @@ func (conf StationaryBehaviourConfig) New() *StationaryBehaviour { if conf.ExistenceDuration == 0 { conf.ExistenceDuration = math.MaxInt64 } - return &StationaryBehaviour{conf: conf} + return &StationaryBehaviour{BaseBehaviour: NewBaseBehaviour(), conf: conf} } // StationaryBehaviour implements the behaviour of an entity that is unable to // move, such as a text entity or an area effect cloud. Applying velocity to // such entities will not move them. type StationaryBehaviour struct { + BaseBehaviour + conf StationaryBehaviourConfig close bool } diff --git a/server/entity/travel.go b/server/entity/travel.go index 6c92a63dd..d57ede60a 100644 --- a/server/entity/travel.go +++ b/server/entity/travel.go @@ -10,10 +10,12 @@ import ( "github.com/go-gl/mathgl/mgl64" ) -// TravelComputer handles the interdimensional travelling of an entity. -type TravelComputer struct { +// PortalTravelComputer handles portal-triggered interdimensional travel for an entity. +type PortalTravelComputer struct { // Instantaneous returns true if the entity should skip the portal wait timer. Players use this for Creative mode. Instantaneous func() bool + // Teleport teleports the entity to the final portal position. If nil, Traveller.Teleport is used. + Teleport func(e Traveller, pos mgl64.Vec3) mu sync.Mutex start time.Time @@ -21,6 +23,16 @@ type TravelComputer struct { awaitingTravel bool travelling bool timedOut bool + pending *world.World +} + +// NewPortalTravelComputer creates a PortalTravelComputer for instant portal travel. +func NewPortalTravelComputer() *PortalTravelComputer { + return &PortalTravelComputer{Instantaneous: func() bool { return true }} +} + +type portalTravelComputerProvider interface { + PortalTravelComputer() *PortalTravelComputer } // Traveller represents a world.Entity that can travel between dimensions. @@ -30,13 +42,33 @@ type Traveller interface { Teleport(pos mgl64.Vec3) } +type portalTravelHandler interface { + HandlePortalTravel(source, destination world.Dimension) +} + // EnterPortal handles an entity touching a portal block. It teleports the entity to the other dimension after four // seconds or instantly if instantaneous is true. -func (t *TravelComputer) EnterPortal(e Traveller, tx *world.Tx, target world.Dimension) { +func (t *PortalTravelComputer) EnterPortal(e Traveller, tx *world.Tx, target world.Dimension) { + if destination := t.enterPortal(tx, target); destination != nil { + t.travelQueued(e, tx, destination) + } +} + +// queuePortalTravel records portal travel to be completed by a terminal Ent tick step. +func (t *PortalTravelComputer) queuePortalTravel(tx *world.Tx, target world.Dimension) { + if destination := t.enterPortal(tx, target); destination != nil { + t.mu.Lock() + t.pending = destination + t.mu.Unlock() + } +} + +// enterPortal updates portal contact state and returns the destination world if travel should start. +func (t *PortalTravelComputer) enterPortal(tx *world.Tx, target world.Dimension) *world.World { source := tx.World() destination := source.PortalDestination(target) if destination == source { - return + return nil } t.mu.Lock() @@ -44,7 +76,7 @@ func (t *TravelComputer) EnterPortal(e Traveller, tx *world.Tx, target world.Dim if t.timedOut { // Timed out, we can't travel through portals. t.mu.Unlock() - return + return nil } travelNow := t.instantaneous() || (t.awaitingTravel && time.Since(t.start) >= time.Second*4) if !travelNow && !t.awaitingTravel { @@ -53,44 +85,106 @@ func (t *TravelComputer) EnterPortal(e Traveller, tx *world.Tx, target world.Dim t.mu.Unlock() if travelNow { - t.travel(e, source, destination) + return destination } + return nil } -func (t *TravelComputer) instantaneous() bool { +func (t *PortalTravelComputer) instantaneous() bool { return t.Instantaneous != nil && t.Instantaneous() } -// StopTravelling resets the travel timer if the entity was not inside a portal this tick. -func (t *TravelComputer) StopTravelling() { +// hasPendingPortalTravel reports whether portal travel was queued during this tick. +func (t *PortalTravelComputer) hasPendingPortalTravel() bool { + t.mu.Lock() + defer t.mu.Unlock() + return t.pending != nil +} + +// finishPendingPortalTravel consumes queued portal travel and starts the terminal transfer. +func (t *PortalTravelComputer) finishPendingPortalTravel(e Traveller, tx *world.Tx) bool { + t.mu.Lock() + destination := t.pending + t.pending = nil + t.mu.Unlock() + + if destination == nil { + return false + } + t.travel(e, tx, destination) + return true +} + +// StopPortalContact resets the portal timer if the entity was not inside a portal this tick. +func (t *PortalTravelComputer) StopPortalContact() { t.mu.Lock() defer t.mu.Unlock() if t.inside { t.inside = false return } - if t.travelling { + if t.travelling || t.pending != nil { return } t.timedOut, t.awaitingTravel = false, false } -// travel moves the entity to the given Nether or Overworld world and translates its current position based on the -// source world. -func (t *TravelComputer) travel(e Traveller, source *world.World, destination *world.World) { +// travel removes the entity from the current world and queues it for the given Nether or Overworld world. +func (t *PortalTravelComputer) travel(e Traveller, tx *world.Tx, destination *world.World) { + source := tx.World() if destination == nil || destination == source { return } - pos := translatePortalPosition(cube.PosFromVec3(e.Position()), source.Dimension(), destination.Dimension()) + sourceDim, destinationDim := source.Dimension(), destination.Dimension() + pos := translatePortalPosition(cube.PosFromVec3(e.Position()), sourceDim, destinationDim) t.mu.Lock() - defer t.mu.Unlock() t.travelling, t.timedOut, t.awaitingTravel = true, true, false + t.mu.Unlock() + + handle := tx.RemoveEntity(e) + if handle == nil { + t.mu.Lock() + t.travelling = false + t.mu.Unlock() + return + } go func() { - spawn := pos.Vec3Middle() + <-destination.Exec(func(tx *world.Tx) { + spawn := pos.Vec3Middle() + if netherPortal, ok := portal.FindOrCreateNetherPortal(tx, pos, 128); ok { + spawn = netherPortal.Spawn().Vec3Middle() + } + + if e, ok := tx.AddEntityAt(handle, spawn).(Traveller); ok { + t.finishTravel(e, spawn, sourceDim, destinationDim) + } + }) + + t.mu.Lock() + t.travelling = false + t.mu.Unlock() + }() +} + +// travelQueued moves the entity after the current transaction finishes. This is used by callers such as players that +// may touch a portal from the middle of a tick and continue running afterwards. +func (t *PortalTravelComputer) travelQueued(e Traveller, tx *world.Tx, destination *world.World) { + source := tx.World() + if destination == nil || destination == source { + return + } + sourceDim, destinationDim := source.Dimension(), destination.Dimension() + pos := translatePortalPosition(cube.PosFromVec3(e.Position()), sourceDim, destinationDim) + + t.mu.Lock() + t.travelling, t.timedOut, t.awaitingTravel = true, true, false + t.mu.Unlock() + + go func() { var handle *world.EntityHandle <-source.Exec(func(tx *world.Tx) { handle = tx.RemoveEntity(e) @@ -103,21 +197,47 @@ func (t *TravelComputer) travel(e Traveller, source *world.World, destination *w } <-destination.Exec(func(tx *world.Tx) { + spawn := pos.Vec3Middle() if netherPortal, ok := portal.FindOrCreateNetherPortal(tx, pos, 128); ok { spawn = netherPortal.Spawn().Vec3Middle() } - if ent, ok := tx.AddEntity(handle).(Traveller); ok { - ent.Teleport(spawn) + if e, ok := tx.AddEntityAt(handle, spawn).(Traveller); ok { + t.finishTravel(e, spawn, sourceDim, destinationDim) } }) t.mu.Lock() - defer t.mu.Unlock() t.travelling = false + t.mu.Unlock() }() } +// finishTravel runs the post-transfer portal hook and places the traveller at +// the destination spawn position. +func (t *PortalTravelComputer) finishTravel(e Traveller, pos mgl64.Vec3, source, destination world.Dimension) { + handlePortalTravel(e, source, destination) + if t.Teleport != nil { + t.Teleport(e, pos) + return + } + e.Teleport(pos) +} + +// handlePortalTravel dispatches portal travel hooks to Ent behaviours and +// non-Ent travellers that implement portalTravelHandler. +func handlePortalTravel(e Traveller, source, destination world.Dimension) { + if ent, ok := e.(*Ent); ok { + if h, ok := ent.Behaviour().(portalTravelHandler); ok { + h.HandlePortalTravel(source, destination) + } + return + } + if h, ok := e.(portalTravelHandler); ok { + h.HandlePortalTravel(source, destination) + } +} + // translatePortalPosition maps a position in the source dimension to the equivalent position in the target dimension. // Overworld coordinates are divided by 8 when crossing to the Nether and Nether coordinates are multiplied by 8 when // crossing to the Overworld; the Y coordinate is clamped to the target dimension's vertical range. diff --git a/server/entity/travel_test.go b/server/entity/travel_test.go index 923840efb..3968bcf19 100644 --- a/server/entity/travel_test.go +++ b/server/entity/travel_test.go @@ -3,25 +3,245 @@ package entity import ( "testing" "time" + + "github.com/df-mc/dragonfly/server/block" + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" ) -func TestTravelComputerStopTravelling(t *testing.T) { +func TestPortalTravelComputerStopPortalContact(t *testing.T) { t.Run("keeps timer after portal contact", func(t *testing.T) { - tc := &TravelComputer{inside: true, awaitingTravel: true, start: time.Now()} - tc.StopTravelling() + tc := &PortalTravelComputer{inside: true, awaitingTravel: true, start: time.Now()} + tc.StopPortalContact() if !tc.awaitingTravel { - t.Fatal("StopTravelling() reset travel timer after portal contact") + t.Fatal("StopPortalContact() reset travel timer after portal contact") } if tc.inside { - t.Fatal("StopTravelling() did not clear portal contact for the next tick") + t.Fatal("StopPortalContact() did not clear portal contact for the next tick") } }) t.Run("resets timer without portal contact", func(t *testing.T) { - tc := &TravelComputer{awaitingTravel: true, start: time.Now()} - tc.StopTravelling() + tc := &PortalTravelComputer{awaitingTravel: true, start: time.Now()} + tc.StopPortalContact() if tc.awaitingTravel { - t.Fatal("StopTravelling() kept travel timer without portal contact") + t.Fatal("StopPortalContact() kept travel timer without portal contact") + } + }) +} + +func TestEntProjectileTravelsThroughPortal(t *testing.T) { + var overworld, nether *world.World + overworld = world.Config{PortalDestination: func(dim world.Dimension) *world.World { + if dim == world.Nether { + return nether + } + return nil + }}.New() + nether = world.Config{Dim: world.Nether, PortalDestination: func(dim world.Dimension) *world.World { + if dim == world.Nether { + return overworld + } + return nil + }}.New() + t.Cleanup(func() { + _ = overworld.Close() + _ = nether.Close() + }) + + spawnRecorder := &entitySpawnRecorder{} + nether.Handle(spawnRecorder) + + sourcePos := mgl64.Vec3{80.5, 64, 80.5} + targetPortal := cube.Pos{10, 64, 10} + <-nether.Exec(func(tx *world.Tx) { + buildActivePortal(tx, targetPortal) + }) + + handle := world.EntitySpawnOpts{Position: sourcePos}.New(EnderPearlType, enderPearlConf) + <-overworld.Exec(func(tx *world.Tx) { + e := tx.AddEntity(handle) + (block.Portal{Axis: cube.Z}).EntityInside(cube.PosFromVec3(sourcePos), tx, e) + if _, ok := handle.Entity(tx); !ok { + t.Fatal("non-terminal portal contact removed entity before the source transaction finished") + } + }) + + waitForEntityWorld(t, handle, nether) + if entityInWorld(handle, overworld) { + t.Fatal("entity remained in the source world after portal travel") + } + if !spawnRecorder.called { + t.Fatal("destination world did not fire an entity spawn event") + } + if got, want := spawnRecorder.pos, targetPortal.Vec3Middle(); !got.ApproxEqual(want) { + t.Fatalf("destination spawn event position = %v, want %v", got, want) + } + + <-nether.Exec(func(tx *world.Tx) { + e, ok := handle.Entity(tx) + if !ok { + t.Fatal("entity was not added to the Nether") + } + if got, want := cube.PosFromVec3(e.Position()), targetPortal; got != want { + t.Fatalf("entity position after portal travel = %v, want %v", got, want) + } + ent, ok := e.(*Ent) + if !ok { + t.Fatalf("entity after portal travel has type %T, want *Ent", e) + } + projectile, ok := ent.Behaviour().(*ProjectileBehaviour) + if !ok { + t.Fatalf("entity behaviour after portal travel has type %T, want *ProjectileBehaviour", ent.Behaviour()) + } + if !projectile.PortalTravel() { + t.Fatal("projectile portal travel state was not preserved") + } + }) +} + +func TestEntTravelsThroughPortalOnTick(t *testing.T) { + var overworld, nether *world.World + overworld = world.Config{PortalDestination: func(dim world.Dimension) *world.World { + if dim == world.Nether { + return nether + } + return nil + }}.New() + nether = world.Config{Dim: world.Nether, PortalDestination: func(dim world.Dimension) *world.World { + if dim == world.Nether { + return overworld + } + return nil + }}.New() + t.Cleanup(func() { + _ = overworld.Close() + _ = nether.Close() + }) + + sourcePortal, targetPortal := cube.Pos{80, 64, 80}, cube.Pos{10, 64, 10} + <-overworld.Exec(func(tx *world.Tx) { + buildActivePortal(tx, sourcePortal) + }) + <-nether.Exec(func(tx *world.Tx) { + buildActivePortal(tx, targetPortal) + }) + + handle := world.EntitySpawnOpts{Position: sourcePortal.Vec3Middle().Sub(mgl64.Vec3{1})}.New(testMovingEntType{}, testMoveConfig{delta: mgl64.Vec3{1}}) + <-overworld.Exec(func(tx *world.Tx) { + e := tx.AddEntity(handle) + ticker, ok := e.(world.TickerEntity) + if !ok { + t.Fatalf("entity has type %T, want world.TickerEntity", e) + } + ticker.Tick(tx, 1) + }) + + waitForEntityWorld(t, handle, nether) + if entityInWorld(handle, overworld) { + t.Fatal("entity remained in the source world after tick-driven portal travel") + } + <-nether.Exec(func(tx *world.Tx) { + e, ok := handle.Entity(tx) + if !ok { + t.Fatal("entity was not added to the Nether") + } + if got, want := cube.PosFromVec3(e.Position()), targetPortal; got != want { + t.Fatalf("entity position after tick-driven portal travel = %v, want %v", got, want) + } + if got := e.(*Ent).Age(); got != 0 { + t.Fatalf("entity age after terminal portal travel tick = %v, want 0", got) } }) } + +func waitForEntityWorld(t *testing.T, handle *world.EntityHandle, w *world.World) { + t.Helper() + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + if entityInWorld(handle, w) { + return + } + time.Sleep(10 * time.Millisecond) + } + t.Fatal("timed out waiting for entity to change worlds") +} + +func entityInWorld(handle *world.EntityHandle, w *world.World) bool { + result := make(chan bool, 1) + go func() { + var ok bool + running := handle.ExecWorld(func(tx *world.Tx, _ world.Entity) { + ok = tx.World() == w + }) + result <- running && ok + }() + + select { + case ok := <-result: + return ok + case <-time.After(50 * time.Millisecond): + return false + } +} + +func buildActivePortal(tx *world.Tx, origin cube.Pos) { + for x := range 2 { + p := origin.Add(cube.Pos{0, 0, x}) + tx.SetBlock(p.Side(cube.FaceDown), block.Obsidian{}, nil) + tx.SetBlock(p.Add(cube.Pos{0, 3}), block.Obsidian{}, nil) + } + for y := range 3 { + p := origin.Add(cube.Pos{0, y}) + tx.SetBlock(p.Side(cube.FaceNorth), block.Obsidian{}, nil) + tx.SetBlock(p.Add(cube.Pos{0, 0, 2}), block.Obsidian{}, nil) + for x := range 2 { + tx.SetBlock(p.Add(cube.Pos{0, 0, x}), block.Portal{Axis: cube.Z}, nil) + } + } +} + +type entitySpawnRecorder struct { + world.NopHandler + + called bool + pos mgl64.Vec3 +} + +func (r *entitySpawnRecorder) HandleEntitySpawn(_ *world.Tx, e world.Entity) { + r.called = true + r.pos = e.Position() +} + +type testMoveConfig struct { + delta mgl64.Vec3 +} + +func (c testMoveConfig) Apply(data *world.EntityData) { + data.Data = &testMoveBehaviour{BaseBehaviour: NewBaseBehaviour(), delta: c.delta} +} + +type testMoveBehaviour struct { + BaseBehaviour + + delta mgl64.Vec3 +} + +func (b *testMoveBehaviour) Tick(e *Ent, _ *world.Tx) *Movement { + e.data.Pos = e.data.Pos.Add(b.delta) + return nil +} + +type testMovingEntType struct{} + +func (testMovingEntType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity { + return &Ent{tx: tx, handle: handle, data: data} +} + +func (testMovingEntType) EncodeEntity() string { return "minecraft:test_moving_ent" } +func (testMovingEntType) BBox(world.Entity) cube.BBox { + return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125) +} +func (testMovingEntType) DecodeNBT(map[string]any, *world.EntityData) {} +func (testMovingEntType) EncodeNBT(*world.EntityData) map[string]any { return nil } diff --git a/server/player/conf.go b/server/player/conf.go index 02bab660b..d2524f4aa 100644 --- a/server/player/conf.go +++ b/server/player/conf.go @@ -56,27 +56,19 @@ func (cfg Config) Apply(data *world.EntityData) { data.Name, data.Pos, data.Rot = conf.Name, conf.Position, conf.Rotation slot := uint32(conf.HeldSlot) pdata := &playerData{ - xuid: conf.XUID, - ui: inventory.New(54, nil), - inv: conf.Inventory, - enderChest: conf.EnderChestInventory, - offHand: conf.OffHand, - armour: conf.Armour, - hunger: newHungerManager(), - health: entity.NewHealthManager(conf.Health, conf.MaxHealth), // 20, 20 - experience: entity.NewExperienceManager(), - effects: entity.NewEffectManager(conf.Effects...), - locale: conf.Locale, - cooldowns: make(map[string]time.Time), - mc: &entity.MovementComputer{Gravity: 0.08, Drag: 0.02, DragBeforeGravity: true}, - tc: &entity.TravelComputer{ - Instantaneous: func() bool { - return cfg.GameMode == world.GameModeCreative - }, - Teleport: func(e entity.Traveller, pos mgl64.Vec3) { - e.(*Player).forceTeleport(pos) - }, - }, + xuid: conf.XUID, + ui: inventory.New(54, nil), + inv: conf.Inventory, + enderChest: conf.EnderChestInventory, + offHand: conf.OffHand, + armour: conf.Armour, + hunger: newHungerManager(), + health: entity.NewHealthManager(conf.Health, conf.MaxHealth), // 20, 20 + experience: entity.NewExperienceManager(), + effects: entity.NewEffectManager(conf.Effects...), + locale: conf.Locale, + cooldowns: make(map[string]time.Time), + mc: &entity.MovementComputer{Gravity: 0.08, Drag: 0.02, DragBeforeGravity: true}, heldSlot: &slot, gameMode: conf.GameMode, skin: conf.Skin, @@ -94,6 +86,14 @@ func (cfg Config) Apply(data *world.EntityData) { fireTicks: conf.FireTicks, fallDistance: conf.FallDistance, } + pdata.portalTravel = &entity.PortalTravelComputer{ + Instantaneous: func() bool { + return pdata.gameMode == world.GameModeCreative + }, + Teleport: func(e entity.Traveller, pos mgl64.Vec3) { + e.(*Player).forceTeleport(pos) + }, + } pdata.hunger.foodLevel, pdata.hunger.foodTick, pdata.hunger.exhaustionLevel, pdata.hunger.saturationLevel = conf.Food, conf.FoodTick, conf.Exhaustion, conf.Saturation pdata.experience.Add(conf.Experience) data.Data = pdata diff --git a/server/player/conf_test.go b/server/player/conf_test.go new file mode 100644 index 000000000..0efd37b49 --- /dev/null +++ b/server/player/conf_test.go @@ -0,0 +1,27 @@ +package player + +import ( + "testing" + + "github.com/df-mc/dragonfly/server/world" +) + +func TestPortalTravelInstantaneousUsesLiveGameMode(t *testing.T) { + var data world.EntityData + Config{GameMode: world.GameModeSurvival}.Apply(&data) + + pdata := data.Data.(*playerData) + if pdata.portalTravel.Instantaneous() { + t.Fatal("survival player had instantaneous portal travel") + } + + pdata.gameMode = world.GameModeCreative + if !pdata.portalTravel.Instantaneous() { + t.Fatal("creative player did not have instantaneous portal travel after game mode change") + } + + pdata.gameMode = world.GameModeSurvival + if pdata.portalTravel.Instantaneous() { + t.Fatal("survival player still had instantaneous portal travel after game mode change") + } +} diff --git a/server/player/player.go b/server/player/player.go index 3bc68b48c..1b422af66 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -91,8 +91,8 @@ type playerData struct { enchantSeed int64 - mc *entity.MovementComputer - tc *entity.TravelComputer + mc *entity.MovementComputer + portalTravel *entity.PortalTravelComputer collidedVertically, collidedHorizontally bool @@ -2586,12 +2586,12 @@ func (p *Player) Tick(tx *world.Tx, current int64) { p.data.Vel = mgl64.Vec3{} } - p.tc.StopTravelling() + p.portalTravel.StopPortalContact() } // TravelThroughPortal handles the player touching a portal block. func (p *Player) TravelThroughPortal(tx *world.Tx, target world.Dimension) { - p.tc.EnterPortal(p, tx, target) + p.portalTravel.EnterPortal(p, tx, target) } // ViewLayer returns the ViewLayer attached to the player's session. diff --git a/server/world/entity.go b/server/world/entity.go index dcc415ce9..3b9a5cb91 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -181,9 +181,10 @@ func (e *EntityHandle) execWorld(f func(tx *Tx, e Entity), weak bool) bool { } // We now arrive at the more complicated part. When we call e.w.Exec(), our // transaction must await earlier transactions in the world. If one of those - // earlier transactions tries to change e.w (through e.unsetAndLockWorld() - // or e.setAndUnlockWorld()), it must lock e.cond.L. This would lead to a - // deadlock, because we already have e.cond.L locked here. + // earlier transactions tries to change e.w (through e.unsetAndLockWorld(), + // e.setAndUnlockWorld(), or e.setAndUnlockWorldAt()), it must lock + // e.cond.L. This would lead to a deadlock, because we already have e.cond.L + // locked here. // We work around this with so-called "weak transactions". This is a // transaction that may be invalidated before it is executed. In this case, // this invalidation happens by setting e.worldless to true. If the @@ -262,6 +263,20 @@ func (e *EntityHandle) setAndUnlockWorld(w *World) { e.cond.Broadcast() } +// setAndUnlockWorldAt sets e's position before publishing e to the World +// passed, then broadcasts e.cond so waiters can open the entity. +func (e *EntityHandle) setAndUnlockWorldAt(w *World, pos mgl64.Vec3) { + e.cond.L.Lock() + defer e.cond.L.Unlock() + + if e.w != nil { + panic("cannot add entity to new world before removing from old world") + } + e.data.Pos = pos + e.w = w + e.cond.Broadcast() +} + // decodeNBT decodes the position, velocity, rotation, age, on-fire duration and // name tag of an entity. func (e *EntityHandle) decodeNBT(m map[string]any) { diff --git a/server/world/handler.go b/server/world/handler.go index 3af92edc6..fabfddc1e 100644 --- a/server/world/handler.go +++ b/server/world/handler.go @@ -53,7 +53,7 @@ type Handler interface { // ctx.Cancel() may be called to prevent leaves from decaying. HandleLeavesDecay(ctx *Context, pos cube.Pos) // HandleEntitySpawn handles an Entity being spawned into a World through a - // call to Tx.AddEntity. + // call to Tx.AddEntity or Tx.AddEntityAt. HandleEntitySpawn(tx *Tx, e Entity) // HandleEntityDespawn handles an Entity being despawned from a World // through a call to Tx.RemoveEntity. diff --git a/server/world/portal/nether_test.go b/server/world/portal/nether_test.go index c8e2fe035..25993643c 100644 --- a/server/world/portal/nether_test.go +++ b/server/world/portal/nether_test.go @@ -2,7 +2,6 @@ package portal_test import ( "testing" - _ "unsafe" "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block/cube" @@ -12,13 +11,6 @@ import ( "github.com/df-mc/dragonfly/server/world/portal" ) -//go:linkname finaliseBlockRegistry github.com/df-mc/dragonfly/server/world.finaliseBlockRegistry -func finaliseBlockRegistry() - -func init() { - finaliseBlockRegistry() -} - func TestNetherPortalFromPos(t *testing.T) { tests := []struct { name string diff --git a/server/world/tx.go b/server/world/tx.go index f7178555a..fc7dcb35d 100644 --- a/server/world/tx.go +++ b/server/world/tx.go @@ -198,6 +198,13 @@ func (tx *Tx) AddEntity(e *EntityHandle) Entity { return tx.World().addEntity(tx, e) } +// AddEntityAt adds an EntityHandle to a World at the position passed. The Entity will be visible to all viewers of +// the World that have the chunk at the position passed. AddEntityAt panics if the EntityHandle is already in a world. +// AddEntityAt returns the Entity created by the EntityHandle. +func (tx *Tx) AddEntityAt(e *EntityHandle, pos mgl64.Vec3) Entity { + return tx.World().addEntityAt(tx, e, pos) +} + // RemoveEntity removes an Entity from the World that is currently present in // it. Any viewers of the Entity will no longer be able to see it. // RemoveEntity returns the EntityHandle of the Entity. After removing an Entity diff --git a/server/world/world.go b/server/world/world.go index abb03a60e..7fb592d42 100644 --- a/server/world/world.go +++ b/server/world/world.go @@ -691,11 +691,16 @@ func (w *World) playSound(tx *Tx, pos mgl64.Vec3, s Sound) { // loaded. addEntity panics if the EntityHandle is already in a world. // addEntity returns the Entity created by the EntityHandle. func (w *World) addEntity(tx *Tx, handle *EntityHandle) Entity { - handle.setAndUnlockWorld(w) - pos := chunkPosFromVec3(handle.data.Pos) - w.entities[handle] = pos + return w.addEntityAt(tx, handle, handle.data.Pos) +} - c := w.chunk(pos) +// addEntityAt adds an EntityHandle to a World at the position passed. +func (w *World) addEntityAt(tx *Tx, handle *EntityHandle, pos mgl64.Vec3) Entity { + handle.setAndUnlockWorldAt(w, pos) + chunkPos := chunkPosFromVec3(handle.data.Pos) + w.entities[handle] = chunkPos + + c := w.chunk(chunkPos) c.Entities, c.modified = append(c.Entities, handle), true e := handle.mustEntity(tx) From 8bbe5fb50c9c592b0e996d36c264ec019a1d36c4 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Sat, 9 May 2026 03:08:36 -0400 Subject: [PATCH 22/41] remove test file --- server/player/conf_test.go | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 server/player/conf_test.go diff --git a/server/player/conf_test.go b/server/player/conf_test.go deleted file mode 100644 index 0efd37b49..000000000 --- a/server/player/conf_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package player - -import ( - "testing" - - "github.com/df-mc/dragonfly/server/world" -) - -func TestPortalTravelInstantaneousUsesLiveGameMode(t *testing.T) { - var data world.EntityData - Config{GameMode: world.GameModeSurvival}.Apply(&data) - - pdata := data.Data.(*playerData) - if pdata.portalTravel.Instantaneous() { - t.Fatal("survival player had instantaneous portal travel") - } - - pdata.gameMode = world.GameModeCreative - if !pdata.portalTravel.Instantaneous() { - t.Fatal("creative player did not have instantaneous portal travel after game mode change") - } - - pdata.gameMode = world.GameModeSurvival - if pdata.portalTravel.Instantaneous() { - t.Fatal("survival player still had instantaneous portal travel after game mode change") - } -} From f2779829d06bcb7dbafd80c584e28fb1b7629895 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Sat, 9 May 2026 04:23:39 -0400 Subject: [PATCH 23/41] add offset and neighborface --- server/block/cube/face.go | 21 +++++++++++++++++++++ server/block/cube/pos.go | 36 ++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/server/block/cube/face.go b/server/block/cube/face.go index d08286b64..8076733f4 100644 --- a/server/block/cube/face.go +++ b/server/block/cube/face.go @@ -1,5 +1,7 @@ package cube +import "github.com/go-gl/mathgl/mgl64" + const ( // FaceDown represents the bottom face of a block. FaceDown Face = iota @@ -89,6 +91,25 @@ func (f Face) RotateLeft() Face { return f } +// Offset returns the position offset of the Face. +func (f Face) Offset() mgl64.Vec3 { + switch f { + case FaceUp: + return mgl64.Vec3{0, 1, 0} + case FaceDown: + return mgl64.Vec3{0, -1, 0} + case FaceNorth: + return mgl64.Vec3{0, 0, -1} + case FaceSouth: + return mgl64.Vec3{0, 0, 1} + case FaceWest: + return mgl64.Vec3{-1, 0, 0} + case FaceEast: + return mgl64.Vec3{1, 0, 0} + } + panic("invalid face") +} + // String returns the Face as a string. func (f Face) String() string { switch f { diff --git a/server/block/cube/pos.go b/server/block/cube/pos.go index 2c87d8659..99bb72000 100644 --- a/server/block/cube/pos.go +++ b/server/block/cube/pos.go @@ -96,21 +96,29 @@ func (p Pos) Side(face Face) Pos { // Face returns the face that the other Pos was on compared to the current Pos. // The other Pos is assumed to be a direct neighbour of the current Pos. func (p Pos) Face(other Pos) Face { - switch other { - case p.Add(Pos{0, 1}): - return FaceUp - case p.Add(Pos{0, -1}): - return FaceDown - case p.Add(Pos{0, 0, -1}): - return FaceNorth - case p.Add(Pos{0, 0, 1}): - return FaceSouth - case p.Add(Pos{-1, 0, 0}): - return FaceWest - case p.Add(Pos{1, 0, 0}): - return FaceEast + face, _ := p.NeighbourFace(other) + return face +} + +// NeighbourFace returns the face that the other Pos was on compared to the +// current Pos, if the other Pos is a direct neighbour of the current Pos. +// Example: Pos{0, 0, 0}.NeighbourFace(Pos{0, 1, 0}) returns FaceUp, true. +func (p Pos) NeighbourFace(other Pos) (Face, bool) { + switch other.Sub(p) { + case Pos{0, 1, 0}: + return FaceUp, true + case Pos{0, -1, 0}: + return FaceDown, true + case Pos{0, 0, -1}: + return FaceNorth, true + case Pos{0, 0, 1}: + return FaceSouth, true + case Pos{-1, 0, 0}: + return FaceWest, true + case Pos{1, 0, 0}: + return FaceEast, true } - return FaceUp + return FaceUp, false } // Neighbours calls the function passed for each of the block position's From 8944d58cdc9ad789efb53a73a223cfee9fd36f4e Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Sat, 9 May 2026 04:23:47 -0400 Subject: [PATCH 24/41] use neighborface --- server/block/portal.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/block/portal.go b/server/block/portal.go index 665ecbfc9..94aabbf89 100644 --- a/server/block/portal.go +++ b/server/block/portal.go @@ -47,7 +47,11 @@ func (p Portal) EncodeBlock() (string, map[string]any) { // NeighbourUpdateTick ... func (p Portal) NeighbourUpdateTick(pos, neighbour cube.Pos, tx *world.Tx) { - axis := pos.Face(neighbour).Axis() + face, ok := pos.NeighbourFace(neighbour) + if !ok { + return + } + axis := face.Axis() if axis != cube.Y && axis != p.Axis { return } From baea2183f52a7daec5a85e60be2a59affc577e31 Mon Sep 17 00:00:00 2001 From: HashimTheArab Date: Sat, 16 May 2026 03:32:26 -0400 Subject: [PATCH 25/41] Use shared range iteration for portal scans Constraint: Preserve portal insider scan bounds while reducing bespoke loop code. Confidence: high Scope-risk: narrow Directive: Keep portal contact checks on the entity tick transaction path. Tested: go test ./server/entity; go test ./server/block/cube Not-tested: full repository test suite --- server/entity/ent_portal.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/server/entity/ent_portal.go b/server/entity/ent_portal.go index 392f6d650..ab296813a 100644 --- a/server/entity/ent_portal.go +++ b/server/entity/ent_portal.go @@ -57,16 +57,11 @@ func (e *Ent) checkPortalInsiders() bool { box := e.H().Type().BBox(e).Translate(e.Position()).Grow(-0.0001) low, high := cube.PosFromVec3(box.Min()), cube.PosFromVec3(box.Max()) - for y := low[1]; y <= high[1]; y++ { - for x := low[0]; x <= high[0]; x++ { - for z := low[2]; z <= high[2]; z++ { - blockPos := cube.Pos{x, y, z} - if p, ok := e.tx.Block(blockPos).(portalBlock); ok { - e.TravelThroughPortal(e.tx, p.Portal()) - if e.pendingPortalTravel() { - return true - } - } + for blockPos := range cube.Range3D(low, high) { + if p, ok := e.tx.Block(blockPos).(portalBlock); ok { + e.TravelThroughPortal(e.tx, p.Portal()) + if e.pendingPortalTravel() { + return true } } } From 62274835d1ef5434b487a3243e0eab12e58abf4f Mon Sep 17 00:00:00 2001 From: Hashim Date: Thu, 28 May 2026 12:46:13 -0400 Subject: [PATCH 26/41] cube/trace: add BlockIntersects and BBoxIntersects methods (#1262) --- server/block/cube/trace/bbox.go | 35 ++++++++++++++++++++++++++++++++ server/block/cube/trace/block.go | 21 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/server/block/cube/trace/bbox.go b/server/block/cube/trace/bbox.go index 249bef5bc..aef3bbf27 100644 --- a/server/block/cube/trace/bbox.go +++ b/server/block/cube/trace/bbox.go @@ -99,6 +99,41 @@ func BBoxIntercept(bb cube.BBox, start, end mgl64.Vec3) (result BBoxResult, ok b return BBoxResult{bb: bb, pos: *vec, face: f}, true } +// BBoxIntersects checks if the line segment from start to end intersects the BBox. +// Unlike BBoxIntercept, it only reports whether an intersection exists and does not +// calculate the closest hit position or face. +func BBoxIntersects(bb cube.BBox, start, end mgl64.Vec3) bool { + min, max := bb.Min(), bb.Max() + dir := end.Sub(start) + tMin, tMax := 0.0, 1.0 + + for axis := range 3 { + if mgl64.FloatEqual(dir[axis], 0) { + if start[axis] < min[axis] || start[axis] > max[axis] { + return false + } + continue + } + + inv := 1 / dir[axis] + t1 := (min[axis] - start[axis]) * inv + t2 := (max[axis] - start[axis]) * inv + if t1 > t2 { + t1, t2 = t2, t1 + } + if t1 > tMin { + tMin = t1 + } + if t2 < tMax { + tMax = t2 + } + if tMin > tMax { + return false + } + } + return true +} + // vec3OnLineWithX returns an mgl64.Vec3 on the line between mgl64.Vec3 a and b with an X value passed. If no such vec3 // could be found, the bool returned is false. func vec3OnLineWithX(a, b mgl64.Vec3, x float64) *mgl64.Vec3 { diff --git a/server/block/cube/trace/block.go b/server/block/cube/trace/block.go index e9a0f2ce3..3139151eb 100644 --- a/server/block/cube/trace/block.go +++ b/server/block/cube/trace/block.go @@ -2,6 +2,7 @@ package trace import ( "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" "math" @@ -70,3 +71,23 @@ func BlockIntercept(pos cube.Pos, src world.BlockSource, b world.Block, start, e return BlockResult{bb: hit.BBox(), pos: hit.Position(), face: hit.Face(), blockPos: pos}, true } + +// BlockIntersects checks if the line segment from start to end intersects the block model of b at pos. Unlike +// BlockIntercept, it only reports whether an intersection exists and does not calculate the closest hit position, face, +// or bounding box. +func BlockIntersects(pos cube.Pos, src world.BlockSource, b world.Block, start, end mgl64.Vec3) bool { + m := b.Model() + switch m.(type) { + case model.Empty: + return false + case model.Solid: + return BBoxIntersects(cube.Box(0, 0, 0, 1, 1, 1).Translate(pos.Vec3()), start, end) + } + + for _, bb := range m.BBox(pos, src) { + if BBoxIntersects(bb.Translate(pos.Vec3()), start, end) { + return true + } + } + return false +} From bb1a7a77d27272cbfb32a44801f42c782da1a8fc Mon Sep 17 00:00:00 2001 From: Hashim Date: Thu, 28 May 2026 13:09:23 -0400 Subject: [PATCH 27/41] world/chunk: add Clone methods to chunk, subchunk, and palette (#1264) --- server/world/chunk/chunk.go | 20 +++++++++++++++++ server/world/chunk/palette.go | 11 ++++++++++ server/world/chunk/paletted_storage.go | 8 ++++++- server/world/chunk/sub_chunk.go | 30 ++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/server/world/chunk/chunk.go b/server/world/chunk/chunk.go index 17ef1f10f..2d09ccd51 100644 --- a/server/world/chunk/chunk.go +++ b/server/world/chunk/chunk.go @@ -49,6 +49,26 @@ func New(br BlockRegistry, r cube.Range) *Chunk { } } +// Clone returns an independent copy of the Chunk. +func (chunk *Chunk) Clone() *Chunk { + clone := &Chunk{ + r: chunk.r, + br: chunk.br, + air: chunk.air, + recalculateHeightMap: chunk.recalculateHeightMap, + heightMap: slices.Clone(chunk.heightMap), + sub: make([]*SubChunk, len(chunk.sub)), + biomes: make([]*PalettedStorage, len(chunk.biomes)), + } + for i, sub := range chunk.sub { + clone.sub[i] = sub.Clone() + } + for i, biomes := range chunk.biomes { + clone.biomes[i] = biomes.Clone() + } + return clone +} + // Equals returns if the chunk passed is equal to the current one func (chunk *Chunk) Equals(c *Chunk) bool { if !chunk.recalculateHeightMap && !c.recalculateHeightMap && !slices.Equal(c.heightMap, chunk.heightMap) { diff --git a/server/world/chunk/palette.go b/server/world/chunk/palette.go index 3b3d32b17..f3121f4f5 100644 --- a/server/world/chunk/palette.go +++ b/server/world/chunk/palette.go @@ -2,6 +2,7 @@ package chunk import ( "math" + "slices" ) // paletteSize is the size of a palette. It indicates the amount of bits occupied per value stored. @@ -23,6 +24,16 @@ func newPalette(size paletteSize, values []uint32) *Palette { return &Palette{size: size, values: values, last: math.MaxUint32} } +// Clone returns an independent copy of the Palette. +func (palette *Palette) Clone() *Palette { + return &Palette{ + last: palette.last, + lastIndex: palette.lastIndex, + size: palette.size, + values: slices.Clone(palette.values), + } +} + // Len returns the amount of unique values in the Palette. func (palette *Palette) Len() int { return len(palette.values) diff --git a/server/world/chunk/paletted_storage.go b/server/world/chunk/paletted_storage.go index 92153e7fb..ac49c67c4 100644 --- a/server/world/chunk/paletted_storage.go +++ b/server/world/chunk/paletted_storage.go @@ -2,6 +2,7 @@ package chunk import ( "bytes" + "slices" "unsafe" ) @@ -59,6 +60,11 @@ func emptyStorage(v uint32) *PalettedStorage { return newPalettedStorage([]uint32{}, newPalette(0, []uint32{v})) } +// Clone returns an independent copy of the PalettedStorage. +func (storage *PalettedStorage) Clone() *PalettedStorage { + return newPalettedStorage(slices.Clone(storage.indices), storage.palette.Clone()) +} + // Palette returns the Palette of the PalettedStorage. func (storage *PalettedStorage) Palette() *Palette { return storage.palette @@ -164,7 +170,7 @@ func (storage *PalettedStorage) resize(newPaletteSize paletteSize) { // relatively heavy task which should only happen right before the sub chunk holding this PalettedStorage is // saved to disk. compact also shrinks the palette size if possible. func (storage *PalettedStorage) compact() { - if storage.palette == nil || storage.palette.Len() == 0 { + if storage.palette.Len() == 0 { return } if storage.palette.Len() == 1 { diff --git a/server/world/chunk/sub_chunk.go b/server/world/chunk/sub_chunk.go index 21436cecb..7a06680de 100644 --- a/server/world/chunk/sub_chunk.go +++ b/server/world/chunk/sub_chunk.go @@ -1,5 +1,7 @@ package chunk +import "slices" + // SubChunk is a cube of blocks located in a chunk. It has a size of 16x16x16 blocks and forms part of a stack // that forms a Chunk. type SubChunk struct { @@ -29,6 +31,34 @@ func NewSubChunk(air uint32) *SubChunk { return &SubChunk{air: air} } +// Clone returns an independent copy of the SubChunk. +func (sub *SubChunk) Clone() *SubChunk { + clone := &SubChunk{ + air: sub.air, + storages: make([]*PalettedStorage, len(sub.storages)), + blockLight: cloneLight(sub.blockLight), + skyLight: cloneLight(sub.skyLight), + } + for i, storage := range sub.storages { + clone.storages[i] = storage.Clone() + } + return clone +} + +func cloneLight(light []uint8) []uint8 { + if len(light) == 0 { + return slices.Clone(light) + } + switch &light[0] { + case noLightPtr: + return noLight + case fullLightPtr: + return fullLight + default: + return slices.Clone(light) + } +} + // Empty checks if the SubChunk is considered empty. This is the case if the SubChunk has 0 block storages or if it has // a single one that is completely filled with air. func (sub *SubChunk) Empty() bool { From c08da5722911791a34d9fe91095126adb624a2e0 Mon Sep 17 00:00:00 2001 From: Dasciam <122279000+Dasciam@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:24:49 +0300 Subject: [PATCH 28/41] block/grass.go: Fix bone meal duplication glitch (#1266) --- server/block/grass.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/block/grass.go b/server/block/grass.go index 10dd0e1aa..56d1286a8 100644 --- a/server/block/grass.go +++ b/server/block/grass.go @@ -1,9 +1,10 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" - "math/rand/v2" ) // Grass blocks generate abundantly across the surface of the world. @@ -89,7 +90,7 @@ func (g Grass) BoneMeal(pos cube.Pos, tx *world.Tx) bool { } } - return false + return true } // BreakInfo ... From 64d40fd85e81b3f6aa53fcc826f131f7c47a8d0e Mon Sep 17 00:00:00 2001 From: Dasciam <122279000+Dasciam@users.noreply.github.com> Date: Tue, 2 Jun 2026 02:37:56 +0300 Subject: [PATCH 29/41] server/block: Implement bonemeal huge growth particles (#1267) --- server/block/beetroot_seeds.go | 8 ++++---- server/block/carrot.go | 6 +++--- server/block/cocoa_bean.go | 9 +++++---- server/block/double_flower.go | 4 ++-- server/block/fern.go | 6 +++--- server/block/flower.go | 10 ++++++---- server/block/grass.go | 10 ++++++---- server/block/kelp.go | 9 +++++---- server/block/melon_seeds.go | 9 +++++---- server/block/pink_petals.go | 6 +++--- server/block/potato.go | 6 +++--- server/block/pumpkin_seeds.go | 9 +++++---- server/block/sea_pickle.go | 13 +++++++------ server/block/short_grass.go | 6 +++--- server/block/sugar_cane.go | 9 +++++---- server/block/wheat_seeds.go | 6 +++--- server/item/bone_meal.go | 26 +++++++++++++++++++++++--- server/session/world.go | 1 + server/world/particle/block.go | 12 ++++++++++-- 19 files changed, 102 insertions(+), 63 deletions(-) diff --git a/server/block/beetroot_seeds.go b/server/block/beetroot_seeds.go index b0f6516df..f6f13ddf8 100644 --- a/server/block/beetroot_seeds.go +++ b/server/block/beetroot_seeds.go @@ -21,16 +21,16 @@ func (BeetrootSeeds) SameCrop(c Crop) bool { } // BoneMeal ... -func (b BeetrootSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (b BeetrootSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if b.Growth == 7 { - return false + return item.BoneMealResultNone } if rand.Float64() < 0.75 { b.Growth++ tx.SetBlock(pos, b, nil) - return true + return item.BoneMealResultSmall } - return false + return item.BoneMealResultNone } // UseOnBlock ... diff --git a/server/block/carrot.go b/server/block/carrot.go index c7d559e4a..834fa35aa 100644 --- a/server/block/carrot.go +++ b/server/block/carrot.go @@ -38,13 +38,13 @@ func (c Carrot) Consume(_ *world.Tx, co item.Consumer) item.Stack { } // BoneMeal ... -func (c Carrot) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (c Carrot) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if c.Growth == 7 { - return false + return item.BoneMealResultNone } c.Growth = min(c.Growth+rand.IntN(4)+2, 7) tx.SetBlock(pos, c, nil) - return true + return item.BoneMealResultSmall } // UseOnBlock ... diff --git a/server/block/cocoa_bean.go b/server/block/cocoa_bean.go index bc2a4048b..bf19b0716 100644 --- a/server/block/cocoa_bean.go +++ b/server/block/cocoa_bean.go @@ -1,12 +1,13 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // CocoaBean is a crop block found in jungle biomes. @@ -20,13 +21,13 @@ type CocoaBean struct { } // BoneMeal ... -func (c CocoaBean) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (c CocoaBean) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if c.Age == 2 { - return false + return item.BoneMealResultNone } c.Age++ tx.SetBlock(pos, c, nil) - return true + return item.BoneMealResultSmall } // HasLiquidDrops ... diff --git a/server/block/double_flower.go b/server/block/double_flower.go index c95748493..00dbc7d28 100644 --- a/server/block/double_flower.go +++ b/server/block/double_flower.go @@ -24,9 +24,9 @@ func (d DoubleFlower) FlammabilityInfo() FlammabilityInfo { } // BoneMeal ... -func (d DoubleFlower) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (d DoubleFlower) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { dropItem(tx, item.NewStack(d, 1), pos.Vec3Centre()) - return true + return item.BoneMealResultSmall } // NeighbourUpdateTick ... diff --git a/server/block/fern.go b/server/block/fern.go index 4317feb93..88479a50d 100644 --- a/server/block/fern.go +++ b/server/block/fern.go @@ -25,14 +25,14 @@ func (g Fern) BreakInfo() BreakInfo { } // BoneMeal attempts to affect the block using a bone meal item. -func (g Fern) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (g Fern) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { upper := DoubleTallGrass{Type: FernDoubleTallGrass(), UpperPart: true} if replaceableWith(tx, pos.Side(cube.FaceUp), upper) { tx.SetBlock(pos, DoubleTallGrass{Type: FernDoubleTallGrass()}, nil) tx.SetBlock(pos.Side(cube.FaceUp), upper, nil) - return true + return item.BoneMealResultSmall } - return false + return item.BoneMealResultNone } // CompostChance ... diff --git a/server/block/flower.go b/server/block/flower.go index a294444e6..3cccb320d 100644 --- a/server/block/flower.go +++ b/server/block/flower.go @@ -1,13 +1,14 @@ package block import ( + "math/rand/v2" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" - "time" ) // Flower is a non-solid plant that occur in a variety of shapes and colours. They are primarily used for decoration @@ -32,7 +33,8 @@ func (f Flower) EntityInside(_ cube.Pos, _ *world.Tx, e world.Entity) { } // BoneMeal ... -func (f Flower) BoneMeal(pos cube.Pos, tx *world.Tx) (success bool) { +func (f Flower) BoneMeal(pos cube.Pos, tx *world.Tx) (result item.BoneMealResult) { + result = item.BoneMealResultNone if f.Type == WitherRose() { return } @@ -54,7 +56,7 @@ func (f Flower) BoneMeal(pos cube.Pos, tx *world.Tx) (success bool) { } } tx.SetBlock(p, Flower{Type: flowerType}, nil) - success = true + result = item.BoneMealResultArea } return } diff --git a/server/block/grass.go b/server/block/grass.go index 56d1286a8..dade87ce7 100644 --- a/server/block/grass.go +++ b/server/block/grass.go @@ -4,6 +4,7 @@ import ( "math/rand/v2" "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" ) @@ -79,18 +80,19 @@ func (g Grass) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { } // BoneMeal ... -func (g Grass) BoneMeal(pos cube.Pos, tx *world.Tx) bool { - for i := 0; i < 14; i++ { +func (g Grass) BoneMeal(pos cube.Pos, tx *world.Tx) (result item.BoneMealResult) { + result = item.BoneMealResultNone + for range 14 { c := pos.Add(cube.Pos{rand.IntN(6) - 3, 0, rand.IntN(6) - 3}) above := c.Side(cube.FaceUp) _, air := tx.Block(above).(Air) _, grass := tx.Block(c).(Grass) if air && grass { tx.SetBlock(above, plantSelection[rand.IntN(len(plantSelection))], nil) + result = item.BoneMealResultArea } } - - return true + return } // BreakInfo ... diff --git a/server/block/kelp.go b/server/block/kelp.go index 27c03af13..7140bbfb5 100644 --- a/server/block/kelp.go +++ b/server/block/kelp.go @@ -1,11 +1,12 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // Kelp is an underwater block which can grow on top of solids underwater. @@ -24,7 +25,7 @@ func (k Kelp) SmeltInfo() item.SmeltInfo { } // BoneMeal ... -func (k Kelp) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (k Kelp) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { for y := pos.Y(); y <= tx.Range()[1]; y++ { currentPos := cube.Pos{pos.X(), y, pos.Z()} block := tx.Block(currentPos) @@ -36,11 +37,11 @@ func (k Kelp) BoneMeal(pos cube.Pos, tx *world.Tx) bool { } if water, ok := block.(Water); ok && water.Depth == 8 { tx.SetBlock(currentPos, Kelp{Age: k.Age + 1}, nil) - return true + return item.BoneMealResultSmall } break } - return false + return item.BoneMealResultNone } // BreakInfo ... diff --git a/server/block/melon_seeds.go b/server/block/melon_seeds.go index 68bbaa2d7..2fcfeece4 100644 --- a/server/block/melon_seeds.go +++ b/server/block/melon_seeds.go @@ -1,11 +1,12 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // MelonSeeds grow melon blocks. @@ -62,13 +63,13 @@ func (m MelonSeeds) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { } // BoneMeal ... -func (m MelonSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (m MelonSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if m.Growth == 7 { - return false + return item.BoneMealResultNone } m.Growth = min(m.Growth+rand.IntN(4)+2, 7) tx.SetBlock(pos, m, nil) - return true + return item.BoneMealResultSmall } // UseOnBlock ... diff --git a/server/block/pink_petals.go b/server/block/pink_petals.go index 1df913167..3093836c0 100644 --- a/server/block/pink_petals.go +++ b/server/block/pink_petals.go @@ -21,14 +21,14 @@ type PinkPetals struct { } // BoneMeal ... -func (p PinkPetals) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (p PinkPetals) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if p.AdditionalCount < 3 { p.AdditionalCount++ tx.SetBlock(pos, p, nil) - return true + return item.BoneMealResultSmall } dropItem(tx, item.NewStack(p, 1), pos.Vec3Centre()) - return true + return item.BoneMealResultSmall } // UseOnBlock ... diff --git a/server/block/potato.go b/server/block/potato.go index ecb9e3b5f..266c36a1d 100644 --- a/server/block/potato.go +++ b/server/block/potato.go @@ -43,13 +43,13 @@ func (p Potato) Consume(_ *world.Tx, c item.Consumer) item.Stack { } // BoneMeal ... -func (p Potato) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (p Potato) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if p.Growth == 7 { - return false + return item.BoneMealResultNone } p.Growth = min(p.Growth+rand.IntN(4)+2, 7) tx.SetBlock(pos, p, nil) - return true + return item.BoneMealResultSmall } // UseOnBlock ... diff --git a/server/block/pumpkin_seeds.go b/server/block/pumpkin_seeds.go index 5a62c6507..e14be3a13 100644 --- a/server/block/pumpkin_seeds.go +++ b/server/block/pumpkin_seeds.go @@ -1,11 +1,12 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // PumpkinSeeds grow pumpkin blocks. @@ -62,13 +63,13 @@ func (p PumpkinSeeds) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) { } // BoneMeal ... -func (p PumpkinSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (p PumpkinSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if p.Growth == 7 { - return false + return item.BoneMealResultNone } p.Growth = min(p.Growth+rand.IntN(4)+2, 7) tx.SetBlock(pos, p, nil) - return true + return item.BoneMealResultSmall } // UseOnBlock ... diff --git a/server/block/sea_pickle.go b/server/block/sea_pickle.go index 3b6acbc87..96df4d893 100644 --- a/server/block/sea_pickle.go +++ b/server/block/sea_pickle.go @@ -1,12 +1,13 @@ package block import ( + "math" + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math" - "math/rand/v2" ) // SeaPickle is a small stationary underwater block that emits light, and is typically found in colonies of up to @@ -41,12 +42,12 @@ func (SeaPickle) canSurvive(pos cube.Pos, tx *world.Tx) bool { } // BoneMeal ... -func (s SeaPickle) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (s SeaPickle) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if s.Dead { - return false + return item.BoneMealResultNone } if coral, ok := tx.Block(pos.Side(cube.FaceDown)).(CoralBlock); !ok || coral.Dead { - return false + return item.BoneMealResultNone } if s.AdditionalCount != 3 { @@ -74,7 +75,7 @@ func (s SeaPickle) BoneMeal(pos cube.Pos, tx *world.Tx) bool { } } - return true + return item.BoneMealResultSmall } // UseOnBlock ... diff --git a/server/block/short_grass.go b/server/block/short_grass.go index 6f74cb832..edf6f3f70 100644 --- a/server/block/short_grass.go +++ b/server/block/short_grass.go @@ -27,14 +27,14 @@ func (g ShortGrass) BreakInfo() BreakInfo { } // BoneMeal attempts to affect the block using a bone meal item. -func (g ShortGrass) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (g ShortGrass) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { upper := DoubleTallGrass{Type: NormalDoubleTallGrass(), UpperPart: true} if replaceableWith(tx, pos.Side(cube.FaceUp), upper) { tx.SetBlock(pos, DoubleTallGrass{Type: NormalDoubleTallGrass()}, nil) tx.SetBlock(pos.Side(cube.FaceUp), upper, nil) - return true + return item.BoneMealResultSmall } - return false + return item.BoneMealResultNone } // CompostChance ... diff --git a/server/block/sugar_cane.go b/server/block/sugar_cane.go index 1c6e21c2a..0f7b467f7 100644 --- a/server/block/sugar_cane.go +++ b/server/block/sugar_cane.go @@ -1,11 +1,12 @@ package block import ( + "math/rand/v2" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "math/rand/v2" ) // SugarCane is a plant block that generates naturally near water. @@ -63,7 +64,7 @@ func (c SugarCane) RandomTick(pos cube.Pos, tx *world.Tx, _ *rand.Rand) { } // BoneMeal ... -func (c SugarCane) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (c SugarCane) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { for _, ok := tx.Block(pos.Side(cube.FaceDown)).(SugarCane); ok; _, ok = tx.Block(pos.Side(cube.FaceDown)).(SugarCane) { pos = pos.Side(cube.FaceDown) } @@ -73,9 +74,9 @@ func (c SugarCane) BoneMeal(pos cube.Pos, tx *world.Tx) bool { tx.SetBlock(pos.Add(cube.Pos{0, y}), SugarCane{}, nil) } } - return true + return item.BoneMealResultSmall } - return false + return item.BoneMealResultNone } // canGrowHere implements logic to check if sugar cane can live/grow here. diff --git a/server/block/wheat_seeds.go b/server/block/wheat_seeds.go index 4103a306b..549f4ceb7 100644 --- a/server/block/wheat_seeds.go +++ b/server/block/wheat_seeds.go @@ -21,13 +21,13 @@ func (WheatSeeds) SameCrop(c Crop) bool { } // BoneMeal ... -func (s WheatSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) bool { +func (s WheatSeeds) BoneMeal(pos cube.Pos, tx *world.Tx) item.BoneMealResult { if s.Growth == 7 { - return false + return item.BoneMealResultNone } s.Growth = min(s.Growth+rand.IntN(4)+2, 7) tx.SetBlock(pos, s, nil) - return true + return item.BoneMealResultSmall } // UseOnBlock ... diff --git a/server/item/bone_meal.go b/server/item/bone_meal.go index ec1151da0..9e7deea18 100644 --- a/server/item/bone_meal.go +++ b/server/item/bone_meal.go @@ -7,20 +7,40 @@ import ( "github.com/go-gl/mathgl/mgl64" ) +// BoneMealResult represents the outcome of a bone meal interaction with a block, +// determining the intensity of the particle effect displayed. +type BoneMealResult int + +const ( + // BoneMealResultNone indicates that the bone meal had no effect on the block. + BoneMealResultNone BoneMealResult = iota + // BoneMealResultSmall indicates a minor growth effect, produces a small particle burst. + BoneMealResultSmall + // BoneMealResultArea indicates a significant growth effect over an area, produces a large particle burst. + BoneMealResultArea +) + // BoneMeal is an item used to force growth in plants & crops. type BoneMeal struct{} // BoneMealAffected represents a block that is affected when bone meal is used on it. type BoneMealAffected interface { // BoneMeal attempts to affect the block using a bone meal item. - BoneMeal(pos cube.Pos, tx *world.Tx) bool + BoneMeal(pos cube.Pos, tx *world.Tx) BoneMealResult } // UseOnBlock ... func (b BoneMeal) UseOnBlock(pos cube.Pos, _ cube.Face, _ mgl64.Vec3, tx *world.Tx, _ User, ctx *UseContext) bool { - if bm, ok := tx.Block(pos).(BoneMealAffected); ok && bm.BoneMeal(pos, tx) { + if bm, ok := tx.Block(pos).(BoneMealAffected); ok { + result := bm.BoneMeal(pos, tx) + if result == BoneMealResultNone { + return false + } + ctx.SubtractFromCount(1) - tx.AddParticle(pos.Vec3(), particle.BoneMeal{}) + tx.AddParticle(pos.Vec3(), particle.BoneMeal{ + Area: result == BoneMealResultArea, + }) return true } return false diff --git a/server/session/world.go b/server/session/world.go index 1ecdfb728..48f853ee2 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -384,6 +384,7 @@ func (s *Session) ViewParticle(pos mgl64.Vec3, p world.Particle) { s.writePacket(&packet.LevelEvent{ EventType: packet.LevelEventParticleCropGrowth, Position: vec64To32(pos), + EventData: int32(boolByte(pa.Area)), }) case particle.BlockForceField: s.writePacket(&packet.LevelEvent{ diff --git a/server/world/particle/block.go b/server/world/particle/block.go index 43130e2b3..71133aaa4 100644 --- a/server/world/particle/block.go +++ b/server/world/particle/block.go @@ -1,11 +1,12 @@ package particle import ( + "image/color" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" - "image/color" ) // Flame is a particle shown around torches. It can have any colour specified with the Colour field. @@ -49,7 +50,14 @@ type PunchBlock struct { type BlockForceField struct{ particle } // BoneMeal is a particle that shows up on bone meal usage. -type BoneMeal struct{ particle } +type BoneMeal struct { + particle + + // Area specifies whether the particle effect should be for area. If false, + // a small burst is used for minor growth. If true, a large burst is used + // for significant growth. + Area bool +} // Note is a particle that shows up on note block interactions. type Note struct { From 83a8f57ce08e5b9034d3f278a0ebc26a62055997 Mon Sep 17 00:00:00 2001 From: Dasciam <122279000+Dasciam@users.noreply.github.com> Date: Tue, 9 Jun 2026 06:23:19 +0300 Subject: [PATCH 30/41] item/crossbow.go: Always set critical flag to match vanilla (#1270) --- server/item/crossbow.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 915b3e274..c6607f601 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -135,6 +135,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext arrowConf := world.ArrowSpawnConfig{ Damage: 9, Owner: releaser, + Critical: true, ObtainArrowOnPickup: !creative, PiercingLevel: pierceLevel, } From 7c304285e8de55cbef8777e7a40384820a4ac282 Mon Sep 17 00:00:00 2001 From: Dasciam <122279000+Dasciam@users.noreply.github.com> Date: Tue, 9 Jun 2026 06:30:05 +0300 Subject: [PATCH 31/41] item/crossbow.go: Fix crossbow charging animation played even if the player has no suitable item to insert projectile into it (#1272) * crossbow.go: Fix crossbow charging animation played even if the player has no suitable item to insert suitable projectile into it * crossbow.go: use blank identifier for unused "tx" variable --- server/item/crossbow.go | 6 ++++++ server/item/item.go | 7 +++++-- server/player/player.go | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index c6607f601..ed90384f4 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -155,6 +155,12 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext return true } +// CanCharge ... +func (c Crossbow) CanCharge(releaser Releaser, _ *world.Tx, ctx *UseContext) bool { + _, found := c.findProjectile(releaser, ctx) + return found && !c.Item.Empty() +} + // shoot fires the crossbow's loaded projectiles. func (c Crossbow) shoot(releaser Releaser, tx *world.Tx, offsetAngle float64, arrowConf world.ArrowSpawnConfig) { rot := releaser.Rotation() diff --git a/server/item/item.go b/server/item/item.go index 1562cd373..33a503cd2 100644 --- a/server/item/item.go +++ b/server/item/item.go @@ -2,12 +2,13 @@ package item import ( "encoding/binary" + "image/color" + "time" + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" - "image/color" - "time" ) // MaxCounter represents an item that has a specific max count. By default, each item will be expected to have @@ -168,6 +169,8 @@ type Chargeable interface { ContinueCharge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) // ReleaseCharge is called when an item is being released. ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool + // CanCharge returns whether the item can currently be charged. + CanCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool } // User represents an entity that is able to use an item in the world, typically entities such as players, diff --git a/server/player/player.go b/server/player/player.go index bbccbd06e..728494a69 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1512,7 +1512,7 @@ func (p *Player) UseItem() { case item.Chargeable: useCtx := p.useContext() if !p.usingItem { - if !usable.ReleaseCharge(p, p.tx, useCtx) { + if !usable.ReleaseCharge(p, p.tx, useCtx) && usable.CanCharge(p, p.tx, useCtx) { // If the item was not charged yet, start charging. p.usingSince, p.usingItem = time.Now(), true } From 0f8cf2e664ae36d8fb473997ca3424f6fe252622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=87=D9=80?= <104190360+didntpot@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:01:26 +0100 Subject: [PATCH 32/41] dragonfly: Updated for 1.26.30 (#1268) --- go.mod | 4 +- go.sum | 8 +-- server/item/creative/creative_items.nbt | Bin 140652 -> 144033 bytes server/item/recipe/crafting_data.nbt | Bin 858557 -> 881633 bytes server/item/recipe/potion_data.nbt | Bin 33442 -> 33442 bytes server/player/bossbar/colour.go | 4 +- server/player/debug/shape.go | 92 ++++++++++++++++++++++++ server/session/player.go | 65 +++++++++++++++-- server/session/text.go | 5 +- server/session/world.go | 7 +- server/world/biome/register.go | 1 + server/world/biome/sulfur_caves.go | 46 ++++++++++++ server/world/block_states.nbt | Bin 2364099 -> 2365370 bytes server/world/chunk/chunk.go | 14 ++-- server/world/vanilla_items.nbt | Bin 192590 -> 192121 bytes 15 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 server/world/biome/sulfur_caves.go diff --git a/go.mod b/go.mod index 9afcbf709..381b9dfcd 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,11 @@ require ( github.com/brentp/intintmap v0.0.0-20251106190759-56907b1f8479 github.com/cespare/xxhash/v2 v2.3.0 github.com/df-mc/goleveldb v1.1.9 - github.com/df-mc/worldupgrader v1.0.20 + github.com/df-mc/worldupgrader v1.0.21 github.com/go-gl/mathgl v1.2.0 github.com/google/uuid v1.6.0 github.com/pelletier/go-toml v1.9.5 - github.com/sandertv/gophertunnel v1.56.2 + github.com/sandertv/gophertunnel v1.57.0 github.com/segmentio/fasthash v1.0.3 golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 golang.org/x/mod v0.22.0 diff --git a/go.sum b/go.sum index 17f22d869..4bc90dae7 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk= github.com/df-mc/jsonc v1.0.5 h1:O7oh07kbS5AYY+l2Fji6l4h0iHcdjKbxCtK5VlZlLMU= github.com/df-mc/jsonc v1.0.5/go.mod h1:+Q++JuCE9IKiP8v7sWImdf/RjQX0nfXyfX6PdfTTmc4= -github.com/df-mc/worldupgrader v1.0.20 h1:wfJyG3bFeaM/HXy7TCiO4HKVw3Mf3N4gPFmgxMHsKnc= -github.com/df-mc/worldupgrader v1.0.20/go.mod h1:tsSOLTRm9mpG7VHvYpAjjZrkRHWmSbKZAm9bOLNnlDk= +github.com/df-mc/worldupgrader v1.0.21 h1:Qr4/QB8ek7En0vkTuRXYq4FrZM0HHSOXsJOL7Ko4Cjg= +github.com/df-mc/worldupgrader v1.0.21/go.mod h1:tsSOLTRm9mpG7VHvYpAjjZrkRHWmSbKZAm9bOLNnlDk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-gl/mathgl v1.2.0 h1:v2eOj/y1B2afDxF6URV1qCYmo1KW08lAMtTbOn3KXCY= github.com/go-gl/mathgl v1.2.0/go.mod h1:pf9+b5J3LFP7iZ4XXaVzZrCle0Q/vNpB/vDe5+3ulRE= @@ -41,8 +41,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217 h1:UZQq2253Q+7co/C9Et62RYPBggzz+L+2yqGlvQhSNM8= github.com/sandertv/go-raknet v1.15.1-0.20260112202637-beca0b10c217/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU= -github.com/sandertv/gophertunnel v1.56.2 h1:eFc58AkMQo43ntR0Wmvz8GRFSdOgABKVDn52GMbIYag= -github.com/sandertv/gophertunnel v1.56.2/go.mod h1:F8+ZPbzxJ0LqunXEaDjqeyUgHVB0rI5ZU+PHnptXGfI= +github.com/sandertv/gophertunnel v1.57.0 h1:UkgVg1xLCsOSm79rP09WmodGSHgA8M7+l4quL01cIL8= +github.com/sandertv/gophertunnel v1.57.0/go.mod h1:W4VnrX9AIPIVXNDMEIKMIRj1T80EdOgdqXpGbQpyAbE= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= diff --git a/server/item/creative/creative_items.nbt b/server/item/creative/creative_items.nbt index 8ebb5d9539eeb32a68fe47ee028fb0a306a82946..ebb532f65b18848b4d5b4ce61f77244f85e42770 100644 GIT binary patch delta 3520 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 G(EkU=mHoT` delta 2850 zcmZuydr(tX8t-=kM7$9bqrBvi1PB2WA0SphkVRLR;v;GuAFPn5h*93gI(F*lbpL3# zzObL=YX=0KZHqgdDzmqG**aQmx6@f?k!WR55L{O~IxQk~-8w!-_uP9E#BKjN=X{Uf z|CT3ze#$NVOv%!E}A9aJzI*huTK^5e9ACR%lWHVF(nkRCM6S(%*w}>%y2Qp z5d`?&^ke>x5X@et!>*|gtbcV4FEV3CPPn*lS`TnX$3$IP2yo2s(Gd8zbi0DEZ)S#E zgqt&#jn0dUX6b&_^#1H3^rareYqJZLwKreDj?9?fz!q?SmRZIZS7tLs1@=1x)a7L2 zaB4B`$}!;uTe#Spzg~m61tr+HAc3#T(NJ&#=N7D`vkyRviG?wEp{Rt0A3&(sT4)CB zDq8#Jq}Mcj@Y(2El#ILG**Lsd5IYy$W<0n6A3Nif~d7sTl+&VvGwu`%<5_fD7@J!`?f1iLKR6WGur4y7M zIoDln=GR(>l{M)cSHQd)vr>Dc<}b?nZf%CLKCMlYSFy6L!73Iv-U-5?W&xvZ02jjJqX*hIBz|TGu#GR+UPsOeqf_VJj?-=ku4|1`1C=}m%upLY8 zr{lZMiDK4&KM1ER9XO?U9T>GV#|W8t_LoSiHozEan*f5;V1&friC|dgDKFhn=c%bE zb8jr&ROa3Q^kpQ>q0&SMBi|%mZi<2#Xo`&s4=QtS^}_gVH8o{*rRyrH*Ka9Z=kZc) zEJR36QShFI24g@c9g2ml;Bbx6Tjz0Cly0qYZ?EF@TfJ^?*(>Bq022izfQ2qtU@Tb^ zAWNn)OM4PvorX%1A(95I5KX&n5KeoOL7=>3@ESFSy0T3>Hq^Pjp6aU7in2G#DyX9o z(rLd9#z@Qt2gZ>vo%_0&0jt)K>wQR;ob@oI!RaSLXj(m(>Dd9Et~ms)A=KOmCFF5& z@ZF6Nhk1ob$p(EOsH{xD2==_KIV`Jv=!hKN9TJ~q$S(ic2J zrw@Y%`*}X6WpqC7_gd-iEno$ON-8`GmnKLHzkr4@q&v+un$AEs8+j+K3b+tse{Sg%RAfd zLI?z_GIn0(C~sa4bd_}T+Tw10RLJ>JOHaC?hhlpIF;fpOQH~tluWIU5uBxtw%YZA8 z0(AWfuNH4`uCQK?u6nX;)JcjB~DY@6n0S8C2zCO@Od;6dr6w!36AByOsevWXU zpCc%iYy&Vrrw3F(MZ;JyD28AHo$G|x=*R1vLz&o->)fqTb-VjtTn%^TZy6okbYrv` ze}nY!hHU2rPK1h6b@L_kNIG|ub1EZSa*I>QzX(nLfg2gTB^$}Q9q^uWJCNqP0rCYB zlIPCLOeWQiS+-L!N`XaH@Wzh;1@evHpR01I?*7CnOMU_i1ToKsM7r3g_Kv0QLFkcV zNpx_CORXQ`GL)Mp@1%u8Pzh9Z&p%&DI}F~@wo~Zh&%Dd`b6|2QGG7#Bz5{}kcOUwK zsQnk1t)5FBLY4Y?@Vj371SZk3uOY1AIfP2WW7r7Fm-GLr*0v|`o0*By?F`OEi-Fj9Dm=+%c(w?nHoZwAhm1R=LXV8vp9-OWNGAz zW(z|hWI=_ed=oeOc?`>;snN{Bm7R-aOKDR)Tf&Frj$v!)tcB@m+ho?L?xw}Cw1xz> zgPM|=g@!GBr;lOrlxkCPi>WM%<ZU9`^)2*lYem>%b%K=8c4~|lEuUpfQp@C zXZfU0U>a}xH#mp233#0Nx_KZ5LY${#lK1iR;W8WmP zCFHQH^7CnzosFYfE06Wr`5?HFbh0~mEH0Tj)tKVR8O;o8;-ATEu}Y-4Q;2rhSe`*6 ztlzfPTU|M?x}v)7v%80N)MjP5vT1pQUCAtiTq!Jq%98jf`r^2P?^4)W6^h$WBL(V@ zsR3R-1AlDB6!uiD?}H=~dD?2Pd6sXXE)l7$2qVSi0?C HUk3dTZpbBM diff --git a/server/item/recipe/crafting_data.nbt b/server/item/recipe/crafting_data.nbt index 800aab6e3aec849e40d287be6fe2281a93738342..350fc0c124367b7a89df91ee05f6c6d44d75eb14 100644 GIT binary patch delta 51379 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

LCA$tAa&pB$8v)he3WgUk_zR-pP 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%0A}wcj`=RoP6>LF8^gd#D|drRcXf|7*Eg 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ÐxQsiQqHn|`wCo=@7yl#rN=(-Rl0O# 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 diff --git a/server/item/recipe/potion_data.nbt b/server/item/recipe/potion_data.nbt index 980737cf3602fb26792d29600142651386093b32..3b9f707eb78842e1a17b50eaf73068b322a3e02c 100644 GIT binary patch delta 284 zcmZ3~%CxAJX@ddN(zTbLD+tK} zrHUrI^TdLbOjghnpDZ8@QB%Mo3^c+9to<5G{$v9tjmZTpKz%F%V14shLHgvuV$rOe zlZ$x_C+D%H0mXzTyYq`rHsn>G{E8iJtu2Qv&;{T`2g>fbf9S*lgb^y?rSfToK#^w`Cge6&`7q)QKe2`C$^Ur L!uXpV%9FSO;dNbB delta 278 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
P9JUSz?AS#m30|2Q&3xGljWVh+q3?uGq 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 literal 2364099 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 0; highest-- { - if !chunk.sub[highest].Empty() { - break + for i, sub := range slices.Backward(chunk.sub) { + if !sub.Empty() { + return uint16(i + 1) } } - return highest + return 0 } diff --git a/server/world/vanilla_items.nbt b/server/world/vanilla_items.nbt index c5d6a1b2b0dbbf12114d5f4501cd2d86d5bec28e..52af4d9e1d6ed874eb13b56f923ffb714097507a 100644 GIT binary patch delta 25018 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 delta 25400 zcmZ{NcR*Cv_O|KhWq9|C<*fAP=L65Ry$C4O(k1^Jm zSYtQF*n3T)n68QXt$og*dGGzce_m9WJNKTm_u6Z(z0Q3vuN%(XHfT=N`=-`gm?s!$ z(-z&H{u!!;h&R0r^rDs4Or#$$Q0fKUj;@C55u&KCfsXgq;zi)ARumYd8EElrTwr5! zy_x)3;c(5KCTD1o>W7u0v{rPci)N;6D|9!r^Pr3m(uNJs=rb^FSkCBdohGi;{VA`8 z?oRu=Y7Qc3eoav{#X{eX)4gfXr@Do%Ro9)VXsjORqdDj13@OOTO6!xGlb@fRRZcVY z&lsGQmOZ3E+zzWEoUd7^O^lXKE@QN8QF_fw+!*y%Z2h3TR$UB9aHoCQnxoig3FYS$ zZzH2AFk3eiH`nZ_up*vr#vCnFoO3pi_(2a7=2ZrY-mE3bUry~FO}CokQGH|d5dM*H zE=&?d-3_!kPmiRt@p=V{oTxd{&!1=(F=<5rMeNtoM2S7FVbtx!iqD%B&&0DBn`y}+ z@KYnLXro2oU(6J|LHDBP^)#cXVF}`B9B}9#B5%bR(sZXRHD~j$K z=*=p+2D;HnZ%*kq@$BR0 z=w?y0+xn2#`~&#&V(v0qDs?*a428u#38Z^iFzH8EMUjU=2)PA@2Nl_M|6UvA*)R{)*9K)Ss%= z(PQMjr@Td|)sEl`V@Lb?NAt29MB^t< zG8%uFaixv;d9;NhYarU{U)LQR%4y~M=H=v;qx`X2W%B)6_n^5Q5r8)<>z*o1D}SrG zT5SG1j>XT1t5L&_x*Mf;(8FovIQ;sldzzbQ`^JHC#_2V+a^l4ZxA|8s^vqwE(Mf-= z(GP3osM2S8s?3}o)xL+=SusLv{mVe{3$#QPgC8_Na&lbEI1?e` zwM!cMZ;%L!D{D=}?W0EeYnO&?L!ybM4-)8hp=PA|<8iIQT)V{O>u}Sec5>z4auKzSk(u3`*jM7V+c5DF1h@4PP>e z;}%JEL64MiVIQW)$?H=4{(3M+omkPqmIl|Yr8 zLKm|2P^_*|%#070Vb!lvH2&@;LOM2)n-lv<91^32+_Bzyy1iH&6)A256#Y-@Q8Lu` zmT0^&Myk<34Cu2ZbJQ|n=R06njZ ztVr03*sFG0iI|$oR~tBAlBBhk3-gS3!0Zd4d37IqT(lccRk~bjBvNnY zF&NRL+PaKc!V<<~X%b}oQ1`ofFvF>l#(E)_KAwqSe(+;sk#jmiE`I6IIL-#)Gr>fu zov>59@PFp%QFOZoKJ-NwJ(SM1#h)yn>*1V36z`~Ub{T25Gj6H$G5*FF-{4yyzRMc~ z1g;?BH%H2+6~FzR#GqhrMFFvNelR`kiZs4F)rIEl17Jtr$KURaaibacw0ffYn_T*! ztL8(4KgG`;j78QDy|2|2-LJY($yfm4vW^I~nb*dl6mhP%>#4Pq3pD8=ay@;o-1Q3d z&*xf@yse#&ks2lT?(|hN4S|bn+bEE2ye0a6EllCF(!Ge>~=R z-i8dDG%uS&i;eV8j^2be4M5cn9H5WY%ax;yQF%1gn(P*IAgy&8|N{HVNEm5%h&130{j zXXDxWtwdGf@CMzqvH(DoHB>M9Yr&Lu0wuXa6ZIMOWD9ao`1nJNZ4|N`!TECnif!5^ zEmq7mcyZzKqn_=wM49=AD(R{3n2Q~q(=-dM7zXwMbf=cv0mKMx6a7(7k0l=mt&Ir# zG+EqQW-F%N25MTFn1k5Y4Rv4@;5`7i$AydjqyVGUmcIo3NQfMWw~-F0~4k&ri4ROmB;x zKt+3Suc0G>0TSuWg`AV`Q35XbPmMHz|Z}x5ie_$pWptJ_&4rrGBVr6yZi2bluw$6eOY?$`2ovpKX^hoO=SXrMNon^3i-m z`3CL8#p0^Eh@uyFabM3zyU@WCn#J-zO@LTgrw$pu1u9RdW1-eM?zdmKg^88~?n4ER zqHkRn3I!RXd{6AaC{6bjb>~--C?I~X;|g4^az*zNw;Cjfwhe2FC81%Q*E8?xtr+5H zQ-L1GTt?)#j{zw)ilF2ck^tqW0EmlhaC_Q402?s)YI!^`L)ratKoBM}OYS<6{tdR2 zyQn>(U`${!q_+c|;mUKccNCd-V8inE0fB7Rqlw~==blD#0Se4Ca3&&0HV9&f;wsqa zShe*(_I0F_#e8kxdh#@_rHaprDcIj}OVPye-fQgvzw#@J;sFotf8h5yiAw6CuyBv( z|BseR&Od7&B3dg?l?5u~ikkJstDpVo%kT9}>Y1uFqQWomv*nj@FV-?{pb3M~(fpDM z01|Jt3Uufw6MLk*{~dgD;47oJQkpDgww-4!+;*bNCM%VHpX$NqxGG^~#vVW@2mRRm zU&X(Ejt+JLq;YA3=)F;lrQzF6X$a^*M1~SE zi_AUsx>)4tRF9$CyTnbD?5!*|#K@3&pRE)fFXO?Dq6g}debTr_sKyd?m!8C<#Z_o3 z>*TN!@CI%jHkqwI-rcOMT|x)$K;hj01zgeUzK}R$ps0s*Z-ga@n_~hvFh{X90k@DLgYBd+;B?LX$gyeUvdqJ2CD@Rs7nWSM|jxW_*|` zhWw#QhM4xJGtKov*zfPg3_QUgLMouc{R5aGdxbFxPTa1Cm&FGDnJbN7GmzS8GebEG zDf}Wz%Z-7$qPU{uWsJP>*If{nvyNlc*UiMWOV%3aFS4Ub?g%EP9(O)x>JEZ9mAmY_ z5}IA8UjV)tbjL`a&(%=|fyz5iBcH;uv_w3pQLO8U9^(6^v~>c0XM7>JJ4a@)Niuyb zW=lj|)o&1SsDtq`4aUCK!sOn3GXsU=t+SRQqmjPY3cAHP{`(nx7~dqWei$heROD5& z6R(!Y=8kR;(CmswV*8_wMo~4r1^HJ5vQ>AJAEBMkn3dxvwU@V`s6Q3x?8awYHL)t4D|Ih2M*x^p>7ch{OwQNFg6D}yIRcxaVu%HazbdFkE|=oY`; z#f1*Pt{{BIn#sEa8+c_DSWJ2y6Oo2ag6X&Td`x}v*HQlG=HWJH&j&GlsZ}e^g8*V* zx4O6$Zf4%?Op~wcmF;l-a!%D~h!*HOe}N+ROw0UA|ztGK8^U?_XoFkN72Vt zTIE0){TVtqZr=mm;Z!s@lH>XLYOPhT^2k-|^dBu!dy%;&gmxtB9>TMmYy(h69L1!a z&1lXWtqE8=%FWrW{}=lvKDlt7WzC<3upQ18Z)^=0h%p$D@HNDg|$V^ zXa)%8r5fV~V)tw|zBZ+CSLSa9su~OxfPbc=zvfGdik@vGb)pL^0Z66i4cy4IRpsn{ z_ZV3d^CwAZO6)mc#WEB$&|PFoGFojG9M(nw#qmrO>w`9u;p5G@6q!_Szu~$IZDH)! z=sQs!Ok&eod)hpMs~(EeT^nR&>1lVd>w=k98bL4lGS5E7zaq`acQL++R}IA4l_o&5 z)25K64>~$7Ql%NPd>ce9$6;I+D~XI=RVexk(4M-Q7hPMf)i7#~_St>=X7?FB%*jD` zIE9IsPA>G*6Wtvdm+6#D1y4cf8?CM-{%~>?2?n#+kN-LthKh_eq2&Guy%|SU@7Wwx zQmIH_!XTzShTaIiFyMtje819-j;3jisdRuIp%%Jn8+09vUt-P-d|<9g1oY+(#vsgb zwlqK8%0GT_K%j7YE&e(00xeI;Mr*3XQ@RMjr&m;}?YR?3@=cZXDh`F3YNqMV3UDl+ zu`X1&U;mBj*Vo&~%o|h-B9!FuAQZ?o#X;N^nLqv+$V<8b&sF;mRhPzXfhNn$>E<#! zk^cfEj2n*w-$4uDAU}C5Og2WJRMF#$gFsgx{Y5HdKi3n;&f0C|8%Z)2|2|)V!XawX zdV5uQN+*KQvL;t^BictMLF4+Mp6)$~PQOgxCC^sa{@2gI4XrneYKArAKC*t6 z6uMBV4HWr3G?z82%f#6ar$gm4f24Jw!riDLV3QTa631}*?IZB=D*fGP#YdV6+`Ui6 z;Ixd~kr|_grDbOf*Tj%sl_+Kk66uAPm!4XV>I~7l%4bSB!)*32dY;Qe%DT%3O`z^b z?CkXz*omJSDWC@$x5C|aVnRpAT?t-fn2!D`Fp~EO(O>jcqS#hkl}sy8mY@N6P)UjI zf*AIoLnpL~WjYV53%<0=Q>$)r${d=VJvuvWXkHeo)A3QLWO6!T(+wOq=p|5L#c1UF zuOsS+;<5hJdYtAbMtxlYJw&1nc9>PylkJkr>Uk6eX7OD%KyS_AN&CVTCylD=2;Bp6 zhrejuf;jFZx!g$n90_qqr?1G}bf*TMf8>lYQFKLPR@ z#fZgf#v;0%lbJ{0X6T{0YyE4ij6f*{3_HkT08;Cz9a4GLc7U2}ke@UG$zjSL7psUB zVE&I##3lNyUP#pFeh}Ckk0C$PlPuJ+J${>)-)EJhNZHU@bd8H-9Y74+>q;ByK^g{Q z6ZJw)P9yFo=cTvS9K_Z2DXfwIlQ}2P3gDs!^-dcQCKZL3(`K={80b>}W__nf%u7+T# z4thWw!4f>az&ba0YTURMs!-13Cf>ZtNLh!#MWc-=vg99X4b0(6Ui=#>E9Y{jc6v+l z`#|&BZCZ}9MHBa-&5a(>gw}M>JJH4Kx+^P{-ckS)KNq-)&GW04@g;w{Pz}M={=Vi< zhsNqPMEcvRtPDEFWr=jA|I-_Xm ztQwl_jpO8GfR;}KZbltzMgz)gS*$II{NFlo>{8pE&=22jMfmSfPI(2*vw1oJiEjgN zmbX;)#YOd*2cEY;u*QnaKCU-a1#x#H$k*;r^7zUXe(xabflFiU2eG+Y8-<^h!Q54p z?3fQslI8ZV3y?vv$L&8s3d(Nt^i0p+r)E5H{sDY zSU$_CMu=Cwzek_-qG9vVdGd{1>CUD2COAHXNyxPIAS3=0A0W|Vat*bk4F|Su++L46#=Mf#MKgSICSPc^jHxrxVJxtFLChxS8%1qPjEFjDLZ@vHVGO z`l`JlDVpg=!IUA%o@%Nm(z)B%yZ$?@`f8P4==qs$7gh$8GnF5|h(XyM%;MZo-9h~M z2_TR~!Pe9Cc49(5dwRARE6k*1o^uDu>cv)*MH~!jIB!`aQ90N}eE416N;alGd3m{6 zc_Rm>v9Mas#&K}L@S!=wbMgk4r$#$8Pkm9OIB)V3&J{srccIoMN_E+Fi4z58ngAtUJbG=_)QhILQuHg#kl!4xOWj^V zm2BTtb2n?w8M(Q6Bh&Kw_D$m(1*z7X)@d@WbNu}&61a)5Ypvt*Aj0BnV=5Sy zot7~;XHW+3b~zhE-=P_h*8Ai^W2aU>K~D1-qjj^|7iObthz{z@{J4=g8CFvgkglC8ls~ z>FOB7Rpa>{B4$KAQD;mp1~?Y8ja^p8pp1Um|1qf-1kHfgSI*(zwI`Cfac9DOjpsWq z24XkTL<~U-l8Z5?I6p-#uA5NdXU(dl&Lb#h5`3suz_aj=%~UR>I7) zXmM`)tvU`RSjX!3uex0Agg9YE8E+&7E#UKSC&#seb?TX86;B3*x?XV8aF(Nmpu zYDfd0>J>!2U|-n)PKZM`{(i8fnnunk0^?G>J~>PS4d5Jo_7+X~>pggsO9i|`dkyqu zHIQY<{w6vbqGk%>NlX5ym7OeEFS(o>HOfXmzSYf01y{jU58X~tSyJH{FfBb-Pv_Z> zIGPo`qj2(hjjoKrH*vt*Sv$Ci_cc$9HqF%Q9!8##UZ^Jr2WzhA1nqp^S#gv#$HvHE zo>eeZM!7h5HARF??ILN2^!IR9L8(DCSy34sSlgd)!82=7;k~CB=<{E}V&#Cv^S%Uf8Lbk_eT?v-@HWLcL0%)==C6_6!`i}vPv#lDE_=`!_Mi6aw26SbSbi? zq^0Vu-c3uuAh7KTN0TIx7SDc(;HqquJ>cz8)9NWL0iuj7qWGfxB6_lnhuh~+@lHxi>6*dIjRs!p8GuH^g_*$L+bR0Mw{x0A)rM$gHr=}>+! z0KqOlFJ~B4d8&JhA6L}^3?#3PK)iTq<>svn5L;H)<1*pFSkOdTeCWh(y``)YuWKR4 zIYz6`1%{XT9cXqYcsy6)*DH*4dl*;{FAIY!-JA!AvqvrGM6TRkc>xmE<1!pwxj*8< zd_Bym&`PsV@JpJ0RP5aACK_)HR(h9dRIDg$YZTpYL9@k&{@Q|E%x=?M(u4j#s4LC5 zfzPfkL_xxqN$NkgGnhTl#6(%oKxp{(x}J}dRA*XCE7j?>4ikwE)srN*9(tpxqQ!k~ z0mzx=l|Hf+W0yy&e=VGW5Me$R)EYen3r^hRhh+dqu1*VJt=~xV^OeFY?Cm4DHY}V2 zNiKReVnHI>fe1|7BzO6CA;Ob2AdzrZiqz!%6I=s4NJP8coE2G>K;Rn6-T@g{vR3J; zqGtEdBm1Az;w82S(>BF)f_>sSy)E?5COiUz@s?E>Fk#ii`g3YDUw#kP0GvLc8cG}U zzD?_}82q$n6Rk>L@mAcXlp6h#?C6aEnUgnt__59t5f;=9;@+1{yLOop4ZU=f50<8HE7fcy|I{+W+#$oG?&lOMGbUHY zpE|W-Y_+Oz?aJgMJJGBMss^l0;@4C*E+GB(M#!`%eWYg41CD~7S)a<0juHwytCd|W zXc!TnL%%?rPB4nj&#nE7UjbH>t#)*AGInO_gDA$0Od7pf^$Fs|HkFZIwb8rK;Q2_r z>kIJP6WSt17c@rg?1oX6yp@dBQevi8lMyL}lg%{s7XTV|mNQZ4M_s8!c?5)9n;(l5 z@pQ@=o;|2RpP@N}hEYsQ-A#KWJrbNIi#$PK+U8?5rDwCC5(qj!O1)~*SS)rGs#few zg=my6IA9S}tHbnY6>(c80&bY;pMBQk{d#o%4eP-GmAVUrhd%)*flq7~9_#EH#3BD| z@J9UXolE1@9?+UG3#|Z!+;tQ-nc?zIzQ~YMPhnRbI(3>s#JaYi&kq5j&pmD_pF8CQ z`d^^5HAd~pVF|Xi)d+@nq#yhOw)D0IC>q}hoL!FO?~d(|_UP!bK*#X0Ku*|5U72?| zcsSTtvIqA;|1>-!6CK8gjNF23cd_SiHL1jZI>sVgruvAp^Bm}kCwTvmv05ei5`!^n z{1VrJP-zm&!z&BFk&x)>7@@!Zr+-SLz8>0KS~?JZ8`wZvV@+9c(Uezdfsi1v1;{@%Ei_g*2*5xL7396lUy z%4xwAj*MS{?0kMGfGurMmF8&Ham0}!7!Y)CsVKpRUE`^t3qHn+*zdg6oT^H%!r>~wU ziRsN{II6&dVfN%l*<<&ea)FyjPIS)Eyi6kL9wG%o)j=I2LjV09G~P)94fMr0C9G5% zik?6=6qnMG8Bm%;lpmbbs>_a(0b*(#``)u~eWVtnmfjkyhm%UuI0-8cRnjWB#+1Kt=%zgU4d-A z9quAZ-gvvd*DX|&hFbLSgx=wOdF!&ws$GeMGb)y@KCMKxK1Ld11?ABM2hr{`%qGF> zP9DH;0d@Z!KXIY34^Z&71!6RrJ{nN~y6sM%rD5xMg1%~K3t1%qfOb@OGI)(F>_6N9 zW0Ze_1uB`AP2+i8uI?ws?KP9#5Tq^pVVB+J?_qu_?JVOTBi8vQ1~vdzGDoK+m8^lo zIW7KPBL{Kt(6_|s0d%!c>@JQy~3OvufFTst0S@W0&1gpU-yrp1;ajDOy)&;eV+FD=z2M^ZEppk*|Gv;6#jZgimw6QnR47B z&*F%dkL2VF$8>htiM@po#vfs$7+xB_M%A=Gg2~gN9-7<2OMn;{QmC{%@~6&KG+fSh zAe6_O_M!oHdL?!s##8t;FtV#QdNsE8iG~p#Y(K0|O)6+!d@E(Bx$T@89=ryEEalEL>FWrBjP{72<3M~^H?&E1riMJT4E3&{H>&g z;)b+%6F4FB)z6>EZeR53DUVUm1ry3Si`i9jky~68PDU%sBwTySN&|((jZwU+vhihW6I2@ef$A)s~OL-lY!a#jmfkNVz#n-{7sP#iM z#ysjg*c)eo>R^7jeXVsub zilsk|p@v_;Fb*HYybK?4HOO5w{W;q3ulWGx=~j0{6Gt7U0d5|r^}gDU1B( zp14c%Dt8f@7$pkFv)u1SZI&RC7*gi-w+aS0&gLvKJ9~&J83F1L$-u?faYPe+u@i6! zE3}c0Y*qnAp}PT2FjTUHR+&TdJ4I?&DCS|Ek@)a#YWuok74!N*!vEVYqm^F+W9 zMOi*^z%rP70EJ{3_pGINrak*uNkyDSb%#X3H|o2N9c=72Im;&nnN&qMC`-7A8kWG* zSPM1$5Q`-16sE;7YS^{wAh+q*ldC1NgQ0^TB9*vT$z5wTgTe4G$7C!V*^=oYQUm;w z5?>$_atm~q;?-!R__Ctc!x&Cb#m*kC*S)k)=#IZuQ__qP`*}^>MXyw>JigF4T`?08 zRn?vPu@iOGFugIA7%@GUC3j0JAg0{iR|`>h>nwURp5lh*_1_ihQy=O48MMf%W;HIVm;gI;zt#DsxcmM;4so=md)AwC>+C`DbG`@Mtct<00BE@kUbwV;+!IMJ!`e| z23?H)PqY2I!sA2d1|lmKhIp3M5@#yqYLm}r1Oh{GG+-dQvAOp6n>i#3=Fn>5Y)B-H zx6!?U8SLy7+nVlrZ|sD~Atr`;msvwY#Sn5A7n%jojs1E>(KyUio*NL=n|jkTU9Sk& zG5R=wpYTnUu81Qo&@P|@feA>S&9$Y16fEb-HUV-sE*Ie#3={g|i_X-28d%HrSlP)# zBXi;Nj-uJ=08mY%hrkaFc5ocj!g7b*vbZE)nKAfI^3LX}QJh_n(3 zH!^k9t5K>Ks@?Rpy4Q~2k%bz|?^Llrn1!r~MQ0(nvF<9qzt^;kld*p1Mz_|(%UULd zL$)$g&K{Hm*4d)kvf~mKiS9rxiG9!FfTmfiPzqVwN*%$@6;#^se<787MCl>LT;Ta) z$Q|)LWXC-<^nm&2Of>a4;7;bhl#`2p4OeJ#>TIyutTT`|x!n|*kqmAqKHJcRF|htT z8{?@mW+8rF1S?Kivq7N=Kn*LH#BmW3fx{J{U%VaCIEa1qC(*2sCCDwr#z5!d*l37m zPohbSKe@*?io|7#h#W|1#m9*hm+4;t>CQ<0_jwNnCDqH&t@FySMMdk;ZoV^{uV{jJ zoLSM@bjJ3vYS*w3sZxQ{nyKQMuk??L=C}FE?&KATTawCN#B?B5(cNBU&+0_ycUhnO z(qUAa^t~M=NAW%K4(@r=pimX>x3 zo_!JP|7NsqJY`ixvv(%$kMRTZ0TFIoUY2&IA9I(MW&pwQ<<%Z7NDKuD-4MMMili;l^#d&Z98~Ve&pr~D~wr@ z9S!~o4)~jAAr3Kjeld87(c~T6rrFj?WjA+LsGBC0p$*@fiQn#6x;|jbfDe|24l3gQ#p08 z)$)xIE%|I-`T?Wpb*T;T)0V1Dz&{lAb)_Y)R$@W*p}MI@mTsQey+EaUHfM7dPDD+( z!m|-2&^gkVbwLW>0gqo_bo-^Jr9lfPgWbRg4t2D4zQ=n=7@+=hAx%MWz=1XvpkVB= zrQ{cgrp%Y}%qjhL8T^8~cWPG`kHBk*_XosDXVBJ29fA#7*OtSuhb#r7{C5>zF;WT_ zMc=9?&Cg+UfuU3UbJ~IaK4V>guSP+HMmNd|0-BOe9F2%6T(yhWk-EFR4i z>r^W~2d;1eS9Zd~bdFPSBwqcXICZ1S;AapaDB*?HU0e#}HkrNiuC&M*2ow z_rr%bP2vRP$=sVKp$Y=-Edka_lULAcd=D3aCwGBbe0z8wg*}a`hH3#R%tTFg136h` zADd$JuYJ8=y^w+)Tm;{e3)XN09E2RJXvbSw=Z8+T*B9#y8EforyvRVR4)s`}a;D4` zdKDVPf7I5EVS$d1iTvev}Qag*)m1z z%r`B}-`G?3T9u4)uzC1S6V<^yHv)v0Zm`+~EP2W39K()*GrEfKergaX z>a>u$ji@>|lC6X$x^Wq!M{DAz$ja2_lB!I_=~!Ax<~J@zw!%2yMKQpJ{eWJ)C9dni zP`Q&Tzn#M!++lKHq!M^xu89VGo5k_{EDFAN5;%%<4>wxgR%1g_;Vz$fow=}GM3xyMEdnT%y(woWtV0f;%6zu%zqdF~$gu5LJVW)wCp zfgwmJ`w=MaQt16<2BbELITc4?+|AQt8fQYo$e06nPppE*?mMwHMOSzds(~mlif7IO zaaPVfSFNM0PAwOyL)(}W+LE!5iNY#*Y|KK-f4~Fq#~dT@F&Q{-T6d;XHBkzFwL`3z zIa7p3Dl}+FvSP2Nqoflpy;yF5-g@+&)hkOAoe<5krcXrahx1*U(Z~O;PMz#Tw#(g{ zYXLK{0*DjUGhplFN!iQY0Uo?S2}}6I7ua04&bTQ!0mRuFs%d^X2TnTRyC1P3FAHNYP=)<}EU+H!09?Zj3*Q2JD)EHwCR^Z7iZC+A>=q~Ze$u4jsgd-h zNuxOh_Cz-(kA_aF4Szmgzq(!~^WN_(lc$rir|uo7XxR^a6eGKMTua)$VwpZcqm5o# zRaVEn%53_09YfW!21okwTyX--S&Wd z6vK(fy!6uVIPHQX8+*?IWS$2Cwq+*#o`=XyfLY!ZL4Uh%vIM(n_DC&OQifzRRDv&k zI$JrPOFo8B%bWN%(q8zlRfod)xTj}k&d@&n|L5fDWO-CoM#HTMJU(IJJ@E+Sc?h|+ z)nVblR!FOxK79JVK7+Lui54wog99Defd^!QU;Q!W++jgns8^xe$GLnJJALn1wOk0> z$}fx)EHHb3RB&w)K2Y#Vsf!>gRd(#B$38&tY**xG~g#_aOYS#gCdTcvh zpJNhp0-7rzo-+<3BL>a}+c6L1a}>{iMt)0>{`oq7mVThE!jObDcu;9iy?muMof-#8 z`(5QM9&4fe4iLL+*P=vSyslTJkZ%8F-t&Knfg+z>p{--}>QY1a(hETU_c(CIGJ+>g zUVnFX#g4r5INe!oODzPC;CM7=Bq_472PZK&{9UU6rM!wH1e;tES#GynFrEJ0+}%$Z6$D z2R!uJG-W=7Klzpn>?)i3j5s-GrK^zc@j{X$u91p`gW)+K%OGpQ<;KDuEbfk?B1|9e zx+JhyCAj!}4@Meeq1lee{de_o@>zZo)etR=#SC2T37;wzIV%iev&ABYc{CQEu3?0L z(dZLQ&rw}WG+-8>l#``s6((J8Ho<($)|gibC^d4{-SP+QJ;%<0ra*E&3C3NLob<2h zj$D-Xr{jAuV?%|torvmq*C5X9Xs@ulULdG!w|#gMRZj)8RR4QKX|%VRSmE^|(cl?y z3O0lqE+xO=$xtad+?!oN%G6kfuaK;A-^Lb{R!d7%B1~d0#0m%U$~Y~Sg9C5yk}hjU zk-D^{e8&O3C_-N-3&D|ej1wiY4zmTjlxQ~bORGrA*>b-=rm=G6LDy7QPKUli_*-p% z7_jou2yhZRezTmbGGUcOO9mB>On^SXCwQH^f}U>>+LoHK663gxg9x5yrWZfxji~Ym zSUbOBU%GS)E|vc{(7o&$oXITR@qq2&6&xsmsu>F*&&x(*z*l1{ZyMqa0v}o1n@^Mb zQ_ov^9UfLyVEHga3=647ALhbHiD{Onc)I}a2%+V0dsHBJFJf!w^6tk z|2n3T{6Zx@0B)p_UYfr!PRX>U4JnE{jv8?*E5X#_#>PK010G{*)3v8XlPk zmAFh6rFmB{0?TzrRqK5fyNbiY z1MfiKhi$FLOQq3^uAGHr!u+DkCj4RRdIu`}5ZKCj_23eGj*z?@=zK-aJLC|`2oJ&B z#h|}~#V?gI)zCnADPwvC>e+&(2@Gj&74i$-`+2Q=c3$FnMgZsg-^xtm}nxc3G zvx$#?#yQidKxDj>V_MAS6P-bb#VymLRW4LL3c!`wCtqB$A&UhS{%H72PzvskuAqG? zS(~h`a5D}UbnBvJ^0SH49AbPv68-*`IDPrbze?yq0 zbeQMFsWf%24rLW~ASXIL7c}tAB2H6O)R1@=5x5nni1^X`@YW87W$|<~Xfdv{q67Q) z3{-6+xE(W}PGQ!r zGTE~I4Hpep+F9KSSYqrT8TRu1PjLAL$evI25CgwZ{g{~ECyqf>Y57!{AVly+tXqkV zD6yAzXWCN-G0)NW#c3WY;=6u&8K-;{=1oXA&`jCJT-$&^`sM&|n=essA-p=gdM?l9 zX>f7JPa1H=)d5LT8ohFPvaHm@0cZFa>>yvRz!o0TbOVFna3DmYlAKt+P*8@sf-1dBKizlYknTI zMI$D^&1D-xdG?VE6-j(pRh&>uO@-g1taG_M=z>_`uCvobxM|T4r2W)NM1I*q@~DGO zRuHLJ35(5oL%o9-9Wa&0NEWj|$QyIg)F9S*!Wuu{mS;Z3M<5d<{M-8R2$stZLX{{1_W298QFb(XKEpp6K%beeDqX4fWa~ z@!SN?uml@ciq$&p1tSyI+9o$=5Z&2}8T*?}$S_WaW9-mLIc}nh6G5o?rdMa+?%$o4 z%UR(~^(^>hz7E6>TxYL|*4@We_v!3T3y1P`ux6t=;$+0pHpGA>cGy|S@iq!ozmb3g zt7Ax>%`D!f`Z~Oy(}_Ng1=?peVYJ7c#C(Q3Vcn=?6Pjuy{}*34lVf}IBEv7^S9q?% zaIm#JR5RSmu^l{TM)vbLeM)(5m6+CFk}4rBzm8qyc&~2eS9G+M$UVP?Rao!&E2Kk| z3o+OnEIauQ!8}go*Om?0s5(F?qnCD-L1Lsg^4tnfUItCk;_=E4QE-a;QlofXu?4>r z#7GBw!#XeTKM!pJyQFs70T>v$T^SmrF)dA#;dG!zwy?W>}+ zc`_ZdhiB<|ZD&z;;Rn*nIx!by&B{9*iQjKVA9-t4F=;$sT#I>({x1TO%cj*}8f=Hn ztvAmojy3H~v+^|;r+zr7nU*uSU*2%4;(;bX`dHajBf^URlQD@zV#3t%opIF%P{yw` z18=G-28hZcdP+r2r}3YH9l@t-9rML}t=AR4Bbn*QSKT<1&2^pMU{-cK+u2LHjsDjQ zTu*w7#N~PX_8ZrCE-p5e7{vlnPfRFogr}YPnR1lV3?CM-dAkC4sgXF0s!qaOzLmK9 zYnbxlPfBiGepq&P7VY0IE|$G zx{r?lC@`KBwZBmM`-Nx#Jm+tjBaUtb@z5q(HYn{R)czg}K)_swCftOR;$$RB?f7lr z%>2}1)|aix%MTz>U#U(6a~j^e7zIazY^08T3pBzTEG#12zzsu z_FxSq@gC3&p|bRoDA+<#N2{+!RstZC4aH7-?u`{g5KbvS}|7-7~G+Mi0IS9nb@qKtP4^Tz*9Vf1Wt(Dn$ ztW|-uF=xc`a|vP_~*;jd43byM@uX0@w53+|?}c+;CI{zP;g_!I7bAy7Pfm zN4on6eUoIYO@D2LuG1(!U!fe!JLdA75MYCGbxY;2rGQvCt*Uv!qGf_1LcL`C*AW)B z;aO1>7BT+&;LcugX4;z!WZIqf)^`s&dw|D|B%!0_U;J!u{8pn zxkRO_kWaZ3Jb4H*z+%VZw&0w+D$bd*%O#{eSUYFzw<|q!w%$~SW4I|U0Q3Zrf2ac5s!dNJkX?DIRTr19c+$2XVNuvz z;0klgR|O5^>wtdw3;O~uq}bQ|%(KfdLSp(8wtpYFlb8)k#2-7suLZ=z1e{CyOI z=n6?V&330>G*echA`jf_sK&s;2{{-|_0Q+G4A8z00O?2^;czfY*)|&e28_Ty7Wg7> zqtlGk-38i4w9$obwT0Z^cL=Og@}T@@=-b$E0fAiD9>J+de$#L38z>YKBqgTtoA>3r z@o5*@9<>5LO~TSion~oC5PSBkiI3a)iml1jY4%6ho6=fn|8N>GLG132_L&bNJ!+wU z%&gB>IvPgip<>RomMFmE5jws7V`)QaZ^Hc^9cB)eR&l`p`wnW4-7zkqIcaVi#ym$;PBP7>qS zb5-1cFyf@?W230BLJcs)lP>BA$v>r_mfUM>sfp0TOsxYSgEUgD<9aMw^0sofsMUD* z44Kep2BTE*27mrFaDWFii$+4?DwFQWk-f2ex!*EzIjMDd9#?`V+fl6V)3hF9EIZgA z^1g`N{{-jX>aA2_Ra=QTW1FIRK?BuFe%*and>uE91JN`}(7s*+izoa2+iUSNZWls# z@c&D~`!3Rsp69^BZ21c5h~-A2I#L(36RI3+Ki=y7Ki4{e+&Z+*MfAoIaxOgg7T}Ql ziYSOB3$h`?$^sHq6$1NSUmBkaCR34LPz*XPHvX+L&FtQWnYZl5gz2T#l;W}v)%5=X Dpd;_- From 716292c7942264e99f29e09d5b65390fafab88fb Mon Sep 17 00:00:00 2001 From: Carter Thomas <90356816+TrippleAWap@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:43:01 -0600 Subject: [PATCH 33/41] leveldat/level_dat.go: Always read full file to prevent potential reading errors (#1269) --- server/world/mcdb/leveldat/level_dat.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/world/mcdb/leveldat/level_dat.go b/server/world/mcdb/leveldat/level_dat.go index eabdd1910..4e4d63ccb 100644 --- a/server/world/mcdb/leveldat/level_dat.go +++ b/server/world/mcdb/leveldat/level_dat.go @@ -4,9 +4,10 @@ import ( "bufio" "encoding/binary" "fmt" - "github.com/sandertv/gophertunnel/minecraft/nbt" "io" "os" + + "github.com/sandertv/gophertunnel/minecraft/nbt" ) // LevelDat implements the encoding and decoding of level.dat files. An empty @@ -40,7 +41,7 @@ func Read(r io.Reader) (*LevelDat, error) { return nil, fmt.Errorf("level.dat: read header: %w", err) } ldat.data = make([]byte, ldat.hdr.FileLength) - if n, err := r.Read(ldat.data); err != nil || int32(n) != ldat.hdr.FileLength { + if n, err := io.ReadFull(r, ldat.data); err != nil || int32(n) != ldat.hdr.FileLength { return nil, fmt.Errorf("level.dat: read data: %w", err) } return &ldat, nil From 7f68b0673dd7f0c5356590c5f9969ba407eb5aed Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC <30378179+TwistedAsylumMC@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:43:24 +0000 Subject: [PATCH 34/41] updated contributor list --- server/session/enchantment_texts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/session/enchantment_texts.go b/server/session/enchantment_texts.go index abe44b56b..078cbe42f 100644 --- a/server/session/enchantment_texts.go +++ b/server/session/enchantment_texts.go @@ -4,4 +4,4 @@ package session // enchantNames are names translated to the 'Standard Galactic Alphabet' client-side. The names generally have no meaning // on the vanilla server implementation, so we can sneak some easter eggs in here without anyone noticing. -var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "akmal fairuz", "alvin0319", "andreashgk", "assassin ghost yt", "atm85", "azvyl", "blackjack200", "cetfu", "cjmustard", "cooldogedev", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "driftlgtm", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "game parrot", "gewinum", "hashim the arab", "hochbaum", "hydzilla", "im da real ani", "inotflying", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "k4ties", "krivey", "manab-pr", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "nope not dark", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "schphe", "sculas", "sergittos", "sqmatheus", "ssaini123456", "studgi", "superomarking", "t14 raptor", "tadhunt", "theaddonn", "thicksunny", "thunder33345", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x toast-dev", "x4caa", "xd-pro"} +var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "akmal fairuz", "alvin0319", "andreashgk", "assassin ghost yt", "atm85", "azvyl", "blackjack200", "cetfu", "cjmustard", "cooldogedev", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "driftlgtm", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "game parrot", "gewinum", "hashim the arab", "hochbaum", "hydzilla", "im da real ani", "inotflying", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "k4ties", "krivey", "manab-pr", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "nope not dark", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "schphe", "sculas", "sergittos", "sqmatheus", "ssaini123456", "studgi", "superomarking", "t14 raptor", "tadhunt", "theaddonn", "thicksunny", "thunder33345", "tripple awap", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x toast-dev", "x4caa", "xd-pro"} From 477b816857d33b10f9262d303217df01836931ac Mon Sep 17 00:00:00 2001 From: RestartFU Date: Mon, 22 Jun 2026 06:56:47 -0400 Subject: [PATCH 35/41] session/session.go: fix panic when sessionless player logs (#1276) * Update session.go * Update session.go --- server/session/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/session/session.go b/server/session/session.go index 0bfcc7934..cdd1a69c1 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -144,7 +144,7 @@ type Conn interface { } // Nop represents a no-operation session. It does not do anything when sending a packet to it. -var Nop = &Session{} +var Nop = &Session{conf: Config{Log: slog.New(slog.DiscardHandler)}} // selfEntityRuntimeID is the entity runtime (or unique) ID of the controllable that the session holds. const selfEntityRuntimeID = 1 From 1746dd7a169471afeabdf2327d94ddd78ea6fd0e Mon Sep 17 00:00:00 2001 From: RestartFU Date: Mon, 22 Jun 2026 09:12:07 -0400 Subject: [PATCH 36/41] session/entity_metadata.go: guard nil session handle in metadata (#1278) * fix(session): guard nil session handle in metadata * fix(session): guard nil entity handles in session helpers * Update session_list.go * various changes * Update session.go --- server/session/entity_metadata.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/session/entity_metadata.go b/server/session/entity_metadata.go index 427266aac..2cdfb2cfe 100644 --- a/server/session/entity_metadata.go +++ b/server/session/entity_metadata.go @@ -148,7 +148,7 @@ func (s *Session) addSpecificMetadata(e any, m protocol.EntityMetadata) { } } - if l, ok := e.(living); ok && s.ent.UUID() == l.UUID() { + if l, ok := e.(living); ok && s.ent != nil && s.ent.UUID() == l.UUID() { deathPos, deathDimension, died := l.DeathPosition() if died { dim, _ := world.DimensionID(deathDimension) From 8be43f37729025ac0127ddce01a3a4d1eb4ce381 Mon Sep 17 00:00:00 2001 From: Smell of curry <75345244+smell-of-curry@users.noreply.github.com> Date: Wed, 24 Jun 2026 04:04:28 -0700 Subject: [PATCH 37/41] leveldat/data.go: Add missing fields (#1277) --- server/world/mcdb/leveldat/data.go | 157 +++++++++++++++-------------- 1 file changed, 80 insertions(+), 77 deletions(-) diff --git a/server/world/mcdb/leveldat/data.go b/server/world/mcdb/leveldat/data.go index 05fd50744..68f45038c 100644 --- a/server/world/mcdb/leveldat/data.go +++ b/server/world/mcdb/leveldat/data.go @@ -74,83 +74,86 @@ type Data struct { WalkSpeed float32 `nbt:"walkSpeed"` VerticalFlySpeed float32 `nbt:"verticalFlySpeed"` } `nbt:"abilities"` - BonusChestEnabled bool `nbt:"bonusChestEnabled"` - BonusChestSpawned bool `nbt:"bonusChestSpawned"` - CommandBlockOutput bool `nbt:"commandblockoutput"` - CommandBlocksEnabled bool `nbt:"commandblocksenabled"` - CommandsEnabled bool `nbt:"commandsEnabled"` - CurrentTick int64 `nbt:"currentTick"` - DoDayLightCycle bool `nbt:"dodaylightcycle"` - DoEntityDrops bool `nbt:"doentitydrops"` - DoFireTick bool `nbt:"dofiretick"` - DoImmediateRespawn bool `nbt:"doimmediaterespawn"` - DoInsomnia bool `nbt:"doinsomnia"` - DoMobLoot bool `nbt:"domobloot"` - DoMobSpawning bool `nbt:"domobspawning"` - DoTileDrops bool `nbt:"dotiledrops"` - DoWeatherCycle bool `nbt:"doweathercycle"` - DrowningDamage bool `nbt:"drowningdamage"` - EduLevel bool `nbt:"eduLevel"` - EducationFeaturesEnabled bool `nbt:"educationFeaturesEnabled"` - ExperimentalGamePlay bool `nbt:"experimentalgameplay"` - FallDamage bool `nbt:"falldamage"` - FireDamage bool `nbt:"firedamage"` - FunctionCommandLimit int32 `nbt:"functioncommandlimit"` - HasBeenLoadedInCreative bool `nbt:"hasBeenLoadedInCreative"` - HasLockedBehaviourPack bool `nbt:"hasLockedBehaviorPack"` - HasLockedResourcePack bool `nbt:"hasLockedResourcePack"` - ImmutableWorld bool `nbt:"immutableWorld"` - IsCreatedInEditor bool `nbt:"isCreatedInEditor"` - IsExportedFromEditor bool `nbt:"isExportedFromEditor"` - IsFromLockedTemplate bool `nbt:"isFromLockedTemplate"` - IsFromWorldTemplate bool `nbt:"isFromWorldTemplate"` - IsWorldTemplateOptionLocked bool `nbt:"isWorldTemplateOptionLocked"` - KeepInventory bool `nbt:"keepinventory"` - LastOpenedWithVersion []int32 `nbt:"lastOpenedWithVersion"` - LightningLevel float32 `nbt:"lightningLevel"` - LightningTime int32 `nbt:"lightningTime"` - MaxCommandChainLength int32 `nbt:"maxcommandchainlength"` - MobGriefing bool `nbt:"mobgriefing"` - NaturalRegeneration bool `nbt:"naturalregeneration"` - PRID string `nbt:"prid"` - PVP bool `nbt:"pvp"` - RainLevel float32 `nbt:"rainLevel"` - RainTime int32 `nbt:"rainTime"` - RandomTickSpeed int32 `nbt:"randomtickspeed"` - RequiresCopiedPackRemovalCheck bool `nbt:"requiresCopiedPackRemovalCheck"` - SendCommandFeedback bool `nbt:"sendcommandfeedback"` - ServerChunkTickRange int32 `nbt:"serverChunkTickRange"` - ShowCoordinates bool `nbt:"showcoordinates"` - ShowDeathMessages bool `nbt:"showdeathmessages"` - SpawnMobs bool `nbt:"spawnMobs"` - SpawnRadius int32 `nbt:"spawnradius"` - StartWithMapEnabled bool `nbt:"startWithMapEnabled"` - TexturePacksRequired bool `nbt:"texturePacksRequired"` - TNTExplodes bool `nbt:"tntexplodes"` - UseMSAGamerTagsOnly bool `nbt:"useMsaGamertagsOnly"` - WorldStartCount int64 `nbt:"worldStartCount"` - Experiments map[string]any `nbt:"experiments"` - FreezeDamage bool `nbt:"freezedamage"` - WorldPolicies map[string]any `nbt:"world_policies"` - WorldVersion int32 `nbt:"WorldVersion"` - RespawnBlocksExplode bool `nbt:"respawnblocksexplode"` - ShowBorderEffect bool `nbt:"showbordereffect"` - PermissionsLevel int32 `nbt:"permissionsLevel"` - PlayerPermissionsLevel int32 `nbt:"playerPermissionsLevel"` - IsRandomSeedAllowed bool `nbt:"isRandomSeedAllowed"` - DoLimitedCrafting bool `nbt:"dolimitedcrafting"` - EditorWorldType int32 `nbt:"editorWorldType"` - PlayersSleepingPercentage int32 `nbt:"playerssleepingpercentage"` - RecipesUnlock bool `nbt:"recipesunlock"` - NaturalGeneration bool `nbt:"naturalgeneration"` - ProjectilesCanBreakBlocks bool `nbt:"projectilescanbreakblocks"` - ShowRecipeMessages bool `nbt:"showrecipemessages"` - IsHardcore bool `nbt:"IsHardcore"` - ShowDaysPlayed bool `nbt:"showdaysplayed"` - TNTExplosionDropDecay bool `nbt:"tntexplosiondropdecay"` - HasUncompleteWorldFileOnDisk bool `nbt:"HasUncompleteWorldFileOnDisk"` - PlayerHasDied bool `nbt:"PlayerHasDied"` - UseAllowList bool `nbt:"UseAllowList"` + BonusChestEnabled bool `nbt:"bonusChestEnabled"` + BonusChestSpawned bool `nbt:"bonusChestSpawned"` + CommandBlockOutput bool `nbt:"commandblockoutput"` + CommandBlocksEnabled bool `nbt:"commandblocksenabled"` + CommandsEnabled bool `nbt:"commandsEnabled"` + CurrentTick int64 `nbt:"currentTick"` + DoDayLightCycle bool `nbt:"dodaylightcycle"` + DoEntityDrops bool `nbt:"doentitydrops"` + DoFireTick bool `nbt:"dofiretick"` + DoImmediateRespawn bool `nbt:"doimmediaterespawn"` + DoInsomnia bool `nbt:"doinsomnia"` + DoMobLoot bool `nbt:"domobloot"` + DoMobSpawning bool `nbt:"domobspawning"` + DoTileDrops bool `nbt:"dotiledrops"` + DoWeatherCycle bool `nbt:"doweathercycle"` + DrowningDamage bool `nbt:"drowningdamage"` + EduLevel bool `nbt:"eduLevel"` + EducationFeaturesEnabled bool `nbt:"educationFeaturesEnabled"` + ExperimentalGamePlay bool `nbt:"experimentalgameplay"` + FallDamage bool `nbt:"falldamage"` + FireDamage bool `nbt:"firedamage"` + FunctionCommandLimit int32 `nbt:"functioncommandlimit"` + HasBeenLoadedInCreative bool `nbt:"hasBeenLoadedInCreative"` + HasLockedBehaviourPack bool `nbt:"hasLockedBehaviorPack"` + HasLockedResourcePack bool `nbt:"hasLockedResourcePack"` + ImmutableWorld bool `nbt:"immutableWorld"` + IsCreatedInEditor bool `nbt:"isCreatedInEditor"` + IsExportedFromEditor bool `nbt:"isExportedFromEditor"` + IsFromLockedTemplate bool `nbt:"isFromLockedTemplate"` + IsFromWorldTemplate bool `nbt:"isFromWorldTemplate"` + IsWorldTemplateOptionLocked bool `nbt:"isWorldTemplateOptionLocked"` + KeepInventory bool `nbt:"keepinventory"` + LastOpenedWithVersion []int32 `nbt:"lastOpenedWithVersion"` + LightningLevel float32 `nbt:"lightningLevel"` + LightningTime int32 `nbt:"lightningTime"` + MaxCommandChainLength int32 `nbt:"maxcommandchainlength"` + MobGriefing bool `nbt:"mobgriefing"` + NaturalRegeneration bool `nbt:"naturalregeneration"` + PRID string `nbt:"prid"` + PVP bool `nbt:"pvp"` + RainLevel float32 `nbt:"rainLevel"` + RainTime int32 `nbt:"rainTime"` + RandomTickSpeed int32 `nbt:"randomtickspeed"` + RequiresCopiedPackRemovalCheck bool `nbt:"requiresCopiedPackRemovalCheck"` + SendCommandFeedback bool `nbt:"sendcommandfeedback"` + ServerChunkTickRange int32 `nbt:"serverChunkTickRange"` + ShowCoordinates bool `nbt:"showcoordinates"` + ShowDeathMessages bool `nbt:"showdeathmessages"` + SpawnMobs bool `nbt:"spawnMobs"` + SpawnRadius int32 `nbt:"spawnradius"` + StartWithMapEnabled bool `nbt:"startWithMapEnabled"` + TexturePacksRequired bool `nbt:"texturePacksRequired"` + TNTExplodes bool `nbt:"tntexplodes"` + UseMSAGamerTagsOnly bool `nbt:"useMsaGamertagsOnly"` + WorldStartCount int64 `nbt:"worldStartCount"` + Experiments map[string]any `nbt:"experiments"` + FreezeDamage bool `nbt:"freezedamage"` + WorldPolicies map[string]any `nbt:"world_policies"` + WorldVersion int32 `nbt:"WorldVersion"` + RespawnBlocksExplode bool `nbt:"respawnblocksexplode"` + ShowBorderEffect bool `nbt:"showbordereffect"` + PermissionsLevel int32 `nbt:"permissionsLevel"` + PlayerPermissionsLevel int32 `nbt:"playerPermissionsLevel"` + IsRandomSeedAllowed bool `nbt:"isRandomSeedAllowed"` + DoLimitedCrafting bool `nbt:"dolimitedcrafting"` + EditorWorldType int32 `nbt:"editorWorldType"` + PlayersSleepingPercentage int32 `nbt:"playerssleepingpercentage"` + RecipesUnlock bool `nbt:"recipesunlock"` + NaturalGeneration bool `nbt:"naturalgeneration"` + ProjectilesCanBreakBlocks bool `nbt:"projectilescanbreakblocks"` + ShowRecipeMessages bool `nbt:"showrecipemessages"` + IsHardcore bool `nbt:"IsHardcore"` + ShowDaysPlayed bool `nbt:"showdaysplayed"` + TNTExplosionDropDecay bool `nbt:"tntexplosiondropdecay"` + HasUncompleteWorldFileOnDisk bool `nbt:"HasUncompleteWorldFileOnDisk"` + PlayerHasDied bool `nbt:"PlayerHasDied"` + UseAllowList bool `nbt:"UseAllowList"` + AllowAnonymousBlockDropsInEditorWorlds bool `nbt:"allowAnonymousBlockDropsInEditorWorlds"` + PlayerWaypoints int32 `nbt:"playerwaypoints"` + ServerEditorConnectionPolicy int32 `nbt:"serverEditorConnectionPolicy"` } // FillDefault fills out d with all the default level.dat values. From 723721f9b5a9ef30084e592a93b530bd1de53463 Mon Sep 17 00:00:00 2001 From: TwistedAsylumMC <30378179+TwistedAsylumMC@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:04:41 +0000 Subject: [PATCH 38/41] updated contributor list --- server/session/enchantment_texts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/session/enchantment_texts.go b/server/session/enchantment_texts.go index 078cbe42f..ed1267f2b 100644 --- a/server/session/enchantment_texts.go +++ b/server/session/enchantment_texts.go @@ -4,4 +4,4 @@ package session // enchantNames are names translated to the 'Standard Galactic Alphabet' client-side. The names generally have no meaning // on the vanilla server implementation, so we can sneak some easter eggs in here without anyone noticing. -var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "akmal fairuz", "alvin0319", "andreashgk", "assassin ghost yt", "atm85", "azvyl", "blackjack200", "cetfu", "cjmustard", "cooldogedev", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "driftlgtm", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "game parrot", "gewinum", "hashim the arab", "hochbaum", "hydzilla", "im da real ani", "inotflying", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "k4ties", "krivey", "manab-pr", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "nope not dark", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "schphe", "sculas", "sergittos", "sqmatheus", "ssaini123456", "studgi", "superomarking", "t14 raptor", "tadhunt", "theaddonn", "thicksunny", "thunder33345", "tripple awap", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x toast-dev", "x4caa", "xd-pro"} +var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "akmal fairuz", "alvin0319", "andreashgk", "assassin ghost yt", "atm85", "azvyl", "blackjack200", "cetfu", "cjmustard", "cooldogedev", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "driftlgtm", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "game parrot", "gewinum", "hashim the arab", "hochbaum", "hydzilla", "im da real ani", "inotflying", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "k4ties", "krivey", "manab-pr", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "nope not dark", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "schphe", "sculas", "sergittos", "smell-of-curry", "sqmatheus", "ssaini123456", "studgi", "superomarking", "t14 raptor", "tadhunt", "theaddonn", "thicksunny", "thunder33345", "tripple awap", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x toast-dev", "x4caa", "xd-pro"} From 26be2a6ed61b74d32e1191d84a40495e7c12225f Mon Sep 17 00:00:00 2001 From: Hashim Date: Sun, 28 Jun 2026 05:28:36 -0400 Subject: [PATCH 39/41] chunk/block_registry.go: Add RuntimeIDToHash in BlockRegistry interface (#1284) --- server/world/chunk/block_registry.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/world/chunk/block_registry.go b/server/world/chunk/block_registry.go index d5a749dde..24888d2ba 100644 --- a/server/world/chunk/block_registry.go +++ b/server/world/chunk/block_registry.go @@ -27,4 +27,6 @@ type BlockRegistry interface { LiquidBlock(rid uint32) bool // HashToRuntimeID resolves a "network block hash" to a runtime ID. HashToRuntimeID(hash uint32) (rid uint32, ok bool) + // RuntimeIDToHash resolves a runtime ID to its "network block hash". + RuntimeIDToHash(runtimeID uint32) (hash uint32, ok bool) } From 263fb85eec476c1980d58b2e967247dfc880a77b Mon Sep 17 00:00:00 2001 From: MEMOxiiii <189345814+MEMOxiiii@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:33:37 +0300 Subject: [PATCH 40/41] server/block: Implement bamboo building blocks and items (#1292) * Implement bamboo building set * Order bamboo item registrations * Improve documentation for allWood function Updated the allWood function documentation to clarify that bamboo blocks are registered separately. * avoid duplicate bamboo block properties --- server/block/bamboo_block.go | 78 +++++++++++++++++++++++++++++++++++ server/block/bamboo_mosaic.go | 42 +++++++++++++++++++ server/block/hash.go | 10 +++++ server/block/log.go | 3 ++ server/block/register.go | 18 +++++--- server/block/slab.go | 3 ++ server/block/slab_type.go | 3 ++ server/block/stairs.go | 3 ++ server/block/stairs_type.go | 3 ++ server/block/wood.go | 6 ++- server/block/wood_type.go | 11 ++++- 11 files changed, 172 insertions(+), 8 deletions(-) create mode 100644 server/block/bamboo_block.go create mode 100644 server/block/bamboo_mosaic.go diff --git a/server/block/bamboo_block.go b/server/block/bamboo_block.go new file mode 100644 index 000000000..c03a1a03f --- /dev/null +++ b/server/block/bamboo_block.go @@ -0,0 +1,78 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl64" + "time" +) + +// BambooBlock is a rotatable flammable block made from bamboo. +type BambooBlock struct { + solid + bass + + // Axis is the axis which the bamboo block faces. + Axis cube.Axis + // Stripped specifies if the bamboo block is stripped. + Stripped bool +} + +// FlammabilityInfo ... +func (BambooBlock) FlammabilityInfo() FlammabilityInfo { + return newFlammabilityInfo(5, 5, true) +} + +// BreakInfo ... +func (b BambooBlock) BreakInfo() BreakInfo { + return newBreakInfo(2.0, alwaysHarvestable, axeEffective, oneOf(b)) +} + +// FuelInfo ... +func (BambooBlock) FuelInfo() item.FuelInfo { + return newFuelInfo(time.Second * 15) +} + +// UseOnBlock ... +func (b BambooBlock) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) { + pos, face, used = firstReplaceable(tx, pos, face, b) + if !used { + return + } + b.Axis = face.Axis() + + place(tx, pos, b, user, ctx) + return placed(ctx) +} + +// Strip ... +func (b BambooBlock) Strip() (world.Block, world.Sound, bool) { + return BambooBlock{Axis: b.Axis, Stripped: true}, nil, !b.Stripped +} + +// EncodeItem ... +func (b BambooBlock) EncodeItem() (name string, meta int16) { + if b.Stripped { + return "minecraft:stripped_bamboo_block", 0 + } + return "minecraft:bamboo_block", 0 +} + +// EncodeBlock ... +func (b BambooBlock) EncodeBlock() (name string, properties map[string]any) { + meta := map[string]any{"pillar_axis": b.Axis.String()} + if b.Stripped { + return "minecraft:stripped_bamboo_block", meta + } + return "minecraft:bamboo_block", meta +} + +// allBambooBlocks ... +func allBambooBlocks() (blocks []world.Block) { + for _, axis := range cube.Axes() { + blocks = append(blocks, BambooBlock{Axis: axis}) + blocks = append(blocks, BambooBlock{Axis: axis, Stripped: true}) + } + return +} diff --git a/server/block/bamboo_mosaic.go b/server/block/bamboo_mosaic.go new file mode 100644 index 000000000..5bd89fda2 --- /dev/null +++ b/server/block/bamboo_mosaic.go @@ -0,0 +1,42 @@ +package block + +import ( + "github.com/df-mc/dragonfly/server/item" + "time" +) + +// BambooMosaic is a decorative bamboo plank variant. +type BambooMosaic struct { + solid + bass +} + +// FlammabilityInfo ... +func (BambooMosaic) FlammabilityInfo() FlammabilityInfo { + return newFlammabilityInfo(5, 20, true) +} + +// BreakInfo ... +func (b BambooMosaic) BreakInfo() BreakInfo { + return newBreakInfo(2, alwaysHarvestable, axeEffective, oneOf(b)).withBlastResistance(15) +} + +// RepairsWoodTools ... +func (BambooMosaic) RepairsWoodTools() bool { + return true +} + +// FuelInfo ... +func (BambooMosaic) FuelInfo() item.FuelInfo { + return newFuelInfo(time.Second * 15) +} + +// EncodeItem ... +func (BambooMosaic) EncodeItem() (name string, meta int16) { + return "minecraft:bamboo_mosaic", 0 +} + +// EncodeBlock ... +func (BambooMosaic) EncodeBlock() (string, map[string]any) { + return "minecraft:bamboo_mosaic", nil +} diff --git a/server/block/hash.go b/server/block/hash.go index 684d9f010..55b51ec16 100644 --- a/server/block/hash.go +++ b/server/block/hash.go @@ -10,6 +10,8 @@ const ( hashAncientDebris hashAndesite hashAnvil + hashBambooBlock + hashBambooMosaic hashBanner hashBarrel hashBarrier @@ -240,6 +242,14 @@ func (a Anvil) Hash() (uint64, uint64) { return hashAnvil, uint64(a.Type.Uint8()) | uint64(a.Facing)<<2 } +func (b BambooBlock) Hash() (uint64, uint64) { + return hashBambooBlock, uint64(b.Axis) | uint64(boolByte(b.Stripped))<<2 +} + +func (BambooMosaic) Hash() (uint64, uint64) { + return hashBambooMosaic, 0 +} + func (b Banner) Hash() (uint64, uint64) { return hashBanner, uint64(b.Attach.Uint8()) } diff --git a/server/block/log.go b/server/block/log.go index 9303dd9f9..61cd441fa 100644 --- a/server/block/log.go +++ b/server/block/log.go @@ -106,6 +106,9 @@ func (l Log) EncodeBlock() (name string, properties map[string]any) { // allLogs returns a list of all possible log states. func allLogs() (logs []world.Block) { for _, w := range WoodTypes() { + if w == BambooWood() { + continue + } for axis := cube.Axis(0); axis < 3; axis++ { logs = append(logs, Log{Axis: axis, Stripped: true, Wood: w}) logs = append(logs, Log{Axis: axis, Stripped: false, Wood: w}) diff --git a/server/block/register.go b/server/block/register.go index 116c454e6..2dc5effd9 100644 --- a/server/block/register.go +++ b/server/block/register.go @@ -14,6 +14,7 @@ func init() { world.RegisterBlock(AncientDebris{}) world.RegisterBlock(Andesite{Polished: true}) world.RegisterBlock(Andesite{}) + world.RegisterBlock(BambooMosaic{}) world.RegisterBlock(Barrier{}) world.RegisterBlock(Beacon{}) world.RegisterBlock(Bedrock{InfiniteBurning: true}) @@ -138,6 +139,7 @@ func init() { } registerAll(allAnvils()) + registerAll(allBambooBlocks()) registerAll(allBanners()) registerAll(allBarrels()) registerAll(allBasalt()) @@ -243,6 +245,9 @@ func init() { world.RegisterItem(AncientDebris{}) world.RegisterItem(Andesite{Polished: true}) world.RegisterItem(Andesite{}) + world.RegisterItem(BambooBlock{}) + world.RegisterItem(BambooBlock{Stripped: true}) + world.RegisterItem(BambooMosaic{}) world.RegisterItem(Barrel{}) world.RegisterItem(Barrier{}) world.RegisterItem(Basalt{Polished: true}) @@ -440,20 +445,21 @@ func init() { world.RegisterItem(Wool{Colour: c}) } for _, w := range WoodTypes() { - if w != WarpedWood() && w != CrimsonWood() { - t, _ := w.Leaves() + if t, ok := w.Leaves(); ok { world.RegisterItem(Leaves{Type: t, Persistent: true}) } - world.RegisterItem(Log{Wood: w, Stripped: true}) - world.RegisterItem(Log{Wood: w}) + if w != BambooWood() { + world.RegisterItem(Log{Wood: w, Stripped: true}) + world.RegisterItem(Log{Wood: w}) + world.RegisterItem(Wood{Wood: w, Stripped: true}) + world.RegisterItem(Wood{Wood: w}) + } world.RegisterItem(Planks{Wood: w}) world.RegisterItem(Sign{Wood: w}) world.RegisterItem(WoodDoor{Wood: w}) world.RegisterItem(WoodFenceGate{Wood: w}) world.RegisterItem(WoodFence{Wood: w}) world.RegisterItem(WoodTrapdoor{Wood: w}) - world.RegisterItem(Wood{Wood: w, Stripped: true}) - world.RegisterItem(Wood{Wood: w}) } world.RegisterItem(Leaves{Type: AzaleaLeaves(), Persistent: true}) world.RegisterItem(Leaves{Type: FloweringAzaleaLeaves(), Persistent: true}) diff --git a/server/block/slab.go b/server/block/slab.go index 13cb1d90f..90c578f95 100644 --- a/server/block/slab.go +++ b/server/block/slab.go @@ -63,6 +63,9 @@ func (s Slab) Instrument() sound.Instrument { if _, ok := s.Block.(Planks); ok { return sound.Bass() } + if _, ok := s.Block.(BambooMosaic); ok { + return sound.Bass() + } return sound.BassDrum() } diff --git a/server/block/slab_type.go b/server/block/slab_type.go index f5e4185bf..476bd6446 100644 --- a/server/block/slab_type.go +++ b/server/block/slab_type.go @@ -25,6 +25,8 @@ func encodeSlabBlock(block world.Block, double bool) (id string, suffix string) } else if block.Type == PolishedBlackstone() { return "polished_blackstone", suffix } + case BambooMosaic: + return "bamboo_mosaic", suffix case Bricks: return "brick", suffix case Cobblestone: @@ -153,6 +155,7 @@ func SlabBlocks() []world.Block { b := []world.Block{ Andesite{Polished: true}, Andesite{}, + BambooMosaic{}, Blackstone{Type: PolishedBlackstone()}, Blackstone{}, Bricks{}, diff --git a/server/block/stairs.go b/server/block/stairs.go index 665199100..ff0e4827d 100644 --- a/server/block/stairs.go +++ b/server/block/stairs.go @@ -55,6 +55,9 @@ func (s Stairs) Instrument() sound.Instrument { if _, ok := s.Block.(Planks); ok { return sound.Bass() } + if _, ok := s.Block.(BambooMosaic); ok { + return sound.Bass() + } return sound.BassDrum() } diff --git a/server/block/stairs_type.go b/server/block/stairs_type.go index e3b2d9f94..a2b1e8dcc 100644 --- a/server/block/stairs_type.go +++ b/server/block/stairs_type.go @@ -18,6 +18,8 @@ func encodeStairsBlock(block world.Block) string { } else if block.Type == PolishedBlackstone() { return "polished_blackstone" } + case BambooMosaic: + return "bamboo_mosaic" case Bricks: return "brick" case Cobblestone: @@ -136,6 +138,7 @@ func StairsBlocks() []world.Block { b := []world.Block{ Andesite{Polished: true}, Andesite{}, + BambooMosaic{}, Blackstone{Type: PolishedBlackstone()}, Blackstone{}, Bricks{}, diff --git a/server/block/wood.go b/server/block/wood.go index 787c8573c..c58a07091 100644 --- a/server/block/wood.go +++ b/server/block/wood.go @@ -98,9 +98,13 @@ func (w Wood) EncodeBlock() (name string, properties map[string]any) { } } -// allWood returns a list of all possible wood states. +// allWood returns all possible Wood block states, excluding bamboo blocks, +// which are registered separately through allBambooBlocks. func allWood() (wood []world.Block) { for _, w := range WoodTypes() { + if w == BambooWood() { + continue + } for axis := cube.Axis(0); axis < 3; axis++ { wood = append(wood, Wood{Axis: axis, Stripped: true, Wood: w}) wood = append(wood, Wood{Axis: axis, Stripped: false, Wood: w}) diff --git a/server/block/wood_type.go b/server/block/wood_type.go index ded6df199..cafd9b064 100644 --- a/server/block/wood_type.go +++ b/server/block/wood_type.go @@ -61,9 +61,14 @@ func PaleOakWood() WoodType { return WoodType{10} } +// BambooWood returns bamboo wood material. +func BambooWood() WoodType { + return WoodType{11} +} + // WoodTypes returns a list of all wood types func WoodTypes() []WoodType { - return []WoodType{OakWood(), SpruceWood(), BirchWood(), JungleWood(), AcaciaWood(), DarkOakWood(), CrimsonWood(), WarpedWood(), MangroveWood(), CherryWood(), PaleOakWood()} + return []WoodType{OakWood(), SpruceWood(), BirchWood(), JungleWood(), AcaciaWood(), DarkOakWood(), CrimsonWood(), WarpedWood(), MangroveWood(), CherryWood(), PaleOakWood(), BambooWood()} } type wood uint8 @@ -98,6 +103,8 @@ func (w wood) Name() string { return "Cherry Wood" case 10: return "Pale Oak Wood" + case 11: + return "Bamboo Wood" } panic("unknown wood type") } @@ -127,6 +134,8 @@ func (w wood) String() string { return "cherry" case 10: return "pale_oak" + case 11: + return "bamboo" } panic("unknown wood type") } From 026c0621cf9b237c2531d119e2e0aae61b19daf2 Mon Sep 17 00:00:00 2001 From: HashimTheArab <13991048+HashimTheArab@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:33:51 +0000 Subject: [PATCH 41/41] updated contributor list --- server/session/enchantment_texts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/session/enchantment_texts.go b/server/session/enchantment_texts.go index ed1267f2b..853823736 100644 --- a/server/session/enchantment_texts.go +++ b/server/session/enchantment_texts.go @@ -4,4 +4,4 @@ package session // enchantNames are names translated to the 'Standard Galactic Alphabet' client-side. The names generally have no meaning // on the vanilla server implementation, so we can sneak some easter eggs in here without anyone noticing. -var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "akmal fairuz", "alvin0319", "andreashgk", "assassin ghost yt", "atm85", "azvyl", "blackjack200", "cetfu", "cjmustard", "cooldogedev", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "driftlgtm", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "game parrot", "gewinum", "hashim the arab", "hochbaum", "hydzilla", "im da real ani", "inotflying", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "k4ties", "krivey", "manab-pr", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "nope not dark", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "schphe", "sculas", "sergittos", "smell-of-curry", "sqmatheus", "ssaini123456", "studgi", "superomarking", "t14 raptor", "tadhunt", "theaddonn", "thicksunny", "thunder33345", "tripple awap", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x toast-dev", "x4caa", "xd-pro"} +var enchantNames = []string{"aabstractt", "abimek", "aericio", "aimjel", "akmal fairuz", "alvin0319", "andreashgk", "assassin ghost yt", "atm85", "azvyl", "blackjack200", "cetfu", "cjmustard", "cooldogedev", "cqdetdev", "da pig guy", "daft0175", "dasciam", "deniel world", "didntpot", "driftlgtm", "eminarican", "endermanbugzjfc", "erkam246", "ethaniccc", "fdutch", "flonja", "game parrot", "gewinum", "hashim the arab", "hochbaum", "hydzilla", "im da real ani", "inotflying", "ipad54", "its zodia x", "ivan craft623", "javier leon9966", "just tal develops", "k4ties", "krivey", "manab-pr", "memoxiiii", "mmm545", "mohamed587100", "myma qc", "natuyasai natuo", "neutronic mc", "nonono697", "nope not dark", "provsalt", "restart fu", "riccskn", "robertdudaa", "royal mcpe", "sallypemdas", "sandertv", "schphe", "sculas", "sergittos", "smell-of-curry", "sqmatheus", "ssaini123456", "studgi", "superomarking", "t14 raptor", "tadhunt", "theaddonn", "thicksunny", "thunder33345", "tripple awap", "tristanmorgan", "twisted asylum mc", "unickorn", "unknown ore", "uramnoil", "wqrro", "x natsuri", "x toast-dev", "x4caa", "xd-pro"}