github.com/df-mc/dragonfly@v0.9.13/server/world/weather.go (about) 1 package world 2 3 import ( 4 "github.com/df-mc/dragonfly/server/block/cube" 5 "github.com/go-gl/mathgl/mgl64" 6 "time" 7 ) 8 9 // weather implements weather related methods for World. World embeds this struct, so any exported methods on weather 10 // are exported methods on World. 11 type weather struct{ w *World } 12 13 // StopWeatherCycle disables weather cycle of the World. 14 func (w weather) StopWeatherCycle() { 15 w.enableWeatherCycle(false) 16 } 17 18 // StartWeatherCycle enables weather cycle of the World. 19 func (w weather) StartWeatherCycle() { 20 w.enableWeatherCycle(true) 21 } 22 23 // SnowingAt checks if it is snowing at a specific cube.Pos in the World. True is returned if the temperature in the 24 // biome at that position is sufficiently low, if it is raining and if it's above the top-most obstructing block. 25 func (w weather) SnowingAt(pos cube.Pos) bool { 26 if w.w == nil || !w.w.Dimension().WeatherCycle() { 27 return false 28 } 29 if b := w.w.Biome(pos); b.Rainfall() == 0 || w.w.Temperature(pos) > 0.15 { 30 return false 31 } 32 w.w.set.Lock() 33 raining := w.w.set.Raining 34 w.w.set.Unlock() 35 return raining && w.w.highestObstructingBlock(pos[0], pos[2]) < pos[1] 36 } 37 38 // RainingAt checks if it is raining at a specific cube.Pos in the World. True is returned if it is raining, if the 39 // temperature is high enough in the biome for it not to be snow and if the block is above the top-most obstructing 40 // block. 41 func (w weather) RainingAt(pos cube.Pos) bool { 42 if w.w == nil || !w.w.Dimension().WeatherCycle() { 43 return false 44 } 45 if b := w.w.Biome(pos); b.Rainfall() == 0 || w.w.Temperature(pos) <= 0.15 { 46 return false 47 } 48 w.w.set.Lock() 49 a := w.w.set.Raining 50 w.w.set.Unlock() 51 return a && w.w.highestObstructingBlock(pos[0], pos[2]) < pos[1] 52 } 53 54 // ThunderingAt checks if it is thundering at a specific cube.Pos in the World. True is returned if RainingAt returns 55 // true and if it is thundering in the world. 56 func (w weather) ThunderingAt(pos cube.Pos) bool { 57 raining := w.RainingAt(pos) 58 w.w.set.Lock() 59 a := w.w.set.Thundering && raining 60 w.w.set.Unlock() 61 return a && w.w.highestObstructingBlock(pos[0], pos[2]) < pos[1] 62 } 63 64 // StartRaining makes it rain in the World. The time.Duration passed will determine how long it will rain. 65 func (w weather) StartRaining(dur time.Duration) { 66 w.w.set.Lock() 67 defer w.w.set.Unlock() 68 w.setRaining(true, dur) 69 } 70 71 // StopRaining makes it stop raining in the World. 72 func (w weather) StopRaining() { 73 w.w.set.Lock() 74 defer w.w.set.Unlock() 75 76 if w.w.set.Raining { 77 w.setRaining(false, time.Second*(time.Duration(w.w.r.Intn(8400)+600))) 78 if w.w.set.Thundering { 79 // Also reset thunder if it was previously thundering. 80 w.setThunder(false, time.Second*(time.Duration(w.w.r.Intn(8400)+600))) 81 } 82 } 83 } 84 85 // StartThundering makes it thunder in the World. The time.Duration passed will determine how long it will thunder. 86 // StartThundering will also make it rain if it wasn't already raining. In this case the rain will, like the thunder, 87 // last for the time.Duration passed. 88 func (w weather) StartThundering(dur time.Duration) { 89 w.w.set.Lock() 90 defer w.w.set.Unlock() 91 92 w.setThunder(true, dur) 93 w.setRaining(true, dur) 94 } 95 96 // StopThundering makes it stop thundering in the current world. 97 func (w weather) StopThundering() { 98 w.w.set.Lock() 99 defer w.w.set.Unlock() 100 if w.w.set.Thundering && w.w.set.Raining { 101 w.setThunder(false, time.Second*(time.Duration(w.w.r.Intn(8400)+600))) 102 } 103 } 104 105 // advanceWeather advances the weather counters of the World. Rain and thunder are stopped/started when the rain and 106 // thunder times reach 0. 107 func (w weather) advanceWeather() { 108 w.w.set.RainTime-- 109 w.w.set.ThunderTime-- 110 111 if w.w.set.RainTime <= 0 { 112 // Wiki: The rain counter counts down to zero, and each time it reaches zero, the rain is toggled on or off. 113 // When the rain is turned on, the counter is reset to a value between 12,000-23,999 ticks (0.5-1 game days) 114 // and when the rain is turned off it is reset to a value of 12,000-179,999 ticks (0.5-7.5 game days). 115 if w.w.set.Raining { 116 w.w.setRaining(false, time.Second*(time.Duration(w.w.r.Intn(8400)+600))) 117 } else { 118 w.w.setRaining(true, time.Second*time.Duration(w.w.r.Intn(600)+600)) 119 } 120 } 121 if w.w.set.ThunderTime <= 0 { 122 // Wiki: the thunder counter toggles thunder on/off when it reaches zero, but clear weather overrides the 123 // "on" state. When thunder is turned on, the thunder counter is reset to 3,600-15,999 ticks (3-13 minutes), 124 // and when thunder is turned off the counter rests to 12,000-179,999 ticks (0.5-7.5 days). 125 if w.w.set.Thundering { 126 w.w.setThunder(false, time.Second*(time.Duration(w.w.r.Intn(8400)+600))) 127 } else { 128 w.w.setThunder(true, time.Second*time.Duration(w.w.r.Intn(620)+180)) 129 } 130 } 131 } 132 133 // setRaining toggles raining depending on the raining argument. 134 // This does not lock the world mutex as opposed to StartRaining and StopRaining. 135 func (w weather) setRaining(raining bool, x time.Duration) { 136 w.w.set.Raining = raining 137 w.w.set.RainTime = int64(x.Seconds() * 20) 138 } 139 140 // setThunder toggles thundering depending on the thundering argument. 141 // This does not lock the world mutex as opposed to StartThundering and StopThundering. 142 func (w weather) setThunder(thundering bool, x time.Duration) { 143 w.w.set.Thundering = thundering 144 w.w.set.ThunderTime = int64(x.Seconds() * 20) 145 } 146 147 // enableWeatherCycle either enables or disables the weather cycle of the World. 148 func (w weather) enableWeatherCycle(v bool) { 149 if w.w == nil { 150 return 151 } 152 w.w.set.Lock() 153 defer w.w.set.Unlock() 154 w.w.set.WeatherCycle = v 155 } 156 157 // tickLightning iterates over all loaded chunks in the World, striking lightning in each one with a 1/100,000 chance. 158 func (w weather) tickLightning() { 159 w.w.chunkMu.Lock() 160 positions := make([]ChunkPos, 0, len(w.w.chunks)/100000) 161 for pos := range w.w.chunks { 162 // Wiki: For each loaded chunk, every tick there is a 1⁄100,000 chance of an attempted lightning strike 163 // during a thunderstorm 164 if w.w.r.Intn(100000) == 0 { 165 positions = append(positions, pos) 166 } 167 } 168 w.w.chunkMu.Unlock() 169 170 for _, pos := range positions { 171 w.w.strikeLightning(pos) 172 } 173 } 174 175 // strikeLightning attempts to strike lightning in the world at a specific ChunkPos. The final position is influenced by 176 // living entities that might be near the lightning strike. If there is no rain at the final position selected, the 177 // lightning strike will fail. 178 func (w weather) strikeLightning(c ChunkPos) { 179 if pos := w.lightningPosition(c); w.ThunderingAt(cube.PosFromVec3(pos)) { 180 w.w.AddEntity(w.w.conf.Entities.conf.Lightning(pos)) 181 } 182 } 183 184 // lightningPosition finds a random position in the ChunkPos to strike lightning and adjusts the position to any of the 185 // living entities found in or above the position if any are found. 186 func (w weather) lightningPosition(c ChunkPos) mgl64.Vec3 { 187 v := w.w.r.Int31() 188 x, z := float64(c[0]<<4+(v&0xf)), float64(c[1]<<4+((v>>8)&0xf)) 189 190 vec := w.adjustPositionToEntities(mgl64.Vec3{x, float64(w.w.HighestBlock(int(x), int(z)) + 1), z}) 191 if pos := cube.PosFromVec3(vec); len(w.w.Block(pos).Model().BBox(pos, w.w)) != 0 { 192 // If lightning is about to strike inside a block that is not fully transparent. In this case, move the 193 // lightning up by one block so that it strikes above the block. 194 return vec.Add(mgl64.Vec3{0, 1}) 195 } 196 return vec 197 } 198 199 // adjustPositionToEntities adjusts the mgl64.Vec3 passed to the position of any entity found in the 3x3 column upwards 200 // from the mgl64.Vec3. If multiple entities are found, the position of one of the entities is selected randomly. 201 func (w weather) adjustPositionToEntities(vec mgl64.Vec3) mgl64.Vec3 { 202 max := vec.Add(mgl64.Vec3{0, float64(w.w.Range().Max())}) 203 ent := w.w.EntitiesWithin(cube.Box(vec[0], vec[1], vec[2], max[0], max[1], max[2]).GrowVec3(mgl64.Vec3{3, 3, 3}), nil) 204 205 list := make([]mgl64.Vec3, 0, len(ent)/3) 206 for _, e := range ent { 207 if h, ok := e.(interface{ Health() float64 }); ok && h.Health() > 0 { 208 // Any (living) entity that is positioned higher than the highest block at its position is eligible to be 209 // struck by lightning. We first save all entity positions where this is the case. 210 pos := cube.PosFromVec3(e.Position()) 211 if w.w.HighestBlock(pos[0], pos[1]) < pos[2] { 212 list = append(list, e.Position()) 213 } 214 } 215 } 216 // We then select one of the positions of entities higher than the highest block and adjust the position of the 217 // lightning to it, so that the entity is struck directly. 218 if len(list) > 0 { 219 vec = list[w.w.r.Intn(len(list))] 220 } 221 return vec 222 }