up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,384 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// Breakable represents a block that may be broken by a player in survival mode. Blocks not include are blocks
|
||||
// such as bedrock.
|
||||
type Breakable interface {
|
||||
// BreakInfo returns information of the block related to the breaking of it. Callers that execute the BreakHandler
|
||||
// must call BreakInfo on the concrete block value being broken, as handlers may need block state after the world
|
||||
// position has been cleared.
|
||||
BreakInfo() BreakInfo
|
||||
}
|
||||
|
||||
// BreakDuration returns the base duration that breaking the block passed takes when being broken using the
|
||||
// item passed.
|
||||
func BreakDuration(b world.Block, i item.Stack) time.Duration {
|
||||
breakable, ok := b.(Breakable)
|
||||
if !ok {
|
||||
return math.MaxInt64
|
||||
}
|
||||
t, ok := i.Item().(item.Tool)
|
||||
if !ok {
|
||||
t = item.ToolNone{}
|
||||
}
|
||||
info := breakable.BreakInfo()
|
||||
|
||||
breakTime := info.Hardness * 5
|
||||
if info.Harvestable(t) {
|
||||
breakTime = info.Hardness * 1.5
|
||||
}
|
||||
if info.Effective(t) {
|
||||
eff := t.BaseMiningEfficiency(b)
|
||||
if e, ok := i.Enchantment(enchantment.Efficiency); ok {
|
||||
eff += enchantment.Efficiency.Addend(e.Level())
|
||||
}
|
||||
breakTime /= eff
|
||||
}
|
||||
// TODO: Account for haste etc here.
|
||||
timeInTicksAccurate := math.Round(breakTime/0.05) * 0.05
|
||||
|
||||
return (time.Duration(math.Round(timeInTicksAccurate*20)) * time.Second) / 20
|
||||
}
|
||||
|
||||
// BreaksInstantly checks if the block passed can be broken instantly using the item stack passed to break
|
||||
// it.
|
||||
func BreaksInstantly(b world.Block, i item.Stack) bool {
|
||||
breakable, ok := b.(Breakable)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
hardness := breakable.BreakInfo().Hardness
|
||||
if hardness == 0 {
|
||||
return true
|
||||
}
|
||||
t, ok := i.Item().(item.Tool)
|
||||
if !ok || !breakable.BreakInfo().Effective(t) {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: Account for haste etc here.
|
||||
efficiencyVal := 0.0
|
||||
if e, ok := i.Enchantment(enchantment.Efficiency); ok {
|
||||
efficiencyVal += enchantment.Efficiency.Addend(e.Level())
|
||||
}
|
||||
hasteVal := 0.0
|
||||
return (t.BaseMiningEfficiency(b)+efficiencyVal)*hasteVal >= hardness*30
|
||||
}
|
||||
|
||||
// BreakInfo is a struct returned by every block. It holds information on block breaking related data, such as
|
||||
// the tool type and tier required to break it.
|
||||
type BreakInfo struct {
|
||||
// Hardness is the hardness of the block, which influences the speed with which the block may be mined.
|
||||
Hardness float64
|
||||
// Harvestable is a function called to check if the block is harvestable using the tool passed. If the
|
||||
// item used to break the block is not a tool, a tool.ToolNone is passed.
|
||||
Harvestable func(t item.Tool) bool
|
||||
// Effective is a function called to check if the block can be mined more effectively with the tool passed
|
||||
// than with an empty hand.
|
||||
Effective func(t item.Tool) bool
|
||||
// Drops is a function called to get the drops of the block if it is broken using the item passed.
|
||||
Drops func(t item.Tool, enchantments []item.Enchantment) []item.Stack
|
||||
// BreakHandler is called after the block has broken.
|
||||
BreakHandler func(pos cube.Pos, w *world.Tx, u item.User)
|
||||
// XPDrops is the range of XP a block can drop when broken.
|
||||
XPDrops XPDropRange
|
||||
// BlastResistance is the blast resistance of the block, which influences the block's ability to withstand an
|
||||
// explosive blast.
|
||||
BlastResistance float64
|
||||
}
|
||||
|
||||
// newBreakInfo creates a BreakInfo struct with the properties passed. The XPDrops field is 0 by default. The blast
|
||||
// resistance is set to the block's hardness*5 by default.
|
||||
func newBreakInfo(hardness float64, harvestable func(item.Tool) bool, effective func(item.Tool) bool, drops func(item.Tool, []item.Enchantment) []item.Stack) BreakInfo {
|
||||
return BreakInfo{
|
||||
Hardness: hardness,
|
||||
BlastResistance: hardness * 5,
|
||||
Harvestable: harvestable,
|
||||
Effective: effective,
|
||||
Drops: drops,
|
||||
}
|
||||
}
|
||||
|
||||
// withXPDropRange sets the XPDropRange field of the BreakInfo struct to the passed value.
|
||||
func (b BreakInfo) withXPDropRange(min, max int) BreakInfo {
|
||||
b.XPDrops = XPDropRange{min, max}
|
||||
return b
|
||||
}
|
||||
|
||||
// withBlastResistance sets the BlastResistance field of the BreakInfo struct to the passed value.
|
||||
func (b BreakInfo) withBlastResistance(res float64) BreakInfo {
|
||||
b.BlastResistance = res
|
||||
return b
|
||||
}
|
||||
|
||||
// withBreakHandler sets the BreakHandler field of the BreakInfo struct to the passed value.
|
||||
func (b BreakInfo) withBreakHandler(handler func(pos cube.Pos, w *world.Tx, u item.User)) BreakInfo {
|
||||
b.BreakHandler = handler
|
||||
return b
|
||||
}
|
||||
|
||||
// XPDropRange holds the min & max XP drop amounts of blocks.
|
||||
type XPDropRange [2]int
|
||||
|
||||
// RandomValue returns a random XP value that falls within the drop range.
|
||||
func (r XPDropRange) RandomValue() int {
|
||||
diff := r[1] - r[0]
|
||||
// Add one because it's a [r[0], r[1]] interval.
|
||||
return rand.IntN(diff+1) + r[0]
|
||||
}
|
||||
|
||||
// pickaxeEffective is a convenience function for blocks that are effectively mined with a pickaxe.
|
||||
var pickaxeEffective = func(t item.Tool) bool {
|
||||
return t.ToolType() == item.TypePickaxe
|
||||
}
|
||||
|
||||
// axeEffective is a convenience function for blocks that are effectively mined with an axe.
|
||||
var axeEffective = func(t item.Tool) bool {
|
||||
return t.ToolType() == item.TypeAxe
|
||||
}
|
||||
|
||||
// shearsEffective is a convenience function for blocks that are effectively mined with shears.
|
||||
var shearsEffective = func(t item.Tool) bool {
|
||||
return t.ToolType() == item.TypeShears
|
||||
}
|
||||
|
||||
// swordEffective is a convenience function for blocks that are effectively mined with a sword.
|
||||
var swordEffective = func(t item.Tool) bool {
|
||||
return t.ToolType() == item.TypeSword
|
||||
}
|
||||
|
||||
// shovelEffective is a convenience function for blocks that are effectively mined with a shovel.
|
||||
var shovelEffective = func(t item.Tool) bool {
|
||||
return t.ToolType() == item.TypeShovel
|
||||
}
|
||||
|
||||
// hoeEffective is a convenience function for blocks that are effectively mined with a hoe.
|
||||
var hoeEffective = func(t item.Tool) bool {
|
||||
return t.ToolType() == item.TypeHoe
|
||||
}
|
||||
|
||||
// nothingEffective is a convenience function for blocks that cannot be mined efficiently with any tool.
|
||||
var nothingEffective = func(item.Tool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// alwaysHarvestable is a convenience function for blocks that are harvestable using any item.
|
||||
var alwaysHarvestable = func(t item.Tool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// neverHarvestable is a convenience function for blocks that are not harvestable by any item.
|
||||
var neverHarvestable = func(t item.Tool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// pickaxeHarvestable is a convenience function for blocks that are harvestable using any kind of pickaxe.
|
||||
var pickaxeHarvestable = pickaxeEffective
|
||||
|
||||
// simpleDrops returns a drops function that returns the items passed.
|
||||
func simpleDrops(s ...item.Stack) func(item.Tool, []item.Enchantment) []item.Stack {
|
||||
return func(item.Tool, []item.Enchantment) []item.Stack {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// oneOf returns a drops function that returns one of each of the item types passed.
|
||||
func oneOf(i ...world.Item) func(item.Tool, []item.Enchantment) []item.Stack {
|
||||
return func(item.Tool, []item.Enchantment) []item.Stack {
|
||||
var s []item.Stack
|
||||
for _, it := range i {
|
||||
s = append(s, item.NewStack(it, 1))
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// hasSilkTouch checks if an item has the silk touch enchantment.
|
||||
func hasSilkTouch(enchantments []item.Enchantment) bool {
|
||||
return slices.IndexFunc(enchantments, func(i item.Enchantment) bool {
|
||||
return i.Type() == enchantment.SilkTouch
|
||||
}) != -1
|
||||
}
|
||||
|
||||
// silkTouchOneOf returns a drop function that returns 1x of the silk touch drop when silk touch exists, or 1x of the
|
||||
// normal drop when it does not.
|
||||
func silkTouchOneOf(normal, silkTouch 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(silkTouch, 1)}
|
||||
}
|
||||
return []item.Stack{item.NewStack(normal, 1)}
|
||||
}
|
||||
}
|
||||
|
||||
// silkTouchDrop returns a drop function that returns the silk touch drop when silk touch exists, or the
|
||||
// normal drop when it does not.
|
||||
func silkTouchDrop(normal, silkTouch item.Stack) func(item.Tool, []item.Enchantment) []item.Stack {
|
||||
return func(t item.Tool, enchantments []item.Enchantment) []item.Stack {
|
||||
if hasSilkTouch(enchantments) {
|
||||
return []item.Stack{silkTouch}
|
||||
}
|
||||
return []item.Stack{normal}
|
||||
}
|
||||
}
|
||||
|
||||
// silkTouchOnlyDrop returns a drop function that returns the drop when silk touch exists.
|
||||
func silkTouchOnlyDrop(it world.Item) func(t item.Tool, enchantments []item.Enchantment) []item.Stack {
|
||||
return func(t item.Tool, enchantments []item.Enchantment) []item.Stack {
|
||||
if hasSilkTouch(enchantments) {
|
||||
return []item.Stack{item.NewStack(it, 1)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
breakBlockNoDrops(b, pos, tx)
|
||||
if breakable, ok := b.(Breakable); ok {
|
||||
for _, drop := range breakable.BreakInfo().Drops(item.ToolNone{}, nil) {
|
||||
dropItem(tx, drop, pos.Vec3Centre())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func breakBlockNoDrops(b world.Block, pos cube.Pos, tx *world.Tx) {
|
||||
// Clear the block first so neighbour-sensitive break handlers observe the post-break world state.
|
||||
tx.SetBlock(pos, nil, nil)
|
||||
if breakable, ok := b.(Breakable); ok {
|
||||
breakHandler := breakable.BreakInfo().BreakHandler
|
||||
if breakHandler != nil {
|
||||
breakHandler(pos, tx, nil)
|
||||
}
|
||||
}
|
||||
tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b})
|
||||
}
|
||||
Reference in New Issue
Block a user