]> git.localhorst.tv Git - blank.git/blob - src/io/WorldSave.cpp
treat head pitch and yaw as entity state
[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.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 == "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.chunk_pos << ';' << endl;
156         out << "position = " << state.block_pos << ';' << 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 Chunk::Pos &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.InvalidateMesh();
187         chunk.ClearSave();
188 }
189
190 void WorldSave::Write(Chunk &chunk) const {
191         const char *path = ChunkPath(chunk.Position());
192         gzFile file = gzopen(path, "w");
193         if (!file) {
194                 // check if it's because of a missing path component
195                 if (errno != ENOENT) {
196                         // nope, fatal
197                         throw runtime_error(strerror(errno));
198                 }
199                 string dir_path(path);
200                 dir_path.erase(dir_path.find_last_of("\\/"));
201                 if (!make_dirs(dir_path)) {
202                         throw runtime_error("failed to create dir for chunk file");
203                 }
204                 file = gzopen(path, "w");
205                 if (!file) {
206                         throw runtime_error("failed to open chunk file");
207                 }
208         }
209         if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) {
210                 gzclose(file); // if this fails, it can't be helped
211                 throw runtime_error("failed to write chunk to file");
212         }
213         if (gzclose(file) != Z_OK) {
214                 throw runtime_error("failed to write chunk file");
215         }
216         chunk.ClearSave();
217 }
218
219
220 const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const {
221         snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
222         return chunk_buf.get();
223 }
224
225 }