Merge branch 'master' of https://git.froggi.es/frogcraft/FeatherMC into master

This commit is contained in:
Joshua Ashton 2020-08-13 05:27:26 +01:00
commit 2d1403548c
24 changed files with 110630 additions and 22 deletions

11
.gitignore vendored
View File

@ -1,9 +1,12 @@
# common meson build dirs
/build
/build.32
/build.64
/build.cross
# /build
# /build.32
# /build.64
# /build.cross
# vscode per-user stuff
.vscode/c_cpp_properties.json
.vscode/settings.json
# Visual Studio
.vs

110013
bin/data/blocks.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
project('FeatherMC', ['c', 'cpp'], version : '0.0', meson_version : '>= 0.55', default_options : [
'warning_level=2',
'backend_startup_project=FeatherMC'
])
add_project_arguments('-DNOMINMAX', language : 'cpp')

View File

@ -1,11 +1,14 @@
#include "DedicatedServer.h"
#include "config/ServerProperties.h"
#include "data/Registry.h"
#include "PacketReader.h"
#include <chrono>
#include <string>
using std::string;
namespace Feather
{
DedicatedServer::DedicatedServer(ServerProperties* properties) :
@ -15,7 +18,9 @@ namespace Feather
{
m_status.descriptionText = properties->motd.GetValue();
m_status.maxPlayers = properties->maxPlayers;
Registry::Init();
m_worldManager.LoadWorld(m_properties->levelName);
while (1)
@ -189,7 +194,7 @@ namespace Feather
Play::ClientboundPlayerPositionAndLook playerPos =
{
.x = 0,
.y = 64,
.y = 70,
.z = 0,
.xRot = 0,
.yRot = 0,
@ -198,6 +203,92 @@ namespace Feather
};
client.SendMessage(playerPos);
NetworkMessage chunkData(5KB);
// Packet ID
chunkData.WriteVarInt(0x22);
// Chunk X, Z
chunkData.Write<int32_t>(-10);
chunkData.Write<int32_t>(-12);
// Full chunk
chunkData.Write(true);
Chunk* chunk = m_worldManager.GetOverworld()->m_chunk;
NBT::CompoundTag* chunkNBT = m_worldManager.GetOverworld()->m_chunkNBT;
int32_t sectionsBits = 0;
NetworkMessage sections(3KB);
for (int i = 0; i < Chunk::NUM_SECTIONS; i++)
{
ChunkSection sect = chunk->sections[i];
Log::Trace("Chunk: Got Section {} with local palette size {}", i, sect.localPalette.Size());
sectionsBits |= 1 << i;
// Block Count: fudge
sections.Write<uint16_t>(1024);
// Bits Per Block
sections.Write<uint8_t>(4);
// Palette
NetworkMessage paletteData(512);
for (int i = 0; i < sect.localPalette.Size(); i++)
{
int id = sect.localPalette.ByID(i);
Log::Trace("Palette: {} -> {}", i, id);
paletteData.WriteVarInt(id);
i++;
}
paletteData.Finalize();
sections.WriteData(paletteData.GetData(), paletteData.GetDataSize());
// Block Data
//NBT::LongArrayTag blocks = sect.Get<NBT::LongArrayTag>("BlockStates");
sections.WriteVarInt(ChunkSection::NUM_LONGS);
sections.WriteData(sect.blockStates, ChunkSection::NUM_LONGS * sizeof(int64_t));
}
//Log::Trace("Section bits: {:#b}", sectionsBits);
chunkData.WriteVarInt(sectionsBits);
// Heightmaps
// TODO: Use chunk->heightmaps
NBT::DataBuffer buf = chunkNBT->Get<NBT::CompoundTag>("Heightmaps").GetData();
chunkData.WriteData(buf.data, buf.length);
// Biomes
for (int i = 0; i < 1024; i++)
chunkData.Write<int32_t>(5);
//NBT::IntArrayTag biomes = chunk->Get<NBT::IntArrayTag>("Biomes");
//NBT::DataBuffer buf1 = biomes.GetData();
//chunkData.WriteData(buf1.data, buf1.length);
sections.Finalize();
chunkData.WriteVarInt(sections.GetDataSize());
chunkData.WriteData(sections.GetData(), sections.GetDataSize());
// Block Ents
chunkData.WriteVarInt(0);
chunkData.Finalize();
client.SendMessage(chunkData);
}
template <>

View File

@ -63,7 +63,7 @@ namespace Feather
ChunkPos(BlockPos pos) : x(pos.x >> 4), z(pos.z >> 4) {}
// Decode from packed int64
ChunkPos(int64_t packed) : x(packed), z(packed >> 32) {}
ChunkPos(int64_t packed) : x((int32_t)packed), z(packed >> 32) {}
// Pack into one int64
inline int64_t Encode()

View File

@ -0,0 +1,36 @@
#pragma once
#include "Common.h"
#include <string>
#include <string_view>
#include <functional>
namespace Feather
{
class BlockState
{
// PLACEHOLDER
std::string_view m_name;
public:
BlockState(std::string_view name) : m_name(name) {}
inline const std::string_view GetName() const { return m_name; }
inline bool operator ==(const BlockState that) const
{
return m_name == that.m_name;
}
};
}
template <>
struct std::hash<Feather::BlockState>
{
size_t operator()(const Feather::BlockState& state) const
{
using std::hash;
using std::string_view;
return hash<string_view>()(state.GetName());
}
};

View File

@ -32,6 +32,9 @@ namespace Feather
}
catch (const std::out_of_range& err)
{
// ignore unreferenced local var
(void)err;
// process default value from constructor
SetValue(value);
}

56
src/data/IDMapper.h Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include <vector>
#include <unordered_map>
namespace Feather
{
template <typename T>
class IDMapper
{
std::vector<T> m_IDtoValue;
std::unordered_map<T, int> m_ValueToID;
int m_NextID = 0;
public:
IDMapper(int size = 512)
{
m_IDtoValue.reserve(size);
m_ValueToID.reserve(size);
}
inline void AddMapping(int id, T& value)
{
m_IDtoValue.insert(m_IDtoValue.begin() + id, value);
m_ValueToID.insert({ value, id });
if (m_NextID <= id) {
m_NextID = id + 1;
}
}
inline void Add(T& value)
{
AddMapping(m_NextID, value);
}
inline int GetID(T& value) const
{
return m_ValueToID.at(value);
}
inline const T& ByID(int id) const
{
return m_IDtoValue.at(id);
}
inline int Size() const
{
return m_IDtoValue.size();
}
};
}

67
src/data/Registry.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "Common.h"
#include "Registry.h"
#include "rapidjson/document.h"
#include "rapidjson/filereadstream.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
namespace Feather
{
IDMapper<BlockState> Registry::BLOCK_STATES;
void Registry::Init()
{
static bool initialized = false;
if (initialized) return;
Log::Info("Loading data/blocks.json...");
{
using namespace rapidjson;
std::string text;
FILE* fp = fopen("data/blocks.json", "rb");
if (!fp) {
Log::Error("Failed to read data/blocks.json");
return;
}
fseek(fp, 0, SEEK_END);
text.resize(ftell(fp));
rewind(fp);
fread(&text[0], 1, text.size(), fp);
fclose(fp);
Document doc;
doc.Parse(text.c_str());
for (auto& block : doc.GetObject())
{
//Log::Info("{}: {} states", m.name.GetString(), m.value["states"].Size());
for (auto& stateData : block.value["states"].GetArray())
{
std::string* name = new std::string(block.name.GetString());
BlockState state(*name);
BLOCK_STATES.AddMapping(stateData["id"].GetInt(), state);
}
}
Log::Info("Loaded {} block states.", BLOCK_STATES.Size());
//StringBuffer buf;
//Writer<StringBuffer> writer(buf);
//doc.Accept(writer);
// FIXME: why does this crash
//Log::Info(text);
}
}
}

16
src/data/Registry.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include "IDMapper.h"
#include "block/state/BlockState.h"
namespace Feather
{
class Registry
{
public:
static IDMapper<BlockState> BLOCK_STATES;
static void Init();
};
}

View File

@ -9,7 +9,7 @@
#include <vector>
#include <array>
//#define FEATHER_Log::COLUMNS
//#define FEATHER_LOG_COLUMNS
namespace Feather::Log
{
@ -105,7 +105,7 @@ namespace Feather::Log
{
std::string_view levelString = GetLevelStringView(level);
std::string_view channelString = m_channels[channel].GetName();
#ifdef FEATHER_Log::COLUMNS
#ifdef FEATHER_LOG_COLUMNS
std::array<char, MaxLevelLength + 1> levelSpaces = {};
std::fill_n(levelSpaces.data(), MaxLevelLength - levelString.size(), ' ');

View File

@ -7,6 +7,8 @@ feather_src = [
'nbt/NBT.cpp',
'data/Registry.cpp',
'network/NetworkManager.cpp',
'network/TCPListener.cpp',
'network/TCPClient.cpp',
@ -18,6 +20,7 @@ feather_src = [
'config/Properties.cpp',
'config/ServerProperties.cpp',
'world/Chunk.cpp',
'world/World.cpp',
'world/WorldManager.cpp',
'world/RegionFile.cpp',

View File

@ -41,7 +41,8 @@ namespace NBT
DataBuffer::~DataBuffer()
{
// equivalent to buffer_free in cNBT buffer.h
free(data);
// FIXME
//free(data);
}
Tag Tag::CreateTag(nbt_node *node)
@ -137,6 +138,11 @@ namespace NBT
return nbt_dump_ascii(m_node);
}
Tag::operator bool() const
{
return m_node != NULL && m_node->type != TAG_INVALID;
}
std::ostream& operator<<(std::ostream& stream, const Tag& tag)
{
return stream << tag.ToString();
@ -155,13 +161,19 @@ namespace NBT
//==================================================
CompoundTag::CompoundTag(const char *filename)
: CompoundTag(nbt_parse_path(filename))
: CompoundTag(nbt_parse_path(filename), true) // m_isRoot = true, this calls malloc
{
}
CompoundTag::CompoundTag(const void* data, size_t length)
: CompoundTag(nbt_parse_compressed(data, length), true) // m_isRoot = true, this calls malloc
{
}
CompoundTag::~CompoundTag()
{
nbt_free(m_node);
if (m_isRoot)
nbt_free(m_node);
}
const Tag CompoundTag::operator[](const char *name) const

View File

@ -45,6 +45,8 @@ namespace NBT
const DataBuffer GetData() const;
const char* ToString() const;
operator bool() const;
};
// Operator overload for ostream
@ -79,6 +81,8 @@ namespace NBT
const T* GetValue() const;
inline const T operator[](int32_t index) const { return GetValue()[index]; }
// Methods for range-based for loops
// TODO: check if this works for IntArrayTag and LongArrayTag
const T* begin() { return GetValue(); }
@ -131,10 +135,19 @@ namespace NBT
// A Compound Tag is an unordered list of named tags
class CompoundTag : public Tag
{
// only true if the tree was allocated by constructor
const bool m_isRoot;
CompoundTag(nbt_node* node, bool isRoot) : Tag(node), m_isRoot(false) {}
public:
CompoundTag(nbt_node* node) : Tag(node) {}
CompoundTag(nbt_node* node) : CompoundTag(node, false) {}
// Read tree from file
CompoundTag(const char* filename);
// Read tree from compressed data
CompoundTag(const void* data, size_t length);
CompoundTag(const char *filename);
~CompoundTag();
template <typename T>
@ -143,6 +156,8 @@ namespace NBT
template <typename T>
const T Get(const char* name) const;
inline const CompoundTag GetCompound(const char* name) const { return Get<CompoundTag>(name); }
const Tag operator[](const char* name) const;
};

View File

@ -194,6 +194,30 @@
id: int64
}
}
/*ChunkData:
{
id: 0x22
vars: {
chunkX: int32
chunkZ: int32
fullChunk: bool
// bitmask for each 16x16x16 chunk section
// LSB: section at y=0, bits for up to y=15
chunkSections: varint
heightmaps: nbt
// only if fullChunk == true
biomes: int32[1024]
dataSize: varint
data: byte[dataSize]
numBlockEntities: varint
blockEntities: nbt[numBlockEntities]
}
}*/
PlayerPositionAndLook:
{

101
src/world/Chunk.cpp Normal file
View File

@ -0,0 +1,101 @@
#include "Chunk.h"
#include "World.h"
namespace Feather
{
Chunk::Chunk(ChunkPos& pos, NBT::CompoundTag& tag) :
pos(pos)
{
using namespace NBT;
CompoundTag level = tag.GetCompound("Level");
Log::Trace("Loading chunk ({}, {}) from NBT", pos.x, pos.z);
if (pos.x != level.Get<int>("xPos") || pos.z != level.Get<int>("zPos"))
{
Log::Error("Tried to load chunk ({}, {}) from NBT but NBT says chunk pos is ({}, {})",
pos.x, pos.z,
level.Get<int>("xPos"), level.Get<int>("zPos")
);
}
// TODO: Heightmaps
ListTag<CompoundTag> sectionsList = level.GetList<CompoundTag>("Sections");
int n = 0;
for (CompoundTag& sectData : sectionsList)
{
//std::cout << sectData << "\n";
int8_t y = sectData.Get<int8_t>("Y");
if (y < 0 || y >= NUM_SECTIONS) {
Log::Trace("Skipping section {}", y);
continue;
}
Log::Trace("Loading section {}/{} at Y {}", n + 1, NUM_SECTIONS, y);
if (n >= NUM_SECTIONS)
{
Log::Error("Chunk ({}, {}) has more than {} sections!", pos.x, pos.z, NUM_SECTIONS);
break;
}
ChunkSection* section = &sections[y];
// Palette
ListTag<CompoundTag> palette = sectData.GetList<CompoundTag>("Palette");
for (int i = 0; i < palette.GetLength(); i++)
{
CompoundTag state = palette[i];
const char* name = state.Get<StringTag>("Name").GetValue();
BlockState stateLookup(name);
int id;
// TODO: handle this in IdMapper
try {
id = World::GLOBAL_PALETTE.GetID(stateLookup);
}
catch (std::out_of_range& err) {
(void)err;
Log::Warn("Chunk Section Palette maps id {} to invalid block state with name '{}'", i, name);
continue;
}
// TODO: could use Add()
section->localPalette.AddMapping(i, id);
Log::Trace(" {} -> {} {}", i, id, name);
}
// Block States
LongArrayTag blockStates = sectData.Get<LongArrayTag>("BlockStates");
if (!blockStates) {
Log::Warn("Skipping section with no block states.");
continue;
}
int len = blockStates.GetLength();
if (len != ChunkSection::NUM_LONGS) {
Log::Warn("Section {} has {} longs, expected {}", y, len, ChunkSection::NUM_LONGS);
}
for (int i = 0; i < blockStates.GetLength() && i < ChunkSection::NUM_LONGS; i++)
{
section->blockStates[i] = blockStates[i];
}
n++;
}
}
}

48
src/world/Chunk.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include "Types.h"
#include "nbt/NBT.h"
#include "PalettedContainer.h"
#include "block/state/BlockState.h"
#include <cstdint>
namespace Feather
{
// 16x16x16 sections of a chunk
class ChunkSection
{
public:
// 4 bits per block
// 16^3 = 4096 blocks total
// 4096 * 4 = 16384 bits = 256x int64
// 16 blocks per int64
static constexpr int NUM_LONGS = 256;
int64_t blockStates[NUM_LONGS];
// TODO: this should eventually be Palette<BlockState>
// Palette of local indexs to indexs in the global palette
IDMapper<int> localPalette;
};
// Chunk: 16x256x16 world chunk
class Chunk
{
public:
static constexpr int NUM_SECTIONS = 16;
// Each chunk is made of up 16 ChunkSections
ChunkSection sections[NUM_SECTIONS];
ChunkPos pos;
// TODO: save heightmaps here
// UNSERIALIZED
//const NBT::CompoundTag* heightmaps;
Chunk(ChunkPos& pos, NBT::CompoundTag& tag);
};
};

35
src/world/Palette.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include "data/IDMapper.h"
namespace Feather
{
template <typename T>
class Palette
{
protected:
const IDMapper<T>& m_registry;
public:
Palette(const IDMapper<T>& registry) :
m_registry(registry)
{}
virtual int GetID(T& value) const = 0;
virtual const T& ByID(int id) const = 0;
};
template <typename T>
class GlobalPalette : public Palette<T>
{
const T* m_defaultValue;
public:
GlobalPalette(const IDMapper<T>& registry, const T* defaultValue) :
Palette(registry),
m_defaultValue(defaultValue)
{}
inline virtual int GetID(T& value) const final { return m_registry.GetID(value); }
inline virtual const T& ByID(int id) const final { return m_registry.ByID(id); }
};
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "Palette.h"
namespace Feather
{
// A container that maps values from a local palette to a
template <typename T>
class PalettedContainer
{
const Palette<T>& m_globalPalette;
};
}

View File

@ -8,15 +8,16 @@ using std::string;
namespace Feather
{
RegionFile::RegionFile(string path)
: m_path(path)
{
std::ifstream stream(path);
stream.read(m_header, 8KB);
m_stream = new std::ifstream(path, std::ifstream::binary);
m_stream->read(m_header, 8KB);
// Populate m_offsets
for (int i = 0; i < 1KB; i++)
for (int i = 0; i < 1024; i++)
{
int32_t readInt = 0;
std::memcpy(&readInt, &m_header[i * 4], sizeof(int32_t));
std::memcpy(&readInt, &m_header[i * sizeof(int32_t)], sizeof(int32_t));
// Read big endian ints.
readInt = ReverseBytes(readInt);
@ -26,6 +27,7 @@ namespace Feather
//int sector = GetSectorNumber(readInt);
//int sectorCount = GetSectorCount(readInt);
//Log::Trace("{}: Sector [{:x}] Count:{} Num:{}", i, readInt, sectorCount, sector);
// We can record which sectors are used by this region here
}
}

View File

@ -1,7 +1,10 @@
#pragma once
#include "Common.h"
#include "Types.h"
#include <string>
#include <iostream>
#include <fstream>
namespace Feather
{
@ -10,6 +13,11 @@ namespace Feather
char m_header[8KB];
int32_t m_offsets[1KB];
// TEMP
std::ifstream* m_stream;
std::string m_path;
inline int GetOffsetIndex(RegionLocalPos pos) { return pos.x + pos.z * 32; }
public:
RegionFile(std::string path);
@ -20,8 +28,14 @@ namespace Feather
// Contains data for this chunk?
inline bool HasChunk(ChunkPos pos) { return GetOffset(RegionLocalPos(pos)) != 0; }
// TEMP
std::ifstream* GetChunkStream(RegionLocalPos pos) {
m_stream->seekg(GetSectorNumber(GetOffset(pos)));
return m_stream;
}
// Unpack sector num from result of GetOffset()
static int GetSectorNumber(int packedOffset) { return packedOffset >> 8; }
static int GetSectorNumber(int packedOffset) { return (packedOffset >> 8) * 4096; }
// Unpack sector count from result of GetOffset()
static int GetSectorCount(int packedOffset) { return packedOffset & 0xFF; }

View File

@ -3,6 +3,7 @@
#include "World.h"
#include "nbt/NBT.h"
#include "RegionFile.h"
#include "data/Registry.h"
#include <iostream>
#include <fstream>
@ -15,6 +16,11 @@ using std::stringstream;
namespace Feather
{
const GlobalPalette<BlockState> World::GLOBAL_PALETTE = GlobalPalette<BlockState>(
Registry::BLOCK_STATES,
new BlockState("minecraft:air") // temp
);
static bool CheckPath(fs::path path, bool dir = false)
{
if (!fs::exists(path) || (dir && !fs::is_directory(path)))
@ -50,19 +56,55 @@ namespace Feather
m_levelData.spawnY = levelDat.Get<int32_t>("SpawnY");
m_levelData.spawnZ = levelDat.Get<int32_t>("SpawnZ");
ChunkPos spawnChunk(BlockPos(m_levelData.spawnX, m_levelData.spawnY, m_levelData.spawnZ));
BlockPos spawnPos(m_levelData.spawnX, m_levelData.spawnY, m_levelData.spawnZ);
ChunkPos spawnChunk(spawnPos);
RegionPos regionPos(spawnChunk);
RegionLocalPos regionChunkPos(spawnChunk);
stringstream regionFile;
regionFile << "r." << regionPos.x << "." << regionPos.z << ".mca";
fs::path mcaFile = regionsPath / regionFile.str();
if (!CheckPath(mcaFile)) return;
Log::Trace("Spawn Chunk: Block({} {} {}) -> Chunk({} {}) -> Region({} {}) -> RegionChunk({} {})",
spawnPos.x, spawnPos.y, spawnPos.z,
spawnChunk.x, spawnChunk.z,
regionPos.x, regionPos.z,
regionChunkPos.x, regionChunkPos.z
);
//Log_Info("Spawn Chunk Region File: %s", mcaFile.string().c_str());
RegionFile region(mcaFile.string());
//int regionOffset = region.GetOffset(regionChunkPos);
//std::cout << region << "\n";
//int sectorNum = RegionFile::GetSectorNumber(regionOffset);
//Log::Trace("Loading chunk from offset: [{}] = {}", regionChunkPos.x + regionChunkPos.z * 32, sectorNum);
std::ifstream* chunkStream = region.GetChunkStream(regionChunkPos);
Log::Trace("{}", chunkStream->good());
char lengthBytes[5];
chunkStream->read(lengthBytes, 5);
int32_t length = 0;
std::memcpy(&length, lengthBytes, sizeof(int32_t));
length = ReverseBytes(length);
int8_t compressionType = lengthBytes[4];
Log::Trace("Loading chunk: Length: {}. Compression: {}", length, compressionType);
char* chunkData = new char[length + 1];
chunkStream->read(chunkData, length);
m_chunkNBT = new NBT::CompoundTag(chunkData, length);
std::cout << *m_chunkNBT << "\n";
m_chunk = new Chunk(spawnChunk, *m_chunkNBT);
/*for (auto& f : fs::directory_iterator(regionsPath))
{

View File

@ -1,6 +1,10 @@
#pragma once
#include "Common.h"
#include "nbt/NBT.h"
#include "Chunk.h"
#include "Palette.h"
#include <cstdint>
#include <string>
@ -9,6 +13,13 @@ namespace Feather
class World
{
public:
// global palette of all block states
static const GlobalPalette<BlockState> GLOBAL_PALETTE;
// TEMP
NBT::CompoundTag* m_chunkNBT;
Chunk* m_chunk;
struct LevelData
{
int32_t spawnX, spawnY, spawnZ;

View File

@ -14,5 +14,7 @@ namespace Feather
World* world;
public:
void LoadWorld(string name);
inline World* GetOverworld() { return world; }
};
}