Mostly implement fluid dynamics with water
This commit is contained in:
parent
a48f243e91
commit
e9b133a7f8
@ -39,6 +39,21 @@ namespace TrueCraft.API
|
|||||||
|
|
||||||
#region Math
|
#region Math
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clamps the coordinates to within the specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value.</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the distance between two Coordinates3D objects.
|
/// Calculates the distance between two Coordinates3D objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -222,6 +237,10 @@ namespace TrueCraft.API
|
|||||||
public static readonly Coordinates3D North = new Coordinates3D(0, 0, -1);
|
public static readonly Coordinates3D North = new Coordinates3D(0, 0, -1);
|
||||||
public static readonly Coordinates3D South = 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
|
#endregion
|
||||||
|
|
||||||
public bool Equals(Coordinates3D other)
|
public bool Equals(Coordinates3D other)
|
||||||
|
@ -2,11 +2,16 @@ using System;
|
|||||||
using TrueCraft.API.Logic;
|
using TrueCraft.API.Logic;
|
||||||
using TrueCraft.API.Server;
|
using TrueCraft.API.Server;
|
||||||
using TrueCraft.API.World;
|
using TrueCraft.API.World;
|
||||||
|
using TrueCraft.API;
|
||||||
|
using TrueCraft.API.Networking;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace TrueCraft.Core.Logic.Blocks
|
namespace TrueCraft.Core.Logic.Blocks
|
||||||
{
|
{
|
||||||
public class WaterBlock : BlockProvider
|
public class WaterBlock : BlockProvider
|
||||||
{
|
{
|
||||||
|
private const byte MaxFlow = 7;
|
||||||
|
|
||||||
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; } }
|
||||||
@ -23,18 +28,288 @@ namespace TrueCraft.Core.Logic.Blocks
|
|||||||
|
|
||||||
public override string DisplayName { get { return "Water"; } }
|
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<Coordinates3D>();
|
||||||
|
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<Coordinates3D>();
|
||||||
|
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 byte ID { get { return 0x09; } }
|
||||||
|
|
||||||
public override string DisplayName { get { return "Water (stationary)"; } }
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,7 +26,7 @@ namespace TrueCraft.Core.Logic.Items
|
|||||||
if (block == WaterBlock.BlockID || block == StationaryWaterBlock.BlockID)
|
if (block == WaterBlock.BlockID || block == StationaryWaterBlock.BlockID)
|
||||||
{
|
{
|
||||||
var meta = world.GetMetadata(coordinates);
|
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);
|
user.Inventory[user.SelectedSlot] = new ItemStack(WaterBucketItem.ItemID);
|
||||||
world.SetBlockID(coordinates, 0);
|
world.SetBlockID(coordinates, 0);
|
||||||
@ -35,7 +35,7 @@ namespace TrueCraft.Core.Logic.Items
|
|||||||
else if (block == LavaBlock.BlockID || block == StationaryLavaBlock.BlockID)
|
else if (block == LavaBlock.BlockID || block == StationaryLavaBlock.BlockID)
|
||||||
{
|
{
|
||||||
var meta = world.GetMetadata(coordinates);
|
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);
|
user.Inventory[user.SelectedSlot] = new ItemStack(LavaBucketItem.ItemID);
|
||||||
world.SetBlockID(coordinates, 0);
|
world.SetBlockID(coordinates, 0);
|
||||||
@ -52,8 +52,10 @@ namespace TrueCraft.Core.Logic.Items
|
|||||||
var blockType = RelevantBlockType.Value;
|
var blockType = RelevantBlockType.Value;
|
||||||
user.Server.BlockUpdatesEnabled = false;
|
user.Server.BlockUpdatesEnabled = false;
|
||||||
world.SetBlockID(coordinates, blockType);
|
world.SetBlockID(coordinates, blockType);
|
||||||
world.SetMetadata(coordinates, 15); // Source block
|
world.SetMetadata(coordinates, 0); // Source block
|
||||||
user.Server.BlockUpdatesEnabled = true;
|
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);
|
user.Inventory[user.SelectedSlot] = new ItemStack(BucketItem.ItemID);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace TrueCraft.Core.TerrainGen
|
|||||||
public FlatlandGenerator()
|
public FlatlandGenerator()
|
||||||
{
|
{
|
||||||
GeneratorOptions = DefaultGeneratorOptions;
|
GeneratorOptions = DefaultGeneratorOptions;
|
||||||
SpawnPoint = new Vector3(0, 4, 0);
|
SpawnPoint = new Vector3(0, 5, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FlatlandGenerator(string generatorOptions)
|
public FlatlandGenerator(string generatorOptions)
|
||||||
|
@ -287,39 +287,35 @@ namespace TrueCraft
|
|||||||
while (client.DataAvailable)
|
while (client.DataAvailable)
|
||||||
{
|
{
|
||||||
idle = false;
|
idle = false;
|
||||||
var packet = PacketReader.ReadPacket(client.MinecraftStream);
|
try
|
||||||
LogPacket(packet, true);
|
|
||||||
if (PacketHandlers[packet.ID] != null)
|
|
||||||
{
|
{
|
||||||
try
|
var packet = PacketReader.ReadPacket(client.MinecraftStream);
|
||||||
{
|
LogPacket(packet, true);
|
||||||
|
if (PacketHandlers[packet.ID] != null)
|
||||||
PacketHandlers[packet.ID](packet, client, this);
|
PacketHandlers[packet.ID](packet, client, this);
|
||||||
}
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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)
|
if (idle)
|
||||||
|
Reference in New Issue
Block a user