Skip to content

Commit

Permalink
entity/effect.go: Start lasting effects at tick 0 to more closely mat…
Browse files Browse the repository at this point in the history
…ch vanilla behaviour.
  • Loading branch information
Sandertv committed Dec 19, 2024
1 parent 9def5fd commit 3236eac
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 110 deletions.
7 changes: 3 additions & 4 deletions server/entity/effect.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (m *EffectManager) Add(e effect.Effect, entity Living) effect.Effect {
}
t, ok := e.Type().(effect.LastingType)
if !ok {
e.Type().Apply(entity, lvl, 0)
e.Type().Apply(entity, e)
return e
}
typ := reflect.TypeOf(e.Type())
Expand Down Expand Up @@ -97,9 +97,8 @@ func (m *EffectManager) Tick(entity Living, tx *world.Tx) {
update = true
continue
}
eff = eff.TickDuration()
eff.Type().Apply(entity, eff.Level(), eff.Duration())
m.effects[i] = eff
eff.Type().Apply(entity, eff)
m.effects[i] = eff.TickDuration()
}

if update {
Expand Down
74 changes: 41 additions & 33 deletions server/entity/effect/effect.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,54 @@ import (
"time"
)

// LastingType represents an effect type that can have a duration. An effect can be made using it by calling effect.New
// with the LastingType.
// LastingType represents an effect type that can have a duration. An effect
// can be made using it by calling effect.New with the LastingType.
type LastingType interface {
Type
// Start is called for lasting effects when they are initially added to an entity.
// Start is called for lasting effects when they are initially added.
Start(e world.Entity, lvl int)
// End is called for lasting effects when they are removed from an entity.
// End is called for lasting effects when they are removed.
End(e world.Entity, lvl int)
}

// PotentType represents an effect type which can have its potency changed.
type PotentType interface {
Type
// WithPotency updates the potency of the type with the one given and returns it.
WithPotency(potency float64) Type
}

// Type is an effect implementation that can be applied to an entity.
type Type interface {
// RGBA returns the colour of the effect. If multiple effects are present, the colours will be mixed
// together to form a new colour.
// RGBA returns the colour of the effect. If multiple effects are present,
// the colours will be mixed together to form a new colour.
RGBA() color.RGBA
// Apply applies the effect to an entity. This method applies the effect to an entity once for instant effects, such
// as healing the world.Entity for instant health.
// Apply always has a duration of 0 passed to it for instant effect implementations. For lasting effects that
// implement LastingType, the appropriate leftover duration is passed.
Apply(e world.Entity, lvl int, d time.Duration)
// Apply applies the effect to an entity. Apply is called only once for
// instant effects, such as InstantHealth, while it is called every tick for
// lasting effects. The Effect holding the Type is passed along with the
// current tick.
Apply(e world.Entity, eff Effect)
}

// Effect is an effect that can be added to an entity. Effects are either instant (applying the effect only once) or
// lasting (applying the effect every tick).
// Effect is an effect that can be added to an entity. Effects are either
// instant (applying the effect only once) or lasting (applying the effect
// every tick).
type Effect struct {
t Type
d time.Duration
lvl int
potency float64
ambient, particlesHidden bool
tick int
}

// NewInstant returns a new instant Effect using the Type passed. The effect will be applied to an entity once
// and will expire immediately after.
// NewInstant returns a new instant Effect using the Type passed. The effect
// will be applied to an entity once and will expire immediately after.
// NewInstant creates an Effect with a potency of 1.0.
func NewInstant(t Type, lvl int) Effect {
return Effect{t: t, lvl: lvl}
return NewInstantWithPotency(t, lvl, 1)
}

// NewInstantWithPotency returns a new instant Effect using the Type and level
// passed. The effect will be applied to an entity once and expire immediately
// after. The potency passed additionally influences the strength of the effect.
// A higher potency (> 1.0) increases the effect power, while a lower potency
// (< 1.0) reduces it.
func NewInstantWithPotency(t Type, lvl int, potency float64) Effect {
return Effect{t: t, lvl: lvl, potency: potency}
}

// New creates a new Effect using a LastingType passed. Once added to an entity, the time.Duration passed will be ticked down
Expand Down Expand Up @@ -102,21 +108,23 @@ func (e Effect) Type() Type {
func (e Effect) TickDuration() Effect {
if _, ok := e.t.(LastingType); ok {
e.d -= time.Second / 20
e.tick++
}
return e
}

// Tick returns the current tick of the Effect. This is the number of ticks that
// the Effect has been applied for.
func (e Effect) Tick() int {
return e.tick
}

// nopLasting is a lasting effect with no (server-side) behaviour. It does not implement the RGBA method.
type nopLasting struct{}

func (nopLasting) Apply(world.Entity, int, time.Duration) {}
func (nopLasting) End(world.Entity, int) {}
func (nopLasting) Start(world.Entity, int) {}

// tickDuration returns the duration as in-game ticks.
func tickDuration(d time.Duration) int {
return int(d / (time.Second / 20))
}
func (nopLasting) Apply(world.Entity, Effect) {}
func (nopLasting) End(world.Entity, int) {}
func (nopLasting) Start(world.Entity, int) {}

// ResultingColour calculates the resulting colour of the effects passed and returns a bool specifying if the
// effects were ambient effects, which will cause their particles to display less frequently.
Expand All @@ -125,8 +133,8 @@ func ResultingColour(effects []Effect) (color.RGBA, bool) {
ambient := true
for _, e := range effects {
if e.particlesHidden {
// Don't take effects with hidden particles into account for colour calculation: Their particles are hidden
// after all.
// Don't take effects with hidden particles into account for colour
// calculation: Their particles are hidden after all.
continue
}
c := e.Type().RGBA()
Expand Down
10 changes: 3 additions & 7 deletions server/entity/effect/fatal_poison.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package effect
import (
"github.com/df-mc/dragonfly/server/world"
"image/color"
"time"
)

// FatalPoison is a lasting effect that causes the affected entity to lose health gradually. FatalPoison,
Expand All @@ -13,12 +12,9 @@ type FatalPoison struct {
}

// Apply ...
func (FatalPoison) Apply(e world.Entity, lvl int, d time.Duration) {
interval := 50 >> (lvl - 1)
if interval < 1 {
interval = 1
}
if tickDuration(d)%interval == 0 {
func (FatalPoison) Apply(e world.Entity, eff Effect) {
interval := max(50>>(eff.Level()-1), 1)
if eff.Tick()%interval == 0 {
if l, ok := e.(living); ok {
l.Hurt(1, PoisonDamageSource{Fatal: true})
}
Expand Down
5 changes: 2 additions & 3 deletions server/entity/effect/hunger.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package effect
import (
"github.com/df-mc/dragonfly/server/world"
"image/color"
"time"
)

// Hunger is a lasting effect that causes an affected player to gradually lose saturation and food.
Expand All @@ -12,11 +11,11 @@ type Hunger struct {
}

// Apply ...
func (Hunger) Apply(e world.Entity, lvl int, _ time.Duration) {
func (Hunger) Apply(e world.Entity, eff Effect) {
if i, ok := e.(interface {
Exhaust(points float64)
}); ok {
i.Exhaust(float64(lvl) * 0.005)
i.Exhaust(float64(eff.Level()) * 0.005)
}
}

Expand Down
24 changes: 4 additions & 20 deletions server/entity/effect/instant_damage.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,17 @@ package effect
import (
"github.com/df-mc/dragonfly/server/world"
"image/color"
"time"
)

// InstantDamage is an instant effect that causes a living entity to immediately take some damage, depending
// on the level and the potency of the effect.
type InstantDamage struct {
// Potency specifies the potency of the instant damage. By default, this value is 1, which means 100% of
// the instant damage will be applied to an entity. A lingering damage potion, for example, has a potency
// of 0.5: It deals 1.5 hearts damage (per tick) instead of 3.
Potency float64
}

// WithPotency ...
func (i InstantDamage) WithPotency(potency float64) Type {
i.Potency = potency
return i
}
type InstantDamage struct{}

// Apply ...
func (i InstantDamage) Apply(e world.Entity, lvl int, _ time.Duration) {
if i.Potency == 0 {
// Potency of 1 by default.
i.Potency = 1
}
base := 3 << lvl
func (i InstantDamage) Apply(e world.Entity, eff Effect) {
base := 3 << eff.Level()
if l, ok := e.(living); ok {
l.Hurt(float64(base)*i.Potency, InstantDamageSource{})
l.Hurt(float64(base)*eff.potency, InstantDamageSource{})
}
}

Expand Down
24 changes: 4 additions & 20 deletions server/entity/effect/instant_health.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,18 @@ package effect
import (
"github.com/df-mc/dragonfly/server/world"
"image/color"
"time"
)

// InstantHealth is an instant effect that causes the player that it is applied to immediately regain some
// health. The amount of health regained depends on the effect level and potency.
type InstantHealth struct {
// Potency specifies the potency of the instant health. By default, this value is 1, which means 100% of
// the instant health will be applied to an entity. A lingering health potion, for example, has a potency
// of 0.5: It heals 1 heart (per tick) instead of 2.
Potency float64
}

// WithPotency ...
func (i InstantHealth) WithPotency(potency float64) Type {
i.Potency = potency
return i
}
type InstantHealth struct{}

// Apply instantly heals the world.Entity passed for a bit of health, depending on the effect level and
// potency.
func (i InstantHealth) Apply(e world.Entity, lvl int, _ time.Duration) {
if i.Potency == 0 {
// Potency of 1 by default.
i.Potency = 1
}
base := 2 << lvl
func (i InstantHealth) Apply(e world.Entity, eff Effect) {
base := 2 << eff.Level()
if l, ok := e.(living); ok {
l.Heal(float64(base)*i.Potency, InstantHealingSource{})
l.Heal(float64(base)*eff.potency, InstantHealingSource{})
}
}

Expand Down
10 changes: 3 additions & 7 deletions server/entity/effect/poison.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package effect
import (
"github.com/df-mc/dragonfly/server/world"
"image/color"
"time"
)

// Poison is a lasting effect that causes the affected entity to lose health gradually. Poison cannot kill,
Expand All @@ -13,12 +12,9 @@ type Poison struct {
}

// Apply ...
func (Poison) Apply(e world.Entity, lvl int, d time.Duration) {
interval := 50 >> (lvl - 1)
if interval < 1 {
interval = 1
}
if tickDuration(d)%interval == 0 {
func (Poison) Apply(e world.Entity, eff Effect) {
interval := max(50>>(eff.Level()-1), 1)
if eff.Tick()%interval == 0 {
if l, ok := e.(living); ok && l.Health() > 1 {
l.Hurt(1, PoisonDamageSource{})
}
Expand Down
10 changes: 3 additions & 7 deletions server/entity/effect/regeneration.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package effect
import (
"github.com/df-mc/dragonfly/server/world"
"image/color"
"time"
)

// Regeneration is an effect that causes the entity that it is added to slowly regenerate health. The level
Expand All @@ -13,12 +12,9 @@ type Regeneration struct {
}

// Apply applies health to the world.Entity passed if the duration of the effect is at the right tick.
func (Regeneration) Apply(e world.Entity, lvl int, d time.Duration) {
interval := 50 >> (lvl - 1)
if interval < 1 {
interval = 1
}
if tickDuration(d)%interval == 0 {
func (Regeneration) Apply(e world.Entity, eff Effect) {
interval := max(50>>(eff.Level()-1), 1)
if eff.Tick()%interval == 0 {
if l, ok := e.(living); ok {
l.Heal(1, RegenerationHealingSource{})
}
Expand Down
5 changes: 2 additions & 3 deletions server/entity/effect/saturation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package effect
import (
"github.com/df-mc/dragonfly/server/world"
"image/color"
"time"
)

// Saturation is a lasting effect that causes the affected player to regain food and saturation.
Expand All @@ -12,11 +11,11 @@ type Saturation struct {
}

// Apply ...
func (Saturation) Apply(e world.Entity, lvl int, _ time.Duration) {
func (Saturation) Apply(e world.Entity, eff Effect) {
if i, ok := e.(interface {
Saturate(food int, saturation float64)
}); ok {
i.Saturate(lvl, 2*float64(lvl))
i.Saturate(eff.Level(), 2*float64(eff.Level()))
}
}

Expand Down
7 changes: 3 additions & 4 deletions server/entity/effect/wither.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package effect
import (
"github.com/df-mc/dragonfly/server/world"
"image/color"
"time"
)

// Wither is a lasting effect that causes an entity to take continuous damage that is capable of killing an
Expand All @@ -13,9 +12,9 @@ type Wither struct {
}

// Apply ...
func (Wither) Apply(e world.Entity, lvl int, d time.Duration) {
interval := max(80>>lvl, 1)
if tickDuration(d)%interval == 0 {
func (Wither) Apply(e world.Entity, eff Effect) {
interval := max(80>>eff.Level(), 1)
if eff.Tick()%interval == 0 {
if l, ok := e.(living); ok {
l.Hurt(1, WitherDamageSource{})
}
Expand Down
4 changes: 2 additions & 2 deletions server/entity/splashable.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ func potionSplash(durMul float64, pot potion.Potion, linger bool) func(e *Ent, t

splashed := otherE.(Living)
for _, eff := range effects {
if p, ok := eff.Type().(effect.PotentType); ok {
splashed.AddEffect(effect.NewInstant(p.WithPotency(f), eff.Level()))
if _, ok := eff.Type().(effect.LastingType); !ok {
splashed.AddEffect(effect.NewInstantWithPotency(eff.Type(), eff.Level(), f))
continue
}

Expand Down

0 comments on commit 3236eac

Please sign in to comment.