Adapt Craft.Net.Anvil for world support

This commit is contained in:
Drew DeVault 2014-12-27 12:34:55 -07:00
parent 51a50d8a03
commit 96ecbc708c
21 changed files with 1142 additions and 4 deletions

View File

@ -46,10 +46,12 @@ this project if you ask nicely.
## Resources
[1.7.3 protocol docs](http://wiki.vg/index.php?title=Protocol&oldid=615)
[1.7.3 protocol docs](http://wiki.vg/index.php?title=Protocol&oldid=517)
[1.7.3 inventory docs](http://wiki.vg/index.php?title=Inventory&oldid=2356)
[1.7.3 protocol faq](http://wiki.vg/index.php?title=Protocol_FAQ&oldid=74)
## Blah blah blah
TrueCraft is not associated with Mojang or Minecraft in any sort of official

View File

@ -4,6 +4,8 @@ namespace TrueCraft.API.Networking
{
public interface IPacketReader
{
int ProtocolVersion { get; }
void RegisterPacketType<T>(bool clientbound = true, bool serverbound = true) where T : IPacket;
IPacket ReadPacket(IMinecraftStream stream, bool serverbound = true);
void WritePacket(IMinecraftStream stream, IPacket packet);

View File

@ -0,0 +1,85 @@
using fNbt;
using fNbt.Serialization;
using System;
using System.Collections.ObjectModel;
namespace TrueCraft.API
{
/// <summary>
/// Represents an array of 4-bit values.
/// </summary>
public class NibbleArray : INbtSerializable
{
/// <summary>
/// The data in the nibble array. Each byte contains
/// two nibbles, stored in big-endian.
/// </summary>
public byte[] Data { get; set; }
public NibbleArray()
{
}
/// <summary>
/// Creates a new nibble array with the given number of nibbles.
/// </summary>
public NibbleArray(int length)
{
Data = new byte[length/2];
}
/// <summary>
/// Gets the current number of nibbles in this array.
/// </summary>
[NbtIgnore]
public int Length
{
get { return Data.Length * 2; }
}
/// <summary>
/// Gets or sets a nibble at the given index.
/// </summary>
[NbtIgnore]
public byte this[int index]
{
get { return (byte)(Data[index / 2] >> ((index) % 2 * 4) & 0xF); }
set
{
value &= 0xF;
Data[index/2] &= (byte)(0xF << ((index + 1) % 2 * 4));
Data[index/2] |= (byte)(value << (index % 2 * 4));
}
}
public NbtTag Serialize(string tagName)
{
return new NbtByteArray(tagName, Data);
}
public void Deserialize(NbtTag value)
{
Data = value.ByteArrayValue;
}
}
public class ReadOnlyNibbleArray
{
private NibbleArray NibbleArray { get; set; }
public ReadOnlyNibbleArray(NibbleArray array)
{
NibbleArray = array;
}
public byte this[int index]
{
get { return NibbleArray[index]; }
}
public ReadOnlyCollection<byte> Data
{
get { return Array.AsReadOnly(NibbleArray.Data); }
}
}
}

View File

@ -53,11 +53,18 @@
<Compile Include="Vector3.cs" />
<Compile Include="Server\IMultiplayerServer.cs" />
<Compile Include="Networking\IRemoteClient.cs" />
<Compile Include="World\IWorld.cs" />
<Compile Include="World\IChunk.cs" />
<Compile Include="World\IChunkProvider.cs" />
<Compile Include="World\ISection.cs" />
<Compile Include="NibbleArray.cs" />
<Compile Include="World\IRegion.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<Folder Include="Networking\" />
<Folder Include="Server\" />
<Folder Include="World\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\externals\fNbt\fNbt\fNbt.csproj">

View File

@ -0,0 +1,22 @@
using System;
namespace TrueCraft.API.World
{
public interface IChunk
{
Coordinates2D Coordinates { get; set; }
bool IsModified { get; set; }
int[] HeightMap { get; }
ISection[] Sections { get; }
byte[] Biomes { get; }
DateTime LastAccessed { get; set; }
short GetBlockID(Coordinates3D coordinates);
byte GetMetadata(Coordinates3D coordinates);
byte GetSkyLight(Coordinates3D coordinates);
byte GetBlockLight(Coordinates3D coordinates);
void SetBlockID(Coordinates3D coordinates, short value);
void SetMetadata(Coordinates3D coordinates, byte value);
void SetSkyLight(Coordinates3D coordinates, byte value);
void SetBlockLight(Coordinates3D coordinates, byte value);
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace TrueCraft.API.World
{
/// <summary>
/// Provides new chunks to worlds. Generally speaking this is a terrain generator.
/// </summary>
public interface IChunkProvider
{
IChunk GenerateChunk(IWorld world, Coordinates2D coordinates);
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
namespace TrueCraft.API.World
{
public interface IRegion : IDisposable
{
IDictionary<Coordinates2D, IChunk> Chunks { get; }
Coordinates2D Position { get; }
IChunk GetChunk(Coordinates2D position);
void UnloadChunk(Coordinates2D position);
void Save(string path);
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace TrueCraft.API.World
{
public interface ISection
{
byte[] Blocks { get; }
NibbleArray Metadata { get; }
NibbleArray BlockLight { get; }
NibbleArray SkyLight { get; }
byte Y { get; }
short GetBlockID(Coordinates3D coordinates);
byte GetMetadata(Coordinates3D coordinates);
byte GetSkyLight(Coordinates3D coordinates);
byte GetBlockLight(Coordinates3D coordinates);
void SetBlockID(Coordinates3D coordinates, short value);
void SetMetadata(Coordinates3D coordinates, byte value);
void SetSkyLight(Coordinates3D coordinates, byte value);
void SetBlockLight(Coordinates3D coordinates, byte value);
void ProcessSection();
}
}

View File

@ -0,0 +1,24 @@
using System;
namespace TrueCraft.API.World
{
// TODO: Entities
/// <summary>
/// An in-game world composed of chunks and blocks.
/// </summary>
public interface IWorld
{
string Name { get; set; }
IChunk GetChunk(Coordinates2D coordinates);
short GetBlockID(Coordinates3D coordinates);
byte GetMetadata(Coordinates3D coordinates);
byte GetSkyLight(Coordinates3D coordinates);
void SetBlockID(Coordinates3D coordinates, short value);
void SetMetadata(Coordinates3D coordinates, byte value);
void SetSkyLight(Coordinates3D coordinates, byte value);
void SetBlockLight(Coordinates3D coordinates, byte value);
bool IsValidPosition(Coordinates3D position);
void Save();
}
}

View File

@ -6,7 +6,8 @@ namespace TrueCraft.Core.Networking
{
public class PacketReader : IPacketReader
{
public static readonly int ProtocolVersion = 18;
public static readonly int Version = 14;
public int ProtocolVersion { get { return Version; } }
private Type[] ClientboundPackets = new Type[0x100];
private Type[] ServerboundPackets = new Type[0x100];

View File

@ -30,6 +30,9 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Ionic.Zip.Reduced">
<HintPath>..\lib\Ionic.Zip.Reduced.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
@ -93,6 +96,10 @@
<Compile Include="Networking\Packets\MapDataPacket.cs" />
<Compile Include="Networking\Packets\UpdateStatisticPacket.cs" />
<Compile Include="Networking\Packets\DisconnectPacket.cs" />
<Compile Include="World\Chunk.cs" />
<Compile Include="World\Region.cs" />
<Compile Include="World\Section.cs" />
<Compile Include="World\World.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
@ -100,8 +107,13 @@
<Project>{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}</Project>
<Name>TrueCraft.API</Name>
</ProjectReference>
<ProjectReference Include="..\externals\fNbt\fNbt\fNbt.csproj">
<Project>{4488498D-976D-4DA3-BF72-109531AF0488}</Project>
<Name>fNbt</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Networking\Packets\" />
<Folder Include="World\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,248 @@
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Reflection;
using fNbt;
using fNbt.Serialization;
using TrueCraft.API.World;
using TrueCraft.API;
namespace TrueCraft.Core.World
{
public class Chunk : INbtSerializable, IChunk
{
public const int Width = 16, Height = 256, Depth = 16;
private static readonly NbtSerializer Serializer = new NbtSerializer(typeof(Chunk));
[NbtIgnore]
public DateTime LastAccessed { get; set; }
public bool IsModified { get; set; }
public byte[] Biomes { get; set; }
public int[] HeightMap { get; set; }
[NbtIgnore]
public ISection[] Sections { get; set; }
[TagName("xPos")]
public int X { get; set; }
[TagName("zPos")]
public int Z { get; set; }
public Coordinates2D Coordinates
{
get
{
return new Coordinates2D(X, Z);
}
set
{
X = value.X;
Z = value.Z;
}
}
public long LastUpdate { get; set; }
public bool TerrainPopulated { get; set; }
[NbtIgnore]
public Region ParentRegion { get; set; }
public Chunk()
{
TerrainPopulated = true;
Sections = new Section[16];
for (int i = 0; i < Sections.Length; i++)
Sections[i] = new Section((byte)i);
Biomes = new byte[Width * Depth];
HeightMap = new int[Width * Depth];
LastAccessed = DateTime.Now;
}
public Chunk(Coordinates2D coordinates) : this()
{
X = coordinates.X;
Z = coordinates.Z;
}
public short GetBlockID(Coordinates3D coordinates)
{
LastAccessed = DateTime.Now;
int section = GetSectionNumber(coordinates.Y);
coordinates.Y = GetPositionInSection(coordinates.Y);
return Sections[section].GetBlockID(coordinates);
}
public byte GetMetadata(Coordinates3D coordinates)
{
LastAccessed = DateTime.Now;
int section = GetSectionNumber(coordinates.Y);
coordinates.Y = GetPositionInSection(coordinates.Y);
return Sections[section].GetMetadata(coordinates);
}
public byte GetSkyLight(Coordinates3D coordinates)
{
LastAccessed = DateTime.Now;
int section = GetSectionNumber(coordinates.Y);
coordinates.Y = GetPositionInSection(coordinates.Y);
return Sections[section].GetSkyLight(coordinates);
}
public byte GetBlockLight(Coordinates3D coordinates)
{
LastAccessed = DateTime.Now;
int section = GetSectionNumber(coordinates.Y);
coordinates.Y = GetPositionInSection(coordinates.Y);
return Sections[section].GetBlockLight(coordinates);
}
public void SetBlockID(Coordinates3D coordinates, short value)
{
LastAccessed = DateTime.Now;
IsModified = true;
int section = GetSectionNumber(coordinates.Y);
coordinates.Y = GetPositionInSection(coordinates.Y);
Sections[section].SetBlockID(coordinates, value);
var oldHeight = GetHeight((byte)coordinates.X, (byte)coordinates.Z);
if (value == 0) // Air
{
if (oldHeight <= coordinates.Y)
{
// Shift height downwards
while (coordinates.Y > 0)
{
coordinates.Y--;
if (GetBlockID(coordinates) != 0)
SetHeight((byte)coordinates.X, (byte)coordinates.Z, coordinates.Y);
}
}
}
else
{
if (oldHeight < coordinates.Y)
SetHeight((byte)coordinates.X, (byte)coordinates.Z, coordinates.Y);
}
}
public void SetMetadata(Coordinates3D coordinates, byte value)
{
LastAccessed = DateTime.Now;
IsModified = true;
int section = GetSectionNumber(coordinates.Y);
coordinates.Y = GetPositionInSection(coordinates.Y);
Sections[section].SetMetadata(coordinates, value);
}
public void SetSkyLight(Coordinates3D coordinates, byte value)
{
LastAccessed = DateTime.Now;
IsModified = true;
int section = GetSectionNumber(coordinates.Y);
coordinates.Y = GetPositionInSection(coordinates.Y);
Sections[section].SetSkyLight(coordinates, value);
}
public void SetBlockLight(Coordinates3D coordinates, byte value)
{
LastAccessed = DateTime.Now;
IsModified = true;
int section = GetSectionNumber(coordinates.Y);
coordinates.Y = GetPositionInSection(coordinates.Y);
Sections[section].SetBlockLight(coordinates, value);
}
private static int GetSectionNumber(int yPos)
{
return yPos / 16;
}
private static int GetPositionInSection(int yPos)
{
return yPos % 16;
}
/// <summary>
/// Gets the height of the specified column.
/// </summary>
public int GetHeight(byte x, byte z)
{
LastAccessed = DateTime.Now;
return HeightMap[(byte)(z * Depth) + x];
}
private void SetHeight(byte x, byte z, int value)
{
LastAccessed = DateTime.Now;
IsModified = true;
HeightMap[(byte)(z * Depth) + x] = value;
}
public NbtFile ToNbt()
{
LastAccessed = DateTime.Now;
var serializer = new NbtSerializer(typeof(Chunk));
var compound = serializer.Serialize(this, "Level") as NbtCompound;
var file = new NbtFile();
file.RootTag.Add(compound);
return file;
}
public static Chunk FromNbt(NbtFile nbt)
{
var serializer = new NbtSerializer(typeof(Chunk));
var chunk = (Chunk)serializer.Deserialize(nbt.RootTag["Level"]);
return chunk;
}
public NbtTag Serialize(string tagName)
{
var chunk = (NbtCompound)Serializer.Serialize(this, tagName, true);
var entities = new NbtList("Entities", NbtTagType.Compound);
chunk.Add(entities);
var sections = new NbtList("Sections", NbtTagType.Compound);
var serializer = new NbtSerializer(typeof(Section));
for (int i = 0; i < Sections.Length; i++)
{
if (Sections[i] is Section)
{
if (!(Sections[i] as Section).IsAir)
sections.Add(serializer.Serialize(Sections[i]));
}
else
sections.Add(serializer.Serialize(Sections[i]));
}
chunk.Add(sections);
return chunk;
}
public void Deserialize(NbtTag value)
{
IsModified = true;
var compound = value as NbtCompound;
var chunk = (Chunk)Serializer.Deserialize(value, true);
this.Biomes = chunk.Biomes;
this.HeightMap = chunk.HeightMap;
this.LastUpdate = chunk.LastUpdate;
this.Sections = chunk.Sections;
this.TerrainPopulated = chunk.TerrainPopulated;
this.X = chunk.X;
this.Z = chunk.Z;
var serializer = new NbtSerializer(typeof(Section));
foreach (var section in compound["Sections"] as NbtList)
{
int index = section["Y"].IntValue;
Sections[index] = (Section)serializer.Deserialize(section);
Sections[index].ProcessSection();
}
}
}
}

View File

@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using fNbt;
using Ionic.Zlib;
using TrueCraft.API;
using TrueCraft.API.World;
using TrueCraft.Core.Networking;
namespace TrueCraft.Core.World
{
/// <summary>
/// Represents a 32x32 area of <see cref="Chunk"/> objects.
/// Not all of these chunks are represented at any given time, and
/// will be loaded from disk or generated when the need arises.
/// </summary>
public class Region : IDisposable, IRegion
{
// In chunks
public const int Width = 32, Depth = 32;
/// <summary>
/// The currently loaded chunk list.
/// </summary>
public IDictionary<Coordinates2D, IChunk> Chunks { get; set; }
/// <summary>
/// The location of this region in the overworld.
/// </summary>
public Coordinates2D Position { get; set; }
public World World { get; set; }
private Stream regionFile { get; set; }
/// <summary>
/// Creates a new Region for server-side use at the given position using
/// the provided terrain generator.
/// </summary>
public Region(Coordinates2D position, World world)
{
Chunks = new Dictionary<Coordinates2D, IChunk>();
Position = position;
World = world;
}
/// <summary>
/// Creates a region from the given region file.
/// </summary>
public Region(Coordinates2D position, World world, string file) : this(position, world)
{
if (File.Exists(file))
regionFile = File.Open(file, FileMode.OpenOrCreate);
else
{
regionFile = File.Open(file, FileMode.OpenOrCreate);
CreateRegionHeader();
}
}
/// <summary>
/// Retrieves the requested chunk from the region, or
/// generates it if a world generator is provided.
/// </summary>
/// <param name="position">The position of the requested local chunk coordinates.</param>
public IChunk GetChunk(Coordinates2D position)
{
// TODO: This could use some refactoring
lock (Chunks)
{
if (!Chunks.ContainsKey(position))
{
if (regionFile != null)
{
// Search the stream for that region
lock (regionFile)
{
var chunkData = GetChunkFromTable(position);
if (chunkData == null)
{
if (World.ChunkProvider == null)
throw new ArgumentException("The requested chunk is not loaded.", "position");
GenerateChunk(position);
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, (IChunk)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
GenerateChunk(position);
}
return Chunks[position];
}
}
/// <summary>
/// Retrieves the requested chunk from the region, without using the
/// world generator if it does not exist.
/// </summary>
/// <param name="position">The position of the requested local chunk coordinates.</param>
public IChunk GetChunkWithoutGeneration(Coordinates2D position)
{
// TODO: This could use some refactoring
lock (Chunks)
{
if (!Chunks.ContainsKey(position))
{
if (regionFile != null)
{
// Search the stream for that region
lock (regionFile)
{
var chunkData = GetChunkFromTable(position);
if (chunkData == null)
return null;
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
break;
case 2: // zlib
var nbt = new NbtFile();
nbt.LoadFromStream(regionFile, NbtCompression.ZLib, null);
var chunk = Chunk.FromNbt(nbt);
Chunks.Add(position, (IChunk)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
GenerateChunk(position);
}
return Chunks[position];
}
}
public void GenerateChunk(Coordinates2D position)
{
var globalPosition = (Position * new Coordinates2D(Width, Depth)) + position;
var chunk = World.ChunkProvider.GenerateChunk(World, globalPosition);
chunk.IsModified = true;
chunk.Coordinates = globalPosition;
Chunks.Add(position, (IChunk)chunk);
}
/// <summary>
/// Sets the chunk at the specified local position to the given value.
/// </summary>
public void SetChunk(Coordinates2D position, IChunk chunk)
{
if (!Chunks.ContainsKey(position))
Chunks.Add(position, chunk);
chunk.IsModified = true;
chunk.Coordinates = position;
chunk.LastAccessed = DateTime.Now;
Chunks[position] = chunk;
}
/// <summary>
/// Saves this region to the specified file.
/// </summary>
public void Save(string file)
{
if(File.Exists(file))
regionFile = regionFile ?? File.Open(file, FileMode.OpenOrCreate);
else
{
regionFile = regionFile ?? File.Open(file, FileMode.OpenOrCreate);
CreateRegionHeader();
}
Save();
}
/// <summary>
/// Saves this region to the open region file.
/// </summary>
public void Save()
{
lock (Chunks)
{
lock (regionFile)
{
var toRemove = new List<Coordinates2D>();
foreach (var kvp in Chunks)
{
var chunk = kvp.Value;
if (chunk.IsModified)
{
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);
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.Now - chunk.LastAccessed).TotalMinutes > 5)
toRemove.Add(kvp.Key);
}
regionFile.Flush();
// Unload idle chunks
foreach (var chunk in toRemove)
Chunks.Remove(chunk);
}
}
}
#region Stream Helpers
private const int ChunkSizeMultiplier = 4096;
private Tuple<int, int> GetChunkFromTable(Coordinates2D position) // <offset, length>
{
int tableOffset = ((position.X % Width) + (position.Z % Depth) * Width) * 4;
regionFile.Seek(tableOffset, SeekOrigin.Begin);
byte[] offsetBuffer = new byte[4];
regionFile.Read(offsetBuffer, 0, 3);
Array.Reverse(offsetBuffer);
int length = regionFile.ReadByte();
int offset = BitConverter.ToInt32(offsetBuffer, 0) << 4;
if (offset == 0 || length == 0)
return null;
return new Tuple<int, int>(offset,
length * ChunkSizeMultiplier);
}
private void CreateRegionHeader()
{
regionFile.Write(new byte[8192], 0, 8192);
regionFile.Flush();
}
private Tuple<int, int> AllocateNewChunks(Coordinates2D position, int length)
{
// Expand region file
regionFile.Seek(0, SeekOrigin.End);
int dataOffset = (int)regionFile.Position;
length /= ChunkSizeMultiplier;
length++;
regionFile.Write(new byte[length * ChunkSizeMultiplier], 0, length * ChunkSizeMultiplier);
// Write table entry
int tableOffset = ((position.X % Width) + (position.Z % Depth) * Width) * 4;
regionFile.Seek(tableOffset, SeekOrigin.Begin);
byte[] entry = BitConverter.GetBytes(dataOffset >> 4);
entry[0] = (byte)length;
Array.Reverse(entry);
regionFile.Write(entry, 0, entry.Length);
return new Tuple<int, int>(dataOffset, length * ChunkSizeMultiplier);
}
#endregion
public static string GetRegionFileName(Coordinates2D position)
{
return string.Format("r.{0}.{1}.mca", position.X, position.Z);
}
public void UnloadChunk(Coordinates2D position)
{
Chunks.Remove(position);
}
public void Dispose()
{
if (regionFile == null)
return;
lock (regionFile)
{
regionFile.Flush();
regionFile.Close();
}
}
}
}

View File

@ -0,0 +1,123 @@
using fNbt.Serialization;
using TrueCraft.API;
using TrueCraft.API.World;
namespace TrueCraft.Core.World
{
public class Section : ISection
{
public const byte Width = 16, Height = 16, Depth = 16;
public byte[] Blocks { get; set; }
[TagName("Data")]
public NibbleArray Metadata { get; set; }
public NibbleArray BlockLight { get; set; }
public NibbleArray SkyLight { get; set; }
[IgnoreOnNull]
public NibbleArray Add { get; set; }
public byte Y { get; set; }
private int nonAirCount;
public Section()
{
}
public Section(byte y)
{
const int size = Width * Height * Depth;
this.Y = y;
Blocks = new byte[size];
Metadata = new NibbleArray(size);
BlockLight = new NibbleArray(size);
SkyLight = new NibbleArray(size);
for (int i = 0; i < size; i++)
SkyLight[i] = 0xFF;
Add = null; // Only used when needed
nonAirCount = 0;
}
[NbtIgnore]
public bool IsAir
{
get { return nonAirCount == 0; }
}
public short GetBlockID(Coordinates3D coordinates)
{
int index = coordinates.X + (coordinates.Z * Width) + (coordinates.Y * Height * Width);
short value = Blocks[index];
if (Add != null)
value |= (short)(Add[index] << 8);
return value;
}
public byte GetMetadata(Coordinates3D coordinates)
{
int index = coordinates.X + (coordinates.Z * Width) + (coordinates.Y * Height * Width);
return Metadata[index];
}
public byte GetSkyLight(Coordinates3D coordinates)
{
int index = coordinates.X + (coordinates.Z * Width) + (coordinates.Y * Height * Width);
return SkyLight[index];
}
public byte GetBlockLight(Coordinates3D coordinates)
{
int index = coordinates.X + (coordinates.Z * Width) + (coordinates.Y * Height * Width);
return BlockLight[index];
}
public void SetBlockID(Coordinates3D coordinates, short value)
{
int index = coordinates.X + (coordinates.Z * Width) + (coordinates.Y * Height * Width);
if (value == 0)
{
if (Blocks[index] != 0)
nonAirCount--;
}
else
{
if (Blocks[index] == 0)
nonAirCount++;
}
Blocks[index] = (byte)value;
if ((value & ~0xFF) != 0)
{
if (Add == null) Add = new NibbleArray(Width * Height * Depth);
Add[index] = (byte)((ushort)value >> 8);
}
}
public void SetMetadata(Coordinates3D coordinates, byte value)
{
int index = coordinates.X + (coordinates.Z * Width) + (coordinates.Y * Height * Width);
Metadata[index] = value;
}
public void SetSkyLight(Coordinates3D coordinates, byte value)
{
int index = coordinates.X + (coordinates.Z * Width) + (coordinates.Y * Height * Width);
SkyLight[index] = value;
}
public void SetBlockLight(Coordinates3D coordinates, byte value)
{
int index = coordinates.X + (coordinates.Z * Width) + (coordinates.Y * Height * Width);
BlockLight[index] = value;
}
public void ProcessSection()
{
// TODO: Schedule updates
nonAirCount = 0;
for (int i = 0; i < Blocks.Length; i++)
{
if (Blocks[i] != 0)
nonAirCount++;
}
}
}
}

View File

@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using TrueCraft.API;
using TrueCraft.API.World;
namespace TrueCraft.Core.World
{
public class World : IDisposable, IWorld
{
public const int Height = 256;
public string Name { get; set; }
public string BaseDirectory { get; internal set; }
public IDictionary<Coordinates2D, IRegion> Regions { get; set; }
public IChunkProvider ChunkProvider { get; set; }
public World(string name)
{
Name = name;
Regions = new Dictionary<Coordinates2D, IRegion>();
}
public World(string name, IChunkProvider chunkProvider) : this(name)
{
ChunkProvider = chunkProvider;
}
public static World LoadWorld(string baseDirectory)
{
if (!Directory.Exists(baseDirectory))
throw new DirectoryNotFoundException();
var world = new World(Path.GetFileName(baseDirectory));
world.BaseDirectory = baseDirectory;
return world;
}
/// <summary>
/// Finds a chunk that contains the specified block coordinates.
/// </summary>
public IChunk FindChunk(Coordinates3D coordinates)
{
IChunk chunk;
FindBlockPosition(coordinates, out chunk);
return chunk;
}
public IChunk GetChunk(Coordinates2D coordinates)
{
int regionX = coordinates.X / Region.Width - ((coordinates.X < 0) ? 1 : 0);
int regionZ = coordinates.Z / Region.Depth - ((coordinates.Z < 0) ? 1 : 0);
var region = LoadOrGenerateRegion(new Coordinates2D(regionX, regionZ));
return region.GetChunk(new Coordinates2D(coordinates.X - regionX * 32, coordinates.Z - regionZ * 32));
}
public void GenerateChunk(Coordinates2D coordinates)
{
int regionX = coordinates.X / Region.Width - ((coordinates.X < 0) ? 1 : 0);
int regionZ = coordinates.Z / Region.Depth - ((coordinates.Z < 0) ? 1 : 0);
var region = LoadOrGenerateRegion(new Coordinates2D(regionX, regionZ));
region.GenerateChunk(new Coordinates2D(coordinates.X - regionX * 32, coordinates.Z - regionZ * 32));
}
public Chunk GetChunkWithoutGeneration(Coordinates2D coordinates)
{
int regionX = coordinates.X / Region.Width - ((coordinates.X < 0) ? 1 : 0);
int regionZ = coordinates.Z / Region.Depth - ((coordinates.Z < 0) ? 1 : 0);
var regionPosition = new Coordinates2D(regionX, regionZ);
if (!Regions.ContainsKey(regionPosition)) return null;
return (Chunk)((Region)Regions[regionPosition]).GetChunkWithoutGeneration(
new Coordinates2D(coordinates.X - regionX * 32, coordinates.Z - regionZ * 32));
}
public void SetChunk(Coordinates2D coordinates, Chunk chunk)
{
int regionX = coordinates.X / Region.Width - ((coordinates.X < 0) ? 1 : 0);
int regionZ = coordinates.Z / Region.Depth - ((coordinates.Z < 0) ? 1 : 0);
var region = LoadOrGenerateRegion(new Coordinates2D(regionX, regionZ));
lock (region)
{
chunk.IsModified = true;
region.SetChunk(new Coordinates2D(coordinates.X - regionX * 32, coordinates.Z - regionZ * 32), chunk);
}
}
public void UnloadRegion(Coordinates2D coordinates)
{
lock (Regions)
{
Regions[coordinates].Save(Path.Combine(BaseDirectory, Region.GetRegionFileName(coordinates)));
Regions.Remove(coordinates);
}
}
public void UnloadChunk(Coordinates2D coordinates)
{
int regionX = coordinates.X / Region.Width - ((coordinates.X < 0) ? 1 : 0);
int regionZ = coordinates.Z / Region.Depth - ((coordinates.Z < 0) ? 1 : 0);
var regionPosition = new Coordinates2D(regionX, regionZ);
if (!Regions.ContainsKey(regionPosition))
throw new ArgumentOutOfRangeException("coordinates");
Regions[regionPosition].UnloadChunk(new Coordinates2D(coordinates.X - regionX * 32, coordinates.Z - regionZ * 32));
}
public short GetBlockID(Coordinates3D coordinates)
{
IChunk chunk;
coordinates = FindBlockPosition(coordinates, out chunk);
return chunk.GetBlockID(coordinates);
}
public byte GetMetadata(Coordinates3D coordinates)
{
IChunk chunk;
coordinates = FindBlockPosition(coordinates, out chunk);
return chunk.GetMetadata(coordinates);
}
public byte GetSkyLight(Coordinates3D coordinates)
{
IChunk chunk;
coordinates = FindBlockPosition(coordinates, out chunk);
return chunk.GetSkyLight(coordinates);
}
public byte GetBlockLight(Coordinates3D coordinates)
{
IChunk chunk;
coordinates = FindBlockPosition(coordinates, out chunk);
return chunk.GetBlockLight(coordinates);
}
public void SetBlockID(Coordinates3D coordinates, short value)
{
IChunk chunk;
var adjustedCoordinates = FindBlockPosition(coordinates, out chunk);
chunk.SetBlockID(adjustedCoordinates, value);
}
public void SetMetadata(Coordinates3D coordinates, byte value)
{
IChunk chunk;
var adjustedCoordinates = FindBlockPosition(coordinates, out chunk);
chunk.SetMetadata(adjustedCoordinates, value);
}
public void SetSkyLight(Coordinates3D coordinates, byte value)
{
IChunk chunk;
coordinates = FindBlockPosition(coordinates, out chunk);
chunk.SetSkyLight(coordinates, value);
}
public void SetBlockLight(Coordinates3D coordinates, byte value)
{
IChunk chunk;
coordinates = FindBlockPosition(coordinates, out chunk);
chunk.SetBlockLight(coordinates, value);
}
public void Save()
{
lock (Regions)
{
foreach (var region in Regions)
region.Value.Save(Path.Combine(BaseDirectory, Region.GetRegionFileName(region.Key)));
}
}
public void Save(string path)
{
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
BaseDirectory = path;
lock (Regions)
{
foreach (var region in Regions)
region.Value.Save(Path.Combine(BaseDirectory, Region.GetRegionFileName(region.Key)));
}
}
public Coordinates3D FindBlockPosition(Coordinates3D coordinates, out IChunk chunk)
{
if (coordinates.Y < 0 || coordinates.Y >= Chunk.Height)
throw new ArgumentOutOfRangeException("coordinates", "Coordinates are out of range");
var chunkX = (int)Math.Floor((double)coordinates.X / Chunk.Width);
var chunkZ = (int)Math.Floor((double)coordinates.Z / Chunk.Depth);
chunk = GetChunk(new Coordinates2D(chunkX, chunkZ));
return new Coordinates3D(
(coordinates.X - chunkX * Chunk.Width) % Chunk.Width,
coordinates.Y,
(coordinates.Z - chunkZ * Chunk.Depth) % Chunk.Depth);
}
public bool IsValidPosition(Coordinates3D position)
{
return position.Y >= 0 && position.Y <= 255;
}
private Region LoadOrGenerateRegion(Coordinates2D coordinates)
{
if (Regions.ContainsKey(coordinates))
return (Region)Regions[coordinates];
Region region;
if (BaseDirectory != null)
{
var file = Path.Combine(BaseDirectory, Region.GetRegionFileName(coordinates));
if (File.Exists(file))
region = new Region(coordinates, this, file);
else
region = new Region(coordinates, this);
}
else
region = new Region(coordinates, this);
lock (Regions)
Regions[coordinates] = region;
return region;
}
public void Dispose()
{
foreach (var region in Regions)
region.Value.Dispose();
}
}
}

View File

@ -19,7 +19,15 @@ namespace TrueCraft.Handlers
{
var packet = (LoginRequestPacket)_packet;
var client = (RemoteClient)_client;
client.QueuePacket(new DisconnectPacket("It works!"));
Console.WriteLine(packet.ProtocolVersion);
if (packet.ProtocolVersion < server.PacketReader.ProtocolVersion)
client.QueuePacket(new DisconnectPacket("Client outdated! Use beta 1.7.3!"));
else if (packet.ProtocolVersion > server.PacketReader.ProtocolVersion)
client.QueuePacket(new DisconnectPacket("Server outdated! Use beta 1.7.3!"));
else
{
client.LoggedIn = true;
}
}
}
}

View File

@ -72,7 +72,7 @@ namespace TrueCraft
{
PacketHandlers[packet.ID](packet, client, this);
}
catch (Exception e)
catch (Exception)
{
// TODO: Something else
Clients.Remove(client);

View File

@ -19,6 +19,7 @@ namespace TrueCraft
public IMinecraftStream MinecraftStream { get; internal set; }
public ConcurrentQueue<IPacket> PacketQueue { get; private set; }
public string Username { get; internal set; }
public bool LoggedIn { get; internal set; }
public bool DataAvailable
{

View File

@ -0,0 +1,3 @@
Differences between TrueCraft and vanilla beta 1.7.3:
- Uses the Anvil level format instead of MCRegion

10
doc/login-sequence Normal file
View File

@ -0,0 +1,10 @@
C->S: Handshake
C<-S: Handshake response
[authenticate if needed]
C->S: Login request
C<-S: Login response or kick
C<-S: Chunks and entities
C<-S: Spawn position
C<-S: Inventory
C<-S: Position+Look
S<-C: Position+Look, player is now logged in

BIN
lib/Ionic.Zip.Reduced.dll Normal file

Binary file not shown.