From 5b67fbdcc659ab6c61e7d3dbbe37df750b844e22 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 9 Dec 2024 23:44:20 -0800 Subject: [PATCH 01/32] first attempt at crossbows --- server/item/crossbow.go | 170 ++++++++++++++++++++++++ server/item/enchantment/quick_charge.go | 47 +++++++ server/item/enchantment/register.go | 2 +- server/item/register.go | 1 + server/player/player.go | 19 +++ 5 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 server/item/crossbow.go create mode 100644 server/item/enchantment/quick_charge.go diff --git a/server/item/crossbow.go b/server/item/crossbow.go new file mode 100644 index 000000000..82e34bab1 --- /dev/null +++ b/server/item/crossbow.go @@ -0,0 +1,170 @@ +package item + +import ( + "github.com/df-mc/dragonfly/server/item/potion" + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" + "time" + _ "unsafe" +) + +type Crossbow struct { + Item Stack +} + +// Charge starts the charging process and prints the intended duration. +func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) { + creative := releaser.GameMode().CreativeInventory() + held, left := releaser.HeldItems() + + chargeDuration := 1250 * time.Millisecond + for _, enchant := range held.Enchantments() { + if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { + chargeDuration = q.ChargeDuration(enchant.Level()) + } + } + + if duration >= chargeDuration { + var projectileItem Stack + if !left.Empty() { + if _, isFirework := left.Item().(Firework); isFirework { + projectileItem = left + } + } + + if projectileItem.Empty() { + var ok bool + projectileItem, ok = ctx.FirstFunc(func(stack Stack) bool { + _, isArrow := stack.Item().(Arrow) + return isArrow + }) + + if !ok && !creative { + return + } + + if projectileItem.Empty() { + projectileItem = NewStack(Arrow{}, 1) + } + } + + c.Item = projectileItem.Grow(-projectileItem.Count() + 1) + + if !creative { + ctx.Consume(c.Item) + } + + crossbow := newCrossbowWith(held, c) + releaser.SetHeldItems(crossbow, left) + } +} + +// Release handles the firing of the crossbow after charging. +func (c Crossbow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext) { + creative := releaser.GameMode().CreativeInventory() + + // Check if the projectile is a firework or an arrow. + if _, isFirework := c.Item.Item().(Firework); isFirework { + create := tx.World().EntityRegistry().Config().Firework + firework := create(world.EntitySpawnOpts{ + Position: eyePosition(releaser), + Velocity: releaser.Rotation().Vec3().Normalize().Mul(0.32), + Rotation: releaser.Rotation(), + }, c.Item.Item(), releaser, false) + tx.AddEntity(firework) + } else { + var tip potion.Potion + if !c.Item.Empty() { + // Arrow is empty if not found in the creative inventory. + tip = c.Item.Item().(Arrow).Tip + } + + create := tx.World().EntityRegistry().Config().Arrow + damage, consume := 6.0, !creative + arrow := create(world.EntitySpawnOpts{ + Position: eyePosition(releaser), + Velocity: releaser.Rotation().Vec3().Normalize().Mul(3), + Rotation: releaser.Rotation(), + }, damage, releaser, true, false, !creative && consume, 0, tip) + + tx.AddEntity(arrow) + } + + releaser.PlaySound(sound.BowShoot{}) + + c.Item = Stack{} + held, left := releaser.HeldItems() + crossbow := newCrossbowWith(held, c) + releaser.SetHeldItems(crossbow, left) +} + +// MaxCount always returns 1. +func (Crossbow) MaxCount() int { + return 1 +} + +// DurabilityInfo ... +func (Crossbow) DurabilityInfo() DurabilityInfo { + return DurabilityInfo{ + MaxDurability: 464, + BrokenItem: simpleItem(Stack{}), + } +} + +// FuelInfo ... +func (Crossbow) FuelInfo() FuelInfo { + return newFuelInfo(time.Second * 10) +} + +// EnchantmentValue ... +func (Crossbow) EnchantmentValue() int { + return 1 +} + +// EncodeItem ... +func (Crossbow) EncodeItem() (name string, meta int16) { + return "minecraft:crossbow", 0 +} + +// DecodeNBT ... +func (c Crossbow) DecodeNBT(data map[string]any) any { + c.Item = mapItem(data, "chargedItem") + return c +} + +// EncodeNBT ... +func (c Crossbow) EncodeNBT() map[string]any { + if !c.Item.Empty() { + return map[string]any{ + "chargedItem": writeItem(c.Item, true), + } + } + return nil +} + +// newCrossbowWith duplicates an item.Stack with the new item type given. +func newCrossbowWith(input Stack, item Crossbow) Stack { + if _, ok := input.Item().(Crossbow); !ok { + return Stack{} + } + outputStack := NewStack(item, input.Count()). + Damage(input.MaxDurability() - input.Durability()). + WithCustomName(input.CustomName()). + WithLore(input.Lore()...). + WithEnchantments(input.Enchantments()...). + WithAnvilCost(input.AnvilCost()) + for k, v := range input.Values() { + outputStack = outputStack.WithValue(k, v) + } + return outputStack +} + +// noinspection ALL +// +//go:linkname writeItem github.com/df-mc/dragonfly/server/internal/nbtconv.WriteItem +func writeItem(s Stack, disk bool) map[string]any + +// noinspection ALL +// +//go:linkname mapItem github.com/df-mc/dragonfly/server/internal/nbtconv.MapItem +func mapItem(x map[string]any, k string) Stack diff --git a/server/item/enchantment/quick_charge.go b/server/item/enchantment/quick_charge.go new file mode 100644 index 000000000..6fd5eaf30 --- /dev/null +++ b/server/item/enchantment/quick_charge.go @@ -0,0 +1,47 @@ +package enchantment + +import ( + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" + "time" +) + +var QuickCharge quickCharge + +type quickCharge struct{} + +// Name ... +func (quickCharge) Name() string { + return "Quick Charge" +} + +// MaxLevel ... +func (quickCharge) MaxLevel() int { + return 3 +} + +// Cost ... +func (quickCharge) Cost(level int) (int, int) { + minCost := 5 + (level-1)*10 + return minCost, minCost + 20 +} + +// Rarity ... +func (quickCharge) Rarity() item.EnchantmentRarity { + return item.EnchantmentRarityCommon +} + +func (quickCharge) ChargeDuration(level int) time.Duration { + return time.Duration(1.25-0.25*float64(level)) * time.Second +} + +// CompatibleWithEnchantment ... +func (quickCharge) CompatibleWithEnchantment(item.EnchantmentType) bool { + return true +} + +// CompatibleWithItem ... +func (quickCharge) CompatibleWithItem(i world.Item) bool { + _, ok := i.(item.Crossbow) + return ok +} diff --git a/server/item/enchantment/register.go b/server/item/enchantment/register.go index 462bf7caf..71ceda3e9 100644 --- a/server/item/enchantment/register.go +++ b/server/item/enchantment/register.go @@ -38,7 +38,7 @@ func init() { // TODO: (32) Channeling. // TODO: (33) Multishot. // TODO: (34) Piercing. - // TODO: (35) Quick Charge. + item.RegisterEnchantment(35, QuickCharge) item.RegisterEnchantment(36, SoulSpeed) item.RegisterEnchantment(37, SwiftSneak) } diff --git a/server/item/register.go b/server/item/register.go index dbdcafb9d..9bdecb100 100644 --- a/server/item/register.go +++ b/server/item/register.go @@ -40,6 +40,7 @@ func init() { world.RegisterItem(Compass{}) world.RegisterItem(Cookie{}) world.RegisterItem(CopperIngot{}) + world.RegisterItem(Crossbow{}) world.RegisterItem(Diamond{}) world.RegisterItem(DiscFragment{}) world.RegisterItem(DragonBreath{}) diff --git a/server/player/player.go b/server/player/player.go index 8267aaa12..56dcc2c46 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1367,6 +1367,25 @@ func (p *Player) UseItem() { p.updateState() } + if chargeable, ok := it.(item.Crossbow); ok { + if !p.usingItem { + if !chargeable.Item.Empty() { + // If the crossbow is charged, fire it. + chargeable.Release(p, p.tx, p.useContext()) + } else { + // Start charging the crossbow. + p.usingSince, p.usingItem = time.Now(), true + p.updateState() + } + return + } + // Stop charging and determine if the crossbow is ready. + duration := time.Since(p.usingSince) + p.usingItem = false + chargeable.Charge(p, p.tx, p.useContext(), duration) + p.updateState() + } + switch usable := it.(type) { case item.Usable: useCtx := p.useContext() From 36b8c332f4e0a2a82e57d55a45c3c21eaae56b38 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Dec 2024 20:41:33 -0800 Subject: [PATCH 02/32] move crossbows to a chargeable interface. --- server/entity/direction.go | 15 +++++++++ server/item/crossbow.go | 68 +++++++++++++++++++++----------------- server/item/item.go | 18 ++++++++++ server/player/player.go | 21 +++++++++--- server/session/world.go | 8 +++++ server/world/sound/item.go | 12 +++++++ 6 files changed, 107 insertions(+), 35 deletions(-) diff --git a/server/entity/direction.go b/server/entity/direction.go index 770cb06e0..85b7ff47c 100644 --- a/server/entity/direction.go +++ b/server/entity/direction.go @@ -20,3 +20,18 @@ func EyePosition(e world.Entity) mgl64.Vec3 { } return pos } + +// Torsoed represents an entity that has a torso. +type Torsoed interface { + // TorsoHeight ... + TorsoHeight() float64 +} + +// TorsoPosition ... +func TorsoPosition(e world.Entity) mgl64.Vec3 { + pos := e.Position() + if torsoed, ok := e.(Torsoed); ok { + pos[1] += torsoed.TorsoHeight() + } + return pos +} diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 82e34bab1..ecb0a9e1a 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -1,23 +1,29 @@ package item import ( + "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" - "github.com/df-mc/dragonfly/server/world/sound" "time" _ "unsafe" ) +// Crossbow is a ranged weapon similar to a bow that uses arrows or fireworks as ammunition. type Crossbow struct { + // Item is the item the crossbow is charged with. Item Stack } // Charge starts the charging process and prints the intended duration. func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) { + if !c.Item.Empty() { + return + } + creative := releaser.GameMode().CreativeInventory() held, left := releaser.HeldItems() - chargeDuration := 1250 * time.Millisecond + chargeDuration := time.Duration(1.25 * float64(time.Second)) for _, enchant := range held.Enchantments() { if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { chargeDuration = q.ChargeDuration(enchant.Level()) @@ -49,53 +55,55 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat } c.Item = projectileItem.Grow(-projectileItem.Count() + 1) - if !creative { ctx.Consume(c.Item) } crossbow := newCrossbowWith(held, c) releaser.SetHeldItems(crossbow, left) + return } + return } -// Release handles the firing of the crossbow after charging. -func (c Crossbow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext) { - creative := releaser.GameMode().CreativeInventory() +func (c Crossbow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext) bool { + if c.Item.Empty() { + return false + } - // Check if the projectile is a firework or an arrow. - if _, isFirework := c.Item.Item().(Firework); isFirework { - create := tx.World().EntityRegistry().Config().Firework - firework := create(world.EntitySpawnOpts{ - Position: eyePosition(releaser), - Velocity: releaser.Rotation().Vec3().Normalize().Mul(0.32), - Rotation: releaser.Rotation(), - }, c.Item.Item(), releaser, false) - tx.AddEntity(firework) - } else { - var tip potion.Potion - if !c.Item.Empty() { - // Arrow is empty if not found in the creative inventory. - tip = c.Item.Item().(Arrow).Tip - } + creative := releaser.GameMode().CreativeInventory() - create := tx.World().EntityRegistry().Config().Arrow - damage, consume := 6.0, !creative - arrow := create(world.EntitySpawnOpts{ - Position: eyePosition(releaser), - Velocity: releaser.Rotation().Vec3().Normalize().Mul(3), - Rotation: releaser.Rotation(), - }, damage, releaser, true, false, !creative && consume, 0, tip) + rot := releaser.Rotation() + rot = cube.Rotation{-rot[0], -rot[1]} + if rot[0] > 180 { + rot[0] = 360 - rot[0] + } - tx.AddEntity(arrow) + dirVec := releaser.Rotation().Vec3().Normalize() + if firework, isFirework := c.Item.Item().(Firework); isFirework { + createFirework := tx.World().EntityRegistry().Config().Firework + fireworkEntity := createFirework(world.EntitySpawnOpts{ + Position: torsoPosition(releaser), + Velocity: dirVec.Mul(1.5), + Rotation: rot, + }, firework, releaser, false) + tx.AddEntity(fireworkEntity) + return true } - releaser.PlaySound(sound.BowShoot{}) + createArrow := tx.World().EntityRegistry().Config().Arrow + arrow := createArrow(world.EntitySpawnOpts{ + Position: torsoPosition(releaser), + Velocity: dirVec.Mul(3.0), + Rotation: rot, + }, 9, releaser, true, false, !creative, 0, potion.Potion{}) + tx.AddEntity(arrow) c.Item = Stack{} held, left := releaser.HeldItems() crossbow := newCrossbowWith(held, c) releaser.SetHeldItems(crossbow, left) + return true } // MaxCount always returns 1. diff --git a/server/item/item.go b/server/item/item.go index bf3bb6248..63ecb10ec 100644 --- a/server/item/item.go +++ b/server/item/item.go @@ -155,6 +155,14 @@ type Releasable interface { Requirements() []Stack } +// Chargeable represents an item that can be charged. +type Chargeable interface { + // Charge is called when an item is being used. + Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) + // Release is called when an item is being released. + Release(releaser Releaser, tx *world.Tx, ctx *UseContext) bool +} + // User represents an entity that is able to use an item in the world, typically entities such as players, // which interact with the world using an item. type User interface { @@ -217,6 +225,16 @@ func eyePosition(e world.Entity) mgl64.Vec3 { return pos } +// torsoPosition returns the position of the torso of the entity if the entity implements entity.Torsoed, or the +// actual position if it doesn't. +func torsoPosition(e world.Entity) mgl64.Vec3 { + pos := e.Position() + if torso, ok := e.(interface{ TorsoHeight() float64 }); ok { + pos = pos.Add(mgl64.Vec3{0, torso.TorsoHeight()}) + } + return pos +} + // Int32FromRGBA converts a color.RGBA into an int32. These int32s are present in things such as signs and dyed leather armour. func int32FromRGBA(x color.RGBA) int32 { if x.R == 0 && x.G == 0 && x.B == 0 { diff --git a/server/player/player.go b/server/player/player.go index 56dcc2c46..cedc831b7 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1367,18 +1367,16 @@ func (p *Player) UseItem() { p.updateState() } - if chargeable, ok := it.(item.Crossbow); ok { + if chargeable, ok := it.(item.Chargeable); ok { if !p.usingItem { - if !chargeable.Item.Empty() { - // If the crossbow is charged, fire it. - chargeable.Release(p, p.tx, p.useContext()) - } else { + if !chargeable.Release(p, p.tx, p.useContext()) { // Start charging the crossbow. p.usingSince, p.usingItem = time.Now(), true p.updateState() } return } + // Stop charging and determine if the crossbow is ready. duration := time.Since(p.usingSince) p.usingItem = false @@ -2655,6 +2653,19 @@ func (p *Player) EyeHeight() float64 { } } +// TorsoHeight returns the torso height of the player: 1.52, 1.16 if the player is sneaking, or 0.42 if the player is +// swimming, gliding, or crawling. +func (p *Player) TorsoHeight() float64 { + switch { + case p.swimming || p.crawling || p.gliding: + return 0.42 + case p.sneaking: + return 1.16 + default: + return 1.52 + } +} + // PlaySound plays a world.Sound that only this Player can hear. Unlike World.PlaySound, it is not broadcast // to players around it. func (p *Player) PlaySound(sound world.Sound) { diff --git a/server/session/world.go b/server/session/world.go index e4628bb13..b39205ff4 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -712,6 +712,14 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventBucketEmptyLava case sound.BowShoot: pk.SoundType = packet.SoundEventBow + case sound.CrossbowShoot: + pk.SoundType = packet.SoundEventCrossbowShoot + case sound.CrossbowLoadingStart: + pk.SoundType = packet.SoundEventCrossbowLoadingStart + case sound.CrossbowLoadingMiddle: + pk.SoundType = packet.SoundEventCrossbowLoadingMiddle + case sound.CrossbowLoadingEnd: + pk.SoundType = packet.SoundEventCrossbowLoadingEnd case sound.ArrowHit: pk.SoundType = packet.SoundEventBowHit case sound.ItemThrow: diff --git a/server/world/sound/item.go b/server/world/sound/item.go index 72c6fe6a7..6fee6670c 100644 --- a/server/world/sound/item.go +++ b/server/world/sound/item.go @@ -47,6 +47,18 @@ type BucketEmpty struct { // BowShoot is a sound played when a bow is shot. type BowShoot struct{ sound } +// CrossbowShoot is a sound played when a crossbow is shot. +type CrossbowShoot struct{ sound } + +// CrossbowLoadingStart is a sound played when a crossbow is starting to load. +type CrossbowLoadingStart struct{ sound } + +// CrossbowLoadingMiddle is a sound played when a crossbow is in the middle of load. +type CrossbowLoadingMiddle struct{ sound } + +// CrossbowLoadingEnd is a sound played when a crossbow is at the end of loading. +type CrossbowLoadingEnd struct{ sound } + // ArrowHit is a sound played when an arrow hits ground. type ArrowHit struct{ sound } From 727cb71ee7dac286907a5970d26e90e10cc31c59 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Dec 2024 21:04:23 -0800 Subject: [PATCH 03/32] remove redundant return and other release change --- main.go | 8 ++++++++ server/item/crossbow.go | 20 +++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 03c5def92..63bb6c792 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,8 @@ package main import ( "fmt" "github.com/df-mc/dragonfly/server" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/item/enchantment" "github.com/df-mc/dragonfly/server/player/chat" "github.com/pelletier/go-toml" "log/slog" @@ -20,8 +22,14 @@ func main() { srv := conf.New() srv.CloseOnProgramEnd() + srv.World().SetTime(0) + srv.Listen() for p := range srv.Accept() { + p.Inventory().Clear() + p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 1))) + p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 2))) + p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 3))) _ = p } } diff --git a/server/item/crossbow.go b/server/item/crossbow.go index ecb0a9e1a..0b2393ade 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -61,9 +61,7 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat crossbow := newCrossbowWith(held, c) releaser.SetHeldItems(crossbow, left) - return } - return } func (c Crossbow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext) bool { @@ -88,17 +86,17 @@ func (c Crossbow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext) bool Rotation: rot, }, firework, releaser, false) tx.AddEntity(fireworkEntity) - return true + } else { + createArrow := tx.World().EntityRegistry().Config().Arrow + arrow := createArrow(world.EntitySpawnOpts{ + Position: torsoPosition(releaser), + Velocity: dirVec.Mul(3.0), + Rotation: rot, + }, 9, releaser, true, false, !creative, 0, potion.Potion{}) + tx.AddEntity(arrow) } - createArrow := tx.World().EntityRegistry().Config().Arrow - arrow := createArrow(world.EntitySpawnOpts{ - Position: torsoPosition(releaser), - Velocity: dirVec.Mul(3.0), - Rotation: rot, - }, 9, releaser, true, false, !creative, 0, potion.Potion{}) - tx.AddEntity(arrow) - + ctx.DamageItem(1) c.Item = Stack{} held, left := releaser.HeldItems() crossbow := newCrossbowWith(held, c) From df4c44c0e8613a7828d0eb5ff762f08cf79155b7 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 12 Dec 2024 21:04:54 -0800 Subject: [PATCH 04/32] revert main.go changes --- main.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/main.go b/main.go index 63bb6c792..4ce6d2231 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,6 @@ package main import ( "fmt" "github.com/df-mc/dragonfly/server" - "github.com/df-mc/dragonfly/server/item" - "github.com/df-mc/dragonfly/server/item/enchantment" "github.com/df-mc/dragonfly/server/player/chat" "github.com/pelletier/go-toml" "log/slog" @@ -26,10 +24,6 @@ func main() { srv.Listen() for p := range srv.Accept() { - p.Inventory().Clear() - p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 1))) - p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 2))) - p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 3))) _ = p } } From 19569d75b5f580d22d0692672a3048d9b6891a49 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 15 Dec 2024 12:50:52 -0800 Subject: [PATCH 05/32] requested changes --- main.go | 2 -- server/item/crossbow.go | 10 ++++++---- server/item/enchantment/quick_charge.go | 12 +++++++----- server/player/player.go | 7 +++---- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/main.go b/main.go index 4ce6d2231..03c5def92 100644 --- a/main.go +++ b/main.go @@ -20,8 +20,6 @@ func main() { srv := conf.New() srv.CloseOnProgramEnd() - srv.World().SetTime(0) - srv.Listen() for p := range srv.Accept() { _ = p diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 0b2393ade..b808652c7 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -14,7 +14,7 @@ type Crossbow struct { Item Stack } -// Charge starts the charging process and prints the intended duration. +// Charge starts the charging process and checks if the charge duration meets the required duration. func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) { if !c.Item.Empty() { return @@ -25,8 +25,8 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat chargeDuration := time.Duration(1.25 * float64(time.Second)) for _, enchant := range held.Enchantments() { - if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { - chargeDuration = q.ChargeDuration(enchant.Level()) + if q, ok := enchant.Type().(interface{ DurationReduction(int) time.Duration }); ok { + chargeDuration = min(chargeDuration, q.DurationReduction(enchant.Level())) } } @@ -64,6 +64,7 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat } } +// Release checks if the item is fully charged and, if so, releases it. func (c Crossbow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext) bool { if c.Item.Empty() { return false @@ -78,6 +79,7 @@ func (c Crossbow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext) bool } dirVec := releaser.Rotation().Vec3().Normalize() + if firework, isFirework := c.Item.Item().(Firework); isFirework { createFirework := tx.World().EntityRegistry().Config().Firework fireworkEntity := createFirework(world.EntitySpawnOpts{ @@ -119,7 +121,7 @@ func (Crossbow) DurabilityInfo() DurabilityInfo { // FuelInfo ... func (Crossbow) FuelInfo() FuelInfo { - return newFuelInfo(time.Second * 10) + return newFuelInfo(time.Second * 15) } // EnchantmentValue ... diff --git a/server/item/enchantment/quick_charge.go b/server/item/enchantment/quick_charge.go index 6fd5eaf30..e3972225a 100644 --- a/server/item/enchantment/quick_charge.go +++ b/server/item/enchantment/quick_charge.go @@ -6,6 +6,7 @@ import ( "time" ) +// QuickCharge is an enchantment for quickly reloading a crossbow. var QuickCharge quickCharge type quickCharge struct{} @@ -22,17 +23,18 @@ func (quickCharge) MaxLevel() int { // Cost ... func (quickCharge) Cost(level int) (int, int) { - minCost := 5 + (level-1)*10 - return minCost, minCost + 20 + minCost := 12 + (level-1)*20 + return minCost, 50 } // Rarity ... func (quickCharge) Rarity() item.EnchantmentRarity { - return item.EnchantmentRarityCommon + return item.EnchantmentRarityUncommon } -func (quickCharge) ChargeDuration(level int) time.Duration { - return time.Duration(1.25-0.25*float64(level)) * time.Second +// DurationReduction returns the charge reduction duration. +func (quickCharge) DurationReduction(level int) time.Duration { + return time.Duration((1.25 - 0.25*float64(level)) * float64(time.Second)) } // CompatibleWithEnchantment ... diff --git a/server/player/player.go b/server/player/player.go index cedc831b7..c8266520d 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1370,17 +1370,16 @@ func (p *Player) UseItem() { if chargeable, ok := it.(item.Chargeable); ok { if !p.usingItem { if !chargeable.Release(p, p.tx, p.useContext()) { - // Start charging the crossbow. + // If the item was not charged yet, start charging. p.usingSince, p.usingItem = time.Now(), true p.updateState() } return } - // Stop charging and determine if the crossbow is ready. - duration := time.Since(p.usingSince) + // Stop charging and determine if the item is ready. p.usingItem = false - chargeable.Charge(p, p.tx, p.useContext(), duration) + chargeable.Charge(p, p.tx, p.useContext(), time.Since(p.usingSince)) p.updateState() } From 91eb288e09f1b015866511a37911d2861808ebe3 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 15 Dec 2024 13:18:38 -0800 Subject: [PATCH 06/32] remove unused interface and rename Release method --- server/entity/direction.go | 15 --------------- server/item/crossbow.go | 4 ++-- server/item/item.go | 4 ++-- server/player/player.go | 2 +- 4 files changed, 5 insertions(+), 20 deletions(-) diff --git a/server/entity/direction.go b/server/entity/direction.go index 85b7ff47c..770cb06e0 100644 --- a/server/entity/direction.go +++ b/server/entity/direction.go @@ -20,18 +20,3 @@ func EyePosition(e world.Entity) mgl64.Vec3 { } return pos } - -// Torsoed represents an entity that has a torso. -type Torsoed interface { - // TorsoHeight ... - TorsoHeight() float64 -} - -// TorsoPosition ... -func TorsoPosition(e world.Entity) mgl64.Vec3 { - pos := e.Position() - if torsoed, ok := e.(Torsoed); ok { - pos[1] += torsoed.TorsoHeight() - } - return pos -} diff --git a/server/item/crossbow.go b/server/item/crossbow.go index b808652c7..677fcba73 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -64,8 +64,8 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat } } -// Release checks if the item is fully charged and, if so, releases it. -func (c Crossbow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext) bool { +// ReleaseCharge checks if the item is fully charged and, if so, releases it. +func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool { if c.Item.Empty() { return false } diff --git a/server/item/item.go b/server/item/item.go index 63ecb10ec..17202dfea 100644 --- a/server/item/item.go +++ b/server/item/item.go @@ -159,8 +159,8 @@ type Releasable interface { type Chargeable interface { // Charge is called when an item is being used. Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) - // Release is called when an item is being released. - Release(releaser Releaser, tx *world.Tx, ctx *UseContext) bool + // ReleaseCharge is called when an item is being released. + ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool } // User represents an entity that is able to use an item in the world, typically entities such as players, diff --git a/server/player/player.go b/server/player/player.go index c8266520d..9fad734df 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1369,7 +1369,7 @@ func (p *Player) UseItem() { if chargeable, ok := it.(item.Chargeable); ok { if !p.usingItem { - if !chargeable.Release(p, p.tx, p.useContext()) { + if !chargeable.ReleaseCharge(p, p.tx, p.useContext()) { // If the item was not charged yet, start charging. p.usingSince, p.usingItem = time.Now(), true p.updateState() From 8cae7d80accce598088d15d3d311bcf4eeb076e7 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 15 Dec 2024 13:53:27 -0800 Subject: [PATCH 07/32] use rotation.Neg() for arrows --- server/item/bow.go | 8 +------- server/item/crossbow.go | 11 ++--------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/server/item/bow.go b/server/item/bow.go index bc9e14ed6..608ebbbf5 100644 --- a/server/item/bow.go +++ b/server/item/bow.go @@ -1,7 +1,6 @@ package item import ( - "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" @@ -55,11 +54,6 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti return } - rot := releaser.Rotation() - rot = cube.Rotation{-rot[0], -rot[1]} - if rot[0] > 180 { - rot[0] = 360 - rot[0] - } var tip potion.Potion if !arrow.Empty() { // Arrow is empty if not found in the creative inventory. @@ -84,7 +78,7 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti } create := tx.World().EntityRegistry().Config().Arrow - opts := world.EntitySpawnOpts{Position: eyePosition(releaser), Velocity: releaser.Rotation().Vec3().Mul(force * 5), Rotation: rot} + opts := world.EntitySpawnOpts{Position: eyePosition(releaser), Velocity: releaser.Rotation().Vec3().Mul(force * 5), Rotation: releaser.Rotation().Neg()} projectile := tx.AddEntity(create(opts, damage, releaser, force >= 1, false, !creative && consume, punchLevel, tip)) if f, ok := projectile.(interface{ SetOnFire(duration time.Duration) }); ok { f.SetOnFire(burnDuration) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 677fcba73..2d2497435 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -1,7 +1,6 @@ package item import ( - "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" "time" @@ -72,12 +71,6 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext creative := releaser.GameMode().CreativeInventory() - rot := releaser.Rotation() - rot = cube.Rotation{-rot[0], -rot[1]} - if rot[0] > 180 { - rot[0] = 360 - rot[0] - } - dirVec := releaser.Rotation().Vec3().Normalize() if firework, isFirework := c.Item.Item().(Firework); isFirework { @@ -85,7 +78,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext fireworkEntity := createFirework(world.EntitySpawnOpts{ Position: torsoPosition(releaser), Velocity: dirVec.Mul(1.5), - Rotation: rot, + Rotation: releaser.Rotation().Opposite(), }, firework, releaser, false) tx.AddEntity(fireworkEntity) } else { @@ -93,7 +86,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext arrow := createArrow(world.EntitySpawnOpts{ Position: torsoPosition(releaser), Velocity: dirVec.Mul(3.0), - Rotation: rot, + Rotation: releaser.Rotation().Neg(), }, 9, releaser, true, false, !creative, 0, potion.Potion{}) tx.AddEntity(arrow) } From 0633645b41830fccfea0b93bc491cfee9a9dd3c2 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 15 Dec 2024 20:17:20 -0800 Subject: [PATCH 08/32] fix rot --- server/item/crossbow.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 2d2497435..001c56855 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -70,7 +70,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext } creative := releaser.GameMode().CreativeInventory() - + rot := releaser.Rotation().Neg() dirVec := releaser.Rotation().Vec3().Normalize() if firework, isFirework := c.Item.Item().(Firework); isFirework { @@ -78,7 +78,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext fireworkEntity := createFirework(world.EntitySpawnOpts{ Position: torsoPosition(releaser), Velocity: dirVec.Mul(1.5), - Rotation: releaser.Rotation().Opposite(), + Rotation: rot, }, firework, releaser, false) tx.AddEntity(fireworkEntity) } else { @@ -86,7 +86,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext arrow := createArrow(world.EntitySpawnOpts{ Position: torsoPosition(releaser), Velocity: dirVec.Mul(3.0), - Rotation: releaser.Rotation().Neg(), + Rotation: rot, }, 9, releaser, true, false, !creative, 0, potion.Potion{}) tx.AddEntity(arrow) } From bbd3c19f4b96ebd6f2c0b43e66fa093f1ff4bb37 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 21 Dec 2024 09:39:06 -0800 Subject: [PATCH 09/32] fix item charge and releasing issue --- server/item/crossbow.go | 8 +++++--- server/item/item.go | 2 +- server/player/player.go | 4 +++- server/session/player.go | 8 ++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 001c56855..2cc08e690 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -14,9 +14,9 @@ type Crossbow struct { } // Charge starts the charging process and checks if the charge duration meets the required duration. -func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) { +func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) bool { if !c.Item.Empty() { - return + return false } creative := releaser.GameMode().CreativeInventory() @@ -45,7 +45,7 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat }) if !ok && !creative { - return + return false } if projectileItem.Empty() { @@ -60,7 +60,9 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat crossbow := newCrossbowWith(held, c) releaser.SetHeldItems(crossbow, left) + return true } + return false } // ReleaseCharge checks if the item is fully charged and, if so, releases it. diff --git a/server/item/item.go b/server/item/item.go index 17202dfea..55c53b14d 100644 --- a/server/item/item.go +++ b/server/item/item.go @@ -158,7 +158,7 @@ type Releasable interface { // Chargeable represents an item that can be charged. type Chargeable interface { // Charge is called when an item is being used. - Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) + Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) bool // ReleaseCharge is called when an item is being released. ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool } diff --git a/server/player/player.go b/server/player/player.go index 925573ad5..76c793360 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1379,7 +1379,9 @@ func (p *Player) UseItem() { // Stop charging and determine if the item is ready. p.usingItem = false - chargeable.Charge(p, p.tx, p.useContext(), time.Since(p.usingSince)) + if chargeable.Charge(p, p.tx, p.useContext(), time.Since(p.usingSince)) { + p.session().SendCrossbowChargeComplete() + } p.updateState() } diff --git a/server/session/player.go b/server/session/player.go index 60f8fcaf3..91b3406d2 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -725,6 +725,14 @@ func (s *Session) SendExperience(level int, progress float64) { }) } +// SendCrossbowChargeComplete sends a packet to indicate that the crossbow charging process has been completed. +func (s *Session) SendCrossbowChargeComplete() { + s.writePacket(&packet.ActorEvent{ + EntityRuntimeID: selfEntityRuntimeID, + EventType: packet.ActorEventFinishedChargingItem, + }) +} + // stackFromItem converts an item.Stack to its network ItemStack representation. func stackFromItem(it item.Stack) protocol.ItemStack { if it.Empty() { From 2a26e69f98abd72d89507a2c41d8d5d0a9dce2e6 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 21 Dec 2024 15:24:07 -0800 Subject: [PATCH 10/32] changes --- server/item/crossbow.go | 25 ++++++++++++++++++------- server/item/enchantment/infinity.go | 3 ++- server/player/player.go | 2 ++ server/session/world.go | 2 -- server/world/sound/item.go | 5 +---- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 2cc08e690..21b003dcc 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -3,6 +3,7 @@ package item import ( "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" "time" _ "unsafe" ) @@ -22,17 +23,22 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat creative := releaser.GameMode().CreativeInventory() held, left := releaser.HeldItems() - chargeDuration := time.Duration(1.25 * float64(time.Second)) + chargeDuration, consume := time.Duration(1.25*float64(time.Second)), !creative for _, enchant := range held.Enchantments() { if q, ok := enchant.Type().(interface{ DurationReduction(int) time.Duration }); ok { chargeDuration = min(chargeDuration, q.DurationReduction(enchant.Level())) } + if i, ok := enchant.Type().(interface{ ConsumesArrows() bool }); ok && !i.ConsumesArrows() { + consume = false + } } if duration >= chargeDuration { var projectileItem Stack if !left.Empty() { - if _, isFirework := left.Item().(Firework); isFirework { + _, isFirework := left.Item().(Firework) + _, isArrow := left.Item().(Arrow) + if isFirework || isArrow { projectileItem = left } } @@ -53,13 +59,14 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat } } - c.Item = projectileItem.Grow(-projectileItem.Count() + 1) - if !creative { - ctx.Consume(c.Item) + c.Item = NewStack(projectileItem.Item(), 1) + if consume { + ctx.Consume(projectileItem.Grow(-projectileItem.Count() + 1)) } crossbow := newCrossbowWith(held, c) releaser.SetHeldItems(crossbow, left) + releaser.PlaySound(sound.CrossbowLoadingMiddle{}) return true } return false @@ -79,10 +86,12 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext createFirework := tx.World().EntityRegistry().Config().Firework fireworkEntity := createFirework(world.EntitySpawnOpts{ Position: torsoPosition(releaser), - Velocity: dirVec.Mul(1.5), + Velocity: dirVec.Mul(0.5), Rotation: rot, }, firework, releaser, false) tx.AddEntity(fireworkEntity) + + ctx.DamageItem(3) } else { createArrow := tx.World().EntityRegistry().Config().Arrow arrow := createArrow(world.EntitySpawnOpts{ @@ -91,13 +100,15 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext Rotation: rot, }, 9, releaser, true, false, !creative, 0, potion.Potion{}) tx.AddEntity(arrow) + + ctx.DamageItem(1) } - ctx.DamageItem(1) c.Item = Stack{} held, left := releaser.HeldItems() crossbow := newCrossbowWith(held, c) releaser.SetHeldItems(crossbow, left) + releaser.PlaySound(sound.CrossbowShoot{}) return true } diff --git a/server/item/enchantment/infinity.go b/server/item/enchantment/infinity.go index 18c03819d..09cb15fc4 100644 --- a/server/item/enchantment/infinity.go +++ b/server/item/enchantment/infinity.go @@ -44,5 +44,6 @@ func (infinity) CompatibleWithEnchantment(t item.EnchantmentType) bool { // CompatibleWithItem ... func (infinity) CompatibleWithItem(i world.Item) bool { _, ok := i.(item.Bow) - return ok + _, ok2 := i.(item.Crossbow) + return ok || ok2 } diff --git a/server/player/player.go b/server/player/player.go index 76c793360..e5c3e22fb 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1371,6 +1371,8 @@ func (p *Player) UseItem() { if !p.usingItem { if !chargeable.ReleaseCharge(p, p.tx, p.useContext()) { // If the item was not charged yet, start charging. + p.PlaySound(sound.CrossbowLoadingStart{}) + p.usingSince, p.usingItem = time.Now(), true p.updateState() } diff --git a/server/session/world.go b/server/session/world.go index 1cdf36c56..00a300c9b 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -718,8 +718,6 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventCrossbowLoadingStart case sound.CrossbowLoadingMiddle: pk.SoundType = packet.SoundEventCrossbowLoadingMiddle - case sound.CrossbowLoadingEnd: - pk.SoundType = packet.SoundEventCrossbowLoadingEnd case sound.ArrowHit: pk.SoundType = packet.SoundEventBowHit case sound.ItemThrow: diff --git a/server/world/sound/item.go b/server/world/sound/item.go index 6fee6670c..0f50f6ce3 100644 --- a/server/world/sound/item.go +++ b/server/world/sound/item.go @@ -53,12 +53,9 @@ type CrossbowShoot struct{ sound } // CrossbowLoadingStart is a sound played when a crossbow is starting to load. type CrossbowLoadingStart struct{ sound } -// CrossbowLoadingMiddle is a sound played when a crossbow is in the middle of load. +// CrossbowLoadingMiddle is a sound played while a crossbow is loading and when a crossbow stops loading. type CrossbowLoadingMiddle struct{ sound } -// CrossbowLoadingEnd is a sound played when a crossbow is at the end of loading. -type CrossbowLoadingEnd struct{ sound } - // ArrowHit is a sound played when an arrow hits ground. type ArrowHit struct{ sound } From 5b5180437a8917d901dd178a67d89b571bd44e60 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 21 Dec 2024 22:33:14 -0800 Subject: [PATCH 11/32] add quick charge sounds --- server/session/world.go | 6 ++++++ server/world/sound/item.go | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/server/session/world.go b/server/session/world.go index 00a300c9b..0c17ed397 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -718,6 +718,12 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventCrossbowLoadingStart case sound.CrossbowLoadingMiddle: pk.SoundType = packet.SoundEventCrossbowLoadingMiddle + case sound.CrossbowQuickChargeLoadingStart: + pk.SoundType = packet.SoundEventCrossbowQuickChargeStart + case sound.CrossbowQuickChargeLoadingMiddle: + pk.SoundType = packet.SoundEventCrossbowQuickChargeMiddle + case sound.CrossbowQuickChargeEnd: + pk.SoundType = packet.SoundEventCrossbowQuickChargeEnd case sound.ArrowHit: pk.SoundType = packet.SoundEventBowHit case sound.ItemThrow: diff --git a/server/world/sound/item.go b/server/world/sound/item.go index 0f50f6ce3..f6c1b730b 100644 --- a/server/world/sound/item.go +++ b/server/world/sound/item.go @@ -56,6 +56,15 @@ type CrossbowLoadingStart struct{ sound } // CrossbowLoadingMiddle is a sound played while a crossbow is loading and when a crossbow stops loading. type CrossbowLoadingMiddle struct{ sound } +// CrossbowQuickChargeLoadingStart is a sound played when a crossbow with Quick Charge starts to load. +type CrossbowQuickChargeLoadingStart struct{ sound } + +// CrossbowQuickChargeLoadingMiddle is a sound played while a crossbow with Quick Charge is loading. +type CrossbowQuickChargeLoadingMiddle struct{ sound } + +// CrossbowQuickChargeEnd is a sound played when a crossbow with Quick Charge stops loading. +type CrossbowQuickChargeEnd struct{ sound } + // ArrowHit is a sound played when an arrow hits ground. type ArrowHit struct{ sound } From bcd9f5ff80de87e12dff6029ea784f066d5aef79 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 22 Dec 2024 01:54:50 -0800 Subject: [PATCH 12/32] requested changes --- server/item/crossbow.go | 69 +++++++++++++---------------- server/item/enchantment/infinity.go | 3 +- server/player/player.go | 13 +++--- server/session/player.go | 4 +- 4 files changed, 42 insertions(+), 47 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 21b003dcc..eb5586310 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -1,7 +1,6 @@ package item import ( - "github.com/df-mc/dragonfly/server/item/potion" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/sound" "time" @@ -23,53 +22,50 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat creative := releaser.GameMode().CreativeInventory() held, left := releaser.HeldItems() - chargeDuration, consume := time.Duration(1.25*float64(time.Second)), !creative + chargeDuration := time.Duration(1.25 * float64(time.Second)) for _, enchant := range held.Enchantments() { if q, ok := enchant.Type().(interface{ DurationReduction(int) time.Duration }); ok { chargeDuration = min(chargeDuration, q.DurationReduction(enchant.Level())) } - if i, ok := enchant.Type().(interface{ ConsumesArrows() bool }); ok && !i.ConsumesArrows() { - consume = false - } } - if duration >= chargeDuration { - var projectileItem Stack - if !left.Empty() { - _, isFirework := left.Item().(Firework) - _, isArrow := left.Item().(Arrow) - if isFirework || isArrow { - projectileItem = left - } + if duration < chargeDuration { + return false + } + + var projectileItem Stack + if !left.Empty() { + _, isFirework := left.Item().(Firework) + _, isArrow := left.Item().(Arrow) + if isFirework || isArrow { + projectileItem = left } + } - if projectileItem.Empty() { - var ok bool - projectileItem, ok = ctx.FirstFunc(func(stack Stack) bool { - _, isArrow := stack.Item().(Arrow) - return isArrow - }) - - if !ok && !creative { - return false - } - - if projectileItem.Empty() { - projectileItem = NewStack(Arrow{}, 1) - } + if projectileItem.Empty() { + var ok bool + projectileItem, ok = ctx.FirstFunc(func(stack Stack) bool { + _, isArrow := stack.Item().(Arrow) + return isArrow + }) + + if !ok && !creative { + return false } - c.Item = NewStack(projectileItem.Item(), 1) - if consume { - ctx.Consume(projectileItem.Grow(-projectileItem.Count() + 1)) + if projectileItem.Empty() { + projectileItem = NewStack(Arrow{}, 1) } + } - crossbow := newCrossbowWith(held, c) - releaser.SetHeldItems(crossbow, left) - releaser.PlaySound(sound.CrossbowLoadingMiddle{}) - return true + c.Item = NewStack(projectileItem.Item(), 1) + if !creative { + ctx.Consume(projectileItem.Grow(-projectileItem.Count() + 1)) } - return false + + crossbow := newCrossbowWith(held, c) + releaser.SetHeldItems(crossbow, left) + return true } // ReleaseCharge checks if the item is fully charged and, if so, releases it. @@ -90,7 +86,6 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext Rotation: rot, }, firework, releaser, false) tx.AddEntity(fireworkEntity) - ctx.DamageItem(3) } else { createArrow := tx.World().EntityRegistry().Config().Arrow @@ -98,7 +93,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext Position: torsoPosition(releaser), Velocity: dirVec.Mul(3.0), Rotation: rot, - }, 9, releaser, true, false, !creative, 0, potion.Potion{}) + }, 9, releaser, false, false, !creative, 0, c.Item.Item().(Arrow).Tip) tx.AddEntity(arrow) ctx.DamageItem(1) diff --git a/server/item/enchantment/infinity.go b/server/item/enchantment/infinity.go index 09cb15fc4..18c03819d 100644 --- a/server/item/enchantment/infinity.go +++ b/server/item/enchantment/infinity.go @@ -44,6 +44,5 @@ func (infinity) CompatibleWithEnchantment(t item.EnchantmentType) bool { // CompatibleWithItem ... func (infinity) CompatibleWithItem(i world.Item) bool { _, ok := i.(item.Bow) - _, ok2 := i.(item.Crossbow) - return ok || ok2 + return ok } diff --git a/server/player/player.go b/server/player/player.go index e5c3e22fb..498119e19 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1368,22 +1368,23 @@ func (p *Player) UseItem() { } if chargeable, ok := it.(item.Chargeable); ok { + useCtx := p.useContext() if !p.usingItem { - if !chargeable.ReleaseCharge(p, p.tx, p.useContext()) { + if !chargeable.ReleaseCharge(p, p.tx, useCtx) { // If the item was not charged yet, start charging. - p.PlaySound(sound.CrossbowLoadingStart{}) - p.usingSince, p.usingItem = time.Now(), true - p.updateState() } + p.handleUseContext(useCtx) + p.updateState() return } // Stop charging and determine if the item is ready. p.usingItem = false - if chargeable.Charge(p, p.tx, p.useContext(), time.Since(p.usingSince)) { - p.session().SendCrossbowChargeComplete() + if chargeable.Charge(p, p.tx, useCtx, time.Since(p.usingSince)) { + p.session().SendChargeItemComplete() } + p.handleUseContext(useCtx) p.updateState() } diff --git a/server/session/player.go b/server/session/player.go index 91b3406d2..14ada1e19 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -725,8 +725,8 @@ func (s *Session) SendExperience(level int, progress float64) { }) } -// SendCrossbowChargeComplete sends a packet to indicate that the crossbow charging process has been completed. -func (s *Session) SendCrossbowChargeComplete() { +// SendChargeItemComplete sends a packet to indicate that the item charging process has been completed. +func (s *Session) SendChargeItemComplete() { s.writePacket(&packet.ActorEvent{ EntityRuntimeID: selfEntityRuntimeID, EventType: packet.ActorEventFinishedChargingItem, From 135f6a71e1274db97c0b51c52fe27ccd6f2fe31f Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 22 Dec 2024 01:59:03 -0800 Subject: [PATCH 13/32] changes --- server/item/crossbow.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index eb5586310..c510bc317 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -58,9 +58,9 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat } } - c.Item = NewStack(projectileItem.Item(), 1) + c.Item = projectileItem.Grow(-projectileItem.Count() + 1) if !creative { - ctx.Consume(projectileItem.Grow(-projectileItem.Count() + 1)) + ctx.Consume(c.Item) } crossbow := newCrossbowWith(held, c) From c89071ca7338ac9c3bbe04954712185cf1702f94 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 22 Dec 2024 02:04:09 -0800 Subject: [PATCH 14/32] change name --- server/item/crossbow.go | 4 ++-- server/item/enchantment/quick_charge.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index c510bc317..071ee6b1c 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -24,8 +24,8 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat chargeDuration := time.Duration(1.25 * float64(time.Second)) for _, enchant := range held.Enchantments() { - if q, ok := enchant.Type().(interface{ DurationReduction(int) time.Duration }); ok { - chargeDuration = min(chargeDuration, q.DurationReduction(enchant.Level())) + if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { + chargeDuration = min(chargeDuration, q.ChargeDuration(enchant.Level())) } } diff --git a/server/item/enchantment/quick_charge.go b/server/item/enchantment/quick_charge.go index e3972225a..e9f305b63 100644 --- a/server/item/enchantment/quick_charge.go +++ b/server/item/enchantment/quick_charge.go @@ -32,8 +32,8 @@ func (quickCharge) Rarity() item.EnchantmentRarity { return item.EnchantmentRarityUncommon } -// DurationReduction returns the charge reduction duration. -func (quickCharge) DurationReduction(level int) time.Duration { +// ChargeDuration returns the charge duration. +func (quickCharge) ChargeDuration(level int) time.Duration { return time.Duration((1.25 - 0.25*float64(level)) * float64(time.Second)) } From 6a2a1d319f5f86192ce3780fe907789b294e83a7 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 22 Dec 2024 09:14:20 -0800 Subject: [PATCH 15/32] move to internal package --- .../internal/iteminternal/stack_transfer.go | 20 ++++++++++++++++ server/item/crossbow.go | 24 +++++-------------- 2 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 server/internal/iteminternal/stack_transfer.go diff --git a/server/internal/iteminternal/stack_transfer.go b/server/internal/iteminternal/stack_transfer.go new file mode 100644 index 000000000..c4a6f68b6 --- /dev/null +++ b/server/internal/iteminternal/stack_transfer.go @@ -0,0 +1,20 @@ +package iteminternal + +import ( + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/world" +) + +// NewItem duplicates an item.Stack with the new item type given. +func NewItem(input item.Stack, i world.Item) item.Stack { + outputStack := item.NewStack(i, input.Count()). + Damage(input.MaxDurability() - input.Durability()). + WithCustomName(input.CustomName()). + WithLore(input.Lore()...). + WithEnchantments(input.Enchantments()...). + WithAnvilCost(input.AnvilCost()) + for k, v := range input.Values() { + outputStack = outputStack.WithValue(k, v) + } + return outputStack +} diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 071ee6b1c..c56c6e5ae 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -63,7 +63,7 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat ctx.Consume(c.Item) } - crossbow := newCrossbowWith(held, c) + crossbow := newItem(held, c) releaser.SetHeldItems(crossbow, left) return true } @@ -101,7 +101,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext c.Item = Stack{} held, left := releaser.HeldItems() - crossbow := newCrossbowWith(held, c) + crossbow := newItem(held, c) releaser.SetHeldItems(crossbow, left) releaser.PlaySound(sound.CrossbowShoot{}) return true @@ -151,22 +151,10 @@ func (c Crossbow) EncodeNBT() map[string]any { return nil } -// newCrossbowWith duplicates an item.Stack with the new item type given. -func newCrossbowWith(input Stack, item Crossbow) Stack { - if _, ok := input.Item().(Crossbow); !ok { - return Stack{} - } - outputStack := NewStack(item, input.Count()). - Damage(input.MaxDurability() - input.Durability()). - WithCustomName(input.CustomName()). - WithLore(input.Lore()...). - WithEnchantments(input.Enchantments()...). - WithAnvilCost(input.AnvilCost()) - for k, v := range input.Values() { - outputStack = outputStack.WithValue(k, v) - } - return outputStack -} +// noinspection ALL +// +//go:linkname newItem github.com/df-mc/dragonfly/server/internal/iteminternal.NewItem +func newItem(stack Stack, item world.Item) Stack // noinspection ALL // From 0abdcceb95f5e06308d666038133a5478dbe2ef9 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 22 Dec 2024 09:44:50 -0800 Subject: [PATCH 16/32] account for offHand --- server/player/player.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/player/player.go b/server/player/player.go index 498119e19..8f51b2bfb 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1494,6 +1494,10 @@ func (p *Player) handleUseContext(ctx *item.UseContext) { p.SetHeldItems(p.subtractItem(p.damageItem(i, ctx.Damage), ctx.CountSub), left) p.addNewItem(ctx) for _, it := range ctx.ConsumedItems { + err := p.offHand.RemoveItem(it) + if err == nil { + continue + } _ = p.Inventory().RemoveItem(it) } } From 9e35521b8275df760c54cb7c4ebf8b7d9e0c7d75 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 22 Dec 2024 10:25:02 -0800 Subject: [PATCH 17/32] better match vanilla velocity --- server/item/crossbow.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index c56c6e5ae..6777c50de 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -91,11 +91,10 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext createArrow := tx.World().EntityRegistry().Config().Arrow arrow := createArrow(world.EntitySpawnOpts{ Position: torsoPosition(releaser), - Velocity: dirVec.Mul(3.0), + Velocity: dirVec.Mul(5.15), Rotation: rot, }, 9, releaser, false, false, !creative, 0, c.Item.Item().(Arrow).Tip) tx.AddEntity(arrow) - ctx.DamageItem(1) } From daf4a392458334a6e40a63c02391a80c4f53c3ed Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 23 Dec 2024 18:25:52 -0800 Subject: [PATCH 18/32] allow bow to use offhand arrows --- main.go | 16 ++++++++++++++++ server/item/bow.go | 15 ++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 03c5def92..0b3f34e28 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,10 @@ package main import ( "fmt" "github.com/df-mc/dragonfly/server" + "github.com/df-mc/dragonfly/server/item" + "github.com/df-mc/dragonfly/server/item/enchantment" "github.com/df-mc/dragonfly/server/player/chat" + "github.com/df-mc/dragonfly/server/world" "github.com/pelletier/go-toml" "log/slog" "os" @@ -20,8 +23,21 @@ func main() { srv := conf.New() srv.CloseOnProgramEnd() + srv.World().StopThundering() + srv.World().StopThundering() + srv.World().StopWeatherCycle() + srv.World().SetTime(0) + srv.Listen() for p := range srv.Accept() { + p.Inventory().Clear() + p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 3))) + p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 2))) + p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 1))) + p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1)) + p.Inventory().AddItem(item.NewStack(item.Bow{}, 1)) + p.Inventory().AddItem(item.NewStack(item.Arrow{}, 64)) + p.SetGameMode(world.GameModeSurvival) _ = p } } diff --git a/server/item/bow.go b/server/item/bow.go index 608ebbbf5..d44927008 100644 --- a/server/item/bow.go +++ b/server/item/bow.go @@ -32,6 +32,7 @@ func (Bow) FuelInfo() FuelInfo { // Release ... func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) { creative := releaser.GameMode().CreativeInventory() + held, left := releaser.HeldItems() ticks := duration.Milliseconds() / 50 if ticks < 3 { // The player must hold the bow for at least three ticks. @@ -45,8 +46,16 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti return } - arrow, ok := ctx.FirstFunc(func(stack Stack) bool { - _, ok := stack.Item().(Arrow) + var arrow Stack + if !left.Empty() { + if _, ok := left.Item().(Arrow); ok { + arrow = left + } + } + + var ok bool + arrow, ok = ctx.FirstFunc(func(stack Stack) bool { + _, ok = stack.Item().(Arrow) return ok }) if !ok && !creative { @@ -60,7 +69,6 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti tip = arrow.Item().(Arrow).Tip } - held, _ := releaser.HeldItems() damage, punchLevel, burnDuration, consume := 2.0, 0, time.Duration(0), !creative for _, enchant := range held.Enchantments() { if f, ok := enchant.Type().(interface{ BurnDuration() time.Duration }); ok { @@ -90,6 +98,7 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti } releaser.PlaySound(sound.BowShoot{}) + releaser.SetHeldItems(held, left) } // EnchantmentValue ... From 9bc920484df34235595ef754cd4977444385b95f Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 23 Dec 2024 18:26:54 -0800 Subject: [PATCH 19/32] rename NewItem func to DuplicateStack and use in handleCraftHandler --- .../internal/iteminternal/stack_transfer.go | 4 ++-- server/item/crossbow.go | 8 ++++---- server/session/handler_crafting.go | 20 ++++++------------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/server/internal/iteminternal/stack_transfer.go b/server/internal/iteminternal/stack_transfer.go index c4a6f68b6..17d509400 100644 --- a/server/internal/iteminternal/stack_transfer.go +++ b/server/internal/iteminternal/stack_transfer.go @@ -5,8 +5,8 @@ import ( "github.com/df-mc/dragonfly/server/world" ) -// NewItem duplicates an item.Stack with the new item type given. -func NewItem(input item.Stack, i world.Item) item.Stack { +// DuplicateStack duplicates an item.Stack with the new item type given. +func DuplicateStack(input item.Stack, i world.Item) item.Stack { outputStack := item.NewStack(i, input.Count()). Damage(input.MaxDurability() - input.Durability()). WithCustomName(input.CustomName()). diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 6777c50de..6d86775b0 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -63,7 +63,7 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat ctx.Consume(c.Item) } - crossbow := newItem(held, c) + crossbow := duplicateStack(held, c) releaser.SetHeldItems(crossbow, left) return true } @@ -100,7 +100,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext c.Item = Stack{} held, left := releaser.HeldItems() - crossbow := newItem(held, c) + crossbow := duplicateStack(held, c) releaser.SetHeldItems(crossbow, left) releaser.PlaySound(sound.CrossbowShoot{}) return true @@ -152,8 +152,8 @@ func (c Crossbow) EncodeNBT() map[string]any { // noinspection ALL // -//go:linkname newItem github.com/df-mc/dragonfly/server/internal/iteminternal.NewItem -func newItem(stack Stack, item world.Item) Stack +//go:linkname duplicateStack github.com/df-mc/dragonfly/server/internal/iteminternal.DuplicateStack +func duplicateStack(stack Stack, item world.Item) Stack // noinspection ALL // diff --git a/server/session/handler_crafting.go b/server/session/handler_crafting.go index cfee0a0d2..218c946e6 100644 --- a/server/session/handler_crafting.go +++ b/server/session/handler_crafting.go @@ -10,6 +10,7 @@ import ( "github.com/sandertv/gophertunnel/minecraft/protocol" "math" "slices" + _ "unsafe" ) // handleCraft handles the CraftRecipe request action. @@ -184,20 +185,6 @@ func (s *Session) craftingOffset() uint32 { return craftingGridSmallOffset } -// duplicateStack duplicates an item.Stack with the new item type given. -func duplicateStack(input item.Stack, newType world.Item) item.Stack { - outputStack := item.NewStack(newType, input.Count()). - Damage(input.MaxDurability() - input.Durability()). - WithCustomName(input.CustomName()). - WithLore(input.Lore()...). - WithEnchantments(input.Enchantments()...). - WithAnvilCost(input.AnvilCost()) - for k, v := range input.Values() { - outputStack = outputStack.WithValue(k, v) - } - return outputStack -} - // matchingStacks returns true if the two stacks are the same in a crafting scenario. func matchingStacks(has, expected recipe.Item) bool { switch expected := expected.(type) { @@ -238,3 +225,8 @@ func grow(i recipe.Item, count int) recipe.Item { } panic(fmt.Errorf("unexpected recipe item %T", i)) } + +// noinspection ALL +// +//go:linkname duplicateStack github.com/df-mc/dragonfly/server/internal/iteminternal.DuplicateStack +func duplicateStack(stack item.Stack, item world.Item) item.Stack From 0552babf5d3a7ab6690bc8f2b5bb90e7c4b317be Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 23 Dec 2024 18:27:30 -0800 Subject: [PATCH 20/32] reverse main changes --- main.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/main.go b/main.go index 0b3f34e28..03c5def92 100644 --- a/main.go +++ b/main.go @@ -3,10 +3,7 @@ package main import ( "fmt" "github.com/df-mc/dragonfly/server" - "github.com/df-mc/dragonfly/server/item" - "github.com/df-mc/dragonfly/server/item/enchantment" "github.com/df-mc/dragonfly/server/player/chat" - "github.com/df-mc/dragonfly/server/world" "github.com/pelletier/go-toml" "log/slog" "os" @@ -23,21 +20,8 @@ func main() { srv := conf.New() srv.CloseOnProgramEnd() - srv.World().StopThundering() - srv.World().StopThundering() - srv.World().StopWeatherCycle() - srv.World().SetTime(0) - srv.Listen() for p := range srv.Accept() { - p.Inventory().Clear() - p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 3))) - p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 2))) - p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1).WithEnchantments(item.NewEnchantment(enchantment.QuickCharge, 1))) - p.Inventory().AddItem(item.NewStack(item.Crossbow{}, 1)) - p.Inventory().AddItem(item.NewStack(item.Bow{}, 1)) - p.Inventory().AddItem(item.NewStack(item.Arrow{}, 64)) - p.SetGameMode(world.GameModeSurvival) _ = p } } From 399205a30b1d310ea533ed6d64932b1ff3c31561 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 23 Dec 2024 23:59:57 -0800 Subject: [PATCH 21/32] changes --- server/item/bow.go | 22 ++++++++++++---------- server/item/crossbow.go | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/server/item/bow.go b/server/item/bow.go index d44927008..496bf2572 100644 --- a/server/item/bow.go +++ b/server/item/bow.go @@ -53,14 +53,17 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti } } - var ok bool - arrow, ok = ctx.FirstFunc(func(stack Stack) bool { - _, ok = stack.Item().(Arrow) - return ok - }) - if !ok && !creative { - // No arrows in inventory and not in creative mode. - return + if arrow.Empty() { + var ok bool + arrow, ok = ctx.FirstFunc(func(stack Stack) bool { + _, ok = stack.Item().(Arrow) + return ok + }) + + if !ok && !creative { + // No arrows in inventory and not in creative mode. + return + } } var tip potion.Potion @@ -97,8 +100,7 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti ctx.Consume(arrow.Grow(-arrow.Count() + 1)) } - releaser.PlaySound(sound.BowShoot{}) - releaser.SetHeldItems(held, left) + tx.PlaySound(releaser.Position(), sound.BowShoot{}) } // EnchantmentValue ... diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 6d86775b0..eec006c02 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -102,7 +102,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext held, left := releaser.HeldItems() crossbow := duplicateStack(held, c) releaser.SetHeldItems(crossbow, left) - releaser.PlaySound(sound.CrossbowShoot{}) + tx.PlaySound(releaser.Position(), sound.CrossbowShoot{}) return true } From a701deba5dce790e8dc7e95529c9b66d0b21d6c5 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Dec 2024 00:27:31 -0800 Subject: [PATCH 22/32] fix --- server/player/player.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/player/player.go b/server/player/player.go index 8543c63fc..bdc81f1b7 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1502,7 +1502,7 @@ func (p *Player) handleUseContext(ctx *item.UseContext) { p.addNewItem(ctx) for _, it := range ctx.ConsumedItems { err := p.offHand.RemoveItem(it) - if err == nil { + if err == nil && it.Count() <= left.Count() { continue } _ = p.Inventory().RemoveItem(it) From 1bde76d44223c8f0fb12a1f75c28fb4e57a866d9 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Dec 2024 08:28:46 -0800 Subject: [PATCH 23/32] use WithItem --- .../internal/iteminternal/stack_transfer.go | 20 ------------------- server/item/crossbow.go | 9 ++------- server/session/handler_crafting.go | 6 ------ 3 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 server/internal/iteminternal/stack_transfer.go diff --git a/server/internal/iteminternal/stack_transfer.go b/server/internal/iteminternal/stack_transfer.go deleted file mode 100644 index 17d509400..000000000 --- a/server/internal/iteminternal/stack_transfer.go +++ /dev/null @@ -1,20 +0,0 @@ -package iteminternal - -import ( - "github.com/df-mc/dragonfly/server/item" - "github.com/df-mc/dragonfly/server/world" -) - -// DuplicateStack duplicates an item.Stack with the new item type given. -func DuplicateStack(input item.Stack, i world.Item) item.Stack { - outputStack := item.NewStack(i, input.Count()). - Damage(input.MaxDurability() - input.Durability()). - WithCustomName(input.CustomName()). - WithLore(input.Lore()...). - WithEnchantments(input.Enchantments()...). - WithAnvilCost(input.AnvilCost()) - for k, v := range input.Values() { - outputStack = outputStack.WithValue(k, v) - } - return outputStack -} diff --git a/server/item/crossbow.go b/server/item/crossbow.go index eec006c02..8ceb8aaf2 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -63,7 +63,7 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat ctx.Consume(c.Item) } - crossbow := duplicateStack(held, c) + crossbow := held.WithItem(c) releaser.SetHeldItems(crossbow, left) return true } @@ -100,7 +100,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext c.Item = Stack{} held, left := releaser.HeldItems() - crossbow := duplicateStack(held, c) + crossbow := held.WithItem(c) releaser.SetHeldItems(crossbow, left) tx.PlaySound(releaser.Position(), sound.CrossbowShoot{}) return true @@ -150,11 +150,6 @@ func (c Crossbow) EncodeNBT() map[string]any { return nil } -// noinspection ALL -// -//go:linkname duplicateStack github.com/df-mc/dragonfly/server/internal/iteminternal.DuplicateStack -func duplicateStack(stack Stack, item world.Item) Stack - // noinspection ALL // //go:linkname writeItem github.com/df-mc/dragonfly/server/internal/nbtconv.WriteItem diff --git a/server/session/handler_crafting.go b/server/session/handler_crafting.go index 218c946e6..79c4f43c5 100644 --- a/server/session/handler_crafting.go +++ b/server/session/handler_crafting.go @@ -10,7 +10,6 @@ import ( "github.com/sandertv/gophertunnel/minecraft/protocol" "math" "slices" - _ "unsafe" ) // handleCraft handles the CraftRecipe request action. @@ -225,8 +224,3 @@ func grow(i recipe.Item, count int) recipe.Item { } panic(fmt.Errorf("unexpected recipe item %T", i)) } - -// noinspection ALL -// -//go:linkname duplicateStack github.com/df-mc/dragonfly/server/internal/iteminternal.DuplicateStack -func duplicateStack(stack item.Stack, item world.Item) item.Stack From e70b3af63b302e2a0c42af5cb413efa09b3ab3d6 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Dec 2024 18:48:53 -0800 Subject: [PATCH 24/32] changes --- server/entity/firework.go | 11 +++++------ server/entity/register.go | 4 ++-- server/item/crossbow.go | 15 ++------------- server/item/enchantment/quick_charge.go | 6 ------ server/item/firework.go | 4 ++-- server/world/entity.go | 2 +- 6 files changed, 12 insertions(+), 30 deletions(-) diff --git a/server/entity/firework.go b/server/entity/firework.go index cd7901e07..622c0139b 100644 --- a/server/entity/firework.go +++ b/server/entity/firework.go @@ -11,13 +11,15 @@ import ( // for creating decorative explosions, boosting when flying with elytra, and // loading into a crossbow as ammunition. func NewFirework(opts world.EntitySpawnOpts, firework item.Firework) *world.EntityHandle { - return NewFireworkAttached(opts, firework, nil, false) + return NewFireworkAttached(opts, firework, nil, 1.15, 0.04, false) } // NewFireworkAttached creates a firework entity with an owner that the firework // may be attached to. -func NewFireworkAttached(opts world.EntitySpawnOpts, firework item.Firework, owner world.Entity, attached bool) *world.EntityHandle { +func NewFireworkAttached(opts world.EntitySpawnOpts, firework item.Firework, owner world.Entity, sidewaysVelocityMultiplier, upwardsAcceleration float64, attached bool) *world.EntityHandle { conf := fireworkConf + conf.SidewaysVelocityMultiplier = sidewaysVelocityMultiplier + conf.UpwardsAcceleration = upwardsAcceleration conf.Firework = firework conf.ExistenceDuration = firework.RandomisedDuration() conf.Attached = attached @@ -25,10 +27,7 @@ func NewFireworkAttached(opts world.EntitySpawnOpts, firework item.Firework, own return opts.New(FireworkType, conf) } -var fireworkConf = FireworkBehaviourConfig{ - SidewaysVelocityMultiplier: 1.15, - UpwardsAcceleration: 0.04, -} +var fireworkConf = FireworkBehaviourConfig{} // FireworkType is a world.EntityType implementation for Firework. var FireworkType fireworkType diff --git a/server/entity/register.go b/server/entity/register.go index 0e7b92653..5085c415b 100644 --- a/server/entity/register.go +++ b/server/entity/register.go @@ -35,8 +35,8 @@ var conf = world.EntityRegistryConfig{ EnderPearl: NewEnderPearl, FallingBlock: NewFallingBlock, Lightning: NewLightning, - Firework: func(opts world.EntitySpawnOpts, firework world.Item, owner world.Entity, attached bool) *world.EntityHandle { - return NewFireworkAttached(opts, firework.(item.Firework), owner, attached) + Firework: func(opts world.EntitySpawnOpts, firework world.Item, owner world.Entity, sidewaysVelocityMultiplier, upwardsAcceleration float64, attached bool) *world.EntityHandle { + return NewFireworkAttached(opts, firework.(item.Firework), owner, sidewaysVelocityMultiplier, upwardsAcceleration, attached) }, Item: func(opts world.EntitySpawnOpts, it any) *world.EntityHandle { return NewItem(opts, it.(item.Stack)) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 8ceb8aaf2..67910d10d 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -22,17 +22,6 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat creative := releaser.GameMode().CreativeInventory() held, left := releaser.HeldItems() - chargeDuration := time.Duration(1.25 * float64(time.Second)) - for _, enchant := range held.Enchantments() { - if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { - chargeDuration = min(chargeDuration, q.ChargeDuration(enchant.Level())) - } - } - - if duration < chargeDuration { - return false - } - var projectileItem Stack if !left.Empty() { _, isFirework := left.Item().(Firework) @@ -82,9 +71,9 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext createFirework := tx.World().EntityRegistry().Config().Firework fireworkEntity := createFirework(world.EntitySpawnOpts{ Position: torsoPosition(releaser), - Velocity: dirVec.Mul(0.5), + Velocity: dirVec.Mul(0.1), Rotation: rot, - }, firework, releaser, false) + }, firework, releaser, 1.15, 0, false) tx.AddEntity(fireworkEntity) ctx.DamageItem(3) } else { diff --git a/server/item/enchantment/quick_charge.go b/server/item/enchantment/quick_charge.go index e9f305b63..465be3553 100644 --- a/server/item/enchantment/quick_charge.go +++ b/server/item/enchantment/quick_charge.go @@ -3,7 +3,6 @@ package enchantment import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" - "time" ) // QuickCharge is an enchantment for quickly reloading a crossbow. @@ -32,11 +31,6 @@ func (quickCharge) Rarity() item.EnchantmentRarity { return item.EnchantmentRarityUncommon } -// ChargeDuration returns the charge duration. -func (quickCharge) ChargeDuration(level int) time.Duration { - return time.Duration((1.25 - 0.25*float64(level)) * float64(time.Second)) -} - // CompatibleWithEnchantment ... func (quickCharge) CompatibleWithEnchantment(item.EnchantmentType) bool { return true diff --git a/server/item/firework.go b/server/item/firework.go index c6e42c4aa..3f6af3185 100644 --- a/server/item/firework.go +++ b/server/item/firework.go @@ -31,7 +31,7 @@ func (f Firework) Use(tx *world.Tx, user User, ctx *UseContext) bool { tx.PlaySound(pos, sound.FireworkLaunch{}) create := tx.World().EntityRegistry().Config().Firework opts := world.EntitySpawnOpts{Position: pos, Rotation: user.Rotation()} - tx.AddEntity(create(opts, f, user, true)) + tx.AddEntity(create(opts, f, user, 1.15, 0.04, true)) ctx.SubtractFromCount(1) return true @@ -42,7 +42,7 @@ func (f Firework) UseOnBlock(pos cube.Pos, _ cube.Face, clickPos mgl64.Vec3, tx fpos := pos.Vec3().Add(clickPos) create := tx.World().EntityRegistry().Config().Firework opts := world.EntitySpawnOpts{Position: fpos, Rotation: cube.Rotation{rand.Float64() * 360, 90}} - tx.AddEntity(create(opts, f, user, false)) + tx.AddEntity(create(opts, f, user, 1.15, 0.04, false)) tx.PlaySound(fpos, sound.FireworkLaunch{}) ctx.SubtractFromCount(1) diff --git a/server/world/entity.go b/server/world/entity.go index aa15e4d10..4b5f61bfd 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -365,7 +365,7 @@ type EntityRegistryConfig struct { Arrow func(opts EntitySpawnOpts, damage float64, owner Entity, critical, disallowPickup, obtainArrowOnPickup bool, punchLevel int, tip any) *EntityHandle Egg func(opts EntitySpawnOpts, owner Entity) *EntityHandle EnderPearl func(opts EntitySpawnOpts, owner Entity) *EntityHandle - Firework func(opts EntitySpawnOpts, firework Item, owner Entity, attached bool) *EntityHandle + Firework func(opts EntitySpawnOpts, firework Item, owner Entity, sidewaysVelocityMultiplier, upwardsAcceleration float64, attached bool) *EntityHandle LingeringPotion func(opts EntitySpawnOpts, t any, owner Entity) *EntityHandle Snowball func(opts EntitySpawnOpts, owner Entity) *EntityHandle SplashPotion func(opts EntitySpawnOpts, t any, owner Entity) *EntityHandle From 9693ec37623f4be946f09d6959c51c2c35f66f37 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Dec 2024 23:42:53 -0800 Subject: [PATCH 25/32] changes --- server/player/player.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/player/player.go b/server/player/player.go index d237b34c6..e166b571e 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1503,11 +1503,12 @@ func (p *Player) handleUseContext(ctx *item.UseContext) { p.SetHeldItems(p.subtractItem(p.damageItem(i, ctx.Damage), ctx.CountSub), left) p.addNewItem(ctx) for _, it := range ctx.ConsumedItems { - err := p.offHand.RemoveItem(it) - if err == nil && it.Count() <= left.Count() { - continue + _ = p.offHand.RemoveItem(it) + it = it.Grow(-left.Count()) + + if it.Count() > 0 { + _ = p.Inventory().RemoveItem(it) } - _ = p.Inventory().RemoveItem(it) } } From d1fc7a71b332f5d8b71a7922a1faa3b207df57e1 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 25 Dec 2024 00:04:10 -0800 Subject: [PATCH 26/32] fix --- server/player/player.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/player/player.go b/server/player/player.go index e166b571e..c6a4c5d8d 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1503,8 +1503,10 @@ func (p *Player) handleUseContext(ctx *item.UseContext) { p.SetHeldItems(p.subtractItem(p.damageItem(i, ctx.Damage), ctx.CountSub), left) p.addNewItem(ctx) for _, it := range ctx.ConsumedItems { - _ = p.offHand.RemoveItem(it) - it = it.Grow(-left.Count()) + if it.Comparable(left) { + _ = p.offHand.RemoveItem(it) + it = it.Grow(-left.Count()) + } if it.Count() > 0 { _ = p.Inventory().RemoveItem(it) From 836f5ecdc9d0fb4f601f1894c1ee1db8134d78b1 Mon Sep 17 00:00:00 2001 From: DaPigGuy Date: Sat, 28 Dec 2024 20:42:04 -0800 Subject: [PATCH 27/32] fixup! --- server/item/bow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/item/bow.go b/server/item/bow.go index 335cc6a45..1a28863da 100644 --- a/server/item/bow.go +++ b/server/item/bow.go @@ -32,7 +32,6 @@ func (Bow) FuelInfo() FuelInfo { // Release ... func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) { creative := releaser.GameMode().CreativeInventory() - held, left := releaser.HeldItems() ticks := duration.Milliseconds() / 50 if ticks < 3 { // The player must hold the bow for at least three ticks. @@ -61,6 +60,7 @@ func (Bow) Release(releaser Releaser, tx *world.Tx, ctx *UseContext, duration ti tip = arrow.Item().(Arrow).Tip } + held, _ := releaser.HeldItems() damage, punchLevel, burnDuration, consume := 2.0, 0, time.Duration(0), !creative for _, enchant := range held.Enchantments() { if f, ok := enchant.Type().(interface{ BurnDuration() time.Duration }); ok { From 7516809e3b2dfa07d4bce27bb1ac5a622cfaf8b4 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 2 Jan 2025 16:31:40 -0800 Subject: [PATCH 28/32] fix --- main.go | 2 ++ server/item/crossbow.go | 44 +++++++++++++++++++++++++ server/item/enchantment/quick_charge.go | 6 ++++ server/item/item.go | 2 ++ server/player/player.go | 9 ++++- 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 03c5def92..1a003bb85 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "github.com/df-mc/dragonfly/server" + "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/player/chat" "github.com/pelletier/go-toml" "log/slog" @@ -22,6 +23,7 @@ func main() { srv.Listen() for p := range srv.Accept() { + p.Inventory().AddItem(item.NewStack(item.Arrow{}, 300)) _ = p } } diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 67910d10d..e031d3914 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -22,6 +22,18 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat creative := releaser.GameMode().CreativeInventory() held, left := releaser.HeldItems() + //TODO: modify chargeDuration to prevent funky loading + chargeDuration := time.Duration(1.25 * float64(time.Second)) + for _, enchant := range held.Enchantments() { + if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { + chargeDuration = min(chargeDuration, q.ChargeDuration(enchant.Level())) + } + } + + if duration < chargeDuration { + return false + } + var projectileItem Stack if !left.Empty() { _, isFirework := left.Item().(Firework) @@ -57,6 +69,38 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat return true } +// ContinueCharge ... +func (c Crossbow) ContinueCharge(releaser Releaser, tx *world.Tx, duration time.Duration) { + if !c.Item.Empty() { + return + } + + //TODO: check if the releaser has required items in inventory. + // currently it will send the sound regardless if charging or not. + + held, _ := releaser.HeldItems() + chargeDuration, quickChargeLevel := time.Duration(1.25*float64(time.Second)), 0 + for _, enchant := range held.Enchantments() { + if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { + chargeDuration = min(chargeDuration, q.ChargeDuration(enchant.Level())) + quickChargeLevel = enchant.Level() + } + } + + progress := float64(duration) / float64(chargeDuration) + if duration.Seconds() <= 0.1 { + if quickChargeLevel > 0 { + tx.PlaySound(releaser.Position(), sound.CrossbowQuickChargeLoadingStart{}) + } else { + tx.PlaySound(releaser.Position(), sound.CrossbowLoadingStart{}) + } + } + + if progress >= 1 && quickChargeLevel > 0 { + tx.PlaySound(releaser.Position(), sound.CrossbowQuickChargeEnd{}) + } +} + // ReleaseCharge checks if the item is fully charged and, if so, releases it. func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool { if c.Item.Empty() { diff --git a/server/item/enchantment/quick_charge.go b/server/item/enchantment/quick_charge.go index 465be3553..e9f305b63 100644 --- a/server/item/enchantment/quick_charge.go +++ b/server/item/enchantment/quick_charge.go @@ -3,6 +3,7 @@ package enchantment import ( "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" + "time" ) // QuickCharge is an enchantment for quickly reloading a crossbow. @@ -31,6 +32,11 @@ func (quickCharge) Rarity() item.EnchantmentRarity { return item.EnchantmentRarityUncommon } +// ChargeDuration returns the charge duration. +func (quickCharge) ChargeDuration(level int) time.Duration { + return time.Duration((1.25 - 0.25*float64(level)) * float64(time.Second)) +} + // CompatibleWithEnchantment ... func (quickCharge) CompatibleWithEnchantment(item.EnchantmentType) bool { return true diff --git a/server/item/item.go b/server/item/item.go index 55c53b14d..a8c981c73 100644 --- a/server/item/item.go +++ b/server/item/item.go @@ -159,6 +159,8 @@ type Releasable interface { type Chargeable interface { // Charge is called when an item is being used. Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) bool + // ContinueCharge continues the charge. + ContinueCharge(releaser Releaser, tx *world.Tx, duration time.Duration) // ReleaseCharge is called when an item is being released. ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool } diff --git a/server/player/player.go b/server/player/player.go index c6a4c5d8d..d8015edec 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2367,8 +2367,8 @@ func (p *Player) Tick(tx *world.Tx, current int64) { } } + held, _ := p.HeldItems() if current%4 == 0 && p.usingItem { - held, _ := p.HeldItems() if _, ok := held.Item().(item.Consumable); ok { // Eating particles seem to happen roughly every 4 ticks. for _, v := range p.viewers() { @@ -2376,6 +2376,13 @@ func (p *Player) Tick(tx *world.Tx, current int64) { } } } + + if p.usingItem { + if c, ok := held.Item().(item.Chargeable); ok { + c.ContinueCharge(p, tx, p.useDuration()) + } + } + for it, ti := range p.cooldowns { if time.Now().After(ti) { delete(p.cooldowns, it) From cdf6253a2973709db8942e4be31315673a0715df Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 2 Jan 2025 16:32:22 -0800 Subject: [PATCH 29/32] revert main.go --- main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.go b/main.go index 1a003bb85..03c5def92 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "github.com/df-mc/dragonfly/server" - "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/player/chat" "github.com/pelletier/go-toml" "log/slog" @@ -23,7 +22,6 @@ func main() { srv.Listen() for p := range srv.Accept() { - p.Inventory().AddItem(item.NewStack(item.Arrow{}, 300)) _ = p } } From af28e0ee9ddda04ab6918e591e92b8edd93ff935 Mon Sep 17 00:00:00 2001 From: RoyalMCPE <20691625+RoyalMCPE@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:46:22 -0700 Subject: [PATCH 30/32] Add middle loading sound and (maybe) fix funky loading --- server/item/crossbow.go | 14 ++++++++++++-- server/player/player.go | 6 +++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index e031d3914..579043576 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -1,10 +1,11 @@ package item import ( - "github.com/df-mc/dragonfly/server/world" - "github.com/df-mc/dragonfly/server/world/sound" "time" _ "unsafe" + + "github.com/df-mc/dragonfly/server/world" + "github.com/df-mc/dragonfly/server/world/sound" ) // Crossbow is a ranged weapon similar to a bow that uses arrows or fireworks as ammunition. @@ -96,6 +97,15 @@ func (c Crossbow) ContinueCharge(releaser Releaser, tx *world.Tx, duration time. } } + ticks := duration.Milliseconds() / 50 + if ticks%16 == 0 { // After 16 ticks play the middle charge sound + if quickChargeLevel > 0 { + tx.PlaySound(releaser.Position(), sound.CrossbowQuickChargeLoadingMiddle{}) + } else { + tx.PlaySound(releaser.Position(), sound.CrossbowLoadingMiddle{}) + } + } + if progress >= 1 && quickChargeLevel > 0 { tx.PlaySound(releaser.Position(), sound.CrossbowQuickChargeEnd{}) } diff --git a/server/player/player.go b/server/player/player.go index 05de7bf63..54fa74f1a 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1390,9 +1390,13 @@ func (p *Player) UseItem() { // Stop charging and determine if the item is ready. p.usingItem = false - if chargeable.Charge(p, p.tx, useCtx, time.Since(p.usingSince)) { + //dur := p.useDuration() + if chargeable.Charge(p, p.tx, useCtx, p.useDuration()) { p.session().SendChargeItemComplete() } + // if chargeable.Charge(p, p.tx, useCtx, dur) { + // p.session().SendChargeItemComplete() + // } p.handleUseContext(useCtx) p.updateState() } From 462a40d327369cb7e74cb91c20324b9eb3634ff9 Mon Sep 17 00:00:00 2001 From: RoyalMCPE <20691625+RoyalMCPE@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:47:41 -0700 Subject: [PATCH 31/32] cleanup --- server/player/player.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server/player/player.go b/server/player/player.go index 54fa74f1a..0cb8b0ee4 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -1390,13 +1390,10 @@ func (p *Player) UseItem() { // Stop charging and determine if the item is ready. p.usingItem = false - //dur := p.useDuration() - if chargeable.Charge(p, p.tx, useCtx, p.useDuration()) { + dur := p.useDuration() + if chargeable.Charge(p, p.tx, useCtx, dur) { p.session().SendChargeItemComplete() } - // if chargeable.Charge(p, p.tx, useCtx, dur) { - // p.session().SendChargeItemComplete() - // } p.handleUseContext(useCtx) p.updateState() } From 1f2b43d37611ebf4f7e14c64dba378c0b81e4cc1 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 4 Jan 2025 00:42:35 -0800 Subject: [PATCH 32/32] changes --- server/item/crossbow.go | 64 +++++++++++++++++++++++++++----------- server/item/item.go | 2 +- server/player/player.go | 2 +- server/session/world.go | 32 ++++++++++++------- server/world/sound/item.go | 33 +++++++++++--------- 5 files changed, 85 insertions(+), 48 deletions(-) diff --git a/server/item/crossbow.go b/server/item/crossbow.go index 579043576..7ceb508d0 100644 --- a/server/item/crossbow.go +++ b/server/item/crossbow.go @@ -23,7 +23,6 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat creative := releaser.GameMode().CreativeInventory() held, left := releaser.HeldItems() - //TODO: modify chargeDuration to prevent funky loading chargeDuration := time.Duration(1.25 * float64(time.Second)) for _, enchant := range held.Enchantments() { if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { @@ -71,15 +70,14 @@ func (c Crossbow) Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, durat } // ContinueCharge ... -func (c Crossbow) ContinueCharge(releaser Releaser, tx *world.Tx, duration time.Duration) { +func (c Crossbow) ContinueCharge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) { if !c.Item.Empty() { return } - //TODO: check if the releaser has required items in inventory. - // currently it will send the sound regardless if charging or not. + creative := releaser.GameMode().CreativeInventory() + held, left := releaser.HeldItems() - held, _ := releaser.HeldItems() chargeDuration, quickChargeLevel := time.Duration(1.25*float64(time.Second)), 0 for _, enchant := range held.Enchantments() { if q, ok := enchant.Type().(interface{ ChargeDuration(int) time.Duration }); ok { @@ -88,26 +86,54 @@ func (c Crossbow) ContinueCharge(releaser Releaser, tx *world.Tx, duration time. } } - progress := float64(duration) / float64(chargeDuration) - if duration.Seconds() <= 0.1 { - if quickChargeLevel > 0 { - tx.PlaySound(releaser.Position(), sound.CrossbowQuickChargeLoadingStart{}) - } else { - tx.PlaySound(releaser.Position(), sound.CrossbowLoadingStart{}) + var projectileItem Stack + if !left.Empty() { + _, isFirework := left.Item().(Firework) + _, isArrow := left.Item().(Arrow) + if isFirework || isArrow { + projectileItem = left } } - ticks := duration.Milliseconds() / 50 - if ticks%16 == 0 { // After 16 ticks play the middle charge sound - if quickChargeLevel > 0 { - tx.PlaySound(releaser.Position(), sound.CrossbowQuickChargeLoadingMiddle{}) - } else { - tx.PlaySound(releaser.Position(), sound.CrossbowLoadingMiddle{}) + if projectileItem.Empty() { + var ok bool + projectileItem, ok = ctx.FirstFunc(func(stack Stack) bool { + _, isArrow := stack.Item().(Arrow) + return isArrow + }) + + if !ok && !creative { + return + } + + if projectileItem.Empty() { + projectileItem = NewStack(Arrow{}, 1) } } + if projectileItem.Empty() { + return + } + + hasQuickCharge := quickChargeLevel > 0 + progress := float64(duration) / float64(chargeDuration) + if duration.Seconds() <= 0.1 { + tx.PlaySound(releaser.Position(), sound.Crossbow{Stage: sound.CrossbowStageLoadStart, QuickCharge: hasQuickCharge}) + } + + // Base reload time is 25 ticks; each Quick Charge level reduces by 5 ticks + multiplier := 25.0 / float64(25-(5*quickChargeLevel)) + + // Adjust ticks based on the multiplier + adjustedTicks := int(float64(duration.Milliseconds()) / (50 / multiplier)) + + // Play sound after every 16 ticks (adjusted by Quick Charge) + if adjustedTicks%16 == 0 { + tx.PlaySound(releaser.Position(), sound.Crossbow{Stage: sound.CrossbowStageMiddle, QuickCharge: hasQuickCharge}) + } + if progress >= 1 && quickChargeLevel > 0 { - tx.PlaySound(releaser.Position(), sound.CrossbowQuickChargeEnd{}) + tx.PlaySound(releaser.Position(), sound.Crossbow{Stage: sound.CrossbowStageLoadEnd, QuickCharge: hasQuickCharge}) } } @@ -145,7 +171,7 @@ func (c Crossbow) ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext held, left := releaser.HeldItems() crossbow := held.WithItem(c) releaser.SetHeldItems(crossbow, left) - tx.PlaySound(releaser.Position(), sound.CrossbowShoot{}) + tx.PlaySound(releaser.Position(), sound.Crossbow{Stage: sound.CrossbowStageShoot}) return true } diff --git a/server/item/item.go b/server/item/item.go index a8c981c73..8a2cb4587 100644 --- a/server/item/item.go +++ b/server/item/item.go @@ -160,7 +160,7 @@ type Chargeable interface { // Charge is called when an item is being used. Charge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) bool // ContinueCharge continues the charge. - ContinueCharge(releaser Releaser, tx *world.Tx, duration time.Duration) + ContinueCharge(releaser Releaser, tx *world.Tx, ctx *UseContext, duration time.Duration) // ReleaseCharge is called when an item is being released. ReleaseCharge(releaser Releaser, tx *world.Tx, ctx *UseContext) bool } diff --git a/server/player/player.go b/server/player/player.go index 0cb8b0ee4..bd013eefb 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2380,7 +2380,7 @@ func (p *Player) Tick(tx *world.Tx, current int64) { if p.usingItem { if c, ok := held.Item().(item.Chargeable); ok { - c.ContinueCharge(p, tx, p.useDuration()) + c.ContinueCharge(p, tx, p.useContext(), p.useDuration()) } } diff --git a/server/session/world.go b/server/session/world.go index 48ef23761..fecb5fa25 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -712,18 +712,26 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) pk.SoundType = packet.SoundEventBucketEmptyLava case sound.BowShoot: pk.SoundType = packet.SoundEventBow - case sound.CrossbowShoot: - pk.SoundType = packet.SoundEventCrossbowShoot - case sound.CrossbowLoadingStart: - pk.SoundType = packet.SoundEventCrossbowLoadingStart - case sound.CrossbowLoadingMiddle: - pk.SoundType = packet.SoundEventCrossbowLoadingMiddle - case sound.CrossbowQuickChargeLoadingStart: - pk.SoundType = packet.SoundEventCrossbowQuickChargeStart - case sound.CrossbowQuickChargeLoadingMiddle: - pk.SoundType = packet.SoundEventCrossbowQuickChargeMiddle - case sound.CrossbowQuickChargeEnd: - pk.SoundType = packet.SoundEventCrossbowQuickChargeEnd + case sound.Crossbow: + switch so.Stage { + case sound.CrossbowStageLoadStart: + pk.SoundType = packet.SoundEventCrossbowLoadingStart + if so.QuickCharge { + pk.SoundType = packet.SoundEventCrossbowQuickChargeStart + } + case sound.CrossbowStageMiddle: + pk.SoundType = packet.SoundEventCrossbowLoadingMiddle + if so.QuickCharge { + pk.SoundType = packet.SoundEventCrossbowQuickChargeMiddle + } + case sound.CrossbowStageLoadEnd: + pk.SoundType = packet.SoundEventCrossbowLoadingEnd + if so.QuickCharge { + pk.SoundType = packet.SoundEventCrossbowQuickChargeEnd + } + case sound.CrossbowStageShoot: + pk.SoundType = packet.SoundEventCrossbowShoot + } case sound.ArrowHit: pk.SoundType = packet.SoundEventBowHit case sound.ItemThrow: diff --git a/server/world/sound/item.go b/server/world/sound/item.go index f6c1b730b..55d190b08 100644 --- a/server/world/sound/item.go +++ b/server/world/sound/item.go @@ -47,23 +47,26 @@ type BucketEmpty struct { // BowShoot is a sound played when a bow is shot. type BowShoot struct{ sound } -// CrossbowShoot is a sound played when a crossbow is shot. -type CrossbowShoot struct{ sound } +// Crossbow is a sound when a crossbow is being used. +type Crossbow struct { + // Stage is the stage of the crossbow. + Stage int + // QuickCharge returns if the item being used has quick charge enchantment. + QuickCharge bool -// CrossbowLoadingStart is a sound played when a crossbow is starting to load. -type CrossbowLoadingStart struct{ sound } - -// CrossbowLoadingMiddle is a sound played while a crossbow is loading and when a crossbow stops loading. -type CrossbowLoadingMiddle struct{ sound } - -// CrossbowQuickChargeLoadingStart is a sound played when a crossbow with Quick Charge starts to load. -type CrossbowQuickChargeLoadingStart struct{ sound } - -// CrossbowQuickChargeLoadingMiddle is a sound played while a crossbow with Quick Charge is loading. -type CrossbowQuickChargeLoadingMiddle struct{ sound } + sound +} -// CrossbowQuickChargeEnd is a sound played when a crossbow with Quick Charge stops loading. -type CrossbowQuickChargeEnd struct{ sound } +const ( + // CrossbowStageLoadStart is the stage where crossbows start to load. + CrossbowStageLoadStart = iota + // CrossbowStageMiddle is the stage where crossbow is loading and stops loading. + CrossbowStageMiddle + // CrossbowStageLoadEnd is the stage where crossbow is finished loading. + CrossbowStageLoadEnd + // CrossbowStageShoot is the stage where a crossbow is shot. + CrossbowStageShoot +) // ArrowHit is a sound played when an arrow hits ground. type ArrowHit struct{ sound }