mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-03-13 01:47:18 +08:00
Implement fetching a player across servers & Add a chainable JsonObject
useful for plugins! might be used in grasscutter eventually
This commit is contained in:
parent
273dadd4ba
commit
d35777d969
@ -76,6 +76,7 @@ import emu.grasscutter.server.game.GameSession.SessionState;
|
||||
import emu.grasscutter.server.packet.send.*;
|
||||
import emu.grasscutter.utils.*;
|
||||
import emu.grasscutter.utils.helpers.DateHelper;
|
||||
import emu.grasscutter.utils.objects.FieldFetch;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
@ -92,7 +93,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
|
||||
|
||||
@Entity(value = "players", useDiscriminator = false)
|
||||
public class Player implements PlayerHook {
|
||||
public class Player implements PlayerHook, FieldFetch {
|
||||
@Id private int id;
|
||||
@Indexed(options = @IndexOptions(unique = true))
|
||||
@Getter private String accountId;
|
||||
|
@ -1,7 +1,5 @@
|
||||
package emu.grasscutter.server.dispatch;
|
||||
|
||||
import static emu.grasscutter.config.Configuration.DISPATCH_INFO;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
@ -12,6 +10,12 @@ 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;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
@ -22,11 +26,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.Getter;
|
||||
import org.java_websocket.WebSocket;
|
||||
import org.java_websocket.client.WebSocketClient;
|
||||
import org.java_websocket.handshake.ServerHandshake;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static emu.grasscutter.config.Configuration.DISPATCH_INFO;
|
||||
|
||||
public final class DispatchClient extends WebSocketClient implements IDispatcher {
|
||||
@Getter private final Logger logger = Grasscutter.getLogger();
|
||||
@ -43,6 +44,7 @@ public final class DispatchClient extends WebSocketClient implements IDispatcher
|
||||
this.registerHandler(PacketIds.GachaHistoryReq, this::fetchGachaHistory);
|
||||
this.registerHandler(PacketIds.GmTalkReq, this::handleHandbookAction);
|
||||
this.registerHandler(PacketIds.GetPlayerFieldsReq, this::fetchPlayerFields);
|
||||
this.registerHandler(PacketIds.GetPlayerByAccountReq, this::fetchPlayerByAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +110,7 @@ public final class DispatchClient extends WebSocketClient implements IDispatcher
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the fields of an online player.
|
||||
* Fetches the fields of a player.
|
||||
*
|
||||
* @param socket The socket the packet was received from.
|
||||
* @param object The packet data.
|
||||
@ -131,6 +133,30 @@ public final class DispatchClient extends WebSocketClient implements IDispatcher
|
||||
this.sendMessage(PacketIds.GetPlayerFieldsRsp, DispatchUtils.getPlayerFields(playerId, fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the fields of a player by the account.
|
||||
*
|
||||
* @param socket The socket the packet was received from.
|
||||
* @param object The packet data.
|
||||
*/
|
||||
private void fetchPlayerByAccount(WebSocket socket, JsonElement object) {
|
||||
var message = IDispatcher.decode(object);
|
||||
var accountId = message.get("accountId").getAsString();
|
||||
var fieldsRaw = message.get("fields").getAsJsonArray();
|
||||
|
||||
// Get the player with the specified ID.
|
||||
var player = Grasscutter.getGameServer().getPlayerByAccountId(accountId);
|
||||
if (player == null) return;
|
||||
|
||||
// Convert the fields array.
|
||||
var fieldsList = new ArrayList<String>();
|
||||
for (var field : fieldsRaw) fieldsList.add(field.getAsString());
|
||||
var fields = fieldsList.toArray(new String[0]);
|
||||
|
||||
// Return the response object.
|
||||
this.sendMessage(PacketIds.GetPlayerByAccountRsp, DispatchUtils.getPlayerByAccount(accountId, fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a serialized encrypted message to the server.
|
||||
*
|
||||
|
@ -1,13 +1,15 @@
|
||||
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 emu.grasscutter.utils.objects.JObject;
|
||||
import org.java_websocket.WebSocket;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -17,14 +19,15 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import org.java_websocket.WebSocket;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static emu.grasscutter.config.Configuration.DISPATCH_INFO;
|
||||
|
||||
public interface IDispatcher {
|
||||
Gson JSON =
|
||||
new GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter(byte[].class, new ByteArrayAdapter())
|
||||
.registerTypeAdapter(JObject.class, new JObject.Adapter())
|
||||
.create();
|
||||
|
||||
Function<JsonElement, JsonObject> DEFAULT_PARSER =
|
||||
|
@ -15,4 +15,6 @@ public interface PacketIds {
|
||||
int GetAccountRsp = 7;
|
||||
int GetPlayerFieldsReq = 8;
|
||||
int GetPlayerFieldsRsp = 9;
|
||||
int GetPlayerByAccountReq = 10;
|
||||
int GetPlayerByAccountRsp = 11;
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import static emu.grasscutter.config.Configuration.DISPATCH_INFO;
|
||||
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest;
|
||||
@ -15,22 +12,15 @@ 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 java.lang.reflect.Field;
|
||||
import java.net.http.HttpClient;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import emu.grasscutter.utils.objects.JObject;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
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. */
|
||||
HttpClient HTTP_CLIENT =
|
||||
HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_2)
|
||||
.followRedirects(HttpClient.Redirect.ALWAYS)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* @return The dispatch URL.
|
||||
*/
|
||||
@ -167,30 +157,44 @@ public interface DispatchUtils {
|
||||
var player = Grasscutter.getGameServer().getPlayerByUid(playerId, true);
|
||||
if (player == null) yield null;
|
||||
|
||||
// Prepare field properties.
|
||||
var fieldValues = new JsonObject();
|
||||
var fieldMap = new HashMap<String, Field>();
|
||||
Arrays.stream(player.getClass().getDeclaredFields())
|
||||
.forEach(field -> fieldMap.put(field.getName(), field));
|
||||
// Return the values.
|
||||
yield player.fetchFields(fields);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Find the values of all requested fields.
|
||||
for (var fieldName : fields) {
|
||||
try {
|
||||
var field = fieldMap.get(fieldName);
|
||||
if (field == null) fieldValues.add(fieldName, JsonNull.INSTANCE);
|
||||
else {
|
||||
var wasAccessible = field.canAccess(player);
|
||||
field.setAccessible(true);
|
||||
fieldValues.add(fieldName, IDispatcher.JSON.toJsonTree(field.get(player)));
|
||||
field.setAccessible(wasAccessible);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
/**
|
||||
* Fetches the values of fields for a player.
|
||||
* Uses an account to find the player.
|
||||
* Similar to {@link DispatchUtils#getPlayerFields(int, String...)}
|
||||
*
|
||||
* @param accountId The account ID.
|
||||
* @param fields The fields to fetch.
|
||||
* @return An object holding the field values.
|
||||
*/
|
||||
@Nullable static JsonObject getPlayerByAccount(String accountId, String... fields) {
|
||||
return switch (Grasscutter.getRunMode()) {
|
||||
case DISPATCH_ONLY -> {
|
||||
// Create a request for player fields.
|
||||
var request = JObject.c()
|
||||
.add("accountId", accountId)
|
||||
.add("fields", fields);
|
||||
|
||||
// Wait for the request to complete.
|
||||
yield Grasscutter.getDispatchServer()
|
||||
.await(
|
||||
request.gson(),
|
||||
PacketIds.GetPlayerByAccountReq,
|
||||
PacketIds.GetPlayerByAccountRsp,
|
||||
IDispatcher.DEFAULT_PARSER);
|
||||
}
|
||||
case HYBRID, GAME_ONLY -> {
|
||||
// Get the player by the account.
|
||||
var player = Grasscutter.getGameServer().getPlayerByAccountId(accountId);
|
||||
if (player == null) yield null;
|
||||
|
||||
// Return the values.
|
||||
yield fieldValues;
|
||||
yield player.fetchFields(fields);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import emu.grasscutter.data.common.DynamicFloat;
|
||||
import emu.grasscutter.game.world.GridPosition;
|
||||
import emu.grasscutter.game.world.Position;
|
||||
import emu.grasscutter.utils.JsonAdapters.*;
|
||||
import emu.grasscutter.utils.objects.JObject;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@ -27,6 +28,7 @@ public final class JsonUtils {
|
||||
.registerTypeAdapter(Position.class, new PositionAdapter())
|
||||
.registerTypeAdapter(GridPosition.class, new GridPositionAdapter())
|
||||
.registerTypeAdapter(byte[].class, new ByteArrayAdapter())
|
||||
.registerTypeAdapter(JObject.class, new JObject.Adapter())
|
||||
.registerTypeAdapterFactory(new EnumTypeAdapterFactory())
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
44
src/main/java/emu/grasscutter/utils/objects/FieldFetch.java
Normal file
44
src/main/java/emu/grasscutter/utils/objects/FieldFetch.java
Normal file
@ -0,0 +1,44 @@
|
||||
package emu.grasscutter.utils.objects;
|
||||
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import emu.grasscutter.server.dispatch.IDispatcher;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
public interface FieldFetch {
|
||||
/**
|
||||
* Fetches the specified fields.
|
||||
* Serializes them into a JSON object.
|
||||
*
|
||||
* @param fields The fields to fetch.
|
||||
* @return The JSON object containing the fields.
|
||||
*/
|
||||
default JsonObject fetchFields(String... fields) {
|
||||
// Prepare field properties.
|
||||
var fieldValues = new JsonObject();
|
||||
var fieldMap = new HashMap<String, Field>();
|
||||
Arrays.stream(this.getClass().getDeclaredFields())
|
||||
.forEach(field -> fieldMap.put(field.getName(), field));
|
||||
|
||||
// Find the values of all requested fields.
|
||||
for (var fieldName : fields) {
|
||||
try {
|
||||
var field = fieldMap.get(fieldName);
|
||||
if (field == null) fieldValues.add(fieldName, JsonNull.INSTANCE);
|
||||
else {
|
||||
var wasAccessible = field.canAccess(this);
|
||||
field.setAccessible(true);
|
||||
fieldValues.add(fieldName, IDispatcher.JSON.toJsonTree(field.get(this)));
|
||||
field.setAccessible(wasAccessible);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return fieldValues;
|
||||
}
|
||||
}
|
132
src/main/java/emu/grasscutter/utils/objects/JObject.java
Normal file
132
src/main/java/emu/grasscutter/utils/objects/JObject.java
Normal file
@ -0,0 +1,132 @@
|
||||
package emu.grasscutter.utils.objects;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import emu.grasscutter.utils.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/* Replica of JsonObject. Includes chaining. */
|
||||
public final class JObject {
|
||||
/* Type adapter for gson. */
|
||||
public static class Adapter extends TypeAdapter<JObject> {
|
||||
@Override
|
||||
public void write(JsonWriter out, JObject value) throws IOException {
|
||||
out.beginObject();
|
||||
for (var entry : value.members.entrySet()) {
|
||||
out.name(entry.getKey());
|
||||
out.jsonValue(entry.getValue().toString());
|
||||
}
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JObject read(JsonReader in) throws IOException {
|
||||
var object = JObject.c();
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
object.add(in.nextName(), JsonParser.parseReader(in));
|
||||
}
|
||||
in.endObject();
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty object.
|
||||
*
|
||||
* @return The new object.
|
||||
*/
|
||||
public static JObject c() {
|
||||
return new JObject();
|
||||
}
|
||||
|
||||
private final LinkedTreeMap<String, JsonElement> members = new LinkedTreeMap<>();
|
||||
|
||||
/**
|
||||
* Adds a member to this object.
|
||||
*
|
||||
* @param name The name of the member.
|
||||
* @param value The value of the member.
|
||||
* @return This object.
|
||||
*/
|
||||
public JObject add(String name, JsonElement value) {
|
||||
this.members.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member to this object.
|
||||
*
|
||||
* @param name The name of the member.
|
||||
* @param value The value of the member.
|
||||
* @return This object.
|
||||
*/
|
||||
public JObject add(String name, String value) {
|
||||
return this.add(name, value == null ? JsonNull.INSTANCE : new JsonPrimitive(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member to this object.
|
||||
*
|
||||
* @param name The name of the member.
|
||||
* @param value The value of the member.
|
||||
* @return This object.
|
||||
*/
|
||||
public JObject add(String name, Number value) {
|
||||
return this.add(name, value == null ? JsonNull.INSTANCE : new JsonPrimitive(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member to this object.
|
||||
*
|
||||
* @param name The name of the member.
|
||||
* @param value The value of the member.
|
||||
* @return This object.
|
||||
*/
|
||||
public JObject add(String name, Boolean value) {
|
||||
return this.add(name, value == null ? JsonNull.INSTANCE : new JsonPrimitive(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member to this object.
|
||||
*
|
||||
* @param name The name of the member.
|
||||
* @param value The value of the member.
|
||||
* @return This object.
|
||||
*/
|
||||
public JObject add(String name, Character value) {
|
||||
return this.add(name, value == null ? JsonNull.INSTANCE : new JsonPrimitive(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member to this object.
|
||||
*
|
||||
* @param name The name of the member.
|
||||
* @param value The value of the member.
|
||||
* @return This object.
|
||||
*/
|
||||
public JObject add(String name, Object[] value) {
|
||||
return this.add(name, value == null ? JsonNull.INSTANCE : JsonUtils.toJson(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link JsonObject} representation of this object.
|
||||
*/
|
||||
public JsonObject gson() {
|
||||
var object = new JsonObject();
|
||||
for (var entry : this.members.entrySet()) {
|
||||
object.add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The property holder.
|
||||
*/
|
||||
public Object json() {
|
||||
return this.members;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user