github.com/df-mc/dragonfly@v0.9.13/server/block/beacon.go (about)

     1  package block
     2  
     3  import (
     4  	"github.com/df-mc/dragonfly/server/block/cube"
     5  	"github.com/df-mc/dragonfly/server/entity/effect"
     6  	"github.com/df-mc/dragonfly/server/internal/nbtconv"
     7  	"github.com/df-mc/dragonfly/server/item"
     8  	"github.com/df-mc/dragonfly/server/world"
     9  	"math"
    10  	"time"
    11  )
    12  
    13  // Beacon is a block that projects a light beam skyward, and can provide status effects such as Speed, Jump
    14  // Boost, Haste, Regeneration, Resistance, or Strength to nearby players.
    15  type Beacon struct {
    16  	solid
    17  	transparent
    18  	clicksAndSticks
    19  	sourceWaterDisplacer
    20  
    21  	// Primary and Secondary are the primary and secondary effects broadcast to nearby entities by the
    22  	// beacon.
    23  	Primary, Secondary effect.LastingType
    24  	// level is the amount of the pyramid's levels, it is defined by the mineral blocks which build up the
    25  	// pyramid, and can be 0-4.
    26  	level int
    27  }
    28  
    29  // BeaconSource represents a block which is capable of contributing to powering a beacon pyramid.
    30  type BeaconSource interface {
    31  	// PowersBeacon returns a bool which indicates whether this block can contribute to powering up a
    32  	// beacon pyramid.
    33  	PowersBeacon() bool
    34  }
    35  
    36  // BreakInfo ...
    37  func (b Beacon) BreakInfo() BreakInfo {
    38  	return newBreakInfo(3, alwaysHarvestable, nothingEffective, oneOf(b))
    39  }
    40  
    41  // Activate manages the opening of a beacon by activating it.
    42  func (b Beacon) Activate(pos cube.Pos, _ cube.Face, _ *world.World, u item.User, _ *item.UseContext) bool {
    43  	if opener, ok := u.(ContainerOpener); ok {
    44  		opener.OpenBlockContainer(pos)
    45  		return true
    46  	}
    47  	return true
    48  }
    49  
    50  // DecodeNBT ...
    51  func (b Beacon) DecodeNBT(data map[string]any) any {
    52  	b.level = int(nbtconv.Int32(data, "Levels"))
    53  	if primary, ok := effect.ByID(int(nbtconv.Int32(data, "Primary"))); ok {
    54  		b.Primary = primary.(effect.LastingType)
    55  	}
    56  	if secondary, ok := effect.ByID(int(nbtconv.Int32(data, "Secondary"))); ok {
    57  		b.Secondary = secondary.(effect.LastingType)
    58  	}
    59  	return b
    60  }
    61  
    62  // EncodeNBT ...
    63  func (b Beacon) EncodeNBT() map[string]any {
    64  	m := map[string]any{
    65  		"id":     "Beacon",
    66  		"Levels": int32(b.level),
    67  	}
    68  	if primary, ok := effect.ID(b.Primary); ok {
    69  		m["Primary"] = int32(primary)
    70  	}
    71  	if secondary, ok := effect.ID(b.Secondary); ok {
    72  		m["Secondary"] = int32(secondary)
    73  	}
    74  	return m
    75  }
    76  
    77  // SideClosed ...
    78  func (b Beacon) SideClosed(cube.Pos, cube.Pos, *world.World) bool {
    79  	return false
    80  }
    81  
    82  // LightEmissionLevel ...
    83  func (Beacon) LightEmissionLevel() uint8 {
    84  	return 15
    85  }
    86  
    87  // Level returns an integer 0-4 which defines the current pyramid level of the beacon.
    88  func (b Beacon) Level() int {
    89  	return b.level
    90  }
    91  
    92  // Tick recalculates level, recalculates the active state of the beacon, and powers players,
    93  // once every 80 ticks (4 seconds).
    94  func (b Beacon) Tick(currentTick int64, pos cube.Pos, w *world.World) {
    95  	if currentTick%80 == 0 {
    96  		before := b.level
    97  		// Recalculating pyramid level and powering up players in range once every 4 seconds.
    98  		b.level = b.recalculateLevel(pos, w)
    99  		if before != b.level {
   100  			w.SetBlock(pos, b, nil)
   101  		}
   102  		if b.level == 0 {
   103  			return
   104  		}
   105  		if !b.obstructed(pos, w) {
   106  			b.broadcastBeaconEffects(pos, w)
   107  		}
   108  	}
   109  }
   110  
   111  // recalculateLevel recalculates the level of the beacon's pyramid and returns it. The level can be 0-4.
   112  func (b Beacon) recalculateLevel(pos cube.Pos, w *world.World) int {
   113  	var lvl int
   114  	iter := 1
   115  	// This loop goes over all 4 possible pyramid levels.
   116  	for y := pos.Y() - 1; y >= pos.Y()-4; y-- {
   117  		for x := pos.X() - iter; x <= pos.X()+iter; x++ {
   118  			for z := pos.Z() - iter; z <= pos.Z()+iter; z++ {
   119  				if s, ok := w.Block(cube.Pos{x, y, z}).(BeaconSource); !ok || !s.PowersBeacon() {
   120  					return lvl
   121  				}
   122  			}
   123  		}
   124  		iter++
   125  		lvl++
   126  	}
   127  	return lvl
   128  }
   129  
   130  // obstructed determines whether the beacon is currently obstructed.
   131  func (b Beacon) obstructed(pos cube.Pos, w *world.World) bool {
   132  	// Fast obstructed light calculation.
   133  	if w.SkyLight(pos.Side(cube.FaceUp)) == 15 {
   134  		return false
   135  	}
   136  	// Slow obstructed light calculation, if the fast way out didn't suffice.
   137  	return w.HighestLightBlocker(pos.X(), pos.Z()) > pos[1]
   138  }
   139  
   140  // broadcastBeaconEffects determines the entities in range which could receive the beacon's powers, and
   141  // determines the powers (effects) that these entities could get. Afterwards, the entities in range that are
   142  // beaconAffected get their according effect(s).
   143  func (b Beacon) broadcastBeaconEffects(pos cube.Pos, w *world.World) {
   144  	seconds := 9 + b.level*2
   145  	if b.level == 4 {
   146  		seconds--
   147  	}
   148  	dur := time.Duration(seconds) * time.Second
   149  
   150  	// Establishing what effects are active with the current amount of beacon levels.
   151  	primary, secondary := b.Primary, effect.LastingType(nil)
   152  	switch b.level {
   153  	case 0:
   154  		primary = nil
   155  	case 1:
   156  		switch primary.(type) {
   157  		case effect.Resistance, effect.JumpBoost, effect.Strength:
   158  			primary = nil
   159  		}
   160  	case 2:
   161  		if _, ok := primary.(effect.Strength); ok {
   162  			primary = nil
   163  		}
   164  	case 3:
   165  		// Accept all effects for primary, but leave secondary as nil.
   166  	default:
   167  		secondary = b.Secondary
   168  	}
   169  	var primaryEff, secondaryEff effect.Effect
   170  	// Determining whether the primary power is set.
   171  	if primary != nil {
   172  		primaryEff = effect.NewAmbient(primary, 1, dur)
   173  		// Secondary power can only be set if the primary power is set.
   174  		if secondary != nil {
   175  			// It is possible to select 2 primary powers if the beacon's level is 4. This then means that the effect
   176  			// should get a level of 2.
   177  			if primary == secondary {
   178  				primaryEff = effect.NewAmbient(primary, 2, dur)
   179  			} else {
   180  				secondaryEff = effect.NewAmbient(secondary, 1, dur)
   181  			}
   182  		}
   183  	}
   184  
   185  	// Finding entities in range.
   186  	r := 10 + (b.level * 10)
   187  	entitiesInRange := w.EntitiesWithin(cube.Box(
   188  		float64(pos.X()-r), -math.MaxFloat64, float64(pos.Z()-r),
   189  		float64(pos.X()+r), math.MaxFloat64, float64(pos.Z()+r),
   190  	), nil)
   191  	for _, e := range entitiesInRange {
   192  		if p, ok := e.(beaconAffected); ok {
   193  			if primaryEff.Type() != nil {
   194  				p.AddEffect(primaryEff)
   195  			}
   196  			if secondaryEff.Type() != nil {
   197  				p.AddEffect(secondaryEff)
   198  			}
   199  		}
   200  	}
   201  }
   202  
   203  // beaconAffected represents an entity that can be powered by a beacon. Only players will implement this.
   204  type beaconAffected interface {
   205  	// AddEffect adds a specific effect to the entity that implements this interface.
   206  	AddEffect(e effect.Effect)
   207  	// BeaconAffected returns whether this entity can be powered by a beacon.
   208  	BeaconAffected() bool
   209  }
   210  
   211  // EncodeItem ...
   212  func (Beacon) EncodeItem() (name string, meta int16) {
   213  	return "minecraft:beacon", 0
   214  }
   215  
   216  // EncodeBlock ...
   217  func (Beacon) EncodeBlock() (string, map[string]any) {
   218  	return "minecraft:beacon", nil
   219  }