public void TickEveryInGameHourServer(double nowTotalHours) { SnowAccumSnapshot latestSnap = new SnowAccumSnapshot() { TotalHours = nowTotalHours, // SumTemperatureByRegionCorner = new API.FloatDataMap3D(snowAccumResolution, snowAccumResolution, snowAccumResolution), SnowAccumulationByRegionCorner = new API.FloatDataMap3D(snowAccumResolution, snowAccumResolution, snowAccumResolution) }; // Idea: We don't want to simulate 512x512 blocks at all times, thats a lot of iterations // lets try with just the 8 corner points of the region cuboid and lerp BlockPos tmpPos = new BlockPos(); int regsize = ws.api.World.BlockAccessor.RegionSize; for (int ix = 0; ix < snowAccumResolution; ix++) { for (int iy = 0; iy < snowAccumResolution; iy++) { for (int iz = 0; iz < snowAccumResolution; iz++) { int y = iy == 0 ? ws.api.World.SeaLevel : ws.api.World.BlockAccessor.MapSizeY - 1; tmpPos.Set( regionX * regsize + ix * (regsize - 1), y, regionZ * regsize + iz * (regsize - 1) ); ClimateCondition nowcond = ws.api.World.BlockAccessor.GetClimateAt(tmpPos, EnumGetClimateMode.ForSuppliedDateValues, nowTotalHours + 0.5); // Sample from the middle of the hour if (nowcond == null) { return; } //latestSnap.SumTemperatureByRegionCorner.AddValue(ix, iy, iz, nowcond.Temperature); if (nowcond.Temperature > 0) { latestSnap.SnowAccumulationByRegionCorner.AddValue(ix, iy, iz, -nowcond.Temperature / 5f); } else { latestSnap.SnowAccumulationByRegionCorner.AddValue(ix, iy, iz, nowcond.Rainfall / 3f); } } } } lock (lockTest) { SnowAccumSnapshots.Add(latestSnap); } latestSnap.Checks++; }
private void cmdSnowAccum(IServerPlayer player, int groupId, CmdArgs args) { WeatherSystemServer wsys = api.ModLoader.GetModSystem <WeatherSystemServer>(); string cmd = args.PopWord(); if (cmd == "on") { wsys.snowSimSnowAccu.ProcessChunks = true; player.SendMessage(groupId, "Snow accum process chunks on", EnumChatType.CommandSuccess); return; } if (cmd == "off") { wsys.snowSimSnowAccu.ProcessChunks = false; player.SendMessage(groupId, "Snow accum process chunks off", EnumChatType.CommandSuccess); return; } if (cmd == "processhere") { BlockPos plrPos = player.Entity.Pos.AsBlockPos; int chunksize = api.World.BlockAccessor.ChunkSize; Vec2i chunkPos = new Vec2i(plrPos.X / chunksize, plrPos.Z / chunksize); wsys.snowSimSnowAccu.AddToCheckQueue(chunkPos); player.SendMessage(groupId, "Ok, added to check queue", EnumChatType.CommandSuccess); return; } if (cmd == "info") { BlockPos plrPos = player.Entity.Pos.AsBlockPos; int chunksize = api.World.BlockAccessor.ChunkSize; Vec2i chunkPos = new Vec2i(plrPos.X / chunksize, plrPos.Z / chunksize); IServerMapChunk mc = sapi.WorldManager.GetMapChunk(chunkPos.X, chunkPos.Y); double lastSnowAccumUpdateTotalHours = mc.GetModdata <double>("lastSnowAccumUpdateTotalHours"); player.SendMessage(groupId, "lastSnowAccumUpdateTotalHours: " + lastSnowAccumUpdateTotalHours, EnumChatType.CommandSuccess); int regionX = (int)player.Entity.Pos.X / sapi.World.BlockAccessor.RegionSize; int regionZ = (int)player.Entity.Pos.Z / sapi.World.BlockAccessor.RegionSize; WeatherSystemServer wsysServer = sapi.ModLoader.GetModSystem <WeatherSystemServer>(); long index2d = wsysServer.MapRegionIndex2D(regionX, regionZ); WeatherSimulationRegion simregion; wsysServer.weatherSimByMapRegion.TryGetValue(index2d, out simregion); int reso = WeatherSimulationRegion.snowAccumResolution; SnowAccumSnapshot sumsnapshot = new SnowAccumSnapshot() { //SumTemperatureByRegionCorner = new API.FloatDataMap3D(reso, reso, reso), SnowAccumulationByRegionCorner = new FloatDataMap3D(reso, reso, reso) }; float[] sumdata = sumsnapshot.SnowAccumulationByRegionCorner.Data; // Can't grow bigger than one full snow block float max = 3 + 0.5f; int len = simregion.SnowAccumSnapshots.Length; int i = simregion.SnowAccumSnapshots.Start; // This code here causes wacky snow patterns // The lerp itself is fine!!! while (len-- > 0) { SnowAccumSnapshot hoursnapshot = simregion.SnowAccumSnapshots[i]; i = (i + 1) % simregion.SnowAccumSnapshots.Length; 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); } for (int j = 0; j < sumdata.Length; j++) { player.SendMessage(groupId, j + ": " + sumdata[j], EnumChatType.CommandSuccess); } return; } if (cmd == "here") { float amount = (float)args.PopFloat(0); BlockPos plrPos = player.Entity.Pos.AsBlockPos; int chunksize = api.World.BlockAccessor.ChunkSize; Vec2i chunkPos = new Vec2i(plrPos.X / chunksize, plrPos.Z / chunksize); IServerMapChunk mc = sapi.WorldManager.GetMapChunk(chunkPos.X, chunkPos.Y); int reso = WeatherSimulationRegion.snowAccumResolution; SnowAccumSnapshot sumsnapshot = new SnowAccumSnapshot() { SumTemperatureByRegionCorner = new FloatDataMap3D(reso, reso, reso), SnowAccumulationByRegionCorner = new FloatDataMap3D(reso, reso, reso) }; sumsnapshot.SnowAccumulationByRegionCorner.Data.Fill(amount); var updatepacket = wsys.snowSimSnowAccu.UpdateSnowLayer(sumsnapshot, true, mc, chunkPos, null); wsys.snowSimSnowAccu.accum = 1f; var ba = sapi.World.GetBlockAccessorBulkMinimalUpdate(true, false); ba.UpdateSnowAccumMap = false; wsys.snowSimSnowAccu.processBlockUpdates(mc, updatepacket, ba); ba.Commit(); player.SendMessage(groupId, "Ok, test snow accum gen complete", EnumChatType.CommandSuccess); return; } }
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 UpdateSnowLayerChunk UpdateSnowLayer(SnowAccumSnapshot sumsnapshot, bool ignoreOldAccum, IServerMapChunk mc, Vec2i chunkPos, IWorldChunk[] chunksCol) { UpdateSnowLayerChunk updateChunk = new UpdateSnowLayerChunk(); var layers = ws.GeneralConfig.SnowLayerBlocks; int chunkX = chunkPos.X; int chunkZ = chunkPos.Y; int regionX = (chunkX * chunksize) / regionsize; int regionZ = (chunkZ * chunksize) / regionsize; int regionBasePosX = regionX * regionsize; int regionBasePosZ = regionZ * regionsize; BlockPos pos = new BlockPos(); BlockPos placePos = new BlockPos(); float aboveSeaLevelHeight = sapi.World.BlockAccessor.MapSizeY - sapi.World.SeaLevel; int[] posIndices = randomShuffles[sapi.World.Rand.Next(randomShuffles.Length)]; int prevChunkY = -99999; IWorldChunk chunk = null; int maxY = sapi.World.BlockAccessor.MapSizeY - 1; for (int i = 0; i < posIndices.Length; i++) { int posIndex = posIndices[i]; int posY = GameMath.Clamp(mc.RainHeightMap[posIndex], 0, maxY); int chunkY = posY / chunksize; pos.Set( chunkX * chunksize + posIndex % chunksize, posY, chunkZ * chunksize + posIndex / chunksize ); if (prevChunkY != chunkY) { chunk = chunksCol?[chunkY] ?? sapi.WorldManager.GetChunk(chunkX, chunkY, chunkZ); prevChunkY = chunkY; } if (chunk == null) { return(null); } float relx = (pos.X - regionBasePosX) / (float)regionsize; float rely = GameMath.Clamp((pos.Y - sapi.World.SeaLevel) / aboveSeaLevelHeight, 0, 1); float relz = (pos.Z - regionBasePosZ) / (float)regionsize; // What needs to be done here? // 1. Get desired snow cover level // 2. Get current snow cover level // - Get topmmost block. Is it snow? // - Yes. Use it as reference pos and stuff // - No. Must have no snow, increment pos.Y by 1 // 3. Compare and place block accordingly // Idea: New method Block.UpdateSnowLayer() returns a new block instance if a block change is needed // What needs to be done here, take 2 // We have 3 possible cases per-block // 1: We find upside solid block. That means it has no snow on top // 2: We find snow. That means below is a solid block. // 3: We find some other block: That means we should try to find its snow-covered variant // We have the following input data // 1. Snow accumulation changes since the last update (usually an in-game hour or 2) // 2. A precise snow level value from the position (if not set, load from snowlayer block type) (set to zero if the snowlayer is removed) // 3. The current block at position, which is either // - A snow layer: Override with internal level + accum changes // - A solid block: Plase snow on top based on internal level + accum changes // - A snow variantable block: Call the method with the new level Block block = chunk.GetLocalLiquidOrBlockAtBlockPos(sapi.World, pos); float hereAccum = 0; Vec2i vec = new Vec2i(pos.X, pos.Z); if (!ignoreOldAccum && !mc.SnowAccum.TryGetValue(vec, out hereAccum)) { hereAccum = block.GetSnowLevel(pos); } float nowAccum = hereAccum + sumsnapshot.GetAvgSnowAccumByRegionCorner(relx, rely, relz); mc.SnowAccum[vec] = GameMath.Clamp(nowAccum, -1, ws.GeneralConfig.SnowLayerBlocks.Count + 0.6f); float hereShouldLevel = nowAccum - GameMath.MurmurHash3Mod(pos.X, 0, pos.Z, 150) / 300f; float shouldIndexf = GameMath.Clamp(hereShouldLevel - 1.1f, -1, ws.GeneralConfig.SnowLayerBlocks.Count - 1); int shouldIndex = shouldIndexf < 0 ? -1 : (int)shouldIndexf; placePos.Set(pos.X, Math.Min(pos.Y + 1, sapi.World.BlockAccessor.MapSizeY - 1), pos.Z); chunkY = placePos.Y / chunksize; if (prevChunkY != chunkY) { chunk = chunksCol?[chunkY] ?? sapi.WorldManager.GetChunk(chunkX, chunkY, chunkZ); prevChunkY = chunkY; } if (chunk == null) { return(null); } Block upBlock = chunk.GetLocalLiquidOrBlockAtBlockPos(sapi.World, placePos); // Case 1: We have a block that can become snow covered (or more snow covered) placePos.Set(pos); Block newblock = block.GetSnowCoveredVariant(placePos, hereShouldLevel); if (newblock != null) { if (block.Id != newblock.Id && upBlock.Replaceable > 6000) { updateChunk.SetBlocks[placePos.Copy()] = new BlockIdAndSnowLevel(newblock, hereShouldLevel); } } // Case 2: We have a solid block that can have snow on top else if (block.AllowSnowCoverage(sapi.World, placePos)) { placePos.Set(pos.X, pos.Y + 1, pos.Z); if (upBlock.Id != 0) { newblock = upBlock.GetSnowCoveredVariant(placePos, hereShouldLevel); if (newblock != null && upBlock.Id != newblock.Id) { updateChunk.SetBlocks[placePos.Copy()] = new BlockIdAndSnowLevel(newblock, hereShouldLevel); } continue; } if (shouldIndex >= 0) { Block toPlaceBlock = layers.GetKeyAtIndex(shouldIndex); updateChunk.SetBlocks[placePos.Copy()] = new BlockIdAndSnowLevel(toPlaceBlock, hereShouldLevel); } } } return(updateChunk); }
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 if (updateSnowLayerQueue.Contains(ch)) { return; } double nowTotalHours = ws.api.World.Calendar.TotalHours; if (nowTotalHours - simregion.LastUpdateTotalHours > 1) // Lets wait until WeatherSimulationRegion is done updating { return; } byte[] data = mc.GetData("lastSnowAccumUpdateTotalHours"); double lastSnowAccumUpdateTotalHours = data == null ? 0 : SerializerUtil.Deserialize <double>(data); double startTotalHours = lastSnowAccumUpdateTotalHours; int reso = WeatherSimulationRegion.snowAccumResolution; SnowAccumSnapshot sumsnapshot = new SnowAccumSnapshot() { //SumTemperatureByRegionCorner = new API.FloatDataMap3D(reso, reso, reso), SnowAccumulationByRegionCorner = new API.FloatDataMap3D(reso, reso, reso) }; float[] sumdata = sumsnapshot.SnowAccumulationByRegionCorner.Data; if (simregion == null) { return; } // Can't grow bigger than one full snow block float max = ws.GeneralConfig.SnowLayerBlocks.Count + 0.5f; int len = simregion.SnowAccumSnapshots.Length; int i = simregion.SnowAccumSnapshots.Start; int newCount = 0; lock (WeatherSimulationRegion.lockTest) { 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; } bool ignoreOldAccum = false; if (lastSnowAccumUpdateTotalHours - startTotalHours >= sapi.World.Calendar.DaysPerYear * sapi.World.Calendar.HoursPerDay) { ignoreOldAccum = true; } ch = UpdateSnowLayer(sumsnapshot, ignoreOldAccum, mc, chunkPos); if (ch != null) { //Console.WriteLine("{0} snaps used for {1}/{2}", newCount, chunkPos.X, chunkPos.Y); ch.LastSnowAccumUpdateTotalHours = lastSnowAccumUpdateTotalHours; ch.Coords = chunkPos.Copy(); lock (updateSnowLayerQueueLock) { updateSnowLayerQueue.Enqueue(ch); } } }