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 ## 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 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 ## Blah blah blah
TrueCraft is not associated with Mojang or Minecraft in any sort of official 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 public interface IPacketReader
{ {
int ProtocolVersion { get; }
void RegisterPacketType<T>(bool clientbound = true, bool serverbound = true) where T : IPacket; void RegisterPacketType<T>(bool clientbound = true, bool serverbound = true) where T : IPacket;
IPacket ReadPacket(IMinecraftStream stream, bool serverbound = true); IPacket ReadPacket(IMinecraftStream stream, bool serverbound = true);
void WritePacket(IMinecraftStream stream, IPacket packet); 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="Vector3.cs" />
<Compile Include="Server\IMultiplayerServer.cs" /> <Compile Include="Server\IMultiplayerServer.cs" />
<Compile Include="Networking\IRemoteClient.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> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
<Folder Include="Networking\" /> <Folder Include="Networking\" />
<Folder Include="Server\" /> <Folder Include="Server\" />
<Folder Include="World\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\externals\fNbt\fNbt\fNbt.csproj"> <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 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[] ClientboundPackets = new Type[0x100];
private Type[] ServerboundPackets = new Type[0x100]; private Type[] ServerboundPackets = new Type[0x100];

View File

@ -30,6 +30,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="Ionic.Zip.Reduced">
<HintPath>..\lib\Ionic.Zip.Reduced.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
@ -93,6 +96,10 @@
<Compile Include="Networking\Packets\MapDataPacket.cs" /> <Compile Include="Networking\Packets\MapDataPacket.cs" />
<Compile Include="Networking\Packets\UpdateStatisticPacket.cs" /> <Compile Include="Networking\Packets\UpdateStatisticPacket.cs" />
<Compile Include="Networking\Packets\DisconnectPacket.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> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>
@ -100,8 +107,13 @@
<Project>{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}</Project> <Project>{FEE55B54-91B0-4325-A2C3-D576C0B7A81F}</Project>
<Name>TrueCraft.API</Name> <Name>TrueCraft.API</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\externals\fNbt\fNbt\fNbt.csproj">
<Project>{4488498D-976D-4DA3-BF72-109531AF0488}</Project>
<Name>fNbt</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Networking\Packets\" /> <Folder Include="Networking\Packets\" />
<Folder Include="World\" />
</ItemGroup> </ItemGroup>
</Project> </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 packet = (LoginRequestPacket)_packet;
var client = (RemoteClient)_client; 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); PacketHandlers[packet.ID](packet, client, this);
} }
catch (Exception e) catch (Exception)
{ {
// TODO: Something else // TODO: Something else
Clients.Remove(client); Clients.Remove(client);

View File

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