Skip to content

Commit

Permalink
server/item: Implemented fireworks and firework stars (#576)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustTalDevelops committed Jul 29, 2022
1 parent b281564 commit 5d6a225
Show file tree
Hide file tree
Showing 18 changed files with 576 additions and 126 deletions.
3 changes: 3 additions & 0 deletions server/entity/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type PickedUpAction struct {
action
}

// FireworkExplosionAction is a world.EntityAction that makes a Firework rocket display an explosion particle.
type FireworkExplosionAction struct{ action }

// action implements the Action interface. Structures in this package may embed it to gets its functionality
// out of the box.
type action struct{}
Expand Down
181 changes: 181 additions & 0 deletions server/entity/firework.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package entity

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/block/cube/trace"
"github.com/df-mc/dragonfly/server/entity/damage"
"github.com/df-mc/dragonfly/server/internal/nbtconv"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/sound"
"github.com/go-gl/mathgl/mgl64"
"math"
"math/rand"
)

// Firework is an item (and entity) used for creating decorative explosions, boosting when flying with elytra, and
// loading into a crossbow as ammunition.
type Firework struct {
transform

yaw, pitch float64
firework item.Firework

owner world.Entity

c *MovementComputer

ticks int
close bool
}

// NewFirework ...
func NewFirework(pos mgl64.Vec3, yaw, pitch float64, firework item.Firework) *Firework {
f := &Firework{
yaw: yaw,
pitch: pitch,
firework: firework,
c: &MovementComputer{},
ticks: int(firework.RandomisedDuration().Milliseconds() / 50),
}
f.transform = newTransform(f, pos)
f.vel = mgl64.Vec3{rand.Float64() * 0.001, 0.05, rand.Float64() * 0.001}
return f
}

// Name ...
func (f *Firework) Name() string {
return "Firework Rocket"
}

// EncodeEntity ...
func (f *Firework) EncodeEntity() string {
return "minecraft:fireworks_rocket"
}

// BBox ...
func (f *Firework) BBox() cube.BBox {
return cube.BBox{}
}

// Firework returns the underlying item.Firework of the Firework.
func (f *Firework) Firework() item.Firework {
return f.firework
}

// Rotation ...
func (f *Firework) Rotation() (float64, float64) {
f.mu.Lock()
defer f.mu.Unlock()
return f.yaw, f.pitch
}

// Tick ...
func (f *Firework) Tick(w *world.World, current int64) {
if f.close {
_ = f.Close()
return
}

f.mu.Lock()
f.vel[0] *= 1.15
f.vel[1] += 0.04
f.vel[2] *= 1.15
m := f.c.TickMovement(f, f.pos, f.vel, f.yaw, f.pitch)
f.pos, f.vel, f.yaw, f.pitch = m.pos, m.vel, m.yaw, m.pitch
f.mu.Unlock()

m.Send()

if m.pos[1] < float64(w.Range()[0]) && current%10 == 0 {
f.close = true
return
}

f.ticks--
if f.ticks >= 0 {
return
}

explosions := f.Firework().Explosions
for _, v := range w.Viewers(m.pos) {
v.ViewEntityAction(f, FireworkExplosionAction{})
}
for _, explosion := range explosions {
if explosion.Shape == item.FireworkShapeHugeSphere() {
w.PlaySound(m.pos, sound.FireworkHugeBlast{})
} else {
w.PlaySound(m.pos, sound.FireworkBlast{})
}
if explosion.Twinkle {
w.PlaySound(m.pos, sound.FireworkTwinkle{})
}
}

if len(explosions) > 0 {
force := float64(len(explosions)*2) + 5.0
for _, e := range w.EntitiesWithin(f.BBox().Translate(m.pos).Grow(5.25), func(e world.Entity) bool {
l, living := e.(Living)
return !living || l.AttackImmune()
}) {
pos := e.Position()
dist := m.pos.Sub(pos).Len()
if dist > 5.0 {
// The maximum distance allowed is 5.0 blocks.
continue
}
if _, ok := trace.Perform(m.pos, pos, w, e.BBox().Grow(0.3), func(world.Entity) bool {
return true
}); ok {
dmg := force * math.Sqrt((5.0-dist)/5.0)
e.(Living).Hurt(dmg, damage.SourceProjectile{Owner: f.Owner(), Projectile: f})
}
}
}

f.close = true
}

// New creates an firework with the position, velocity, yaw, and pitch provided. It doesn't spawn the firework,
// only returns it.
func (f *Firework) New(pos mgl64.Vec3, yaw, pitch float64, firework item.Firework) world.Entity {
return NewFirework(pos, yaw, pitch, firework)
}

// Owner ...
func (f *Firework) Owner() world.Entity {
f.mu.Lock()
defer f.mu.Unlock()
return f.owner
}

// Own ...
func (f *Firework) Own(owner world.Entity) {
f.mu.Lock()
defer f.mu.Unlock()
f.owner = owner
}

// DecodeNBT decodes the properties in a map to a Firework and returns a new Firework entity.
func (f *Firework) DecodeNBT(data map[string]any) any {
firework := NewFirework(
nbtconv.MapVec3(data, "Pos"),
float64(nbtconv.Map[float32](data, "Pitch")),
float64(nbtconv.Map[float32](data, "Yaw")),
nbtconv.MapItem(data, "Item").Item().(item.Firework),
)
firework.vel = nbtconv.MapVec3(data, "Motion")
return firework
}

// EncodeNBT encodes the Firework entity's properties as a map and returns it.
func (f *Firework) EncodeNBT() map[string]any {
yaw, pitch := f.Rotation()
return map[string]any{
"Item": nbtconv.WriteItem(item.NewStack(f.Firework(), 1), true),
"Pos": nbtconv.Vec3ToFloat32Slice(f.Position()),
"Motion": nbtconv.Vec3ToFloat32Slice(f.Velocity()),
"Yaw": yaw,
"Pitch": pitch,
}
}
1 change: 1 addition & 0 deletions server/entity/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ func init() {
world.RegisterEntity(&Lightning{})
world.RegisterEntity(&Arrow{})
world.RegisterEntity(&ExperienceOrb{})
world.RegisterEntity(&Firework{})
}
10 changes: 10 additions & 0 deletions server/item/colour.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,13 @@ func (c colour) String() string {
func (c colour) Uint8() uint8 {
return uint8(c)
}

// invertColour converts the item.Colour passed and returns the colour ID inverted.
func invertColour(c Colour) int16 {
return ^int16(c.Uint8()) & 0xf
}

// invertColourID converts the int16 passed the returns the item.Colour inverted.
func invertColourID(id int16) Colour {
return Colours()[uint8(^id&0xf)]
}
9 changes: 0 additions & 9 deletions server/item/dye.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,6 @@ type dyeable interface {
Dye(c Colour) (world.Block, bool)
}

// AllDyes returns all 16 dye items
func AllDyes() []world.Item {
b := make([]world.Item, 0, 16)
for _, c := range Colours() {
b = append(b, Dye{Colour: c})
}
return b
}

// EncodeItem ...
func (d Dye) EncodeItem() (name string, meta int16) {
if d.Colour.String() == "silver" {
Expand Down
2 changes: 0 additions & 2 deletions server/item/ender_pearl.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ func (e EnderPearl) Use(w *world.World, user User, ctx *UseContext) bool {
ctx.SubtractFromCount(1)

w.PlaySound(user.Position(), sound.ItemThrow{})

w.AddEntity(entity)

return true
}

Expand Down
86 changes: 86 additions & 0 deletions server/item/firework.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package item

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/sound"
"github.com/go-gl/mathgl/mgl64"
"math/rand"
"time"
)

// Firework is an item (and entity) used for creating decorative explosions, boosting when flying with elytra, and
// loading into a crossbow as ammunition.
type Firework struct {
// Duration is the flight duration of the firework.
Duration time.Duration
// Explosions is the list of explosions the firework should create when launched.
Explosions []FireworkExplosion
}

// UseOnBlock ...
func (f Firework) UseOnBlock(blockPos cube.Pos, _ cube.Face, clickPos mgl64.Vec3, w *world.World, user User, ctx *UseContext) bool {
firework, ok := world.EntityByName("minecraft:fireworks_rocket")
if !ok {
return false
}

p, ok := firework.(interface {
New(pos mgl64.Vec3, yaw, pitch float64, firework Firework) world.Entity
})
if !ok {
return false
}

pos := blockPos.Vec3().Add(clickPos)
entity := p.New(pos, rand.Float64()*360, 90, f)
if o, ok := entity.(owned); ok {
o.Own(user)
}

ctx.SubtractFromCount(1)

w.PlaySound(pos, sound.FireworkLaunch{})
w.AddEntity(entity)
return true
}

// EncodeNBT ...
func (f Firework) EncodeNBT() map[string]any {
explosions := make([]any, 0, len(f.Explosions))
for _, explosion := range f.Explosions {
explosions = append(explosions, explosion.EncodeNBT())
}
return map[string]any{"Fireworks": map[string]any{
"Explosions": explosions,
"Flight": uint8(f.Duration.Milliseconds() / 50),
}}
}

// DecodeNBT ...
func (f Firework) DecodeNBT(data map[string]any) any {
if fireworks, ok := data["Fireworks"].(map[string]any); ok {
if explosions, ok := fireworks["Explosions"].([]any); ok {
f.Explosions = make([]FireworkExplosion, len(explosions))
for i, explosion := range f.Explosions {
f.Explosions[i] = explosion.DecodeNBT(explosions[i].(map[string]any)).(FireworkExplosion)
}
}
if durationTicks, ok := fireworks["Flight"].(uint8); ok {
f.Duration = time.Duration(durationTicks) * 50 * time.Millisecond
}
}
return f
}

// RandomisedDuration returns the randomised flight duration of the firework.
func (f Firework) RandomisedDuration() time.Duration {
definite := f.Duration + time.Millisecond*50
randomness := time.Duration(rand.Intn(int(time.Millisecond * 600)))
return definite*10 + randomness
}

// EncodeItem ...
func (Firework) EncodeItem() (name string, meta int16) {
return "minecraft:firework_rocket", 0
}
51 changes: 51 additions & 0 deletions server/item/firework_explosion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package item

// FireworkExplosion represents an explosion of a firework.
type FireworkExplosion struct {
// Shape represents the shape of the explosion.
Shape FireworkShape
// Colour is the colour of the explosion.
Colour Colour
// Fade is the colour the explosion should fade into. Fades must be set to true in order for this to function.
Fade Colour
// Fades is true if the explosion should fade into the fade colour.
Fades bool
// Twinkle is true if the explosion should twinkle on explode.
Twinkle bool
// Trail is true if the explosion should have a trail.
Trail bool
}

// EncodeNBT ...
func (f FireworkExplosion) EncodeNBT() map[string]any {
data := map[string]any{
"FireworkType": f.Shape.Uint8(),
"FireworkColor": [1]uint8{uint8(invertColour(f.Colour))},
"FireworkFade": [0]uint8{},
"FireworkFlicker": boolByte(f.Twinkle),
"FireworkTrail": boolByte(f.Trail),
}
if f.Fades {
data["FireworkFade"] = [1]uint8{uint8(invertColour(f.Fade))}
}
return data
}

// DecodeNBT ...
func (f FireworkExplosion) DecodeNBT(data map[string]any) any {
f.Shape = FireworkTypes()[data["FireworkType"].(uint8)]
f.Twinkle = data["FireworkFlicker"].(uint8) == 1
f.Trail = data["FireworkTrail"].(uint8) == 1

colours := data["FireworkColor"]
if diskColour, ok := colours.([1]uint8); ok {
f.Colour = invertColourID(int16(diskColour[0]))
} else if networkColours, ok := colours.([]any); ok {
f.Colour = invertColourID(int16(networkColours[0].(uint8)))
}

if fades, ok := data["FireworkFade"].([1]uint8); ok {
f.Fade, f.Fades = invertColourID(int16(fades[0])), true
}
return f
}
Loading

0 comments on commit 5d6a225

Please sign in to comment.