1 #include "WorldSave.hpp"
3 #include "filesystem.hpp"
4 #include "TokenStreamReader.hpp"
5 #include "../app/error.hpp"
14 #include <glm/gtx/io.hpp>
21 WorldSave::WorldSave(const string &path)
23 , world_conf_path(path + "world.conf")
24 , gen_conf_path(path + "gen.conf")
25 , player_path(path + "player/")
26 , chunk_path(path + "chunks/%d/%d/%d.gz")
27 , chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits<int>::digits10)
28 , chunk_buf(new char[chunk_bufsiz]) {
33 bool WorldSave::Exists() const noexcept {
34 return is_dir(root_path) && is_file(world_conf_path);
38 void WorldSave::Read(World::Config &conf) const {
39 ifstream is(world_conf_path);
41 throw runtime_error("failed to open world config");
43 TokenStreamReader in(is);
46 while (in.HasMore()) {
47 in.ReadIdentifier(name);
48 in.Skip(Token::EQUALS);
49 if (name == "spawn") {
50 in.ReadVec(conf.spawn);
52 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
53 in.Skip(Token::SEMICOLON);
58 throw runtime_error("IO error reading world config");
62 void WorldSave::Write(const World::Config &conf) const {
63 if (!make_dirs(root_path)) {
64 throw runtime_error("failed to create world save directory");
67 ofstream out(world_conf_path);
68 out << "spawn = " << conf.spawn << ';' << endl;
72 throw runtime_error("failed to write world config");
77 void WorldSave::Read(Generator::Config &conf) const {
78 ifstream is(gen_conf_path);
80 throw runtime_error("failed to open generator config");
82 TokenStreamReader in(is);
85 while (in.HasMore()) {
86 in.ReadIdentifier(name);
87 in.Skip(Token::EQUALS);
89 in.ReadNumber(conf.seed);
91 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
92 in.Skip(Token::SEMICOLON);
97 throw runtime_error("IO error reading generator config");
101 void WorldSave::Write(const Generator::Config &conf) const {
102 if (!make_dirs(root_path)) {
103 throw runtime_error("failed to create world save directory");
106 ofstream out(gen_conf_path);
107 out << "seed = " << conf.seed << ';' << endl;
111 throw runtime_error("failed to write generator config");
116 bool WorldSave::Exists(const Player &player) const {
117 return is_file(PlayerPath(player));
120 void WorldSave::Read(Player &player) const {
121 ifstream is(PlayerPath(player));
122 TokenStreamReader in(is);
125 while (in.HasMore()) {
126 in.ReadIdentifier(name);
127 in.Skip(Token::EQUALS);
128 if (name == "chunk") {
129 in.ReadVec(state.pos.chunk);
130 } else if (name == "position") {
131 in.ReadVec(state.pos.block);
132 } else if (name == "orientation") {
133 in.ReadQuat(state.orient);
134 } else if (name == "pitch") {
135 state.pitch = in.GetFloat();
136 } else if (name == "yaw") {
137 state.yaw = in.GetFloat();
138 } else if (name == "slot") {
141 player.SetInventorySlot(slot);
143 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
144 in.Skip(Token::SEMICOLON);
147 player.GetEntity().SetState(state);
151 void WorldSave::Write(const Player &player) const {
152 if (!make_dirs(player_path)) {
153 throw runtime_error("failed to create player save directory");
155 const EntityState &state = player.GetEntity().GetState();
156 ofstream out(PlayerPath(player));
157 out << "chunk = " << state.pos.chunk << ';' << endl;
158 out << "position = " << state.pos.block << ';' << endl;
159 out << "orientation = " << state.orient << ';' << endl;
160 out << "pitch = " << state.pitch << ';' << endl;
161 out << "yaw = " << state.yaw << ';' << endl;
162 out << "slot = " << player.GetInventorySlot() << ';' << endl;
165 string WorldSave::PlayerPath(const Player &player) const {
166 // TODO: this is potentially dangerous, server and client should
167 // provide a sanitized name for storage
168 return player_path + player.Name();
172 bool WorldSave::Exists(const ExactLocation::Coarse &pos) const noexcept {
173 return is_file(ChunkPath(pos));
176 void WorldSave::Read(Chunk &chunk) const {
177 const char *path = ChunkPath(chunk.Position());
178 gzFile file = gzopen(path, "r");
180 throw runtime_error("failed to open chunk file");
182 if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
183 throw runtime_error("failed to read chunk from file");
185 if (gzclose(file) != Z_OK) {
186 throw runtime_error("failed to read chunk file");
189 chunk.InvalidateMesh();
193 void WorldSave::Write(Chunk &chunk) const {
194 const char *path = ChunkPath(chunk.Position());
195 gzFile file = gzopen(path, "w");
197 // check if it's because of a missing path component
198 if (errno != ENOENT) {
202 string dir_path(path);
203 dir_path.erase(dir_path.find_last_of("\\/"));
204 if (!make_dirs(dir_path)) {
205 throw runtime_error("failed to create dir for chunk file");
207 file = gzopen(path, "w");
209 throw runtime_error("failed to open chunk file");
212 if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) {
213 gzclose(file); // if this fails, it can't be helped
214 throw runtime_error("failed to write chunk to file");
216 if (gzclose(file) != Z_OK) {
217 throw runtime_error("failed to write chunk file");
223 const char *WorldSave::ChunkPath(const ExactLocation::Coarse &pos) const {
224 snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
225 return chunk_buf.get();