diff --git a/extension/deployment/pom.xml b/extension/deployment/pom.xml
index 8b3c8df..e2cf01c 100644
--- a/extension/deployment/pom.xml
+++ b/extension/deployment/pom.xml
@@ -1,44 +1,48 @@
- 4.0.0
-
- org.acme
- minecrafter-parent
- 1.0.0-SNAPSHOT
-
- minecrafter-deployment
- Minecrafter - Deployment
-
-
- io.quarkus
- quarkus-rest-client-reactive-jackson-deployment
-
-
- org.acme
- minecrafter
- ${project.version}
-
-
- io.quarkus
- quarkus-junit5-internal
- test
-
-
-
-
-
- maven-compiler-plugin
-
-
-
- io.quarkus
- quarkus-extension-processor
- ${quarkus.version}
-
-
-
-
-
-
+ 4.0.0
+
+ org.acme
+ minecrafter-parent
+ 1.0.0-SNAPSHOT
+
+ minecrafter-deployment
+ Minecrafter - Deployment
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-jackson-deployment
+
+
+ io.quarkus
+ quarkus-devservices-common
+
+
+ org.acme
+ minecrafter
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.version}
+
+
+
+
+
+
diff --git a/extension/deployment/src/main/java/org/acme/minecrafter/deployment/MinecraftContainer.java b/extension/deployment/src/main/java/org/acme/minecrafter/deployment/MinecraftContainer.java
new file mode 100644
index 0000000..8355106
--- /dev/null
+++ b/extension/deployment/src/main/java/org/acme/minecrafter/deployment/MinecraftContainer.java
@@ -0,0 +1,43 @@
+package org.acme.minecrafter.deployment;
+
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MinecraftContainer extends GenericContainer {
+ private static final int MINECRAFT_PORT = 25565;
+ static final int OBSERVABILITY_PORT = 8081;
+
+ public MinecraftContainer(DockerImageName image) {
+ super(image);
+
+ List portBindings = new ArrayList<>();
+ portBindings.add("25565:25565"); // Make life easy for the minecraft client
+ setPortBindings(portBindings);
+ //withReuse(true);
+
+ // withExposedPorts(MINECRAFT_PORT);
+ // This is a bit of a cheat, since at this point the client isn't ready, but otherwise it's too slow
+ waitingFor(Wait.forLogMessage(".*" + "Preparing" + ".*", 1));
+ }
+
+
+ @Override
+ protected void configure() {
+ withNetwork(Network.SHARED);
+ addExposedPorts(OBSERVABILITY_PORT);
+ addExposedPorts(MINECRAFT_PORT);
+ }
+
+ public Integer getApiPort() {
+ return this.getMappedPort(OBSERVABILITY_PORT);
+ }
+
+ public Integer getGamePort() {
+ return this.getMappedPort(MINECRAFT_PORT);
+ }
+}
\ No newline at end of file
diff --git a/extension/deployment/src/main/java/org/acme/minecrafter/deployment/MinecrafterProcessor.java b/extension/deployment/src/main/java/org/acme/minecrafter/deployment/MinecrafterProcessor.java
index ff68f8f..44c8a96 100644
--- a/extension/deployment/src/main/java/org/acme/minecrafter/deployment/MinecrafterProcessor.java
+++ b/extension/deployment/src/main/java/org/acme/minecrafter/deployment/MinecrafterProcessor.java
@@ -4,11 +4,15 @@
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.processor.AnnotationsTransformer;
+import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.DevServicesResultBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.LogHandlerBuildItem;
+import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem;
import org.acme.minecrafter.runtime.HelloRecorder;
import org.acme.minecrafter.runtime.MinecraftLog;
@@ -17,8 +21,10 @@
import org.acme.minecrafter.runtime.MinecraftService;
import org.acme.minecrafter.runtime.RestExceptionMapper;
import org.jboss.jandex.DotName;
+import org.testcontainers.utility.DockerImageName;
import javax.ws.rs.Priorities;
+import java.util.Map;
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
@@ -63,8 +69,12 @@ public boolean appliesTo(org.jboss.jandex.AnnotationTarget.Kind kind) {
}
public void transform(TransformationContext context) {
- if (context.getTarget().asMethod().hasAnnotation(JAX_RS_GET)) {
- context.transform().add(MinecraftLog.class).done();
+ if (context.getTarget()
+ .asMethod()
+ .hasAnnotation(JAX_RS_GET)) {
+ context.transform()
+ .add(MinecraftLog.class)
+ .done();
}
}
});
@@ -76,4 +86,27 @@ ExceptionMapperBuildItem exceptionMappers() {
Exception.class.getName(), Priorities.USER + 100, true);
}
+ @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class)
+ public DevServicesResultBuildItem createContainer(LaunchModeBuildItem launchMode) {
+ // Normally, this would be a remote image, but we need to build one with the right mods, so use a local one
+ DockerImageName dockerImageName = DockerImageName.parse("minecraft-server");
+
+ // Don't be tempted to put this in a try-with-resources block, even if the IDE advises it
+ // Otherwise the dev service gets shut down after startup :)
+ MinecraftContainer container = new MinecraftContainer(dockerImageName).withExposedPorts(8081, 25565);
+ container.start();
+
+ // Set a config property so that anything using the container can find it, even on the random port
+
+ Map props = Map.of("quarkus.minecrafter.base-url",
+ "http://" + container.getHost() + ":" + container.getApiPort());
+
+ System.out.println("API port: " + "http://" + container.getHost() + ":" + container.getApiPort());
+ System.out.println("Game port: " + "http://" + container.getHost() + ":" + container.getGamePort());
+
+ return new DevServicesResultBuildItem.RunningDevService(FEATURE, container.getContainerId(),
+ container::close, props)
+ .toBuildItem();
+ }
}
+
diff --git a/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecraftLogHandler.java b/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecraftLogHandler.java
index 0fec2cf..6939a7b 100644
--- a/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecraftLogHandler.java
+++ b/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecraftLogHandler.java
@@ -15,7 +15,8 @@ public void publish(LogRecord record) {
String formattedMessage = String.format(record.getMessage(), record.getParameters());
System.out.println("⛏️ [Minecrafter] " + formattedMessage);
- minecraft.log(formattedMessage);
+ // TODO this hangs if the minecraft server is a dev service; make it fire-and-forget
+ // minecraft.log(formattedMessage);
}
diff --git a/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecraftService.java b/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecraftService.java
index 4754327..aef54ce 100644
--- a/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecraftService.java
+++ b/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecraftService.java
@@ -1,21 +1,10 @@
package org.acme.minecrafter.runtime;
import javax.inject.Singleton;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.WebTarget;
-
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-import org.eclipse.microprofile.rest.client.RestClientBuilder;
-
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URI;
-import java.net.URL;
-import java.util.Set;
@Singleton
public class MinecraftService {
@@ -39,8 +28,10 @@ public void boom() {
public void log(String message) {
try {
- client.target(minecrafterConfig.baseURL).path("log")
- .request(MediaType.TEXT_PLAIN).post(Entity.text(message));
+ client.target(minecrafterConfig.baseURL)
+ .path("observability/log")
+ .request(MediaType.TEXT_PLAIN)
+ .post(Entity.text(message));
// Don't log anything back about the response or it ends up with too much circular logging
} catch (Throwable e) {
System.out.println("\uD83D\uDDE1️ [Minecrafter] Connection error: " + e);
@@ -49,9 +40,10 @@ public void log(String message) {
private void invokeMinecraft(String path) {
try {
- String response = client.target(minecrafterConfig.baseURL).path(path)
- .request(MediaType.TEXT_PLAIN)
- .get(String.class);
+ String response = client.target(minecrafterConfig.baseURL)
+ .path("observability/" + path)
+ .request(MediaType.TEXT_PLAIN)
+ .get(String.class);
System.out.println("\uD83D\uDDE1️ [Minecrafter] Mod response: " + response);
} catch (Throwable e) {
diff --git a/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecrafterConfig.java b/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecrafterConfig.java
index a92ec26..334adde 100644
--- a/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecrafterConfig.java
+++ b/extension/runtime/src/main/java/org/acme/minecrafter/runtime/MinecrafterConfig.java
@@ -10,6 +10,6 @@ public class MinecrafterConfig {
/**
* The minecraft server's observability base URL
*/
- @ConfigItem(defaultValue = "http://localhost:8081/observability/")
+ @ConfigItem(defaultValue = "http://localhost:8081/")
public String baseURL;
}
diff --git a/modded-minecraft/Dockerfile b/modded-minecraft/Dockerfile
index dfb313b..54bde32 100644
--- a/modded-minecraft/Dockerfile
+++ b/modded-minecraft/Dockerfile
@@ -10,6 +10,8 @@ FROM webhippie/minecraft-forge:43.0
# This is needed for the client launched with `./gradlew runClient` to be able to connect
ENV MINECRAFT_ONLINE_MODE=false
+# For performance reasons, keep the world small
+ENV MINECRAFT_MAX_WORLD_SIZE=299
RUN find / -name mods
EXPOSE 8081