Implement server API for handbook controls (avatar)

This commit is contained in:
KingRainbow44 2023-04-10 03:22:48 -04:00
parent 62fd82fa54
commit 2bd992592d
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
5 changed files with 731 additions and 587 deletions

View File

@ -21,6 +21,7 @@ import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.dispatch.DispatchHandler;
import emu.grasscutter.server.http.dispatch.RegionHandler;
import emu.grasscutter.server.http.documentation.DocumentationServerHandler;
import emu.grasscutter.server.http.documentation.HandbookHandler;
import emu.grasscutter.server.http.handlers.AnnouncementsHandler;
import emu.grasscutter.server.http.handlers.GachaHandler;
import emu.grasscutter.server.http.handlers.GenericHandler;
@ -136,6 +137,7 @@ public final class Grasscutter {
httpServer.addRouter(DispatchHandler.class);
httpServer.addRouter(GachaHandler.class);
httpServer.addRouter(DocumentationServerHandler.class);
httpServer.addRouter(HandbookHandler.class);
// Start servers.
var runMode = Grasscutter.getRunMode();

View File

@ -227,6 +227,8 @@ public class ConfigContainer {
public ResinOptions resinOptions = new ResinOptions();
public Rates rates = new Rates();
public HandbookOptions handbook = new HandbookOptions();
public static class InventoryLimits {
public int weapons = 2000;
public int relics = 2000;
@ -251,6 +253,13 @@ public class ConfigContainer {
public int cap = 160;
public int rechargeTime = 480;
}
public static class HandbookOptions {
public boolean enable = false;
public boolean allowCommands = true;
public int maxRequests = 10;
public int maxEntities = 100;
}
}
public static class JoinOptions {

View File

@ -0,0 +1,110 @@
package emu.grasscutter.server.http.documentation;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.objects.HandbookBody;
import io.javalin.Javalin;
import io.javalin.http.Context;
/** Handles requests for the new GM Handbook. */
public final class HandbookHandler implements Router {
private final byte[] handbook;
private final boolean serve;
/**
* Constructor for the handbook router.
* Enables serving the handbook if the handbook file is found.
*/
public HandbookHandler() {
this.handbook = FileUtils.readResource("/handbook.html");
this.serve = this.handbook.length > 0;
}
@Override
public void applyRoutes(Javalin javalin) {
// The handbook content. (built from src/handbook)
javalin.get("/handbook", this::serveHandbook);
// Handbook control routes.
javalin.post("/handbook/avatar", this::grantAvatar);
}
/**
* @return True if the server can execute handbook commands.
*/
private boolean controlSupported() {
return Grasscutter.getRunMode() == ServerRunMode.HYBRID;
}
/**
* Serves the handbook if it is found.
*
* @route GET /handbook
* @param ctx The Javalin request context.
*/
private void serveHandbook(Context ctx) {
if (!this.serve) {
ctx.status(500).result("Handbook not found.");
} else {
ctx.contentType("text/html").result(this.handbook);
}
}
/**
* Grants the avatar to the user.
*
* @route POST /handbook/avatar
* @param ctx The Javalin request context.
*/
private void grantAvatar(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.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()));
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.");
}
}
}

View File

@ -203,7 +203,7 @@ public final class FileUtils {
return is.readAllBytes();
} catch (Exception exception) {
Grasscutter.getLogger().warn("Failed to read resource: " + resourcePath);
exception.printStackTrace();
Grasscutter.getLogger().debug("Failed to load resource: " + resourcePath, exception);
}
return new byte[0];

View File

@ -0,0 +1,23 @@
package emu.grasscutter.utils.objects;
import lombok.Builder;
import lombok.Getter;
/** HTTP request object for handbook controls. */
public interface HandbookBody {
@Builder
class Response {
private int status;
private String message;
}
@Getter
class GrantAvatar {
private String player; // Parse into online player ID.
private String avatar; // Parse into avatar ID.
private int level = 90; // Range between 1 - 90.
private int constellations = 6; // Range between 0 - 6.
private int talentLevels = 10; // Range between 1 - 15.
}
}