diff --git a/TrueCraft.API/Entities/IMobEntity.cs b/TrueCraft.API/Entities/IMobEntity.cs index 563ae78..d15e133 100644 --- a/TrueCraft.API/Entities/IMobEntity.cs +++ b/TrueCraft.API/Entities/IMobEntity.cs @@ -1,4 +1,5 @@ using System; +using TrueCraft.API.AI; using TrueCraft.API.Physics; namespace TrueCraft.API.Entities @@ -8,6 +9,7 @@ namespace TrueCraft.API.Entities event EventHandler PathComplete; PathResult CurrentPath { get; set; } bool AdvancePath(TimeSpan time, bool faceRoute = true); + IMobState CurrentState { get; set; } void Face(Vector3 target); } } diff --git a/TrueCraft.API/Server/IEntityManager.cs b/TrueCraft.API/Server/IEntityManager.cs index 72b7cc5..26b5a23 100644 --- a/TrueCraft.API/Server/IEntityManager.cs +++ b/TrueCraft.API/Server/IEntityManager.cs @@ -2,11 +2,13 @@ using TrueCraft.API.Entities; using System.Collections.Generic; using TrueCraft.API.Networking; +using TrueCraft.API.World; namespace TrueCraft.API.Server { public interface IEntityManager { + IWorld World { get; } TimeSpan TimeSinceLastUpdate { get; } /// /// Adds an entity to the world and assigns it an entity ID. diff --git a/TrueCraft.API/World/IBiomeMap.cs b/TrueCraft.API/World/IBiomeMap.cs index d3db2b5..06333d6 100644 --- a/TrueCraft.API/World/IBiomeMap.cs +++ b/TrueCraft.API/World/IBiomeMap.cs @@ -22,7 +22,7 @@ namespace TrueCraft.API.World IList BiomeCells { get; } void AddCell(BiomeCell cell); byte GetBiome(Coordinates2D location); - byte GenerateBiome(int seed, IBiomeRepository biomes, Coordinates2D location); + byte GenerateBiome(int seed, IBiomeRepository biomes, Coordinates2D location, bool spawn); BiomeCell ClosestCell(Coordinates2D location); double ClosestCellPoint(Coordinates2D location); } diff --git a/TrueCraft.API/World/IBiomeProvider.cs b/TrueCraft.API/World/IBiomeProvider.cs index 7a5ee20..0cda4f1 100644 --- a/TrueCraft.API/World/IBiomeProvider.cs +++ b/TrueCraft.API/World/IBiomeProvider.cs @@ -8,6 +8,7 @@ namespace TrueCraft.API.World { public interface IBiomeProvider { + bool Spawn { get; } byte ID { get; } int Elevation { get; } double Temperature { get; } diff --git a/TrueCraft.API/World/IBiomeRepository.cs b/TrueCraft.API/World/IBiomeRepository.cs index 8f5070b..1ac8bd4 100644 --- a/TrueCraft.API/World/IBiomeRepository.cs +++ b/TrueCraft.API/World/IBiomeRepository.cs @@ -8,7 +8,7 @@ namespace TrueCraft.API.World public interface IBiomeRepository { IBiomeProvider GetBiome(byte id); - IBiomeProvider GetBiome(double temperature, double rainfall); + IBiomeProvider GetBiome(double temperature, double rainfall, bool spawn); void RegisterBiomeProvider(IBiomeProvider provider); } } \ No newline at end of file diff --git a/TrueCraft.Core.Test/Logic/BlockProviderTest.cs b/TrueCraft.Core.Test/Logic/BlockProviderTest.cs index 0decf27..e69b0e6 100644 --- a/TrueCraft.Core.Test/Logic/BlockProviderTest.cs +++ b/TrueCraft.Core.Test/Logic/BlockProviderTest.cs @@ -23,7 +23,7 @@ namespace TrueCraft.Core.Test.Logic public Mock User { get; set; } public Mock BlockRepository { get; set; } - [TestFixtureSetUp] + [OneTimeSetUp] public void SetUp() { World = new Mock(); diff --git a/TrueCraft.Core.Test/World/ChunkTest.cs b/TrueCraft.Core.Test/World/ChunkTest.cs index 7c4b5a9..64caa3b 100644 --- a/TrueCraft.Core.Test/World/ChunkTest.cs +++ b/TrueCraft.Core.Test/World/ChunkTest.cs @@ -14,7 +14,7 @@ namespace TrueCraft.Core.Test.World { public Chunk Chunk { get; set; } - [TestFixtureSetUp] + [OneTimeSetUp] public void SetUp() { var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); diff --git a/TrueCraft.Core.Test/World/RegionTest.cs b/TrueCraft.Core.Test/World/RegionTest.cs index 6e4a266..5d447bb 100644 --- a/TrueCraft.Core.Test/World/RegionTest.cs +++ b/TrueCraft.Core.Test/World/RegionTest.cs @@ -12,7 +12,7 @@ namespace TrueCraft.Core.Test.World { public Region Region { get; set; } - [TestFixtureSetUp] + [OneTimeSetUp] public void SetUp() { var world = new TrueCraft.Core.World.World(); diff --git a/TrueCraft.Core.Test/World/WorldTest.cs b/TrueCraft.Core.Test/World/WorldTest.cs index 7459116..c867dff 100644 --- a/TrueCraft.Core.Test/World/WorldTest.cs +++ b/TrueCraft.Core.Test/World/WorldTest.cs @@ -14,7 +14,7 @@ namespace TrueCraft.Core.Test.World { public TrueCraft.Core.World.World World { get; set; } - [TestFixtureSetUp] + [OneTimeSetUp] public void SetUp() { var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); diff --git a/TrueCraft.Core/AI/AStarPathFinder.cs b/TrueCraft.Core/AI/AStarPathFinder.cs index c1817be..42edf9c 100644 --- a/TrueCraft.Core/AI/AStarPathFinder.cs +++ b/TrueCraft.Core/AI/AStarPathFinder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using TrueCraft.API; using TrueCraft.API.World; using System.Diagnostics; +using TrueCraft.API.Logic; namespace TrueCraft.Core.AI { @@ -40,8 +41,12 @@ namespace TrueCraft.Core.AI private bool CanOccupyVoxel(IWorld world, BoundingBox box, Coordinates3D voxel) { var id = world.GetBlockID(voxel); - // TODO: Make this more sophisticated - return id == 0; + if (world.BlockRepository == null) + return id == 0; + var provider = world.BlockRepository.GetBlockProvider(id); + if (provider == null) + return true; + return provider.BoundingBox == null; } private IEnumerable GetNeighbors(IWorld world, BoundingBox subject, Coordinates3D current) diff --git a/TrueCraft.Core/AI/IdleState.cs b/TrueCraft.Core/AI/IdleState.cs new file mode 100644 index 0000000..451c7e5 --- /dev/null +++ b/TrueCraft.Core/AI/IdleState.cs @@ -0,0 +1,28 @@ +using System; +using TrueCraft.API.AI; +using TrueCraft.API.Entities; +using TrueCraft.API.Server; + +namespace TrueCraft.Core.AI +{ + public class IdleState : IMobState + { + private DateTime Expiry { get; set; } + private IMobState NextState { get; set; } + + public IdleState(IMobState nextState, DateTime? expiry = null) + { + NextState = nextState; + if (expiry != null) + Expiry = expiry.Value; + else + Expiry = DateTime.UtcNow.AddSeconds(MathHelper.Random.Next(5, 15)); + } + + public void Update(IMobEntity entity, IEntityManager manager) + { + if (DateTime.UtcNow >= Expiry) + entity.CurrentState = NextState; + } + } +} diff --git a/TrueCraft.Core/AI/WanderState.cs b/TrueCraft.Core/AI/WanderState.cs index 5ba7630..db2d839 100644 --- a/TrueCraft.Core/AI/WanderState.cs +++ b/TrueCraft.Core/AI/WanderState.cs @@ -5,16 +5,12 @@ using TrueCraft.API.Server; using TrueCraft.API; using TrueCraft.API.World; using System.Threading.Tasks; +using TrueCraft.API.Logic; namespace TrueCraft.Core.AI { public class WanderState : IMobState { - /// - /// Chance that mob will decide to move during an update when idle. - /// Chance is equal to 1 / IdleChance. - /// - public int IdleChance { get; set; } /// /// The maximum distance the mob will move in an iteration. /// @@ -24,7 +20,6 @@ namespace TrueCraft.Core.AI public WanderState() { - IdleChance = 10; Distance = 25; PathFinder = new AStarPathFinder(); } @@ -33,25 +28,27 @@ namespace TrueCraft.Core.AI { var cast = entity as IEntity; if (entity.CurrentPath != null) - entity.AdvancePath(manager.TimeSinceLastUpdate); + { + if (entity.AdvancePath(manager.TimeSinceLastUpdate)) + { + entity.CurrentState = new IdleState(new WanderState()); + } + } else { - if (MathHelper.Random.Next(IdleChance) == 0) + var target = new Coordinates3D( + (int)(cast.Position.X + (MathHelper.Random.Next(Distance) - Distance / 2)), + 0, + (int)(cast.Position.Z + (MathHelper.Random.Next(Distance) - Distance / 2)) + ); + IChunk chunk; + var adjusted = entity.World.FindBlockPosition(target, out chunk, generate: false); + target.Y = chunk.GetHeight((byte)adjusted.X, (byte)adjusted.Z) + 1; + Task.Factory.StartNew(() => { - var target = new Coordinates3D( - (int)(cast.Position.X + (MathHelper.Random.Next(Distance) - Distance / 2)), - 0, - (int)(cast.Position.Z + (MathHelper.Random.Next(Distance) - Distance / 2)) - ); - IChunk chunk; - var adjusted = entity.World.FindBlockPosition(target, out chunk, generate: false); - target.Y = chunk.GetHeight((byte)adjusted.X, (byte)adjusted.Z) + 1; - Task.Factory.StartNew(() => - { - entity.CurrentPath = PathFinder.FindPath(entity.World, entity.BoundingBox, - (Coordinates3D)cast.Position, target); - }); - } + entity.CurrentPath = PathFinder.FindPath(entity.World, entity.BoundingBox, + (Coordinates3D)cast.Position, target); + }); } } } diff --git a/TrueCraft.Core/Entities/MobEntity.cs b/TrueCraft.Core/Entities/MobEntity.cs index 63bc122..4319b34 100644 --- a/TrueCraft.Core/Entities/MobEntity.cs +++ b/TrueCraft.Core/Entities/MobEntity.cs @@ -97,11 +97,6 @@ namespace TrueCraft.Core.Entities public IMobState CurrentState { get; set; } - public void ChangeState(IMobState state) - { - CurrentState = state; - } - public void Face(Vector3 target) { var diff = target - Position; @@ -115,14 +110,17 @@ namespace TrueCraft.Core.Entities { // Advance along path var target = (Vector3)CurrentPath.Waypoints[CurrentPath.Index]; + target += new Vector3(Size.Width / 2, 0, Size.Depth / 2); // Center it target.Y = Position.Y; // TODO: Find better way of doing this if (faceRoute) Face(target); var lookAt = Vector3.Forwards.Transform(Matrix.CreateRotationY(MathHelper.ToRadians(-(Yaw - 180) + 180))); lookAt *= modifier; Velocity = new Vector3(lookAt.X, Velocity.Y, lookAt.Z); - if (Position.DistanceTo(target) < 0.1) + if (Position.DistanceTo(target) < Velocity.Distance) { + Position = target; + Velocity = Vector3.Zero; CurrentPath.Index++; if (CurrentPath.Index >= CurrentPath.Waypoints.Count) { diff --git a/TrueCraft.Core/Lighting/WorldLighting.cs b/TrueCraft.Core/Lighting/WorldLighting.cs index bf8a5b0..a106814 100644 --- a/TrueCraft.Core/Lighting/WorldLighting.cs +++ b/TrueCraft.Core/Lighting/WorldLighting.cs @@ -300,6 +300,7 @@ namespace TrueCraft.Core.Lighting EnqueueOperation(new BoundingBox(new Vector3(coords.X, 0, coords.Z), new Vector3(coords.X + Chunk.Width, chunk.MaxHeight + 2, coords.Z + Chunk.Depth)), true, true); + TryLightNext(); while (flush && TryLightNext()) { } diff --git a/TrueCraft.Core/Physics/PhysicsEngine.cs b/TrueCraft.Core/Physics/PhysicsEngine.cs index 4f6d1d3..27ac334 100644 --- a/TrueCraft.Core/Physics/PhysicsEngine.cs +++ b/TrueCraft.Core/Physics/PhysicsEngine.cs @@ -41,10 +41,21 @@ namespace TrueCraft.Core.Physics Entities.Remove(entity); } + private void TruncateVelocity(IPhysicsEntity entity, double multiplier) + { + if (Math.Abs(entity.Velocity.X) < 0.1 * multiplier) + entity.Velocity = new Vector3(0, entity.Velocity.Y, entity.Velocity.Z); + if (Math.Abs(entity.Velocity.Y) < 0.1 * multiplier) + entity.Velocity = new Vector3(entity.Velocity.X, 0, entity.Velocity.Z); + if (Math.Abs(entity.Velocity.Z) < 0.1 * multiplier) + entity.Velocity = new Vector3(entity.Velocity.X, entity.Velocity.Y, 0); + entity.Velocity.Clamp(entity.TerminalVelocity); + } + public void Update(TimeSpan time) { double multiplier = time.TotalSeconds; - if (multiplier == 0) + if (multiplier == 0 || multiplier > 1) return; lock (EntityLock) { @@ -55,9 +66,7 @@ namespace TrueCraft.Core.Physics { entity.Velocity -= new Vector3(0, entity.AccelerationDueToGravity * multiplier, 0); entity.Velocity *= 1 - entity.Drag * multiplier; - if (entity.Velocity.Distance < 0.001) - entity.Velocity = Vector3.Zero; - entity.Velocity.Clamp(entity.TerminalVelocity); + TruncateVelocity(entity, multiplier); Vector3 collision, before = entity.Velocity; @@ -74,8 +83,8 @@ namespace TrueCraft.Core.Physics if (TestTerrainCollisionCylinder(aabbEntity, out collision)) aabbEntity.TerrainCollision(collision, before); } - entity.EndUpdate(entity.Position + entity.Velocity); + TruncateVelocity(entity, multiplier); } } } diff --git a/TrueCraft.Core/TerrainGen/BiomeRepository.cs b/TrueCraft.Core/TerrainGen/BiomeRepository.cs index 9d157b2..26bf0f9 100644 --- a/TrueCraft.Core/TerrainGen/BiomeRepository.cs +++ b/TrueCraft.Core/TerrainGen/BiomeRepository.cs @@ -47,7 +47,7 @@ namespace TrueCraft.Core.TerrainGen return BiomeProviders[id]; } - public IBiomeProvider GetBiome(double temperature, double rainfall) + public IBiomeProvider GetBiome(double temperature, double rainfall, bool spawn) { List temperatureResults = new List(); foreach (var biome in BiomeProviders) @@ -79,7 +79,10 @@ namespace TrueCraft.Core.TerrainGen foreach (var biome in BiomeProviders) { - if (biome != null && biome.Rainfall.Equals(rainfall) && temperatureResults.Contains(biome)) + if (biome != null + && biome.Rainfall.Equals(rainfall) + && temperatureResults.Contains(biome) + && (!spawn || biome.Spawn)) { return biome; } @@ -92,14 +95,15 @@ namespace TrueCraft.Core.TerrainGen if (biome != null) { var difference = Math.Abs(temperature - biome.Temperature); - if (biomeProvider == null || difference < rainfallDifference) + if ((biomeProvider == null || difference < rainfallDifference) + && (!spawn || biome.Spawn)) { biomeProvider = biome; rainfallDifference = (float)difference; } } } - return biomeProvider; + return biomeProvider ?? new PlainsBiome(); } } } \ No newline at end of file diff --git a/TrueCraft.Core/TerrainGen/Biomes/BiomeProvider.cs b/TrueCraft.Core/TerrainGen/Biomes/BiomeProvider.cs index 6054ed2..d05d555 100644 --- a/TrueCraft.Core/TerrainGen/Biomes/BiomeProvider.cs +++ b/TrueCraft.Core/TerrainGen/Biomes/BiomeProvider.cs @@ -27,6 +27,14 @@ namespace TrueCraft.Core.TerrainGen.Biomes /// The base rainfall of the biome. /// public abstract double Rainfall { get; } + + public virtual bool Spawn + { + get + { + return true; + } + } /// /// The tree types generated in the biome. diff --git a/TrueCraft.Core/TerrainGen/Biomes/DesertBiome.cs b/TrueCraft.Core/TerrainGen/Biomes/DesertBiome.cs index 31be824..bb3762c 100644 --- a/TrueCraft.Core/TerrainGen/Biomes/DesertBiome.cs +++ b/TrueCraft.Core/TerrainGen/Biomes/DesertBiome.cs @@ -25,6 +25,11 @@ namespace TrueCraft.Core.TerrainGen.Biomes { get { return 0.0f; } } + + public override bool Spawn + { + get { return false; } + } public override TreeSpecies[] Trees { diff --git a/TrueCraft.Core/TerrainGen/Decorators/LiquidDecorator.cs b/TrueCraft.Core/TerrainGen/Decorators/LiquidDecorator.cs index 1ea0e0d..2e4aa69 100644 --- a/TrueCraft.Core/TerrainGen/Decorators/LiquidDecorator.cs +++ b/TrueCraft.Core/TerrainGen/Decorators/LiquidDecorator.cs @@ -12,7 +12,7 @@ namespace TrueCraft.Core.TerrainGen.Decorators { public class LiquidDecorator : IChunkDecorator { - const int WaterLevel = 40; + public static readonly int WaterLevel = 40; public void Decorate(IWorld world, IChunk chunk, IBiomeRepository biomes) { diff --git a/TrueCraft.Core/TerrainGen/StandardGenerator.cs b/TrueCraft.Core/TerrainGen/StandardGenerator.cs index a61cde5..4123a1d 100644 --- a/TrueCraft.Core/TerrainGen/StandardGenerator.cs +++ b/TrueCraft.Core/TerrainGen/StandardGenerator.cs @@ -137,7 +137,9 @@ namespace TrueCraft.Core.TerrainGen || cellValue.Equals(1) && world.BiomeDiagram.ClosestCellPoint(location) >= featurePointDistance) { - byte id = (SingleBiome) ? GenerationBiome : world.BiomeDiagram.GenerateBiome(seed, Biomes, location); + byte id = (SingleBiome) ? GenerationBiome + : world.BiomeDiagram.GenerateBiome(seed, Biomes, location, + IsSpawnCoordinate(location.X, location.Z)); var cell = new BiomeCell(id, location); world.BiomeDiagram.AddCell(cell); } @@ -207,14 +209,23 @@ namespace TrueCraft.Core.TerrainGen return world.BiomeDiagram.GetBiome(location); } + bool IsSpawnCoordinate(int x, int z) + { + return x > -1000 && x < 1000 || z > -1000 && z < 1000; + } + int GetHeight(int x, int z) { - var NoiseValue = FinalNoise.Value2D(x, z) + GroundLevel; - if (NoiseValue < 0) - NoiseValue = GroundLevel; - if (NoiseValue > Chunk.Height) - NoiseValue = Chunk.Height - 1; - return (int)NoiseValue; + var value = FinalNoise.Value2D(x, z) + GroundLevel; + var coords = new Coordinates2D(x, z); + double distance = IsSpawnCoordinate(x, z) ? coords.Distance : 1000; + if (distance < 1000) // Avoids deep water within 1km sq of spawn + value += (1 - distance / 1000f) * 12; + if (value < 0) + value = GroundLevel; + if (value > Chunk.Height) + value = Chunk.Height - 1; + return (int)value; } } } \ No newline at end of file diff --git a/TrueCraft.Core/TrueCraft.Core.csproj b/TrueCraft.Core/TrueCraft.Core.csproj index c1a1700..246f610 100644 --- a/TrueCraft.Core/TrueCraft.Core.csproj +++ b/TrueCraft.Core/TrueCraft.Core.csproj @@ -355,6 +355,7 @@ + diff --git a/TrueCraft.Core/World/BiomeMap.cs b/TrueCraft.Core/World/BiomeMap.cs index 4894f54..662ea09 100644 --- a/TrueCraft.Core/World/BiomeMap.cs +++ b/TrueCraft.Core/World/BiomeMap.cs @@ -42,11 +42,11 @@ namespace TrueCraft.Core.World return BiomeID; } - public byte GenerateBiome(int seed, IBiomeRepository biomes, Coordinates2D location) + public byte GenerateBiome(int seed, IBiomeRepository biomes, Coordinates2D location, bool spawn) { double temp = Math.Abs(TempNoise.Value2D(location.X, location.Z)); double rainfall = Math.Abs(RainNoise.Value2D(location.X, location.Z)); - byte ID = biomes.GetBiome(temp, rainfall).ID; + byte ID = biomes.GetBiome(temp, rainfall, spawn).ID; return ID; } diff --git a/TrueCraft.Core/World/Region.cs b/TrueCraft.Core/World/Region.cs index 1a9b8f7..10b964f 100644 --- a/TrueCraft.Core/World/Region.cs +++ b/TrueCraft.Core/World/Region.cs @@ -67,58 +67,55 @@ namespace TrueCraft.Core.World public IChunk GetChunk(Coordinates2D position, bool generate = true) { // TODO: This could use some refactoring - lock (Chunks) + if (!Chunks.ContainsKey(position)) { - if (!Chunks.ContainsKey(position)) + if (regionFile != null) { - if (regionFile != null) + // Search the stream for that region + lock (streamLock) { - // Search the stream for that region - lock (regionFile) + var chunkData = GetChunkFromTable(position); + if (chunkData == null) { - var chunkData = GetChunkFromTable(position); - if (chunkData == null) - { - if (World.ChunkProvider == null) - throw new ArgumentException("The requested chunk is not loaded.", "position"); - if (generate) - GenerateChunk(position); - else - return null; - return Chunks[position]; - } - regionFile.Seek(chunkData.Item1, SeekOrigin.Begin); - /*int length = */ - new MinecraftStream(regionFile).ReadInt32(); // TODO: Avoid making new objects here, and in the WriteInt32 - int compressionMode = regionFile.ReadByte(); - switch (compressionMode) - { - case 1: // gzip - throw new NotImplementedException("gzipped chunks are not implemented"); - case 2: // zlib - var nbt = new NbtFile(); - nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null); - var chunk = Chunk.FromNbt(nbt); - Chunks.Add(position, chunk); - World.OnChunkLoaded(new ChunkLoadedEventArgs(chunk)); - break; - default: - throw new InvalidDataException("Invalid compression scheme provided by region file."); - } + if (World.ChunkProvider == null) + throw new ArgumentException("The requested chunk is not loaded.", "position"); + if (generate) + GenerateChunk(position); + else + return null; + return Chunks[position]; + } + regionFile.Seek(chunkData.Item1, SeekOrigin.Begin); + /*int length = */ + new MinecraftStream(regionFile).ReadInt32(); // TODO: Avoid making new objects here, and in the WriteInt32 + int compressionMode = regionFile.ReadByte(); + switch (compressionMode) + { + case 1: // gzip + throw new NotImplementedException("gzipped chunks are not implemented"); + case 2: // zlib + var nbt = new NbtFile(); + nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null); + var chunk = Chunk.FromNbt(nbt); + Chunks.Add(position, chunk); + World.OnChunkLoaded(new ChunkLoadedEventArgs(chunk)); + break; + default: + throw new InvalidDataException("Invalid compression scheme provided by region file."); } } - else if (World.ChunkProvider == null) - throw new ArgumentException("The requested chunk is not loaded.", "position"); - else - { - if (generate) - GenerateChunk(position); - else - return null; - } } - return Chunks[position]; + else if (World.ChunkProvider == null) + throw new ArgumentException("The requested chunk is not loaded.", "position"); + else + { + if (generate) + GenerateChunk(position); + else + return null; + } } + return Chunks[position]; } public void GenerateChunk(Coordinates2D position) @@ -162,41 +159,38 @@ namespace TrueCraft.Core.World /// public void Save() { - lock (Chunks) + lock (streamLock) { - lock (streamLock) + var toRemove = new List(); + foreach (var kvp in Chunks) { - var toRemove = new List(); - foreach (var kvp in Chunks) + var chunk = kvp.Value; + if (chunk.IsModified) { - var chunk = kvp.Value; - if (chunk.IsModified) - { - var data = ((Chunk)chunk).ToNbt(); - byte[] raw = data.SaveToBuffer(NbtCompression.ZLib); + var data = ((Chunk)chunk).ToNbt(); + byte[] raw = data.SaveToBuffer(NbtCompression.ZLib); - var header = GetChunkFromTable(kvp.Key); - if (header == null || header.Item2 > raw.Length) - header = AllocateNewChunks(kvp.Key, raw.Length); + var header = GetChunkFromTable(kvp.Key); + if (header == null || header.Item2 > raw.Length) + header = AllocateNewChunks(kvp.Key, raw.Length); - regionFile.Seek(header.Item1, SeekOrigin.Begin); - new MinecraftStream(regionFile).WriteInt32(raw.Length); - regionFile.WriteByte(2); // Compressed with zlib - regionFile.Write(raw, 0, raw.Length); + regionFile.Seek(header.Item1, SeekOrigin.Begin); + new MinecraftStream(regionFile).WriteInt32(raw.Length); + regionFile.WriteByte(2); // Compressed with zlib + regionFile.Write(raw, 0, raw.Length); - chunk.IsModified = false; - } - if ((DateTime.UtcNow - chunk.LastAccessed).TotalMinutes > 5) - toRemove.Add(kvp.Key); - } - regionFile.Flush(); - // Unload idle chunks - foreach (var chunk in toRemove) - { - var c = Chunks[chunk]; - Chunks.Remove(chunk); - c.Dispose(); + chunk.IsModified = false; } + if ((DateTime.UtcNow - chunk.LastAccessed).TotalMinutes > 5) + toRemove.Add(kvp.Key); + } + regionFile.Flush(); + // Unload idle chunks + foreach (var chunk in toRemove) + { + var c = Chunks[chunk]; + Chunks.Remove(chunk); + c.Dispose(); } } } diff --git a/TrueCraft/Commands/DebugCommands.cs b/TrueCraft/Commands/DebugCommands.cs index 80eae93..13ba9e5 100644 --- a/TrueCraft/Commands/DebugCommands.cs +++ b/TrueCraft/Commands/DebugCommands.cs @@ -210,9 +210,13 @@ namespace TrueCraft.Commands if (path == null) { client.SendMessage(ChatColor.Red + "It is impossible for this entity to reach you."); - return; } - entity.CurrentPath = path; + else + { + client.SendMessage(string.Format(ChatColor.Blue + + "Executing path with {0} waypoints", path.Waypoints.Count())); + entity.CurrentPath = path; + } }); } @@ -222,6 +226,64 @@ namespace TrueCraft.Commands } } + public class EntityInfoCommand : Command + { + public override string Name + { + get { return "entity"; } + } + + public override string Description + { + get { return "Provides information about an entity ID."; } + } + + public override string[] Aliases + { + get { return new string[0]; } + } + + public override void Handle(IRemoteClient client, string alias, string[] arguments) + { + if (arguments.Length != 1) + { + Help(client, alias, arguments); + return; + } + + int id; + if (!int.TryParse(arguments[0], out id)) + { + Help(client, alias, arguments); + return; + } + + var manager = client.Server.GetEntityManagerForWorld(client.World); + var entity = manager.GetEntityByID(id); + if (entity == null) + { + client.SendMessage(ChatColor.Red + "An entity with that ID does not exist in this world."); + return; + } + client.SendMessage(string.Format( + "{0} {1}", entity.GetType().Name, entity.Position)); + if (entity is MobEntity) + { + var mob = entity as MobEntity; + client.SendMessage(string.Format( + "{0}/{1} HP, {2} State, moving to to {3}", + mob.Health, mob.MaxHealth, + mob.CurrentState?.GetType().Name ?? "null", + mob.CurrentPath?.Waypoints.Last().ToString() ?? "null")); + } + } + + public override void Help(IRemoteClient client, string alias, string[] arguments) + { + client.SendMessage("/entity [id]: Shows information about this entity."); + } + } + public class DestroyCommand : Command { public override string Name diff --git a/TrueCraft/MultiplayerServer.cs b/TrueCraft/MultiplayerServer.cs index 2324998..bdd278d 100644 --- a/TrueCraft/MultiplayerServer.cs +++ b/TrueCraft/MultiplayerServer.cs @@ -75,6 +75,7 @@ namespace TrueCraft private TcpListener Listener; private readonly PacketHandler[] PacketHandlers; private IList LogProviders; + private Stopwatch Time; private ConcurrentBag> ChunksToSchedule; internal object ClientLock = new object(); @@ -109,6 +110,7 @@ namespace TrueCraft QueryProtocol = new TrueCraft.QueryProtocol(this); WorldLighters = new List(); ChunksToSchedule = new ConcurrentBag>(); + Time = new Stopwatch(); AccessConfiguration = Configuration.LoadConfiguration("access.yaml"); @@ -124,6 +126,8 @@ namespace TrueCraft public void Start(IPEndPoint endPoint) { ShuttingDown = false; + Time.Reset(); + Time.Start(); Listener = new TcpListener(endPoint); Listener.Start(); EndPoint = (IPEndPoint)Listener.LocalEndpoint; @@ -135,7 +139,7 @@ namespace TrueCraft AcceptClient(this, args); Log(LogCategory.Notice, "Running TrueCraft server on {0}", EndPoint); - EnvironmentWorker.Change(MillisecondsPerTick, 0); + EnvironmentWorker.Change(MillisecondsPerTick, Timeout.Infinite); if(Program.ServerConfiguration.Query) QueryProtocol.Start(); } @@ -152,6 +156,16 @@ namespace TrueCraft DisconnectClient(c); } + public void Pause() + { + EnvironmentWorker.Change(Timeout.Infinite, Timeout.Infinite); + } + + public void Resume() + { + EnvironmentWorker.Change(0, Timeout.Infinite); + } + public void AddWorld(IWorld world) { Worlds.Add(world); @@ -216,7 +230,7 @@ namespace TrueCraft if (Program.ServerConfiguration.EnableLighting) { var lighter = new WorldLighting(sender as IWorld, BlockRepository); - lighter.InitialLighting(e.Chunk); + lighter.InitialLighting(e.Chunk, false); } else { @@ -385,6 +399,8 @@ namespace TrueCraft if (ShuttingDown) return; + long start = Time.ElapsedMilliseconds; + long limit = Time.ElapsedMilliseconds + MillisecondsPerTick; Profiler.Start("environment"); Scheduler.Update(); @@ -396,15 +412,20 @@ namespace TrueCraft } Profiler.Done(); - Profiler.Start("environment.lighting"); - foreach (var lighter in WorldLighters) + if (Program.ServerConfiguration.EnableLighting) { - int attempts = 500; - while (attempts-- > 0 && lighter.TryLightNext()) + Profiler.Start("environment.lighting"); + foreach (var lighter in WorldLighters) { + while (Time.ElapsedMilliseconds < limit && lighter.TryLightNext()) + { + // This space intentionally left blank + } + if (Time.ElapsedMilliseconds >= limit) + Log(LogCategory.Warning, "Lighting queue is backed up"); } + Profiler.Done(); } - Profiler.Done(); Profiler.Start("environment.chunks"); Tuple t; @@ -413,8 +434,12 @@ namespace TrueCraft Profiler.Done(); Profiler.Done(MillisecondsPerTick); - - EnvironmentWorker.Change(MillisecondsPerTick, 0); + long end = Time.ElapsedMilliseconds; + long next = MillisecondsPerTick - (end - start); + if (next < 0) + next = 0; + + EnvironmentWorker.Change(next, Timeout.Infinite); } public bool PlayerIsWhitelisted(string client) diff --git a/TrueCraft/Program.cs b/TrueCraft/Program.cs index 67994a0..40139b2 100644 --- a/TrueCraft/Program.cs +++ b/TrueCraft/Program.cs @@ -98,6 +98,11 @@ namespace TrueCraft if (progress % 10 == 0) Server.Log(LogCategory.Notice, "{0}% complete", progress + 10); } + Server.Log(LogCategory.Notice, "Lighting the world (this will take a moment)..."); + foreach (var lighter in Server.WorldLighters) + { + while (lighter.TryLightNext()) ; + } } world.Save(); CommandManager = new CommandManager(); @@ -107,10 +112,14 @@ namespace TrueCraft while (true) { Thread.Sleep(1000 * ServerConfiguration.WorldSaveInterval); + Server.Pause(); + Server.Log(LogCategory.Notice, "Saving world..."); foreach (var w in Server.Worlds) { w.Save(); } + Server.Log(LogCategory.Notice, "Done."); + Server.Resume(); } }