From 10a310869c61cc52046e165f36ac9639fe9d0c69 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Mon, 26 Oct 2015 15:56:35 +0100 Subject: [PATCH] added simple command line --- doc/todo | 12 +++-- src/app/init.cpp | 2 + src/client/client.cpp | 5 ++ src/server/Server.hpp | 6 +++ src/server/net.cpp | 16 ++++++- src/shared/CLI.hpp | 41 +++++++++++++++++ src/shared/ChatState.hpp | 4 ++ src/shared/cli.cpp | 84 ++++++++++++++++++++++++++++++++++ src/shared/commands.hpp | 18 ++++++++ src/shared/states.cpp | 12 +++++ src/standalone/MasterState.cpp | 13 +++++- src/standalone/MasterState.hpp | 3 ++ 12 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 src/shared/CLI.hpp create mode 100644 src/shared/cli.cpp create mode 100644 src/shared/commands.hpp diff --git a/doc/todo b/doc/todo index 704487f..c6a3808 100644 --- a/doc/todo +++ b/doc/todo @@ -19,7 +19,8 @@ font rendering command line - useful for development and later on world administration + more commands pls + and show me their output persistence @@ -30,10 +31,11 @@ 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 diff --git a/src/app/init.cpp b/src/app/init.cpp index f4bcd01..a0ea7b5 100644 --- a/src/app/init.cpp +++ b/src/app/init.cpp @@ -91,6 +91,8 @@ InitVideo::InitVideo() { 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() { diff --git a/src/client/client.cpp b/src/client/client.cpp index ae1bf73..26e0e0d 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -105,6 +105,11 @@ void InteractiveState::Handle(const SDL_Event &event) { 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 { diff --git a/src/server/Server.hpp b/src/server/Server.hpp index 683d362..3bffafc 100644 --- a/src/server/Server.hpp +++ b/src/server/Server.hpp @@ -2,6 +2,7 @@ #define BLANK_SERVER_SERVER_HPP #include "../app/Config.hpp" +#include "../shared/CLI.hpp" #include "../world/World.hpp" #include "../world/WorldManipulator.hpp" @@ -46,6 +47,9 @@ public: 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); @@ -66,6 +70,8 @@ private: const WorldSave &save; const Model *player_model; + CLI cli; + }; } diff --git a/src/server/net.cpp b/src/server/net.cpp index 38224ec..f7c874f 100644 --- a/src/server/net.cpp +++ b/src/server/net.cpp @@ -593,7 +593,7 @@ void ClientConnection::On(const Packet::Message &pack) { pack.ReadMessage(msg); if (type == 1 && HasPlayer()) { - server.DistributeMessage(1, PlayerEntity().ID(), msg); + server.DispatchMessage(input->GetPlayer(), msg); } } @@ -609,7 +609,8 @@ Server::Server( , 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"); @@ -724,6 +725,17 @@ void Server::SetBlock(Chunk &chunk, int index, const Block &block) { } } +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(serv_pack); pack.WriteType(type); diff --git a/src/shared/CLI.hpp b/src/shared/CLI.hpp new file mode 100644 index 0000000..b601e57 --- /dev/null +++ b/src/shared/CLI.hpp @@ -0,0 +1,41 @@ +#ifndef BLANK_SHARED_CLI_HPP_ +#define BLANK_SHARED_CLI_HPP_ + +#include +#include + + +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 commands; + +}; + +} + +#endif diff --git a/src/shared/ChatState.hpp b/src/shared/ChatState.hpp index 7b8a993..67523d7 100644 --- a/src/shared/ChatState.hpp +++ b/src/shared/ChatState.hpp @@ -23,6 +23,9 @@ public: public: ChatState(Environment &env, State &parent, Responder &); + void Preset(const std::string &); + void Clear(); + void OnResume() override; void OnPause() override; @@ -39,6 +42,7 @@ private: State &parent; Responder &responder; + std::string preset; TextInput input; }; diff --git a/src/shared/cli.cpp b/src/shared/cli.cpp new file mode 100644 index 0000000..aa6629f --- /dev/null +++ b/src/shared/cli.cpp @@ -0,0 +1,84 @@ +#include "CLI.hpp" +#include "commands.hpp" + +#include "../io/TokenStreamReader.hpp" +#include "../world/Entity.hpp" +#include "../world/Player.hpp" + +#include +#include + +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); +} + +} diff --git a/src/shared/commands.hpp b/src/shared/commands.hpp new file mode 100644 index 0000000..53ab697 --- /dev/null +++ b/src/shared/commands.hpp @@ -0,0 +1,18 @@ +#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 diff --git a/src/shared/states.cpp b/src/shared/states.cpp index b9a48b0..f98382e 100644 --- a/src/shared/states.cpp +++ b/src/shared/states.cpp @@ -14,6 +14,7 @@ ChatState::ChatState(Environment &env, State &parent, Responder &responder) : 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); @@ -21,9 +22,20 @@ ChatState::ChatState(Environment &env, State &parent, Responder &responder) 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); } diff --git a/src/standalone/MasterState.cpp b/src/standalone/MasterState.cpp index 81017f7..5ade4f1 100644 --- a/src/standalone/MasterState.cpp +++ b/src/standalone/MasterState.cpp @@ -36,6 +36,7 @@ MasterState::MasterState( , 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) { @@ -99,6 +100,11 @@ void MasterState::Handle(const SDL_Event &event) { 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 { @@ -206,7 +212,12 @@ void MasterState::Exit() { } 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); } } diff --git a/src/standalone/MasterState.hpp b/src/standalone/MasterState.hpp index 26fcf26..9cb095a 100644 --- a/src/standalone/MasterState.hpp +++ b/src/standalone/MasterState.hpp @@ -10,6 +10,7 @@ #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" @@ -87,6 +88,8 @@ private: SkyBox sky; + CLI cli; + PreloadState preload; UnloadState unload; ChatState chat; -- 2.39.2