From: Daniel Karbach Date: Wed, 12 Aug 2015 11:46:09 +0000 (+0200) Subject: load block types from data file X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;ds=sidebyside;h=ede25c0a2f59e21521d1cd962e6ea9d78169ca12;p=blank.git load block types from data file --- diff --git a/TODO b/TODO index 33a5c8d..057b735 100644 --- a/TODO +++ b/TODO @@ -25,6 +25,11 @@ command line usefull for development and later on world administration +audio + + do the sources thing right™ + (or at least in a way that it doesn't error out on shutdown :P ) + persistence merge IO counters, so number of operations per frame is kept @@ -32,6 +37,15 @@ persistence store some kind of byte order mark? +block asset loading + + block type loader can determine which textures are needed in the array + it should compose a map while loading, assigning an ID to each + individual texture and after all types are loaded, load the appropriate + textures into the array + + also, parameterization of chunk generator should be less static/dangerous + networking exchange of chunks and entities diff --git a/assets b/assets index 449410e..424ac96 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 449410ec8e8e4b4bd7878b99aef9dc77ed422ff1 +Subproject commit 424ac96eaafa7e1eb49fabcc9f670a5aa3f54ca3 diff --git a/src/app/Assets.hpp b/src/app/Assets.hpp index 40623a2..e6c209e 100644 --- a/src/app/Assets.hpp +++ b/src/app/Assets.hpp @@ -9,6 +9,7 @@ namespace blank { class ArrayTexture; +class BlockTypeRegistry; class Sound; class Texture; @@ -17,6 +18,7 @@ class Assets { public: explicit Assets(const std::string &base); + void LoadBlockTypes(const std::string &set_name, BlockTypeRegistry &) const; Font LoadFont(const std::string &name, int size) const; Sound LoadSound(const std::string &name) const; Texture LoadTexture(const std::string &name) const; @@ -26,6 +28,7 @@ private: std::string fonts; std::string sounds; std::string textures; + std::string data; public: // common assets shared by may states diff --git a/src/app/UnloadState.cpp b/src/app/UnloadState.cpp index dfe270e..534a14a 100644 --- a/src/app/UnloadState.cpp +++ b/src/app/UnloadState.cpp @@ -1,8 +1,8 @@ #include "UnloadState.hpp" #include "Environment.hpp" +#include "../io/WorldSave.hpp" #include "../world/ChunkLoader.hpp" -#include "../world/WorldSave.hpp" namespace blank { diff --git a/src/app/app.cpp b/src/app/app.cpp index 8817d1f..3257f8b 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -10,13 +10,18 @@ #include "../graphics/ArrayTexture.hpp" #include "../graphics/Font.hpp" #include "../graphics/Texture.hpp" +#include "../io/TokenStreamReader.hpp" +#include "../model/shapes.hpp" #include "../world/BlockType.hpp" +#include "../world/BlockTypeRegistry.hpp" #include "../world/Entity.hpp" +#include #include #include #include +using std::runtime_error; using std::string; @@ -220,11 +225,95 @@ Assets::Assets(const string &base) : fonts(base + "fonts/") , sounds(base + "sounds/") , textures(base + "textures/") +, data(base + "data/") , large_ui_font(LoadFont("DejaVuSans", 24)) , small_ui_font(LoadFont("DejaVuSans", 16)) { } +namespace { + +CuboidShape block_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}); +StairShape stair_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f }); +CuboidShape slab_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }}); + +} + +void Assets::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry ®) const { + string full = data + set_name + ".types"; + std::ifstream file(full); + if (!file) { + throw std::runtime_error("failed to open block type file " + full); + } + TokenStreamReader in(file); + string type_name; + string name; + string tex_name; + string shape_name; + while (in.HasMore()) { + in.ReadIdentifier(type_name); + in.Skip(Token::EQUALS); + BlockType type; + + // read block type + in.Skip(Token::ANGLE_BRACKET_OPEN); + while (in.Peek().type != Token::ANGLE_BRACKET_CLOSE) { + in.ReadIdentifier(name); + in.Skip(Token::EQUALS); + if (name == "visible") { + type.visible = in.GetBool(); + } else if (name == "texture") { + // TODO: load textures as requested + in.ReadString(tex_name); + if (tex_name == "rock-1") { + type.texture = 1; + } else if (tex_name == "rock-2") { + type.texture = 2; + } else if (tex_name == "rock-3") { + type.texture = 3; + } else if (tex_name == "debug") { + type.texture = 0; + } else { + throw runtime_error("unknown texture: " + tex_name); + } + } else if (name == "color") { + in.ReadVec(type.color); + } else if (name == "label") { + in.ReadString(type.label); + } else if (name == "luminosity") { + type.luminosity = in.GetInt(); + } else if (name == "block_light") { + type.block_light = in.GetBool(); + } else if (name == "collision") { + type.collision = in.GetBool(); + } else if (name == "collide_block") { + type.collide_block = in.GetBool(); + } else if (name == "shape") { + in.ReadIdentifier(shape_name); + if (shape_name == "block") { + type.shape = &block_shape; + type.fill = { true, true, true, true, true, true }; + } else if (shape_name == "slab") { + type.shape = &slab_shape; + type.fill = { false, true, false, false, false, false }; + } else if (shape_name == "stair") { + type.shape = &stair_shape; + type.fill = { false, true, false, false, false, true }; + } else { + throw runtime_error("unknown block shape: " + shape_name); + } + } else { + throw runtime_error("unknown block property: " + name); + } + in.Skip(Token::SEMICOLON); + } + in.Skip(Token::ANGLE_BRACKET_CLOSE); + in.Skip(Token::SEMICOLON); + + reg.Add(type); + } +} + Font Assets::LoadFont(const string &name, int size) const { string full = fonts + name + ".ttf"; return Font(full.c_str(), size); diff --git a/src/app/io.cpp b/src/app/io.cpp deleted file mode 100644 index f9c5805..0000000 --- a/src/app/io.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "io.hpp" - -#include -#ifdef _WIN32 -# include -#endif -#include - - -namespace blank { - -bool is_dir(const char *path) { -#ifdef _WIN32 - struct _stat info; - if (_stat(path, &info) != 0) { - return false; - } - return (info.st_mode & _S_IFDIR) != 0; -#else - struct stat info; - if (stat(path, &info) != 0) { - return false; - } - return S_ISDIR(info.st_mode); -#endif -} - -bool is_file(const char *path) { -#ifdef _WIN32 - struct _stat info; - if (_stat(path, &info) != 0) { - return false; - } - return (info.st_mode & _S_IFREG) != 0; -#else - struct stat info; - if (stat(path, &info) != 0) { - return false; - } - return S_ISREG(info.st_mode); -#endif -} - - -bool make_dir(const char *path) { -#ifdef _WIN32 - int ret = _mkdir(path); -#else - int ret = mkdir(path, 0777); -#endif - return ret == 0; -} - - -bool make_dirs(const std::string &path) { - if (make_dir(path)) { - return true; - } - - switch (errno) { - - case ENOENT: - // missing component - { -#ifdef _WIN32 - auto pos = path.find_last_of("\\/"); -#else - auto pos = path.find_last_of('/'); -#endif - if (pos == std::string::npos) { - return false; - } - if (pos == path.length() - 1) { - // trailing separator, would make final make_dir fail -#ifdef _WIN32 - pos = path.find_last_of("\\/", pos - 1); -#else - pos = path.find_last_of('/', pos - 1); -#endif - if (pos == std::string::npos) { - return false; - } - } - if (!make_dirs(path.substr(0, pos))) { - return false; - } - } - // try again - return make_dir(path); - - case EEXIST: - // something's there, check if it's a dir and we're good - return is_dir(path); - - default: - // whatever else went wrong, it can't be good - return false; - - } -} - -} diff --git a/src/app/io.hpp b/src/app/io.hpp deleted file mode 100644 index 966c4dc..0000000 --- a/src/app/io.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef BLANK_APP_IO_HPP_ -#define BLANK_APP_IO_HPP_ - -#include - - -namespace blank { - -/// check if give path points to an existing directory -bool is_dir(const char *); -inline bool is_dir(const std::string &s) { - return is_dir(s.c_str()); -} -/// check if give path points to an existing file -bool is_file(const char *); -inline bool is_file(const std::string &s) { - return is_file(s.c_str()); -} - -/// create given directory -/// @return true if the directory was created -/// the directory might already exist, see errno -bool make_dir(const char *); -inline bool make_dir(const std::string &s) { - return make_dir(s.c_str()); -} -/// create given directory and all parents -/// @return true if the directory was created or already exists -bool make_dirs(const std::string &); - -} - -#endif diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index 85d8280..ff69979 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -4,7 +4,7 @@ #include "WorldState.hpp" #include "init.hpp" -#include "../world/WorldSave.hpp" +#include "../io/WorldSave.hpp" #include #include diff --git a/src/io/Token.hpp b/src/io/Token.hpp new file mode 100644 index 0000000..9813eb0 --- /dev/null +++ b/src/io/Token.hpp @@ -0,0 +1,34 @@ +#ifndef BLANK_IO_TOKEN_HPP_ +#define BLANK_IO_TOKEN_HPP_ + +#include + + +namespace blank { + +struct Token { + enum Type { + UNKNOWN = 0, + ANGLE_BRACKET_OPEN = '{', + ANGLE_BRACKET_CLOSE = '}', + CHEVRON_OPEN = '<', + CHEVRON_CLOSE = '>', + BRACKET_OPEN = '[', + BRACKET_CLOSE = ']', + PARENTHESIS_OPEN = '(', + PARENTHESIS_CLOSE = ')', + COLON = ':', + SEMICOLON = ';', + COMMA = ',', + EQUALS = '=', + NUMBER = '0', + STRING = '"', + IDENTIFIER = 'a', + COMMENT = '#', + } type = UNKNOWN; + std::string value; +}; + +} + +#endif diff --git a/src/io/TokenStreamReader.hpp b/src/io/TokenStreamReader.hpp new file mode 100644 index 0000000..caa6207 --- /dev/null +++ b/src/io/TokenStreamReader.hpp @@ -0,0 +1,57 @@ +#ifndef BLANK_IO_TOKENSTREAMREADER_HPP_ +#define BLANK_IO_TOKENSTREAMREADER_HPP_ + +#include "Token.hpp" +#include "Tokenizer.hpp" + +#include +#include +#include + + +namespace blank { + +class TokenStreamReader { + +public: + TokenStreamReader(std::istream &); + + bool HasMore(); + const Token &Next(); + const Token &Peek(); + + void Skip(Token::Type); + + void ReadBoolean(bool &); + void ReadIdentifier(std::string &); + void ReadNumber(float &); + void ReadNumber(int &); + void ReadNumber(unsigned long &); + void ReadString(std::string &); + + void ReadVec(glm::vec2 &); + void ReadVec(glm::vec3 &); + void ReadVec(glm::vec4 &); + + void ReadVec(glm::ivec2 &); + void ReadVec(glm::ivec3 &); + void ReadVec(glm::ivec4 &); + + bool GetBool(); + float GetFloat(); + int GetInt(); + unsigned long GetULong(); + +private: + void Assert(Token::Type); + Token::Type GetType() const noexcept; + const std::string &GetValue() const noexcept; + + Tokenizer in; + bool cached; + +}; + +} + +#endif diff --git a/src/io/Tokenizer.hpp b/src/io/Tokenizer.hpp new file mode 100644 index 0000000..8fb9800 --- /dev/null +++ b/src/io/Tokenizer.hpp @@ -0,0 +1,37 @@ +#ifndef BLANK_IO_TOKENIZER_HPP_ +#define BLANK_IO_TOKENIZER_HPP_ + +#include "Token.hpp" + +#include + + +namespace blank { + +class Tokenizer { + +public: + +public: + explicit Tokenizer(std::istream &in); + + bool HasMore(); + const Token &Next(); + const Token &Current() const noexcept { return current; } + +private: + void ReadToken(); + + void ReadNumber(); + void ReadString(); + void ReadComment(); + void ReadIdentifier(); + + std::istream ∈ + Token current; + +}; + +} + +#endif diff --git a/src/io/WorldSave.cpp b/src/io/WorldSave.cpp new file mode 100644 index 0000000..d90bce1 --- /dev/null +++ b/src/io/WorldSave.cpp @@ -0,0 +1,145 @@ +#include "WorldSave.hpp" + +#include "filesystem.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +namespace blank { + +WorldSave::WorldSave(const string &path) +: root_path(path) +, conf_path(path + "world.conf") +, chunk_path(path + "chunks/%d/%d/%d.gz") +, chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits::digits10) +, chunk_buf(new char[chunk_bufsiz]) { + +} + + +bool WorldSave::Exists() const noexcept { + return is_dir(root_path) && is_file(conf_path); +} + + +void WorldSave::Read(World::Config &conf) const { + cout << "reading world save" << endl; + + ifstream in(conf_path); + if (!in) { + throw runtime_error("failed to open world config"); + } + + constexpr char spaces[] = "\n\r\t "; + + string line; + while (getline(in, line)) { + if (line.empty() || line[0] == '#') continue; + auto equals_pos = line.find_first_of('='); + + auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces)); + auto name_end = equals_pos - 1; + while (name_end > name_begin && isspace(line[name_end])) { + --name_end; + } + + auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces)); + auto value_end = line.length() - 1; + while (value_end > value_begin && isspace(line[value_end])) { + --value_end; + } + + string name(line, name_begin, name_end - name_begin + 1); + string value(line, value_begin, value_end - value_begin + 1); + + if (name == "seed") { + conf.gen.seed = stoul(value); + } else { + throw runtime_error("unknown world option: " + name); + } + } + if (in.bad()) { + throw runtime_error("IO error reading world config"); + } +} + +void WorldSave::Write(const World::Config &conf) const { + cout << "writing world save" << endl; + + if (!make_dirs(root_path)) { + throw runtime_error("failed to create world save directory"); + } + + ofstream out(conf_path); + out << "seed = " << conf.gen.seed << endl; + out.close(); + + if (!out) { + throw runtime_error("failed to write world config"); + } +} + + +bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept { + return is_file(ChunkPath(pos)); +} + +void WorldSave::Read(Chunk &chunk) const { + const char *path = ChunkPath(chunk.Position()); + gzFile file = gzopen(path, "r"); + if (!file) { + throw runtime_error("failed to open chunk file"); + } + if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) { + throw runtime_error("failed to read chunk from file"); + } + if (gzclose(file) != Z_OK) { + throw runtime_error("failed to read chunk file"); + } + chunk.InvalidateModel(); + chunk.ClearSave(); +} + +void WorldSave::Write(Chunk &chunk) const { + const char *path = ChunkPath(chunk.Position()); + gzFile file = gzopen(path, "w"); + if (!file) { + // check if it's because of a missing path component + if (errno != ENOENT) { + // nope, fatal + throw runtime_error(strerror(errno)); + } + string dir_path(path); + dir_path.erase(dir_path.find_last_of("\\/")); + if (!make_dirs(dir_path)) { + throw runtime_error("failed to create dir for chunk file"); + } + file = gzopen(path, "w"); + if (!file) { + throw runtime_error("failed to open chunk file"); + } + } + if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) { + gzclose(file); // if this fails, it can't be helped + throw runtime_error("failed to write chunk to file"); + } + if (gzclose(file) != Z_OK) { + throw runtime_error("failed to write chunk file"); + } + chunk.ClearSave(); +} + + +const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const { + snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z); + return chunk_buf.get(); +} + +} diff --git a/src/io/WorldSave.hpp b/src/io/WorldSave.hpp new file mode 100644 index 0000000..4095016 --- /dev/null +++ b/src/io/WorldSave.hpp @@ -0,0 +1,42 @@ +#ifndef BLANK_IO_WORLDSAVE_HPP_ +#define BLANK_IO_WORLDSAVE_HPP_ + +#include "../world/Chunk.hpp" +#include "../world/World.hpp" + +#include +#include + + +namespace blank { + +class WorldSave { + +public: + explicit WorldSave(const std::string &path); + +public: + // whole save + bool Exists() const noexcept; + void Read(World::Config &) const; + void Write(const World::Config &) const; + + // single chunk + bool Exists(const Chunk::Pos &) const noexcept; + void Read(Chunk &) const; + void Write(Chunk &) const; + + const char *ChunkPath(const Chunk::Pos &) const; + +private: + std::string root_path; + std::string conf_path; + std::string chunk_path; + std::size_t chunk_bufsiz; + std::unique_ptr chunk_buf; + +}; + +} + +#endif diff --git a/src/io/filesystem.cpp b/src/io/filesystem.cpp new file mode 100644 index 0000000..8a06d1c --- /dev/null +++ b/src/io/filesystem.cpp @@ -0,0 +1,102 @@ +#include "filesystem.hpp" + +#include +#ifdef _WIN32 +# include +#endif +#include + + +namespace blank { + +bool is_dir(const char *path) { +#ifdef _WIN32 + struct _stat info; + if (_stat(path, &info) != 0) { + return false; + } + return (info.st_mode & _S_IFDIR) != 0; +#else + struct stat info; + if (stat(path, &info) != 0) { + return false; + } + return S_ISDIR(info.st_mode); +#endif +} + +bool is_file(const char *path) { +#ifdef _WIN32 + struct _stat info; + if (_stat(path, &info) != 0) { + return false; + } + return (info.st_mode & _S_IFREG) != 0; +#else + struct stat info; + if (stat(path, &info) != 0) { + return false; + } + return S_ISREG(info.st_mode); +#endif +} + + +bool make_dir(const char *path) { +#ifdef _WIN32 + int ret = _mkdir(path); +#else + int ret = mkdir(path, 0777); +#endif + return ret == 0; +} + + +bool make_dirs(const std::string &path) { + if (make_dir(path)) { + return true; + } + + switch (errno) { + + case ENOENT: + // missing component + { +#ifdef _WIN32 + auto pos = path.find_last_of("\\/"); +#else + auto pos = path.find_last_of('/'); +#endif + if (pos == std::string::npos) { + return false; + } + if (pos == path.length() - 1) { + // trailing separator, would make final make_dir fail +#ifdef _WIN32 + pos = path.find_last_of("\\/", pos - 1); +#else + pos = path.find_last_of('/', pos - 1); +#endif + if (pos == std::string::npos) { + return false; + } + } + if (!make_dirs(path.substr(0, pos))) { + return false; + } + } + // try again + return make_dir(path); + + case EEXIST: + // something's there, check if it's a dir and we're good + return is_dir(path); + + default: + // whatever else went wrong, it can't be good + return false; + + } +} + +} diff --git a/src/io/filesystem.hpp b/src/io/filesystem.hpp new file mode 100644 index 0000000..3829e0d --- /dev/null +++ b/src/io/filesystem.hpp @@ -0,0 +1,33 @@ +#ifndef BLANK_IO_FILESYSTEM_HPP_ +#define BLANK_IO_FILESYSTEM_HPP_ + +#include + + +namespace blank { + +/// check if give path points to an existing directory +bool is_dir(const char *); +inline bool is_dir(const std::string &s) { + return is_dir(s.c_str()); +} +/// check if give path points to an existing file +bool is_file(const char *); +inline bool is_file(const std::string &s) { + return is_file(s.c_str()); +} + +/// create given directory +/// @return true if the directory was created +/// the directory might already exist, see errno +bool make_dir(const char *); +inline bool make_dir(const std::string &s) { + return make_dir(s.c_str()); +} +/// create given directory and all parents +/// @return true if the directory was created or already exists +bool make_dirs(const std::string &); + +} + +#endif diff --git a/src/io/token.cpp b/src/io/token.cpp new file mode 100644 index 0000000..5e734ad --- /dev/null +++ b/src/io/token.cpp @@ -0,0 +1,368 @@ +#include "Tokenizer.hpp" +#include "TokenStreamReader.hpp" + +#include +#include +#include + +using namespace std; + + +namespace blank { + +Tokenizer::Tokenizer(istream &in) +: in(in) +, current() { + +} + + +bool Tokenizer::HasMore() { + return bool(istream::sentry(in)); +} + +const Token &Tokenizer::Next() { + ReadToken(); + return Current(); +} + +void Tokenizer::ReadToken() { + current.type = Token::UNKNOWN; + current.value.clear(); + + istream::sentry s(in); + if (!s) { + // TODO: error? + return; + } + + istream::char_type c; + in.get(c); + switch (c) { + case '{': case '}': + case '<': case '>': + case '[': case ']': + case '(': case ')': + case ';': case ':': + case ',': case '=': + current.type = Token::Type(c); + break; + case '+': case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + in.putback(c); + ReadNumber(); + break; + case '"': + ReadString(); + break; + case '#': + case '/': + in.putback(c); + ReadComment(); + break; + default: + in.putback(c); + ReadIdentifier(); + break; + } +} + +namespace { + +bool is_num_char(istream::char_type c) { + return isdigit(c) + || c == '.' + || c == '-' + || c == '+' + ; +} + +} + +void Tokenizer::ReadNumber() { + current.type = Token::NUMBER; + istream::char_type c; + while (in.get(c)) { + if (is_num_char(c)) { + current.value += c; + } else { + in.putback(c); + break; + } + } +} + +void Tokenizer::ReadString() { + current.type = Token::STRING; + bool escape = false; + + istream::char_type c; + while (in.get(c)) { + if (escape) { + escape = false; + switch (c) { + case 'n': + current.value += '\n'; + break; + case 'r': + current.value += '\t'; + break; + case 't': + current.value += '\t'; + break; + default: + current.value += c; + break; + } + } else if (c == '"') { + break; + } else if (c == '\\') { + escape = true; + } else { + current.value += c; + } + } +} + +void Tokenizer::ReadComment() { + current.type = Token::COMMENT; + istream::char_type c; + in.get(c); + + if (c == '#') { + while (in.get(c) && c != '\n') { + current.value += c; + } + return; + } + + // c is guaranteed to be '/' now + if (!in.get(c)) { + throw runtime_error("unexpected end of stream"); + } + if (c == '/') { + while (in.get(c) && c != '\n') { + current.value += c; + } + return; + } else if (c != '*') { + throw runtime_error("invalid character after /"); + } + + while (in.get(c)) { + if (c == '*') { + istream::char_type c2; + if (!in.get(c2)) { + throw runtime_error("unexpected end of stream"); + } + if (c2 == '/') { + break; + } else { + current.value += c; + current.value += c2; + } + } else { + current.value += c; + } + } +} + +void Tokenizer::ReadIdentifier() { + current.type = Token::IDENTIFIER; + + istream::char_type c; + while (in.get(c)) { + if (isalnum(c) || c == '_') { + current.value += c; + } else { + in.putback(c); + break; + } + } +} + + +TokenStreamReader::TokenStreamReader(istream &in) +: in(in) +, cached(false) { + +} + + +bool TokenStreamReader::HasMore() { + while (in.HasMore()) { + if (in.Next().type != Token::COMMENT) { + cached = true; + return true; + } + } + return false; +} + +const Token &TokenStreamReader::Next() { + if (cached) { + cached = false; + return in.Current(); + } else { + return in.Next(); + } +} + +const Token &TokenStreamReader::Peek() { + if (!cached) { + in.Next(); + cached = true; + } + return in.Current(); +} + + +void TokenStreamReader::Assert(Token::Type t) { + if (GetType() != t) { + throw runtime_error("unexpected token in input stream"); + } +} + +Token::Type TokenStreamReader::GetType() const noexcept { + return in.Current().type; +} + +const std::string &TokenStreamReader::GetValue() const noexcept { + return in.Current().value; +} + +void TokenStreamReader::Skip(Token::Type t) { + Next(); + Assert(t); +} + + +void TokenStreamReader::ReadBoolean(bool &b) { + b = GetBool(); +} + +void TokenStreamReader::ReadIdentifier(string &out) { + Next(); + Assert(Token::IDENTIFIER); + out = GetValue(); +} + +void TokenStreamReader::ReadNumber(float &n) { + n = GetFloat(); +} + +void TokenStreamReader::ReadNumber(int &n) { + n = GetInt(); +} + +void TokenStreamReader::ReadNumber(unsigned long &n) { + n = GetULong(); +} + +void TokenStreamReader::ReadString(string &out) { + Next(); + Assert(Token::STRING); + out = GetValue(); +} + + +void TokenStreamReader::ReadVec(glm::vec2 &v) { + Skip(Token::BRACKET_OPEN); + ReadNumber(v.x); + Skip(Token::COMMA); + ReadNumber(v.y); + Skip(Token::BRACKET_CLOSE); +} + +void TokenStreamReader::ReadVec(glm::vec3 &v) { + Skip(Token::BRACKET_OPEN); + ReadNumber(v.x); + Skip(Token::COMMA); + ReadNumber(v.y); + Skip(Token::COMMA); + ReadNumber(v.z); + Skip(Token::BRACKET_CLOSE); +} + +void TokenStreamReader::ReadVec(glm::vec4 &v) { + Skip(Token::BRACKET_OPEN); + ReadNumber(v.x); + Skip(Token::COMMA); + ReadNumber(v.y); + Skip(Token::COMMA); + ReadNumber(v.z); + Skip(Token::COMMA); + ReadNumber(v.w); + Skip(Token::BRACKET_CLOSE); +} + +void TokenStreamReader::ReadVec(glm::ivec2 &v) { + Skip(Token::BRACKET_OPEN); + ReadNumber(v.x); + Skip(Token::COMMA); + ReadNumber(v.y); + Skip(Token::BRACKET_CLOSE); +} + +void TokenStreamReader::ReadVec(glm::ivec3 &v) { + Skip(Token::BRACKET_OPEN); + ReadNumber(v.x); + Skip(Token::COMMA); + ReadNumber(v.y); + Skip(Token::COMMA); + ReadNumber(v.z); + Skip(Token::BRACKET_CLOSE); +} + +void TokenStreamReader::ReadVec(glm::ivec4 &v) { + Skip(Token::BRACKET_OPEN); + ReadNumber(v.x); + Skip(Token::COMMA); + ReadNumber(v.y); + Skip(Token::COMMA); + ReadNumber(v.z); + Skip(Token::COMMA); + ReadNumber(v.w); + Skip(Token::BRACKET_CLOSE); +} + + +bool TokenStreamReader::GetBool() { + Next(); + switch (GetType()) { + case Token::NUMBER: + return GetInt() != 0; + case Token::IDENTIFIER: + case Token::STRING: + if (GetValue() == "true" || GetValue() == "yes" || GetValue() == "on") { + return true; + } else if (GetValue() == "false" || GetValue() == "no" || GetValue() == "off") { + return false; + } else { + throw runtime_error("unexpected value in input stream"); + } + default: + throw runtime_error("unexpected token in input stream"); + } +} + +float TokenStreamReader::GetFloat() { + Next(); + Assert(Token::NUMBER); + return stof(GetValue()); +} + +int TokenStreamReader::GetInt() { + Next(); + Assert(Token::NUMBER); + return stoi(GetValue()); +} + +unsigned long TokenStreamReader::GetULong() { + Next(); + Assert(Token::NUMBER); + return stoul(GetValue()); +} + +} diff --git a/src/world/World.cpp b/src/world/World.cpp index 19219e4..5827e8f 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -13,21 +13,14 @@ namespace blank { World::World(const Assets &assets, const Config &config, const WorldSave &save) -: blockType() -, blockShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}) -, stairShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f }) -, slabShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }}) +: block_type() , block_tex() , generate(config.gen) -, chunks(config.load, blockType, generate, save) +, chunks(config.load, block_type, generate, save) , player() , entities() , light_direction(config.light_direction) , fog_density(config.fog_density) { - BlockType::Faces block_fill = { true, true, true, true, true, true }; - BlockType::Faces slab_fill = { false, true, false, false, false, false }; - BlockType::Faces stair_fill = { false, true, false, false, false, true }; - block_tex.Bind(); block_tex.Reserve(16, 16, 4, Format()); assets.LoadTexture("debug", block_tex, 0); @@ -36,153 +29,7 @@ World::World(const Assets &assets, const Config &config, const WorldSave &save) assets.LoadTexture("rock-3", block_tex, 3); block_tex.FilterNearest(); - { // white block - BlockType type(true, { 1.0f, 1.0f, 1.0f }, &blockShape); - type.texture = 1; - type.label = "White Block"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = block_fill; - blockType.Add(type); - } - { // white slab - BlockType type(true, { 1.0f, 1.0f, 1.0f }, &slabShape); - type.texture = 1; - type.label = "White Slab"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = slab_fill; - blockType.Add(type); - } - { // white stair - BlockType type(true, { 1.0f, 1.0f, 1.0f }, &stairShape); - type.texture = 1; - type.label = "White Stair"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = stair_fill; - blockType.Add(type); - } - - { // red block - BlockType type(true, { 1.0f, 0.0f, 0.0f }, &blockShape); - type.texture = 3; - type.label = "Red Block"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = block_fill; - blockType.Add(type); - } - { // red slab - BlockType type(true, { 1.0f, 0.0f, 0.0f }, &slabShape); - type.texture = 3; - type.label = "Red Slab"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = slab_fill; - blockType.Add(type); - } - { // red stair - BlockType type(true, { 1.0f, 0.0f, 0.0f }, &stairShape); - type.texture = 3; - type.label = "Red Stair"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = stair_fill; - blockType.Add(type); - } - - { // green block - BlockType type(true, { 0.0f, 1.0f, 0.0f }, &blockShape); - type.texture = 1; - type.label = "Green Block"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = block_fill; - blockType.Add(type); - } - { // green slab - BlockType type(true, { 0.0f, 1.0f, 0.0f }, &slabShape); - type.texture = 1; - type.label = "Green Slab"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = slab_fill; - blockType.Add(type); - } - { // green stair - BlockType type(true, { 0.0f, 1.0f, 0.0f }, &stairShape); - type.texture = 1; - type.label = "Green Stair"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = stair_fill; - blockType.Add(type); - } - - { // blue block - BlockType type(true, { 0.0f, 0.0f, 1.0f }, &blockShape); - type.texture = 3; - type.label = "Blue Block"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = block_fill; - blockType.Add(type); - } - { // blue slab - BlockType type(true, { 0.0f, 0.0f, 1.0f }, &slabShape); - type.texture = 3; - type.label = "Blue Slab"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = slab_fill; - blockType.Add(type); - } - { // blue stair - BlockType type(true, { 0.0f, 0.0f, 1.0f }, &stairShape); - type.texture = 3; - type.label = "Blue Stair"; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = stair_fill; - blockType.Add(type); - } - - { // glowing yellow block - BlockType type(true, { 1.0f, 1.0f, 0.0f }, &blockShape); - type.texture = 2; - type.label = "Light"; - type.luminosity = 15; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = block_fill; - blockType.Add(type); - } - - { // the mysterious debug cube - BlockType type(true, { 1.0f, 1.0f, 1.0f }, &blockShape); - type.texture = 0; - type.label = "Debug Cube"; - type.luminosity = 0; - type.block_light = true; - type.collision = true; - type.collide_block = true; - type.fill = block_fill; - blockType.Add(type); - } + assets.LoadBlockTypes("default", block_type); generate.Space(0); generate.Light(13); diff --git a/src/world/World.hpp b/src/world/World.hpp index 2c343dc..2fd312c 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -6,7 +6,6 @@ #include "Entity.hpp" #include "Generator.hpp" #include "../graphics/ArrayTexture.hpp" -#include "../model/shapes.hpp" #include #include @@ -50,7 +49,7 @@ public: bool Intersection(const Entity &e, std::vector &); void Resolve(Entity &e, std::vector &); - BlockTypeRegistry &BlockTypes() noexcept { return blockType; } + BlockTypeRegistry &BlockTypes() noexcept { return block_type; } ChunkLoader &Loader() noexcept { return chunks; } Entity &Player() { return *player; } @@ -64,10 +63,7 @@ public: void Render(Viewport &); private: - BlockTypeRegistry blockType; - CuboidShape blockShape; - StairShape stairShape; - CuboidShape slabShape; + BlockTypeRegistry block_type; ArrayTexture block_tex; diff --git a/src/world/WorldSave.cpp b/src/world/WorldSave.cpp deleted file mode 100644 index d0e8112..0000000 --- a/src/world/WorldSave.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "WorldSave.hpp" - -#include "../app/io.hpp" - -#include -#include -#include -#include -#include -#include - -using namespace std; - - -namespace blank { - -WorldSave::WorldSave(const string &path) -: root_path(path) -, conf_path(path + "world.conf") -, chunk_path(path + "chunks/%d/%d/%d.gz") -, chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits::digits10) -, chunk_buf(new char[chunk_bufsiz]) { - -} - - -bool WorldSave::Exists() const noexcept { - return is_dir(root_path) && is_file(conf_path); -} - - -void WorldSave::Read(World::Config &conf) const { - cout << "reading world save" << endl; - - ifstream in(conf_path); - if (!in) { - throw runtime_error("failed to open world config"); - } - - constexpr char spaces[] = "\n\r\t "; - - string line; - while (getline(in, line)) { - if (line.empty() || line[0] == '#') continue; - auto equals_pos = line.find_first_of('='); - - auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces)); - auto name_end = equals_pos - 1; - while (name_end > name_begin && isspace(line[name_end])) { - --name_end; - } - - auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces)); - auto value_end = line.length() - 1; - while (value_end > value_begin && isspace(line[value_end])) { - --value_end; - } - - string name(line, name_begin, name_end - name_begin + 1); - string value(line, value_begin, value_end - value_begin + 1); - - if (name == "seed") { - conf.gen.seed = stoul(value); - } else { - throw runtime_error("unknown world option: " + name); - } - } - if (in.bad()) { - throw runtime_error("IO error reading world config"); - } -} - -void WorldSave::Write(const World::Config &conf) const { - cout << "writing world save" << endl; - - if (!make_dirs(root_path)) { - throw runtime_error("failed to create world save directory"); - } - - ofstream out(conf_path); - out << "seed = " << conf.gen.seed << endl; - out.close(); - - if (!out) { - throw runtime_error("failed to write world config"); - } -} - - -bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept { - return is_file(ChunkPath(pos)); -} - -void WorldSave::Read(Chunk &chunk) const { - const char *path = ChunkPath(chunk.Position()); - gzFile file = gzopen(path, "r"); - if (!file) { - throw runtime_error("failed to open chunk file"); - } - if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) { - throw runtime_error("failed to read chunk from file"); - } - if (gzclose(file) != Z_OK) { - throw runtime_error("failed to read chunk file"); - } - chunk.InvalidateModel(); - chunk.ClearSave(); -} - -void WorldSave::Write(Chunk &chunk) const { - const char *path = ChunkPath(chunk.Position()); - gzFile file = gzopen(path, "w"); - if (!file) { - // check if it's because of a missing path component - if (errno != ENOENT) { - // nope, fatal - throw runtime_error(strerror(errno)); - } - string dir_path(path); - dir_path.erase(dir_path.find_last_of("\\/")); - if (!make_dirs(dir_path)) { - throw runtime_error("failed to create dir for chunk file"); - } - file = gzopen(path, "w"); - if (!file) { - throw runtime_error("failed to open chunk file"); - } - } - if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) { - gzclose(file); // if this fails, it can't be helped - throw runtime_error("failed to write chunk to file"); - } - if (gzclose(file) != Z_OK) { - throw runtime_error("failed to write chunk file"); - } - chunk.ClearSave(); -} - - -const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const { - snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z); - return chunk_buf.get(); -} - -} diff --git a/src/world/WorldSave.hpp b/src/world/WorldSave.hpp deleted file mode 100644 index b08b373..0000000 --- a/src/world/WorldSave.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef BLANK_WORLD_WORLDSAVE_HPP_ -#define BLANK_WORLD_WORLDSAVE_HPP_ - -#include "Chunk.hpp" -#include "World.hpp" - -#include -#include - - -namespace blank { - -class WorldSave { - -public: - explicit WorldSave(const std::string &path); - -public: - // whole save - bool Exists() const noexcept; - void Read(World::Config &) const; - void Write(const World::Config &) const; - - // single chunk - bool Exists(const Chunk::Pos &) const noexcept; - void Read(Chunk &) const; - void Write(Chunk &) const; - - const char *ChunkPath(const Chunk::Pos &) const; - -private: - std::string root_path; - std::string conf_path; - std::string chunk_path; - std::size_t chunk_bufsiz; - std::unique_ptr chunk_buf; - -}; - -} - -#endif diff --git a/src/world/chunk.cpp b/src/world/chunk.cpp index 31dfe80..a6decf7 100644 --- a/src/world/chunk.cpp +++ b/src/world/chunk.cpp @@ -4,7 +4,7 @@ #include "Generator.hpp" #include "WorldCollision.hpp" -#include "WorldSave.hpp" +#include "../io/WorldSave.hpp" #include #include