mirror of
https://github.com/mingjun97/gc-mojoconsole-plus.git
synced 2024-11-27 12:16:26 +00:00
Add multi server support
This commit is contained in:
parent
aafb6b1430
commit
b51006edd0
@ -17,6 +17,14 @@ public class AuthHandler {
|
||||
signatureStub = stub;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signatureStub;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
signatureStub = signature;
|
||||
}
|
||||
|
||||
public Boolean auth(int uid, long expire, String dg) {
|
||||
return digestUid(uid+":"+expire).equals(dg);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.mojo.consoleplus;
|
||||
|
||||
import com.mojo.consoleplus.socket.SocketClient;
|
||||
import com.mojo.consoleplus.socket.SocketServer;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.command.CommandMap;
|
||||
import emu.grasscutter.plugin.Plugin;
|
||||
@ -12,20 +14,33 @@ import java.io.InputStreamReader;
|
||||
import com.google.gson.Gson;
|
||||
import com.mojo.consoleplus.command.PluginCommand;
|
||||
|
||||
import emu.grasscutter.server.event.EventHandler;
|
||||
import emu.grasscutter.server.event.HandlerPriority;
|
||||
import emu.grasscutter.server.event.player.PlayerJoinEvent;
|
||||
import emu.grasscutter.server.event.player.PlayerQuitEvent;
|
||||
import io.javalin.http.staticfiles.Location;
|
||||
import emu.grasscutter.plugin.PluginConfig;
|
||||
import static emu.grasscutter.Configuration.PLUGIN;
|
||||
import static emu.grasscutter.Configuration.HTTP_POLICIES;
|
||||
|
||||
import com.mojo.consoleplus.config.MojoConfig;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class ConsolePlus extends Plugin{
|
||||
public static MojoConfig config = MojoConfig.loadConfig();
|
||||
public static String versionTag;
|
||||
public static AuthHandler authHandler;
|
||||
public static Logger logger;
|
||||
public static ConsolePlus instance;
|
||||
|
||||
public static ConsolePlus getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
instance = this;
|
||||
logger = getLogger();
|
||||
try (InputStream in = getClass().getResourceAsStream("/plugin.json");
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
|
||||
Gson gson = new Gson();
|
||||
@ -56,10 +71,27 @@ public class ConsolePlus extends Plugin{
|
||||
return;
|
||||
}
|
||||
}
|
||||
Grasscutter.getHttpServer().addRouter(RequestHandler.class);
|
||||
|
||||
authHandler = new AuthHandler();
|
||||
|
||||
if (Grasscutter.config.server.runMode == Grasscutter.ServerRunMode.DISPATCH_ONLY) {
|
||||
SocketServer.startServer();
|
||||
Grasscutter.getHttpServer().addRouter(RequestOnlyHttpHandler.class);
|
||||
} else if (Grasscutter.config.server.runMode == Grasscutter.ServerRunMode.GAME_ONLY) {
|
||||
SocketClient.connectServer();
|
||||
new EventHandler<>(PlayerJoinEvent.class)
|
||||
.priority(HandlerPriority.HIGH)
|
||||
.listener(EventListeners::onPlayerJoin)
|
||||
.register(this);
|
||||
new EventHandler<>(PlayerQuitEvent.class)
|
||||
.priority(HandlerPriority.HIGH)
|
||||
.listener(EventListeners::onPlayerQuit)
|
||||
.register(this);
|
||||
} else {
|
||||
Grasscutter.getHttpServer().addRouter(RequestHandler.class);
|
||||
}
|
||||
CommandMap.getInstance().registerCommand("mojoconsole", new PluginCommand());
|
||||
this.getLogger().info("[MojoConsole] enabled. Version: " + versionTag);
|
||||
authHandler = new AuthHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,40 @@
|
||||
package com.mojo.consoleplus;
|
||||
|
||||
import com.mojo.consoleplus.socket.SocketClient;
|
||||
import com.mojo.consoleplus.socket.packet.player.PlayerList;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.server.event.player.PlayerJoinEvent;
|
||||
import emu.grasscutter.server.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class EventListeners {
|
||||
public static void onPlayerJoin(PlayerJoinEvent playerJoinEvent) {
|
||||
PlayerList playerList = new PlayerList();
|
||||
playerList.player = Grasscutter.getGameServer().getPlayers().size();
|
||||
ArrayList<String> playerNames = new ArrayList<>();
|
||||
playerNames.add(playerJoinEvent.getPlayer().getNickname());
|
||||
playerList.playerMap.put(playerJoinEvent.getPlayer().getUid(), playerJoinEvent.getPlayer().getNickname());
|
||||
for (Player player : Grasscutter.getGameServer().getPlayers().values()) {
|
||||
playerNames.add(player.getNickname());
|
||||
playerList.playerMap.put(player.getUid(), player.getNickname());
|
||||
}
|
||||
playerList.playerList = playerNames;
|
||||
SocketClient.sendPacket(playerList);
|
||||
}
|
||||
|
||||
public static void onPlayerQuit(PlayerQuitEvent playerQuitEvent) {
|
||||
PlayerList playerList = new PlayerList();
|
||||
playerList.player = Grasscutter.getGameServer().getPlayers().size();
|
||||
ArrayList<String> playerNames = new ArrayList<>();
|
||||
for (Player player : Grasscutter.getGameServer().getPlayers().values()) {
|
||||
playerNames.add(player.getNickname());
|
||||
playerList.playerMap.put(player.getUid(), player.getNickname());
|
||||
}
|
||||
playerList.playerMap.remove(playerQuitEvent.getPlayer().getUid());
|
||||
playerNames.remove(playerQuitEvent.getPlayer().getNickname());
|
||||
playerList.playerList = playerNames;
|
||||
SocketClient.sendPacket(playerList);
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
package com.mojo.consoleplus;
|
||||
|
||||
import com.mojo.consoleplus.command.PluginCommand;
|
||||
import com.mojo.consoleplus.forms.RequestAuth;
|
||||
import com.mojo.consoleplus.forms.RequestJson;
|
||||
import com.mojo.consoleplus.forms.ResponseAuth;
|
||||
import com.mojo.consoleplus.forms.ResponseJson;
|
||||
import com.mojo.consoleplus.socket.SocketData;
|
||||
import com.mojo.consoleplus.socket.SocketDataWait;
|
||||
import com.mojo.consoleplus.socket.SocketServer;
|
||||
import com.mojo.consoleplus.socket.packet.HttpPacket;
|
||||
import com.mojo.consoleplus.socket.packet.OtpPacket;
|
||||
import com.mojo.consoleplus.socket.packet.player.Player;
|
||||
import com.mojo.consoleplus.socket.packet.player.PlayerEnum;
|
||||
import com.mojo.consoleplus.socket.packet.player.PlayerList;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.server.http.Router;
|
||||
import express.Express;
|
||||
import express.http.Request;
|
||||
import express.http.Response;
|
||||
import io.javalin.Javalin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.lang.Integer.parseInt;
|
||||
import static java.lang.Long.parseLong;
|
||||
|
||||
|
||||
public final class RequestOnlyHttpHandler implements Router {
|
||||
// private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
@Override public void applyRoutes(Express app, Javalin handle) {
|
||||
app.post("/mojoplus/api", RequestOnlyHttpHandler::processRequest);
|
||||
app.post("/mojoplus/auth", RequestOnlyHttpHandler::requestKey);
|
||||
}
|
||||
|
||||
|
||||
public static void processRequest(Request req, Response res) throws IOException {
|
||||
RequestJson request = req.body(RequestJson.class);
|
||||
res.type("application/json");
|
||||
String player = null;
|
||||
int uid = -1;
|
||||
|
||||
if (request.k2 != null) { // version 2 token
|
||||
long expire;
|
||||
String hashDigest;
|
||||
uid = parseInt(request.k2.split(":")[0]);
|
||||
expire = parseLong(request.k2.split(":")[1]);
|
||||
hashDigest = request.k2.split(":")[2];
|
||||
if (ConsolePlus.authHandler.auth(uid, expire, hashDigest)){
|
||||
player = SocketData.getPlayer(uid);
|
||||
}
|
||||
}
|
||||
|
||||
if (player != null) {
|
||||
SocketDataWait<HttpPacket> wait = null;
|
||||
switch (request.request){
|
||||
case "invoke":
|
||||
wait = new SocketDataWait<>(2000) {
|
||||
@Override
|
||||
public void run() {}
|
||||
@Override
|
||||
public HttpPacket initData(HttpPacket data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void timeout() {
|
||||
res.json(new ResponseJson("timeout", 500));
|
||||
}
|
||||
};
|
||||
try{
|
||||
// TODO: Enable execut commands to third party
|
||||
Player p = new Player();
|
||||
p.type = PlayerEnum.RunCommand;
|
||||
p.uid = uid;
|
||||
p.data = request.payload;
|
||||
SocketServer.sendUidPacket(uid, p, wait);
|
||||
} catch (Exception e) {
|
||||
res.json(new ResponseJson("error", 500, e.getStackTrace().toString()));
|
||||
break;
|
||||
}
|
||||
case "ping":
|
||||
// res.json(new ResponseJson("success", 200));
|
||||
if (wait == null) {
|
||||
res.json(new ResponseJson("success", 200, null));
|
||||
} else {
|
||||
var data = wait.getData();
|
||||
if (data == null) {
|
||||
res.json(new ResponseJson("timeout", 500));
|
||||
} else {
|
||||
res.json(new ResponseJson(data.message, data.code, data.data));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
res.json(new ResponseJson("400 Bad Request", 400));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(new ResponseJson("403 Forbidden", 403));
|
||||
}
|
||||
|
||||
public static void requestKey(Request req, Response res) throws IOException {
|
||||
RequestAuth request = req.body(RequestAuth.class);
|
||||
if (request.otp != null && !request.otp.equals("")) {
|
||||
if (PluginCommand.getInstance().tickets.get(request.otp) == null) {
|
||||
res.json(new ResponseAuth(404, "Not found", null));
|
||||
return;
|
||||
}
|
||||
String key = SocketData.tickets.get(request.otp).key;
|
||||
if (key == null){
|
||||
res.json(new ResponseAuth(403, "Not ready yet", null));
|
||||
} else {
|
||||
SocketData.tickets.remove(request.otp);
|
||||
res.json(new ResponseAuth(200, "", key));
|
||||
}
|
||||
return;
|
||||
} else if (request.uid != 0) {
|
||||
String otp = new DecimalFormat("000000").format(new Random().nextInt(999999));
|
||||
while (PluginCommand.getInstance().tickets.containsKey(otp)){
|
||||
otp = new DecimalFormat("000000").format(new Random().nextInt(999999));
|
||||
}
|
||||
String targetPlayer = SocketData.getPlayer(request.uid);
|
||||
if (targetPlayer == null){
|
||||
res.json(new ResponseAuth(404, "Not found", null));
|
||||
return;
|
||||
}
|
||||
var otpPacket = new OtpPacket(request.uid, otp, System.currentTimeMillis() / 1000 + 300, true);
|
||||
if (!SocketServer.sendPacket(SocketData.getPlayerInServer(request.uid), otpPacket)) {
|
||||
res.json(new ResponseAuth(500, "Send otp to server failed.", null));
|
||||
return;
|
||||
}
|
||||
SocketData.tickets.put(otp, otpPacket);
|
||||
res.json(new ResponseAuth(201, "Code generated", otp));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.mojo.consoleplus.socket.SocketClient;
|
||||
import com.mojo.consoleplus.socket.packet.OtpPacket;
|
||||
import emu.grasscutter.command.Command;
|
||||
import emu.grasscutter.command.CommandHandler;
|
||||
import emu.grasscutter.game.mail.Mail;
|
||||
@ -59,7 +61,9 @@ public class PluginCommand implements CommandHandler {
|
||||
}
|
||||
CommandHandler.sendMessage(sender, ConsolePlus.config.responseMessageThird.replace("{{OTP}}", otp));
|
||||
flushTicket();
|
||||
tickets.put(otp, new Ticket(sender, targetPlayer, System.currentTimeMillis()/ 1000 + 300));
|
||||
var time = System.currentTimeMillis()/ 1000 + 300;
|
||||
SocketClient.sendPacket(new OtpPacket(targetPlayer.getUid(), otp, time, false));
|
||||
tickets.put(otp, new Ticket(sender, targetPlayer, time));
|
||||
return;
|
||||
}
|
||||
String link_type = "webview";
|
||||
@ -140,6 +144,7 @@ public class PluginCommand implements CommandHandler {
|
||||
for (String otp : tickets.keySet()) {
|
||||
if (curtime > tickets.get(otp).expire) {
|
||||
tickets.remove(otp);
|
||||
SocketClient.sendPacket(new OtpPacket(otp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ public class MojoConfig {
|
||||
public String responseMessageThird = "[MojoConsole] You are trying to obtain link for third-party user, please ask him/her send \"/mojo {{OTP}}\" to server in-game";
|
||||
public String responseMessageError = "[MojoConsole] Invalid argument.";
|
||||
public String responseMessageSuccess = "[MojoConsole] Success!";
|
||||
public String socketToken = "";
|
||||
public int socketPort = 7812;
|
||||
public String socketHost = "127.0.0.1";
|
||||
|
||||
static public class MailTemplate {
|
||||
public String title = "Mojo Console Link";
|
||||
|
@ -0,0 +1,231 @@
|
||||
package com.mojo.consoleplus.socket;
|
||||
|
||||
import com.mojo.consoleplus.ConsolePlus;
|
||||
import com.mojo.consoleplus.command.PluginCommand;
|
||||
import com.mojo.consoleplus.config.MojoConfig;
|
||||
import com.mojo.consoleplus.socket.packet.*;
|
||||
import com.mojo.consoleplus.socket.packet.player.Player;
|
||||
import com.mojo.consoleplus.socket.packet.player.PlayerList;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.command.CommandMap;
|
||||
import emu.grasscutter.utils.MessageHandler;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
// Socket 客户端
|
||||
public class SocketClient {
|
||||
public static ClientThread clientThread;
|
||||
|
||||
public static Logger mLogger;
|
||||
|
||||
public static Timer timer;
|
||||
|
||||
public static boolean connect = false;
|
||||
|
||||
public static ReceiveThread receiveThread;
|
||||
|
||||
// 连接服务器
|
||||
public static void connectServer() {
|
||||
if (connect) return;
|
||||
MojoConfig config = ConsolePlus.config;
|
||||
mLogger = ConsolePlus.logger;
|
||||
clientThread = new ClientThread(config.socketHost, config.socketPort);
|
||||
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
}
|
||||
timer = new Timer();
|
||||
timer.schedule(new SendHeartBeatPacket(), 500);
|
||||
timer.schedule(new SendPlayerListPacket(), 1000);
|
||||
}
|
||||
|
||||
// 发送数据包
|
||||
public static boolean sendPacket(BasePacket packet) {
|
||||
var p = SocketUtils.getPacket(packet);
|
||||
if (!clientThread.sendPacket(p)) {
|
||||
mLogger.warn("[Mojo Console] Send packet to server failed");
|
||||
mLogger.info("[Mojo Console] Reconnect to server");
|
||||
connect = false;
|
||||
connectServer();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 发送数据包带数据包ID
|
||||
public static boolean sendPacket(BasePacket packet, String packetID) {
|
||||
if (!clientThread.sendPacket(SocketUtils.getPacketAndPackID(packet, packetID))) {
|
||||
mLogger.warn("[Mojo Console] Send packet to server failed");
|
||||
mLogger.info("[Mojo Console] Reconnect to server");
|
||||
connect = false;
|
||||
connectServer();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 心跳包发送
|
||||
private static class SendHeartBeatPacket extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
if (connect) {
|
||||
sendPacket(new HeartBeat("Pong"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SendPlayerListPacket extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
if (connect) {
|
||||
PlayerList playerList = new PlayerList();
|
||||
playerList.player = Grasscutter.getGameServer().getPlayers().size();
|
||||
ArrayList<String> playerNames = new ArrayList<>();
|
||||
for (emu.grasscutter.game.player.Player player : Grasscutter.getGameServer().getPlayers().values()) {
|
||||
playerNames.add(player.getNickname());
|
||||
playerList.playerMap.put(player.getUid(), player.getNickname());
|
||||
}
|
||||
playerList.playerList = playerNames;
|
||||
sendPacket(playerList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 数据包接收
|
||||
private static class ReceiveThread extends Thread {
|
||||
private InputStream is;
|
||||
private boolean exit;
|
||||
|
||||
public ReceiveThread(Socket socket) {
|
||||
try {
|
||||
is = socket.getInputStream();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//noinspection InfiniteLoopStatement
|
||||
while (true) {
|
||||
try {
|
||||
if (exit) return;
|
||||
String data = SocketUtils.readString(is);
|
||||
Packet packet = Grasscutter.getGsonFactory().fromJson(data, Packet.class);
|
||||
switch (packet.type) {
|
||||
// 玩家类
|
||||
case Player:
|
||||
var player = Grasscutter.getGsonFactory().fromJson(packet.data, Player.class);
|
||||
switch (player.type) {
|
||||
// 运行命令
|
||||
case RunCommand -> {
|
||||
var command = player.data;
|
||||
var playerData = ConsolePlus.getInstance().getServer().getPlayerByUid(player.uid);
|
||||
if (playerData == null) {
|
||||
sendPacket(new HttpPacket(404, "Player not found."), packet.packetID);
|
||||
return;
|
||||
}
|
||||
// Player MessageHandler do not support concurrency
|
||||
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
synchronized (playerData) {
|
||||
try {
|
||||
var resultCollector = new MessageHandler();
|
||||
playerData.setMessageHandler(resultCollector);
|
||||
CommandMap.getInstance().invoke(playerData, playerData, command);
|
||||
sendPacket(new HttpPacket(200, resultCollector.getMessage()), packet.packetID);
|
||||
} catch (Exception e) {
|
||||
mLogger.warn("Run command failed.", e);
|
||||
sendPacket(new HttpPacket(500, "error", e.getLocalizedMessage()), packet.packetID);
|
||||
} finally {
|
||||
playerData.setMessageHandler(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 发送信息
|
||||
case DropMessage -> {
|
||||
var playerData = ConsolePlus.getInstance().getServer().getPlayerByUid(player.uid);
|
||||
if (playerData == null) {
|
||||
return;
|
||||
}
|
||||
playerData.dropMessage(player.data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OtpPacket:
|
||||
var otpPacket = Grasscutter.getGsonFactory().fromJson(packet.data, OtpPacket.class);
|
||||
PluginCommand.getInstance().tickets.put(otpPacket.otp, new PluginCommand.Ticket(Grasscutter.getGameServer().getPlayerByUid(otpPacket.uid), otpPacket.expire, otpPacket.api));
|
||||
case Signature:
|
||||
var signaturePacket = Grasscutter.getGsonFactory().fromJson(packet.data, SignaturePacket.class);
|
||||
ConsolePlus.authHandler.setSignature(signaturePacket.signature);
|
||||
break;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
if (!sendPacket(new HeartBeat("Pong"))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
exit = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 客户端连接线程
|
||||
private static class ClientThread extends Thread {
|
||||
private final String ip;
|
||||
private final int port;
|
||||
private Socket socket;
|
||||
private OutputStream os;
|
||||
|
||||
public ClientThread(String ip, int port) {
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
start();
|
||||
}
|
||||
|
||||
public Socket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public boolean sendPacket(String string) {
|
||||
return SocketUtils.writeString(os, string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (receiveThread != null) {
|
||||
receiveThread.exit();
|
||||
}
|
||||
|
||||
socket = new Socket(ip, port);
|
||||
connect = true;
|
||||
os = socket.getOutputStream();
|
||||
mLogger.info("[Mojo Console] Connect to server: " + ip + ":" + port);
|
||||
SocketClient.sendPacket(new AuthPacket(ConsolePlus.config.socketToken));
|
||||
receiveThread = new ReceiveThread(socket);
|
||||
} catch (IOException e) {
|
||||
connect = false;
|
||||
mLogger.warn("[Mojo Console] Connect to server failed: " + ip + ":" + port);
|
||||
mLogger.warn("[Mojo Console] Retry connecting to the server after 15 seconds");
|
||||
try {
|
||||
Thread.sleep(15000);
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
connectServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.mojo.consoleplus.socket;
|
||||
|
||||
import com.mojo.consoleplus.socket.packet.OtpPacket;
|
||||
import com.mojo.consoleplus.socket.packet.player.PlayerList;
|
||||
import org.luaj.vm2.ast.Str;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
// Socket 数据保存
|
||||
public class SocketData {
|
||||
public static HashMap<String, PlayerList> playerList = new HashMap<>();
|
||||
|
||||
public static HashMap<String, OtpPacket> tickets = new HashMap<>();
|
||||
|
||||
public static String getPlayer(int uid) {
|
||||
for (PlayerList player : playerList.values()) {
|
||||
if (player.playerMap.get(uid) != null) {
|
||||
return player.playerMap.get(uid);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getPlayerInServer(int uid) {
|
||||
AtomicReference<String> ret = new AtomicReference<>();
|
||||
playerList.forEach((key, value) -> {
|
||||
if (value.playerMap.get(uid) != null) {
|
||||
ret.set(key);
|
||||
}
|
||||
});
|
||||
return ret.get();
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.mojo.consoleplus.socket;
|
||||
|
||||
// 异步等待数据返回
|
||||
public abstract class SocketDataWait<T> extends Thread {
|
||||
public T data;
|
||||
public long timeout;
|
||||
public long time;
|
||||
public String uid;
|
||||
|
||||
/**
|
||||
* 异步等待数据返回
|
||||
* @param timeout 超时时间
|
||||
*/
|
||||
public SocketDataWait(long timeout) {
|
||||
this.timeout = timeout;
|
||||
start();
|
||||
}
|
||||
|
||||
public abstract void run();
|
||||
|
||||
/**
|
||||
* 数据处理
|
||||
* @param data 数据
|
||||
* @return 处理后的数据
|
||||
*/
|
||||
public abstract T initData(T data);
|
||||
|
||||
/**
|
||||
* 超时回调
|
||||
*/
|
||||
public abstract void timeout();
|
||||
|
||||
/**
|
||||
* 异步设置数据
|
||||
* @param data 数据
|
||||
*/
|
||||
public void setData(Object data) {
|
||||
this.data = initData((T) data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异步数据(此操作会一直堵塞直到获取到数据)
|
||||
* @return 数据
|
||||
*/
|
||||
public T getData() {
|
||||
while (data == null) {
|
||||
try {
|
||||
time += 100;
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (time > timeout) {
|
||||
timeout();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
@ -0,0 +1,239 @@
|
||||
package com.mojo.consoleplus.socket;
|
||||
|
||||
import com.mojo.consoleplus.ConsolePlus;
|
||||
import com.mojo.consoleplus.socket.packet.*;
|
||||
import com.mojo.consoleplus.socket.packet.player.PlayerList;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
// Socket 服务器
|
||||
public class SocketServer {
|
||||
// 客户端超时时间
|
||||
private static final int TIMEOUT = 5000;
|
||||
private static final HashMap<String, ClientThread> clientList = new HashMap<>();
|
||||
|
||||
private static final HashMap<String, Integer> clientTimeout = new HashMap<>();
|
||||
private static Logger mLogger;
|
||||
|
||||
public static void startServer() {
|
||||
try {
|
||||
int port = ConsolePlus.config.socketPort;
|
||||
mLogger = ConsolePlus.logger;
|
||||
new Timer().schedule(new SocketClientCheck(), 500);
|
||||
new WaitClientConnect(port);
|
||||
} catch (Throwable e) {
|
||||
mLogger.error("[Mojo Console] Socket server start failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 向全部客户端发送数据
|
||||
public static boolean sendAllPacket(BasePacket packet) {
|
||||
var p = SocketUtils.getPacket(packet);
|
||||
HashMap<String, ClientThread> old = (HashMap<String, ClientThread>) clientList.clone();
|
||||
for (var client : old.entrySet()) {
|
||||
if (!client.getValue().sendPacket(p)) {
|
||||
mLogger.warn("[Mojo Console] Send packet to client {} failed", client.getKey());
|
||||
clientList.remove(client.getKey());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据地址发送到相应的客户端
|
||||
public static boolean sendPacket(String address, BasePacket packet) {
|
||||
var p = SocketUtils.getPacket(packet);
|
||||
var client = clientList.get(address);
|
||||
if (client != null) {
|
||||
if (client.sendPacket(p)) {
|
||||
return true;
|
||||
}
|
||||
mLogger.warn("[Mojo Console] Send packet to client {} failed", address);
|
||||
clientList.remove(address);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据Uid发送到相应的客户端异步返回数据
|
||||
public static boolean sendUidPacket(Integer playerId, BasePacket player, SocketDataWait<?> socketDataWait) {
|
||||
var p = SocketUtils.getPacketAndPackID(player);
|
||||
var clientID = SocketData.getPlayerInServer(playerId);
|
||||
if (clientID == null) return false;
|
||||
var client = clientList.get(clientID);
|
||||
if (client != null) {
|
||||
socketDataWait.uid = p.get(0);
|
||||
if (!client.sendPacket(p.get(1), socketDataWait)) {
|
||||
mLogger.warn("[Mojo Console] Send packet to client {} failed", clientID);
|
||||
clientList.remove(clientID);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 客户端超时检测
|
||||
private static class SocketClientCheck extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
HashMap<String, Integer> old = (HashMap<String, Integer>) clientTimeout.clone();
|
||||
for (var client : old.entrySet()) {
|
||||
var clientID = client.getKey();
|
||||
var clientTime = client.getValue();
|
||||
if (clientTime > TIMEOUT) {
|
||||
mLogger.info("[Mojo Console] Client {} timeout, disconnect.", clientID);
|
||||
clientList.remove(clientID);
|
||||
clientTimeout.remove(clientID);
|
||||
SocketData.playerList.remove(clientID);
|
||||
} else {
|
||||
clientTimeout.put(clientID, clientTime + 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 客户端数据包处理
|
||||
private static class ClientThread extends Thread {
|
||||
private final Socket socket;
|
||||
private InputStream is;
|
||||
private OutputStream os;
|
||||
private final String address;
|
||||
private final String token;
|
||||
private boolean auth = false;
|
||||
|
||||
private final HashMap<String, SocketDataWait<?>> socketDataWaitList = new HashMap<>();
|
||||
|
||||
public ClientThread(Socket accept) {
|
||||
socket = accept;
|
||||
address = socket.getInetAddress() + ":" + socket.getPort();
|
||||
token = ConsolePlus.config.socketToken;
|
||||
try {
|
||||
is = accept.getInputStream();
|
||||
os = accept.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
public Socket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
// 发送数据包
|
||||
public boolean sendPacket(String packet) {
|
||||
return SocketUtils.writeString(os, packet);
|
||||
}
|
||||
|
||||
// 发送异步数据包
|
||||
public boolean sendPacket(String packet, SocketDataWait<?> socketDataWait) {
|
||||
if (SocketUtils.writeString(os, packet)) {
|
||||
socketDataWaitList.put(socketDataWait.uid, socketDataWait);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// noinspection InfiniteLoopStatement
|
||||
while (true) {
|
||||
try {
|
||||
String data = SocketUtils.readString(is);
|
||||
Packet packet = Grasscutter.getGsonFactory().fromJson(data, Packet.class);
|
||||
if (packet.type == PacketEnum.AuthPacket) {
|
||||
AuthPacket authPacket = Grasscutter.getGsonFactory().fromJson(packet.data, AuthPacket.class);
|
||||
if (authPacket.token.equals(token)) {
|
||||
mLogger.info("[Mojo Console] Client {} auth success.", address);
|
||||
auth = true;
|
||||
clientList.put(address, this);
|
||||
clientTimeout.put(address, 0);
|
||||
sendPacket(SocketUtils.getPacket(new SignaturePacket(ConsolePlus.authHandler.getSignature())));
|
||||
} else {
|
||||
mLogger.error("[Mojo Console] AuthPacket: {} auth filed.", address);
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!auth) {
|
||||
mLogger.error("[Mojo Console] AuthPacket: {} not auth", address);
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
switch (packet.type) {
|
||||
// 缓存玩家列表
|
||||
case PlayerList -> {
|
||||
PlayerList playerList = Grasscutter.getGsonFactory().fromJson(packet.data, PlayerList.class);
|
||||
SocketData.playerList.put(address, playerList);
|
||||
}
|
||||
// Http信息返回
|
||||
case HttpPacket -> {
|
||||
HttpPacket httpPacket = Grasscutter.getGsonFactory().fromJson(packet.data, HttpPacket.class);
|
||||
var socketWait = socketDataWaitList.get(packet.packetID);
|
||||
if (socketWait == null) {
|
||||
mLogger.error("[Mojo Console] HttpPacket: {} not found", packet.packetID);
|
||||
return;
|
||||
}
|
||||
socketWait.setData(httpPacket);
|
||||
socketDataWaitList.remove(packet.packetID);
|
||||
}
|
||||
case OtpPacket -> {
|
||||
OtpPacket otpPacket = Grasscutter.getGsonFactory().fromJson(packet.data, OtpPacket.class);
|
||||
if (otpPacket.remove) {
|
||||
SocketData.tickets.remove(otpPacket.otp);
|
||||
} else {
|
||||
SocketData.tickets.put(otpPacket.otp, otpPacket);
|
||||
}
|
||||
}
|
||||
// 心跳包
|
||||
case HeartBeat -> {
|
||||
clientTimeout.put(address, 0);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
mLogger.error("[Mojo Console] Client {} disconnect.", address);
|
||||
clientList.remove(address);
|
||||
clientTimeout.remove(address);
|
||||
SocketData.playerList.remove(address);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待客户端连接
|
||||
private static class WaitClientConnect extends Thread {
|
||||
ServerSocket socketServer;
|
||||
|
||||
public WaitClientConnect(int port) throws IOException {
|
||||
socketServer = new ServerSocket(port);
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mLogger.info("[Mojo Console] Start socket server on port " + socketServer.getLocalPort());
|
||||
// noinspection InfiniteLoopStatement
|
||||
while (true) {
|
||||
try {
|
||||
Socket accept = socketServer.accept();
|
||||
String address = accept.getInetAddress() + ":" + accept.getPort();
|
||||
mLogger.info("[Mojo Console] Client connect: " + address);
|
||||
new ClientThread(accept);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package com.mojo.consoleplus.socket;
|
||||
|
||||
import com.mojo.consoleplus.ConsolePlus;
|
||||
import com.mojo.consoleplus.socket.packet.BasePacket;
|
||||
import com.mojo.consoleplus.socket.packet.Packet;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
// Socket 工具类
|
||||
public class SocketUtils {
|
||||
|
||||
/**
|
||||
* 获取打包后的数据包
|
||||
* @param bPacket 数据包
|
||||
* @return 打包后的数据包
|
||||
*/
|
||||
public static String getPacket(BasePacket bPacket) {
|
||||
Packet packet = new Packet();
|
||||
packet.type = bPacket.getType();
|
||||
packet.data = bPacket.getPacket();
|
||||
packet.packetID = UUID.randomUUID().toString();
|
||||
return Grasscutter.getGsonFactory().toJson(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取打包后的数据包
|
||||
* @param bPacket BasePacket
|
||||
* @return list[0] 是包ID, list[1] 是数据包
|
||||
*/
|
||||
public static List<String> getPacketAndPackID(BasePacket bPacket) {
|
||||
Packet packet = new Packet();
|
||||
packet.type = bPacket.getType();
|
||||
packet.data = bPacket.getPacket();
|
||||
packet.packetID = UUID.randomUUID().toString();
|
||||
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add(packet.packetID);
|
||||
list.add(Grasscutter.getGsonFactory().toJson(packet));
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取打包后的数据包
|
||||
* @param bPacket 数据包
|
||||
* @param packetID 数据包ID
|
||||
* @return 打包后的数据包
|
||||
*/
|
||||
public static String getPacketAndPackID(BasePacket bPacket, String packetID) {
|
||||
Packet packet = new Packet();
|
||||
packet.type = bPacket.getType();
|
||||
packet.data = bPacket.getPacket();
|
||||
packet.packetID = packetID;
|
||||
return Grasscutter.getGsonFactory().toJson(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读整数
|
||||
* @param is 输入流
|
||||
* @return 整数
|
||||
*/
|
||||
public static int readInt(InputStream is) {
|
||||
int[] values = new int[4];
|
||||
try {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
values[i] = is.read();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return values[0]<<24 | values[1]<<16 | values[2]<<8 | values[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* 写整数
|
||||
* @param os 输出流
|
||||
* @param value 整数
|
||||
*/
|
||||
public static void writeInt(OutputStream os, int value) {
|
||||
int[] values = new int[4];
|
||||
values[0] = (value>>24)&0xFF;
|
||||
values[1] = (value>>16)&0xFF;
|
||||
values[2] = (value>>8)&0xFF;
|
||||
values[3] = (value)&0xFF;
|
||||
|
||||
try{
|
||||
for (int i = 0; i < 4; i++) {
|
||||
os.write(values[i]);
|
||||
}
|
||||
}catch (IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读字符串
|
||||
* @param is 输入流
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String readString(InputStream is) {
|
||||
int len = readInt(is);
|
||||
byte[] sByte = new byte[len];
|
||||
try {
|
||||
is.read(sByte);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String s = new String(sByte);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写字符串
|
||||
* @param os 输出流
|
||||
* @param s 字符串
|
||||
* @return 是否成功
|
||||
*/
|
||||
public static boolean writeString(OutputStream os,String s) {
|
||||
try {
|
||||
byte[] bytes = s.getBytes();
|
||||
int len = bytes.length;
|
||||
writeInt(os,len);
|
||||
os.write(bytes);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.mojo.consoleplus.socket.packet;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
public class AuthPacket extends BasePacket {
|
||||
public String token;
|
||||
|
||||
public AuthPacket(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPacket() {
|
||||
return Grasscutter.getGsonFactory().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEnum getType() {
|
||||
return PacketEnum.AuthPacket;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.mojo.consoleplus.socket.packet;
|
||||
|
||||
// 基本数据包
|
||||
public abstract class BasePacket {
|
||||
public abstract String getPacket();
|
||||
|
||||
public abstract PacketEnum getType();
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.mojo.consoleplus.socket.packet;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
// 心跳包
|
||||
public class HeartBeat extends BasePacket {
|
||||
public String ping;
|
||||
|
||||
public HeartBeat(String ping) {
|
||||
this.ping = ping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPacket() {
|
||||
return Grasscutter.getGsonFactory().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEnum getType() {
|
||||
return PacketEnum.HeartBeat;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.mojo.consoleplus.socket.packet;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
// http返回数据
|
||||
public class HttpPacket extends BasePacket {
|
||||
public int code;
|
||||
public String message;
|
||||
public String data;
|
||||
|
||||
public HttpPacket(int code, String message, String data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public HttpPacket(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPacket() {
|
||||
return Grasscutter.getGsonFactory().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEnum getType() {
|
||||
return PacketEnum.HttpPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HttpPacket [code=" + code + ", message=" + message + ", data=" + data + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.mojo.consoleplus.socket.packet;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
public class OtpPacket extends BasePacket {
|
||||
public int uid;
|
||||
public String otp;
|
||||
public long expire;
|
||||
public Boolean api;
|
||||
public String key;
|
||||
|
||||
public boolean remove = false;
|
||||
|
||||
public OtpPacket(int uid, String opt, long expire, Boolean api) {
|
||||
this.uid = uid;
|
||||
this.expire = expire;
|
||||
this.api = api;
|
||||
this.otp = opt;
|
||||
}
|
||||
|
||||
public OtpPacket(String opt) {
|
||||
this.otp = opt;
|
||||
remove = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPacket() {
|
||||
return Grasscutter.getGsonFactory().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEnum getType() {
|
||||
return PacketEnum.OtpPacket;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.mojo.consoleplus.socket.packet;
|
||||
|
||||
// 数据包结构
|
||||
public class Packet {
|
||||
public PacketEnum type;
|
||||
public String data;
|
||||
public String packetID;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Packet [type=" + type + ", data=" + data + ", packetID=" + packetID + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.mojo.consoleplus.socket.packet;
|
||||
|
||||
// 数据包类型列表
|
||||
public enum PacketEnum {
|
||||
PlayerList,
|
||||
Player,
|
||||
HttpPacket,
|
||||
HeartBeat,
|
||||
Signature,
|
||||
OtpPacket,
|
||||
AuthPacket
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.mojo.consoleplus.socket.packet;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
public class SignaturePacket extends BasePacket {
|
||||
public String signature;
|
||||
|
||||
public SignaturePacket(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPacket() {
|
||||
return Grasscutter.getGsonFactory().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEnum getType() {
|
||||
return PacketEnum.Signature;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.mojo.consoleplus.socket.packet.player;
|
||||
|
||||
import com.mojo.consoleplus.socket.SocketServer;
|
||||
import com.mojo.consoleplus.socket.packet.BasePacket;
|
||||
import com.mojo.consoleplus.socket.packet.PacketEnum;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
// 玩家操作类
|
||||
public class Player extends BasePacket {
|
||||
public PlayerEnum type;
|
||||
public int uid;
|
||||
public String data;
|
||||
|
||||
@Override
|
||||
public String getPacket() {
|
||||
return Grasscutter.getGsonFactory().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEnum getType() {
|
||||
return PacketEnum.Player;
|
||||
}
|
||||
|
||||
public static void dropMessage(int uid, String str) {
|
||||
Player p = new Player();
|
||||
p.type = PlayerEnum.DropMessage;
|
||||
p.uid = uid;
|
||||
p.data = str;
|
||||
SocketServer.sendAllPacket(p);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.mojo.consoleplus.socket.packet.player;
|
||||
|
||||
// 玩家操作列表
|
||||
public enum PlayerEnum {
|
||||
DropMessage,
|
||||
RunCommand
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.mojo.consoleplus.socket.packet.player;
|
||||
|
||||
import com.mojo.consoleplus.socket.packet.BasePacket;
|
||||
import com.mojo.consoleplus.socket.packet.PacketEnum;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// 玩家列表信息
|
||||
public class PlayerList extends BasePacket {
|
||||
public int player = -1;
|
||||
public List<String> playerList = new ArrayList<>();
|
||||
public Map<Integer, String> playerMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public String getPacket() {
|
||||
return Grasscutter.getGsonFactory().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEnum getType() {
|
||||
return PacketEnum.PlayerList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlayerList [player=" + player + ", playerList=" + playerList + ", playerMap=" + playerMap + "]";
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user