forked from mirrors/NBTExplorer
565 lines
21 KiB
C#
565 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Substrate.Core
|
|
{
|
|
// Rules:
|
|
// - Water must be calculated in steps breadth-first
|
|
// - If there are any "holes" within 5 steps (manhattan distance) of a water tile, only the edges
|
|
// that can be part of a shortest path to the closest hole(s) are part of the outflow.
|
|
// - Any blocks in the water tile's outflow are added to the queue
|
|
// - A water source's strength is calculated as strongest inflow - 1.
|
|
|
|
public class BlockFluid
|
|
{
|
|
private IBoundedDataBlockCollection _blockset;
|
|
|
|
private readonly int _xdim;
|
|
private readonly int _ydim;
|
|
private readonly int _zdim;
|
|
|
|
private Dictionary<ChunkKey, IBoundedDataBlockCollection> _chunks;
|
|
|
|
public delegate IBoundedDataBlockCollection NeighborLookupHandler (int relx, int rely, int relz);
|
|
|
|
public event NeighborLookupHandler ResolveNeighbor;
|
|
|
|
internal class BlockCoord
|
|
{
|
|
internal IBoundedDataBlockCollection chunk;
|
|
internal int lx;
|
|
internal int ly;
|
|
internal int lz;
|
|
|
|
internal BlockCoord (IBoundedDataBlockCollection _chunk, int _lx, int _ly, int _lz)
|
|
{
|
|
chunk = _chunk;
|
|
lx = _lx;
|
|
ly = _ly;
|
|
lz = _lz;
|
|
}
|
|
}
|
|
|
|
public BlockFluid (IBoundedDataBlockCollection blockset)
|
|
{
|
|
_blockset = blockset;
|
|
|
|
_xdim = _blockset.XDim;
|
|
_ydim = _blockset.YDim;
|
|
_zdim = _blockset.ZDim;
|
|
|
|
_chunks = new Dictionary<ChunkKey,IBoundedDataBlockCollection>();
|
|
}
|
|
|
|
public BlockFluid (BlockFluid bl)
|
|
{
|
|
_blockset = bl._blockset;
|
|
|
|
_xdim = bl._xdim;
|
|
_ydim = bl._ydim;
|
|
_zdim = bl._zdim;
|
|
|
|
_chunks = new Dictionary<ChunkKey, IBoundedDataBlockCollection>();
|
|
}
|
|
|
|
public void ResetWater (IDataArray blocks, IDataArray data)
|
|
{
|
|
for (int i = 0; i < blocks.Length; i++) {
|
|
if ((blocks[i] == BlockInfo.StationaryWater.ID || blocks[i] == BlockInfo.Water.ID) && data[i] != 0) {
|
|
blocks[i] = (byte)BlockInfo.Air.ID;
|
|
data[i] = 0;
|
|
}
|
|
else if (blocks[i] == BlockInfo.Water.ID) {
|
|
blocks[i] = (byte)BlockInfo.StationaryWater.ID;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ResetLava (IDataArray blocks, IDataArray data)
|
|
{
|
|
for (int i = 0; i < blocks.Length; i++) {
|
|
if ((blocks[i] == BlockInfo.StationaryLava.ID || blocks[i] == BlockInfo.Lava.ID) && data[i] != 0) {
|
|
blocks[i] = (byte)BlockInfo.Air.ID;
|
|
data[i] = 0;
|
|
}
|
|
else if (blocks[i] == BlockInfo.Lava.ID) {
|
|
blocks[i] = (byte)BlockInfo.StationaryLava.ID;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UpdateWater (int x, int y, int z)
|
|
{
|
|
DoWater(x, y, z);
|
|
_chunks.Clear();
|
|
}
|
|
|
|
public void UpdateLava (int x, int y, int z)
|
|
{
|
|
DoLava(x, y, z);
|
|
_chunks.Clear();
|
|
}
|
|
|
|
public void RebuildWater ()
|
|
{
|
|
int xdim = _xdim;
|
|
int ydim = _ydim;
|
|
int zdim = _zdim;
|
|
|
|
List<BlockKey> buildQueue = new List<BlockKey>();
|
|
|
|
for (int x = 0; x < xdim; x++) {
|
|
for (int z = 0; z < zdim; z++) {
|
|
for (int y = 0; y < ydim; y++) {
|
|
BlockInfo info = _blockset.GetInfo(x, y, z);
|
|
if (info.ID == BlockInfo.StationaryWater.ID && _blockset.GetData(x, y, z) == 0) {
|
|
buildQueue.Add(new BlockKey(x, y, z));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (BlockKey key in buildQueue) {
|
|
DoWater(key.x, key.y, key.z);
|
|
}
|
|
|
|
_chunks.Clear();
|
|
}
|
|
|
|
public void RebuildLava ()
|
|
{
|
|
int xdim = _xdim;
|
|
int ydim = _ydim;
|
|
int zdim = _zdim;
|
|
|
|
List<BlockKey> buildQueue = new List<BlockKey>();
|
|
|
|
for (int x = 0; x < xdim; x++) {
|
|
for (int z = 0; z < zdim; z++) {
|
|
for (int y = 0; y < ydim; y++) {
|
|
BlockInfo info = _blockset.GetInfo(x, y, z);
|
|
if (info.ID == BlockInfo.StationaryLava.ID && _blockset.GetData(x, y, z) == 0) {
|
|
buildQueue.Add(new BlockKey(x, y, z));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (BlockKey key in buildQueue) {
|
|
DoLava(key.x, key.y, key.z);
|
|
}
|
|
|
|
_chunks.Clear();
|
|
}
|
|
|
|
private BlockCoord TranslateCoord (int x, int y, int z)
|
|
{
|
|
IBoundedDataBlockCollection chunk = GetChunk(x, z);
|
|
|
|
int lx = ((x % _xdim) + _xdim) % _xdim;
|
|
int lz = ((z % _zdim) + _zdim) % _zdim;
|
|
|
|
return new BlockCoord(chunk, lx, y, lz);
|
|
}
|
|
|
|
private IBoundedDataBlockCollection GetChunk (int x, int z)
|
|
{
|
|
int cx = x / _xdim + (x >> 31);
|
|
int cz = z / _zdim + (z >> 31);
|
|
|
|
ChunkKey key = new ChunkKey(cx, cz);
|
|
|
|
IBoundedDataBlockCollection chunk;
|
|
if (!_chunks.TryGetValue(key, out chunk)) {
|
|
chunk = OnResolveNeighbor(cx, 0, cz);
|
|
_chunks[key] = chunk;
|
|
}
|
|
|
|
return chunk;
|
|
}
|
|
|
|
private IBoundedDataBlockCollection OnResolveNeighbor (int relX, int relY, int relZ)
|
|
{
|
|
if (ResolveNeighbor != null) {
|
|
IBoundedDataBlockCollection n = ResolveNeighbor(relX, relY, relZ);
|
|
|
|
if (n == null) {
|
|
return null;
|
|
}
|
|
|
|
if (n.XDim != _xdim ||
|
|
n.YDim != _ydim ||
|
|
n.ZDim != _zdim) {
|
|
throw new InvalidOperationException("Subscriber returned incompatible IDataBlockCollection");
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// -----
|
|
|
|
private List<BlockKey> TileOutflow (BlockKey key, int reach = 5)
|
|
{
|
|
Queue<BlockKey> searchQueue = new Queue<BlockKey>();
|
|
Queue<KeyValuePair<BlockKey, int>> traceQueue = new Queue<KeyValuePair<BlockKey, int>>();
|
|
Dictionary<BlockKey, int> markTable = new Dictionary<BlockKey,int>();
|
|
|
|
searchQueue.Enqueue(key);
|
|
markTable.Add(key, 0);
|
|
|
|
// Identify sinks
|
|
while (searchQueue.Count > 0) {
|
|
BlockKey branch = searchQueue.Dequeue();
|
|
int distance = markTable[branch];
|
|
|
|
// Ignore blocks out of range
|
|
if (distance > reach) {
|
|
continue;
|
|
}
|
|
|
|
// Ignore invalid blocks
|
|
BlockCoord branchHigh = TranslateCoord(branch.x, branch.y, branch.z);
|
|
if (branchHigh.chunk == null || branch.y == 0) {
|
|
markTable.Remove(branch);
|
|
continue;
|
|
}
|
|
|
|
// If we're not the magical source block...
|
|
if (distance > 0) {
|
|
// Ignore blocks that block fluid (and thus could not become a fluid)
|
|
BlockInfo branchHighInfo = branchHigh.chunk.GetInfo(branchHigh.lx, branchHigh.ly, branchHigh.lz);
|
|
if (branchHighInfo.BlocksFluid) {
|
|
markTable.Remove(branch);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If we found a hole, add as a sink, mark the sink distance.
|
|
BlockCoord branchLow = TranslateCoord(branch.x, branch.y - 1, branch.z);
|
|
BlockInfo branchLowInfo = branchLow.chunk.GetInfo(branchLow.lx, branchLow.ly, branchLow.lz);
|
|
if (!branchLowInfo.BlocksFluid) {
|
|
// If we are our own sink, return the only legal outflow
|
|
if (key == branch) {
|
|
List<BlockKey> ret = new List<BlockKey>();
|
|
ret.Add(new BlockKey(branch.x, branch.y - 1, branch.z));
|
|
return ret;
|
|
}
|
|
|
|
reach = distance;
|
|
traceQueue.Enqueue(new KeyValuePair<BlockKey, int>(branch, distance));
|
|
continue;
|
|
}
|
|
|
|
// Expand to neighbors
|
|
if (distance < reach) {
|
|
BlockKey[] keys = {
|
|
new BlockKey(branch.x - 1, branch.y, branch.z),
|
|
new BlockKey(branch.x + 1, branch.y, branch.z),
|
|
new BlockKey(branch.x, branch.y, branch.z - 1),
|
|
new BlockKey(branch.x, branch.y, branch.z + 1),
|
|
};
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
if (!markTable.ContainsKey(keys[i])) {
|
|
searchQueue.Enqueue(keys[i]);
|
|
markTable.Add(keys[i], distance + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Candidate outflows are marked
|
|
BlockKey[] neighbors = {
|
|
new BlockKey(key.x - 1, key.y, key.z),
|
|
new BlockKey(key.x + 1, key.y, key.z),
|
|
new BlockKey(key.x, key.y, key.z - 1),
|
|
new BlockKey(key.x, key.y, key.z + 1),
|
|
};
|
|
|
|
List<BlockKey> outflow = new List<BlockKey>();
|
|
foreach (BlockKey n in neighbors) {
|
|
if (markTable.ContainsKey(n)) {
|
|
outflow.Add(n);
|
|
}
|
|
}
|
|
|
|
// If there's no sinks, all neighbors are valid outflows
|
|
if (traceQueue.Count == 0) {
|
|
return outflow;
|
|
}
|
|
|
|
// Trace back from each sink eliminating shortest path marks
|
|
while (traceQueue.Count > 0) {
|
|
|
|
KeyValuePair<BlockKey, int> tilekv = traceQueue.Dequeue();
|
|
BlockKey tile = tilekv.Key;
|
|
|
|
int distance = tilekv.Value;
|
|
markTable.Remove(tile);
|
|
|
|
BlockKey[] keys = {
|
|
new BlockKey(tile.x - 1, tile.y, tile.z),
|
|
new BlockKey(tile.x + 1, tile.y, tile.z),
|
|
new BlockKey(tile.x, tile.y, tile.z - 1),
|
|
new BlockKey(tile.x, tile.y, tile.z + 1),
|
|
};
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
int nval;
|
|
if (!markTable.TryGetValue(keys[i], out nval)) {
|
|
continue;
|
|
}
|
|
|
|
if (nval < distance) {
|
|
markTable.Remove(keys[i]);
|
|
traceQueue.Enqueue(new KeyValuePair<BlockKey, int>(keys[i], nval));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove any candidates that are still marked
|
|
foreach (BlockKey n in neighbors) {
|
|
if (markTable.ContainsKey(n)) {
|
|
outflow.Remove(n);
|
|
}
|
|
}
|
|
|
|
return outflow;
|
|
}
|
|
|
|
private int TileInflow (BlockKey key)
|
|
{
|
|
// Check if water is falling on us
|
|
if (key.y < _ydim - 1) {
|
|
BlockCoord up = TranslateCoord(key.x, key.y + 1, key.z);
|
|
BlockInfo upInfo = up.chunk.GetInfo(up.lx, up.ly, up.lz);
|
|
|
|
if (upInfo.State == BlockState.FLUID) {
|
|
return up.chunk.GetData(up.lx, up.ly, up.lz) | (int)LiquidState.FALLING;
|
|
}
|
|
}
|
|
|
|
// Otherwise return the min inflow of our neighbors + step
|
|
BlockKey[] keys = {
|
|
new BlockKey(key.x - 1, key.y, key.z),
|
|
new BlockKey(key.x + 1, key.y, key.z),
|
|
new BlockKey(key.x, key.y, key.z - 1),
|
|
new BlockKey(key.x, key.y, key.z + 1),
|
|
};
|
|
|
|
int minFlow = 16;
|
|
|
|
// XXX: Might have different neighboring fluids
|
|
for (int i = 0; i < 4; i++) {
|
|
BlockCoord neighbor = TranslateCoord(keys[i].x, keys[i].y, keys[i].z);
|
|
if (neighbor.chunk == null) {
|
|
continue;
|
|
}
|
|
|
|
BlockInfo neighborInfo = neighbor.chunk.GetInfo(neighbor.lx, neighbor.ly, neighbor.lz);
|
|
|
|
if (neighborInfo.State == BlockState.FLUID) {
|
|
int flow = neighbor.chunk.GetData(neighbor.lx, neighbor.ly, neighbor.lz);
|
|
bool flowFall = (flow & (int)LiquidState.FALLING) != 0;
|
|
|
|
if (flowFall) {
|
|
if (keys[i].y == 0) {
|
|
continue;
|
|
}
|
|
|
|
BlockCoord low = TranslateCoord(keys[i].x, keys[i].y - 1, keys[i].z);
|
|
BlockInfo lowinfo = low.chunk.GetInfo(low.lx, low.ly, low.lz);
|
|
|
|
if (lowinfo.BlocksFluid) {
|
|
return 0;
|
|
}
|
|
continue;
|
|
}
|
|
if (flow < minFlow) {
|
|
minFlow = flow;
|
|
}
|
|
}
|
|
}
|
|
|
|
return minFlow;
|
|
}
|
|
|
|
private void DoWater (int x, int y, int z)
|
|
{
|
|
Queue<BlockKey> flowQueue = new Queue<BlockKey>();
|
|
|
|
BlockKey prikey = new BlockKey(x, y, z);
|
|
flowQueue.Enqueue(prikey);
|
|
|
|
List<BlockKey> outflow = TileOutflow(prikey);
|
|
foreach (BlockKey outkey in outflow) {
|
|
flowQueue.Enqueue(outkey);
|
|
}
|
|
|
|
while (flowQueue.Count > 0) {
|
|
BlockKey key = flowQueue.Dequeue();
|
|
|
|
int curflow = 16;
|
|
int inflow = TileInflow(key);
|
|
|
|
BlockCoord tile = TranslateCoord(key.x, key.y, key.z);
|
|
BlockInfo tileInfo = tile.chunk.GetInfo(tile.lx, tile.ly, tile.lz);
|
|
if (tileInfo.ID == BlockInfo.StationaryWater.ID || tileInfo.ID == BlockInfo.Water.ID) {
|
|
curflow = tile.chunk.GetData(tile.lx, tile.ly, tile.lz);
|
|
}
|
|
else if (tileInfo.BlocksFluid) {
|
|
continue;
|
|
}
|
|
|
|
bool curFall = (curflow & (int)LiquidState.FALLING) != 0;
|
|
bool inFall = (inflow & (int)LiquidState.FALLING) != 0;
|
|
|
|
// We won't update from the following states
|
|
if (curflow == 0 || curflow == inflow || curFall) {
|
|
continue;
|
|
}
|
|
|
|
int newflow = curflow;
|
|
|
|
// Update from inflow if necessary
|
|
if (inFall) {
|
|
newflow = inflow;
|
|
}
|
|
else if (inflow >= 7) {
|
|
newflow = 16;
|
|
}
|
|
else {
|
|
newflow = inflow + 1;
|
|
}
|
|
|
|
// If we haven't changed the flow, don't propagate
|
|
if (newflow == curflow) {
|
|
continue;
|
|
}
|
|
|
|
// Update flow, add or remove water tile as necessary
|
|
if (newflow < 16 && curflow == 16) {
|
|
// If we're overwriting lava, replace with appropriate stone type and abort propagation
|
|
if (tileInfo.ID == BlockInfo.StationaryLava.ID || tileInfo.ID == BlockInfo.Lava.ID) {
|
|
if ((newflow & (int)LiquidState.FALLING) != 0) {
|
|
int odata = tile.chunk.GetData(tile.lx, tile.ly, tile.lz);
|
|
if (odata == 0) {
|
|
tile.chunk.SetID(tile.lx, tile.ly, tile.lz, BlockInfo.Obsidian.ID);
|
|
}
|
|
else {
|
|
tile.chunk.SetID(tile.lx, tile.ly, tile.lz, BlockInfo.Cobblestone.ID);
|
|
}
|
|
tile.chunk.SetData(tile.lx, tile.ly, tile.lz, 0);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Otherwise replace the tile with our water flow
|
|
tile.chunk.SetID(tile.lx, tile.ly, tile.lz, BlockInfo.StationaryWater.ID);
|
|
tile.chunk.SetData(tile.lx, tile.ly, tile.lz, newflow);
|
|
}
|
|
else if (newflow == 16) {
|
|
tile.chunk.SetID(tile.lx, tile.ly, tile.lz, BlockInfo.Air.ID);
|
|
tile.chunk.SetData(tile.lx, tile.ly, tile.lz, 0);
|
|
}
|
|
else {
|
|
tile.chunk.SetData(tile.lx, tile.ly, tile.lz, newflow);
|
|
}
|
|
|
|
// Process outflows
|
|
outflow = TileOutflow(key);
|
|
|
|
foreach (BlockKey nkey in outflow) {
|
|
flowQueue.Enqueue(nkey);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DoLava (int x, int y, int z)
|
|
{
|
|
Queue<BlockKey> flowQueue = new Queue<BlockKey>();
|
|
|
|
BlockKey prikey = new BlockKey(x, y, z);
|
|
flowQueue.Enqueue(prikey);
|
|
|
|
List<BlockKey> outflow = TileOutflow(prikey);
|
|
foreach (BlockKey outkey in outflow) {
|
|
flowQueue.Enqueue(outkey);
|
|
}
|
|
|
|
while (flowQueue.Count > 0) {
|
|
BlockKey key = flowQueue.Dequeue();
|
|
|
|
int curflow = 16;
|
|
int inflow = TileInflow(key);
|
|
|
|
BlockCoord tile = TranslateCoord(key.x, key.y, key.z);
|
|
BlockInfo tileInfo = tile.chunk.GetInfo(tile.lx, tile.ly, tile.lz);
|
|
if (tileInfo.ID == BlockInfo.StationaryLava.ID || tileInfo.ID == BlockInfo.Lava.ID) {
|
|
curflow = tile.chunk.GetData(tile.lx, tile.ly, tile.lz);
|
|
}
|
|
else if (tileInfo.BlocksFluid) {
|
|
continue;
|
|
}
|
|
|
|
bool curFall = (curflow & (int)LiquidState.FALLING) != 0;
|
|
bool inFall = (inflow & (int)LiquidState.FALLING) != 0;
|
|
|
|
// We won't update from the following states
|
|
if (curflow == 0 || curflow == inflow || curFall) {
|
|
continue;
|
|
}
|
|
|
|
int newflow = curflow;
|
|
|
|
// Update from inflow if necessary
|
|
if (inFall) {
|
|
newflow = inflow;
|
|
}
|
|
else if (inflow >= 6) {
|
|
newflow = 16;
|
|
}
|
|
else {
|
|
newflow = inflow + 2;
|
|
}
|
|
|
|
// If we haven't changed the flow, don't propagate
|
|
if (newflow == curflow) {
|
|
continue;
|
|
}
|
|
|
|
// Update flow, add or remove lava tile as necessary
|
|
if (newflow < 16 && curflow == 16) {
|
|
// If we're overwriting water, replace with appropriate stone type and abort propagation
|
|
if (tileInfo.ID == BlockInfo.StationaryWater.ID || tileInfo.ID == BlockInfo.Water.ID) {
|
|
if ((newflow & (int)LiquidState.FALLING) == 0) {
|
|
tile.chunk.SetID(tile.lx, tile.ly, tile.lz, BlockInfo.Cobblestone.ID);
|
|
tile.chunk.SetData(tile.lx, tile.ly, tile.lz, 0);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
tile.chunk.SetID(tile.lx, tile.ly, tile.lz, BlockInfo.StationaryLava.ID);
|
|
tile.chunk.SetData(tile.lx, tile.ly, tile.lz, newflow);
|
|
}
|
|
else if (newflow == 16) {
|
|
tile.chunk.SetID(tile.lx, tile.ly, tile.lz, BlockInfo.Air.ID);
|
|
tile.chunk.SetData(tile.lx, tile.ly, tile.lz, 0);
|
|
}
|
|
else {
|
|
tile.chunk.SetData(tile.lx, tile.ly, tile.lz, newflow);
|
|
}
|
|
|
|
// Process outflows
|
|
outflow = TileOutflow(key);
|
|
|
|
foreach (BlockKey nkey in outflow) {
|
|
flowQueue.Enqueue(nkey);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|