From e9b133a7f848963eddc4de7966881eddebbfae8b Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Mon, 9 Feb 2015 22:41:31 -0700 Subject: [PATCH] Mostly implement fluid dynamics with water --- TrueCraft.API/Coordinates3D.cs | 19 ++ TrueCraft.Core/Logic/Blocks/WaterBlock.cs | 283 +++++++++++++++++- TrueCraft.Core/Logic/Items/BucketItem.cs | 8 +- .../TerrainGen/FlatlandGenerator.cs | 2 +- TrueCraft/MultiplayerServer.cs | 54 ++-- 5 files changed, 329 insertions(+), 37 deletions(-) diff --git a/TrueCraft.API/Coordinates3D.cs b/TrueCraft.API/Coordinates3D.cs index 84d62a2..b835465 100644 --- a/TrueCraft.API/Coordinates3D.cs +++ b/TrueCraft.API/Coordinates3D.cs @@ -39,6 +39,21 @@ namespace TrueCraft.API #region Math + /// + /// Clamps the coordinates to within the specified value. + /// + /// Value. + public void Clamp(int value) + { + // TODO: Fix for negative values + if (Math.Abs(X) > value) + X = value * (X < 0 ? -1 : 1); + if (Math.Abs(Y) > value) + Y = value * (Y < 0 ? -1 : 1); + if (Math.Abs(Z) > value) + Z = value * (Z < 0 ? -1 : 1); + } + /// /// Calculates the distance between two Coordinates3D objects. /// @@ -222,6 +237,10 @@ namespace TrueCraft.API public static readonly Coordinates3D North = new Coordinates3D(0, 0, -1); public static readonly Coordinates3D South = new Coordinates3D(0, 0, 1); + public static readonly Coordinates3D OneX = new Coordinates3D(1, 0, 0); + public static readonly Coordinates3D OneY = new Coordinates3D(0, 1, 0); + public static readonly Coordinates3D OneZ = new Coordinates3D(0, 0, 1); + #endregion public bool Equals(Coordinates3D other) diff --git a/TrueCraft.Core/Logic/Blocks/WaterBlock.cs b/TrueCraft.Core/Logic/Blocks/WaterBlock.cs index e5f7c6f..646d5c2 100644 --- a/TrueCraft.Core/Logic/Blocks/WaterBlock.cs +++ b/TrueCraft.Core/Logic/Blocks/WaterBlock.cs @@ -2,11 +2,16 @@ using System; using TrueCraft.API.Logic; using TrueCraft.API.Server; using TrueCraft.API.World; +using TrueCraft.API; +using TrueCraft.API.Networking; +using System.Collections.Generic; namespace TrueCraft.Core.Logic.Blocks { public class WaterBlock : BlockProvider { + private const byte MaxFlow = 7; + public static readonly byte BlockID = 0x08; public override byte ID { get { return 0x08; } } @@ -23,18 +28,288 @@ namespace TrueCraft.Core.Logic.Blocks public override string DisplayName { get { return "Water"; } } - public override void BlockUpdate(BlockDescriptor descriptor, IMultiplayerServer server, IWorld world) + public override BoundingBox? BoundingBox { - base.BlockUpdate(descriptor, server, world); + get + { + return null; + } + } + + private bool PlaceWater(IMultiplayerServer server, Coordinates3D coords, IWorld world, byte meta = 0) + { + var old = world.GetBlockID(coords); + if (old == WaterBlock.BlockID || old == StationaryWaterBlock.BlockID) + return false; + world.SetBlockID(coords, BlockID); + world.SetMetadata(coords, meta); + server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(0.25), (s) => + AutomataUpdate(s, world, coords)); + return true; + } + + 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(0.25), (_server) => + DoAutomata(_server, world, coords)); + } + } + + internal bool CanFlow(IWorld world, Coordinates3D coords) + { + var down = world.BlockRepository.GetBlockProvider(world.GetBlockID(coords + Coordinates3D.Down)); + if (!down.Opaque) + return true; + const int maxDistance = 5; + var extraLocations = new List(); + var nearest = new Coordinates3D(maxDistance + 1, -1, maxDistance + 1); + for (int x = -maxDistance; x < maxDistance; x++) + { + for (int z = -maxDistance; z < maxDistance; z++) + { + if (Math.Abs(z) + Math.Abs(x) > maxDistance) + continue; + var check = new Coordinates3D(x, -1, z); + var c = world.GetBlockID(check + coords); + if (c == 0 || c == WaterBlock.BlockID || c == StationaryWaterBlock.BlockID) + { + if (!LineOfSight(world, check + coords, coords)) + continue; + if (coords.DistanceTo(check + coords) == coords.DistanceTo(nearest + coords)) + extraLocations.Add(check); + if (coords.DistanceTo(check + coords) < coords.DistanceTo(nearest + coords)) + { + extraLocations.Clear(); + nearest = check; + } + } + } + } + if (nearest == new Coordinates3D(maxDistance + 1, -1, maxDistance + 1)) + { + extraLocations.Add(new Coordinates3D(-maxDistance - 1, -1, maxDistance + 1)); + extraLocations.Add(new Coordinates3D(maxDistance + 1, -1, -maxDistance - 1)); + extraLocations.Add(new Coordinates3D(-maxDistance - 1, -1, -maxDistance - 1)); + } + extraLocations.Add(nearest); + bool spread = false; + for (int i = 0; i < extraLocations.Count; i++) + { + var location = extraLocations[i]; + location.Clamp(1); + var xPotential = world.GetBlockID(new Coordinates3D(location.X, 0, 0) + coords); + if (xPotential == 0) + { + var old = world.GetBlockID(coords); + return old != WaterBlock.BlockID && old != StationaryWaterBlock.BlockID; + } + + var zPotential = world.GetBlockID(new Coordinates3D(0, 0, location.Z) + coords); + if (zPotential == 0) + { + var old = world.GetBlockID(coords); + return old != WaterBlock.BlockID && old != StationaryWaterBlock.BlockID; + } + } + return spread; + } + + public bool DoAutomata(IMultiplayerServer server, IWorld world, Coordinates3D coords) + { + var meta = world.GetMetadata(coords); + Coordinates3D[] neighbors = + { + Coordinates3D.Left, + Coordinates3D.Right, + Coordinates3D.Forwards, + Coordinates3D.Backwards + }; + var up = world.GetBlockID(coords + Coordinates3D.Up); + var down = world.BlockRepository.GetBlockProvider(world.GetBlockID(coords + Coordinates3D.Down)); + + if (!down.Opaque) + { + PlaceWater(server, coords + Coordinates3D.Down, world, 1); + if (meta != 0) + return true; + } + + // Check inward flow + if (up == WaterBlock.BlockID || up == StationaryWaterBlock.BlockID) + meta = 1; + else + { + if (meta != 0) + { + byte minMeta = 15; + int sources = 0; + for (int i = 0; i < neighbors.Length; i++) + { + var nId = world.GetBlockID(coords + neighbors[i]); + if (nId == WaterBlock.BlockID || nId == StationaryWaterBlock.BlockID) + { + var _meta = world.GetMetadata(coords + neighbors[i]); + if (_meta < minMeta) + minMeta = _meta; + if (_meta == 0) + sources++; + } + } + if (sources >= 2) + { + world.SetMetadata(coords, 0); + return true; + } + if (minMeta > 0) + { + meta = (byte)(minMeta + 1); + if (meta >= MaxFlow + 1) + { + world.SetBlockID(coords, 0); + return true; + } + } + } + } + world.SetMetadata(coords, meta); + + // Check outward flow + if (meta < MaxFlow) + { + const int maxDistance = 5; + var extraLocations = new List(); + var nearest = new Coordinates3D(maxDistance + 1, -1, maxDistance + 1); + for (int x = -maxDistance; x < maxDistance; x++) + { + for (int z = -maxDistance; z < maxDistance; z++) + { + if (Math.Abs(z) + Math.Abs(x) > maxDistance) + continue; + var check = new Coordinates3D(x, -1, z); + var c = world.GetBlockID(check + coords); + if (c == 0 || c == WaterBlock.BlockID || c == StationaryWaterBlock.BlockID) + { + if (!LineOfSight(world, check + coords, coords)) + continue; + if (coords.DistanceTo(check + coords) == coords.DistanceTo(nearest + coords)) + extraLocations.Add(check); + if (coords.DistanceTo(check + coords) < coords.DistanceTo(nearest + coords)) + { + extraLocations.Clear(); + nearest = check; + } + } + } + } + if (nearest == new Coordinates3D(maxDistance + 1, -1, maxDistance + 1)) + { + extraLocations.Add(new Coordinates3D(-maxDistance - 1, -1, maxDistance + 1)); + extraLocations.Add(new Coordinates3D(maxDistance + 1, -1, -maxDistance - 1)); + extraLocations.Add(new Coordinates3D(-maxDistance - 1, -1, -maxDistance - 1)); + } + extraLocations.Add(nearest); + bool spread = false; + for (int i = 0; i < extraLocations.Count; i++) + { + var location = extraLocations[i]; + location.Clamp(1); + var xPotential = world.GetBlockID(new Coordinates3D(location.X, 0, 0) + coords); + if (xPotential == 0) + { + if (PlaceWater(server, new Coordinates3D(location.X, 0, 0) + coords, world, (byte)(meta + 1))) + spread = true; + } + + var zPotential = world.GetBlockID(new Coordinates3D(0, 0, location.Z) + coords); + if (zPotential == 0) + { + if (PlaceWater(server, new Coordinates3D(0, 0, location.Z) + coords, world, (byte)(meta + 1))) + spread = true; + } + } + if (!spread) + { + world.SetBlockID(coords, StationaryWaterBlock.BlockID); + return false; + } + } + return true; + } + + 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; + } + + public void ScheduleNextEvent(Coordinates3D coords, IWorld world, IMultiplayerServer server) + { + server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(0.25), (_server) => + AutomataUpdate(_server, world, coords)); + } + + public override void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user) + { + ScheduleNextEvent(descriptor.Coordinates, world, user.Server); } } - public class StationaryWaterBlock : WaterBlock + public class StationaryWaterBlock : BlockProvider { - public static readonly new byte BlockID = 0x09; + public static readonly byte BlockID = 0x09; public override byte ID { get { return 0x09; } } public override string DisplayName { get { return "Water (stationary)"; } } + + public override double BlastResistance { get { return 500; } } + + public override double Hardness { get { return 100; } } + + public override byte Luminance { get { return 0; } } + + public override bool Opaque { get { return false; } } + + public override byte LightModifier { get { return 3; } } + + public override BoundingBox? BoundingBox + { + get + { + return null; + } + } + + public override void BlockUpdate(BlockDescriptor descriptor, IMultiplayerServer server, IWorld world) + { + var provider = server.BlockRepository.GetBlockProvider(WaterBlock.BlockID) as WaterBlock; + if (provider.CanFlow(world, descriptor.Coordinates)) + { + world.SetBlockID(descriptor.Coordinates, provider.ID); + provider.ScheduleNextEvent(descriptor.Coordinates, world, server); + } + } } } \ No newline at end of file diff --git a/TrueCraft.Core/Logic/Items/BucketItem.cs b/TrueCraft.Core/Logic/Items/BucketItem.cs index 3a101ef..078b531 100644 --- a/TrueCraft.Core/Logic/Items/BucketItem.cs +++ b/TrueCraft.Core/Logic/Items/BucketItem.cs @@ -26,7 +26,7 @@ namespace TrueCraft.Core.Logic.Items if (block == WaterBlock.BlockID || block == StationaryWaterBlock.BlockID) { var meta = world.GetMetadata(coordinates); - if (meta == 15) // Is source block? + if (meta == 0) // Is source block? { user.Inventory[user.SelectedSlot] = new ItemStack(WaterBucketItem.ItemID); world.SetBlockID(coordinates, 0); @@ -35,7 +35,7 @@ namespace TrueCraft.Core.Logic.Items else if (block == LavaBlock.BlockID || block == StationaryLavaBlock.BlockID) { var meta = world.GetMetadata(coordinates); - if (meta == 15) // Is source block? + if (meta == 0) // Is source block? { user.Inventory[user.SelectedSlot] = new ItemStack(LavaBucketItem.ItemID); world.SetBlockID(coordinates, 0); @@ -52,8 +52,10 @@ namespace TrueCraft.Core.Logic.Items var blockType = RelevantBlockType.Value; user.Server.BlockUpdatesEnabled = false; world.SetBlockID(coordinates, blockType); - world.SetMetadata(coordinates, 15); // Source block + world.SetMetadata(coordinates, 0); // Source block user.Server.BlockUpdatesEnabled = true; + var liquidProvider = world.BlockRepository.GetBlockProvider(blockType); + liquidProvider.BlockPlaced(new BlockDescriptor { Coordinates = coordinates }, face, world, user); } user.Inventory[user.SelectedSlot] = new ItemStack(BucketItem.ItemID); } diff --git a/TrueCraft.Core/TerrainGen/FlatlandGenerator.cs b/TrueCraft.Core/TerrainGen/FlatlandGenerator.cs index 0e833a0..84d2705 100644 --- a/TrueCraft.Core/TerrainGen/FlatlandGenerator.cs +++ b/TrueCraft.Core/TerrainGen/FlatlandGenerator.cs @@ -19,7 +19,7 @@ namespace TrueCraft.Core.TerrainGen public FlatlandGenerator() { GeneratorOptions = DefaultGeneratorOptions; - SpawnPoint = new Vector3(0, 4, 0); + SpawnPoint = new Vector3(0, 5, 0); } public FlatlandGenerator(string generatorOptions) diff --git a/TrueCraft/MultiplayerServer.cs b/TrueCraft/MultiplayerServer.cs index f004c4d..72e4fe4 100644 --- a/TrueCraft/MultiplayerServer.cs +++ b/TrueCraft/MultiplayerServer.cs @@ -287,39 +287,35 @@ namespace TrueCraft while (client.DataAvailable) { idle = false; - var packet = PacketReader.ReadPacket(client.MinecraftStream); - LogPacket(packet, true); - if (PacketHandlers[packet.ID] != null) + try { - try - { + var packet = PacketReader.ReadPacket(client.MinecraftStream); + LogPacket(packet, true); + if (PacketHandlers[packet.ID] != null) PacketHandlers[packet.ID](packet, client, this); - } - catch (PlayerDisconnectException) - { - DisconnectClient(client); - break; - } - catch (SocketException e) - { - Log(LogCategory.Debug, "Disconnecting client due to exception in network worker"); - Log(LogCategory.Debug, e.ToString()); - DisconnectClient(client); - break; - } - catch (Exception e) - { - Log(LogCategory.Debug, "Disconnecting client due to exception in network worker"); - Log(LogCategory.Debug, e.ToString()); - PacketReader.WritePacket(client.MinecraftStream, new DisconnectPacket("An exception has occured on the server.")); - client.MinecraftStream.BaseStream.Flush(); - DisconnectClient(client); - break; - } + else + client.Log("Unhandled packet {0}", packet.GetType().Name); } - else + catch (PlayerDisconnectException) { - client.Log("Unhandled packet {0}", packet.GetType().Name); + DisconnectClient(client); + break; + } + catch (SocketException e) + { + Log(LogCategory.Debug, "Disconnecting client due to exception in network worker"); + Log(LogCategory.Debug, e.ToString()); + DisconnectClient(client); + break; + } + catch (Exception e) + { + Log(LogCategory.Debug, "Disconnecting client due to exception in network worker"); + Log(LogCategory.Debug, e.ToString()); + PacketReader.WritePacket(client.MinecraftStream, new DisconnectPacket("An exception has occured on the server.")); + client.MinecraftStream.BaseStream.Flush(); + DisconnectClient(client); + break; } } if (idle)