mirror of
https://github.com/abakh/nbsdgames
synced 2025-04-28 14:09:32 -04:00
1197 lines
42 KiB
Java
1197 lines
42 KiB
Java
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<length; i++) {
|
|
if (s.charAt(i) == delim)
|
|
count++;
|
|
}
|
|
String[] result = new String[count];
|
|
int origin = 0;
|
|
count = 0;
|
|
for (int i=0; i<length; i++) {
|
|
if (s.charAt(i) == delim) {
|
|
result[count++] = s.substring(origin, i);
|
|
origin = i+1;
|
|
}
|
|
}
|
|
result[count] = s.substring(origin);
|
|
return result;
|
|
}
|
|
|
|
public static String readLine(InputStream st) throws IOException {
|
|
String result = "";
|
|
int c;
|
|
while ((c = st.read()) != (byte) '\n') {
|
|
if (c < 0)
|
|
throw new IOException("unexpected end of stream");
|
|
result += (char) c;
|
|
}
|
|
return result.trim();
|
|
}
|
|
|
|
public static void readAll(InputStream st, byte[] buffer, int off, int len)
|
|
throws IOException {
|
|
while (len > 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<h; y++) {
|
|
readAll(st, lineBuffer, 0, w3);
|
|
for (int x3=0; x3<w3; x3+=3) {
|
|
int rgb = (((((int) lineBuffer[x3] ) & 0xFF) << 16) |
|
|
((((int) lineBuffer[x3+1]) & 0xFF) << 8 ) |
|
|
((((int) lineBuffer[x3+2]) & 0xFF) ));
|
|
if (rgb == keycol)
|
|
rgb = 0;
|
|
else
|
|
rgb |= 0xFF << 24;
|
|
pixelData[target++] = rgb;
|
|
}
|
|
}
|
|
}
|
|
|
|
public Image extractIcon(int x1, int y1, int w1, int h1) {
|
|
return createImage(new MemoryImageSource(w1, h1, pixelData,
|
|
x1 + y1*w, w));
|
|
}
|
|
}
|
|
|
|
// Host choosing
|
|
|
|
public static final int defaultPort = 8056;
|
|
public static final String pingMessage = "pclient-game-ping";
|
|
public static final String pongMessage = "server-game-pong";
|
|
|
|
public Socket pickHost(String udphostname, int port)
|
|
throws IOException {
|
|
InetAddress addr = InetAddress.getByName(udphostname);
|
|
byte[] msg = pingMessage.getBytes("UTF8");
|
|
DatagramPacket outp = new DatagramPacket(msg, msg.length, addr, port);
|
|
byte[] buffer = new byte[200];
|
|
DatagramPacket inp = new DatagramPacket(buffer, buffer.length);
|
|
DatagramSocket s = new DatagramSocket();
|
|
showStatus("Looking for a game server on "+udphostname+":"+
|
|
Integer.toString(port)+"...");
|
|
s.send(outp);
|
|
|
|
s.receive(inp);
|
|
String inpmsg = new String(inp.getData(), 0, inp.getLength(), "UTF8");
|
|
String[] data = splitString(inpmsg, ':');
|
|
//System.out.println(inpmsg);
|
|
//System.out.println(data.length);
|
|
//System.out.println("<<<"+data[0]+">>>");
|
|
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<players.length; i++)
|
|
if (players[i] != null)
|
|
return players[i];
|
|
return null;
|
|
}
|
|
|
|
public Player firstPlayer() {
|
|
int i;
|
|
for (i=0; i<players.length; i++)
|
|
if (players[i] != null)
|
|
return players[i];
|
|
return null;
|
|
}
|
|
|
|
public void setTaskbar(boolean nmode) {
|
|
if (taskbarfree) {
|
|
Player p;
|
|
boolean nolocalplayer = true;
|
|
for (p=firstPlayer(); p!=null; p=nextPlayer(p))
|
|
if (p.local)
|
|
nolocalplayer = false;
|
|
boolean prevmode = taskbarmode;
|
|
taskbarmode = nmode || nolocalplayer || keydefinition_k != null;
|
|
if (prevmode != taskbarmode)
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
public final Image getIcon(int ico) {
|
|
if (ico < 0 || ico >= 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<TASKBAR_HEIGHT; y++) {
|
|
int alpha = y * 256 / TASKBAR_HEIGHT;
|
|
int rgb = 0x8080FF | (alpha<<24);
|
|
for (int x=0; x<32; x++)
|
|
pixelData[target++] = rgb;
|
|
}
|
|
tbCache = createImage(new MemoryImageSource(32, TASKBAR_HEIGHT,
|
|
pixelData, 0, 32));
|
|
}
|
|
|
|
public synchronized byte[] setSprites(byte[] buf, int buflen) {
|
|
byte[] old = pendingBuffer;
|
|
pendingBuffer = buf;
|
|
//System.out.println("UDP packet for "+Integer.toString(buflen/6));
|
|
|
|
/* sound support -- no volume */
|
|
int base = 0;
|
|
int[] currentSounds = new int[samples.length];
|
|
while (base+6 <= buflen &&
|
|
pendingBuffer[base+4] == -1 &&
|
|
pendingBuffer[base+5] == -1) {
|
|
int key = pendingBuffer[base+1];
|
|
key = (key & 0xFF) | (((int) pendingBuffer[base]) << 8);
|
|
if (0 <= key && key <= 9999) { /* safety bound check */
|
|
if (key >= 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<count; valid++)
|
|
if (spriteData[valid] != pendingBuffer[pendingBufOfs+valid])
|
|
break;
|
|
validDataLen = valid;
|
|
|
|
if (pendingBufLen > 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<newspr.length; i++)
|
|
newspr[i] = new Sprite();
|
|
sprites = newspr;
|
|
}
|
|
|
|
//System.out.println("drawImage: -"+
|
|
// Integer.toString(numSprites-count)+
|
|
// " +"+Integer.toString(nspr-count));
|
|
|
|
// erase extra sprites
|
|
Graphics backGC = backBuffer.getGraphics();
|
|
for (int i=numSprites-1; 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<pfwidth; i+=32)
|
|
g.drawImage(tbCache, i, y0, client);
|
|
|
|
double f = 0.0015 * new Date().getTime();
|
|
f = f - (int) f;
|
|
double f2 = f * (1.0-f) * 4.0;
|
|
|
|
int lpos = 0;
|
|
int rpos = pfwidth;
|
|
for (Player p=firstPlayer(); p!=null; p=nextPlayer(p))
|
|
if (p.icon != null) {
|
|
Image ico = p.icon;
|
|
int x, y;
|
|
int w = ico.getWidth(client);
|
|
int h = ico.getHeight(client);
|
|
int dx = w * 5 / 3;
|
|
p.xmin = p.xmax = 0;
|
|
|
|
if (p.local) {
|
|
rpos -= dx;
|
|
int dy = TASKBAR_HEIGHT - h - 1;
|
|
x = rpos;
|
|
y = pfheight - h - (int)(dy*f2);
|
|
animated = true;
|
|
}
|
|
else {
|
|
lpos += dx;
|
|
if (p.playing)
|
|
continue;
|
|
x = lpos - w;
|
|
y = pfheight - h;
|
|
if (keydefinition_k != null &&
|
|
keydefinition_pid == p.pid) {
|
|
Image[] icons = keydefinition_k.keyicons;
|
|
if (icons.length > 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<msgWelcome.length; i++) {
|
|
int recv = socketInput.read();
|
|
if (recv != msgWelcome[i])
|
|
throw new IOException
|
|
("connected to something not a game server");
|
|
}
|
|
showStatus("Let's "+client.readLine(socketInput)+"!");
|
|
}
|
|
|
|
public void sendData(byte[] buffer, int ofs, int size)
|
|
throws IOException {
|
|
socketOutput.write(buffer, ofs, size);
|
|
}
|
|
|
|
public byte[] codeMessage(int p, byte msgcode, int[] args,
|
|
String lastarg) {
|
|
int bufsize = 1;
|
|
String mode = "";
|
|
for (int i=0; i<args.length; i++) {
|
|
mode = mode + "l";
|
|
bufsize = bufsize + 4;
|
|
}
|
|
if (lastarg != null) {
|
|
mode = mode + Integer.toString(lastarg.length()) + "s";
|
|
bufsize = bufsize + lastarg.length();
|
|
}
|
|
byte[] buffer = new byte[p + 1 + mode.length() + bufsize];
|
|
buffer[p++] = (byte) mode.length();
|
|
for (int i=0; i<mode.length(); i++) {
|
|
buffer[p++] = (byte) mode.charAt(i);
|
|
}
|
|
buffer[p++] = msgcode;
|
|
for (int i=0; i<args.length; i++) {
|
|
int n;
|
|
int value = args[i];
|
|
buffer[p++] = (byte) (value >> 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<lastarg.length(); i++) {
|
|
buffer[p++] = (byte) lastarg.charAt(i);
|
|
}
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
public void sendMessage(byte msgcode, int[] args) throws IOException {
|
|
sendMessageEx(msgcode, args, null);
|
|
}
|
|
|
|
public void sendMessageEx(byte msgcode, int[] args, String lastarg)
|
|
throws IOException {
|
|
byte[] buffer = codeMessage(0, msgcode, args, lastarg);
|
|
sendData(buffer, 0, buffer.length);
|
|
}
|
|
|
|
public int decodeMessage(byte[] buffer, int ofs, int end)
|
|
throws IOException {
|
|
if (ofs == end) return -1;
|
|
int typecodes = buffer[ofs]; typecodes &= 0xFF;
|
|
int base = ofs+1+typecodes;
|
|
if (base >= end) return -1;
|
|
byte msgcode = buffer[base++];
|
|
int[] args = new int[typecodes];
|
|
int repeatcount = 0;
|
|
int nargs = 0;
|
|
for (int i=0; i<typecodes; i++) {
|
|
byte c = buffer[ofs+1+i];
|
|
if (c == (byte) 'B') {
|
|
if (base+1 > 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<nargs; i++)
|
|
// System.out.print(" " + Integer.toString(args[i]));
|
|
//System.out.println();
|
|
|
|
switch (msgcode) {
|
|
|
|
case MSG_PLAYER_JOIN: {
|
|
int id = args[0];
|
|
int local = args[1];
|
|
Player p = client.getPlayer(id);
|
|
p.playing = true;
|
|
p.local = local != 0;
|
|
if (p.local)
|
|
client.setTaskbar(false);
|
|
break;
|
|
}
|
|
case MSG_PLAYER_KILL: {
|
|
int id = args[0];
|
|
Hashtable nkeycodes = new Hashtable();
|
|
Player p = client.getPlayer(id);
|
|
p.playing = false;
|
|
p.local = false;
|
|
for (Enumeration e = client.keycodes.keys();
|
|
e.hasMoreElements(); ) {
|
|
Object key = e.nextElement();
|
|
byte[] msg = (byte[]) keycodes.get(key);
|
|
if (msg[0] != id)
|
|
nkeycodes.put(key, msg);
|
|
}
|
|
client.keycodes = nkeycodes;
|
|
break;
|
|
}
|
|
case MSG_DEF_PLAYFIELD: {
|
|
int width = args[0];
|
|
int height = args[1];
|
|
Color bkgnd = client.makeColor(args[2]);
|
|
client.playfield = new pclient.Playfield(client, width, height,
|
|
bkgnd);
|
|
int[] singleint = new int[1];
|
|
singleint[0] = -1;
|
|
sendMessage(CMSG_ENABLE_SOUND, singleint);
|
|
sendMessage(CMSG_PING, new int[0]);
|
|
break;
|
|
}
|
|
case MSG_DEF_KEY: {
|
|
int i;
|
|
int nameofs = args[0];
|
|
int namelen = args[1];
|
|
int keyid = args[2];
|
|
int nicons = nargs - 3;
|
|
KeyName key = new KeyName();
|
|
key.keyname = new String(buffer, nameofs, namelen, "UTF8");
|
|
key.keyid = keyid;
|
|
key.keyicons= new Image[nicons];
|
|
for (i=0; i<nicons; i++)
|
|
key.keyicons[i] = client.getIcon(args[3+i]);
|
|
if (client.keys == null || keyid < client.keys.keyid) {
|
|
key.next = client.keys;
|
|
client.keys = key;
|
|
}
|
|
else {
|
|
KeyName k = client.keys;
|
|
while (k.next != null && k.next.keyid < keyid)
|
|
k = k.next;
|
|
key.next = k.next;
|
|
k.next = key;
|
|
}
|
|
break;
|
|
}
|
|
case MSG_DEF_ICON: {
|
|
int bmpcode = args[0];
|
|
int icocode = args[1];
|
|
int ix = args[2];
|
|
int iy = args[3];
|
|
int iw = args[4];
|
|
int ih = args[5];
|
|
if (bmpcode < client.bitmaps.length) {
|
|
Bitmap bmp = client.bitmaps[bmpcode];
|
|
if (bmp != null)
|
|
client.setIcon(icocode,
|
|
bmp.extractIcon(ix, iy, iw, ih));
|
|
}
|
|
break;
|
|
}
|
|
case MSG_DEF_BITMAP: {
|
|
int bmpcode = args[0];
|
|
int dataofs = args[1];
|
|
int datalen = args[2];
|
|
int colorkey = (nargs > 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;
|
|
// }
|
|
}
|