diff --git a/.gitignore b/.gitignore index 6c8dd21..f32e792 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .idea/ out/ build/ -.gradle \ No newline at end of file +.gradle +__pycache__ +*.iml \ No newline at end of file diff --git a/README.md b/README.md index c519d0e..d354da3 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,23 @@ # RLBotJavaExample An example bot implemented in Java +## Video Guide + +https://youtu.be/mPfYqKe_KRs (slightly outdated because it uses the old GUI) + ## Usage Instructions: -These instructions should get dramatically simpler once we get our packages in jcenter. -For now, this is what you need to do: +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. 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%". + - 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` -1. Setup the RLBot framework - https://github.com/RLBot/RLBot (v4 branch for now) -2. Create a new folder in the framework at /agents/protoBotJava. -3. Copy the contents of the src/main/python folder the protoBotJava folder. -4. Copy src/main/resources/port.cfg into the protoBotJava folder. -5. In the framework directory, find rlbot.cfg and modify one of the lines to point to ./agents/protoBotJava/protoBotJava.cfg -6. On the command line in this directory, execute `gradlew.bat run` -7. On a different command line, in the framework directory, execute `python runner.py` +See the [wiki](https://github.com/RLBot/RLBotJavaExample/wiki) +for tips to improve your programming experience. diff --git a/RefreshEnv.cmd b/RefreshEnv.cmd new file mode 100644 index 0000000..567fd78 --- /dev/null +++ b/RefreshEnv.cmd @@ -0,0 +1,66 @@ +@echo off +:: This file is taken from chocolatey: +:: https://github.com/chocolatey/choco/blob/master/src/chocolatey.resources/redirects/RefreshEnv.cmd +:: +:: RefreshEnv.cmd +:: +:: Batch file to read environment variables from registry and +:: set session variables to these values. +:: +:: With this batch file, there should be no need to reload command +:: environment every time you want environment changes to propagate + +::echo "RefreshEnv.cmd only works from cmd.exe, please install the Chocolatey Profile to take advantage of refreshenv from PowerShell" +echo | set /p dummy="Refreshing environment variables from registry for cmd.exe. Please wait..." + +goto main + +:: Set one environment variable from registry key +:SetFromReg + "%WinDir%\System32\Reg" QUERY "%~1" /v "%~2" > "%TEMP%\_envset.tmp" 2>NUL + for /f "usebackq skip=2 tokens=2,*" %%A IN ("%TEMP%\_envset.tmp") do ( + echo/set "%~3=%%B" + ) + goto :EOF + +:: Get a list of environment variables from registry +:GetRegEnv + "%WinDir%\System32\Reg" QUERY "%~1" > "%TEMP%\_envget.tmp" + for /f "usebackq skip=2" %%A IN ("%TEMP%\_envget.tmp") do ( + if /I not "%%~A"=="Path" ( + call :SetFromReg "%~1" "%%~A" "%%~A" + ) + ) + goto :EOF + +:main + echo/@echo off >"%TEMP%\_env.cmd" + + :: Slowly generating final file + call :GetRegEnv "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" >> "%TEMP%\_env.cmd" + call :GetRegEnv "HKCU\Environment">>"%TEMP%\_env.cmd" >> "%TEMP%\_env.cmd" + + :: Special handling for PATH - mix both User and System + call :SetFromReg "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" Path Path_HKLM >> "%TEMP%\_env.cmd" + call :SetFromReg "HKCU\Environment" Path Path_HKCU >> "%TEMP%\_env.cmd" + + :: Caution: do not insert space-chars before >> redirection sign + echo/set "Path=%%Path_HKLM%%;%%Path_HKCU%%" >> "%TEMP%\_env.cmd" + + :: Cleanup + del /f /q "%TEMP%\_envset.tmp" 2>nul + del /f /q "%TEMP%\_envget.tmp" 2>nul + + :: capture user / architecture + SET "OriginalUserName=%USERNAME%" + SET "OriginalArchitecture=%PROCESSOR_ARCHITECTURE%" + + :: Set these variables + call "%TEMP%\_env.cmd" + + :: reset user / architecture + SET "USERNAME=%OriginalUserName%" + SET "PROCESSOR_ARCHITECTURE=%OriginalArchitecture%" + + echo | set /p dummy="Finished." + echo . \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8d5e6bb..e4bda5e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ -group 'RLBot' -version '0.0.1' +// This file controls gradle, which we are using to install and update the RLBot framework used by this example bot, +// and also compile and run the java code used by this bot. apply plugin: 'java' apply plugin: 'application' @@ -7,17 +7,48 @@ apply plugin: 'application' sourceCompatibility = 1.8 repositories { - jcenter() - maven { url 'https://jitpack.io' } + mavenCentral() } -mainClassName = 'rlbot.JavaExample' +mainClassName = 'rlbotexample.JavaExample' + +// This directory will be created and the interface dll copied into it at runtime. +// The end result is that the interface dll will be available for loading. +def dllDirectory = 'build/dll' +applicationDefaultJvmArgs = ["-Djna.library.path=" + dllDirectory] dependencies { - compile group: 'net.sf.py4j', name: 'py4j', version: '0.10.6' - compile group: 'net.java.dev.jna', name: 'jna', version: '4.5.1' - compile group: 'net.java.dev.jna', name: 'jna-platform', version: '4.5.1' - compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.1.0' - compile 'com.github.RLBot:RLBotProtobuf:0.0.2' - runtime files('lib') // This is temporary until we can get more stuff in jcenter. + // Fetch the framework jar file + 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 createDllDirectory { + mkdir dllDirectory +} + +run.dependsOn createDllDirectory + +applicationDistribution.exclude(dllDirectory) + +// You can run gradew.bat distZip to generate a zip file suitable for tournament submissions. +// It will be generated in build/distributions +distZip { + into ('python') { + from fileTree('src/main/python') { + exclude '__pycache__' + } + } +} + +// 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__' + } + } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 51288f9..29953ea 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5d2b245..e0b3fb8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sun Mar 18 21:15:05 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip diff --git a/gradlew b/gradlew index 4453cce..cccdd3d 100644 --- a/gradlew +++ b/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } diff --git a/lib/RLBot_Core_Interface.dll b/lib/RLBot_Core_Interface.dll deleted file mode 100644 index 4ccf336..0000000 Binary files a/lib/RLBot_Core_Interface.dll and /dev/null differ 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/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b419194 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +# Include everything the framework requires +# You will automatically get updates for all versions starting with "1.". +rlbot==1.* + +# Used to allow python to send commands to java, e.g. start running the bot +py4j diff --git a/rlbot.cfg b/rlbot.cfg new file mode 100644 index 0000000..7944055 --- /dev/null +++ b/rlbot.cfg @@ -0,0 +1,76 @@ +[RLBot Configuration] +# Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. + +[Team Configuration] +# Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. + +[Match Configuration] +# 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. + +[Participant Configuration] +# Put the name of your bot config file here. Only num_participants config files will be read! +# Everything needs a config, even players and default bots. We still set loadouts and names from config! +participant_config_0 = src/main/python/javaExample.cfg +participant_config_1 = src/main/python/javaExample.cfg +participant_config_2 = src/main/python/javaExample.cfg +participant_config_3 = src/main/python/javaExample.cfg +participant_config_4 = src/main/python/javaExample.cfg +participant_config_5 = src/main/python/javaExample.cfg +participant_config_6 = src/main/python/javaExample.cfg +participant_config_7 = src/main/python/javaExample.cfg +participant_config_8 = src/main/python/javaExample.cfg +participant_config_9 = src/main/python/javaExample.cfg + +# team 0 shoots on positive goal, team 1 shoots on negative goal +participant_team_0 = 0 +participant_team_1 = 1 +participant_team_2 = 0 +participant_team_3 = 1 +participant_team_4 = 0 +participant_team_5 = 1 +participant_team_6 = 0 +participant_team_7 = 1 +participant_team_8 = 0 +participant_team_9 = 1 + +# Accepted values are "human", "rlbot", "psyonix", and "party_member_bot" +# You can have up to 4 local players and they must be activated in game or it will crash. +# If no player is specified you will be spawned in as spectator! +# human - not controlled by the framework +# rlbot - controlled by the framework +# psyonix - default bots (skill level can be changed with participant_bot_skill +# party_member_bot - controlled by the framework but the game detects it as a human +participant_type_0 = rlbot +participant_type_1 = rlbot +participant_type_2 = rlbot +participant_type_3 = rlbot +participant_type_4 = rlbot +participant_type_5 = rlbot +participant_type_6 = rlbot +participant_type_7 = rlbot +participant_type_8 = rlbot +participant_type_9 = rlbot + + +# If participant is a bot and not RLBot controlled, this value will be used to set bot skill. +# 0.0 is Rookie, 0.5 is pro, 1.0 is all-star. You can set values in-between as well. +# Please leave a value here even if it isn't used :) +participant_bot_skill_0 = 1.0 +participant_bot_skill_1 = 1.0 +participant_bot_skill_2 = 1.0 +participant_bot_skill_3 = 1.0 +participant_bot_skill_4 = 1.0 +participant_bot_skill_5 = 1.0 +participant_bot_skill_6 = 1.0 +participant_bot_skill_7 = 1.0 +participant_bot_skill_8 = 1.0 +participant_bot_skill_9 = 1.0 diff --git a/run-bot.bat b/run-bot.bat new file mode 100644 index 0000000..fd7a00b --- /dev/null +++ b/run-bot.bat @@ -0,0 +1,7 @@ +@rem Change the working directory to the location of this file so that relative paths will work +cd /D "%~dp0" + +@rem Start running the bot. +call ./gradlew.bat --no-daemon run + +pause diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..b3318d9 --- /dev/null +++ b/run.bat @@ -0,0 +1,11 @@ +@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 + +pause diff --git a/run.py b/run.py new file mode 100644 index 0000000..23ea98a --- /dev/null +++ b/run.py @@ -0,0 +1,33 @@ +import subprocess +import sys + +DEFAULT_LOGGER = 'rlbot' + +if __name__ == '__main__': + + try: + 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']) + + # 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) + + except ImportError: + subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + + try: + from rlbot import runner + runner.main() + except Exception as e: + print("Encountered exception: ", e) + print("Press enter to close.") + input() diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 6da6a06..0000000 --- a/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'java-example' - diff --git a/src/main/java/rlbot/Bot.java b/src/main/java/rlbot/Bot.java deleted file mode 100644 index 7527ab3..0000000 --- a/src/main/java/rlbot/Bot.java +++ /dev/null @@ -1,81 +0,0 @@ -package rlbot; - -import rlbot.api.GameData; -import rlbot.input.CarData; -import rlbot.input.DataPacket; -import rlbot.interop.RLBotDll; -import rlbot.vector.Vector2; - -import java.time.Duration; -import java.time.LocalDateTime; - -public class Bot { - - private final int playerIndex; - private boolean keepRunning; - private Thread looper; - - public Bot(int playerIndex) { - this.playerIndex = playerIndex; - } - - private ControlsOutput processInput(DataPacket input) { - - Vector2 ballPosition = input.ball.position.flatten(); - CarData myCar = input.car; - Vector2 carPosition = myCar.position.flatten(); - Vector2 carDirection = myCar.orientation.noseVector.flatten(); - Vector2 carToBall = ballPosition.minus(carPosition); - - double steerCorrectionRadians = carDirection.correctionAngle(carToBall); - float steer; - if (steerCorrectionRadians > 0) { - steer = -1; - } else { - steer = 1; - } - - return new ControlsOutput() - .withSteer(steer) - .withThrottle(1); - } - - private GameData.ControllerState reactToFrame(GameData.GameTickPacket request) { - DataPacket dataPacket = new DataPacket(request, playerIndex); - return processInput(dataPacket).toControllerState(); - } - - public void start() { - keepRunning = true; - looper = new Thread(this::doLoop); - looper.start(); - } - - private void doLoop() { - while (keepRunning) { - - LocalDateTime before = LocalDateTime.now(); - try { - GameData.GameTickPacket packet = BotManager.latestPacket; - if (packet != null) { - GameData.ControllerState controllerState = reactToFrame(packet); - RLBotDll.setControllerState(controllerState, playerIndex); - } - } catch (final Throwable e) { - e.printStackTrace(); - } - - try { - long executionMillis = Duration.between(before, LocalDateTime.now()).toMillis(); - long sleepTime = Math.max(0, (1000 / 60) - executionMillis); - Thread.sleep(sleepTime); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - public void retire() { - keepRunning = false; - } -} diff --git a/src/main/java/rlbot/BotManager.java b/src/main/java/rlbot/BotManager.java deleted file mode 100644 index 99ea9de..0000000 --- a/src/main/java/rlbot/BotManager.java +++ /dev/null @@ -1,72 +0,0 @@ -package rlbot; - -import com.google.protobuf.InvalidProtocolBufferException; -import rlbot.api.GameData; -import rlbot.interop.RLBotDll; - -import java.util.HashMap; -import java.util.Map; - -public class BotManager { - - private static final Map initializedBots = new HashMap<>(); - - private boolean keepRunning; - private Thread looper; - - public static GameData.GameTickPacket latestPacket; - - public void registerBot(final int index, final String botType) { - if (initializedBots.containsKey(index)) { - return; - } - - initializedBots.computeIfAbsent(index, (idx) -> { - Bot bot = this.createBot(idx, botType); - bot.start(); - return bot; - }); - } - - private void retireBots() { - initializedBots.values().forEach(Bot::retire); - initializedBots.clear(); - } - - private Bot createBot(int playerIndex, String botType) { - return new Bot(playerIndex); - } - - public void start() { - if (keepRunning) { - return; // Already started - } - - keepRunning = true; - looper = new Thread(this::doLoop); - looper.start(); - } - - private void doLoop() { - while (keepRunning) { - - try { - latestPacket = RLBotDll.getProtoPacket(); - - } catch (InvalidProtocolBufferException e) { - e.printStackTrace(); - } - - try { - Thread.sleep(16); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - public void retire() { - keepRunning = false; - retireBots(); - } -} diff --git a/src/main/java/rlbot/JavaExample.java b/src/main/java/rlbot/JavaExample.java deleted file mode 100644 index f296ab1..0000000 --- a/src/main/java/rlbot/JavaExample.java +++ /dev/null @@ -1,42 +0,0 @@ -package rlbot; - -import com.google.protobuf.InvalidProtocolBufferException; -import py4j.GatewayServer; - -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; - -/** - * See JavaAgent.py for usage instructions - */ -public class JavaExample { - - public static void main(String[] args) throws InvalidProtocolBufferException { - - // Scenario: you finished your bot and submitted it to a tournament. Your opponent hard-coded the same - // as you, and the match can't start because of the conflict. Because of this line, you can ask the - // organizer make a file called "port.txt" in the same directory as your .jar, and put some other number in it. - // This matches code in JavaAgent.py - int port = readPortFromFile(); - - GatewayServer gatewayServer = new GatewayServer(new PythonEntryPoint(), port); - gatewayServer.start(); - System.out.println(String.format("Gateway server started on port %s. Listening for Rocket League data!", port)); - } - - private static Integer readPortFromFile() { - Path path = Paths.get("port.cfg"); - - 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()); - } - } - -} \ No newline at end of file diff --git a/src/main/java/rlbot/PythonEntryPoint.java b/src/main/java/rlbot/PythonEntryPoint.java deleted file mode 100644 index b919d0e..0000000 --- a/src/main/java/rlbot/PythonEntryPoint.java +++ /dev/null @@ -1,18 +0,0 @@ -package rlbot; - -public class PythonEntryPoint { - - private static final BotManager botManager = new BotManager(); - - public void startup() { - botManager.start(); - } - - public void shutdown() { - botManager.retire(); - } - - public void registerBot(int index, String botType) { - botManager.registerBot(index, botType); - } -} diff --git a/src/main/java/rlbot/input/BallData.java b/src/main/java/rlbot/input/BallData.java deleted file mode 100644 index a6de918..0000000 --- a/src/main/java/rlbot/input/BallData.java +++ /dev/null @@ -1,17 +0,0 @@ -package rlbot.input; - - -import rlbot.api.GameData; -import rlbot.vector.Vector3; - -public class BallData { - public final Vector3 position; - public final Vector3 velocity; - public final Vector3 spin; - - public BallData(final GameData.BallInfo ballInfo) { - this.position = Vector3.fromProto(ballInfo.getLocation()); - this.velocity = Vector3.fromProto(ballInfo.getLocation()); - this.spin = Vector3.fromProto(ballInfo.getAngularVelocity()); - } -} diff --git a/src/main/java/rlbot/input/CarData.java b/src/main/java/rlbot/input/CarData.java deleted file mode 100644 index 6559591..0000000 --- a/src/main/java/rlbot/input/CarData.java +++ /dev/null @@ -1,28 +0,0 @@ -package rlbot.input; - - -import rlbot.api.GameData; -import rlbot.vector.Vector3; - -public class CarData { - public final Vector3 position; - public final Vector3 velocity; - public final CarOrientation orientation; - public final double boost; - public final boolean isMidair; - public final boolean isSupersonic; - public final int team; - public final float elapsedSeconds; - - public CarData(GameData.PlayerInfo playerInfo, float elapsedSeconds) { - - this.position = Vector3.fromProto(playerInfo.getLocation()); - this.velocity = Vector3.fromProto(playerInfo.getVelocity()); - this.orientation = CarOrientation.fromPlayerInfo(playerInfo); - this.boost = playerInfo.getBoost(); - this.isSupersonic = playerInfo.getIsSupersonic(); - this.team = playerInfo.getTeam(); - this.isMidair = playerInfo.getIsMidair(); - this.elapsedSeconds = elapsedSeconds; - } -} diff --git a/src/main/java/rlbot/input/DataPacket.java b/src/main/java/rlbot/input/DataPacket.java deleted file mode 100644 index 5ef9734..0000000 --- a/src/main/java/rlbot/input/DataPacket.java +++ /dev/null @@ -1,34 +0,0 @@ -package rlbot.input; - -import rlbot.vector.Vector3; - -import java.util.ArrayList; -import java.util.List; - -public class DataPacket { - - public final CarData car; - public final BallData ball; - public final int team; - public final int playerIndex; - public final List fullBoosts = new ArrayList<>(6); - public final rlbot.api.GameData.GameInfo matchInfo; - - public DataPacket(rlbot.api.GameData.GameTickPacket request, int playerIndex) { - - this.playerIndex = playerIndex; - this.matchInfo = request.getGameInfo(); - this.ball = new BallData(request.getBall()); - - rlbot.api.GameData.PlayerInfo myPlayerInfo = request.getPlayers(playerIndex); - this.team = myPlayerInfo.getTeam(); - this.car = new CarData(myPlayerInfo, request.getGameInfo().getSecondsElapsed()); - - for (rlbot.api.GameData.BoostInfo boostInfo: request.getBoostPadsList()) { - Vector3 location = Vector3.fromProto(boostInfo.getLocation()); - if (FullBoost.isFullBoostLocation(location)) { - fullBoosts.add(new FullBoost(location, boostInfo.getIsActive())); - } - } - } -} diff --git a/src/main/java/rlbot/input/FullBoost.java b/src/main/java/rlbot/input/FullBoost.java deleted file mode 100644 index 9290707..0000000 --- a/src/main/java/rlbot/input/FullBoost.java +++ /dev/null @@ -1,41 +0,0 @@ -package rlbot.input; - - -import rlbot.vector.Vector3; - -import java.util.Arrays; -import java.util.List; - -public class FullBoost { - - private static final int MIDFIELD_BOOST_WIDTH = 3584; - private static final int CORNER_BOOST_WIDTH = 3072; - private static final int CORNER_BOOST_DEPTH = 4096; - - private static final List fullBoostLocations = Arrays.asList( - new Vector3(MIDFIELD_BOOST_WIDTH, 0, 0), - new Vector3(-MIDFIELD_BOOST_WIDTH, 0, 0), - new Vector3(-CORNER_BOOST_WIDTH, -CORNER_BOOST_DEPTH, 0), - new Vector3(-CORNER_BOOST_WIDTH, CORNER_BOOST_DEPTH, 0), - new Vector3(CORNER_BOOST_WIDTH, -CORNER_BOOST_DEPTH, 0), - new Vector3(CORNER_BOOST_WIDTH, CORNER_BOOST_DEPTH, 0) - ); - - public Vector3 location; - public boolean isActive; - - public FullBoost(Vector3 location, boolean isActive) { - this.location = location; - this.isActive = isActive; - } - - public static boolean isFullBoostLocation(Vector3 location) { - for (Vector3 boostLoc: fullBoostLocations) { - if (boostLoc.distance(location) < 10) { - return true; - } - } - return false; - } - -} diff --git a/src/main/java/rlbot/interop/ByteBufferStruct.java b/src/main/java/rlbot/interop/ByteBufferStruct.java deleted file mode 100644 index 921e10b..0000000 --- a/src/main/java/rlbot/interop/ByteBufferStruct.java +++ /dev/null @@ -1,20 +0,0 @@ -package rlbot.interop; - -import com.sun.jna.Pointer; -import com.sun.jna.Structure; - -import java.util.Arrays; -import java.util.List; - -public class ByteBufferStruct extends Structure implements Structure.ByValue { - - private static final List fields = Arrays.asList("ptr", "size"); - - public Pointer ptr; - public int size; - - @Override - protected List getFieldOrder() { - return fields; - } -} diff --git a/src/main/java/rlbot/interop/RLBotDll.java b/src/main/java/rlbot/interop/RLBotDll.java deleted file mode 100644 index 2b453e5..0000000 --- a/src/main/java/rlbot/interop/RLBotDll.java +++ /dev/null @@ -1,38 +0,0 @@ -package rlbot.interop; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.sun.jna.Memory; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import rlbot.api.GameData; - -public class RLBotDll { - - private static native ByteBufferStruct UpdateLiveDataPacketProto(); - private static native int UpdatePlayerInputProto(Pointer ptr, int size, int playerIndex); - - static { - Native.register("RLBot_Core_Interface"); - } - - public static GameData.GameTickPacket getProtoPacket() throws InvalidProtocolBufferException { - ByteBufferStruct struct = UpdateLiveDataPacketProto(); - byte[] protoBytes = struct.ptr.getByteArray(0, struct.size); - GameData.GameTickPacket packet = GameData.GameTickPacket.parseFrom(protoBytes); - - return packet; - } - - public static void setControllerState(GameData.ControllerState controllerState, int playerIndex) { - byte[] protoBytes = controllerState.toByteArray(); - Memory memory = getMemory(protoBytes); - UpdatePlayerInputProto(memory, protoBytes.length, playerIndex); - } - - private static Memory getMemory(byte[] protoBytes) { - final Memory mem = new Memory(protoBytes.length); - mem.write(0, protoBytes, 0, protoBytes.length); - return mem; - } - -} diff --git a/src/main/java/rlbotexample/JavaExample.java b/src/main/java/rlbotexample/JavaExample.java new file mode 100644 index 0000000..d3d7230 --- /dev/null +++ b/src/main/java/rlbotexample/JavaExample.java @@ -0,0 +1,80 @@ +package rlbotexample; + +import rlbot.manager.BotManager; +import rlbotexample.util.PortReader; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.ActionListener; +import java.net.URL; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * See JavaAgent.py for usage instructions. + * + * Look inside SampleBot.java for the actual bot logic! + */ +public class JavaExample { + + private static final int DEFAULT_PORT = 17357; + + public static void main(String[] args) { + + BotManager botManager = new BotManager(); + 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)); + 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: "); + 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(); + + String botsStr; + if (runningBotIndices.isEmpty()) { + botsStr = "None"; + } else { + botsStr = runningBotIndices.stream() + .sorted() + .map(i -> "#" + i) + .collect(Collectors.joining(", ")); + } + 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 new file mode 100644 index 0000000..0a82f17 --- /dev/null +++ b/src/main/java/rlbotexample/SampleBot.java @@ -0,0 +1,128 @@ +package rlbotexample; + +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.input.DataPacket; +import rlbotexample.input.car.CarData; +import rlbotexample.output.ControlsOutput; +import rlbotexample.prediction.BallPredictionHelper; +import rlbotexample.vector.Vector2; + +import java.awt.*; + +public class SampleBot implements Bot { + + private final int playerIndex; + + public SampleBot(int playerIndex) { + this.playerIndex = playerIndex; + } + + /** + * This is where we keep the actual bot logic. This function shows how to chase the ball. + * Modify it to make your bot smarter! + */ + private ControlsOutput processInput(DataPacket input) { + + Vector2 ballPosition = input.ball.position.flatten(); + CarData myCar = input.car; + Vector2 carPosition = myCar.position.flatten(); + Vector2 carDirection = myCar.orientation.noseVector.flatten(); + + // Subtract the two positions to get a vector pointing from the car to the ball. + Vector2 carToBall = ballPosition.minus(carPosition); + + // How far does the car need to rotate before it's pointing exactly at the ball? + double steerCorrectionRadians = carDirection.correctionAngle(carToBall); + + boolean goLeft = steerCorrectionRadians > 0; + + // This is optional! + drawDebugLines(input, myCar, goLeft); + + // This is also optional! + if (input.ball.position.z > 300) { + RLBotDll.sendQuickChat(playerIndex, false, QuickChatSelection.Compliments_NiceOne); + } + + return new ControlsOutput() + .withSteer(goLeft ? -1 : 1) + .withThrottle(1); + } + + /** + * This is a nice example of using the rendering feature. + */ + private void drawDebugLines(DataPacket input, CarData myCar, boolean goLeft) { + // Here's an example of rendering debug data on the screen. + Renderer renderer = BotLoopRenderer.forBotLoop(this); + + // Draw a line from the car to the ball + renderer.drawLine3d(Color.LIGHT_GRAY, myCar.position, input.ball.position); + + // Draw a line that points out from the nose of the car. + renderer.drawLine3d(goLeft ? Color.BLUE : Color.RED, + myCar.position.plus(myCar.orientation.noseVector.scaled(150)), + myCar.position.plus(myCar.orientation.noseVector.scaled(300))); + + renderer.drawString3d(goLeft ? "left" : "right", Color.WHITE, myCar.position, 2, 2); + + 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); + } + + 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(); + } + } + + + @Override + public int getIndex() { + return this.playerIndex; + } + + /** + * This is the most important function. It will automatically get called by the framework with fresh data + * every frame. Respond with appropriate controls! + */ + @Override + public ControllerState processInput(GameTickPacket packet) { + + if (packet.playersLength() <= playerIndex || packet.ball() == null || !packet.gameInfo().isRoundActive()) { + // Just return immediately if something looks wrong with the data. This helps us avoid stack traces. + return new ControlsOutput(); + } + + // Update the boost manager and tile manager with the latest data + BoostManager.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! + DataPacket dataPacket = new DataPacket(packet, playerIndex); + + // Do the actual logic using our dataPacket. + ControlsOutput controlsOutput = processInput(dataPacket); + + 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 new file mode 100644 index 0000000..aa679bc --- /dev/null +++ b/src/main/java/rlbotexample/SamplePythonInterface.java @@ -0,0 +1,17 @@ +package rlbotexample; + +import rlbot.Bot; +import rlbot.manager.BotManager; +import rlbot.pyinterop.SocketServer; + +public class SamplePythonInterface extends SocketServer { + + 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 new file mode 100644 index 0000000..b93d7a6 --- /dev/null +++ b/src/main/java/rlbotexample/boost/BoostManager.java @@ -0,0 +1,72 @@ +package rlbotexample.boost; + +import rlbot.cppinterop.RLBotDll; +import rlbot.flat.BoostPadState; +import rlbot.flat.FieldInfo; +import rlbot.flat.GameTickPacket; +import rlbotexample.vector.Vector3; + +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. + * + * 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 BoostManager { + + private static final List orderedBoosts = new ArrayList<>(); + private static final List fullBoosts = new ArrayList<>(); + private static final List smallBoosts = new ArrayList<>(); + + public static List getFullBoosts() { + return fullBoosts; + } + + public static List getSmallBoosts() { + return smallBoosts; + } + + private static void loadFieldInfo(FieldInfo fieldInfo) { + + synchronized (orderedBoosts) { + + orderedBoosts.clear(); + fullBoosts.clear(); + smallBoosts.clear(); + + for (int i = 0; i < fieldInfo.boostPadsLength(); i++) { + rlbot.flat.BoostPad flatPad = fieldInfo.boostPads(i); + BoostPad ourPad = new BoostPad(new Vector3(flatPad.location()), flatPad.isFullBoost()); + orderedBoosts.add(ourPad); + if (ourPad.isFullBoost()) { + fullBoosts.add(ourPad); + } else { + smallBoosts.add(ourPad); + } + } + } + } + + public static void loadGameTickPacket(GameTickPacket packet) { + + if (packet.boostPadStatesLength() > orderedBoosts.size()) { + try { + loadFieldInfo(RLBotDll.getFieldInfo()); + } catch (IOException e) { + e.printStackTrace(); + return; + } + } + + for (int i = 0; i < packet.boostPadStatesLength(); i++) { + BoostPadState boost = packet.boostPadStates(i); + BoostPad existingPad = orderedBoosts.get(i); // existingPad is also referenced from the fullBoosts and smallBoosts lists + existingPad.setActive(boost.isActive()); + } + } + +} diff --git a/src/main/java/rlbotexample/boost/BoostPad.java b/src/main/java/rlbotexample/boost/BoostPad.java new file mode 100644 index 0000000..c4f0c2d --- /dev/null +++ b/src/main/java/rlbotexample/boost/BoostPad.java @@ -0,0 +1,38 @@ +package rlbotexample.boost; + + +import rlbotexample.vector.Vector3; + +/** + * Representation of one of the boost pads on the field. + * + * 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 BoostPad { + + private final Vector3 location; + private final boolean isFullBoost; + private boolean isActive; + + public BoostPad(Vector3 location, boolean isFullBoost) { + this.location = location; + this.isFullBoost = isFullBoost; + } + + public void setActive(boolean active) { + isActive = active; + } + + public Vector3 getLocation() { + return location; + } + + public boolean isFullBoost() { + return isFullBoost; + } + + public boolean isActive() { + return isActive; + } +} diff --git a/src/main/java/rlbotexample/input/DataPacket.java b/src/main/java/rlbotexample/input/DataPacket.java new file mode 100644 index 0000000..a61b69c --- /dev/null +++ b/src/main/java/rlbotexample/input/DataPacket.java @@ -0,0 +1,43 @@ +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; + +/** + * 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. The benefits of using this instead of rlbot.flat.GameTickPacket are: + * 1. You end up with nice custom Vector3 objects that you can call methods on. + * 2. If the framework changes its data format, you can just update the code here + * and leave your bot logic alone. + */ +public class DataPacket { + + /** Your own car, based on the playerIndex */ + public final CarData car; + + public final List allCars; + + public final BallData ball; + public final int team; + + /** The index of your player */ + public final int playerIndex; + + public DataPacket(GameTickPacket request, int playerIndex) { + + this.playerIndex = playerIndex; + this.ball = new BallData(request.ball()); + + allCars = new ArrayList<>(); + for (int i = 0; i < request.playersLength(); i++) { + allCars.add(new CarData(request.players(i), request.gameInfo().secondsElapsed())); + } + + this.car = allCars.get(playerIndex); + this.team = this.car.team; + } +} diff --git a/src/main/java/rlbotexample/input/ball/BallData.java b/src/main/java/rlbotexample/input/ball/BallData.java new file mode 100644 index 0000000..8dffab4 --- /dev/null +++ b/src/main/java/rlbotexample/input/ball/BallData.java @@ -0,0 +1,27 @@ +package rlbotexample.input.ball; + + +import rlbot.flat.BallInfo; +import rlbotexample.vector.Vector3; + +/** + * Basic information about the ball. + * + * 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 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/car/CarData.java b/src/main/java/rlbotexample/input/car/CarData.java new file mode 100644 index 0000000..1023e73 --- /dev/null +++ b/src/main/java/rlbotexample/input/car/CarData.java @@ -0,0 +1,56 @@ +package rlbotexample.input.car; + + +import rlbotexample.vector.Vector3; + +/** + * Basic information about the car. + * + * 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 CarData { + + /** The location of the car on the field. (0, 0, 0) is center field. */ + public final Vector3 position; + + /** The velocity of the car. */ + public final Vector3 velocity; + + /** The orientation of the car */ + public final CarOrientation orientation; + + /** Boost ranges from 0 to 100 */ + public final double boost; + + /** True if the car is driving on the ground, the wall, etc. In other words, true if you can steer. */ + public final boolean hasWheelContact; + + /** + * True if the car is showing the supersonic and can demolish enemies on contact. + * This is a close approximation for whether the car is at max speed. + */ + public final boolean isSupersonic; + + /** + * 0 for blue team, 1 for orange team. + */ + public final int team; + + /** + * This is not really a car-specific attribute, but it's often very useful to know. It's included here + * so you don't need to pass around DataPacket everywhere. + */ + public final float elapsedSeconds; + + public CarData(rlbot.flat.PlayerInfo playerInfo, float elapsedSeconds) { + this.position = new Vector3(playerInfo.physics().location()); + this.velocity = new Vector3(playerInfo.physics().velocity()); + this.orientation = CarOrientation.fromFlatbuffer(playerInfo); + this.boost = playerInfo.boost(); + this.isSupersonic = playerInfo.isSupersonic(); + this.team = playerInfo.team(); + this.hasWheelContact = playerInfo.hasWheelContact(); + this.elapsedSeconds = elapsedSeconds; + } +} diff --git a/src/main/java/rlbot/input/CarOrientation.java b/src/main/java/rlbotexample/input/car/CarOrientation.java similarity index 51% rename from src/main/java/rlbot/input/CarOrientation.java rename to src/main/java/rlbotexample/input/car/CarOrientation.java index 93ff2c6..68370ec 100644 --- a/src/main/java/rlbot/input/CarOrientation.java +++ b/src/main/java/rlbotexample/input/car/CarOrientation.java @@ -1,14 +1,25 @@ -package rlbot.input; +package rlbotexample.input.car; -import rlbot.api.GameData; -import rlbot.vector.Vector3; +import rlbot.flat.PlayerInfo; +import rlbotexample.vector.Vector3; +/** + * The car's orientation in space, a.k.a. what direction it's pointing. + * + * 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 CarOrientation { - public Vector3 noseVector; - public Vector3 roofVector; - public Vector3 rightVector; + /** The direction that the front of the car is facing */ + public final Vector3 noseVector; + + /** The direction the roof of the car is facing. (0, 0, 1) means the car is upright. */ + public final Vector3 roofVector; + + /** The direction that the right side of the car is facing. */ + public final Vector3 rightVector; public CarOrientation(Vector3 noseVector, Vector3 roofVector) { @@ -17,11 +28,11 @@ public CarOrientation(Vector3 noseVector, Vector3 roofVector) { this.rightVector = noseVector.crossProduct(roofVector); } - public static CarOrientation fromPlayerInfo(final GameData.PlayerInfo playerInfo) { + public static CarOrientation fromFlatbuffer(PlayerInfo playerInfo) { return convert( - playerInfo.getRotation().getPitch(), - playerInfo.getRotation().getYaw(), - playerInfo.getRotation().getRoll()); + playerInfo.physics().rotation().pitch(), + playerInfo.physics().rotation().yaw(), + playerInfo.physics().rotation().roll()); } /** diff --git a/src/main/java/rlbot/ControlsOutput.java b/src/main/java/rlbotexample/output/ControlsOutput.java similarity index 59% rename from src/main/java/rlbot/ControlsOutput.java rename to src/main/java/rlbotexample/output/ControlsOutput.java index 8cd599d..9cdbd2b 100644 --- a/src/main/java/rlbot/ControlsOutput.java +++ b/src/main/java/rlbotexample/output/ControlsOutput.java @@ -1,8 +1,14 @@ -package rlbot; +package rlbotexample.output; -import rlbot.api.GameData; +import rlbot.ControllerState; -public class ControlsOutput { +/** + * A helper class for returning controls for your bot. + * + * 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 ControlsOutput implements ControllerState { // 0 is straight, -1 is hard left, 1 is hard right. private float steer; @@ -22,6 +28,7 @@ public class ControlsOutput { private boolean jumpDepressed; private boolean boostDepressed; private boolean slideDepressed; + private boolean useItemDepressed; public ControlsOutput() { } @@ -66,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; @@ -81,20 +93,57 @@ 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)); } - GameData.ControllerState toControllerState() { - return GameData.ControllerState.newBuilder() - .setThrottle(throttle) - .setSteer(steer) - .setPitch(pitch) - .setYaw(yaw) - .setRoll(roll) - .setBoost(boostDepressed) - .setHandbrake(slideDepressed) - .setJump(jumpDepressed) - .build(); + @Override + public float getSteer() { + return steer; + } + + @Override + public float getThrottle() { + return throttle; + } + + @Override + public float getPitch() { + return pitch; + } + + @Override + public float getYaw() { + return yaw; + } + + @Override + public float getRoll() { + return roll; + } + + @Override + public boolean holdJump() { + return jumpDepressed; + } + + @Override + public boolean holdBoost() { + return boostDepressed; + } + + @Override + 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 new file mode 100644 index 0000000..3664bdd --- /dev/null +++ b/src/main/java/rlbotexample/util/PortReader.java @@ -0,0 +1,23 @@ +package rlbotexample.util; + +import java.util.Optional; + +/** + * 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 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/rlbot/vector/Vector2.java b/src/main/java/rlbotexample/vector/Vector2.java similarity index 78% rename from src/main/java/rlbot/vector/Vector2.java rename to src/main/java/rlbotexample/vector/Vector2.java index 9bd2a8d..dad1633 100644 --- a/src/main/java/rlbot/vector/Vector2.java +++ b/src/main/java/rlbotexample/vector/Vector2.java @@ -1,5 +1,11 @@ -package rlbot.vector; - +package rlbotexample.vector; + +/** + * A vector that only knows about x and y components. + * + * 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 Vector2 { public final double x; @@ -39,6 +45,9 @@ public double distance(Vector2 other) { return Math.sqrt(xDiff * xDiff + yDiff * yDiff); } + /** + * This is the length of the vector. + */ public double magnitude() { return Math.sqrt(magnitudeSquared()); } @@ -63,6 +72,10 @@ public boolean isZero() { return x == 0 && y == 0; } + /** + * The correction angle is how many radians you need to rotate this vector to make it line up with the "ideal" + * vector. This is very useful for deciding which direction to steer. + */ public double correctionAngle(Vector2 ideal) { double currentRad = Math.atan2(y, x); double idealRad = Math.atan2(ideal.y, ideal.x); @@ -85,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/rlbot/vector/Vector3.java b/src/main/java/rlbotexample/vector/Vector3.java similarity index 73% rename from src/main/java/rlbot/vector/Vector3.java rename to src/main/java/rlbotexample/vector/Vector3.java index fe455e8..506ab21 100644 --- a/src/main/java/rlbot/vector/Vector3.java +++ b/src/main/java/rlbotexample/vector/Vector3.java @@ -1,26 +1,31 @@ -package rlbot.vector; +package rlbotexample.vector; -import rlbot.api.GameData; +import com.google.flatbuffers.FlatBufferBuilder; -public class Vector3 { - - public final double x; - public final double y; - public final double z; +/** + * A simple 3d vector class with the most essential operations. + * + * 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 Vector3 extends rlbot.vector.Vector3 { public Vector3(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; + super((float) x, (float) y, (float) z); + } + + public Vector3() { + this(0, 0, 0); } - public static Vector3 fromProto(GameData.Vector3 vec) { + public Vector3(rlbot.flat.Vector3 vec) { // Invert the X value so that the axes make more sense. - return new Vector3(-vec.getX(), vec.getY(), vec.getZ()); + this(-vec.x(), vec.y(), vec.z()); } - public Vector3() { - this(0, 0, 0); + public int toFlatbuffer(FlatBufferBuilder builder) { + // Invert the X value again so that rlbot sees the format it expects. + return rlbot.flat.Vector3.createVector3(builder, -x, y, z); } public Vector3 plus(Vector3 other) { @@ -94,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 new file mode 100644 index 0000000..6ff3059 --- /dev/null +++ b/src/main/python/README_Tournament.md @@ -0,0 +1,19 @@ +These instructions are intended for a tournament organizer. In theory, you're reading this because +you've just extracted a zip file submitted to a tournament. If that's not your situation, go look at README.md +instead. + +1. Install Java 8 or newer. +2. Reference the included `.cfg` file as you would do with a plain python bot. +3. Before running the match, double click on the `.bat` file included in +this submission. It should stay open and manage the java bot. + + +You only need to run a single instance of the `.bat`, and it can handle all the bots in this zip +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 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.cfg b/src/main/python/javaExample.cfg new file mode 100644 index 0000000..d85a9f3 --- /dev/null +++ b/src/main/python/javaExample.cfg @@ -0,0 +1,30 @@ +[Locations] +# Path to loadout config from runner +looks_config = javaExampleAppearance.cfg + +# Path to module from runner +python_file = javaExample.py + +# Name that will be displayed in game +name = MyJavaExample + +[Bot Parameters] +java_executable_path = ../RLBotJavaExample/bin/RLBotJavaExample.bat + +[Details] +# These values are optional but useful metadata for helper programs +# Name of the bot's creator/developer +developer = The RLBot community + +# Short description of the bot +description = This is a multi-line description + of the official java example bot + +# Fun fact about the bot +fun_fact = + +# Link to github repository +github = https://github.com/RLBot/RLBotJavaExample + +# Programming language +language = java \ No newline at end of file diff --git a/src/main/python/javaExample.py b/src/main/python/javaExample.py new file mode 100644 index 0000000..faf3765 --- /dev/null +++ b/src/main/python/javaExample.py @@ -0,0 +1,18 @@ +from rlbot.agents.base_agent import BOT_CONFIG_AGENT_HEADER +from rlbot.agents.executable_with_socket_agent import ExecutableWithSocketAgent +from rlbot.parsing.custom_config import ConfigHeader, ConfigObject + + +class JavaExample(ExecutableWithSocketAgent): + def get_port(self) -> int: + return 17357 + + def load_config(self, config_header: ConfigHeader): + 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): + params = config.get_header(BOT_CONFIG_AGENT_HEADER) + params.add_value('java_executable_path', str, default=None, + description='Relative path to the executable that runs java.') diff --git a/src/main/python/javaExampleAppearance.cfg b/src/main/python/javaExampleAppearance.cfg new file mode 100644 index 0000000..468bbe3 --- /dev/null +++ b/src/main/python/javaExampleAppearance.cfg @@ -0,0 +1,49 @@ +[Bot Loadout] +team_color_id = 60 +custom_color_id = 0 +car_id = 23 +decal_id = 0 +wheels_id = 1565 +boost_id = 35 +antenna_id = 0 +hat_id = 0 +paint_finish_id = 1681 +custom_finish_id = 1681 +engine_audio_id = 0 +trails_id = 3220 +goal_explosion_id = 3018 + +[Bot Loadout Orange] +team_color_id = 3 +custom_color_id = 0 +car_id = 23 +decal_id = 0 +wheels_id = 1565 +boost_id = 35 +antenna_id = 0 +hat_id = 0 +paint_finish_id = 1681 +custom_finish_id = 1681 +engine_audio_id = 0 +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/protoBotJava.cfg b/src/main/python/protoBotJava.cfg deleted file mode 100644 index 66cd98c..0000000 --- a/src/main/python/protoBotJava.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[Locations] -# Path to loadout config from runner -looks_config = ./agents/protoBotJava/protoBotJavaAppearance.cfg - -# Path to module from runner -agent_module = agents.protoBotJava.protoBotJava diff --git a/src/main/python/protoBotJava.py b/src/main/python/protoBotJava.py deleted file mode 100644 index a52e512..0000000 --- a/src/main/python/protoBotJava.py +++ /dev/null @@ -1,52 +0,0 @@ -import os - -from py4j.java_gateway import GatewayParameters -from py4j.java_gateway import JavaGateway - -from RLBotFramework.agents.base_independent_agent import BaseIndependentAgent -from RLBotFramework.utils.logging_utils import get_logger - - -class ProtoJava(BaseIndependentAgent): - - def __init__(self, name, team, index): - super().__init__(name, team, index) - self.gateway = None - self.javaAgent = None - self.logger = get_logger('protoBotJava' + str(self.index)) - self.port = self.read_port_from_file() - - def read_port_from_file(self): - try: - # Look for a port.cfg file in the same directory as THIS python file. - location = os.path.realpath( - os.path.join(os.getcwd(), os.path.dirname(__file__))) - - with open(os.path.join(location, "port.cfg"), "r") as portFile: - return int(portFile.readline().rstrip()) - - except ValueError: - self.logger.warn("Failed to parse port file!") - raise - - def run_independently(self): - self.init_py4j_stuff() - self.javaAgent.registerBot(self.index, self.name) - self.javaAgent.startup() - print() - - def get_extra_pids(self): - """ - Gets the list of process ids that should be marked as high priority. - :return: A list of process ids that are used by this bot in addition to the ones inside the python process. - """ - return [] - - def retire(self): - self.javaAgent.shutdown() - - def init_py4j_stuff(self): - self.logger.info("Connecting to Java Gateway on port " + str(self.port)) - self.gateway = JavaGateway(gateway_parameters=GatewayParameters(auto_convert=True, port=self.port)) - self.javaAgent = self.gateway.entry_point - self.logger.info("Connection to Java successful!") diff --git a/src/main/python/protoBotJavaAppearance.cfg b/src/main/python/protoBotJavaAppearance.cfg deleted file mode 100644 index eede139..0000000 --- a/src/main/python/protoBotJavaAppearance.cfg +++ /dev/null @@ -1,33 +0,0 @@ -[Bot Loadout] -# Name that will be displayed in game -name = ProtoBotJava -team_color_id = 27 -custom_color_id = 75 -car_id = 23 -decal_id = 307 -wheels_id = 1656 -boost_id = 0 -antenna_id = 287 -hat_id = 0 -paint_finish_1_id = 1978 -paint_finish_2_id = 1978 -engine_audio_id = 0 -trails_id = 0 -goal_explosion_id = 1971 - -[Bot Loadout Orange] -# Name that will be displayed in game -name = ProtoBotJava -team_color_id = 1 -custom_color_id = 1 -car_id = 23 -decal_id = 0 -wheels_id = 818 -boost_id = 0 -antenna_id = 287 -hat_id = 0 -paint_finish_1_id = 266 -paint_finish_2_id = 266 -engine_audio_id = 0 -trails_id = 0 -goal_explosion_id = 1971 \ No newline at end of file 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 diff --git a/src/main/resources/port.cfg b/src/main/resources/port.cfg deleted file mode 100644 index 1256cfb..0000000 Binary files a/src/main/resources/port.cfg and /dev/null differ