]> git.localhorst.tv Git - blank.git/blob - src/io/WorldSave.cpp
ce7c71d35ea55c19d85b85b56b0a3f7d8e83db01
[blank.git] / src / io / WorldSave.cpp
1 #include "WorldSave.hpp"
2
3 #include "filesystem.hpp"
4 #include "TokenStreamReader.hpp"
5
6 #include <cctype>
7 #include <cstring>
8 #include <fstream>
9 #include <iostream>
10 #include <limits>
11 #include <stdexcept>
12 #include <zlib.h>
13 #include <glm/gtx/io.hpp>
14
15 using namespace std;
16
17
18 namespace blank {
19
20 WorldSave::WorldSave(const string &path)
21 : root_path(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]) {
28
29 }
30
31
32 bool WorldSave::Exists() const noexcept {
33         return is_dir(root_path) && is_file(world_conf_path);
34 }
35
36
37 void WorldSave::Read(World::Config &conf) const {
38         ifstream is(world_conf_path);
39         if (!is) {
40                 throw runtime_error("failed to open world config");
41         }
42         TokenStreamReader in(is);
43
44         string name;
45         while (in.HasMore()) {
46                 in.ReadIdentifier(name);
47                 in.Skip(Token::EQUALS);
48                 if (name == "spawn") {
49                         in.ReadVec(conf.spawn);
50                 }
51                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
52                         in.Skip(Token::SEMICOLON);
53                 }
54         }
55
56         if (is.bad()) {
57                 throw runtime_error("IO error reading world config");
58         }
59 }
60
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");
64         }
65
66         ofstream out(world_conf_path);
67         out << "spawn = " << conf.spawn << ';' << endl;
68         out.close();
69
70         if (!out) {
71                 throw runtime_error("failed to write world config");
72         }
73 }
74
75
76 void WorldSave::Read(Generator::Config &conf) const {
77         ifstream is(gen_conf_path);
78         if (!is) {
79                 throw runtime_error("failed to open generator config");
80         }
81         TokenStreamReader in(is);
82
83         string name;
84         while (in.HasMore()) {
85                 in.ReadIdentifier(name);
86                 in.Skip(Token::EQUALS);
87                 if (name == "seed") {
88                         in.ReadNumber(conf.seed);
89                 }
90                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
91                         in.Skip(Token::SEMICOLON);
92                 }
93         }
94
95         if (is.bad()) {
96                 throw runtime_error("IO error reading generator config");
97         }
98 }
99
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");
103         }
104
105         ofstream out(gen_conf_path);
106         out << "seed = " << conf.seed << ';' << endl;
107         out.close();
108
109         if (!out) {
110                 throw runtime_error("failed to write generator config");
111         }
112 }
113
114
115 bool WorldSave::Exists(const Player &player) const {
116         return is_file(PlayerPath(player));
117 }
118
119 void WorldSave::Read(Player &player) const {
120         ifstream is(PlayerPath(player));
121         TokenStreamReader in(is);
122         string name;
123         EntityState state;
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") {
138                         int slot;
139                         in.ReadNumber(slot);
140                         player.SetInventorySlot(slot);
141                 }
142                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
143                         in.Skip(Token::SEMICOLON);
144                 }
145         }
146         player.GetEntity().SetState(state);
147         player.Update(0);
148 }
149
150 void WorldSave::Write(const Player &player) const {
151         if (!make_dirs(player_path)) {
152                 throw runtime_error("failed to create player save directory");
153         }
154         const EntityState &state = player.GetEntity().GetState();
155         ofstream out(PlayerPath(player));
156         out << "chunk = " << state.pos.chunk << ';' << endl;
157         out << "position = " << state.pos.block << ';' << endl;
158         out << "orientation = " << state.orient << ';' << endl;
159         out << "pitch = " << state.pitch << ';' << endl;
160         out << "yaw = " << state.yaw << ';' << endl;
161         out << "slot = " << player.GetInventorySlot() << ';' << endl;
162 }
163
164 string WorldSave::PlayerPath(const Player &player) const {
165         // TODO: this is potentially dangerous, server and client should
166         //       provide a sanitized name for storage
167         return player_path + player.Name();
168 }
169
170
171 bool WorldSave::Exists(const ExactLocation::Coarse &pos) const noexcept {
172         return is_file(ChunkPath(pos));
173 }
174
175 void WorldSave::Read(Chunk &chunk) const {
176         const char *path = ChunkPath(chunk.Position());
177         gzFile file = gzopen(path, "r");
178         if (!file) {
179                 throw runtime_error("failed to open chunk file");
180         }
181         if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
182                 throw runtime_error("failed to read chunk from file");
183         }
184         if (gzclose(file) != Z_OK) {
185                 throw runtime_error("failed to read chunk file");
186         }
187         chunk.ScanActive();
188         chunk.InvalidateMesh();
189         chunk.ClearSave();
190 }
191
192 void WorldSave::Write(Chunk &chunk) const {
193         const char *path = ChunkPath(chunk.Position());
194         gzFile file = gzopen(path, "w");
195         if (!file) {
196                 // check if it's because of a missing path component
197                 if (errno != ENOENT) {
198                         // nope, fatal
199                         throw runtime_error(strerror(errno));
200                 }
201                 string dir_path(path);
202                 dir_path.erase(dir_path.find_last_of("\\/"));
203                 if (!make_dirs(dir_path)) {
204                         throw runtime_error("failed to create dir for chunk file");
205                 }
206                 file = gzopen(path, "w");
207                 if (!file) {
208                         throw runtime_error("failed to open chunk file");
209                 }
210         }
211         if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) {
212                 gzclose(file); // if this fails, it can't be helped
213                 throw runtime_error("failed to write chunk to file");
214         }
215         if (gzclose(file) != Z_OK) {
216                 throw runtime_error("failed to write chunk file");
217         }
218         chunk.ClearSave();
219 }
220
221
222 const char *WorldSave::ChunkPath(const ExactLocation::Coarse &pos) const {
223         snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
224         return chunk_buf.get();
225 }
226
227 }