diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c363e1d..43671da5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,10 +15,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' # Step that caches and restores maven dependencies - name: Cache maven dependencies @@ -32,3 +32,7 @@ jobs: run: sudo apt-get install -y genisoimage - name: Build with Maven run: mvn -B install --file pom.xml + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + tags: philippvk/minigolf:latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43af4dd6..4737b92b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,10 +18,10 @@ jobs: uses: metcalfc/changelog-generator@v0.4.3 with: myToken: ${{ secrets.GITHUB_TOKEN }} - - name: Set up JDK 17 - uses: actions/setup-java@v3 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' # Step that caches and restores maven dependencies - name: Cache maven dependencies diff --git a/Dockerfile b/Dockerfile index bf1f8521..5d067365 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,15 @@ # # Build stage # -FROM maven:3.6.0-jdk-8-slim AS build +FROM maven:3.9.7-eclipse-temurin-21 AS build COPY . /home/app/ RUN mvn -f /home/app/pom.xml -pl server -am clean package # # Package stage # -FROM openjdk:8-alpine +FROM eclipse-temurin:21-alpine COPY --from=build /home/app/server/target/server-*.jar /home/minigolf/server.jar -COPY tracks/ /home/minigolf/tracks/ EXPOSE 4242 WORKDIR /home/minigolf ENTRYPOINT ["java","-jar","server.jar"] \ No newline at end of file diff --git a/README.md b/README.md index 28c0fba2..6e32f15e 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The Java Applet-based Minigolf Client was one of the most popular multiplayer ga ### Prerequisites - Clone this repo: `git clone git@github.com:PhilippvK/playforia-minigolf.git` -- Install Java Development Kit 17 (https://adoptium.net/en-GB/temurin/releases/) +- Install Java Development Kit 21 (https://adoptium.net/en-GB/temurin/releases/) - Install Apache `maven` for building: https://maven.apache.org/install.html - *Optional:* Install IntelliJ IDEA Java IDE (https://www.jetbrains.com/idea/download/) and import this repository as project @@ -62,7 +62,13 @@ java -jar client.jar -server 192.168.1.7 -lang en_US # Replace IP with the one o #### Running Minigolf Server in Docker Container -We provide an experimental Dockerfile for easy hosting of the server application. You can either build the image by yourself or download the pre-build images from [quay.io](https://quay.io/repository/philippvk/minigolf) via `docker pull quay.io/philippvk/minigolf:latest`. +We provide an experimental Dockerfile for easy hosting of the server application. +You can either build and run the image: +```sh +docker build -t pfmg . +docker run pfmg +``` +or download the pre-built images from [quay.io](https://quay.io/repository/philippvk/minigolf) via `docker pull quay.io/philippvk/minigolf:latest`. Running the Editor is quite straightforward as it can be started like expected: `java -jar editor.jar` @@ -83,7 +89,7 @@ Client CLI options: ## Compatibility Tested: -- Ubuntu 22.04 with Java version `17.0.6` +- Ubuntu 22.04 with Java version `21.0.3` - Windows 10/11 ## Problems diff --git a/client/pom.xml b/client/pom.xml index 82ac99c6..20887f87 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -15,8 +15,8 @@ org.moparforia.client.Launcher Playforia Minigolf Client ${project.build.directory}/downloads - https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.7%2B7/OpenJDK17U-jre_x64_mac_hotspot_17.0.7_7.tar.gz - jdk-17.0.7+7-jre + https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jre_aarch64_mac_hotspot_21.0.2_13.tar.gz + jdk-21.0.2+13-jre @@ -84,7 +84,7 @@ com.googlecode.maven-download-plugin download-maven-plugin - 1.6.8 + 1.9.0 download-osx-jre @@ -104,7 +104,6 @@ sh.tak.appbundler appbundle-maven-plugin - 1.2.0 ${project.name} src/main/resources/icons/playforia.icns @@ -133,7 +132,6 @@ com.akathist.maven.plugins.launch4j launch4j-maven-plugin - 2.3.3 windows-build @@ -154,7 +152,7 @@ src/main/resources/icons/playforia.ico jre - 17 + 21 1.0.0.0 @@ -174,7 +172,7 @@ plantuml-generator-maven-plugin de.elnarion.maven - 1.1.2 + 2.4.1 generate-simple-diagram diff --git a/client/src/test/java/org/moparforia/client/LauncherCLITest.java b/client/src/test/java/org/moparforia/client/LauncherCLITest.java index c0d7e069..13434423 100644 --- a/client/src/test/java/org/moparforia/client/LauncherCLITest.java +++ b/client/src/test/java/org/moparforia/client/LauncherCLITest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.quality.Strictness; import picocli.CommandLine; import javax.swing.JFrame; @@ -42,7 +43,7 @@ class LauncherCLITest { void setUp() throws Exception { // Mock game launcher = mock(Launcher.class, withSettings() - .lenient() + .strictness(Strictness.LENIENT) .withoutAnnotations()); // Use real methods diff --git a/pom.xml b/pom.xml index 0f4b3fc3..63d497e7 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,11 @@ pom 2.1.2.0-BETA Playforia Minigolf + + Playforia + + 2002 + An online multiplayer minigolf game server @@ -18,7 +23,7 @@ UTF-8 test.mainClass - 4.5.2 + 4.7.6 @@ -26,16 +31,16 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.13.0 - 17 - 17 + 21 + 21 org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.5.3 package @@ -55,6 +60,7 @@ + false @@ -67,16 +73,20 @@ com.akathist.maven.plugins.launch4j launch4j-maven-plugin - 1.7.25 + 2.5.0 org.codehaus.mojo exec-maven-plugin - 3.0.0 + 3.3.0 + org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.2.5 + + -XX:+EnableDynamicAgentLoading + @@ -105,37 +115,31 @@ org.junit.jupiter junit-jupiter-engine - 5.6.2 + 5.10.2 test org.mockito mockito-core - 3.5.10 + 5.12.0 test org.mockito mockito-junit-jupiter - 3.5.10 + 5.12.0 test com.github.marschall memoryfilesystem - 2.1.0 - test - - - org.softsmithy.lib - softsmithy-lib-core - 2.1.1 + 2.8.0 test de.elnarion.util plantuml-generator-util - 1.1.2 + 2.4.1 diff --git a/server/pom.xml b/server/pom.xml index 2b543bdc..c8678bbd 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -81,7 +81,7 @@ plantuml-generator-maven-plugin de.elnarion.maven - 1.1.2 + 2.4.1 generate-simple-diagram diff --git a/server/src/main/java/org/moparforia/server/Server.java b/server/src/main/java/org/moparforia/server/Server.java index 8659aeb8..1fe8a4f7 100644 --- a/server/src/main/java/org/moparforia/server/Server.java +++ b/server/src/main/java/org/moparforia/server/Server.java @@ -50,6 +50,9 @@ public class Server implements Runnable { private int port; private Optional tracksDirectory; + private Channel serverChannel; + private boolean running; + private HashMap lobbies = new HashMap(); //private ArrayList lobbies = new ArrayList(); //private HashMap games = new HashMap(); @@ -188,17 +191,23 @@ public void start() { bootstrap.setOption("child.tcpNoDelay", true); bootstrap.setOption("child.keepAlive", true); try { - bootstrap.bind(new InetSocketAddress(host, port)); + this.serverChannel = bootstrap.bind(new InetSocketAddress(host, port)); + this.running = true; new Thread(this).start(); } catch (Exception ex) { ex.printStackTrace(); } } + public void stop() throws InterruptedException { + this.serverChannel.close().sync(); + this.running = false; + } + @Override public void run() { System.out.println("Started server on host " + this.host + " with port " + this.port); - while (true) { + while (this.running) { try { Thread.sleep(10); Iterator iterator = events.iterator(); diff --git a/server/src/main/java/org/moparforia/server/event/ClientDisconnectedEvent.java b/server/src/main/java/org/moparforia/server/event/ClientDisconnectedEvent.java index 09852fd8..6c24f985 100644 --- a/server/src/main/java/org/moparforia/server/event/ClientDisconnectedEvent.java +++ b/server/src/main/java/org/moparforia/server/event/ClientDisconnectedEvent.java @@ -18,7 +18,7 @@ public void process(Server server) { Player player; if ((player = (Player)channel.getAttachment()) != null) { if (player.getLobby() != null) { - player.getLobby().removePlayer(player, Lobby.PART_REASON_USERLEFT,null); + player.getLobby().removePlayer(player, Lobby.PART_REASON_USERLEFT); } } System.out.println("Client disconnected: " + channel); diff --git a/server/src/main/java/org/moparforia/server/net/packethandlers/QuitHandler.java b/server/src/main/java/org/moparforia/server/net/packethandlers/QuitHandler.java index 815b72e3..454bfa90 100644 --- a/server/src/main/java/org/moparforia/server/net/packethandlers/QuitHandler.java +++ b/server/src/main/java/org/moparforia/server/net/packethandlers/QuitHandler.java @@ -28,7 +28,7 @@ public Pattern getPattern() { public boolean handle(Server server, Packet packet, Matcher message) { Player player = (Player) packet.getChannel().getAttachment(); if (message.group(1).contains("lobby")) { - player.getLobby().removePlayer(player, Lobby.PART_REASON_USERLEFT, null); + player.getLobby().removePlayer(player, Lobby.PART_REASON_USERLEFT); } packet.getChannel().disconnect(); packet.getChannel().close(); diff --git a/server/src/test/java/org/moparforia/server/ServerTest.java b/server/src/test/java/org/moparforia/server/ServerTest.java new file mode 100644 index 00000000..65dbe18e --- /dev/null +++ b/server/src/test/java/org/moparforia/server/ServerTest.java @@ -0,0 +1,117 @@ +package org.moparforia.server; + +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ServerTest { + + private void sendMessage(BufferedWriter writer, String message) throws IOException { + writer.write(message); + writer.newLine(); + writer.flush(); + } + + @Test + void testSinglePlayerFlow() throws IOException, InterruptedException { + Server server = new Server("127.0.0.1", 4243, Optional.empty()); + server.start(); + + Socket socket = new Socket("127.0.0.1", 4243); + InputStream in = socket.getInputStream(); + OutputStream out = socket.getOutputStream(); + InputStreamReader r = new InputStreamReader(in); + OutputStreamWriter w = new OutputStreamWriter(out); + BufferedReader reader = new BufferedReader(r); + BufferedWriter writer = new BufferedWriter(w); + + // init + String h = reader.readLine(); + String cIo = reader.readLine(); + String cCrt = reader.readLine(); + String cCtr = reader.readLine(); + assertEquals("h 1", h); + assertTrue(cIo.matches("c io \\d+")); + assertEquals("c crt 250", cCrt); + assertEquals("c ctr", cCtr); + + String nickname = "nick"; + + // client id + this.sendMessage(writer, "c new"); + String cId = reader.readLine(); + assertEquals("c id 0", cId); + + // version + this.sendMessage(writer, "d 0 version\t35"); + String statusLogin = reader.readLine(); + assertEquals("d 0 status\tlogin", statusLogin); + + // login + this.sendMessage(writer, "d 1 ttlogin\t" + nickname + "\t"); + String basicInfo = reader.readLine(); + String statusLobbyselect = reader.readLine(); + assertEquals("d 1 basicinfo\tt\t0\tt\tf", basicInfo); + assertEquals("d 2 status\tlobbyselect\t300", statusLobbyselect); + + // number of players + this.sendMessage(writer, "d 2 lobbyselect\trnop"); + String lobbySelectNop = reader.readLine(); + assertEquals("d 3 lobbyselect\tnop\t0\t0\t0", lobbySelectNop); + + // select single player + this.sendMessage(writer, "d 3 lobbyselect\tselect\t1"); + String statusLobby = reader.readLine(); + String lobbyUsers = reader.readLine(); + String lobbyOwnjoin = reader.readLine(); + assertEquals("d 4 status\tlobby\t1", statusLobby); + assertEquals("d 5 lobby\tusers", lobbyUsers); + assertEquals("d 6 lobby\townjoin\t3:" + nickname + "^w^10000^-^-^-", lobbyOwnjoin); + this.sendMessage(writer, "d 4 lobby\ttracksetlist"); + String tracksetlist = reader.readLine(); + assertEquals(tracksetlist, "d 7 lobby\ttracksetlist\tBirchwood\t1\t9\tNo one\t1\tNo one\t1\tNo one\t1\tNo one\t1\tOak Park\t1\t18\tNo one\t1\tNo one\t1\tNo one\t1\tNo one\t1\tOne by One\t2\t18\tNo one\t1\tNo one\t1\tNo one\t1\tNo one\t1\tScary Set\t3\t9\tNo one\t1\tNo one\t1\tNo one\t1\tNo one\t1\tSpruce Corpse\t2\t9\tNo one\t1\tNo one\t1\tNo one\t1\tNo one\t1\tThe First\t2\t18\tNo one\t1\tNo one\t1\tNo one\t1\tNo one\t1\tTorment Fields\t3\t18\tNo one\t1\tNo one\t1\tNo one\t1\tNo one\t1"); + + // start championship game + this.sendMessage(writer, "d 5 lobby\tcspc\t6"); + String statusGame = reader.readLine(); + String gameInfo = reader.readLine(); + String players = reader.readLine(); + String ownInfo = reader.readLine(); + String gameStart = reader.readLine(); + String resetVoteSkip = reader.readLine(); + String startTrack = reader.readLine(); + String startTurn = reader.readLine(); + assertEquals("d 8 status\tgame", statusGame); + assertEquals("d 9 game\tgameinfo\tderp\tf\t0\t1\t18\t0\t0\t0\t0\t1\t0\t0\tf", gameInfo); + assertEquals("d 10 game\tplayers", players); + assertEquals("d 11 game\towninfo\t0\t" + nickname + "\t-", ownInfo); + assertEquals("d 12 game\tstart", gameStart); + assertEquals("d 13 game\tresetvoteskip", resetVoteSkip); + assertTrue(startTrack.matches("d 14 game\tstarttrack\tt\t0\tV 1\tA Leonardo\tN Revocations\tT BAMM21DBAQQ7DBAMM18DBAQQDB3ADDBAQQ6DEDDBAQQ7DB3A11DBHAQBGAQB3ABAQQ4DB3ADDBHAMEDEB3A10DEDDB3A3DBHAQBGAQB3AEE14DBAGABAIADDBAKAE3DEDEE10DEDDE6DEE6DBAQQE6DEEDDEE3DEDEE10DEDDE4DBIAHBAIAEE6DEE6DEEDDEE3DEDEE10DEDDE4DBAGA3EDBGMABHMAEDDEE6DEEDDEE3DEDEE10DEDDE4D4EDBHAMBGAMEDDEE6DEEDDEE3DEDEE10DEDDE4D4E6DEE6DEEDDEE3DEDEE9DBGLABAEADDBHFAE3DBAQQ3E6DEE6DEEDDEE3DEDEE9DBAKAGDDBAGAE3D4E6DEEDBAQQ6DEG7DBAMMEDBEAQBFAQE5DEEDDEE3D4E6DEE5DEDDEE7DEEDBHAQBAQQDE4DEEDDEE3DEBAGADE6DEE5DEG9D3EDDEBGAQE4DEEDDEE3DEDDE6DEE5DEE9DEBAQQE9DEEDDEE5DEE6DEE5DBGAQE9D3E9DEEDDEE5DEE4DBAQQDDE16D3E5DBAQQ4DEDDBAQQ7DE4DEBAGADE5DBEAQBAQQDDBGAQEDDBGMABAMMDDEBFAQE4DEBAMM3DEDDBAMA7DE4DEBAEAGE5DBAQQBGAQH4DBGMABAMM3DEDFE3DEE3DEDDE7DE4D4E5DEG5DBHAMEBGAMB3ABAQQEDDFEDDEEBIMAB3A17D4E5DEE6DBSAMGDEE3DFEDEEB3A18DBAGA3E5DEE9DEEBIQAB3A4DEEBLMAE17D4EDDBQAMEDEE9DEEB3ACBAE3DEE3DEDDBAMA7DE4D4EDBEAMBAMMBFAM3E7DCAA3EBLQAF4DEE3DEDDE7DEDBEAQBFAQ5EBEAMBAMMDBEMA3E9DEE11DEDDBAQQ15DBAMMDDBEMAB3ADE11DBAMM10DE4DBAMM32D,Ads:C0204\tI 308939,4480252,4,2203\tB d2b,\\d+\tR 633,173,118,129,125,546,495,461,467,556,4472")); + assertEquals("d 15 game\tstartturn\t0", startTurn); + + // leave game + this.sendMessage(writer, "d 6 game\tback"); + statusLobby = reader.readLine(); + lobbyUsers = reader.readLine(); + lobbyOwnjoin = reader.readLine(); + assertEquals("d 16 status\tlobby\t1", statusLobby); + assertEquals("d 17 lobby\tusers", lobbyUsers); + assertEquals("d 18 lobby\townjoin\t3:" + nickname + "^w^10000^-^-^-", lobbyOwnjoin); + + // quit game + this.sendMessage(writer, "d 7 lobby\tquit"); + server.stop(); + } +} diff --git a/shared/pom.xml b/shared/pom.xml index dca5fc52..6692a2b0 100644 --- a/shared/pom.xml +++ b/shared/pom.xml @@ -38,11 +38,6 @@ memoryfilesystem test - - org.softsmithy.lib - softsmithy-lib-core - test - @@ -53,7 +48,7 @@ plantuml-generator-maven-plugin de.elnarion.maven - 1.1.2 + 2.4.1 generate-simple-diagram diff --git a/shared/src/main/java/org/moparforia/shared/tracks/filesystem/FileSystemTrackManager.java b/shared/src/main/java/org/moparforia/shared/tracks/filesystem/FileSystemTrackManager.java index 00030f3a..c0c3c12f 100644 --- a/shared/src/main/java/org/moparforia/shared/tracks/filesystem/FileSystemTrackManager.java +++ b/shared/src/main/java/org/moparforia/shared/tracks/filesystem/FileSystemTrackManager.java @@ -95,7 +95,7 @@ private List loadTrackSets(TracksLocation tracksLocation) throws IOExc Scanner scanner = new Scanner(filePath); String setName = scanner.nextLine(); TrackSetDifficulty trackSetDifficulty = TrackSetDifficulty.valueOf(scanner.nextLine()); - List trackNames = new ArrayList(); + List trackNames = new ArrayList<>(); while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); if (!line.isEmpty()) { @@ -106,8 +106,9 @@ private List loadTrackSets(TracksLocation tracksLocation) throws IOExc // Convert fileNames to already loaded tracks List tracks = getTracks().stream() .filter(track -> trackNames.contains(track.getName())) + .sorted(Comparator.comparing((track) -> trackNames.indexOf(track.getName()))) .collect(Collectors.toList()); - if (tracks.size() < 1) { + if (tracks.isEmpty()) { logger.warning("TrackSet " + setName + " has no valid tracks associated with it, ignoring"); continue; } @@ -119,6 +120,7 @@ private List loadTrackSets(TracksLocation tracksLocation) throws IOExc } trackSets.add(new TrackSet(setName, trackSetDifficulty, tracks)); } + trackSets.sort(Comparator.comparing(TrackSet::getName)); return trackSets; } diff --git a/shared/src/test/java/org/moparforia/shared/tracks/util/FileSystemExtension.java b/shared/src/test/java/org/moparforia/shared/tracks/util/FileSystemExtension.java index 65eef3dd..cc204f9c 100644 --- a/shared/src/test/java/org/moparforia/shared/tracks/util/FileSystemExtension.java +++ b/shared/src/test/java/org/moparforia/shared/tracks/util/FileSystemExtension.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import org.softsmithy.lib.nio.file.CopyFileVisitor; import java.io.IOException; import java.net.URISyntaxException; @@ -43,7 +42,7 @@ public FileSystem getFileSystem() { public void copyFile(String src) throws IOException, URISyntaxException { Path srcPath = getRootDir().resolve(src); Path targetPath = fileSystem.getPath(src); - CopyFileVisitor.copy(srcPath, targetPath); + Files.copy(srcPath, targetPath); } /** @@ -58,7 +57,7 @@ public void copyDir(String dir) throws IOException, URISyntaxException { .collect(Collectors.toList()); for (Path file : files) { Path relative_path = fileSystem.getPath(base.relativize(file).toString().replace(FileSystems.getDefault().getSeparator(), fileSystem.getSeparator())); - CopyFileVisitor.copy(base.resolve(file), relative_path); + Files.copy(base.resolve(file), relative_path); } }