diff --git a/README.md b/README.md index 05f8b47..d354da3 100644 --- a/README.md +++ b/README.md @@ -3,24 +3,21 @@ An example bot implemented in Java ## Video Guide -https://youtu.be/mPfYqKe_KRs +https://youtu.be/mPfYqKe_KRs (slightly outdated because it uses the old GUI) ## Usage Instructions: -1. Make sure you've installed Python 3.6.5 or newer. Here's [Python 3.7 64 bit](https://www.python.org/ftp/python/3.7.0/python-3.7.0-amd64.exe). Some older versions like 3.6.0 will not work. During installation: - - Select "Add Python to PATH" - - Make sure pip is included in the installation 1. Make sure you've installed the Java 8 JDK or newer. Here's the [Java 8 JDK](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html). 1. Make sure you've [set the JAVA_HOME environment variable](https://javatutorial.net/set-java-home-windows-10). 1. Download this repository -1. Open Rocket League 1. Double click on run-bot.bat and leave it running. It's supposed to stay open and it's OK if it says something like "75%". -1. Double click on run-gui.bat -1. Click the 'Run' button + - Alternatively you can launch the bot from inside an IDE +1. Get RLBotGUI (see https://youtu.be/lPkID_IH88U for instructions). +1. Use Add -> Load folder in RLBotGUI on the current directory. This bot should appear in the list. +1. In RLBotGUI, put the bot on a team and start the match. - Bot behavior is controlled by `src/main/java/rlbotexample/SampleBot.java` -- Bot appearance is controlled by `src/main/python/javaExampleAppearance.cfg` See the [wiki](https://github.com/RLBot/RLBotJavaExample/wiki) for tips to improve your programming experience. diff --git a/build.gradle b/build.gradle index b4ccb9f..e4bda5e 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ apply plugin: 'application' sourceCompatibility = 1.8 repositories { - jcenter() + mavenCentral() } mainClassName = 'rlbotexample.JavaExample' @@ -19,54 +19,18 @@ applicationDefaultJvmArgs = ["-Djna.library.path=" + dllDirectory] dependencies { // Fetch the framework jar file - compile 'org.rlbot.commons:framework:1.+' + compile 'org.rlbot.commons:framework:2.+' // This is makes it easy to find the dll when running in intellij, where JVM args don't get passed from gradle. runtime files(dllDirectory) } -task checkPipUpgradeSafety { - doLast { - new ByteArrayOutputStream().withStream { os -> - def exitValue = exec { - commandLine "python", "-c", "from rlbot.utils import public_utils; print(public_utils.is_safe_to_upgrade());" - standardOutput = os - ignoreExitValue = true - }.exitValue - - // If the exit value is nonzero, the command probably failed because rlbot is not installed at all. - ext.isSafe = exitValue != 0 || os.toString().trim() == "True" - } - } -} - - -// Uses pip (the python package manager) to install all the python packages needed for this bot, as defined -// in requirements.txt. -task pipInstallRequirements { - dependsOn 'checkPipUpgradeSafety' - - doLast { - if (checkPipUpgradeSafety.isSafe) { - exec { - commandLine "python", "-m", "pip", "install", "-r", "requirements.txt", "--upgrade" - } - } else { - println 'Skipping upgrade attempt because files are in use.' - } - } -} task createDllDirectory { mkdir dllDirectory } -// Installs or updates RLBot. Empty task for now. It still does stuff because it "dependsOn" tasks that do things. -task updateRLBot { - dependsOn 'pipInstallRequirements' - dependsOn 'createDllDirectory' -} -updateRLBot.dependsOn pipInstallRequirements +run.dependsOn createDllDirectory applicationDistribution.exclude(dllDirectory) @@ -78,7 +42,13 @@ distZip { exclude '__pycache__' } } - into (applicationName + '/bin') { - from fileTree('port.cfg') +} + +// This is the same as distZip, but not zipped. Handy for testing your tournament submission more rapidly. +installDist { + into ('../python') { + from fileTree('src/main/python') { + exclude '__pycache__' + } } -} \ No newline at end of file +} diff --git a/port.cfg b/port.cfg deleted file mode 100644 index 1256cfb..0000000 Binary files a/port.cfg and /dev/null differ diff --git a/rlbot.cfg b/rlbot.cfg index 80411c4..7944055 100644 --- a/rlbot.cfg +++ b/rlbot.cfg @@ -5,10 +5,13 @@ # Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. [Match Configuration] -# Number of bots/players which will be spawned. We support up to max 10. +# Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. +# Number of bots/players which will be spawned. We support up to max 64. num_participants = 2 game_mode = Soccer game_map = Mannfield +enable_rendering = True +enable_state_setting = True [Mutator Configuration] # Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. diff --git a/run-bot.bat b/run-bot.bat index 4508712..fd7a00b 100644 --- a/run-bot.bat +++ b/run-bot.bat @@ -1,12 +1,6 @@ @rem Change the working directory to the location of this file so that relative paths will work cd /D "%~dp0" -@rem Make sure the environment variables are up-to-date. This is useful if the user installed python a moment ago. -call ./RefreshEnv.cmd - -@rem Install or update rlbot and related python packages. -call ./gradlew.bat --no-daemon updateRLBot - @rem Start running the bot. call ./gradlew.bat --no-daemon run diff --git a/run-gui.bat b/run-gui.bat deleted file mode 100644 index a1c00d9..0000000 --- a/run-gui.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off - -@rem Change the working directory to the location of this file so that relative paths will work -cd /D "%~dp0" - -@rem Make sure the environment variables are up-to-date. This is useful if the user installed python a moment ago. -call ./RefreshEnv.cmd - -python run.py gui - -pause diff --git a/run.py b/run.py index 89b5a0e..23ea98a 100644 --- a/run.py +++ b/run.py @@ -1,37 +1,32 @@ -# https://stackoverflow.com/a/51704613 -try: - from pip import main as pipmain -except ImportError: - from pip._internal import main as pipmain +import subprocess +import sys +DEFAULT_LOGGER = 'rlbot' -# https://stackoverflow.com/a/24773951 -def install_and_import(package): - import importlib +if __name__ == '__main__': try: - importlib.import_module(package) - except ImportError: - pipmain(['install', package]) - finally: - globals()[package] = importlib.import_module(package) + from rlbot.utils import public_utils, logging_utils + logger = logging_utils.get_logger(DEFAULT_LOGGER) + if not public_utils.have_internet(): + logger.log(logging_utils.logging_level, + 'Skipping upgrade check for now since it looks like you have no internet') + elif public_utils.is_safe_to_upgrade(): + subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt']) + subprocess.call([sys.executable, "-m", "pip", "install", 'rlbot', '--upgrade']) -if __name__ == '__main__': - install_and_import('rlbot') - from rlbot.utils import public_utils + # https://stackoverflow.com/a/44401013 + rlbots = [module for module in sys.modules if module.startswith('rlbot')] + for rlbot_module in rlbots: + sys.modules.pop(rlbot_module) - if public_utils.is_safe_to_upgrade(): - pipmain(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + except ImportError: + subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) try: - import sys - if len(sys.argv) > 1 and sys.argv[1] == 'gui': - from rlbot.gui.qt_root import RLBotQTGui - RLBotQTGui.main() - else: - from rlbot import runner - runner.main() + from rlbot import runner + runner.main() except Exception as e: print("Encountered exception: ", e) print("Press enter to close.") diff --git a/src/main/java/rlbotexample/JavaExample.java b/src/main/java/rlbotexample/JavaExample.java index 3b910ab..d3d7230 100644 --- a/src/main/java/rlbotexample/JavaExample.java +++ b/src/main/java/rlbotexample/JavaExample.java @@ -1,15 +1,15 @@ package rlbotexample; import rlbot.manager.BotManager; -import rlbot.pyinterop.PythonInterface; -import rlbot.pyinterop.PythonServer; import rlbotexample.util.PortReader; import javax.swing.*; import javax.swing.border.EmptyBorder; +import java.awt.*; import java.awt.event.ActionListener; -import java.util.OptionalInt; +import java.net.URL; import java.util.Set; +import java.util.stream.Collectors; /** * See JavaAgent.py for usage instructions. @@ -18,41 +18,61 @@ */ public class JavaExample { + private static final int DEFAULT_PORT = 17357; + public static void main(String[] args) { BotManager botManager = new BotManager(); - PythonInterface pythonInterface = new SamplePythonInterface(botManager); - Integer port = PortReader.readPortFromFile("port.cfg"); - PythonServer pythonServer = new PythonServer(pythonInterface, port); - pythonServer.start(); + int port = PortReader.readPortFromArgs(args).orElseGet(() -> { + System.out.println("Could not read port from args, using default!"); + return DEFAULT_PORT; + }); + + SamplePythonInterface pythonInterface = new SamplePythonInterface(port, botManager); + new Thread(pythonInterface::start).start(); + displayWindow(botManager, port); + } + + private static void displayWindow(BotManager botManager, int port) { JFrame frame = new JFrame("Java Bot"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setBorder(new EmptyBorder(10, 10, 10, 10)); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - panel.add(new JLabel("Listening on port " + port)); - panel.add(new JLabel("I'm the thing controlling the Java bot, keep me open :)")); + BorderLayout borderLayout = new BorderLayout(); + panel.setLayout(borderLayout); + JPanel dataPanel = new JPanel(); + dataPanel.setLayout(new BoxLayout(dataPanel, BoxLayout.Y_AXIS)); + dataPanel.setBorder(new EmptyBorder(0, 10, 0, 0)); + dataPanel.add(new JLabel("Listening on port " + port), BorderLayout.CENTER); + dataPanel.add(new JLabel("I'm the thing controlling the Java bot, keep me open :)"), BorderLayout.CENTER); JLabel botsRunning = new JLabel("Bots running: "); - panel.add(botsRunning); + dataPanel.add(botsRunning, BorderLayout.CENTER); + panel.add(dataPanel, BorderLayout.CENTER); frame.add(panel); + URL url = JavaExample.class.getClassLoader().getResource("icon.png"); + Image image = Toolkit.getDefaultToolkit().createImage(url); + panel.add(new JLabel(new ImageIcon(image)), BorderLayout.WEST); + frame.setIconImage(image); + frame.pack(); frame.setVisible(true); ActionListener myListener = e -> { Set runningBotIndices = botManager.getRunningBotIndices(); - OptionalInt maxIndex = runningBotIndices.stream().mapToInt(k -> k).max(); - String botsStr = "None"; - if (maxIndex.isPresent()) { - StringBuilder botsStrBuilder = new StringBuilder(); - for (int i = 0; i <= maxIndex.getAsInt(); i++) { - botsStrBuilder.append(runningBotIndices.contains(i) ? "☑ " : "☐ "); - } - botsStr = botsStrBuilder.toString(); + + String botsStr; + if (runningBotIndices.isEmpty()) { + botsStr = "None"; + } else { + botsStr = runningBotIndices.stream() + .sorted() + .map(i -> "#" + i) + .collect(Collectors.joining(", ")); } - botsRunning.setText("Bots running: " + botsStr); + botsRunning.setText("Bots indices running: " + botsStr); }; new Timer(1000, myListener).start(); diff --git a/src/main/java/rlbotexample/SampleBot.java b/src/main/java/rlbotexample/SampleBot.java index b4a116f..0a82f17 100644 --- a/src/main/java/rlbotexample/SampleBot.java +++ b/src/main/java/rlbotexample/SampleBot.java @@ -3,17 +3,17 @@ import rlbot.Bot; import rlbot.ControllerState; import rlbot.cppinterop.RLBotDll; +import rlbot.cppinterop.RLBotInterfaceException; +import rlbot.flat.BallPrediction; import rlbot.flat.GameTickPacket; import rlbot.flat.QuickChatSelection; import rlbot.manager.BotLoopRenderer; import rlbot.render.Renderer; import rlbotexample.boost.BoostManager; -import rlbotexample.dropshot.DropshotTile; -import rlbotexample.dropshot.DropshotTileManager; -import rlbotexample.dropshot.DropshotTileState; -import rlbotexample.input.CarData; import rlbotexample.input.DataPacket; +import rlbotexample.input.car.CarData; import rlbotexample.output.ControlsOutput; +import rlbotexample.prediction.BallPredictionHelper; import rlbotexample.vector.Vector2; import java.awt.*; @@ -75,17 +75,19 @@ private void drawDebugLines(DataPacket input, CarData myCar, boolean goLeft) { renderer.drawString3d(goLeft ? "left" : "right", Color.WHITE, myCar.position, 2, 2); - for (DropshotTile tile: DropshotTileManager.getTiles()) { - if (tile.getState() == DropshotTileState.DAMAGED) { - renderer.drawCenteredRectangle3d(Color.YELLOW, tile.getLocation(), 4, 4, true); - } else if (tile.getState() == DropshotTileState.DESTROYED) { - renderer.drawCenteredRectangle3d(Color.RED, tile.getLocation(), 4, 4, true); - } + if(input.ball.hasBeenTouched) { + float lastTouchTime = myCar.elapsedSeconds - input.ball.latestTouch.gameSeconds; + Color touchColor = input.ball.latestTouch.team == 0 ? Color.BLUE : Color.ORANGE; + renderer.drawString3d((int)lastTouchTime + "s", touchColor, input.ball.position, 2, 2); } - // Draw a rectangle on the tile that the car is on - DropshotTile tile = DropshotTileManager.pointToTile(myCar.position.flatten()); - if (tile != null) renderer.drawCenteredRectangle3d(Color.green, tile.getLocation(), 8, 8, false); + try { + // Draw 3 seconds of ball prediction + BallPrediction ballPrediction = RLBotDll.getBallPrediction(); + BallPredictionHelper.drawTillMoment(ballPrediction, myCar.elapsedSeconds + 3, Color.CYAN, renderer); + } catch (RLBotInterfaceException e) { + e.printStackTrace(); + } } @@ -108,7 +110,6 @@ public ControllerState processInput(GameTickPacket packet) { // Update the boost manager and tile manager with the latest data BoostManager.loadGameTickPacket(packet); - DropshotTileManager.loadGameTickPacket(packet); // Translate the raw packet data (which is in an unpleasant format) into our custom DataPacket class. // The DataPacket might not include everything from GameTickPacket, so improve it if you need to! @@ -120,6 +121,7 @@ public ControllerState processInput(GameTickPacket packet) { return controlsOutput; } + @Override public void retire() { System.out.println("Retiring sample bot " + playerIndex); } diff --git a/src/main/java/rlbotexample/SamplePythonInterface.java b/src/main/java/rlbotexample/SamplePythonInterface.java index e6812ca..aa679bc 100644 --- a/src/main/java/rlbotexample/SamplePythonInterface.java +++ b/src/main/java/rlbotexample/SamplePythonInterface.java @@ -2,14 +2,15 @@ import rlbot.Bot; import rlbot.manager.BotManager; -import rlbot.pyinterop.DefaultPythonInterface; +import rlbot.pyinterop.SocketServer; -public class SamplePythonInterface extends DefaultPythonInterface { +public class SamplePythonInterface extends SocketServer { - public SamplePythonInterface(BotManager botManager) { - super(botManager); + public SamplePythonInterface(int port, BotManager botManager) { + super(port, botManager); } + @Override protected Bot initBot(int index, String botType, int team) { return new SampleBot(index); } diff --git a/src/main/java/rlbotexample/boost/BoostManager.java b/src/main/java/rlbotexample/boost/BoostManager.java index a9fa1bf..b93d7a6 100644 --- a/src/main/java/rlbotexample/boost/BoostManager.java +++ b/src/main/java/rlbotexample/boost/BoostManager.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.List; /** * Information about where boost pads are located on the field and what status they have. @@ -17,15 +18,15 @@ */ public class BoostManager { - private static final ArrayList orderedBoosts = new ArrayList<>(); - private static final ArrayList fullBoosts = new ArrayList<>(); - private static final ArrayList smallBoosts = new ArrayList<>(); + private static final List orderedBoosts = new ArrayList<>(); + private static final List fullBoosts = new ArrayList<>(); + private static final List smallBoosts = new ArrayList<>(); - public static ArrayList getFullBoosts() { + public static List getFullBoosts() { return fullBoosts; } - public static ArrayList getSmallBoosts() { + public static List getSmallBoosts() { return smallBoosts; } diff --git a/src/main/java/rlbotexample/dropshot/DropshotTile.java b/src/main/java/rlbotexample/dropshot/DropshotTile.java deleted file mode 100644 index 06c639a..0000000 --- a/src/main/java/rlbotexample/dropshot/DropshotTile.java +++ /dev/null @@ -1,42 +0,0 @@ -package rlbotexample.dropshot; - - -import rlbotexample.vector.Vector3; - -/** - * Representation of one of the floor tiles in dropshot mode. - * - * This class is here for your convenience, it is NOT part of the framework. You can change it as much - * as you want, or delete it. - */ -public class DropshotTile { - - public static final double TILE_SIZE = 443.405; // side length and length from center to side - public static final double TILE_WIDTH = 768; // length from side to opposite side - public static final double TILE_HEIGHT = 886.81; // length from corner to opposite corner - - private final Vector3 location; - private final int team; - private DropshotTileState state; - - public DropshotTile(Vector3 location) { - this.location = location; - this.team = location.y < 0 ? 0 : 1; - } - - public void setState(DropshotTileState state) { - this.state = state; - } - - public Vector3 getLocation() { - return location; - } - - public DropshotTileState getState() { - return state; - } - - public int getTeam() { - return team; - } -} diff --git a/src/main/java/rlbotexample/dropshot/DropshotTileManager.java b/src/main/java/rlbotexample/dropshot/DropshotTileManager.java deleted file mode 100644 index fcd82dd..0000000 --- a/src/main/java/rlbotexample/dropshot/DropshotTileManager.java +++ /dev/null @@ -1,100 +0,0 @@ -package rlbotexample.dropshot; - -import rlbot.cppinterop.RLBotDll; -import rlbot.flat.FieldInfo; -import rlbot.flat.GameTickPacket; -import rlbotexample.vector.Vector2; -import rlbotexample.vector.Vector3; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import static rlbotexample.dropshot.DropshotTile.TILE_HEIGHT; -import static rlbotexample.dropshot.DropshotTile.TILE_WIDTH; - -/** - * Information about where dropshot tiles are located in the arena and what state they have. Can also convert a - * vector2 point to a tile, which is useful for checking the state of the tile where the ball lands. - * - * This class is here for your convenience, it is NOT part of the framework. You can change it as much - * as you want, or delete it. - */ -public class DropshotTileManager { - - private static final ArrayList tiles = new ArrayList<>(); - private static final HashMap blueTileMap = new HashMap<>(); - private static final HashMap orangeTileMap = new HashMap<>(); - - public static List getTiles() { - return tiles; - } - - private static void loadFieldInfo(FieldInfo fieldInfo) { - - synchronized (tiles) { - - tiles.clear(); - - for (int i = 0; i < fieldInfo.goalsLength(); i++) { - rlbot.flat.GoalInfo goalInfo = fieldInfo.goals(i); - Vector3 location = new Vector3(goalInfo.location()); - DropshotTile tile = new DropshotTile(location); - tiles.add(new DropshotTile(location)); - - Hex hex = pointToHex(location.flatten()); - if (location.y < 0) { - blueTileMap.put(hex, tile); - } else { - orangeTileMap.put(hex, tile); - } - } - } - } - - public static void loadGameTickPacket(GameTickPacket packet) { - - if (packet.tileInformationLength() > tiles.size()) { - try { - loadFieldInfo(RLBotDll.getFieldInfo()); - } catch (IOException e) { - e.printStackTrace(); - return; - } - } - - for (int i = 0; i < packet.tileInformationLength(); i++) { - rlbot.flat.DropshotTile tile = packet.tileInformation(i); - DropshotTile existingTile = tiles.get(i); - existingTile.setState(DropshotTileState.values()[tile.tileState()]); - } - } - - /** - * Returns the tile under the point, or null if none is. - */ - public static DropshotTile pointToTile(Vector2 point) { - Hex hex = pointToHex(point); - if (point.y < 0) return blueTileMap.get(hex); - else return orangeTileMap.get(hex); - } - - /** - * Converts a point to a hex. - */ - private static Hex pointToHex(Vector2 point) { - - // Apply offset - if (point.y < 0) { - point = point.plus(new Vector2(0, 128)); - } else { - point = point.plus(new Vector2(0, -128)); - } - - // Calculate q and r component - double q = point.x / TILE_WIDTH - point.y * 2 / (3 * TILE_HEIGHT); - double r = point.y * 4 / (3 * TILE_HEIGHT); - return Hex.fromRounding(q, r); - } -} diff --git a/src/main/java/rlbotexample/dropshot/DropshotTileState.java b/src/main/java/rlbotexample/dropshot/DropshotTileState.java deleted file mode 100644 index b2dba3f..0000000 --- a/src/main/java/rlbotexample/dropshot/DropshotTileState.java +++ /dev/null @@ -1,8 +0,0 @@ -package rlbotexample.dropshot; - -public enum DropshotTileState { - UNKNOWN, - FRESH, - DAMAGED, - DESTROYED -} diff --git a/src/main/java/rlbotexample/dropshot/Hex.java b/src/main/java/rlbotexample/dropshot/Hex.java deleted file mode 100644 index eef320e..0000000 --- a/src/main/java/rlbotexample/dropshot/Hex.java +++ /dev/null @@ -1,61 +0,0 @@ -package rlbotexample.dropshot; - -import java.util.Objects; - -/** - * This class is used to convert a 2d point to a hex grid in the DropshotTileManager. Look here for more information: - * https://www.redblobgames.com/grids/hexagons/ - * - * This class is here for your convenience, it is NOT part of the framework. You can add to it as much - * as you want, or delete it. - */ -public class Hex { - - // For a hex the following must always be true: q + r + s == 0 - public final int q, r, s; - - public Hex(int q, int r) { - this.q = q; - this.r = r; - this.s = -q - r; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Hex that = (Hex) o; - return q == that.q && - r == that.r; - } - - @Override - public int hashCode() { - return Objects.hash(q, r); - } - - /** - * Construct a Hex from rounding two floating point q and r coordinates. - */ - public static Hex fromRounding(double fq, double fr) { - double fs = -fq - fr; - - int rx = (int)Math.round(fq); - int ry = (int)Math.round(fr); - int rz = (int)Math.round(fs); - - // Find how much each component was rounded - double x_diff = Math.abs(rx - fq); - double y_diff = Math.abs(ry - fr); - double z_diff = Math.abs(rz - fs); - - // We reset the component with the largest change back to what the constraint rx + ry + rz = 0 requires - if (x_diff > y_diff && x_diff > z_diff) { - rx = -ry - rz; - } else if (y_diff > z_diff) { - ry = -rx - rz; - } - - return new Hex(rx, ry); - } -} diff --git a/src/main/java/rlbotexample/input/DataPacket.java b/src/main/java/rlbotexample/input/DataPacket.java index 7001897..a61b69c 100644 --- a/src/main/java/rlbotexample/input/DataPacket.java +++ b/src/main/java/rlbotexample/input/DataPacket.java @@ -1,6 +1,8 @@ package rlbotexample.input; import rlbot.flat.GameTickPacket; +import rlbotexample.input.ball.BallData; +import rlbotexample.input.car.CarData; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/rlbotexample/input/BallData.java b/src/main/java/rlbotexample/input/ball/BallData.java similarity index 70% rename from src/main/java/rlbotexample/input/BallData.java rename to src/main/java/rlbotexample/input/ball/BallData.java index c7f042f..8dffab4 100644 --- a/src/main/java/rlbotexample/input/BallData.java +++ b/src/main/java/rlbotexample/input/ball/BallData.java @@ -1,4 +1,4 @@ -package rlbotexample.input; +package rlbotexample.input.ball; import rlbot.flat.BallInfo; @@ -14,10 +14,14 @@ public class BallData { public final Vector3 position; public final Vector3 velocity; public final Vector3 spin; + public final BallTouch latestTouch; + public final boolean hasBeenTouched; public BallData(final BallInfo ball) { this.position = new Vector3(ball.physics().location()); this.velocity = new Vector3(ball.physics().velocity()); this.spin = new Vector3(ball.physics().angularVelocity()); + this.hasBeenTouched = ball.latestTouch() != null; + this.latestTouch = this.hasBeenTouched ? new BallTouch(ball.latestTouch()) : null; } } diff --git a/src/main/java/rlbotexample/input/ball/BallTouch.java b/src/main/java/rlbotexample/input/ball/BallTouch.java new file mode 100644 index 0000000..d0af1bb --- /dev/null +++ b/src/main/java/rlbotexample/input/ball/BallTouch.java @@ -0,0 +1,29 @@ +package rlbotexample.input.ball; + + +import rlbot.flat.Touch; +import rlbotexample.vector.Vector3; + +/** + * Basic information about the ball's latest touch. + * + * This class is here for your convenience, it is NOT part of the framework. You can change it as much + * as you want, or delete it. + */ +public class BallTouch { + public final Vector3 position; + public final Vector3 normal; + public final String playerName; + public final float gameSeconds; + public final int playerIndex; + public final int team; + + public BallTouch(final Touch touch) { + this.position = new Vector3(touch.location()); + this.normal = new Vector3(touch.normal()); + this.playerName = touch.playerName(); + this.gameSeconds = touch.gameSeconds(); + this.playerIndex = touch.playerIndex(); + this.team = touch.team(); + } +} diff --git a/src/main/java/rlbotexample/input/CarData.java b/src/main/java/rlbotexample/input/car/CarData.java similarity index 98% rename from src/main/java/rlbotexample/input/CarData.java rename to src/main/java/rlbotexample/input/car/CarData.java index 87fbfa9..1023e73 100644 --- a/src/main/java/rlbotexample/input/CarData.java +++ b/src/main/java/rlbotexample/input/car/CarData.java @@ -1,4 +1,4 @@ -package rlbotexample.input; +package rlbotexample.input.car; import rlbotexample.vector.Vector3; diff --git a/src/main/java/rlbotexample/input/CarOrientation.java b/src/main/java/rlbotexample/input/car/CarOrientation.java similarity index 98% rename from src/main/java/rlbotexample/input/CarOrientation.java rename to src/main/java/rlbotexample/input/car/CarOrientation.java index 84a0fa3..68370ec 100644 --- a/src/main/java/rlbotexample/input/CarOrientation.java +++ b/src/main/java/rlbotexample/input/car/CarOrientation.java @@ -1,4 +1,4 @@ -package rlbotexample.input; +package rlbotexample.input.car; import rlbot.flat.PlayerInfo; diff --git a/src/main/java/rlbotexample/output/ControlsOutput.java b/src/main/java/rlbotexample/output/ControlsOutput.java index b544024..9cdbd2b 100644 --- a/src/main/java/rlbotexample/output/ControlsOutput.java +++ b/src/main/java/rlbotexample/output/ControlsOutput.java @@ -28,6 +28,7 @@ public class ControlsOutput implements ControllerState { private boolean jumpDepressed; private boolean boostDepressed; private boolean slideDepressed; + private boolean useItemDepressed; public ControlsOutput() { } @@ -72,6 +73,11 @@ public ControlsOutput withSlide(boolean slideDepressed) { return this; } + public ControlsOutput withUseItem(boolean useItemDepressed) { + this.useItemDepressed = useItemDepressed; + return this; + } + public ControlsOutput withJump() { this.jumpDepressed = true; return this; @@ -87,6 +93,11 @@ public ControlsOutput withSlide() { return this; } + public ControlsOutput withUseItem() { + this.useItemDepressed = true; + return this; + } + private float clamp(float value) { return Math.max(-1, Math.min(1, value)); } @@ -130,4 +141,9 @@ public boolean holdBoost() { public boolean holdHandbrake() { return slideDepressed; } -} + + @Override + public boolean holdUseItem() { + return useItemDepressed; + } +} \ No newline at end of file diff --git a/src/main/java/rlbotexample/prediction/BallPredictionHelper.java b/src/main/java/rlbotexample/prediction/BallPredictionHelper.java new file mode 100644 index 0000000..77b56c7 --- /dev/null +++ b/src/main/java/rlbotexample/prediction/BallPredictionHelper.java @@ -0,0 +1,30 @@ +package rlbotexample.prediction; + +import rlbot.flat.BallPrediction; +import rlbot.flat.PredictionSlice; +import rlbot.render.Renderer; +import rlbotexample.vector.Vector3; + +import java.awt.*; + +/** + * This class can help you get started with ball prediction. Feel free to change it as much as you want, + * this is part of your bot, not part of the framework! + */ +public class BallPredictionHelper { + + public static void drawTillMoment(BallPrediction ballPrediction, float gameSeconds, Color color, Renderer renderer) { + Vector3 previousLocation = null; + for (int i = 0; i < ballPrediction.slicesLength(); i += 4) { + PredictionSlice slice = ballPrediction.slices(i); + if (slice.gameSeconds() > gameSeconds) { + break; + } + Vector3 location = new Vector3(slice.physics().location()); + if (previousLocation != null) { + renderer.drawLine3d(color, previousLocation, location); + } + previousLocation = location; + } + } +} diff --git a/src/main/java/rlbotexample/util/PortReader.java b/src/main/java/rlbotexample/util/PortReader.java index 892b662..3664bdd 100644 --- a/src/main/java/rlbotexample/util/PortReader.java +++ b/src/main/java/rlbotexample/util/PortReader.java @@ -1,28 +1,23 @@ package rlbotexample.util; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Optional; -import java.util.stream.Stream; /** - * Utility for reading a network port out of a config file. We're expecting a file that has only one line - * that's just a number. + * Utility for reading a network port out of a command line arguments. * * This class is here for your convenience, it is NOT part of the framework. You can add to it as much * as you want, or delete it. */ public class PortReader { - public static Integer readPortFromFile(String s) { - Path path = Paths.get(s); - try (Stream lines = Files.lines(path)) { - Optional firstLine = lines.findFirst(); - return firstLine.map(Integer::parseInt).orElseThrow(() -> new RuntimeException("Port config file was empty!")); - } catch (final IOException e) { - throw new RuntimeException("Failed to read port file! Tried to find it at " + path.toAbsolutePath().toString()); + public static Optional readPortFromArgs(String[] args) { + if (args.length == 0) { + return Optional.empty(); + } + try { + return Optional.of(Integer.parseInt(args[0])); + } catch (NumberFormatException e) { + return Optional.empty(); } } } diff --git a/src/main/java/rlbotexample/vector/Vector2.java b/src/main/java/rlbotexample/vector/Vector2.java index 2197505..dad1633 100644 --- a/src/main/java/rlbotexample/vector/Vector2.java +++ b/src/main/java/rlbotexample/vector/Vector2.java @@ -98,4 +98,9 @@ public double correctionAngle(Vector2 ideal) { public static double angle(Vector2 a, Vector2 b) { return Math.abs(a.correctionAngle(b)); } + + @Override + public String toString() { + return String.format("(%s, %s)", x, y); + } } diff --git a/src/main/java/rlbotexample/vector/Vector3.java b/src/main/java/rlbotexample/vector/Vector3.java index b359c52..506ab21 100644 --- a/src/main/java/rlbotexample/vector/Vector3.java +++ b/src/main/java/rlbotexample/vector/Vector3.java @@ -99,4 +99,9 @@ public Vector3 crossProduct(Vector3 v) { double tz = x * v.y - y * v.x; return new Vector3(tx, ty, tz); } + + @Override + public String toString() { + return String.format("(%s, %s, %s)", x, y, z); + } } diff --git a/src/main/python/README_Tournament.md b/src/main/python/README_Tournament.md index ecff5a5..6ff3059 100644 --- a/src/main/python/README_Tournament.md +++ b/src/main/python/README_Tournament.md @@ -15,5 +15,5 @@ in the whole match, even on opposite teams. Advanced: - It's fine to close and restart `.bat` while the framework is active. -- If there is a port conflict, you can modify both copies of `port.cfg` +- If there is a port conflict, you can modify the port in the bot's python file. to use a different one. There's one here and one in the bin folder. diff --git a/src/main/python/javaExample.py b/src/main/python/javaExample.py index dad0097..faf3765 100644 --- a/src/main/python/javaExample.py +++ b/src/main/python/javaExample.py @@ -1,18 +1,15 @@ -import os - from rlbot.agents.base_agent import BOT_CONFIG_AGENT_HEADER -from rlbot.agents.base_java_agent import BaseJavaAgent +from rlbot.agents.executable_with_socket_agent import ExecutableWithSocketAgent from rlbot.parsing.custom_config import ConfigHeader, ConfigObject -class JavaExample(BaseJavaAgent): - def get_port_file_path(self): - # Look for a port.cfg file in the same directory as THIS python file. - return os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__), 'port.cfg')) +class JavaExample(ExecutableWithSocketAgent): + def get_port(self) -> int: + return 17357 def load_config(self, config_header: ConfigHeader): - self.java_executable_path = config_header.getpath('java_executable_path') - self.logger.info("Java executable is configured as {}".format(self.java_executable_path)) + self.executable_path = config_header.getpath('java_executable_path') + self.logger.info("Java executable is configured as {}".format(self.executable_path)) @staticmethod def create_agent_configurations(config: ConfigObject): diff --git a/src/main/python/javaExampleAppearance.cfg b/src/main/python/javaExampleAppearance.cfg index 5cdf0a4..468bbe3 100644 --- a/src/main/python/javaExampleAppearance.cfg +++ b/src/main/python/javaExampleAppearance.cfg @@ -1,33 +1,49 @@ [Bot Loadout] -# Name that will be displayed in game -name = JavaExample -team_color_id = 27 -custom_color_id = 75 +team_color_id = 60 +custom_color_id = 0 car_id = 23 -decal_id = 307 -wheels_id = 1656 -boost_id = 0 -antenna_id = 287 +decal_id = 0 +wheels_id = 1565 +boost_id = 35 +antenna_id = 0 hat_id = 0 -paint_finish_id = 1978 -custom_finish_id = 1978 +paint_finish_id = 1681 +custom_finish_id = 1681 engine_audio_id = 0 -trails_id = 0 -goal_explosion_id = 1971 +trails_id = 3220 +goal_explosion_id = 3018 [Bot Loadout Orange] -# Name that will be displayed in game -name = JavaExample -team_color_id = 1 -custom_color_id = 1 +team_color_id = 3 +custom_color_id = 0 car_id = 23 decal_id = 0 -wheels_id = 818 -boost_id = 0 -antenna_id = 287 +wheels_id = 1565 +boost_id = 35 +antenna_id = 0 hat_id = 0 -paint_finish_id = 266 -custom_finish_id = 266 +paint_finish_id = 1681 +custom_finish_id = 1681 engine_audio_id = 0 -trails_id = 0 -goal_explosion_id = 1971 \ No newline at end of file +trails_id = 3220 +goal_explosion_id = 3018 + +[Bot Paint Blue] +car_paint_id = 12 +decal_paint_id = 0 +wheels_paint_id = 7 +boost_paint_id = 7 +antenna_paint_id = 0 +hat_paint_id = 0 +trails_paint_id = 2 +goal_explosion_paint_id = 0 + +[Bot Paint Orange] +car_paint_id = 12 +decal_paint_id = 0 +wheels_paint_id = 14 +boost_paint_id = 14 +antenna_paint_id = 0 +hat_paint_id = 0 +trails_paint_id = 14 +goal_explosion_paint_id = 0 diff --git a/src/main/python/port.cfg b/src/main/python/port.cfg deleted file mode 100644 index 1256cfb..0000000 Binary files a/src/main/python/port.cfg and /dev/null differ diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png new file mode 100644 index 0000000..0bafcdf Binary files /dev/null and b/src/main/resources/icon.png differ