1 #include "WorldSave.hpp"
3 #include "filesystem.hpp"
4 #include "TokenStreamReader.hpp"
13 #include <glm/gtx/io.hpp>
20 WorldSave::WorldSave(const string &path)
22 , world_conf_path(path + "world.conf")
23 , gen_conf_path(path + "gen.conf")
24 , player_path(path + "player/")
25 , chunk_path(path + "chunks/%d/%d/%d.gz")
26 , chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits<int>::digits10)
27 , chunk_buf(new char[chunk_bufsiz]) {
32 bool WorldSave::Exists() const noexcept {
33 return is_dir(root_path) && is_file(world_conf_path);
37 void WorldSave::Read(World::Config &conf) const {
38 ifstream is(world_conf_path);
40 throw runtime_error("failed to open world config");
42 TokenStreamReader in(is);
45 while (in.HasMore()) {
46 in.ReadIdentifier(name);
47 in.Skip(Token::EQUALS);
48 if (name == "spawn") {
49 in.ReadVec(conf.spawn);
51 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
52 in.Skip(Token::SEMICOLON);
57 throw runtime_error("IO error reading world config");
61 void WorldSave::Write(const World::Config &conf) const {
62 if (!make_dirs(root_path)) {
63 throw runtime_error("failed to create world save directory");
66 ofstream out(world_conf_path);
67 out << "spawn = " << conf.spawn << ';' << endl;
71 throw runtime_error("failed to write world config");
76 void WorldSave::Read(Generator::Config &conf) const {
77 ifstream is(gen_conf_path);
79 throw runtime_error("failed to open generator config");
81 TokenStreamReader in(is);
84 while (in.HasMore()) {
85 in.ReadIdentifier(name);
86 in.Skip(Token::EQUALS);
88 in.ReadNumber(conf.seed);
90 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
91 in.Skip(Token::SEMICOLON);
96 throw runtime_error("IO error reading generator config");
100 void WorldSave::Write(const Generator::Config &conf) const {
101 if (!make_dirs(root_path)) {
102 throw runtime_error("failed to create world save directory");
105 ofstream out(gen_conf_path);
106 out << "seed = " << conf.seed << ';' << endl;
110 throw runtime_error("failed to write generator config");
115 bool WorldSave::Exists(const Player &player) const {
116 return is_file(PlayerPath(player));
119 void WorldSave::Read(Player &player) const {
120 ifstream is(PlayerPath(player));
121 TokenStreamReader in(is);
124 while (in.HasMore()) {
125 in.ReadIdentifier(name);
126 in.Skip(Token::EQUALS);
127 if (name == "chunk") {
128 in.ReadVec(state.pos.chunk);
129 } else if (name == "position") {
130 in.ReadVec(state.pos.block);
131 } else if (name == "orientation") {
132 in.ReadQuat(state.orient);
133 } else if (name == "pitch") {
134 state.pitch = in.GetFloat();
135 } else if (name == "yaw") {
136 state.yaw = in.GetFloat();
137 } else if (name == "slot") {
140 player.SetInventorySlot(slot);
142 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
143 in.Skip(Token::SEMICOLON);
146 player.GetEntity().SetState(state);
149 void WorldSave::Write(const Player &player) const {
150 if (!make_dirs(player_path)) {
151 throw runtime_error("failed to create player save directory");
153 const EntityState &state = player.GetEntity().GetState();
154 ofstream out(PlayerPath(player));
155 out << "chunk = " << state.pos.chunk << ';' << endl;
156 out << "position = " << state.pos.block << ';' << endl;
157 out << "orientation = " << state.orient << ';' << endl;
158 out << "pitch = " << state.pitch << ';' << endl;
159 out << "yaw = " << state.yaw << ';' << endl;
160 out << "slot = " << player.GetInventorySlot() << ';' << endl;
163 string WorldSave::PlayerPath(const Player &player) const {
164 // TODO: this is potentially dangerous, server and client should
165 // provide a sanitized name for storage
166 return player_path + player.Name();
170 bool WorldSave::Exists(const ExactLocation::Coarse &pos) const noexcept {
171 return is_file(ChunkPath(pos));
174 void WorldSave::Read(Chunk &chunk) const {
175 const char *path = ChunkPath(chunk.Position());
176 gzFile file = gzopen(path, "r");
178 throw runtime_error("failed to open chunk file");
180 if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
181 throw runtime_error("failed to read chunk from file");
183 if (gzclose(file) != Z_OK) {
184 throw runtime_error("failed to read chunk file");
187 chunk.InvalidateMesh();
191 void WorldSave::Write(Chunk &chunk) const {
192 const char *path = ChunkPath(chunk.Position());
193 gzFile file = gzopen(path, "w");
195 // check if it's because of a missing path component
196 if (errno != ENOENT) {
198 throw runtime_error(strerror(errno));
200 string dir_path(path);
201 dir_path.erase(dir_path.find_last_of("\\/"));
202 if (!make_dirs(dir_path)) {
203 throw runtime_error("failed to create dir for chunk file");
205 file = gzopen(path, "w");
207 throw runtime_error("failed to open chunk file");
210 if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) {
211 gzclose(file); // if this fails, it can't be helped
212 throw runtime_error("failed to write chunk to file");
214 if (gzclose(file) != Z_OK) {
215 throw runtime_error("failed to write chunk file");
221 const char *WorldSave::ChunkPath(const ExactLocation::Coarse &pos) const {
222 snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
223 return chunk_buf.get();