Files

220 lines
6.3 KiB
Go
Raw Permalink Normal View History

2026-07-09 08:33:57 +08:00
package block
import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/entity/effect"
"github.com/df-mc/dragonfly/server/internal/nbtconv"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"math"
"time"
)
// Beacon is a block that projects a light beam skyward, and can provide status effects such as speed, Jump
// Boost, haste, regeneration, resistance, or strength to nearby players.
type Beacon struct {
solid
transparent
clicksAndSticks
sourceWaterDisplacer
// Primary and Secondary are the primary and secondary effects broadcast to nearby entities by the
// beacon.
Primary, Secondary effect.LastingType
// level is the amount of the pyramid's levels, it is defined by the mineral blocks which build up the
// pyramid, and can be 0-4.
level int
}
// BeaconSource represents a block which is capable of contributing to powering a beacon pyramid.
type BeaconSource interface {
// PowersBeacon returns a bool which indicates whether this block can contribute to powering up a
// beacon pyramid.
PowersBeacon() bool
}
// BreakInfo ...
func (b Beacon) BreakInfo() BreakInfo {
return newBreakInfo(3, alwaysHarvestable, nothingEffective, oneOf(Beacon{}))
}
// Activate manages the opening of a beacon by activating it.
func (b Beacon) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool {
if opener, ok := u.(ContainerOpener); ok {
opener.OpenBlockContainer(pos, tx)
return true
}
return true
}
// DecodeNBT ...
func (b Beacon) DecodeNBT(data map[string]any) any {
b.level = int(nbtconv.Int32(data, "Levels"))
if primary, ok := effect.ByID(int(nbtconv.Int32(data, "Primary"))); ok {
b.Primary = primary.(effect.LastingType)
}
if secondary, ok := effect.ByID(int(nbtconv.Int32(data, "Secondary"))); ok {
b.Secondary = secondary.(effect.LastingType)
}
return b
}
// EncodeNBT ...
func (b Beacon) EncodeNBT() map[string]any {
m := map[string]any{
"id": "Beacon",
"Levels": int32(b.level),
}
if primary, ok := effect.ID(b.Primary); ok {
m["Primary"] = int32(primary)
}
if secondary, ok := effect.ID(b.Secondary); ok {
m["Secondary"] = int32(secondary)
}
return m
}
// SideClosed ...
func (b Beacon) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool {
return false
}
// LightEmissionLevel ...
func (Beacon) LightEmissionLevel() uint8 {
return 15
}
// Level returns an integer 0-4 which defines the current pyramid level of the beacon.
func (b Beacon) Level() int {
return b.level
}
// Tick recalculates level, recalculates the active state of the beacon, and powers players,
// once every 80 ticks (4 seconds).
func (b Beacon) Tick(currentTick int64, pos cube.Pos, tx *world.Tx) {
if currentTick%80 == 0 {
before := b.level
// Recalculating pyramid level and powering up players in range once every 4 seconds.
b.level = b.recalculateLevel(pos, tx)
if before != b.level {
tx.SetBlock(pos, b, nil)
}
if b.level == 0 {
return
}
if !b.obstructed(pos, tx) {
b.broadcastBeaconEffects(pos, tx)
}
}
}
// recalculateLevel recalculates the level of the beacon's pyramid and returns it. The level can be 0-4.
func (b Beacon) recalculateLevel(pos cube.Pos, tx *world.Tx) int {
var lvl int
iter := 1
// This loop goes over all 4 possible pyramid levels.
for y := pos.Y() - 1; y >= pos.Y()-4; y-- {
for x := pos.X() - iter; x <= pos.X()+iter; x++ {
for z := pos.Z() - iter; z <= pos.Z()+iter; z++ {
if s, ok := tx.Block(cube.Pos{x, y, z}).(BeaconSource); !ok || !s.PowersBeacon() {
return lvl
}
}
}
iter++
lvl++
}
return lvl
}
// obstructed determines whether the beacon is currently obstructed.
func (b Beacon) obstructed(pos cube.Pos, tx *world.Tx) bool {
// Fast obstructed light calculation.
if tx.SkyLight(pos.Side(cube.FaceUp)) == 15 {
return false
}
// Slow obstructed light calculation, if the fast way out didn't suffice.
return tx.HighestLightBlocker(pos.X(), pos.Z()) > pos[1]
}
// broadcastBeaconEffects determines the entities in range which could receive the beacon's powers, and
// determines the powers (effects) that these entities could get. Afterwards, the entities in range that are
// beaconAffected get their according effect(s).
func (b Beacon) broadcastBeaconEffects(pos cube.Pos, tx *world.Tx) {
seconds := 9 + b.level*2
if b.level == 4 {
seconds--
}
dur := time.Duration(seconds) * time.Second
// Establishing what effects are active with the current amount of beacon levels.
primary, secondary := b.Primary, effect.LastingType(nil)
switch b.level {
case 0:
primary = nil
case 1:
switch primary {
case effect.Resistance, effect.JumpBoost, effect.Strength:
primary = nil
}
case 2:
if primary == effect.Strength {
primary = nil
}
case 3:
// Accept all effects for primary, but leave secondary as nil.
default:
secondary = b.Secondary
}
var primaryEff, secondaryEff effect.Effect
// Determining whether the primary power is set.
if primary != nil {
primaryEff = effect.NewAmbient(primary, 1, dur)
// Secondary power can only be set if the primary power is set.
if secondary != nil {
// It is possible to select 2 primary powers if the beacon's level is 4. This then means that the effect
// should get a level of 2.
if primary == secondary {
primaryEff = effect.NewAmbient(primary, 2, dur)
} else {
secondaryEff = effect.NewAmbient(secondary, 1, dur)
}
}
}
// Finding entities in range.
r := 10 + (b.level * 10)
entitiesInRange := tx.EntitiesWithin(cube.Box(
float64(pos.X()-r), -math.MaxFloat64, float64(pos.Z()-r),
float64(pos.X()+r), math.MaxFloat64, float64(pos.Z()+r),
))
for e := range entitiesInRange {
if p, ok := e.(beaconAffected); ok {
if primaryEff.Type() != nil {
p.AddEffect(primaryEff)
}
if secondaryEff.Type() != nil {
p.AddEffect(secondaryEff)
}
}
}
}
// beaconAffected represents an entity that can be powered by a beacon. Only players will implement this.
type beaconAffected interface {
// AddEffect adds a specific effect to the entity that implements this interface.
AddEffect(e effect.Effect)
// BeaconAffected returns whether this entity can be powered by a beacon.
BeaconAffected() bool
}
// EncodeItem ...
func (Beacon) EncodeItem() (name string, meta int16) {
return "minecraft:beacon", 0
}
// EncodeBlock ...
func (Beacon) EncodeBlock() (string, map[string]any) {
return "minecraft:beacon", nil
}