]> git.localhorst.tv Git - blank.git/blob - src/io/WorldSave.cpp
d483c915939407426fbab0d4a7c70387a9a11bce
[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 }
148
149 void WorldSave::Write(const Player &player) const {
150         if (!make_dirs(player_path)) {
151                 throw runtime_error("failed to create player save directory");
152         }
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;
161 }
162
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();
167 }
168
169
170 bool WorldSave::Exists(const ExactLocation::Coarse &pos) const noexcept {
171         return is_file(ChunkPath(pos));
172 }
173
174 void WorldSave::Read(Chunk &chunk) const {
175         const char *path = ChunkPath(chunk.Position());
176         gzFile file = gzopen(path, "r");
177         if (!file) {
178                 throw runtime_error("failed to open chunk file");
179         }
180         if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
181                 throw runtime_error("failed to read chunk from file");
182         }
183         if (gzclose(file) != Z_OK) {
184                 throw runtime_error("failed to read chunk file");
185         }
186         chunk.ScanActive();
187         chunk.InvalidateMesh();
188         chunk.ClearSave();
189 }
190
191 void WorldSave::Write(Chunk &chunk) const {
192         const char *path = ChunkPath(chunk.Position());
193         gzFile file = gzopen(path, "w");
194         if (!file) {
195                 // check if it's because of a missing path component
196                 if (errno != ENOENT) {
197                         // nope, fatal
198                         throw runtime_error(strerror(errno));
199                 }
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");
204                 }
205                 file = gzopen(path, "w");
206                 if (!file) {
207                         throw runtime_error("failed to open chunk file");
208                 }
209         }
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");
213         }
214         if (gzclose(file) != Z_OK) {
215                 throw runtime_error("failed to write chunk file");
216         }
217         chunk.ClearSave();
218 }
219
220
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();
224 }
225
226 }