001    /**
002     * Jetrix TetriNET Server
003     * Copyright (C) 2008  Emmanuel Bourg
004     *
005     * This program is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU General Public License
007     * as published by the Free Software Foundation; either version 2
008     * of the License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
018     */
019    
020    package net.jetrix.agent;
021    
022    import java.io.BufferedInputStream;
023    import java.io.BufferedWriter;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.OutputStreamWriter;
027    import java.io.Writer;
028    import java.net.Socket;
029    import java.util.Locale;
030    
031    import net.jetrix.Message;
032    import net.jetrix.Protocol;
033    import net.jetrix.config.ServerConfig;
034    import net.jetrix.protocols.TetrinetProtocol;
035    import net.jetrix.messages.*;
036    import net.jetrix.messages.channel.*;
037    import net.jetrix.messages.channel.specials.*;
038    
039    /**
040     * Tetrinet agent to log on a server as a player.
041     * 
042     * @author Emmanuel Bourg
043     * @version $Revision: 848 $, $Date: 2010-05-03 22:51:32 +0200 (lun., 03 mai 2010) $
044     */
045    public class TetrinetAgent implements Agent
046    {
047        /** The name of the agent. */
048        protected String clientName = "Jetrix";
049    
050        /** The version of the agent. */
051        protected String clientVersion = ServerConfig.VERSION;
052    
053        /** The name of the player. */
054        protected String name;
055    
056        /** The name of the team. */
057        protected String teamname;
058    
059        /** The hostname of the TetriNET server. */
060        private String hostname;
061    
062        /** The current slot assigned by the server. */
063        private int slot;
064    
065        private Socket socket;
066        private InputStream in;
067        private Writer out;
068        private String encoding = "Cp1252";
069        
070        protected Protocol protocol = new TetrinetProtocol();
071        private boolean running;
072    
073        public TetrinetAgent(String name)
074        {
075            this.name = name;
076        }
077    
078        public String getHostname()
079        {
080            return hostname;
081        }
082    
083        public void connect(String hostname) throws IOException
084        {
085            connect(hostname, 31457, "1.13");
086        }
087    
088        protected void connect(String hostname, int port, String version) throws IOException
089        {
090            if (running)
091            {
092                return;
093            }
094    
095            this.hostname = hostname;
096    
097            socket = new Socket(hostname, port);
098            socket.setSoTimeout(15000);
099            
100            in = new BufferedInputStream(socket.getInputStream());
101            out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), encoding));
102    
103            send(TetrinetProtocol.encode(name, version, socket.getInetAddress().getAddress(), false));
104    
105            // block until the playernum message is received
106            String line = protocol.readLine(in, encoding);
107            Message message = protocol.getMessage(line);
108            receive(message);
109    
110            if (message instanceof NoConnectingMessage)
111            {
112                throw new IOException("Connexion rejected (noconnecting) : " + ((NoConnectingMessage) message).getText());
113            }
114    
115            running = true;
116            
117            socket.setSoTimeout(0);
118    
119            // start the message listener
120            new MessageListener().start();
121        }
122    
123        private class MessageListener extends Thread
124        {
125            private MessageListener()
126            {
127                setName("listener");
128            }
129    
130            public void run()
131            {
132                try
133                {
134                    while (running)
135                    {
136                        String line = protocol.readLine(in, encoding);
137    
138                        Message message = protocol.getMessage(line);
139    
140                        receive(message);
141                    }
142                }
143                catch (IOException e)
144                {
145                    if (running)
146                    {
147                        e.printStackTrace();
148                        running = false;
149                    }
150                }
151            }
152        }
153    
154        public void disconnect() throws IOException
155        {
156            if (socket != null)
157            {
158                running = false;
159                socket.close();
160            }
161        }
162    
163        protected synchronized void send(String message) throws IOException
164        {
165            out.write(message);
166            out.write(protocol.getEOL());
167            out.flush();
168        }
169    
170        public void send(Message message) throws IOException
171        {
172            // set the slot for channel messages
173            if (message instanceof ChannelMessage)
174            {
175                ((ChannelMessage) message).setSlot(slot);
176            }
177    
178            send(protocol.translate(message, Locale.getDefault()));
179        }
180    
181        /**
182         * Join the specified channel
183         * 
184         * @param channel
185         */
186        public void join(String channel) throws IOException
187        {
188            send(new PlineMessage("/join " + channel));
189        }
190    
191        public final void receive(Message m)
192        {
193            // overwritable pre processing
194            onMessage(m);
195    
196            // message dispatching
197            if (m instanceof SpecialMessage)           { onMessage((SpecialMessage) m); }
198            else if (m instanceof FieldMessage)        { onMessage((FieldMessage) m); }
199            else if (m instanceof CommandMessage)      { onMessage((CommandMessage) m); }
200            else if (m instanceof PlineMessage)        { onMessage((PlineMessage) m); }
201            else if (m instanceof LevelMessage)        { onMessage((LevelMessage) m); }
202            else if (m instanceof PlayerLostMessage)   { onMessage((PlayerLostMessage) m); }
203            else if (m instanceof PlineActMessage)     { onMessage((PlineActMessage) m); }
204            else if (m instanceof TeamMessage)         { onMessage((TeamMessage) m); }
205            else if (m instanceof JoinMessage)         { onMessage((JoinMessage) m); }
206            else if (m instanceof LeaveMessage)        { onMessage((LeaveMessage) m); }
207            else if (m instanceof PlayerNumMessage)    { onMessage((PlayerNumMessage) m); }
208            else if (m instanceof StartGameMessage)    { onMessage((StartGameMessage) m); }
209            else if (m instanceof StopGameMessage)     { onMessage((StopGameMessage) m); }
210            else if (m instanceof NewGameMessage)      { onMessage((NewGameMessage) m); }
211            else if (m instanceof EndGameMessage)      { onMessage((EndGameMessage) m); }
212            else if (m instanceof PauseMessage)        { onMessage((PauseMessage) m); }
213            else if (m instanceof ResumeMessage)       { onMessage((ResumeMessage) m); }
214            else if (m instanceof GmsgMessage)         { onMessage((GmsgMessage) m); }
215            else if (m instanceof PlayerWonMessage)    { onMessage((PlayerWonMessage) m); }
216            else if (m instanceof NoConnectingMessage) { onMessage((NoConnectingMessage) m); }
217            else
218            {
219                // nothing, log an error ?
220            }
221    
222            // todo add onMessage(DisconnectedMessage)
223        }
224    
225        /**
226         * Message pre-processing. This method is called at the beginning of the
227         * <tt>process(Message m, List out)</tt> method and allow custom
228         * processing for all filtered messages.
229         */
230        public void onMessage(Message m) { }
231    
232        public void onMessage(PlineMessage m) { }
233    
234        public void onMessage(PlineActMessage m) { }
235    
236        public void onMessage(NoConnectingMessage m) { }
237    
238        public void onMessage(TeamMessage m) { }
239    
240        public void onMessage(JoinMessage m) { }
241    
242        public void onMessage(LeaveMessage m) { }
243    
244        public void onMessage(PlayerNumMessage m)
245        {
246            this.slot = m.getSlot();
247            
248            // repond with the team name
249            TeamMessage response = new TeamMessage();
250            response.setSlot(slot);
251            response.setName(teamname);
252            
253            try
254            {
255                send(response);
256            }
257            catch (IOException e)
258            {
259                e.printStackTrace();
260            }
261        }
262    
263        public void onMessage(StartGameMessage m) { }
264    
265        public void onMessage(StopGameMessage m) { }
266    
267        public void onMessage(NewGameMessage m) { }
268    
269        public void onMessage(EndGameMessage m) { }
270    
271        public void onMessage(PauseMessage m) { }
272    
273        public void onMessage(ResumeMessage m) { }
274    
275        public void onMessage(GmsgMessage m) { }
276    
277        private void onMessage(SpecialMessage m)
278        {
279            // message pre-processing
280            onSpecial(m);
281    
282            // message dispatching
283            if (m instanceof LinesAddedMessage)        { onMessage((LinesAddedMessage) m); }
284            else if (m instanceof AddLineMessage)        { onMessage((AddLineMessage) m); }
285            else if (m instanceof ClearLineMessage)      { onMessage((ClearLineMessage) m); }
286            else if (m instanceof ClearSpecialsMessage)  { onMessage((ClearSpecialsMessage) m); }
287            else if (m instanceof RandomClearMessage)    { onMessage((RandomClearMessage) m); }
288            else if (m instanceof BlockQuakeMessage)     { onMessage((BlockQuakeMessage) m); }
289            else if (m instanceof BlockBombMessage)      { onMessage((BlockBombMessage) m); }
290            else if (m instanceof GravityMessage)        { onMessage((GravityMessage) m); }
291            else if (m instanceof NukeFieldMessage)      { onMessage((NukeFieldMessage) m); }
292            else if (m instanceof SwitchFieldsMessage)   { onMessage((SwitchFieldsMessage) m); }
293        }
294    
295        /**
296         * Special block message pre-processing. This method is called for all
297         * specials filtered and allow custom processing for all specials
298         * (lines added, blockbomb switchs, etc...).
299         */
300        public void onSpecial(SpecialMessage m) { }
301    
302        public void onMessage(LevelMessage m)
303        {
304            if (m.getLevel() == 0 && m.getSlot() == 0)
305            {
306                ClientInfoMessage response = new ClientInfoMessage();
307                response.setName(clientName);
308                response.setVersion(clientVersion);
309                
310                try
311                {
312                    send(response);
313                }
314                catch (IOException e)
315                {
316                    e.printStackTrace();
317                }
318            }
319        }
320    
321        public void onMessage(FieldMessage m) { }
322    
323        public void onMessage(PlayerLostMessage m) { }
324    
325        public void onMessage(PlayerWonMessage m) { }
326    
327        public void onMessage(CommandMessage m) { }
328    
329        public void onMessage(LinesAddedMessage m)
330        {
331            if (m instanceof OneLineAddedMessage)        { onMessage((OneLineAddedMessage) m); }
332            else if (m instanceof TwoLinesAddedMessage)  { onMessage((TwoLinesAddedMessage) m); }
333            else if (m instanceof FourLinesAddedMessage) { onMessage((FourLinesAddedMessage) m); }
334        }
335    
336        public void onMessage(OneLineAddedMessage m) { }
337    
338        public void onMessage(TwoLinesAddedMessage m) { }
339    
340        public void onMessage(FourLinesAddedMessage m) { }
341    
342        public void onMessage(AddLineMessage m) { }
343    
344        public void onMessage(ClearLineMessage m) { }
345    
346        public void onMessage(NukeFieldMessage m) { }
347    
348        public void onMessage(RandomClearMessage m) { }
349    
350        public void onMessage(SwitchFieldsMessage m) { }
351    
352        public void onMessage(ClearSpecialsMessage m) { }
353    
354        public void onMessage(GravityMessage m) { }
355    
356        public void onMessage(BlockQuakeMessage m) { }
357    
358        public void onMessage(BlockBombMessage m) { }
359    
360    }