]> git.localhorst.tv Git - blank.git/blobdiff - src/io/WorldSave.cpp
move common exceptions to app/error
[blank.git] / src / io / WorldSave.cpp
index d90bce1654c4c5b0e1745043e328e8746d3e92c2..529d0d91a5784da2f1cf2cea55ae62a1fc87b75c 100644 (file)
@@ -1,13 +1,17 @@
 #include "WorldSave.hpp"
 
 #include "filesystem.hpp"
+#include "TokenStreamReader.hpp"
+#include "../app/error.hpp"
 
 #include <cctype>
+#include <cstring>
 #include <fstream>
 #include <iostream>
 #include <limits>
 #include <stdexcept>
 #include <zlib.h>
+#include <glm/gtx/io.hpp>
 
 using namespace std;
 
@@ -16,7 +20,9 @@ namespace blank {
 
 WorldSave::WorldSave(const string &path)
 : root_path(path)
-, conf_path(path + "world.conf")
+, world_conf_path(path + "world.conf")
+, gen_conf_path(path + "gen.conf")
+, player_path(path + "player/")
 , 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]) {
@@ -25,69 +31,145 @@ WorldSave::WorldSave(const string &path)
 
 
 bool WorldSave::Exists() const noexcept {
-       return is_dir(root_path) && is_file(conf_path);
+       return is_dir(root_path) && is_file(world_conf_path);
 }
 
 
 void WorldSave::Read(World::Config &conf) const {
-       cout << "reading world save" << endl;
-
-       ifstream in(conf_path);
-       if (!in) {
+       ifstream is(world_conf_path);
+       if (!is) {
                throw runtime_error("failed to open world config");
        }
+       TokenStreamReader in(is);
+
+       string name;
+       while (in.HasMore()) {
+               in.ReadIdentifier(name);
+               in.Skip(Token::EQUALS);
+               if (name == "spawn") {
+                       in.ReadVec(conf.spawn);
+               }
+               if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
+                       in.Skip(Token::SEMICOLON);
+               }
+       }
+
+       if (is.bad()) {
+               throw runtime_error("IO error reading world config");
+       }
+}
 
-       constexpr char spaces[] = "\n\r\t ";
+void WorldSave::Write(const World::Config &conf) const {
+       if (!make_dirs(root_path)) {
+               throw runtime_error("failed to create world save directory");
+       }
 
-       string line;
-       while (getline(in, line)) {
-               if (line.empty() || line[0] == '#') continue;
-               auto equals_pos = line.find_first_of('=');
+       ofstream out(world_conf_path);
+       out << "spawn = " << conf.spawn << ';' << endl;
+       out.close();
 
-               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;
-               }
+       if (!out) {
+               throw runtime_error("failed to write world config");
+       }
+}
 
-               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);
+void WorldSave::Read(Generator::Config &conf) const {
+       ifstream is(gen_conf_path);
+       if (!is) {
+               throw runtime_error("failed to open generator config");
+       }
+       TokenStreamReader in(is);
 
+       string name;
+       while (in.HasMore()) {
+               in.ReadIdentifier(name);
+               in.Skip(Token::EQUALS);
                if (name == "seed") {
-                       conf.gen.seed = stoul(value);
-               } else {
-                       throw runtime_error("unknown world option: " + name);
+                       in.ReadNumber(conf.seed);
+               }
+               if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
+                       in.Skip(Token::SEMICOLON);
                }
        }
-       if (in.bad()) {
-               throw runtime_error("IO error reading world config");
+
+       if (is.bad()) {
+               throw runtime_error("IO error reading generator config");
        }
 }
 
-void WorldSave::Write(const World::Config &conf) const {
-       cout << "writing world save" << endl;
-
+void WorldSave::Write(const Generator::Config &conf) const {
        if (!make_dirs(root_path)) {
                throw runtime_error("failed to create world save directory");
        }
 
-       ofstream out(conf_path);
-       out << "seed = " << conf.gen.seed << endl;
+       ofstream out(gen_conf_path);
+       out << "seed = " << conf.seed << ';' << endl;
        out.close();
 
        if (!out) {
-               throw runtime_error("failed to write world config");
+               throw runtime_error("failed to write generator config");
        }
 }
 
 
-bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept {
+bool WorldSave::Exists(const Player &player) const {
+       return is_file(PlayerPath(player));
+}
+
+void WorldSave::Read(Player &player) const {
+       ifstream is(PlayerPath(player));
+       TokenStreamReader in(is);
+       string name;
+       EntityState state;
+       while (in.HasMore()) {
+               in.ReadIdentifier(name);
+               in.Skip(Token::EQUALS);
+               if (name == "chunk") {
+                       in.ReadVec(state.pos.chunk);
+               } else if (name == "position") {
+                       in.ReadVec(state.pos.block);
+               } else if (name == "orientation") {
+                       in.ReadQuat(state.orient);
+               } else if (name == "pitch") {
+                       state.pitch = in.GetFloat();
+               } else if (name == "yaw") {
+                       state.yaw = in.GetFloat();
+               } else if (name == "slot") {
+                       int slot;
+                       in.ReadNumber(slot);
+                       player.SetInventorySlot(slot);
+               }
+               if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
+                       in.Skip(Token::SEMICOLON);
+               }
+       }
+       player.GetEntity().SetState(state);
+       player.Update(0);
+}
+
+void WorldSave::Write(const Player &player) const {
+       if (!make_dirs(player_path)) {
+               throw runtime_error("failed to create player save directory");
+       }
+       const EntityState &state = player.GetEntity().GetState();
+       ofstream out(PlayerPath(player));
+       out << "chunk = " << state.pos.chunk << ';' << endl;
+       out << "position = " << state.pos.block << ';' << endl;
+       out << "orientation = " << state.orient << ';' << endl;
+       out << "pitch = " << state.pitch << ';' << endl;
+       out << "yaw = " << state.yaw << ';' << endl;
+       out << "slot = " << player.GetInventorySlot() << ';' << endl;
+}
+
+string WorldSave::PlayerPath(const Player &player) const {
+       // TODO: this is potentially dangerous, server and client should
+       //       provide a sanitized name for storage
+       return player_path + player.Name();
+}
+
+
+bool WorldSave::Exists(const ExactLocation::Coarse &pos) const noexcept {
        return is_file(ChunkPath(pos));
 }
 
@@ -103,7 +185,8 @@ void WorldSave::Read(Chunk &chunk) const {
        if (gzclose(file) != Z_OK) {
                throw runtime_error("failed to read chunk file");
        }
-       chunk.InvalidateModel();
+       chunk.ScanActive();
+       chunk.InvalidateMesh();
        chunk.ClearSave();
 }
 
@@ -114,7 +197,7 @@ void WorldSave::Write(Chunk &chunk) const {
                // check if it's because of a missing path component
                if (errno != ENOENT) {
                        // nope, fatal
-                       throw runtime_error(strerror(errno));
+                       throw SysError();
                }
                string dir_path(path);
                dir_path.erase(dir_path.find_last_of("\\/"));
@@ -137,7 +220,7 @@ void WorldSave::Write(Chunk &chunk) const {
 }
 
 
-const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const {
+const char *WorldSave::ChunkPath(const ExactLocation::Coarse &pos) const {
        snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
        return chunk_buf.get();
 }