command line
- useful for development and later on world administration
+ more commands pls
+ and show me their output
persistence
networking
- write tests
- do some manual testing
- some more testing
- a little optimization
+ definitely needs throttling for the internets
+
+ players stats (who's connected, their ping, and game-relevant
+ things) should be sent to clients
+
launcher ui
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
throw SDLError("SDL_InitSubSystem(SDL_INIT_VIDEO)");
}
+ // SDL seems to start out in text input state
+ SDL_StopTextInput();
}
InitVideo::~InitVideo() {
case SDL_KEYDOWN:
// TODO: move to interface
if (event.key.keysym.sym == SDLK_RETURN) {
+ chat.Clear();
+ master.GetEnv().state.Push(&chat);
+ hud.KeepMessages(true);
+ } else if (event.key.keysym.sym == SDLK_SLASH) {
+ chat.Preset("/");
master.GetEnv().state.Push(&chat);
hud.KeepMessages(true);
} else {
#define BLANK_SERVER_SERVER_HPP
#include "../app/Config.hpp"
+#include "../shared/CLI.hpp"
#include "../world/World.hpp"
#include "../world/WorldManipulator.hpp"
void SetBlock(Chunk &, int, const Block &) override;
+ /// for use by client connections when they receive a line from the player
+ void DispatchMessage(Player &, const std::string &);
+
/// send message to all connected clients
void DistributeMessage(std::uint8_t type, std::uint32_t ref, const std::string &msg);
const WorldSave &save;
const Model *player_model;
+ CLI cli;
+
};
}
pack.ReadMessage(msg);
if (type == 1 && HasPlayer()) {
- server.DistributeMessage(1, PlayerEntity().ID(), msg);
+ server.DispatchMessage(input->GetPlayer(), msg);
}
}
, world(world)
, spawn_index(world.Chunks().MakeIndex(wc.spawn, 3))
, save(save)
-, player_model(nullptr) {
+, player_model(nullptr)
+, cli(world) {
serv_sock = SDLNet_UDP_Open(conf.port);
if (!serv_sock) {
throw NetError("SDLNet_UDP_Open");
}
}
+void Server::DispatchMessage(Player &player, const string &msg) {
+ if (msg.empty()) {
+ return;
+ }
+ if (msg[0] == '/' && msg.size() > 1 && msg[1] != '/') {
+ cli.Execute(player, msg.substr(1));
+ } else {
+ DistributeMessage(1, player.GetEntity().ID(), msg);
+ }
+}
+
void Server::DistributeMessage(uint8_t type, uint32_t ref, const string &msg) {
auto pack = Packet::Make<Packet::Message>(serv_pack);
pack.WriteType(type);
--- /dev/null
+#ifndef BLANK_SHARED_CLI_HPP_
+#define BLANK_SHARED_CLI_HPP_
+
+#include <map>
+#include <string>
+
+
+namespace blank {
+
+class Player;
+class TokenStreamReader;
+class World;
+
+class CLI {
+
+public:
+ struct Command {
+ virtual ~Command();
+ virtual void Execute(CLI &, Player &, TokenStreamReader &) = 0;
+ };
+
+public:
+ explicit CLI(World &);
+ ~CLI();
+
+ void AddCommand(const std::string &name, Command *);
+
+ void Execute(Player &, const std::string &);
+
+ void Message(const std::string &msg);
+ void Error(const std::string &msg);
+
+private:
+ World &world;
+ std::map<std::string, Command *> commands;
+
+};
+
+}
+
+#endif
public:
ChatState(Environment &env, State &parent, Responder &);
+ void Preset(const std::string &);
+ void Clear();
+
void OnResume() override;
void OnPause() override;
State &parent;
Responder &responder;
+ std::string preset;
TextInput input;
};
--- /dev/null
+#include "CLI.hpp"
+#include "commands.hpp"
+
+#include "../io/TokenStreamReader.hpp"
+#include "../world/Entity.hpp"
+#include "../world/Player.hpp"
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+
+
+namespace blank {
+
+CLI::CLI(World &world)
+: world(world)
+, commands() {
+ AddCommand("tp", new TeleportCommand);
+}
+
+CLI::~CLI() {
+ for (auto &entry : commands) {
+ delete entry.second;
+ }
+}
+
+void CLI::AddCommand(const string &name, Command *cmd) {
+ commands[name] = cmd;
+}
+
+void CLI::Execute(Player &player, const string &line) {
+ stringstream s(line);
+ TokenStreamReader args(s);
+ if (!args.HasMore()) {
+ // ignore empty command line
+ return;
+ }
+ if (args.Peek().type != Token::IDENTIFIER) {
+ Error("I don't understand");
+ return;
+ }
+ string name;
+ args.ReadIdentifier(name);
+ auto entry = commands.find(name);
+ if (entry == commands.end()) {
+ Error(name + ": command not found");
+ return;
+ }
+ try {
+ entry->second->Execute(*this, player, args);
+ } catch (exception &e) {
+ Error(name + ": " + e.what());
+ } catch (...) {
+ Error(name + ": unknown execution error");
+ }
+}
+
+void CLI::Message(const string &msg) {
+ // TODO: display message to player
+ cout << msg << endl;
+}
+
+void CLI::Error(const string &msg) {
+ Message("CLI error: " + msg);
+}
+
+CLI::Command::~Command() {
+
+}
+
+
+void TeleportCommand::Execute(CLI &cli, Player &player, TokenStreamReader &args) {
+ glm::vec3 pos(args.GetFloat(), args.GetFloat(), args.GetFloat());
+ glm::ivec3 chunk(pos);
+ chunk /= Chunk::Extent();
+ pos -= chunk;
+ EntityState state = player.GetEntity().GetState();
+ state.chunk_pos = chunk;
+ state.block_pos = pos;
+ player.GetEntity().SetState(state);
+}
+
+}
--- /dev/null
+#ifndef BLANK_SHARED_COMMANDS_HPP_
+#define BLANK_SHARED_COMMANDS_HPP_
+
+#include "CLI.hpp"
+
+
+namespace blank {
+
+class TeleportCommand
+: public CLI::Command {
+
+ void Execute(CLI &, Player &, TokenStreamReader &) override;
+
+};
+
+}
+
+#endif
: env(env)
, parent(parent)
, responder(responder)
+, preset()
, input(env.assets.small_ui_font) {
input.Position(glm::vec3(25.0f, -25.0f, -1.0f), Gravity::SOUTH_WEST, Gravity::SOUTH_WEST);
input.Width(env.viewport.Width() - 50.0f);
input.Background(glm::vec4(0.5f));
}
+void ChatState::Preset(const std::string &text) {
+ preset = text;
+}
+
+void ChatState::Clear() {
+ preset.clear();
+}
+
void ChatState::OnResume() {
OnResize(env.viewport);
input.Clear();
+ if (!preset.empty()) {
+ input.Insert(preset.c_str());
+ }
input.Focus(env.viewport);
}
, chunk_renderer(player.GetChunks())
, spawner(world, res.models, env.rng)
, sky(env.loader.LoadCubeMap("skybox"))
+, cli(world)
, preload(env, chunk_loader, chunk_renderer)
, unload(env, world.Chunks(), save)
, chat(env, *this, *this) {
case SDL_KEYDOWN:
// TODO: move to interface
if (event.key.keysym.sym == SDLK_RETURN) {
+ chat.Clear();
+ env.state.Push(&chat);
+ hud.KeepMessages(true);
+ } else if (event.key.keysym.sym == SDLK_SLASH) {
+ chat.Preset("/");
env.state.Push(&chat);
hud.KeepMessages(true);
} else {
}
void MasterState::OnLineSubmit(const std::string &line) {
- if (!line.empty()) {
+ if (line.empty()) {
+ return;
+ }
+ if (line[0] == '/' && line.size() > 1 && line[1] != '/') {
+ cli.Execute(player, line.substr(1));
+ } else {
hud.PostMessage(line);
}
}
#include "../audio/SoundBank.hpp"
#include "../graphics/SkyBox.hpp"
#include "../shared/ChatState.hpp"
+#include "../shared/CLI.hpp"
#include "../shared/WorldResources.hpp"
#include "../ui/DirectInput.hpp"
#include "../ui/HUD.hpp"
SkyBox sky;
+ CLI cli;
+
PreloadState preload;
UnloadState unload;
ChatState chat;