build
cachegrind.out.*
callgrind.out.*
+client-saves
saves
server: $(ASSET_DEP) blank
./blank --server --save-path saves/
+client: $(ASSET_DEP) blank
+ ./blank --client --save-path client-saves/
+
gdb: $(ASSET_DEP) blank.debug
gdb ./blank.debug
distclean: clean
rm -f $(BIN) cachegrind.out.* callgrind.out.*
- rm -Rf build saves
+ rm -Rf build client-saves saves
.PHONY: all release debug profile tests run gdb cachegrind callgrind test clean distclean
+++ /dev/null
-Dependencies
-============
-
- GLEW, GLM, SDL2, SDL2_image, SDL2_net, SDL2_ttf, OpenAL, freealut, zlib
-
- CppUnit for tests
-
-archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_net sdl2_ttf openal freealut zlib cppunit
-
-manual:
- CppUnit http://sourceforge.net/projects/cppunit/
- GLEW http://glew.sourceforge.net/
- GLM http://glm.g-truc.net/0.9.6/index.html
- OpenAL http://openal.org/
- SDL http://www.libsdl.org/
- zlib http://zlib.net/
-
-
-Makefile
-========
-
-Targets
--------
-
-all:
- build everything
-
-release (default), debug, profile:
- build executables tuned for running, debugging, and profiling
-
-run:
- build and execute the main binary with state path set to ./saves
-
-server:
- same as run, only in server mode
-
-test:
- build and run unittests
-
-gdb, cachegrind, callgrind:
- build the binary suited for given tool and launch
-
-clean:
- remove intermediates
-
-distclean:
- remove intermediates and artifacts
- (also those generated by tool invocations!)
-
-
-Variables
----------
-
-CXX, LXX:
- compiler/linker used for C++ sources/objects
-
-LIBS:
- names of libraries (for pkg-config)
-
-CPPFLAGS, CXXFLAGS, LDXXFLAGS:
- flags for the preprocessor, compiler, and linker
-
-DEBUG_FLAGS, PROFILE_FLAGS, RELEASE_FLAGS:
- flags for building binaries in debug, profile, and release mode
--- /dev/null
+Dependencies
+============
+
+ GLEW, GLM, SDL2, SDL2_image, SDL2_net, SDL2_ttf, OpenAL, freealut, zlib
+
+ CppUnit for tests
+
+archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_net sdl2_ttf openal freealut zlib cppunit
+
+manual:
+ CppUnit http://sourceforge.net/projects/cppunit/
+ GLEW http://glew.sourceforge.net/
+ GLM http://glm.g-truc.net/0.9.6/index.html
+ OpenAL http://openal.org/
+ SDL http://www.libsdl.org/
+ zlib http://zlib.net/
+
+
+Makefile
+========
+
+Targets
+-------
+
+all:
+ build everything
+
+release (default), debug, profile:
+ build executables tuned for running, debugging, and profiling
+
+run:
+ build and execute the main binary with state path set to ./saves
+
+server:
+ same as run, only in server mode
+
+server:
+ same as run, only in client mode and the save path is set to
+ ./client-saved to prevent clashes with a running `make server`
+
+test:
+ build and run unittests
+
+gdb, cachegrind, callgrind:
+ build the binary suited for given tool and launch
+
+clean:
+ remove intermediates
+
+distclean:
+ remove intermediates and artifacts
+ (also those generated by tool invocations!)
+
+
+Variables
+---------
+
+CXX, LXX:
+ compiler/linker used for C++ sources/objects
+
+LIBS:
+ names of libraries (for pkg-config)
+
+CPPFLAGS, CXXFLAGS, LDXXFLAGS:
+ flags for the preprocessor, compiler, and linker
+
+DEBUG_FLAGS, PROFILE_FLAGS, RELEASE_FLAGS:
+ flags for building binaries in debug, profile, and release mode
--- /dev/null
+Packets
+=======
+
+Ping
+----
+
+To tell the other side we're still alive.
+Both server and client will send this if they haven't sent something in
+a while.
+
+Code: 0
+Payload: none
+
+
+Login
+-----
+
+Sent from client to serveri as a request to join. The server may
+respond negatively if the player name is already taken or some cap has
+been reached.
+
+Code: 1
+Payload:
+ player name, max 32 byte UTF-8 string,
+ shorter names should be zero terminated
--- /dev/null
+Arguments
+=========
+
+Runtime
+-------
+
+-n <n>
+ terminate after <n> frames
+
+-t <t>
+ terminate after <t> milliseconds
+
+if both n and t are given, terminate after n frames and
+assume <t> milliseconds pass each frame
+
+--asset-path <path>
+ load assets from given path
+ default is application dir + "assets"
+
+--save-path <path>
+ store and load saves at given path
+ default is whatever SDL thinks is good
+ (should be ~/.local/share/localhorst/blank/)
+
+Application
+-----------
+
+-d
+ disable double buffering
+
+-m <num>
+ set sample size to <num> (samples per pixel)
+
+--no-vsync
+ disable vsync
+
+--standalone
+ run as standalone (the default)
+
+--client
+ run as client
+
+--server
+ run as server
+
+Interface
+---------
+
+--no-keyboard
+ disable keyboard input handling
+
+--no-mouse
+ disable mouse input handling
+
+--no-hud
+ disable HUD drawing (includes the selected block outline)
+
+--no-audio
+ disable audio
+ the audio device and sounds will still be allocated
+ it just stops the interface from queueing buffers
+
+Network
+-------
+
+--host <hostname>
+ hostname to connect to in client mode
+
+--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
+-----
+
+-s <seed>
+ use <seed> (unsigned integer) as the world seed
+ only used for newly created worlds
+ default is 0
+
+--world-name <name>
+ use given name for the world save
+ no checks are being done right now, so make sure it can be
+ used as a directory name
+
+
+Controls
+========
+
+Move around with WSAD, shift, and space, look around with mouse motion.
+Mouse button 1 deletes the block you're pointing at, button 2 selects it
+as the active block, and button 3 places the active block next to the one
+you're pointing at.
+
+As an alternative to picking, you can also use the mousewheel up/down to
+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).
+
+Press N to toggle player/world collision.
+
+F1 toggles UI rendering.
+F3 toggles a display telling how long on average it takes to compute a frame.
+F4 toggles audio.
+
+Controls are interpreted by scancode, meaning you don't have to break your
+fingers when you're on an AZERTY. WSAD will be ZSQD there and the above
+description is just wrong.
+
+Also I've added a plethora of alternate keys that can be used, like arrow
+keys for movement, ins/del for placing/removing blocks, etc.
+++ /dev/null
-Arguments
-=========
-
-Runtime
--------
-
--n <n>
- terminate after <n> frames
-
--t <t>
- terminate after <t> milliseconds
-
-if both n and t are given, terminate after n frames and
-assume <t> milliseconds pass each frame
-
---asset-path <path>
- load assets from given path
- default is application dir + "assets"
-
---save-path <path>
- store and load saves at given path
- default is whatever SDL thinks is good
- (should be ~/.local/share/localhorst/blank/)
-
-Application
------------
-
--d
- disable double buffering
-
--m <num>
- set sample size to <num> (samples per pixel)
-
---no-vsync
- disable vsync
-
---standalone
- run as standalone (the default)
-
---client
- run as client
-
---server
- run as server
-
-Interface
----------
-
---no-keyboard
- disable keyboard input handling
-
---no-mouse
- disable mouse input handling
-
---no-hud
- disable HUD drawing (includes the selected block outline)
-
---no-audio
- disable audio
- the audio device and sounds will still be allocated
- it just stops the interface from queueing buffers
-
-Network
--------
-
---host <hostname>
- hostname to connect to in client mode
-
---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
------
-
--s <seed>
- use <seed> (unsigned integer) as the world seed
- only used for newly created worlds
- default is 0
-
---world-name <name>
- use given name for the world save
- no checks are being done right now, so make sure it can be
- used as a directory name
-
-
-Controls
-========
-
-Move around with WSAD, shift, and space, look around with mouse motion.
-Mouse button 1 deletes the block you're pointing at, button 2 selects it
-as the active block, and button 3 places the active block next to the one
-you're pointing at.
-
-As an alternative to picking, you can also use the mousewheel up/down to
-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).
-
-Press N to toggle player/world collision.
-
-F1 toggles UI rendering.
-F3 toggles a display telling how long on average it takes to compute a frame.
-F4 toggles audio.
-
-Controls are interpreted by scancode, meaning you don't have to break your
-fingers when you're on an AZERTY. WSAD will be ZSQD there and the above
-description is just wrong.
-
-Also I've added a plethora of alternate keys that can be used, like arrow
-keys for movement, ins/del for placing/removing blocks, etc.
#include "ClientState.hpp"
#include "Environment.hpp"
+#include "init.hpp"
#include "TextureIndex.hpp"
namespace blank {
Environment &env,
const World::Config &wc,
const WorldSave &ws,
+ const Interface::Config &ic,
const Client::Config &cc
)
: env(env)
, block_types()
, world(block_types, wc, ws)
+, chunk_renderer(world, wc.load.load_dist)
+, interface(ic, env, world)
, client(cc, world) {
+ TextureIndex tex_index;
+ env.loader.LoadBlockTypes("default", block_types, tex_index);
+ chunk_renderer.LoadTextures(env.loader, tex_index);
+ chunk_renderer.FogDensity(wc.fog_density);
+ // TODO: better solution for initializing HUD
+ interface.SelectNext();
+ client.SendLogin(ic.player_name);
+}
+
+void ClientState::OnEnter() {
+ env.window.GrabMouse();
}
void ClientState::Handle(const SDL_Event &event) {
- if (event.type == SDL_QUIT) {
- env.state.PopAll();
+ switch (event.type) {
+ case SDL_KEYDOWN:
+ interface.HandlePress(event.key);
+ break;
+ case SDL_KEYUP:
+ interface.HandleRelease(event.key);
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ interface.HandlePress(event.button);
+ break;
+ case SDL_MOUSEBUTTONUP:
+ interface.HandleRelease(event.button);
+ break;
+ case SDL_MOUSEMOTION:
+ interface.Handle(event.motion);
+ break;
+ case SDL_MOUSEWHEEL:
+ interface.Handle(event.wheel);
+ break;
+ case SDL_QUIT:
+ env.state.Pop();
+ break;
+ default:
+ break;
}
}
if (client.TimedOut()) {
env.state.Pop();
}
+
+ interface.Update(dt);
+ world.Update(dt);
+ chunk_renderer.Rebase(interface.Player().ChunkCoords());
+ chunk_renderer.Update(dt);
+
+ 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));
+ env.audio.Position(interface.Player().Position());
+ env.audio.Velocity(interface.Player().Velocity());
+ env.audio.Orientation(dir, up);
}
void ClientState::Render(Viewport &viewport) {
-
+ viewport.WorldPosition(interface.Player().Transform(interface.Player().ChunkCoords()));
+ chunk_renderer.Render(viewport);
+ world.Render(viewport);
+ interface.Render(viewport);
}
}
#include "State.hpp"
#include "../net/Client.hpp"
+#include "../ui/Interface.hpp"
#include "../world/BlockTypeRegistry.hpp"
+#include "../world/ChunkRenderer.hpp"
#include "../world/World.hpp"
Environment &,
const World::Config &,
const WorldSave &,
+ const Interface::Config &,
const Client::Config &
);
+ void OnEnter() override;
+
void Handle(const SDL_Event &) override;
void Update(int dt) override;
void Render(Viewport &) override;
Environment &env;
BlockTypeRegistry block_types;
World world;
+ ChunkRenderer chunk_renderer;
+ Interface interface;
Client client;
};
env.audio.Position(interface.Player().Position());
env.audio.Velocity(interface.Player().Velocity());
env.audio.Orientation(dir, up);
-
}
void WorldState::Render(Viewport &viewport) {
}
Application app(env);
- ClientState client_state(env, config.world, save, config.client);
+ ClientState client_state(env, config.world, save, config.interface, config.client);
app.PushState(&client_state);
Run(app);
}
bool TimedOut() { return conn.TimedOut(); }
+ void SendPing();
+ void SendLogin(const std::string &);
+
private:
void HandlePacket(const UDPpacket &);
#define BLANK_NET_PACKET_HPP_
#include <cstdint>
+#include <string>
namespace blank {
static constexpr std::uint32_t TAG = 0xFB1AB1AF;
enum Type {
- PING,
+ PING = 0,
+ LOGIN = 1,
};
struct Header {
void Tag() noexcept;
std::size_t Ping() noexcept;
+ std::size_t Login(const std::string &name) noexcept;
};
void OnConnect(Connection &);
void OnDisconnect(Connection &);
+ void HandleLogin(Connection &client, const UDPpacket &);
+
private:
UDPsocket serv_sock;
UDPpacket serv_pack;
#include "Server.hpp"
#include "../app/init.hpp"
+#include "../world/World.hpp"
#include <cstring>
#include <iostream>
client_pack.data = new Uint8[sizeof(Packet)];
client_pack.maxlen = sizeof(Packet);
// establish connection
- conn.SendPing(client_pack, client_sock);
+ SendPing();
}
Client::~Client() {
if (conn.TimedOut()) {
cout << "connection timed out :(" << endl;
} else if (conn.ShouldPing()) {
- conn.SendPing(client_pack, client_sock);
+ SendPing();
}
}
+void Client::SendPing() {
+ conn.SendPing(client_pack, client_sock);
+}
+
+void Client::SendLogin(const string &name) {
+ Packet &pack = *reinterpret_cast<Packet *>(client_pack.data);
+ client_pack.len = pack.Login(name);
+ conn.Send(client_pack, client_sock);
+}
+
Connection::Connection(const IPaddress &addr)
: addr(addr)
return sizeof(Header);
}
+size_t Packet::Login(const string &name) noexcept {
+ constexpr size_t maxname = 32;
+
+ Tag();
+ header.type = LOGIN;
+ if (name.size() < maxname) {
+ memset(payload, '\0', maxname);
+ memcpy(payload, name.c_str(), name.size());
+ } else {
+ memcpy(payload, name.c_str(), maxname);
+ }
+ return sizeof(Header) + maxname;
+}
+
Server::Server(const Config &conf, World &world)
: serv_sock(nullptr)
Connection &client = GetClient(udp_pack.address);
client.FlagRecv();
+
+ switch (pack.header.type) {
+ case Packet::PING:
+ // already done all that's supposed to do
+ break;
+ case Packet::LOGIN:
+ HandleLogin(client, udp_pack);
+ break;
+ default:
+ // just drop packets of unknown type
+ break;
+ }
}
Connection &Server::GetClient(const IPaddress &addr) {
cout << "connection timeout from " << client.Address() << endl;
}
+
+void Server::HandleLogin(Connection &client, const UDPpacket &udp_pack) {
+ const Packet &pack = *reinterpret_cast<const Packet *>(udp_pack.data);
+ size_t maxlen = min(udp_pack.len - int(sizeof(Packet::Header)), 32);
+ string name;
+ name.reserve(maxlen);
+ for (size_t i = 0; i < maxlen && pack.payload[i] != '\0'; ++i) {
+ name.push_back(pack.payload[i]);
+ }
+ cout << "got login request from player \"" << name << '"' << endl;
+
+ Entity *player = world.AddPlayer(name);
+ if (player) {
+ // success!
+ cout << "\taccepted" << endl;
+ } else {
+ // aw no :(
+ cout << "\trejected" << endl;
+ }
+}
+
}