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

Implemented fireworks and firework stars #576

Merged
merged 15 commits into from
Jul 29, 2022
Merged
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
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