220 lines
6.3 KiB
Go
220 lines
6.3 KiB
Go
|
|
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
|
||
|
|
}
|