diff --git a/pom.xml b/pom.xml index edbb368..a7b36e2 100644 --- a/pom.xml +++ b/pom.xml @@ -61,8 +61,10 @@ maven-surefire-plugin 2.22.2 - all - true + + + 10 + -Xms1g -Xmx1g diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index b2b594d..81004a2 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -1,38 +1,74 @@ package bwapi; +import com.sun.jna.platform.win32.Kernel32; + import java.util.Objects; /** * Client class to connect to the game with. */ public class BWClient { + private BWClientConfiguration configuration = new BWClientConfiguration(); private final BWEventListener eventListener; - private final boolean debugConnection; - private EventHandler handler; + private BotWrapper botWrapper; + private Client client; + private PerformanceMetrics performanceMetrics; public BWClient(final BWEventListener eventListener) { - this(eventListener, false); - } - - /** - * @param debugConnection set to `true` for more explicit error messages (might spam the terminal). - * `false` by default - */ - public BWClient(final BWEventListener eventListener, final boolean debugConnection) { Objects.requireNonNull(eventListener); - this.debugConnection = debugConnection; this.eventListener = eventListener; } /** * Get the {@link Game} instance of the currently running game. + * When running in asynchronous mode, this is the game from the bot's perspective, eg. potentially a previous frame. */ public Game getGame() { - return handler == null ? null : handler.getGame(); + return botWrapper == null ? null : botWrapper.getGame(); + } + + /** + * @return JBWAPI performance metrics. + */ + public PerformanceMetrics getPerformanceMetrics() { + return performanceMetrics; + } + + /** + * @return The current configuration + */ + public BWClientConfiguration getConfiguration() { + return configuration; + } + + /** + * @return Whether the current frame should be subject to timing. + */ + boolean doTime() { + return ! configuration.getUnlimitedFrameZero() || (client.isConnected() && client.liveClientData().gameData().getFrameCount() > 0); + } + + /** + * @return The number of frames between the one exposed to the bot and the most recent received by JBWAPI. + * This tracks the size of the frame buffer except when the game is paused (which results in multiple frames arriving with the same count). + */ + public int framesBehind() { + return botWrapper == null ? 0 : Math.max(0, client.liveClientData().gameData().getFrameCount() - getGame().getFrameCount()); } + /** + * For internal test use. + */ + Client getClient() { + return client; + } + + /** + * Start the game with default settings. + */ public void startGame() { - startGame(false); + BWClientConfiguration configuration = new BWClientConfiguration(); + startGame(configuration); } /** @@ -40,25 +76,67 @@ public void startGame() { * * @param autoContinue automatically continue playing the next game(s). false by default */ + @Deprecated public void startGame(boolean autoContinue) { - Client client = new Client(debugConnection); + BWClientConfiguration configuration = new BWClientConfiguration(); + configuration.withAutoContinue(autoContinue); + startGame(configuration); + } + + /** + * Start the game. + * + * @param gameConfiguration Settings for playing games with this client. + */ + public void startGame(BWClientConfiguration gameConfiguration) { + gameConfiguration.validateAndLock(); + this.configuration = gameConfiguration; + this.performanceMetrics = new PerformanceMetrics(configuration); + botWrapper = new BotWrapper(configuration, eventListener); + + // Use reduced priority to encourage Windows to give priority to StarCraft.exe/BWAPI. + // If BWAPI doesn't get priority, it may not detect completion of a frame on our end in timely fashion. + Thread.currentThread().setName("JBWAPI Client"); + if (configuration.getAsync()) { + Thread.currentThread().setPriority(4); + } + + if (client == null) { + client = new Client(this); + } client.reconnect(); - handler = new EventHandler(eventListener, client); do { - while (!getGame().isInGame()) { + ClientData.GameData liveGameData = client.liveClientData().gameData(); + while (!liveGameData.isInGame()) { if (!client.isConnected()) { return; } - client.update(handler); + client.sendFrameReceiveFrame(); + if (liveGameData.isInGame()) { + performanceMetrics = new PerformanceMetrics(configuration); + botWrapper.startNewGame(client.mapFile(), performanceMetrics); + } } - while (getGame().isInGame()) { - client.update(handler); + while (liveGameData.isInGame()) { + botWrapper.onFrame(); + performanceMetrics.getFlushSideEffects().time(() -> getGame().sideEffects.flushTo(liveGameData)); + performanceMetrics.getFrameDurationReceiveToSend().stopTiming(); + + client.sendFrameReceiveFrame(); if (!client.isConnected()) { System.out.println("Reconnecting..."); client.reconnect(); } } - } while (autoContinue); // lgtm [java/constant-loop-condition] + botWrapper.endGame(); + } while (configuration.getAutoContinue()); + } + + /** + * Provides a Client. Intended for test consumers only. + */ + void setClient(Client client) { + this.client = client; } } \ No newline at end of file diff --git a/src/main/java/bwapi/BWClientConfiguration.java b/src/main/java/bwapi/BWClientConfiguration.java new file mode 100644 index 0000000..c440fa8 --- /dev/null +++ b/src/main/java/bwapi/BWClientConfiguration.java @@ -0,0 +1,163 @@ +package bwapi; + +/** + * Configuration for constructing a BWClient + */ +public class BWClientConfiguration { + + /** + * Set to `true` for more explicit error messages (which might spam the terminal). + */ + public BWClientConfiguration withDebugConnection(boolean value) { + throwIfLocked(); + debugConnection = value; + return this; + } + public boolean getDebugConnection() { + return debugConnection; + } + private boolean debugConnection; + + /** + * When true, restarts the client loop when a game ends, allowing the client to play multiple games without restarting. + */ + public BWClientConfiguration withAutoContinue(boolean value) { + throwIfLocked(); + autoContinue = value; + return this; + } + public boolean getAutoContinue() { + return autoContinue; + } + private boolean autoContinue = false; + + /** + * Most bot tournaments allow bots to take an indefinite amount of time on frame #0 (the first frame of the game) to analyze the map and load data, + * as the bot has no prior access to BWAPI or game information. + * + * This flag indicates that taking arbitrarily long on frame zero is acceptable. + * Performance metrics omit the frame as an outlier. + * Asynchronous operation will block until the bot's event handlers are complete. + */ + public BWClientConfiguration withUnlimitedFrameZero(boolean value) { + throwIfLocked(); + unlimitedFrameZero = value; + return this; + } + public boolean getUnlimitedFrameZero() { + return unlimitedFrameZero; + } + private boolean unlimitedFrameZero = true; + + /** + * The maximum amount of time the bot is supposed to spend on a single frame. + * In asynchronous mode, JBWAPI will attempt to let the bot use up to this much time to process all frames before returning control to BWAPI. + * In synchronous mode, JBWAPI is not empowered to prevent the bot to exceed this amount, but will record overruns in performance metrics. + * Real-time human play typically uses the "fastest" game speed, which has 42.86ms (42,860ns) between frames. + */ + public BWClientConfiguration withMaxFrameDurationMs(int value) { + throwIfLocked(); + maxFrameDurationMs = value; + return this; + } + public int getMaxFrameDurationMs() { + return maxFrameDurationMs; + } + private int maxFrameDurationMs = 40; + + /** + * Runs the bot in asynchronous mode. Asynchronous mode helps attempt to ensure that the bot adheres to real-time performance constraints. + * + * Humans playing StarCraft (and some tournaments) expect bots to return commands within a certain period of time; ~42ms for humans ("fastesT" game speed), + * and some tournaments enforce frame-wise time limits (at time of writing, 55ms for COG and AIIDE; 85ms for SSCAIT). + * + * Asynchronous mode invokes bot event handlers in a separate thread, and if all event handlers haven't returned by a specified period of time, sends an + * returns control to StarCraft, allowing the game to proceed while the bot continues to step in the background. This increases the likelihood of meeting + * real-time performance requirements, while not fully guaranteeing it (subject to the whims of the JVM thread scheduler), at a cost of the bot possibly + * issuing commands later than intended, and a marginally larger memory footprint. + * + * Asynchronous mode is not compatible with latency compensation. Enabling asynchronous mode automatically disables latency compensation. + */ + public BWClientConfiguration withAsync(boolean value) { + throwIfLocked(); + async = value; + return this; + } + public boolean getAsync() { + return async; + } + private boolean async = false; + + /** + * The maximum number of frames to buffer while waiting on a bot. + * Each frame buffered adds about 33 megabytes to JBWAPI's memory footprint. + */ + public BWClientConfiguration withAsyncFrameBufferCapacity(int size) { + throwIfLocked(); + asyncFrameBufferCapacity = size; + return this; + } + public int getAsyncFrameBufferCapacity() { + return asyncFrameBufferCapacity; + } + private int asyncFrameBufferCapacity = 10; + + /** + * Enables thread-unsafe async mode. + * In this mode, the bot is allowed to read directly from shared memory until shared memory has been copied into the frame buffer, + * at wihch point the bot switches to using the frame buffer. + * This should enhance performance by allowing the bot to act while the frame is copied, but poses unidentified risk due to + * the non-thread-safe switc from shared memory reads to frame buffer reads. + */ + public BWClientConfiguration withAsyncUnsafe(boolean value) { + throwIfLocked(); + asyncUnsafe = value; + return this; + } + public boolean getAsyncUnsafe() { + return asyncUnsafe; + } + private boolean asyncUnsafe = false; + + /** + * Toggles verbose logging, particularly of synchronization steps. + */ + public BWClientConfiguration withLogVerbosely(boolean value) { + throwIfLocked(); + logVerbosely = value; + return this; + } + public boolean getLogVerbosely() { + return logVerbosely; + } + private boolean logVerbosely = false; + + /** + * Checks that the configuration is in a valid state. Throws an IllegalArgumentException if it isn't. + */ + void validateAndLock() { + if (asyncUnsafe && ! async) { + throw new IllegalArgumentException("asyncUnsafe mode needs async mode."); + } + if (async && maxFrameDurationMs < 0) { + throw new IllegalArgumentException("maxFrameDurationMs needs to be a non-negative number (it's how long JBWAPI waits for a bot response before returning control to BWAPI)."); + } + if (async && asyncFrameBufferCapacity < 1) { + throw new IllegalArgumentException("asyncFrameBufferCapacity needs to be a positive number (There needs to be at least one frame buffer)."); + } + locked = true; + } + private boolean locked = false; + + void throwIfLocked() { + if (locked) { + throw new RuntimeException("Configuration can not be modified after the game has started"); + } + } + + void log(String value) { + if (logVerbosely) { + System.out.println(value); + } + } +} \ No newline at end of file diff --git a/src/main/java/bwapi/BotWrapper.java b/src/main/java/bwapi/BotWrapper.java new file mode 100644 index 0000000..d94c58e --- /dev/null +++ b/src/main/java/bwapi/BotWrapper.java @@ -0,0 +1,257 @@ +package bwapi; + +import java.nio.ByteBuffer; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Manages invocation of bot event handlers + */ +class BotWrapper { + private final ClientData liveClientData = new ClientData(); + private final BWClientConfiguration configuration; + private final BWEventListener eventListener; + private final FrameBuffer frameBuffer; + private WrappedBuffer liveData; + private Game botGame; + private Thread botThread; + private boolean gameOver; + private PerformanceMetrics performanceMetrics; + private Throwable lastBotThrow; + private ReentrantLock lastBotThrowLock = new ReentrantLock(); + private ReentrantLock unsafeReadReadyLock = new ReentrantLock(); + private boolean unsafeReadReady = false; + + BotWrapper(BWClientConfiguration configuration, BWEventListener eventListener) { + this.configuration = configuration; + this.eventListener = eventListener; + frameBuffer = configuration.getAsync() ? new FrameBuffer(configuration) : null; + } + + /** + * Resets the BotWrapper for a new botGame. + */ + void startNewGame(WrappedBuffer liveData, PerformanceMetrics performanceMetrics) { + if (configuration.getAsync()) { + frameBuffer.initialize(liveData, performanceMetrics); + } + this.performanceMetrics = performanceMetrics; + botGame = new Game(); + botGame.setConfiguration(configuration); + botGame.botClientData().setBuffer(liveData); + liveClientData.setBuffer(liveData); + this.liveData = liveData; + botThread = null; + gameOver = false; + } + + /** + * @return The Game object used by the bot + * In asynchronous mode this Game object may point at a copy of a previous frame. + */ + Game getGame() { + return botGame; + } + + private boolean isUnsafeReadReady() { + unsafeReadReadyLock.lock(); + try { return unsafeReadReady; } + finally { unsafeReadReadyLock.unlock(); } + } + + private void setUnsafeReadReady(boolean value) { + unsafeReadReadyLock.lock(); + try { unsafeReadReady = value; } + finally { unsafeReadReadyLock.unlock(); } + frameBuffer.lockSize.lock(); + try { + frameBuffer.conditionSize.signalAll(); + } finally { + frameBuffer.lockSize.unlock(); + } + } + + /** + * Handles the arrival of a new frame from BWAPI + */ + void onFrame() { + if (configuration.getAsync()) { + configuration.log("Main: onFrame asynchronous start"); + long startNanos = System.nanoTime(); + long endNanos = startNanos + (long) configuration.getMaxFrameDurationMs() * 1000000; + if (botThread == null) { + configuration.log("Main: Starting bot thread"); + botThread = createBotThread(); + botThread.setName("JBWAPI Bot"); + // Reduced priority helps ensure that StarCraft.exe/BWAPI pick up on our frame completion in timely fashion + botThread.setPriority(3); + botThread.start(); + } + + // Unsafe mode: + // If the frame buffer is empty (meaning the bot must be idle) + // allow the bot to read directly from shared memory while we copy it over + if (configuration.getAsyncUnsafe()) { + frameBuffer.lockSize.lock(); + try { + if (frameBuffer.empty()) { + configuration.log("Main: Putting bot on live data"); + botGame.botClientData().setBuffer(liveData); + setUnsafeReadReady(true); + } else { + setUnsafeReadReady(false); + } + } finally { + frameBuffer.lockSize.unlock(); + } + } + + // Add a frame to buffer + // If buffer is full, will wait until it has capacity. + // Then wait for the buffer to empty or to run out of time in the frame. + int frame = liveClientData.gameData().getFrameCount(); + configuration.log("Main: Enqueuing frame #" + frame); + frameBuffer.enqueueFrame(); + + configuration.log("Main: Enqueued frame #" + frame); + if (frame > 0) { + performanceMetrics.getClientIdle().startTiming(); + } + frameBuffer.lockSize.lock(); + try { + while (!frameBuffer.empty()) { + // Unsafe mode: Move the bot off of live data onto the frame buffer + // This is the unsafe step! + // We don't synchronize on calls which access the buffer + // (to avoid tens of thousands of synchronized calls per frame) + // so there's no guarantee of safety here. + if (configuration.getAsyncUnsafe() && frameBuffer.size() == 1) { + configuration.log("Main: Weaning bot off live data"); + botGame.botClientData().setBuffer(frameBuffer.peek()); + } + + // Make bot exceptions fall through to the main thread. + Throwable lastThrow = getLastBotThrow(); + if (lastThrow != null) { + configuration.log("Main: Rethrowing bot throwable"); + throw new RuntimeException(lastThrow); + } + + if (configuration.getUnlimitedFrameZero() && frame == 0) { + configuration.log("Main: Waiting indefinitely on frame #" + frame); + frameBuffer.conditionSize.await(); + } else { + long remainingNanos = endNanos - System.nanoTime(); + if (remainingNanos <= 0) { + configuration.log("Main: Out of time in frame #" + frame); + break; + } + configuration.log("Main: Waiting " + remainingNanos / 1000000 + "ms for bot on frame #" + frame); + frameBuffer.conditionSize.awaitNanos(remainingNanos); + long excessNanos = Math.max(0, (System.nanoTime() - endNanos) / 1000000); + performanceMetrics.getExcessSleep().record(excessNanos); + } + } + } catch(InterruptedException ignored) { + } finally { + frameBuffer.lockSize.unlock(); + performanceMetrics.getClientIdle().stopTiming(); + configuration.log("Main: onFrame asynchronous end"); + } + } else { + configuration.log("Main: onFrame synchronous start"); + handleEvents(); + configuration.log("Main: onFrame synchronous end"); + } + } + + /** + * Allows an asynchronous bot time to finish operation + */ + void endGame() { + if (botThread != null) { + try { + botThread.join(); + } catch (InterruptedException ignored) {} + } + } + + Throwable getLastBotThrow() { + lastBotThrowLock.lock(); + Throwable output = lastBotThrow; + lastBotThrowLock.unlock(); + return output; + } + + private Thread createBotThread() { + return new Thread(() -> { + try { + configuration.log("Bot: Thread started"); + while (!gameOver) { + + boolean doUnsafeRead = false; + configuration.log("Bot: Ready for another frame"); + performanceMetrics.getBotIdle().startTiming(); + frameBuffer.lockSize.lock(); + try { + doUnsafeRead = isUnsafeReadReady(); + while ( ! doUnsafeRead && frameBuffer.empty()) { + configuration.log("Bot: Waiting for a frame"); + frameBuffer.conditionSize.awaitUninterruptibly(); + doUnsafeRead = isUnsafeReadReady(); + } + } finally { + frameBuffer.lockSize.unlock(); + } + performanceMetrics.getBotIdle().stopTiming(); + + if (doUnsafeRead) { + configuration.log("Bot: Reading live frame"); + setUnsafeReadReady(false); + } else { + configuration.log("Bot: Peeking next frame from buffer"); + botGame.botClientData().setBuffer(frameBuffer.peek()); + } + + configuration.log("Bot: Handling events on frame #" + botGame.getFrameCount()); + handleEvents(); + + configuration.log("Bot: Events handled. Dequeuing frame #" + botGame.getFrameCount()); + frameBuffer.dequeue(); + } + } catch (Throwable throwable) { + // Record the throw, + // Then allow the thread to terminate silently. + // The main thread will look for the stored throw. + lastBotThrowLock.lock(); + lastBotThrow = throwable; + lastBotThrowLock.unlock(); + + // Awaken any threads waiting on bot progress + while (!frameBuffer.empty()) { + frameBuffer.dequeue(); + } + } + }); + } + + private void handleEvents() { + ClientData.GameData botGameData = botGame.botClientData().gameData(); + + // Populate gameOver before invoking event handlers (in case the bot throws) + for (int i = 0; i < botGameData.getEventCount(); i++) { + gameOver = gameOver || botGameData.getEvents(i).getType() == EventType.MatchEnd; + } + + if (configuration.getAsync()) { + performanceMetrics.getFramesBehind().record(Math.max(1, frameBuffer.framesBuffered()) - 1); + } + + performanceMetrics.getBotResponse().timeIf( + ! gameOver && (botGameData.getFrameCount() > 0 || ! configuration.getUnlimitedFrameZero()), + () -> { + for (int i = 0; i < botGameData.getEventCount(); i++) { + EventHandler.operation(eventListener, botGame, botGameData.getEvents(i)); + } + }); + } +} diff --git a/src/main/java/bwapi/Bullet.java b/src/main/java/bwapi/Bullet.java index 7661220..e462500 100644 --- a/src/main/java/bwapi/Bullet.java +++ b/src/main/java/bwapi/Bullet.java @@ -27,7 +27,7 @@ * @see Game#getBullets * @see Bullet#exists */ -public class Bullet implements Comparable { +public final class Bullet implements Comparable { private final BulletData bulletData; private final int id; private final Game game; diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index 3cbf86f..bf2a633 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -25,9 +25,6 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; -import bwapi.ClientData.Command; -import bwapi.ClientData.GameData; -import bwapi.ClientData.Shape; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.Kernel32; @@ -42,42 +39,34 @@ interface MappingKernel extends Kernel32 { HANDLE OpenFileMapping(int desiredAccess, boolean inherit, String name); } - public interface EventHandler { - void operation(ClientData.Event event); - } - private static final int READ_WRITE = 0x1 | 0x2 | 0x4; - private static final int SUPPORTED_BWAPI_VERSION = 10003; - static final int MAX_COUNT = 19999; private ClientData clientData; - private ClientData.GameData gameData; + private BWClient bwClient; private boolean connected = false; private RandomAccessFile pipeObjectHandle = null; - private WrappedBuffer mapFileHandle = null; private WrappedBuffer gameTableFileHandle = null; + private WrappedBuffer mapFileHandle = null; - private boolean debugConnection = false; - - Client(boolean debugConnection) { - this.debugConnection = debugConnection; + Client(BWClient bwClient) { + this.bwClient = bwClient; } /** * For test purposes only */ Client(final WrappedBuffer buffer) { - clientData = new ClientData(buffer); - gameData = clientData.new GameData(0); + clientData = new ClientData(); + clientData.setBuffer(buffer); } - ClientData clientData() { + ClientData liveClientData() { return clientData; } - GameData gameData() { - return gameData; + WrappedBuffer mapFile() { + return mapFileHandle; } boolean isConnected() { @@ -90,8 +79,8 @@ void reconnect() { } } - void disconnect() { - if (debugConnection) { + private void disconnect() { + if (bwClient.getConfiguration().getDebugConnection()) { System.err.print("Disconnect called by: "); System.err.println(Thread.currentThread().getStackTrace()[2]); } @@ -108,9 +97,9 @@ void disconnect() { pipeObjectHandle = null; } - mapFileHandle = null; gameTableFileHandle = null; - gameData = null; + mapFileHandle = null; + clientData = null; connected = false; } @@ -123,6 +112,7 @@ boolean connect() { int serverProcID = -1; int gameTableIndex = -1; + // Expose the BWAPI list of games from shared memory via a ByteBuffer try { final Pointer gameTableView = Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE .OpenFileMapping(READ_WRITE, false, "Local\\bwapi_shared_memory_game_list"), READ_WRITE, @@ -138,7 +128,7 @@ boolean connect() { gameTable = new GameTable(gameTableFileHandle); } catch (Exception e) { System.err.println("Unable to map Game table."); - if (debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } return false; @@ -171,7 +161,7 @@ boolean connect() { pipeObjectHandle = new RandomAccessFile(communicationPipe, "rw"); } catch (Exception e) { System.err.println("Unable to open communications pipe: " + communicationPipe); - if (debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } gameTableFileHandle = null; @@ -179,14 +169,15 @@ boolean connect() { } System.out.println("Connected"); + // Expose the raw game data from shared memory via a ByteBuffer try { final Pointer mapFileView = Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE .OpenFileMapping(READ_WRITE, false, sharedMemoryName), READ_WRITE, - 0, 0, GameData.SIZE); - mapFileHandle = new WrappedBuffer(mapFileView, GameData.SIZE); + 0, 0, ClientData.GameData.SIZE); + mapFileHandle = new WrappedBuffer(mapFileView, ClientData.GameData.SIZE); } catch (Exception e) { System.err.println("Unable to open shared memory mapping: " + sharedMemoryName); - if (debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } pipeObjectHandle = null; @@ -194,20 +185,21 @@ boolean connect() { return false; } try { - clientData = new ClientData(mapFileHandle); - gameData = clientData.new GameData(0); - } catch (Exception e) { + clientData = new ClientData(); + clientData.setBuffer(mapFileHandle); + } + catch (Exception e) { System.err.println("Unable to map game data."); - if (debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } return false; } - if (SUPPORTED_BWAPI_VERSION != gameData.getClient_version()) { + if (SUPPORTED_BWAPI_VERSION != clientData.gameData().getClient_version()) { System.err.println("Error: Client and Server are not compatible!"); System.err.println("Client version: " + SUPPORTED_BWAPI_VERSION); - System.err.println("Server version: " + gameData.getClient_version()); + System.err.println("Server version: " + clientData.gameData().getClient_version()); disconnect(); sleep(2000); return false; @@ -218,7 +210,7 @@ boolean connect() { code = pipeObjectHandle.readByte(); } catch (Exception e) { System.err.println("Unable to read pipe object."); - if (debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } disconnect(); @@ -231,75 +223,65 @@ boolean connect() { return true; } - void update(final EventHandler handler) { - byte code = 1; + void sendFrameReceiveFrame() { + final PerformanceMetrics metrics = bwClient.getPerformanceMetrics(); + + // Tell BWAPI that we are done with the current frame + metrics.getFrameDurationReceiveToSend().stopTiming(); + if (bwClient.doTime()) { + metrics.getCommunicationSendToReceive().startTiming(); + metrics.getCommunicationSendToSent().startTiming(); + } try { - pipeObjectHandle.writeByte(code); - } catch (Exception e) { + // 1 is the "frame done" signal to BWAPI + pipeObjectHandle.writeByte(1); + } + catch (Exception e) { System.err.println("failed, disconnecting"); - if (debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } disconnect(); return; } - while (code != 2) { + metrics.getCommunicationSendToSent().stopTiming(); + metrics.getFrameDurationReceiveToSent().stopTiming(); + if (bwClient.doTime()) { + final int eventCount = clientData.gameData().getEventCount(); + metrics.getNumberOfEvents().record(eventCount); + metrics.getNumberOfEventsTimesDurationReceiveToSent().record(eventCount * metrics.getFrameDurationReceiveToSent().getRunningTotal().getLast()); + } + + // Listen for BWAPI to indicate that a new frame is ready + if (bwClient.doTime()) { + metrics.getCommunicationListenToReceive().startTiming(); + } + boolean frameReady = false; + while (!frameReady) { try { - code = pipeObjectHandle.readByte(); + // 2 is the "frame ready" signal from BWAPI + frameReady = pipeObjectHandle.readByte() == 2; } catch (Exception e) { System.err.println("failed, disconnecting"); - if (debugConnection) { + if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } disconnect(); - return; + break; } } - for (int i = 0; i < gameData.getEventCount(); i++) { - handler.operation(gameData.getEvents(i)); - } - } - - String eventString(final int s) { - return gameData.getEventStrings(s); - } - - int addString(final String string) { - int stringCount = gameData.getStringCount(); - if (stringCount >= MAX_COUNT) { - throw new IllegalStateException("Too many strings!"); - } - - gameData.setStringCount(stringCount + 1); - gameData.setStrings(stringCount, string); - return stringCount; - } - Shape addShape() { - int shapeCount = gameData.getShapeCount(); - if (shapeCount >= MAX_COUNT) { - throw new IllegalStateException("Too many shapes!"); - } - gameData.setShapeCount(shapeCount + 1); - return gameData.getShapes(shapeCount); - } + metrics.getCommunicationListenToReceive().stopTiming(); + metrics.getCommunicationSendToReceive().stopTiming(); - Command addCommand() { - final int commandCount = gameData.getCommandCount(); - if (commandCount >= MAX_COUNT) { - throw new IllegalStateException("Too many commands!"); + if (bwClient.doTime()) { + metrics.getFrameDurationReceiveToSend().startTiming(); + metrics.getFrameDurationReceiveToSent().startTiming(); } - gameData.setCommandCount(commandCount + 1); - return gameData.getCommands(commandCount); - } - - ClientData.UnitCommand addUnitCommand() { - int unitCommandCount = gameData.getUnitCommandCount(); - if (unitCommandCount >= MAX_COUNT) { - throw new IllegalStateException("Too many unit commands!"); + metrics.getFrameDurationReceiveToReceive().stopTiming(); + if (bwClient.doTime()) { + metrics.getFrameDurationReceiveToReceive().startTiming(); } - gameData.setUnitCommandCount(unitCommandCount + 1); - return gameData.getUnitCommands(unitCommandCount); } private void sleep(final int millis) { diff --git a/src/main/java/bwapi/ClientData.java b/src/main/java/bwapi/ClientData.java index 5dff380..5c957b2 100644 --- a/src/main/java/bwapi/ClientData.java +++ b/src/main/java/bwapi/ClientData.java @@ -1,1994 +1,2005 @@ -package bwapi; - -final class ClientData { - final WrappedBuffer buffer; - - ClientData(final WrappedBuffer buffer) { - this.buffer = buffer; - } - class UnitCommand { - static final int SIZE = 24; - private int myOffset; - public UnitCommand(int myOffset) { - this.myOffset = myOffset; - } - int getTid() { - int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setTid(int value) { - buffer.putInt(myOffset + 0, value); - } - int getUnitIndex() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setUnitIndex(int value) { - buffer.putInt(myOffset + 4, value); - } - int getTargetIndex() { - int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setTargetIndex(int value) { - buffer.putInt(myOffset + 8, value); - } - int getX() { - int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setX(int value) { - buffer.putInt(myOffset + 12, value); - } - int getY() { - int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setY(int value) { - buffer.putInt(myOffset + 16, value); - } - int getExtra() { - int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setExtra(int value) { - buffer.putInt(myOffset + 20, value); - } - } - class GameData { - static final int SIZE = 33017048; - private int myOffset; - public GameData(int myOffset) { - this.myOffset = myOffset; - } - int getClient_version() { - int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setClient_version(int value) { - buffer.putInt(myOffset + 0, value); - } - int getRevision() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setRevision(int value) { - buffer.putInt(myOffset + 4, value); - } - boolean isDebug() { - int offset = myOffset + 8; - return buffer.getByte(offset) != 0; - } - void setIsDebug(boolean value) { - buffer.putByte(myOffset + 8, (byte) (value ? 1 : 0)); - } - int getInstanceID() { - int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setInstanceID(int value) { - buffer.putInt(myOffset + 12, value); - } - int getBotAPM_noselects() { - int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setBotAPM_noselects(int value) { - buffer.putInt(myOffset + 16, value); - } - int getBotAPM_selects() { - int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setBotAPM_selects(int value) { - buffer.putInt(myOffset + 20, value); - } - int getForceCount() { - int offset = myOffset + 24; - return buffer.getInt(offset); - } - void setForceCount(int value) { - buffer.putInt(myOffset + 24, value); - } - ForceData getForces(int i) { - int offset = myOffset + 28 + 32 * 1 * i; - return new ForceData(offset); - } - int getPlayerCount() { - int offset = myOffset + 188; - return buffer.getInt(offset); - } - void setPlayerCount(int value) { - buffer.putInt(myOffset + 188, value); - } - PlayerData getPlayers(int i) { - int offset = myOffset + 192 + 5788 * 1 * i; - return new PlayerData(offset); - } - int getInitialUnitCount() { - int offset = myOffset + 69648; - return buffer.getInt(offset); - } - void setInitialUnitCount(int value) { - buffer.putInt(myOffset + 69648, value); - } - UnitData getUnits(int i) { - int offset = myOffset + 69656 + 336 * 1 * i; - return new UnitData(offset); - } - int getUnitArray(int i) { - int offset = myOffset + 3429656 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setUnitArray(int i, int value) { - buffer.putInt(myOffset + 3429656 + 4 * 1 * i, value); - } - BulletData getBullets(int i) { - int offset = myOffset + 3436456 + 80 * 1 * i; - return new BulletData(offset); - } - int getNukeDotCount() { - int offset = myOffset + 3444456; - return buffer.getInt(offset); - } - void setNukeDotCount(int value) { - buffer.putInt(myOffset + 3444456, value); - } - Position getNukeDots(int i) { - int offset = myOffset + 3444460 + 8 * 1 * i; - return new Position(offset); - } - int getGameType() { - int offset = myOffset + 3446060; - return buffer.getInt(offset); - } - void setGameType(int value) { - buffer.putInt(myOffset + 3446060, value); - } - int getLatency() { - int offset = myOffset + 3446064; - return buffer.getInt(offset); - } - void setLatency(int value) { - buffer.putInt(myOffset + 3446064, value); - } - int getLatencyFrames() { - int offset = myOffset + 3446068; - return buffer.getInt(offset); - } - void setLatencyFrames(int value) { - buffer.putInt(myOffset + 3446068, value); - } - int getLatencyTime() { - int offset = myOffset + 3446072; - return buffer.getInt(offset); - } - void setLatencyTime(int value) { - buffer.putInt(myOffset + 3446072, value); - } - int getRemainingLatencyFrames() { - int offset = myOffset + 3446076; - return buffer.getInt(offset); - } - void setRemainingLatencyFrames(int value) { - buffer.putInt(myOffset + 3446076, value); - } - int getRemainingLatencyTime() { - int offset = myOffset + 3446080; - return buffer.getInt(offset); - } - void setRemainingLatencyTime(int value) { - buffer.putInt(myOffset + 3446080, value); - } - boolean getHasLatCom() { - int offset = myOffset + 3446084; - return buffer.getByte(offset) != 0; - } - void setHasLatCom(boolean value) { - buffer.putByte(myOffset + 3446084, (byte) (value ? 1 : 0)); - } - boolean getHasGUI() { - int offset = myOffset + 3446085; - return buffer.getByte(offset) != 0; - } - void setHasGUI(boolean value) { - buffer.putByte(myOffset + 3446085, (byte) (value ? 1 : 0)); - } - int getReplayFrameCount() { - int offset = myOffset + 3446088; - return buffer.getInt(offset); - } - void setReplayFrameCount(int value) { - buffer.putInt(myOffset + 3446088, value); - } - int getRandomSeed() { - int offset = myOffset + 3446092; - return buffer.getInt(offset); - } - void setRandomSeed(int value) { - buffer.putInt(myOffset + 3446092, value); - } - int getFrameCount() { - int offset = myOffset + 3446096; - return buffer.getInt(offset); - } - void setFrameCount(int value) { - buffer.putInt(myOffset + 3446096, value); - } - int getElapsedTime() { - int offset = myOffset + 3446100; - return buffer.getInt(offset); - } - void setElapsedTime(int value) { - buffer.putInt(myOffset + 3446100, value); - } - int getCountdownTimer() { - int offset = myOffset + 3446104; - return buffer.getInt(offset); - } - void setCountdownTimer(int value) { - buffer.putInt(myOffset + 3446104, value); - } - int getFps() { - int offset = myOffset + 3446108; - return buffer.getInt(offset); - } - void setFps(int value) { - buffer.putInt(myOffset + 3446108, value); - } - double getAverageFPS() { - int offset = myOffset + 3446112; - return buffer.getDouble(offset); - } - void setAverageFPS(double value) { - buffer.putDouble(myOffset + 3446112, value); - } - int getMouseX() { - int offset = myOffset + 3446120; - return buffer.getInt(offset); - } - void setMouseX(int value) { - buffer.putInt(myOffset + 3446120, value); - } - int getMouseY() { - int offset = myOffset + 3446124; - return buffer.getInt(offset); - } - void setMouseY(int value) { - buffer.putInt(myOffset + 3446124, value); - } - boolean getMouseState(int i) { - int offset = myOffset + 3446128 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setMouseState(int i, boolean value) { - buffer.putByte(myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - boolean getKeyState(int i) { - int offset = myOffset + 3446131 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setKeyState(int i, boolean value) { - buffer.putByte(myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - int getScreenX() { - int offset = myOffset + 3446388; - return buffer.getInt(offset); - } - void setScreenX(int value) { - buffer.putInt(myOffset + 3446388, value); - } - int getScreenY() { - int offset = myOffset + 3446392; - return buffer.getInt(offset); - } - void setScreenY(int value) { - buffer.putInt(myOffset + 3446392, value); - } - boolean getFlags(int i) { - int offset = myOffset + 3446396 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setFlags(int i, boolean value) { - buffer.putByte(myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - int getMapWidth() { - int offset = myOffset + 3446400; - return buffer.getInt(offset); - } - void setMapWidth(int value) { - buffer.putInt(myOffset + 3446400, value); - } - int getMapHeight() { - int offset = myOffset + 3446404; - return buffer.getInt(offset); - } - void setMapHeight(int value) { - buffer.putInt(myOffset + 3446404, value); - } - String getMapFileName() { - int offset = myOffset + 3446408; - return buffer.getString(offset, 261); - } - void setMapFileName(String value) { - buffer.putString(myOffset + 3446408, 261, value); - } - String getMapPathName() { - int offset = myOffset + 3446669; - return buffer.getString(offset, 261); - } - void setMapPathName(String value) { - buffer.putString(myOffset + 3446669, 261, value); - } - String getMapName() { - int offset = myOffset + 3446930; - return buffer.getString(offset, 33); - } - void setMapName(String value) { - buffer.putString(myOffset + 3446930, 33, value); - } - String getMapHash() { - int offset = myOffset + 3446963; - return buffer.getString(offset, 41); - } - void setMapHash(String value) { - buffer.putString(myOffset + 3446963, 41, value); - } - int getGroundHeight(int i, int j) { - int offset = myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; - return buffer.getInt(offset); - } - void setGetGroundHeight(int i, int j, int value) { - buffer.putInt(myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); - } - boolean isWalkable(int i, int j) { - int offset = myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; - return buffer.getByte(offset) != 0; - } - void setIsWalkable(int i, int j, boolean value) { - buffer.putByte(myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); - } - boolean isBuildable(int i, int j) { - int offset = myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setIsBuildable(int i, int j, boolean value) { - buffer.putByte(myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } - boolean isVisible(int i, int j) { - int offset = myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setIsVisible(int i, int j, boolean value) { - buffer.putByte(myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } - boolean isExplored(int i, int j) { - int offset = myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setIsExplored(int i, int j, boolean value) { - buffer.putByte(myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } - boolean getHasCreep(int i, int j) { - int offset = myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setHasCreep(int i, int j, boolean value) { - buffer.putByte(myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } - boolean isOccupied(int i, int j) { - int offset = myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; - return buffer.getByte(offset) != 0; - } - void setIsOccupied(int i, int j, boolean value) { - buffer.putByte(myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); - } - short getMapTileRegionId(int i, int j) { - int offset = myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; - return buffer.getShort(offset); - } - void setMapTileRegionId(int i, int j, short value) { - buffer.putShort(myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); - } - short getMapSplitTilesMiniTileMask(int i) { - int offset = myOffset + 5216476 + 2 * 1 * i; - return buffer.getShort(offset); - } - void setMapSplitTilesMiniTileMask(int i, short value) { - buffer.putShort(myOffset + 5216476 + 2 * 1 * i, value); - } - short getMapSplitTilesRegion1(int i) { - int offset = myOffset + 5226476 + 2 * 1 * i; - return buffer.getShort(offset); - } - void setMapSplitTilesRegion1(int i, short value) { - buffer.putShort(myOffset + 5226476 + 2 * 1 * i, value); - } - short getMapSplitTilesRegion2(int i) { - int offset = myOffset + 5236476 + 2 * 1 * i; - return buffer.getShort(offset); - } - void setMapSplitTilesRegion2(int i, short value) { - buffer.putShort(myOffset + 5236476 + 2 * 1 * i, value); - } - int getRegionCount() { - int offset = myOffset + 5246476; - return buffer.getInt(offset); - } - void setRegionCount(int value) { - buffer.putInt(myOffset + 5246476, value); - } - RegionData getRegions(int i) { - int offset = myOffset + 5246480 + 1068 * 1 * i; - return new RegionData(offset); - } - int getStartLocationCount() { - int offset = myOffset + 10586480; - return buffer.getInt(offset); - } - void setStartLocationCount(int value) { - buffer.putInt(myOffset + 10586480, value); - } - Position getStartLocations(int i) { - int offset = myOffset + 10586484 + 8 * 1 * i; - return new Position(offset); - } - boolean isInGame() { - int offset = myOffset + 10586548; - return buffer.getByte(offset) != 0; - } - void setIsInGame(boolean value) { - buffer.putByte(myOffset + 10586548, (byte) (value ? 1 : 0)); - } - boolean isMultiplayer() { - int offset = myOffset + 10586549; - return buffer.getByte(offset) != 0; - } - void setIsMultiplayer(boolean value) { - buffer.putByte(myOffset + 10586549, (byte) (value ? 1 : 0)); - } - boolean isBattleNet() { - int offset = myOffset + 10586550; - return buffer.getByte(offset) != 0; - } - void setIsBattleNet(boolean value) { - buffer.putByte(myOffset + 10586550, (byte) (value ? 1 : 0)); - } - boolean isPaused() { - int offset = myOffset + 10586551; - return buffer.getByte(offset) != 0; - } - void setIsPaused(boolean value) { - buffer.putByte(myOffset + 10586551, (byte) (value ? 1 : 0)); - } - boolean isReplay() { - int offset = myOffset + 10586552; - return buffer.getByte(offset) != 0; - } - void setIsReplay(boolean value) { - buffer.putByte(myOffset + 10586552, (byte) (value ? 1 : 0)); - } - int getSelectedUnitCount() { - int offset = myOffset + 10586556; - return buffer.getInt(offset); - } - void setSelectedUnitCount(int value) { - buffer.putInt(myOffset + 10586556, value); - } - int getSelectedUnits(int i) { - int offset = myOffset + 10586560 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setSelectedUnits(int i, int value) { - buffer.putInt(myOffset + 10586560 + 4 * 1 * i, value); - } - int getSelf() { - int offset = myOffset + 10586608; - return buffer.getInt(offset); - } - void setSelf(int value) { - buffer.putInt(myOffset + 10586608, value); - } - int getEnemy() { - int offset = myOffset + 10586612; - return buffer.getInt(offset); - } - void setEnemy(int value) { - buffer.putInt(myOffset + 10586612, value); - } - int getNeutral() { - int offset = myOffset + 10586616; - return buffer.getInt(offset); - } - void setNeutral(int value) { - buffer.putInt(myOffset + 10586616, value); - } - int getEventCount() { - int offset = myOffset + 10586620; - return buffer.getInt(offset); - } - void setEventCount(int value) { - buffer.putInt(myOffset + 10586620, value); - } - Event getEvents(int i) { - int offset = myOffset + 10586624 + 12 * 1 * i; - return new Event(offset); - } - int getEventStringCount() { - int offset = myOffset + 10706624; - return buffer.getInt(offset); - } - void setEventStringCount(int value) { - buffer.putInt(myOffset + 10706624, value); - } - String getEventStrings(int i) { - int offset = myOffset + 10706628 + 1 * 256 * i; - return buffer.getString(offset, 256); - } - void setEventStrings(int i, String value) { - buffer.putString(myOffset + 10706628 + 1 * 256 * i, 256, value); - } - int getStringCount() { - int offset = myOffset + 10962628; - return buffer.getInt(offset); - } - void setStringCount(int value) { - buffer.putInt(myOffset + 10962628, value); - } - String getStrings(int i) { - int offset = myOffset + 10962632 + 1 * 1024 * i; - return buffer.getString(offset, 1024); - } - void setStrings(int i, String value) { - buffer.putString(myOffset + 10962632 + 1 * 1024 * i, 1024, value); - } - int getShapeCount() { - int offset = myOffset + 31442632; - return buffer.getInt(offset); - } - void setShapeCount(int value) { - buffer.putInt(myOffset + 31442632, value); - } - Shape getShapes(int i) { - int offset = myOffset + 31442636 + 40 * 1 * i; - return new Shape(offset); - } - int getCommandCount() { - int offset = myOffset + 32242636; - return buffer.getInt(offset); - } - void setCommandCount(int value) { - buffer.putInt(myOffset + 32242636, value); - } - Command getCommands(int i) { - int offset = myOffset + 32242640 + 12 * 1 * i; - return new Command(offset); - } - int getUnitCommandCount() { - int offset = myOffset + 32482640; - return buffer.getInt(offset); - } - void setUnitCommandCount(int value) { - buffer.putInt(myOffset + 32482640, value); - } - UnitCommand getUnitCommands(int i) { - int offset = myOffset + 32482644 + 24 * 1 * i; - return new UnitCommand(offset); - } - int getUnitSearchSize() { - int offset = myOffset + 32962644; - return buffer.getInt(offset); - } - void setUnitSearchSize(int value) { - buffer.putInt(myOffset + 32962644, value); - } - unitFinder getXUnitSearch(int i) { - int offset = myOffset + 32962648 + 8 * 1 * i; - return new unitFinder(offset); - } - unitFinder getYUnitSearch(int i) { - int offset = myOffset + 32989848 + 8 * 1 * i; - return new unitFinder(offset); - } - } - class Shape { - static final int SIZE = 40; - private int myOffset; - public Shape(int myOffset) { - this.myOffset = myOffset; - } - ShapeType getType() { - int offset = myOffset + 0; - return ShapeType.idToEnum[buffer.getInt(offset)]; - } - void setType(ShapeType value) { - buffer.putInt(myOffset + 0, value.id); - } - CoordinateType getCtype() { - int offset = myOffset + 4; - return CoordinateType.idToEnum[buffer.getInt(offset)]; - } - void setCtype(CoordinateType value) { - buffer.putInt(myOffset + 4, value.id); - } - int getX1() { - int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setX1(int value) { - buffer.putInt(myOffset + 8, value); - } - int getY1() { - int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setY1(int value) { - buffer.putInt(myOffset + 12, value); - } - int getX2() { - int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setX2(int value) { - buffer.putInt(myOffset + 16, value); - } - int getY2() { - int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setY2(int value) { - buffer.putInt(myOffset + 20, value); - } - int getExtra1() { - int offset = myOffset + 24; - return buffer.getInt(offset); - } - void setExtra1(int value) { - buffer.putInt(myOffset + 24, value); - } - int getExtra2() { - int offset = myOffset + 28; - return buffer.getInt(offset); - } - void setExtra2(int value) { - buffer.putInt(myOffset + 28, value); - } - int getColor() { - int offset = myOffset + 32; - return buffer.getInt(offset); - } - void setColor(int value) { - buffer.putInt(myOffset + 32, value); - } - boolean isSolid() { - int offset = myOffset + 36; - return buffer.getByte(offset) != 0; - } - void setIsSolid(boolean value) { - buffer.putByte(myOffset + 36, (byte) (value ? 1 : 0)); - } - } - class Command { - static final int SIZE = 12; - private int myOffset; - public Command(int myOffset) { - this.myOffset = myOffset; - } - CommandType getType() { - int offset = myOffset + 0; - return CommandType.idToEnum[buffer.getInt(offset)]; - } - void setType(CommandType value) { - buffer.putInt(myOffset + 0, value.id); - } - int getValue1() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setValue1(int value) { - buffer.putInt(myOffset + 4, value); - } - int getValue2() { - int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setValue2(int value) { - buffer.putInt(myOffset + 8, value); - } - } - class Position { - static final int SIZE = 8; - private int myOffset; - public Position(int myOffset) { - this.myOffset = myOffset; - } - int getX() { - int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setX(int value) { - buffer.putInt(myOffset + 0, value); - } - int getY() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setY(int value) { - buffer.putInt(myOffset + 4, value); - } - } - class Event { - static final int SIZE = 12; - private int myOffset; - public Event(int myOffset) { - this.myOffset = myOffset; - } - EventType getType() { - int offset = myOffset + 0; - return EventType.idToEnum[buffer.getInt(offset)]; - } - void setType(EventType value) { - buffer.putInt(myOffset + 0, value.id); - } - int getV1() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setV1(int value) { - buffer.putInt(myOffset + 4, value); - } - int getV2() { - int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setV2(int value) { - buffer.putInt(myOffset + 8, value); - } - } - class RegionData { - static final int SIZE = 1068; - private int myOffset; - public RegionData(int myOffset) { - this.myOffset = myOffset; - } - int getId() { - int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setId(int value) { - buffer.putInt(myOffset + 0, value); - } - int islandID() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setIslandID(int value) { - buffer.putInt(myOffset + 4, value); - } - int getCenter_x() { - int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setCenter_x(int value) { - buffer.putInt(myOffset + 8, value); - } - int getCenter_y() { - int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setCenter_y(int value) { - buffer.putInt(myOffset + 12, value); - } - int getPriority() { - int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setPriority(int value) { - buffer.putInt(myOffset + 16, value); - } - int getLeftMost() { - int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setLeftMost(int value) { - buffer.putInt(myOffset + 20, value); - } - int getRightMost() { - int offset = myOffset + 24; - return buffer.getInt(offset); - } - void setRightMost(int value) { - buffer.putInt(myOffset + 24, value); - } - int getTopMost() { - int offset = myOffset + 28; - return buffer.getInt(offset); - } - void setTopMost(int value) { - buffer.putInt(myOffset + 28, value); - } - int getBottomMost() { - int offset = myOffset + 32; - return buffer.getInt(offset); - } - void setBottomMost(int value) { - buffer.putInt(myOffset + 32, value); - } - int getNeighborCount() { - int offset = myOffset + 36; - return buffer.getInt(offset); - } - void setNeighborCount(int value) { - buffer.putInt(myOffset + 36, value); - } - int getNeighbors(int i) { - int offset = myOffset + 40 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setNeighbors(int i, int value) { - buffer.putInt(myOffset + 40 + 4 * 1 * i, value); - } - boolean isAccessible() { - int offset = myOffset + 1064; - return buffer.getByte(offset) != 0; - } - void setIsAccessible(boolean value) { - buffer.putByte(myOffset + 1064, (byte) (value ? 1 : 0)); - } - boolean isHigherGround() { - int offset = myOffset + 1065; - return buffer.getByte(offset) != 0; - } - void setIsHigherGround(boolean value) { - buffer.putByte(myOffset + 1065, (byte) (value ? 1 : 0)); - } - } - class ForceData { - static final int SIZE = 32; - private int myOffset; - public ForceData(int myOffset) { - this.myOffset = myOffset; - } - String getName() { - int offset = myOffset + 0; - return buffer.getString(offset, 32); - } - void setName(String value) { - buffer.putString(myOffset + 0, 32, value); - } - } - class PlayerData { - static final int SIZE = 5788; - private int myOffset; - public PlayerData(int myOffset) { - this.myOffset = myOffset; - } - String getName() { - int offset = myOffset + 0; - return buffer.getString(offset, 25); - } - void setName(String value) { - buffer.putString(myOffset + 0, 25, value); - } - int getRace() { - int offset = myOffset + 28; - return buffer.getInt(offset); - } - void setRace(int value) { - buffer.putInt(myOffset + 28, value); - } - int getType() { - int offset = myOffset + 32; - return buffer.getInt(offset); - } - void setType(int value) { - buffer.putInt(myOffset + 32, value); - } - int getForce() { - int offset = myOffset + 36; - return buffer.getInt(offset); - } - void setForce(int value) { - buffer.putInt(myOffset + 36, value); - } - boolean isAlly(int i) { - int offset = myOffset + 40 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsAlly(int i, boolean value) { - buffer.putByte(myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - boolean isEnemy(int i) { - int offset = myOffset + 52 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsEnemy(int i, boolean value) { - buffer.putByte(myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - boolean isNeutral() { - int offset = myOffset + 64; - return buffer.getByte(offset) != 0; - } - void setIsNeutral(boolean value) { - buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); - } - int getStartLocationX() { - int offset = myOffset + 68; - return buffer.getInt(offset); - } - void setStartLocationX(int value) { - buffer.putInt(myOffset + 68, value); - } - int getStartLocationY() { - int offset = myOffset + 72; - return buffer.getInt(offset); - } - void setStartLocationY(int value) { - buffer.putInt(myOffset + 72, value); - } - boolean isVictorious() { - int offset = myOffset + 76; - return buffer.getByte(offset) != 0; - } - void setIsVictorious(boolean value) { - buffer.putByte(myOffset + 76, (byte) (value ? 1 : 0)); - } - boolean isDefeated() { - int offset = myOffset + 77; - return buffer.getByte(offset) != 0; - } - void setIsDefeated(boolean value) { - buffer.putByte(myOffset + 77, (byte) (value ? 1 : 0)); - } - boolean getLeftGame() { - int offset = myOffset + 78; - return buffer.getByte(offset) != 0; - } - void setLeftGame(boolean value) { - buffer.putByte(myOffset + 78, (byte) (value ? 1 : 0)); - } - boolean isParticipating() { - int offset = myOffset + 79; - return buffer.getByte(offset) != 0; - } - void setIsParticipating(boolean value) { - buffer.putByte(myOffset + 79, (byte) (value ? 1 : 0)); - } - int getMinerals() { - int offset = myOffset + 80; - return buffer.getInt(offset); - } - void setMinerals(int value) { - buffer.putInt(myOffset + 80, value); - } - int getGas() { - int offset = myOffset + 84; - return buffer.getInt(offset); - } - void setGas(int value) { - buffer.putInt(myOffset + 84, value); - } - int getGatheredMinerals() { - int offset = myOffset + 88; - return buffer.getInt(offset); - } - void setGatheredMinerals(int value) { - buffer.putInt(myOffset + 88, value); - } - int getGatheredGas() { - int offset = myOffset + 92; - return buffer.getInt(offset); - } - void setGatheredGas(int value) { - buffer.putInt(myOffset + 92, value); - } - int getRepairedMinerals() { - int offset = myOffset + 96; - return buffer.getInt(offset); - } - void setRepairedMinerals(int value) { - buffer.putInt(myOffset + 96, value); - } - int getRepairedGas() { - int offset = myOffset + 100; - return buffer.getInt(offset); - } - void setRepairedGas(int value) { - buffer.putInt(myOffset + 100, value); - } - int getRefundedMinerals() { - int offset = myOffset + 104; - return buffer.getInt(offset); - } - void setRefundedMinerals(int value) { - buffer.putInt(myOffset + 104, value); - } - int getRefundedGas() { - int offset = myOffset + 108; - return buffer.getInt(offset); - } - void setRefundedGas(int value) { - buffer.putInt(myOffset + 108, value); - } - int getSupplyTotal(int i) { - int offset = myOffset + 112 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setSupplyTotal(int i, int value) { - buffer.putInt(myOffset + 112 + 4 * 1 * i, value); - } - int getSupplyUsed(int i) { - int offset = myOffset + 124 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setSupplyUsed(int i, int value) { - buffer.putInt(myOffset + 124 + 4 * 1 * i, value); - } - int getAllUnitCount(int i) { - int offset = myOffset + 136 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setAllUnitCount(int i, int value) { - buffer.putInt(myOffset + 136 + 4 * 1 * i, value); - } - int getVisibleUnitCount(int i) { - int offset = myOffset + 1072 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setVisibleUnitCount(int i, int value) { - buffer.putInt(myOffset + 1072 + 4 * 1 * i, value); - } - int getCompletedUnitCount(int i) { - int offset = myOffset + 2008 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setCompletedUnitCount(int i, int value) { - buffer.putInt(myOffset + 2008 + 4 * 1 * i, value); - } - int getDeadUnitCount(int i) { - int offset = myOffset + 2944 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setDeadUnitCount(int i, int value) { - buffer.putInt(myOffset + 2944 + 4 * 1 * i, value); - } - int getKilledUnitCount(int i) { - int offset = myOffset + 3880 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setKilledUnitCount(int i, int value) { - buffer.putInt(myOffset + 3880 + 4 * 1 * i, value); - } - int getUpgradeLevel(int i) { - int offset = myOffset + 4816 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setUpgradeLevel(int i, int value) { - buffer.putInt(myOffset + 4816 + 4 * 1 * i, value); - } - boolean getHasResearched(int i) { - int offset = myOffset + 5068 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setHasResearched(int i, boolean value) { - buffer.putByte(myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - boolean isResearching(int i) { - int offset = myOffset + 5115 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsResearching(int i, boolean value) { - buffer.putByte(myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - boolean isUpgrading(int i) { - int offset = myOffset + 5162 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsUpgrading(int i, boolean value) { - buffer.putByte(myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - int getColor() { - int offset = myOffset + 5228; - return buffer.getInt(offset); - } - void setColor(int value) { - buffer.putInt(myOffset + 5228, value); - } - int getTotalUnitScore() { - int offset = myOffset + 5232; - return buffer.getInt(offset); - } - void setTotalUnitScore(int value) { - buffer.putInt(myOffset + 5232, value); - } - int getTotalKillScore() { - int offset = myOffset + 5236; - return buffer.getInt(offset); - } - void setTotalKillScore(int value) { - buffer.putInt(myOffset + 5236, value); - } - int getTotalBuildingScore() { - int offset = myOffset + 5240; - return buffer.getInt(offset); - } - void setTotalBuildingScore(int value) { - buffer.putInt(myOffset + 5240, value); - } - int getTotalRazingScore() { - int offset = myOffset + 5244; - return buffer.getInt(offset); - } - void setTotalRazingScore(int value) { - buffer.putInt(myOffset + 5244, value); - } - int getCustomScore() { - int offset = myOffset + 5248; - return buffer.getInt(offset); - } - void setCustomScore(int value) { - buffer.putInt(myOffset + 5248, value); - } - int getMaxUpgradeLevel(int i) { - int offset = myOffset + 5252 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setMaxUpgradeLevel(int i, int value) { - buffer.putInt(myOffset + 5252 + 4 * 1 * i, value); - } - boolean isResearchAvailable(int i) { - int offset = myOffset + 5504 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsResearchAvailable(int i, boolean value) { - buffer.putByte(myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - boolean isUnitAvailable(int i) { - int offset = myOffset + 5551 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsUnitAvailable(int i, boolean value) { - buffer.putByte(myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - } - class BulletData { - static final int SIZE = 80; - private int myOffset; - public BulletData(int myOffset) { - this.myOffset = myOffset; - } - int getId() { - int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setId(int value) { - buffer.putInt(myOffset + 0, value); - } - int getPlayer() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setPlayer(int value) { - buffer.putInt(myOffset + 4, value); - } - int getType() { - int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setType(int value) { - buffer.putInt(myOffset + 8, value); - } - int getSource() { - int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setSource(int value) { - buffer.putInt(myOffset + 12, value); - } - int getPositionX() { - int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setPositionX(int value) { - buffer.putInt(myOffset + 16, value); - } - int getPositionY() { - int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setPositionY(int value) { - buffer.putInt(myOffset + 20, value); - } - double getAngle() { - int offset = myOffset + 24; - return buffer.getDouble(offset); - } - void setAngle(double value) { - buffer.putDouble(myOffset + 24, value); - } - double getVelocityX() { - int offset = myOffset + 32; - return buffer.getDouble(offset); - } - void setVelocityX(double value) { - buffer.putDouble(myOffset + 32, value); - } - double getVelocityY() { - int offset = myOffset + 40; - return buffer.getDouble(offset); - } - void setVelocityY(double value) { - buffer.putDouble(myOffset + 40, value); - } - int getTarget() { - int offset = myOffset + 48; - return buffer.getInt(offset); - } - void setTarget(int value) { - buffer.putInt(myOffset + 48, value); - } - int getTargetPositionX() { - int offset = myOffset + 52; - return buffer.getInt(offset); - } - void setTargetPositionX(int value) { - buffer.putInt(myOffset + 52, value); - } - int getTargetPositionY() { - int offset = myOffset + 56; - return buffer.getInt(offset); - } - void setTargetPositionY(int value) { - buffer.putInt(myOffset + 56, value); - } - int getRemoveTimer() { - int offset = myOffset + 60; - return buffer.getInt(offset); - } - void setRemoveTimer(int value) { - buffer.putInt(myOffset + 60, value); - } - boolean getExists() { - int offset = myOffset + 64; - return buffer.getByte(offset) != 0; - } - void setExists(boolean value) { - buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); - } - boolean isVisible(int i) { - int offset = myOffset + 65 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsVisible(int i, boolean value) { - buffer.putByte(myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - } - class unitFinder { - static final int SIZE = 8; - private int myOffset; - public unitFinder(int myOffset) { - this.myOffset = myOffset; - } - int getUnitIndex() { - int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setUnitIndex(int value) { - buffer.putInt(myOffset + 0, value); - } - int getSearchValue() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setSearchValue(int value) { - buffer.putInt(myOffset + 4, value); - } - } - class UnitData { - static final int SIZE = 336; - private int myOffset; - public UnitData(int myOffset) { - this.myOffset = myOffset; - } - int getClearanceLevel() { - int offset = myOffset + 0; - return buffer.getInt(offset); - } - void setClearanceLevel(int value) { - buffer.putInt(myOffset + 0, value); - } - int getId() { - int offset = myOffset + 4; - return buffer.getInt(offset); - } - void setId(int value) { - buffer.putInt(myOffset + 4, value); - } - int getPlayer() { - int offset = myOffset + 8; - return buffer.getInt(offset); - } - void setPlayer(int value) { - buffer.putInt(myOffset + 8, value); - } - int getType() { - int offset = myOffset + 12; - return buffer.getInt(offset); - } - void setType(int value) { - buffer.putInt(myOffset + 12, value); - } - int getPositionX() { - int offset = myOffset + 16; - return buffer.getInt(offset); - } - void setPositionX(int value) { - buffer.putInt(myOffset + 16, value); - } - int getPositionY() { - int offset = myOffset + 20; - return buffer.getInt(offset); - } - void setPositionY(int value) { - buffer.putInt(myOffset + 20, value); - } - double getAngle() { - int offset = myOffset + 24; - return buffer.getDouble(offset); - } - void setAngle(double value) { - buffer.putDouble(myOffset + 24, value); - } - double getVelocityX() { - int offset = myOffset + 32; - return buffer.getDouble(offset); - } - void setVelocityX(double value) { - buffer.putDouble(myOffset + 32, value); - } - double getVelocityY() { - int offset = myOffset + 40; - return buffer.getDouble(offset); - } - void setVelocityY(double value) { - buffer.putDouble(myOffset + 40, value); - } - int getHitPoints() { - int offset = myOffset + 48; - return buffer.getInt(offset); - } - void setHitPoints(int value) { - buffer.putInt(myOffset + 48, value); - } - int getLastHitPoints() { - int offset = myOffset + 52; - return buffer.getInt(offset); - } - void setLastHitPoints(int value) { - buffer.putInt(myOffset + 52, value); - } - int getShields() { - int offset = myOffset + 56; - return buffer.getInt(offset); - } - void setShields(int value) { - buffer.putInt(myOffset + 56, value); - } - int getEnergy() { - int offset = myOffset + 60; - return buffer.getInt(offset); - } - void setEnergy(int value) { - buffer.putInt(myOffset + 60, value); - } - int getResources() { - int offset = myOffset + 64; - return buffer.getInt(offset); - } - void setResources(int value) { - buffer.putInt(myOffset + 64, value); - } - int getResourceGroup() { - int offset = myOffset + 68; - return buffer.getInt(offset); - } - void setResourceGroup(int value) { - buffer.putInt(myOffset + 68, value); - } - int getKillCount() { - int offset = myOffset + 72; - return buffer.getInt(offset); - } - void setKillCount(int value) { - buffer.putInt(myOffset + 72, value); - } - int getAcidSporeCount() { - int offset = myOffset + 76; - return buffer.getInt(offset); - } - void setAcidSporeCount(int value) { - buffer.putInt(myOffset + 76, value); - } - int getScarabCount() { - int offset = myOffset + 80; - return buffer.getInt(offset); - } - void setScarabCount(int value) { - buffer.putInt(myOffset + 80, value); - } - int getInterceptorCount() { - int offset = myOffset + 84; - return buffer.getInt(offset); - } - void setInterceptorCount(int value) { - buffer.putInt(myOffset + 84, value); - } - int getSpiderMineCount() { - int offset = myOffset + 88; - return buffer.getInt(offset); - } - void setSpiderMineCount(int value) { - buffer.putInt(myOffset + 88, value); - } - int getGroundWeaponCooldown() { - int offset = myOffset + 92; - return buffer.getInt(offset); - } - void setGroundWeaponCooldown(int value) { - buffer.putInt(myOffset + 92, value); - } - int getAirWeaponCooldown() { - int offset = myOffset + 96; - return buffer.getInt(offset); - } - void setAirWeaponCooldown(int value) { - buffer.putInt(myOffset + 96, value); - } - int getSpellCooldown() { - int offset = myOffset + 100; - return buffer.getInt(offset); - } - void setSpellCooldown(int value) { - buffer.putInt(myOffset + 100, value); - } - int getDefenseMatrixPoints() { - int offset = myOffset + 104; - return buffer.getInt(offset); - } - void setDefenseMatrixPoints(int value) { - buffer.putInt(myOffset + 104, value); - } - int getDefenseMatrixTimer() { - int offset = myOffset + 108; - return buffer.getInt(offset); - } - void setDefenseMatrixTimer(int value) { - buffer.putInt(myOffset + 108, value); - } - int getEnsnareTimer() { - int offset = myOffset + 112; - return buffer.getInt(offset); - } - void setEnsnareTimer(int value) { - buffer.putInt(myOffset + 112, value); - } - int getIrradiateTimer() { - int offset = myOffset + 116; - return buffer.getInt(offset); - } - void setIrradiateTimer(int value) { - buffer.putInt(myOffset + 116, value); - } - int getLockdownTimer() { - int offset = myOffset + 120; - return buffer.getInt(offset); - } - void setLockdownTimer(int value) { - buffer.putInt(myOffset + 120, value); - } - int getMaelstromTimer() { - int offset = myOffset + 124; - return buffer.getInt(offset); - } - void setMaelstromTimer(int value) { - buffer.putInt(myOffset + 124, value); - } - int getOrderTimer() { - int offset = myOffset + 128; - return buffer.getInt(offset); - } - void setOrderTimer(int value) { - buffer.putInt(myOffset + 128, value); - } - int getPlagueTimer() { - int offset = myOffset + 132; - return buffer.getInt(offset); - } - void setPlagueTimer(int value) { - buffer.putInt(myOffset + 132, value); - } - int getRemoveTimer() { - int offset = myOffset + 136; - return buffer.getInt(offset); - } - void setRemoveTimer(int value) { - buffer.putInt(myOffset + 136, value); - } - int getStasisTimer() { - int offset = myOffset + 140; - return buffer.getInt(offset); - } - void setStasisTimer(int value) { - buffer.putInt(myOffset + 140, value); - } - int getStimTimer() { - int offset = myOffset + 144; - return buffer.getInt(offset); - } - void setStimTimer(int value) { - buffer.putInt(myOffset + 144, value); - } - int getBuildType() { - int offset = myOffset + 148; - return buffer.getInt(offset); - } - void setBuildType(int value) { - buffer.putInt(myOffset + 148, value); - } - int getTrainingQueueCount() { - int offset = myOffset + 152; - return buffer.getInt(offset); - } - void setTrainingQueueCount(int value) { - buffer.putInt(myOffset + 152, value); - } - int getTrainingQueue(int i) { - int offset = myOffset + 156 + 4 * 1 * i; - return buffer.getInt(offset); - } - void setTrainingQueue(int i, int value) { - buffer.putInt(myOffset + 156 + 4 * 1 * i, value); - } - int getTech() { - int offset = myOffset + 176; - return buffer.getInt(offset); - } - void setTech(int value) { - buffer.putInt(myOffset + 176, value); - } - int getUpgrade() { - int offset = myOffset + 180; - return buffer.getInt(offset); - } - void setUpgrade(int value) { - buffer.putInt(myOffset + 180, value); - } - int getRemainingBuildTime() { - int offset = myOffset + 184; - return buffer.getInt(offset); - } - void setRemainingBuildTime(int value) { - buffer.putInt(myOffset + 184, value); - } - int getRemainingTrainTime() { - int offset = myOffset + 188; - return buffer.getInt(offset); - } - void setRemainingTrainTime(int value) { - buffer.putInt(myOffset + 188, value); - } - int getRemainingResearchTime() { - int offset = myOffset + 192; - return buffer.getInt(offset); - } - void setRemainingResearchTime(int value) { - buffer.putInt(myOffset + 192, value); - } - int getRemainingUpgradeTime() { - int offset = myOffset + 196; - return buffer.getInt(offset); - } - void setRemainingUpgradeTime(int value) { - buffer.putInt(myOffset + 196, value); - } - int getBuildUnit() { - int offset = myOffset + 200; - return buffer.getInt(offset); - } - void setBuildUnit(int value) { - buffer.putInt(myOffset + 200, value); - } - int getTarget() { - int offset = myOffset + 204; - return buffer.getInt(offset); - } - void setTarget(int value) { - buffer.putInt(myOffset + 204, value); - } - int getTargetPositionX() { - int offset = myOffset + 208; - return buffer.getInt(offset); - } - void setTargetPositionX(int value) { - buffer.putInt(myOffset + 208, value); - } - int getTargetPositionY() { - int offset = myOffset + 212; - return buffer.getInt(offset); - } - void setTargetPositionY(int value) { - buffer.putInt(myOffset + 212, value); - } - int getOrder() { - int offset = myOffset + 216; - return buffer.getInt(offset); - } - void setOrder(int value) { - buffer.putInt(myOffset + 216, value); - } - int getOrderTarget() { - int offset = myOffset + 220; - return buffer.getInt(offset); - } - void setOrderTarget(int value) { - buffer.putInt(myOffset + 220, value); - } - int getOrderTargetPositionX() { - int offset = myOffset + 224; - return buffer.getInt(offset); - } - void setOrderTargetPositionX(int value) { - buffer.putInt(myOffset + 224, value); - } - int getOrderTargetPositionY() { - int offset = myOffset + 228; - return buffer.getInt(offset); - } - void setOrderTargetPositionY(int value) { - buffer.putInt(myOffset + 228, value); - } - int getSecondaryOrder() { - int offset = myOffset + 232; - return buffer.getInt(offset); - } - void setSecondaryOrder(int value) { - buffer.putInt(myOffset + 232, value); - } - int getRallyPositionX() { - int offset = myOffset + 236; - return buffer.getInt(offset); - } - void setRallyPositionX(int value) { - buffer.putInt(myOffset + 236, value); - } - int getRallyPositionY() { - int offset = myOffset + 240; - return buffer.getInt(offset); - } - void setRallyPositionY(int value) { - buffer.putInt(myOffset + 240, value); - } - int getRallyUnit() { - int offset = myOffset + 244; - return buffer.getInt(offset); - } - void setRallyUnit(int value) { - buffer.putInt(myOffset + 244, value); - } - int getAddon() { - int offset = myOffset + 248; - return buffer.getInt(offset); - } - void setAddon(int value) { - buffer.putInt(myOffset + 248, value); - } - int getNydusExit() { - int offset = myOffset + 252; - return buffer.getInt(offset); - } - void setNydusExit(int value) { - buffer.putInt(myOffset + 252, value); - } - int getPowerUp() { - int offset = myOffset + 256; - return buffer.getInt(offset); - } - void setPowerUp(int value) { - buffer.putInt(myOffset + 256, value); - } - int getTransport() { - int offset = myOffset + 260; - return buffer.getInt(offset); - } - void setTransport(int value) { - buffer.putInt(myOffset + 260, value); - } - int getCarrier() { - int offset = myOffset + 264; - return buffer.getInt(offset); - } - void setCarrier(int value) { - buffer.putInt(myOffset + 264, value); - } - int getHatchery() { - int offset = myOffset + 268; - return buffer.getInt(offset); - } - void setHatchery(int value) { - buffer.putInt(myOffset + 268, value); - } - boolean getExists() { - int offset = myOffset + 272; - return buffer.getByte(offset) != 0; - } - void setExists(boolean value) { - buffer.putByte(myOffset + 272, (byte) (value ? 1 : 0)); - } - boolean getHasNuke() { - int offset = myOffset + 273; - return buffer.getByte(offset) != 0; - } - void setHasNuke(boolean value) { - buffer.putByte(myOffset + 273, (byte) (value ? 1 : 0)); - } - boolean isAccelerating() { - int offset = myOffset + 274; - return buffer.getByte(offset) != 0; - } - void setIsAccelerating(boolean value) { - buffer.putByte(myOffset + 274, (byte) (value ? 1 : 0)); - } - boolean isAttacking() { - int offset = myOffset + 275; - return buffer.getByte(offset) != 0; - } - void setIsAttacking(boolean value) { - buffer.putByte(myOffset + 275, (byte) (value ? 1 : 0)); - } - boolean isAttackFrame() { - int offset = myOffset + 276; - return buffer.getByte(offset) != 0; - } - void setIsAttackFrame(boolean value) { - buffer.putByte(myOffset + 276, (byte) (value ? 1 : 0)); - } - boolean isBeingGathered() { - int offset = myOffset + 277; - return buffer.getByte(offset) != 0; - } - void setIsBeingGathered(boolean value) { - buffer.putByte(myOffset + 277, (byte) (value ? 1 : 0)); - } - boolean isBlind() { - int offset = myOffset + 278; - return buffer.getByte(offset) != 0; - } - void setIsBlind(boolean value) { - buffer.putByte(myOffset + 278, (byte) (value ? 1 : 0)); - } - boolean isBraking() { - int offset = myOffset + 279; - return buffer.getByte(offset) != 0; - } - void setIsBraking(boolean value) { - buffer.putByte(myOffset + 279, (byte) (value ? 1 : 0)); - } - boolean isBurrowed() { - int offset = myOffset + 280; - return buffer.getByte(offset) != 0; - } - void setIsBurrowed(boolean value) { - buffer.putByte(myOffset + 280, (byte) (value ? 1 : 0)); - } - int getCarryResourceType() { - int offset = myOffset + 284; - return buffer.getInt(offset); - } - void setCarryResourceType(int value) { - buffer.putInt(myOffset + 284, value); - } - boolean isCloaked() { - int offset = myOffset + 288; - return buffer.getByte(offset) != 0; - } - void setIsCloaked(boolean value) { - buffer.putByte(myOffset + 288, (byte) (value ? 1 : 0)); - } - boolean isCompleted() { - int offset = myOffset + 289; - return buffer.getByte(offset) != 0; - } - void setIsCompleted(boolean value) { - buffer.putByte(myOffset + 289, (byte) (value ? 1 : 0)); - } - boolean isConstructing() { - int offset = myOffset + 290; - return buffer.getByte(offset) != 0; - } - void setIsConstructing(boolean value) { - buffer.putByte(myOffset + 290, (byte) (value ? 1 : 0)); - } - boolean isDetected() { - int offset = myOffset + 291; - return buffer.getByte(offset) != 0; - } - void setIsDetected(boolean value) { - buffer.putByte(myOffset + 291, (byte) (value ? 1 : 0)); - } - boolean isGathering() { - int offset = myOffset + 292; - return buffer.getByte(offset) != 0; - } - void setIsGathering(boolean value) { - buffer.putByte(myOffset + 292, (byte) (value ? 1 : 0)); - } - boolean isHallucination() { - int offset = myOffset + 293; - return buffer.getByte(offset) != 0; - } - void setIsHallucination(boolean value) { - buffer.putByte(myOffset + 293, (byte) (value ? 1 : 0)); - } - boolean isIdle() { - int offset = myOffset + 294; - return buffer.getByte(offset) != 0; - } - void setIsIdle(boolean value) { - buffer.putByte(myOffset + 294, (byte) (value ? 1 : 0)); - } - boolean isInterruptible() { - int offset = myOffset + 295; - return buffer.getByte(offset) != 0; - } - void setIsInterruptible(boolean value) { - buffer.putByte(myOffset + 295, (byte) (value ? 1 : 0)); - } - boolean isInvincible() { - int offset = myOffset + 296; - return buffer.getByte(offset) != 0; - } - void setIsInvincible(boolean value) { - buffer.putByte(myOffset + 296, (byte) (value ? 1 : 0)); - } - boolean isLifted() { - int offset = myOffset + 297; - return buffer.getByte(offset) != 0; - } - void setIsLifted(boolean value) { - buffer.putByte(myOffset + 297, (byte) (value ? 1 : 0)); - } - boolean isMorphing() { - int offset = myOffset + 298; - return buffer.getByte(offset) != 0; - } - void setIsMorphing(boolean value) { - buffer.putByte(myOffset + 298, (byte) (value ? 1 : 0)); - } - boolean isMoving() { - int offset = myOffset + 299; - return buffer.getByte(offset) != 0; - } - void setIsMoving(boolean value) { - buffer.putByte(myOffset + 299, (byte) (value ? 1 : 0)); - } - boolean isParasited() { - int offset = myOffset + 300; - return buffer.getByte(offset) != 0; - } - void setIsParasited(boolean value) { - buffer.putByte(myOffset + 300, (byte) (value ? 1 : 0)); - } - boolean isSelected() { - int offset = myOffset + 301; - return buffer.getByte(offset) != 0; - } - void setIsSelected(boolean value) { - buffer.putByte(myOffset + 301, (byte) (value ? 1 : 0)); - } - boolean isStartingAttack() { - int offset = myOffset + 302; - return buffer.getByte(offset) != 0; - } - void setIsStartingAttack(boolean value) { - buffer.putByte(myOffset + 302, (byte) (value ? 1 : 0)); - } - boolean isStuck() { - int offset = myOffset + 303; - return buffer.getByte(offset) != 0; - } - void setIsStuck(boolean value) { - buffer.putByte(myOffset + 303, (byte) (value ? 1 : 0)); - } - boolean isTraining() { - int offset = myOffset + 304; - return buffer.getByte(offset) != 0; - } - void setIsTraining(boolean value) { - buffer.putByte(myOffset + 304, (byte) (value ? 1 : 0)); - } - boolean isUnderStorm() { - int offset = myOffset + 305; - return buffer.getByte(offset) != 0; - } - void setIsUnderStorm(boolean value) { - buffer.putByte(myOffset + 305, (byte) (value ? 1 : 0)); - } - boolean isUnderDarkSwarm() { - int offset = myOffset + 306; - return buffer.getByte(offset) != 0; - } - void setIsUnderDarkSwarm(boolean value) { - buffer.putByte(myOffset + 306, (byte) (value ? 1 : 0)); - } - boolean isUnderDWeb() { - int offset = myOffset + 307; - return buffer.getByte(offset) != 0; - } - void setIsUnderDWeb(boolean value) { - buffer.putByte(myOffset + 307, (byte) (value ? 1 : 0)); - } - boolean isPowered() { - int offset = myOffset + 308; - return buffer.getByte(offset) != 0; - } - void setIsPowered(boolean value) { - buffer.putByte(myOffset + 308, (byte) (value ? 1 : 0)); - } - boolean isVisible(int i) { - int offset = myOffset + 309 + 1 * 1 * i; - return buffer.getByte(offset) != 0; - } - void setIsVisible(int i, boolean value) { - buffer.putByte(myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); - } - int getButtonset() { - int offset = myOffset + 320; - return buffer.getInt(offset); - } - void setButtonset(int value) { - buffer.putInt(myOffset + 320, value); - } - int getLastAttackerPlayer() { - int offset = myOffset + 324; - return buffer.getInt(offset); - } - void setLastAttackerPlayer(int value) { - buffer.putInt(myOffset + 324, value); - } - boolean getRecentlyAttacked() { - int offset = myOffset + 328; - return buffer.getByte(offset) != 0; - } - void setRecentlyAttacked(boolean value) { - buffer.putByte(myOffset + 328, (byte) (value ? 1 : 0)); - } - int getReplayID() { - int offset = myOffset + 332; - return buffer.getInt(offset); - } - void setReplayID(int value) { - buffer.putInt(myOffset + 332, value); - } - } -} - +package bwapi; + +import com.sun.jna.Pointer; + +final class ClientData { + private WrappedBuffer buffer; + private GameData gameData; + ClientData() { + gameData = new ClientData.GameData(0); + } + GameData gameData() { + return gameData; + } + void setBuffer(WrappedBuffer buffer) { + this.buffer = buffer; + } + void setPointer(Pointer pointer) { + setBuffer(new WrappedBuffer(pointer, GameData.SIZE)); + } + class UnitCommand { + static final int SIZE = 24; + private int myOffset; + UnitCommand(int myOffset) { + this.myOffset = myOffset; + } + int getTid() { + int offset = myOffset + 0; + return buffer.getInt(offset); + } + void setTid(int value) { + buffer.putInt(myOffset + 0, value); + } + int getUnitIndex() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setUnitIndex(int value) { + buffer.putInt(myOffset + 4, value); + } + int getTargetIndex() { + int offset = myOffset + 8; + return buffer.getInt(offset); + } + void setTargetIndex(int value) { + buffer.putInt(myOffset + 8, value); + } + int getX() { + int offset = myOffset + 12; + return buffer.getInt(offset); + } + void setX(int value) { + buffer.putInt(myOffset + 12, value); + } + int getY() { + int offset = myOffset + 16; + return buffer.getInt(offset); + } + void setY(int value) { + buffer.putInt(myOffset + 16, value); + } + int getExtra() { + int offset = myOffset + 20; + return buffer.getInt(offset); + } + void setExtra(int value) { + buffer.putInt(myOffset + 20, value); + } + } + class GameData { + static final int SIZE = 33017048; + private int myOffset; + GameData(int myOffset) { + this.myOffset = myOffset; + } + int getClient_version() { + int offset = myOffset + 0; + return buffer.getInt(offset); + } + void setClient_version(int value) { + buffer.putInt(myOffset + 0, value); + } + int getRevision() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setRevision(int value) { + buffer.putInt(myOffset + 4, value); + } + boolean isDebug() { + int offset = myOffset + 8; + return buffer.getByte(offset) != 0; + } + void setIsDebug(boolean value) { + buffer.putByte(myOffset + 8, (byte) (value ? 1 : 0)); + } + int getInstanceID() { + int offset = myOffset + 12; + return buffer.getInt(offset); + } + void setInstanceID(int value) { + buffer.putInt(myOffset + 12, value); + } + int getBotAPM_noselects() { + int offset = myOffset + 16; + return buffer.getInt(offset); + } + void setBotAPM_noselects(int value) { + buffer.putInt(myOffset + 16, value); + } + int getBotAPM_selects() { + int offset = myOffset + 20; + return buffer.getInt(offset); + } + void setBotAPM_selects(int value) { + buffer.putInt(myOffset + 20, value); + } + int getForceCount() { + int offset = myOffset + 24; + return buffer.getInt(offset); + } + void setForceCount(int value) { + buffer.putInt(myOffset + 24, value); + } + ForceData getForces(int i) { + int offset = myOffset + 28 + 32 * 1 * i; + return new ForceData(offset); + } + int getPlayerCount() { + int offset = myOffset + 188; + return buffer.getInt(offset); + } + void setPlayerCount(int value) { + buffer.putInt(myOffset + 188, value); + } + PlayerData getPlayers(int i) { + int offset = myOffset + 192 + 5788 * 1 * i; + return new PlayerData(offset); + } + int getInitialUnitCount() { + int offset = myOffset + 69648; + return buffer.getInt(offset); + } + void setInitialUnitCount(int value) { + buffer.putInt(myOffset + 69648, value); + } + UnitData getUnits(int i) { + int offset = myOffset + 69656 + 336 * 1 * i; + return new UnitData(offset); + } + int getUnitArray(int i) { + int offset = myOffset + 3429656 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setUnitArray(int i, int value) { + buffer.putInt(myOffset + 3429656 + 4 * 1 * i, value); + } + BulletData getBullets(int i) { + int offset = myOffset + 3436456 + 80 * 1 * i; + return new BulletData(offset); + } + int getNukeDotCount() { + int offset = myOffset + 3444456; + return buffer.getInt(offset); + } + void setNukeDotCount(int value) { + buffer.putInt(myOffset + 3444456, value); + } + Position getNukeDots(int i) { + int offset = myOffset + 3444460 + 8 * 1 * i; + return new Position(offset); + } + int getGameType() { + int offset = myOffset + 3446060; + return buffer.getInt(offset); + } + void setGameType(int value) { + buffer.putInt(myOffset + 3446060, value); + } + int getLatency() { + int offset = myOffset + 3446064; + return buffer.getInt(offset); + } + void setLatency(int value) { + buffer.putInt(myOffset + 3446064, value); + } + int getLatencyFrames() { + int offset = myOffset + 3446068; + return buffer.getInt(offset); + } + void setLatencyFrames(int value) { + buffer.putInt(myOffset + 3446068, value); + } + int getLatencyTime() { + int offset = myOffset + 3446072; + return buffer.getInt(offset); + } + void setLatencyTime(int value) { + buffer.putInt(myOffset + 3446072, value); + } + int getRemainingLatencyFrames() { + int offset = myOffset + 3446076; + return buffer.getInt(offset); + } + void setRemainingLatencyFrames(int value) { + buffer.putInt(myOffset + 3446076, value); + } + int getRemainingLatencyTime() { + int offset = myOffset + 3446080; + return buffer.getInt(offset); + } + void setRemainingLatencyTime(int value) { + buffer.putInt(myOffset + 3446080, value); + } + boolean getHasLatCom() { + int offset = myOffset + 3446084; + return buffer.getByte(offset) != 0; + } + void setHasLatCom(boolean value) { + buffer.putByte(myOffset + 3446084, (byte) (value ? 1 : 0)); + } + boolean getHasGUI() { + int offset = myOffset + 3446085; + return buffer.getByte(offset) != 0; + } + void setHasGUI(boolean value) { + buffer.putByte(myOffset + 3446085, (byte) (value ? 1 : 0)); + } + int getReplayFrameCount() { + int offset = myOffset + 3446088; + return buffer.getInt(offset); + } + void setReplayFrameCount(int value) { + buffer.putInt(myOffset + 3446088, value); + } + int getRandomSeed() { + int offset = myOffset + 3446092; + return buffer.getInt(offset); + } + void setRandomSeed(int value) { + buffer.putInt(myOffset + 3446092, value); + } + int getFrameCount() { + int offset = myOffset + 3446096; + return buffer.getInt(offset); + } + void setFrameCount(int value) { + buffer.putInt(myOffset + 3446096, value); + } + int getElapsedTime() { + int offset = myOffset + 3446100; + return buffer.getInt(offset); + } + void setElapsedTime(int value) { + buffer.putInt(myOffset + 3446100, value); + } + int getCountdownTimer() { + int offset = myOffset + 3446104; + return buffer.getInt(offset); + } + void setCountdownTimer(int value) { + buffer.putInt(myOffset + 3446104, value); + } + int getFps() { + int offset = myOffset + 3446108; + return buffer.getInt(offset); + } + void setFps(int value) { + buffer.putInt(myOffset + 3446108, value); + } + double getAverageFPS() { + int offset = myOffset + 3446112; + return buffer.getDouble(offset); + } + void setAverageFPS(double value) { + buffer.putDouble(myOffset + 3446112, value); + } + int getMouseX() { + int offset = myOffset + 3446120; + return buffer.getInt(offset); + } + void setMouseX(int value) { + buffer.putInt(myOffset + 3446120, value); + } + int getMouseY() { + int offset = myOffset + 3446124; + return buffer.getInt(offset); + } + void setMouseY(int value) { + buffer.putInt(myOffset + 3446124, value); + } + boolean getMouseState(int i) { + int offset = myOffset + 3446128 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setMouseState(int i, boolean value) { + buffer.putByte(myOffset + 3446128 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + boolean getKeyState(int i) { + int offset = myOffset + 3446131 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setKeyState(int i, boolean value) { + buffer.putByte(myOffset + 3446131 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + int getScreenX() { + int offset = myOffset + 3446388; + return buffer.getInt(offset); + } + void setScreenX(int value) { + buffer.putInt(myOffset + 3446388, value); + } + int getScreenY() { + int offset = myOffset + 3446392; + return buffer.getInt(offset); + } + void setScreenY(int value) { + buffer.putInt(myOffset + 3446392, value); + } + boolean getFlags(int i) { + int offset = myOffset + 3446396 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setFlags(int i, boolean value) { + buffer.putByte(myOffset + 3446396 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + int getMapWidth() { + int offset = myOffset + 3446400; + return buffer.getInt(offset); + } + void setMapWidth(int value) { + buffer.putInt(myOffset + 3446400, value); + } + int getMapHeight() { + int offset = myOffset + 3446404; + return buffer.getInt(offset); + } + void setMapHeight(int value) { + buffer.putInt(myOffset + 3446404, value); + } + String getMapFileName() { + int offset = myOffset + 3446408; + return buffer.getString(offset, 261); + } + void setMapFileName(String value) { + buffer.putString(myOffset + 3446408, 261, value); + } + String getMapPathName() { + int offset = myOffset + 3446669; + return buffer.getString(offset, 261); + } + void setMapPathName(String value) { + buffer.putString(myOffset + 3446669, 261, value); + } + String getMapName() { + int offset = myOffset + 3446930; + return buffer.getString(offset, 33); + } + void setMapName(String value) { + buffer.putString(myOffset + 3446930, 33, value); + } + String getMapHash() { + int offset = myOffset + 3446963; + return buffer.getString(offset, 41); + } + void setMapHash(String value) { + buffer.putString(myOffset + 3446963, 41, value); + } + int getGroundHeight(int i, int j) { + int offset = myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i; + return buffer.getInt(offset); + } + void setGetGroundHeight(int i, int j, int value) { + buffer.putInt(myOffset + 3447004 + 4 * 1 * j + 4 * 256 * i, value); + } + boolean isWalkable(int i, int j) { + int offset = myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i; + return buffer.getByte(offset) != 0; + } + void setIsWalkable(int i, int j, boolean value) { + buffer.putByte(myOffset + 3709148 + 1 * 1 * j + 1 * 1024 * i, (byte) (value ? 1 : 0)); + } + boolean isBuildable(int i, int j) { + int offset = myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i; + return buffer.getByte(offset) != 0; + } + void setIsBuildable(int i, int j, boolean value) { + buffer.putByte(myOffset + 4757724 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + } + boolean isVisible(int i, int j) { + int offset = myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i; + return buffer.getByte(offset) != 0; + } + void setIsVisible(int i, int j, boolean value) { + buffer.putByte(myOffset + 4823260 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + } + boolean isExplored(int i, int j) { + int offset = myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i; + return buffer.getByte(offset) != 0; + } + void setIsExplored(int i, int j, boolean value) { + buffer.putByte(myOffset + 4888796 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + } + boolean getHasCreep(int i, int j) { + int offset = myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i; + return buffer.getByte(offset) != 0; + } + void setHasCreep(int i, int j, boolean value) { + buffer.putByte(myOffset + 4954332 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + } + boolean isOccupied(int i, int j) { + int offset = myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i; + return buffer.getByte(offset) != 0; + } + void setIsOccupied(int i, int j, boolean value) { + buffer.putByte(myOffset + 5019868 + 1 * 1 * j + 1 * 256 * i, (byte) (value ? 1 : 0)); + } + short getMapTileRegionId(int i, int j) { + int offset = myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i; + return buffer.getShort(offset); + } + void setMapTileRegionId(int i, int j, short value) { + buffer.putShort(myOffset + 5085404 + 2 * 1 * j + 2 * 256 * i, value); + } + short getMapSplitTilesMiniTileMask(int i) { + int offset = myOffset + 5216476 + 2 * 1 * i; + return buffer.getShort(offset); + } + void setMapSplitTilesMiniTileMask(int i, short value) { + buffer.putShort(myOffset + 5216476 + 2 * 1 * i, value); + } + short getMapSplitTilesRegion1(int i) { + int offset = myOffset + 5226476 + 2 * 1 * i; + return buffer.getShort(offset); + } + void setMapSplitTilesRegion1(int i, short value) { + buffer.putShort(myOffset + 5226476 + 2 * 1 * i, value); + } + short getMapSplitTilesRegion2(int i) { + int offset = myOffset + 5236476 + 2 * 1 * i; + return buffer.getShort(offset); + } + void setMapSplitTilesRegion2(int i, short value) { + buffer.putShort(myOffset + 5236476 + 2 * 1 * i, value); + } + int getRegionCount() { + int offset = myOffset + 5246476; + return buffer.getInt(offset); + } + void setRegionCount(int value) { + buffer.putInt(myOffset + 5246476, value); + } + RegionData getRegions(int i) { + int offset = myOffset + 5246480 + 1068 * 1 * i; + return new RegionData(offset); + } + int getStartLocationCount() { + int offset = myOffset + 10586480; + return buffer.getInt(offset); + } + void setStartLocationCount(int value) { + buffer.putInt(myOffset + 10586480, value); + } + Position getStartLocations(int i) { + int offset = myOffset + 10586484 + 8 * 1 * i; + return new Position(offset); + } + boolean isInGame() { + int offset = myOffset + 10586548; + return buffer.getByte(offset) != 0; + } + void setIsInGame(boolean value) { + buffer.putByte(myOffset + 10586548, (byte) (value ? 1 : 0)); + } + boolean isMultiplayer() { + int offset = myOffset + 10586549; + return buffer.getByte(offset) != 0; + } + void setIsMultiplayer(boolean value) { + buffer.putByte(myOffset + 10586549, (byte) (value ? 1 : 0)); + } + boolean isBattleNet() { + int offset = myOffset + 10586550; + return buffer.getByte(offset) != 0; + } + void setIsBattleNet(boolean value) { + buffer.putByte(myOffset + 10586550, (byte) (value ? 1 : 0)); + } + boolean isPaused() { + int offset = myOffset + 10586551; + return buffer.getByte(offset) != 0; + } + void setIsPaused(boolean value) { + buffer.putByte(myOffset + 10586551, (byte) (value ? 1 : 0)); + } + boolean isReplay() { + int offset = myOffset + 10586552; + return buffer.getByte(offset) != 0; + } + void setIsReplay(boolean value) { + buffer.putByte(myOffset + 10586552, (byte) (value ? 1 : 0)); + } + int getSelectedUnitCount() { + int offset = myOffset + 10586556; + return buffer.getInt(offset); + } + void setSelectedUnitCount(int value) { + buffer.putInt(myOffset + 10586556, value); + } + int getSelectedUnits(int i) { + int offset = myOffset + 10586560 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setSelectedUnits(int i, int value) { + buffer.putInt(myOffset + 10586560 + 4 * 1 * i, value); + } + int getSelf() { + int offset = myOffset + 10586608; + return buffer.getInt(offset); + } + void setSelf(int value) { + buffer.putInt(myOffset + 10586608, value); + } + int getEnemy() { + int offset = myOffset + 10586612; + return buffer.getInt(offset); + } + void setEnemy(int value) { + buffer.putInt(myOffset + 10586612, value); + } + int getNeutral() { + int offset = myOffset + 10586616; + return buffer.getInt(offset); + } + void setNeutral(int value) { + buffer.putInt(myOffset + 10586616, value); + } + int getEventCount() { + int offset = myOffset + 10586620; + return buffer.getInt(offset); + } + void setEventCount(int value) { + buffer.putInt(myOffset + 10586620, value); + } + Event getEvents(int i) { + int offset = myOffset + 10586624 + 12 * 1 * i; + return new Event(offset); + } + int getEventStringCount() { + int offset = myOffset + 10706624; + return buffer.getInt(offset); + } + void setEventStringCount(int value) { + buffer.putInt(myOffset + 10706624, value); + } + String getEventStrings(int i) { + int offset = myOffset + 10706628 + 1 * 256 * i; + return buffer.getString(offset, 256); + } + void setEventStrings(int i, String value) { + buffer.putString(myOffset + 10706628 + 1 * 256 * i, 256, value); + } + int getStringCount() { + int offset = myOffset + 10962628; + return buffer.getInt(offset); + } + void setStringCount(int value) { + buffer.putInt(myOffset + 10962628, value); + } + String getStrings(int i) { + int offset = myOffset + 10962632 + 1 * 1024 * i; + return buffer.getString(offset, 1024); + } + void setStrings(int i, String value) { + buffer.putString(myOffset + 10962632 + 1 * 1024 * i, 1024, value); + } + int getShapeCount() { + int offset = myOffset + 31442632; + return buffer.getInt(offset); + } + void setShapeCount(int value) { + buffer.putInt(myOffset + 31442632, value); + } + Shape getShapes(int i) { + int offset = myOffset + 31442636 + 40 * 1 * i; + return new Shape(offset); + } + int getCommandCount() { + int offset = myOffset + 32242636; + return buffer.getInt(offset); + } + void setCommandCount(int value) { + buffer.putInt(myOffset + 32242636, value); + } + Command getCommands(int i) { + int offset = myOffset + 32242640 + 12 * 1 * i; + return new Command(offset); + } + int getUnitCommandCount() { + int offset = myOffset + 32482640; + return buffer.getInt(offset); + } + void setUnitCommandCount(int value) { + buffer.putInt(myOffset + 32482640, value); + } + UnitCommand getUnitCommands(int i) { + int offset = myOffset + 32482644 + 24 * 1 * i; + return new UnitCommand(offset); + } + int getUnitSearchSize() { + int offset = myOffset + 32962644; + return buffer.getInt(offset); + } + void setUnitSearchSize(int value) { + buffer.putInt(myOffset + 32962644, value); + } + unitFinder getXUnitSearch(int i) { + int offset = myOffset + 32962648 + 8 * 1 * i; + return new unitFinder(offset); + } + unitFinder getYUnitSearch(int i) { + int offset = myOffset + 32989848 + 8 * 1 * i; + return new unitFinder(offset); + } + } + class Shape { + static final int SIZE = 40; + private int myOffset; + Shape(int myOffset) { + this.myOffset = myOffset; + } + ShapeType getType() { + int offset = myOffset + 0; + return ShapeType.idToEnum[buffer.getInt(offset)]; + } + void setType(ShapeType value) { + buffer.putInt(myOffset + 0, value.id); + } + CoordinateType getCtype() { + int offset = myOffset + 4; + return CoordinateType.idToEnum[buffer.getInt(offset)]; + } + void setCtype(CoordinateType value) { + buffer.putInt(myOffset + 4, value.id); + } + int getX1() { + int offset = myOffset + 8; + return buffer.getInt(offset); + } + void setX1(int value) { + buffer.putInt(myOffset + 8, value); + } + int getY1() { + int offset = myOffset + 12; + return buffer.getInt(offset); + } + void setY1(int value) { + buffer.putInt(myOffset + 12, value); + } + int getX2() { + int offset = myOffset + 16; + return buffer.getInt(offset); + } + void setX2(int value) { + buffer.putInt(myOffset + 16, value); + } + int getY2() { + int offset = myOffset + 20; + return buffer.getInt(offset); + } + void setY2(int value) { + buffer.putInt(myOffset + 20, value); + } + int getExtra1() { + int offset = myOffset + 24; + return buffer.getInt(offset); + } + void setExtra1(int value) { + buffer.putInt(myOffset + 24, value); + } + int getExtra2() { + int offset = myOffset + 28; + return buffer.getInt(offset); + } + void setExtra2(int value) { + buffer.putInt(myOffset + 28, value); + } + int getColor() { + int offset = myOffset + 32; + return buffer.getInt(offset); + } + void setColor(int value) { + buffer.putInt(myOffset + 32, value); + } + boolean isSolid() { + int offset = myOffset + 36; + return buffer.getByte(offset) != 0; + } + void setIsSolid(boolean value) { + buffer.putByte(myOffset + 36, (byte) (value ? 1 : 0)); + } + } + class Command { + static final int SIZE = 12; + private int myOffset; + Command(int myOffset) { + this.myOffset = myOffset; + } + CommandType getType() { + int offset = myOffset + 0; + return CommandType.idToEnum[buffer.getInt(offset)]; + } + void setType(CommandType value) { + buffer.putInt(myOffset + 0, value.id); + } + int getValue1() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setValue1(int value) { + buffer.putInt(myOffset + 4, value); + } + int getValue2() { + int offset = myOffset + 8; + return buffer.getInt(offset); + } + void setValue2(int value) { + buffer.putInt(myOffset + 8, value); + } + } + class Position { + static final int SIZE = 8; + private int myOffset; + Position(int myOffset) { + this.myOffset = myOffset; + } + int getX() { + int offset = myOffset + 0; + return buffer.getInt(offset); + } + void setX(int value) { + buffer.putInt(myOffset + 0, value); + } + int getY() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setY(int value) { + buffer.putInt(myOffset + 4, value); + } + } + class Event { + static final int SIZE = 12; + private int myOffset; + Event(int myOffset) { + this.myOffset = myOffset; + } + EventType getType() { + int offset = myOffset + 0; + return EventType.idToEnum[buffer.getInt(offset)]; + } + void setType(EventType value) { + buffer.putInt(myOffset + 0, value.id); + } + int getV1() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setV1(int value) { + buffer.putInt(myOffset + 4, value); + } + int getV2() { + int offset = myOffset + 8; + return buffer.getInt(offset); + } + void setV2(int value) { + buffer.putInt(myOffset + 8, value); + } + } + class RegionData { + static final int SIZE = 1068; + private int myOffset; + RegionData(int myOffset) { + this.myOffset = myOffset; + } + int getId() { + int offset = myOffset + 0; + return buffer.getInt(offset); + } + void setId(int value) { + buffer.putInt(myOffset + 0, value); + } + int islandID() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setIslandID(int value) { + buffer.putInt(myOffset + 4, value); + } + int getCenter_x() { + int offset = myOffset + 8; + return buffer.getInt(offset); + } + void setCenter_x(int value) { + buffer.putInt(myOffset + 8, value); + } + int getCenter_y() { + int offset = myOffset + 12; + return buffer.getInt(offset); + } + void setCenter_y(int value) { + buffer.putInt(myOffset + 12, value); + } + int getPriority() { + int offset = myOffset + 16; + return buffer.getInt(offset); + } + void setPriority(int value) { + buffer.putInt(myOffset + 16, value); + } + int getLeftMost() { + int offset = myOffset + 20; + return buffer.getInt(offset); + } + void setLeftMost(int value) { + buffer.putInt(myOffset + 20, value); + } + int getRightMost() { + int offset = myOffset + 24; + return buffer.getInt(offset); + } + void setRightMost(int value) { + buffer.putInt(myOffset + 24, value); + } + int getTopMost() { + int offset = myOffset + 28; + return buffer.getInt(offset); + } + void setTopMost(int value) { + buffer.putInt(myOffset + 28, value); + } + int getBottomMost() { + int offset = myOffset + 32; + return buffer.getInt(offset); + } + void setBottomMost(int value) { + buffer.putInt(myOffset + 32, value); + } + int getNeighborCount() { + int offset = myOffset + 36; + return buffer.getInt(offset); + } + void setNeighborCount(int value) { + buffer.putInt(myOffset + 36, value); + } + int getNeighbors(int i) { + int offset = myOffset + 40 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setNeighbors(int i, int value) { + buffer.putInt(myOffset + 40 + 4 * 1 * i, value); + } + boolean isAccessible() { + int offset = myOffset + 1064; + return buffer.getByte(offset) != 0; + } + void setIsAccessible(boolean value) { + buffer.putByte(myOffset + 1064, (byte) (value ? 1 : 0)); + } + boolean isHigherGround() { + int offset = myOffset + 1065; + return buffer.getByte(offset) != 0; + } + void setIsHigherGround(boolean value) { + buffer.putByte(myOffset + 1065, (byte) (value ? 1 : 0)); + } + } + class ForceData { + static final int SIZE = 32; + private int myOffset; + ForceData(int myOffset) { + this.myOffset = myOffset; + } + String getName() { + int offset = myOffset + 0; + return buffer.getString(offset, 32); + } + void setName(String value) { + buffer.putString(myOffset + 0, 32, value); + } + } + class PlayerData { + static final int SIZE = 5788; + private int myOffset; + PlayerData(int myOffset) { + this.myOffset = myOffset; + } + String getName() { + int offset = myOffset + 0; + return buffer.getString(offset, 25); + } + void setName(String value) { + buffer.putString(myOffset + 0, 25, value); + } + int getRace() { + int offset = myOffset + 28; + return buffer.getInt(offset); + } + void setRace(int value) { + buffer.putInt(myOffset + 28, value); + } + int getType() { + int offset = myOffset + 32; + return buffer.getInt(offset); + } + void setType(int value) { + buffer.putInt(myOffset + 32, value); + } + int getForce() { + int offset = myOffset + 36; + return buffer.getInt(offset); + } + void setForce(int value) { + buffer.putInt(myOffset + 36, value); + } + boolean isAlly(int i) { + int offset = myOffset + 40 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setIsAlly(int i, boolean value) { + buffer.putByte(myOffset + 40 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + boolean isEnemy(int i) { + int offset = myOffset + 52 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setIsEnemy(int i, boolean value) { + buffer.putByte(myOffset + 52 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + boolean isNeutral() { + int offset = myOffset + 64; + return buffer.getByte(offset) != 0; + } + void setIsNeutral(boolean value) { + buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); + } + int getStartLocationX() { + int offset = myOffset + 68; + return buffer.getInt(offset); + } + void setStartLocationX(int value) { + buffer.putInt(myOffset + 68, value); + } + int getStartLocationY() { + int offset = myOffset + 72; + return buffer.getInt(offset); + } + void setStartLocationY(int value) { + buffer.putInt(myOffset + 72, value); + } + boolean isVictorious() { + int offset = myOffset + 76; + return buffer.getByte(offset) != 0; + } + void setIsVictorious(boolean value) { + buffer.putByte(myOffset + 76, (byte) (value ? 1 : 0)); + } + boolean isDefeated() { + int offset = myOffset + 77; + return buffer.getByte(offset) != 0; + } + void setIsDefeated(boolean value) { + buffer.putByte(myOffset + 77, (byte) (value ? 1 : 0)); + } + boolean getLeftGame() { + int offset = myOffset + 78; + return buffer.getByte(offset) != 0; + } + void setLeftGame(boolean value) { + buffer.putByte(myOffset + 78, (byte) (value ? 1 : 0)); + } + boolean isParticipating() { + int offset = myOffset + 79; + return buffer.getByte(offset) != 0; + } + void setIsParticipating(boolean value) { + buffer.putByte(myOffset + 79, (byte) (value ? 1 : 0)); + } + int getMinerals() { + int offset = myOffset + 80; + return buffer.getInt(offset); + } + void setMinerals(int value) { + buffer.putInt(myOffset + 80, value); + } + int getGas() { + int offset = myOffset + 84; + return buffer.getInt(offset); + } + void setGas(int value) { + buffer.putInt(myOffset + 84, value); + } + int getGatheredMinerals() { + int offset = myOffset + 88; + return buffer.getInt(offset); + } + void setGatheredMinerals(int value) { + buffer.putInt(myOffset + 88, value); + } + int getGatheredGas() { + int offset = myOffset + 92; + return buffer.getInt(offset); + } + void setGatheredGas(int value) { + buffer.putInt(myOffset + 92, value); + } + int getRepairedMinerals() { + int offset = myOffset + 96; + return buffer.getInt(offset); + } + void setRepairedMinerals(int value) { + buffer.putInt(myOffset + 96, value); + } + int getRepairedGas() { + int offset = myOffset + 100; + return buffer.getInt(offset); + } + void setRepairedGas(int value) { + buffer.putInt(myOffset + 100, value); + } + int getRefundedMinerals() { + int offset = myOffset + 104; + return buffer.getInt(offset); + } + void setRefundedMinerals(int value) { + buffer.putInt(myOffset + 104, value); + } + int getRefundedGas() { + int offset = myOffset + 108; + return buffer.getInt(offset); + } + void setRefundedGas(int value) { + buffer.putInt(myOffset + 108, value); + } + int getSupplyTotal(int i) { + int offset = myOffset + 112 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setSupplyTotal(int i, int value) { + buffer.putInt(myOffset + 112 + 4 * 1 * i, value); + } + int getSupplyUsed(int i) { + int offset = myOffset + 124 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setSupplyUsed(int i, int value) { + buffer.putInt(myOffset + 124 + 4 * 1 * i, value); + } + int getAllUnitCount(int i) { + int offset = myOffset + 136 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setAllUnitCount(int i, int value) { + buffer.putInt(myOffset + 136 + 4 * 1 * i, value); + } + int getVisibleUnitCount(int i) { + int offset = myOffset + 1072 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setVisibleUnitCount(int i, int value) { + buffer.putInt(myOffset + 1072 + 4 * 1 * i, value); + } + int getCompletedUnitCount(int i) { + int offset = myOffset + 2008 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setCompletedUnitCount(int i, int value) { + buffer.putInt(myOffset + 2008 + 4 * 1 * i, value); + } + int getDeadUnitCount(int i) { + int offset = myOffset + 2944 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setDeadUnitCount(int i, int value) { + buffer.putInt(myOffset + 2944 + 4 * 1 * i, value); + } + int getKilledUnitCount(int i) { + int offset = myOffset + 3880 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setKilledUnitCount(int i, int value) { + buffer.putInt(myOffset + 3880 + 4 * 1 * i, value); + } + int getUpgradeLevel(int i) { + int offset = myOffset + 4816 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setUpgradeLevel(int i, int value) { + buffer.putInt(myOffset + 4816 + 4 * 1 * i, value); + } + boolean getHasResearched(int i) { + int offset = myOffset + 5068 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setHasResearched(int i, boolean value) { + buffer.putByte(myOffset + 5068 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + boolean isResearching(int i) { + int offset = myOffset + 5115 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setIsResearching(int i, boolean value) { + buffer.putByte(myOffset + 5115 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + boolean isUpgrading(int i) { + int offset = myOffset + 5162 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setIsUpgrading(int i, boolean value) { + buffer.putByte(myOffset + 5162 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + int getColor() { + int offset = myOffset + 5228; + return buffer.getInt(offset); + } + void setColor(int value) { + buffer.putInt(myOffset + 5228, value); + } + int getTotalUnitScore() { + int offset = myOffset + 5232; + return buffer.getInt(offset); + } + void setTotalUnitScore(int value) { + buffer.putInt(myOffset + 5232, value); + } + int getTotalKillScore() { + int offset = myOffset + 5236; + return buffer.getInt(offset); + } + void setTotalKillScore(int value) { + buffer.putInt(myOffset + 5236, value); + } + int getTotalBuildingScore() { + int offset = myOffset + 5240; + return buffer.getInt(offset); + } + void setTotalBuildingScore(int value) { + buffer.putInt(myOffset + 5240, value); + } + int getTotalRazingScore() { + int offset = myOffset + 5244; + return buffer.getInt(offset); + } + void setTotalRazingScore(int value) { + buffer.putInt(myOffset + 5244, value); + } + int getCustomScore() { + int offset = myOffset + 5248; + return buffer.getInt(offset); + } + void setCustomScore(int value) { + buffer.putInt(myOffset + 5248, value); + } + int getMaxUpgradeLevel(int i) { + int offset = myOffset + 5252 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setMaxUpgradeLevel(int i, int value) { + buffer.putInt(myOffset + 5252 + 4 * 1 * i, value); + } + boolean isResearchAvailable(int i) { + int offset = myOffset + 5504 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setIsResearchAvailable(int i, boolean value) { + buffer.putByte(myOffset + 5504 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + boolean isUnitAvailable(int i) { + int offset = myOffset + 5551 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setIsUnitAvailable(int i, boolean value) { + buffer.putByte(myOffset + 5551 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + } + class BulletData { + static final int SIZE = 80; + private int myOffset; + BulletData(int myOffset) { + this.myOffset = myOffset; + } + int getId() { + int offset = myOffset + 0; + return buffer.getInt(offset); + } + void setId(int value) { + buffer.putInt(myOffset + 0, value); + } + int getPlayer() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setPlayer(int value) { + buffer.putInt(myOffset + 4, value); + } + int getType() { + int offset = myOffset + 8; + return buffer.getInt(offset); + } + void setType(int value) { + buffer.putInt(myOffset + 8, value); + } + int getSource() { + int offset = myOffset + 12; + return buffer.getInt(offset); + } + void setSource(int value) { + buffer.putInt(myOffset + 12, value); + } + int getPositionX() { + int offset = myOffset + 16; + return buffer.getInt(offset); + } + void setPositionX(int value) { + buffer.putInt(myOffset + 16, value); + } + int getPositionY() { + int offset = myOffset + 20; + return buffer.getInt(offset); + } + void setPositionY(int value) { + buffer.putInt(myOffset + 20, value); + } + double getAngle() { + int offset = myOffset + 24; + return buffer.getDouble(offset); + } + void setAngle(double value) { + buffer.putDouble(myOffset + 24, value); + } + double getVelocityX() { + int offset = myOffset + 32; + return buffer.getDouble(offset); + } + void setVelocityX(double value) { + buffer.putDouble(myOffset + 32, value); + } + double getVelocityY() { + int offset = myOffset + 40; + return buffer.getDouble(offset); + } + void setVelocityY(double value) { + buffer.putDouble(myOffset + 40, value); + } + int getTarget() { + int offset = myOffset + 48; + return buffer.getInt(offset); + } + void setTarget(int value) { + buffer.putInt(myOffset + 48, value); + } + int getTargetPositionX() { + int offset = myOffset + 52; + return buffer.getInt(offset); + } + void setTargetPositionX(int value) { + buffer.putInt(myOffset + 52, value); + } + int getTargetPositionY() { + int offset = myOffset + 56; + return buffer.getInt(offset); + } + void setTargetPositionY(int value) { + buffer.putInt(myOffset + 56, value); + } + int getRemoveTimer() { + int offset = myOffset + 60; + return buffer.getInt(offset); + } + void setRemoveTimer(int value) { + buffer.putInt(myOffset + 60, value); + } + boolean getExists() { + int offset = myOffset + 64; + return buffer.getByte(offset) != 0; + } + void setExists(boolean value) { + buffer.putByte(myOffset + 64, (byte) (value ? 1 : 0)); + } + boolean isVisible(int i) { + int offset = myOffset + 65 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setIsVisible(int i, boolean value) { + buffer.putByte(myOffset + 65 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + } + class unitFinder { + static final int SIZE = 8; + private int myOffset; + unitFinder(int myOffset) { + this.myOffset = myOffset; + } + int getUnitIndex() { + int offset = myOffset + 0; + return buffer.getInt(offset); + } + void setUnitIndex(int value) { + buffer.putInt(myOffset + 0, value); + } + int getSearchValue() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setSearchValue(int value) { + buffer.putInt(myOffset + 4, value); + } + } + class UnitData { + static final int SIZE = 336; + private int myOffset; + UnitData(int myOffset) { + this.myOffset = myOffset; + } + int getClearanceLevel() { + int offset = myOffset + 0; + return buffer.getInt(offset); + } + void setClearanceLevel(int value) { + buffer.putInt(myOffset + 0, value); + } + int getId() { + int offset = myOffset + 4; + return buffer.getInt(offset); + } + void setId(int value) { + buffer.putInt(myOffset + 4, value); + } + int getPlayer() { + int offset = myOffset + 8; + return buffer.getInt(offset); + } + void setPlayer(int value) { + buffer.putInt(myOffset + 8, value); + } + int getType() { + int offset = myOffset + 12; + return buffer.getInt(offset); + } + void setType(int value) { + buffer.putInt(myOffset + 12, value); + } + int getPositionX() { + int offset = myOffset + 16; + return buffer.getInt(offset); + } + void setPositionX(int value) { + buffer.putInt(myOffset + 16, value); + } + int getPositionY() { + int offset = myOffset + 20; + return buffer.getInt(offset); + } + void setPositionY(int value) { + buffer.putInt(myOffset + 20, value); + } + double getAngle() { + int offset = myOffset + 24; + return buffer.getDouble(offset); + } + void setAngle(double value) { + buffer.putDouble(myOffset + 24, value); + } + double getVelocityX() { + int offset = myOffset + 32; + return buffer.getDouble(offset); + } + void setVelocityX(double value) { + buffer.putDouble(myOffset + 32, value); + } + double getVelocityY() { + int offset = myOffset + 40; + return buffer.getDouble(offset); + } + void setVelocityY(double value) { + buffer.putDouble(myOffset + 40, value); + } + int getHitPoints() { + int offset = myOffset + 48; + return buffer.getInt(offset); + } + void setHitPoints(int value) { + buffer.putInt(myOffset + 48, value); + } + int getLastHitPoints() { + int offset = myOffset + 52; + return buffer.getInt(offset); + } + void setLastHitPoints(int value) { + buffer.putInt(myOffset + 52, value); + } + int getShields() { + int offset = myOffset + 56; + return buffer.getInt(offset); + } + void setShields(int value) { + buffer.putInt(myOffset + 56, value); + } + int getEnergy() { + int offset = myOffset + 60; + return buffer.getInt(offset); + } + void setEnergy(int value) { + buffer.putInt(myOffset + 60, value); + } + int getResources() { + int offset = myOffset + 64; + return buffer.getInt(offset); + } + void setResources(int value) { + buffer.putInt(myOffset + 64, value); + } + int getResourceGroup() { + int offset = myOffset + 68; + return buffer.getInt(offset); + } + void setResourceGroup(int value) { + buffer.putInt(myOffset + 68, value); + } + int getKillCount() { + int offset = myOffset + 72; + return buffer.getInt(offset); + } + void setKillCount(int value) { + buffer.putInt(myOffset + 72, value); + } + int getAcidSporeCount() { + int offset = myOffset + 76; + return buffer.getInt(offset); + } + void setAcidSporeCount(int value) { + buffer.putInt(myOffset + 76, value); + } + int getScarabCount() { + int offset = myOffset + 80; + return buffer.getInt(offset); + } + void setScarabCount(int value) { + buffer.putInt(myOffset + 80, value); + } + int getInterceptorCount() { + int offset = myOffset + 84; + return buffer.getInt(offset); + } + void setInterceptorCount(int value) { + buffer.putInt(myOffset + 84, value); + } + int getSpiderMineCount() { + int offset = myOffset + 88; + return buffer.getInt(offset); + } + void setSpiderMineCount(int value) { + buffer.putInt(myOffset + 88, value); + } + int getGroundWeaponCooldown() { + int offset = myOffset + 92; + return buffer.getInt(offset); + } + void setGroundWeaponCooldown(int value) { + buffer.putInt(myOffset + 92, value); + } + int getAirWeaponCooldown() { + int offset = myOffset + 96; + return buffer.getInt(offset); + } + void setAirWeaponCooldown(int value) { + buffer.putInt(myOffset + 96, value); + } + int getSpellCooldown() { + int offset = myOffset + 100; + return buffer.getInt(offset); + } + void setSpellCooldown(int value) { + buffer.putInt(myOffset + 100, value); + } + int getDefenseMatrixPoints() { + int offset = myOffset + 104; + return buffer.getInt(offset); + } + void setDefenseMatrixPoints(int value) { + buffer.putInt(myOffset + 104, value); + } + int getDefenseMatrixTimer() { + int offset = myOffset + 108; + return buffer.getInt(offset); + } + void setDefenseMatrixTimer(int value) { + buffer.putInt(myOffset + 108, value); + } + int getEnsnareTimer() { + int offset = myOffset + 112; + return buffer.getInt(offset); + } + void setEnsnareTimer(int value) { + buffer.putInt(myOffset + 112, value); + } + int getIrradiateTimer() { + int offset = myOffset + 116; + return buffer.getInt(offset); + } + void setIrradiateTimer(int value) { + buffer.putInt(myOffset + 116, value); + } + int getLockdownTimer() { + int offset = myOffset + 120; + return buffer.getInt(offset); + } + void setLockdownTimer(int value) { + buffer.putInt(myOffset + 120, value); + } + int getMaelstromTimer() { + int offset = myOffset + 124; + return buffer.getInt(offset); + } + void setMaelstromTimer(int value) { + buffer.putInt(myOffset + 124, value); + } + int getOrderTimer() { + int offset = myOffset + 128; + return buffer.getInt(offset); + } + void setOrderTimer(int value) { + buffer.putInt(myOffset + 128, value); + } + int getPlagueTimer() { + int offset = myOffset + 132; + return buffer.getInt(offset); + } + void setPlagueTimer(int value) { + buffer.putInt(myOffset + 132, value); + } + int getRemoveTimer() { + int offset = myOffset + 136; + return buffer.getInt(offset); + } + void setRemoveTimer(int value) { + buffer.putInt(myOffset + 136, value); + } + int getStasisTimer() { + int offset = myOffset + 140; + return buffer.getInt(offset); + } + void setStasisTimer(int value) { + buffer.putInt(myOffset + 140, value); + } + int getStimTimer() { + int offset = myOffset + 144; + return buffer.getInt(offset); + } + void setStimTimer(int value) { + buffer.putInt(myOffset + 144, value); + } + int getBuildType() { + int offset = myOffset + 148; + return buffer.getInt(offset); + } + void setBuildType(int value) { + buffer.putInt(myOffset + 148, value); + } + int getTrainingQueueCount() { + int offset = myOffset + 152; + return buffer.getInt(offset); + } + void setTrainingQueueCount(int value) { + buffer.putInt(myOffset + 152, value); + } + int getTrainingQueue(int i) { + int offset = myOffset + 156 + 4 * 1 * i; + return buffer.getInt(offset); + } + void setTrainingQueue(int i, int value) { + buffer.putInt(myOffset + 156 + 4 * 1 * i, value); + } + int getTech() { + int offset = myOffset + 176; + return buffer.getInt(offset); + } + void setTech(int value) { + buffer.putInt(myOffset + 176, value); + } + int getUpgrade() { + int offset = myOffset + 180; + return buffer.getInt(offset); + } + void setUpgrade(int value) { + buffer.putInt(myOffset + 180, value); + } + int getRemainingBuildTime() { + int offset = myOffset + 184; + return buffer.getInt(offset); + } + void setRemainingBuildTime(int value) { + buffer.putInt(myOffset + 184, value); + } + int getRemainingTrainTime() { + int offset = myOffset + 188; + return buffer.getInt(offset); + } + void setRemainingTrainTime(int value) { + buffer.putInt(myOffset + 188, value); + } + int getRemainingResearchTime() { + int offset = myOffset + 192; + return buffer.getInt(offset); + } + void setRemainingResearchTime(int value) { + buffer.putInt(myOffset + 192, value); + } + int getRemainingUpgradeTime() { + int offset = myOffset + 196; + return buffer.getInt(offset); + } + void setRemainingUpgradeTime(int value) { + buffer.putInt(myOffset + 196, value); + } + int getBuildUnit() { + int offset = myOffset + 200; + return buffer.getInt(offset); + } + void setBuildUnit(int value) { + buffer.putInt(myOffset + 200, value); + } + int getTarget() { + int offset = myOffset + 204; + return buffer.getInt(offset); + } + void setTarget(int value) { + buffer.putInt(myOffset + 204, value); + } + int getTargetPositionX() { + int offset = myOffset + 208; + return buffer.getInt(offset); + } + void setTargetPositionX(int value) { + buffer.putInt(myOffset + 208, value); + } + int getTargetPositionY() { + int offset = myOffset + 212; + return buffer.getInt(offset); + } + void setTargetPositionY(int value) { + buffer.putInt(myOffset + 212, value); + } + int getOrder() { + int offset = myOffset + 216; + return buffer.getInt(offset); + } + void setOrder(int value) { + buffer.putInt(myOffset + 216, value); + } + int getOrderTarget() { + int offset = myOffset + 220; + return buffer.getInt(offset); + } + void setOrderTarget(int value) { + buffer.putInt(myOffset + 220, value); + } + int getOrderTargetPositionX() { + int offset = myOffset + 224; + return buffer.getInt(offset); + } + void setOrderTargetPositionX(int value) { + buffer.putInt(myOffset + 224, value); + } + int getOrderTargetPositionY() { + int offset = myOffset + 228; + return buffer.getInt(offset); + } + void setOrderTargetPositionY(int value) { + buffer.putInt(myOffset + 228, value); + } + int getSecondaryOrder() { + int offset = myOffset + 232; + return buffer.getInt(offset); + } + void setSecondaryOrder(int value) { + buffer.putInt(myOffset + 232, value); + } + int getRallyPositionX() { + int offset = myOffset + 236; + return buffer.getInt(offset); + } + void setRallyPositionX(int value) { + buffer.putInt(myOffset + 236, value); + } + int getRallyPositionY() { + int offset = myOffset + 240; + return buffer.getInt(offset); + } + void setRallyPositionY(int value) { + buffer.putInt(myOffset + 240, value); + } + int getRallyUnit() { + int offset = myOffset + 244; + return buffer.getInt(offset); + } + void setRallyUnit(int value) { + buffer.putInt(myOffset + 244, value); + } + int getAddon() { + int offset = myOffset + 248; + return buffer.getInt(offset); + } + void setAddon(int value) { + buffer.putInt(myOffset + 248, value); + } + int getNydusExit() { + int offset = myOffset + 252; + return buffer.getInt(offset); + } + void setNydusExit(int value) { + buffer.putInt(myOffset + 252, value); + } + int getPowerUp() { + int offset = myOffset + 256; + return buffer.getInt(offset); + } + void setPowerUp(int value) { + buffer.putInt(myOffset + 256, value); + } + int getTransport() { + int offset = myOffset + 260; + return buffer.getInt(offset); + } + void setTransport(int value) { + buffer.putInt(myOffset + 260, value); + } + int getCarrier() { + int offset = myOffset + 264; + return buffer.getInt(offset); + } + void setCarrier(int value) { + buffer.putInt(myOffset + 264, value); + } + int getHatchery() { + int offset = myOffset + 268; + return buffer.getInt(offset); + } + void setHatchery(int value) { + buffer.putInt(myOffset + 268, value); + } + boolean getExists() { + int offset = myOffset + 272; + return buffer.getByte(offset) != 0; + } + void setExists(boolean value) { + buffer.putByte(myOffset + 272, (byte) (value ? 1 : 0)); + } + boolean getHasNuke() { + int offset = myOffset + 273; + return buffer.getByte(offset) != 0; + } + void setHasNuke(boolean value) { + buffer.putByte(myOffset + 273, (byte) (value ? 1 : 0)); + } + boolean isAccelerating() { + int offset = myOffset + 274; + return buffer.getByte(offset) != 0; + } + void setIsAccelerating(boolean value) { + buffer.putByte(myOffset + 274, (byte) (value ? 1 : 0)); + } + boolean isAttacking() { + int offset = myOffset + 275; + return buffer.getByte(offset) != 0; + } + void setIsAttacking(boolean value) { + buffer.putByte(myOffset + 275, (byte) (value ? 1 : 0)); + } + boolean isAttackFrame() { + int offset = myOffset + 276; + return buffer.getByte(offset) != 0; + } + void setIsAttackFrame(boolean value) { + buffer.putByte(myOffset + 276, (byte) (value ? 1 : 0)); + } + boolean isBeingGathered() { + int offset = myOffset + 277; + return buffer.getByte(offset) != 0; + } + void setIsBeingGathered(boolean value) { + buffer.putByte(myOffset + 277, (byte) (value ? 1 : 0)); + } + boolean isBlind() { + int offset = myOffset + 278; + return buffer.getByte(offset) != 0; + } + void setIsBlind(boolean value) { + buffer.putByte(myOffset + 278, (byte) (value ? 1 : 0)); + } + boolean isBraking() { + int offset = myOffset + 279; + return buffer.getByte(offset) != 0; + } + void setIsBraking(boolean value) { + buffer.putByte(myOffset + 279, (byte) (value ? 1 : 0)); + } + boolean isBurrowed() { + int offset = myOffset + 280; + return buffer.getByte(offset) != 0; + } + void setIsBurrowed(boolean value) { + buffer.putByte(myOffset + 280, (byte) (value ? 1 : 0)); + } + int getCarryResourceType() { + int offset = myOffset + 284; + return buffer.getInt(offset); + } + void setCarryResourceType(int value) { + buffer.putInt(myOffset + 284, value); + } + boolean isCloaked() { + int offset = myOffset + 288; + return buffer.getByte(offset) != 0; + } + void setIsCloaked(boolean value) { + buffer.putByte(myOffset + 288, (byte) (value ? 1 : 0)); + } + boolean isCompleted() { + int offset = myOffset + 289; + return buffer.getByte(offset) != 0; + } + void setIsCompleted(boolean value) { + buffer.putByte(myOffset + 289, (byte) (value ? 1 : 0)); + } + boolean isConstructing() { + int offset = myOffset + 290; + return buffer.getByte(offset) != 0; + } + void setIsConstructing(boolean value) { + buffer.putByte(myOffset + 290, (byte) (value ? 1 : 0)); + } + boolean isDetected() { + int offset = myOffset + 291; + return buffer.getByte(offset) != 0; + } + void setIsDetected(boolean value) { + buffer.putByte(myOffset + 291, (byte) (value ? 1 : 0)); + } + boolean isGathering() { + int offset = myOffset + 292; + return buffer.getByte(offset) != 0; + } + void setIsGathering(boolean value) { + buffer.putByte(myOffset + 292, (byte) (value ? 1 : 0)); + } + boolean isHallucination() { + int offset = myOffset + 293; + return buffer.getByte(offset) != 0; + } + void setIsHallucination(boolean value) { + buffer.putByte(myOffset + 293, (byte) (value ? 1 : 0)); + } + boolean isIdle() { + int offset = myOffset + 294; + return buffer.getByte(offset) != 0; + } + void setIsIdle(boolean value) { + buffer.putByte(myOffset + 294, (byte) (value ? 1 : 0)); + } + boolean isInterruptible() { + int offset = myOffset + 295; + return buffer.getByte(offset) != 0; + } + void setIsInterruptible(boolean value) { + buffer.putByte(myOffset + 295, (byte) (value ? 1 : 0)); + } + boolean isInvincible() { + int offset = myOffset + 296; + return buffer.getByte(offset) != 0; + } + void setIsInvincible(boolean value) { + buffer.putByte(myOffset + 296, (byte) (value ? 1 : 0)); + } + boolean isLifted() { + int offset = myOffset + 297; + return buffer.getByte(offset) != 0; + } + void setIsLifted(boolean value) { + buffer.putByte(myOffset + 297, (byte) (value ? 1 : 0)); + } + boolean isMorphing() { + int offset = myOffset + 298; + return buffer.getByte(offset) != 0; + } + void setIsMorphing(boolean value) { + buffer.putByte(myOffset + 298, (byte) (value ? 1 : 0)); + } + boolean isMoving() { + int offset = myOffset + 299; + return buffer.getByte(offset) != 0; + } + void setIsMoving(boolean value) { + buffer.putByte(myOffset + 299, (byte) (value ? 1 : 0)); + } + boolean isParasited() { + int offset = myOffset + 300; + return buffer.getByte(offset) != 0; + } + void setIsParasited(boolean value) { + buffer.putByte(myOffset + 300, (byte) (value ? 1 : 0)); + } + boolean isSelected() { + int offset = myOffset + 301; + return buffer.getByte(offset) != 0; + } + void setIsSelected(boolean value) { + buffer.putByte(myOffset + 301, (byte) (value ? 1 : 0)); + } + boolean isStartingAttack() { + int offset = myOffset + 302; + return buffer.getByte(offset) != 0; + } + void setIsStartingAttack(boolean value) { + buffer.putByte(myOffset + 302, (byte) (value ? 1 : 0)); + } + boolean isStuck() { + int offset = myOffset + 303; + return buffer.getByte(offset) != 0; + } + void setIsStuck(boolean value) { + buffer.putByte(myOffset + 303, (byte) (value ? 1 : 0)); + } + boolean isTraining() { + int offset = myOffset + 304; + return buffer.getByte(offset) != 0; + } + void setIsTraining(boolean value) { + buffer.putByte(myOffset + 304, (byte) (value ? 1 : 0)); + } + boolean isUnderStorm() { + int offset = myOffset + 305; + return buffer.getByte(offset) != 0; + } + void setIsUnderStorm(boolean value) { + buffer.putByte(myOffset + 305, (byte) (value ? 1 : 0)); + } + boolean isUnderDarkSwarm() { + int offset = myOffset + 306; + return buffer.getByte(offset) != 0; + } + void setIsUnderDarkSwarm(boolean value) { + buffer.putByte(myOffset + 306, (byte) (value ? 1 : 0)); + } + boolean isUnderDWeb() { + int offset = myOffset + 307; + return buffer.getByte(offset) != 0; + } + void setIsUnderDWeb(boolean value) { + buffer.putByte(myOffset + 307, (byte) (value ? 1 : 0)); + } + boolean isPowered() { + int offset = myOffset + 308; + return buffer.getByte(offset) != 0; + } + void setIsPowered(boolean value) { + buffer.putByte(myOffset + 308, (byte) (value ? 1 : 0)); + } + boolean isVisible(int i) { + int offset = myOffset + 309 + 1 * 1 * i; + return buffer.getByte(offset) != 0; + } + void setIsVisible(int i, boolean value) { + buffer.putByte(myOffset + 309 + 1 * 1 * i, (byte) (value ? 1 : 0)); + } + int getButtonset() { + int offset = myOffset + 320; + return buffer.getInt(offset); + } + void setButtonset(int value) { + buffer.putInt(myOffset + 320, value); + } + int getLastAttackerPlayer() { + int offset = myOffset + 324; + return buffer.getInt(offset); + } + void setLastAttackerPlayer(int value) { + buffer.putInt(myOffset + 324, value); + } + boolean getRecentlyAttacked() { + int offset = myOffset + 328; + return buffer.getByte(offset) != 0; + } + void setRecentlyAttacked(boolean value) { + buffer.putByte(myOffset + 328, (byte) (value ? 1 : 0)); + } + int getReplayID() { + int offset = myOffset + 332; + return buffer.getInt(offset); + } + void setReplayID(int value) { + buffer.putInt(myOffset + 332, value); + } + } +} + diff --git a/src/main/java/bwapi/Color.java b/src/main/java/bwapi/Color.java index 03b86ad..f37544d 100644 --- a/src/main/java/bwapi/Color.java +++ b/src/main/java/bwapi/Color.java @@ -11,7 +11,7 @@ * Starcraft uses a 256 color palette for rendering. Thus, the colors available are * limited to this palette. */ -public class Color { +public final class Color { /** * The default color for Player 1. */ diff --git a/src/main/java/bwapi/EventHandler.java b/src/main/java/bwapi/EventHandler.java index a0bf860..aad6f8d 100644 --- a/src/main/java/bwapi/EventHandler.java +++ b/src/main/java/bwapi/EventHandler.java @@ -1,19 +1,7 @@ package bwapi; - -class EventHandler implements Client.EventHandler { - private final BWEventListener eventListener; - private final Game game; - private final Client client; - - EventHandler(final BWEventListener eventListener, final Client client) { - this.eventListener = eventListener; - this.game = new Game(client); - this.client = client; - } - - @Override - public void operation(final ClientData.Event event) { +class EventHandler { + static void operation(BWEventListener eventListener, Game game, final ClientData.Event event) { final Unit u; final int frames = game.getFrameCount(); switch (event.getType()) { @@ -30,10 +18,10 @@ public void operation(final ClientData.Event event) { break; //case 3: //MenuFrame case SendText: - eventListener.onSendText(client.eventString(event.getV1())); + eventListener.onSendText(game.botClientData().gameData().getEventStrings(event.getV1())); break; case ReceiveText: - eventListener.onReceiveText(game.getPlayer(event.getV1()), client.eventString(event.getV2())); + eventListener.onReceiveText(game.getPlayer(event.getV1()), game.botClientData().gameData().getEventStrings(event.getV2())); break; case PlayerLeft: eventListener.onPlayerLeft(game.getPlayer(event.getV1())); @@ -42,7 +30,7 @@ public void operation(final ClientData.Event event) { eventListener.onNukeDetect(new Position(event.getV1(), event.getV2())); break; case SaveGame: - eventListener.onSaveGame(client.eventString(event.getV1())); + eventListener.onSaveGame(game.botClientData().gameData().getEventStrings(event.getV1())); break; case UnitDiscover: game.unitCreate(event.getV1()); @@ -93,8 +81,4 @@ public void operation(final ClientData.Event event) { break; } } - - public Game getGame() { - return game; - } } \ No newline at end of file diff --git a/src/main/java/bwapi/Force.java b/src/main/java/bwapi/Force.java index 9c20e76..3e01e61 100644 --- a/src/main/java/bwapi/Force.java +++ b/src/main/java/bwapi/Force.java @@ -13,7 +13,7 @@ * It is not called a team because players on the same force do not necessarily need * to be allied at the beginning of a match. */ -public class Force implements Comparable { +public final class Force implements Comparable { private final Game game; private final int id; diff --git a/src/main/java/bwapi/FrameBuffer.java b/src/main/java/bwapi/FrameBuffer.java new file mode 100644 index 0000000..cebe980 --- /dev/null +++ b/src/main/java/bwapi/FrameBuffer.java @@ -0,0 +1,197 @@ +package bwapi; + +import sun.misc.Unsafe; + +import java.util.ArrayList; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Circular buffer of game states. + */ +class FrameBuffer { + private static final int BUFFER_SIZE = ClientData.GameData.SIZE; + private static final Unsafe unsafe = UnsafeTools.getUnsafe(); + + private WrappedBuffer liveData; + private PerformanceMetrics performanceMetrics; + private BWClientConfiguration configuration; + private int capacity; + private int stepGame = 0; + private int stepBot = 0; + private ArrayList dataBuffer = new ArrayList<>(); + + private final Lock lockWrite = new ReentrantLock(); + final Lock lockSize = new ReentrantLock(); + final Condition conditionSize = lockSize.newCondition(); + + FrameBuffer(BWClientConfiguration configuration) { + this.capacity = configuration.getAsyncFrameBufferCapacity(); + this.configuration = configuration; + while(dataBuffer.size() < capacity) { + dataBuffer.add(new WrappedBuffer(BUFFER_SIZE)); + } + } + + /** + * Resets for a new game + */ + void initialize(WrappedBuffer liveData, PerformanceMetrics performanceMetrics) { + this.liveData = liveData; + this.performanceMetrics = performanceMetrics; + stepGame = 0; + stepBot = 0; + } + + /** + * @return The number of frames currently buffered ahead of the bot's current frame + */ + synchronized int framesBuffered() { + return stepGame - stepBot; + } + + /** + * @return Number of frames currently stored in the buffer + */ + int size() { + lockSize.lock(); + try { + return framesBuffered(); + } finally { + lockSize.unlock(); + } + } + + /** + * @return Whether the frame buffer is empty and has no frames available for the bot to consume. + */ + boolean empty() { + return size() <= 0; + } + + /** + * @return Whether the frame buffer is full and can not buffer any additional frames. + * When the frame buffer is full, JBWAPI must wait for the bot to complete a frame before returning control to StarCraft. + */ + boolean full() { + lockSize.lock(); + try { + return framesBuffered() >= capacity; + } finally { + lockSize.unlock(); + } + } + + private int indexGame() { + return stepGame % capacity; + } + + private int indexBot() { + return stepBot % capacity; + } + + /** + * Copy dataBuffer from shared memory into the head of the frame buffer. + */ + void enqueueFrame() { + lockWrite.lock(); + try { + lockSize.lock(); + try { + while (full()) { + configuration.log("Main: Waiting for frame buffer capacity"); + performanceMetrics.getIntentionallyBlocking().startTiming(); + conditionSize.awaitUninterruptibly(); + } + performanceMetrics.getIntentionallyBlocking().stopTiming(); + } finally { lockSize.unlock(); }; + + // For the first frame of the game, populate all buffers completely + // This is to ensure all buffers have access to immutable data like regions/walkability/buildability + // Afterwards, we want to shorten this process by only copying important and mutable data + if (stepGame == 0) { + for (WrappedBuffer frameBuffer : dataBuffer) { + copyBuffer(liveData, frameBuffer, true); + } + } else { + performanceMetrics.getCopyingToBuffer().time(() -> { + WrappedBuffer dataTarget = dataBuffer.get(indexGame()); + copyBuffer(liveData, dataTarget, false); + }); + } + + lockSize.lock(); + try { + performanceMetrics.getFrameBufferSize().record(framesBuffered()); + ++stepGame; + conditionSize.signalAll(); + } finally { lockSize.unlock(); } + } finally { lockWrite.unlock(); } + } + + /** + * Peeks the front-most value in the buffer. + */ + WrappedBuffer peek() { + lockSize.lock(); + try { + while(empty()) conditionSize.awaitUninterruptibly(); + return dataBuffer.get(indexBot()); + } finally { lockSize.unlock(); } + } + + /** + * Removes the front-most frame in the buffer. + */ + void dequeue() { + lockSize.lock(); + try { + while(empty()) conditionSize.awaitUninterruptibly(); + ++stepBot; + conditionSize.signalAll(); + } finally { lockSize.unlock(); } + } + + /** + * + * @param source Address to copy from + * @param destination Address to copy to + * @param size Number of bytes to copy + */ + private void copyBuffer(WrappedBuffer source, WrappedBuffer destination, long offset, int size) { + long addressSource = source.getAddress() + offset; + long addressDestination = destination.getAddress() + offset; + unsafe.copyMemory(addressSource, addressDestination, size); + } + + void copyBuffer(WrappedBuffer source, WrappedBuffer destination, boolean copyEverything) { + /* + The speed at which we copy data into the frame buffer is a major cost of JBWAPI's asynchronous operation. + Copy times observed in the wild for the complete buffer usually range from 2.6ms - 19ms + but are prone to large amounts of variance. + + The normal Java way to execute this copy is via ByteBuffer.put(), which has reasonably good performance characteristics. + */ + + if (copyEverything) { + copyBuffer(source, destination, 0, FrameBuffer.BUFFER_SIZE); + } else { + // After the buffer has been filled the first time, + // we can omit copying blocks of data which are unused or which don't change after game start. + // These blocks account for *most* of the 33MB shared memory, + // so omitting them drastically reduces the copy duration + final int STATICTILES_START = 3447004; // getGroundHeight, isWalkable, isBuildable + final int STATICTILES_END = 4823260; + final int REGION_START = 5085404; // getMapTileRegionId, ..., getRegions + final int REGION_END = 10586480; + final int STRINGSSHAPES_START = 10962632; // getStringCount, ... getShapes + final int STRINGSHAPES_END = 32242636; + final int UNITFINDER_START = 32962644; + copyBuffer(source, destination, 0, STATICTILES_START); + copyBuffer(source, destination, STATICTILES_END, REGION_START - STATICTILES_END); + copyBuffer(source, destination, REGION_END, STRINGSSHAPES_START - REGION_END); + copyBuffer(source, destination, STRINGSHAPES_END, UNITFINDER_START - STRINGSHAPES_END); + } + } +} diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 62d06f0..045b794 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -1,8 +1,6 @@ package bwapi; -import bwapi.ClientData.Command; import bwapi.ClientData.GameData; -import bwapi.ClientData.Shape; import java.util.*; import java.util.stream.Collectors; @@ -18,7 +16,7 @@ * game state information from Starcraft Broodwar. Game state information includes all units, * resources, players, forces, bullets, terrain, fog of war, regions, etc. */ -public class Game { +public final class Game { private static final int[][] damageRatio = { // Ind, Sml, Med, Lrg, Non, Unk {0, 0, 0, 0, 0, 0}, // Independent @@ -46,8 +44,7 @@ public class Game { private final Set visibleUnits = new HashSet<>(); private List allUnits; - private final Client client; - private final GameData gameData; + private final ClientData clientData; private List staticMinerals; private List staticGeysers; @@ -98,16 +95,25 @@ public class Game { // USER DEFINED private Text.Size textSize = Text.Size.Default; + private BWClientConfiguration configuration = new BWClientConfiguration(); private boolean latcom = true; + final SideEffectQueue sideEffects = new SideEffectQueue(); - Game(Client client) { - this.client = client; - this.gameData = client.gameData(); + Game() { + clientData = new ClientData(); } - Client getClient() { - return client; + void setConfiguration(BWClientConfiguration configuration) { + this.configuration = configuration; + } + + ClientData botClientData() { + return clientData; + } + + private GameData gameData() { + return clientData.gameData(); } private static boolean hasPower(final int x, final int y, final UnitType unitType, final List pylons) { @@ -141,18 +147,18 @@ private static boolean hasPower(final int x, final int y, final UnitType unitTyp void init() { visibleUnits.clear(); - final int forceCount = gameData.getForceCount(); + final int forceCount = gameData().getForceCount(); forces = new Force[forceCount]; for (int id = 0; id < forceCount; id++) { - forces[id] = new Force(gameData.getForces(id), id, this); + forces[id] = new Force(gameData().getForces(id), id, this); } forceSet = Collections.unmodifiableList(Arrays.asList(forces)); - final int playerCount = gameData.getPlayerCount(); + final int playerCount = gameData().getPlayerCount(); players = new Player[playerCount]; for (int id = 0; id < playerCount; id++) { - players[id] = new Player(gameData.getPlayers(id), id, this); + players[id] = new Player(gameData().getPlayers(id), id, this); } playerSet = Collections.unmodifiableList(Arrays.asList(players)); @@ -160,13 +166,13 @@ void init() { final int bulletCount = 100; bullets = new Bullet[bulletCount]; for (int id = 0; id < bulletCount; id++) { - bullets[id] = new Bullet(gameData.getBullets(id), id, this); + bullets[id] = new Bullet(gameData().getBullets(id), id, this); } - final int regionCount = gameData.getRegionCount(); + final int regionCount = gameData().getRegionCount(); regions = new Region[regionCount]; for (int id = 0; id < regionCount; id++) { - regions[id] = new Region(gameData.getRegions(id), this); + regions[id] = new Region(gameData().getRegions(id), this); } for (final Region region : regions) { @@ -177,32 +183,32 @@ void init() { units = new Unit[10000]; - randomSeed = gameData.getRandomSeed(); - - revision = gameData.getRevision(); - debug = gameData.isDebug(); - replay = gameData.isReplay(); - neutral = players[gameData.getNeutral()]; - self = isReplay() ? null : players[gameData.getSelf()]; - enemy = isReplay() ? null : players[gameData.getEnemy()]; - multiplayer = gameData.isMultiplayer(); - battleNet = gameData.isBattleNet(); - startLocations = IntStream.range(0, gameData.getStartLocationCount()) - .mapToObj(i -> new TilePosition(gameData.getStartLocations(i))) + randomSeed = gameData().getRandomSeed(); + + revision = gameData().getRevision(); + debug = gameData().isDebug(); + replay = gameData().isReplay(); + neutral = players[gameData().getNeutral()]; + self = isReplay() ? null : players[gameData().getSelf()]; + enemy = isReplay() ? null : players[gameData().getEnemy()]; + multiplayer = gameData().isMultiplayer(); + battleNet = gameData().isBattleNet(); + startLocations = IntStream.range(0, gameData().getStartLocationCount()) + .mapToObj(i -> new TilePosition(gameData().getStartLocations(i))) .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); - mapWidth = gameData.getMapWidth(); - mapHeight = gameData.getMapHeight(); - mapFileName = gameData.getMapFileName(); - mapPathName = gameData.getMapPathName(); - mapName = gameData.getMapName(); - mapHash = gameData.getMapHash(); + mapWidth = gameData().getMapWidth(); + mapHeight = gameData().getMapHeight(); + mapFileName = gameData().getMapFileName(); + mapPathName = gameData().getMapPathName(); + mapName = gameData().getMapName(); + mapHash = gameData().getMapHash(); final List staticMinerals = new ArrayList<>(); final List staticGeysers = new ArrayList<>(); final List staticNeutralUnits = new ArrayList<>(); final List allUnits = new ArrayList<>(); - for (int id = 0; id < gameData.getInitialUnitCount(); id++) { - final Unit unit = new Unit(gameData.getUnits(id), id, this); + for (int id = 0; id < gameData().getInitialUnitCount(); id++) { + final Unit unit = new Unit(gameData().getUnits(id), id, this); //skip ghost units if (unit.getInitialType() == UnitType.Terran_Marine && unit.getInitialHitPoints() == 0) { continue; @@ -231,15 +237,15 @@ void init() { mapTileRegionID = new short[mapWidth][mapHeight]; for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { - buildable[x][y] = gameData.isBuildable(x, y); - groundHeight[x][y] = gameData.getGroundHeight(x, y); - mapTileRegionID[x][y] = gameData.getMapTileRegionId(x, y); + buildable[x][y] = gameData().isBuildable(x, y); + groundHeight[x][y] = gameData().getGroundHeight(x, y); + mapTileRegionID[x][y] = gameData().getMapTileRegionId(x, y); } } walkable = new boolean[mapWidth * TILE_WALK_FACTOR][mapHeight * TILE_WALK_FACTOR]; for (int i = 0; i < mapWidth * TILE_WALK_FACTOR; i++) { for (int j = 0; j < mapHeight * TILE_WALK_FACTOR; j++) { - walkable[i][j] = gameData.isWalkable(i, j); + walkable[i][j] = gameData().isWalkable(i, j); } } @@ -247,9 +253,9 @@ void init() { mapSplitTilesRegion1 = new short[REGION_DATA_SIZE]; mapSplitTilesRegion2 = new short[REGION_DATA_SIZE]; for (int i = 0; i < REGION_DATA_SIZE; i++) { - mapSplitTilesMiniTileMask[i] = gameData.getMapSplitTilesMiniTileMask(i); - mapSplitTilesRegion1[i] = gameData.getMapSplitTilesRegion1(i); - mapSplitTilesRegion2[i] = gameData.getMapSplitTilesRegion2(i); + mapSplitTilesMiniTileMask[i] = gameData().getMapSplitTilesMiniTileMask(i); + mapSplitTilesRegion1[i] = gameData().getMapSplitTilesRegion1(i); + mapSplitTilesRegion2[i] = gameData().getMapSplitTilesRegion2(i); } mapPixelWidth = mapWidth * TilePosition.SIZE_IN_PIXELS; @@ -269,7 +275,7 @@ void init() { observers = playerSet.stream().filter(p -> !p.equals(self()) && p.isObserver()) .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); } - setLatCom(true); + setLatCom(!configuration.getAsync()); } void unitCreate(final int id) { @@ -281,7 +287,7 @@ void unitCreate(final int id) { } if (units[id] == null) { - final Unit u = new Unit(gameData.getUnits(id), id, this); + final Unit u = new Unit(gameData().getUnits(id), id, this); units[id] = u; } } @@ -304,37 +310,6 @@ void onFrame(final int frame) { getAllUnits().forEach(u -> u.updatePosition(frame)); } - void addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { - ClientData.UnitCommand unitCommand = client.addUnitCommand(); - unitCommand.setTid(type); - unitCommand.setUnitIndex(unit); - unitCommand.setTargetIndex(target); - unitCommand.setX(x); - unitCommand.setY(y); - unitCommand.setExtra(extra); - } - - void addCommand(final CommandType type, final int value1, final int value2) { - Command command = client.addCommand(); - command.setType(type); - command.setValue1(value1); - command.setValue2(value2); - } - - void addShape(final ShapeType type, final CoordinateType coordType, final int x1, final int y1, final int x2, final int y2, final int extra1, final int extra2, final int color, final boolean isSolid) { - Shape shape = client.addShape(); - shape.setType(type); - shape.setCtype(coordType); - shape.setX1(x1); - shape.setY1(y1); - shape.setX2(x2); - shape.setY2(y2); - shape.setExtra1(extra1); - shape.setExtra2(extra2); - shape.setColor(color); - shape.setIsSolid(isSolid); - } - /** * Retrieves the set of all teams/forces. Forces are commonly seen in @UMS * game types and some others such as @TvB and the team versions of game types. @@ -457,8 +432,8 @@ public List getBullets() { * @return Set of Positions giving the coordinates of nuke locations. */ public List getNukeDots() { - return IntStream.range(0, gameData.getNukeDotCount()) - .mapToObj(id -> new Position(gameData.getNukeDots(id))) + return IntStream.range(0, gameData().getNukeDotCount()) + .mapToObj(id -> new Position(gameData().getNukeDots(id))) .collect(Collectors.toList()); } @@ -521,7 +496,7 @@ public Region getRegion(final int regionID) { * @see GameType */ public GameType getGameType() { - return GameType.idToEnum[gameData.getGameType()]; + return GameType.idToEnum[gameData().getGameType()]; } /** @@ -532,7 +507,7 @@ public GameType getGameType() { * @see Latency */ public Latency getLatency() { - return Latency.idToEnum[gameData.getLatency()]; + return Latency.idToEnum[gameData().getLatency()]; } /** @@ -542,7 +517,7 @@ public Latency getLatency() { * @return Number of logical frames that have elapsed since the game started as an integer. */ public int getFrameCount() { - return gameData.getFrameCount(); + return gameData().getFrameCount(); } /** @@ -552,7 +527,7 @@ public int getFrameCount() { * @return The number of logical frames that the replay contains. */ public int getReplayFrameCount() { - return gameData.getReplayFrameCount(); + return gameData().getReplayFrameCount(); } /** @@ -562,7 +537,7 @@ public int getReplayFrameCount() { * @see #getAverageFPS */ public int getFPS() { - return gameData.getFps(); + return gameData().getFps(); } /** @@ -573,7 +548,7 @@ public int getFPS() { * @see #getFPS */ public double getAverageFPS() { - return gameData.getAverageFPS(); + return gameData().getAverageFPS(); } /** @@ -582,7 +557,7 @@ public double getAverageFPS() { * @return {@link Position} indicating the location of the mouse. Returns {@link Position#Unknown} if {@link Flag#UserInput} is disabled. */ public Position getMousePosition() { - return new Position(gameData.getMouseX(), gameData.getMouseY()); + return new Position(gameData().getMouseX(), gameData().getMouseY()); } /** @@ -593,8 +568,8 @@ public Position getMousePosition() { * and false if it was not. Returns false always if {@link Flag#UserInput} is disabled. * @see MouseButton */ - public boolean getMouseState(final MouseButton button) { - return gameData.getMouseState(button.id); + final public boolean getMouseState(final MouseButton button) { + return gameData().getMouseState(button.id); } /** @@ -605,8 +580,8 @@ public boolean getMouseState(final MouseButton button) { * and false if it was not. Returns false always if {@link Flag#UserInput} is disabled. * @see Key */ - public boolean getKeyState(final Key key) { - return gameData.getKeyState(key.id); + final public boolean getKeyState(final Key key) { + return gameData().getKeyState(key.id); } /** @@ -617,7 +592,7 @@ public boolean getKeyState(final Key key) { * @see #setScreenPosition */ public Position getScreenPosition() { - return new Position(gameData.getScreenX(), gameData.getScreenY()); + return new Position(gameData().getScreenX(), gameData().getScreenY()); } public void setScreenPosition(final Position p) { @@ -661,8 +636,8 @@ public void pingMinimap(final Position p) { * @return true if the given flag is enabled, false if the flag is disabled. * @see Flag */ - public boolean isFlagEnabled(final Flag flag) { - return gameData.getFlags(flag.id); + final public boolean isFlagEnabled(final Flag flag) { + return gameData().getFlags(flag.id); } /** @@ -892,11 +867,11 @@ public String mapHash() { * @param walkY The y coordinate of the mini-tile, in mini-tile units (8 pixels). * @return true if the mini-tile is walkable and false if it is impassable for ground units. */ - public boolean isWalkable(final int walkX, final int walkY) { + final public boolean isWalkable(final int walkX, final int walkY) { return isWalkable(new WalkPosition(walkX, walkY)); } - public boolean isWalkable(final WalkPosition position) { + final public boolean isWalkable(final WalkPosition position) { if (!position.isValid(this)) { return false; } @@ -928,7 +903,7 @@ public int getGroundHeight(final TilePosition position) { return groundHeight[position.x][position.y]; } - public boolean isBuildable(final int tileX, final int tileY) { + final public boolean isBuildable(final int tileX, final int tileY) { return isBuildable(tileX, tileY, false); } @@ -944,19 +919,24 @@ public boolean isBuildable(final int tileX, final int tileY) { * If includeBuildings was provided, then it will return false if a structure is currently * occupying the tile. */ - public boolean isBuildable(final int tileX, final int tileY, final boolean includeBuildings) { - return isBuildable(new TilePosition(tileX, tileY), includeBuildings); + final public boolean isBuildable(final int tileX, final int tileY, final boolean includeBuildings) { + return isValidTile(tileX, tileY) && buildable[tileX][tileY] && (!includeBuildings || !gameData().isOccupied(tileX, tileY)); } - public boolean isBuildable(final TilePosition position) { + final public boolean isBuildable(final TilePosition position) { return isBuildable(position, false); } - public boolean isBuildable(final TilePosition position, final boolean includeBuildings) { - if (!position.isValid(this)) { - return false; - } - return buildable[position.x][position.y] && (!includeBuildings || !gameData.isOccupied(position.x, position.y)); + boolean isValidPosition(final int x, final int y) { + return x >= 0 && y >= 0 && x < mapPixelWidth && y < mapPixelHeight; + } + + boolean isValidTile(final int x, final int y) { + return x >= 0 && y >= 0 && x < mapWidth && y < mapHeight; + } + + final public boolean isBuildable(final TilePosition position, final boolean includeBuildings) { + return isBuildable(position.x, position.y, includeBuildings); } /** @@ -968,15 +948,12 @@ public boolean isBuildable(final TilePosition position, final boolean includeBui * the value is true. If the given tile is concealed by the fog of war, then this value will * be false. */ - public boolean isVisible(final int tileX, final int tileY) { - return isVisible(new TilePosition(tileX, tileY)); + final public boolean isVisible(final int tileX, final int tileY) { + return isValidTile(tileX, tileY) && gameData().isVisible(tileX, tileY); } - public boolean isVisible(final TilePosition position) { - if (!position.isValid(this)) { - return false; - } - return gameData.isVisible(position.x, position.y); + final public boolean isVisible(final TilePosition position) { + return isVisible(position.x, position.y); } /** @@ -989,15 +966,12 @@ public boolean isVisible(final TilePosition position) { * @return true if the player has explored the given tile position (partially revealed fog), false if the tile position was never explored (completely black fog). * @see #isVisible */ - public boolean isExplored(final int tileX, final int tileY) { - return isExplored(new TilePosition(tileX, tileY)); + final public boolean isExplored(final int tileX, final int tileY) { + return isValidTile(tileX, tileY) && gameData().isExplored(tileX, tileY); } - public boolean isExplored(final TilePosition position) { - if (!position.isValid(this)) { - return false; - } - return gameData.isExplored(position.x, position.y); + final public boolean isExplored(final TilePosition position) { + return isExplored(position.x, position.y); } /** @@ -1007,18 +981,15 @@ public boolean isExplored(final TilePosition position) { * @param tileY The y tile coordinate to check. * @return true if the given tile has creep on it, false if the given tile does not have creep, or if it is concealed by the fog of war. */ - public boolean hasCreep(final int tileX, final int tileY) { - return hasCreep(new TilePosition(tileX, tileY)); + final public boolean hasCreep(final int tileX, final int tileY) { + return isValidTile(tileX, tileY) && gameData().getHasCreep(tileX, tileY); } - public boolean hasCreep(final TilePosition position) { - if (!position.isValid(this)) { - return false; - } - return gameData.getHasCreep(position.x, position.y); + final public boolean hasCreep(final TilePosition position) { + return hasCreep(position.x, position.y); } - public boolean hasPowerPrecise(final int x, final int y) { + final public boolean hasPowerPrecise(final int x, final int y) { return hasPowerPrecise(new Position(x, y)); } @@ -1031,41 +1002,38 @@ public boolean hasPowerPrecise(final int x, final int y) { * @param unitType Checks if the given {@link UnitType} requires power or not. If ommitted, then it will assume that the position requires power for any unit type. * @return true if the type at the given position will have power, false if the type at the given position will be unpowered. */ - public boolean hasPowerPrecise(final int x, final int y, final UnitType unitType) { - return hasPowerPrecise(new Position(x, y), unitType); + final public boolean hasPowerPrecise(final int x, final int y, final UnitType unitType) { + return isValidPosition(x, y) && hasPower(x, y, unitType, self().getUnits().stream().filter(u -> u.getType() == Protoss_Pylon).collect(Collectors.toList())); } - public boolean hasPowerPrecise(final Position position) { - return hasPowerPrecise(position, UnitType.None); + final public boolean hasPowerPrecise(final Position position) { + return hasPowerPrecise(position.x, position.y, UnitType.None); } - public boolean hasPowerPrecise(final Position position, final UnitType unitType) { - if (!position.isValid(this)) { - return false; - } - return hasPower(position.x, position.y, unitType, self().getUnits().stream().filter(u -> u.getType() == Protoss_Pylon).collect(Collectors.toList())); + final public boolean hasPowerPrecise(final Position position, final UnitType unitType) { + return hasPowerPrecise(position.x, position.y, unitType); } - public boolean hasPower(final int tileX, final int tileY) { + final public boolean hasPower(final int tileX, final int tileY) { return hasPower(new TilePosition(tileX, tileY)); } - public boolean hasPower(final int tileX, final int tileY, final UnitType unitType) { + final public boolean hasPower(final int tileX, final int tileY, final UnitType unitType) { return hasPower(new TilePosition(tileX, tileY), unitType); } - public boolean hasPower(final TilePosition position) { + final public boolean hasPower(final TilePosition position) { return hasPower(position.x, position.y, UnitType.None); } - public boolean hasPower(final TilePosition position, final UnitType unitType) { + final public boolean hasPower(final TilePosition position, final UnitType unitType) { if (unitType.id >= 0 && unitType.id < UnitType.None.id) { return hasPowerPrecise(position.x * 32 + unitType.tileWidth() * 16, position.y * 32 + unitType.tileHeight() * 16, unitType); } return hasPowerPrecise(position.x * 32, position.y * 32, UnitType.None); } - public boolean hasPower(final int tileX, final int tileY, final int tileWidth, final int tileHeight) { + final public boolean hasPower(final int tileX, final int tileY, final int tileWidth, final int tileHeight) { return hasPower(tileX, tileY, tileWidth, tileHeight, UnitType.Unknown); } @@ -1078,23 +1046,23 @@ public boolean hasPower(final int tileX, final int tileY, final int tileWidth, f * @param unitType Checks if the given UnitType will be powered if placed at the given tile position. If omitted, then only the immediate tile position is checked for power, and the function will assume that the location requires power for any unit type. * @return true if the type at the given tile position will receive power, false if the type will be unpowered at the given tile position. */ - public boolean hasPower(final int tileX, final int tileY, final int tileWidth, final int tileHeight, final UnitType unitType) { + final public boolean hasPower(final int tileX, final int tileY, final int tileWidth, final int tileHeight, final UnitType unitType) { return hasPowerPrecise(tileX * 32 + tileWidth * 16, tileY * 32 + tileHeight * 16, unitType); } - public boolean hasPower(final TilePosition position, final int tileWidth, final int tileHeight) { + final public boolean hasPower(final TilePosition position, final int tileWidth, final int tileHeight) { return hasPower(position.x, position.y, tileWidth, tileHeight); } - public boolean hasPower(final TilePosition position, final int tileWidth, final int tileHeight, final UnitType unitType) { + final public boolean hasPower(final TilePosition position, final int tileWidth, final int tileHeight, final UnitType unitType) { return hasPower(position.x, position.y, tileWidth, tileHeight, unitType); } - public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder) { + final public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder) { return canBuildHere(position, type, builder, false); } - public boolean canBuildHere(final TilePosition position, final UnitType type) { + final public boolean canBuildHere(final TilePosition position, final UnitType type) { return canBuildHere(position, type, null); } @@ -1118,7 +1086,7 @@ public boolean canBuildHere(final TilePosition position, final UnitType type) { * @return true indicating that the structure can be placed at the given tile position, and * false if something may be obstructing the build location. */ - public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder, final boolean checkExplored) { + final public boolean canBuildHere(final TilePosition position, final UnitType type, final Unit builder, final boolean checkExplored) { // lt = left top, rb = right bottom final TilePosition lt = builder != null && type.isAddon() ? position.add(new TilePosition(4, 1)) : // addon build offset @@ -1233,7 +1201,7 @@ public boolean canBuildHere(final TilePosition position, final UnitType type, fi return true; } - public boolean canMake(final UnitType type) { + final public boolean canMake(final UnitType type) { return canMake(type, null); } @@ -1248,7 +1216,7 @@ public boolean canMake(final UnitType type) { * only true if builder can make the type. Otherwise it will return false, indicating * that the unit type can not be made. */ - public boolean canMake(final UnitType type, final Unit builder) { + final public boolean canMake(final UnitType type, final Unit builder) { final Player pSelf = self(); // Error checking if (pSelf == null) { @@ -1358,11 +1326,11 @@ public boolean canMake(final UnitType type, final Unit builder) { (builder.getAddon() != null && builder.getAddon().getType() == addon); } - public boolean canResearch(final TechType type, final Unit unit) { + final public boolean canResearch(final TechType type, final Unit unit) { return canResearch(type, unit, true); } - public boolean canResearch(final TechType type) { + final public boolean canResearch(final TechType type) { return canResearch(type, null); } @@ -1378,7 +1346,7 @@ public boolean canResearch(final TechType type) { * only true if unit can research the type. Otherwise it will return false, indicating * that the technology can not be researched. */ - public boolean canResearch(final TechType type, final Unit unit, final boolean checkCanIssueCommandType) { + final public boolean canResearch(final TechType type, final Unit unit, final boolean checkCanIssueCommandType) { final Player self = self(); // Error checking if (self == null) { @@ -1422,11 +1390,11 @@ public boolean canResearch(final TechType type, final Unit unit, final boolean c } - public boolean canUpgrade(final UpgradeType type, final Unit unit) { + final public boolean canUpgrade(final UpgradeType type, final Unit unit) { return canUpgrade(type, unit, true); } - public boolean canUpgrade(final UpgradeType type) { + final public boolean canUpgrade(final UpgradeType type) { return canUpgrade(type, null); } @@ -1442,7 +1410,7 @@ public boolean canUpgrade(final UpgradeType type) { * only true if unit can upgrade the type. Otherwise it will return false, indicating * that the upgrade can not be upgraded. */ - public boolean canUpgrade(final UpgradeType type, final Unit unit, final boolean checkCanIssueCommandType) { + final public boolean canUpgrade(final UpgradeType type, final Unit unit, final boolean checkCanIssueCommandType) { final Player self = self(); if (self == null) { return false; @@ -1513,7 +1481,7 @@ static String formatString(final String string, final Text... colors) { */ public void printf(final String string, final Text... colors) { final String formatted = formatString(string, colors); - addCommand(Printf, client.addString(formatted), 0); + addCommand(Printf, formatted, 0); } /** @@ -1538,7 +1506,7 @@ public void sendText(final String string, final Text... colors) { */ public void sendTextEx(final boolean toAllies, final String string, final Text... colors) { final String formatted = formatString(string, colors); - addCommand(SendText, client.addString(formatted), toAllies ? 1 : 0); + addCommand(SendText, formatted, toAllies ? 1 : 0); } /** @@ -1546,8 +1514,8 @@ public void sendTextEx(final boolean toAllies, final String string, final Text.. * * @return true if the client is in a game, and false if it is not. */ - public boolean isInGame() { - return gameData.isInGame(); + final public boolean isInGame() { + return gameData().isInGame(); } /** @@ -1556,7 +1524,7 @@ public boolean isInGame() { * @return true if the client is in a multiplayer game, and false if it is a single player * game, a replay, or some other state. */ - public boolean isMultiplayer() { + final public boolean isMultiplayer() { return multiplayer; } @@ -1566,7 +1534,7 @@ public boolean isMultiplayer() { * * @return true if the client is in a multiplayer Battle.net game and false if it is not. */ - public boolean isBattleNet() { + final public boolean isBattleNet() { return battleNet; } @@ -1578,8 +1546,8 @@ public boolean isBattleNet() { * @see #pauseGame * @see #resumeGame */ - public boolean isPaused() { - return gameData.isPaused(); + final public boolean isPaused() { + return gameData().isPaused(); } /** @@ -1587,7 +1555,7 @@ public boolean isPaused() { * * @return true if the client is watching a replay and false otherwise */ - public boolean isReplay() { + final public boolean isReplay() { return replay; } @@ -1660,7 +1628,7 @@ public void setLocalSpeed(final int speed) { * @return true if any one of the units in the List were capable of executing the * command, and false if none of the units were capable of executing the command. */ - public boolean issueCommand(final Collection units, final UnitCommand command) { + final public boolean issueCommand(final Collection units, final UnitCommand command) { return units.stream() .map(u -> u.issueCommand(command)) .reduce(false, (a, b) -> a | b); @@ -1678,8 +1646,8 @@ public List getSelectedUnits() { if (!isFlagEnabled(Flag.UserInput)) { return Collections.emptyList(); } - return IntStream.range(0, gameData.getSelectedUnitCount()) - .mapToObj(i -> units[gameData.getSelectedUnits(i)]) + return IntStream.range(0, gameData().getSelectedUnitCount()) + .mapToObj(i -> units[gameData().getSelectedUnits(i)]) .collect(Collectors.toList()); } @@ -1747,8 +1715,7 @@ public List observers() { public void drawText(final CoordinateType ctype, final int x, final int y, final String string, final Text... colors) { final String formatted = formatString(string, colors); - final int stringId = client.addString(formatted); - addShape(ShapeType.Text, ctype, x, y, 0, 0, stringId, textSize.id, 0, false); + addShape(ShapeType.Text, ctype, x, y, 0, 0, formatted, textSize.id, 0, false); } public void drawTextMap(final int x, final int y, final String string, final Text... colors) { @@ -2130,7 +2097,7 @@ public void drawLineScreen(Position a, Position b, Color color) { * @see #getRemainingLatencyFrames */ public int getLatencyFrames() { - return gameData.getLatencyFrames(); + return gameData().getLatencyFrames(); } /** @@ -2142,7 +2109,7 @@ public int getLatencyFrames() { * @see #getRemainingLatencyTime */ public int getLatencyTime() { - return gameData.getLatencyTime(); + return gameData().getLatencyTime(); } /** @@ -2155,7 +2122,7 @@ public int getLatencyTime() { * @see #getLatencyFrames */ public int getRemainingLatencyFrames() { - return gameData.getRemainingLatencyFrames(); + return gameData().getRemainingLatencyFrames(); } /** @@ -2168,7 +2135,7 @@ public int getRemainingLatencyFrames() { * @see #getLatencyTime */ public int getRemainingLatencyTime() { - return gameData.getRemainingLatencyTime(); + return gameData().getRemainingLatencyTime(); } /** @@ -2185,7 +2152,7 @@ public int getRevision() { * * @return true if the BWAPI module is a DEBUG build, and false if it is a RELEASE build. */ - public boolean isDebug() { + final public boolean isDebug() { return debug; } @@ -2195,7 +2162,7 @@ public boolean isDebug() { * @return true if latency compensation is enabled, false if it is disabled. * @see #setLatCom */ - public boolean isLatComEnabled() { + final public boolean isLatComEnabled() { return latcom; } @@ -2209,8 +2176,11 @@ public boolean isLatComEnabled() { * @see #isLatComEnabled */ public void setLatCom(final boolean isEnabled) { + if (isEnabled && configuration.getAsync()) { + throw new IllegalStateException("Latency compensation is not compatible with JBWAPI asynchronous mode."); + } //update shared memory - gameData.setHasLatCom(isEnabled); + gameData().setHasLatCom(isEnabled); //update internal memory latcom = isEnabled; //update server @@ -2225,7 +2195,7 @@ public void setLatCom(final boolean isEnabled) { * @return An integer value representing the instance number. */ public int getInstanceNumber() { - return gameData.getInstanceID(); + return gameData().getInstanceID(); } public int getAPM() { @@ -2239,12 +2209,12 @@ public int getAPM() { * @return The number of actions that the bot has executed per minute, on average. */ public int getAPM(final boolean includeSelects) { - return includeSelects ? gameData.getBotAPM_selects() : gameData.getBotAPM_noselects(); + return includeSelects ? gameData().getBotAPM_selects() : gameData().getBotAPM_noselects(); } /** * Sets the number of graphical frames for every logical frame. This - * allows the game to run more logical frames per graphical frame, increasing the speed at + * allows the game to step more logical frames per graphical frame, increasing the speed at * which the game runs. * * @param frameSkip Number of graphical frames per logical frame. If this value is 0 or less, then it will default to 1. @@ -2264,7 +2234,7 @@ public void setFrameSkip(int frameSkip) { * allied players have eliminated their opponents. Otherwise, the game will only end if * no other players are remaining in the game. This value is true by default. */ - public boolean setAlliance(Player player, boolean allied, boolean alliedVictory) { + final public boolean setAlliance(Player player, boolean allied, boolean alliedVictory) { if (self() == null || isReplay() || player == null || player.equals(self())) { return false; } @@ -2273,11 +2243,11 @@ public boolean setAlliance(Player player, boolean allied, boolean alliedVictory) return true; } - public boolean setAlliance(Player player, boolean allied) { + final public boolean setAlliance(Player player, boolean allied) { return setAlliance(player, allied, true); } - public boolean setAlliance(Player player) { + final public boolean setAlliance(Player player) { return setAlliance(player, true); } @@ -2293,7 +2263,7 @@ public boolean setAlliance(Player player) { * of the target player will be shown, otherwise the target player will be hidden. This * value is true by default. */ - public boolean setVision(Player player, boolean enabled) { + final public boolean setVision(Player player, boolean enabled) { if (player == null) { return false; } @@ -2316,7 +2286,7 @@ public boolean setVision(Player player, boolean enabled) { * @see #setGUI */ boolean isGUIEnabled() { - return gameData.getHasGUI(); + return gameData().getHasGUI(); } /** @@ -2329,7 +2299,7 @@ boolean isGUIEnabled() { * @see #isGUIEnabled */ public void setGUI(boolean enabled) { - gameData.setHasGUI(enabled); + gameData().setHasGUI(enabled); //queue up command for server so it also applies the change addCommand(CommandType.SetGui, enabled ? 1 : 0, 0); } @@ -2358,12 +2328,12 @@ public int getLastEventTime() { * does not have permission from the tournament module, failed to find the map specified, or received an invalid * parameter. */ - public boolean setMap(final String mapFileName) { + final public boolean setMap(final String mapFileName) { if (mapFileName == null || mapFileName.length() >= 260 || mapFileName.charAt(0) == 0) { return false; } - addCommand(CommandType.SetMap, client.addString(mapFileName), 0); + addCommand(CommandType.SetMap, mapFileName, 0); return true; } @@ -2373,7 +2343,7 @@ public boolean setMap(final String mapFileName) { * @param reveal The state of the reveal all flag. If false, all fog of war will be enabled. If true, * then the fog of war will be revealed. It is true by default. */ - public boolean setRevealAll(boolean reveal) { + final public boolean setRevealAll(boolean reveal) { if (!isReplay()) { return false; } @@ -2381,7 +2351,7 @@ public boolean setRevealAll(boolean reveal) { return true; } - public boolean setRevealAll() { + final public boolean setRevealAll() { return setRevealAll(true); } @@ -2401,7 +2371,7 @@ public boolean setRevealAll() { * @return true if there is a path between the two positions, and false if there is not. * @see Unit#hasPath */ - public boolean hasPath(final Position source, final Position destination) { + final public boolean hasPath(final Position source, final Position destination) { if (source == null || destination == null) { return false; } @@ -2433,7 +2403,7 @@ public void setTextSize(final Text.Size size) { * @return Time, in seconds, that the game has elapsed as an integer. */ public int elapsedTime() { - return gameData.getElapsedTime(); + return gameData().getElapsedTime(); } /** @@ -2537,7 +2507,7 @@ public void setCommandOptimizationLevel(final int level) { * @return Integer containing the time (in game seconds) on the countdown timer. */ public int countdownTimer() { - return gameData.getCountdownTimer(); + return gameData().getCountdownTimer(); } /** @@ -2687,4 +2657,57 @@ public int getDamageTo(final UnitType toType, final UnitType fromType, final Pla public int getRandomSeed() { return randomSeed; } + + /** + * Convenience method for adding a unit command from raw arguments. + */ + void addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { + enqueueOrDo(SideEffect.addUnitCommand(type, unit, target, x, y, extra)); + } + + /** + * Convenience method for adding a game command from raw arguments. + */ + void addCommand(final CommandType type, final int value1, final int value2) { + enqueueOrDo(SideEffect.addCommand(type, value1, value2)); + } + + /** + * Convenience method for adding a game command from raw arguments. + */ + void addCommand(final CommandType type, final String value1, final int value2) { + enqueueOrDo(SideEffect.addCommand(type, value1, value2)); + } + + /** + * Convenience method for adding a shape from raw arguments. + */ + void addShape(final ShapeType type, final CoordinateType coordType, final int x1, final int y1, final int x2, final int y2, final int extra1, final int extra2, final int color, final boolean isSolid) { + enqueueOrDo(SideEffect.addShape(type, coordType, x1, y1, x2, y2, extra1, extra2, color, isSolid)); + } + + /** + * Convenience method for adding a shape from raw arguments. + */ + void addShape(final ShapeType type, final CoordinateType coordType, final int x1, final int y1, final int x2, final int y2, final String text, final int extra2, final int color, final boolean isSolid) { + enqueueOrDo(SideEffect.addShape(type, coordType, x1, y1, x2, y2, text, extra2, color, isSolid)); + } + + /** + * Applies a side effect, either immediately (if operating synchronously) + * or by enqueuing it for later execution (if operating asynchronously). + * + * @param sideEffect + */ + void enqueueOrDo(SideEffect sideEffect) { + if (configuration.getAsync()) { + sideEffects.enqueue(sideEffect); + } else { + sideEffect.apply(gameData()); + } + } + + void setAllUnits(List units) { + allUnits = Collections.unmodifiableList(units); + } } diff --git a/src/main/java/bwapi/GameDataUtils.java b/src/main/java/bwapi/GameDataUtils.java new file mode 100644 index 0000000..c637c5b --- /dev/null +++ b/src/main/java/bwapi/GameDataUtils.java @@ -0,0 +1,79 @@ +/* +MIT License + +Copyright (c) 2018 Hannes Bredberg +Modified work Copyright (c) 2018 Jasper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package bwapi; + +/** + * Static functions for modifying GameData. + * These functions live outside GameData because GameData is auto-generated. + */ +class GameDataUtils { + + static final int MAX_COUNT = 19999; + private static final int MAX_STRING_SIZE = 1024; + + static int addString(ClientData.GameData gameData, final String string) { + int stringCount = gameData.getStringCount(); + if (stringCount >= MAX_COUNT) { + throw new IllegalStateException("Too many strings!"); + } + + // Truncate string if its size equals or exceeds 1024 + final String stringTruncated = string.length() >= MAX_STRING_SIZE + ? string.substring(0, MAX_STRING_SIZE - 1) + : string; + + gameData.setStringCount(stringCount + 1); + gameData.setStrings(stringCount, stringTruncated); + return stringCount; + } + + static ClientData.Shape addShape(ClientData.GameData gameData) { + int shapeCount = gameData.getShapeCount(); + if (shapeCount >= MAX_COUNT) { + throw new IllegalStateException("Too many shapes!"); + } + gameData.setShapeCount(shapeCount + 1); + return gameData.getShapes(shapeCount); + } + + static ClientData.Command addCommand(ClientData.GameData gameData) { + final int commandCount = gameData.getCommandCount(); + if (commandCount >= MAX_COUNT) { + throw new IllegalStateException("Too many commands!"); + } + gameData.setCommandCount(commandCount + 1); + return gameData.getCommands(commandCount); + } + + static ClientData.UnitCommand addUnitCommand(ClientData.GameData gameData) { + int unitCommandCount = gameData.getUnitCommandCount(); + if (unitCommandCount >= MAX_COUNT) { + throw new IllegalStateException("Too many unit commands!"); + } + gameData.setUnitCommandCount(unitCommandCount + 1); + return gameData.getUnitCommands(unitCommandCount); + } +} diff --git a/src/main/java/bwapi/PerformanceMetric.java b/src/main/java/bwapi/PerformanceMetric.java new file mode 100644 index 0000000..b296c93 --- /dev/null +++ b/src/main/java/bwapi/PerformanceMetric.java @@ -0,0 +1,167 @@ +package bwapi; + +import com.sun.jna.platform.win32.Kernel32; + +import java.text.DecimalFormat; +import java.util.ArrayList; + +/** + * Aggregates labeled time series data. + */ +public class PerformanceMetric { + public class RunningTotal { + private int samples = 0; + private double last = 0d; + private double mean = 0d; + private double min = Long.MAX_VALUE; + private double max = Long.MIN_VALUE; + void record(double value) { + last = value; + min = Math.min(min, value); + max = Math.max(max, value); + mean = (mean * samples + value) / (samples + 1d); + ++samples; + } + public double getSamples() { + return samples; + } + public double getLast() { + return last; + } + public double getMean() { + return mean; + } + public double getMin() { + return min; + } + public double getMax() { + return max; + } + } + class Threshold { + double threshold; + RunningTotal runningTotal = new RunningTotal(); + Threshold(double value) { + threshold = value; + } + void record(double value) { + if (value >= threshold) { + runningTotal.record(value); + } + } + public String toString() { + if (runningTotal.samples <= 0) { + return ""; + } + DecimalFormat formatter = new DecimalFormat("###,###.#"); + return "\n>= " + formatter.format(threshold) + ": " + runningTotal.samples + " samples averaging " + formatter.format(runningTotal.mean); + } + } + + private final String name; + private long timeStarted = 0; + private int interrupted = 0; + + private final RunningTotal runningTotal = new RunningTotal(); + private ArrayList thresholds = new ArrayList<>(); + + PerformanceMetric(PerformanceMetrics metrics, String name, double... thresholds) { + this.name = name; + for (double threshold : thresholds) { + this.thresholds.add(new Threshold(threshold)); + } + metrics.addMetric(this); + } + + public RunningTotal getRunningTotal() { + return runningTotal; + } + + public int getInterrupted() { + return interrupted; + } + + /** + * Records the duration of a function call. + * @param runnable The function to time + */ + void time(Runnable runnable) { + startTiming(); + runnable.run(); + stopTiming(); + } + + /** + * Convenience method; calls a function; but only records the duration if a condition is met + * @param condition Whether to record the function call duration + * @param runnable The function to call + */ + void timeIf(boolean condition, Runnable runnable) { + if (condition) { + time(runnable); + } else { + runnable.run(); + } + } + + /** + * Manually start timing. + * The next call to stopTiming() will record the duration in fractional milliseconds. + */ + void startTiming() { + if (timeStarted > 0) { + ++interrupted; + } + timeStarted = System.nanoTime(); + } + + + /** + * Manually stop timing. + * If paired with a previous call to startTiming(), records the measured time between the calls in fractional milliseconds. + */ + void stopTiming() { + if (timeStarted <= 0) return; + // Use nanosecond resolution timer, but record in units of milliseconds. + long timeEnded = System.nanoTime(); + long timeDiff = timeEnded - timeStarted; + timeStarted = 0; + record(timeDiff / 1000000d); + } + + /** + * Manually records a specific value. + */ + void record(double value) { + runningTotal.record(value); + thresholds.forEach(threshold -> threshold.record(value)); + } + + /** + * @return A pretty-printed description of the recorded values. + */ + @Override + public String toString() { + if (runningTotal.samples <= 0) { + return name + ": No samples."; + } + DecimalFormat formatter = new DecimalFormat("###,###.#"); + String output = name + + ":\n" + + formatter.format(runningTotal.samples) + + " samples averaging " + + formatter.format(runningTotal.mean) + + " [" + + formatter.format(runningTotal.min) + + " - " + + formatter.format(runningTotal.max) + + "]"; + for (Threshold threshold : thresholds) { + output += threshold.toString(); + } + if (interrupted > 0) { + output += "\n\tInterrupted " + interrupted + " times"; + } + return output; + } +} diff --git a/src/main/java/bwapi/PerformanceMetrics.java b/src/main/java/bwapi/PerformanceMetrics.java new file mode 100644 index 0000000..473f435 --- /dev/null +++ b/src/main/java/bwapi/PerformanceMetrics.java @@ -0,0 +1,218 @@ +package bwapi; + +import java.util.ArrayList; + +/** + * Collects various performance metrics. + */ +public class PerformanceMetrics { + + /** + * Duration of the frame cycle steps measured by BWAPI, + * from receiving a frame to BWAPI + * to sending commands back + * *exclusive* of the time spent sending commands back. + */ + public PerformanceMetric getFrameDurationReceiveToSend() { + return frameDurationReceiveToSend; + } + private PerformanceMetric frameDurationReceiveToSend; + + /** + * Duration of the frame cycle steps measured by BWAPI, + * from receiving a frame to BWAPI + * to sending commands back + * *inclusive* of the time spent sending commands back. + */ + public PerformanceMetric getFrameDurationReceiveToSent() { + return frameDurationReceiveToSent; + } + private PerformanceMetric frameDurationReceiveToSent; + + /** + * Duration of a frame cycle originating at + * the time when JBWAPI observes a new frame in shared memory. + */ + public PerformanceMetric getFrameDurationReceiveToReceive() { + return frameDurationReceiveToReceive; + } + private PerformanceMetric frameDurationReceiveToReceive; + + /** + * Time spent copying game data from system pipe shared memory to a frame buffer. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric getCopyingToBuffer() { + return copyingToBuffer; + } + private PerformanceMetric copyingToBuffer; + + /** + * Time spent intentionally blocking on bot operation due to a full frame buffer. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric getIntentionallyBlocking() { + return intentionallyBlocking; + } + private PerformanceMetric intentionallyBlocking; + + /** + * Number of frames backed up in the frame buffer, after enqueuing each frame (and not including the newest frame). + * Applicable only in asynchronous mode. + */ + public PerformanceMetric getFrameBufferSize() { + return frameBufferSize; + } + private PerformanceMetric frameBufferSize; + + /** + * Number of frames behind real-time the bot is at the time it handles events. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric getFramesBehind() { + return framesBehind; + } + private PerformanceMetric framesBehind; + + /** + * Time spent applying bot commands to the live frame. + */ + public PerformanceMetric getFlushSideEffects() { + return flushSideEffects; + } + private PerformanceMetric flushSideEffects; + + /** + * Time spent waiting for bot event handlers to complete for a single frame. + */ + public PerformanceMetric getBotResponse() { + return botResponse; + } + private PerformanceMetric botResponse; + + /** + * Time spent waiting for a response from BWAPI, + * inclusive of the time spent sending the signal to BWAPI + * and the time spent waiting for and receiving it. + */ + public PerformanceMetric getCommunicationSendToReceive() { + return communicationSendToReceive; + } + private PerformanceMetric communicationSendToReceive; + + /** + * Time spent sending the "frame complete" signal to BWAPI. + * Significant durations would indicate something blocking writes to shared memory. + */ + public PerformanceMetric getCommunicationSendToSent() { + return communicationSendToSent; + } + private PerformanceMetric communicationSendToSent; + + /** + * Time spent waiting for a "frame ready" signal from BWAPI. + * This time likely additional response time spent by other bots and StarCraft itself. + */ + public PerformanceMetric getCommunicationListenToReceive() { + return communicationListenToReceive; + } + private PerformanceMetric communicationListenToReceive; + + /** + * Time bot spends idle. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric getBotIdle() { + return botIdle; + } + private PerformanceMetric botIdle; + + /** + * Time the main thread spends idle, waiting for the bot to finish processing frames. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric getClientIdle() { + return clientIdle; + } + private PerformanceMetric clientIdle; + + /** + * Time the main thread spends oversleeping its timeout target, potentially causing overtime frames. + * Applicable only in asynchronous mode. + */ + public PerformanceMetric getExcessSleep() { + return excessSleep; + } + private PerformanceMetric excessSleep; + + /** + * The number of events sent by BWAPI each frame. + * Helps detect use of broken BWAPI 4.4 tournament modules, with respect to: + * - https://github.com/bwapi/bwapi/issues/860 + * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 + */ + public PerformanceMetric getNumberOfEvents() { + return numberOfEvents; + } + private PerformanceMetric numberOfEvents; + + /** + * The number of events sent by BWAPI each frame, + * multiplied by the duration of time spent on that frame (receive-to-sent). + * Helps detect use of broken BWAPI 4.4 tournament modules, with respect to: + * - https://github.com/bwapi/bwapi/issues/860 + * - https://github.com/davechurchill/StarcraftAITournamentManager/issues/42 + */ + public PerformanceMetric getNumberOfEventsTimesDurationReceiveToSent() { + return numberOfEventsTimesDurationReceiveToSent; + } + PerformanceMetric numberOfEventsTimesDurationReceiveToSent; + + private BWClientConfiguration configuration; + private ArrayList performanceMetrics = new ArrayList<>(); + + PerformanceMetrics(BWClientConfiguration configuration) { + this.configuration = configuration; + reset(); + } + + /** + * Clears all tracked data and starts counting from a blank slate. + */ + public void reset() { + performanceMetrics.clear(); + frameDurationReceiveToSend = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> before sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + frameDurationReceiveToSent = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> after sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + frameDurationReceiveToReceive = new PerformanceMetric(this, "Frame duration: After receiving 'frame ready' -> receiving next 'frame ready'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + communicationSendToReceive = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30); + communicationSendToSent = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After sending 'frame done'", 1, 3, 5, 10, 15, 20, 30); + communicationListenToReceive = new PerformanceMetric(this, "BWAPI duration: Before listening for 'frame ready' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30); + copyingToBuffer = new PerformanceMetric(this, "Copying frame to buffer", 5, 10, 15, 20, 25, 30); + intentionallyBlocking = new PerformanceMetric(this, "Time holding frame until buffer frees capacity", 0); + frameBufferSize = new PerformanceMetric(this, "Frames already buffered when enqueuing a new frame", 0, 1); + framesBehind = new PerformanceMetric(this, "Frames behind real-time when handling events", 0, 1); + flushSideEffects = new PerformanceMetric(this, "Time flushing side effects", 1, 3, 5); + botResponse = new PerformanceMetric(this, "Duration of bot event handlers", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + botIdle = new PerformanceMetric(this, "Time bot spent idle", Long.MAX_VALUE); + clientIdle = new PerformanceMetric(this, "Time client spent waiting for bot", configuration.getMaxFrameDurationMs()); + excessSleep = new PerformanceMetric(this, "Excess duration of client sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + numberOfEvents = new PerformanceMetric(this, "Number of events received from BWAPI", 1, 2, 3, 4, 5, 6, 8, 10, 15, 20); + numberOfEventsTimesDurationReceiveToSent = new PerformanceMetric(this, "Number of events received from BWAPI, multiplied by the receive-to-sent duration of that frame", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85); + } + + void addMetric(PerformanceMetric performanceMetric) { + performanceMetrics.add(performanceMetric); + } + + @Override + public String toString() { + StringBuilder outputBuilder = new StringBuilder(); + outputBuilder.append("Performance metrics:"); + performanceMetrics.forEach(metric -> { + outputBuilder.append("\n"); + outputBuilder.append(metric.toString()); + }); + return outputBuilder.toString(); + } +} + diff --git a/src/main/java/bwapi/Position.java b/src/main/java/bwapi/Position.java index 9f8187b..20b71e7 100644 --- a/src/main/java/bwapi/Position.java +++ b/src/main/java/bwapi/Position.java @@ -1,7 +1,7 @@ package bwapi; -public class Position extends Point { +public final class Position extends Point { public static final int SIZE_IN_PIXELS = 1; public static final Position Invalid = new Position(32000 / SIZE_IN_PIXELS, 32000 / SIZE_IN_PIXELS); diff --git a/src/main/java/bwapi/Region.java b/src/main/java/bwapi/Region.java index 80aec60..a7c2011 100644 --- a/src/main/java/bwapi/Region.java +++ b/src/main/java/bwapi/Region.java @@ -21,7 +21,7 @@ * @see Game#getRegionAt * @see Unit#getRegion */ -public class Region implements Comparable { +public final class Region implements Comparable { private final RegionData regionData; private final Game game; diff --git a/src/main/java/bwapi/SideEffect.java b/src/main/java/bwapi/SideEffect.java new file mode 100644 index 0000000..3914f2d --- /dev/null +++ b/src/main/java/bwapi/SideEffect.java @@ -0,0 +1,90 @@ +package bwapi; + +import java.util.function.Consumer; + +/** +* A side effect is an interaction that a bot attempts to have with the game. +* This entails sending a game or unit command, or drawing a shape. +*/ +class SideEffect { + + private Consumer application; + + void apply(ClientData.GameData gameData) { + application.accept(gameData); + } + + private SideEffect() {} + + static SideEffect addUnitCommand(final int type, final int unit, final int target, final int x, final int y, final int extra) { + SideEffect output = new SideEffect(); + output.application = (ClientData.GameData gameData) -> { + ClientData.UnitCommand unitCommand = GameDataUtils.addUnitCommand(gameData); + unitCommand.setTid(type); + unitCommand.setUnitIndex(unit); + unitCommand.setTargetIndex(target); + unitCommand.setX(x); + unitCommand.setY(y); + unitCommand.setExtra(extra); + }; + return output; + } + + static SideEffect addCommand(final CommandType type, final int value1, final int value2) { + SideEffect output = new SideEffect(); + output.application = (ClientData.GameData gameData) -> { + ClientData.Command command = GameDataUtils.addCommand(gameData); + command.setType(type); + command.setValue1(value1); + command.setValue2(value2); + }; + return output; + } + + static SideEffect addCommand(final CommandType type, final String text, final int value2) { + SideEffect output = new SideEffect(); + output.application = (ClientData.GameData gameData) -> { + ClientData.Command command = GameDataUtils.addCommand(gameData); + command.setType(type); + command.setValue1(GameDataUtils.addString(gameData, text)); + command.setValue2(value2); + }; + return output; + } + + static SideEffect addShape(final ShapeType type, final CoordinateType coordType, final int x1, final int y1, final int x2, final int y2, final int extra1, final int extra2, final int color, final boolean isSolid) { + SideEffect output = new SideEffect(); + output.application = (ClientData.GameData gameData) -> { + ClientData.Shape shape = GameDataUtils.addShape(gameData); + shape.setType(type); + shape.setCtype(coordType); + shape.setX1(x1); + shape.setY1(y1); + shape.setX2(x2); + shape.setY2(y2); + shape.setExtra1(extra1); + shape.setExtra2(extra2); + shape.setColor(color); + shape.setIsSolid(isSolid); + }; + return output; + } + + static SideEffect addShape(final ShapeType type, final CoordinateType coordType, final int x1, final int y1, final int x2, final int y2, final String text, final int extra2, final int color, final boolean isSolid) { + SideEffect output = new SideEffect(); + output.application = (ClientData.GameData gameData) -> { + ClientData.Shape shape = GameDataUtils.addShape(gameData); + shape.setType(type); + shape.setCtype(coordType); + shape.setX1(x1); + shape.setY1(y1); + shape.setX2(x2); + shape.setY2(y2); + shape.setExtra1(GameDataUtils.addString(gameData, text)); + shape.setExtra2(extra2); + shape.setColor(color); + shape.setIsSolid(isSolid); + }; + return output; + } +} \ No newline at end of file diff --git a/src/main/java/bwapi/SideEffectQueue.java b/src/main/java/bwapi/SideEffectQueue.java new file mode 100644 index 0000000..8dc49b8 --- /dev/null +++ b/src/main/java/bwapi/SideEffectQueue.java @@ -0,0 +1,32 @@ +package bwapi; + +import java.util.ArrayList; + +/** + * Queue of intended bot interactions with the game, to be flushed as JBWAPI returns control to StarCraft after a frame. + */ +class SideEffectQueue { + + private ArrayList queue = new ArrayList<>(); + + /** + * Includes a side effect to be sent back to BWAPI in the future. + * + * @param sideEffect + * A side effect to be applied to the game state the next time the queue is flushed. + */ + synchronized void enqueue(SideEffect sideEffect) { + queue.add(sideEffect); + } + + /** + * Applies all enqueued side effects to the current BWAPI frame. + * + * @param liveGameData + * The live game frame's data, using the BWAPI shared memory. + */ + synchronized void flushTo(ClientData.GameData liveGameData) { + queue.forEach(x -> x.apply(liveGameData)); + queue.clear(); + } +} diff --git a/src/main/java/bwapi/TilePosition.java b/src/main/java/bwapi/TilePosition.java index 8c63f3a..b2dfa76 100644 --- a/src/main/java/bwapi/TilePosition.java +++ b/src/main/java/bwapi/TilePosition.java @@ -1,6 +1,6 @@ package bwapi; -public class TilePosition extends Point { +public final class TilePosition extends Point { public static final int SIZE_IN_PIXELS = 32; public static final TilePosition Invalid = new TilePosition(32000 / SIZE_IN_PIXELS, 32000 / SIZE_IN_PIXELS); diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 821f648..50c9114 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -115,7 +115,7 @@ public int getID() { *

* In the event that this function returns false, there are two cases to consider: * 1. You own the unit. This means the unit is dead. - * 2. Another player owns the unit. This could either mean that you don't have access + * 2. Another player owns the unit. This could either runningTotal that you don't have access * to the unit or that the unit has died. You can specifically identify dead units * by polling onUnitDestroy. * @see #isVisible diff --git a/src/main/java/bwapi/UnitCommand.java b/src/main/java/bwapi/UnitCommand.java index f0c67d8..4752d23 100644 --- a/src/main/java/bwapi/UnitCommand.java +++ b/src/main/java/bwapi/UnitCommand.java @@ -6,7 +6,7 @@ import static bwapi.TechType.*; import static bwapi.UnitCommandType.*; -public class UnitCommand { +public final class UnitCommand { Unit unit; UnitCommandType type; Unit target = null; diff --git a/src/main/java/bwapi/WrappedBuffer.java b/src/main/java/bwapi/WrappedBuffer.java index 8db1dfc..fa9fe80 100644 --- a/src/main/java/bwapi/WrappedBuffer.java +++ b/src/main/java/bwapi/WrappedBuffer.java @@ -3,7 +3,6 @@ import com.sun.jna.Pointer; import sun.misc.Unsafe; -import java.lang.reflect.Field; import java.nio.ByteBuffer; /** @@ -13,19 +12,7 @@ class WrappedBuffer { private final ByteBuffer buffer; private final long address; - private static Unsafe unsafe; - - static { - try { - final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); - theUnsafe.setAccessible(true); - unsafe = (Unsafe) theUnsafe.get(null); - - } catch (final Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } + private static final Unsafe unsafe = UnsafeTools.getUnsafe(); WrappedBuffer(final int size) { buffer = ByteBuffer.allocateDirect(size); @@ -94,4 +81,8 @@ void putString(final int offset, final int maxLen, final String string) { ByteBuffer getBuffer() { return buffer; } + + long getAddress() { + return address; + } } diff --git a/src/test/java/DumpToClient.java b/src/test/java/DumpToClient.java index acabc72..16ef449 100644 --- a/src/test/java/DumpToClient.java +++ b/src/test/java/DumpToClient.java @@ -14,7 +14,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -public class DumpToClient { +class DumpToClient { private static final Pattern VAR_DECL = Pattern.compile( "^\\s+(\\d+) \\| +((?:struct )?BWAPI[^:]*::)?(\\S[^\\[]+)\\s([\\[0-9\\]]+)?\\s?(\\S+)$"); @@ -92,16 +92,27 @@ public static void main(String[] args) throws IOException { out.println("package bwapi;"); out.println(""); out.println("final class ClientData {"); - out.println(" final WrappedBuffer buffer;"); - out.println(""); - out.println(" ClientData(final WrappedBuffer buffer) {"); - out.println(" this.buffer = buffer;"); + out.println(" private WrappedBuffer buffer;"); + out.println(" private GameData gameData;"); + out.println(" ClientData() {"); + out.println(" gameData = new ClientData.GameData(0);"); + out.println(" }"); + out.println(" GameData gameData() {"); + out.println(" return gameData;"); + out.println(" }"); + out.println(" void setBuffer(WrappedBuffer buffer) {"); + out.println(" this.buffer = buffer;"); out.println(" }"); + out.println(" void setPointer(Pointer pointer) {"); + out.println(" setBuffer(new WrappedBuffer(pointer, GameData.SIZE));"); + out.println(" }"); + out.println(""); + structs.values().forEach(s -> { out.printf(" class %s {\n", s.name); out.printf(" static final int SIZE = %d;\n", s.size); out.println(" private int myOffset;"); - out.printf(" public %s(int myOffset) {\n", s.name); + out.printf(" %s(int myOffset) {\n", s.name); out.println(" this.myOffset = myOffset;"); out.println(" }"); s.variables.forEach(v -> { @@ -265,7 +276,7 @@ public static void main(String[] args) throws IOException { StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); } - public enum Type { + enum Type { STRUCT, BOOLEAN, INT, @@ -276,7 +287,7 @@ public enum Type { ENUM } - public static class Variable { + static class Variable { private final String name; private final Type type; @@ -285,19 +296,19 @@ public static class Variable { private String enumName; private List arraySizes = Collections.emptyList(); - public Variable(String name, Type type) { + Variable(String name, Type type) { this.name = name; this.type = type; } } - public static class Struct { + static class Struct { final String name; int size; List variables = new ArrayList<>(); - public Struct(String name) { + Struct(String name) { this.name = name; } } diff --git a/src/test/java/bwapi/ClientDataBenchmark.java b/src/test/java/bwapi/ClientDataBenchmark.java index 4d86226..2734b4e 100644 --- a/src/test/java/bwapi/ClientDataBenchmark.java +++ b/src/test/java/bwapi/ClientDataBenchmark.java @@ -18,8 +18,8 @@ public static class EmptyState { @Setup(Level.Invocation) public void setup() { - client = new Client(new WrappedBuffer(ClientData.GameData.SIZE)); - game = new Game(client); + game = new Game(); + game.botClientData().setBuffer(new WrappedBuffer(ClientData.GameData.SIZE)); strings = buildStrings(); } @@ -33,19 +33,19 @@ public static class FilledWithStrings { @Setup(Level.Invocation) public void setup() { - client = new Client(new WrappedBuffer(ClientData.GameData.SIZE)); - data = client.gameData(); - game = new Game(client); + data = client.liveClientData().gameData(); + game = new Game(); + game.botClientData().setBuffer(new WrappedBuffer(ClientData.GameData.SIZE)); String[] strings = buildStrings(); for (String s : strings) { - client.addString(s); + GameDataUtils.addString(client.liveClientData().gameData(), s); } } } private static String[] buildStrings() { SplittableRandom rnd = new SplittableRandom(987654321L); - String[] strings = new String[Client.MAX_COUNT]; + String[] strings = new String[GameDataUtils.MAX_COUNT]; for (int i = 0; i < strings.length; i++) { strings[i] = rnd.ints(1022, 0, 9) .mapToObj(Integer::toString) @@ -63,27 +63,27 @@ public void reference(Blackhole blackhole) { } @Benchmark - @OperationsPerInvocation(Client.MAX_COUNT) + @OperationsPerInvocation(GameDataUtils.MAX_COUNT) public int addUnitCommand(EmptyState s) { - for (int i = 0; i < Client.MAX_COUNT; i++) { + for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { s.game.addUnitCommand(0, 1, 2, 3, 4, 5); } - return s.client.gameData().getCommandCount(); + return s.client.liveClientData().gameData().getCommandCount(); } @Benchmark - @OperationsPerInvocation(Client.MAX_COUNT) + @OperationsPerInvocation(GameDataUtils.MAX_COUNT) public int addString(EmptyState s) { - for (int i = 0; i < Client.MAX_COUNT; i++) { - s.client.addString(s.strings[i]); + for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { + GameDataUtils.addString(s.client.liveClientData().gameData(), s.strings[i]); } - return s.client.gameData().getStringCount(); + return s.client.liveClientData().gameData().getStringCount(); } @Benchmark - @OperationsPerInvocation(Client.MAX_COUNT) + @OperationsPerInvocation(GameDataUtils.MAX_COUNT) public void getString(FilledWithStrings s, Blackhole blackhole) { - for (int i = 0; i < Client.MAX_COUNT; i++) { + for (int i = 0; i < GameDataUtils.MAX_COUNT; i++) { blackhole.consume(s.data.getStrings(i)); } } diff --git a/src/test/java/bwapi/GameBuilder.java b/src/test/java/bwapi/GameBuilder.java index cdcaee6..ec020c9 100644 --- a/src/test/java/bwapi/GameBuilder.java +++ b/src/test/java/bwapi/GameBuilder.java @@ -8,13 +8,20 @@ public class GameBuilder { + private final static String RESOURCES = "src/test/resources/"; + public final static String DEFAULT_MAP_FILE = "(2)Benzene.scx"; + public final static String DEFAULT_BUFFER_PATH = RESOURCES + DEFAULT_MAP_FILE + "_frame0_buffer.bin"; + public static Game createGame() throws IOException { - return createGame("(2)Benzene.scx"); + return createGame(DEFAULT_MAP_FILE); } public static Game createGame(String mapName) throws IOException { - final WrappedBuffer buffer = binToBuffer("src/test/resources/" + mapName + "_frame0_buffer.bin"); - return createGame(new Client(buffer)); + final WrappedBuffer buffer = binToBuffer(RESOURCES + mapName + "_frame0_buffer.bin"); + final Game game = new Game(); + game.botClientData().setBuffer(buffer); + game.init(); + return game; } public static WrappedBuffer binToBuffer(String binLocation) throws IOException { @@ -30,9 +37,12 @@ public static WrappedBuffer binToBuffer(String binLocation) throws IOException { return buffer; } - public static Game createGame(Client client) throws IOException { - final Game game = new Game(client); - game.init(); - return game; + public static WrappedBuffer binToBufferUnchecked(String binLocation) { + try { + return binToBuffer(binLocation); + } catch(IOException exception) { + throw new RuntimeException(exception); + } } + } diff --git a/src/test/java/bwapi/GameStateDumper.java b/src/test/java/bwapi/GameStateDumper.java index afd4945..324db9c 100644 --- a/src/test/java/bwapi/GameStateDumper.java +++ b/src/test/java/bwapi/GameStateDumper.java @@ -36,7 +36,7 @@ public void onStart() { } private void dumpBuffer(String name) throws IOException { - ByteBuffer buf = game.getClient().clientData().buffer.getBuffer(); + ByteBuffer buf = client.getClient().mapFile().getBuffer(); buf.rewind(); byte[] bytearr = new byte[buf.remaining()]; buf.get(bytearr); diff --git a/src/test/java/bwapi/GameTest.java b/src/test/java/bwapi/GameTest.java index 88b0601..557b625 100644 --- a/src/test/java/bwapi/GameTest.java +++ b/src/test/java/bwapi/GameTest.java @@ -1,5 +1,15 @@ package bwapi; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNull; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.experimental.theories.DataPoints; @@ -20,13 +30,7 @@ @RunWith(Theories.class) public class GameTest { - private final List allUnits = new ArrayList<>(); - private final Game sut = new Game(mock(Client.class)) { - @Override - public List getAllUnits() { - return allUnits; - } - }; + private Game sut = new Game(); private Unit dummy; @DataPoints("overlapping") @@ -63,7 +67,7 @@ public void setup() { public void shouldFindOverlappingUnits( @FromDataPoints("overlapping") Pair rect) { // GIVEN - allUnits.add(dummy); + sut.setAllUnits(Collections.singletonList(dummy)); // WHEN List unitsInRectangle = sut @@ -77,7 +81,7 @@ public void shouldFindOverlappingUnits( public void shouldNotFindNonOverlappingUnits( @FromDataPoints("non-overlapping") Pair rect) { // GIVEN - allUnits.add(dummy); + sut.setAllUnits(Collections.singletonList(dummy)); // WHEN List unitsInRectangle = sut @@ -89,13 +93,16 @@ public void shouldNotFindNonOverlappingUnits( @Test public void ifReplaySelfAndEnemyShouldBeNull() throws IOException { - WrappedBuffer buffer = GameBuilder.binToBuffer("src/test/resources/" + "(2)Benzene.scx" + "_frame0_buffer.bin"); + WrappedBuffer buffer = GameBuilder.binToBuffer(GameBuilder.DEFAULT_BUFFER_PATH); - Client client = new Client(buffer); // modify the buffer to fake a replay - client.gameData().setIsReplay(true); + ClientData clientData = new ClientData(); + clientData.setBuffer(buffer); + clientData.gameData().setIsReplay(true); - Game game = GameBuilder.createGame(client); + Game game = new Game(); + game.botClientData().setBuffer(buffer); + game.init(); assertThat(game.isReplay()); assertNull(game.self()); diff --git a/src/test/java/bwapi/PointTest.java b/src/test/java/bwapi/PointTest.java index d36d111..946e504 100644 --- a/src/test/java/bwapi/PointTest.java +++ b/src/test/java/bwapi/PointTest.java @@ -5,9 +5,8 @@ import java.util.Random; import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public class PointTest { @@ -54,10 +53,16 @@ public void alternativeConstructorTest() { @Test public void isValidChecks() { - Game game = mock(Game.class); - when(game.mapPixelWidth()).thenReturn(32 * 256); - when(game.mapPixelHeight()).thenReturn(32 * 256); - + Game game = new Game(); + game.botClientData().setBuffer(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); + game.botClientData().gameData().setMapWidth(256); + game.botClientData().gameData().setMapHeight(256); + game.init(); + + assertEquals(256, game.mapHeight()); + assertEquals(256, game.mapWidth()); + assertEquals(32 * 256, game.mapPixelHeight()); + assertEquals(32 * 256, game.mapPixelWidth()); assertTrue(new Position(0,0).isValid(game)); assertTrue(new Position(1, 1).isValid(game)); diff --git a/src/test/java/bwapi/SynchronizationEnvironment.java b/src/test/java/bwapi/SynchronizationEnvironment.java new file mode 100644 index 0000000..049a828 --- /dev/null +++ b/src/test/java/bwapi/SynchronizationEnvironment.java @@ -0,0 +1,130 @@ +package bwapi; + +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Mocks BWAPI and a bot listener, for synchronization tests. + */ +class SynchronizationEnvironment { + BWClientConfiguration configuration; + BWClient bwClient; + private Client client; + private int onEndFrame; + private long bwapiDelayMs; + private Map onFrames; + + SynchronizationEnvironment() { + BWEventListener listener = mock(BWEventListener.class); + + configuration = new BWClientConfiguration(); + client = mock(Client.class); + bwClient = new BWClient(listener); + bwClient.setClient(client); + onEndFrame = -1; + bwapiDelayMs = 0; + onFrames = new HashMap<>(); + + WrappedBuffer newGameState = GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH); + when(client.mapFile()).thenReturn(newGameState); + when(client.liveClientData()).thenReturn(new ClientData()); + client.liveClientData().setBuffer(client.mapFile()); + client.liveClientData().gameData().setFrameCount(-1); + client.liveClientData().gameData().setIsInGame(false); + + when(client.isConnected()).thenReturn(true); + doAnswer(answer -> { + clientUpdate(); + return null; + }).when(client).sendFrameReceiveFrame(); + doAnswer(answer -> { + configuration.log("Test: onStart()"); + return null; + }).when(listener).onStart(); + doAnswer(answer -> { + configuration.log("Test: onEnd()"); + return null; + }).when(listener).onEnd(anyBoolean()); + doAnswer(answer -> { + configuration.log("Test: onFrame() start"); + int botFrame = bwClient.getGame().getFrameCount(); + if (onFrames.containsKey(botFrame)) { + onFrames.get(botFrame).run(); + } + configuration.log("Test: onFrame() end"); + return null; + }).when(listener).onFrame(); + } + + ClientData.GameData liveGameData() { + return client.liveClientData().gameData(); + } + + PerformanceMetrics metrics() { + return bwClient.getPerformanceMetrics(); + } + + void onFrame(Integer frame, Runnable runnable) { + onFrames.put(frame, runnable); + } + + void setBwapiDelayMs(long milliseconds) { + bwapiDelayMs = milliseconds; + } + + void runGame() { + runGame(10); + } + + void runGame(int onEndFrame) { + this.onEndFrame = onEndFrame; + if (configuration.getAsync()) { + final long MEGABYTE = 1024 * 1024; + long memoryFree = Runtime.getRuntime().freeMemory() / MEGABYTE; + long memoryRequired = configuration.getAsyncFrameBufferCapacity() * ClientData.GameData.SIZE / MEGABYTE; + assertTrue( + "Unit test needs to be run with sufficient memory to allocate frame buffer. Has " + + memoryFree + + "mb of " + + memoryRequired + + "mb required.\n" + + "Current JVM arguments: " + + ManagementFactory.getRuntimeMXBean().getInputArguments(), + memoryFree > memoryRequired); + } + bwClient.startGame(configuration); + } + + private int liveFrame() { + return client.liveClientData().gameData().getFrameCount(); + } + + private void clientUpdate() throws InterruptedException{ + Thread.sleep(bwapiDelayMs); + client.liveClientData().gameData().setFrameCount(liveFrame() + 1); + configuration.log("Test: clientUpdate() to liveFrame #" + liveFrame()); + if (liveFrame() == 0) { + client.liveClientData().gameData().setIsInGame(true); + client.liveClientData().gameData().setEventCount(2); + client.liveClientData().gameData().getEvents(0).setType(EventType.MatchStart); + client.liveClientData().gameData().getEvents(1).setType(EventType.MatchFrame); + } else if (liveFrame() < onEndFrame) { + client.liveClientData().gameData().setIsInGame(true); + client.liveClientData().gameData().setEventCount(1); + client.liveClientData().gameData().getEvents(0).setType(EventType.MatchFrame); + } else if (liveFrame() == onEndFrame) { + client.liveClientData().gameData().setIsInGame(true); + client.liveClientData().gameData().getEvents(0).setType(EventType.MatchEnd); + } else { + client.liveClientData().gameData().setIsInGame(false); + client.liveClientData().gameData().setEventCount(0); + } + } +} diff --git a/src/test/java/bwapi/SynchronizationTest.java b/src/test/java/bwapi/SynchronizationTest.java new file mode 100644 index 0000000..1bf111f --- /dev/null +++ b/src/test/java/bwapi/SynchronizationTest.java @@ -0,0 +1,301 @@ +package bwapi; + +import org.junit.Test; + +import java.util.stream.IntStream; + +import static org.junit.Assert.*; + +public class SynchronizationTest { + + private void sleepUnchecked(long milliseconds) { + try { + Thread.sleep(milliseconds); + } catch(InterruptedException exception) { + throw new RuntimeException(exception); + } + } + + private String describeApproximateExpectation(double expected, double actual, double margin) { + return "Expected " + expected + " == " + actual + " +/- " + margin; + } + + private boolean measureApproximateEquality(double expected, double actual, double margin) { + return expected + margin >= actual && expected - margin <= actual; + } + + private void assertWithin(String message, double expected, double actual, double margin) { + assertTrue( + message + ": " + describeApproximateExpectation(expected, actual, margin), + measureApproximateEquality(expected, actual, margin)); + } + + @Test + public void sync_IfException_ThrowException() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.withAsync(false); + environment.onFrame(0, () -> { throw new RuntimeException("Simulated bot exception"); }); + assertThrows(RuntimeException.class, environment::runGame); + } + + @Test + public void async_IfException_ThrowException() { + // An exception in the bot thread must be re-thrown by the main thread. + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(true) + .withAsyncFrameBufferCapacity(3); + environment.onFrame(0, () -> { throw new RuntimeException("Simulated bot exception"); }); + assertThrows(RuntimeException.class, environment::runGame); + } + + @Test + public void sync_IfDelay_ThenNoBuffer() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(false) + .withMaxFrameDurationMs(1) + .withAsyncFrameBufferCapacity(3); + + IntStream.range(0, 5).forEach(frame -> { + environment.onFrame(frame, () -> { + sleepUnchecked(5); + assertEquals(0, environment.bwClient.framesBehind()); + assertEquals(frame, environment.bwClient.getGame().getFrameCount()); + assertEquals(frame, environment.liveGameData().getFrameCount()); + }); + }); + + environment.runGame(); + } + + @Test + public void async_IfBotDelay_ThenClientBuffers() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(true) + .withMaxFrameDurationMs(100) + .withAsyncFrameBufferCapacity(4); + + environment.onFrame(1, () -> { + sleepUnchecked(500); + assertEquals("Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("Client should be as far ahead as the frame buffer allows", 5, environment.liveGameData().getFrameCount()); + assertEquals("Bot should be behind the live game", 4, environment.bwClient.framesBehind()); + }); + + environment.onFrame(6, () -> { // Maybe it should be possible to demand that these assertions pass a frame earlier? + assertEquals("Bot should be observing the live frame", 6, environment.bwClient.getGame().getFrameCount()); + assertEquals("Client should not be ahead of the bot", 6, environment.liveGameData().getFrameCount()); + assertEquals("Bot should not be behind the live game", 0, environment.bwClient.framesBehind()); + }); + + environment.runGame(); + } + + @Test + public void async_IfBotDelay_ThenClientStalls() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(true) + .withMaxFrameDurationMs(200) + .withAsyncFrameBufferCapacity(5); + + environment.onFrame(1, () -> { + sleepUnchecked(500); + assertEquals("3: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("3: Client should have progressed as slowly as possible", 3, environment.liveGameData().getFrameCount()); + assertEquals("3: Bot should be behind the live game by as little as possible", 2, environment.bwClient.framesBehind()); + sleepUnchecked(200); + assertEquals("4: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("4: Client should have progressed as slowly as possible", 4, environment.liveGameData().getFrameCount()); + assertEquals("4: Bot should be behind the live game by as little as possible", 3, environment.bwClient.framesBehind()); + sleepUnchecked(200); + assertEquals("5: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount()); + assertEquals("5: Client should have progressed as slowly as possible", 5, environment.liveGameData().getFrameCount()); + assertEquals("5: Bot should be behind the live game by as little as possible", 4, environment.bwClient.framesBehind()); + }); + + environment.runGame(); + } + + @Test + public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(true) + .withUnlimitedFrameZero(true) + .withMaxFrameDurationMs(5) + .withAsyncFrameBufferCapacity(2); + + environment.onFrame(0, () -> { + sleepUnchecked(50); + assertEquals("Bot should still be on frame zero", 0, environment.bwClient.getGame().getFrameCount()); + assertEquals("Client should still be on frame zero", 0, environment.liveGameData().getFrameCount()); + assertEquals("Bot should not be behind the live game", 0, environment.bwClient.framesBehind()); + }); + + environment.runGame(2); + } + + @Test + public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(true) + .withUnlimitedFrameZero(false) + .withMaxFrameDurationMs(5) + .withAsyncFrameBufferCapacity(2); + + environment.onFrame(0, () -> { + sleepUnchecked(50); + assertEquals("Bot should still be on frame zero", 0, environment.bwClient.getGame().getFrameCount()); + assertEquals("Client should have advanced to the next frame", 2, environment.liveGameData().getFrameCount()); + assertEquals("Bot should be behind the live game", 2, environment.bwClient.framesBehind()); + }); + + environment.runGame(2); + } + + @Test + public void async_MeasurePerformance_CopyingToBuffer() { + // Somewhat lazy test; just verify that we're getting sane values + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration.withAsync(true); + environment.runGame(20); + final double minObserved = 0.25; + final double maxObserved = 15; + final double meanObserved = (minObserved + maxObserved) / 2; + final double rangeObserved = (maxObserved - minObserved) / 2; + assertWithin("Copy to buffer: minimum", environment.metrics().getCopyingToBuffer().getRunningTotal().getMin(), meanObserved, rangeObserved); + assertWithin("Copy to buffer: maximum", environment.metrics().getCopyingToBuffer().getRunningTotal().getMax(), meanObserved, rangeObserved); + assertWithin("Copy to buffer: average", environment.metrics().getCopyingToBuffer().getRunningTotal().getMean(), meanObserved, rangeObserved); + assertWithin("Copy to buffer: previous", environment.metrics().getCopyingToBuffer().getRunningTotal().getLast(), meanObserved, rangeObserved); + } + + @Test + public void async_MeasurePerformance_FrameBufferSizeAndFramesBehind() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(true) + .withUnlimitedFrameZero(true) + .withAsyncFrameBufferCapacity(3) + .withMaxFrameDurationMs(20); + + environment.onFrame(5, () -> { + assertWithin("5: Frame buffer average", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMean(), 0.1); + assertWithin("5: Frame buffer minimum", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMin(), 0.1); + assertWithin("5: Frame buffer maximum", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMax(), 0.1); + assertWithin("5: Frame buffer previous", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getLast(), 0.1); + assertWithin("5: Frames behind average", 0, environment.metrics().getFramesBehind().getRunningTotal().getMean(), 0.1); + assertWithin("5: Frames behind minimum", 0, environment.metrics().getFramesBehind().getRunningTotal().getMin(), 0.1); + assertWithin("5: Frames behind maximum", 0, environment.metrics().getFramesBehind().getRunningTotal().getMax(), 0.1); + assertWithin("5: Frames behind previous", 0, environment.metrics().getFramesBehind().getRunningTotal().getLast(), 0.1); + sleepUnchecked(200); + }); + environment.onFrame(6, () -> { + assertWithin("6: Frame buffer average", 1 / 6.0 + 2 / 7.0, environment.metrics().getFrameBufferSize().getRunningTotal().getMean(), 0.1); + assertWithin("6: Frame buffer minimum", 0, environment.metrics().getFrameBufferSize().getRunningTotal().getMin(), 0.1); + assertWithin("6: Frame buffer maximum", 2, environment.metrics().getFrameBufferSize().getRunningTotal().getMax(), 0.1); + assertWithin("6: Frame buffer previous", 2, environment.metrics().getFrameBufferSize().getRunningTotal().getLast(), 0.1); + assertWithin("6: Frames behind average", 1 / 6.0, environment.metrics().getFramesBehind().getRunningTotal().getMean(), 0.1); + assertWithin("6: Frames behind minimum", 0, environment.metrics().getFramesBehind().getRunningTotal().getMin(), 0.1); + assertWithin("6: Frames behind maximum", 1, environment.metrics().getFramesBehind().getRunningTotal().getMax(), 0.1); + assertWithin("6: Frames behind previous", 1, environment.metrics().getFramesBehind().getRunningTotal().getLast(), 0.1); + }); + + environment.runGame(8); + } + + /** + * Number of milliseconds of leeway to give in potentially noisy performance metrics. + * Increase if tests are flaky due to variance in execution speed. + */ + private final static long MS_MARGIN = 20; + + @Test + public void MeasurePerformance_BotResponse() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + + // Frame zero appears to take an extra 60ms, so let's disable timing for it + // (and also verify that we omit frame zero from performance metrics) + environment.configuration.withUnlimitedFrameZero(true); + + environment.onFrame(1, () -> { + sleepUnchecked(100); + }); + environment.onFrame(2, () -> { + assertWithin("2: Bot response average", 100, environment.metrics().getBotResponse().getRunningTotal().getMean(), MS_MARGIN); + assertWithin("2: Bot response minimum", 100, environment.metrics().getBotResponse().getRunningTotal().getMin(), MS_MARGIN); + assertWithin("2: Bot response maximum", 100, environment.metrics().getBotResponse().getRunningTotal().getMax(), MS_MARGIN); + assertWithin("2: Bot response previous", 100, environment.metrics().getBotResponse().getRunningTotal().getLast(), MS_MARGIN); + sleepUnchecked(300); + }); + environment.onFrame(3, () -> { + assertWithin("3: Bot response average", 200, environment.metrics().getBotResponse().getRunningTotal().getMean(), MS_MARGIN); + assertWithin("3: Bot response minimum", 100, environment.metrics().getBotResponse().getRunningTotal().getMin(), MS_MARGIN); + assertWithin("3: Bot response maximum", 300, environment.metrics().getBotResponse().getRunningTotal().getMax(), MS_MARGIN); + assertWithin("3: Bot response previous", 300, environment.metrics().getBotResponse().getRunningTotal().getLast(), MS_MARGIN); + sleepUnchecked(200); + }); + + environment.runGame(4); + + assertWithin("Final: Bot response average", 200, environment.metrics().getBotResponse().getRunningTotal().getMean(), MS_MARGIN); + assertWithin("Final: Bot response minimum", 100, environment.metrics().getBotResponse().getRunningTotal().getMin(), MS_MARGIN); + assertWithin("Final: Bot response maximum", 300, environment.metrics().getBotResponse().getRunningTotal().getMax(), MS_MARGIN); + assertWithin("Final: Bot response previous", 200, environment.metrics().getBotResponse().getRunningTotal().getLast(), MS_MARGIN); + } + + @Test + public void MeasurePerformance_BotIdle() { + final long bwapiDelayMs = 10; + final int frames = 10; + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(true) + .withAsyncFrameBufferCapacity(3) + .withUnlimitedFrameZero(true); + environment.setBwapiDelayMs(bwapiDelayMs); + environment.runGame(frames); + double expected = environment.metrics().getCopyingToBuffer().getRunningTotal().getMean() + bwapiDelayMs; + assertWithin("Bot Idle: Average", environment.metrics().getBotIdle().getRunningTotal().getMean(), expected, MS_MARGIN); + } + + @Test + public void async_MeasurePerformance_IntentionallyBlocking() { + SynchronizationEnvironment environment = new SynchronizationEnvironment(); + environment.configuration + .withAsync(true) + .withUnlimitedFrameZero(true) + .withAsyncFrameBufferCapacity(2) + .withMaxFrameDurationMs(20); + final int frameDelayMs = 100; + environment.onFrame(1, () -> { + sleepUnchecked(100); + }); + environment.onFrame(2, () -> { + assertWithin( + "2: Intentionally blocking previous", + environment.metrics().getIntentionallyBlocking().getRunningTotal().getLast(), + frameDelayMs - environment.configuration.getAsyncFrameBufferCapacity() * environment.configuration.getMaxFrameDurationMs(), + MS_MARGIN); + sleepUnchecked(100); + }); + environment.runGame(3); + } + + @Test + public void async_DisablesLatencyCompensation() { + SynchronizationEnvironment environmentSync = new SynchronizationEnvironment(); + environmentSync.configuration.withAsync(false); + environmentSync.onFrame(1, () -> { assertTrue(environmentSync.bwClient.getGame().isLatComEnabled()); }); + environmentSync.runGame(2); + + SynchronizationEnvironment environmentAsync = new SynchronizationEnvironment(); + environmentAsync.configuration.withAsync(true).withAsyncFrameBufferCapacity(2); + environmentAsync.onFrame(1, () -> { assertFalse(environmentAsync.bwClient.getGame().isLatComEnabled()); }); + environmentAsync.onFrame(2, () -> { assertThrows(IllegalStateException.class, () -> environmentAsync.bwClient.getGame().setLatCom(true)); }); + environmentAsync.runGame(3); + } +}