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();
}
}