From a50aa0f2a2fea14f5f8c56209e2ecde3088ef913 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Thu, 1 Oct 2015 15:22:31 +0200 Subject: [PATCH] store players in world save --- src/client/client.cpp | 6 +- src/io/TokenStreamReader.hpp | 2 + src/io/WorldSave.cpp | 141 +++++++++++++++++++-------------- src/io/WorldSave.hpp | 10 ++- src/io/token.cpp | 13 +++ src/rand/SimplexNoise.hpp | 3 +- src/rand/noise.cpp | 2 +- src/server/Server.hpp | 5 +- src/server/ServerState.cpp | 2 +- src/server/net.cpp | 8 +- src/standalone/MasterState.cpp | 10 ++- src/standalone/MasterState.hpp | 1 + src/world/Generator.hpp | 3 +- src/world/world.cpp | 2 - 14 files changed, 136 insertions(+), 72 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 91d6aed..e270224 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -131,6 +131,9 @@ InteractiveState::InteractiveState(MasterState &master, uint32_t player_id) chunk_renderer.FogDensity(master.GetWorldConf().fog_density); skeletons.Load(); loop_timer.Start(); + if (save.Exists(player)) { + save.Read(player); + } } void InteractiveState::OnEnter() { @@ -158,7 +161,7 @@ void InteractiveState::Handle(const SDL_Event &event) { interface.Handle(event.wheel); break; case SDL_QUIT: - master.Quit(); + Exit(); break; default: break; @@ -273,6 +276,7 @@ void InteractiveState::SetDebug(bool b) { } void InteractiveState::Exit() { + save.Write(player); master.Quit(); } diff --git a/src/io/TokenStreamReader.hpp b/src/io/TokenStreamReader.hpp index caa6207..d4356aa 100644 --- a/src/io/TokenStreamReader.hpp +++ b/src/io/TokenStreamReader.hpp @@ -37,6 +37,8 @@ public: void ReadVec(glm::ivec3 &); void ReadVec(glm::ivec4 &); + void ReadQuat(glm::quat &); + bool GetBool(); float GetFloat(); int GetInt(); diff --git a/src/io/WorldSave.cpp b/src/io/WorldSave.cpp index 5cfb7f9..f45410b 100644 --- a/src/io/WorldSave.cpp +++ b/src/io/WorldSave.cpp @@ -1,6 +1,7 @@ #include "WorldSave.hpp" #include "filesystem.hpp" +#include "TokenStreamReader.hpp" #include #include @@ -9,6 +10,7 @@ #include #include #include +#include using namespace std; @@ -19,6 +21,7 @@ WorldSave::WorldSave(const string &path) : root_path(path) , world_conf_path(path + "world.conf") , gen_conf_path(path + "gen.conf") +, player_path(path + "player/") , chunk_path(path + "chunks/%d/%d/%d.gz") , chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits::digits10) , chunk_buf(new char[chunk_bufsiz]) { @@ -31,42 +34,26 @@ bool WorldSave::Exists() const noexcept { } -// TODO: better implementation of config files void WorldSave::Read(World::Config &conf) const { - ifstream in(world_conf_path); - if (!in) { + ifstream is(world_conf_path); + if (!is) { throw runtime_error("failed to open world config"); } + TokenStreamReader in(is); - constexpr char spaces[] = "\n\r\t "; - - string line; - while (getline(in, line)) { - if (line.empty() || line[0] == '#') continue; - auto equals_pos = line.find_first_of('='); - - auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces)); - auto name_end = equals_pos - 1; - while (name_end > name_begin && isspace(line[name_end])) { - --name_end; + string name; + while (in.HasMore()) { + in.ReadIdentifier(name); + in.Skip(Token::EQUALS); + if (name == "spawn") { + in.ReadVec(conf.spawn); } - - auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces)); - auto value_end = line.length() - 1; - while (value_end > value_begin && isspace(line[value_end])) { - --value_end; + if (in.HasMore() && in.Peek().type == Token::SEMICOLON) { + in.Skip(Token::SEMICOLON); } - - string name(line, name_begin, name_end - name_begin + 1); - string value(line, value_begin, value_end - value_begin + 1); - - // if (name == "seed") { - // conf.gen.seed = stoul(value); - // } else { - throw runtime_error("unknown world option: " + name); - // } } - if (in.bad()) { + + if (is.bad()) { throw runtime_error("IO error reading world config"); } } @@ -77,7 +64,7 @@ void WorldSave::Write(const World::Config &conf) const { } ofstream out(world_conf_path); - //out << "seed = " << conf.gen.seed << endl; + out << "spawn = " << conf.spawn << ';' << endl; out.close(); if (!out) { @@ -87,41 +74,26 @@ void WorldSave::Write(const World::Config &conf) const { void WorldSave::Read(Generator::Config &conf) const { - ifstream in(gen_conf_path); - if (!in) { + ifstream is(gen_conf_path); + if (!is) { throw runtime_error("failed to open generator config"); } + TokenStreamReader in(is); - constexpr char spaces[] = "\n\r\t "; - - string line; - while (getline(in, line)) { - if (line.empty() || line[0] == '#') continue; - auto equals_pos = line.find_first_of('='); - - auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces)); - auto name_end = equals_pos - 1; - while (name_end > name_begin && isspace(line[name_end])) { - --name_end; - } - - auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces)); - auto value_end = line.length() - 1; - while (value_end > value_begin && isspace(line[value_end])) { - --value_end; - } - - string name(line, name_begin, name_end - name_begin + 1); - string value(line, value_begin, value_end - value_begin + 1); - + string name; + while (in.HasMore()) { + in.ReadIdentifier(name); + in.Skip(Token::EQUALS); if (name == "seed") { - conf.seed = stoul(value); - } else { - throw runtime_error("unknown generator option: " + name); + in.ReadNumber(conf.seed); + } + if (in.HasMore() && in.Peek().type == Token::SEMICOLON) { + in.Skip(Token::SEMICOLON); } } - if (in.bad()) { - throw runtime_error("IO error reading world config"); + + if (is.bad()) { + throw runtime_error("IO error reading generator config"); } } @@ -131,7 +103,7 @@ void WorldSave::Write(const Generator::Config &conf) const { } ofstream out(gen_conf_path); - out << "seed = " << conf.seed << endl; + out << "seed = " << conf.seed << ';' << endl; out.close(); if (!out) { @@ -140,6 +112,55 @@ void WorldSave::Write(const Generator::Config &conf) const { } +bool WorldSave::Exists(const Player &player) const { + return is_file(PlayerPath(player)); +} + +void WorldSave::Read(Player &player) const { + ifstream is(PlayerPath(player)); + TokenStreamReader in(is); + string name; + EntityState state; + while (in.HasMore()) { + in.ReadIdentifier(name); + in.Skip(Token::EQUALS); + if (name == "chunk") { + in.ReadVec(state.chunk_pos); + } else if (name == "position") { + in.ReadVec(state.block_pos); + } else if (name == "orientation") { + in.ReadQuat(state.orient); + } else if (name == "slot") { + int slot; + in.ReadNumber(slot); + player.SetInventorySlot(slot); + } + if (in.HasMore() && in.Peek().type == Token::SEMICOLON) { + in.Skip(Token::SEMICOLON); + } + } + player.GetEntity().SetState(state); +} + +void WorldSave::Write(const Player &player) const { + if (!make_dirs(player_path)) { + throw runtime_error("failed to create player save directory"); + } + const EntityState &state = player.GetEntity().GetState(); + ofstream out(PlayerPath(player)); + out << "chunk = " << state.chunk_pos << ';' << endl; + out << "position = " << state.block_pos << ';' << endl; + out << "orientation = " << state.orient << ';' << endl; + out << "slot = " << player.GetInventorySlot() << ';' << endl; +} + +string WorldSave::PlayerPath(const Player &player) const { + // TODO: this is potentially dangerous, server and client should + // provide a sanitized name for storage + return player_path + player.Name(); +} + + bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept { return is_file(ChunkPath(pos)); } diff --git a/src/io/WorldSave.hpp b/src/io/WorldSave.hpp index 1796257..d12de95 100644 --- a/src/io/WorldSave.hpp +++ b/src/io/WorldSave.hpp @@ -11,6 +11,8 @@ namespace blank { +class Player; + class WorldSave { public: @@ -24,17 +26,23 @@ public: void Read(Generator::Config &) const; void Write(const Generator::Config &) const; + // player + bool Exists(const Player &) const; + void Read(Player &) const; + void Write(const Player &) const; + std::string PlayerPath(const Player &) const; + // single chunk bool Exists(const Chunk::Pos &) const noexcept; void Read(Chunk &) const; void Write(Chunk &) const; - const char *ChunkPath(const Chunk::Pos &) const; private: std::string root_path; std::string world_conf_path; std::string gen_conf_path; + std::string player_path; std::string chunk_path; std::size_t chunk_bufsiz; std::unique_ptr chunk_buf; diff --git a/src/io/token.cpp b/src/io/token.cpp index 76a9c2f..cf3ac3b 100644 --- a/src/io/token.cpp +++ b/src/io/token.cpp @@ -4,6 +4,7 @@ #include #include #include +#include using namespace std; @@ -330,6 +331,18 @@ void TokenStreamReader::ReadVec(glm::ivec4 &v) { Skip(Token::BRACKET_CLOSE); } +void TokenStreamReader::ReadQuat(glm::quat &q) { + Skip(Token::BRACKET_OPEN); + ReadNumber(q.w); + Skip(Token::COMMA); + ReadNumber(q.x); + Skip(Token::COMMA); + ReadNumber(q.y); + Skip(Token::COMMA); + ReadNumber(q.z); + Skip(Token::BRACKET_CLOSE); +} + bool TokenStreamReader::GetBool() { Next(); diff --git a/src/rand/SimplexNoise.hpp b/src/rand/SimplexNoise.hpp index 7a7242b..1578931 100644 --- a/src/rand/SimplexNoise.hpp +++ b/src/rand/SimplexNoise.hpp @@ -1,6 +1,7 @@ #ifndef BLANK_RAND_SIMPLEXNOISE_HPP_ #define BLANK_RAND_SIMPLEXNOISE_HPP_ +#include #include @@ -10,7 +11,7 @@ namespace blank { class SimplexNoise { public: - explicit SimplexNoise(unsigned int seed) noexcept; + explicit SimplexNoise(std::uint64_t seed) noexcept; float operator ()(const glm::vec3 &) const noexcept; diff --git a/src/rand/noise.cpp b/src/rand/noise.cpp index 664096d..e4ad310 100644 --- a/src/rand/noise.cpp +++ b/src/rand/noise.cpp @@ -14,7 +14,7 @@ constexpr float one_sixth = 1.0f/6.0f; namespace blank { -SimplexNoise::SimplexNoise(unsigned int seed) noexcept +SimplexNoise::SimplexNoise(std::uint64_t seed) noexcept : grad({ { 1.0f, 1.0f, 0.0f }, { -1.0f, 1.0f, 0.0f }, diff --git a/src/server/Server.hpp b/src/server/Server.hpp index 1a4c0b4..372a947 100644 --- a/src/server/Server.hpp +++ b/src/server/Server.hpp @@ -12,6 +12,7 @@ namespace blank { class CompositeModel; class World; +class WorldSave; namespace server { @@ -21,7 +22,7 @@ class Server : public WorldManipulator { public: - Server(const Config::Network &, World &); + Server(const Config::Network &, World &, const WorldSave &); ~Server(); void Handle(); @@ -32,6 +33,7 @@ public: UDPpacket &GetPacket() noexcept { return serv_pack; } World &GetWorld() noexcept { return world; } + const WorldSave &GetWorldSave() noexcept { return save; } void SetPlayerModel(const CompositeModel &) noexcept; bool HasPlayerModel() const noexcept; @@ -50,6 +52,7 @@ private: std::list clients; World &world; + const WorldSave &save; const CompositeModel *player_model; }; diff --git a/src/server/ServerState.cpp b/src/server/ServerState.cpp index 8657df0..cedae4e 100644 --- a/src/server/ServerState.cpp +++ b/src/server/ServerState.cpp @@ -24,7 +24,7 @@ ServerState::ServerState( , chunk_loader(world.Chunks(), generator, ws) , skeletons() , spawner(world, skeletons, gc.seed) -, server(config.net, world) +, server(config.net, world, ws) , loop_timer(16) { TextureIndex tex_index; env.loader.LoadBlockTypes("default", block_types, tex_index); diff --git a/src/server/net.cpp b/src/server/net.cpp index 8b4448e..7cb07cd 100644 --- a/src/server/net.cpp +++ b/src/server/net.cpp @@ -3,6 +3,7 @@ #include "Server.hpp" #include "../app/init.hpp" +#include "../io/WorldSave.hpp" #include "../model/CompositeModel.hpp" #include "../world/ChunkIndex.hpp" #include "../world/Entity.hpp" @@ -394,6 +395,9 @@ void ClientConnection::AttachPlayer(Player &player) { DetachPlayer(); input.reset(new DirectInput(server.GetWorld(), player, server)); PlayerEntity().Ref(); + if (server.GetWorldSave().Exists(player)) { + server.GetWorldSave().Read(player); + } old_base = PlayerChunks().Base(); Chunk::Pos begin = PlayerChunks().CoordsBegin(); @@ -415,6 +419,7 @@ void ClientConnection::AttachPlayer(Player &player) { void ClientConnection::DetachPlayer() { if (!HasPlayer()) return; cout << "player \"" << input->GetPlayer().Name() << "\" left" << endl; + server.GetWorldSave().Write(input->GetPlayer()); PlayerEntity().Kill(); PlayerEntity().UnRef(); input.reset(); @@ -560,11 +565,12 @@ bool ClientConnection::ChunkInRange(const glm::ivec3 &pos) const noexcept { } -Server::Server(const Config::Network &conf, World &world) +Server::Server(const Config::Network &conf, World &world, const WorldSave &save) : serv_sock(nullptr) , serv_pack{ -1, nullptr, 0 } , clients() , world(world) +, save(save) , player_model(nullptr) { serv_sock = SDLNet_UDP_Open(conf.port); if (!serv_sock) { diff --git a/src/standalone/MasterState.cpp b/src/standalone/MasterState.cpp index 635f273..98d3c83 100644 --- a/src/standalone/MasterState.cpp +++ b/src/standalone/MasterState.cpp @@ -4,6 +4,7 @@ #include "../app/Environment.hpp" #include "../app/init.hpp" #include "../app/TextureIndex.hpp" +#include "../io/WorldSave.hpp" #include @@ -21,6 +22,7 @@ MasterState::MasterState( : config(config) , env(env) , block_types() +, save(save) , world(block_types, wc) , player(*world.AddPlayer(config.player.name)) , hud(env, config, player) @@ -42,6 +44,9 @@ MasterState::MasterState( chunk_renderer.FogDensity(wc.fog_density); skeletons.Load(); spawner.LimitSkeletons(0, skeletons.Size()); + if (save.Exists(player)) { + save.Read(player); + } } @@ -72,7 +77,7 @@ void MasterState::Handle(const SDL_Event &event) { interface.Handle(event.wheel); break; case SDL_QUIT: - env.state.Switch(&unload); + Exit(); break; default: break; @@ -151,7 +156,8 @@ void MasterState::SetDebug(bool b) { } void MasterState::Exit() { - env.state.Pop(); + save.Write(player); + env.state.Switch(&unload); } } diff --git a/src/standalone/MasterState.hpp b/src/standalone/MasterState.hpp index f2c702f..823e7f3 100644 --- a/src/standalone/MasterState.hpp +++ b/src/standalone/MasterState.hpp @@ -60,6 +60,7 @@ private: Config &config; Environment &env; BlockTypeRegistry block_types; + const WorldSave &save; World world; Player &player; HUD hud; diff --git a/src/world/Generator.hpp b/src/world/Generator.hpp index b3cb61f..aadf2cf 100644 --- a/src/world/Generator.hpp +++ b/src/world/Generator.hpp @@ -5,6 +5,7 @@ #include "../rand/SimplexNoise.hpp" #include "../rand/WorleyNoise.hpp" +#include #include @@ -16,7 +17,7 @@ class Generator { public: struct Config { - unsigned int seed = 0; + std::uint64_t seed = 0; float stretch = 64.0f; float solid_threshold = 0.5f; }; diff --git a/src/world/world.cpp b/src/world/world.cpp index e659322..f648baa 100644 --- a/src/world/world.cpp +++ b/src/world/world.cpp @@ -167,7 +167,6 @@ Player *World::AddPlayer(const std::string &name) { } Entity &entity = AddEntity(); entity.Name(name); - // TODO: load from save file here entity.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); entity.WorldCollidable(true); entity.Position(config.spawn); @@ -187,7 +186,6 @@ Player *World::AddPlayer(const std::string &name, std::uint32_t id) { return nullptr; } entity->Name(name); - // TODO: load from save file here entity->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); entity->WorldCollidable(true); entity->Position(config.spawn); -- 2.39.2