diff --git a/server/block/action.go b/server/block/action.go index 5242340e6..f4faa4931 100644 --- a/server/block/action.go +++ b/server/block/action.go @@ -26,6 +26,14 @@ type ContinueCrackAction struct { // StopCrackAction is a world.BlockAction to make the cracks forming in a block stop and disappear. type StopCrackAction struct{ action } +// DecoratedPotWobbleAction is a world.BlockAction to make a decorated pot wobble when interacted with. +type DecoratedPotWobbleAction struct { + action + DecoratedPot DecoratedPot + // Success is whether an item was successfully inserted into the decorated pot. + Success bool +} + // action implements the Action interface. Structures in this package may embed it to gets its functionality // out of the box. type action struct{} diff --git a/server/block/block.go b/server/block/block.go index 164b02eb2..8a541eed4 100644 --- a/server/block/block.go +++ b/server/block/block.go @@ -74,6 +74,12 @@ type EntityInsider interface { EntityInside(pos cube.Pos, tx *world.Tx, e world.Entity) } +// ProjectileHitter represents a block that handles being hit by a projectile. +type ProjectileHitter interface { + // ProjectileHit is called when a projectile hits the block. + ProjectileHit(pos cube.Pos, tx *world.Tx, e world.Entity, face cube.Face) +} + // Frictional represents a block that may have a custom friction value. Friction is used for entity drag when the // entity is on ground. If a block does not implement this interface, it should be assumed that its friction is 0.6. type Frictional interface { diff --git a/server/block/break_info.go b/server/block/break_info.go index 7be767c81..bfe74b641 100644 --- a/server/block/break_info.go +++ b/server/block/break_info.go @@ -248,6 +248,12 @@ func breakBlock(b world.Block, pos cube.Pos, tx *world.Tx) { } func breakBlockNoDrops(b world.Block, pos cube.Pos, tx *world.Tx) { + if breakable, ok := b.(Breakable); ok { + breakHandler := breakable.BreakInfo().BreakHandler + if breakHandler != nil { + breakHandler(pos, tx, nil) + } + } tx.SetBlock(pos, nil, nil) tx.AddParticle(pos.Vec3Centre(), particle.BlockBreak{Block: b}) } diff --git a/server/block/brewing_stand.go b/server/block/brewing_stand.go index b217f3b83..bc54092a6 100644 --- a/server/block/brewing_stand.go +++ b/server/block/brewing_stand.go @@ -114,7 +114,7 @@ func (b BrewingStand) DecodeNBT(data map[string]any) any { // BreakInfo ... func (b BrewingStand) BreakInfo() BreakInfo { - return newBreakInfo(0.5, alwaysHarvestable, pickaxeEffective, oneOf(b)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + return newBreakInfo(0.5, alwaysHarvestable, pickaxeEffective, oneOf(BrewingStand{})).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { for _, i := range b.Inventory(tx, pos).Clear() { dropItem(tx, i, pos.Vec3Centre()) } diff --git a/server/block/campfire.go b/server/block/campfire.go index 8544f7b7d..88a143925 100644 --- a/server/block/campfire.go +++ b/server/block/campfire.go @@ -51,23 +51,22 @@ func (Campfire) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool { // BreakInfo ... func (c Campfire) BreakInfo() BreakInfo { return newBreakInfo(2, alwaysHarvestable, axeEffective, func(t item.Tool, enchantments []item.Enchantment) []item.Stack { - var drops []item.Stack if hasSilkTouch(enchantments) { - drops = append(drops, item.NewStack(c, 1)) - } else { - switch c.Type { - case NormalFire(): - drops = append(drops, item.NewStack(item.Charcoal{}, 2)) - case SoulFire(): - drops = append(drops, item.NewStack(SoulSoil{}, 1)) - } + 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() { - drops = append(drops, v.Item) + dropItem(tx, v.Item, pos.Vec3Centre()) } } - return drops }) } @@ -133,6 +132,10 @@ func (c Campfire) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, return false } + if _, ok = tx.Liquid(pos); ok { + return false + } + for i, it := range c.Items { if it.Item.Empty() { c.Items[i] = CampfireItem{ @@ -198,9 +201,22 @@ func (c Campfire) Tick(_ int64, pos cube.Pos, tx *world.Tx) { // NeighbourUpdateTick ... func (c Campfire) NeighbourUpdateTick(pos, _ cube.Pos, tx *world.Tx) { - _, ok := tx.Liquid(pos) - liquid, okTwo := tx.Liquid(pos.Side(cube.FaceUp)) - if (ok || (okTwo && liquid.LiquidType() == "water")) && !c.Extinguished { + 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) } } diff --git a/server/block/decorated_pot.go b/server/block/decorated_pot.go index 88925007e..958249510 100644 --- a/server/block/decorated_pot.go +++ b/server/block/decorated_pot.go @@ -7,6 +7,8 @@ import ( "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/particle" + "github.com/df-mc/dragonfly/server/world/sound" "github.com/go-gl/mathgl/mgl64" ) @@ -31,6 +33,23 @@ type DecoratedPot struct { Decorations [4]PotDecoration } +// ProjectileHit ... +func (p DecoratedPot) ProjectileHit(pos cube.Pos, tx *world.Tx, _ world.Entity, _ cube.Face) { + for _, d := range p.Decorations { + if d == nil { + dropItem(tx, item.NewStack(item.Brick{}, 1), pos.Vec3Centre()) + continue + } + dropItem(tx, item.NewStack(d, 1), pos.Vec3Centre()) + } + breakBlockNoDrops(p, pos, tx) +} + +// Pick ... +func (p DecoratedPot) Pick() item.Stack { + return item.NewStack(DecoratedPot{Decorations: p.Decorations}, 1) +} + // ExtractItem ... func (p DecoratedPot) ExtractItem(h Hopper, pos cube.Pos, tx *world.Tx) bool { if p.Item.Empty() { @@ -61,10 +80,25 @@ func (p DecoratedPot) InsertItem(h Hopper, pos cube.Pos, tx *world.Tx) bool { return false } +// wobble ... +func (p DecoratedPot) wobble(pos cube.Pos, tx *world.Tx, success bool) { + for _, v := range tx.Viewers(pos.Vec3Centre()) { + v.ViewBlockAction(pos, DecoratedPotWobbleAction{DecoratedPot: p, Success: success}) + } + + if success { + tx.AddParticle(pos.Vec3Middle().Add(mgl64.Vec3{0, 1.2}), particle.DustPlume{}) + tx.PlaySound(pos.Vec3Centre(), sound.DecoratedPotInserted{Progress: float64(p.Item.Count()) / float64(p.Item.MaxCount())}) + } else { + tx.PlaySound(pos.Vec3Centre(), sound.DecoratedPotInsertFailed{}) + } +} + // Activate ... func (p DecoratedPot) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { held, _ := u.HeldItems() if held.Empty() || !p.Item.Comparable(held) || p.Item.Count() == p.Item.MaxCount() { + p.wobble(pos, tx, false) return false } @@ -74,27 +108,14 @@ func (p DecoratedPot) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.U p.Item = p.Item.Grow(1) } tx.SetBlock(pos, p, nil) + p.wobble(pos, tx, true) ctx.SubtractFromCount(1) return true } // BreakInfo ... func (p DecoratedPot) BreakInfo() BreakInfo { - return newBreakInfo(0, alwaysHarvestable, nothingEffective, func(tool item.Tool, enchantments []item.Enchantment) []item.Stack { - if hasSilkTouch(enchantments) { - p.Item = item.Stack{} - return []item.Stack{item.NewStack(p, 1)} - } - var drops []item.Stack - for _, d := range p.Decorations { - if d == nil { - drops = append(drops, item.NewStack(item.Brick{}, 1)) - continue - } - drops = append(drops, item.NewStack(d, 1)) - } - return drops - }).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + return newBreakInfo(0, alwaysHarvestable, nothingEffective, oneOf(DecoratedPot{Decorations: p.Decorations})).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { if !p.Item.Empty() { dropItem(tx, p.Item, pos.Vec3Centre()) } diff --git a/server/block/explosion.go b/server/block/explosion.go index de4495365..52f07c6e2 100644 --- a/server/block/explosion.go +++ b/server/block/explosion.go @@ -137,27 +137,16 @@ func (c ExplosionConfig) Explode(tx *world.Tx, explosionPos mgl64.Vec3) { if explodable, ok := bl.(Explodable); ok { explodable.Explode(explosionPos, pos, tx, c) } else if breakable, ok := bl.(Breakable); ok { + breakHandler := breakable.BreakInfo().BreakHandler + if breakHandler != nil { + breakHandler(pos, tx, nil) + } tx.SetBlock(pos, nil, nil) if c.ItemDropChance > r.Float64() { for _, drop := range breakable.BreakInfo().Drops(item.ToolNone{}, nil) { dropItem(tx, drop, pos.Vec3Centre()) } } - - if container, ok := bl.(Container); ok { - if cb, ok := bl.(Chest); ok { - if cb.Paired() { - pairPos := cb.pairPos(pos) - if _, pair, ok := cb.unpair(tx, pos); ok { - cb.paired = false - tx.SetBlock(pairPos, pair, nil) - } - } - } - for _, i := range container.Inventory(tx, pos).Clear() { - dropItem(tx, i, pos.Vec3()) - } - } } } if c.SpawnFire { diff --git a/server/block/jukebox.go b/server/block/jukebox.go index 4928d0ea3..20520bebe 100644 --- a/server/block/jukebox.go +++ b/server/block/jukebox.go @@ -55,11 +55,7 @@ func (j Jukebox) FuelInfo() item.FuelInfo { // BreakInfo ... func (j Jukebox) BreakInfo() BreakInfo { - d := []item.Stack{item.NewStack(Jukebox{}, 1)} - if !j.Item.Empty() { - d = append(d, j.Item) - } - return newBreakInfo(0.8, alwaysHarvestable, axeEffective, simpleDrops(d...)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { + return newBreakInfo(2, alwaysHarvestable, axeEffective, oneOf(Jukebox{})).withBlastResistance(30).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) { if _, hasDisc := j.Disc(); hasDisc { dropItem(tx, j.Item, pos.Vec3()) tx.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{}) diff --git a/server/block/tnt.go b/server/block/tnt.go index 5ed5c81f8..4606f4512 100644 --- a/server/block/tnt.go +++ b/server/block/tnt.go @@ -16,6 +16,13 @@ type TNT struct { solid } +// ProjectileHit ... +func (t TNT) ProjectileHit(pos cube.Pos, tx *world.Tx, e world.Entity, _ cube.Face) { + if f, ok := e.(flammableEntity); ok && f.OnFireDuration() > 0 { + t.Ignite(pos, tx, nil) + } +} + // Activate ... func (t TNT) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool { held, _ := u.HeldItems() diff --git a/server/entity/projectile.go b/server/entity/projectile.go index 4b07ecd55..c2323d3b5 100644 --- a/server/entity/projectile.go +++ b/server/entity/projectile.go @@ -172,8 +172,8 @@ func (lt *ProjectileBehaviour) Tick(e *Ent, tx *world.Tx) *Movement { } case trace.BlockResult: bpos := r.BlockPosition() - if t, ok := tx.Block(bpos).(block.TNT); ok && e.OnFireDuration() > 0 { - t.Ignite(bpos, tx, nil) + if h, ok := tx.Block(bpos).(block.ProjectileHitter); ok { + h.ProjectileHit(bpos, tx, e, r.Face()) } if lt.conf.SurviveBlockCollision { lt.hitBlockSurviving(e, r, m, tx) diff --git a/server/session/world.go b/server/session/world.go index 41ba19b3d..5183ce3dc 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -453,6 +453,11 @@ func (s *Session) ViewParticle(pos mgl64.Vec3, p world.Particle) { EventType: packet.LevelEventParticleLegacyEvent | 10, Position: vec64To32(pos), }) + case particle.DustPlume: + s.writePacket(&packet.LevelEvent{ + EventType: packet.LevelEventParticleLegacyEvent | 88, + Position: vec64To32(pos), + }) } } @@ -810,6 +815,16 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) EventType: packet.LevelEventSoundTotemUsed, Position: vec64To32(pos), }) + case sound.DecoratedPotInserted: + s.writePacket(&packet.PlaySound{ + SoundName: "block.decorated_pot.insert", + Position: vec64To32(pos), + Volume: 1, + Pitch: 0.7 + 0.5*float32(so.Progress), + }) + return + case sound.DecoratedPotInsertFailed: + pk.SoundType = packet.SoundEventDecoratedPotInsertFail } s.writePacket(pk) } @@ -1152,6 +1167,14 @@ func (s *Session) ViewBlockAction(pos cube.Pos, a world.BlockAction) { Position: vec64To32(pos.Vec3()), EventData: int32(65535 / (t.BreakTime.Seconds() * 20)), }) + case block.DecoratedPotWobbleAction: + nbt := t.DecoratedPot.EncodeNBT() + nbt["x"], nbt["y"], nbt["z"] = blockPos.X(), blockPos.Y(), blockPos.Z() + nbt["animation"] = boolByte(t.Success) + 1 + s.writePacket(&packet.BlockActorData{ + Position: blockPos, + NBTData: nbt, + }) } } diff --git a/server/world/particle/block.go b/server/world/particle/block.go index d6b02b052..43130e2b3 100644 --- a/server/world/particle/block.go +++ b/server/world/particle/block.go @@ -81,6 +81,9 @@ type LavaDrip struct{ particle } // Lava is a particle that shows up randomly above lava. type Lava struct{ particle } +// DustPlume is a particle that shows up when an item is successfully inserted into a decorated pot. +type DustPlume struct{ particle } + // particle serves as a base for all particles in this package. type particle struct{} diff --git a/server/world/sound/block.go b/server/world/sound/block.go index d9a35c73d..e87c2b7ab 100644 --- a/server/world/sound/block.go +++ b/server/world/sound/block.go @@ -200,6 +200,16 @@ type WaxRemoved struct{ sound } // CopperScraped is a sound played when a player scrapes a copper block to reduce its oxidation level. type CopperScraped struct{ sound } +// DecoratedPotInserted is a sound played when an item is successfully inserted into a decorated pot. +type DecoratedPotInserted struct { + sound + // Progress is how much of the decorated pot has been filled. + Progress float64 +} + +// DecoratedPotInsertFailed is a sound played when an item fails to be inserted into a decorated pot. +type DecoratedPotInsertFailed struct{ sound } + // sound implements the world.Sound interface. type sound struct{}