initial
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
build/
|
||||||
|
.gradle/
|
||||||
|
gradle/
|
||||||
|
.gradle_cache/
|
||||||
33
build.gradle
Normal file
33
build.gradle
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
plugins {
|
||||||
|
id 'fabric-loom' version '1.7-SNAPSHOT'
|
||||||
|
id 'maven-publish'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com.example'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url = "https://maven.fabricmc.net/" }
|
||||||
|
maven { url = "https://maven.architectury.dev/" }
|
||||||
|
maven { url = "https://maven.minecraftforge.net/" }
|
||||||
|
maven { url = "https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/" }
|
||||||
|
maven { url = "https://maven.impactdev.net/repository/development/" }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
minecraft "com.mojang:minecraft:1.21.1"
|
||||||
|
mappings "net.fabricmc:yarn:1.21.1+build.3:v2"
|
||||||
|
modImplementation "net.fabricmc:fabric-loader:0.16.5"
|
||||||
|
|
||||||
|
modImplementation "net.fabricmc.fabric-api:fabric-api:0.106.1+1.21.1"
|
||||||
|
|
||||||
|
// Cobblemon
|
||||||
|
modImplementation "com.cobblemon:fabric:1.7.3+1.21.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
withSourcesJar()
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
24
build.sh
Executable file
24
build.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
IMAGE="gradle:8.7-jdk21"
|
||||||
|
|
||||||
|
echo "=== Building with Docker ($IMAGE) ==="
|
||||||
|
echo "Project dir: $(pwd)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
docker run --rm \
|
||||||
|
-v "$PWD":/project \
|
||||||
|
-v "$PWD/.gradle_cache:/home/gradle/.gradle" \
|
||||||
|
-w /project \
|
||||||
|
"$IMAGE" \
|
||||||
|
gradle build "$@"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Build complete ==="
|
||||||
|
ls -lh build/libs/*.jar 2>/dev/null || echo "No jars found in build/libs/"
|
||||||
|
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "put the build/libs/cobblemon-battle-api-1.0.0.jar in the mods folder of your minecraft instance to use it"
|
||||||
1
gradle.properties
Normal file
1
gradle.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2G
|
||||||
14
jar.sh
Executable file
14
jar.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell -i bash -p jdk17
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Usage: ./jar.sh <jar-args...>"
|
||||||
|
echo "Examples:"
|
||||||
|
echo " ./jar.sh tf some.jar # list contents"
|
||||||
|
echo " ./jar.sh tf some.jar | grep ClassName # find a class"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
jar "$@"
|
||||||
12
javap.sh
Executable file
12
javap.sh
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell -i bash -p jdk17
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Usage: ./javap.sh <class-name> <jar-file>"
|
||||||
|
echo "Example: ./javap.sh com.cobblemon.mod.common.battles.BattleRegistry some.jar"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
javap -classpath "$2" "$1"
|
||||||
8
settings.gradle
Normal file
8
settings.gradle
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven { url = "https://maven.fabricmc.net/" }
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "cobblemon-battle-api"
|
||||||
406
src/main/java/com/example/cobbleapi/CobbleBattleApiMod.java
Normal file
406
src/main/java/com/example/cobbleapi/CobbleBattleApiMod.java
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
package com.example.cobbleapi;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
|
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
|
||||||
|
import com.cobblemon.mod.common.api.battles.model.PokemonBattle;
|
||||||
|
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor;
|
||||||
|
import com.cobblemon.mod.common.battles.BattleRegistry;
|
||||||
|
import com.cobblemon.mod.common.battles.ActiveBattlePokemon;
|
||||||
|
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor;
|
||||||
|
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
|
||||||
|
import com.cobblemon.mod.common.api.storage.pc.PCStore;
|
||||||
|
import com.cobblemon.mod.common.Cobblemon;
|
||||||
|
import com.cobblemon.mod.common.pokemon.Pokemon;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fabric server-side mod that exposes Cobblemon battle and Pokémon data
|
||||||
|
* over a lightweight HTTP API.
|
||||||
|
*
|
||||||
|
* Endpoints:
|
||||||
|
* GET /player/<uuid> — is the player in a battle?
|
||||||
|
* GET /player/<uuid>/battle — full battle state for that player
|
||||||
|
* GET /player/<uuid>/party — player's party Pokémon
|
||||||
|
* GET /player/<uuid>/pc — player's PC Pokémon
|
||||||
|
* GET /battles — list all active battles
|
||||||
|
*/
|
||||||
|
public class CobbleBattleApiMod implements ModInitializer {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("CobbleBattleAPI");
|
||||||
|
private static final int PORT = 8085;
|
||||||
|
|
||||||
|
private volatile MinecraftServer server;
|
||||||
|
private HttpServer httpServer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
|
||||||
|
ServerLifecycleEvents.SERVER_STARTED.register(mc -> {
|
||||||
|
this.server = mc;
|
||||||
|
startHttpServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
ServerLifecycleEvents.SERVER_STOPPING.register(mc -> {
|
||||||
|
stopHttpServer();
|
||||||
|
this.server = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── HTTP server lifecycle ──────────────────────────────────────────
|
||||||
|
|
||||||
|
private void startHttpServer() {
|
||||||
|
try {
|
||||||
|
httpServer = HttpServer.create(new InetSocketAddress(PORT), 0);
|
||||||
|
httpServer.createContext("/player", this::handlePlayer);
|
||||||
|
httpServer.createContext("/battles", this::handleBattles);
|
||||||
|
httpServer.start();
|
||||||
|
LOGGER.info("CobbleBattleAPI listening on port {}", PORT);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Failed to start CobbleBattleAPI HTTP server", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopHttpServer() {
|
||||||
|
if (httpServer != null) {
|
||||||
|
httpServer.stop(0);
|
||||||
|
LOGGER.info("CobbleBattleAPI HTTP server stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── /player/<uuid>[/battle|/party|/pc] ─────────────────────────────
|
||||||
|
|
||||||
|
private void handlePlayer(HttpExchange exchange) throws IOException {
|
||||||
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
send(exchange, 405, json("error", "method not allowed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = exchange.getRequestURI().getPath(); // e.g. /player/<uuid>/battle
|
||||||
|
String[] parts = path.split("/"); // ["", "player", "<uuid>", ...]
|
||||||
|
|
||||||
|
if (parts.length < 3) {
|
||||||
|
send(exchange, 400, json("error", "missing uuid"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID uuid;
|
||||||
|
try {
|
||||||
|
uuid = UUID.fromString(parts[2]);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
send(exchange, 400, json("error", "invalid uuid"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to touch the MC server on the server thread.
|
||||||
|
String subRoute = parts.length >= 4 ? parts[3] : "";
|
||||||
|
String response = runOnServerThread(() -> dispatch(uuid, subRoute));
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
send(exchange, 500, json("error", "server unavailable"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(exchange, 200, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String dispatch(UUID uuid, String subRoute) {
|
||||||
|
ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid);
|
||||||
|
if (player == null) {
|
||||||
|
return json("error", "player not online");
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (subRoute) {
|
||||||
|
case "battle" -> buildBattleState(uuid, player);
|
||||||
|
case "party" -> buildParty(uuid);
|
||||||
|
case "pc" -> buildPc(uuid);
|
||||||
|
default -> buildBasicStatus(uuid, player);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── /battles ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void handleBattles(HttpExchange exchange) throws IOException {
|
||||||
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
send(exchange, 405, json("error", "method not allowed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String response = runOnServerThread(this::buildAllBattles);
|
||||||
|
if (response == null) {
|
||||||
|
send(exchange, 500, json("error", "server unavailable"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send(exchange, 200, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Response builders ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /player/<uuid> → { uuid, inBattle }
|
||||||
|
*/
|
||||||
|
private String buildBasicStatus(UUID uuid, ServerPlayerEntity player) {
|
||||||
|
boolean inBattle = findBattle(uuid) != null;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append('{');
|
||||||
|
jsonField(sb, "uuid", uuid.toString());
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "inBattle", String.valueOf(inBattle));
|
||||||
|
sb.append('}');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /player/<uuid>/battle → full battle snapshot
|
||||||
|
*/
|
||||||
|
private String buildBattleState(UUID uuid, ServerPlayerEntity player) {
|
||||||
|
PokemonBattle battle = findBattle(uuid);
|
||||||
|
if (battle == null) {
|
||||||
|
return json("error", "not in battle");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append('{');
|
||||||
|
jsonField(sb, "battleId", battle.getBattleId().toString());
|
||||||
|
sb.append(',');
|
||||||
|
|
||||||
|
// actors
|
||||||
|
sb.append("\"actors\":[");
|
||||||
|
boolean first = true;
|
||||||
|
for (BattleActor actor : battle.getActors()) {
|
||||||
|
if (!first) sb.append(',');
|
||||||
|
first = false;
|
||||||
|
sb.append('{');
|
||||||
|
jsonField(sb, "name", actor.getName().getString());
|
||||||
|
sb.append(',');
|
||||||
|
|
||||||
|
String type = (actor instanceof PlayerBattleActor) ? "player" : "npc";
|
||||||
|
jsonField(sb, "type", type);
|
||||||
|
sb.append(',');
|
||||||
|
|
||||||
|
// active pokémon
|
||||||
|
sb.append("\"activePokemon\":[");
|
||||||
|
boolean afirst = true;
|
||||||
|
for (ActiveBattlePokemon abp : actor.getActivePokemon()) {
|
||||||
|
if (abp.getBattlePokemon() == null) continue;
|
||||||
|
if (!afirst) sb.append(',');
|
||||||
|
afirst = false;
|
||||||
|
var bp = abp.getBattlePokemon();
|
||||||
|
sb.append('{');
|
||||||
|
jsonField(sb, "species", bp.getOriginalPokemon().getSpecies().getName());
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "level", String.valueOf(bp.getOriginalPokemon().getLevel()));
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "hp", String.valueOf(bp.getHealth()));
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "maxHp", String.valueOf(bp.getMaxHealth()));
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
sb.append('}');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /player/<uuid>/party → party pokémon list
|
||||||
|
*/
|
||||||
|
private String buildParty(UUID uuid) {
|
||||||
|
PlayerPartyStore party;
|
||||||
|
try {
|
||||||
|
party = Cobblemon.INSTANCE.getStorage().getParty(uuid);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return json("error", "could not load party");
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("{\"uuid\":\"").append(uuid).append("\",\"party\":[");
|
||||||
|
boolean first = true;
|
||||||
|
for (Pokemon p : party) {
|
||||||
|
if (!first) sb.append(',');
|
||||||
|
first = false;
|
||||||
|
appendPokemonJson(sb, p);
|
||||||
|
}
|
||||||
|
sb.append("]}");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /player/<uuid>/pc → PC pokémon list
|
||||||
|
*/
|
||||||
|
private String buildPc(UUID uuid) {
|
||||||
|
PCStore pc;
|
||||||
|
try {
|
||||||
|
pc = Cobblemon.INSTANCE.getStorage().getPC(uuid);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return json("error", "could not load pc");
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("{\"uuid\":\"").append(uuid).append("\",\"pc\":[");
|
||||||
|
boolean first = true;
|
||||||
|
for (Pokemon p : pc) {
|
||||||
|
if (!first) sb.append(',');
|
||||||
|
first = false;
|
||||||
|
appendPokemonJson(sb, p);
|
||||||
|
}
|
||||||
|
sb.append("]}");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /battles → all active battles
|
||||||
|
*/
|
||||||
|
private String buildAllBattles() {
|
||||||
|
// No getBattles() in this Cobblemon version — collect battles from online players
|
||||||
|
java.util.LinkedHashMap<UUID, PokemonBattle> seen = new java.util.LinkedHashMap<>();
|
||||||
|
for (ServerPlayerEntity p : server.getPlayerManager().getPlayerList()) {
|
||||||
|
PokemonBattle b = BattleRegistry.INSTANCE.getBattleByParticipatingPlayer(p);
|
||||||
|
if (b != null) {
|
||||||
|
seen.putIfAbsent(b.getBattleId(), b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("{\"battles\":[");
|
||||||
|
boolean first = true;
|
||||||
|
for (PokemonBattle battle : seen.values()) {
|
||||||
|
if (!first) sb.append(',');
|
||||||
|
first = false;
|
||||||
|
sb.append('{');
|
||||||
|
jsonField(sb, "battleId", battle.getBattleId().toString());
|
||||||
|
sb.append(',');
|
||||||
|
sb.append("\"actors\":[");
|
||||||
|
boolean afirst = true;
|
||||||
|
for (BattleActor actor : battle.getActors()) {
|
||||||
|
if (!afirst) sb.append(',');
|
||||||
|
afirst = false;
|
||||||
|
sb.append('{');
|
||||||
|
jsonField(sb, "name", actor.getName().getString());
|
||||||
|
sb.append(',');
|
||||||
|
String type = (actor instanceof PlayerBattleActor) ? "player" : "npc";
|
||||||
|
jsonField(sb, "type", type);
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
sb.append("]}");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private PokemonBattle findBattle(UUID playerUuid) {
|
||||||
|
return BattleRegistry.INSTANCE.getBattleByParticipatingPlayerId(playerUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendPokemonJson(StringBuilder sb, Pokemon p) {
|
||||||
|
sb.append('{');
|
||||||
|
jsonField(sb, "uuid", p.getUuid().toString());
|
||||||
|
sb.append(',');
|
||||||
|
jsonField(sb, "species", p.getSpecies().getName());
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "level", String.valueOf(p.getLevel()));
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "hp", String.valueOf(p.getCurrentHealth()));
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "maxHp", String.valueOf(p.getHp()));
|
||||||
|
sb.append(',');
|
||||||
|
jsonField(sb, "ability", p.getAbility().getName());
|
||||||
|
sb.append(',');
|
||||||
|
jsonField(sb, "nature", p.getNature().getName().toString());
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "shiny", String.valueOf(p.getShiny()));
|
||||||
|
|
||||||
|
// moves
|
||||||
|
sb.append(",\"moves\":[");
|
||||||
|
boolean mfirst = true;
|
||||||
|
for (var move : p.getMoveSet()) {
|
||||||
|
if (!mfirst) sb.append(',');
|
||||||
|
mfirst = false;
|
||||||
|
sb.append('{');
|
||||||
|
jsonField(sb, "name", move.getName());
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "pp", String.valueOf(move.getCurrentPp()));
|
||||||
|
sb.append(',');
|
||||||
|
jsonFieldRaw(sb, "maxPp", String.valueOf(move.getMaxPp()));
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
|
||||||
|
sb.append('}');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String json(String key, String value) {
|
||||||
|
return "{\"" + escapeJson(key) + "\":\"" + escapeJson(value) + "\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void jsonField(StringBuilder sb, String key, String value) {
|
||||||
|
sb.append('"').append(escapeJson(key)).append("\":\"").append(escapeJson(value)).append('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void jsonFieldRaw(StringBuilder sb, String key, String rawValue) {
|
||||||
|
sb.append('"').append(escapeJson(key)).append("\":").append(rawValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escapeJson(String s) {
|
||||||
|
if (s == null) return "";
|
||||||
|
return s.replace("\\", "\\\\")
|
||||||
|
.replace("\"", "\\\"")
|
||||||
|
.replace("\n", "\\n")
|
||||||
|
.replace("\r", "\\r")
|
||||||
|
.replace("\t", "\\t");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task on the MC server thread and wait for the result.
|
||||||
|
* Returns null if the server is unavailable.
|
||||||
|
*/
|
||||||
|
private String runOnServerThread(java.util.function.Supplier<String> task) {
|
||||||
|
MinecraftServer mc = this.server;
|
||||||
|
if (mc == null) return null;
|
||||||
|
|
||||||
|
CompletableFuture<String> future = new CompletableFuture<>();
|
||||||
|
mc.execute(() -> {
|
||||||
|
try {
|
||||||
|
future.complete(task.get());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Error processing API request", e);
|
||||||
|
future.complete(json("error", "internal error"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
return future.get(5, java.util.concurrent.TimeUnit.SECONDS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Timeout waiting for server thread", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(HttpExchange exchange, int code, String body) throws IOException {
|
||||||
|
byte[] bytes = body.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
|
||||||
|
exchange.sendResponseHeaders(code, bytes.length);
|
||||||
|
try (OutputStream os = exchange.getResponseBody()) {
|
||||||
|
os.write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/resources/fabric.mod.json
Normal file
18
src/main/resources/fabric.mod.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "cobblebattleapi",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"name": "Cobblemon Battle API",
|
||||||
|
"description": "HTTP API exposing Cobblemon battle state",
|
||||||
|
"authors": ["you"],
|
||||||
|
"environment": "server",
|
||||||
|
"entrypoints": {
|
||||||
|
"main": ["com.example.cobbleapi.CobbleBattleApiMod"]
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": ">=0.16",
|
||||||
|
"minecraft": "1.21.1",
|
||||||
|
"fabric-api": "*",
|
||||||
|
"cobblemon": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user