Message Channels
In Sponge, every object that messages may be sent to implements the MessageReceiver interface. A
MessageChannel is a device that may send messages to an arbitrary number of MessageReceiver
s and even
perform actions like recipient-specific formatting of the message.
Selecting Message Recipients
Within the MessageChannel
interface there are constants and static methods that can be used to obtain or create
commonly used channels. There are also other classes and interfaces that can be used to create a MessageChannel
,
such as AbstractMutableMessageChannel and MutableMessageChannel. A complete list of these can be
found at org.spongepowered.api.text.channel and its sub-packages.
Broadcasting
The most common channel will be the broadcasting channel. It can be obtained either from the
Server via the Server#getBroadcastChannel() method or from either the
MessageChannel#TO_PLAYERS or MessageChannel#TO_ALL constant. The only difference is that
TO_ALL
includes not only every player connected to the server but also the server Console
.
The channel returned by getBroadcastChannel()
will generally be the MessageChannel.TO_ALL
channel, however a
different channel may be set using the Server#setBroadcastChannel(MessageChannel) method.
Sending to a Fixed List of MessageReceivers
It is also possible to send a message not to all players connected, but to a number of hand-selected receivers. This can be done by passing the list of the intended recipients to the MessageChannel#fixed(MessageReceiver…) method. Unlike most other channels, the list of recipients will not be generated dynamically every time something is sent through the channel but remains static for the rest of its existence. However, the references kept are weak. This means that if for example a player disconnects and the corresponding Player object is removed by Java’s garbage collector, that player will also vanish from the channels recipient list.
Permission-based Selection
It is also possible to create a channel sending to all recipients that hold a specific permission. The channel is obtained from the MessageChannel#permission(String) method with the permission to check for as an argument. When a message is then sent through this channel, it will obtain all subjects from the PermissionService that have the given permission.
Combining Channels
It is also possible to combine multiple channels into one. This can be done by passing all channels into the MessageChannel#combined(MessageChannel…) method. The resulting channel will relay messages to every recipient that is targeted by at least one of the combined channels.
Sending Messages
Once you have obtained a MessageChannel
you can send a message through it via the
MessageChannel#send(Object, Text) method. This method is preferred over the
MessageChannel#send(Text) method, as the Object
can be used for identification or for performing other
various actions. Alternatively, you can use a ChatType to specify where the message will be sent to. Using
the MessageChannel#send(Object, Text, ChatType) method will allow you to accomplish this. The channel will
then transform the message for every recipient and send the transformed message.
Extended Application: Chat Channels
Message channels have a very useful application as they can be used to establish chat channels. For example, you could
establish a message channel for every chat channel you wish to have. Then, when a MessageReceiver
joins a channel,
such as with /join <channel name>
, simply set the MessageReceiver
’s MessageChannel
to the appropriate
channel using MessageReceiver#setMessageChannel(MessageChannel). This will cause every message sent from
the MessageReceiver
to be passed to the given MessageChannel
instead of the default one. Alternatively,
you could listen to MessageChannelEvent, and set the appropriate MessageChannel
using
MessageChannelEvent#setChannel(MessageChannel). Passing null
to that method will unset any custom
channel, causing the message to be sent to the original MessageChannel instead.
Transforming Messages
You can apply a filter to all Text
s that pass through a MessageChannel
to change the message however you
like. This is possible by extending MessageChannel
and overriding the
MessageChannel#transformMessage(Object, MessageReceiver, Text, ChatType) method as demonstrated below.
Example: Transforming Messages
The following code excerpt defines an AdminMessageChannel
class which overrides the default transformMessage
method. The new transformMessage
method will take the original message and append a red [Admin]
tag to the
front of the message.
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.channel.MessageChannel;
import org.spongepowered.api.text.channel.MessageReceiver;
import org.spongepowered.api.text.chat.ChatType;
import org.spongepowered.api.text.format.TextColors;
public class AdminMessageChannel implements MessageChannel {
@Override
public Optional<Text> transformMessage(Object sender, MessageReceiver recipient,
Text original, ChatType type) {
Text text = original;
text = Text.of(TextColors.RED, "[Admin]", TextColors.RESET, text);
return Optional.of(text);
}
@Override
public Collection<MessageReceiver> getMembers() {
return Collections.emptyList();
}
}
Note that we do not wish to define any additional recipients, so we return an empty collection in the MessageChannel#getMembers() method.
Thanks to the capabilities of combined MessageChannel
s, we can combine our newly created AdminMessageChannel
with any other MessageChannel
. Now if a player joins with the myplugin.admin
permission, we will obtain the
MessageChannel
his messages are sent to, combine it with an AdminMessageChannel
and set the combined channel
back onto the player. That way all his messages are sent to everyone specified in the original channel, but due to the
addition of the AdminMessageChannel
, a red [Admin]
tag will be prefixed.
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.network.ClientConnectionEvent;
import org.spongepowered.api.text.channel.MessageChannel;
private AdminMessageChannel adminChannel = new AdminMessageChannel();
@Listener
public void onClientConnectionJoin(ClientConnectionEvent.Join event) {
Player player = event.getTargetEntity();
if(player.hasPermission("myplugin.admin")) {
MessageChannel originalChannel = event.getOriginalChannel();
MessageChannel newChannel = MessageChannel.combined(originalChannel,
adminChannel);
player.setMessageChannel(newChannel);
}
}
Note that this will prefix all messages pertaining to a player. This includes death messages, leave messages, etc. If
you only want to prefix all chat messages, you would need to listen to MessageChannelEvent.Chat and set
the channel onto the event rather than the player. This would be done using event.setChannel(newChannel)
onto the
MessageChannelEvent.Chat
event. To get the player from the event to check for permissions, you would need to get a
Player
from the Cause
of the event. This is demonstrated below:
Optional<Player> playerOptional = event.getCause().<Player>first(Player.class);
More on causes can be found on the causes page.
Notitie
When combining multiple MessageChannel
s defining different message transformations, the Text
will be
transformed in the order that the MessageChannel
s are passed in to the
MessageChannel#combined(MessageChannel... channels)
method. Note that any transformations resulting in an
empty Optional
will be ignored unless performed by the last channel in the chain.
Mutable Message Channels
A MutableMessageChannel contains methods for changing who may receive the messages sent through our channel.
As such, we must implement methods for performing actions that modify our members. To do this, we simply will create a
class named MutableAdminMessageChannel
that will implement a MutableMessageChannel
.
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.channel.MessageReceiver;
import org.spongepowered.api.text.channel.MutableMessageChannel;
import org.spongepowered.api.text.chat.ChatType;
import org.spongepowered.api.text.format.TextColors;
public class MutableAdminMessageChannel implements MutableMessageChannel {
private Set<MessageReceiver> members;
public MutableAdminMessageChannel() {
this(Collections.emptySet());
}
public MutableAdminMessageChannel(Collection<MessageReceiver> members) {
this.members = Collections.newSetFromMap(new WeakHashMap<>());
this.members.addAll(members);
}
@Override
public Collection<MessageReceiver> getMembers() {
return Collections.unmodifiableSet(this.members);
}
@Override
public boolean addMember(MessageReceiver member) {
return this.members.add(member);
}
@Override
public boolean removeMember(MessageReceiver member) {
return this.members.remove(member);
}
@Override
public void clearMembers() {
this.members.clear();
}
@Override
public Optional<Text> transformMessage(Object sender, MessageReceiver recipient,
Text original, ChatType type) {
Text text = original;
if(this.members.contains(recipient)) {
text = Text.of(TextColors.RED, "[Admin]", TextColors.RESET, text);
}
return Optional.of(text);
}
}
The main difference between our AdminMessageChannel
and our new MutableAdminMessageChannel
is that we check if
the recipient is in the member list before transforming the message. If it is, then we may alter the message that is
sent, appending the red [Admin]
prefix. In our getMembers()
method we return an immutable set, so that the set
can only be modified by the appropriate methods in our MutableAdminMessageChannel
.
Note that an abstract implementation for MutableMessageChannel
s exists in SpongeAPI as
AbstractMutableMessageChannel
. Also note that our members do not persist. If a player were to leave the server,
they would be removed from the set.
Modifying the Members
To make full use of our MutableAdminMessageChannel
, we need to be able to add and remove members from the channel.
To do this, we can simply call our MutableMessageChannel#addMember(MessageReceiver)
and MutableMessageChannel#removeMember(MessageReceiver) methods we implemented previously whenever we need
to add or remove a member from the member set.