Adapt Craft.Net.Anvil for world support
This commit is contained in:
parent
51a50d8a03
commit
96ecbc708c
@ -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
|
||||||
|
@ -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);
|
||||||
|
85
TrueCraft.API/NibbleArray.cs
Normal file
85
TrueCraft.API/NibbleArray.cs
Normal 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); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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">
|
||||||
|
22
TrueCraft.API/World/IChunk.cs
Normal file
22
TrueCraft.API/World/IChunk.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
12
TrueCraft.API/World/IChunkProvider.cs
Normal file
12
TrueCraft.API/World/IChunkProvider.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
15
TrueCraft.API/World/IRegion.cs
Normal file
15
TrueCraft.API/World/IRegion.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
22
TrueCraft.API/World/ISection.cs
Normal file
22
TrueCraft.API/World/ISection.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
24
TrueCraft.API/World/IWorld.cs
Normal file
24
TrueCraft.API/World/IWorld.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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];
|
||||||
|
@ -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>
|
248
TrueCraft.Core/World/Chunk.cs
Normal file
248
TrueCraft.Core/World/Chunk.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
306
TrueCraft.Core/World/Region.cs
Normal file
306
TrueCraft.Core/World/Region.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
TrueCraft.Core/World/Section.cs
Normal file
123
TrueCraft.Core/World/Section.cs
Normal 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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
235
TrueCraft.Core/World/World.cs
Normal file
235
TrueCraft.Core/World/World.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
3
doc/differences-from-vanilla
Normal file
3
doc/differences-from-vanilla
Normal 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
10
doc/login-sequence
Normal 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
BIN
lib/Ionic.Zip.Reduced.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user