]> git.localhorst.tv Git - blank.git/commitdiff
special treatment for players
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 2 Sep 2015 19:18:49 +0000 (21:18 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 2 Sep 2015 19:18:49 +0000 (21:18 +0200)
with some drawbacks and cut corners, but more to come

running
src/ai/Spawner.cpp
src/ai/Spawner.hpp
src/app/WorldState.cpp
src/app/runtime.cpp
src/ui/Interface.hpp
src/ui/Keymap.hpp
src/ui/ui.cpp
src/world/World.cpp
src/world/World.hpp

diff --git a/running b/running
index 7c089ff27db902799bb68b1024f1f8ac5bfb0091..846f27d8ba389f8f98ca7062d03c9ea4245c355b 100644 (file)
--- a/running
+++ b/running
@@ -69,6 +69,11 @@ Network
 --port <number>
        port number to connection to (client) or listen on (server)
 
 --port <number>
        port number to connection to (client) or listen on (server)
 
+--player-name <name>
+       use given name to identify with the server (client mode)
+       default player name is "default"
+       the server will reject players with names that are already taken
+
 World
 -----
 
 World
 -----
 
@@ -97,10 +102,6 @@ flip through available blocks.
 Q changes the face of the active block (loops over up, down, right, left,
 front, and back) and E changes the turn (none, left, around, and right).
 
 Q changes the face of the active block (loops over up, down, right, left,
 front, and back) and E changes the turn (none, left, around, and right).
 
-Pressing B prints details about the block you're pointing at and P prints
-info about the active block. L spits out the player position and light
-level there. C dumps info about the chunk of the pointed at block.
-
 Press N to toggle player/world collision.
 
 F1 toggles UI rendering.
 Press N to toggle player/world collision.
 
 F1 toggles UI rendering.
index fb2a1eafc46c69906826e98c91f0ed117eb70f97..6a692b11077b47f3d7b871505d0ab2db0ab606d3 100644 (file)
@@ -52,7 +52,6 @@ Spawner::Spawner(World &world, std::uint64_t seed)
        }
 
        timer.Start();
        }
 
        timer.Start();
-       Spawn(world.Player().ChunkCoords(), { 0.5f, 0.5f, 0.5f });
 }
 
 Spawner::~Spawner() {
 }
 
 Spawner::~Spawner() {
@@ -75,7 +74,7 @@ void Spawner::Update(int dt) {
 
 
 void Spawner::CheckDespawn() noexcept {
 
 
 void Spawner::CheckDespawn() noexcept {
-       const Entity &reference = world.Player();
+       const auto &refs = world.Players();
        for (auto iter = controllers.begin(), end = controllers.end(); iter != end;) {
                Entity &e = (*iter)->Controlled();
                if (e.Dead()) {
        for (auto iter = controllers.begin(), end = controllers.end(); iter != end;) {
                Entity &e = (*iter)->Controlled();
                if (e.Dead()) {
@@ -83,8 +82,15 @@ void Spawner::CheckDespawn() noexcept {
                        iter = controllers.erase(iter);
                        continue;
                }
                        iter = controllers.erase(iter);
                        continue;
                }
-               glm::vec3 diff(reference.AbsoluteDifference(e));
-               if (dot(diff, diff) > despawn_range) {
+               bool safe = false;
+               for (const Entity *ref : refs) {
+                       glm::vec3 diff(ref->AbsoluteDifference(e));
+                       if (dot(diff, diff) < despawn_range) {
+                               safe = true;
+                               break;
+                       }
+               }
+               if (!safe) {
                        e.Kill();
                        delete *iter;
                        iter = controllers.erase(iter);
                        e.Kill();
                        delete *iter;
                        iter = controllers.erase(iter);
@@ -97,6 +103,11 @@ void Spawner::CheckDespawn() noexcept {
 void Spawner::TrySpawn() {
        if (controllers.size() >= max_entities) return;
 
 void Spawner::TrySpawn() {
        if (controllers.size() >= max_entities) return;
 
+       // select random player to punish
+       auto &players = world.Players();
+       if (players.size() == 0) return;
+       Entity &player = *players[random.Next<unsigned short>() % players.size()];
+
        glm::ivec3 chunk(
                (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range,
                (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range,
        glm::ivec3 chunk(
                (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range,
                (random.Next<unsigned char>() % (chunk_range * 2 + 1)) - chunk_range,
@@ -111,14 +122,16 @@ void Spawner::TrySpawn() {
 
 
        // distance check
 
 
        // distance check
-       glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + world.Player().Position());
+       glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + player.Position());
        float dist = dot(diff, diff);
        if (dist > despawn_range || dist < spawn_distance) {
                return;
        }
 
        // check if the spawn block and the one above it are loaded and inhabitable
        float dist = dot(diff, diff);
        if (dist > despawn_range || dist < spawn_distance) {
                return;
        }
 
        // check if the spawn block and the one above it are loaded and inhabitable
-       BlockLookup spawn_block(&world.PlayerChunk(), chunk * Chunk::Extent() + pos);
+       BlockLookup spawn_block(
+               world.Loader().Loaded(player.ChunkCoords()),
+               chunk * Chunk::Extent() + pos);
        if (!spawn_block || spawn_block.GetType().collide_block) {
                return;
        }
        if (!spawn_block || spawn_block.GetType().collide_block) {
                return;
        }
@@ -128,10 +141,10 @@ void Spawner::TrySpawn() {
                return;
        }
 
                return;
        }
 
-       Spawn(world.Player().ChunkCoords() + chunk, glm::vec3(pos) + glm::vec3(0.5f));
+       Spawn(player, player.ChunkCoords() + chunk, glm::vec3(pos) + glm::vec3(0.5f));
 }
 
 }
 
-void Spawner::Spawn(const glm::ivec3 &chunk, const glm::vec3 &pos) {
+void Spawner::Spawn(Entity &reference, const glm::ivec3 &chunk, const glm::vec3 &pos) {
        glm::vec3 rot(0.000001f);
        rot.x *= (random.Next<unsigned short>() % 1024);
        rot.y *= (random.Next<unsigned short>() % 1024);
        glm::vec3 rot(0.000001f);
        rot.x *= (random.Next<unsigned short>() % 1024);
        rot.y *= (random.Next<unsigned short>() % 1024);
@@ -148,7 +161,7 @@ void Spawner::Spawn(const glm::ivec3 &chunk, const glm::vec3 &pos) {
        if (random()) {
                ctrl = new RandomWalk(e, random.Next<std::uint64_t>());
        } else {
        if (random()) {
                ctrl = new RandomWalk(e, random.Next<std::uint64_t>());
        } else {
-               ctrl = new Chaser(world, e, world.Player());
+               ctrl = new Chaser(world, e, reference);
        }
        controllers.emplace_back(ctrl);
 }
        }
        controllers.emplace_back(ctrl);
 }
index 9fb3d368de0dcd2a02156006422526a54a95a53f..a1181d30bf96f77ec0809228f2079c7d0c7fa88f 100644 (file)
@@ -13,6 +13,7 @@
 namespace blank {
 
 class Controller;
 namespace blank {
 
 class Controller;
+class Entity;
 class World;
 
 class Spawner {
 class World;
 
 class Spawner {
@@ -26,7 +27,7 @@ public:
 private:
        void CheckDespawn() noexcept;
        void TrySpawn();
 private:
        void CheckDespawn() noexcept;
        void TrySpawn();
-       void Spawn(const glm::ivec3 &, const glm::vec3 &);
+       void Spawn(Entity &reference, const glm::ivec3 &, const glm::vec3 &);
 
 private:
        World &world;
 
 private:
        World &world;
index 03abe36c163c4b7c120b725fd74a9d3a337f9bd8..521f5b5023235281a9c51fb2336f62155f1eb9a3 100644 (file)
@@ -70,20 +70,20 @@ void WorldState::Update(int dt) {
        interface.Update(dt);
        spawner.Update(dt);
        world.Update(dt);
        interface.Update(dt);
        spawner.Update(dt);
        world.Update(dt);
-       chunk_renderer.Rebase(world.Player().ChunkCoords());
+       chunk_renderer.Rebase(interface.Player().ChunkCoords());
        chunk_renderer.Update(dt);
 
        chunk_renderer.Update(dt);
 
-       glm::mat4 trans = world.Player().Transform(world.Player().ChunkCoords());
+       glm::mat4 trans = interface.Player().Transform(interface.Player().ChunkCoords());
        glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
        glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
        glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
        glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
-       env.audio.Position(world.Player().Position());
-       env.audio.Velocity(world.Player().Velocity());
+       env.audio.Position(interface.Player().Position());
+       env.audio.Velocity(interface.Player().Velocity());
        env.audio.Orientation(dir, up);
 
 }
 
 void WorldState::Render(Viewport &viewport) {
        env.audio.Orientation(dir, up);
 
 }
 
 void WorldState::Render(Viewport &viewport) {
-       viewport.WorldPosition(world.Player().Transform(world.Player().ChunkCoords()));
+       viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords()));
        chunk_renderer.Render(viewport);
        world.Render(viewport);
        interface.Render(viewport);
        chunk_renderer.Render(viewport);
        world.Render(viewport);
        interface.Render(viewport);
index 2f0f82ec1522e845602236aa32cf745d0cfc90f6..f979c653bcf8c496302d366d13a18d15c64e33c5 100644 (file)
@@ -142,6 +142,14 @@ void Runtime::ReadArgs(int argc, const char *const *argv) {
                                                        config.server.port = strtoul(argv[i], nullptr, 10);
                                                        config.client.port = config.server.port;
                                                }
                                                        config.server.port = strtoul(argv[i], nullptr, 10);
                                                        config.client.port = config.server.port;
                                                }
+                                       } else if (strcmp(param, "player-name") == 0) {
+                                               ++i;
+                                               if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+                                                       cerr << "missing argument to --player-name" << endl;
+                                                       error = true;
+                                               } else {
+                                                       config.interface.player_name = argv[i];
+                                               }
                                        } else if (strcmp(param, "save-path") == 0) {
                                                ++i;
                                                if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
                                        } else if (strcmp(param, "save-path") == 0) {
                                                ++i;
                                                if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
index 5cf82076a74b0f629dc15930f2dca2b3c001296e..aab8b156596e5cdf42ba691bb8cbe8a381c46dc4 100644 (file)
@@ -28,6 +28,8 @@ class Interface {
 
 public:
        struct Config {
 
 public:
        struct Config {
+               std::string player_name = "default";
+
                float move_velocity = 0.005f;
                float pitch_sensitivity = -0.0025f;
                float yaw_sensitivity = -0.001f;
                float move_velocity = 0.005f;
                float pitch_sensitivity = -0.0025f;
                float yaw_sensitivity = -0.001f;
@@ -40,6 +42,9 @@ public:
 
        Interface(const Config &, Environment &, World &);
 
 
        Interface(const Config &, Environment &, World &);
 
+       Entity &Player() noexcept { return ctrl.Controlled(); }
+       const Entity &Player() const noexcept { return ctrl.Controlled(); }
+
        void HandlePress(const SDL_KeyboardEvent &);
        void HandleRelease(const SDL_KeyboardEvent &);
        void Handle(const SDL_MouseMotionEvent &);
        void HandlePress(const SDL_KeyboardEvent &);
        void HandleRelease(const SDL_KeyboardEvent &);
        void Handle(const SDL_MouseMotionEvent &);
@@ -56,12 +61,6 @@ public:
        void PlaceBlock();
        void RemoveBlock() noexcept;
 
        void PlaceBlock();
        void RemoveBlock() noexcept;
 
-       void PrintBlockInfo();
-       void PrintChunkInfo();
-       void PrintLightInfo();
-       void PrintSelectionInfo();
-       void Print(const Block &);
-
        void SelectNext();
        void SelectPrevious();
 
        void SelectNext();
        void SelectPrevious();
 
index e96179ae9c4f8bce945251560163bb0125617fb1..0e34ba9979d2406a78b98e91171e242702c0bcda 100644 (file)
@@ -35,11 +35,6 @@ public:
                TOGGLE_VISUAL,
                TOGGLE_DEBUG,
 
                TOGGLE_VISUAL,
                TOGGLE_DEBUG,
 
-               PRINT_BLOCK,
-               PRINT_CHUNK,
-               PRINT_LIGHT,
-               PRINT_SELECTION,
-
                EXIT,
        };
 
                EXIT,
        };
 
index e6376614dacd3525bf4cfed6999d467bb99c2bea..d8459e5190419f1c27e8f069d0da6efdae388d01 100644 (file)
@@ -105,7 +105,8 @@ Interface::Interface(
        World &world)
 : env(env)
 , world(world)
        World &world)
 : env(env)
 , world(world)
-, ctrl(world.Player())
+// let's assume this succeeds and hope for the best for now
+, ctrl(*world.AddPlayer(config.player_name))
 , hud(world.BlockTypes(), env.assets.small_ui_font)
 , aim{{ 0, 0, 0 }, { 0, 0, -1 }}
 , aim_world()
 , hud(world.BlockTypes(), env.assets.small_ui_font)
 , aim{{ 0, 0, 0 }, { 0, 0, -1 }}
 , aim_world()
@@ -204,19 +205,6 @@ void Interface::HandlePress(const SDL_KeyboardEvent &event) {
                        ToggleCollision();
                        break;
 
                        ToggleCollision();
                        break;
 
-               case Keymap::PRINT_BLOCK:
-                       PrintBlockInfo();
-                       break;
-               case Keymap::PRINT_CHUNK:
-                       PrintChunkInfo();
-                       break;
-               case Keymap::PRINT_LIGHT:
-                       PrintLightInfo();
-                       break;
-               case Keymap::PRINT_SELECTION:
-                       PrintSelectionInfo();
-                       break;
-
                case Keymap::TOGGLE_VISUAL:
                        ToggleVisual();
                        break;
                case Keymap::TOGGLE_VISUAL:
                        ToggleVisual();
                        break;
@@ -283,92 +271,6 @@ void Interface::ToggleCollision() {
        }
 }
 
        }
 }
 
-void Interface::PrintBlockInfo() {
-       std::cout << std::endl;
-       if (!aim_world) {
-               PostMessage("not looking at any block");
-               Ray aim = ctrl.Aim();
-               std::stringstream s;
-               s << "aim ray: " << aim.orig << ", " << aim.dir;
-               PostMessage(s.str());
-               return;
-       }
-       std::stringstream s;
-       s << "looking at block " << aim_world.block
-               << " " << aim_world.BlockCoords()
-               << " of chunk " << aim_world.GetChunk().Position()
-       ;
-       PostMessage(s.str());
-       Print(aim_world.GetBlock());
-}
-
-void Interface::PrintChunkInfo() {
-       std::cout << std::endl;
-       if (!aim_world) {
-               PostMessage("not looking at any block");
-               return;
-       }
-       std::stringstream s;
-       s << "looking at chunk " << aim_world.GetChunk().Position();
-       PostMessage(s.str());
-
-       PostMessage("  neighbors:");
-       if (aim_world.GetChunk().HasNeighbor(Block::FACE_LEFT)) {
-               s.str("");
-               s << " left  " << aim_world.GetChunk().GetNeighbor(Block::FACE_LEFT).Position();
-               PostMessage(s.str());
-       }
-       if (aim_world.GetChunk().HasNeighbor(Block::FACE_RIGHT)) {
-               s.str("");
-               s << " right " << aim_world.GetChunk().GetNeighbor(Block::FACE_RIGHT).Position();
-               PostMessage(s.str());
-       }
-       if (aim_world.GetChunk().HasNeighbor(Block::FACE_UP)) {
-               s.str("");
-               s << " up    " << aim_world.GetChunk().GetNeighbor(Block::FACE_UP).Position();
-               PostMessage(s.str());
-       }
-       if (aim_world.GetChunk().HasNeighbor(Block::FACE_DOWN)) {
-               s.str("");
-               s << " down  " << aim_world.GetChunk().GetNeighbor(Block::FACE_DOWN).Position();
-               PostMessage(s.str());
-       }
-       if (aim_world.GetChunk().HasNeighbor(Block::FACE_FRONT)) {
-               s.str("");
-               s << " front " << aim_world.GetChunk().GetNeighbor(Block::FACE_FRONT).Position();
-               PostMessage(s.str());
-       }
-       if (aim_world.GetChunk().HasNeighbor(Block::FACE_BACK)) {
-               s.str("");
-               s << " back  " << aim_world.GetChunk().GetNeighbor(Block::FACE_BACK).Position();
-               PostMessage(s.str());
-       }
-       std::cout << std::endl;
-}
-
-void Interface::PrintLightInfo() {
-       std::stringstream s;
-       s
-               << "light level " << world.PlayerChunk().GetLight(world.Player().Position())
-               << " at position " << world.Player().Position()
-       ;
-       PostMessage(s.str());
-}
-
-void Interface::PrintSelectionInfo() {
-       std::cout << std::endl;
-       Print(selection);
-}
-
-void Interface::Print(const Block &block) {
-       std::stringstream s;
-       s << "type: " << block.type
-               << ", face: " << block.GetFace()
-               << ", turn: " << block.GetTurn()
-       ;
-       PostMessage(s.str());
-}
-
 void Interface::ToggleAudio() {
        config.audio_disabled = !config.audio_disabled;
        if (config.audio_disabled) {
 void Interface::ToggleAudio() {
        config.audio_disabled = !config.audio_disabled;
        if (config.audio_disabled) {
@@ -623,7 +525,7 @@ void Interface::UpdateOutline() {
        outl_buf.Clear();
        aim_world.GetType().FillOutlineModel(outl_buf);
        outline.Update(outl_buf);
        outl_buf.Clear();
        aim_world.GetType().FillOutlineModel(outl_buf);
        outline.Update(outl_buf);
-       outline_transform = aim_world.GetChunk().Transform(world.Player().ChunkCoords());
+       outline_transform = aim_world.GetChunk().Transform(Player().ChunkCoords());
        outline_transform *= aim_world.BlockTransform();
        outline_transform *= glm::scale(glm::vec3(1.005f));
 }
        outline_transform *= aim_world.BlockTransform();
        outline_transform *= glm::scale(glm::vec3(1.005f));
 }
@@ -710,11 +612,6 @@ void Keymap::LoadDefault() {
        Map(SDL_SCANCODE_F3, TOGGLE_DEBUG);
        Map(SDL_SCANCODE_F4, TOGGLE_AUDIO);
 
        Map(SDL_SCANCODE_F3, TOGGLE_DEBUG);
        Map(SDL_SCANCODE_F4, TOGGLE_AUDIO);
 
-       Map(SDL_SCANCODE_B, PRINT_BLOCK);
-       Map(SDL_SCANCODE_C, PRINT_CHUNK);
-       Map(SDL_SCANCODE_L, PRINT_LIGHT);
-       Map(SDL_SCANCODE_P, PRINT_SELECTION);
-
        Map(SDL_SCANCODE_ESCAPE, EXIT);
 }
 
        Map(SDL_SCANCODE_ESCAPE, EXIT);
 }
 
@@ -806,14 +703,6 @@ const char *Keymap::ActionToString(Action action) {
                        return "toggle_visual";
                case TOGGLE_DEBUG:
                        return "toggle_debug";
                        return "toggle_visual";
                case TOGGLE_DEBUG:
                        return "toggle_debug";
-               case PRINT_BLOCK:
-                       return "print_block";
-               case PRINT_CHUNK:
-                       return "print_chunk";
-               case PRINT_LIGHT:
-                       return "print_light";
-               case PRINT_SELECTION:
-                       return "print_selection";
                case EXIT:
                        return "exit";
        }
                case EXIT:
                        return "exit";
        }
@@ -854,14 +743,6 @@ Keymap::Action Keymap::StringToAction(const std::string &str) {
                return TOGGLE_VISUAL;
        } else if (str == "toggle_debug") {
                return TOGGLE_DEBUG;
                return TOGGLE_VISUAL;
        } else if (str == "toggle_debug") {
                return TOGGLE_DEBUG;
-       } else if (str == "print_block") {
-               return PRINT_BLOCK;
-       } else if (str == "print_chunk") {
-               return PRINT_CHUNK;
-       } else if (str == "print_light") {
-               return PRINT_LIGHT;
-       } else if (str == "print_selection") {
-               return PRINT_SELECTION;
        } else if (str == "exit") {
                return EXIT;
        } else {
        } else if (str == "exit") {
                return EXIT;
        } else {
index 1d25e01ba2acd61289aac298a5cccbded2d35f68..06ccb62cfea1f97f0341ac313c3848f9ee81e9a6 100644 (file)
 namespace blank {
 
 World::World(const BlockTypeRegistry &types, const Config &config, const WorldSave &save)
 namespace blank {
 
 World::World(const BlockTypeRegistry &types, const Config &config, const WorldSave &save)
-: block_type(types)
+: config(config)
+, block_type(types)
 , generate(config.gen)
 , chunks(config.load, types, generate, save)
 , generate(config.gen)
 , chunks(config.load, types, generate, save)
-, player()
+, players()
 , entities()
 , light_direction(config.light_direction)
 , fog_density(config.fog_density) {
        generate.Space(0);
        generate.Light(13);
        generate.Solids({ 1, 4, 7, 10 });
 , entities()
 , light_direction(config.light_direction)
 , fog_density(config.fog_density) {
        generate.Space(0);
        generate.Light(13);
        generate.Solids({ 1, 4, 7, 10 });
+}
 
 
-       player = &AddEntity();
-       player->Name("player");
-       player->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
-       player->WorldCollidable(true);
-       player->Position(config.spawn);
 
 
-       chunks.QueueSurrounding(player->ChunkCoords());
+Entity *World::AddPlayer(const std::string &name) {
+       for (Entity *e : players) {
+               if (e->Name() == name) {
+                       return nullptr;
+               }
+       }
+       Entity &player = AddEntity();
+       player.Name(name);
+       // TODO: load from save file here
+       player.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
+       player.WorldCollidable(true);
+       player.Position(config.spawn);
+       players.push_back(&player);
+       chunks.QueueSurrounding(player.ChunkCoords());
+       return &player;
 }
 
 
 }
 
 
@@ -133,11 +144,6 @@ bool World::Intersection(const Entity &e, std::vector<WorldCollision> &col) {
 }
 
 
 }
 
 
-Chunk &World::PlayerChunk() {
-       return chunks.ForceLoad(player->ChunkCoords());
-}
-
-
 namespace {
 
 std::vector<WorldCollision> col;
 namespace {
 
 std::vector<WorldCollision> col;
@@ -162,7 +168,8 @@ void World::Update(int dt) {
                        ++iter;
                }
        }
                        ++iter;
                }
        }
-       chunks.Rebase(player->ChunkCoords());
+       // TODO: make flexible
+       chunks.Rebase(players[0]->ChunkCoords());
        chunks.Update(dt);
 }
 
        chunks.Update(dt);
 }
 
@@ -205,7 +212,7 @@ void World::Render(Viewport &viewport) {
        entity_prog.SetFogDensity(fog_density);
 
        for (Entity &entity : entities) {
        entity_prog.SetFogDensity(fog_density);
 
        for (Entity &entity : entities) {
-               entity.Render(entity.ChunkTransform(player->ChunkCoords()), entity_prog);
+               entity.Render(entity.ChunkTransform(players[0]->ChunkCoords()), entity_prog);
        }
 }
 
        }
 }
 
index 76cdeb23ccd3aabcb882a8d8cd211e14aa84b81f..3981b28e336f9dcf8e28ee84411486d1e23121d2 100644 (file)
@@ -63,22 +63,27 @@ public:
        const BlockTypeRegistry &BlockTypes() noexcept { return block_type; }
        ChunkLoader &Loader() noexcept { return chunks; }
 
        const BlockTypeRegistry &BlockTypes() noexcept { return block_type; }
        ChunkLoader &Loader() noexcept { return chunks; }
 
-       Entity &Player() { return *player; }
+       /// add player with given name
+       /// returns nullptr if the name is already taken
+       Entity *AddPlayer(const std::string &name);
        Entity &AddEntity() { entities.emplace_back(); return entities.back(); }
 
        Entity &AddEntity() { entities.emplace_back(); return entities.back(); }
 
-       Chunk &PlayerChunk();
+       const std::vector<Entity *> &Players() const noexcept { return players; }
+       const std::list<Entity> &Entities() const noexcept { return entities; }
 
        void Update(int dt);
 
        void Render(Viewport &);
 
 private:
 
        void Update(int dt);
 
        void Render(Viewport &);
 
 private:
+       Config config;
+
        const BlockTypeRegistry &block_type;
 
        Generator generate;
        ChunkLoader chunks;
 
        const BlockTypeRegistry &block_type;
 
        Generator generate;
        ChunkLoader chunks;
 
-       Entity *player;
+       std::vector<Entity *> players;
        std::list<Entity> entities;
 
        glm::vec3 light_direction;
        std::list<Entity> entities;
 
        glm::vec3 light_direction;