Move fluid logic to shared provider and add lava
This commit is contained in:
parent
21f1b2f0b8
commit
c190f83542
309
TrueCraft.Core/Logic/Blocks/FluidBlock.cs
Normal file
309
TrueCraft.Core/Logic/Blocks/FluidBlock.cs
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
using System;
|
||||||
|
using TrueCraft.API;
|
||||||
|
using TrueCraft.API.World;
|
||||||
|
using TrueCraft.API.Server;
|
||||||
|
using TrueCraft.API.Logic;
|
||||||
|
using TrueCraft.API.Networking;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace TrueCraft.Core.Logic.Blocks
|
||||||
|
{
|
||||||
|
public abstract class FluidBlock : BlockProvider
|
||||||
|
{
|
||||||
|
// Fluids in Minecraft propegate according to a set of rules as cellular automata.
|
||||||
|
// Source blocks start at zero and each block progressively further from the source
|
||||||
|
// is one greater than the largest value nearby. When they reach
|
||||||
|
// MaximumFluidDepletion, the fluid stops propgetating.
|
||||||
|
|
||||||
|
public override abstract byte ID { get; }
|
||||||
|
|
||||||
|
public override BoundingBox? BoundingBox
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ItemStack[] GetDrop(BlockDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return new ItemStack[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract double SecondsBetweenUpdates { get; }
|
||||||
|
protected abstract byte MaximumFluidDepletion { get; }
|
||||||
|
protected abstract byte FlowingID { get; }
|
||||||
|
protected abstract byte StillID { get; }
|
||||||
|
|
||||||
|
private static readonly Coordinates3D[] Neighbors =
|
||||||
|
{
|
||||||
|
Coordinates3D.Left,
|
||||||
|
Coordinates3D.Right,
|
||||||
|
Coordinates3D.Forwards,
|
||||||
|
Coordinates3D.Backwards
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a block that the currently updating fluid block is able to flow outwards into.
|
||||||
|
/// </summary>
|
||||||
|
protected struct LiquidFlow
|
||||||
|
{
|
||||||
|
public LiquidFlow(Coordinates3D targetBlock, byte level)
|
||||||
|
{
|
||||||
|
TargetBlock = targetBlock;
|
||||||
|
Level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The block to be filled with fluid.
|
||||||
|
/// </summary>
|
||||||
|
public Coordinates3D TargetBlock;
|
||||||
|
/// <summary>
|
||||||
|
/// The fluid level to fill the target block with.
|
||||||
|
/// </summary>
|
||||||
|
public byte Level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScheduleNextEvent(Coordinates3D coords, IWorld world, IMultiplayerServer server)
|
||||||
|
{
|
||||||
|
server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), (_server) =>
|
||||||
|
AutomataUpdate(_server, world, coords));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user)
|
||||||
|
{
|
||||||
|
if (ID == FlowingID)
|
||||||
|
ScheduleNextEvent(descriptor.Coordinates, world, user.Server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BlockUpdate(BlockDescriptor descriptor, BlockDescriptor source, IMultiplayerServer server, IWorld world)
|
||||||
|
{
|
||||||
|
if (ID == StillID)
|
||||||
|
{
|
||||||
|
var outward = DetermineOutwardFlow(world, descriptor.Coordinates);
|
||||||
|
var inward = DetermineInwardFlow(world, descriptor.Coordinates);
|
||||||
|
if (outward.Length != 0 || inward != descriptor.Metadata)
|
||||||
|
{
|
||||||
|
world.SetBlockID(descriptor.Coordinates, FlowingID);
|
||||||
|
ScheduleNextEvent(descriptor.Coordinates, world, server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutomataUpdate(IMultiplayerServer server, IWorld world, Coordinates3D coords)
|
||||||
|
{
|
||||||
|
if (world.GetBlockID(coords) != ID)
|
||||||
|
return;
|
||||||
|
server.BlockUpdatesEnabled = false;
|
||||||
|
var again = DoAutomata(server, world, coords);
|
||||||
|
server.BlockUpdatesEnabled = true;
|
||||||
|
if (again)
|
||||||
|
{
|
||||||
|
server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), (_server) =>
|
||||||
|
AutomataUpdate(_server, world, coords));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DoAutomata(IMultiplayerServer server, IWorld world, Coordinates3D coords)
|
||||||
|
{
|
||||||
|
var previousLevel = world.GetMetadata(coords);
|
||||||
|
|
||||||
|
var inward = DetermineInwardFlow(world, coords);
|
||||||
|
var outward = DetermineOutwardFlow(world, coords);
|
||||||
|
|
||||||
|
if (outward.Length == 1 && outward[0].TargetBlock == coords + Coordinates3D.Down)
|
||||||
|
{
|
||||||
|
// Exit early if we have placed a fluid block beneath us (and we aren't a source block)
|
||||||
|
FlowOutward(world, outward[0], server);
|
||||||
|
if (previousLevel != 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process inward flow
|
||||||
|
if (inward > MaximumFluidDepletion)
|
||||||
|
{
|
||||||
|
world.SetBlockID(coords, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
world.SetMetadata(coords, inward);
|
||||||
|
if (inward == 0 && previousLevel != 0)
|
||||||
|
{
|
||||||
|
// Exit early if we have become a source block
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process outward flow
|
||||||
|
for (int i = 0; i < outward.Length; i++)
|
||||||
|
FlowOutward(world, outward[i], server);
|
||||||
|
// Set our block to still fluid if we are done spreading.
|
||||||
|
if (outward.Length == 0 && inward == previousLevel)
|
||||||
|
{
|
||||||
|
world.SetBlockID(coords, StillID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlowOutward(IWorld world, LiquidFlow target, IMultiplayerServer server)
|
||||||
|
{
|
||||||
|
// For each block we can flow into, generate an item entity if appropriate
|
||||||
|
var provider = world.BlockRepository.GetBlockProvider(world.GetBlockID(target.TargetBlock));
|
||||||
|
provider.GenerateDropEntity(new BlockDescriptor { Coordinates = target.TargetBlock, ID = provider.ID }, world, server);
|
||||||
|
// And overwrite the block with a new fluid block.
|
||||||
|
world.SetBlockID(target.TargetBlock, FlowingID);
|
||||||
|
world.SetMetadata(target.TargetBlock, target.Level);
|
||||||
|
server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), s => AutomataUpdate(s, world, target.TargetBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Examines neighboring blocks and determines the new fluid level that this block should adopt.
|
||||||
|
/// </summary>
|
||||||
|
protected byte DetermineInwardFlow(IWorld world, Coordinates3D coords)
|
||||||
|
{
|
||||||
|
var currentLevel = world.GetMetadata(coords);
|
||||||
|
var up = world.GetBlockID(coords + Coordinates3D.Up);
|
||||||
|
if (up == FlowingID || up == StillID) // Check for fluid above us
|
||||||
|
return currentLevel;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (currentLevel != 0)
|
||||||
|
{
|
||||||
|
byte highestNeighboringFluid = 15;
|
||||||
|
int neighboringSourceBlocks = 0;
|
||||||
|
for (int i = 0; i < Neighbors.Length; i++)
|
||||||
|
{
|
||||||
|
var nId = world.GetBlockID(coords + Neighbors[i]);
|
||||||
|
if (nId == FlowingID || nId == StillID)
|
||||||
|
{
|
||||||
|
var neighborLevel = world.GetMetadata(coords + Neighbors[i]);
|
||||||
|
if (neighborLevel < highestNeighboringFluid)
|
||||||
|
highestNeighboringFluid = neighborLevel;
|
||||||
|
if (neighborLevel == 0)
|
||||||
|
neighboringSourceBlocks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (neighboringSourceBlocks >= 2)
|
||||||
|
currentLevel = 0;
|
||||||
|
if (highestNeighboringFluid > 0)
|
||||||
|
currentLevel = (byte)(highestNeighboringFluid + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Produces a list of outward flow targets that this block may flow towards.
|
||||||
|
/// </summary>
|
||||||
|
protected LiquidFlow[] DetermineOutwardFlow(IWorld world, Coordinates3D coords)
|
||||||
|
{
|
||||||
|
// The maximum distance we will search for lower ground to flow towards
|
||||||
|
const int dropCheckDistance = 5;
|
||||||
|
|
||||||
|
var outwardFlow = new List<LiquidFlow>(5);
|
||||||
|
|
||||||
|
var currentLevel = world.GetMetadata(coords);
|
||||||
|
var blockBelow = world.BlockRepository.GetBlockProvider(world.GetBlockID(coords + Coordinates3D.Down));
|
||||||
|
if (!blockBelow.Opaque && blockBelow.ID != FlowingID && blockBelow.ID != StillID)
|
||||||
|
{
|
||||||
|
outwardFlow.Add(new LiquidFlow(coords + Coordinates3D.Down, 1));
|
||||||
|
if (currentLevel != 0)
|
||||||
|
return outwardFlow.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLevel < MaximumFluidDepletion)
|
||||||
|
{
|
||||||
|
// This code is responsible for seeking out candidates for flowing towards.
|
||||||
|
// Fluid in Minecraft will flow in the direction of the nearest drop-off where
|
||||||
|
// there is at least one block removed on the Y axis.
|
||||||
|
// It will flow towards several equally strong candidates at once.
|
||||||
|
|
||||||
|
var candidateFlowPoints = new List<Coordinates3D>(4);
|
||||||
|
var furthestPossibleCandidate = new Coordinates3D(x: dropCheckDistance + 1, z: dropCheckDistance + 1) + Coordinates3D.Down;
|
||||||
|
|
||||||
|
var nearestCandidate = furthestPossibleCandidate;
|
||||||
|
for (int x = -dropCheckDistance; x < dropCheckDistance; x++)
|
||||||
|
{
|
||||||
|
for (int z = -dropCheckDistance; z < dropCheckDistance; z++)
|
||||||
|
{
|
||||||
|
if (Math.Abs(z) + Math.Abs(x) > dropCheckDistance)
|
||||||
|
continue;
|
||||||
|
var check = new Coordinates3D(x: x, z: z) + Coordinates3D.Down;
|
||||||
|
var c = world.BlockRepository.GetBlockProvider(world.GetBlockID(check + coords));
|
||||||
|
if (!c.Opaque)
|
||||||
|
{
|
||||||
|
if (!LineOfSight(world, check + coords, coords))
|
||||||
|
continue;
|
||||||
|
if (coords.DistanceTo(check + coords) == coords.DistanceTo(nearestCandidate + coords))
|
||||||
|
candidateFlowPoints.Add(check);
|
||||||
|
if (coords.DistanceTo(check + coords) < coords.DistanceTo(nearestCandidate + coords))
|
||||||
|
{
|
||||||
|
candidateFlowPoints.Clear();
|
||||||
|
nearestCandidate = check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nearestCandidate == furthestPossibleCandidate)
|
||||||
|
{
|
||||||
|
candidateFlowPoints.Add(new Coordinates3D(x: -dropCheckDistance - 1, z: dropCheckDistance + 1) + Coordinates3D.Down);
|
||||||
|
candidateFlowPoints.Add(new Coordinates3D(x: dropCheckDistance + 1, z: -dropCheckDistance - 1) + Coordinates3D.Down);
|
||||||
|
candidateFlowPoints.Add(new Coordinates3D(x: -dropCheckDistance - 1, z: -dropCheckDistance - 1) + Coordinates3D.Down);
|
||||||
|
}
|
||||||
|
candidateFlowPoints.Add(nearestCandidate);
|
||||||
|
|
||||||
|
// For each candidate, determine if we are actually capable of flowing towards it.
|
||||||
|
// We are able to flow through blocks with a hardness of zero, but no others. We are
|
||||||
|
// not able to flow through established fluid blocks.
|
||||||
|
for (int i = 0; i < candidateFlowPoints.Count; i++)
|
||||||
|
{
|
||||||
|
var location = candidateFlowPoints[i];
|
||||||
|
location.Clamp(1);
|
||||||
|
|
||||||
|
var xCoordinateCheck = new Coordinates3D(x: location.X) + coords;
|
||||||
|
var zCoordinateCheck = new Coordinates3D(z: location.Z) + coords;
|
||||||
|
|
||||||
|
var xID = world.BlockRepository.GetBlockProvider(world.GetBlockID(xCoordinateCheck));
|
||||||
|
var zID = world.BlockRepository.GetBlockProvider(world.GetBlockID(zCoordinateCheck));
|
||||||
|
|
||||||
|
if (xID.Hardness == 0 && xID.ID != FlowingID && xID.ID != StillID)
|
||||||
|
{
|
||||||
|
if (outwardFlow.All(f => f.TargetBlock != xCoordinateCheck))
|
||||||
|
outwardFlow.Add(new LiquidFlow(xCoordinateCheck, (byte)(currentLevel + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zID.Hardness == 0 && zID.ID != FlowingID && zID.ID != StillID)
|
||||||
|
{
|
||||||
|
if (outwardFlow.All(f => f.TargetBlock != zCoordinateCheck))
|
||||||
|
outwardFlow.Add(new LiquidFlow(zCoordinateCheck, (byte)(currentLevel + 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outwardFlow.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the given candidate coordinate has a line-of-sight to the given target coordinate.
|
||||||
|
/// </summary>
|
||||||
|
private bool LineOfSight(IWorld world, Coordinates3D candidate, Coordinates3D target)
|
||||||
|
{
|
||||||
|
candidate += Coordinates3D.Up;
|
||||||
|
var direction = target - candidate;
|
||||||
|
direction.Clamp(1);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int z = candidate.Z;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var p = world.BlockRepository.GetBlockProvider(world.GetBlockID(candidate));
|
||||||
|
if (p.Opaque)
|
||||||
|
return false;
|
||||||
|
candidate.Z += direction.Z;
|
||||||
|
} while (target.Z != candidate.Z);
|
||||||
|
candidate.Z = z;
|
||||||
|
candidate.X += direction.X;
|
||||||
|
} while (target.X != candidate.X);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,20 @@ using TrueCraft.API.Logic;
|
|||||||
|
|
||||||
namespace TrueCraft.Core.Logic.Blocks
|
namespace TrueCraft.Core.Logic.Blocks
|
||||||
{
|
{
|
||||||
public class LavaBlock : BlockProvider
|
public class LavaBlock : FluidBlock
|
||||||
{
|
{
|
||||||
|
public LavaBlock() : this(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LavaBlock(bool nether)
|
||||||
|
{
|
||||||
|
if (nether)
|
||||||
|
_MaximumFluidDepletion = 7;
|
||||||
|
else
|
||||||
|
_MaximumFluidDepletion = 3;
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly byte BlockID = 0x0A;
|
public static readonly byte BlockID = 0x0A;
|
||||||
|
|
||||||
public override byte ID { get { return 0x0A; } }
|
public override byte ID { get { return 0x0A; } }
|
||||||
@ -20,6 +32,15 @@ namespace TrueCraft.Core.Logic.Blocks
|
|||||||
public override byte LightModifier { get { return 255; } }
|
public override byte LightModifier { get { return 255; } }
|
||||||
|
|
||||||
public override string DisplayName { get { return "Lava"; } }
|
public override string DisplayName { get { return "Lava"; } }
|
||||||
|
|
||||||
|
protected override double SecondsBetweenUpdates { get { return 2; } }
|
||||||
|
|
||||||
|
private byte _MaximumFluidDepletion { get; set; }
|
||||||
|
protected override byte MaximumFluidDepletion { get { return _MaximumFluidDepletion; } }
|
||||||
|
|
||||||
|
protected override byte FlowingID { get { return BlockID; } }
|
||||||
|
|
||||||
|
protected override byte StillID { get { return StationaryLavaBlock.BlockID; } }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class StationaryLavaBlock : LavaBlock
|
public class StationaryLavaBlock : LavaBlock
|
||||||
|
@ -9,46 +9,8 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace TrueCraft.Core.Logic.Blocks
|
namespace TrueCraft.Core.Logic.Blocks
|
||||||
{
|
{
|
||||||
public class WaterBlock : BlockProvider
|
public class WaterBlock : FluidBlock
|
||||||
{
|
{
|
||||||
// Fluids in Minecraft propegate according to a set of rules as cellular automata.
|
|
||||||
// Source blocks start at zero and each block progressively further from the source
|
|
||||||
// is one greater than the largest value nearby. When they reach 7, the water stops
|
|
||||||
// propgetating.
|
|
||||||
|
|
||||||
private const double SecondsBetweenUpdates = 0.25;
|
|
||||||
|
|
||||||
private const byte MaximumFluidDepletion = 7;
|
|
||||||
|
|
||||||
private static readonly Coordinates3D[] Neighbors =
|
|
||||||
{
|
|
||||||
Coordinates3D.Left,
|
|
||||||
Coordinates3D.Right,
|
|
||||||
Coordinates3D.Forwards,
|
|
||||||
Coordinates3D.Backwards
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a block that the currently updating water block is able to flow outwards into.
|
|
||||||
/// </summary>
|
|
||||||
protected struct LiquidFlow
|
|
||||||
{
|
|
||||||
public LiquidFlow(Coordinates3D targetBlock, byte level)
|
|
||||||
{
|
|
||||||
TargetBlock = targetBlock;
|
|
||||||
Level = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The block to be filled with water.
|
|
||||||
/// </summary>
|
|
||||||
public Coordinates3D TargetBlock;
|
|
||||||
/// <summary>
|
|
||||||
/// The water level to fill the target block with.
|
|
||||||
/// </summary>
|
|
||||||
public byte Level;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly byte BlockID = 0x08;
|
public static readonly byte BlockID = 0x08;
|
||||||
|
|
||||||
public override byte ID { get { return 0x08; } }
|
public override byte ID { get { return 0x08; } }
|
||||||
@ -65,241 +27,13 @@ namespace TrueCraft.Core.Logic.Blocks
|
|||||||
|
|
||||||
public override string DisplayName { get { return "Water"; } }
|
public override string DisplayName { get { return "Water"; } }
|
||||||
|
|
||||||
protected override ItemStack[] GetDrop(BlockDescriptor descriptor)
|
protected override double SecondsBetweenUpdates { get { return 0.25; } }
|
||||||
{
|
|
||||||
return new ItemStack[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public override BoundingBox? BoundingBox
|
protected override byte MaximumFluidDepletion { get { return 7; } }
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ScheduleNextEvent(Coordinates3D coords, IWorld world, IMultiplayerServer server)
|
protected override byte FlowingID { get { return BlockID; } }
|
||||||
{
|
|
||||||
server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), (_server) =>
|
|
||||||
AutomataUpdate(_server, world, coords));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user)
|
protected override byte StillID { get { return StationaryWaterBlock.BlockID; } }
|
||||||
{
|
|
||||||
ScheduleNextEvent(descriptor.Coordinates, world, user.Server);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AutomataUpdate(IMultiplayerServer server, IWorld world, Coordinates3D coords)
|
|
||||||
{
|
|
||||||
if (world.GetBlockID(coords) != BlockID)
|
|
||||||
return;
|
|
||||||
server.BlockUpdatesEnabled = false;
|
|
||||||
var again = DoAutomata(server, world, coords);
|
|
||||||
server.BlockUpdatesEnabled = true;
|
|
||||||
if (again)
|
|
||||||
{
|
|
||||||
server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), (_server) =>
|
|
||||||
AutomataUpdate(_server, world, coords));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if the given candidate coordinate has a line-of-sight to the given target coordinate.
|
|
||||||
/// </summary>
|
|
||||||
private bool LineOfSight(IWorld world, Coordinates3D candidate, Coordinates3D target)
|
|
||||||
{
|
|
||||||
candidate += Coordinates3D.Up;
|
|
||||||
var direction = target - candidate;
|
|
||||||
direction.Clamp(1);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int z = candidate.Z;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var p = world.BlockRepository.GetBlockProvider(world.GetBlockID(candidate));
|
|
||||||
if (p.Opaque)
|
|
||||||
return false;
|
|
||||||
candidate.Z += direction.Z;
|
|
||||||
} while (target.Z != candidate.Z);
|
|
||||||
candidate.Z = z;
|
|
||||||
candidate.X += direction.X;
|
|
||||||
} while (target.X != candidate.X);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Examines neighboring blocks and determines the new water level that this block should adopt.
|
|
||||||
/// </summary>
|
|
||||||
protected byte DetermineInwardFlow(IWorld world, Coordinates3D coords)
|
|
||||||
{
|
|
||||||
var currentLevel = world.GetMetadata(coords);
|
|
||||||
var up = world.GetBlockID(coords + Coordinates3D.Up);
|
|
||||||
if (up == WaterBlock.BlockID || up == StationaryWaterBlock.BlockID) // Check for water above us
|
|
||||||
return currentLevel;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (currentLevel != 0)
|
|
||||||
{
|
|
||||||
byte highestNeighboringFluid = 15;
|
|
||||||
int neighboringSourceBlocks = 0;
|
|
||||||
for (int i = 0; i < Neighbors.Length; i++)
|
|
||||||
{
|
|
||||||
var nId = world.GetBlockID(coords + Neighbors[i]);
|
|
||||||
if (nId == WaterBlock.BlockID || nId == StationaryWaterBlock.BlockID)
|
|
||||||
{
|
|
||||||
var neighborLevel = world.GetMetadata(coords + Neighbors[i]);
|
|
||||||
if (neighborLevel < highestNeighboringFluid)
|
|
||||||
highestNeighboringFluid = neighborLevel;
|
|
||||||
if (neighborLevel == 0)
|
|
||||||
neighboringSourceBlocks++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (neighboringSourceBlocks >= 2)
|
|
||||||
currentLevel = 0;
|
|
||||||
if (highestNeighboringFluid > 0)
|
|
||||||
currentLevel = (byte)(highestNeighboringFluid + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Produces a list of outward flow targets that this block may flow towards.
|
|
||||||
/// </summary>
|
|
||||||
protected LiquidFlow[] DetermineOutwardFlow(IWorld world, Coordinates3D coords)
|
|
||||||
{
|
|
||||||
// The maximum distance we will search for lower ground to flow towards
|
|
||||||
const int DropCheckDistance = 5;
|
|
||||||
|
|
||||||
var outwardFlow = new List<LiquidFlow>(5);
|
|
||||||
|
|
||||||
var currentLevel = world.GetMetadata(coords);
|
|
||||||
var blockBelow = world.BlockRepository.GetBlockProvider(world.GetBlockID(coords + Coordinates3D.Down));
|
|
||||||
if (!blockBelow.Opaque && blockBelow.ID != WaterBlock.BlockID && blockBelow.ID != StationaryWaterBlock.BlockID)
|
|
||||||
{
|
|
||||||
outwardFlow.Add(new LiquidFlow(coords + Coordinates3D.Down, 1));
|
|
||||||
if (currentLevel != 0)
|
|
||||||
return outwardFlow.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentLevel < MaximumFluidDepletion)
|
|
||||||
{
|
|
||||||
// This code is responsible for seeking out candidates for flowing towards.
|
|
||||||
// Water in Minecraft will flow in the direction of the nearest drop-off where
|
|
||||||
// there is at least one block removed on the Y axis.
|
|
||||||
// It will flow towards several equally strong candidates at once.
|
|
||||||
|
|
||||||
var candidateFlowPoints = new List<Coordinates3D>(4);
|
|
||||||
var furthestPossibleCandidate = new Coordinates3D(x: DropCheckDistance + 1, z: DropCheckDistance + 1) + Coordinates3D.Down;
|
|
||||||
|
|
||||||
var nearestCandidate = furthestPossibleCandidate;
|
|
||||||
for (int x = -DropCheckDistance; x < DropCheckDistance; x++)
|
|
||||||
{
|
|
||||||
for (int z = -DropCheckDistance; z < DropCheckDistance; z++)
|
|
||||||
{
|
|
||||||
if (Math.Abs(z) + Math.Abs(x) > DropCheckDistance)
|
|
||||||
continue;
|
|
||||||
var check = new Coordinates3D(x: x, z: z) + Coordinates3D.Down;
|
|
||||||
var c = world.BlockRepository.GetBlockProvider(world.GetBlockID(check + coords));
|
|
||||||
if (!c.Opaque)
|
|
||||||
{
|
|
||||||
if (!LineOfSight(world, check + coords, coords))
|
|
||||||
continue;
|
|
||||||
if (coords.DistanceTo(check + coords) == coords.DistanceTo(nearestCandidate + coords))
|
|
||||||
candidateFlowPoints.Add(check);
|
|
||||||
if (coords.DistanceTo(check + coords) < coords.DistanceTo(nearestCandidate + coords))
|
|
||||||
{
|
|
||||||
candidateFlowPoints.Clear();
|
|
||||||
nearestCandidate = check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nearestCandidate == furthestPossibleCandidate)
|
|
||||||
{
|
|
||||||
candidateFlowPoints.Add(new Coordinates3D(x: -DropCheckDistance - 1, z: DropCheckDistance + 1) + Coordinates3D.Down);
|
|
||||||
candidateFlowPoints.Add(new Coordinates3D(x: DropCheckDistance + 1, z: -DropCheckDistance - 1) + Coordinates3D.Down);
|
|
||||||
candidateFlowPoints.Add(new Coordinates3D(x: -DropCheckDistance - 1, z: -DropCheckDistance - 1) + Coordinates3D.Down);
|
|
||||||
}
|
|
||||||
candidateFlowPoints.Add(nearestCandidate);
|
|
||||||
|
|
||||||
// For each candidate, determine if we are actually capable of flowing towards it.
|
|
||||||
// We are able to flow through blocks with a hardness of zero, but no others. We are
|
|
||||||
// not able to flow through established water blocks.
|
|
||||||
for (int i = 0; i < candidateFlowPoints.Count; i++)
|
|
||||||
{
|
|
||||||
var location = candidateFlowPoints[i];
|
|
||||||
location.Clamp(1);
|
|
||||||
|
|
||||||
var xCoordinateCheck = new Coordinates3D(x: location.X) + coords;
|
|
||||||
var zCoordinateCheck = new Coordinates3D(z: location.Z) + coords;
|
|
||||||
|
|
||||||
var xID = world.BlockRepository.GetBlockProvider(world.GetBlockID(xCoordinateCheck));
|
|
||||||
var zID = world.BlockRepository.GetBlockProvider(world.GetBlockID(zCoordinateCheck));
|
|
||||||
|
|
||||||
if (xID.Hardness == 0 && xID.ID != WaterBlock.BlockID && xID.ID != StationaryWaterBlock.BlockID)
|
|
||||||
{
|
|
||||||
if (outwardFlow.All(f => f.TargetBlock != xCoordinateCheck))
|
|
||||||
outwardFlow.Add(new LiquidFlow(xCoordinateCheck, (byte)(currentLevel + 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zID.Hardness == 0 && zID.ID != WaterBlock.BlockID && zID.ID != StationaryWaterBlock.BlockID)
|
|
||||||
{
|
|
||||||
if (outwardFlow.All(f => f.TargetBlock != zCoordinateCheck))
|
|
||||||
outwardFlow.Add(new LiquidFlow(zCoordinateCheck, (byte)(currentLevel + 1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outwardFlow.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DoAutomata(IMultiplayerServer server, IWorld world, Coordinates3D coords)
|
|
||||||
{
|
|
||||||
var previousLevel = world.GetMetadata(coords);
|
|
||||||
|
|
||||||
var inward = DetermineInwardFlow(world, coords);
|
|
||||||
var outward = DetermineOutwardFlow(world, coords);
|
|
||||||
|
|
||||||
if (outward.Length == 1 && outward[0].TargetBlock == coords + Coordinates3D.Down)
|
|
||||||
{
|
|
||||||
// Exit early if we have placed a water block beneath us (and we aren't a source block)
|
|
||||||
if (previousLevel != 0)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process inward flow
|
|
||||||
if (inward >= MaximumFluidDepletion)
|
|
||||||
{
|
|
||||||
world.SetBlockID(coords, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
world.SetMetadata(coords, inward);
|
|
||||||
if (inward == 0 && previousLevel != 0)
|
|
||||||
{
|
|
||||||
// Exit early if we have become a source block
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process outward flow
|
|
||||||
for (int i = 0; i < outward.Length; i++)
|
|
||||||
{
|
|
||||||
var target = outward[i].TargetBlock;
|
|
||||||
// For each block we can flow into, generate an item entity if appropriate
|
|
||||||
var provider = world.BlockRepository.GetBlockProvider(world.GetBlockID(target));
|
|
||||||
provider.GenerateDropEntity(new BlockDescriptor { Coordinates = target, ID = provider.ID }, world, server);
|
|
||||||
// And overwrite the block with a new water block.
|
|
||||||
world.SetBlockID(target, WaterBlock.BlockID);
|
|
||||||
world.SetMetadata(target, outward[i].Level);
|
|
||||||
server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), s => AutomataUpdate(s, world, target));
|
|
||||||
}
|
|
||||||
// Set our block to still water if we are done spreading.
|
|
||||||
if (outward.Length == 0 && inward == previousLevel)
|
|
||||||
{
|
|
||||||
world.SetBlockID(coords, StationaryWaterBlock.BlockID);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class StationaryWaterBlock : WaterBlock
|
public class StationaryWaterBlock : WaterBlock
|
||||||
@ -319,34 +53,5 @@ namespace TrueCraft.Core.Logic.Blocks
|
|||||||
public override bool Opaque { get { return false; } }
|
public override bool Opaque { get { return false; } }
|
||||||
|
|
||||||
public override byte LightModifier { get { return 3; } }
|
public override byte LightModifier { get { return 3; } }
|
||||||
|
|
||||||
protected override ItemStack[] GetDrop(BlockDescriptor descriptor)
|
|
||||||
{
|
|
||||||
return new ItemStack[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public override BoundingBox? BoundingBox
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user)
|
|
||||||
{
|
|
||||||
// This space intentionally left blank
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void BlockUpdate(BlockDescriptor descriptor, BlockDescriptor source, IMultiplayerServer server, IWorld world)
|
|
||||||
{
|
|
||||||
var outward = DetermineOutwardFlow(world, descriptor.Coordinates);
|
|
||||||
var inward = DetermineInwardFlow(world, descriptor.Coordinates);
|
|
||||||
if (outward.Length != 0 || inward != descriptor.Metadata)
|
|
||||||
{
|
|
||||||
world.SetBlockID(descriptor.Coordinates, WaterBlock.BlockID);
|
|
||||||
ScheduleNextEvent(descriptor.Coordinates, world, server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -271,6 +271,7 @@
|
|||||||
<Compile Include="Windows\CraftingBenchWindow.cs" />
|
<Compile Include="Windows\CraftingBenchWindow.cs" />
|
||||||
<Compile Include="Networking\Packets\CloseWindowPacket.cs" />
|
<Compile Include="Networking\Packets\CloseWindowPacket.cs" />
|
||||||
<Compile Include="Logic\Items\ArmorItem.cs" />
|
<Compile Include="Logic\Items\ArmorItem.cs" />
|
||||||
|
<Compile Include="Logic\Blocks\FluidBlock.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -254,17 +254,21 @@ namespace TrueCraft
|
|||||||
IEntity entity;
|
IEntity entity;
|
||||||
while (PendingDespawns.Count != 0)
|
while (PendingDespawns.Count != 0)
|
||||||
{
|
{
|
||||||
while (!PendingDespawns.TryTake(out entity));
|
while (!PendingDespawns.TryTake(out entity))
|
||||||
|
;
|
||||||
if (entity is IPhysicsEntity)
|
if (entity is IPhysicsEntity)
|
||||||
PhysicsEngine.RemoveEntity((IPhysicsEntity)entity);
|
PhysicsEngine.RemoveEntity((IPhysicsEntity)entity);
|
||||||
for (int i = 0, ServerClientsCount = Server.Clients.Count; i < ServerClientsCount; i++)
|
lock ((Server as MultiplayerServer).ClientLock) // TODO: Thread safe way to iterate over client collection
|
||||||
{
|
{
|
||||||
var client = (RemoteClient)Server.Clients[i];
|
for (int i = 0, ServerClientsCount = Server.Clients.Count; i < ServerClientsCount; i++)
|
||||||
if (client.KnownEntities.Contains(entity) && !client.Disconnected)
|
|
||||||
{
|
{
|
||||||
client.QueuePacket(new DestroyEntityPacket(entity.EntityID));
|
var client = (RemoteClient)Server.Clients[i];
|
||||||
client.KnownEntities.Remove(entity);
|
if (client.KnownEntities.Contains(entity) && !client.Disconnected)
|
||||||
client.Log("Destroying entity {0} ({1})", entity.EntityID, entity.GetType().Name);
|
{
|
||||||
|
client.QueuePacket(new DestroyEntityPacket(entity.EntityID));
|
||||||
|
client.KnownEntities.Remove(entity);
|
||||||
|
client.Log("Destroying entity {0} ({1})", entity.EntityID, entity.GetType().Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lock (EntityLock)
|
lock (EntityLock)
|
||||||
|
@ -28,6 +28,8 @@ namespace TrueCraft.Handlers
|
|||||||
client.QueuePacket(new DisconnectPacket("Server outdated! Use beta 1.7.3."));
|
client.QueuePacket(new DisconnectPacket("Server outdated! Use beta 1.7.3."));
|
||||||
else if (server.Worlds.Count == 0)
|
else if (server.Worlds.Count == 0)
|
||||||
client.QueuePacket(new DisconnectPacket("Server has no worlds configured."));
|
client.QueuePacket(new DisconnectPacket("Server has no worlds configured."));
|
||||||
|
else if (client.Username == "PattyMac7")
|
||||||
|
client.QueuePacket(new DisconnectPacket("Come on dude"));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
client.LoggedIn = true;
|
client.LoggedIn = true;
|
||||||
|
@ -59,7 +59,7 @@ namespace TrueCraft
|
|||||||
private TcpListener Listener;
|
private TcpListener Listener;
|
||||||
private readonly PacketHandler[] PacketHandlers;
|
private readonly PacketHandler[] PacketHandlers;
|
||||||
private IList<ILogProvider> LogProviders;
|
private IList<ILogProvider> LogProviders;
|
||||||
private object ClientLock = new object();
|
internal object ClientLock = new object();
|
||||||
|
|
||||||
public MultiplayerServer()
|
public MultiplayerServer()
|
||||||
{
|
{
|
||||||
@ -215,8 +215,8 @@ namespace TrueCraft
|
|||||||
SendMessage(ChatColor.Yellow + "{0} has left the server.", client.Username);
|
SendMessage(ChatColor.Yellow + "{0} has left the server.", client.Username);
|
||||||
GetEntityManagerForWorld(client.World).DespawnEntity(client.Entity);
|
GetEntityManagerForWorld(client.World).DespawnEntity(client.Entity);
|
||||||
GetEntityManagerForWorld(client.World).FlushDespawns();
|
GetEntityManagerForWorld(client.World).FlushDespawns();
|
||||||
client.Disconnected = true;
|
|
||||||
}
|
}
|
||||||
|
client.Disconnected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AcceptClient(IAsyncResult result)
|
private void AcceptClient(IAsyncResult result)
|
||||||
@ -253,7 +253,8 @@ namespace TrueCraft
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
IPacket packet;
|
IPacket packet;
|
||||||
while (!client.PacketQueue.TryDequeue(out packet)) ;
|
while (!client.PacketQueue.TryDequeue(out packet))
|
||||||
|
;
|
||||||
LogPacket(packet, false);
|
LogPacket(packet, false);
|
||||||
PacketReader.WritePacket(client.MinecraftStream, packet);
|
PacketReader.WritePacket(client.MinecraftStream, packet);
|
||||||
client.MinecraftStream.BaseStream.Flush();
|
client.MinecraftStream.BaseStream.Flush();
|
||||||
@ -293,6 +294,7 @@ namespace TrueCraft
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var packet = PacketReader.ReadPacket(client.MinecraftStream);
|
var packet = PacketReader.ReadPacket(client.MinecraftStream);
|
||||||
|
client.LastSuccessfulPacket = packet;
|
||||||
LogPacket(packet, true);
|
LogPacket(packet, true);
|
||||||
if (PacketHandlers[packet.ID] != null)
|
if (PacketHandlers[packet.ID] != null)
|
||||||
PacketHandlers[packet.ID](packet, client, this);
|
PacketHandlers[packet.ID](packet, client, this);
|
||||||
@ -315,8 +317,12 @@ namespace TrueCraft
|
|||||||
{
|
{
|
||||||
Log(LogCategory.Debug, "Disconnecting client due to exception in network worker");
|
Log(LogCategory.Debug, "Disconnecting client due to exception in network worker");
|
||||||
Log(LogCategory.Debug, e.ToString());
|
Log(LogCategory.Debug, e.ToString());
|
||||||
PacketReader.WritePacket(client.MinecraftStream, new DisconnectPacket("An exception has occured on the server."));
|
try
|
||||||
client.MinecraftStream.BaseStream.Flush();
|
{
|
||||||
|
PacketReader.WritePacket(client.MinecraftStream, new DisconnectPacket("An exception has occured on the server."));
|
||||||
|
client.MinecraftStream.BaseStream.Flush();
|
||||||
|
}
|
||||||
|
catch { /* Silently ignore, by now it's too late */ }
|
||||||
DisconnectClient(client);
|
DisconnectClient(client);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ namespace TrueCraft
|
|||||||
public ItemStack ItemStaging { get; set; }
|
public ItemStack ItemStaging { get; set; }
|
||||||
public IWindow CurrentWindow { get; internal set; }
|
public IWindow CurrentWindow { get; internal set; }
|
||||||
public bool EnableLogging { get; set; }
|
public bool EnableLogging { get; set; }
|
||||||
|
public IPacket LastSuccessfulPacket { get; set; }
|
||||||
|
|
||||||
private IEntity _Entity;
|
private IEntity _Entity;
|
||||||
public IEntity Entity
|
public IEntity Entity
|
||||||
|
Reference in New Issue
Block a user