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
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
-Subproject commit 449410ec8e8e4b4bd7878b99aef9dc77ed422ff1
+Subproject commit 424ac96eaafa7e1eb49fabcc9f670a5aa3f54ca3
namespace blank {
class ArrayTexture;
+class BlockTypeRegistry;
class Sound;
class Texture;
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;
std::string fonts;
std::string sounds;
std::string textures;
+ std::string data;
public:
// common assets shared by may states
#include "UnloadState.hpp"
#include "Environment.hpp"
+#include "../io/WorldSave.hpp"
#include "../world/ChunkLoader.hpp"
-#include "../world/WorldSave.hpp"
namespace blank {
#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;
: 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);
+++ /dev/null
-#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;
-
- }
-}
-
-}
+++ /dev/null
-#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
#include "WorldState.hpp"
#include "init.hpp"
-#include "../world/WorldSave.hpp"
+#include "../io/WorldSave.hpp"
#include <cctype>
#include <cstdlib>
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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 ∈
+ Token current;
+
+};
+
+}
+
+#endif
--- /dev/null
+#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();
+}
+
+}
--- /dev/null
+#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
--- /dev/null
+#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;
+
+ }
+}
+
+}
--- /dev/null
+#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
--- /dev/null
+#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());
+}
+
+}
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);
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);
#include "Entity.hpp"
#include "Generator.hpp"
#include "../graphics/ArrayTexture.hpp"
-#include "../model/shapes.hpp"
#include <list>
#include <vector>
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; }
void Render(Viewport &);
private:
- BlockTypeRegistry blockType;
- CuboidShape blockShape;
- StairShape stairShape;
- CuboidShape slabShape;
+ BlockTypeRegistry block_type;
ArrayTexture block_tex;
+++ /dev/null
-#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();
-}
-
-}
+++ /dev/null
-#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
#include "Generator.hpp"
#include "WorldCollision.hpp"
-#include "WorldSave.hpp"
+#include "../io/WorldSave.hpp"
#include <algorithm>
#include <limits>