]> git.localhorst.tv Git - blank.git/blob - src/io/WorldSave.cpp
store players in world save
[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 == "slot") {
134                         int slot;
135                         in.ReadNumber(slot);
136                         player.SetInventorySlot(slot);
137                 }
138                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
139                         in.Skip(Token::SEMICOLON);
140                 }
141         }
142         player.GetEntity().SetState(state);
143 }
144
145 void WorldSave::Write(const Player &player) const {
146         if (!make_dirs(player_path)) {
147                 throw runtime_error("failed to create player save directory");
148         }
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;
155 }
156
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();
161 }
162
163
164 bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept {
165         return is_file(ChunkPath(pos));
166 }
167
168 void WorldSave::Read(Chunk &chunk) const {
169         const char *path = ChunkPath(chunk.Position());
170         gzFile file = gzopen(path, "r");
171         if (!file) {
172                 throw runtime_error("failed to open chunk file");
173         }
174         if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
175                 throw runtime_error("failed to read chunk from file");
176         }
177         if (gzclose(file) != Z_OK) {
178                 throw runtime_error("failed to read chunk file");
179         }
180         chunk.InvalidateModel();
181         chunk.ClearSave();
182 }
183
184 void WorldSave::Write(Chunk &chunk) const {
185         const char *path = ChunkPath(chunk.Position());
186         gzFile file = gzopen(path, "w");
187         if (!file) {
188                 // check if it's because of a missing path component
189                 if (errno != ENOENT) {
190                         // nope, fatal
191                         throw runtime_error(strerror(errno));
192                 }
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");
197                 }
198                 file = gzopen(path, "w");
199                 if (!file) {
200                         throw runtime_error("failed to open chunk file");
201                 }
202         }
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");
206         }
207         if (gzclose(file) != Z_OK) {
208                 throw runtime_error("failed to write chunk file");
209         }
210         chunk.ClearSave();
211 }
212
213
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();
217 }
218
219 }