Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.server.core.universe.world.World;
import com.top_serveurs.hytale.plugins.mcp.auth.McpAuthManager;
import com.top_serveurs.hytale.plugins.mcp.config.McpConfig;
import com.top_serveurs.hytale.plugins.mcp.models.McpTool;
import com.top_serveurs.hytale.plugins.mcp.models.McpToolCall;
import com.top_serveurs.hytale.plugins.mcp.models.McpToolResponse;

import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

public class GetPlayerPositionFeature implements McpFeature {
private static final Gson GSON = new Gson();
Expand Down Expand Up @@ -56,38 +58,61 @@ public McpToolResponse execute(McpToolCall call, McpAuthManager.AuthLevel authLe
}

String playerIdentifier = args.get("player").toString();
PlayerRef player = findPlayer(playerIdentifier);

if (player == null) {
return McpToolResponse.error("Player not found: " + playerIdentifier);
Map<String, World> worlds = Universe.get().getWorlds();
if (worlds.isEmpty()) {
return McpToolResponse.error("No world available to get player position");
}
World world = worlds.values().iterator().next();

// Universe/PlayerRef state (including live transform) must be read on the
// owning world's thread, otherwise the MCP SDK's blocking sync tool call
// hangs forever with no exception (the Jetty request thread is not the
// world thread).
CompletableFuture<McpToolResponse> future = new CompletableFuture<>();

world.execute(() -> {
try {
PlayerRef player = findPlayer(playerIdentifier);

if (player == null) {
future.complete(McpToolResponse.error("Player not found: " + playerIdentifier));
return;
}

com.hypixel.hytale.math.vector.Transform transform = player.getTransform();
org.joml.Vector3d pos = transform.getPosition();
com.hypixel.hytale.math.vector.Rotation3f rotation = transform.getRotation();

JsonObject position = new JsonObject();
position.addProperty("x", pos.x());
position.addProperty("y", pos.y());
position.addProperty("z", pos.z());
position.addProperty("worldUuid", player.getWorldUuid().toString());
position.addProperty("yaw", rotation.yaw());
position.addProperty("pitch", rotation.pitch());

JsonObject response = new JsonObject();
response.addProperty("name", player.getUsername());
response.addProperty("uuid", player.getUuid().toString());
response.add("position", position);

future.complete(McpToolResponse.success(GSON.toJson(response)));
} catch (Throwable t) {
logger.atSevere().withCause(t).log("Error getting player position");
future.complete(McpToolResponse.error("Failed to get player position: " + t.getMessage()));
}
});

com.hypixel.hytale.math.vector.Transform transform = player.getTransform();
com.hypixel.hytale.math.vector.Vector3d pos = transform.getPosition();
com.hypixel.hytale.math.vector.Vector3f rotation = transform.getRotation();

JsonObject position = new JsonObject();
position.addProperty("x", pos.getX());
position.addProperty("y", pos.getY());
position.addProperty("z", pos.getZ());
position.addProperty("worldUuid", player.getWorldUuid().toString());
position.addProperty("yaw", rotation.getY());
position.addProperty("pitch", rotation.getX());

JsonObject response = new JsonObject();
response.addProperty("name", player.getUsername());
response.addProperty("uuid", player.getUuid().toString());
response.add("position", position);

return McpToolResponse.success(GSON.toJson(response));
return future.join();
} catch (Exception e) {
logger.atSevere().withCause(e).log("Error getting player position");
return McpToolResponse.error("Failed to get player position: " + e.getMessage());
}
}

private PlayerRef findPlayer(String identifier) {
List<PlayerRef> players = Universe.get().getPlayers();
Collection<PlayerRef> players = Universe.get().getPlayers();

try {
UUID uuid = UUID.fromString(identifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.server.core.universe.world.World;
import com.top_serveurs.hytale.plugins.mcp.auth.McpAuthManager;
import com.top_serveurs.hytale.plugins.mcp.config.McpConfig;
import com.top_serveurs.hytale.plugins.mcp.models.McpTool;
import com.top_serveurs.hytale.plugins.mcp.models.McpToolCall;
import com.top_serveurs.hytale.plugins.mcp.models.McpToolResponse;

import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

public class ListPlayersFeature implements McpFeature {
private static final Gson GSON = new Gson();
Expand Down Expand Up @@ -44,21 +47,41 @@ public String getInputSchema() {
@Override
public McpToolResponse execute(McpToolCall call, McpAuthManager.AuthLevel authLevel) {
try {
List<PlayerRef> players = Universe.get().getPlayers();
JsonArray playerArray = new JsonArray();

for (PlayerRef player : players) {
JsonObject playerObj = new JsonObject();
playerObj.addProperty("uuid", player.getUuid().toString());
playerObj.addProperty("name", player.getUsername());
playerArray.add(playerObj);
Map<String, World> worlds = Universe.get().getWorlds();
if (worlds.isEmpty()) {
return McpToolResponse.error("No world available to list players");
}
World world = worlds.values().iterator().next();

// Universe/PlayerRef state must be read on the owning world's thread,
// otherwise the MCP SDK's blocking sync tool call hangs forever with no
// exception (the Jetty request thread is not the world thread).
CompletableFuture<McpToolResponse> future = new CompletableFuture<>();

world.execute(() -> {
try {
Collection<PlayerRef> players = Universe.get().getPlayers();
JsonArray playerArray = new JsonArray();

for (PlayerRef player : players) {
JsonObject playerObj = new JsonObject();
playerObj.addProperty("uuid", player.getUuid().toString());
playerObj.addProperty("name", player.getUsername());
playerArray.add(playerObj);
}

JsonObject response = new JsonObject();
response.addProperty("count", players.size());
response.add("players", playerArray);

JsonObject response = new JsonObject();
response.addProperty("count", players.size());
response.add("players", playerArray);
future.complete(McpToolResponse.success(GSON.toJson(response)));
} catch (Throwable t) {
logger.atSevere().withCause(t).log("Error listing players");
future.complete(McpToolResponse.error("Failed to list players: " + t.getMessage()));
}
});

return McpToolResponse.success(GSON.toJson(response));
return future.join();
} catch (Exception e) {
logger.atSevere().withCause(e).log("Error listing players");
return McpToolResponse.error("Failed to list players: " + e.getMessage());
Expand Down