import java.applet.*; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.*; import java.util.zip.*; import java.lang.*; public class pclient extends Applet { // Utilities public static String[] splitString(String s, char delim) { // StringTokenizer drops empty tokens :-( int count = 1; int length = s.length(); for (int i=0; i 0) { int count = st.read(buffer, off, len); if (count <= 0) throw new IOException("unexpected end of data"); off += count; len -= count; } } public static Color makeColor(int color) { return new Color(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF); } public static InputStream decompresser(byte[] data, int off, int len) { return new InflaterInputStream(new ByteArrayInputStream(data, off, len)); } /*class ArraySlice { public byte[] array; public int ofs, size; ArraySlice(byte[] aarray, int aofs, int asize) { array = aarray; ofs = aofs; size = asize; } }*/ public void debug(Throwable e) { showStatus(e.toString()); e.printStackTrace(); } // Bitmaps and icons class Bitmap { int w, h; public int[] pixelData; Bitmap(pclient client, InputStream st, int keycol) throws IOException { String line = readLine(st); if (!"P6".equals(line)) throw new IOException("not a P6 PPM image"); while ((line = readLine(st)).startsWith("#")) ; String[] wh = splitString(line, ' '); if (wh.length != 2) throw new IOException("invalid PPM image size"); w = Integer.parseInt(wh[0]); h = Integer.parseInt(wh[1]); line = readLine(st); if (!"255".equals(line)) throw new IOException("not a 255-levels PPM image"); // over-allocate an extra uninitialized line at the bottom of the // image to work around a bug in the MemoryImageSource constructor pixelData = new int[w*(h+1)]; int target = 0; int w3 = 3*w; byte[] lineBuffer = new byte[w3]; for (int y=0; y>>"); if (data.length >= 4 && pongMessage.equals(data[0])) { InetAddress result; if (data[2].length() == 0) { result = inp.getAddress(); } else { result = InetAddress.getByName(data[2]); } port = Integer.parseInt(data[3]); showStatus("Connecting to "+data[1]+" at "+ result.toString()+":"+Integer.toString(port)+"..."); return new Socket(result, port); } else throw new IOException("got an unexpected answer from " + inp.getAddress().toString()); } // Game state class Player { public int pid; public boolean playing; public boolean local; public Image icon; public int xmin, xmax; } class KeyName { public String keyname; public int keyid; public Image[] keyicons; public KeyName next; public int newkeycode; } public Player[] players = new Player[0]; public KeyName keys = null; public Hashtable keycodes = new Hashtable(); public boolean taskbarfree = false; public boolean taskbarmode = false; public KeyName keydefinition_k = null; public int keydefinition_pid; public Image[] iconImages = new Image[0]; public Bitmap[] bitmaps = new Bitmap[0]; public Player getPlayer(int id) { if (id >= players.length) { Player[] newply = new Player[id+1]; System.arraycopy(players, 0, newply, 0, players.length); players = newply; } if (players[id] == null) { players[id] = new Player(); players[id].pid = id; players[id].playing = false; players[id].local = false; } return players[id]; } public Player nextPlayer(Player prev) { int i; for (i=prev.pid+1; i= iconImages.length) return null; else return iconImages[ico]; } public void setIcon(int ico, Image img) { if (ico >= iconImages.length) { Image[] newico = new Image[ico+1]; System.arraycopy(iconImages, 0, newico, 0, iconImages.length); iconImages = newico; } iconImages[ico] = img; } public void setBitmap(int n, Bitmap bmp) { if (n >= bitmaps.length) { Bitmap[] newbmp = new Bitmap[n+1]; System.arraycopy(bitmaps, 0, newbmp, 0, bitmaps.length); bitmaps = newbmp; } bitmaps[n] = bmp; } // Sprites class Sprite { public int x, y, ico; public Image bkgnd; public final boolean draw(pclient client, Image backBuffer, Graphics backGC) { Image iconImage = client.getIcon(ico); if (iconImage == null) { ico = -1; return false; } int w = iconImage.getWidth(client); int h = iconImage.getHeight(client); if (bkgnd == null || bkgnd.getWidth(client) != w || bkgnd.getHeight(client) != h) { bkgnd = client.createImage(w, h); } bkgnd.getGraphics().drawImage(backBuffer, -x, -y, client); backGC.drawImage(iconImage, x, y, client); //System.out.println("Draw at "+Integer.toString(x)+", "+ // Integer.toString(y)); return true; } public final void erase(pclient client, Graphics backGC) { if (ico != -1) { //System.out.println("Erase at "+Integer.toString(x)+", "+ // Integer.toString(y)); backGC.drawImage(bkgnd, x, y, client); } } } // Playfield class Playfield { public static final int TASKBAR_HEIGHT = 48; public pclient client; public int pfwidth, pfheight; public Image backBuffer; public Sprite[] sprites = new Sprite[0]; public int numSprites = 0; public byte[] pendingBuffer = new byte[SocketDisplayer.UDP_BUF_SIZE]; public int pendingBufOfs = 0; public int pendingBufLen = 0; public byte[] spriteData = new byte[0]; public int validDataLen = 0; public Image tbCache; public AudioClip[] samples = new AudioClip[0]; public int[] playingSounds = new int[0]; Playfield(pclient aclient, int width, int height, Color bkgnd) { client = aclient; pfwidth = width; pfheight = height; backBuffer = createImage(width, height); Graphics backGC = backBuffer.getGraphics(); backGC.setColor(bkgnd); backGC.fillRect(0, 0, width, height); backGC.dispose(); client.resize(width, height); client.setBackground(bkgnd); int[] pixelData = new int[32*TASKBAR_HEIGHT]; int target = 0; for (int y=0; y= samples.length) { AudioClip[] newclip = new AudioClip[key+5]; System.arraycopy(samples, 0, newclip, 0, samples.length); samples = newclip; } if (samples[key] == null) { String filename = "sample.wav?code=" + Integer.toString(key); samples[key] = getAudioClip(getCodeBase(), filename); } else if (playingSounds.length > key && playingSounds[key] > 0) { currentSounds[key] = playingSounds[key] - 1; } else { samples[key].play(); currentSounds[key] = 4; } } base += 6; } playingSounds = currentSounds; pendingBufOfs = base; pendingBufLen = buflen - base; return old; } public synchronized int fetchSprites() { int valid; int count = (pendingBufLen < validDataLen) ? pendingBufLen : validDataLen; for (valid=0; valid spriteData.length) { spriteData = new byte[pendingBufLen+90]; valid = 0; } System.arraycopy(pendingBuffer, pendingBufOfs+valid, spriteData, valid, pendingBufLen-valid); return pendingBufLen; } public void paint(Graphics g) { int buflen = fetchSprites(); byte[] buffer = spriteData; int count = validDataLen / 6; int base = count * 6; int nspr = buflen / 6; Image tback; if (nspr > sprites.length) { Sprite[] newspr = new Sprite[nspr+15]; System.arraycopy(sprites, 0, newspr, 0, sprites.length); for (int i=sprites.length; i=count; i--) { sprites[i].erase(client, backGC); } // draw new sprites validDataLen = pendingBufLen; while (count < nspr) { Sprite s = sprites[count++]; int x = buffer[base+1]; s.x = (x & 0xFF) | (((int) buffer[base ]) << 8); int y = buffer[base+3]; s.y = (y & 0xFF) | (((int) buffer[base+2]) << 8); int ico = buffer[base+5]; s.ico = (ico & 0xFF) | (((int) buffer[base+4]) << 8); if (!s.draw(client, backBuffer, backGC)) { if (base < validDataLen) validDataLen = base; //System.out.println(Integer.toString(s.x)+';'+ // Integer.toString(s.y)+';'+ // Integer.toString(s.ico)); } base += 6; } numSprites = count; if (client.taskbarmode) tback = paintTaskbar(backGC); else tback = null; g.drawImage(backBuffer, 0, 0, client); if (tback != null) eraseTaskbar(backGC, tback); backGC.dispose(); } public Image paintTaskbar(Graphics g) { boolean animated = false; int y0 = pfheight - TASKBAR_HEIGHT; Image bkgnd = client.createImage(pfwidth, TASKBAR_HEIGHT); bkgnd.getGraphics().drawImage(backBuffer, 0, -y0, client); for (int i=0; i 0) { int index = (int)(f*icons.length); ico = icons[index % icons.length]; animated = true; } y = y0 + (TASKBAR_HEIGHT-h)/2; } } p.xmin = x; p.xmax = x + w; g.drawImage(ico, x, y, client); } if (animated) repaint(50); return bkgnd; } public void eraseTaskbar(Graphics g, Image bkgnd) { int y0 = pfheight - TASKBAR_HEIGHT; g.drawImage(bkgnd, 0, y0, client); } } // Socket listener class SocketListener extends Thread { public pclient client; public Socket socket; public InputStream socketInput; public OutputStream socketOutput; public static final String MSG_WELCOME = "Welcome to gamesrv.py(3) !\n"; public static final byte MSG_DEF_PLAYFIELD = (byte) 'p'; public static final byte MSG_DEF_KEY = (byte) 'k'; public static final byte MSG_DEF_ICON = (byte) 'r'; public static final byte MSG_DEF_BITMAP = (byte) 'm'; public static final byte MSG_DEF_SAMPLE = (byte) 'w'; public static final byte MSG_DEF_MUSIC = (byte) 'z'; public static final byte MSG_PLAY_MUSIC = (byte) 'Z'; public static final byte MSG_FADEOUT = (byte) 'f'; public static final byte MSG_PLAYER_JOIN = (byte) '+'; public static final byte MSG_PLAYER_KILL = (byte) '-'; public static final byte MSG_PLAYER_ICON = (byte) 'i'; public static final byte MSG_PING = (byte) 'g'; public static final byte MSG_PONG = (byte) 'G'; public static final byte MSG_INLINE_FRAME = (byte) '\\'; public static final byte CMSG_KEY = (byte) 'k'; public static final byte CMSG_ADD_PLAYER = (byte) '+'; public static final byte CMSG_REMOVE_PLAYER= (byte) '-'; public static final byte CMSG_UDP_PORT = (byte) '<'; public static final byte CMSG_ENABLE_SOUND = (byte) 's'; public static final byte CMSG_ENABLE_MUSIC = (byte) 'm'; public static final byte CMSG_PING = (byte) 'g'; public static final byte CMSG_PONG = (byte) 'G'; public static final byte CMSG_PLAYER_NAME = (byte) 'n'; public void connectionClosed() throws IOException { throw new IOException("connection closed"); } public void protocolError() throws IOException { throw new IOException("protocol error"); } SocketListener(pclient aclient, Socket asocket) throws IOException { setDaemon(true); byte[] msgWelcome = MSG_WELCOME.getBytes("UTF8"); client = aclient; socket = asocket; socketInput = socket.getInputStream(); socketOutput = socket.getOutputStream(); for (int i=0; i> 24); n = value >> 16; buffer[p++] = (byte)(((n&0x80) == 0) ? n&0x7F : n|0xFFFFFF80); n = value >> 8; buffer[p++] = (byte)(((n&0x80) == 0) ? n&0x7F : n|0xFFFFFF80); n = value; buffer[p++] = (byte)(((n&0x80) == 0) ? n&0x7F : n|0xFFFFFF80); } if (lastarg != null) { for (int i=0; i= end) return -1; byte msgcode = buffer[base++]; int[] args = new int[typecodes]; int repeatcount = 0; int nargs = 0; for (int i=0; i end) return -1; args[nargs++] = ((int) buffer[base++]) & 0xFF; } else if (c == (byte) 'l') { if (base+4 > end) return -1; int n4 = buffer[base++]; int n3 = buffer[base++]; n3 &= 0xFF; int n2 = buffer[base++]; n2 &= 0xFF; int n1 = buffer[base++]; n1 &= 0xFF; int value = n1 | (n2<<8) | (n3<<16) | (n4<<24); //System.out.println(n4); //System.out.println(n3); //System.out.println(n2); //System.out.println(n1); //System.out.println(value); //System.out.println(); args[nargs++] = value; } else if ((byte) '0' <= c && c <= (byte) '9') { repeatcount = repeatcount*10 + (c - (byte) '0'); } else if (c == (byte) 's') { if (base+repeatcount > end) return -1; args[nargs++] = base; args[nargs++] = repeatcount; base += repeatcount; repeatcount = 0; } else protocolError(); } //System.out.print("Message "); //System.out.print((char) msgcode); //for (int i=0; i 3) ? args[3] : -1; InputStream st = decompresser(buffer, dataofs, datalen); client.setBitmap(bmpcode, new Bitmap(client, st, colorkey)); break; } case MSG_PLAYER_ICON: { int pid = args[0]; int icocode = args[1]; Player p = client.getPlayer(pid); p.icon = client.getIcon(icocode); break; } case MSG_PING: { buffer[ofs+1+typecodes] = CMSG_PONG; sendData(buffer, ofs, base-ofs); if (nargs > 0 && !client.udpovertcp) { int udpkbytes = args[0]; /* switch to udp_over_tcp if the udp socket didn't receive at least 60% of the packets sent by the server, or if the socketdisplayer thread died */ if (sockdisplayer != null && !sockdisplayer.isAlive()) { showStatus("routing UDP traffic over TCP (no UDP socket)"); client.start_udp_over_tcp(); } else if (udpkbytes * 1024.0 * 0.60 > client.udpbytecounter) { client.udpsock_low += 1; if (client.udpsock_low >= 4) { double inp =client.udpbytecounter/(udpkbytes*1024.0); int loss = (int)(100.0*(1.0-inp)); showStatus("routing UDP traffic over TCP (" + Integer.toString(loss) + "% packet loss)"); client.start_udp_over_tcp(); } } else client.udpsock_low = 0; } break; } case MSG_PONG: { if (!client.taskbarfree && !client.taskbarmode) { client.taskbarfree = true; client.setTaskbar(true); } break; } case MSG_INLINE_FRAME: { if (client.uinflater != null) { int dataofs = args[0]; int datalen = args[1]; int len; byte[] pkt = client.uinflater_buffer; client.uinflater.setInput(buffer, dataofs, datalen); try { len = client.uinflater.inflate(pkt); } catch (DataFormatException e) { len = 0; } Playfield pf = client.playfield; if (len > 0 && pf != null) { client.uinflater_buffer = pf.setSprites(pkt, len); client.repaint(); } } break; } default: { System.err.println("Note: unknown message " + Byte.toString(msgcode)); break; } } return base; } public void run() { try { byte[] buffer = new byte[0xC000]; int begin = 0; int end = 0; try { while (true) { if (end + 0x6000 > buffer.length) { // compact buffer byte[] newbuf; end = end-begin; if (end + 0x8000 > buffer.length) newbuf = new byte[end + 0x8000]; else newbuf = buffer; System.arraycopy(buffer, begin, newbuf, 0, end); begin = 0; buffer = newbuf; } int count = socketInput.read(buffer, end, 0x6000); if (isInterrupted()) break; if (count <= 0) connectionClosed(); end += count; while ((count=decodeMessage(buffer, begin, end)) >= 0) { begin = count; } } } catch (InterruptedIOException e) { } socket.close(); } catch (IOException e) { client.debug(e); } } } // UDP Socket messages class SocketDisplayer extends Thread { public static final int UDP_BUF_SIZE = 0x10000; public pclient client; public DatagramSocket socket; SocketDisplayer(pclient aclient) { setDaemon(true); client = aclient; } public void run() { /* This thread may die early, typically because of JVM security restrictions. */ byte[] buffer = new byte[UDP_BUF_SIZE]; DatagramPacket pkt = new DatagramPacket(buffer, UDP_BUF_SIZE); try { { socket = new DatagramSocket(); int[] args = new int[1]; args[0] = socket.getLocalPort(); client.socklistener.sendMessage(SocketListener.CMSG_UDP_PORT, args); } try { while (true) { socket.receive(pkt); if (isInterrupted()) break; client.udpbytecounter += (double) pkt.getLength(); Playfield pf = client.playfield; if (pf != null) { pkt.setData(pf.setSprites(pkt.getData(), pkt.getLength())); pkt.setLength(UDP_BUF_SIZE); client.repaint(); } } } catch (InterruptedIOException e) { } socket.close(); } catch (IOException e) { client.debug(e); } } } // Applet methods public SocketListener socklistener = null; public SocketDisplayer sockdisplayer = null; public Playfield playfield = null; public void init() { try { Socket link; String param; String gamesrv = getParameter("gamesrv"); if (gamesrv == null) { gamesrv = getDocumentBase().getHost(); } param = getParameter("gameport"); if (param != null) { // direct TCP connexion to the game server link = new Socket(gamesrv, Integer.parseInt(param)); } else { // UCP query param = getParameter("port"); int port = (param != null) ? Integer.parseInt(param) : defaultPort; link = pickHost(gamesrv, port); } socklistener = new SocketListener(this, link); socklistener.start(); } catch (IOException e) { debug(e); } } public void destroy() { if (socklistener != null) { socklistener.interrupt(); socklistener = null; } } public void start() { enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); if (socklistener != null) { sockdisplayer = new SocketDisplayer(this); sockdisplayer.start(); } } public void stop() { if (sockdisplayer != null) { sockdisplayer.interrupt(); sockdisplayer = null; } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { Playfield pf = playfield; if (pf != null) { pf.paint(g); } else { int appWidth = getSize().width; int appHeight = getSize().height; g.clearRect(0, 0, appWidth, appHeight); } } protected void processKeyEvent(KeyEvent e) { int num; byte[] msg; e.consume(); switch (e.getID()) { case KeyEvent.KEY_PRESSED: num = e.getKeyCode(); break; case KeyEvent.KEY_RELEASED: num = -e.getKeyCode(); break; default: return; } msg = (byte[]) keycodes.get(new Integer(num)); if (msg != null && socklistener != null) { Player p = getPlayer(msg[0]); if (p.local) { try { socklistener.sendData(msg, 1, msg.length-1); } catch (IOException ioe) { debug(ioe); } return; } } if (keydefinition_k != null && e.getID() == KeyEvent.KEY_PRESSED) defineKey(num); } public void nextKey() { KeyName k = keydefinition_k; if (k == null) k = keys; else k = k.next; while (k != null && k.keyname.charAt(0) == '-') { k.newkeycode = 0; k = k.next; } keydefinition_k = k; } public void defineKey(int num) { KeyName k; for (k=keys; k!=keydefinition_k; k=k.next) if (k.newkeycode == num) return; k.newkeycode = num; nextKey(); if (keydefinition_k == null) { if (socklistener != null) { try { byte[] buffer; int[] args = new int[1]; args[0] = keydefinition_pid; socklistener.sendMessage(SocketListener.CMSG_ADD_PLAYER, args); String param = "player" + Integer.toString(keydefinition_pid); param = getParameter(param); if (param != null) { socklistener.sendMessageEx( SocketListener.CMSG_PLAYER_NAME, args, param); } args = new int[2]; args[0] = keydefinition_pid; for (k=keys; k!=null; k=k.next) { if (k.keyname.charAt(0) == '-') { String test = k.keyname.substring(1); for (KeyName r=keys; r!=null; r=r.next) if (r.keyname.equals(test)) k.newkeycode = -r.newkeycode; } args[1] = k.keyid; buffer = socklistener.codeMessage (1, SocketListener.CMSG_KEY, args, null); buffer[0] = (byte) keydefinition_pid; keycodes.put(new Integer(k.newkeycode), buffer); } } catch (IOException ioe) { debug(ioe); } } } repaint(); } protected void processMouseMotionEvent(MouseEvent e) { Playfield pf = playfield; if (pf != null) setTaskbar(e.getY() >= pf.pfheight - pf.TASKBAR_HEIGHT); e.consume(); } protected void processMouseEvent(MouseEvent e) { if (e.getID() != MouseEvent.MOUSE_PRESSED) { e.consume(); return; } requestFocus(); keydefinition_k = null; Playfield pf = playfield; if (pf != null && e.getY() >= pf.pfheight - pf.TASKBAR_HEIGHT) { int x = e.getX(); for (Player p=firstPlayer(); p!=null; p=nextPlayer(p)) if (p.xmin <= x && x < p.xmax) { if (p.local) { if (socklistener != null) { try { int[] args = new int[1]; args[0] = p.pid; socklistener.sendMessage (SocketListener.CMSG_REMOVE_PLAYER, args); } catch (IOException ioe) { debug(ioe); } } } else { keydefinition_pid = p.pid; nextKey(); } break; } } e.consume(); repaint(); } // UDP-over-TCP public double udpbytecounter = 0.0; public int udpsock_low = 0; public boolean udpovertcp = false; public Inflater uinflater = null; public byte[] uinflater_buffer = null; public void start_udp_over_tcp() { udpovertcp = true; int[] args = new int[1]; args[0] = 0; try { socklistener.sendMessage(SocketListener.CMSG_UDP_PORT, args); } catch (IOException e) { return; } uinflater_buffer = new byte[SocketDisplayer.UDP_BUF_SIZE]; uinflater = new Inflater(); if (sockdisplayer != null) { sockdisplayer.interrupt(); sockdisplayer = null; } } // // ImageObserver interface // public boolean imageUpdate(Image img, // int infoflags, // int x, // int y, // int width, // int height) { // return false; // } }