diff --git a/server/block/liquid.go b/server/block/liquid.go index fdebc4ed2..9c31559a1 100644 --- a/server/block/liquid.go +++ b/server/block/liquid.go @@ -5,6 +5,7 @@ import ( "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" "sync" ) @@ -33,6 +34,52 @@ func (s flowingWaterDisplacer) CanDisplace(b world.Liquid) bool { return ok } +// FlowVector returns the flow vector of the liquid at the given position. +func FlowVector(pos cube.Pos, w *world.World) (v mgl64.Vec3) { + l, decay, ok := effectiveFlowDecay(pos, w) + if !ok { + return + } + for _, f := range cube.HorizontalFaces() { + sidePos := pos.Side(f) + offset := sidePos.Sub(pos).Vec3Middle() + + _, blockDecay, ok := effectiveFlowDecay(sidePos, w) + if !ok { + if _, ok = w.Block(sidePos).(LiquidRemovable); !ok { + continue + } + if _, blockDecay, ok = effectiveFlowDecay(sidePos.Side(cube.FaceDown), w); !ok { + continue + } + v = v.Sub(offset.Mul(blockDecay - decay - 8)) + continue + } + v = v.Sub(offset.Mul(blockDecay - decay)) + } + if l.LiquidFalling() { + for _, f := range cube.HorizontalFaces() { + p := pos.Side(f) + if !canFlowInto(l, w, p, true) || !canFlowInto(l, w, p.Side(cube.FaceUp), true) { + v = v.Normalize().Sub(mgl64.Vec3{0, 6}) + } + } + } + return v.Normalize() +} + +// effectiveFlowDecay ... +func effectiveFlowDecay(pos cube.Pos, w *world.World) (world.Liquid, float64, bool) { + l, ok := w.Liquid(pos) + if !ok { + return l, 0, false + } + if l.LiquidFalling() { + return l, 0, true + } + return l, float64(l.LiquidDepth()), true +} + // tickLiquid ticks the liquid block passed at a specific position in the world. Depending on the surroundings // and the liquid block, the liquid will either spread or decrease in depth. Additionally, the liquid might // be turned into a solid block if a different liquid is next to it. diff --git a/server/entity/direction.go b/server/entity/direction.go index 770cb06e0..6b54837be 100644 --- a/server/entity/direction.go +++ b/server/entity/direction.go @@ -17,6 +17,8 @@ func EyePosition(e world.Entity) mgl64.Vec3 { pos := e.Position() if eyed, ok := e.(Eyed); ok { pos[1] += eyed.EyeHeight() + } else { + pos[1] += e.Type().BBox(e).Height() * 0.85 } return pos } diff --git a/server/entity/item.go b/server/entity/item.go index ccb883b43..933f16aba 100644 --- a/server/entity/item.go +++ b/server/entity/item.go @@ -32,8 +32,8 @@ func NewItem(i item.Stack, pos mgl64.Vec3) *Item { it := &Item{i: i, pickupDelay: 10, c: &MovementComputer{ Gravity: 0.04, - DragBeforeGravity: true, Drag: 0.02, + DragBeforeGravity: true, }} it.transform = newTransform(it, pos) return it @@ -77,6 +77,7 @@ func (it *Item) Tick(w *world.World, current int64) { return } + it.tickLiquid(w, m.vel) if it.pickupDelay == 0 { it.checkNearby(w, m.pos) } else if it.pickupDelay != math.MaxInt16 { @@ -84,6 +85,42 @@ func (it *Item) Tick(w *world.World, current int64) { } } +// tickLiquid ticks the item's liquid velocity and updates it as necessary. +func (it *Item) tickLiquid(w *world.World, vel mgl64.Vec3) { + if l, ok := it.insideLiquid(EyePosition(it), w); ok { + vel[1] += it.c.Gravity + vel[1] /= 1 - it.c.Drag + if vel[1] < 0.06 { + vel[1] += 0.0005 + } + switch l.(type) { + case block.Water: + vel[0] *= 0.99 + vel[2] *= 0.99 + case block.Lava: + vel[0] *= 0.95 + vel[2] *= 0.95 + } + it.SetVelocity(vel) + } +} + +// liquidLevel is the lowest distance the item can be in liquid. +const liquidLevel = 0.11111111 + +// insideLiquid returns true if the item is currently in a liquid. +func (it *Item) insideLiquid(pos mgl64.Vec3, w *world.World) (world.Liquid, bool) { + blockPos := cube.PosFromVec3(pos) + if l, ok := w.Liquid(blockPos); ok { + d := float64(l.SpreadDecay()) + 1 + if l.LiquidFalling() { + d = 1 + } + return l, pos.Y() < (blockPos.Vec3().Y()+1)-(d/9-liquidLevel) + } + return nil, false +} + // checkNearby checks the entities of the chunks around for item collectors and other item stacks. If a // collector is found in range, the item will be picked up. If another item stack with the same item type is // found in range, the item stacks will merge. diff --git a/server/entity/movement.go b/server/entity/movement.go index daaca4a85..ffa083fc2 100644 --- a/server/entity/movement.go +++ b/server/entity/movement.go @@ -1,6 +1,7 @@ package entity import ( + "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" @@ -69,6 +70,7 @@ func (c *MovementComputer) TickMovement(e world.Entity, pos, vel mgl64.Vec3, yaw velBefore := vel vel = c.applyHorizontalForces(w, pos, c.applyVerticalForces(vel)) dPos, vel := c.checkCollision(e, pos, vel) + vel = c.applyLiquidFlow(e, pos, vel) return &Movement{v: viewers, e: e, pos: pos.Add(dPos), vel: vel, dpos: dPos, dvel: vel.Sub(velBefore), @@ -101,9 +103,10 @@ func (c *MovementComputer) applyVerticalForces(vel mgl64.Vec3) mgl64.Vec3 { // applyHorizontalForces applies friction to the velocity based on the Drag value, reducing it on the X and Z axes. func (c *MovementComputer) applyHorizontalForces(w *world.World, pos, vel mgl64.Vec3) mgl64.Vec3 { + blockPos := cube.PosFromVec3(pos) friction := 1 - c.Drag if c.onGround { - if f, ok := w.Block(cube.PosFromVec3(pos).Side(cube.FaceDown)).(interface { + if f, ok := w.Block(blockPos.Side(cube.FaceDown)).(interface { Friction() float64 }); ok { friction *= f.Friction() @@ -116,6 +119,38 @@ func (c *MovementComputer) applyHorizontalForces(w *world.World, pos, vel mgl64. return vel } +// applyLiquidFlow applies liquid flow to the entity's velocity. +func (c *MovementComputer) applyLiquidFlow(e world.Entity, pos, vel mgl64.Vec3) mgl64.Vec3 { + w := e.World() + box := e.Type().BBox(e).Grow(-0.001).Translate(pos) + + 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])) + + flow, i := zeroVec3, 0 + for y := minY; y <= maxY; y++ { + for x := minX; x <= maxX; x++ { + for z := minZ; z <= maxZ; z++ { + blockPos := cube.Pos{x, y, z} + if _, ok := w.Liquid(blockPos); ok { + flow = flow.Add(block.FlowVector(blockPos, w)) + i++ + } + } + } + } + + if flow.Len() > 0.0 { + flow = flow.Mul(1.0 / float64(i)).Normalize().Mul(0.014) + if math.Abs(vel.X()) < 0.003 && math.Abs(vel.Z()) < 0.003 && flow.Len() < 0.0045 { + flow = flow.Normalize().Mul(0.0045) + } + return vel.Add(flow) + } + return vel +} + // checkCollision handles the collision of the entity with blocks, adapting the velocity of the entity if it // happens to collide with a block. // The final velocity and the Vec3 that the entity should move is returned.