001/*
002 * Forge Mod Loader
003 * Copyright (c) 2012-2013 cpw.
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser Public License v2.1
006 * which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
008 * 
009 * Contributors:
010 *     cpw - implementation
011 */
012
013package cpw.mods.fml.common.network;
014
015import java.util.Arrays;
016import java.util.List;
017import java.util.Map;
018import java.util.Set;
019import java.util.logging.Level;
020
021import net.minecraft.entity.player.EntityPlayer;
022import net.minecraft.entity.player.EntityPlayerMP;
023import net.minecraft.inventory.Container;
024import net.minecraft.network.*;
025import net.minecraft.network.packet.*;
026import net.minecraft.server.MinecraftServer;
027import net.minecraft.world.World;
028
029import com.google.common.base.Charsets;
030import com.google.common.base.Joiner;
031import com.google.common.base.Splitter;
032import com.google.common.base.Strings;
033import com.google.common.collect.ArrayListMultimap;
034import com.google.common.collect.Iterables;
035import com.google.common.collect.Lists;
036import com.google.common.collect.Maps;
037import com.google.common.collect.Multimap;
038import com.google.common.collect.Sets;
039
040import cpw.mods.fml.common.FMLCommonHandler;
041import cpw.mods.fml.common.FMLLog;
042import cpw.mods.fml.common.Loader;
043import cpw.mods.fml.common.ModContainer;
044import cpw.mods.fml.common.network.FMLPacket.Type;
045import cpw.mods.fml.relauncher.Side;
046
047/**
048 * @author cpw
049 *
050 */
051public class NetworkRegistry
052{
053
054    private static final NetworkRegistry INSTANCE = new NetworkRegistry();
055    /**
056     * A map of active channels per player
057     */
058    private Multimap<Player, String> activeChannels = ArrayListMultimap.create();
059    /**
060     * A map of the packet handlers for packets
061     */
062    private Multimap<String, IPacketHandler> universalPacketHandlers = ArrayListMultimap.create();
063    private Multimap<String, IPacketHandler> clientPacketHandlers = ArrayListMultimap.create();
064    private Multimap<String, IPacketHandler> serverPacketHandlers = ArrayListMultimap.create();
065    /**
066     * A linked set of registered connection handlers
067     */
068    private Set<IConnectionHandler> connectionHandlers = Sets.newLinkedHashSet();
069    private Map<ModContainer, IGuiHandler> serverGuiHandlers = Maps.newHashMap();
070    private Map<ModContainer, IGuiHandler> clientGuiHandlers = Maps.newHashMap();
071    private List<IChatListener> chatListeners = Lists.newArrayList();
072
073    public static NetworkRegistry instance()
074    {
075        return INSTANCE;
076    }
077    /**
078     * Get the packet 250 channel registration string
079     * @return the {@link Packet250CustomPayload} channel registration string
080     */
081    byte[] getPacketRegistry(Side side)
082    {
083        return Joiner.on('\0').join(Iterables.concat(Arrays.asList("FML"),universalPacketHandlers.keySet(), side.isClient() ? clientPacketHandlers.keySet() : serverPacketHandlers.keySet())).getBytes(Charsets.UTF_8);
084    }
085    /**
086     * Is the specified channel active for the player?
087     * @param channel
088     * @param player
089     */
090    public boolean isChannelActive(String channel, Player player)
091    {
092        return activeChannels.containsEntry(player,channel);
093    }
094    /**
095     * register a channel to a mod
096     * @param handler the packet handler
097     * @param channelName the channel name to register it with
098     */
099    public void registerChannel(IPacketHandler handler, String channelName)
100    {
101        if (Strings.isNullOrEmpty(channelName) || (channelName!=null && channelName.length()>16))
102        {
103            FMLLog.severe("Invalid channel name '%s' : %s", channelName, Strings.isNullOrEmpty(channelName) ? "Channel name is empty" : "Channel name is too long (16 chars is maximum)");
104            throw new RuntimeException("Channel name is invalid");
105
106        }
107        universalPacketHandlers.put(channelName, handler);
108    }
109
110    public void registerChannel(IPacketHandler handler, String channelName, Side side)
111    {
112        if (side == null)
113        {
114            registerChannel(handler, channelName);
115            return;
116        }
117        if (Strings.isNullOrEmpty(channelName) || (channelName!=null && channelName.length()>16))
118        {
119            FMLLog.severe("Invalid channel name '%s' : %s", channelName, Strings.isNullOrEmpty(channelName) ? "Channel name is empty" : "Channel name is too long (16 chars is maximum)");
120            throw new RuntimeException("Channel name is invalid");
121
122        }
123        if (side.isClient())
124        {
125            clientPacketHandlers.put(channelName, handler);
126        }
127        else
128        {
129            serverPacketHandlers.put(channelName, handler);
130        }
131    }
132    /**
133     * Activate the channel for the player
134     * @param player
135     */
136    void activateChannel(Player player, String channel)
137    {
138        activeChannels.put(player, channel);
139    }
140    /**
141     * Deactivate the channel for the player
142     * @param player
143     * @param channel
144     */
145    void deactivateChannel(Player player, String channel)
146    {
147        activeChannels.remove(player, channel);
148    }
149    /**
150     * Register a connection handler
151     *
152     * @param handler
153     */
154    public void registerConnectionHandler(IConnectionHandler handler)
155    {
156        connectionHandlers.add(handler);
157    }
158
159    /**
160     * Register a chat listener
161     * @param listener
162     */
163    public void registerChatListener(IChatListener listener)
164    {
165        chatListeners.add(listener);
166    }
167
168    void playerLoggedIn(EntityPlayerMP player, NetServerHandler netHandler, INetworkManager manager)
169    {
170        generateChannelRegistration(player, netHandler, manager);
171        for (IConnectionHandler handler : connectionHandlers)
172        {
173            handler.playerLoggedIn((Player)player, netHandler, manager);
174        }
175    }
176
177    String connectionReceived(NetLoginHandler netHandler, INetworkManager manager)
178    {
179        for (IConnectionHandler handler : connectionHandlers)
180        {
181            String kick = handler.connectionReceived(netHandler, manager);
182            if (!Strings.isNullOrEmpty(kick))
183            {
184                return kick;
185            }
186        }
187        return null;
188    }
189
190    void connectionOpened(NetHandler netClientHandler, String server, int port, INetworkManager networkManager)
191    {
192        for (IConnectionHandler handler : connectionHandlers)
193        {
194            handler.connectionOpened(netClientHandler, server, port, networkManager);
195        }
196    }
197
198    void connectionOpened(NetHandler netClientHandler, MinecraftServer server, INetworkManager networkManager)
199    {
200        for (IConnectionHandler handler : connectionHandlers)
201        {
202            handler.connectionOpened(netClientHandler, server, networkManager);
203        }
204    }
205
206    void clientLoggedIn(NetHandler clientHandler, INetworkManager manager, Packet1Login login)
207    {
208        generateChannelRegistration(clientHandler.getPlayer(), clientHandler, manager);
209        for (IConnectionHandler handler : connectionHandlers)
210        {
211            handler.clientLoggedIn(clientHandler, manager, login);
212        }
213    }
214
215    void connectionClosed(INetworkManager manager, EntityPlayer player)
216    {
217        for (IConnectionHandler handler : connectionHandlers)
218        {
219            handler.connectionClosed(manager);
220        }
221        activeChannels.removeAll(player);
222    }
223
224    void generateChannelRegistration(EntityPlayer player, NetHandler netHandler, INetworkManager manager)
225    {
226        Packet250CustomPayload pkt = new Packet250CustomPayload();
227        pkt.channel = "REGISTER";
228        pkt.data = getPacketRegistry(player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT);
229        pkt.length = pkt.data.length;
230        manager.addToSendQueue(pkt);
231    }
232
233    void handleCustomPacket(Packet250CustomPayload packet, INetworkManager network, NetHandler handler)
234    {
235        if ("REGISTER".equals(packet.channel))
236        {
237            handleRegistrationPacket(packet, (Player)handler.getPlayer());
238        }
239        else if ("UNREGISTER".equals(packet.channel))
240        {
241            handleUnregistrationPacket(packet, (Player)handler.getPlayer());
242        }
243        else
244        {
245            handlePacket(packet, network, (Player)handler.getPlayer());
246        }
247    }
248
249
250    private void handlePacket(Packet250CustomPayload packet, INetworkManager network, Player player)
251    {
252        String channel = packet.channel;
253        for (IPacketHandler handler : Iterables.concat(universalPacketHandlers.get(channel), player instanceof EntityPlayerMP ? serverPacketHandlers.get(channel) : clientPacketHandlers.get(channel)))
254        {
255            handler.onPacketData(network, packet, player);
256        }
257    }
258
259    private void handleRegistrationPacket(Packet250CustomPayload packet, Player player)
260    {
261        List<String> channels = extractChannelList(packet);
262        for (String channel : channels)
263        {
264            activateChannel(player, channel);
265        }
266    }
267    private void handleUnregistrationPacket(Packet250CustomPayload packet, Player player)
268    {
269        List<String> channels = extractChannelList(packet);
270        for (String channel : channels)
271        {
272            deactivateChannel(player, channel);
273        }
274    }
275
276    private List<String> extractChannelList(Packet250CustomPayload packet)
277    {
278        String request = new String(packet.data, Charsets.UTF_8);
279        List<String> channels = Lists.newArrayList(Splitter.on('\0').split(request));
280        return channels;
281    }
282
283    public void registerGuiHandler(Object mod, IGuiHandler handler)
284    {
285        ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod);
286        if (mc == null)
287        {
288            mc = Loader.instance().activeModContainer();
289            FMLLog.log(Level.WARNING, "Mod %s attempted to register a gui network handler during a construction phase", mc.getModId());
290        }
291        NetworkModHandler nmh = FMLNetworkHandler.instance().findNetworkModHandler(mc);
292        if (nmh == null)
293        {
294            FMLLog.log(Level.FINE, "The mod %s needs to be a @NetworkMod to register a Networked Gui Handler", mc.getModId());
295        }
296        else
297        {
298            serverGuiHandlers.put(mc, handler);
299        }
300        clientGuiHandlers.put(mc, handler);
301    }
302    void openRemoteGui(ModContainer mc, EntityPlayerMP player, int modGuiId, World world, int x, int y, int z)
303    {
304        IGuiHandler handler = serverGuiHandlers.get(mc);
305        NetworkModHandler nmh = FMLNetworkHandler.instance().findNetworkModHandler(mc);
306        if (handler != null && nmh != null)
307        {
308            Container container = (Container)handler.getServerGuiElement(modGuiId, player, world, x, y, z);
309            if (container != null)
310            {
311                player.incrementWindowID();
312                player.closeInventory();
313                int windowId = player.currentWindowId;
314                Packet250CustomPayload pkt = new Packet250CustomPayload();
315                pkt.channel = "FML";
316                pkt.data = FMLPacket.makePacket(Type.GUIOPEN, windowId, nmh.getNetworkId(), modGuiId, x, y, z);
317                pkt.length = pkt.data.length;
318                player.playerNetServerHandler.sendPacketToPlayer(pkt);
319                player.openContainer = container;
320                player.openContainer.windowId = windowId;
321                player.openContainer.addCraftingToCrafters(player);
322            }
323        }
324    }
325    void openLocalGui(ModContainer mc, EntityPlayer player, int modGuiId, World world, int x, int y, int z)
326    {
327        IGuiHandler handler = clientGuiHandlers.get(mc);
328        FMLCommonHandler.instance().showGuiScreen(handler.getClientGuiElement(modGuiId, player, world, x, y, z));
329    }
330    public Packet3Chat handleChat(NetHandler handler, Packet3Chat chat)
331    {
332        Side s = Side.CLIENT;
333        if (handler instanceof NetServerHandler)
334        {
335            s = Side.SERVER;
336        }
337        for (IChatListener listener : chatListeners)
338        {
339            chat = s.isClient() ? listener.clientChat(handler, chat) : listener.serverChat(handler, chat);
340        }
341
342        return chat;
343    }
344    public void handleTinyPacket(NetHandler handler, Packet131MapData mapData)
345    {
346        NetworkModHandler nmh = FMLNetworkHandler.instance().findNetworkModHandler((int)mapData.itemID);
347        if (nmh == null)
348        {
349            FMLLog.info("Received a tiny packet for network id %d that is not recognised here", mapData.itemID);
350            return;
351        }
352        if (nmh.hasTinyPacketHandler())
353        {
354            nmh.getTinyPacketHandler().handle(handler, mapData);
355        }
356        else
357        {
358            FMLLog.info("Received a tiny packet for a network mod that does not accept tiny packets %s", nmh.getContainer().getModId());
359        }
360    }
361}