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  }