private void OnClientGameTick(float dt)
        {
            quarterSecAccum += dt;
            if (quarterSecAccum > 0.25f)
            {
                clientClimateCond = capi.World.BlockAccessor.GetClimateAt(plrPos);
                quarterSecAccum   = 0;
            }

            simLightning.ClientTick(dt);

            for (int i = 0; i < 4; i++)
            {
                WeatherSimulationRegion sim = adjacentSims[i];
                if (sim == dummySim)
                {
                    continue;
                }
                sim.TickEvery25ms(dt);
            }

            simSounds.Update(dt);
        }
        private void onThreadStart()
        {
            while (!isShuttingDown)
            {
                Thread.Sleep(5);

                if (shouldPauseThread)
                {
                    isThreadPaused = true;
                    continue;
                }
                isThreadPaused = false;

                int i = 0;

                while (chunkColsstoCheckQueue.Count > 0 && i++ < 10)
                {
                    Vec2i chunkCoord;
                    lock (chunkColsstoCheckQueue)
                    {
                        chunkCoord = chunkColsstoCheckQueue.Dequeue();
                    }

                    int regionX = (chunkCoord.X * chunksize) / regionsize;
                    int regionZ = (chunkCoord.Y * chunksize) / regionsize;

                    WeatherSimulationRegion sim = ws.getOrCreateWeatherSimForRegion(regionX, regionZ);

                    IServerMapChunk mc = sapi.WorldManager.GetMapChunk(chunkCoord.X, chunkCoord.Y);

                    if (mc != null && sim != null)
                    {
                        UpdateSnowLayerOffThread(sim, mc, chunkCoord);
                    }
                }
            }
        }
        private string getWeatherInfo <T>(IPlayer player) where T : WeatherSystemBase
        {
            T wsys = api.ModLoader.GetModSystem <T>();

            Vec3d    plrPos = player.Entity.SidedPos.XYZ;
            BlockPos pos    = plrPos.AsBlockPos;

            var wreader = wsys.getWeatherDataReaderPreLoad();

            wreader.LoadAdjacentSimsAndLerpValues(plrPos, 1);

            int regionX = (int)pos.X / api.World.BlockAccessor.RegionSize;
            int regionZ = (int)pos.Z / api.World.BlockAccessor.RegionSize;

            WeatherSimulationRegion weatherSim;
            long index2d = wsys.MapRegionIndex2D(regionX, regionZ);

            wsys.weatherSimByMapRegion.TryGetValue(index2d, out weatherSim);
            if (weatherSim == null)
            {
                return("weatherSim is null. No idea what to do here");
            }

            StringBuilder sb = new StringBuilder();

            sb.AppendLine(string.Format("Weather by region:")); // (lerp-lr: {0}, lerp-bt: {1}), wsys.lerpLeftRight.ToString("0.##"), wsys.lerpTopBot.ToString("0.##")));
            string[] cornerNames = new string[] { "tl", "tr", "bl", "br" };

            //topBlendedWeatherData.SetLerped(adjacentSims[0].weatherData, adjacentSims[1].weatherData, (float)lerpLeftRight);
            //botBlendedWeatherData.SetLerped(adjacentSims[2].weatherData, adjacentSims[3].weatherData, (float)lerpLeftRight);
            //blendedWeatherData.SetLerped(topBlendedWeatherData, botBlendedWeatherData, (float)lerpTopBot);

            double tlLerp = GameMath.BiLerp(1, 0, 0, 0, wreader.LerpLeftRight, wreader.LerpTopBot);
            double trLerp = GameMath.BiLerp(0, 1, 0, 0, wreader.LerpLeftRight, wreader.LerpTopBot);
            double blLerp = GameMath.BiLerp(0, 0, 1, 0, wreader.LerpLeftRight, wreader.LerpTopBot);
            double brLerp = GameMath.BiLerp(0, 0, 0, 1, wreader.LerpLeftRight, wreader.LerpTopBot);

            int[] lerps = new int[] { (int)(100 * tlLerp), (int)(100 * trLerp), (int)(100 * blLerp), (int)(100 * brLerp) };

            for (int i = 0; i < 4; i++)
            {
                WeatherSimulationRegion sim = wreader.AdjacentSims[i];

                if (sim == wsys.dummySim)
                {
                    sb.AppendLine(string.Format("{0}: missing", cornerNames[i]));
                }
                else
                {
                    /*sb.AppendLine(string.Format("{10}% of {0}@{8}/{9}: {1}% {2}, {3}% {4}. Prec: {5}, Wind: {6} (v={7})",
                     *  cornerNames[i], (int)(100 * sim.Weight), sim.NewWePattern.GetWeatherName(), (int)(100 - 100 * sim.Weight),
                     *  sim.OldWePattern.GetWeatherName(), sim.weatherData.PrecIntensity.ToString("0.###"),
                     *  sim.CurWindPattern.GetWindName(), sim.GetWindSpeed(pos.Y).ToString("0.###"),
                     *  sim.regionX, sim.regionZ,
                     *  lerps[i]
                     * )) ;*/

                    sb.AppendLine(string.Format("{9}% of {0}@{7}/{8}: {1}% {2}, {3}% {4}. Wind: {5}, Event: {10} (v={6})",
                                                cornerNames[i], (int)(100 * sim.Weight), sim.NewWePattern.GetWeatherName(), (int)(100 - 100 * sim.Weight),
                                                sim.OldWePattern.GetWeatherName(),
                                                sim.CurWindPattern.GetWindName(), sim.GetWindSpeed(pos.Y).ToString("0.###"),
                                                sim.regionX, sim.regionZ,
                                                lerps[i],
                                                sim.CurWeatherEvent.config.Code
                                                ));
                }
            }

            //wsys.updateAdjacentAndBlendWeatherData();
            //WeatherDataSnapshot wData = wsys.blendedWeatherData;
            //sb.AppendLine(string.Format(string.Format("Blended:\nPrecipitation: {0}, Particle size: {1}, Type: {2}, Wind speed: {3}", wData.PrecIntensity, wData.PrecParticleSize, wData.BlendedPrecType, wsys.GetWindSpeed(plrPos))));
            ClimateCondition climate = api.World.BlockAccessor.GetClimateAt(player.Entity.Pos.AsBlockPos, EnumGetClimateMode.NowValues);

            sb.AppendLine(string.Format("Current precipitation: {0}%", (int)(climate.Rainfall * 100f)));
            sb.AppendLine(string.Format("Current wind: {0}", API.Config.GlobalConstants.CurrentWindSpeedClient));

            return(sb.ToString());
        }
        private UpdateSnowLayerChunk GetSnowUpdate(WeatherSimulationRegion simregion, IServerMapChunk mc, Vec2i chunkPos, IWorldChunk[] chunksCol)
        {
            double lastSnowAccumUpdateTotalHours = mc.GetModdata <double>("lastSnowAccumUpdateTotalHours");
            double startTotalHours = lastSnowAccumUpdateTotalHours;

            int reso = WeatherSimulationRegion.snowAccumResolution;

            SnowAccumSnapshot sumsnapshot = new SnowAccumSnapshot()
            {
                SnowAccumulationByRegionCorner = new FloatDataMap3D(reso, reso, reso)
            };

            float[] sumdata = sumsnapshot.SnowAccumulationByRegionCorner.Data;

            // Can't grow bigger than one full snow block
            float max = ws.GeneralConfig.SnowLayerBlocks.Count + 0.6f;

            int len      = simregion.SnowAccumSnapshots.Length;
            int i        = simregion.SnowAccumSnapshots.Start;
            int newCount = 0;

            lock (WeatherSimulationRegion.snowAccumSnapshotLock)
            {
                while (len-- > 0)
                {
                    SnowAccumSnapshot hoursnapshot = simregion.SnowAccumSnapshots[i];
                    i = (i + 1) % simregion.SnowAccumSnapshots.Length;

                    if (hoursnapshot == null || lastSnowAccumUpdateTotalHours >= hoursnapshot.TotalHours)
                    {
                        continue;
                    }

                    float[] snowaccumdata = hoursnapshot.SnowAccumulationByRegionCorner.Data;
                    for (int j = 0; j < snowaccumdata.Length; j++)
                    {
                        sumdata[j] = GameMath.Clamp(sumdata[j] + snowaccumdata[j], -max, max);
                    }

                    lastSnowAccumUpdateTotalHours = Math.Max(lastSnowAccumUpdateTotalHours, hoursnapshot.TotalHours);
                    newCount++;
                }
            }

            if (newCount == 0)
            {
                return(null);
            }

            bool ignoreOldAccum = false;

            if (lastSnowAccumUpdateTotalHours - startTotalHours >= sapi.World.Calendar.DaysPerYear * sapi.World.Calendar.HoursPerDay)
            {
                ignoreOldAccum = true;
            }

            UpdateSnowLayerChunk ch = UpdateSnowLayer(sumsnapshot, ignoreOldAccum, mc, chunkPos, chunksCol);

            if (ch != null)
            {
                ch.LastSnowAccumUpdateTotalHours = lastSnowAccumUpdateTotalHours;
                ch.Coords = chunkPos.Copy();
            }
            return(ch);
        }
        public void UpdateSnowLayerOffThread(WeatherSimulationRegion simregion, IServerMapChunk mc, Vec2i chunkPos)
        {
            #region Tyrons brain cloud
            // Trick 1: Each x/z coordinate gets a "snow accum" threshold by using a locational random (murmurhash3). Once that threshold is reached, spawn snow. If its doubled, spawn 2nd layer of snow. => Patchy "fade in" of snow \o/
            // Trick 2: We store a region wide snow accum value for the ground level and the map ceiling level. We can now interpolate between those values for each Y-Coordinate \o/
            // Trick 3: We loop through each x/z block in a separate thread, then hand over "place snow" tasks to the main thread
            // Trick 4: Lets pre-gen 50 random shuffles for every x/z coordinate of a chunk. Loop through the region chunks, check which one is loaded and select one random shuffle from the list, then iterate over every x/z coord

            // Trick 5: Snowed over blocks:
            // - New VSMC util: "Automatically Try to add a snow cover to all horizontal faces"
            // - New Block property: SnowCoverableShape.
            // - Block.OnJsonTesselation adds snow adds cover shape to the sourceMesh!!


            // Trick 6: Turn Cloud Patterns into a "dumb slave system". They are visual information only, so lets make them follow internal mechanisms.
            // - Create a precipitation perlin noise generator. If the precipitation value goes above or below a certain value, we force the cloud pattern system to adapt to a fitting pattern
            // => We gain easy to probe, deterministic precipitation values!!
            // => We gain the ability to do unloaded chunk snow accumulation and unloaded chunk farmland rain-wetness accum

            // Trick 6 v2.0:
            // Rain clouds are simply overlaid onto the normal clouds.


            // Questions:
            // - Q1: When should it hail now?
            // - Q2: How is particle size determined?
            // - Q3: When should there be thunder?
            // - Q4: How to control the precipitation by command?

            // A1/A3: What if we read the slope of precipitation change. If there is a drastic increase of rain fall launch a
            // a. wind + thunder event
            // b. thunder event
            // c. rarely a hail event
            // d. extra rarely thunder + hail event

            // A2: Particle size is determiend by precipitation intensity


            // Trick 7 v2.0
            // - Hail and Thunder are also triggered by a perlin noise generator. That way I don't need to care about event range.

            // A4: /weather setprecip [auto or 0..1]

            // - Q5: How do we overlay rain clouds onto the normal clouds?
            //         Q5a: Will they be hardcoded? Or configurable?
            //         Q5b: How does the overlay work? Lerp?
            //         Q5c: Rain cloud intensity should relate to precip level.
            //         How? Lerp from zero to max rain clouds? Multiple cloud configs and lerp between them?

            // - A5a: Configurable
            //   A5b: Lerp.
            //   A5c: Single max rain cloud config seems sufficient


            // TODO:
            // 1. Rain cloud overlay
            // 2. Snow accum
            // 3. Hail, Thunder perlin noise
            // 4. Done?


            // Idea 8:
            // - F**K the region based weather sim.
            // - Generate clouds patterns like you generate terrain from landforms
            // - Which is grid based indices, neatly abstracted with LerpedIndex2DMap and nicely shaped with domain warping
            // - Give it enough padding to ensure domain warping does not go out of bounds
            // - Every 2-3 minutes regenerate this map in a seperate thread, cloud renderer lerps between old and new map.
            // - Since the basic indices input is grid based, we can cycle those individually through time



            // for a future version
            // Hm. Maybe one noise generator for cloud coverage?
            // => Gain the ability to affect local temperature based on cloud coverage

            // Hm. Or maybe one noise generator for each cloud pattern?
            // => Gain the abillity for small scale and very large scale cloud patterns

            // Maybe even completely ditch per-region simulation?
            // => Gain the ability for migrating weather patterns

            // but then what will determine the cloud pattern?

            // Region-less Concept:
            // Take an LCGRandom. Use xpos and zpos+((int)totalDays) / 5 for coords
            // Iterate over every player
            //  - iterate over a 20x20 chunk area around it (or max view dist + 5 chunks)
            //    - domain warp x/z coords. use those coords to init position seed on lcgrand. get random value
            //    - store in an LerpedWeightedIndex2DMap
            // Iterate over every cloud tile
            //  - read cloud pattern data from the map



            // Snow accum needs to take the existing world information into account, i.e. current snow level
            // We should probably
            // - Store snow accumulation as a float value in mapchunkdata as Dictionary<BlockPos, float>
            // - Every 3 seconds or so, "commit" that snow accum into actual snow layer blocks, i.e. if accum >= 1 then add one snow layer and do accum-=1



            #endregion

            UpdateSnowLayerChunk ch = new UpdateSnowLayerChunk()
            {
                Coords = chunkPos
            };

            // Lets wait until we're done with the current job for this chunk
            lock (updateSnowLayerQueue)
            {
                if (updateSnowLayerQueue.Contains(ch))
                {
                    return;
                }
            }

            if (simregion == null)
            {
                return;
            }

            double nowTotalHours = ws.api.World.Calendar.TotalHours;
            if (nowTotalHours - simregion.LastUpdateTotalHours > 1) // Lets wait until WeatherSimulationRegion is done updating
            {
                return;
            }

            ch = GetSnowUpdate(simregion, mc, chunkPos, null);

            if (ch != null)
            {
                lock (updateSnowLayerQueue)
                {
                    updateSnowLayerQueue.Enqueue(ch);
                }
            }
        }