line separators??

This commit is contained in:
KingRainbow44 2023-04-13 15:49:38 -04:00
parent 349f76b7d8
commit 6819ef6bdc
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
17 changed files with 2176 additions and 2176 deletions

View File

@ -1,29 +1,29 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import java.util.List; import java.util.List;
@Command( @Command(
label = "reload", label = "reload",
permission = "server.reload", permission = "server.reload",
targetRequirement = Command.TargetRequirement.NONE) targetRequirement = Command.TargetRequirement.NONE)
public final class ReloadCommand implements CommandHandler { public final class ReloadCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start")); CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start"));
Grasscutter.loadConfig(); Grasscutter.loadConfig();
Grasscutter.loadLanguage(); Grasscutter.loadLanguage();
Grasscutter.getGameServer().getGachaSystem().load(); Grasscutter.getGameServer().getGachaSystem().load();
Grasscutter.getGameServer().getDropSystem().load(); Grasscutter.getGameServer().getDropSystem().load();
Grasscutter.getGameServer().getShopSystem().load(); Grasscutter.getGameServer().getShopSystem().load();
CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done")); CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done"));
} }
} }

View File

@ -1,51 +1,51 @@
package emu.grasscutter.game.drop; package emu.grasscutter.game.drop;
public class DropData { public class DropData {
private int minWeight; private int minWeight;
private int maxWeight; private int maxWeight;
private int itemId; private int itemId;
private int minCount; private int minCount;
private int maxCount; private int maxCount;
private boolean share = false; private boolean share = false;
private boolean give = false; private boolean give = false;
public boolean isGive() { public boolean isGive() {
return give; return give;
} }
public void setGive(boolean give) { public void setGive(boolean give) {
this.give = give; this.give = give;
} }
public int getItemId() { public int getItemId() {
return itemId; return itemId;
} }
public void setItemId(int itemId) { public void setItemId(int itemId) {
this.itemId = itemId; this.itemId = itemId;
} }
public int getMinCount() { public int getMinCount() {
return minCount; return minCount;
} }
public int getMaxCount() { public int getMaxCount() {
return maxCount; return maxCount;
} }
public int getMinWeight() { public int getMinWeight() {
return minWeight; return minWeight;
} }
public int getMaxWeight() { public int getMaxWeight() {
return maxWeight; return maxWeight;
} }
public boolean isShare() { public boolean isShare() {
return share; return share;
} }
public void setIsShare(boolean share) { public void setIsShare(boolean share) {
this.share = share; this.share = share;
} }
} }

View File

@ -1,111 +1,111 @@
package emu.grasscutter.game.drop; package emu.grasscutter.game.drop;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List; import java.util.List;
public class DropSystem extends BaseGameSystem { public class DropSystem extends BaseGameSystem {
private final Int2ObjectMap<List<DropData>> dropData; private final Int2ObjectMap<List<DropData>> dropData;
public DropSystem(GameServer server) { public DropSystem(GameServer server) {
super(server); super(server);
this.dropData = new Int2ObjectOpenHashMap<>(); this.dropData = new Int2ObjectOpenHashMap<>();
this.load(); this.load();
} }
public Int2ObjectMap<List<DropData>> getDropData() { public Int2ObjectMap<List<DropData>> getDropData() {
return dropData; return dropData;
} }
public synchronized void load() { public synchronized void load() {
getDropData().clear(); getDropData().clear();
try { try {
List<DropInfo> banners = DataLoader.loadList("Drop.json", DropInfo.class); List<DropInfo> banners = DataLoader.loadList("Drop.json", DropInfo.class);
if (banners.size() > 0) { if (banners.size() > 0) {
for (DropInfo di : banners) { for (DropInfo di : banners) {
getDropData().put(di.getMonsterId(), di.getDropDataList()); getDropData().put(di.getMonsterId(), di.getDropDataList());
} }
Grasscutter.getLogger().debug("Drop data successfully loaded."); Grasscutter.getLogger().debug("Drop data successfully loaded.");
} else { } else {
Grasscutter.getLogger().error("Unable to load drop data. Drop data size is 0."); Grasscutter.getLogger().error("Unable to load drop data. Drop data size is 0.");
} }
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Unable to load drop data.", e); Grasscutter.getLogger().error("Unable to load drop data.", e);
} }
} }
private void addDropEntity( private void addDropEntity(
DropData dd, Scene dropScene, ItemData itemData, Position pos, int num, Player target) { DropData dd, Scene dropScene, ItemData itemData, Position pos, int num, Player target) {
if (!dd.isGive() if (!dd.isGive()
&& (itemData.getItemType() != ItemType.ITEM_VIRTUAL || itemData.getGadgetId() != 0)) { && (itemData.getItemType() != ItemType.ITEM_VIRTUAL || itemData.getGadgetId() != 0)) {
EntityItem entity = new EntityItem(dropScene, target, itemData, pos, num, dd.isShare()); EntityItem entity = new EntityItem(dropScene, target, itemData, pos, num, dd.isShare());
if (!dd.isShare()) dropScene.addEntityToSingleClient(target, entity); if (!dd.isShare()) dropScene.addEntityToSingleClient(target, entity);
else dropScene.addEntity(entity); else dropScene.addEntity(entity);
} else { } else {
if (target != null) { if (target != null) {
target.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true); target.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true);
} else { } else {
// target is null if items will be added are shared. no one could pick it up because of the // target is null if items will be added are shared. no one could pick it up because of the
// combination(give + shared) // combination(give + shared)
// so it will be sent to all players' inventories directly. // so it will be sent to all players' inventories directly.
dropScene dropScene
.getPlayers() .getPlayers()
.forEach( .forEach(
x -> x ->
x.getInventory() x.getInventory()
.addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true)); .addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true));
} }
} }
} }
private void processDrop(DropData dd, EntityMonster em, Player gp) { private void processDrop(DropData dd, EntityMonster em, Player gp) {
int target = Utils.randomRange(1, 10000); int target = Utils.randomRange(1, 10000);
if (target >= dd.getMinWeight() && target < dd.getMaxWeight()) { if (target >= dd.getMinWeight() && target < dd.getMaxWeight()) {
ItemData itemData = GameData.getItemDataMap().get(dd.getItemId()); ItemData itemData = GameData.getItemDataMap().get(dd.getItemId());
int num = Utils.randomRange(dd.getMinCount(), dd.getMaxCount()); int num = Utils.randomRange(dd.getMinCount(), dd.getMaxCount());
if (itemData == null) { if (itemData == null) {
return; return;
} }
if (itemData.isEquip()) { if (itemData.isEquip()) {
for (int i = 0; i < num; i++) { for (int i = 0; i < num; i++) {
float range = (2.5f + (.05f * num)); float range = (2.5f + (.05f * num));
Position pos = em.getPosition().nearby2d(range).addY(3f); Position pos = em.getPosition().nearby2d(range).addY(3f);
addDropEntity(dd, em.getScene(), itemData, pos, num, gp); addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
} }
} else { } else {
Position pos = em.getPosition().clone().addY(3f); Position pos = em.getPosition().clone().addY(3f);
addDropEntity(dd, em.getScene(), itemData, pos, num, gp); addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
} }
} }
} }
public void callDrop(EntityMonster em) { public void callDrop(EntityMonster em) {
int id = em.getMonsterData().getId(); int id = em.getMonsterData().getId();
if (getDropData().containsKey(id)) { if (getDropData().containsKey(id)) {
for (DropData dd : getDropData().get(id)) { for (DropData dd : getDropData().get(id)) {
if (dd.isShare()) processDrop(dd, em, null); if (dd.isShare()) processDrop(dd, em, null);
else { else {
for (Player gp : em.getScene().getPlayers()) { for (Player gp : em.getScene().getPlayers()) {
processDrop(dd, em, gp); processDrop(dd, em, gp);
} }
} }
} }
} }
} }
} }

View File

@ -1,88 +1,88 @@
package emu.grasscutter.game.entity.gadget; package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler; import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo; import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType; import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType;
import emu.grasscutter.net.proto.InteractTypeOuterClass; import emu.grasscutter.net.proto.InteractTypeOuterClass;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType; import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.ResinCostTypeOuterClass; import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.scripts.constants.ScriptGadgetState; import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public class GadgetChest extends GadgetContent { public class GadgetChest extends GadgetContent {
public GadgetChest(EntityGadget gadget) { public GadgetChest(EntityGadget gadget) {
super(gadget); super(gadget);
} }
public boolean onInteract(Player player, GadgetInteractReq req) { public boolean onInteract(Player player, GadgetInteractReq req) {
var chestInteractHandlerMap = var chestInteractHandlerMap =
getGadget() getGadget()
.getScene() .getScene()
.getWorld() .getWorld()
.getServer() .getServer()
.getWorldDataSystem() .getWorldDataSystem()
.getChestInteractHandlerMap(); .getChestInteractHandlerMap();
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName()); var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
if (handler == null) { if (handler == null) {
Grasscutter.getLogger() Grasscutter.getLogger()
.warn( .warn(
"Could not found the handler of this type of Chests {}", "Could not found the handler of this type of Chests {}",
getGadget().getGadgetData().getJsonName()); getGadget().getGadgetData().getJsonName());
return false; return false;
} }
if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) { if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) {
player.sendPacket( player.sendPacket(
new PacketGadgetInteractRsp( new PacketGadgetInteractRsp(
getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START)); getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
return false; return false;
} else { } else {
boolean success; boolean success;
if (handler instanceof BossChestInteractHandler bossChestInteractHandler) { if (handler instanceof BossChestInteractHandler bossChestInteractHandler) {
success = success =
bossChestInteractHandler.onInteract( bossChestInteractHandler.onInteract(
this, this,
player, player,
req.getResinCostType() req.getResinCostType()
== ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE); == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE);
} else { } else {
success = handler.onInteract(this, player); success = handler.onInteract(this, player);
} }
if (!success) { if (!success) {
return false; return false;
} }
getGadget().updateState(ScriptGadgetState.ChestOpened); getGadget().updateState(ScriptGadgetState.ChestOpened);
player.sendPacket( player.sendPacket(
new PacketGadgetInteractRsp( new PacketGadgetInteractRsp(
this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST)); this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST));
return true; return true;
} }
} }
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
if (getGadget().getMetaGadget() == null) { if (getGadget().getMetaGadget() == null) {
return; return;
} }
var bossChest = getGadget().getMetaGadget().boss_chest; var bossChest = getGadget().getMetaGadget().boss_chest;
if (bossChest != null) { if (bossChest != null) {
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList(); var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
gadgetInfo.setBossChest( gadgetInfo.setBossChest(
BossChestInfo.newBuilder() BossChestInfo.newBuilder()
.setMonsterConfigId(bossChest.monster_config_id) .setMonsterConfigId(bossChest.monster_config_id)
.setResin(bossChest.resin) .setResin(bossChest.resin)
.addAllQualifyUidList(players) .addAllQualifyUidList(players)
.addAllRemainUidList(players) .addAllRemainUidList(players)
.build()); .build());
} }
} }
} }

View File

@ -1,17 +1,17 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.ConsoleAppender;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import java.util.Arrays; import java.util.Arrays;
public class JlineLogbackAppender extends ConsoleAppender<ILoggingEvent> { public class JlineLogbackAppender extends ConsoleAppender<ILoggingEvent> {
@Override @Override
protected void append(ILoggingEvent eventObject) { protected void append(ILoggingEvent eventObject) {
if (!started) { if (!started) {
return; return;
} }
Arrays.stream(new String(encoder.encode(eventObject)).split("\n\r")) Arrays.stream(new String(encoder.encode(eventObject)).split("\n\r"))
.forEach(Grasscutter.getConsole()::printAbove); .forEach(Grasscutter.getConsole()::printAbove);
} }
} }

View File

@ -1,171 +1,171 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory; import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import emu.grasscutter.data.common.DynamicFloat; import emu.grasscutter.data.common.DynamicFloat;
import it.unimi.dsi.fastutil.floats.FloatArrayList; import it.unimi.dsi.fastutil.floats.FloatArrayList;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Objects; import java.util.Objects;
import lombok.val; import lombok.val;
public class JsonAdapters { public class JsonAdapters {
static class DynamicFloatAdapter extends TypeAdapter<DynamicFloat> { static class DynamicFloatAdapter extends TypeAdapter<DynamicFloat> {
@Override @Override
public DynamicFloat read(JsonReader reader) throws IOException { public DynamicFloat read(JsonReader reader) throws IOException {
switch (reader.peek()) { switch (reader.peek()) {
case STRING: case STRING:
return new DynamicFloat(reader.nextString()); return new DynamicFloat(reader.nextString());
case NUMBER: case NUMBER:
return new DynamicFloat((float) reader.nextDouble()); return new DynamicFloat((float) reader.nextDouble());
case BOOLEAN: case BOOLEAN:
return new DynamicFloat(reader.nextBoolean()); return new DynamicFloat(reader.nextBoolean());
case BEGIN_ARRAY: case BEGIN_ARRAY:
reader.beginArray(); reader.beginArray();
val opStack = new ArrayList<DynamicFloat.StackOp>(); val opStack = new ArrayList<DynamicFloat.StackOp>();
while (reader.hasNext()) { while (reader.hasNext()) {
opStack.add( opStack.add(
switch (reader.peek()) { switch (reader.peek()) {
case STRING -> new DynamicFloat.StackOp(reader.nextString()); case STRING -> new DynamicFloat.StackOp(reader.nextString());
case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble()); case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble());
case BOOLEAN -> new DynamicFloat.StackOp(reader.nextBoolean()); case BOOLEAN -> new DynamicFloat.StackOp(reader.nextBoolean());
default -> throw new IOException( default -> throw new IOException(
"Invalid DynamicFloat definition - " + reader.peek().name()); "Invalid DynamicFloat definition - " + reader.peek().name());
}); });
} }
reader.endArray(); reader.endArray();
return new DynamicFloat(opStack); return new DynamicFloat(opStack);
default: default:
throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name()); throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name());
} }
} }
@Override @Override
public void write(JsonWriter writer, DynamicFloat f) {} public void write(JsonWriter writer, DynamicFloat f) {}
} }
static class IntListAdapter extends TypeAdapter<IntList> { static class IntListAdapter extends TypeAdapter<IntList> {
@Override @Override
public IntList read(JsonReader reader) throws IOException { public IntList read(JsonReader reader) throws IOException {
if (Objects.requireNonNull(reader.peek()) == JsonToken.BEGIN_ARRAY) { if (Objects.requireNonNull(reader.peek()) == JsonToken.BEGIN_ARRAY) {
reader.beginArray(); reader.beginArray();
val i = new IntArrayList(); val i = new IntArrayList();
while (reader.hasNext()) i.add(reader.nextInt()); while (reader.hasNext()) i.add(reader.nextInt());
reader.endArray(); reader.endArray();
i.trim(); // We might have a ton of these from resources and almost all of them i.trim(); // We might have a ton of these from resources and almost all of them
// immutable, don't overprovision! // immutable, don't overprovision!
return i; return i;
} }
throw new IOException("Invalid IntList definition - " + reader.peek().name()); throw new IOException("Invalid IntList definition - " + reader.peek().name());
} }
@Override @Override
public void write(JsonWriter writer, IntList l) throws IOException { public void write(JsonWriter writer, IntList l) throws IOException {
writer.beginArray(); writer.beginArray();
for (val i : l) // .forEach() doesn't appreciate exceptions for (val i : l) // .forEach() doesn't appreciate exceptions
writer.value(i); writer.value(i);
writer.endArray(); writer.endArray();
} }
} }
static class PositionAdapter extends TypeAdapter<Position> { static class PositionAdapter extends TypeAdapter<Position> {
@Override @Override
public Position read(JsonReader reader) throws IOException { public Position read(JsonReader reader) throws IOException {
switch (reader.peek()) { switch (reader.peek()) {
case BEGIN_ARRAY: // "pos": [x,y,z] case BEGIN_ARRAY: // "pos": [x,y,z]
reader.beginArray(); reader.beginArray();
val array = new FloatArrayList(3); val array = new FloatArrayList(3);
while (reader.hasNext()) array.add(reader.nextInt()); while (reader.hasNext()) array.add(reader.nextInt());
reader.endArray(); reader.endArray();
return new Position(array); return new Position(array);
case BEGIN_OBJECT: // "pos": {"x": x, "y": y, "z": z} case BEGIN_OBJECT: // "pos": {"x": x, "y": y, "z": z}
float x = 0f; float x = 0f;
float y = 0f; float y = 0f;
float z = 0f; float z = 0f;
reader.beginObject(); reader.beginObject();
for (var next = reader.peek(); next != JsonToken.END_OBJECT; next = reader.peek()) { for (var next = reader.peek(); next != JsonToken.END_OBJECT; next = reader.peek()) {
val name = reader.nextName(); val name = reader.nextName();
switch (name) { switch (name) {
case "x", "X", "_x" -> x = (float) reader.nextDouble(); case "x", "X", "_x" -> x = (float) reader.nextDouble();
case "y", "Y", "_y" -> y = (float) reader.nextDouble(); case "y", "Y", "_y" -> y = (float) reader.nextDouble();
case "z", "Z", "_z" -> z = (float) reader.nextDouble(); case "z", "Z", "_z" -> z = (float) reader.nextDouble();
default -> throw new IOException("Invalid field in Position definition - " + name); default -> throw new IOException("Invalid field in Position definition - " + name);
} }
} }
reader.endObject(); reader.endObject();
return new Position(x, y, z); return new Position(x, y, z);
default: default:
throw new IOException("Invalid Position definition - " + reader.peek().name()); throw new IOException("Invalid Position definition - " + reader.peek().name());
} }
} }
@Override @Override
public void write(JsonWriter writer, Position i) throws IOException { public void write(JsonWriter writer, Position i) throws IOException {
writer.beginArray(); writer.beginArray();
writer.value(i.getX()); writer.value(i.getX());
writer.value(i.getY()); writer.value(i.getY());
writer.value(i.getZ()); writer.value(i.getZ());
writer.endArray(); writer.endArray();
} }
} }
static class EnumTypeAdapterFactory implements TypeAdapterFactory { static class EnumTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> enumClass = (Class<T>) type.getRawType(); Class<T> enumClass = (Class<T>) type.getRawType();
if (!enumClass.isEnum()) return null; if (!enumClass.isEnum()) return null;
// Make mappings of (string) names to enum constants // Make mappings of (string) names to enum constants
val map = new HashMap<String, T>(); val map = new HashMap<String, T>();
val enumConstants = enumClass.getEnumConstants(); val enumConstants = enumClass.getEnumConstants();
for (val constant : enumConstants) map.put(constant.toString(), constant); for (val constant : enumConstants) map.put(constant.toString(), constant);
// If the enum also has a numeric value, map those to the constants too // If the enum also has a numeric value, map those to the constants too
// System.out.println("Looking for enum value field"); // System.out.println("Looking for enum value field");
for (Field f : enumClass.getDeclaredFields()) { for (Field f : enumClass.getDeclaredFields()) {
if (switch (f.getName()) { if (switch (f.getName()) {
case "value", "id" -> true; case "value", "id" -> true;
default -> false; default -> false;
}) { }) {
// System.out.println("Enum value field found - " + f.getName()); // System.out.println("Enum value field found - " + f.getName());
boolean acc = f.isAccessible(); boolean acc = f.isAccessible();
f.setAccessible(true); f.setAccessible(true);
try { try {
for (val constant : enumConstants) for (val constant : enumConstants)
map.put(String.valueOf(f.getInt(constant)), constant); map.put(String.valueOf(f.getInt(constant)), constant);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
// System.out.println("Failed to access enum id field."); // System.out.println("Failed to access enum id field.");
} }
f.setAccessible(acc); f.setAccessible(acc);
break; break;
} }
} }
return new TypeAdapter<T>() { return new TypeAdapter<T>() {
public T read(JsonReader reader) throws IOException { public T read(JsonReader reader) throws IOException {
switch (reader.peek()) { switch (reader.peek()) {
case STRING: case STRING:
return map.get(reader.nextString()); return map.get(reader.nextString());
case NUMBER: case NUMBER:
return map.get(String.valueOf(reader.nextInt())); return map.get(String.valueOf(reader.nextInt()));
default: default:
throw new IOException("Invalid Enum definition - " + reader.peek().name()); throw new IOException("Invalid Enum definition - " + reader.peek().name());
} }
} }
public void write(JsonWriter writer, T value) throws IOException { public void write(JsonWriter writer, T value) throws IOException {
writer.value(value.toString()); writer.value(value.toString());
} }
}; };
} }
} }
} }

View File

@ -1,129 +1,129 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import emu.grasscutter.data.common.DynamicFloat; import emu.grasscutter.data.common.DynamicFloat;
import emu.grasscutter.utils.JsonAdapters.DynamicFloatAdapter; import emu.grasscutter.utils.JsonAdapters.DynamicFloatAdapter;
import emu.grasscutter.utils.JsonAdapters.EnumTypeAdapterFactory; import emu.grasscutter.utils.JsonAdapters.EnumTypeAdapterFactory;
import emu.grasscutter.utils.JsonAdapters.IntListAdapter; import emu.grasscutter.utils.JsonAdapters.IntListAdapter;
import emu.grasscutter.utils.JsonAdapters.PositionAdapter; import emu.grasscutter.utils.JsonAdapters.PositionAdapter;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public final class JsonUtils { public final class JsonUtils {
static final Gson gson = static final Gson gson =
new GsonBuilder() new GsonBuilder()
.setPrettyPrinting() .setPrettyPrinting()
.registerTypeAdapter(DynamicFloat.class, new DynamicFloatAdapter()) .registerTypeAdapter(DynamicFloat.class, new DynamicFloatAdapter())
.registerTypeAdapter(IntList.class, new IntListAdapter()) .registerTypeAdapter(IntList.class, new IntListAdapter())
.registerTypeAdapter(Position.class, new PositionAdapter()) .registerTypeAdapter(Position.class, new PositionAdapter())
.registerTypeAdapterFactory(new EnumTypeAdapterFactory()) .registerTypeAdapterFactory(new EnumTypeAdapterFactory())
.create(); .create();
/* /*
* Encode an object to a JSON string * Encode an object to a JSON string
*/ */
public static String encode(Object object) { public static String encode(Object object) {
return gson.toJson(object); return gson.toJson(object);
} }
public static <T> T decode(JsonElement jsonElement, Class<T> classType) public static <T> T decode(JsonElement jsonElement, Class<T> classType)
throws JsonSyntaxException { throws JsonSyntaxException {
return gson.fromJson(jsonElement, classType); return gson.fromJson(jsonElement, classType);
} }
public static <T> T loadToClass(Reader fileReader, Class<T> classType) throws IOException { public static <T> T loadToClass(Reader fileReader, Class<T> classType) throws IOException {
return gson.fromJson(fileReader, classType); return gson.fromJson(fileReader, classType);
} }
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public static <T> T loadToClass(String filename, Class<T> classType) throws IOException { public static <T> T loadToClass(String filename, Class<T> classType) throws IOException {
try (InputStreamReader fileReader = try (InputStreamReader fileReader =
new InputStreamReader( new InputStreamReader(
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) { new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
return loadToClass(fileReader, classType); return loadToClass(fileReader, classType);
} }
} }
public static <T> T loadToClass(Path filename, Class<T> classType) throws IOException { public static <T> T loadToClass(Path filename, Class<T> classType) throws IOException {
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) { try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
return loadToClass(fileReader, classType); return loadToClass(fileReader, classType);
} }
} }
public static <T> List<T> loadToList(Reader fileReader, Class<T> classType) throws IOException { public static <T> List<T> loadToList(Reader fileReader, Class<T> classType) throws IOException {
return gson.fromJson(fileReader, TypeToken.getParameterized(List.class, classType).getType()); return gson.fromJson(fileReader, TypeToken.getParameterized(List.class, classType).getType());
} }
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public static <T> List<T> loadToList(String filename, Class<T> classType) throws IOException { public static <T> List<T> loadToList(String filename, Class<T> classType) throws IOException {
try (InputStreamReader fileReader = try (InputStreamReader fileReader =
new InputStreamReader( new InputStreamReader(
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) { new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
return loadToList(fileReader, classType); return loadToList(fileReader, classType);
} }
} }
public static <T> List<T> loadToList(Path filename, Class<T> classType) throws IOException { public static <T> List<T> loadToList(Path filename, Class<T> classType) throws IOException {
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) { try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
return loadToList(fileReader, classType); return loadToList(fileReader, classType);
} }
} }
public static <T1, T2> Map<T1, T2> loadToMap( public static <T1, T2> Map<T1, T2> loadToMap(
Reader fileReader, Class<T1> keyType, Class<T2> valueType) throws IOException { Reader fileReader, Class<T1> keyType, Class<T2> valueType) throws IOException {
return gson.fromJson( return gson.fromJson(
fileReader, TypeToken.getParameterized(Map.class, keyType, valueType).getType()); fileReader, TypeToken.getParameterized(Map.class, keyType, valueType).getType());
} }
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public static <T1, T2> Map<T1, T2> loadToMap( public static <T1, T2> Map<T1, T2> loadToMap(
String filename, Class<T1> keyType, Class<T2> valueType) throws IOException { String filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
try (InputStreamReader fileReader = try (InputStreamReader fileReader =
new InputStreamReader( new InputStreamReader(
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) { new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
return loadToMap(fileReader, keyType, valueType); return loadToMap(fileReader, keyType, valueType);
} }
} }
public static <T1, T2> Map<T1, T2> loadToMap( public static <T1, T2> Map<T1, T2> loadToMap(
Path filename, Class<T1> keyType, Class<T2> valueType) throws IOException { Path filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) { try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
return loadToMap(fileReader, keyType, valueType); return loadToMap(fileReader, keyType, valueType);
} }
} }
/** /**
* Safely JSON decodes a given string. * Safely JSON decodes a given string.
* *
* @param jsonData The JSON-encoded data. * @param jsonData The JSON-encoded data.
* @return JSON decoded data, or null if an exception occurred. * @return JSON decoded data, or null if an exception occurred.
*/ */
public static <T> T decode(String jsonData, Class<T> classType) { public static <T> T decode(String jsonData, Class<T> classType) {
try { try {
return gson.fromJson(jsonData, classType); return gson.fromJson(jsonData, classType);
} catch (Exception ignored) { } catch (Exception ignored) {
return null; return null;
} }
} }
public static <T> T decode(String jsonData, Type type) { public static <T> T decode(String jsonData, Type type) {
try { try {
return gson.fromJson(jsonData, type); return gson.fromJson(jsonData, type);
} catch (Exception ignored) { } catch (Exception ignored) {
return null; return null;
} }
} }
} }

View File

@ -1,40 +1,40 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient; import dev.morphia.annotations.Transient;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Entity @Entity
public class Location extends Position { public class Location extends Position {
@Transient @Getter @Setter private Scene scene; @Transient @Getter @Setter private Scene scene;
public Location(Scene scene, Position position) { public Location(Scene scene, Position position) {
this.set(position); this.set(position);
this.scene = scene; this.scene = scene;
} }
public Location(Scene scene, float x, float y) { public Location(Scene scene, float x, float y) {
this.set(x, y); this.set(x, y);
this.scene = scene; this.scene = scene;
} }
public Location(Scene scene, float x, float y, float z) { public Location(Scene scene, float x, float y, float z) {
this.set(x, y, z); this.set(x, y, z);
this.scene = scene; this.scene = scene;
} }
@Override @Override
public Location clone() { public Location clone() {
return new Location(this.scene, super.clone()); return new Location(this.scene, super.clone());
} }
@Override @Override
public String toString() { public String toString() {
return String.format("%s:%s,%s,%s", this.scene.getId(), this.getX(), this.getY(), this.getZ()); return String.format("%s:%s,%s,%s", this.scene.getId(), this.getX(), this.getY(), this.getZ());
} }
} }

View File

@ -1,21 +1,21 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
public class MessageHandler { public class MessageHandler {
private String message; private String message;
public MessageHandler() { public MessageHandler() {
this.message = ""; this.message = "";
} }
public void append(String message) { public void append(String message) {
this.message += message + "\r\n\r\n"; this.message += message + "\r\n\r\n";
} }
public String getMessage() { public String getMessage() {
return this.message; return this.message;
} }
public void setMessage(String message) { public void setMessage(String message) {
this.message = message; this.message = message;
} }
} }

View File

@ -1,196 +1,196 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import com.github.davidmoten.rtreemulti.geometry.Point; import com.github.davidmoten.rtreemulti.geometry.Point;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Entity @Entity
public class Position implements Serializable { public class Position implements Serializable {
private static final long serialVersionUID = -2001232313615923575L; private static final long serialVersionUID = -2001232313615923575L;
@SerializedName( @SerializedName(
value = "x", value = "x",
alternate = {"_x", "X"}) alternate = {"_x", "X"})
@Getter @Getter
@Setter @Setter
private float x; private float x;
@SerializedName( @SerializedName(
value = "y", value = "y",
alternate = {"_y", "Y"}) alternate = {"_y", "Y"})
@Getter @Getter
@Setter @Setter
private float y; private float y;
@SerializedName( @SerializedName(
value = "z", value = "z",
alternate = {"_z", "Z"}) alternate = {"_z", "Z"})
@Getter @Getter
@Setter @Setter
private float z; private float z;
public Position() {} public Position() {}
public Position(float x, float y) { public Position(float x, float y) {
set(x, y); set(x, y);
} }
public Position(float x, float y, float z) { public Position(float x, float y, float z) {
set(x, y, z); set(x, y, z);
} }
public Position(List<Float> xyz) { public Position(List<Float> xyz) {
switch (xyz.size()) { switch (xyz.size()) {
default: // Might want to error on excess elements, but maybe we want to extend to 3+3 default: // Might want to error on excess elements, but maybe we want to extend to 3+3
// representation later. // representation later.
case 3: case 3:
this.z = xyz.get(2); // Fall-through this.z = xyz.get(2); // Fall-through
case 2: case 2:
this.y = xyz.get(1); // Fall-through this.y = xyz.get(1); // Fall-through
case 1: case 1:
this.y = xyz.get(0); // pointless fall-through this.y = xyz.get(0); // pointless fall-through
case 0: case 0:
break; break;
} }
} }
public Position(String p) { public Position(String p) {
String[] split = p.split(","); String[] split = p.split(",");
if (split.length >= 2) { if (split.length >= 2) {
this.x = Float.parseFloat(split[0]); this.x = Float.parseFloat(split[0]);
this.y = Float.parseFloat(split[1]); this.y = Float.parseFloat(split[1]);
} }
if (split.length >= 3) { if (split.length >= 3) {
this.z = Float.parseFloat(split[2]); this.z = Float.parseFloat(split[2]);
} }
} }
public Position(Vector vector) { public Position(Vector vector) {
this.set(vector); this.set(vector);
} }
public Position(Position pos) { public Position(Position pos) {
this.set(pos); this.set(pos);
} }
public Position set(float x, float y) { public Position set(float x, float y) {
this.x = x; this.x = x;
this.y = y; this.y = y;
return this; return this;
} }
// Deep copy // Deep copy
public Position set(Position pos) { public Position set(Position pos) {
return this.set(pos.getX(), pos.getY(), pos.getZ()); return this.set(pos.getX(), pos.getY(), pos.getZ());
} }
public Position set(Vector pos) { public Position set(Vector pos) {
return this.set(pos.getX(), pos.getY(), pos.getZ()); return this.set(pos.getX(), pos.getY(), pos.getZ());
} }
public Position set(float x, float y, float z) { public Position set(float x, float y, float z) {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
return this; return this;
} }
public Position add(Position add) { public Position add(Position add) {
this.x += add.getX(); this.x += add.getX();
this.y += add.getY(); this.y += add.getY();
this.z += add.getZ(); this.z += add.getZ();
return this; return this;
} }
public Position addX(float d) { public Position addX(float d) {
this.x += d; this.x += d;
return this; return this;
} }
public Position addY(float d) { public Position addY(float d) {
this.y += d; this.y += d;
return this; return this;
} }
public Position addZ(float d) { public Position addZ(float d) {
this.z += d; this.z += d;
return this; return this;
} }
public Position subtract(Position sub) { public Position subtract(Position sub) {
this.x -= sub.getX(); this.x -= sub.getX();
this.y -= sub.getY(); this.y -= sub.getY();
this.z -= sub.getZ(); this.z -= sub.getZ();
return this; return this;
} }
/** In radians */ /** In radians */
public Position translate(float dist, float angle) { public Position translate(float dist, float angle) {
this.x += dist * Math.sin(angle); this.x += dist * Math.sin(angle);
this.y += dist * Math.cos(angle); this.y += dist * Math.cos(angle);
return this; return this;
} }
public boolean equal2d(Position other) { public boolean equal2d(Position other) {
// Y is height // Y is height
return getX() == other.getX() && getZ() == other.getZ(); return getX() == other.getX() && getZ() == other.getZ();
} }
public boolean equal3d(Position other) { public boolean equal3d(Position other) {
return getX() == other.getX() && getY() == other.getY() && getZ() == other.getZ(); return getX() == other.getX() && getY() == other.getY() && getZ() == other.getZ();
} }
public double computeDistance(Position b) { public double computeDistance(Position b) {
double detX = getX() - b.getX(); double detX = getX() - b.getX();
double detY = getY() - b.getY(); double detY = getY() - b.getY();
double detZ = getZ() - b.getZ(); double detZ = getZ() - b.getZ();
return Math.sqrt(detX * detX + detY * detY + detZ * detZ); return Math.sqrt(detX * detX + detY * detY + detZ * detZ);
} }
public Position nearby2d(float range) { public Position nearby2d(float range) {
Position position = clone(); Position position = clone();
position.z += Utils.randomFloatRange(-range, range); position.z += Utils.randomFloatRange(-range, range);
position.x += Utils.randomFloatRange(-range, range); position.x += Utils.randomFloatRange(-range, range);
return position; return position;
} }
public Position translateWithDegrees(float dist, float angle) { public Position translateWithDegrees(float dist, float angle) {
angle = (float) Math.toRadians(angle); angle = (float) Math.toRadians(angle);
this.x += dist * Math.sin(angle); this.x += dist * Math.sin(angle);
this.y += -dist * Math.cos(angle); this.y += -dist * Math.cos(angle);
return this; return this;
} }
@Override @Override
public Position clone() { public Position clone() {
return new Position(x, y, z); return new Position(x, y, z);
} }
@Override @Override
public String toString() { public String toString() {
return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")"; return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")";
} }
public Vector toProto() { public Vector toProto() {
return Vector.newBuilder().setX(this.getX()).setY(this.getY()).setZ(this.getZ()).build(); return Vector.newBuilder().setX(this.getX()).setY(this.getY()).setZ(this.getZ()).build();
} }
public Point toPoint() { public Point toPoint() {
return Point.create(x, y, z); return Point.create(x, y, z);
} }
/** To XYZ array for Spatial Index */ /** To XYZ array for Spatial Index */
public double[] toDoubleArray() { public double[] toDoubleArray() {
return new double[] {x, y, z}; return new double[] {x, y, z};
} }
/** To XZ array for Spatial Index (Blocks) */ /** To XZ array for Spatial Index (Blocks) */
public double[] toXZDoubleArray() { public double[] toXZDoubleArray() {
return new double[] {x, z}; return new double[] {x, z};
} }
} }

View File

@ -1,10 +1,10 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.PropValueOuterClass.PropValue; import emu.grasscutter.net.proto.PropValueOuterClass.PropValue;
public final class ProtoHelper { public final class ProtoHelper {
public static PropValue newPropValue(PlayerProperty key, int value) { public static PropValue newPropValue(PlayerProperty key, int value) {
return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build(); return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build();
} }
} }

View File

@ -1,27 +1,27 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase; import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.encoder.Encoder; import ch.qos.logback.core.encoder.Encoder;
import emu.grasscutter.server.event.internal.ServerLogEvent; import emu.grasscutter.server.event.internal.ServerLogEvent;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
public class ServerLogEventAppender<E> extends AppenderBase<E> { public class ServerLogEventAppender<E> extends AppenderBase<E> {
protected Encoder<E> encoder; protected Encoder<E> encoder;
@Override @Override
protected void append(E event) { protected void append(E event) {
byte[] byteArray = this.encoder.encode(event); byte[] byteArray = this.encoder.encode(event);
ServerLogEvent sle = ServerLogEvent sle =
new ServerLogEvent((ILoggingEvent) event, new String(byteArray, StandardCharsets.UTF_8)); new ServerLogEvent((ILoggingEvent) event, new String(byteArray, StandardCharsets.UTF_8));
sle.call(); sle.call();
} }
public Encoder<E> getEncoder() { public Encoder<E> getEncoder() {
return this.encoder; return this.encoder;
} }
public void setEncoder(Encoder<E> encoder) { public void setEncoder(Encoder<E> encoder) {
this.encoder = encoder; this.encoder = encoder;
} }
} }

View File

@ -1,67 +1,67 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
public final class SparseSet { public final class SparseSet {
private final List<Range> rangeEntries; private final List<Range> rangeEntries;
private final Set<Integer> denseEntries; private final Set<Integer> denseEntries;
public SparseSet(String csv) { public SparseSet(String csv) {
this.rangeEntries = new ArrayList<>(); this.rangeEntries = new ArrayList<>();
this.denseEntries = new TreeSet<>(); this.denseEntries = new TreeSet<>();
for (String token : csv.replace("\n", "").replace(" ", "").split(",")) { for (String token : csv.replace("\n", "").replace(" ", "").split(",")) {
String[] tokens = token.split("-"); String[] tokens = token.split("-");
switch (tokens.length) { switch (tokens.length) {
case 1: case 1:
this.denseEntries.add(Integer.parseInt(tokens[0])); this.denseEntries.add(Integer.parseInt(tokens[0]));
break; break;
case 2: case 2:
this.rangeEntries.add( this.rangeEntries.add(
new Range(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]))); new Range(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1])));
break; break;
default: default:
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Invalid token passed to SparseSet initializer - " "Invalid token passed to SparseSet initializer - "
+ token + token
+ " (split length " + " (split length "
+ tokens.length + tokens.length
+ ")"); + ")");
} }
} }
} }
public boolean contains(int i) { public boolean contains(int i) {
for (Range range : this.rangeEntries) { for (Range range : this.rangeEntries) {
if (range.check(i)) { if (range.check(i)) {
return true; return true;
} }
} }
return this.denseEntries.contains(i); return this.denseEntries.contains(i);
} }
/* /*
* A convenience class for constructing integer sets out of large ranges * A convenience class for constructing integer sets out of large ranges
* Designed to be fed literal strings from this project only - * Designed to be fed literal strings from this project only -
* can and will throw exceptions to tell you to fix your code if you feed it garbage. :) * can and will throw exceptions to tell you to fix your code if you feed it garbage. :)
*/ */
private static class Range { private static class Range {
private final int min, max; private final int min, max;
public Range(int min, int max) { public Range(int min, int max) {
if (min > max) { if (min > max) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Range passed minimum higher than maximum - " + min + " > " + max); "Range passed minimum higher than maximum - " + min + " > " + max);
} }
this.min = min; this.min = min;
this.max = max; this.max = max;
} }
public boolean check(int value) { public boolean check(int value) {
return value >= this.min && value <= this.max; return value >= this.min && value <= this.max;
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,449 +1,449 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import static emu.grasscutter.utils.FileUtils.getResourcePath; import static emu.grasscutter.utils.FileUtils.getResourcePath;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.config.ConfigContainer; import emu.grasscutter.config.ConfigContainer;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.time.DayOfWeek; import java.time.DayOfWeek;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.temporal.TemporalAdjusters; import java.time.temporal.TemporalAdjusters;
import java.util.*; import java.util.*;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
@SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"}) @SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"})
public final class Utils { public final class Utils {
public static final Random random = new Random(); public static final Random random = new Random();
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
public static int randomRange(int min, int max) { public static int randomRange(int min, int max) {
return random.nextInt(max - min + 1) + min; return random.nextInt(max - min + 1) + min;
} }
public static float randomFloatRange(float min, float max) { public static float randomFloatRange(float min, float max) {
return random.nextFloat() * (max - min) + min; return random.nextFloat() * (max - min) + min;
} }
public static double getDist(Position pos1, Position pos2) { public static double getDist(Position pos1, Position pos2) {
double xs = pos1.getX() - pos2.getX(); double xs = pos1.getX() - pos2.getX();
xs = xs * xs; xs = xs * xs;
double ys = pos1.getY() - pos2.getY(); double ys = pos1.getY() - pos2.getY();
ys = ys * ys; ys = ys * ys;
double zs = pos1.getZ() - pos2.getZ(); double zs = pos1.getZ() - pos2.getZ();
zs = zs * zs; zs = zs * zs;
return Math.sqrt(xs + zs + ys); return Math.sqrt(xs + zs + ys);
} }
public static int getCurrentSeconds() { public static int getCurrentSeconds() {
return (int) (System.currentTimeMillis() / 1000.0); return (int) (System.currentTimeMillis() / 1000.0);
} }
public static String lowerCaseFirstChar(String s) { public static String lowerCaseFirstChar(String s) {
StringBuilder sb = new StringBuilder(s); StringBuilder sb = new StringBuilder(s);
sb.setCharAt(0, Character.toLowerCase(sb.charAt(0))); sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
return sb.toString(); return sb.toString();
} }
public static String toString(InputStream inputStream) throws IOException { public static String toString(InputStream inputStream) throws IOException {
BufferedInputStream bis = new BufferedInputStream(inputStream); BufferedInputStream bis = new BufferedInputStream(inputStream);
ByteArrayOutputStream buf = new ByteArrayOutputStream(); ByteArrayOutputStream buf = new ByteArrayOutputStream();
for (int result = bis.read(); result != -1; result = bis.read()) { for (int result = bis.read(); result != -1; result = bis.read()) {
buf.write((byte) result); buf.write((byte) result);
} }
return buf.toString(); return buf.toString();
} }
public static void logByteArray(byte[] array) { public static void logByteArray(byte[] array) {
ByteBuf b = Unpooled.wrappedBuffer(array); ByteBuf b = Unpooled.wrappedBuffer(array);
Grasscutter.getLogger().info("\n" + ByteBufUtil.prettyHexDump(b)); Grasscutter.getLogger().info("\n" + ByteBufUtil.prettyHexDump(b));
b.release(); b.release();
} }
public static String bytesToHex(byte[] bytes) { public static String bytesToHex(byte[] bytes) {
if (bytes == null) return ""; if (bytes == null) return "";
char[] hexChars = new char[bytes.length * 2]; char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) { for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF; int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4]; hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
} }
return new String(hexChars); return new String(hexChars);
} }
public static String bytesToHex(ByteBuf buf) { public static String bytesToHex(ByteBuf buf) {
return bytesToHex(byteBufToArray(buf)); return bytesToHex(byteBufToArray(buf));
} }
public static byte[] byteBufToArray(ByteBuf buf) { public static byte[] byteBufToArray(ByteBuf buf) {
byte[] bytes = new byte[buf.capacity()]; byte[] bytes = new byte[buf.capacity()];
buf.getBytes(0, bytes); buf.getBytes(0, bytes);
return bytes; return bytes;
} }
public static int abilityHash(String str) { public static int abilityHash(String str) {
int v7 = 0; int v7 = 0;
int v8 = 0; int v8 = 0;
while (v8 < str.length()) { while (v8 < str.length()) {
v7 = str.charAt(v8++) + 131 * v7; v7 = str.charAt(v8++) + 131 * v7;
} }
return v7; return v7;
} }
/** /**
* Creates a string with the path to a file. * Creates a string with the path to a file.
* *
* @param path The path to the file. * @param path The path to the file.
* @return A path using the operating system's file separator. * @return A path using the operating system's file separator.
*/ */
public static String toFilePath(String path) { public static String toFilePath(String path) {
return path.replace("/", File.separator); return path.replace("/", File.separator);
} }
/** /**
* Checks if a file exists on the file system. * Checks if a file exists on the file system.
* *
* @param path The path to the file. * @param path The path to the file.
* @return True if the file exists, false otherwise. * @return True if the file exists, false otherwise.
*/ */
public static boolean fileExists(String path) { public static boolean fileExists(String path) {
return new File(path).exists(); return new File(path).exists();
} }
/** /**
* Creates a folder on the file system. * Creates a folder on the file system.
* *
* @param path The path to the folder. * @param path The path to the folder.
* @return True if the folder was created, false otherwise. * @return True if the folder was created, false otherwise.
*/ */
public static boolean createFolder(String path) { public static boolean createFolder(String path) {
return new File(path).mkdirs(); return new File(path).mkdirs();
} }
/** /**
* Copies a file from the archive's resources to the file system. * Copies a file from the archive's resources to the file system.
* *
* @param resource The path to the resource. * @param resource The path to the resource.
* @param destination The path to copy the resource to. * @param destination The path to copy the resource to.
* @return True if the file was copied, false otherwise. * @return True if the file was copied, false otherwise.
*/ */
public static boolean copyFromResources(String resource, String destination) { public static boolean copyFromResources(String resource, String destination) {
try (InputStream stream = Grasscutter.class.getResourceAsStream(resource)) { try (InputStream stream = Grasscutter.class.getResourceAsStream(resource)) {
if (stream == null) { if (stream == null) {
Grasscutter.getLogger().warn("Could not find resource: " + resource); Grasscutter.getLogger().warn("Could not find resource: " + resource);
return false; return false;
} }
Files.copy(stream, new File(destination).toPath(), StandardCopyOption.REPLACE_EXISTING); Files.copy(stream, new File(destination).toPath(), StandardCopyOption.REPLACE_EXISTING);
return true; return true;
} catch (Exception exception) { } catch (Exception exception) {
Grasscutter.getLogger() Grasscutter.getLogger()
.warn("Unable to copy resource " + resource + " to " + destination, exception); .warn("Unable to copy resource " + resource + " to " + destination, exception);
return false; return false;
} }
} }
/** /**
* Logs an object to the console. * Logs an object to the console.
* *
* @param object The object to log. * @param object The object to log.
*/ */
public static void logObject(Object object) { public static void logObject(Object object) {
Grasscutter.getLogger().info(JsonUtils.encode(object)); Grasscutter.getLogger().info(JsonUtils.encode(object));
} }
/** Checks for required files and folders before startup. */ /** Checks for required files and folders before startup. */
public static void startupCheck() { public static void startupCheck() {
ConfigContainer config = Grasscutter.getConfig(); ConfigContainer config = Grasscutter.getConfig();
Logger logger = Grasscutter.getLogger(); Logger logger = Grasscutter.getLogger();
boolean exit = false; boolean exit = false;
String dataFolder = config.folderStructure.data; String dataFolder = config.folderStructure.data;
// Check for resources folder. // Check for resources folder.
if (!Files.exists(getResourcePath(""))) { if (!Files.exists(getResourcePath(""))) {
logger.info(translate("messages.status.create_resources")); logger.info(translate("messages.status.create_resources"));
logger.info(translate("messages.status.resources_error")); logger.info(translate("messages.status.resources_error"));
createFolder(config.folderStructure.resources); createFolder(config.folderStructure.resources);
exit = true; exit = true;
} }
// Check for BinOutput + ExcelBinOutput. // Check for BinOutput + ExcelBinOutput.
if (!Files.exists(getResourcePath("BinOutput")) if (!Files.exists(getResourcePath("BinOutput"))
|| !Files.exists(getResourcePath("ExcelBinOutput"))) { || !Files.exists(getResourcePath("ExcelBinOutput"))) {
logger.info(translate("messages.status.resources_error")); logger.info(translate("messages.status.resources_error"));
exit = true; exit = true;
} }
// Check for game data. // Check for game data.
if (!fileExists(dataFolder)) createFolder(dataFolder); if (!fileExists(dataFolder)) createFolder(dataFolder);
// Make sure the data folder is populated, if there are any missing files copy them from // Make sure the data folder is populated, if there are any missing files copy them from
// resources // resources
DataLoader.checkAllFiles(); DataLoader.checkAllFiles();
if (exit) System.exit(1); if (exit) System.exit(1);
} }
/** /**
* Gets the timestamp of the next hour. * Gets the timestamp of the next hour.
* *
* @return The timestamp in UNIX seconds. * @return The timestamp in UNIX seconds.
*/ */
public static int getNextTimestampOfThisHour(int hour, String timeZone, int param) { public static int getNextTimestampOfThisHour(int hour, String timeZone, int param) {
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
for (int i = 0; i < param; i++) { for (int i = 0; i < param; i++) {
if (zonedDateTime.getHour() < hour) { if (zonedDateTime.getHour() < hour) {
zonedDateTime = zonedDateTime.withHour(hour).withMinute(0).withSecond(0); zonedDateTime = zonedDateTime.withHour(hour).withMinute(0).withSecond(0);
} else { } else {
zonedDateTime = zonedDateTime.plusDays(1).withHour(hour).withMinute(0).withSecond(0); zonedDateTime = zonedDateTime.plusDays(1).withHour(hour).withMinute(0).withSecond(0);
} }
} }
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
} }
/** /**
* Gets the timestamp of the next hour in a week. * Gets the timestamp of the next hour in a week.
* *
* @return The timestamp in UNIX seconds. * @return The timestamp in UNIX seconds.
*/ */
public static int getNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) { public static int getNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) {
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
for (int i = 0; i < param; i++) { for (int i = 0; i < param; i++) {
if (zonedDateTime.getDayOfWeek() == DayOfWeek.MONDAY && zonedDateTime.getHour() < hour) { if (zonedDateTime.getDayOfWeek() == DayOfWeek.MONDAY && zonedDateTime.getHour() < hour) {
zonedDateTime = zonedDateTime =
ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0); ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0);
} else { } else {
zonedDateTime = zonedDateTime =
zonedDateTime zonedDateTime
.with(TemporalAdjusters.next(DayOfWeek.MONDAY)) .with(TemporalAdjusters.next(DayOfWeek.MONDAY))
.withHour(hour) .withHour(hour)
.withMinute(0) .withMinute(0)
.withSecond(0); .withSecond(0);
} }
} }
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
} }
/** /**
* Gets the timestamp of the next hour in a month. * Gets the timestamp of the next hour in a month.
* *
* @return The timestamp in UNIX seconds. * @return The timestamp in UNIX seconds.
*/ */
public static int getNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) { public static int getNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) {
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone)); ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
for (int i = 0; i < param; i++) { for (int i = 0; i < param; i++) {
if (zonedDateTime.getDayOfMonth() == 1 && zonedDateTime.getHour() < hour) { if (zonedDateTime.getDayOfMonth() == 1 && zonedDateTime.getHour() < hour) {
zonedDateTime = zonedDateTime =
ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0); ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0);
} else { } else {
zonedDateTime = zonedDateTime =
zonedDateTime zonedDateTime
.with(TemporalAdjusters.firstDayOfNextMonth()) .with(TemporalAdjusters.firstDayOfNextMonth())
.withHour(hour) .withHour(hour)
.withMinute(0) .withMinute(0)
.withSecond(0); .withSecond(0);
} }
} }
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond(); return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
} }
/** /**
* Retrieves a string from an input stream. * Retrieves a string from an input stream.
* *
* @param stream The input stream. * @param stream The input stream.
* @return The string. * @return The string.
*/ */
public static String readFromInputStream(@Nullable InputStream stream) { public static String readFromInputStream(@Nullable InputStream stream) {
if (stream == null) return "empty"; if (stream == null) return "empty";
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = try (BufferedReader reader =
new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
stringBuilder.append(line); stringBuilder.append(line);
} }
stream.close(); stream.close();
} catch (IOException e) { } catch (IOException e) {
Grasscutter.getLogger().warn("Failed to read from input stream."); Grasscutter.getLogger().warn("Failed to read from input stream.");
} catch (NullPointerException ignored) { } catch (NullPointerException ignored) {
return "empty"; return "empty";
} }
return stringBuilder.toString(); return stringBuilder.toString();
} }
/** /**
* Performs a linear interpolation using a table of fixed points to create an effective piecewise * Performs a linear interpolation using a table of fixed points to create an effective piecewise
* f(x) = y function. * f(x) = y function.
* *
* @param x The x value. * @param x The x value.
* @param xyArray Array of points in [[x0,y0], ... [xN, yN]] format * @param xyArray Array of points in [[x0,y0], ... [xN, yN]] format
* @return f(x) = y * @return f(x) = y
*/ */
public static int lerp(int x, int[][] xyArray) { public static int lerp(int x, int[][] xyArray) {
try { try {
if (x <= xyArray[0][0]) { // Clamp to first point if (x <= xyArray[0][0]) { // Clamp to first point
return xyArray[0][1]; return xyArray[0][1];
} else if (x >= xyArray[xyArray.length - 1][0]) { // Clamp to last point } else if (x >= xyArray[xyArray.length - 1][0]) { // Clamp to last point
return xyArray[xyArray.length - 1][1]; return xyArray[xyArray.length - 1][1];
} }
// At this point we're guaranteed to have two lerp points, and pity be somewhere between them. // At this point we're guaranteed to have two lerp points, and pity be somewhere between them.
for (int i = 0; i < xyArray.length - 1; i++) { for (int i = 0; i < xyArray.length - 1; i++) {
if (x == xyArray[i + 1][0]) { if (x == xyArray[i + 1][0]) {
return xyArray[i + 1][1]; return xyArray[i + 1][1];
} }
if (x < xyArray[i + 1][0]) { if (x < xyArray[i + 1][0]) {
// We are between [i] and [i+1], interpolation time! // We are between [i] and [i+1], interpolation time!
// Using floats would be slightly cleaner but we can just as easily use ints if we're // Using floats would be slightly cleaner but we can just as easily use ints if we're
// careful with order of operations. // careful with order of operations.
int position = x - xyArray[i][0]; int position = x - xyArray[i][0];
int fullDist = xyArray[i + 1][0] - xyArray[i][0]; int fullDist = xyArray[i + 1][0] - xyArray[i][0];
int prevValue = xyArray[i][1]; int prevValue = xyArray[i][1];
int fullDelta = xyArray[i + 1][1] - prevValue; int fullDelta = xyArray[i + 1][1] - prevValue;
return prevValue + ((position * fullDelta) / fullDist); return prevValue + ((position * fullDelta) / fullDist);
} }
} }
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
Grasscutter.getLogger() Grasscutter.getLogger()
.error("Malformed lerp point array. Must be of form [[x0, y0], ..., [xN, yN]]."); .error("Malformed lerp point array. Must be of form [[x0, y0], ..., [xN, yN]].");
} }
return 0; return 0;
} }
/** /**
* Checks if an int is in an int[] * Checks if an int is in an int[]
* *
* @param key int to look for * @param key int to look for
* @param array int[] to look in * @param array int[] to look in
* @return key in array * @return key in array
*/ */
public static boolean intInArray(int key, int[] array) { public static boolean intInArray(int key, int[] array) {
for (int i : array) { for (int i : array) {
if (i == key) { if (i == key) {
return true; return true;
} }
} }
return false; return false;
} }
/** /**
* Return a copy of minuend without any elements found in subtrahend. * Return a copy of minuend without any elements found in subtrahend.
* *
* @param minuend The array we want elements from * @param minuend The array we want elements from
* @param subtrahend The array whose elements we don't want * @param subtrahend The array whose elements we don't want
* @return The array with only the elements we want, in the order that minuend had them * @return The array with only the elements we want, in the order that minuend had them
*/ */
public static int[] setSubtract(int[] minuend, int[] subtrahend) { public static int[] setSubtract(int[] minuend, int[] subtrahend) {
IntList temp = new IntArrayList(); IntList temp = new IntArrayList();
for (int i : minuend) { for (int i : minuend) {
if (!intInArray(i, subtrahend)) { if (!intInArray(i, subtrahend)) {
temp.add(i); temp.add(i);
} }
} }
return temp.toIntArray(); return temp.toIntArray();
} }
/** /**
* Gets the language code from a given locale. * Gets the language code from a given locale.
* *
* @param locale A locale. * @param locale A locale.
* @return A string in the format of 'XX-XX'. * @return A string in the format of 'XX-XX'.
*/ */
public static String getLanguageCode(Locale locale) { public static String getLanguageCode(Locale locale) {
return String.format("%s-%s", locale.getLanguage(), locale.getCountry()); return String.format("%s-%s", locale.getLanguage(), locale.getCountry());
} }
/** /**
* Base64 encodes a given byte array. * Base64 encodes a given byte array.
* *
* @param toEncode An array of bytes. * @param toEncode An array of bytes.
* @return A base64 encoded string. * @return A base64 encoded string.
*/ */
public static String base64Encode(byte[] toEncode) { public static String base64Encode(byte[] toEncode) {
return Base64.getEncoder().encodeToString(toEncode); return Base64.getEncoder().encodeToString(toEncode);
} }
/** /**
* Base64 decodes a given string. * Base64 decodes a given string.
* *
* @param toDecode A base64 encoded string. * @param toDecode A base64 encoded string.
* @return An array of bytes. * @return An array of bytes.
*/ */
public static byte[] base64Decode(String toDecode) { public static byte[] base64Decode(String toDecode) {
return Base64.getDecoder().decode(toDecode); return Base64.getDecoder().decode(toDecode);
} }
/*** /***
* Draws a random element from the given list, following the given probability distribution, if given. * Draws a random element from the given list, following the given probability distribution, if given.
* @param list The list from which to draw the element. * @param list The list from which to draw the element.
* @param probabilities The probability distribution. This is given as a list of probabilities of the same length it `list`. * @param probabilities The probability distribution. This is given as a list of probabilities of the same length it `list`.
* @return A randomly drawn element from the given list. * @return A randomly drawn element from the given list.
*/ */
public static <T> T drawRandomListElement(List<T> list, List<Integer> probabilities) { public static <T> T drawRandomListElement(List<T> list, List<Integer> probabilities) {
// If we don't have a probability distribution, or the size of the distribution does not match // If we don't have a probability distribution, or the size of the distribution does not match
// the size of the list, we assume uniform distribution. // the size of the list, we assume uniform distribution.
if (probabilities == null || probabilities.size() <= 1 || probabilities.size() != list.size()) { if (probabilities == null || probabilities.size() <= 1 || probabilities.size() != list.size()) {
int index = ThreadLocalRandom.current().nextInt(0, list.size()); int index = ThreadLocalRandom.current().nextInt(0, list.size());
return list.get(index); return list.get(index);
} }
// Otherwise, we roll with the given distribution. // Otherwise, we roll with the given distribution.
int totalProbabilityMass = probabilities.stream().reduce(Integer::sum).get(); int totalProbabilityMass = probabilities.stream().reduce(Integer::sum).get();
int roll = ThreadLocalRandom.current().nextInt(1, totalProbabilityMass + 1); int roll = ThreadLocalRandom.current().nextInt(1, totalProbabilityMass + 1);
int currentTotalChance = 0; int currentTotalChance = 0;
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
currentTotalChance += probabilities.get(i); currentTotalChance += probabilities.get(i);
if (roll <= currentTotalChance) { if (roll <= currentTotalChance) {
return list.get(i); return list.get(i);
} }
} }
// Should never happen. // Should never happen.
return list.get(0); return list.get(0);
} }
/*** /***
* Draws a random element from the given list, following a uniform probability distribution. * Draws a random element from the given list, following a uniform probability distribution.
* @param list The list from which to draw the element. * @param list The list from which to draw the element.
* @return A randomly drawn element from the given list. * @return A randomly drawn element from the given list.
*/ */
public static <T> T drawRandomListElement(List<T> list) { public static <T> T drawRandomListElement(List<T> list) {
return drawRandomListElement(list, null); return drawRandomListElement(list, null);
} }
/*** /***
* Splits a string by a character, into a list * Splits a string by a character, into a list
* @param input The string to split * @param input The string to split
* @param separator The character to use as the split points * @param separator The character to use as the split points
* @return A list of all the substrings * @return A list of all the substrings
*/ */
public static List<String> nonRegexSplit(String input, int separator) { public static List<String> nonRegexSplit(String input, int separator) {
var output = new ArrayList<String>(); var output = new ArrayList<String>();
int start = 0; int start = 0;
for (int next = input.indexOf(separator); next > 0; next = input.indexOf(separator, start)) { for (int next = input.indexOf(separator); next > 0; next = input.indexOf(separator, start)) {
output.add(input.substring(start, next)); output.add(input.substring(start, next));
start = next + 1; start = next + 1;
} }
if (start < input.length()) output.add(input.substring(start)); if (start < input.length()) output.add(input.substring(start));
return output; return output;
} }
} }

View File

@ -1,28 +1,28 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import java.util.NavigableMap; import java.util.NavigableMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
public class WeightedList<E> { public class WeightedList<E> {
private final NavigableMap<Double, E> map = new TreeMap<Double, E>(); private final NavigableMap<Double, E> map = new TreeMap<Double, E>();
private double total = 0; private double total = 0;
public WeightedList() {} public WeightedList() {}
public WeightedList<E> add(double weight, E result) { public WeightedList<E> add(double weight, E result) {
if (weight <= 0) return this; if (weight <= 0) return this;
total += weight; total += weight;
map.put(total, result); map.put(total, result);
return this; return this;
} }
public E next() { public E next() {
double value = ThreadLocalRandom.current().nextDouble() * total; double value = ThreadLocalRandom.current().nextDouble() * total;
return map.higherEntry(value).getValue(); return map.higherEntry(value).getValue();
} }
public int size() { public int size() {
return map.size(); return map.size();
} }
} }

View File

@ -1,60 +1,60 @@
package io.grasscutter; package io.grasscutter;
import com.mchange.util.AssertException; import com.mchange.util.AssertException;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.config.Configuration; import emu.grasscutter.config.Configuration;
import java.io.IOException; import java.io.IOException;
import lombok.Getter; import lombok.Getter;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
/** Testing entrypoint for {@link Grasscutter}. */ /** Testing entrypoint for {@link Grasscutter}. */
public final class GrasscutterTest { public final class GrasscutterTest {
@Getter private static final OkHttpClient httpClient = new OkHttpClient(); @Getter private static final OkHttpClient httpClient = new OkHttpClient();
@Getter private static int httpPort = -1; @Getter private static int httpPort = -1;
@Getter private static int gamePort = -1; @Getter private static int gamePort = -1;
/** /**
* Creates an HTTP URL. * Creates an HTTP URL.
* *
* @param route The route to use. * @param route The route to use.
* @return The URL. * @return The URL.
*/ */
public static String http(String route) { public static String http(String route) {
return "http://127.0.0.1:" + GrasscutterTest.httpPort + "/" + route; return "http://127.0.0.1:" + GrasscutterTest.httpPort + "/" + route;
} }
@BeforeAll @BeforeAll
public static void main() { public static void main() {
try { try {
// Start Grasscutter. // Start Grasscutter.
Grasscutter.main(new String[] {"-test"}); Grasscutter.main(new String[] {"-test"});
} catch (Exception ignored) { } catch (Exception ignored) {
throw new AssertException("Grasscutter failed to start."); throw new AssertException("Grasscutter failed to start.");
} }
// Set the ports. // Set the ports.
GrasscutterTest.httpPort = Configuration.SERVER.http.bindPort; GrasscutterTest.httpPort = Configuration.SERVER.http.bindPort;
GrasscutterTest.gamePort = Configuration.SERVER.game.bindPort; GrasscutterTest.gamePort = Configuration.SERVER.game.bindPort;
} }
@Test @Test
@DisplayName("HTTP server check") @DisplayName("HTTP server check")
public void checkHttpServer() { public void checkHttpServer() {
// Create a request. // Create a request.
var request = new Request.Builder().url(GrasscutterTest.http("")).build(); var request = new Request.Builder().url(GrasscutterTest.http("")).build();
// Perform the request. // Perform the request.
try (var response = GrasscutterTest.httpClient.newCall(request).execute()) { try (var response = GrasscutterTest.httpClient.newCall(request).execute()) {
// Check the response. // Check the response.
Assertions.assertTrue(response.isSuccessful()); Assertions.assertTrue(response.isSuccessful());
} catch (IOException exception) { } catch (IOException exception) {
throw new AssertionError(exception); throw new AssertionError(exception);
} }
} }
} }