Implement mod communication channels (#6351)
authorLoïc Blot <nerzhul@users.noreply.github.com>
Mon, 25 Sep 2017 22:11:20 +0000 (00:11 +0200)
committerGitHub <noreply@github.com>
Mon, 25 Sep 2017 22:11:20 +0000 (00:11 +0200)
Implement network communication for channels

* Implement ModChannel manager server side to route incoming messages from clients to other clients
* Add signal handler switch on client & ModChannelMgr on client to handle channels
* Add Lua API bindings + client packet sending + unittests
* Implement server message sending
* Add callback from received message handler to Lua API using registration method

37 files changed:
builtin/client/register.lua
builtin/game/register.lua
builtin/settingtypes.txt
clientmods/preview/init.lua
doc/client_lua_api.md
doc/lua_api.txt
doc/mod_channels.png [new file with mode: 0644]
games/minimal/mods/experimental/init.lua
games/minimal/mods/experimental/modchannels.lua [new file with mode: 0644]
minetest.conf.example
src/CMakeLists.txt
src/client.cpp
src/client.h
src/defaultsettings.cpp
src/gamedef.h
src/modchannels.cpp [new file with mode: 0644]
src/modchannels.h [new file with mode: 0644]
src/network/clientopcodes.cpp
src/network/clientpackethandler.cpp
src/network/networkprotocol.h
src/network/serveropcodes.cpp
src/network/serverpackethandler.cpp
src/script/cpp_api/CMakeLists.txt
src/script/cpp_api/s_modchannels.cpp [new file with mode: 0644]
src/script/cpp_api/s_modchannels.h [new file with mode: 0644]
src/script/lua_api/CMakeLists.txt
src/script/lua_api/l_modchannels.cpp [new file with mode: 0644]
src/script/lua_api/l_modchannels.h [new file with mode: 0644]
src/script/scripting_client.cpp
src/script/scripting_client.h
src/script/scripting_server.cpp
src/script/scripting_server.h
src/server.cpp
src/server.h
src/unittest/CMakeLists.txt
src/unittest/test.cpp
src/unittest/test_modchannels.cpp [new file with mode: 0644]

index cfc5abaa7bd4296dd067f7d915d5532638e2af12..2b835c744d7ee1d9d8d4566172b8e9c28c962402 100644 (file)
@@ -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()
index 06c9c61d20b3495a4400ad1c52a476f45b08b545..f5d4ea216585e454d3a36ad3eb9fc9833265f191 100644 (file)
@@ -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()
index 4cfd2fe5349a5526ecb84332e59d5d78703c442e..926a881fe5aa766098a7301963a11755a752c8bc 100644 (file)
@@ -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
 
index 150db181605ad2e8864e2b8fb38db5c7bd715b29..bf5fb3db810acfbe031dec11ff8f2d85e5e7b906 100644 (file)
@@ -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()
index 44c7c27874274cb35e6da40ce7faad8374ca2e91..4c48b6619adf3dbc07c5bf97e3f1464ea9ab1206 100644 (file)
@@ -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
index b71eb5477acaa9e2fcb79fabe68d2b8e7acb23d8..812b857f71cba28c135d141cbd4c2f23d7c87aa3 100644 (file)
@@ -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 (file)
index 0000000..08fdfca
Binary files /dev/null and b/doc/mod_channels.png differ
index afa136777a748c62bd7b87fc32f07ed229d72581..657148dce263433e0362ede8211a091e835afdec 100644 (file)
@@ -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 (file)
index 0000000..8fce6cf
--- /dev/null
@@ -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!")
index a0284f1826a4b61221f5083656cdc6deee38a212..6eca94401c4e8f1615d814cc8ccd8485bd25973f 100644 (file)
 #    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 =
index cc3d651090bc53c8bd17b92b39e2e3d5516b2e12..c7eb5fb593ed248b28af502a58663e493f533245 100644 (file)
@@ -420,6 +420,7 @@ set(common_SRCS
        mg_decoration.cpp
        mg_ore.cpp
        mg_schematic.cpp
+       modchannels.cpp
        mods.cpp
        nameidmapping.cpp
        nodedef.cpp
index a56e3c97458b0391c0365b5db3a4bc2b19b80efc..4e6dc102c9824f26e2355da31b2628db477a4324 100644 (file)
@@ -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);
+}
index 420d188951c610cb08838fa7d44f1da3ccf67b0b..36f5640867b27172a975d4d78f7c3b71f018ccd5 100644 (file)
@@ -52,6 +52,7 @@ class IWritableNodeDefManager;
 //class IWritableCraftDefManager;
 class ClientMediaDownloader;
 struct MapDrawControl;
+class ModChannelMgr;
 class MtEventManager;
 struct PointedThing;
 class MapDatabase;
@@ -224,6 +225,8 @@ public:
        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 @@ public:
                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 @@ private:
        // CSM flavour limits byteflag
        u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE;
        u32 m_csm_noderange_limit = 8;
+
+       std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
 };
index 59a13883416ca6b842b52e716f0669bf299458ca..5bba3bc80af07f88979e2c4f0fd77eff62bb517b 100644 (file)
@@ -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");
index e7fe6c4abb7e6084c5f0f281c3d2f9c402f7e9c8..8117319bc0d29c34c4504b1f8270daace4d4dc1c 100644 (file)
@@ -33,6 +33,7 @@ class MtEventManager;
 class IRollbackManager;
 class EmergeManager;
 class Camera;
+class ModChannel;
 class ModMetadata;
 
 namespace irr { namespace scene {
@@ -78,4 +79,10 @@ public:
        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 (file)
index 0000000..a6babce
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+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 <algorithm>
+#include <cassert>
+#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<ModChannel>(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<u16> empty_channel_list;
+const std::vector<u16> &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 (file)
index 0000000..5f1aa88
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+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 <unordered_map>
+#include <string>
+#include <vector>
+#include <memory>
+#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<u16> &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<u16> 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<u16> &getChannelPeers(const std::string &channel) const;
+
+private:
+       bool removeChannel(const std::string &channel);
+
+       std::unordered_map<std::string, std::unique_ptr<ModChannel>>
+                       m_registered_channels;
+};
index fa63fba6510150a4362ae7d38def7dc4b154d6f0..bc5bd37dddfd91f550341e4d4c58eecb65a66be3 100644 (file)
@@ -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
index 3ff23453d9cd89a9c5c312751a390d3f8ca72bd1..b5e2203c83e5edba5a94d45eff0ab0c7d4167078 100644 (file)
@@ -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);
+}
index f4258e9cdfff3c466c775b51afa98774307b9e99..28132396bebb2172c708fc5eef9040ee15203bef 100644 (file)
@@ -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
index 8c6d159cd8b106d2d38c916a5de3dde8b2ea0aaa..6dcf9c93a21dca17baf01455a07470f6faf94af6 100644 (file)
@@ -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,
index c4c0c9d0d8dda933c4681ea0082c7e354521c833..7d2f6009f996cbc4b0f2c2b22f8bb2d5b0c6e974 100644 (file)
@@ -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());
+}
index 4b13356a89a0933df1b73fd3436daf153dea4db1..3cfd7709a7018fe46a26e50fe7cf03ff3fc52f2f 100644 (file)
@@ -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 (file)
index 0000000..caff3f0
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+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 (file)
index 0000000..4de7a82
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+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);
+};
index 1a78580e60077e14c8bf80d5306921732fe1b4cf..55bbf934b3d12f481b968a9e159448c138f45a8d 100644 (file)
@@ -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 (file)
index 0000000..ac28e2a
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+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 <cassert>
+#include <log.h>
+#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 (file)
index 0000000..dbbf11a
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+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[];
+};
index b121f3712252c6efd89cdd529f7e99d02e6fe8a2..29836c47b516a8b0b0fdaa582d0a7d6405affc66 100644 (file)
@@ -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)
index 721bb2b058d7192a8b71bd82e5a8545b3ae7aef2..cfecfa16596190a49feb13aba79d5d6773f1dec7 100644 (file)
@@ -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);
index 01e8e2fb501a6293f343abb43e7879126775de16..095216a74cb4c6ad46eb07e3b05e95bcbe1d9112 100644 (file)
@@ -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)
index b549a1bc95e7c724626537939488a265627883d7..88cea143ca227d1692a1cfffc02fbed55636409e 100644 (file)
@@ -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,
index 2fdd76c22541d65207bcf819c7bde352e23c0d24..0f5c0677380d33a4066cf0d24501030ea9c1b638 100644 (file)
@@ -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(NetworkPacketpkt)
+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<u16> &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);
+       }
+}
index 551bd27878ae9ebb43f1f6f9383caee7eda99826..097be32c430dba854a9232224490fd3b4cec5193 100644 (file)
@@ -50,6 +50,7 @@ class IWritableCraftDefManager;
 class BanManager;
 class EventManager;
 class Inventory;
+class ModChannelMgr;
 class RemotePlayer;
 class PlayerSAO;
 class IRollbackManager;
@@ -144,6 +145,9 @@ public:
        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 @@ public:
 
        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 @@ public:
        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 @@ public:
        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 @@ private:
                        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 @@ private:
        // CSM flavour limits byteflag
        u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE;
        u32 m_csm_noderange_limit = 8;
+
+       // ModChannel manager
+       std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
 };
 
 /*
index 7ad38099cbee92f4f698a70ca1a035549d413185..3a4450fac97175bfef96caec04e640b23e789092 100644 (file)
@@ -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
index 84fad41cef895b1fd43bb3555fd897f59e689162..1985fdc6c550513b77a6506daa2fe856dcf2995a 100644 (file)
@@ -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 @@ public:
        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 @@ private:
        scene::ISceneManager *m_scenemgr = nullptr;
        IRollbackManager *m_rollbackmgr = nullptr;
        EmergeManager *m_emergemgr = nullptr;
+       std::unique_ptr<ModChannelMgr> 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 (file)
index 0000000..069f439
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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"));
+}