I am working on plugin-like code for the l2jmobius. It needs to be as easy integratable as possible (e.g. no need to patch up the existing server files).
I found that it is hard to get multiple chat handlers working as there is only 1 handler spot per chat type. Imagine if we want to have an extra chat handler provided by a plugin/server extension - it would have to call the "original" handler after it's done. The problem is that it won't work if there are more than 1 such plugins - they might not know about each other, so one of them ends up taking it for itself.
There is a relatively simple fix for it - introduce a multi handler for chats.
Here is a file I am using:
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.l2jmobius.gameserver.handler;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.network.enums.ChatType;
/**
* This class handles all chat handlers
* @author durgus
*/
public class ChatHandler implements IHandler<IChatHandler, ChatType>
{
private final Map<ChatType, MultiHandler> _datatable = new EnumMap<>(ChatType.class);
private class MultiHandler implements IChatHandler {
public ArrayList<IChatHandler> list;
public MultiHandler() {
list = new ArrayList<>();
list.ensureCapacity(1);
}
[member=79]override[/member]
public void handleChat(ChatType type, Player player, String target, String text) {
for (IChatHandler h : list) {
h.handleChat(type, player, target, text);
}
}
[member=79]override[/member]
public ChatType[] getChatTypeList() {
// This method is only called when registering/unloading
// handlers, so there is hardly any reason to call it
// on a multi-handler.
switch (list.size()) {
case 0:
return null;
case 1:
return list.getFirst().getChatTypeList();
}
throw new UnsupportedOperationException();
}
}
/**
* Singleton constructor
*/
protected ChatHandler()
{
}
/**
* Register a new chat handler
* @param handler
*/
[member=79]override[/member]
public void registerHandler(IChatHandler handler)
{
for (ChatType type : handler.getChatTypeList())
{
MultiHandler multi = _datatable.get(type);
if (multi == null) {
multi = new MultiHandler();
_datatable.put(type, multi);
}
multi.list.add(handler);
}
}
[member=79]override[/member]
public synchronized void removeHandler(IChatHandler handler)
{
for (ChatType type : handler.getChatTypeList())
{
MultiHandler multi = _datatable.get(type);
if (multi == null) {
continue;
}
multi.list.remove(handler);
}
}
/**
* Get the chat handler for the given chat type
* @param chatType
* [member=10368]return[/member]
*/
[member=79]override[/member]
public IChatHandler getHandler(ChatType chatType)
{
return _datatable.get(chatType);
}
/**
* Returns the size
* [member=10368]return[/member]
*/
[member=79]override[/member]
public int size()
{
return _datatable.size();
}
public static ChatHandler getInstance()
{
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder
{
protected static final ChatHandler INSTANCE = new ChatHandler();
}
}
In form of a patch:
/gameserver/handler/ChatHandler.java
index 15c5d870601..67fb998cea5 100644
--- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ChatHandler.java
+++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/handler/ChatHandler.java
@@ -16,9 +16,11 @@
*/
package org.l2jmobius.gameserver.handler;
+import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Map;
+import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.network.enums.ChatType;
/**
@@ -27,7 +29,37 @@ import org.l2jmobius.gameserver.network.enums.ChatType;
*/
public class ChatHandler implements IHandler<IChatHandler, ChatType>
{
- private final Map<ChatType, IChatHandler> _datatable = new EnumMap<>(ChatType.class);
+ private final Map<ChatType, MultiHandler> _datatable = new EnumMap<>(ChatType.class);
+
+ private class MultiHandler implements IChatHandler {
+ public ArrayList<IChatHandler> list;
+
+ public MultiHandler() {
+ list = new ArrayList<>();
+ list.ensureCapacity(1);
+ }
+
+ [member=79]override[/member]
+ public void handleChat(ChatType type, Player player, String target, String text) {
+ for (IChatHandler h : list) {
+ h.handleChat(type, player, target, text);
+ }
+ }
+
+ [member=79]override[/member]
+ public ChatType[] getChatTypeList() {
+ // This method is only called when registering/unloading
+ // handlers, so there is hardly any reason to call it
+ // on a multi-handler.
+ switch (list.size()) {
+ case 0:
+ return null;
+ case 1:
+ return list.getFirst().getChatTypeList();
+ }
+ throw new UnsupportedOperationException();
+ }
+ }
/**
* Singleton constructor
@@ -45,7 +77,12 @@ public class ChatHandler implements IHandler<IChatHandler, ChatType>
{
for (ChatType type : handler.getChatTypeList())
{
- _datatable.put(type, handler);
+ MultiHandler multi = _datatable.get(type);
+ if (multi == null) {
+ multi = new MultiHandler();
+ _datatable.put(type, multi);
+ }
+ multi.list.add(handler);
}
}
@@ -54,7 +91,11 @@ public class ChatHandler implements IHandler<IChatHandler, ChatType>
{
for (ChatType type : handler.getChatTypeList())
{
- _datatable.remove(type);
+ MultiHandler multi = _datatable.get(type);
+ if (multi == null) {
+ continue;
+ }
+ multi.list.remove(handler);
}
}
@@ -88,4 +129,4 @@ public class ChatHandler implements IHandler<IChatHandler, ChatType>
{
protected static final ChatHandler INSTANCE = new ChatHandler();
}
-}
\ No newline at end of file
+}
I tested it with a 1 (original) and 2 chat handlers - it seem to be working fine.
If you like the idea, could you incorporate something like that in the core? I do think that having an ability to make non-intrusive plugins/extensions to the server is a very good thing for the project.
If you don't like it, that's alright too. I am interested to know if there are better ways to handle the chat without modifying the core server files.