diff --git a/builtin/client/register.lua b/builtin/client/register.lua index cfc5abaa7bd4..2b835c744d7e 100644 --- a/builtin/client/register.lua +++ b/builtin/client/register.lua @@ -71,3 +71,5 @@ core.registered_on_dignode, core.register_on_dignode = make_registration() core.registered_on_punchnode, core.register_on_punchnode = make_registration() core.registered_on_placenode, core.register_on_placenode = make_registration() core.registered_on_item_use, core.register_on_item_use = make_registration() +core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration() +core.registered_on_modchannel_signal, core.register_on_modchannel_signal = make_registration() diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 06c9c61d20b3..f5d4ea216585 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -583,6 +583,7 @@ core.registered_on_punchplayers, core.register_on_punchplayer = make_registratio core.registered_on_priv_grant, core.register_on_priv_grant = make_registration() core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration() core.registered_can_bypass_userlimit, core.register_can_bypass_userlimit = make_registration() +core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration() -- -- Compatibility for on_mapgen_init() diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 4cfd2fe5349a..926a881fe5aa 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -928,6 +928,9 @@ player_transfer_distance (Player transfer distance) int 0 # Whether to allow players to damage and kill each other. enable_pvp (Player versus Player) bool true +# Enable mod channels support. +enable_mod_channels (Mod channels) bool false + # If this is set, players will always (re)spawn at the given position. static_spawnpoint (Static spawnpoint) string diff --git a/clientmods/preview/init.lua b/clientmods/preview/init.lua index 150db181605a..bf5fb3db810a 100644 --- a/clientmods/preview/init.lua +++ b/clientmods/preview/init.lua @@ -1,5 +1,6 @@ local modname = core.get_current_modname() or "??" local modstorage = core.get_mod_storage() +local mod_channel dofile("preview:example.lua") -- This is an example function to ensure it's working properly, should be removed before merge @@ -14,6 +15,21 @@ core.register_on_connect(function() print("Server ip: " .. server_info.ip) print("Server address: " .. server_info.address) print("Server port: " .. server_info.port) + + mod_channel = core.mod_channel_join("experimental_preview") +end) + +core.register_on_modchannel_message(function(channel, sender, message) + print("[PREVIEW][modchannels] Received message `" .. message .. "` on channel `" + .. channel .. "` from sender `" .. sender .. "`") + core.after(1, function() + mod_channel:send_all("CSM preview received " .. message) + end) +end) + +core.register_on_modchannel_signal(function(channel, signal) + print("[PREVIEW][modchannels] Received signal id `" .. signal .. "` on channel `" + .. channel) end) core.register_on_placenode(function(pointed_thing, node) @@ -100,6 +116,12 @@ core.after(2, function() preview_minimap() end) +core.after(4, function() + if mod_channel:is_writeable() then + mod_channel:send_all("preview talk to experimental") + end +end) + core.after(5, function() if core.ui.minimap then core.ui.minimap:show() diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index 44c7c2787427..4c48b6619adf 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -683,6 +683,12 @@ Call these functions only at load time! * Called when the local player uses an item. * Newest functions are called first. * If any function returns true, the item use is not sent to server. +* `minetest.register_on_modchannel_message(func(channel_name, sender, message))` + * Called when an incoming mod channel message is received + * You must have joined some channels before, and server must acknowledge the + join request. + * If message comes from a server mod, `sender` field is an empty string. + ### Sounds * `minetest.sound_play(spec, parameters)`: returns a handle * `spec` is a `SimpleSoundSpec` @@ -754,6 +760,16 @@ Call these functions only at load time! * returns reference to mod private `StorageRef` * must be called during mod load time +### Mod channels +![Mod channels communication scheme](docs/mod channels.png) + +* `minetest.mod_channel_join(channel_name)` + * Client joins channel `channel_name`, and creates it, if necessary. You + should listen from incoming messages with `minetest.register_on_modchannel_message` + call to receive incoming messages. Warning, this function is asynchronous. + * You should use a minetest.register_on_connect(function() ... end) to perform + a successful channel join on client startup. + ### Misc. * `minetest.parse_json(string[, nullvalue])`: returns something * Convert a string containing JSON data into the Lua equivalent @@ -827,9 +843,25 @@ Call these functions only at load time! Class reference --------------- +### ModChannel + +An interface to use mod channels on client and server + +#### Methods +* `leave()`: leave the mod channel. + * Client leaves channel `channel_name`. + * No more incoming or outgoing messages can be sent to this channel from client mods. + * This invalidate all future object usage + * Ensure your set mod_channel to nil after that to free Lua resources +* `is_writeable()`: returns true if channel is writeable and mod can send over it. +* `send_all(message)`: Send `message` though the mod channel. + * If mod channel is not writeable or invalid, message will be dropped. + * Message size is limited to 65535 characters by protocol. + ### Minimap An interface to manipulate minimap on client UI +#### Methods * `show()`: shows the minimap (if not disabled by server) * `hide()`: hides the minimap * `set_pos(pos)`: sets the minimap position on screen diff --git a/doc/lua_api.txt b/doc/lua_api.txt index b71eb5477aca..812b857f71cb 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1126,7 +1126,7 @@ The 2D perlin noise described by `noise_params` varies the Y co-ordinate of the stratum midpoint. The 2D perlin noise described by `np_stratum_thickness` varies the stratum's vertical thickness (in units of nodes). Due to being continuous across mapchunk borders the stratum's vertical thickness is -unlimited. +unlimited. `y_min` and `y_max` define the limits of the ore generation and for performance reasons should be set as close together as possible but without clipping the stratum's Y variation. @@ -2496,6 +2496,20 @@ Call these functions only at load time! * `minetest.register_can_bypass_userlimit(function(name, ip))` * Called when `name` user connects with `ip`. * Return `true` to by pass the player limit +* `minetest.register_on_modchannel_message(func(channel_name, sender, message))` + * Called when an incoming mod channel message is received + * You should have joined some channels to receive events. + * If message comes from a server mod, `sender` field is an empty string. +* `minetest.register_on_modchannel_signal(func(channel_name, signal))` + * Called when a valid incoming mod channel signal is received + * Signal id permit to react to server mod channel events + * Possible values are: + 0: join_ok + 1: join_failed + 2: leave_ok + 3: leave_failed + 4: event_on_not_joined_channel + 5: state_changed ### Other registration functions * `minetest.register_chatcommand(cmd, chatcommand definition)` @@ -2773,6 +2787,14 @@ and `minetest.auth_reload` call the authetification handler. * spread these updates to neighbours and can cause a cascade of nodes to fall. +### Mod channels +You can find mod channels communication scheme in `docs/mod_channels.png`. + +* `minetest.mod_channel_join(channel_name)` + * Server joins channel `channel_name`, and creates it if necessary. You + should listen from incoming messages with `minetest.register_on_modchannel_message` + call to receive incoming messages + ### Inventory `minetest.get_inventory(location)`: returns an `InvRef` @@ -3256,6 +3278,21 @@ These functions return the leftover itemstack. Class reference --------------- +### ModChannel + +An interface to use mod channels on client and server + +#### Methods +* `leave()`: leave the mod channel. + * Server leaves channel `channel_name`. + * No more incoming or outgoing messages can be sent to this channel from server mods. + * This invalidate all future object usage + * Ensure your set mod_channel to nil after that to free Lua resources +* `is_writeable()`: returns true if channel is writeable and mod can send over it. +* `send_all(message)`: Send `message` though the mod channel. + * If mod channel is not writeable or invalid, message will be dropped. + * Message size is limited to 65535 characters by protocol. + ### `MetaDataRef` See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`. diff --git a/doc/mod_channels.png b/doc/mod_channels.png new file mode 100644 index 000000000000..08fdfca29eb1 Binary files /dev/null and b/doc/mod_channels.png differ diff --git a/games/minimal/mods/experimental/init.lua b/games/minimal/mods/experimental/init.lua index afa136777a74..657148dce263 100644 --- a/games/minimal/mods/experimental/init.lua +++ b/games/minimal/mods/experimental/init.lua @@ -2,6 +2,8 @@ -- Experimental things -- +dofile(minetest.get_modpath("experimental").."/modchannels.lua") + -- For testing random stuff experimental = {} diff --git a/games/minimal/mods/experimental/modchannels.lua b/games/minimal/mods/experimental/modchannels.lua new file mode 100644 index 000000000000..8fce6cfc46af --- /dev/null +++ b/games/minimal/mods/experimental/modchannels.lua @@ -0,0 +1,16 @@ +-- +-- Mod channels experimental handlers +-- +local mod_channel = core.mod_channel_join("experimental_preview") + +core.register_on_modchannel_message(function(channel, sender, message) + print("[minimal][modchannels] Server received message `" .. message + .. "` on channel `" .. channel .. "` from sender `" .. sender .. "`") + + if mod_channel:is_writeable() then + mod_channel:send_all("experimental answers to preview") + mod_channel:leave() + end +end) + +print("[minimal][modchannels] Code loaded!") diff --git a/minetest.conf.example b/minetest.conf.example index a0284f1826a4..6eca94401c4e 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -1126,6 +1126,10 @@ # type: bool # enable_pvp = true +# Enable mod channels. +# type: bool +# enable_mod_channels = false + # If this is set, players will always (re)spawn at the given position. # type: string # static_spawnpoint = diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cc3d651090bc..c7eb5fb593ed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -420,6 +420,7 @@ set(common_SRCS mg_decoration.cpp mg_ore.cpp mg_schematic.cpp + modchannels.cpp mods.cpp nameidmapping.cpp nodedef.cpp diff --git a/src/client.cpp b/src/client.cpp index a56e3c97458b..4e6dc102c982 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock_mesh.h" #include "mapblock.h" #include "minimap.h" +#include "modchannels.h" #include "mods.h" #include "profiler.h" #include "shader.h" @@ -94,7 +95,8 @@ Client::Client( m_chosen_auth_mech(AUTH_MECHANISM_NONE), m_media_downloader(new ClientMediaDownloader()), m_state(LC_Created), - m_game_ui_flags(game_ui_flags) + m_game_ui_flags(game_ui_flags), + m_modchannel_mgr(new ModChannelMgr()) { // Add local player m_env.setLocalPlayer(new LocalPlayer(this, playername)); @@ -1919,3 +1921,57 @@ std::string Client::getModStoragePath() const { return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage"; } + +/* + * Mod channels + */ + +bool Client::joinModChannel(const std::string &channel) +{ + if (m_modchannel_mgr->channelRegistered(channel)) + return false; + + NetworkPacket pkt(TOSERVER_MODCHANNEL_JOIN, 2 + channel.size()); + pkt << channel; + Send(&pkt); + + m_modchannel_mgr->joinChannel(channel, 0); + return true; +} + +bool Client::leaveModChannel(const std::string &channel) +{ + if (!m_modchannel_mgr->channelRegistered(channel)) + return false; + + NetworkPacket pkt(TOSERVER_MODCHANNEL_LEAVE, 2 + channel.size()); + pkt << channel; + Send(&pkt); + + m_modchannel_mgr->leaveChannel(channel, 0); + return true; +} + +bool Client::sendModChannelMessage(const std::string &channel, const std::string &message) +{ + if (!m_modchannel_mgr->canWriteOnChannel(channel)) + return false; + + if (message.size() > STRING_MAX_LEN) { + warningstream << "ModChannel message too long, dropping before sending " + << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: " + << channel << ")" << std::endl; + return false; + } + + // @TODO: do some client rate limiting + NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size()); + pkt << channel << message; + Send(&pkt); + return true; +} + +ModChannel* Client::getModChannel(const std::string &channel) +{ + return m_modchannel_mgr->getModChannel(channel); +} diff --git a/src/client.h b/src/client.h index 420d188951c6..36f5640867b2 100644 --- a/src/client.h +++ b/src/client.h @@ -52,6 +52,7 @@ class IWritableNodeDefManager; //class IWritableCraftDefManager; class ClientMediaDownloader; struct MapDrawControl; +class ModChannelMgr; class MtEventManager; struct PointedThing; class MapDatabase; @@ -224,6 +225,8 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); void handleCommand_EyeOffset(NetworkPacket* pkt); void handleCommand_UpdatePlayerList(NetworkPacket* pkt); + void handleCommand_ModChannelMsg(NetworkPacket *pkt); + void handleCommand_ModChannelSignal(NetworkPacket *pkt); void handleCommand_SrpBytesSandB(NetworkPacket* pkt); void handleCommand_CSMFlavourLimits(NetworkPacket *pkt); @@ -424,6 +427,11 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef return m_csm_noderange_limit; } + bool joinModChannel(const std::string &channel); + bool leaveModChannel(const std::string &channel); + bool sendModChannelMessage(const std::string &channel, const std::string &message); + ModChannel *getModChannel(const std::string &channel); + private: // Virtual methods from con::PeerHandler @@ -580,4 +588,6 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef // CSM flavour limits byteflag u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE; u32 m_csm_noderange_limit = 8; + + std::unique_ptr m_modchannel_mgr; }; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 59a13883416c..5bba3bc80af0 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -300,6 +300,7 @@ void set_default_settings(Settings *settings) settings->setDefault("default_password", ""); settings->setDefault("default_privs", "interact, shout"); settings->setDefault("enable_pvp", "true"); + settings->setDefault("enable_mod_channels", "false"); settings->setDefault("disallow_empty_password", "false"); settings->setDefault("disable_anticheat", "false"); settings->setDefault("enable_rollback_recording", "false"); diff --git a/src/gamedef.h b/src/gamedef.h index e7fe6c4abb7e..8117319bc0d2 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -33,6 +33,7 @@ class MtEventManager; class IRollbackManager; class EmergeManager; class Camera; +class ModChannel; class ModMetadata; namespace irr { namespace scene { @@ -78,4 +79,10 @@ class IGameDef virtual std::string getModStoragePath() const = 0; virtual bool registerModStorage(ModMetadata *storage) = 0; virtual void unregisterModStorage(const std::string &name) = 0; + + virtual bool joinModChannel(const std::string &channel) = 0; + virtual bool leaveModChannel(const std::string &channel) = 0; + virtual bool sendModChannelMessage(const std::string &channel, + const std::string &message) = 0; + virtual ModChannel *getModChannel(const std::string &channel) = 0; }; diff --git a/src/modchannels.cpp b/src/modchannels.cpp new file mode 100644 index 000000000000..a6babceca724 --- /dev/null +++ b/src/modchannels.cpp @@ -0,0 +1,152 @@ +/* +Minetest +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "modchannels.h" +#include +#include +#include "util/basic_macros.h" + +bool ModChannel::registerConsumer(u16 peer_id) +{ + + // ignore if peer_id already joined + if (CONTAINS(m_client_consumers, peer_id)) + return false; + + m_client_consumers.push_back(peer_id); + return true; +} + +bool ModChannel::removeConsumer(u16 peer_id) +{ + bool found = false; + auto peer_removal_fct = [peer_id, &found](u16 p) { + if (p == peer_id) + found = true; + + return p == peer_id; + }; + + m_client_consumers.erase( + std::remove_if(m_client_consumers.begin(), + m_client_consumers.end(), peer_removal_fct), + m_client_consumers.end()); + + return found; +} + +bool ModChannel::canWrite() const +{ + return m_state == MODCHANNEL_STATE_READ_WRITE; +} + +void ModChannel::setState(ModChannelState state) +{ + assert(state != MODCHANNEL_STATE_INIT); + + m_state = state; +} + +bool ModChannelMgr::channelRegistered(const std::string &channel) const +{ + return m_registered_channels.find(channel) != m_registered_channels.end(); +} + +ModChannel *ModChannelMgr::getModChannel(const std::string &channel) +{ + if (!channelRegistered(channel)) + return nullptr; + + return m_registered_channels[channel].get(); +} + +bool ModChannelMgr::canWriteOnChannel(const std::string &channel) const +{ + const auto channel_it = m_registered_channels.find(channel); + if (channel_it == m_registered_channels.end()) { + return false; + } + + return channel_it->second->canWrite(); +} + +void ModChannelMgr::registerChannel(const std::string &channel) +{ + m_registered_channels[channel] = + std::unique_ptr(new ModChannel(channel)); +} + +bool ModChannelMgr::setChannelState(const std::string &channel, ModChannelState state) +{ + if (!channelRegistered(channel)) + return false; + + auto channel_it = m_registered_channels.find(channel); + channel_it->second->setState(state); + + return true; +} + +bool ModChannelMgr::removeChannel(const std::string &channel) +{ + if (!channelRegistered(channel)) + return false; + + m_registered_channels.erase(channel); + return true; +} + +bool ModChannelMgr::joinChannel(const std::string &channel, u16 peer_id) +{ + if (!channelRegistered(channel)) + registerChannel(channel); + + return m_registered_channels[channel]->registerConsumer(peer_id); +} + +bool ModChannelMgr::leaveChannel(const std::string &channel, u16 peer_id) +{ + if (!channelRegistered(channel)) + return false; + + // Remove consumer from channel + bool consumerRemoved = m_registered_channels[channel]->removeConsumer(peer_id); + + // If channel is empty, remove it + if (m_registered_channels[channel]->getChannelPeers().empty()) { + removeChannel(channel); + } + return consumerRemoved; +} + +void ModChannelMgr::leaveAllChannels(u16 peer_id) +{ + for (auto &channel_it : m_registered_channels) + channel_it.second->removeConsumer(peer_id); +} + +static std::vector empty_channel_list; +const std::vector &ModChannelMgr::getChannelPeers(const std::string &channel) const +{ + const auto &channel_it = m_registered_channels.find(channel); + if (channel_it == m_registered_channels.end()) + return empty_channel_list; + + return channel_it->second->getChannelPeers(); +} diff --git a/src/modchannels.h b/src/modchannels.h new file mode 100644 index 000000000000..5f1aa884e011 --- /dev/null +++ b/src/modchannels.h @@ -0,0 +1,92 @@ +/* +Minetest +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include +#include +#include +#include "irrlichttypes.h" + +enum ModChannelState : u8 +{ + MODCHANNEL_STATE_INIT, + MODCHANNEL_STATE_READ_WRITE, + MODCHANNEL_STATE_READ_ONLY, + MODCHANNEL_STATE_MAX, +}; + +class ModChannel +{ +public: + ModChannel(const std::string &name) : m_name(name) {} + ~ModChannel() = default; + + const std::string &getName() const { return m_name; } + bool registerConsumer(u16 peer_id); + bool removeConsumer(u16 peer_id); + const std::vector &getChannelPeers() const { return m_client_consumers; } + bool canWrite() const; + void setState(ModChannelState state); + +private: + std::string m_name; + ModChannelState m_state = MODCHANNEL_STATE_INIT; + std::vector m_client_consumers; +}; + +enum ModChannelSignal : u8 +{ + MODCHANNEL_SIGNAL_JOIN_OK, + MODCHANNEL_SIGNAL_JOIN_FAILURE, + MODCHANNEL_SIGNAL_LEAVE_OK, + MODCHANNEL_SIGNAL_LEAVE_FAILURE, + MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED, + MODCHANNEL_SIGNAL_SET_STATE, +}; + +class ModChannelMgr +{ +public: + ModChannelMgr() = default; + ~ModChannelMgr() = default; + + void registerChannel(const std::string &channel); + bool setChannelState(const std::string &channel, ModChannelState state); + bool joinChannel(const std::string &channel, u16 peer_id); + bool leaveChannel(const std::string &channel, u16 peer_id); + bool channelRegistered(const std::string &channel) const; + ModChannel *getModChannel(const std::string &channel); + /** + * This function check if a local mod can write on the channel + * + * @param channel + * @return true if write is allowed + */ + bool canWriteOnChannel(const std::string &channel) const; + void leaveAllChannels(u16 peer_id); + const std::vector &getChannelPeers(const std::string &channel) const; + +private: + bool removeChannel(const std::string &channel); + + std::unordered_map> + m_registered_channels; +}; diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index fa63fba65101..bc5bd37dddfd 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -111,8 +111,8 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CloudParams }, // 0x54 { "TOCLIENT_FADE_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FadeSound }, // 0x55 { "TOCLIENT_UPDATE_PLAYER_LIST", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_UpdatePlayerList }, // 0x56 - null_command_handler, - null_command_handler, + { "TOCLIENT_MODCHANNEL_MSG", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ModChannelMsg }, // 0x57 + { "TOCLIENT_MODCHANNEL_SIGNAL", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ModChannelSignal }, // 0x58 null_command_handler, null_command_handler, null_command_handler, @@ -150,9 +150,9 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] = null_command_factory, // 0x14 null_command_factory, // 0x15 null_command_factory, // 0x16 - null_command_factory, // 0x17 - null_command_factory, // 0x18 - null_command_factory, // 0x19 + { "TOSERVER_MODCHANNEL_JOIN", 0, true }, // 0x17 + { "TOSERVER_MODCHANNEL_LEAVE", 0, true }, // 0x18 + { "TOSERVER_MODCHANNEL_MSG", 0, true }, // 0x19 null_command_factory, // 0x1a null_command_factory, // 0x1b null_command_factory, // 0x1c diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 3ff23453d9cd..b5e2203c83e5 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "mapsector.h" #include "minimap.h" +#include "modchannels.h" #include "nodedef.h" #include "serialization.h" #include "server.h" @@ -1330,3 +1331,95 @@ void Client::handleCommand_CSMFlavourLimits(NetworkPacket *pkt) { *pkt >> m_csm_flavour_limits >> m_csm_noderange_limit; } + +/* + * Mod channels + */ + +void Client::handleCommand_ModChannelMsg(NetworkPacket *pkt) +{ + std::string channel_name, sender, channel_msg; + *pkt >> channel_name >> sender >> channel_msg; + + verbosestream << "Mod channel message received from server " << pkt->getPeerId() + << " on channel " << channel_name << ". sender: `" << sender << "`, message: " + << channel_msg << std::endl; + + if (!m_modchannel_mgr->channelRegistered(channel_name)) { + verbosestream << "Server sent us messages on unregistered channel " + << channel_name << ", ignoring." << std::endl; + return; + } + + m_script->on_modchannel_message(channel_name, sender, channel_msg); +} + +void Client::handleCommand_ModChannelSignal(NetworkPacket *pkt) +{ + u8 signal_tmp; + ModChannelSignal signal; + std::string channel; + + *pkt >> signal_tmp >> channel; + + signal = (ModChannelSignal)signal_tmp; + + bool valid_signal = true; + // @TODO: send Signal to Lua API + switch (signal) { + case MODCHANNEL_SIGNAL_JOIN_OK: + m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE); + infostream << "Server ack our mod channel join on channel `" << channel + << "`, joining." << std::endl; + break; + case MODCHANNEL_SIGNAL_JOIN_FAILURE: + // Unable to join, remove channel + m_modchannel_mgr->leaveChannel(channel, 0); + infostream << "Server refused our mod channel join on channel `" << channel + << "`" << std::endl; + break; + case MODCHANNEL_SIGNAL_LEAVE_OK: +#ifndef NDEBUG + infostream << "Server ack our mod channel leave on channel " << channel + << "`, leaving." << std::endl; +#endif + break; + case MODCHANNEL_SIGNAL_LEAVE_FAILURE: + infostream << "Server refused our mod channel leave on channel `" << channel + << "`" << std::endl; + break; + case MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED: +#ifndef NDEBUG + // Generally unused, but ensure we don't do an implementation error + infostream << "Server tells us we sent a message on channel `" << channel + << "` but we are not registered. Message was dropped." << std::endl; +#endif + break; + case MODCHANNEL_SIGNAL_SET_STATE: { + u8 state; + *pkt >> state; + + if (state == MODCHANNEL_STATE_INIT || state >= MODCHANNEL_STATE_MAX) { + infostream << "Received wrong channel state " << state + << ", ignoring." << std::endl; + return; + } + + m_modchannel_mgr->setChannelState(channel, (ModChannelState) state); + infostream << "Server sets mod channel `" << channel + << "` in read-only mode." << std::endl; + break; + } + default: +#ifndef NDEBUG + warningstream << "Received unhandled mod channel signal ID " + << signal << ", ignoring." << std::endl; +#endif + valid_signal = false; + break; + } + + // If signal is valid, forward it to client side mods + if (valid_signal) + m_script->on_modchannel_signal(channel, signal); +} diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index f4258e9cdfff..28132396bebb 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -180,6 +180,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Backwards compatibility drop Add 'can_zoom' to player object properties Add glow to object properties + Mod channels */ #define LATEST_PROTOCOL_VERSION 36 @@ -611,6 +612,22 @@ enum ToClientCommand u8[len] player name */ + TOCLIENT_MODCHANNEL_MSG = 0x57, + /* + u16 channel name length + std::string channel name + u16 channel name sender + std::string channel name + u16 message length + std::string message + */ + TOCLIENT_MODCHANNEL_SIGNAL = 0x58, + /* + u8 signal id + u16 channel name length + std::string channel name + */ + TOCLIENT_SRP_BYTES_S_B = 0x60, /* Belonging to AUTH_MECHANISM_SRP. @@ -645,6 +662,26 @@ enum ToServerCommand [0] u16 TOSERVER_INIT2 */ + TOSERVER_MODCHANNEL_JOIN = 0x17, + /* + u16 channel name length + std::string channel name + */ + + TOSERVER_MODCHANNEL_LEAVE = 0x18, + /* + u16 channel name length + std::string channel name + */ + + TOSERVER_MODCHANNEL_MSG = 0x19, + /* + u16 channel name length + std::string channel name + u16 message length + std::string message + */ + TOSERVER_GETBLOCK = 0x20, // Obsolete TOSERVER_ADDNODE = 0x21, // Obsolete TOSERVER_REMOVENODE = 0x22, // Obsolete diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 8c6d159cd8b1..6dcf9c93a21d 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -47,9 +47,9 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = null_command_handler, // 0x14 null_command_handler, // 0x15 null_command_handler, // 0x16 - null_command_handler, // 0x17 - null_command_handler, // 0x18 - null_command_handler, // 0x19 + { "TOSERVER_MODCHANNEL_JOIN", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelJoin }, // 0x17 + { "TOSERVER_MODCHANNEL_LEAVE", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelLeave }, // 0x18 + { "TOSERVER_MODCHANNEL_MSG", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelMsg }, // 0x19 null_command_handler, // 0x1a null_command_handler, // 0x1b null_command_handler, // 0x1c @@ -200,8 +200,8 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_CLOUD_PARAMS", 0, true }, // 0x54 { "TOCLIENT_FADE_SOUND", 0, true }, // 0x55 { "TOCLIENT_UPDATE_PLAYER_LIST", 0, true }, // 0x56 - null_command_factory, - null_command_factory, + { "TOCLIENT_MODCHANNEL_MSG", 0, true}, // 0x57 + { "TOCLIENT_MODCHANNEL_SIGNAL", 0, true}, // 0x58 null_command_factory, null_command_factory, null_command_factory, diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index c4c0c9d0d8dd..7d2f6009f996 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_sao.h" #include "emerge.h" #include "mapblock.h" +#include "modchannels.h" #include "nodedef.h" #include "remoteplayer.h" #include "rollback_interface.h" @@ -363,7 +364,7 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) actionstream << "TOSERVER_CLIENT_READY stage 2 client init failed for peer_id: " << peer_id << std::endl; - m_con->DisconnectPeer(peer_id); + DisconnectPeer(peer_id); return; } @@ -372,7 +373,7 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) errorstream << "TOSERVER_CLIENT_READY client sent inconsistent data, disconnecting peer_id: " << peer_id << std::endl; - m_con->DisconnectPeer(peer_id); + DisconnectPeer(peer_id); return; } @@ -503,7 +504,7 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -512,7 +513,7 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player object for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -564,7 +565,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -573,7 +574,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player object for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -745,7 +746,7 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -773,7 +774,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -782,7 +783,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player object for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -839,7 +840,7 @@ void Server::handleCommand_Password(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -892,7 +893,7 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -901,7 +902,7 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player object for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -919,7 +920,7 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -972,7 +973,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -981,7 +982,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player object for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -1411,7 +1412,7 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -1420,7 +1421,7 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player object for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -1462,7 +1463,7 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -1471,7 +1472,7 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt) errorstream << "Server::ProcessData(): Canceling: " "No player object for peer_id=" << pkt->getPeerId() << " disconnecting peer!" << std::endl; - m_con->DisconnectPeer(pkt->getPeerId()); + DisconnectPeer(pkt->getPeerId()); return; } @@ -1733,3 +1734,81 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) acceptAuth(pkt->getPeerId(), wantSudo); } + +/* + * Mod channels + */ + +void Server::handleCommand_ModChannelJoin(NetworkPacket *pkt) +{ + std::string channel_name; + *pkt >> channel_name; + + NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, 1 + 2 + channel_name.size(), + pkt->getPeerId()); + + // Send signal to client to notify join succeed or not + if (g_settings->getBool("enable_mod_channels") && + m_modchannel_mgr->joinChannel(channel_name, pkt->getPeerId())) { + resp_pkt << (u8) MODCHANNEL_SIGNAL_JOIN_OK; + infostream << "Peer " << pkt->getPeerId() << " joined channel " << channel_name + << std::endl; + } + else { + resp_pkt << (u8)MODCHANNEL_SIGNAL_JOIN_FAILURE; + infostream << "Peer " << pkt->getPeerId() << " tried to join channel " + << channel_name << ", but was already registered." << std::endl; + } + resp_pkt << channel_name; + Send(&resp_pkt); +} + +void Server::handleCommand_ModChannelLeave(NetworkPacket *pkt) +{ + std::string channel_name; + *pkt >> channel_name; + + NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, 1 + 2 + channel_name.size(), + pkt->getPeerId()); + + // Send signal to client to notify join succeed or not + if (g_settings->getBool("enable_mod_channels") && + m_modchannel_mgr->leaveChannel(channel_name, pkt->getPeerId())) { + resp_pkt << (u8)MODCHANNEL_SIGNAL_LEAVE_OK; + infostream << "Peer " << pkt->getPeerId() << " left channel " << channel_name + << std::endl; + } else { + resp_pkt << (u8) MODCHANNEL_SIGNAL_LEAVE_FAILURE; + infostream << "Peer " << pkt->getPeerId() << " left channel " << channel_name + << ", but was not registered." << std::endl; + } + resp_pkt << channel_name; + Send(&resp_pkt); +} + +void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt) +{ + std::string channel_name, channel_msg; + *pkt >> channel_name >> channel_msg; + + verbosestream << "Mod channel message received from peer " << pkt->getPeerId() + << " on channel " << channel_name << " message: " << channel_msg << std::endl; + + // If mod channels are not enabled, discard message + if (!g_settings->getBool("enable_mod_channels")) { + return; + } + + // If channel not registered, signal it and ignore message + if (!m_modchannel_mgr->channelRegistered(channel_name)) { + NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, 1 + 2 + channel_name.size(), + pkt->getPeerId()); + resp_pkt << (u8)MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED << channel_name; + Send(&resp_pkt); + return; + } + + // @TODO: filter, rate limit + + broadcastModChannelMessage(channel_name, channel_msg, pkt->getPeerId()); +} diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt index 4b13356a89a0..3cfd7709a701 100644 --- a/src/script/cpp_api/CMakeLists.txt +++ b/src/script/cpp_api/CMakeLists.txt @@ -5,6 +5,7 @@ set(common_SCRIPT_CPP_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/s_env.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_item.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/s_modchannels.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_player.cpp diff --git a/src/script/cpp_api/s_modchannels.cpp b/src/script/cpp_api/s_modchannels.cpp new file mode 100644 index 000000000000..caff3f05e57e --- /dev/null +++ b/src/script/cpp_api/s_modchannels.cpp @@ -0,0 +1,50 @@ +/* +Minetest +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "s_modchannels.h" +#include "s_internal.h" + +void ScriptApiModChannels::on_modchannel_message(const std::string &channel, + const std::string &sender, const std::string &message) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_generateds + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_modchannel_message"); + // Call callbacks + lua_pushstring(L, channel.c_str()); + lua_pushstring(L, sender.c_str()); + lua_pushstring(L, message.c_str()); + runCallbacks(3, RUN_CALLBACKS_MODE_AND); +} + +void ScriptApiModChannels::on_modchannel_signal( + const std::string &channel, ModChannelSignal signal) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_generateds + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_modchannel_signal"); + // Call callbacks + lua_pushstring(L, channel.c_str()); + lua_pushinteger(L, (int)signal); + runCallbacks(2, RUN_CALLBACKS_MODE_AND); +} diff --git a/src/script/cpp_api/s_modchannels.h b/src/script/cpp_api/s_modchannels.h new file mode 100644 index 000000000000..4de7a8291d0c --- /dev/null +++ b/src/script/cpp_api/s_modchannels.h @@ -0,0 +1,31 @@ +/* +Minetest +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "cpp_api/s_base.h" +#include "modchannels.h" + +class ScriptApiModChannels : virtual public ScriptApiBase +{ +public: + void on_modchannel_message(const std::string &channel, const std::string &sender, + const std::string &message); + void on_modchannel_signal(const std::string &channel, ModChannelSignal signal); +}; diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 1a78580e6007..55bbf934b3d1 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -8,6 +8,7 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_itemstackmeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mapgen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_metadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_modchannels.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_nodemeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_nodetimer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_noise.cpp diff --git a/src/script/lua_api/l_modchannels.cpp b/src/script/lua_api/l_modchannels.cpp new file mode 100644 index 000000000000..ac28e2ada013 --- /dev/null +++ b/src/script/lua_api/l_modchannels.cpp @@ -0,0 +1,161 @@ +/* +Minetest +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include "lua_api/l_modchannels.h" +#include "l_internal.h" +#include "modchannels.h" + +int ModApiChannels::l_mod_channel_join(lua_State *L) +{ + if (!lua_isstring(L, 1)) + return 0; + + std::string channel = luaL_checkstring(L, 1); + if (channel.empty()) + return 0; + + getGameDef(L)->joinModChannel(channel); + ModChannel *channelObj = getGameDef(L)->getModChannel(channel); + assert(channelObj); + ModChannelRef::create(L, channelObj); + + int object = lua_gettop(L); + lua_pushvalue(L, object); + return 1; +} + +void ModApiChannels::Initialize(lua_State *L, int top) +{ + API_FCT(mod_channel_join); +} + +/* + * ModChannelRef + */ + +ModChannelRef::ModChannelRef(ModChannel *modchannel) : m_modchannel(modchannel) +{ +} + +int ModChannelRef::l_leave(lua_State *L) +{ + ModChannelRef *ref = checkobject(L, 1); + ModChannel *channel = getobject(ref); + if (!channel) + return 0; + + getGameDef(L)->leaveModChannel(channel->getName()); + // Channel left, invalidate the channel object ptr + // This permits to invalidate every object action from Lua because core removed + // channel consuming link + ref->m_modchannel = nullptr; + return 0; +} + +int ModChannelRef::l_send_all(lua_State *L) +{ + ModChannelRef *ref = checkobject(L, 1); + ModChannel *channel = getobject(ref); + if (!channel || !channel->canWrite()) + return 0; + + // @TODO serialize message + std::string message = luaL_checkstring(L, 2); + + getGameDef(L)->sendModChannelMessage(channel->getName(), message); + return 0; +} + +int ModChannelRef::l_is_writeable(lua_State *L) +{ + ModChannelRef *ref = checkobject(L, 1); + ModChannel *channel = getobject(ref); + if (!channel) + return 0; + + lua_pushboolean(L, channel->canWrite()); + return 1; +} +void ModChannelRef::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); // hide metatable from lua getmetatable() + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); // Drop metatable + + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // Drop methodtable +} + +void ModChannelRef::create(lua_State *L, ModChannel *channel) +{ + ModChannelRef *o = new ModChannelRef(channel); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); +} + +int ModChannelRef::gc_object(lua_State *L) +{ + ModChannelRef *o = *(ModChannelRef **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + +ModChannelRef *ModChannelRef::checkobject(lua_State *L, int narg) +{ + luaL_checktype(L, narg, LUA_TUSERDATA); + + void *ud = luaL_checkudata(L, narg, className); + if (!ud) + luaL_typerror(L, narg, className); + + return *(ModChannelRef **)ud; // unbox pointer +} + +ModChannel *ModChannelRef::getobject(ModChannelRef *ref) +{ + return ref->m_modchannel; +} + +// clang-format off +const char ModChannelRef::className[] = "ModChannelRef"; +const luaL_Reg ModChannelRef::methods[] = { + luamethod(ModChannelRef, leave), + luamethod(ModChannelRef, is_writeable), + luamethod(ModChannelRef, send_all), + {0, 0}, +}; +// clang-format on diff --git a/src/script/lua_api/l_modchannels.h b/src/script/lua_api/l_modchannels.h new file mode 100644 index 000000000000..dbbf11a056e3 --- /dev/null +++ b/src/script/lua_api/l_modchannels.h @@ -0,0 +1,66 @@ +/* +Minetest +Copyright (C) 2017 nerzhul, Loic Blot + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "lua_api/l_base.h" +#include "config.h" + +class ModChannel; + +class ModApiChannels : public ModApiBase +{ +private: + // mod_channel_join(name) + static int l_mod_channel_join(lua_State *L); + +public: + static void Initialize(lua_State *L, int top); +}; + +class ModChannelRef : public ModApiBase +{ +public: + ModChannelRef(ModChannel *modchannel); + ~ModChannelRef() = default; + + static void Register(lua_State *L); + static void create(lua_State *L, ModChannel *channel); + + // leave() + static int l_leave(lua_State *L); + + // send(message) + static int l_send_all(lua_State *L); + + // is_writeable() + static int l_is_writeable(lua_State *L); + +private: + // garbage collector + static int gc_object(lua_State *L); + + static ModChannelRef *checkobject(lua_State *L, int narg); + static ModChannel *getobject(ModChannelRef *ref); + + ModChannel *m_modchannel = nullptr; + + static const char className[]; + static const luaL_Reg methods[]; +}; diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index b121f3712252..29836c47b516 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_client.h" #include "lua_api/l_env.h" #include "lua_api/l_minimap.h" +#include "lua_api/l_modchannels.h" #include "lua_api/l_storage.h" #include "lua_api/l_sound.h" #include "lua_api/l_util.h" @@ -72,11 +73,13 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) NodeMetaRef::RegisterClient(L); LuaLocalPlayer::Register(L); LuaCamera::Register(L); + ModChannelRef::Register(L); ModApiUtil::InitializeClient(L, top); ModApiClient::Initialize(L, top); ModApiStorage::Initialize(L, top); ModApiEnvMod::InitializeClient(L, top); + ModApiChannels::Initialize(L, top); } void ClientScripting::on_client_ready(LocalPlayer *localplayer) diff --git a/src/script/scripting_client.h b/src/script/scripting_client.h index 721bb2b058d7..cfecfa165961 100644 --- a/src/script/scripting_client.h +++ b/src/script/scripting_client.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "cpp_api/s_client.h" +#include "cpp_api/s_modchannels.h" #include "cpp_api/s_security.h" class Client; @@ -30,7 +31,8 @@ class Camera; class ClientScripting: virtual public ScriptApiBase, public ScriptApiSecurity, - public ScriptApiClient + public ScriptApiClient, + public ScriptApiModChannels { public: ClientScripting(Client *client); diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 01e8e2fb501a..095216a74cb4 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_item.h" #include "lua_api/l_itemstackmeta.h" #include "lua_api/l_mapgen.h" +#include "lua_api/l_modchannels.h" #include "lua_api/l_nodemeta.h" #include "lua_api/l_nodetimer.h" #include "lua_api/l_noise.h" @@ -100,6 +101,7 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ObjectRef::Register(L); LuaSettings::Register(L); StorageRef::Register(L); + ModChannelRef::Register(L); // Initialize mod api modules ModApiCraft::Initialize(L, top); @@ -113,6 +115,7 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ModApiUtil::Initialize(L, top); ModApiHttp::Initialize(L, top); ModApiStorage::Initialize(L, top); + ModApiChannels::Initialize(L, top); } void log_deprecated(const std::string &message) diff --git a/src/script/scripting_server.h b/src/script/scripting_server.h index b549a1bc95e7..88cea143ca22 100644 --- a/src/script/scripting_server.h +++ b/src/script/scripting_server.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_entity.h" #include "cpp_api/s_env.h" #include "cpp_api/s_inventory.h" +#include "cpp_api/s_modchannels.h" #include "cpp_api/s_node.h" #include "cpp_api/s_player.h" #include "cpp_api/s_server.h" @@ -36,6 +37,7 @@ class ServerScripting: public ScriptApiDetached, public ScriptApiEntity, public ScriptApiEnv, + public ScriptApiModChannels, public ScriptApiNode, public ScriptApiPlayer, public ScriptApiServer, diff --git a/src/server.cpp b/src/server.cpp index 2fdd76c22541..0f5c0677380d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_sao.h" #include "mods.h" #include "event_manager.h" +#include "modchannels.h" #include "serverlist.h" #include "util/string.h" #include "rollback.h" @@ -168,7 +169,8 @@ Server::Server( m_event(new EventManager()), m_uptime(0), m_clients(m_con), - m_admin_chat(iface) + m_admin_chat(iface), + m_modchannel_mgr(new ModChannelMgr()) { m_lag = g_settings->getFloat("dedicated_server_step"); @@ -1374,9 +1376,14 @@ void Server::printToConsoleOnly(const std::string &text) } } -void Server::Send(NetworkPacket* pkt) +void Server::Send(NetworkPacket *pkt) { - m_clients.send(pkt->getPeerId(), + Send(pkt->getPeerId(), pkt); +} + +void Server::Send(u16 peer_id, NetworkPacket *pkt) +{ + m_clients.send(peer_id, clientCommandFactoryTable[pkt->getCommand()].channel, pkt, clientCommandFactoryTable[pkt->getCommand()].reliable); @@ -2567,7 +2574,7 @@ void Server::DenyAccessVerCompliant(u16 peer_id, u16 proto_ver, AccessDeniedCode SendAccessDenied(peer_id, reason, str_reason, reconnect); m_clients.event(peer_id, CSE_SetDenied); - m_con->DisconnectPeer(peer_id); + DisconnectPeer(peer_id); } @@ -2575,7 +2582,7 @@ void Server::DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string { SendAccessDenied(peer_id, reason, custom_reason); m_clients.event(peer_id, CSE_SetDenied); - m_con->DisconnectPeer(peer_id); + DisconnectPeer(peer_id); } // 13/03/15: remove this function when protocol version 25 will become @@ -2584,6 +2591,12 @@ void Server::DenyAccess_Legacy(u16 peer_id, const std::wstring &reason) { SendAccessDenied_Legacy(peer_id, reason); m_clients.event(peer_id, CSE_SetDenied); + DisconnectPeer(peer_id); +} + +void Server::DisconnectPeer(u16 peer_id) +{ + m_modchannel_mgr->leaveAllChannels(peer_id); m_con->DisconnectPeer(peer_id); } @@ -3570,3 +3583,68 @@ void dedicated_server_loop(Server &server, bool &kill) server.m_bind_addr.getPort()); #endif } + +/* + * Mod channels + */ + + +bool Server::joinModChannel(const std::string &channel) +{ + return m_modchannel_mgr->joinChannel(channel, PEER_ID_SERVER) && + m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE); +} + +bool Server::leaveModChannel(const std::string &channel) +{ + return m_modchannel_mgr->leaveChannel(channel, PEER_ID_SERVER); +} + +bool Server::sendModChannelMessage(const std::string &channel, const std::string &message) +{ + if (!m_modchannel_mgr->canWriteOnChannel(channel)) + return false; + + broadcastModChannelMessage(channel, message, PEER_ID_SERVER); + return true; +} + +ModChannel* Server::getModChannel(const std::string &channel) +{ + return m_modchannel_mgr->getModChannel(channel); +} + +void Server::broadcastModChannelMessage(const std::string &channel, + const std::string &message, u16 from_peer) +{ + const std::vector &peers = m_modchannel_mgr->getChannelPeers(channel); + if (peers.empty()) + return; + + if (message.size() > STRING_MAX_LEN) { + warningstream << "ModChannel message too long, dropping before sending " + << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: " + << channel << ")" << std::endl; + return; + } + + std::string sender; + if (from_peer != PEER_ID_SERVER) { + sender = getPlayerName(from_peer); + } + + NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_MSG, + 2 + channel.size() + 2 + sender.size() + 2 + message.size()); + resp_pkt << channel << sender << message; + for (u16 peer_id : peers) { + // Ignore sender + if (peer_id == from_peer) + continue; + + Send(peer_id, &resp_pkt); + } + + if (from_peer != PEER_ID_SERVER) { + m_script->on_modchannel_message(channel, sender, message); + } +} diff --git a/src/server.h b/src/server.h index 551bd27878ae..097be32c430d 100644 --- a/src/server.h +++ b/src/server.h @@ -50,6 +50,7 @@ class IWritableCraftDefManager; class BanManager; class EventManager; class Inventory; +class ModChannelMgr; class RemotePlayer; class PlayerSAO; class IRollbackManager; @@ -144,6 +145,9 @@ class Server : public con::PeerHandler, public MapEventReceiver, void handleCommand_Deprecated(NetworkPacket* pkt); void handleCommand_Init(NetworkPacket* pkt); void handleCommand_Init2(NetworkPacket* pkt); + void handleCommand_ModChannelJoin(NetworkPacket *pkt); + void handleCommand_ModChannelLeave(NetworkPacket *pkt); + void handleCommand_ModChannelMsg(NetworkPacket *pkt); void handleCommand_RequestMedia(NetworkPacket* pkt); void handleCommand_ClientReady(NetworkPacket* pkt); void handleCommand_GotBlocks(NetworkPacket* pkt); @@ -165,7 +169,8 @@ class Server : public con::PeerHandler, public MapEventReceiver, void ProcessData(NetworkPacket *pkt); - void Send(NetworkPacket* pkt); + void Send(NetworkPacket *pkt); + void Send(u16 peer_id, NetworkPacket *pkt); // Helper for handleCommand_PlayerPos and handleCommand_Interact void process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, @@ -319,6 +324,7 @@ class Server : public con::PeerHandler, public MapEventReceiver, void DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason=""); void acceptAuth(u16 peer_id, bool forSudoMode); void DenyAccess_Legacy(u16 peer_id, const std::wstring &reason); + void DisconnectPeer(u16 peer_id); bool getClientConInfo(u16 peer_id, con::rtt_stat_type type, float* retval); bool getClientInfo(u16 peer_id,ClientState* state, u32* uptime, u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch, @@ -334,6 +340,11 @@ class Server : public con::PeerHandler, public MapEventReceiver, virtual bool registerModStorage(ModMetadata *storage); virtual void unregisterModStorage(const std::string &name); + bool joinModChannel(const std::string &channel); + bool leaveModChannel(const std::string &channel); + bool sendModChannelMessage(const std::string &channel, const std::string &message); + ModChannel *getModChannel(const std::string &channel); + // Bind address Address m_bind_addr; @@ -383,6 +394,8 @@ class Server : public con::PeerHandler, public MapEventReceiver, float thickness, const v2f &speed); void SendOverrideDayNightRatio(u16 peer_id, bool do_override, float ratio); + void broadcastModChannelMessage(const std::string &channel, + const std::string &message, u16 from_peer); /* Send a node removal/addition event to all clients except ignore_id. @@ -640,6 +653,9 @@ class Server : public con::PeerHandler, public MapEventReceiver, // CSM flavour limits byteflag u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE; u32 m_csm_noderange_limit = 8; + + // ModChannel manager + std::unique_ptr m_modchannel_mgr; }; /* diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 7ad38099cbee..3a4450fac971 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -8,6 +8,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_nodedef.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 84fad41cef89..1985fdc6c550 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "itemdef.h" #include "gamedef.h" +#include "modchannels.h" #include "mods.h" #include "util/numeric.h" @@ -69,6 +70,13 @@ class TestGameDef : public IGameDef { virtual std::string getModStoragePath() const { return "."; } virtual bool registerModStorage(ModMetadata *meta) { return true; } virtual void unregisterModStorage(const std::string &name) {} + bool joinModChannel(const std::string &channel); + bool leaveModChannel(const std::string &channel); + bool sendModChannelMessage(const std::string &channel, const std::string &message); + ModChannel *getModChannel(const std::string &channel) + { + return m_modchannel_mgr->getModChannel(channel); + } private: IItemDefManager *m_itemdef = nullptr; @@ -81,10 +89,12 @@ class TestGameDef : public IGameDef { scene::ISceneManager *m_scenemgr = nullptr; IRollbackManager *m_rollbackmgr = nullptr; EmergeManager *m_emergemgr = nullptr; + std::unique_ptr m_modchannel_mgr; }; -TestGameDef::TestGameDef() +TestGameDef::TestGameDef() : + m_modchannel_mgr(new ModChannelMgr()) { m_itemdef = createItemDefManager(); m_nodedef = createNodeDefManager(); @@ -222,6 +232,25 @@ void TestGameDef::defineSomeNodes() t_CONTENT_BRICK = ndef->set(f.name, f); } +bool TestGameDef::joinModChannel(const std::string &channel) +{ + return m_modchannel_mgr->joinChannel(channel, PEER_ID_SERVER); +} + +bool TestGameDef::leaveModChannel(const std::string &channel) +{ + return m_modchannel_mgr->leaveChannel(channel, PEER_ID_SERVER); +} + +bool TestGameDef::sendModChannelMessage(const std::string &channel, + const std::string &message) +{ + if (!m_modchannel_mgr->channelRegistered(channel)) + return false; + + return true; +} + //// //// run_tests //// diff --git a/src/unittest/test_modchannels.cpp b/src/unittest/test_modchannels.cpp new file mode 100644 index 000000000000..069f439a1efa --- /dev/null +++ b/src/unittest/test_modchannels.cpp @@ -0,0 +1,76 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "gamedef.h" +#include "modchannels.h" + +class TestModChannels : public TestBase +{ +public: + TestModChannels() { TestManager::registerTestModule(this); } + const char *getName() { return "TestModChannels"; } + + void runTests(IGameDef *gamedef); + + void testJoinChannel(IGameDef *gamedef); + void testLeaveChannel(IGameDef *gamedef); + void testSendMessageToChannel(IGameDef *gamedef); +}; + +static TestModChannels g_test_instance; + +void TestModChannels::runTests(IGameDef *gamedef) +{ + TEST(testJoinChannel, gamedef); + TEST(testLeaveChannel, gamedef); + TEST(testSendMessageToChannel, gamedef); +} + +void TestModChannels::testJoinChannel(IGameDef *gamedef) +{ + // Test join + UASSERT(gamedef->joinModChannel("test_join_channel")); + // Test join (fail, already join) + UASSERT(!gamedef->joinModChannel("test_join_channel")); +} + +void TestModChannels::testLeaveChannel(IGameDef *gamedef) +{ + // Test leave (not joined) + UASSERT(!gamedef->leaveModChannel("test_leave_channel")); + + UASSERT(gamedef->joinModChannel("test_leave_channel")); + + // Test leave (joined) + UASSERT(gamedef->leaveModChannel("test_leave_channel")); +} + +void TestModChannels::testSendMessageToChannel(IGameDef *gamedef) +{ + // Test sendmsg (not joined) + UASSERT(!gamedef->sendModChannelMessage( + "test_sendmsg_channel", "testmsgchannel")); + + UASSERT(gamedef->joinModChannel("test_sendmsg_channel")); + + // Test sendmsg (joined) + UASSERT(gamedef->sendModChannelMessage("test_sendmsg_channel", "testmsgchannel")); +}