From 78ee3e8db15de54407e62a5d8b32d51fcfbbf942 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Fri, 12 May 2023 20:18:53 -0400 Subject: [PATCH] Implement handbook teleporting also a few formatting changes and sort data by logical sense --- src/handbook/src/backend/commands.ts | 15 ++++ src/handbook/src/backend/data.ts | 28 +++++--- src/handbook/src/backend/server.ts | 18 +++++ src/handbook/src/css/widgets/Character.scss | 2 +- src/handbook/src/ui/pages/ScenesPage.tsx | 23 +++++-- .../grasscutter/config/ConfigContainer.java | 5 +- .../emu/grasscutter/game/world/Scene.java | 16 +++-- .../http/documentation/HandbookHandler.java | 69 +++++++++++++++++-- .../utils/objects/HandbookBody.java | 6 ++ 9 files changed, 149 insertions(+), 33 deletions(-) diff --git a/src/handbook/src/backend/commands.ts b/src/handbook/src/backend/commands.ts index 6389ba62b..1f4d24264 100644 --- a/src/handbook/src/backend/commands.ts +++ b/src/handbook/src/backend/commands.ts @@ -17,6 +17,21 @@ function basicGive(item: number, amount = 1): string { return `/give ${item} x${amount}`; } +/** + * Generates a basic teleport command. + * This creates a relative teleport command. + */ +function teleport(scene: number): string { + // Validate the number. + if (invalid(scene)) return "Invalid arguments."; + + return `/teleport ~ ~ ~ ${scene}`; +} + export const give = { basic: basicGive }; + +export const action = { + teleport: teleport +}; diff --git a/src/handbook/src/backend/data.ts b/src/handbook/src/backend/data.ts index 72238d0b9..f4d03e6b2 100644 --- a/src/handbook/src/backend/data.ts +++ b/src/handbook/src/backend/data.ts @@ -70,7 +70,9 @@ export function getCommands(): CommandDump { * Fetches and lists all the commands in the file. */ export function listCommands(): Command[] { - return Object.values(getCommands()); + return Object.values(getCommands()) + .sort((a, b) => + a.name[0].localeCompare(b.name[0])); } /** @@ -110,22 +112,26 @@ export function getAvatars(): AvatarDump { * Fetches and lists all the avatars in the file. */ export function listAvatars(): Avatar[] { - return Object.values(getAvatars()); + return Object.values(getAvatars()) + .sort((a, b) => + a.name.localeCompare(b.name)); } /** * Fetches and casts all scenes in the file. */ export function getScenes(): Scene[] { - return scenes.map((entry) => { - const values = Object.values(entry) as string[]; - const id = parseInt(values[0]); - return { - id, - identifier: values[1], - type: values[2] as SceneType - }; - }); + return scenes + .map((entry) => { + const values = Object.values(entry) as string[]; + const id = parseInt(values[0]); + return { + id, + identifier: values[1], + type: values[2] as SceneType + }; + }) + .sort((a, b) => a.id - b.id); } /** diff --git a/src/handbook/src/backend/server.ts b/src/handbook/src/backend/server.ts index 7bf1240ee..2f7c4c68f 100644 --- a/src/handbook/src/backend/server.ts +++ b/src/handbook/src/backend/server.ts @@ -77,3 +77,21 @@ export async function giveItem(item: number, amount = 1): Promise res.json()); } + +/** + * Teleports the player to a new scene. + * + * @param scene The scene's ID. + */ +export async function teleportTo(scene: number): Promise { + // Validate the number. + if (isNaN(scene) || scene < 1) return { status: -1, message: "Invalid scene." }; + + return await fetch(`https://localhost:443/handbook/teleport`, { + method: "POST", + body: JSON.stringify({ + player: targetPlayer.toString(), + scene: scene.toString() + }) + }).then((res) => res.json()); +} diff --git a/src/handbook/src/css/widgets/Character.scss b/src/handbook/src/css/widgets/Character.scss index 2188c0ff9..975bd4d2e 100644 --- a/src/handbook/src/css/widgets/Character.scss +++ b/src/handbook/src/css/widgets/Character.scss @@ -10,11 +10,11 @@ width: 100%; overflow: hidden; + box-sizing: border-box; } .Character :hover { cursor: pointer; - box-shadow: 1px 1px black; } .Character_Icon { diff --git a/src/handbook/src/ui/pages/ScenesPage.tsx b/src/handbook/src/ui/pages/ScenesPage.tsx index 4d8686128..9e799b4f5 100644 --- a/src/handbook/src/ui/pages/ScenesPage.tsx +++ b/src/handbook/src/ui/pages/ScenesPage.tsx @@ -4,6 +4,9 @@ import Card from "@widgets/Card"; import { SceneType } from "@backend/types"; import { getScenes } from "@backend/data"; +import { connected, teleportTo } from "@backend/server"; +import { action } from "@backend/commands"; +import { copyToClipboard } from "@app/utils"; import "@css/pages/ScenesPage.scss"; @@ -38,8 +41,12 @@ class ScenesPage extends React.PureComponent { * Teleports the player to the specified scene. * @private */ - private async teleport(): Promise { - // TODO: Implement teleporting. + private async teleport(scene: number): Promise { + if (connected) { + await teleportTo(scene); + } else { + await copyToClipboard(action.teleport(scene)) + } } render() { @@ -48,13 +55,15 @@ class ScenesPage extends React.PureComponent {

Scenes

- {getScenes().map((command) => ( + {getScenes().map((scene) => ( + } diff --git a/src/main/java/emu/grasscutter/config/ConfigContainer.java b/src/main/java/emu/grasscutter/config/ConfigContainer.java index 23100124c..3e9a879c9 100644 --- a/src/main/java/emu/grasscutter/config/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/config/ConfigContainer.java @@ -9,11 +9,10 @@ import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.utils.JsonUtils; import lombok.NoArgsConstructor; -import java.net.URI; -import java.util.Set; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Locale; +import java.util.Set; import static emu.grasscutter.Grasscutter.config; @@ -204,7 +203,7 @@ public class ConfigContainer { public Level serverLoggerLevel = Level.DEBUG; /* Log level of the third-party services (works only with -debug arg): - javalin, quartz, reflections, jetty, mongodb.driver*/ + javalin, quartz, reflections, jetty, mongodb.driver */ public Level servicesLoggersLevel = Level.INFO; /* Controls whether packets should be logged in console or not */ diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 498b112c7..710960fe0 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -5,7 +5,8 @@ import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.binout.SceneNpcBornEntry; import emu.grasscutter.data.binout.routes.Route; -import emu.grasscutter.data.excels.*; +import emu.grasscutter.data.excels.ItemData; +import emu.grasscutter.data.excels.SceneData; import emu.grasscutter.data.excels.codex.CodexAnimalData; import emu.grasscutter.data.excels.monster.MonsterData; import emu.grasscutter.data.excels.world.WorldLevelData; @@ -40,14 +41,15 @@ import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.KahnsSort; import emu.grasscutter.utils.Position; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import lombok.Getter; +import lombok.Setter; +import lombok.val; + +import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; -import javax.annotation.Nullable; -import lombok.Getter; -import lombok.Setter; -import lombok.val; public final class Scene { @Getter private final World world; @@ -555,7 +557,7 @@ public final class Scene { /** * @return The script's default rotation, or the player's rotation. */ - private Position getDefaultRot(Player player) { + public Position getDefaultRotation(Player player) { var defaultRotation = this.getScriptManager().getConfig().born_rot; return defaultRotation != null ? defaultRotation : player.getRotation(); } @@ -581,7 +583,7 @@ public final class Scene { private Position getRespawnRotation(Player player) { var lastCheckpointRot = this.dungeonManager != null ? this.dungeonManager.getRespawnRotation() : null; - return lastCheckpointRot != null ? lastCheckpointRot : this.getDefaultRot(player); + return lastCheckpointRot != null ? lastCheckpointRot : this.getDefaultRotation(player); } /** 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 98fddda5f..9028562d7 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/HandbookHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/HandbookHandler.java @@ -1,7 +1,5 @@ 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; @@ -14,6 +12,10 @@ import emu.grasscutter.utils.objects.HandbookBody; 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 { private final byte[] handbook; @@ -38,6 +40,7 @@ public final class HandbookHandler implements Router { // Handbook control routes. javalin.post("/handbook/avatar", this::grantAvatar); javalin.post("/handbook/item", this::giveItem); + javalin.post("/handbook/teleport", this::teleportTo); } /** @@ -100,8 +103,8 @@ public final class HandbookHandler implements Router { var avatar = new Avatar(avatarData); avatar.setLevel(request.getLevel()); avatar.setPromoteLevel(Avatar.getMinPromoteLevel(avatar.getLevel())); - avatar - .getSkillDepot() + Objects.requireNonNull(avatar + .getSkillDepot()) .getSkillsAndEnergySkill() .forEach(id -> avatar.setSkillLevel(id, request.getTalentLevels())); avatar.forceConstellationLevel(request.getConstellations()); @@ -166,4 +169,62 @@ public final class HandbookHandler implements Router { Grasscutter.getLogger().debug("A handbook command error occurred.", exception); } } + + /** + * Teleports the user to a location. + * + * @route POST /handbook/teleport + * @param ctx The Javalin request context. + */ + private void teleportTo(Context ctx) { + if (!this.controlSupported()) { + ctx.status(500).result("Handbook control not supported."); + return; + } + + // 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 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); + } + } } diff --git a/src/main/java/emu/grasscutter/utils/objects/HandbookBody.java b/src/main/java/emu/grasscutter/utils/objects/HandbookBody.java index f0f76a161..f1c96e93f 100644 --- a/src/main/java/emu/grasscutter/utils/objects/HandbookBody.java +++ b/src/main/java/emu/grasscutter/utils/objects/HandbookBody.java @@ -29,4 +29,10 @@ public interface HandbookBody { private int amount = 1; // Range between 1 - Long.MAX_VALUE. } + + @Getter + class TeleportTo { + private String player; // Parse into online player ID. + private String scene; // Parse into a scene ID. + } }