import java.lang.*; import java.net.*; import java.io.*; import java.awt.*; public class viewer extends java.applet.Applet implements Runnable { final static String DEFAULT_IMAGELIB = "ships.dir"; final static boolean DEBUG = false; final static int DEFAULT_DELAY = 160; final static int MINSLEEP = 10; final static int CHECKPOINT_INTERVAL = 500; /** game state */ NetrekGame game; NetrekImageLib lib; NetrekColors cols; DataInput data; FullyBufferedInputStream inbuf; GameStateChain checkpoints; byte data_buf[]; int data_len; /** game thread */ Thread gameRunner; boolean suspended; /** size of the display area */ Dimension size; /** filename of the recording */ String recording; String imagelib; boolean observer; boolean playing; double scale; long timesync; long delay = DEFAULT_DELAY; boolean running; boolean painted; int step; int packet, target, initial_target, view_mode; BorderLayout cl, mcl; Label stat, frame; Panel pan1, pan2, menu; Button brestart, bstop, bplay, bfast, bvfast; TextField fld; void getParameters() { recording = getParameter("RECORDING"); if (recording == null) return; imagelib = getParameter("IMAGELIB"); if (imagelib == null) imagelib = DEFAULT_IMAGELIB; String sdelay = getParameter("DELAY"); if (sdelay != null) try { delay = Integer.parseInt(sdelay); } catch (NumberFormatException ex) { } initial_target = -1; String starget = getParameter("START"); if (starget != null) try { initial_target = Integer.parseInt(starget); } catch (NumberFormatException ex) { } String obs = getParameter("OBSERVER"); observer = !(obs == null || obs.length() == 0 || obs == "OFF" || obs == "NO"); String view = getParameter("VIEW"); if (view == null) view = "UNSET"; if (view.equals("LOCAL") || view.equals("TACTICAL")) view_mode = NetrekGame.VIEWLOCAL; else if (view.equals("GALACTIC")) view_mode = NetrekGame.VIEWGALACTIC; else view_mode = NetrekGame.VIEWALL; } void setupLayout() { menu = new Panel(); mcl = new BorderLayout(); menu.setLayout(mcl); pan1 = new Panel(); stat = new MyLabel(" Initialization in progress... "); stat.setAlignment(Label.CENTER); pan1.add(stat); pan2 = new Panel(); brestart = new Button("Restart"); pan2.add(brestart); bstop = new Button("Stop"); pan2.add(bstop); bplay = new Button("Play"); pan2.add(bplay); bfast = new Button("Play x5"); pan2.add(bfast); bvfast = new Button("Play x25"); pan2.add(bvfast); frame = new MyLabel("Packet: 000000"); pan1.add(frame); pan1.add(new Label("Go To:")); fld = new TextField(8); pan1.add(fld); menu.add("North", pan1); menu.add("South", pan2); cl = new BorderLayout(); setLayout(cl); add("North", menu); add("South", game); } public void init() { if (DEBUG) { System.err.println("Total: " + Runtime.getRuntime().totalMemory()); System.err.println("Free: " + Runtime.getRuntime().freeMemory()); } lib = null; inbuf = null; step = 1; packet = 0; timesync = 0; target = -1; cols = new NetrekColors(); game = new NetrekGame(); game.disable(); getParameters(); game.SetViewMode(view_mode); game.SetObserver(observer); initCheckpoints(); setupLayout(); size = cl.preferredLayoutSize(this); resize(size.width, size.height); layout(); show(); } public void start() { if (gameRunner == null) { game.disable(); running = false; gameRunner = new Thread(this); gameRunner.start(); } else if (suspended) { suspended = false; gameRunner.resume(); } } public void stop() { if (gameRunner != null && gameRunner.isAlive()) { gameRunner.stop(); suspended = false; } gameRunner = null; } public void destroy() { if (gameRunner != null && gameRunner.isAlive()) gameRunner.stop(); gameRunner = null; playing = false; } public synchronized boolean action(Event evt, Object what) { if (evt.target == brestart) { if (gameRunner != null && gameRunner.isAlive()) gameRunner.stop(); playing = false; gameRunner = new Thread(this); gameRunner.start(); } else if (evt.target == bstop) { step = 0; } else if (evt.target == bplay) { step = 1; } else if (evt.target == bfast) { step = 5; } else if (evt.target == bvfast) { step = 25; } else if (evt.target == fld) { try { target = Integer.valueOf(fld.getText()).intValue(); if (gameRunner == null || !gameRunner.isAlive()) { gameRunner = new Thread(this); gameRunner.start(); } } catch (NumberFormatException ex) { target = -1; } } return false; } void loadImages() { if (lib == null) { stat.setText("Waiting for image data..."); redraw(); Runtime rt = Runtime.getRuntime(); if (DEBUG) { rt.gc(); System.err.println("Free before images: " + rt.freeMemory()); System.err.println("Total: " + rt.totalMemory()); } lib = new NetrekImageLib(cols, this); try { if (imagelib == null) { System.err.println("ImageLib URL not set. Lib not loaded."); lib = null; return; } URL datafile = new URL(getCodeBase(), imagelib); DataInputStream libdata = new DataInputStream(datafile.openStream()); while (libdata.available() == 0) try {Thread.sleep(100);} catch (InterruptedException e){} stat.setText("Loading images..."); redraw(); lib.LoadLibrary(libdata); System.err.println("Lib loaded."); } catch (IOException ex) { System.err.println("Lib not loaded."); lib = null; return; } if (DEBUG) { rt.gc(); System.err.println("Free after images: " + rt.freeMemory()); System.err.println("Total: " + rt.totalMemory()); } } } void loadRecording() { URL input; packet = 0; Runtime rt = Runtime.getRuntime(); if (DEBUG) { rt.gc(); System.err.println("Free before recording: " + rt.freeMemory()); System.err.println("Total: " + rt.totalMemory()); } if (inbuf != null && data != null) { try { inbuf.setPosition(0); } catch (IOException e) { } return; } try { stat.setText("Waiting for recording data..."); redraw(); input = new URL(getDocumentBase(), recording); InputStream ins = input.openStream(); while (ins.available() == 0) try {Thread.sleep(100);} catch (InterruptedException e){} stat.setText("Loading recording..."); redraw(); inbuf = new FullyBufferedInputStream(ins); ins.close(); data = new DataInputStream(inbuf); } catch (MalformedURLException e) { inbuf = null; data = null; } catch (IOException e) { inbuf = null; data = null; } if (DEBUG) { rt.gc(); System.err.println("Free after recording: " + rt.freeMemory()); System.err.println("Total: " + rt.totalMemory()); } } void initCheckpoints() { checkpoints = new GameStateChain(0, 0, game.GetGameState(), null); } void addCheckpoint() { Runtime rt = Runtime.getRuntime(); if (DEBUG) { rt.gc(); System.err.println("Free before checkpoint: " + rt.freeMemory()); System.err.println("Total: " + rt.totalMemory()); } if (packet <= checkpoints.packet) return; checkpoints = new GameStateChain(packet, inbuf.getPosition(), game.GetGameState(), checkpoints); if (DEBUG) { rt.gc(); System.err.println("Free after checkpoint: " + rt.freeMemory()); System.err.println("Total: " + rt.totalMemory()); } } void findClosestCheckpoint(int packet) { GameStateChain ptr; if (packet < 0 || checkpoints == null) return; for (ptr = checkpoints; ptr.packet > packet; ptr=ptr.next); if (packet < this.packet || this.packet < ptr.packet) { try inbuf.setPosition(ptr.filepos); catch (IOException ex) { return; } this.packet = ptr.packet; game.SetGameState(ptr.game_state.copy()); } } void handlePacket() { if (playing) { playing = game.step(data); packet++; if ((packet % CHECKPOINT_INTERVAL) == 0) addCheckpoint(); } } void executeStep() { int lastpack = packet; while (playing && packet - lastpack < step) handlePacket(); frame.setText("Packet: " + packet); timesync -= System.currentTimeMillis(); if (timesync <= MINSLEEP) timesync = MINSLEEP; try {Thread.sleep(timesync);} catch (InterruptedException e){} timesync = System.currentTimeMillis() + delay; if (packet != lastpack) { stat.setText("Replaying..."); game.DrawState(); } } void forwardToTarget() { if (!playing || target < 0) return; findClosestCheckpoint(target); while (playing && packet < target) { handlePacket(); if (((target - packet) % 50) == 0) { stat.setText("Fast Forward..."); frame.setText("Packet: " + packet); try {Thread.sleep(MINSLEEP);} catch (InterruptedException e){} } } target = -1; game.Refresh(); game.DrawState(); } protected void hook() { } public void run() { if (recording == null) { stat.setText("Game recording not specified"); repaint(); return; } if (!playing) { if (target < 0) target = initial_target; loadImages(); loadRecording(); game.restart(); game.SetObserver(observer); layout(); stat.setText("Initializing..."); redraw(); playing = true; } if (lib == null) { stat.setText("Could not load image library"); repaint(); return; } game.SetImageLib(lib); if (data == null) { stat.setText("Could not load recording..."); repaint(); return; } game.enable(); while (playing) { hook(); if (target >= 0) forwardToTarget(); executeStep(); } stat.setText("End of recording..."); repaint(); } public void paint(Graphics g) { super.paint(g); painted = true; } public void update(Graphics g) { paint(g); } void redraw() { painted = false; repaint(); while (!painted) try {Thread.sleep(5);} catch (InterruptedException e){} } } class GameStateChain { int packet; int filepos; NetrekState game_state; GameStateChain next; public GameStateChain(int packet, int filepos, NetrekState game_state, GameStateChain next) { this.packet = packet; this.filepos = filepos; this.game_state = game_state; this.next = next; } } class MyLabel extends java.awt.Label { Dimension sz; public MyLabel(String init) { super(init); sz = new Dimension(0, 0); } public Dimension preferredSize() { if (sz.width != 0) return sz; else return (sz = super.preferredSize()); } } class ByteArrayChain { int size; int data; byte buf[]; ByteArrayChain next; ByteArrayChain(int asize) { size = asize; data = 0; buf = new byte[size]; next = null; } } class FullyBufferedInputStream extends java.io.FilterInputStream { final int DEFAULT_RING_SIZE = 65536; int ring_size; int data_length; ByteArrayChain datachain, current; int ringnum, bytenum; public FullyBufferedInputStream(InputStream ins) throws IOException { super(ins); initDatachain(DEFAULT_RING_SIZE); } public FullyBufferedInputStream(InputStream ins, int new_ring_size) throws IOException { super(ins); initDatachain(new_ring_size); } void initDatachain(int new_ring_size) throws IOException { ring_size = new_ring_size; datachain = current = null; ringnum = 0; do { ByteArrayChain newring = new ByteArrayChain(ring_size); if (current == null) datachain = newring; else current.next = newring; current = newring; ringnum++; int len; for (current.data = 0; current.data= ptr.data) { ptr = ptr.next; newringnum++; pos -= ptr.data; if (ptr == null) throw new EOFException(); } ringnum = newringnum; bytenum = pos; current = ptr; } public int read() { while (bytenum >= current.data) { if (current.next == null) return -1; current = current.next; ringnum++; bytenum = 0; } int ret = current.buf[bytenum++]; if (ret < 0) ret += 256; return ret; } public int read(byte b[]) { return read(b, 0, b.length); } public int read(byte b[], int off, int len) { int num; for (num = 0; num < len;) { while (bytenum >= current.data) { if (current.next == null) if (num == 0) return -1; else return num; current = current.next; ringnum++; bytenum = 0; } for (;num < len && bytenum < current.data; num++) b[off++] = current.buf[bytenum++]; } return num; } public long skip(long n) { int num; for (num = 0; num < n;) { while (bytenum >= current.data) { if (current.next == null) if (num == 0) return -1; else return num; current = current.next; ringnum++; bytenum = 0; } int avail = current.data - bytenum; if (num + avail > n) avail = (int)n - num; num += avail; bytenum += avail; } return num; } public int available() { int ret = length() - getPosition(); if (ret < 0) ret = 0; return ret; } public void close() { data_length = 0; ringnum = bytenum = 0; current = datachain; current.data = 0; current.size = 0; current.buf = null; current.next = null; } } class PositionInputStream extends java.io.FilterInputStream { long pos = 0; public PositionInputStream(InputStream ins) { super(ins); } public long getPosition() { return pos; } public int read() throws IOException { int ret = in.read(); if (ret >= 0) pos++; return ret; } public int read(byte b[]) throws IOException { int ret = in.read(b); if (ret >= 0) pos+=ret; return ret; } public int read(byte b[], int off, int len) throws IOException { int ret = in.read(b, off, len); if (ret >= 0) pos+=ret; return ret; } public long skip(long n) throws IOException { long ret = in.skip(n); if (ret >= 0) pos+=ret; return ret; } }