Implement mod communication channels (#6351)

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
This commit is contained in:
Loïc Blot 2017-09-26 00:11:20 +02:00 committed by GitHub
parent 6df312a608
commit 6f1c907204
37 changed files with 1206 additions and 39 deletions

@ -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_punchnode, core.register_on_punchnode = make_registration()
core.registered_on_placenode, core.register_on_placenode = 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_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()

@ -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_grant, core.register_on_priv_grant = make_registration()
core.registered_on_priv_revoke, core.register_on_priv_revoke = 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_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() -- Compatibility for on_mapgen_init()

@ -928,6 +928,9 @@ player_transfer_distance (Player transfer distance) int 0
# Whether to allow players to damage and kill each other. # Whether to allow players to damage and kill each other.
enable_pvp (Player versus Player) bool true 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. # If this is set, players will always (re)spawn at the given position.
static_spawnpoint (Static spawnpoint) string static_spawnpoint (Static spawnpoint) string

@ -1,5 +1,6 @@
local modname = core.get_current_modname() or "??" local modname = core.get_current_modname() or "??"
local modstorage = core.get_mod_storage() local modstorage = core.get_mod_storage()
local mod_channel
dofile("preview:example.lua") dofile("preview:example.lua")
-- This is an example function to ensure it's working properly, should be removed before merge -- 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 ip: " .. server_info.ip)
print("Server address: " .. server_info.address) print("Server address: " .. server_info.address)
print("Server port: " .. server_info.port) 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) end)
core.register_on_placenode(function(pointed_thing, node) core.register_on_placenode(function(pointed_thing, node)
@ -100,6 +116,12 @@ core.after(2, function()
preview_minimap() preview_minimap()
end) 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() core.after(5, function()
if core.ui.minimap then if core.ui.minimap then
core.ui.minimap:show() core.ui.minimap:show()

@ -683,6 +683,12 @@ Call these functions only at load time!
* Called when the local player uses an item. * Called when the local player uses an item.
* Newest functions are called first. * Newest functions are called first.
* If any function returns true, the item use is not sent to server. * 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 ### Sounds
* `minetest.sound_play(spec, parameters)`: returns a handle * `minetest.sound_play(spec, parameters)`: returns a handle
* `spec` is a `SimpleSoundSpec` * `spec` is a `SimpleSoundSpec`
@ -754,6 +760,16 @@ Call these functions only at load time!
* returns reference to mod private `StorageRef` * returns reference to mod private `StorageRef`
* must be called during mod load time * 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. ### Misc.
* `minetest.parse_json(string[, nullvalue])`: returns something * `minetest.parse_json(string[, nullvalue])`: returns something
* Convert a string containing JSON data into the Lua equivalent * Convert a string containing JSON data into the Lua equivalent
@ -827,9 +843,25 @@ Call these functions only at load time!
Class reference 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 ### Minimap
An interface to manipulate minimap on client UI An interface to manipulate minimap on client UI
#### Methods
* `show()`: shows the minimap (if not disabled by server) * `show()`: shows the minimap (if not disabled by server)
* `hide()`: hides the minimap * `hide()`: hides the minimap
* `set_pos(pos)`: sets the minimap position on screen * `set_pos(pos)`: sets the minimap position on screen

@ -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` stratum midpoint. The 2D perlin noise described by `np_stratum_thickness`
varies the stratum's vertical thickness (in units of nodes). Due to being varies the stratum's vertical thickness (in units of nodes). Due to being
continuous across mapchunk borders the stratum's vertical thickness is 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 `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 reasons should be set as close together as possible but without clipping the
stratum's Y variation. stratum's Y variation.
@ -2496,6 +2496,20 @@ Call these functions only at load time!
* `minetest.register_can_bypass_userlimit(function(name, ip))` * `minetest.register_can_bypass_userlimit(function(name, ip))`
* Called when `name` user connects with `ip`. * Called when `name` user connects with `ip`.
* Return `true` to by pass the player limit * 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 ### Other registration functions
* `minetest.register_chatcommand(cmd, chatcommand definition)` * `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 * spread these updates to neighbours and can cause a cascade
of nodes to fall. 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 ### Inventory
`minetest.get_inventory(location)`: returns an `InvRef` `minetest.get_inventory(location)`: returns an `InvRef`
@ -3256,6 +3278,21 @@ These functions return the leftover itemstack.
Class reference 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` ### `MetaDataRef`
See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`. See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`.

BIN
doc/mod_channels.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

@ -2,6 +2,8 @@
-- Experimental things -- Experimental things
-- --
dofile(minetest.get_modpath("experimental").."/modchannels.lua")
-- For testing random stuff -- For testing random stuff
experimental = {} experimental = {}

@ -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!")

@ -1126,6 +1126,10 @@
# type: bool # type: bool
# enable_pvp = true # 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. # If this is set, players will always (re)spawn at the given position.
# type: string # type: string
# static_spawnpoint = # static_spawnpoint =

@ -420,6 +420,7 @@ set(common_SRCS
mg_decoration.cpp mg_decoration.cpp
mg_ore.cpp mg_ore.cpp
mg_schematic.cpp mg_schematic.cpp
modchannels.cpp
mods.cpp mods.cpp
nameidmapping.cpp nameidmapping.cpp
nodedef.cpp nodedef.cpp

@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock_mesh.h" #include "mapblock_mesh.h"
#include "mapblock.h" #include "mapblock.h"
#include "minimap.h" #include "minimap.h"
#include "modchannels.h"
#include "mods.h" #include "mods.h"
#include "profiler.h" #include "profiler.h"
#include "shader.h" #include "shader.h"
@ -94,7 +95,8 @@ Client::Client(
m_chosen_auth_mech(AUTH_MECHANISM_NONE), m_chosen_auth_mech(AUTH_MECHANISM_NONE),
m_media_downloader(new ClientMediaDownloader()), m_media_downloader(new ClientMediaDownloader()),
m_state(LC_Created), 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 // Add local player
m_env.setLocalPlayer(new LocalPlayer(this, playername)); 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"; 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);
}

@ -52,6 +52,7 @@ class IWritableNodeDefManager;
//class IWritableCraftDefManager; //class IWritableCraftDefManager;
class ClientMediaDownloader; class ClientMediaDownloader;
struct MapDrawControl; struct MapDrawControl;
class ModChannelMgr;
class MtEventManager; class MtEventManager;
struct PointedThing; struct PointedThing;
class MapDatabase; class MapDatabase;
@ -224,6 +225,8 @@ public:
void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt);
void handleCommand_EyeOffset(NetworkPacket* pkt); void handleCommand_EyeOffset(NetworkPacket* pkt);
void handleCommand_UpdatePlayerList(NetworkPacket* pkt); void handleCommand_UpdatePlayerList(NetworkPacket* pkt);
void handleCommand_ModChannelMsg(NetworkPacket *pkt);
void handleCommand_ModChannelSignal(NetworkPacket *pkt);
void handleCommand_SrpBytesSandB(NetworkPacket* pkt); void handleCommand_SrpBytesSandB(NetworkPacket* pkt);
void handleCommand_CSMFlavourLimits(NetworkPacket *pkt); void handleCommand_CSMFlavourLimits(NetworkPacket *pkt);
@ -424,6 +427,11 @@ public:
return m_csm_noderange_limit; 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: private:
// Virtual methods from con::PeerHandler // Virtual methods from con::PeerHandler
@ -580,4 +588,6 @@ private:
// CSM flavour limits byteflag // CSM flavour limits byteflag
u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE; u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE;
u32 m_csm_noderange_limit = 8; u32 m_csm_noderange_limit = 8;
std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
}; };

@ -300,6 +300,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("default_password", ""); settings->setDefault("default_password", "");
settings->setDefault("default_privs", "interact, shout"); settings->setDefault("default_privs", "interact, shout");
settings->setDefault("enable_pvp", "true"); settings->setDefault("enable_pvp", "true");
settings->setDefault("enable_mod_channels", "false");
settings->setDefault("disallow_empty_password", "false"); settings->setDefault("disallow_empty_password", "false");
settings->setDefault("disable_anticheat", "false"); settings->setDefault("disable_anticheat", "false");
settings->setDefault("enable_rollback_recording", "false"); settings->setDefault("enable_rollback_recording", "false");

@ -33,6 +33,7 @@ class MtEventManager;
class IRollbackManager; class IRollbackManager;
class EmergeManager; class EmergeManager;
class Camera; class Camera;
class ModChannel;
class ModMetadata; class ModMetadata;
namespace irr { namespace scene { namespace irr { namespace scene {
@ -78,4 +79,10 @@ public:
virtual std::string getModStoragePath() const = 0; virtual std::string getModStoragePath() const = 0;
virtual bool registerModStorage(ModMetadata *storage) = 0; virtual bool registerModStorage(ModMetadata *storage) = 0;
virtual void unregisterModStorage(const std::string &name) = 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;
}; };

152
src/modchannels.cpp Normal file

@ -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();
}

92
src/modchannels.h Normal file

@ -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;
};

@ -111,8 +111,8 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
{ "TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CloudParams }, // 0x54 { "TOCLIENT_CLOUD_PARAMS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CloudParams }, // 0x54
{ "TOCLIENT_FADE_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FadeSound }, // 0x55 { "TOCLIENT_FADE_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FadeSound }, // 0x55
{ "TOCLIENT_UPDATE_PLAYER_LIST", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_UpdatePlayerList }, // 0x56 { "TOCLIENT_UPDATE_PLAYER_LIST", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_UpdatePlayerList }, // 0x56
null_command_handler, { "TOCLIENT_MODCHANNEL_MSG", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ModChannelMsg }, // 0x57
null_command_handler, { "TOCLIENT_MODCHANNEL_SIGNAL", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ModChannelSignal }, // 0x58
null_command_handler, null_command_handler,
null_command_handler, 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, // 0x14
null_command_factory, // 0x15 null_command_factory, // 0x15
null_command_factory, // 0x16 null_command_factory, // 0x16
null_command_factory, // 0x17 { "TOSERVER_MODCHANNEL_JOIN", 0, true }, // 0x17
null_command_factory, // 0x18 { "TOSERVER_MODCHANNEL_LEAVE", 0, true }, // 0x18
null_command_factory, // 0x19 { "TOSERVER_MODCHANNEL_MSG", 0, true }, // 0x19
null_command_factory, // 0x1a null_command_factory, // 0x1a
null_command_factory, // 0x1b null_command_factory, // 0x1b
null_command_factory, // 0x1c null_command_factory, // 0x1c

@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "map.h" #include "map.h"
#include "mapsector.h" #include "mapsector.h"
#include "minimap.h" #include "minimap.h"
#include "modchannels.h"
#include "nodedef.h" #include "nodedef.h"
#include "serialization.h" #include "serialization.h"
#include "server.h" #include "server.h"
@ -1330,3 +1331,95 @@ void Client::handleCommand_CSMFlavourLimits(NetworkPacket *pkt)
{ {
*pkt >> m_csm_flavour_limits >> m_csm_noderange_limit; *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);
}

@ -180,6 +180,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Backwards compatibility drop Backwards compatibility drop
Add 'can_zoom' to player object properties Add 'can_zoom' to player object properties
Add glow to object properties Add glow to object properties
Mod channels
*/ */
#define LATEST_PROTOCOL_VERSION 36 #define LATEST_PROTOCOL_VERSION 36
@ -611,6 +612,22 @@ enum ToClientCommand
u8[len] player name 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, TOCLIENT_SRP_BYTES_S_B = 0x60,
/* /*
Belonging to AUTH_MECHANISM_SRP. Belonging to AUTH_MECHANISM_SRP.
@ -645,6 +662,26 @@ enum ToServerCommand
[0] u16 TOSERVER_INIT2 [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_GETBLOCK = 0x20, // Obsolete
TOSERVER_ADDNODE = 0x21, // Obsolete TOSERVER_ADDNODE = 0x21, // Obsolete
TOSERVER_REMOVENODE = 0x22, // Obsolete TOSERVER_REMOVENODE = 0x22, // Obsolete

@ -47,9 +47,9 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] =
null_command_handler, // 0x14 null_command_handler, // 0x14
null_command_handler, // 0x15 null_command_handler, // 0x15
null_command_handler, // 0x16 null_command_handler, // 0x16
null_command_handler, // 0x17 { "TOSERVER_MODCHANNEL_JOIN", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelJoin }, // 0x17
null_command_handler, // 0x18 { "TOSERVER_MODCHANNEL_LEAVE", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelLeave }, // 0x18
null_command_handler, // 0x19 { "TOSERVER_MODCHANNEL_MSG", TOSERVER_STATE_INGAME, &Server::handleCommand_ModChannelMsg }, // 0x19
null_command_handler, // 0x1a null_command_handler, // 0x1a
null_command_handler, // 0x1b null_command_handler, // 0x1b
null_command_handler, // 0x1c null_command_handler, // 0x1c
@ -200,8 +200,8 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
{ "TOCLIENT_CLOUD_PARAMS", 0, true }, // 0x54 { "TOCLIENT_CLOUD_PARAMS", 0, true }, // 0x54
{ "TOCLIENT_FADE_SOUND", 0, true }, // 0x55 { "TOCLIENT_FADE_SOUND", 0, true }, // 0x55
{ "TOCLIENT_UPDATE_PLAYER_LIST", 0, true }, // 0x56 { "TOCLIENT_UPDATE_PLAYER_LIST", 0, true }, // 0x56
null_command_factory, { "TOCLIENT_MODCHANNEL_MSG", 0, true}, // 0x57
null_command_factory, { "TOCLIENT_MODCHANNEL_SIGNAL", 0, true}, // 0x58
null_command_factory, null_command_factory,
null_command_factory, null_command_factory,
null_command_factory, null_command_factory,

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content_sao.h" #include "content_sao.h"
#include "emerge.h" #include "emerge.h"
#include "mapblock.h" #include "mapblock.h"
#include "modchannels.h"
#include "nodedef.h" #include "nodedef.h"
#include "remoteplayer.h" #include "remoteplayer.h"
#include "rollback_interface.h" #include "rollback_interface.h"
@ -363,7 +364,7 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
actionstream actionstream
<< "TOSERVER_CLIENT_READY stage 2 client init failed for peer_id: " << "TOSERVER_CLIENT_READY stage 2 client init failed for peer_id: "
<< peer_id << std::endl; << peer_id << std::endl;
m_con->DisconnectPeer(peer_id); DisconnectPeer(peer_id);
return; return;
} }
@ -372,7 +373,7 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
errorstream errorstream
<< "TOSERVER_CLIENT_READY client sent inconsistent data, disconnecting peer_id: " << "TOSERVER_CLIENT_READY client sent inconsistent data, disconnecting peer_id: "
<< peer_id << std::endl; << peer_id << std::endl;
m_con->DisconnectPeer(peer_id); DisconnectPeer(peer_id);
return; return;
} }
@ -503,7 +504,7 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -512,7 +513,7 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player object for peer_id=" << pkt->getPeerId() "No player object for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -564,7 +565,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -573,7 +574,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player object for peer_id=" << pkt->getPeerId() "No player object for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -745,7 +746,7 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -773,7 +774,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -782,7 +783,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player object for peer_id=" << pkt->getPeerId() "No player object for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -839,7 +840,7 @@ void Server::handleCommand_Password(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -892,7 +893,7 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -901,7 +902,7 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player object for peer_id=" << pkt->getPeerId() "No player object for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -919,7 +920,7 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -972,7 +973,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -981,7 +982,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player object for peer_id=" << pkt->getPeerId() "No player object for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -1411,7 +1412,7 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -1420,7 +1421,7 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player object for peer_id=" << pkt->getPeerId() "No player object for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -1462,7 +1463,7 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player for peer_id=" << pkt->getPeerId() "No player for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -1471,7 +1472,7 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt)
errorstream << "Server::ProcessData(): Canceling: " errorstream << "Server::ProcessData(): Canceling: "
"No player object for peer_id=" << pkt->getPeerId() "No player object for peer_id=" << pkt->getPeerId()
<< " disconnecting peer!" << std::endl; << " disconnecting peer!" << std::endl;
m_con->DisconnectPeer(pkt->getPeerId()); DisconnectPeer(pkt->getPeerId());
return; return;
} }
@ -1733,3 +1734,81 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
acceptAuth(pkt->getPeerId(), wantSudo); 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());
}

@ -5,6 +5,7 @@ set(common_SCRIPT_CPP_API_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/s_env.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_env.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_inventory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_item.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_node.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s_player.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_player.cpp

@ -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);
}

@ -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);
};

@ -8,6 +8,7 @@ set(common_SCRIPT_LUA_API_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/l_itemstackmeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_itemstackmeta.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_mapgen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mapgen.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_metadata.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_nodemeta.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_nodetimer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_nodetimer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/l_noise.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_noise.cpp

@ -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

@ -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[];
};

@ -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_client.h"
#include "lua_api/l_env.h" #include "lua_api/l_env.h"
#include "lua_api/l_minimap.h" #include "lua_api/l_minimap.h"
#include "lua_api/l_modchannels.h"
#include "lua_api/l_storage.h" #include "lua_api/l_storage.h"
#include "lua_api/l_sound.h" #include "lua_api/l_sound.h"
#include "lua_api/l_util.h" #include "lua_api/l_util.h"
@ -72,11 +73,13 @@ void ClientScripting::InitializeModApi(lua_State *L, int top)
NodeMetaRef::RegisterClient(L); NodeMetaRef::RegisterClient(L);
LuaLocalPlayer::Register(L); LuaLocalPlayer::Register(L);
LuaCamera::Register(L); LuaCamera::Register(L);
ModChannelRef::Register(L);
ModApiUtil::InitializeClient(L, top); ModApiUtil::InitializeClient(L, top);
ModApiClient::Initialize(L, top); ModApiClient::Initialize(L, top);
ModApiStorage::Initialize(L, top); ModApiStorage::Initialize(L, top);
ModApiEnvMod::InitializeClient(L, top); ModApiEnvMod::InitializeClient(L, top);
ModApiChannels::Initialize(L, top);
} }
void ClientScripting::on_client_ready(LocalPlayer *localplayer) void ClientScripting::on_client_ready(LocalPlayer *localplayer)

@ -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_base.h"
#include "cpp_api/s_client.h" #include "cpp_api/s_client.h"
#include "cpp_api/s_modchannels.h"
#include "cpp_api/s_security.h" #include "cpp_api/s_security.h"
class Client; class Client;
@ -30,7 +31,8 @@ class Camera;
class ClientScripting: class ClientScripting:
virtual public ScriptApiBase, virtual public ScriptApiBase,
public ScriptApiSecurity, public ScriptApiSecurity,
public ScriptApiClient public ScriptApiClient,
public ScriptApiModChannels
{ {
public: public:
ClientScripting(Client *client); ClientScripting(Client *client);

@ -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_item.h"
#include "lua_api/l_itemstackmeta.h" #include "lua_api/l_itemstackmeta.h"
#include "lua_api/l_mapgen.h" #include "lua_api/l_mapgen.h"
#include "lua_api/l_modchannels.h"
#include "lua_api/l_nodemeta.h" #include "lua_api/l_nodemeta.h"
#include "lua_api/l_nodetimer.h" #include "lua_api/l_nodetimer.h"
#include "lua_api/l_noise.h" #include "lua_api/l_noise.h"
@ -100,6 +101,7 @@ void ServerScripting::InitializeModApi(lua_State *L, int top)
ObjectRef::Register(L); ObjectRef::Register(L);
LuaSettings::Register(L); LuaSettings::Register(L);
StorageRef::Register(L); StorageRef::Register(L);
ModChannelRef::Register(L);
// Initialize mod api modules // Initialize mod api modules
ModApiCraft::Initialize(L, top); ModApiCraft::Initialize(L, top);
@ -113,6 +115,7 @@ void ServerScripting::InitializeModApi(lua_State *L, int top)
ModApiUtil::Initialize(L, top); ModApiUtil::Initialize(L, top);
ModApiHttp::Initialize(L, top); ModApiHttp::Initialize(L, top);
ModApiStorage::Initialize(L, top); ModApiStorage::Initialize(L, top);
ModApiChannels::Initialize(L, top);
} }
void log_deprecated(const std::string &message) void log_deprecated(const std::string &message)

@ -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_entity.h"
#include "cpp_api/s_env.h" #include "cpp_api/s_env.h"
#include "cpp_api/s_inventory.h" #include "cpp_api/s_inventory.h"
#include "cpp_api/s_modchannels.h"
#include "cpp_api/s_node.h" #include "cpp_api/s_node.h"
#include "cpp_api/s_player.h" #include "cpp_api/s_player.h"
#include "cpp_api/s_server.h" #include "cpp_api/s_server.h"
@ -36,6 +37,7 @@ class ServerScripting:
public ScriptApiDetached, public ScriptApiDetached,
public ScriptApiEntity, public ScriptApiEntity,
public ScriptApiEnv, public ScriptApiEnv,
public ScriptApiModChannels,
public ScriptApiNode, public ScriptApiNode,
public ScriptApiPlayer, public ScriptApiPlayer,
public ScriptApiServer, public ScriptApiServer,

@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content_sao.h" #include "content_sao.h"
#include "mods.h" #include "mods.h"
#include "event_manager.h" #include "event_manager.h"
#include "modchannels.h"
#include "serverlist.h" #include "serverlist.h"
#include "util/string.h" #include "util/string.h"
#include "rollback.h" #include "rollback.h"
@ -168,7 +169,8 @@ Server::Server(
m_event(new EventManager()), m_event(new EventManager()),
m_uptime(0), m_uptime(0),
m_clients(m_con), 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"); 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, clientCommandFactoryTable[pkt->getCommand()].channel,
pkt, pkt,
clientCommandFactoryTable[pkt->getCommand()].reliable); 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); SendAccessDenied(peer_id, reason, str_reason, reconnect);
m_clients.event(peer_id, CSE_SetDenied); 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); SendAccessDenied(peer_id, reason, custom_reason);
m_clients.event(peer_id, CSE_SetDenied); 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 // 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); SendAccessDenied_Legacy(peer_id, reason);
m_clients.event(peer_id, CSE_SetDenied); 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); m_con->DisconnectPeer(peer_id);
} }
@ -3570,3 +3583,68 @@ void dedicated_server_loop(Server &server, bool &kill)
server.m_bind_addr.getPort()); server.m_bind_addr.getPort());
#endif #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);
}
}

@ -50,6 +50,7 @@ class IWritableCraftDefManager;
class BanManager; class BanManager;
class EventManager; class EventManager;
class Inventory; class Inventory;
class ModChannelMgr;
class RemotePlayer; class RemotePlayer;
class PlayerSAO; class PlayerSAO;
class IRollbackManager; class IRollbackManager;
@ -144,6 +145,9 @@ public:
void handleCommand_Deprecated(NetworkPacket* pkt); void handleCommand_Deprecated(NetworkPacket* pkt);
void handleCommand_Init(NetworkPacket* pkt); void handleCommand_Init(NetworkPacket* pkt);
void handleCommand_Init2(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_RequestMedia(NetworkPacket* pkt);
void handleCommand_ClientReady(NetworkPacket* pkt); void handleCommand_ClientReady(NetworkPacket* pkt);
void handleCommand_GotBlocks(NetworkPacket* pkt); void handleCommand_GotBlocks(NetworkPacket* pkt);
@ -165,7 +169,8 @@ public:
void ProcessData(NetworkPacket *pkt); 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 // Helper for handleCommand_PlayerPos and handleCommand_Interact
void process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, 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 DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason="");
void acceptAuth(u16 peer_id, bool forSudoMode); void acceptAuth(u16 peer_id, bool forSudoMode);
void DenyAccess_Legacy(u16 peer_id, const std::wstring &reason); 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 getClientConInfo(u16 peer_id, con::rtt_stat_type type, float* retval);
bool getClientInfo(u16 peer_id,ClientState* state, u32* uptime, bool getClientInfo(u16 peer_id,ClientState* state, u32* uptime,
u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch, u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch,
@ -334,6 +340,11 @@ public:
virtual bool registerModStorage(ModMetadata *storage); virtual bool registerModStorage(ModMetadata *storage);
virtual void unregisterModStorage(const std::string &name); 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 // Bind address
Address m_bind_addr; Address m_bind_addr;
@ -383,6 +394,8 @@ private:
float thickness, float thickness,
const v2f &speed); const v2f &speed);
void SendOverrideDayNightRatio(u16 peer_id, bool do_override, float ratio); 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. Send a node removal/addition event to all clients except ignore_id.
@ -640,6 +653,9 @@ private:
// CSM flavour limits byteflag // CSM flavour limits byteflag
u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE; u64 m_csm_flavour_limits = CSMFlavourLimit::CSM_FL_NONE;
u32 m_csm_noderange_limit = 8; u32 m_csm_noderange_limit = 8;
// ModChannel manager
std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
}; };
/* /*

@ -8,6 +8,7 @@ set (UNITTEST_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.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_nodedef.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp

@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "nodedef.h" #include "nodedef.h"
#include "itemdef.h" #include "itemdef.h"
#include "gamedef.h" #include "gamedef.h"
#include "modchannels.h"
#include "mods.h" #include "mods.h"
#include "util/numeric.h" #include "util/numeric.h"
@ -69,6 +70,13 @@ public:
virtual std::string getModStoragePath() const { return "."; } virtual std::string getModStoragePath() const { return "."; }
virtual bool registerModStorage(ModMetadata *meta) { return true; } virtual bool registerModStorage(ModMetadata *meta) { return true; }
virtual void unregisterModStorage(const std::string &name) {} 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: private:
IItemDefManager *m_itemdef = nullptr; IItemDefManager *m_itemdef = nullptr;
@ -81,10 +89,12 @@ private:
scene::ISceneManager *m_scenemgr = nullptr; scene::ISceneManager *m_scenemgr = nullptr;
IRollbackManager *m_rollbackmgr = nullptr; IRollbackManager *m_rollbackmgr = nullptr;
EmergeManager *m_emergemgr = 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_itemdef = createItemDefManager();
m_nodedef = createNodeDefManager(); m_nodedef = createNodeDefManager();
@ -222,6 +232,25 @@ void TestGameDef::defineSomeNodes()
t_CONTENT_BRICK = ndef->set(f.name, f); 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 //// run_tests
//// ////

@ -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"));
}