diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c3acea..7650b9a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,6 +211,7 @@ file(GLOB SERVER_SOURCES "src/world/level/tile/entity/*.cpp" "src/world/phys/HitResult.cpp" + "src/commands/*.cpp" ) file(GLOB CLIENT_SOURCES @@ -292,6 +293,8 @@ file(GLOB CLIENT_SOURCES "src/AppPlatform_glfw.cpp" "src/main.cpp" + + "src/commands/*.cpp" ) if (${PLATFORM} STREQUAL "Desktop") diff --git a/src/client/Minecraft.cpp b/src/client/Minecraft.cpp index 51ad3a3..2b0075e 100755 --- a/src/client/Minecraft.cpp +++ b/src/client/Minecraft.cpp @@ -2,6 +2,7 @@ #include "Options.h" #include "client/Options.h" #include "client/player/input/IBuildInput.h" +#include "commands/CommandManager.hpp" #include "platform/input/Keyboard.h" #include "world/item/Item.h" #include "world/item/ItemInstance.h" @@ -175,7 +176,8 @@ Minecraft::Minecraft() : _powerVr(false), commandPort(4711), reserved_d1(0),reserved_d2(0), - reserved_f1(0),reserved_f2(0), options(this) + reserved_f1(0),reserved_f2(0), options(this), + m_commandManager() { //#ifdef ANDROID @@ -1597,3 +1599,11 @@ void Minecraft::optionUpdated(OptionId option, int value ) { setSize(width, height); } } + +void Minecraft::addMessage(const std::string& msg) { +#ifndef STANDALONE_SERVER + gui.addMessage(msg); +#else + LOGI("%s", msg.c_str()); +#endif +} \ No newline at end of file diff --git a/src/client/Minecraft.h b/src/client/Minecraft.h index a7f5abc..61768cb 100755 --- a/src/client/Minecraft.h +++ b/src/client/Minecraft.h @@ -2,6 +2,7 @@ #define NET_MINECRAFT_CLIENT__Minecraft_H__ #include "Options.h" +#include "commands/CommandManager.hpp" #ifndef STANDALONE_SERVER #include "MouseHandler.h" #include "gui/Gui.h" @@ -122,11 +123,15 @@ public: void optionUpdated(OptionId option, int value); int getTicks() { return ticks; } + + void addMessage(const std::string& msg); #ifdef __APPLE__ bool _isSuperFast; bool isSuperFast() { return _isSuperFast; } #endif + CommandManager& commandManager() { return m_commandManager; } + protected: void _levelGenerated(); @@ -227,6 +232,8 @@ private: PerfRenderer* _perfRenderer; CommandServer* _commandServer; + + CommandManager m_commandManager; }; #endif /*NET_MINECRAFT_CLIENT__Minecraft_H__*/ diff --git a/src/client/gui/screens/ConsoleScreen.cpp b/src/client/gui/screens/ConsoleScreen.cpp index 939cb74..6380f48 100644 --- a/src/client/gui/screens/ConsoleScreen.cpp +++ b/src/client/gui/screens/ConsoleScreen.cpp @@ -8,6 +8,7 @@ #include "../../../network/ServerSideNetworkHandler.h" #include "../../../network/packet/ChatPacket.h" #include "../../../platform/log.h" +#include "util/StringUtils.h" #include #include @@ -54,141 +55,33 @@ void ConsoleScreen::charPressed(char inputChar) _input += inputChar; } -// --------------------------------------------------------------------------- -// execute: run _input as a command, print result, close screen -// --------------------------------------------------------------------------- void ConsoleScreen::execute() { + if (!minecraft->level) return; + if (_input.empty()) { - minecraft->setScreen(NULL); - return; + return minecraft->setScreen(NULL); } if (_input[0] == '/') { // Command - std::string result = processCommand(_input); - if (!result.empty()) - minecraft->gui.addMessage(result); + _input = Util::stringTrim(_input.substr(1)); + minecraft->commandManager().execute(*minecraft, _input); } else { - // Chat message: message - std::string msg = std::string("<") + minecraft->player->name + "> " + _input; + // @ai @rewrite if (minecraft->netCallback && minecraft->raknetInstance->isServer()) { - // Hosting a LAN game: displayGameMessage shows locally + broadcasts MessagePacket to clients - static_cast(minecraft->netCallback)->displayGameMessage(msg); + static_cast(minecraft->netCallback)->displayGameMessage(_input); } else if (minecraft->netCallback) { - // Connected client: send ChatPacket to server; server echoes it back as MessagePacket - ChatPacket chatPkt(msg); + ChatPacket chatPkt(_input); minecraft->raknetInstance->send(chatPkt); } else { - // Singleplayer: show locally only - minecraft->gui.addMessage(msg); + minecraft->gui.addMessage(_input); } } minecraft->setScreen(NULL); } -// --------------------------------------------------------------------------- -// processCommand -// --------------------------------------------------------------------------- -static std::string trim(const std::string& s) { - size_t a = s.find_first_not_of(" \t"); - if (a == std::string::npos) return ""; - size_t b = s.find_last_not_of(" \t"); - return s.substr(a, b - a + 1); -} - -std::string ConsoleScreen::processCommand(const std::string& raw) -{ - // strip leading '/' - std::string line = raw; - if (!line.empty() && line[0] == '/') line = line.substr(1); - line = trim(line); - - // tokenise - std::vector args; - { - std::istringstream ss(line); - std::string tok; - while (ss >> tok) args.push_back(tok); - } - - if (args.empty()) return ""; - - Level* level = minecraft->level; - if (!level) return "No level loaded."; - - // ----------------------------------------------------------------------- - // /time ... - // ----------------------------------------------------------------------- - if (args[0] == "time") { - if (args.size() < 2) - return "Usage: /time ..."; - - const std::string& sub = args[1]; - - // -- time add ----------------------------------------------- - if (sub == "add") { - if (args.size() < 3) return "Usage: /time add "; - long delta = std::atol(args[2].c_str()); - long newTime = level->getTime() + delta; - level->setTime(newTime); - std::ostringstream out; - out << "Set the time to " << (newTime % Level::TICKS_PER_DAY); - return out.str(); - } - - // -- time set ----------------------- - if (sub == "set") { - if (args.size() < 3) return "Usage: /time set "; - const std::string& val = args[2]; - - long t = -1; - if (val == "day") t = 1000; - else if (val == "noon") t = 6000; - else if (val == "night") t = 13000; - else if (val == "midnight") t = 18000; - else { - // numeric — accept positive integers only - bool numeric = true; - for (char c : val) - if (!std::isdigit((unsigned char)c)) { numeric = false; break; } - if (!numeric) return std::string("Unknown value: ") + val; - t = std::atol(val.c_str()); - } - - // Preserve the total day count so only the time-of-day changes - long dayCount = level->getTime() / Level::TICKS_PER_DAY; - long newTime = dayCount * Level::TICKS_PER_DAY + (t % Level::TICKS_PER_DAY); - level->setTime(newTime); - std::ostringstream out; - out << "Set the time to " << t; - return out.str(); - } - - // -- time query ------------------------------ - if (sub == "query") { - if (args.size() < 3) return "Usage: /time query "; - const std::string& what = args[2]; - - long total = level->getTime(); - long daytime = total % Level::TICKS_PER_DAY; - long day = total / Level::TICKS_PER_DAY; - - std::ostringstream out; - if (what == "daytime") { out << "The time of day is " << daytime; } - else if (what == "gametime") { out << "The game time is " << total; } - else if (what == "day") { out << "The day is " << day; } - else return std::string("Unknown query: ") + what; - return out.str(); - } - - return "Unknown sub-command. Usage: /time ..."; - } - - return std::string("Unknown command: /") + args[0]; -} - // --------------------------------------------------------------------------- // render // --------------------------------------------------------------------------- diff --git a/src/client/gui/screens/ConsoleScreen.h b/src/client/gui/screens/ConsoleScreen.h index e0074b9..f0c1fd5 100644 --- a/src/client/gui/screens/ConsoleScreen.h +++ b/src/client/gui/screens/ConsoleScreen.h @@ -25,7 +25,6 @@ public: private: void execute(); - std::string processCommand(const std::string& cmd); std::string _input; int _cursorBlink; // tick counter for cursor blink diff --git a/src/commands/Command.hpp b/src/commands/Command.hpp new file mode 100644 index 0000000..ee2a5e2 --- /dev/null +++ b/src/commands/Command.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +enum CommandFlags { + COMMAND_FLAG_SINGLEPLAYER_ONLY = (1 << 1), + COMMAND_FLAG_NO_ARGS = (1 << 2), +}; + +class Minecraft; + +class Command { +public: + const std::string& getName() { return m_name; } + const CommandFlags getFlags() { return m_flags; } + + virtual void execute(Minecraft& mc, const std::vector& args) = 0; + virtual void printHelp(Minecraft& mc) = 0; + +protected: + Command(const std::string& name, CommandFlags flags = (CommandFlags)0) : m_name(name), m_flags(flags) {} + + const std::string m_name; + const CommandFlags m_flags; +}; \ No newline at end of file diff --git a/src/commands/CommandHelp.cpp b/src/commands/CommandHelp.cpp new file mode 100644 index 0000000..c922b4b --- /dev/null +++ b/src/commands/CommandHelp.cpp @@ -0,0 +1,27 @@ +#include "CommandHelp.hpp" +#include "commands/Command.hpp" +#include "CommandManager.hpp" +#include + +CommandHelp::CommandHelp() : Command("help") {} + +void CommandHelp::execute(Minecraft& mc, const std::vector& args) { + if (args.empty()) { + auto cmds = mc.commandManager().getListAllCommands(); + + mc.addMessage("Usage: /help "); + mc.addMessage("List of all commands:"); + + for (auto& cmd : cmds) { + mc.addMessage(" - " + cmd); + } + } else { + Command* cmd = mc.commandManager().getCommand(args[0]); + + if (cmd != nullptr) { + cmd->printHelp(mc); + } + } +} + +void CommandHelp::printHelp(Minecraft& mc) {} \ No newline at end of file diff --git a/src/commands/CommandHelp.hpp b/src/commands/CommandHelp.hpp new file mode 100644 index 0000000..88bee09 --- /dev/null +++ b/src/commands/CommandHelp.hpp @@ -0,0 +1,9 @@ +#include "Command.hpp" + +class CommandHelp : public Command { +public: + CommandHelp(); + + void execute(Minecraft& mc, const std::vector& args); + void printHelp(Minecraft& mc); +}; \ No newline at end of file diff --git a/src/commands/CommandKick.cpp b/src/commands/CommandKick.cpp new file mode 100644 index 0000000..ed167e4 --- /dev/null +++ b/src/commands/CommandKick.cpp @@ -0,0 +1,37 @@ +#include "CommandKick.hpp" +#include "commands/Command.hpp" +#include "network/RakNetInstance.h" +#include "raknet/RakPeer.h" +#include "world/level/Level.h" +#include +#include + + +CommandKick::CommandKick() : Command("kick") {} + +void CommandKick::execute(Minecraft& mc, const std::vector& args) { + if (args.empty()) { + return printHelp(mc); + } + + auto it = std::find_if(mc.level->players.begin(), mc.level->players.end(), [args] (auto& it) -> bool { + return it->name == args[0]; + }); + + if (it == mc.level->players.end()) { + return mc.addMessage("kick: can't find player with name " + args[0]); + } + + if (*it == (Player*)mc.player) { + return mc.addMessage("kick: you can't kick urself lol"); + } + + mc.level->removePlayer(*it); + (*it)->remove(); + mc.raknetInstance->getPeer()->CloseConnection((*it)->owner, true); + mc.addMessage("kick: successfully kicked player " + args[0]); +} + +void CommandKick::printHelp(Minecraft& mc) { + mc.addMessage("Usage: /kick "); +} \ No newline at end of file diff --git a/src/commands/CommandKick.hpp b/src/commands/CommandKick.hpp new file mode 100644 index 0000000..c743aa4 --- /dev/null +++ b/src/commands/CommandKick.hpp @@ -0,0 +1,9 @@ +#include "Command.hpp" + +class CommandKick : public Command { +public: + CommandKick(); + + void execute(Minecraft& mc, const std::vector& args); + void printHelp(Minecraft& mc); +}; \ No newline at end of file diff --git a/src/commands/CommandManager.cpp b/src/commands/CommandManager.cpp new file mode 100644 index 0000000..8ed2d18 --- /dev/null +++ b/src/commands/CommandManager.cpp @@ -0,0 +1,71 @@ +#include "CommandManager.hpp" +#include +#include +#include +#include "client/Minecraft.h" +#include "commands/Command.hpp" +#include "commands/CommandHelp.hpp" +#include "commands/CommandKick.hpp" +#include "network/packet/ChatPacket.h" +#include "network/RakNetInstance.h" +#include "world/level/Level.h" + + + +CommandManager::CommandManager() { + registerAllCommands(); +} + +void CommandManager::registerAllCommands() { + m_commands.push_back(new CommandHelp()); + m_commands.push_back(new CommandKick()); +} + +std::vector CommandManager::getListAllCommands() { + std::vector ret; + + for (auto& cmd : m_commands) { + ret.push_back(cmd->getName()); + } + + return ret; +} + +void CommandManager::execute(Minecraft& mc, const std::string& input) { + std::istringstream ss(input); + std::string cmd; + + ss >> cmd; + + auto it = std::find_if(m_commands.begin(), m_commands.end(), [cmd](auto& it) -> bool { + return it->getName() == cmd; + }); + + if (it == m_commands.end()) { + return mc.addMessage("Command /" + cmd + " not found"); + } + + std::vector args; + + std::string tok; + while (ss >> tok) args.push_back(tok); + + if (!mc.level->isClientSide || (*it)->getFlags() & CommandFlags::COMMAND_FLAG_SINGLEPLAYER_ONLY) { + (*it)->execute(mc, args); + } else { + ChatPacket packet("/" + input); + mc.raknetInstance->send(packet); + } +} + +Command* CommandManager::getCommand(const std::string& name) { + auto it = std::find_if(m_commands.begin(), m_commands.end(), [name](auto& it) -> bool { + return it->getName() == name; + }); + + if (it == m_commands.end()) { + return *it; + } + + return nullptr; +} \ No newline at end of file diff --git a/src/commands/CommandManager.hpp b/src/commands/CommandManager.hpp new file mode 100644 index 0000000..1304877 --- /dev/null +++ b/src/commands/CommandManager.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +#include "Command.hpp" + +class CommandManager { +public: + CommandManager(); + + std::vector getListAllCommands(); + + void execute(Minecraft& mc, const std::string& input); + + Command* getCommand(const std::string& name); + +private: + void registerAllCommands(); + + std::vector m_commands; +}; \ No newline at end of file