mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-01-25 17:02:57 +08:00
line separators??
This commit is contained in:
parent
349f76b7d8
commit
6819ef6bdc
@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user