diff --git a/TrueCraft.API/Coordinates3D.cs b/TrueCraft.API/Coordinates3D.cs index b835465..f5150a0 100644 --- a/TrueCraft.API/Coordinates3D.cs +++ b/TrueCraft.API/Coordinates3D.cs @@ -14,7 +14,7 @@ namespace TrueCraft.API X = Y = Z = value; } - public Coordinates3D(int x, int y, int z) + public Coordinates3D(int x = 0, int y = 0, int z = 0) { X = x; Y = y; diff --git a/TrueCraft.API/Logic/IBlockProvider.cs b/TrueCraft.API/Logic/IBlockProvider.cs index 4ea085c..43aa8c6 100644 --- a/TrueCraft.API/Logic/IBlockProvider.cs +++ b/TrueCraft.API/Logic/IBlockProvider.cs @@ -20,7 +20,7 @@ namespace TrueCraft.API.Logic bool BlockRightClicked(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user); void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user); void BlockMined(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user); - void BlockUpdate(BlockDescriptor descriptor, IMultiplayerServer server, IWorld world); + void BlockUpdate(BlockDescriptor descriptor, BlockDescriptor source, IMultiplayerServer server, IWorld world); void BlockScheduledEvent(BlockDescriptor descriptor, IWorld world, object data); } } \ No newline at end of file diff --git a/TrueCraft.Core/Logic/BlockProvider.cs b/TrueCraft.Core/Logic/BlockProvider.cs index ad6367f..7ff706b 100644 --- a/TrueCraft.Core/Logic/BlockProvider.cs +++ b/TrueCraft.Core/Logic/BlockProvider.cs @@ -54,7 +54,7 @@ namespace TrueCraft.Core.Logic return true; } - public virtual void BlockUpdate(BlockDescriptor descriptor, IMultiplayerServer server, IWorld world) + public virtual void BlockUpdate(BlockDescriptor descriptor, BlockDescriptor source, IMultiplayerServer server, IWorld world) { if (!IsSupported(descriptor, server, world)) { diff --git a/TrueCraft.Core/Logic/Blocks/BedBlock.cs b/TrueCraft.Core/Logic/Blocks/BedBlock.cs index fc26faa..b4ca601 100644 --- a/TrueCraft.Core/Logic/Blocks/BedBlock.cs +++ b/TrueCraft.Core/Logic/Blocks/BedBlock.cs @@ -83,11 +83,11 @@ namespace TrueCraft.Core.Logic.Blocks return true; } - public override void BlockUpdate(BlockDescriptor descriptor, IMultiplayerServer server, IWorld world) + public override void BlockUpdate(BlockDescriptor descriptor, BlockDescriptor source, IMultiplayerServer server, IWorld world) { if (!ValidBedPosition(descriptor, server.BlockRepository, world)) world.SetBlockID(descriptor.Coordinates, 0); - base.BlockUpdate(descriptor, server, world); + base.BlockUpdate(descriptor, source, server, world); } } } \ No newline at end of file diff --git a/TrueCraft.Core/Logic/Blocks/CropsBlock.cs b/TrueCraft.Core/Logic/Blocks/CropsBlock.cs index 6c35681..a14a477 100644 --- a/TrueCraft.Core/Logic/Blocks/CropsBlock.cs +++ b/TrueCraft.Core/Logic/Blocks/CropsBlock.cs @@ -51,7 +51,7 @@ namespace TrueCraft.Core.Logic.Blocks } } - public override void BlockUpdate(BlockDescriptor descriptor, IMultiplayerServer server, IWorld world) + public override void BlockUpdate(BlockDescriptor descriptor, BlockDescriptor source, IMultiplayerServer server, IWorld world) { if (world.GetBlockID(descriptor.Coordinates + Coordinates3D.Down) != FarmlandBlock.BlockID) { diff --git a/TrueCraft.Core/Logic/Blocks/SugarcaneBlock.cs b/TrueCraft.Core/Logic/Blocks/SugarcaneBlock.cs index a64a2de..9a12ca8 100644 --- a/TrueCraft.Core/Logic/Blocks/SugarcaneBlock.cs +++ b/TrueCraft.Core/Logic/Blocks/SugarcaneBlock.cs @@ -74,7 +74,7 @@ namespace TrueCraft.Core.Logic.Blocks return true; } - public override void BlockUpdate(BlockDescriptor descriptor, IMultiplayerServer server, IWorld world) + public override void BlockUpdate(BlockDescriptor descriptor, BlockDescriptor source, IMultiplayerServer server, IWorld world) { if (!ValidPlacement(descriptor, world)) { diff --git a/TrueCraft.Core/Logic/Blocks/WaterBlock.cs b/TrueCraft.Core/Logic/Blocks/WaterBlock.cs index be65895..48a0be3 100644 --- a/TrueCraft.Core/Logic/Blocks/WaterBlock.cs +++ b/TrueCraft.Core/Logic/Blocks/WaterBlock.cs @@ -5,12 +5,49 @@ using TrueCraft.API.World; using TrueCraft.API; using TrueCraft.API.Networking; using System.Collections.Generic; +using System.Linq; namespace TrueCraft.Core.Logic.Blocks { public class WaterBlock : BlockProvider { - private const byte MaxFlow = 7; + // 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; @@ -28,6 +65,11 @@ namespace TrueCraft.Core.Logic.Blocks public override string DisplayName { get { return "Water"; } } + protected override ItemStack[] GetDrop(BlockDescriptor descriptor) + { + return new ItemStack[0]; + } + public override BoundingBox? BoundingBox { get @@ -36,16 +78,15 @@ namespace TrueCraft.Core.Logic.Blocks } } - private bool PlaceWater(IMultiplayerServer server, Coordinates3D coords, IWorld world, byte meta = 0) + public void ScheduleNextEvent(Coordinates3D coords, IWorld world, IMultiplayerServer server) { - 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; + server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), (_server) => + AutomataUpdate(_server, world, coords)); + } + + 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) @@ -57,200 +98,14 @@ namespace TrueCraft.Core.Logic.Blocks server.BlockUpdatesEnabled = true; if (again) { - server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(0.25), (_server) => + server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(SecondsBetweenUpdates), (_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.BlockRepository.GetBlockProvider(world.GetBlockID(check + coords)); - if (!c.Opaque) - { - 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.BlockRepository.GetBlockProvider(world.GetBlockID(new Coordinates3D(location.X, 0, 0) + coords)); - if (xPotential.Hardness == 0 && xPotential.ID != WaterBlock.BlockID && xPotential.ID != StationaryWaterBlock.BlockID) - { - if (PlaceWater(server, new Coordinates3D(location.X, 0, 0) + coords, world, (byte)(meta + 1))) - { - spread = true; - xPotential.GenerateDropEntity(new BlockDescriptor - { Coordinates = new Coordinates3D(location.X, 0, 0) + coords, ID = xPotential.ID }, world, server); - } - } - - var zPotential = world.BlockRepository.GetBlockProvider(world.GetBlockID(new Coordinates3D(0, 0, location.Z) + coords)); - if (zPotential.Hardness == 0 && zPotential.ID != WaterBlock.BlockID && zPotential.ID != StationaryWaterBlock.BlockID) - { - if (PlaceWater(server, new Coordinates3D(0, 0, location.Z) + coords, world, (byte)(meta + 1))) - { - spread = true; - zPotential.GenerateDropEntity(new BlockDescriptor - { Coordinates = new Coordinates3D(0, 0, location.Z) + coords, ID = zPotential.ID }, world, server); - } - } - } - if (!spread) - { - world.SetBlockID(coords, StationaryWaterBlock.BlockID); - return false; - } - } - return true; - } - + /// + /// 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; @@ -272,21 +127,184 @@ namespace TrueCraft.Core.Logic.Blocks return true; } - public void ScheduleNextEvent(Coordinates3D coords, IWorld world, IMultiplayerServer server) + /// + /// Examines neighboring blocks and determines the new water level that this block should adopt. + /// + protected byte DetermineInwardFlow(IWorld world, Coordinates3D coords) { - server.Scheduler.ScheduleEvent(DateTime.Now.AddSeconds(0.25), (_server) => - AutomataUpdate(_server, world, 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; } - public override void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user) + /// + /// Produces a list of outward flow targets that this block may flow towards. + /// + protected LiquidFlow[] DetermineOutwardFlow(IWorld world, Coordinates3D coords) { - ScheduleNextEvent(descriptor.Coordinates, world, user.Server); + // 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) + { + 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) + { + world.SetBlockID(coords, StationaryWaterBlock.BlockID); + return false; + } + return true; } } - public class StationaryWaterBlock : BlockProvider + public class StationaryWaterBlock : WaterBlock { - public static readonly byte BlockID = 0x09; + public static readonly new byte BlockID = 0x09; public override byte ID { get { return 0x09; } } @@ -302,6 +320,11 @@ namespace TrueCraft.Core.Logic.Blocks public override byte LightModifier { get { return 3; } } + protected override ItemStack[] GetDrop(BlockDescriptor descriptor) + { + return new ItemStack[0]; + } + public override BoundingBox? BoundingBox { get @@ -310,13 +333,20 @@ namespace TrueCraft.Core.Logic.Blocks } } - public override void BlockUpdate(BlockDescriptor descriptor, IMultiplayerServer server, IWorld world) + public override void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user) { - var provider = server.BlockRepository.GetBlockProvider(WaterBlock.BlockID) as WaterBlock; - if (provider.CanFlow(world, descriptor.Coordinates)) + // This space intentionally left blank + } + + public override void BlockUpdate(BlockDescriptor descriptor, BlockDescriptor source, IMultiplayerServer server, IWorld world) + { + if (source.ID == StationaryWaterBlock.BlockID || source.ID == WaterBlock.BlockID) + return; + var outward = DetermineOutwardFlow(world, descriptor.Coordinates); + if (outward.Length != 0) { - world.SetBlockID(descriptor.Coordinates, provider.ID); - provider.ScheduleNextEvent(descriptor.Coordinates, world, server); + world.SetBlockID(descriptor.Coordinates, WaterBlock.BlockID); + ScheduleNextEvent(descriptor.Coordinates, world, server); } } } diff --git a/TrueCraft/MultiplayerServer.cs b/TrueCraft/MultiplayerServer.cs index 72e4fe4..8f7034f 100644 --- a/TrueCraft/MultiplayerServer.cs +++ b/TrueCraft/MultiplayerServer.cs @@ -142,12 +142,13 @@ namespace TrueCraft while (PendingBlockUpdates.Count != 0) { var update = PendingBlockUpdates.Dequeue(); + var source = update.World.GetBlockData(update.Coordinates); foreach (var offset in adjacent) { var descriptor = update.World.GetBlockData(update.Coordinates + offset); var provider = BlockRepository.GetBlockProvider(descriptor.ID); if (provider != null) - provider.BlockUpdate(descriptor, this, update.World); + provider.BlockUpdate(descriptor, source, this, update.World); } } } @@ -284,7 +285,9 @@ namespace TrueCraft Clients.RemoveAt(i); break; } - while (client.DataAvailable) + const long maxTicks = 100000 * 200; // 200ms + var start = DateTime.Now; + while (client.DataAvailable && (DateTime.Now.Ticks - start.Ticks) < maxTicks) { idle = false; try