Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Implement provider adapters to bridge your world and player systems:
```go
sim := bedsim.Simulator{
World: myWorldProvider, // block lookups, collisions, chunk-loaded checks
BlockSemantics: myBlockSemantics, // optional: per-world names, friction, climbability
BlockSemantics: myBlockSemantics, // optional: complete per-world movement semantics
Liquids: myLiquidProvider, // second block layer (waterlogged blocks)
Effects: myEffectsProvider, // jump boost, levitation, slow falling
Inventory: myInventoryProvider, // elytra equipped check
Expand All @@ -44,8 +44,18 @@ if result.NeedsCorrection {

Set `BlockSemantics` when movement behavior must come from a per-world block
registry or custom block data instead of bedsim's Dragonfly-backed defaults.
Custom friction values must be finite and positive; invalid values fall back to
Dragonfly defaults.
The adapter implements `BlockMovementSemanticsProvider` and returns the full
`block.MovementSemantics` bundle: ground friction, any acceleration-only
friction multiplier, climbability, cobweb status, and slime/bed bounce
behavior. Built-in rules live in the
`github.com/oomph-ac/bedsim/block` package. Custom ground friction must be
finite and positive; an invalid value falls back to the built-in resolver. An
invalid acceleration multiplier likewise falls back to the built-in block
semantics.

BedSim's semantics package does not mutate Dragonfly's registry. Applications
own registry setup and must register any additional block implementations
before finalizing their registry.

Implement `DepthStriderProvider` on the inventory adapter when Depth Strider
should affect water movement.
Expand Down
34 changes: 0 additions & 34 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,40 +33,6 @@ func BlockName(b world.Block) string {
return stored.(string)
}

// BlockFriction returns the friction of the block.
func BlockFriction(b world.Block) float32 {
if f, ok := b.(block.Frictional); ok {
return float32(f.Friction())
}

switch BlockName(b) {
case "minecraft:slime":
return 0.8
case "minecraft:ice", "minecraft:packed_ice":
return 0.98
case "minecraft:blue_ice":
return 0.99
default:
return 0.6
}
}

// BlockClimbable returns whether the given block is climbable.
func BlockClimbable(b world.Block) bool {
switch b.(type) {
case block.Ladder:
return true
}

switch BlockName(b) {
case "minecraft:vine", "minecraft:cave_vines", "minecraft:cave_vines_body_with_berries", "minecraft:cave_vines_head_with_berries",
"minecraft:twisting_vines", "minecraft:weeping_vines":
return true
default:
return false
}
}

// BlockSupportHeight returns the effective standing surface height for a ground
// block by sampling its collision boxes at the block centre (0.5, 0.5).
// This handles slabs, stairs, and any other sub-block geometry correctly.
Expand Down
36 changes: 36 additions & 0 deletions block/bounce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package block

import (
dfblock "github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/world"
)

type slime struct{}

func (slime) Matches(b world.Block, name string) bool {
if _, ok := b.(dfblock.Slime); ok {
return true
}
return name == "minecraft:slime"
}

func (slime) Apply(s *resolution) {
if !s.groundFrictionSet {
s.GroundFriction = 0.8
s.groundFrictionSet = true
}
s.Bounce = BounceSlime
}

type bed struct{}

func (bed) Matches(b world.Block, name string) bool {
if _, ok := b.(dfblock.Bed); ok {
return true
}
return name == "minecraft:bed"
}

func (bed) Apply(s *resolution) {
s.Bounce = BounceBed
}
26 changes: 26 additions & 0 deletions block/climbable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package block

import (
dfblock "github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/world"
)

type climbableBlock struct{}

func (climbableBlock) Matches(b world.Block, name string) bool {
if _, ok := b.(dfblock.Ladder); ok {
return true
}

switch name {
case "minecraft:ladder", "minecraft:vine", "minecraft:cave_vines", "minecraft:cave_vines_body_with_berries", "minecraft:cave_vines_head_with_berries",
"minecraft:twisting_vines", "minecraft:weeping_vines":
return true
default:
return false
}
}

func (climbableBlock) Apply(s *resolution) {
s.Climbable = true
}
13 changes: 13 additions & 0 deletions block/contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package block

import "github.com/df-mc/dragonfly/server/world"

type cobweb struct{}

func (cobweb) Matches(_ world.Block, name string) bool {
return name == "minecraft:web" || name == "minecraft:cobweb"
}

func (cobweb) Apply(s *resolution) {
s.Cobweb = true
}
19 changes: 19 additions & 0 deletions block/friction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package block

import "github.com/df-mc/dragonfly/server/world"

type frictionBlock struct {
name string
friction float32
}

func (b frictionBlock) Matches(_ world.Block, name string) bool {
return name == b.name
}

func (b frictionBlock) Apply(s *resolution) {
if !s.groundFrictionSet {
s.GroundFriction = b.friction
s.groundFrictionSet = true
}
}
19 changes: 19 additions & 0 deletions block/ground.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package block

import (
dfblock "github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/world"
)

type soulSand struct{}

func (soulSand) Matches(b world.Block, name string) bool {
if _, ok := b.(dfblock.SoulSand); ok {
return true
}
return name == "minecraft:soul_sand"
}

func (soulSand) Apply(s *resolution) {
s.GroundAccelerationFrictionMultiplier *= SoulSandAccelerationFrictionMultiplier
}
80 changes: 80 additions & 0 deletions block/semantics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Package block provides BedSim's built-in movement semantics.
package block

import (
dfblock "github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/world"
)

// SoulSandAccelerationFrictionMultiplier is the native adjustment used when
// calculating grounded acceleration on soul sand. Ordinary drag still uses the
// block's unadjusted friction. This exact native value replaces the legacy
// 0.543 speed approximation.
const SoulSandAccelerationFrictionMultiplier float32 = 1.225000023841858

// Bounce identifies a block's landing response.
type Bounce uint8

const (
BounceNone Bounce = iota
BounceSlime
BounceBed
)

// MovementSemantics is the movement behavior resolved for a block.
type MovementSemantics struct {
GroundFriction float32
GroundAccelerationFrictionMultiplier float32
Climbable bool
Cobweb bool
Bounce Bounce
}

// rule contributes movement behavior for a block family.
type rule interface {
Matches(world.Block, string) bool
Apply(*resolution)
}

type resolution struct {
MovementSemantics
groundFrictionSet bool
}

var rules = [...]rule{
soulSand{},
climbableBlock{},
cobweb{},
slime{},
bed{},
frictionBlock{name: "minecraft:ice", friction: 0.98},
frictionBlock{name: "minecraft:packed_ice", friction: 0.98},
frictionBlock{name: "minecraft:blue_ice", friction: 0.989},
}

// Resolve returns built-in movement semantics for b.
func Resolve(b world.Block, name string) MovementSemantics {
result := resolution{
MovementSemantics: MovementSemantics{
GroundAccelerationFrictionMultiplier: 1,
},
}
if f, ok := b.(dfblock.Frictional); ok {
result.GroundFriction = float32(f.Friction())
result.groundFrictionSet = true
}
for _, rule := range rules {
if rule.Matches(b, name) {
rule.Apply(&result)
}
}
if !result.groundFrictionSet {
result.GroundFriction = 0.6
}
return result.MovementSemantics
}

// Friction returns a block's ordinary friction.
func Friction(b world.Block, name string) float32 {
return Resolve(b, name).GroundFriction
}
49 changes: 49 additions & 0 deletions block/semantics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package block

import (
"testing"

"github.com/df-mc/dragonfly/server/world"
)

type countingRule struct {
matches int
}

func (r *countingRule) Matches(world.Block, string) bool {
r.matches++
return true
}

func (*countingRule) Apply(s *resolution) {
s.GroundFriction = 0.7
s.groundFrictionSet = true
}

func TestResolveVisitsEachRuleOnce(t *testing.T) {
original := rules[0]
rule := &countingRule{}
rules[0] = rule
t.Cleanup(func() { rules[0] = original })

Resolve(namedBlock{"minecraft:test"}, "minecraft:test")
if rule.matches != 1 {
t.Fatalf("rule matches called %d times, want 1", rule.matches)
}
}

type namedBlock struct {
name string
}

func (b namedBlock) EncodeBlock() (string, map[string]any) {
return b.name, nil
}

func (namedBlock) Hash() (uint64, uint64) {
return 0, 0
}

func (namedBlock) Model() world.BlockModel {
return nil
}
Loading