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.chunk_pos);
129 } else if (name == "position") {
130 in.ReadVec(state.block_pos);
131 } else if (name == "orientation") {
132 in.ReadQuat(state.orient);
133 } else if (name == "slot") {
136 player.SetInventorySlot(slot);
138 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
139 in.Skip(Token::SEMICOLON);
142 player.GetEntity().SetState(state);
145 void WorldSave::Write(const Player &player) const {
146 if (!make_dirs(player_path)) {
147 throw runtime_error("failed to create player save directory");
149 const EntityState &state = player.GetEntity().GetState();
150 ofstream out(PlayerPath(player));
151 out << "chunk = " << state.chunk_pos << ';' << endl;
152 out << "position = " << state.block_pos << ';' << endl;
153 out << "orientation = " << state.orient << ';' << endl;
154 out << "slot = " << player.GetInventorySlot() << ';' << endl;
157 string WorldSave::PlayerPath(const Player &player) const {
158 // TODO: this is potentially dangerous, server and client should
159 // provide a sanitized name for storage
160 return player_path + player.Name();
164 bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept {
165 return is_file(ChunkPath(pos));
168 void WorldSave::Read(Chunk &chunk) const {
169 const char *path = ChunkPath(chunk.Position());
170 gzFile file = gzopen(path, "r");
172 throw runtime_error("failed to open chunk file");
174 if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
175 throw runtime_error("failed to read chunk from file");
177 if (gzclose(file) != Z_OK) {
178 throw runtime_error("failed to read chunk file");
180 chunk.InvalidateModel();
184 void WorldSave::Write(Chunk &chunk) const {
185 const char *path = ChunkPath(chunk.Position());
186 gzFile file = gzopen(path, "w");
188 // check if it's because of a missing path component
189 if (errno != ENOENT) {
191 throw runtime_error(strerror(errno));
193 string dir_path(path);
194 dir_path.erase(dir_path.find_last_of("\\/"));
195 if (!make_dirs(dir_path)) {
196 throw runtime_error("failed to create dir for chunk file");
198 file = gzopen(path, "w");
200 throw runtime_error("failed to open chunk file");
203 if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) {
204 gzclose(file); // if this fails, it can't be helped
205 throw runtime_error("failed to write chunk to file");
207 if (gzclose(file) != Z_OK) {
208 throw runtime_error("failed to write chunk file");
214 const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const {
215 snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
216 return chunk_buf.get();