300 lines
7.2 KiB
Go
300 lines
7.2 KiB
Go
|
|
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/world"
|
||
|
|
"github.com/df-mc/dragonfly/server/world/sound"
|
||
|
|
"github.com/go-gl/mathgl/mgl64"
|
||
|
|
"math/rand/v2"
|
||
|
|
"strconv"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Campfire is a block that can be used to cook food, pacify bees, act as a spread-proof light source, smoke signal or
|
||
|
|
// damaging trap block.
|
||
|
|
type Campfire struct {
|
||
|
|
transparent
|
||
|
|
bass
|
||
|
|
sourceWaterDisplacer
|
||
|
|
|
||
|
|
// Items represents the items in the campfire that are being cooked.
|
||
|
|
Items [4]CampfireItem
|
||
|
|
// Facing represents the direction that the campfire is facing.
|
||
|
|
Facing cube.Direction
|
||
|
|
// Extinguished is true if the campfire was extinguished by a water source.
|
||
|
|
Extinguished bool
|
||
|
|
// Type represents the type of Campfire, currently there are Normal and Soul campfires.
|
||
|
|
Type FireType
|
||
|
|
}
|
||
|
|
|
||
|
|
// CampfireItem holds data about the items in the campfire.
|
||
|
|
type CampfireItem struct {
|
||
|
|
// Item is a specific item being cooked on top of the campfire.
|
||
|
|
Item item.Stack
|
||
|
|
// Time is the countdown of ticks until the food item is cooked (when 0).
|
||
|
|
Time time.Duration
|
||
|
|
}
|
||
|
|
|
||
|
|
// Model ...
|
||
|
|
func (Campfire) Model() world.BlockModel {
|
||
|
|
return model.Campfire{}
|
||
|
|
}
|
||
|
|
|
||
|
|
// SideClosed ...
|
||
|
|
func (Campfire) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// BreakInfo ...
|
||
|
|
func (c Campfire) BreakInfo() BreakInfo {
|
||
|
|
return newBreakInfo(2, alwaysHarvestable, axeEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack {
|
||
|
|
if hasSilkTouch(enchantments) {
|
||
|
|
return []item.Stack{item.NewStack(Campfire{Type: c.Type}, 1)}
|
||
|
|
}
|
||
|
|
switch c.Type {
|
||
|
|
case NormalFire():
|
||
|
|
return []item.Stack{item.NewStack(item.Charcoal{}, 2)}
|
||
|
|
case SoulFire():
|
||
|
|
return []item.Stack{item.NewStack(SoulSoil{}, 1)}
|
||
|
|
}
|
||
|
|
panic("should never happen")
|
||
|
|
}).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) {
|
||
|
|
for _, v := range c.Items {
|
||
|
|
if !v.Item.Empty() {
|
||
|
|
dropItem(tx, v.Item, pos.Vec3Centre())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// LightEmissionLevel ...
|
||
|
|
func (c Campfire) LightEmissionLevel() uint8 {
|
||
|
|
if c.Extinguished {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
return c.Type.LightLevel()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ignite ...
|
||
|
|
func (c Campfire) Ignite(pos cube.Pos, tx *world.Tx, _ world.Entity) bool {
|
||
|
|
tx.PlaySound(pos.Vec3(), sound.Ignite{})
|
||
|
|
if !c.Extinguished {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
if _, ok := tx.Liquid(pos); ok {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
c.Extinguished = false
|
||
|
|
tx.SetBlock(pos, c, nil)
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
// Splash ...
|
||
|
|
func (c Campfire) Splash(tx *world.Tx, pos cube.Pos) {
|
||
|
|
if c.Extinguished {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
c.extinguish(pos, tx)
|
||
|
|
}
|
||
|
|
|
||
|
|
// extinguish extinguishes the campfire.
|
||
|
|
func (c Campfire) extinguish(pos cube.Pos, tx *world.Tx) {
|
||
|
|
tx.PlaySound(pos.Vec3Centre(), sound.FireExtinguish{})
|
||
|
|
c.Extinguished = true
|
||
|
|
|
||
|
|
for i := range c.Items {
|
||
|
|
c.Items[i].Time = time.Second * 30
|
||
|
|
}
|
||
|
|
|
||
|
|
tx.SetBlock(pos, c, nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Activate ...
|
||
|
|
func (c Campfire) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool {
|
||
|
|
held, _ := u.HeldItems()
|
||
|
|
if held.Empty() {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, ok := held.Item().(item.Shovel); ok && !c.Extinguished {
|
||
|
|
c.extinguish(pos, tx)
|
||
|
|
ctx.DamageItem(1)
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
rawFood, ok := held.Item().(item.Smeltable)
|
||
|
|
if !ok || !rawFood.SmeltInfo().Food {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, ok = tx.Liquid(pos); ok {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
for i, it := range c.Items {
|
||
|
|
if it.Item.Empty() {
|
||
|
|
c.Items[i] = CampfireItem{
|
||
|
|
Item: held.Grow(-held.Count() + 1),
|
||
|
|
Time: time.Second * 30,
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.SubtractFromCount(1)
|
||
|
|
|
||
|
|
tx.PlaySound(pos.Vec3Centre(), sound.ItemAdd{})
|
||
|
|
tx.SetBlock(pos, c, nil)
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// UseOnBlock ...
|
||
|
|
func (c Campfire) 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
|
||
|
|
}
|
||
|
|
if _, ok := tx.Block(pos.Side(cube.FaceDown)).(Campfire); ok {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
c.Facing = user.Rotation().Direction().Opposite()
|
||
|
|
place(tx, pos, c, user, ctx)
|
||
|
|
return placed(ctx)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Tick is called to cook the items within the campfire.
|
||
|
|
func (c Campfire) Tick(_ int64, pos cube.Pos, tx *world.Tx) {
|
||
|
|
if c.Extinguished {
|
||
|
|
// Extinguished, do nothing.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if rand.Float64() <= 0.016 { // Every three or so seconds.
|
||
|
|
tx.PlaySound(pos.Vec3Centre(), sound.CampfireCrackle{})
|
||
|
|
}
|
||
|
|
|
||
|
|
updated := false
|
||
|
|
for i, it := range c.Items {
|
||
|
|
if it.Item.Empty() {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
updated = true
|
||
|
|
if it.Time > 0 {
|
||
|
|
c.Items[i].Time = it.Time - time.Millisecond*50
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
if food, ok := it.Item.Item().(item.Smeltable); ok {
|
||
|
|
dropItem(tx, food.SmeltInfo().Product, pos.Vec3Middle())
|
||
|
|
}
|
||
|
|
c.Items[i].Item = item.Stack{}
|
||
|
|
}
|
||
|
|
if updated {
|
||
|
|
tx.SetBlock(pos, c, nil)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// NeighbourUpdateTick ...
|
||
|
|
func (c Campfire) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) {
|
||
|
|
if _, ok := tx.Liquid(pos); ok {
|
||
|
|
var updated bool
|
||
|
|
for i, it := range c.Items {
|
||
|
|
if !it.Item.Empty() {
|
||
|
|
dropItem(tx, it.Item, pos.Vec3Centre())
|
||
|
|
c.Items[i].Item, updated = item.Stack{}, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !c.Extinguished {
|
||
|
|
c.extinguish(pos, tx)
|
||
|
|
} else if updated {
|
||
|
|
tx.SetBlock(pos, c, nil)
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if liquid, ok := tx.Liquid(pos.Side(cube.FaceUp)); ok && liquid.LiquidType() == "water" && !c.Extinguished {
|
||
|
|
c.extinguish(pos, tx)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// EntityInside ...
|
||
|
|
func (c Campfire) EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) {
|
||
|
|
if flammable, ok := e.(flammableEntity); ok {
|
||
|
|
if flammable.OnFireDuration() > 0 && c.Extinguished {
|
||
|
|
c.Extinguished = false
|
||
|
|
tx.PlaySound(pos.Vec3(), sound.Ignite{})
|
||
|
|
tx.SetBlock(pos, c, nil)
|
||
|
|
}
|
||
|
|
if !c.Extinguished {
|
||
|
|
if l, ok := e.(livingEntity); ok {
|
||
|
|
l.Hurt(c.Type.Damage(), FireDamageSource{})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// EncodeNBT ...
|
||
|
|
func (c Campfire) EncodeNBT() map[string]any {
|
||
|
|
m := map[string]any{"id": "Campfire"}
|
||
|
|
for i, v := range c.Items {
|
||
|
|
id := strconv.Itoa(i + 1)
|
||
|
|
if !v.Item.Empty() {
|
||
|
|
m["Item"+id] = nbtconv.WriteItem(v.Item, true)
|
||
|
|
m["ItemTime"+id] = uint8(v.Time.Milliseconds() / 50)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return m
|
||
|
|
}
|
||
|
|
|
||
|
|
// DecodeNBT ...
|
||
|
|
func (c Campfire) DecodeNBT(data map[string]any) any {
|
||
|
|
for i := 0; i < 4; i++ {
|
||
|
|
id := strconv.Itoa(i + 1)
|
||
|
|
c.Items[i] = CampfireItem{
|
||
|
|
Item: nbtconv.MapItem(data, "Item"+id),
|
||
|
|
Time: time.Duration(nbtconv.Int16(data, "ItemTime"+id)) * time.Millisecond * 50,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return c
|
||
|
|
}
|
||
|
|
|
||
|
|
// EncodeItem ...
|
||
|
|
func (c Campfire) EncodeItem() (name string, meta int16) {
|
||
|
|
switch c.Type {
|
||
|
|
case NormalFire():
|
||
|
|
return "minecraft:campfire", 0
|
||
|
|
case SoulFire():
|
||
|
|
return "minecraft:soul_campfire", 0
|
||
|
|
}
|
||
|
|
panic("invalid fire type")
|
||
|
|
}
|
||
|
|
|
||
|
|
// EncodeBlock ...
|
||
|
|
func (c Campfire) EncodeBlock() (name string, properties map[string]any) {
|
||
|
|
switch c.Type {
|
||
|
|
case NormalFire():
|
||
|
|
name = "minecraft:campfire"
|
||
|
|
case SoulFire():
|
||
|
|
name = "minecraft:soul_campfire"
|
||
|
|
}
|
||
|
|
return name, map[string]any{
|
||
|
|
"minecraft:cardinal_direction": c.Facing.String(),
|
||
|
|
"extinguished": c.Extinguished,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// allCampfires ...
|
||
|
|
func allCampfires() (campfires []world.Block) {
|
||
|
|
for _, d := range cube.Directions() {
|
||
|
|
for _, f := range FireTypes() {
|
||
|
|
campfires = append(campfires, Campfire{Facing: d, Type: f, Extinguished: true})
|
||
|
|
campfires = append(campfires, Campfire{Facing: d, Type: f})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return campfires
|
||
|
|
}
|