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 }