From c190f83542e34df5c21285c11337ef5622ece9eb Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Tue, 10 Feb 2015 22:35:22 -0700 Subject: [PATCH] Move fluid logic to shared provider and add lava --- TrueCraft.Core/Logic/Blocks/FluidBlock.cs | 309 ++++++++++++++++++++++ TrueCraft.Core/Logic/Blocks/LavaBlock.cs | 23 +- TrueCraft.Core/Logic/Blocks/WaterBlock.cs | 305 +-------------------- TrueCraft.Core/TrueCraft.Core.csproj | 1 + TrueCraft/EntityManager.cs | 18 +- TrueCraft/Handlers/LoginHandlers.cs | 2 + TrueCraft/MultiplayerServer.cs | 16 +- TrueCraft/RemoteClient.cs | 1 + 8 files changed, 362 insertions(+), 313 deletions(-) create mode 100644 TrueCraft.Core/Logic/Blocks/FluidBlock.cs diff --git a/TrueCraft.Core/Logic/Blocks/FluidBlock.cs b/TrueCraft.Core/Logic/Blocks/FluidBlock.cs new file mode 100644 index 0000000..6fe61e2 --- /dev/null +++ b/TrueCraft.Core/Logic/Blocks/FluidBlock.cs @@ -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 + }; + + /// + /// Represents a block that the currently updating fluid block is able to flow outwards into. + /// + protected struct LiquidFlow + { + public LiquidFlow(Coordinates3D targetBlock, byte level) + { + TargetBlock = targetBlock; + Level = level; + } + + /// + /// The block to be filled with fluid. + /// + public Coordinates3D TargetBlock; + /// + /// The fluid level to fill the target block with. + /// + 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)); + } + + /// + /// Examines neighboring blocks and determines the new fluid level that this block should adopt. + /// + 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; + } + + /// + /// Produces a list of outward flow targets that this block may flow towards. + /// + 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(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(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(); + } + + /// + /// Returns true if the given candidate coordinate has a line-of-sight to the given target coordinate. + /// + 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; + } + } +} \ No newline at end of file diff --git a/TrueCraft.Core/Logic/Blocks/LavaBlock.cs b/TrueCraft.Core/Logic/Blocks/LavaBlock.cs index 1cf2b55..11eac8b 100644 --- a/TrueCraft.Core/Logic/Blocks/LavaBlock.cs +++ b/TrueCraft.Core/Logic/Blocks/LavaBlock.cs @@ -3,8 +3,20 @@ using TrueCraft.API.Logic; 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 override byte ID { get { return 0x0A; } } @@ -20,6 +32,15 @@ namespace TrueCraft.Core.Logic.Blocks public override byte LightModifier { get { return 255; } } 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 diff --git a/TrueCraft.Core/Logic/Blocks/WaterBlock.cs b/TrueCraft.Core/Logic/Blocks/WaterBlock.cs index 05f4de0..d5ecbfb 100644 --- a/TrueCraft.Core/Logic/Blocks/WaterBlock.cs +++ b/TrueCraft.Core/Logic/Blocks/WaterBlock.cs @@ -9,46 +9,8 @@ using System.Linq; 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 - }; - - /// - /// Represents a block that the currently updating water block is able to flow outwards into. - /// - protected struct LiquidFlow - { - public LiquidFlow(Coordinates3D targetBlock, byte level) - { - TargetBlock = targetBlock; - Level = level; - } - - /// - /// The block to be filled with water. - /// - public Coordinates3D TargetBlock; - /// - /// The water level to fill the target block with. - /// - public byte Level; - } - public static readonly byte BlockID = 0x08; public override byte ID { get { return 0x08; } } @@ -65,241 +27,13 @@ namespace TrueCraft.Core.Logic.Blocks public override string DisplayName { get { return "Water"; } } - protected override ItemStack[] GetDrop(BlockDescriptor descriptor) - { - return new ItemStack[0]; - } + protected override double SecondsBetweenUpdates { get { return 0.25; } } - public override BoundingBox? BoundingBox - { - get - { - return null; - } - } + protected override byte MaximumFluidDepletion { get { return 7; } } - public void ScheduleNextEvent(Coordinates3D coords, IWorld world, IMultiplayerServer server) - { - server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), (_server) => - AutomataUpdate(_server, world, coords)); - } + protected override byte FlowingID { get { return BlockID; } } - public override void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user) - { - 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)); - } - } - - /// - /// Returns true if the given candidate coordinate has a line-of-sight to the given target coordinate. - /// - 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; - } - - /// - /// Examines neighboring blocks and determines the new water level that this block should adopt. - /// - 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; - } - - /// - /// Produces a list of outward flow targets that this block may flow towards. - /// - 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(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(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; - } + protected override byte StillID { get { return StationaryWaterBlock.BlockID; } } } public class StationaryWaterBlock : WaterBlock @@ -319,34 +53,5 @@ namespace TrueCraft.Core.Logic.Blocks public override bool Opaque { get { return false; } } 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); - } - } } } \ No newline at end of file diff --git a/TrueCraft.Core/TrueCraft.Core.csproj b/TrueCraft.Core/TrueCraft.Core.csproj index a6293d9..a8dd888 100644 --- a/TrueCraft.Core/TrueCraft.Core.csproj +++ b/TrueCraft.Core/TrueCraft.Core.csproj @@ -271,6 +271,7 @@ + diff --git a/TrueCraft/EntityManager.cs b/TrueCraft/EntityManager.cs index 57dab83..98658fa 100644 --- a/TrueCraft/EntityManager.cs +++ b/TrueCraft/EntityManager.cs @@ -254,17 +254,21 @@ namespace TrueCraft IEntity entity; while (PendingDespawns.Count != 0) { - while (!PendingDespawns.TryTake(out entity)); + while (!PendingDespawns.TryTake(out entity)) + ; if (entity is IPhysicsEntity) 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]; - if (client.KnownEntities.Contains(entity) && !client.Disconnected) + for (int i = 0, ServerClientsCount = Server.Clients.Count; i < ServerClientsCount; i++) { - client.QueuePacket(new DestroyEntityPacket(entity.EntityID)); - client.KnownEntities.Remove(entity); - client.Log("Destroying entity {0} ({1})", entity.EntityID, entity.GetType().Name); + var client = (RemoteClient)Server.Clients[i]; + if (client.KnownEntities.Contains(entity) && !client.Disconnected) + { + client.QueuePacket(new DestroyEntityPacket(entity.EntityID)); + client.KnownEntities.Remove(entity); + client.Log("Destroying entity {0} ({1})", entity.EntityID, entity.GetType().Name); + } } } lock (EntityLock) diff --git a/TrueCraft/Handlers/LoginHandlers.cs b/TrueCraft/Handlers/LoginHandlers.cs index 3d71d2f..293af00 100644 --- a/TrueCraft/Handlers/LoginHandlers.cs +++ b/TrueCraft/Handlers/LoginHandlers.cs @@ -28,6 +28,8 @@ namespace TrueCraft.Handlers client.QueuePacket(new DisconnectPacket("Server outdated! Use beta 1.7.3.")); else if (server.Worlds.Count == 0) client.QueuePacket(new DisconnectPacket("Server has no worlds configured.")); + else if (client.Username == "PattyMac7") + client.QueuePacket(new DisconnectPacket("Come on dude")); else { client.LoggedIn = true; diff --git a/TrueCraft/MultiplayerServer.cs b/TrueCraft/MultiplayerServer.cs index 8f7034f..8bc92d9 100644 --- a/TrueCraft/MultiplayerServer.cs +++ b/TrueCraft/MultiplayerServer.cs @@ -59,7 +59,7 @@ namespace TrueCraft private TcpListener Listener; private readonly PacketHandler[] PacketHandlers; private IList LogProviders; - private object ClientLock = new object(); + internal object ClientLock = new object(); public MultiplayerServer() { @@ -215,8 +215,8 @@ namespace TrueCraft SendMessage(ChatColor.Yellow + "{0} has left the server.", client.Username); GetEntityManagerForWorld(client.World).DespawnEntity(client.Entity); GetEntityManagerForWorld(client.World).FlushDespawns(); - client.Disconnected = true; } + client.Disconnected = true; } private void AcceptClient(IAsyncResult result) @@ -253,7 +253,8 @@ namespace TrueCraft try { IPacket packet; - while (!client.PacketQueue.TryDequeue(out packet)) ; + while (!client.PacketQueue.TryDequeue(out packet)) + ; LogPacket(packet, false); PacketReader.WritePacket(client.MinecraftStream, packet); client.MinecraftStream.BaseStream.Flush(); @@ -293,6 +294,7 @@ namespace TrueCraft try { var packet = PacketReader.ReadPacket(client.MinecraftStream); + client.LastSuccessfulPacket = packet; LogPacket(packet, true); if (PacketHandlers[packet.ID] != null) 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, e.ToString()); - PacketReader.WritePacket(client.MinecraftStream, new DisconnectPacket("An exception has occured on the server.")); - client.MinecraftStream.BaseStream.Flush(); + try + { + 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); break; } diff --git a/TrueCraft/RemoteClient.cs b/TrueCraft/RemoteClient.cs index db9905a..6eeb14b 100644 --- a/TrueCraft/RemoteClient.cs +++ b/TrueCraft/RemoteClient.cs @@ -59,6 +59,7 @@ namespace TrueCraft public ItemStack ItemStaging { get; set; } public IWindow CurrentWindow { get; internal set; } public bool EnableLogging { get; set; } + public IPacket LastSuccessfulPacket { get; set; } private IEntity _Entity; public IEntity Entity