Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Crossbows #957

Merged
merged 52 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5b67fbd
first attempt at crossbows
xNatsuri Dec 10, 2024
36b8c33
move crossbows to a chargeable interface.
xNatsuri Dec 13, 2024
727cb71
remove redundant return and other release change
xNatsuri Dec 13, 2024
df4c44c
revert main.go changes
xNatsuri Dec 13, 2024
203d970
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 14, 2024
25932db
Merge branch 'master' of https://github.com/xNatsuri/dragonfly into c…
xNatsuri Dec 14, 2024
4fb2a99
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 15, 2024
bc196c3
Merge branch 'crossbow' of https://github.com/xNatsuri/dragonfly into…
xNatsuri Dec 15, 2024
19569d7
requested changes
xNatsuri Dec 15, 2024
91eb288
remove unused interface and rename Release method
xNatsuri Dec 15, 2024
534d257
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 15, 2024
8cae7d8
use rotation.Neg() for arrows
xNatsuri Dec 15, 2024
0633645
fix rot
xNatsuri Dec 16, 2024
f801edd
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 20, 2024
bbd3c19
fix item charge and releasing issue
xNatsuri Dec 21, 2024
2a26e69
changes
xNatsuri Dec 21, 2024
5b51804
add quick charge sounds
xNatsuri Dec 22, 2024
6d784ec
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 22, 2024
bcd9f5f
requested changes
xNatsuri Dec 22, 2024
af49dae
Merge remote-tracking branch 'origin/crossbow' into crossbow
xNatsuri Dec 22, 2024
135f6a7
changes
xNatsuri Dec 22, 2024
c89071c
change name
xNatsuri Dec 22, 2024
6a2a1d3
move to internal package
xNatsuri Dec 22, 2024
0abdcce
account for offHand
xNatsuri Dec 22, 2024
9e35521
better match vanilla velocity
xNatsuri Dec 22, 2024
d044460
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 23, 2024
ff324a3
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 24, 2024
daf4a39
allow bow to use offhand arrows
xNatsuri Dec 24, 2024
9bc9204
rename NewItem func to DuplicateStack and use in handleCraftHandler
xNatsuri Dec 24, 2024
0552bab
reverse main changes
xNatsuri Dec 24, 2024
399205a
changes
xNatsuri Dec 24, 2024
d427bff
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 24, 2024
a701deb
fix
xNatsuri Dec 24, 2024
662f8e8
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 24, 2024
ccdca06
Merge branch 'crossbow' of https://github.com/xNatsuri/dragonfly into…
xNatsuri Dec 24, 2024
1bde76d
use WithItem
xNatsuri Dec 24, 2024
db7cbe3
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 24, 2024
e70b3af
changes
xNatsuri Dec 25, 2024
9693ec3
changes
xNatsuri Dec 25, 2024
d1fc7a7
fix
xNatsuri Dec 25, 2024
212cd1f
Merge branch 'df-mc:master' into crossbow
xNatsuri Dec 28, 2024
0639438
Merge branch 'master' into crossbow
DaPigGuy Dec 29, 2024
836f5ec
fixup!
DaPigGuy Dec 29, 2024
f8488c6
Merge branch 'df-mc:master' into crossbow
xNatsuri Jan 2, 2025
7516809
fix
xNatsuri Jan 3, 2025
60f5b35
Merge remote-tracking branch 'origin/crossbow' into crossbow
xNatsuri Jan 3, 2025
cdf6253
revert main.go
xNatsuri Jan 3, 2025
af28e0e
Add middle loading sound and (maybe) fix funky loading
RoyalMCPE Jan 3, 2025
462a40d
cleanup
RoyalMCPE Jan 3, 2025
9148ace
Merge pull request #1 from RoyalMCPE/crossbow
xNatsuri Jan 3, 2025
bec2619
Merge branch 'df-mc:master' into crossbow
xNatsuri Jan 4, 2025
1f2b43d
changes
xNatsuri Jan 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions server/entity/firework.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,10 +29,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
Expand Down
4 changes: 2 additions & 2 deletions server/entity/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
204 changes: 204 additions & 0 deletions server/item/crossbow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package item

import (
"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.
type Crossbow struct {
// Item is the item the crossbow is charged with.
Item Stack
}

// 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) bool {
if !c.Item.Empty() {
return false
}

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)
_, 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)
}
}

c.Item = projectileItem.Grow(-projectileItem.Count() + 1)
if !creative {
ctx.Consume(c.Item)
}

crossbow := held.WithItem(c)
releaser.SetHeldItems(crossbow, left)
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{})
}
}

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{})
}
}

// 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
}

creative := releaser.GameMode().CreativeInventory()
rot := releaser.Rotation().Neg()
dirVec := releaser.Rotation().Vec3().Normalize()

if firework, isFirework := c.Item.Item().(Firework); isFirework {
createFirework := tx.World().EntityRegistry().Config().Firework
xNatsuri marked this conversation as resolved.
Show resolved Hide resolved
fireworkEntity := createFirework(world.EntitySpawnOpts{
Position: torsoPosition(releaser),
Velocity: dirVec.Mul(0.1),
Rotation: rot,
}, firework, releaser, 1.15, 0, false)
tx.AddEntity(fireworkEntity)
ctx.DamageItem(3)
} else {
createArrow := tx.World().EntityRegistry().Config().Arrow
arrow := createArrow(world.EntitySpawnOpts{
Position: torsoPosition(releaser),
Velocity: dirVec.Mul(5.15),
Rotation: rot,
}, 9, releaser, false, false, !creative, 0, c.Item.Item().(Arrow).Tip)
tx.AddEntity(arrow)
ctx.DamageItem(1)
}

c.Item = Stack{}
held, left := releaser.HeldItems()
crossbow := held.WithItem(c)
releaser.SetHeldItems(crossbow, left)
tx.PlaySound(releaser.Position(), sound.CrossbowShoot{})
return true
}

// 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 * 15)
}

// 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
}

// 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
49 changes: 49 additions & 0 deletions server/item/enchantment/quick_charge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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.
var QuickCharge quickCharge
xNatsuri marked this conversation as resolved.
Show resolved Hide resolved

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 := 12 + (level-1)*20
return minCost, 50
}

// Rarity ...
func (quickCharge) Rarity() item.EnchantmentRarity {
return item.EnchantmentRarityUncommon
}

// ChargeDuration returns the charge duration.
func (quickCharge) ChargeDuration(level int) time.Duration {
xNatsuri marked this conversation as resolved.
Show resolved Hide resolved
return time.Duration((1.25 - 0.25*float64(level)) * float64(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
}
2 changes: 1 addition & 1 deletion server/item/enchantment/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
4 changes: 2 additions & 2 deletions server/item/firework.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions server/item/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ 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) 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
}

// 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 {
Expand Down Expand Up @@ -217,6 +227,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 {
Expand Down
1 change: 1 addition & 0 deletions server/item/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{})
Expand Down
Loading
Loading