diff --git a/docs/lua-api-core.md b/docs/lua-api-core.md index 180c49b7c..dd3afec52 100644 --- a/docs/lua-api-core.md +++ b/docs/lua-api-core.md @@ -40,3 +40,14 @@ Possible events: - `BlockActivated`: `function(pos, block, player, world, client, server)` - `PlayerConnected`: `function(pos, player, client, server)` +### `openminer:get_config(name)` + +Get value for config option. + +Example: +```lua +use_item_drop = openminer:get_config("default:use_item_drop") +``` + +See [this page](lua-api-mod.md#config) for more details about config options. + diff --git a/docs/lua-api-mod.md b/docs/lua-api-mod.md index d77b4c2e4..9a6a64411 100644 --- a/docs/lua-api-mod.md +++ b/docs/lua-api-mod.md @@ -116,6 +116,17 @@ Defines a block from a table, see [this page](lua-api-block.md) for more informa Defines a biome from a table, see [this page](lua-api-biome.md) for more information. +### `config` + +Defines a config option. + +Example: +```lua +mod:option("use_item_drops", false); +``` + +See [this page](lua-api-core.md#openminerget_configname) for more details. + ### `dimension` Defines a dimension from a table, see [this page](lua-api-dimension.md) for more information. diff --git a/mods/default/item_drop.lua b/mods/default/item_drop.lua index 30dc07049..9b08b18d5 100644 --- a/mods/default/item_drop.lua +++ b/mods/default/item_drop.lua @@ -61,8 +61,10 @@ mod:entity { end, } +mod:option("use_item_drops", false) + openminer:add_listener(Event.BlockDigged, function(pos, block, player, world, client, server) - if ServerConfig.useItemDrops then + if openminer.get_config("default:use_item_drops")then mods["default"]:spawn_entity("default:item_drop", { position = {pos.x + 0.5, pos.y + 0.5, pos.z + 0.5}, dimension = world:dimension():id(), diff --git a/source/client/hud/Chat.hpp b/source/client/hud/Chat.hpp index d708137dc..ef6d9027d 100644 --- a/source/client/hud/Chat.hpp +++ b/source/client/hud/Chat.hpp @@ -39,10 +39,15 @@ class Chat : public gk::Drawable, public gk::Transformable { void setMessageVisibility(bool areMessagesVisible); + const std::string &getHistoryEntry(u32 id) const { return m_history.at(m_history.size() - id - 1); } + void addHistoryEntry(const std::string &entry) { m_history.emplace_back(entry); } + u32 historySize() const { return m_history.size(); } + private: void draw(gk::RenderTarget &target, gk::RenderStates states) const override; std::deque m_chatMessages; + std::deque m_history; u32 m_posY = 0; }; diff --git a/source/client/states/ChatState.cpp b/source/client/states/ChatState.cpp index 1117527b4..9def6d28d 100644 --- a/source/client/states/ChatState.cpp +++ b/source/client/states/ChatState.cpp @@ -79,8 +79,10 @@ void ChatState::onEvent(const sf::Event &event) { } if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Return) { - if (!m_textInput.text().empty()) + if (!m_textInput.text().empty()) { m_clientCommandHandler.sendChatMessage(m_textInput.text()); + m_chat.addHistoryEntry(m_textInput.text()); + } gk::Mouse::setCursorGrabbed(true); gk::Mouse::setCursorVisible(false); @@ -91,6 +93,21 @@ void ChatState::onEvent(const sf::Event &event) { if (!m_stateStack->empty()) m_stateStack->pop(); } + + if (event.type == sf::Event::KeyPressed) { + if (event.key.code == sf::Keyboard::Up && m_currentHistoryEntry < (int)m_chat.historySize() - 1) { + if (m_currentHistoryEntry == -1) + m_oldEntry = m_textInput.text(); + m_textInput.setText(m_chat.getHistoryEntry(++m_currentHistoryEntry)); + } + else if (event.key.code == sf::Keyboard::Down && m_currentHistoryEntry >= 0) { + --m_currentHistoryEntry; + if (m_currentHistoryEntry == -1) + m_textInput.setText(m_oldEntry); + else + m_textInput.setText(m_chat.getHistoryEntry(m_currentHistoryEntry)); + } + } } void ChatState::draw(gk::RenderTarget &target, gk::RenderStates states) const { diff --git a/source/client/states/ChatState.hpp b/source/client/states/ChatState.hpp index 5eba81885..cc6ba4cf2 100644 --- a/source/client/states/ChatState.hpp +++ b/source/client/states/ChatState.hpp @@ -49,6 +49,9 @@ class ChatState : public InterfaceState { ClientCommandHandler &m_clientCommandHandler; Chat &m_chat; + + s32 m_currentHistoryEntry = -1; + std::string m_oldEntry; }; #endif // CHATSTATE_HPP_ diff --git a/source/server/core/ServerApplication.cpp b/source/server/core/ServerApplication.cpp index 17dab2b0b..029ff95d7 100644 --- a/source/server/core/ServerApplication.cpp +++ b/source/server/core/ServerApplication.cpp @@ -117,6 +117,9 @@ int ServerApplication::run(bool isProtected) { gkError() << "Fatal error" << e.what(); + ServerConfig::saveConfigToFile("config/server.lua"); + ServerConfig::options.clear(); + m_serverCommandHandler.sendServerClosed(std::string("Server error ") + e.what()); m_registry.clear(); @@ -132,6 +135,9 @@ int ServerApplication::run(bool isProtected) { gkInfo() << "Stopping server..."; + ServerConfig::saveConfigToFile("config/server.lua"); + ServerConfig::options.clear(); + m_serverCommandHandler.sendServerClosed("Server closed."); m_registry.clear(); diff --git a/source/server/core/ServerConfig.cpp b/source/server/core/ServerConfig.cpp index c8f1cfe3b..1c52895fb 100644 --- a/source/server/core/ServerConfig.cpp +++ b/source/server/core/ServerConfig.cpp @@ -24,29 +24,33 @@ * * ===================================================================================== */ -#include "ServerConfig.hpp" +#include -// Gameplay -bool ServerConfig::useItemDrops = false; +#include +#include + +#include "ServerConfig.hpp" // Server u8 ServerConfig::maxPlayers = 5; -#include -#include +// Mod-defined options +std::unordered_map ServerConfig::options; -#include +static sol::state lua; void ServerConfig::loadConfigFromFile(const char *file) { if (gk::Filesystem::fileExists(file)) { - sol::state lua; - try { lua.safe_script_file(file); - useItemDrops = lua["useItemDrops"].get_or(useItemDrops); + maxPlayers = lua["max_players"].get_or(maxPlayers); - maxPlayers = lua["maxPlayers"].get_or(maxPlayers); + if (lua["mod_options"].valid() && lua["mod_options"].get_type() == sol::type::table) { + for (auto &it : lua["mod_options"].get()) { + options.emplace(it.first.as(), it.second); + } + } gkInfo() << "Config file loaded successfully"; } @@ -56,3 +60,55 @@ void ServerConfig::loadConfigFromFile(const char *file) { } } +void ServerConfig::saveConfigToFile(const char *filename) { + std::ofstream file{filename, std::ofstream::out | std::ofstream::trunc}; + file << "max_players = " << (u16)maxPlayers << std::endl; + file << "mod_options = {" << std::endl; + + for (auto &it : options) { + file << "\t[\"" << it.first << "\"] = "; + + if (it.second.get_type() == sol::type::boolean) { + bool value = it.second.as(); + file << (value ? "true" : "false"); + } + else if (it.second.get_type() == sol::type::number) { + file << it.second.as(); + } + else if (it.second.get_type() == sol::type::string) { + file << it.second.as(); + } + else { + file << "nil"; + } + + file << "," << std::endl; + } + + file << "}" << std::endl; +} + +bool ServerConfig::assignOption(const std::string &name, const std::string &value) { + auto it = options.find(name); + if (it != options.end()) { + try { + sol::object object = lua.load("return " + value)(); + if (object.valid() + && (object.get_type() == sol::type::boolean + || object.get_type() == sol::type::number + || object.get_type() == sol::type::string)) { + it->second = object; + return true; + } + } + catch (sol::error &e) { + gkWarning() << e.what(); + } + } + else { + gkWarning() << "Can't assign option: '" + name + "' doesn't exist"; + } + + return false; +} + diff --git a/source/server/core/ServerConfig.hpp b/source/server/core/ServerConfig.hpp index 169de5b73..5364f5086 100644 --- a/source/server/core/ServerConfig.hpp +++ b/source/server/core/ServerConfig.hpp @@ -27,16 +27,24 @@ #ifndef SERVERCONFIG_HPP_ #define SERVERCONFIG_HPP_ +#include +#include + #include -namespace ServerConfig { - // Gameplay - extern bool useItemDrops; +#include +namespace ServerConfig { // Server extern u8 maxPlayers; + // Mod-defined options + extern std::unordered_map options; + void loadConfigFromFile(const char *file); + void saveConfigToFile(const char *file); + + bool assignOption(const std::string &name, const std::string &value); } #endif // SERVERCONFIG_HPP_ diff --git a/source/server/lua/LuaCore.cpp b/source/server/lua/LuaCore.cpp index ca6ef32ac..3161c7a02 100644 --- a/source/server/lua/LuaCore.cpp +++ b/source/server/lua/LuaCore.cpp @@ -43,15 +43,20 @@ void LuaCore::initUsertype(sol::state &lua) { "PlayerConnected", LuaEventType::PlayerConnected ); - lua["ServerConfig"] = lua.create_table_with( - "useItemDrops", ServerConfig::useItemDrops - ); - lua.new_usertype("LuaCore", + "registry", &LuaCore::m_registry, + "mod_loader", &LuaCore::m_modLoader, + "add_listener", &LuaCore::addListener, + "get_config", [&](const std::string &option) { + auto it = ServerConfig::options.find(option); + if (it == ServerConfig::options.end()) { + gkWarning() << "Option" << option << "doesn't exist"; + return sol::object{}; + } - "registry", &LuaCore::m_registry, - "mod_loader", &LuaCore::m_modLoader + return it->second; + } ); } diff --git a/source/server/lua/LuaMod.cpp b/source/server/lua/LuaMod.cpp index 626d0f762..935076c64 100644 --- a/source/server/lua/LuaMod.cpp +++ b/source/server/lua/LuaMod.cpp @@ -30,6 +30,7 @@ #include "PlacementEntry.hpp" #include "Registry.hpp" #include "ServerCommandHandler.hpp" +#include "ServerConfig.hpp" #include "ServerPlayer.hpp" #include "Tree.hpp" #include "WorldController.hpp" @@ -79,7 +80,13 @@ void LuaMod::initUsertype(sol::state &lua) { "biome", DEF_FUNC(DefinitionType::Biome), "dimension", DEF_FUNC(DefinitionType::Dimension), "key", DEF_FUNC(DefinitionType::Key), - "entity", DEF_FUNC(DefinitionType::Entity) + "entity", DEF_FUNC(DefinitionType::Entity), + + "option", [&] (LuaMod &self, const std::string &name, sol::object defaultValue) { + auto it = ServerConfig::options.find(name); + if (it == ServerConfig::options.end()) + ServerConfig::options.emplace(self.m_id + ":" + name, defaultValue); + } ); } diff --git a/source/server/network/ChatCommandHandler.cpp b/source/server/network/ChatCommandHandler.cpp index 340246fb2..dd5d2183a 100644 --- a/source/server/network/ChatCommandHandler.cpp +++ b/source/server/network/ChatCommandHandler.cpp @@ -47,6 +47,7 @@ void ChatCommandHandler::parseCommand(const std::string &str, ClientInfo &client {"save", &ChatCommandHandler::saveCommand}, {"load", &ChatCommandHandler::loadCommand}, {"stop", &ChatCommandHandler::stopCommand}, + {"option", &ChatCommandHandler::optionCommand}, }; if (!command.empty()) { @@ -117,3 +118,42 @@ void ChatCommandHandler::stopCommand(const std::vector &, ClientInf m_server.stopServer(); } +#include "ServerConfig.hpp" + +void ChatCommandHandler::optionCommand(const std::vector &command, ClientInfo &client) const { + if (command.size() < 2 || command.size() > 3) { + m_server.sendChatMessage(0, "Usage: /option []", &client); + } + else { + std::string name = command.at(1); + + auto it = ServerConfig::options.find(name); + if (it != ServerConfig::options.end()) { + if (command.size() < 3) { + if (it->second.get_type() == sol::type::boolean) { + bool value = it->second.as(); + m_server.sendChatMessage(0, std::string("Value: ") + (value ? "true" : "false")); + } + else if (it->second.get_type() == sol::type::number) { + m_server.sendChatMessage(0, "Value: " + std::to_string(it->second.as())); + } + else if (it->second.get_type() == sol::type::string) { + m_server.sendChatMessage(0, "Value: " + it->second.as()); + } + else { + m_server.sendChatMessage(0, "Value: nil"); + } + } + else { + std::string value = command.at(2); + if (ServerConfig::assignOption(name, value)) + m_server.sendChatMessage(0, "Value: " + value); + else + m_server.sendChatMessage(0, "Invalid option value"); + } + } + else { + m_server.sendChatMessage(0, "Option '" + name + "' doesn't exist"); + } + } +} diff --git a/source/server/network/ChatCommandHandler.hpp b/source/server/network/ChatCommandHandler.hpp index c5717d313..204467159 100644 --- a/source/server/network/ChatCommandHandler.hpp +++ b/source/server/network/ChatCommandHandler.hpp @@ -46,6 +46,7 @@ class ChatCommandHandler { void saveCommand(const std::vector &command, ClientInfo &client) const; void loadCommand(const std::vector &command, ClientInfo &client) const; void stopCommand(const std::vector &command, ClientInfo &client) const; + void optionCommand(const std::vector &command, ClientInfo &client) const; ServerCommandHandler &m_server; WorldController &m_worldController;