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
|
||||
|
||||
[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
|
||||
|
@ -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);
|
||||
|
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="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">
|
||||
|
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 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];
|
||||
|
@ -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>
|
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 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);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Something else
|
||||
Clients.Remove(client);
|
||||
|
@ -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
|
||||
{
|
||||
|
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.
Loading…
x
Reference in New Issue
Block a user