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.concurrent.ConcurrentMap;
017import java.util.logging.Level;
018
019import net.minecraft.network.INetworkManager;
020import net.minecraft.network.packet.NetHandler;
021
022import com.google.common.base.Throwables;
023import com.google.common.collect.MapMaker;
024import com.google.common.primitives.Bytes;
025import com.google.common.primitives.Ints;
026import com.google.common.primitives.UnsignedBytes;
027
028import cpw.mods.fml.common.FMLLog;
029
030public abstract class FMLPacket
031{
032    enum Type
033    {
034        /**
035         * Opening salutation from the server to the client -> request all mods from the client
036         */
037        MOD_LIST_REQUEST(ModListRequestPacket.class, false),
038        /**
039         * The client responds with the list of mods and versions it has. This is verified by the server.
040         */
041        MOD_LIST_RESPONSE(ModListResponsePacket.class, false),
042        /**
043         * At which point the server tells the client the mod identifiers for this session.
044         */
045        MOD_IDENTIFIERS(ModIdentifiersPacket.class, false),
046        /**
047         * Or, if there is missing stuff, the server tells the client what's missing and drops the connection.
048         */
049        MOD_MISSING(ModMissingPacket.class, false),
050        /**
051         * Open a GUI on the client from the server
052         */
053        GUIOPEN(OpenGuiPacket.class, false),
054        /**
055         * Spawn an entity on the client from the server
056         */
057        ENTITYSPAWN(EntitySpawnPacket.class, false),
058        /**
059         * Fixes entity location data after spawning
060         */
061        ENTITYSPAWNADJUSTMENT(EntitySpawnAdjustmentPacket.class, false),
062        /**
063         * The ID map to send to the client
064         */
065        MOD_IDMAP(ModIdMapPacket.class, true);
066
067
068        private Class<? extends FMLPacket> packetType;
069        private boolean isMultipart;
070        private ConcurrentMap<INetworkManager, FMLPacket> partTracker;
071
072        private Type(Class<? extends FMLPacket> clazz, boolean isMultipart)
073        {
074            this.packetType = clazz;
075            this.isMultipart = isMultipart;
076        }
077
078        FMLPacket make()
079        {
080            try
081            {
082                return this.packetType.newInstance();
083            }
084            catch (Exception e)
085            {
086                Throwables.propagateIfPossible(e);
087                FMLLog.log(Level.SEVERE, e, "A bizarre critical error occured during packet encoding");
088                throw new FMLNetworkException(e);
089            }
090        }
091
092        public boolean isMultipart()
093        {
094            return isMultipart;
095        }
096
097        private FMLPacket findCurrentPart(INetworkManager network)
098        {
099            if (partTracker == null)
100            {
101                partTracker = new MapMaker().weakKeys().weakValues().makeMap();
102            }
103            if (!partTracker.containsKey(network))
104            {
105                partTracker.put(network, make());
106            }
107            return partTracker.get(network);
108        }
109    }
110
111    private Type type;
112
113    public static byte[][] makePacketSet(Type type, Object... data)
114    {
115        if (!type.isMultipart())
116        {
117            return new byte[0][];
118        }
119        byte[] packetData = type.make().generatePacket(data);
120
121        byte[][] chunks = new byte[packetData.length / 32000 + 1][];
122        for (int i = 0; i < packetData.length / 32000 + 1; i++)
123        {
124            int len = Math.min(32000, packetData.length - i* 32000);
125            chunks[i] = Bytes.concat(new byte[] { UnsignedBytes.checkedCast(type.ordinal()), UnsignedBytes.checkedCast(i), UnsignedBytes.checkedCast(chunks.length)}, Ints.toByteArray(len), Arrays.copyOfRange(packetData, i * 32000, len + i * 32000));
126        }
127        return chunks;
128    }
129    public static byte[] makePacket(Type type, Object... data)
130    {
131        byte[] packetData = type.make().generatePacket(data);
132        return Bytes.concat(new byte[] { UnsignedBytes.checkedCast(type.ordinal()) }, packetData );
133    }
134
135    public static FMLPacket readPacket(INetworkManager network, byte[] payload)
136    {
137        int type = UnsignedBytes.toInt(payload[0]);
138        Type eType = Type.values()[type];
139        FMLPacket pkt;
140        if (eType.isMultipart())
141        {
142            pkt = eType.findCurrentPart(network);
143        }
144        else
145        {
146            pkt = eType.make();
147        }
148        return pkt.consumePacket(Arrays.copyOfRange(payload, 1, payload.length));
149    }
150
151    public FMLPacket(Type type)
152    {
153        this.type = type;
154    }
155
156    public abstract byte[] generatePacket(Object... data);
157
158    public abstract FMLPacket consumePacket(byte[] data);
159
160    public abstract void execute(INetworkManager network, FMLNetworkHandler handler, NetHandler netHandler, String userName);
161}