public MapComponent LoadMapData(Vec2i chunkCoord, int[] pixels) { ICoreClientAPI capi = api as ICoreClientAPI; int chunksize = api.World.BlockAccessor.ChunkSize; LoadedTexture tex = new LoadedTexture(capi, 0, chunksize, chunksize); capi.Render.LoadOrUpdateTextureFromRgba(pixels, false, 0, ref tex); ChunkMapComponent cmp = new ChunkMapComponent(capi, chunkCoord.Copy()); cmp.Texture = tex; return(cmp); }
public MapComponent LoadMapData(Vec2i chunkCoord, int[] pixels) { ICoreClientAPI capi = api as ICoreClientAPI; int chunksize = api.World.BlockAccessor.ChunkSize; int textureId = capi.Render.LoadTextureFromRgba( pixels, api.World.BlockAccessor.ChunkSize, api.World.BlockAccessor.ChunkSize, false, 0 ); ChunkMapComponent cmp = new ChunkMapComponent(capi, chunkCoord.Copy()); cmp.Texture = new LoadedTexture(capi, textureId, chunksize, chunksize); return(cmp); }
private void tryBfsRemove(int x, int z) { Queue <Vec2i> nodesToVisit = new Queue <Vec2i>(); HashSet <Vec2i> nodesVisited = new HashSet <Vec2i>(); nodesToVisit.Enqueue(new Vec2i(x, z)); List <Vec2i> foundPieces = new List <Vec2i>(); while (nodesToVisit.Count > 0) { Vec2i node = nodesToVisit.Dequeue(); for (int i = 0; i < BlockFacing.HORIZONTALS.Length; i++) { BlockFacing face = BlockFacing.HORIZONTALS[i]; Vec2i nnode = node.Copy().Add(face.Normali.X, face.Normali.Z); if (nnode.X < 0 || nnode.X >= 16 || nnode.Y < 0 || nnode.Y >= 16) { continue; } if (!Voxels[nnode.X, nnode.Y]) { continue; } if (nodesVisited.Contains(nnode)) { continue; } nodesVisited.Add(nnode); foundPieces.Add(nnode); if (SelectedRecipe.Voxels[nnode.X, 0, nnode.Y]) { return; } nodesToVisit.Enqueue(nnode); } } // Single voxel with no neighbours if (nodesVisited.Count == 0 && foundPieces.Count == 0) { foundPieces.Add(new Vec2i(x, z)); } Vec3d tmp = new Vec3d(); foreach (var val in foundPieces) { Voxels[val.X, val.Y] = false; if (Api.Side == EnumAppSide.Client) { tmp.Set(Pos.X + val.X / 16f, Pos.Y, Pos.Z + val.Y / 16f); spawnParticles(tmp); } } }
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 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); } } }
public void TryPlacePondAt(int dx, int pondYPos, int dz, int chunkX, int chunkZ, int depth = 0) { searchPositionsDeltas.Clear(); pondPositions.Clear(); // Clear Array for (int i = 0; i < didCheckPosition.Length; i++) { didCheckPosition[i] = false; } int basePosX = chunkX * chunksize; int basePosZ = chunkZ * chunksize; Vec2i tmp = new Vec2i(); searchPositionsDeltas.Enqueue(new Vec2i(dx, dz)); pondPositions.Enqueue(new Vec2i(basePosX + dx, basePosZ + dz)); didCheckPosition[(dz + mapOffset) * searchSize + dx + mapOffset] = true; while (searchPositionsDeltas.Count > 0) { Vec2i p = searchPositionsDeltas.Dequeue(); foreach (BlockFacing facing in BlockFacing.HORIZONTALS) { ndx = p.X + facing.Normali.X; ndz = p.Y + facing.Normali.Z; tmp.Set(chunkX * chunksize + ndx, chunkZ * chunksize + ndz); Block belowBlock = blockAccessor.GetBlock(tmp.X, pondYPos - 1, tmp.Y); bool inBoundary = ndx > minBoundary && ndz > minBoundary && ndx < maxBoundary && ndz < maxBoundary; // Only continue when within our 3x3 chunk search area and having a more or less solid block below (or water) if (inBoundary && (belowBlock.Replaceable < 6000 || belowBlock.BlockId == GlobalConfig.waterBlockId)) { int arrayIndex = (ndz + mapOffset) * searchSize + ndx + mapOffset; // Already checked or did we reach a pond border? if (!didCheckPosition[arrayIndex] && blockAccessor.GetBlock(tmp.X, pondYPos, tmp.Y).Replaceable >= 6000) { searchPositionsDeltas.Enqueue(new Vec2i(ndx, ndz)); pondPositions.Enqueue(tmp.Copy()); didCheckPosition[arrayIndex] = true; } } else { pondPositions.Clear(); searchPositionsDeltas.Clear(); return; } } } if (pondPositions.Count == 0) { return; } int curChunkX, curChunkZ; int prevChunkX = -1, prevChunkZ = -1; int regionChunkSize = api.WorldManager.RegionSize / chunksize; IMapChunk mapchunk = null; IServerChunk chunk = null; IServerChunk chunkOneBlockBelow = null; int ly = GameMath.Mod(pondYPos, chunksize); bool extraPondDepth = rand.NextDouble() > 0.5; bool withSeabed = extraPondDepth || pondPositions.Count > 16; foreach (Vec2i p in pondPositions) { curChunkX = p.X / chunksize; curChunkZ = p.Y / chunksize; int lx = GameMath.Mod(p.X, chunksize); int lz = GameMath.Mod(p.Y, chunksize); // Get correct chunk and correct climate data if we don't have it already if (curChunkX != prevChunkX || curChunkZ != prevChunkZ) { chunk = (IServerChunk)blockAccessor.GetChunk(curChunkX, pondYPos / chunksize, curChunkZ); if (chunk == null) { chunk = api.WorldManager.GetChunk(curChunkX, pondYPos / chunksize, curChunkZ); } chunk.Unpack(); if (ly == 0) { chunkOneBlockBelow = ((IServerChunk)blockAccessor.GetChunk(curChunkX, (pondYPos - 1) / chunksize, curChunkZ)); if (chunkOneBlockBelow == null) { return; } chunkOneBlockBelow.Unpack(); } else { chunkOneBlockBelow = chunk; } mapchunk = chunk.MapChunk; IntDataMap2D climateMap = mapchunk.MapRegion.ClimateMap; float fac = (float)climateMap.InnerSize / regionChunkSize; int rlX = curChunkX % regionChunkSize; int rlZ = curChunkZ % regionChunkSize; climateUpLeft = climateMap.GetUnpaddedInt((int)(rlX * fac), (int)(rlZ * fac)); climateUpRight = climateMap.GetUnpaddedInt((int)(rlX * fac + fac), (int)(rlZ * fac)); climateBotLeft = climateMap.GetUnpaddedInt((int)(rlX * fac), (int)(rlZ * fac + fac)); climateBotRight = climateMap.GetUnpaddedInt((int)(rlX * fac + fac), (int)(rlZ * fac + fac)); prevChunkX = curChunkX; prevChunkZ = curChunkZ; chunkOneBlockBelow.MarkModified(); chunk.MarkModified(); } // Raise heightmap by 1 mapchunk.RainHeightMap[lz * chunksize + lx] = Math.Max(mapchunk.RainHeightMap[lz * chunksize + lx], (ushort)pondYPos); // Identify correct climate at this position int climate = GameMath.BiLerpRgbColor((float)lx / chunksize, (float)lz / chunksize, climateUpLeft, climateUpRight, climateBotLeft, climateBotRight); float temp = TerraGenConfig.GetScaledAdjustedTemperatureFloat((climate >> 16) & 0xff, pondYPos - TerraGenConfig.seaLevel); // 1. Place water or ice block chunk.Blocks[(ly * chunksize + lz) * chunksize + lx] = temp < -5 ? GlobalConfig.lakeIceBlockId : GlobalConfig.waterBlockId; // 2. Let's make a nice muddy gravely sea bed if (!withSeabed) { continue; } // Need to check the block below first int index = ly == 0 ? ((31 * chunksize + lz) * chunksize + lx) : (((ly - 1) * chunksize + lz) * chunksize + lx) ; Block belowBlock = api.World.Blocks[chunkOneBlockBelow.Blocks[index]]; // Water below? Seabed already placed if (belowBlock.IsLiquid()) { continue; } float rainRel = TerraGenConfig.GetRainFall((climate >> 8) & 0xff, pondYPos) / 255f; int rockBlockId = mapchunk.TopRockIdMap[lz * chunksize + lx]; if (rockBlockId == 0) { continue; } for (int i = 0; i < lakebedLayerConfig.BlockCodeByMin.Length; i++) { if (lakebedLayerConfig.BlockCodeByMin[i].Suitable(temp, rainRel, (float)pondYPos / mapheight, rand)) { chunkOneBlockBelow.Blocks[index] = lakebedLayerConfig.BlockCodeByMin[i].GetBlockForMotherRock(rockBlockId); break; } } } if (pondPositions.Count > 0 && extraPondDepth) { TryPlacePondAt(dx, pondYPos + 1, dz, chunkX, chunkZ, depth + 1); } }