Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions server/block/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ type Activatable interface {
Activate(pos cube.Pos, clickedFace cube.Face, tx *world.Tx, u item.User, ctx *item.UseContext) bool
}

// WindChargeAffected represents a block that is toggled by a wind charge
// burst, such as doors, trapdoors and fence gates.
// TODO: Buttons, levers, bells and candles should also implement this.
type WindChargeAffected interface {
Activatable
WindChargeAffected()
}

// Pickable represents a block that may give a different item then the block itself when picked.
type Pickable interface {
// Pick returns the item that is picked when the block is picked.
Expand Down
3 changes: 3 additions & 0 deletions server/block/copper_door.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ func (d CopperDoor) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.Use
return true
}

// WindChargeAffected ...
func (CopperDoor) WindChargeAffected() {}

func (d CopperDoor) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) {
attemptOxidation(pos, tx, r, d)
}
Expand Down
3 changes: 3 additions & 0 deletions server/block/copper_trapdoor.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ func (t CopperTrapdoor) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item
return true
}

// WindChargeAffected ...
func (CopperTrapdoor) WindChargeAffected() {}

func (t CopperTrapdoor) RandomTick(pos cube.Pos, tx *world.Tx, r *rand.Rand) {
attemptOxidation(pos, tx, r, t)
}
Expand Down
3 changes: 3 additions & 0 deletions server/block/wood_door.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ func (d WoodDoor) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.User,
return true
}

// WindChargeAffected ...
func (WoodDoor) WindChargeAffected() {}

// BreakInfo ...
func (d WoodDoor) BreakInfo() BreakInfo {
return newBreakInfo(3, alwaysHarvestable, axeEffective, oneOf(d))
Expand Down
5 changes: 4 additions & 1 deletion server/block/wood_fence_gate.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (f WoodFenceGate) shouldBeLowered(pos cube.Pos, tx *world.Tx) bool {
// Activate ...
func (f WoodFenceGate) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool {
f.Open = !f.Open
if f.Open && f.Facing.Opposite() == u.Rotation().Direction() {
if f.Open && u != nil && f.Facing.Opposite() == u.Rotation().Direction() {
Comment thread
TwistedAsylumMC marked this conversation as resolved.
f.Facing = f.Facing.Opposite()
}
tx.SetBlock(pos, f, nil)
Expand All @@ -92,6 +92,9 @@ func (f WoodFenceGate) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.
return true
}

// WindChargeAffected ...
func (WoodFenceGate) WindChargeAffected() {}

// SideClosed ...
func (f WoodFenceGate) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool {
return false
Expand Down
3 changes: 3 additions & 0 deletions server/block/wood_trapdoor.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ func (t WoodTrapdoor) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, _ item.U
return true
}

// WindChargeAffected ...
func (WoodTrapdoor) WindChargeAffected() {}

// BreakInfo ...
func (t WoodTrapdoor) BreakInfo() BreakInfo {
return newBreakInfo(3, alwaysHarvestable, axeEffective, oneOf(t))
Expand Down
2 changes: 2 additions & 0 deletions server/entity/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var DefaultRegistry = conf.New([]world.EntityType{
SplashPotionType,
TNTType,
TextType,
WindChargeType,
})

var conf = world.EntityRegistryConfig{
Expand All @@ -35,6 +36,7 @@ var conf = world.EntityRegistryConfig{
EnderPearl: NewEnderPearl,
FallingBlock: NewFallingBlock,
Lightning: NewLightning,
WindCharge: NewWindCharge,
Firework: func(opts world.EntitySpawnOpts, firework world.Item, owner world.Entity, sidewaysVelocityMultiplier, upwardsAcceleration float64, attached bool) *world.EntityHandle {
return newFirework(opts, firework.(item.Firework), owner, sidewaysVelocityMultiplier, upwardsAcceleration, attached)
},
Expand Down
113 changes: 113 additions & 0 deletions server/entity/wind_charge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package entity

import (
"github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/block/cube/trace"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/particle"
"github.com/df-mc/dragonfly/server/world/sound"
)

// windChargeBurstRadius is the maximum radius within which entities are
// knocked back by a wind charge burst.
const windChargeBurstRadius = 2.5

// NewWindCharge creates a wind charge entity at a position with an owner
// entity. Wind charges fly in a straight line (no gravity) and create a burst
// of wind on impact that knocks back nearby entities and toggles certain
// interactive blocks.
func NewWindCharge(opts world.EntitySpawnOpts, owner world.Entity) *world.EntityHandle {
conf := windChargeConf
conf.Owner = owner.H()
return opts.New(WindChargeType, conf)
}

// TODO: Wind charges should have increased drag when travelling through water
// or lava, but per-medium drag is not yet supported by ProjectileBehaviour.
var windChargeConf = ProjectileBehaviourConfig{
Gravity: 0,
Drag: 0,
Damage: -1,
Particle: particle.WindExplosion{},
Sound: sound.WindChargeBurst{},
Hit: windChargeBurst,
}
Comment thread
HashimTheArab marked this conversation as resolved.

// windChargeBurst is called when a wind charge hits a target. It deals 1 HP
// damage on a direct entity hit, knocks back all living entities within the
// burst radius, and toggles interactive blocks at the impact point.
func windChargeBurst(e *Ent, tx *world.Tx, target trace.Result) {
pos := target.Position()
owner, _ := e.Behaviour().(*ProjectileBehaviour).Owner().Entity(tx)

// Deal flat 1 HP damage to the directly-hit entity.
if er, ok := target.(trace.EntityResult); ok {
if l, ok := er.Entity().(Living); ok {
l.Hurt(1, ProjectileDamageSource{Projectile: e, Owner: owner})
}
}

// Apply knockback to all living entities within the burst radius. Impact
// scales with distance (closer = stronger) and is split into horizontal
// and vertical components.
box := e.H().Type().BBox(e).Translate(pos).Grow(windChargeBurstRadius)
for other := range tx.EntitiesWithin(box) {
if other.H() == e.H() {
continue
}
l, ok := other.(Living)
if !ok {
continue
}
entityPos := other.Position()
dist := entityPos.Sub(pos).Len()
impact := 1.3 - dist/windChargeBurstRadius
if impact <= 0 {
continue
}

vel := l.Velocity()
// If the entity is directly above the impact, apply a flat upward
// boost. Otherwise split into horizontal and vertical components.
dx := entityPos[0] - pos[0]
dz := entityPos[2] - pos[2]
if dx*dx+dz*dz < 0.01 {
vel[1] += 1.1
} else {
dir := entityPos.Sub(pos)
dir[1] = 0
dir = dir.Normalize()
vel = vel.Add(dir.Mul(impact))
vel[1] += impact * 0.4
}
l.SetVelocity(vel)
}

// Toggle interactive blocks at the impact point.
if r, ok := target.(trace.BlockResult); ok {
pos := r.BlockPosition()
if b, ok := tx.Block(pos).(block.WindChargeAffected); ok {
b.Activate(pos, r.Face(), tx, nil, nil)
}
}
}

// WindChargeType is a world.EntityType implementation for WindCharge.
var WindChargeType windChargeType

type windChargeType struct{}

func (windChargeType) Open(tx *world.Tx, handle *world.EntityHandle, data *world.EntityData) world.Entity {
return &Ent{tx: tx, handle: handle, data: data}
}

func (windChargeType) EncodeEntity() string { return "minecraft:wind_charge_projectile" }
func (windChargeType) BBox(world.Entity) cube.BBox {
return cube.Box(-0.15625, 0, -0.15625, 0.15625, 0.3125, 0.15625)
}

func (windChargeType) DecodeNBT(_ map[string]any, data *world.EntityData) {
data.Data = windChargeConf.New()
}
func (windChargeType) EncodeNBT(*world.EntityData) map[string]any { return nil }
1 change: 1 addition & 0 deletions server/item/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func init() {
world.RegisterItem(TurtleShell{})
world.RegisterItem(WarpedFungusOnAStick{})
world.RegisterItem(Wheat{})
world.RegisterItem(WindCharge{})
world.RegisterItem(WrittenBook{})
for _, t := range ArmourTiers() {
world.RegisterItem(Helmet{Tier: t})
Expand Down
33 changes: 33 additions & 0 deletions server/item/wind_charge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package item

import (
"time"

"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/sound"
)

// WindCharge is a throwable item that creates a burst of wind on impact, knocking back nearby entities and
// toggling certain blocks such as doors, trapdoors and fence gates.
type WindCharge struct{}

// Use ...
func (WindCharge) Use(tx *world.Tx, user User, ctx *UseContext) bool {
create := tx.World().EntityRegistry().Config().WindCharge
opts := world.EntitySpawnOpts{Position: eyePosition(user), Velocity: user.Rotation().Vec3().Mul(3.0)}
Comment thread
HashimTheArab marked this conversation as resolved.
tx.AddEntity(create(opts, user))
tx.PlaySound(user.Position(), sound.ItemThrow{})

ctx.SubtractFromCount(1)
return true
}

// Cooldown ...
func (WindCharge) Cooldown() time.Duration {
return time.Millisecond * 500
}

// EncodeItem ...
func (WindCharge) EncodeItem() (name string, meta int16) {
return "minecraft:wind_charge", 0
}
7 changes: 7 additions & 0 deletions server/session/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,11 @@ func (s *Session) ViewParticle(pos mgl64.Vec3, p world.Particle) {
EventType: packet.LevelEventParticleLegacyEvent | 88,
Position: vec64To32(pos),
})
case particle.WindExplosion:
s.writePacket(&packet.LevelEvent{
EventType: packet.LevelEventParticlesWindExplosion,
Position: vec64To32(pos),
})
}
}

Expand Down Expand Up @@ -652,6 +657,8 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool)
case sound.Dream():
pk.SoundType = packet.SoundEventGoatCall7
}
case sound.WindChargeBurst:
pk.SoundType = packet.SoundEventWindChargeBurst
case sound.FireExtinguish:
pk.SoundType = packet.SoundEventExtinguishFire
case sound.Ignite:
Expand Down
1 change: 1 addition & 0 deletions server/world/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ type EntityRegistryConfig struct {
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
WindCharge func(opts EntitySpawnOpts, owner Entity) *EntityHandle
Lightning func(opts EntitySpawnOpts) *EntityHandle
}

Expand Down
3 changes: 3 additions & 0 deletions server/world/particle/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ type Effect struct {

// EntityFlame is a particle shown when an entity is set on fire.
type EntityFlame struct{ particle }

// WindExplosion is a particle shown when a wind charge bursts on impact.
type WindExplosion struct{ particle }
3 changes: 3 additions & 0 deletions server/world/sound/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,8 @@ type GoatHorn struct {
// blaze shoots a fireball.
type FireCharge struct{ sound }

// WindChargeBurst is a sound played when a wind charge bursts on impact.
type WindChargeBurst struct{ sound }

// Totem is a sound played when a player uses a totem.
type Totem struct{ sound }
Loading