]> git.localhorst.tv Git - blank.git/commitdiff
load block types from data file
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 12 Aug 2015 11:46:09 +0000 (13:46 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 12 Aug 2015 11:46:09 +0000 (13:46 +0200)
21 files changed:
TODO
assets
src/app/Assets.hpp
src/app/UnloadState.cpp
src/app/app.cpp
src/app/io.cpp [deleted file]
src/app/io.hpp [deleted file]
src/app/runtime.cpp
src/io/Token.hpp [new file with mode: 0644]
src/io/TokenStreamReader.hpp [new file with mode: 0644]
src/io/Tokenizer.hpp [new file with mode: 0644]
src/io/WorldSave.cpp [new file with mode: 0644]
src/io/WorldSave.hpp [new file with mode: 0644]
src/io/filesystem.cpp [new file with mode: 0644]
src/io/filesystem.hpp [new file with mode: 0644]
src/io/token.cpp [new file with mode: 0644]
src/world/World.cpp
src/world/World.hpp
src/world/WorldSave.cpp [deleted file]
src/world/WorldSave.hpp [deleted file]
src/world/chunk.cpp

diff --git a/TODO b/TODO
index 33a5c8de0e9bfbb26ab7e204c46ba9767158aab9..057b7359385a55917da083adbb8ae2df18fdfa1b 100644 (file)
--- 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 449410ec8e8e4b4bd7878b99aef9dc77ed422ff1..424ac96eaafa7e1eb49fabcc9f670a5aa3f54ca3 160000 (submodule)
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 449410ec8e8e4b4bd7878b99aef9dc77ed422ff1
+Subproject commit 424ac96eaafa7e1eb49fabcc9f670a5aa3f54ca3
index 40623a21af6bdeb483e1f480eccdd3b3a3036a75..e6c209effdd7745b6c0d9ad6e798b47a131f1e9f 100644 (file)
@@ -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
index dfe270ed61f39301032abd3a6a3121f8df017363..534a14acb4a0b9496382fb6023b2ea3dd9418a35 100644 (file)
@@ -1,8 +1,8 @@
 #include "UnloadState.hpp"
 
 #include "Environment.hpp"
+#include "../io/WorldSave.hpp"
 #include "../world/ChunkLoader.hpp"
-#include "../world/WorldSave.hpp"
 
 
 namespace blank {
index 8817d1f75bf821d48f58c5c1b6726f735bb60d53..3257f8b890867d4ae1e001cbd7816a58f1cab0d6 100644 (file)
 #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 <fstream>
 #include <iostream>
 #include <stdexcept>
 #include <SDL_image.h>
 
+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 &reg) 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 (file)
index f9c5805..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-#include "io.hpp"
-
-#include <cerrno>
-#ifdef _WIN32
-#  include <direct.h>
-#endif
-#include <sys/stat.h>
-
-
-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 (file)
index 966c4dc..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef BLANK_APP_IO_HPP_
-#define BLANK_APP_IO_HPP_
-
-#include <string>
-
-
-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
index 85d8280987b8459f3e50fbaac37d026a5924a92a..ff699794c95f15e2604a45a6ec57e9b289aab4cb 100644 (file)
@@ -4,7 +4,7 @@
 #include "WorldState.hpp"
 
 #include "init.hpp"
-#include "../world/WorldSave.hpp"
+#include "../io/WorldSave.hpp"
 
 #include <cctype>
 #include <cstdlib>
diff --git a/src/io/Token.hpp b/src/io/Token.hpp
new file mode 100644 (file)
index 0000000..9813eb0
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef BLANK_IO_TOKEN_HPP_
+#define BLANK_IO_TOKEN_HPP_
+
+#include <string>
+
+
+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 (file)
index 0000000..caa6207
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef BLANK_IO_TOKENSTREAMREADER_HPP_
+#define BLANK_IO_TOKENSTREAMREADER_HPP_
+
+#include "Token.hpp"
+#include "Tokenizer.hpp"
+
+#include <iosfwd>
+#include <string>
+#include <glm/glm.hpp>
+
+
+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 (file)
index 0000000..8fb9800
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef BLANK_IO_TOKENIZER_HPP_
+#define BLANK_IO_TOKENIZER_HPP_
+
+#include "Token.hpp"
+
+#include <iosfwd>
+
+
+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 &in;
+       Token current;
+
+};
+
+}
+
+#endif
diff --git a/src/io/WorldSave.cpp b/src/io/WorldSave.cpp
new file mode 100644 (file)
index 0000000..d90bce1
--- /dev/null
@@ -0,0 +1,145 @@
+#include "WorldSave.hpp"
+
+#include "filesystem.hpp"
+
+#include <cctype>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <stdexcept>
+#include <zlib.h>
+
+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<int>::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 (file)
index 0000000..4095016
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef BLANK_IO_WORLDSAVE_HPP_
+#define BLANK_IO_WORLDSAVE_HPP_
+
+#include "../world/Chunk.hpp"
+#include "../world/World.hpp"
+
+#include <memory>
+#include <string>
+
+
+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<char[]> chunk_buf;
+
+};
+
+}
+
+#endif
diff --git a/src/io/filesystem.cpp b/src/io/filesystem.cpp
new file mode 100644 (file)
index 0000000..8a06d1c
--- /dev/null
@@ -0,0 +1,102 @@
+#include "filesystem.hpp"
+
+#include <cerrno>
+#ifdef _WIN32
+#  include <direct.h>
+#endif
+#include <sys/stat.h>
+
+
+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 (file)
index 0000000..3829e0d
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef BLANK_IO_FILESYSTEM_HPP_
+#define BLANK_IO_FILESYSTEM_HPP_
+
+#include <string>
+
+
+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 (file)
index 0000000..5e734ad
--- /dev/null
@@ -0,0 +1,368 @@
+#include "Tokenizer.hpp"
+#include "TokenStreamReader.hpp"
+
+#include <cctype>
+#include <istream>
+#include <stdexcept>
+
+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());
+}
+
+}
index 19219e4dd9360a85b68c69cc81b9f982fd94eb54..5827e8f872f2112e6c057d5c98958ab6d4bbd5bf 100644 (file)
 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);
index 2c343dc7fd5a857e87c4705ee6ed1d972d311643..2fd312c7ce39efb990ed2be07b16f0ce6b7313a8 100644 (file)
@@ -6,7 +6,6 @@
 #include "Entity.hpp"
 #include "Generator.hpp"
 #include "../graphics/ArrayTexture.hpp"
-#include "../model/shapes.hpp"
 
 #include <list>
 #include <vector>
@@ -50,7 +49,7 @@ public:
        bool Intersection(const Entity &e, std::vector<WorldCollision> &);
        void Resolve(Entity &e, std::vector<WorldCollision> &);
 
-       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 (file)
index d0e8112..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-#include "WorldSave.hpp"
-
-#include "../app/io.hpp"
-
-#include <cctype>
-#include <fstream>
-#include <iostream>
-#include <limits>
-#include <stdexcept>
-#include <zlib.h>
-
-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<int>::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 (file)
index b08b373..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-#ifndef BLANK_WORLD_WORLDSAVE_HPP_
-#define BLANK_WORLD_WORLDSAVE_HPP_
-
-#include "Chunk.hpp"
-#include "World.hpp"
-
-#include <memory>
-#include <string>
-
-
-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<char[]> chunk_buf;
-
-};
-
-}
-
-#endif
index 31dfe80b3134bb1a9dea1ba29a4df28c2df317e8..a6decf70906408558fc68f08a790da5225c126b8 100644 (file)
@@ -4,7 +4,7 @@
 
 #include "Generator.hpp"
 #include "WorldCollision.hpp"
-#include "WorldSave.hpp"
+#include "../io/WorldSave.hpp"
 
 #include <algorithm>
 #include <limits>