diff --git a/src/main/java/emu/grasscutter/config/ConfigContainer.java b/src/main/java/emu/grasscutter/config/ConfigContainer.java index 1438d2b37..b4bb262af 100644 --- a/src/main/java/emu/grasscutter/config/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/config/ConfigContainer.java @@ -245,7 +245,7 @@ public class ConfigContainer { public Policies.CORS cors = new Policies.CORS(); public static class CORS { - public boolean enabled = false; + public boolean enabled = true; public String[] allowedOrigins = new String[]{"*"}; } } diff --git a/src/main/java/emu/grasscutter/game/HandbookActions.java b/src/main/java/emu/grasscutter/game/HandbookActions.java new file mode 100644 index 000000000..7e3945e18 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/HandbookActions.java @@ -0,0 +1,247 @@ +package emu.grasscutter.game; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.server.packet.send.PacketAddNoGachaAvatarCardNotify; +import emu.grasscutter.utils.objects.HandbookBody.*; + +import java.util.Objects; + +/** Commands executed by the handbook. */ +public interface HandbookActions { + /** + * Grants an avatar to the player. + * + * @param request The request object. + * @return The response object. + */ + static Response grantAvatar(GrantAvatar request) { + // Validate the request. + if (request.getPlayer() == null || request.getAvatar() == null) { + return Response.builder().status(400) + .message("Invalid request.").build(); + } + + try { + // Parse the requested player. + var playerId = Integer.parseInt(request.getPlayer()); + var player = Grasscutter.getGameServer().getPlayerByUid(playerId); + + // Parse the requested avatar. + var avatarId = Integer.parseInt(request.getAvatar()); + var avatarData = GameData.getAvatarDataMap().get(avatarId); + + // Validate the request. + if (player == null) { + return Response.builder().status(1) + .message("Player not found.").build(); + } + if (avatarData == null) { + return Response.builder().status(400) + .message("Invalid avatar ID.").build(); + } + + // Create the new avatar. + var avatar = new Avatar(avatarData); + avatar.setLevel(request.getLevel()); + avatar.setPromoteLevel(Avatar.getMinPromoteLevel(avatar.getLevel())); + Objects.requireNonNull(avatar.getSkillDepot()) + .getSkillsAndEnergySkill() + .forEach(id -> avatar.setSkillLevel(id, request.getTalentLevels())); + avatar.forceConstellationLevel(request.getConstellations()); + avatar.recalcStats(true); + avatar.save(); + + // Add the avatar. + player.addAvatar(avatar); + player.sendPacket(new PacketAddNoGachaAvatarCardNotify( + avatar, ActionReason.Gm)); + return Response.builder().status(200) + .message("Avatar granted.").build(); + } catch (NumberFormatException ignored) { + return Response.builder().status(500) + .message("Invalid player UID or avatar ID.").build(); + } catch (Exception exception) { + Grasscutter.getLogger().debug("A handbook command error occurred.", exception); + return Response.builder().status(500) + .message("An error occurred while granting the avatar.").build(); + } + } + + /** + * Gives an item to the player. + * + * @param request The request object. + * @return The response object. + */ + static Response giveItem(GiveItem request) { + // Validate the request. + if (request.getPlayer() == null || request.getItem() == null) { + return Response.builder().status(400) + .message("Invalid request.").build(); + } + + try { + // Parse the requested player. + var playerId = Integer.parseInt(request.getPlayer()); + var player = Grasscutter.getGameServer().getPlayerByUid(playerId); + + // Parse the requested item. + var itemId = Integer.parseInt(request.getItem()); + var itemData = GameData.getItemDataMap().get(itemId); + + // Validate the request. + if (player == null) { + return Response.builder().status(1) + .message("Player not found.").build(); + } + if (itemData == null) { + return Response.builder().status(400) + .message("Invalid player UID or item ID.").build(); + } + + // Add the item to the player's inventory. + var amount = request.getAmount(); + if (amount > Integer.MAX_VALUE) { + // Calculate the amount of times we need to add the item. + var times = Math.floor((double) amount / Integer.MAX_VALUE); + amount = amount % Integer.MAX_VALUE; + + // Add the item the amount of times we need to. + for (var i = 0; i < times; i++) { + var itemStack = new GameItem(itemData, Integer.MAX_VALUE); + player.getInventory().addItem(itemStack, ActionReason.Gm); + } + } + + // Create the item stack and add it to the player's inventory. + var itemStack = new GameItem(itemData, (int) amount); + player.getInventory().addItem(itemStack, ActionReason.Gm); + + return Response.builder().status(200) + .message("Item granted.").build(); + } catch (NumberFormatException ignored) { + return Response.builder().status(500) + .message("Invalid player UID or item ID.").build(); + } catch (Exception exception) { + Grasscutter.getLogger().debug("A handbook command error occurred.", exception); + return Response.builder().status(500) + .message("An error occurred while granting the item.").build(); + } + } + + /** + * Teleports the player to a location. + * + * @param request The request object. + * @return The response object. + */ + static Response teleportTo(TeleportTo request) { + // Validate the request. + if (request.getPlayer() == null || request.getScene() == null) { + return Response.builder().status(400) + .message("Invalid request.").build(); + } + + try { + // Parse the requested player. + var playerId = Integer.parseInt(request.getPlayer()); + var player = Grasscutter.getGameServer().getPlayerByUid(playerId); + + // Parse the requested scene. + var sceneId = Integer.parseInt(request.getScene()); + + // Validate the request. + if (player == null) { + return Response.builder().status(1) + .message("Player not found.").build(); + } + + // Find the scene in the player's world. + var scene = player.getWorld().getSceneById(sceneId); + if (scene == null) { + return Response.builder().status(400) + .message("Invalid scene ID.").build(); + } + + // Resolve the correct teleport position. + var position = scene.getDefaultLocation(player); + var rotation = scene.getDefaultRotation(player); + // Teleport the player. + scene.getWorld().transferPlayerToScene(player, scene.getId(), position); + player.getRotation().set(rotation); + + return Response.builder().status(200) + .message("Player teleported.").build(); + } catch (NumberFormatException ignored) { + return Response.builder().status(400) + .message("Invalid player UID or scene ID.").build(); + } catch (Exception exception) { + Grasscutter.getLogger().debug("A handbook command error occurred.", exception); + return Response.builder().status(500) + .message("An error occurred while teleporting to the scene.").build(); + } + } + + /** + * Spawns an entity(s) in the player's world. + * + * @param request The request object. + * @return The response object. + */ + static Response spawnEntity(SpawnEntity request) { + // Validate the request. + if (request.getPlayer() == null || request.getEntity() == null) { + return Response.builder().status(400) + .message("Invalid request.").build(); + } + + try { + // Parse the requested player. + var playerId = Integer.parseInt(request.getPlayer()); + var player = Grasscutter.getGameServer().getPlayerByUid(playerId); + + // Parse the requested entity. + var entityId = Integer.parseInt(request.getEntity()); + var entityData = GameData.getMonsterDataMap().get(entityId); + + // Validate the request. + if (player == null) { + return Response.builder().status(1) + .message("Player not found.").build(); + } + if (entityData == null) { + return Response.builder().status(400) + .message("Invalid entity ID.").build(); + } + + // Validate request properties. + var scene = player.getScene(); + var level = request.getLevel(); + if (scene == null || level > 200 || level < 1) { + return Response.builder().status(400) + .message("Invalid scene or level.").build(); + } + + // Create the entity. + for (var i = 1; i <= request.getAmount(); i++) { + var entity = new EntityMonster(scene, entityData, player.getPosition(), level); + scene.addEntity(entity); + } + + return Response.builder().status(200) + .message("Entity(s) spawned.").build(); + } catch (NumberFormatException ignored) { + return Response.builder().status(400) + .message("Invalid player UID or entity ID.").build(); + } catch (Exception exception) { + Grasscutter.getLogger().debug("A handbook command error occurred.", exception); + return Response.builder().status(500) + .message("An error occurred while teleporting to the scene.").build(); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchClient.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchClient.java index 71d353cbc..efe71ba97 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchClient.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchClient.java @@ -7,6 +7,9 @@ import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.http.handlers.GachaHandler; import emu.grasscutter.utils.Crypto; +import emu.grasscutter.utils.DispatchUtils; +import emu.grasscutter.utils.JsonUtils; +import emu.grasscutter.utils.objects.HandbookBody; import lombok.Getter; import org.java_websocket.WebSocket; import org.java_websocket.client.WebSocketClient; @@ -38,6 +41,7 @@ public final class DispatchClient extends WebSocketClient implements IDispatcher this.setAttachment(true); this.registerHandler(PacketIds.GachaHistoryReq, this::fetchGachaHistory); + this.registerHandler(PacketIds.GmTalkReq, this::handleHandbookAction); } /** @@ -70,6 +74,35 @@ public final class DispatchClient extends WebSocketClient implements IDispatcher this.sendMessage(PacketIds.GachaHistoryRsp, response); } + /** + * Handles the handbook action packet sent by the client. + * + * @param socket The socket the packet was received from. + * @param object The packet data. + */ + private void handleHandbookAction(WebSocket socket, JsonElement object) { + var message = IDispatcher.decode(object); + var actionStr = message.get("action").getAsString(); + var data = message.getAsJsonObject("data"); + + // Parse the action into an enum. + var action = HandbookBody.Action.valueOf(actionStr); + + // Produce a handbook response. + var response = DispatchUtils.performHandbookAction(action, switch (action) { + case GRANT_AVATAR -> JsonUtils.decode(data, HandbookBody.GrantAvatar.class); + case GIVE_ITEM -> JsonUtils.decode(data, HandbookBody.GiveItem.class); + case TELEPORT_TO -> JsonUtils.decode(data, HandbookBody.TeleportTo.class); + case SPAWN_ENTITY -> JsonUtils.decode(data, HandbookBody.SpawnEntity.class); + }); + + // Check if the response's status is '1'. + if (response.getStatus() == 1) return; + + // Send the response to the server. + this.sendMessage(PacketIds.GmTalkRsp, response); + } + /** * Sends a serialized encrypted message to the server. * diff --git a/src/main/java/emu/grasscutter/server/dispatch/IDispatcher.java b/src/main/java/emu/grasscutter/server/dispatch/IDispatcher.java index 3e09f4723..69862c34a 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/IDispatcher.java +++ b/src/main/java/emu/grasscutter/server/dispatch/IDispatcher.java @@ -1,21 +1,22 @@ package emu.grasscutter.server.dispatch; -import static emu.grasscutter.config.Configuration.DISPATCH_INFO; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.JsonAdapters.ByteArrayAdapter; +import org.java_websocket.WebSocket; +import org.slf4j.Logger; + import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; -import org.java_websocket.WebSocket; -import org.slf4j.Logger; + +import static emu.grasscutter.config.Configuration.DISPATCH_INFO; public interface IDispatcher { Gson JSON = @@ -53,7 +54,7 @@ public interface IDispatcher { } // Un-escape the data. - data = data.replaceAll("\"", ""); + data = data.replaceAll("\\\\\"", "\""); data = data.replaceAll("\\\\", ""); // De-serialize the data. diff --git a/src/main/java/emu/grasscutter/server/dispatch/PacketIds.java b/src/main/java/emu/grasscutter/server/dispatch/PacketIds.java index d64c56182..4e131ab17 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/PacketIds.java +++ b/src/main/java/emu/grasscutter/server/dispatch/PacketIds.java @@ -1,5 +1,7 @@ package emu.grasscutter.server.dispatch; +import emu.grasscutter.net.packet.PacketOpcodes; + /* Packet IDs for the dispatch server. */ public interface PacketIds { int LoginNotify = 1; @@ -7,4 +9,6 @@ public interface PacketIds { int TokenValidateRsp = 3; int GachaHistoryReq = 4; int GachaHistoryRsp = 5; + int GmTalkReq = PacketOpcodes.GmTalkReq; + int GmTalkRsp = PacketOpcodes.GmTalkRsp; } diff --git a/src/main/java/emu/grasscutter/server/http/HttpServer.java b/src/main/java/emu/grasscutter/server/http/HttpServer.java index 9bfac1122..278c016d7 100644 --- a/src/main/java/emu/grasscutter/server/http/HttpServer.java +++ b/src/main/java/emu/grasscutter/server/http/HttpServer.java @@ -45,9 +45,11 @@ public final class HttpServer { if (HTTP_POLICIES.cors.enabled) { var allowedOrigins = HTTP_POLICIES.cors.allowedOrigins; config.plugins.enableCors(cors -> cors.add(corsConfig -> { - if (allowedOrigins.length > 0) - corsConfig.allowHost(Arrays.toString(allowedOrigins)); - else corsConfig.anyHost(); + if (allowedOrigins.length > 0) { + if (Arrays.asList(allowedOrigins).contains("*")) + corsConfig.anyHost(); + else corsConfig.allowHost(Arrays.toString(allowedOrigins)); + } else corsConfig.anyHost(); })); } diff --git a/src/main/java/emu/grasscutter/server/http/documentation/HandbookHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/HandbookHandler.java index 789cb5182..9eabdbe1e 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/HandbookHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/HandbookHandler.java @@ -1,20 +1,14 @@ package emu.grasscutter.server.http.documentation; -import static emu.grasscutter.config.Configuration.HANDBOOK; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.Grasscutter.ServerRunMode; -import emu.grasscutter.data.GameData; -import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.server.http.Router; +import emu.grasscutter.utils.DispatchUtils; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.objects.HandbookBody; +import emu.grasscutter.utils.objects.HandbookBody.Action; import io.javalin.Javalin; import io.javalin.http.Context; -import java.util.Objects; + +import static emu.grasscutter.config.Configuration.HANDBOOK; /** Handles requests for the new GM Handbook. */ public final class HandbookHandler implements Router { @@ -41,13 +35,14 @@ public final class HandbookHandler implements Router { javalin.post("/handbook/avatar", this::grantAvatar); javalin.post("/handbook/item", this::giveItem); javalin.post("/handbook/teleport", this::teleportTo); + javalin.post("/handbook/spawn", this::spawnEntity); } /** * @return True if the server can execute handbook commands. */ private boolean controlSupported() { - return HANDBOOK.enable && Grasscutter.getRunMode() == ServerRunMode.HYBRID; + return HANDBOOK.enable; } /** @@ -78,46 +73,12 @@ public final class HandbookHandler implements Router { // Parse the request body into a class. var request = ctx.bodyAsClass(HandbookBody.GrantAvatar.class); - // Validate the request. - if (request.getPlayer() == null || request.getAvatar() == null) { - ctx.status(400).result("Invalid request."); - return; - } - - try { - // Parse the requested player. - var playerId = Integer.parseInt(request.getPlayer()); - var player = Grasscutter.getGameServer().getPlayerByUid(playerId); - - // Parse the requested avatar. - var avatarId = Integer.parseInt(request.getAvatar()); - var avatarData = GameData.getAvatarDataMap().get(avatarId); - - // Validate the request. - if (player == null || avatarData == null) { - ctx.status(400).result("Invalid player UID or avatar ID."); - return; - } - - // Create the new avatar. - var avatar = new Avatar(avatarData); - avatar.setLevel(request.getLevel()); - avatar.setPromoteLevel(Avatar.getMinPromoteLevel(avatar.getLevel())); - Objects.requireNonNull(avatar.getSkillDepot()) - .getSkillsAndEnergySkill() - .forEach(id -> avatar.setSkillLevel(id, request.getTalentLevels())); - avatar.forceConstellationLevel(request.getConstellations()); - avatar.recalcStats(true); - avatar.save(); - - player.addAvatar(avatar); // Add the avatar. - ctx.json(HandbookBody.Response.builder().status(200).message("Avatar granted.").build()); - } catch (NumberFormatException ignored) { - ctx.status(500).result("Invalid player UID or avatar ID."); - } catch (Exception exception) { - ctx.status(500).result("An error occurred while granting the avatar."); - Grasscutter.getLogger().debug("A handbook command error occurred.", exception); - } + // Get the response. + var response = DispatchUtils.performHandbookAction( + Action.GRANT_AVATAR, request); + // Send the response. + ctx.status(response.getStatus() > 100 ? + response.getStatus() : 500).json(response); } /** @@ -134,39 +95,12 @@ public final class HandbookHandler implements Router { // Parse the request body into a class. var request = ctx.bodyAsClass(HandbookBody.GiveItem.class); - // Validate the request. - if (request.getPlayer() == null || request.getItem() == null) { - ctx.status(400).result("Invalid request."); - return; - } - - try { - // Parse the requested player. - var playerId = Integer.parseInt(request.getPlayer()); - var player = Grasscutter.getGameServer().getPlayerByUid(playerId); - - // Parse the requested item. - var itemId = Integer.parseInt(request.getItem()); - var itemData = GameData.getItemDataMap().get(itemId); - - // Validate the request. - if (player == null || itemData == null) { - ctx.status(400).result("Invalid player UID or item ID."); - return; - } - - // Create the new item stack. - var itemStack = new GameItem(itemData, request.getAmount()); - // Add the item to the inventory. - player.getInventory().addItem(itemStack, ActionReason.Gm); - - ctx.json(HandbookBody.Response.builder().status(200).message("Item granted.").build()); - } catch (NumberFormatException ignored) { - ctx.status(500).result("Invalid player UID or item ID."); - } catch (Exception exception) { - ctx.status(500).result("An error occurred while granting the item."); - Grasscutter.getLogger().debug("A handbook command error occurred.", exception); - } + // Get the response. + var response = DispatchUtils.performHandbookAction( + Action.GIVE_ITEM, request); + // Send the response. + ctx.status(response.getStatus() > 100 ? + response.getStatus() : 500).json(response); } /** @@ -183,47 +117,12 @@ public final class HandbookHandler implements Router { // Parse the request body into a class. var request = ctx.bodyAsClass(HandbookBody.TeleportTo.class); - // Validate the request. - if (request.getPlayer() == null || request.getScene() == null) { - ctx.status(400).result("Invalid request."); - return; - } - - try { - // Parse the requested player. - var playerId = Integer.parseInt(request.getPlayer()); - var player = Grasscutter.getGameServer().getPlayerByUid(playerId); - - // Parse the requested scene. - var sceneId = Integer.parseInt(request.getScene()); - - // Validate the request. - if (player == null) { - ctx.status(400).result("Invalid player UID."); - return; - } - - // Find the scene in the player's world. - var scene = player.getWorld().getSceneById(sceneId); - if (scene == null) { - ctx.status(400).result("Invalid scene ID."); - return; - } - - // Resolve the correct teleport position. - var position = scene.getDefaultLocation(player); - var rotation = scene.getDefaultRotation(player); - // Teleport the player. - scene.getWorld().transferPlayerToScene(player, scene.getId(), position); - player.getRotation().set(rotation); - - ctx.json(HandbookBody.Response.builder().status(200).message("Player teleported.").build()); - } catch (NumberFormatException ignored) { - ctx.status(400).result("Invalid player UID or scene ID."); - } catch (Exception exception) { - ctx.status(500).result("An error occurred while teleporting to the scene."); - Grasscutter.getLogger().debug("A handbook command error occurred.", exception); - } + // Get the response. + var response = DispatchUtils.performHandbookAction( + Action.TELEPORT_TO, request); + // Send the response. + ctx.status(response.getStatus() > 100 ? + response.getStatus() : 500).json(response); } /** @@ -239,48 +138,13 @@ public final class HandbookHandler implements Router { } // Parse the request body into a class. - var request = ctx.bodyAsClass(HandbookBody.SpawnEntity.class); - // Validate the request. - if (request.getPlayer() == null || request.getEntity() == null) { - ctx.status(400).result("Invalid request."); - return; - } - - try { - // Parse the requested player. - var playerId = Integer.parseInt(request.getPlayer()); - var player = Grasscutter.getGameServer().getPlayerByUid(playerId); - - // Parse the requested entity. - var entityId = Integer.parseInt(request.getEntity()); - var entityData = GameData.getMonsterDataMap().get(entityId); - - // Validate the request. - if (player == null || entityData == null) { - ctx.status(400).result("Invalid player UID or entity ID."); - return; - } - - // Validate request properties. - var scene = player.getScene(); - var level = request.getLevel(); - if (scene == null || level > 200 || level < 1) { - ctx.status(400).result("Invalid scene or level."); - return; - } - - // Create the entity. - for (var i = 1; i <= request.getAmount(); i++) { - var entity = new EntityMonster(scene, entityData, player.getPosition(), level); - scene.addEntity(entity); - } - - ctx.json(HandbookBody.Response.builder().status(200).message("Entity(s) spawned.").build()); - } catch (NumberFormatException ignored) { - ctx.status(400).result("Invalid player UID or entity ID."); - } catch (Exception exception) { - ctx.status(500).result("An error occurred while teleporting to the scene."); - Grasscutter.getLogger().debug("A handbook command error occurred.", exception); - } + var request = ctx.bodyAsClass( + HandbookBody.SpawnEntity.class); + // Get the response. + var response = DispatchUtils.performHandbookAction( + Action.SPAWN_ENTITY, request); + // Send the response. + ctx.status(response.getStatus() > 100 ? + response.getStatus() : 500).json(response); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAddNoGachaAvatarCardNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAddNoGachaAvatarCardNotify.java index 8e9c01d49..4140bf546 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketAddNoGachaAvatarCardNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAddNoGachaAvatarCardNotify.java @@ -9,16 +9,16 @@ import emu.grasscutter.net.proto.AddNoGachaAvatarCardNotifyOuterClass.AddNoGacha public class PacketAddNoGachaAvatarCardNotify extends BasePacket { - public PacketAddNoGachaAvatarCardNotify(Avatar avatar, ActionReason reason, GameItem item) { + public PacketAddNoGachaAvatarCardNotify(Avatar avatar, ActionReason reason) { super(PacketOpcodes.AddNoGachaAvatarCardNotify, true); AddNoGachaAvatarCardNotify proto = AddNoGachaAvatarCardNotify.newBuilder() .setAvatarId(avatar.getAvatarId()) .setReason(reason.getValue()) - .setInitialLevel(1) - .setItemId(item.getItemId()) - .setInitialPromoteLevel(0) + .setInitialLevel(avatar.getLevel()) + .setItemId(1000 + (avatar.getAvatarId() % 10000000)) + .setInitialPromoteLevel(avatar.getPromoteLevel()) .build(); this.setData(proto); diff --git a/src/main/java/emu/grasscutter/utils/DispatchUtils.java b/src/main/java/emu/grasscutter/utils/DispatchUtils.java index 4e905d627..73669cbb1 100644 --- a/src/main/java/emu/grasscutter/utils/DispatchUtils.java +++ b/src/main/java/emu/grasscutter/utils/DispatchUtils.java @@ -1,20 +1,24 @@ package emu.grasscutter.utils; -import static emu.grasscutter.config.Configuration.DISPATCH_INFO; - import com.google.gson.JsonObject; import emu.grasscutter.Grasscutter; import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; +import emu.grasscutter.game.HandbookActions; import emu.grasscutter.server.dispatch.IDispatcher; import emu.grasscutter.server.dispatch.PacketIds; import emu.grasscutter.server.http.handlers.GachaHandler; import emu.grasscutter.server.http.objects.LoginTokenRequestJson; +import emu.grasscutter.utils.objects.HandbookBody; +import emu.grasscutter.utils.objects.HandbookBody.*; + +import javax.annotation.Nullable; import java.net.http.HttpClient; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; + +import static emu.grasscutter.config.Configuration.DISPATCH_INFO; public interface DispatchUtils { /** HTTP client used for dispatch queries. */ @@ -112,4 +116,46 @@ public interface DispatchUtils { } }; } + + /** + * Performs a handbook action. + * + * @param action The action. + * @param data The data. + * @return The response. + */ + static Response performHandbookAction(HandbookBody.Action action, Object data) { + return switch (Grasscutter.getRunMode()) { + case DISPATCH_ONLY -> { + // Create a request for the 'GM Talk' action. + var request = new JsonObject(); + request.addProperty("action", action.name()); + request.add("data", JsonUtils.toJson(data)); + + // Create a future for the response. + var future = new CompletableFuture(); + // Listen for the response. + var server = Grasscutter.getDispatchServer(); + server.registerCallback(PacketIds.GmTalkRsp, packet -> + future.complete(IDispatcher.decode(packet, Response.class))); + + // Broadcast the request. + server.sendMessage(PacketIds.GmTalkReq, request); + + try { + // Wait for the response. + yield future.get(5L, TimeUnit.SECONDS); + } catch (Exception ignored) { + yield Response.builder().status(400) + .message("No response received from any server.").build(); + } + } + case HYBRID, GAME_ONLY -> switch (action) { + case GRANT_AVATAR -> HandbookActions.grantAvatar((GrantAvatar) data); + case GIVE_ITEM -> HandbookActions.giveItem((GiveItem) data); + case TELEPORT_TO -> HandbookActions.teleportTo((TeleportTo) data); + case SPAWN_ENTITY -> HandbookActions.spawnEntity((SpawnEntity) data); + }; + }; + } } diff --git a/src/main/java/emu/grasscutter/utils/objects/HandbookBody.java b/src/main/java/emu/grasscutter/utils/objects/HandbookBody.java index a200ecef9..e6367d858 100644 --- a/src/main/java/emu/grasscutter/utils/objects/HandbookBody.java +++ b/src/main/java/emu/grasscutter/utils/objects/HandbookBody.java @@ -6,12 +6,19 @@ import lombok.Getter; /** HTTP request object for handbook controls. */ @SuppressWarnings("FieldMayBeFinal") public interface HandbookBody { - @Builder + @Builder @Getter class Response { private int status; private String message; } + enum Action { + GRANT_AVATAR, + GIVE_ITEM, + TELEPORT_TO, + SPAWN_ENTITY + } + @Getter class GrantAvatar { private String player; // Parse into online player ID. @@ -27,7 +34,7 @@ public interface HandbookBody { private String player; // Parse into online player ID. private String item; // Parse into item ID. - private int amount = 1; // Range between 1 - Long.MAX_VALUE. + private long amount = 1; // Range between 1 - Long.MAX_VALUE. } @Getter @@ -41,7 +48,7 @@ public interface HandbookBody { private String player; // Parse into online player ID. private String entity; // Parse into entity ID. - private int amount = 1; // Range between 1 - Long.MAX_VALUE. + private long amount = 1; // Range between 1 - Long.MAX_VALUE. private int level = 1; // Range between 1 - 200. } }