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