mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-01-10 12:33:21 +08:00
line separators??
This commit is contained in:
parent
349f76b7d8
commit
6819ef6bdc
@ -1,29 +1,29 @@
|
||||
package emu.grasscutter.command.commands;
|
||||
|
||||
import static emu.grasscutter.utils.Language.translate;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.command.Command;
|
||||
import emu.grasscutter.command.CommandHandler;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import java.util.List;
|
||||
|
||||
@Command(
|
||||
label = "reload",
|
||||
permission = "server.reload",
|
||||
targetRequirement = Command.TargetRequirement.NONE)
|
||||
public final class ReloadCommand implements CommandHandler {
|
||||
|
||||
@Override
|
||||
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start"));
|
||||
|
||||
Grasscutter.loadConfig();
|
||||
Grasscutter.loadLanguage();
|
||||
Grasscutter.getGameServer().getGachaSystem().load();
|
||||
Grasscutter.getGameServer().getDropSystem().load();
|
||||
Grasscutter.getGameServer().getShopSystem().load();
|
||||
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done"));
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.command.commands;
|
||||
|
||||
import static emu.grasscutter.utils.Language.translate;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.command.Command;
|
||||
import emu.grasscutter.command.CommandHandler;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import java.util.List;
|
||||
|
||||
@Command(
|
||||
label = "reload",
|
||||
permission = "server.reload",
|
||||
targetRequirement = Command.TargetRequirement.NONE)
|
||||
public final class ReloadCommand implements CommandHandler {
|
||||
|
||||
@Override
|
||||
public void execute(Player sender, Player targetPlayer, List<String> args) {
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start"));
|
||||
|
||||
Grasscutter.loadConfig();
|
||||
Grasscutter.loadLanguage();
|
||||
Grasscutter.getGameServer().getGachaSystem().load();
|
||||
Grasscutter.getGameServer().getDropSystem().load();
|
||||
Grasscutter.getGameServer().getShopSystem().load();
|
||||
|
||||
CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done"));
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +1,51 @@
|
||||
package emu.grasscutter.game.drop;
|
||||
|
||||
public class DropData {
|
||||
private int minWeight;
|
||||
private int maxWeight;
|
||||
private int itemId;
|
||||
private int minCount;
|
||||
private int maxCount;
|
||||
private boolean share = false;
|
||||
private boolean give = false;
|
||||
|
||||
public boolean isGive() {
|
||||
return give;
|
||||
}
|
||||
|
||||
public void setGive(boolean give) {
|
||||
this.give = give;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
public void setItemId(int itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
public int getMinCount() {
|
||||
return minCount;
|
||||
}
|
||||
|
||||
public int getMaxCount() {
|
||||
return maxCount;
|
||||
}
|
||||
|
||||
public int getMinWeight() {
|
||||
return minWeight;
|
||||
}
|
||||
|
||||
public int getMaxWeight() {
|
||||
return maxWeight;
|
||||
}
|
||||
|
||||
public boolean isShare() {
|
||||
return share;
|
||||
}
|
||||
|
||||
public void setIsShare(boolean share) {
|
||||
this.share = share;
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.drop;
|
||||
|
||||
public class DropData {
|
||||
private int minWeight;
|
||||
private int maxWeight;
|
||||
private int itemId;
|
||||
private int minCount;
|
||||
private int maxCount;
|
||||
private boolean share = false;
|
||||
private boolean give = false;
|
||||
|
||||
public boolean isGive() {
|
||||
return give;
|
||||
}
|
||||
|
||||
public void setGive(boolean give) {
|
||||
this.give = give;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
public void setItemId(int itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
public int getMinCount() {
|
||||
return minCount;
|
||||
}
|
||||
|
||||
public int getMaxCount() {
|
||||
return maxCount;
|
||||
}
|
||||
|
||||
public int getMinWeight() {
|
||||
return minWeight;
|
||||
}
|
||||
|
||||
public int getMaxWeight() {
|
||||
return maxWeight;
|
||||
}
|
||||
|
||||
public boolean isShare() {
|
||||
return share;
|
||||
}
|
||||
|
||||
public void setIsShare(boolean share) {
|
||||
this.share = share;
|
||||
}
|
||||
}
|
||||
|
@ -1,111 +1,111 @@
|
||||
package emu.grasscutter.game.drop;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.DataLoader;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.excels.ItemData;
|
||||
import emu.grasscutter.game.entity.EntityItem;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.game.inventory.GameItem;
|
||||
import emu.grasscutter.game.inventory.ItemType;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.ActionReason;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.server.game.BaseGameSystem;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class DropSystem extends BaseGameSystem {
|
||||
private final Int2ObjectMap<List<DropData>> dropData;
|
||||
|
||||
public DropSystem(GameServer server) {
|
||||
super(server);
|
||||
this.dropData = new Int2ObjectOpenHashMap<>();
|
||||
this.load();
|
||||
}
|
||||
|
||||
public Int2ObjectMap<List<DropData>> getDropData() {
|
||||
return dropData;
|
||||
}
|
||||
|
||||
public synchronized void load() {
|
||||
getDropData().clear();
|
||||
try {
|
||||
List<DropInfo> banners = DataLoader.loadList("Drop.json", DropInfo.class);
|
||||
if (banners.size() > 0) {
|
||||
for (DropInfo di : banners) {
|
||||
getDropData().put(di.getMonsterId(), di.getDropDataList());
|
||||
}
|
||||
Grasscutter.getLogger().debug("Drop data successfully loaded.");
|
||||
} else {
|
||||
Grasscutter.getLogger().error("Unable to load drop data. Drop data size is 0.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Grasscutter.getLogger().error("Unable to load drop data.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDropEntity(
|
||||
DropData dd, Scene dropScene, ItemData itemData, Position pos, int num, Player target) {
|
||||
if (!dd.isGive()
|
||||
&& (itemData.getItemType() != ItemType.ITEM_VIRTUAL || itemData.getGadgetId() != 0)) {
|
||||
EntityItem entity = new EntityItem(dropScene, target, itemData, pos, num, dd.isShare());
|
||||
if (!dd.isShare()) dropScene.addEntityToSingleClient(target, entity);
|
||||
else dropScene.addEntity(entity);
|
||||
} else {
|
||||
if (target != null) {
|
||||
target.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true);
|
||||
} else {
|
||||
// target is null if items will be added are shared. no one could pick it up because of the
|
||||
// combination(give + shared)
|
||||
// so it will be sent to all players' inventories directly.
|
||||
dropScene
|
||||
.getPlayers()
|
||||
.forEach(
|
||||
x ->
|
||||
x.getInventory()
|
||||
.addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processDrop(DropData dd, EntityMonster em, Player gp) {
|
||||
int target = Utils.randomRange(1, 10000);
|
||||
if (target >= dd.getMinWeight() && target < dd.getMaxWeight()) {
|
||||
ItemData itemData = GameData.getItemDataMap().get(dd.getItemId());
|
||||
int num = Utils.randomRange(dd.getMinCount(), dd.getMaxCount());
|
||||
|
||||
if (itemData == null) {
|
||||
return;
|
||||
}
|
||||
if (itemData.isEquip()) {
|
||||
for (int i = 0; i < num; i++) {
|
||||
float range = (2.5f + (.05f * num));
|
||||
Position pos = em.getPosition().nearby2d(range).addY(3f);
|
||||
addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
|
||||
}
|
||||
} else {
|
||||
Position pos = em.getPosition().clone().addY(3f);
|
||||
addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void callDrop(EntityMonster em) {
|
||||
int id = em.getMonsterData().getId();
|
||||
if (getDropData().containsKey(id)) {
|
||||
for (DropData dd : getDropData().get(id)) {
|
||||
if (dd.isShare()) processDrop(dd, em, null);
|
||||
else {
|
||||
for (Player gp : em.getScene().getPlayers()) {
|
||||
processDrop(dd, em, gp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.drop;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.DataLoader;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.excels.ItemData;
|
||||
import emu.grasscutter.game.entity.EntityItem;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.game.inventory.GameItem;
|
||||
import emu.grasscutter.game.inventory.ItemType;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.ActionReason;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.server.game.BaseGameSystem;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class DropSystem extends BaseGameSystem {
|
||||
private final Int2ObjectMap<List<DropData>> dropData;
|
||||
|
||||
public DropSystem(GameServer server) {
|
||||
super(server);
|
||||
this.dropData = new Int2ObjectOpenHashMap<>();
|
||||
this.load();
|
||||
}
|
||||
|
||||
public Int2ObjectMap<List<DropData>> getDropData() {
|
||||
return dropData;
|
||||
}
|
||||
|
||||
public synchronized void load() {
|
||||
getDropData().clear();
|
||||
try {
|
||||
List<DropInfo> banners = DataLoader.loadList("Drop.json", DropInfo.class);
|
||||
if (banners.size() > 0) {
|
||||
for (DropInfo di : banners) {
|
||||
getDropData().put(di.getMonsterId(), di.getDropDataList());
|
||||
}
|
||||
Grasscutter.getLogger().debug("Drop data successfully loaded.");
|
||||
} else {
|
||||
Grasscutter.getLogger().error("Unable to load drop data. Drop data size is 0.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Grasscutter.getLogger().error("Unable to load drop data.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDropEntity(
|
||||
DropData dd, Scene dropScene, ItemData itemData, Position pos, int num, Player target) {
|
||||
if (!dd.isGive()
|
||||
&& (itemData.getItemType() != ItemType.ITEM_VIRTUAL || itemData.getGadgetId() != 0)) {
|
||||
EntityItem entity = new EntityItem(dropScene, target, itemData, pos, num, dd.isShare());
|
||||
if (!dd.isShare()) dropScene.addEntityToSingleClient(target, entity);
|
||||
else dropScene.addEntity(entity);
|
||||
} else {
|
||||
if (target != null) {
|
||||
target.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true);
|
||||
} else {
|
||||
// target is null if items will be added are shared. no one could pick it up because of the
|
||||
// combination(give + shared)
|
||||
// so it will be sent to all players' inventories directly.
|
||||
dropScene
|
||||
.getPlayers()
|
||||
.forEach(
|
||||
x ->
|
||||
x.getInventory()
|
||||
.addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processDrop(DropData dd, EntityMonster em, Player gp) {
|
||||
int target = Utils.randomRange(1, 10000);
|
||||
if (target >= dd.getMinWeight() && target < dd.getMaxWeight()) {
|
||||
ItemData itemData = GameData.getItemDataMap().get(dd.getItemId());
|
||||
int num = Utils.randomRange(dd.getMinCount(), dd.getMaxCount());
|
||||
|
||||
if (itemData == null) {
|
||||
return;
|
||||
}
|
||||
if (itemData.isEquip()) {
|
||||
for (int i = 0; i < num; i++) {
|
||||
float range = (2.5f + (.05f * num));
|
||||
Position pos = em.getPosition().nearby2d(range).addY(3f);
|
||||
addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
|
||||
}
|
||||
} else {
|
||||
Position pos = em.getPosition().clone().addY(3f);
|
||||
addDropEntity(dd, em.getScene(), itemData, pos, num, gp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void callDrop(EntityMonster em) {
|
||||
int id = em.getMonsterData().getId();
|
||||
if (getDropData().containsKey(id)) {
|
||||
for (DropData dd : getDropData().get(id)) {
|
||||
if (dd.isShare()) processDrop(dd, em, null);
|
||||
else {
|
||||
for (Player gp : em.getScene().getPlayers()) {
|
||||
processDrop(dd, em, gp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,88 +1,88 @@
|
||||
package emu.grasscutter.game.entity.gadget;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo;
|
||||
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
|
||||
import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType;
|
||||
import emu.grasscutter.net.proto.InteractTypeOuterClass;
|
||||
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
|
||||
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
|
||||
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
|
||||
import emu.grasscutter.scripts.constants.ScriptGadgetState;
|
||||
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
|
||||
|
||||
public class GadgetChest extends GadgetContent {
|
||||
|
||||
public GadgetChest(EntityGadget gadget) {
|
||||
super(gadget);
|
||||
}
|
||||
|
||||
public boolean onInteract(Player player, GadgetInteractReq req) {
|
||||
var chestInteractHandlerMap =
|
||||
getGadget()
|
||||
.getScene()
|
||||
.getWorld()
|
||||
.getServer()
|
||||
.getWorldDataSystem()
|
||||
.getChestInteractHandlerMap();
|
||||
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
|
||||
if (handler == null) {
|
||||
Grasscutter.getLogger()
|
||||
.warn(
|
||||
"Could not found the handler of this type of Chests {}",
|
||||
getGadget().getGadgetData().getJsonName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) {
|
||||
player.sendPacket(
|
||||
new PacketGadgetInteractRsp(
|
||||
getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
|
||||
return false;
|
||||
} else {
|
||||
boolean success;
|
||||
if (handler instanceof BossChestInteractHandler bossChestInteractHandler) {
|
||||
success =
|
||||
bossChestInteractHandler.onInteract(
|
||||
this,
|
||||
player,
|
||||
req.getResinCostType()
|
||||
== ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE);
|
||||
} else {
|
||||
success = handler.onInteract(this, player);
|
||||
}
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getGadget().updateState(ScriptGadgetState.ChestOpened);
|
||||
player.sendPacket(
|
||||
new PacketGadgetInteractRsp(
|
||||
this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
|
||||
if (getGadget().getMetaGadget() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bossChest = getGadget().getMetaGadget().boss_chest;
|
||||
if (bossChest != null) {
|
||||
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
|
||||
|
||||
gadgetInfo.setBossChest(
|
||||
BossChestInfo.newBuilder()
|
||||
.setMonsterConfigId(bossChest.monster_config_id)
|
||||
.setResin(bossChest.resin)
|
||||
.addAllQualifyUidList(players)
|
||||
.addAllRemainUidList(players)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.entity.gadget;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.entity.gadget.chest.BossChestInteractHandler;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo;
|
||||
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
|
||||
import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType;
|
||||
import emu.grasscutter.net.proto.InteractTypeOuterClass;
|
||||
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
|
||||
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
|
||||
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
|
||||
import emu.grasscutter.scripts.constants.ScriptGadgetState;
|
||||
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
|
||||
|
||||
public class GadgetChest extends GadgetContent {
|
||||
|
||||
public GadgetChest(EntityGadget gadget) {
|
||||
super(gadget);
|
||||
}
|
||||
|
||||
public boolean onInteract(Player player, GadgetInteractReq req) {
|
||||
var chestInteractHandlerMap =
|
||||
getGadget()
|
||||
.getScene()
|
||||
.getWorld()
|
||||
.getServer()
|
||||
.getWorldDataSystem()
|
||||
.getChestInteractHandlerMap();
|
||||
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
|
||||
if (handler == null) {
|
||||
Grasscutter.getLogger()
|
||||
.warn(
|
||||
"Could not found the handler of this type of Chests {}",
|
||||
getGadget().getGadgetData().getJsonName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) {
|
||||
player.sendPacket(
|
||||
new PacketGadgetInteractRsp(
|
||||
getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
|
||||
return false;
|
||||
} else {
|
||||
boolean success;
|
||||
if (handler instanceof BossChestInteractHandler bossChestInteractHandler) {
|
||||
success =
|
||||
bossChestInteractHandler.onInteract(
|
||||
this,
|
||||
player,
|
||||
req.getResinCostType()
|
||||
== ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE);
|
||||
} else {
|
||||
success = handler.onInteract(this, player);
|
||||
}
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getGadget().updateState(ScriptGadgetState.ChestOpened);
|
||||
player.sendPacket(
|
||||
new PacketGadgetInteractRsp(
|
||||
this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
|
||||
if (getGadget().getMetaGadget() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bossChest = getGadget().getMetaGadget().boss_chest;
|
||||
if (bossChest != null) {
|
||||
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
|
||||
|
||||
gadgetInfo.setBossChest(
|
||||
BossChestInfo.newBuilder()
|
||||
.setMonsterConfigId(bossChest.monster_config_id)
|
||||
.setResin(bossChest.resin)
|
||||
.addAllQualifyUidList(players)
|
||||
.addAllRemainUidList(players)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.ConsoleAppender;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class JlineLogbackAppender extends ConsoleAppender<ILoggingEvent> {
|
||||
@Override
|
||||
protected void append(ILoggingEvent eventObject) {
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
Arrays.stream(new String(encoder.encode(eventObject)).split("\n\r"))
|
||||
.forEach(Grasscutter.getConsole()::printAbove);
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.ConsoleAppender;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class JlineLogbackAppender extends ConsoleAppender<ILoggingEvent> {
|
||||
@Override
|
||||
protected void append(ILoggingEvent eventObject) {
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
Arrays.stream(new String(encoder.encode(eventObject)).split("\n\r"))
|
||||
.forEach(Grasscutter.getConsole()::printAbove);
|
||||
}
|
||||
}
|
||||
|
@ -1,171 +1,171 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import emu.grasscutter.data.common.DynamicFloat;
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import lombok.val;
|
||||
|
||||
public class JsonAdapters {
|
||||
static class DynamicFloatAdapter extends TypeAdapter<DynamicFloat> {
|
||||
@Override
|
||||
public DynamicFloat read(JsonReader reader) throws IOException {
|
||||
switch (reader.peek()) {
|
||||
case STRING:
|
||||
return new DynamicFloat(reader.nextString());
|
||||
case NUMBER:
|
||||
return new DynamicFloat((float) reader.nextDouble());
|
||||
case BOOLEAN:
|
||||
return new DynamicFloat(reader.nextBoolean());
|
||||
case BEGIN_ARRAY:
|
||||
reader.beginArray();
|
||||
val opStack = new ArrayList<DynamicFloat.StackOp>();
|
||||
while (reader.hasNext()) {
|
||||
opStack.add(
|
||||
switch (reader.peek()) {
|
||||
case STRING -> new DynamicFloat.StackOp(reader.nextString());
|
||||
case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble());
|
||||
case BOOLEAN -> new DynamicFloat.StackOp(reader.nextBoolean());
|
||||
default -> throw new IOException(
|
||||
"Invalid DynamicFloat definition - " + reader.peek().name());
|
||||
});
|
||||
}
|
||||
reader.endArray();
|
||||
return new DynamicFloat(opStack);
|
||||
default:
|
||||
throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, DynamicFloat f) {}
|
||||
}
|
||||
|
||||
static class IntListAdapter extends TypeAdapter<IntList> {
|
||||
@Override
|
||||
public IntList read(JsonReader reader) throws IOException {
|
||||
if (Objects.requireNonNull(reader.peek()) == JsonToken.BEGIN_ARRAY) {
|
||||
reader.beginArray();
|
||||
val i = new IntArrayList();
|
||||
while (reader.hasNext()) i.add(reader.nextInt());
|
||||
reader.endArray();
|
||||
i.trim(); // We might have a ton of these from resources and almost all of them
|
||||
// immutable, don't overprovision!
|
||||
return i;
|
||||
}
|
||||
throw new IOException("Invalid IntList definition - " + reader.peek().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, IntList l) throws IOException {
|
||||
writer.beginArray();
|
||||
for (val i : l) // .forEach() doesn't appreciate exceptions
|
||||
writer.value(i);
|
||||
writer.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
static class PositionAdapter extends TypeAdapter<Position> {
|
||||
@Override
|
||||
public Position read(JsonReader reader) throws IOException {
|
||||
switch (reader.peek()) {
|
||||
case BEGIN_ARRAY: // "pos": [x,y,z]
|
||||
reader.beginArray();
|
||||
val array = new FloatArrayList(3);
|
||||
while (reader.hasNext()) array.add(reader.nextInt());
|
||||
reader.endArray();
|
||||
return new Position(array);
|
||||
case BEGIN_OBJECT: // "pos": {"x": x, "y": y, "z": z}
|
||||
float x = 0f;
|
||||
float y = 0f;
|
||||
float z = 0f;
|
||||
reader.beginObject();
|
||||
for (var next = reader.peek(); next != JsonToken.END_OBJECT; next = reader.peek()) {
|
||||
val name = reader.nextName();
|
||||
switch (name) {
|
||||
case "x", "X", "_x" -> x = (float) reader.nextDouble();
|
||||
case "y", "Y", "_y" -> y = (float) reader.nextDouble();
|
||||
case "z", "Z", "_z" -> z = (float) reader.nextDouble();
|
||||
default -> throw new IOException("Invalid field in Position definition - " + name);
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
return new Position(x, y, z);
|
||||
default:
|
||||
throw new IOException("Invalid Position definition - " + reader.peek().name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, Position i) throws IOException {
|
||||
writer.beginArray();
|
||||
writer.value(i.getX());
|
||||
writer.value(i.getY());
|
||||
writer.value(i.getZ());
|
||||
writer.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
static class EnumTypeAdapterFactory implements TypeAdapterFactory {
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
Class<T> enumClass = (Class<T>) type.getRawType();
|
||||
if (!enumClass.isEnum()) return null;
|
||||
|
||||
// Make mappings of (string) names to enum constants
|
||||
val map = new HashMap<String, T>();
|
||||
val enumConstants = enumClass.getEnumConstants();
|
||||
for (val constant : enumConstants) map.put(constant.toString(), constant);
|
||||
|
||||
// If the enum also has a numeric value, map those to the constants too
|
||||
// System.out.println("Looking for enum value field");
|
||||
for (Field f : enumClass.getDeclaredFields()) {
|
||||
if (switch (f.getName()) {
|
||||
case "value", "id" -> true;
|
||||
default -> false;
|
||||
}) {
|
||||
// System.out.println("Enum value field found - " + f.getName());
|
||||
boolean acc = f.isAccessible();
|
||||
f.setAccessible(true);
|
||||
try {
|
||||
for (val constant : enumConstants)
|
||||
map.put(String.valueOf(f.getInt(constant)), constant);
|
||||
} catch (IllegalAccessException e) {
|
||||
// System.out.println("Failed to access enum id field.");
|
||||
}
|
||||
f.setAccessible(acc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new TypeAdapter<T>() {
|
||||
public T read(JsonReader reader) throws IOException {
|
||||
switch (reader.peek()) {
|
||||
case STRING:
|
||||
return map.get(reader.nextString());
|
||||
case NUMBER:
|
||||
return map.get(String.valueOf(reader.nextInt()));
|
||||
default:
|
||||
throw new IOException("Invalid Enum definition - " + reader.peek().name());
|
||||
}
|
||||
}
|
||||
|
||||
public void write(JsonWriter writer, T value) throws IOException {
|
||||
writer.value(value.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import emu.grasscutter.data.common.DynamicFloat;
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import lombok.val;
|
||||
|
||||
public class JsonAdapters {
|
||||
static class DynamicFloatAdapter extends TypeAdapter<DynamicFloat> {
|
||||
@Override
|
||||
public DynamicFloat read(JsonReader reader) throws IOException {
|
||||
switch (reader.peek()) {
|
||||
case STRING:
|
||||
return new DynamicFloat(reader.nextString());
|
||||
case NUMBER:
|
||||
return new DynamicFloat((float) reader.nextDouble());
|
||||
case BOOLEAN:
|
||||
return new DynamicFloat(reader.nextBoolean());
|
||||
case BEGIN_ARRAY:
|
||||
reader.beginArray();
|
||||
val opStack = new ArrayList<DynamicFloat.StackOp>();
|
||||
while (reader.hasNext()) {
|
||||
opStack.add(
|
||||
switch (reader.peek()) {
|
||||
case STRING -> new DynamicFloat.StackOp(reader.nextString());
|
||||
case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble());
|
||||
case BOOLEAN -> new DynamicFloat.StackOp(reader.nextBoolean());
|
||||
default -> throw new IOException(
|
||||
"Invalid DynamicFloat definition - " + reader.peek().name());
|
||||
});
|
||||
}
|
||||
reader.endArray();
|
||||
return new DynamicFloat(opStack);
|
||||
default:
|
||||
throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, DynamicFloat f) {}
|
||||
}
|
||||
|
||||
static class IntListAdapter extends TypeAdapter<IntList> {
|
||||
@Override
|
||||
public IntList read(JsonReader reader) throws IOException {
|
||||
if (Objects.requireNonNull(reader.peek()) == JsonToken.BEGIN_ARRAY) {
|
||||
reader.beginArray();
|
||||
val i = new IntArrayList();
|
||||
while (reader.hasNext()) i.add(reader.nextInt());
|
||||
reader.endArray();
|
||||
i.trim(); // We might have a ton of these from resources and almost all of them
|
||||
// immutable, don't overprovision!
|
||||
return i;
|
||||
}
|
||||
throw new IOException("Invalid IntList definition - " + reader.peek().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, IntList l) throws IOException {
|
||||
writer.beginArray();
|
||||
for (val i : l) // .forEach() doesn't appreciate exceptions
|
||||
writer.value(i);
|
||||
writer.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
static class PositionAdapter extends TypeAdapter<Position> {
|
||||
@Override
|
||||
public Position read(JsonReader reader) throws IOException {
|
||||
switch (reader.peek()) {
|
||||
case BEGIN_ARRAY: // "pos": [x,y,z]
|
||||
reader.beginArray();
|
||||
val array = new FloatArrayList(3);
|
||||
while (reader.hasNext()) array.add(reader.nextInt());
|
||||
reader.endArray();
|
||||
return new Position(array);
|
||||
case BEGIN_OBJECT: // "pos": {"x": x, "y": y, "z": z}
|
||||
float x = 0f;
|
||||
float y = 0f;
|
||||
float z = 0f;
|
||||
reader.beginObject();
|
||||
for (var next = reader.peek(); next != JsonToken.END_OBJECT; next = reader.peek()) {
|
||||
val name = reader.nextName();
|
||||
switch (name) {
|
||||
case "x", "X", "_x" -> x = (float) reader.nextDouble();
|
||||
case "y", "Y", "_y" -> y = (float) reader.nextDouble();
|
||||
case "z", "Z", "_z" -> z = (float) reader.nextDouble();
|
||||
default -> throw new IOException("Invalid field in Position definition - " + name);
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
return new Position(x, y, z);
|
||||
default:
|
||||
throw new IOException("Invalid Position definition - " + reader.peek().name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, Position i) throws IOException {
|
||||
writer.beginArray();
|
||||
writer.value(i.getX());
|
||||
writer.value(i.getY());
|
||||
writer.value(i.getZ());
|
||||
writer.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
static class EnumTypeAdapterFactory implements TypeAdapterFactory {
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
Class<T> enumClass = (Class<T>) type.getRawType();
|
||||
if (!enumClass.isEnum()) return null;
|
||||
|
||||
// Make mappings of (string) names to enum constants
|
||||
val map = new HashMap<String, T>();
|
||||
val enumConstants = enumClass.getEnumConstants();
|
||||
for (val constant : enumConstants) map.put(constant.toString(), constant);
|
||||
|
||||
// If the enum also has a numeric value, map those to the constants too
|
||||
// System.out.println("Looking for enum value field");
|
||||
for (Field f : enumClass.getDeclaredFields()) {
|
||||
if (switch (f.getName()) {
|
||||
case "value", "id" -> true;
|
||||
default -> false;
|
||||
}) {
|
||||
// System.out.println("Enum value field found - " + f.getName());
|
||||
boolean acc = f.isAccessible();
|
||||
f.setAccessible(true);
|
||||
try {
|
||||
for (val constant : enumConstants)
|
||||
map.put(String.valueOf(f.getInt(constant)), constant);
|
||||
} catch (IllegalAccessException e) {
|
||||
// System.out.println("Failed to access enum id field.");
|
||||
}
|
||||
f.setAccessible(acc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new TypeAdapter<T>() {
|
||||
public T read(JsonReader reader) throws IOException {
|
||||
switch (reader.peek()) {
|
||||
case STRING:
|
||||
return map.get(reader.nextString());
|
||||
case NUMBER:
|
||||
return map.get(String.valueOf(reader.nextInt()));
|
||||
default:
|
||||
throw new IOException("Invalid Enum definition - " + reader.peek().name());
|
||||
}
|
||||
}
|
||||
|
||||
public void write(JsonWriter writer, T value) throws IOException {
|
||||
writer.value(value.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,129 +1,129 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import emu.grasscutter.data.common.DynamicFloat;
|
||||
import emu.grasscutter.utils.JsonAdapters.DynamicFloatAdapter;
|
||||
import emu.grasscutter.utils.JsonAdapters.EnumTypeAdapterFactory;
|
||||
import emu.grasscutter.utils.JsonAdapters.IntListAdapter;
|
||||
import emu.grasscutter.utils.JsonAdapters.PositionAdapter;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class JsonUtils {
|
||||
static final Gson gson =
|
||||
new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(DynamicFloat.class, new DynamicFloatAdapter())
|
||||
.registerTypeAdapter(IntList.class, new IntListAdapter())
|
||||
.registerTypeAdapter(Position.class, new PositionAdapter())
|
||||
.registerTypeAdapterFactory(new EnumTypeAdapterFactory())
|
||||
.create();
|
||||
|
||||
/*
|
||||
* Encode an object to a JSON string
|
||||
*/
|
||||
public static String encode(Object object) {
|
||||
return gson.toJson(object);
|
||||
}
|
||||
|
||||
public static <T> T decode(JsonElement jsonElement, Class<T> classType)
|
||||
throws JsonSyntaxException {
|
||||
return gson.fromJson(jsonElement, classType);
|
||||
}
|
||||
|
||||
public static <T> T loadToClass(Reader fileReader, Class<T> classType) throws IOException {
|
||||
return gson.fromJson(fileReader, classType);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T> T loadToClass(String filename, Class<T> classType) throws IOException {
|
||||
try (InputStreamReader fileReader =
|
||||
new InputStreamReader(
|
||||
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
|
||||
return loadToClass(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T loadToClass(Path filename, Class<T> classType) throws IOException {
|
||||
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
|
||||
return loadToClass(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> loadToList(Reader fileReader, Class<T> classType) throws IOException {
|
||||
return gson.fromJson(fileReader, TypeToken.getParameterized(List.class, classType).getType());
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T> List<T> loadToList(String filename, Class<T> classType) throws IOException {
|
||||
try (InputStreamReader fileReader =
|
||||
new InputStreamReader(
|
||||
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
|
||||
return loadToList(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> loadToList(Path filename, Class<T> classType) throws IOException {
|
||||
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
|
||||
return loadToList(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T1, T2> Map<T1, T2> loadToMap(
|
||||
Reader fileReader, Class<T1> keyType, Class<T2> valueType) throws IOException {
|
||||
return gson.fromJson(
|
||||
fileReader, TypeToken.getParameterized(Map.class, keyType, valueType).getType());
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T1, T2> Map<T1, T2> loadToMap(
|
||||
String filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
|
||||
try (InputStreamReader fileReader =
|
||||
new InputStreamReader(
|
||||
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
|
||||
return loadToMap(fileReader, keyType, valueType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T1, T2> Map<T1, T2> loadToMap(
|
||||
Path filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
|
||||
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
|
||||
return loadToMap(fileReader, keyType, valueType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely JSON decodes a given string.
|
||||
*
|
||||
* @param jsonData The JSON-encoded data.
|
||||
* @return JSON decoded data, or null if an exception occurred.
|
||||
*/
|
||||
public static <T> T decode(String jsonData, Class<T> classType) {
|
||||
try {
|
||||
return gson.fromJson(jsonData, classType);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T decode(String jsonData, Type type) {
|
||||
try {
|
||||
return gson.fromJson(jsonData, type);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import emu.grasscutter.data.common.DynamicFloat;
|
||||
import emu.grasscutter.utils.JsonAdapters.DynamicFloatAdapter;
|
||||
import emu.grasscutter.utils.JsonAdapters.EnumTypeAdapterFactory;
|
||||
import emu.grasscutter.utils.JsonAdapters.IntListAdapter;
|
||||
import emu.grasscutter.utils.JsonAdapters.PositionAdapter;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class JsonUtils {
|
||||
static final Gson gson =
|
||||
new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(DynamicFloat.class, new DynamicFloatAdapter())
|
||||
.registerTypeAdapter(IntList.class, new IntListAdapter())
|
||||
.registerTypeAdapter(Position.class, new PositionAdapter())
|
||||
.registerTypeAdapterFactory(new EnumTypeAdapterFactory())
|
||||
.create();
|
||||
|
||||
/*
|
||||
* Encode an object to a JSON string
|
||||
*/
|
||||
public static String encode(Object object) {
|
||||
return gson.toJson(object);
|
||||
}
|
||||
|
||||
public static <T> T decode(JsonElement jsonElement, Class<T> classType)
|
||||
throws JsonSyntaxException {
|
||||
return gson.fromJson(jsonElement, classType);
|
||||
}
|
||||
|
||||
public static <T> T loadToClass(Reader fileReader, Class<T> classType) throws IOException {
|
||||
return gson.fromJson(fileReader, classType);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T> T loadToClass(String filename, Class<T> classType) throws IOException {
|
||||
try (InputStreamReader fileReader =
|
||||
new InputStreamReader(
|
||||
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
|
||||
return loadToClass(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T loadToClass(Path filename, Class<T> classType) throws IOException {
|
||||
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
|
||||
return loadToClass(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> loadToList(Reader fileReader, Class<T> classType) throws IOException {
|
||||
return gson.fromJson(fileReader, TypeToken.getParameterized(List.class, classType).getType());
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T> List<T> loadToList(String filename, Class<T> classType) throws IOException {
|
||||
try (InputStreamReader fileReader =
|
||||
new InputStreamReader(
|
||||
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
|
||||
return loadToList(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> loadToList(Path filename, Class<T> classType) throws IOException {
|
||||
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
|
||||
return loadToList(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T1, T2> Map<T1, T2> loadToMap(
|
||||
Reader fileReader, Class<T1> keyType, Class<T2> valueType) throws IOException {
|
||||
return gson.fromJson(
|
||||
fileReader, TypeToken.getParameterized(Map.class, keyType, valueType).getType());
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T1, T2> Map<T1, T2> loadToMap(
|
||||
String filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
|
||||
try (InputStreamReader fileReader =
|
||||
new InputStreamReader(
|
||||
new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
|
||||
return loadToMap(fileReader, keyType, valueType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T1, T2> Map<T1, T2> loadToMap(
|
||||
Path filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
|
||||
try (var fileReader = Files.newBufferedReader(filename, StandardCharsets.UTF_8)) {
|
||||
return loadToMap(fileReader, keyType, valueType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely JSON decodes a given string.
|
||||
*
|
||||
* @param jsonData The JSON-encoded data.
|
||||
* @return JSON decoded data, or null if an exception occurred.
|
||||
*/
|
||||
public static <T> T decode(String jsonData, Class<T> classType) {
|
||||
try {
|
||||
return gson.fromJson(jsonData, classType);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T decode(String jsonData, Type type) {
|
||||
try {
|
||||
return gson.fromJson(jsonData, type);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,40 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Transient;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
public class Location extends Position {
|
||||
@Transient @Getter @Setter private Scene scene;
|
||||
|
||||
public Location(Scene scene, Position position) {
|
||||
this.set(position);
|
||||
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
public Location(Scene scene, float x, float y) {
|
||||
this.set(x, y);
|
||||
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
public Location(Scene scene, float x, float y, float z) {
|
||||
this.set(x, y, z);
|
||||
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location clone() {
|
||||
return new Location(this.scene, super.clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s:%s,%s,%s", this.scene.getId(), this.getX(), this.getY(), this.getZ());
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import dev.morphia.annotations.Entity;
|
||||
import dev.morphia.annotations.Transient;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
public class Location extends Position {
|
||||
@Transient @Getter @Setter private Scene scene;
|
||||
|
||||
public Location(Scene scene, Position position) {
|
||||
this.set(position);
|
||||
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
public Location(Scene scene, float x, float y) {
|
||||
this.set(x, y);
|
||||
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
public Location(Scene scene, float x, float y, float z) {
|
||||
this.set(x, y, z);
|
||||
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location clone() {
|
||||
return new Location(this.scene, super.clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s:%s,%s,%s", this.scene.getId(), this.getX(), this.getY(), this.getZ());
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
public class MessageHandler {
|
||||
private String message;
|
||||
|
||||
public MessageHandler() {
|
||||
this.message = "";
|
||||
}
|
||||
|
||||
public void append(String message) {
|
||||
this.message += message + "\r\n\r\n";
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
public class MessageHandler {
|
||||
private String message;
|
||||
|
||||
public MessageHandler() {
|
||||
this.message = "";
|
||||
}
|
||||
|
||||
public void append(String message) {
|
||||
this.message += message + "\r\n\r\n";
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
@ -1,196 +1,196 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import com.github.davidmoten.rtreemulti.geometry.Point;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
public class Position implements Serializable {
|
||||
private static final long serialVersionUID = -2001232313615923575L;
|
||||
|
||||
@SerializedName(
|
||||
value = "x",
|
||||
alternate = {"_x", "X"})
|
||||
@Getter
|
||||
@Setter
|
||||
private float x;
|
||||
|
||||
@SerializedName(
|
||||
value = "y",
|
||||
alternate = {"_y", "Y"})
|
||||
@Getter
|
||||
@Setter
|
||||
private float y;
|
||||
|
||||
@SerializedName(
|
||||
value = "z",
|
||||
alternate = {"_z", "Z"})
|
||||
@Getter
|
||||
@Setter
|
||||
private float z;
|
||||
|
||||
public Position() {}
|
||||
|
||||
public Position(float x, float y) {
|
||||
set(x, y);
|
||||
}
|
||||
|
||||
public Position(float x, float y, float z) {
|
||||
set(x, y, z);
|
||||
}
|
||||
|
||||
public Position(List<Float> xyz) {
|
||||
switch (xyz.size()) {
|
||||
default: // Might want to error on excess elements, but maybe we want to extend to 3+3
|
||||
// representation later.
|
||||
case 3:
|
||||
this.z = xyz.get(2); // Fall-through
|
||||
case 2:
|
||||
this.y = xyz.get(1); // Fall-through
|
||||
case 1:
|
||||
this.y = xyz.get(0); // pointless fall-through
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Position(String p) {
|
||||
String[] split = p.split(",");
|
||||
if (split.length >= 2) {
|
||||
this.x = Float.parseFloat(split[0]);
|
||||
this.y = Float.parseFloat(split[1]);
|
||||
}
|
||||
if (split.length >= 3) {
|
||||
this.z = Float.parseFloat(split[2]);
|
||||
}
|
||||
}
|
||||
|
||||
public Position(Vector vector) {
|
||||
this.set(vector);
|
||||
}
|
||||
|
||||
public Position(Position pos) {
|
||||
this.set(pos);
|
||||
}
|
||||
|
||||
public Position set(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Deep copy
|
||||
public Position set(Position pos) {
|
||||
return this.set(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
public Position set(Vector pos) {
|
||||
return this.set(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
public Position set(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position add(Position add) {
|
||||
this.x += add.getX();
|
||||
this.y += add.getY();
|
||||
this.z += add.getZ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position addX(float d) {
|
||||
this.x += d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position addY(float d) {
|
||||
this.y += d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position addZ(float d) {
|
||||
this.z += d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position subtract(Position sub) {
|
||||
this.x -= sub.getX();
|
||||
this.y -= sub.getY();
|
||||
this.z -= sub.getZ();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** In radians */
|
||||
public Position translate(float dist, float angle) {
|
||||
this.x += dist * Math.sin(angle);
|
||||
this.y += dist * Math.cos(angle);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean equal2d(Position other) {
|
||||
// Y is height
|
||||
return getX() == other.getX() && getZ() == other.getZ();
|
||||
}
|
||||
|
||||
public boolean equal3d(Position other) {
|
||||
return getX() == other.getX() && getY() == other.getY() && getZ() == other.getZ();
|
||||
}
|
||||
|
||||
public double computeDistance(Position b) {
|
||||
double detX = getX() - b.getX();
|
||||
double detY = getY() - b.getY();
|
||||
double detZ = getZ() - b.getZ();
|
||||
return Math.sqrt(detX * detX + detY * detY + detZ * detZ);
|
||||
}
|
||||
|
||||
public Position nearby2d(float range) {
|
||||
Position position = clone();
|
||||
position.z += Utils.randomFloatRange(-range, range);
|
||||
position.x += Utils.randomFloatRange(-range, range);
|
||||
return position;
|
||||
}
|
||||
|
||||
public Position translateWithDegrees(float dist, float angle) {
|
||||
angle = (float) Math.toRadians(angle);
|
||||
this.x += dist * Math.sin(angle);
|
||||
this.y += -dist * Math.cos(angle);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position clone() {
|
||||
return new Position(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")";
|
||||
}
|
||||
|
||||
public Vector toProto() {
|
||||
return Vector.newBuilder().setX(this.getX()).setY(this.getY()).setZ(this.getZ()).build();
|
||||
}
|
||||
|
||||
public Point toPoint() {
|
||||
return Point.create(x, y, z);
|
||||
}
|
||||
|
||||
/** To XYZ array for Spatial Index */
|
||||
public double[] toDoubleArray() {
|
||||
return new double[] {x, y, z};
|
||||
}
|
||||
|
||||
/** To XZ array for Spatial Index (Blocks) */
|
||||
public double[] toXZDoubleArray() {
|
||||
return new double[] {x, z};
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import com.github.davidmoten.rtreemulti.geometry.Point;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import dev.morphia.annotations.Entity;
|
||||
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
public class Position implements Serializable {
|
||||
private static final long serialVersionUID = -2001232313615923575L;
|
||||
|
||||
@SerializedName(
|
||||
value = "x",
|
||||
alternate = {"_x", "X"})
|
||||
@Getter
|
||||
@Setter
|
||||
private float x;
|
||||
|
||||
@SerializedName(
|
||||
value = "y",
|
||||
alternate = {"_y", "Y"})
|
||||
@Getter
|
||||
@Setter
|
||||
private float y;
|
||||
|
||||
@SerializedName(
|
||||
value = "z",
|
||||
alternate = {"_z", "Z"})
|
||||
@Getter
|
||||
@Setter
|
||||
private float z;
|
||||
|
||||
public Position() {}
|
||||
|
||||
public Position(float x, float y) {
|
||||
set(x, y);
|
||||
}
|
||||
|
||||
public Position(float x, float y, float z) {
|
||||
set(x, y, z);
|
||||
}
|
||||
|
||||
public Position(List<Float> xyz) {
|
||||
switch (xyz.size()) {
|
||||
default: // Might want to error on excess elements, but maybe we want to extend to 3+3
|
||||
// representation later.
|
||||
case 3:
|
||||
this.z = xyz.get(2); // Fall-through
|
||||
case 2:
|
||||
this.y = xyz.get(1); // Fall-through
|
||||
case 1:
|
||||
this.y = xyz.get(0); // pointless fall-through
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Position(String p) {
|
||||
String[] split = p.split(",");
|
||||
if (split.length >= 2) {
|
||||
this.x = Float.parseFloat(split[0]);
|
||||
this.y = Float.parseFloat(split[1]);
|
||||
}
|
||||
if (split.length >= 3) {
|
||||
this.z = Float.parseFloat(split[2]);
|
||||
}
|
||||
}
|
||||
|
||||
public Position(Vector vector) {
|
||||
this.set(vector);
|
||||
}
|
||||
|
||||
public Position(Position pos) {
|
||||
this.set(pos);
|
||||
}
|
||||
|
||||
public Position set(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Deep copy
|
||||
public Position set(Position pos) {
|
||||
return this.set(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
public Position set(Vector pos) {
|
||||
return this.set(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
public Position set(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position add(Position add) {
|
||||
this.x += add.getX();
|
||||
this.y += add.getY();
|
||||
this.z += add.getZ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position addX(float d) {
|
||||
this.x += d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position addY(float d) {
|
||||
this.y += d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position addZ(float d) {
|
||||
this.z += d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Position subtract(Position sub) {
|
||||
this.x -= sub.getX();
|
||||
this.y -= sub.getY();
|
||||
this.z -= sub.getZ();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** In radians */
|
||||
public Position translate(float dist, float angle) {
|
||||
this.x += dist * Math.sin(angle);
|
||||
this.y += dist * Math.cos(angle);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean equal2d(Position other) {
|
||||
// Y is height
|
||||
return getX() == other.getX() && getZ() == other.getZ();
|
||||
}
|
||||
|
||||
public boolean equal3d(Position other) {
|
||||
return getX() == other.getX() && getY() == other.getY() && getZ() == other.getZ();
|
||||
}
|
||||
|
||||
public double computeDistance(Position b) {
|
||||
double detX = getX() - b.getX();
|
||||
double detY = getY() - b.getY();
|
||||
double detZ = getZ() - b.getZ();
|
||||
return Math.sqrt(detX * detX + detY * detY + detZ * detZ);
|
||||
}
|
||||
|
||||
public Position nearby2d(float range) {
|
||||
Position position = clone();
|
||||
position.z += Utils.randomFloatRange(-range, range);
|
||||
position.x += Utils.randomFloatRange(-range, range);
|
||||
return position;
|
||||
}
|
||||
|
||||
public Position translateWithDegrees(float dist, float angle) {
|
||||
angle = (float) Math.toRadians(angle);
|
||||
this.x += dist * Math.sin(angle);
|
||||
this.y += -dist * Math.cos(angle);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position clone() {
|
||||
return new Position(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")";
|
||||
}
|
||||
|
||||
public Vector toProto() {
|
||||
return Vector.newBuilder().setX(this.getX()).setY(this.getY()).setZ(this.getZ()).build();
|
||||
}
|
||||
|
||||
public Point toPoint() {
|
||||
return Point.create(x, y, z);
|
||||
}
|
||||
|
||||
/** To XYZ array for Spatial Index */
|
||||
public double[] toDoubleArray() {
|
||||
return new double[] {x, y, z};
|
||||
}
|
||||
|
||||
/** To XZ array for Spatial Index (Blocks) */
|
||||
public double[] toXZDoubleArray() {
|
||||
return new double[] {x, z};
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.net.proto.PropValueOuterClass.PropValue;
|
||||
|
||||
public final class ProtoHelper {
|
||||
public static PropValue newPropValue(PlayerProperty key, int value) {
|
||||
return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build();
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.net.proto.PropValueOuterClass.PropValue;
|
||||
|
||||
public final class ProtoHelper {
|
||||
public static PropValue newPropValue(PlayerProperty key, int value) {
|
||||
return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build();
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.AppenderBase;
|
||||
import ch.qos.logback.core.encoder.Encoder;
|
||||
import emu.grasscutter.server.event.internal.ServerLogEvent;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ServerLogEventAppender<E> extends AppenderBase<E> {
|
||||
protected Encoder<E> encoder;
|
||||
|
||||
@Override
|
||||
protected void append(E event) {
|
||||
byte[] byteArray = this.encoder.encode(event);
|
||||
ServerLogEvent sle =
|
||||
new ServerLogEvent((ILoggingEvent) event, new String(byteArray, StandardCharsets.UTF_8));
|
||||
sle.call();
|
||||
}
|
||||
|
||||
public Encoder<E> getEncoder() {
|
||||
return this.encoder;
|
||||
}
|
||||
|
||||
public void setEncoder(Encoder<E> encoder) {
|
||||
this.encoder = encoder;
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.AppenderBase;
|
||||
import ch.qos.logback.core.encoder.Encoder;
|
||||
import emu.grasscutter.server.event.internal.ServerLogEvent;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ServerLogEventAppender<E> extends AppenderBase<E> {
|
||||
protected Encoder<E> encoder;
|
||||
|
||||
@Override
|
||||
protected void append(E event) {
|
||||
byte[] byteArray = this.encoder.encode(event);
|
||||
ServerLogEvent sle =
|
||||
new ServerLogEvent((ILoggingEvent) event, new String(byteArray, StandardCharsets.UTF_8));
|
||||
sle.call();
|
||||
}
|
||||
|
||||
public Encoder<E> getEncoder() {
|
||||
return this.encoder;
|
||||
}
|
||||
|
||||
public void setEncoder(Encoder<E> encoder) {
|
||||
this.encoder = encoder;
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +1,67 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public final class SparseSet {
|
||||
private final List<Range> rangeEntries;
|
||||
private final Set<Integer> denseEntries;
|
||||
|
||||
public SparseSet(String csv) {
|
||||
this.rangeEntries = new ArrayList<>();
|
||||
this.denseEntries = new TreeSet<>();
|
||||
|
||||
for (String token : csv.replace("\n", "").replace(" ", "").split(",")) {
|
||||
String[] tokens = token.split("-");
|
||||
switch (tokens.length) {
|
||||
case 1:
|
||||
this.denseEntries.add(Integer.parseInt(tokens[0]));
|
||||
break;
|
||||
case 2:
|
||||
this.rangeEntries.add(
|
||||
new Range(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1])));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid token passed to SparseSet initializer - "
|
||||
+ token
|
||||
+ " (split length "
|
||||
+ tokens.length
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(int i) {
|
||||
for (Range range : this.rangeEntries) {
|
||||
if (range.check(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this.denseEntries.contains(i);
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenience class for constructing integer sets out of large ranges
|
||||
* 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. :)
|
||||
*/
|
||||
private static class Range {
|
||||
private final int min, max;
|
||||
|
||||
public Range(int min, int max) {
|
||||
if (min > max) {
|
||||
throw new IllegalArgumentException(
|
||||
"Range passed minimum higher than maximum - " + min + " > " + max);
|
||||
}
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public boolean check(int value) {
|
||||
return value >= this.min && value <= this.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public final class SparseSet {
|
||||
private final List<Range> rangeEntries;
|
||||
private final Set<Integer> denseEntries;
|
||||
|
||||
public SparseSet(String csv) {
|
||||
this.rangeEntries = new ArrayList<>();
|
||||
this.denseEntries = new TreeSet<>();
|
||||
|
||||
for (String token : csv.replace("\n", "").replace(" ", "").split(",")) {
|
||||
String[] tokens = token.split("-");
|
||||
switch (tokens.length) {
|
||||
case 1:
|
||||
this.denseEntries.add(Integer.parseInt(tokens[0]));
|
||||
break;
|
||||
case 2:
|
||||
this.rangeEntries.add(
|
||||
new Range(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1])));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid token passed to SparseSet initializer - "
|
||||
+ token
|
||||
+ " (split length "
|
||||
+ tokens.length
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(int i) {
|
||||
for (Range range : this.rangeEntries) {
|
||||
if (range.check(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this.denseEntries.contains(i);
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenience class for constructing integer sets out of large ranges
|
||||
* 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. :)
|
||||
*/
|
||||
private static class Range {
|
||||
private final int min, max;
|
||||
|
||||
public Range(int min, int max) {
|
||||
if (min > max) {
|
||||
throw new IllegalArgumentException(
|
||||
"Range passed minimum higher than maximum - " + min + " > " + max);
|
||||
}
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public boolean check(int value) {
|
||||
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;
|
||||
|
||||
import static emu.grasscutter.utils.FileUtils.getResourcePath;
|
||||
import static emu.grasscutter.utils.Language.translate;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.config.ConfigContainer;
|
||||
import emu.grasscutter.data.DataLoader;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import javax.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"})
|
||||
public final class Utils {
|
||||
public static final Random random = new Random();
|
||||
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
|
||||
|
||||
public static int randomRange(int min, int max) {
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
|
||||
public static float randomFloatRange(float min, float max) {
|
||||
return random.nextFloat() * (max - min) + min;
|
||||
}
|
||||
|
||||
public static double getDist(Position pos1, Position pos2) {
|
||||
double xs = pos1.getX() - pos2.getX();
|
||||
xs = xs * xs;
|
||||
|
||||
double ys = pos1.getY() - pos2.getY();
|
||||
ys = ys * ys;
|
||||
|
||||
double zs = pos1.getZ() - pos2.getZ();
|
||||
zs = zs * zs;
|
||||
|
||||
return Math.sqrt(xs + zs + ys);
|
||||
}
|
||||
|
||||
public static int getCurrentSeconds() {
|
||||
return (int) (System.currentTimeMillis() / 1000.0);
|
||||
}
|
||||
|
||||
public static String lowerCaseFirstChar(String s) {
|
||||
StringBuilder sb = new StringBuilder(s);
|
||||
sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String toString(InputStream inputStream) throws IOException {
|
||||
BufferedInputStream bis = new BufferedInputStream(inputStream);
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
for (int result = bis.read(); result != -1; result = bis.read()) {
|
||||
buf.write((byte) result);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static void logByteArray(byte[] array) {
|
||||
ByteBuf b = Unpooled.wrappedBuffer(array);
|
||||
Grasscutter.getLogger().info("\n" + ByteBufUtil.prettyHexDump(b));
|
||||
b.release();
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
if (bytes == null) return "";
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static String bytesToHex(ByteBuf buf) {
|
||||
return bytesToHex(byteBufToArray(buf));
|
||||
}
|
||||
|
||||
public static byte[] byteBufToArray(ByteBuf buf) {
|
||||
byte[] bytes = new byte[buf.capacity()];
|
||||
buf.getBytes(0, bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int abilityHash(String str) {
|
||||
int v7 = 0;
|
||||
int v8 = 0;
|
||||
while (v8 < str.length()) {
|
||||
v7 = str.charAt(v8++) + 131 * v7;
|
||||
}
|
||||
return v7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string with the path to a file.
|
||||
*
|
||||
* @param path The path to the file.
|
||||
* @return A path using the operating system's file separator.
|
||||
*/
|
||||
public static String toFilePath(String path) {
|
||||
return path.replace("/", File.separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file exists on the file system.
|
||||
*
|
||||
* @param path The path to the file.
|
||||
* @return True if the file exists, false otherwise.
|
||||
*/
|
||||
public static boolean fileExists(String path) {
|
||||
return new File(path).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a folder on the file system.
|
||||
*
|
||||
* @param path The path to the folder.
|
||||
* @return True if the folder was created, false otherwise.
|
||||
*/
|
||||
public static boolean createFolder(String path) {
|
||||
return new File(path).mkdirs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a file from the archive's resources to the file system.
|
||||
*
|
||||
* @param resource The path to the resource.
|
||||
* @param destination The path to copy the resource to.
|
||||
* @return True if the file was copied, false otherwise.
|
||||
*/
|
||||
public static boolean copyFromResources(String resource, String destination) {
|
||||
try (InputStream stream = Grasscutter.class.getResourceAsStream(resource)) {
|
||||
if (stream == null) {
|
||||
Grasscutter.getLogger().warn("Could not find resource: " + resource);
|
||||
return false;
|
||||
}
|
||||
|
||||
Files.copy(stream, new File(destination).toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
return true;
|
||||
} catch (Exception exception) {
|
||||
Grasscutter.getLogger()
|
||||
.warn("Unable to copy resource " + resource + " to " + destination, exception);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an object to the console.
|
||||
*
|
||||
* @param object The object to log.
|
||||
*/
|
||||
public static void logObject(Object object) {
|
||||
Grasscutter.getLogger().info(JsonUtils.encode(object));
|
||||
}
|
||||
|
||||
/** Checks for required files and folders before startup. */
|
||||
public static void startupCheck() {
|
||||
ConfigContainer config = Grasscutter.getConfig();
|
||||
Logger logger = Grasscutter.getLogger();
|
||||
boolean exit = false;
|
||||
|
||||
String dataFolder = config.folderStructure.data;
|
||||
|
||||
// Check for resources folder.
|
||||
if (!Files.exists(getResourcePath(""))) {
|
||||
logger.info(translate("messages.status.create_resources"));
|
||||
logger.info(translate("messages.status.resources_error"));
|
||||
createFolder(config.folderStructure.resources);
|
||||
exit = true;
|
||||
}
|
||||
|
||||
// Check for BinOutput + ExcelBinOutput.
|
||||
if (!Files.exists(getResourcePath("BinOutput"))
|
||||
|| !Files.exists(getResourcePath("ExcelBinOutput"))) {
|
||||
logger.info(translate("messages.status.resources_error"));
|
||||
exit = true;
|
||||
}
|
||||
|
||||
// Check for game data.
|
||||
if (!fileExists(dataFolder)) createFolder(dataFolder);
|
||||
|
||||
// Make sure the data folder is populated, if there are any missing files copy them from
|
||||
// resources
|
||||
DataLoader.checkAllFiles();
|
||||
|
||||
if (exit) System.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the next hour.
|
||||
*
|
||||
* @return The timestamp in UNIX seconds.
|
||||
*/
|
||||
public static int getNextTimestampOfThisHour(int hour, String timeZone, int param) {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
|
||||
for (int i = 0; i < param; i++) {
|
||||
if (zonedDateTime.getHour() < hour) {
|
||||
zonedDateTime = zonedDateTime.withHour(hour).withMinute(0).withSecond(0);
|
||||
} else {
|
||||
zonedDateTime = zonedDateTime.plusDays(1).withHour(hour).withMinute(0).withSecond(0);
|
||||
}
|
||||
}
|
||||
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the next hour in a week.
|
||||
*
|
||||
* @return The timestamp in UNIX seconds.
|
||||
*/
|
||||
public static int getNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
|
||||
for (int i = 0; i < param; i++) {
|
||||
if (zonedDateTime.getDayOfWeek() == DayOfWeek.MONDAY && zonedDateTime.getHour() < hour) {
|
||||
zonedDateTime =
|
||||
ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0);
|
||||
} else {
|
||||
zonedDateTime =
|
||||
zonedDateTime
|
||||
.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
|
||||
.withHour(hour)
|
||||
.withMinute(0)
|
||||
.withSecond(0);
|
||||
}
|
||||
}
|
||||
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the next hour in a month.
|
||||
*
|
||||
* @return The timestamp in UNIX seconds.
|
||||
*/
|
||||
public static int getNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
|
||||
for (int i = 0; i < param; i++) {
|
||||
if (zonedDateTime.getDayOfMonth() == 1 && zonedDateTime.getHour() < hour) {
|
||||
zonedDateTime =
|
||||
ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0);
|
||||
} else {
|
||||
zonedDateTime =
|
||||
zonedDateTime
|
||||
.with(TemporalAdjusters.firstDayOfNextMonth())
|
||||
.withHour(hour)
|
||||
.withMinute(0)
|
||||
.withSecond(0);
|
||||
}
|
||||
}
|
||||
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a string from an input stream.
|
||||
*
|
||||
* @param stream The input stream.
|
||||
* @return The string.
|
||||
*/
|
||||
public static String readFromInputStream(@Nullable InputStream stream) {
|
||||
if (stream == null) return "empty";
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
try (BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
stringBuilder.append(line);
|
||||
}
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
Grasscutter.getLogger().warn("Failed to read from input stream.");
|
||||
} catch (NullPointerException ignored) {
|
||||
return "empty";
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a linear interpolation using a table of fixed points to create an effective piecewise
|
||||
* f(x) = y function.
|
||||
*
|
||||
* @param x The x value.
|
||||
* @param xyArray Array of points in [[x0,y0], ... [xN, yN]] format
|
||||
* @return f(x) = y
|
||||
*/
|
||||
public static int lerp(int x, int[][] xyArray) {
|
||||
try {
|
||||
if (x <= xyArray[0][0]) { // Clamp to first point
|
||||
return xyArray[0][1];
|
||||
} else if (x >= xyArray[xyArray.length - 1][0]) { // Clamp to last point
|
||||
return xyArray[xyArray.length - 1][1];
|
||||
}
|
||||
// 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++) {
|
||||
if (x == xyArray[i + 1][0]) {
|
||||
return xyArray[i + 1][1];
|
||||
}
|
||||
if (x < xyArray[i + 1][0]) {
|
||||
// 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
|
||||
// careful with order of operations.
|
||||
int position = x - xyArray[i][0];
|
||||
int fullDist = xyArray[i + 1][0] - xyArray[i][0];
|
||||
int prevValue = xyArray[i][1];
|
||||
int fullDelta = xyArray[i + 1][1] - prevValue;
|
||||
return prevValue + ((position * fullDelta) / fullDist);
|
||||
}
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
Grasscutter.getLogger()
|
||||
.error("Malformed lerp point array. Must be of form [[x0, y0], ..., [xN, yN]].");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an int is in an int[]
|
||||
*
|
||||
* @param key int to look for
|
||||
* @param array int[] to look in
|
||||
* @return key in array
|
||||
*/
|
||||
public static boolean intInArray(int key, int[] array) {
|
||||
for (int i : array) {
|
||||
if (i == key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of minuend without any elements found in subtrahend.
|
||||
*
|
||||
* @param minuend The array we want elements from
|
||||
* @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
|
||||
*/
|
||||
public static int[] setSubtract(int[] minuend, int[] subtrahend) {
|
||||
IntList temp = new IntArrayList();
|
||||
for (int i : minuend) {
|
||||
if (!intInArray(i, subtrahend)) {
|
||||
temp.add(i);
|
||||
}
|
||||
}
|
||||
return temp.toIntArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the language code from a given locale.
|
||||
*
|
||||
* @param locale A locale.
|
||||
* @return A string in the format of 'XX-XX'.
|
||||
*/
|
||||
public static String getLanguageCode(Locale locale) {
|
||||
return String.format("%s-%s", locale.getLanguage(), locale.getCountry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 encodes a given byte array.
|
||||
*
|
||||
* @param toEncode An array of bytes.
|
||||
* @return A base64 encoded string.
|
||||
*/
|
||||
public static String base64Encode(byte[] toEncode) {
|
||||
return Base64.getEncoder().encodeToString(toEncode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 decodes a given string.
|
||||
*
|
||||
* @param toDecode A base64 encoded string.
|
||||
* @return An array of bytes.
|
||||
*/
|
||||
public static byte[] base64Decode(String toDecode) {
|
||||
return Base64.getDecoder().decode(toDecode);
|
||||
}
|
||||
|
||||
/***
|
||||
* 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 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.
|
||||
*/
|
||||
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
|
||||
// the size of the list, we assume uniform distribution.
|
||||
if (probabilities == null || probabilities.size() <= 1 || probabilities.size() != list.size()) {
|
||||
int index = ThreadLocalRandom.current().nextInt(0, list.size());
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
// Otherwise, we roll with the given distribution.
|
||||
int totalProbabilityMass = probabilities.stream().reduce(Integer::sum).get();
|
||||
int roll = ThreadLocalRandom.current().nextInt(1, totalProbabilityMass + 1);
|
||||
|
||||
int currentTotalChance = 0;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
currentTotalChance += probabilities.get(i);
|
||||
|
||||
if (roll <= currentTotalChance) {
|
||||
return list.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Should never happen.
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
/***
|
||||
* Draws a random element from the given list, following a uniform probability distribution.
|
||||
* @param list The list from which to draw the element.
|
||||
* @return A randomly drawn element from the given list.
|
||||
*/
|
||||
public static <T> T drawRandomListElement(List<T> list) {
|
||||
return drawRandomListElement(list, null);
|
||||
}
|
||||
|
||||
/***
|
||||
* Splits a string by a character, into a list
|
||||
* @param input The string to split
|
||||
* @param separator The character to use as the split points
|
||||
* @return A list of all the substrings
|
||||
*/
|
||||
public static List<String> nonRegexSplit(String input, int separator) {
|
||||
var output = new ArrayList<String>();
|
||||
int start = 0;
|
||||
for (int next = input.indexOf(separator); next > 0; next = input.indexOf(separator, start)) {
|
||||
output.add(input.substring(start, next));
|
||||
start = next + 1;
|
||||
}
|
||||
if (start < input.length()) output.add(input.substring(start));
|
||||
return output;
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import static emu.grasscutter.utils.FileUtils.getResourcePath;
|
||||
import static emu.grasscutter.utils.Language.translate;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.config.ConfigContainer;
|
||||
import emu.grasscutter.data.DataLoader;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import javax.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"})
|
||||
public final class Utils {
|
||||
public static final Random random = new Random();
|
||||
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
|
||||
|
||||
public static int randomRange(int min, int max) {
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
|
||||
public static float randomFloatRange(float min, float max) {
|
||||
return random.nextFloat() * (max - min) + min;
|
||||
}
|
||||
|
||||
public static double getDist(Position pos1, Position pos2) {
|
||||
double xs = pos1.getX() - pos2.getX();
|
||||
xs = xs * xs;
|
||||
|
||||
double ys = pos1.getY() - pos2.getY();
|
||||
ys = ys * ys;
|
||||
|
||||
double zs = pos1.getZ() - pos2.getZ();
|
||||
zs = zs * zs;
|
||||
|
||||
return Math.sqrt(xs + zs + ys);
|
||||
}
|
||||
|
||||
public static int getCurrentSeconds() {
|
||||
return (int) (System.currentTimeMillis() / 1000.0);
|
||||
}
|
||||
|
||||
public static String lowerCaseFirstChar(String s) {
|
||||
StringBuilder sb = new StringBuilder(s);
|
||||
sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String toString(InputStream inputStream) throws IOException {
|
||||
BufferedInputStream bis = new BufferedInputStream(inputStream);
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
for (int result = bis.read(); result != -1; result = bis.read()) {
|
||||
buf.write((byte) result);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static void logByteArray(byte[] array) {
|
||||
ByteBuf b = Unpooled.wrappedBuffer(array);
|
||||
Grasscutter.getLogger().info("\n" + ByteBufUtil.prettyHexDump(b));
|
||||
b.release();
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
if (bytes == null) return "";
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static String bytesToHex(ByteBuf buf) {
|
||||
return bytesToHex(byteBufToArray(buf));
|
||||
}
|
||||
|
||||
public static byte[] byteBufToArray(ByteBuf buf) {
|
||||
byte[] bytes = new byte[buf.capacity()];
|
||||
buf.getBytes(0, bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int abilityHash(String str) {
|
||||
int v7 = 0;
|
||||
int v8 = 0;
|
||||
while (v8 < str.length()) {
|
||||
v7 = str.charAt(v8++) + 131 * v7;
|
||||
}
|
||||
return v7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string with the path to a file.
|
||||
*
|
||||
* @param path The path to the file.
|
||||
* @return A path using the operating system's file separator.
|
||||
*/
|
||||
public static String toFilePath(String path) {
|
||||
return path.replace("/", File.separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file exists on the file system.
|
||||
*
|
||||
* @param path The path to the file.
|
||||
* @return True if the file exists, false otherwise.
|
||||
*/
|
||||
public static boolean fileExists(String path) {
|
||||
return new File(path).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a folder on the file system.
|
||||
*
|
||||
* @param path The path to the folder.
|
||||
* @return True if the folder was created, false otherwise.
|
||||
*/
|
||||
public static boolean createFolder(String path) {
|
||||
return new File(path).mkdirs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a file from the archive's resources to the file system.
|
||||
*
|
||||
* @param resource The path to the resource.
|
||||
* @param destination The path to copy the resource to.
|
||||
* @return True if the file was copied, false otherwise.
|
||||
*/
|
||||
public static boolean copyFromResources(String resource, String destination) {
|
||||
try (InputStream stream = Grasscutter.class.getResourceAsStream(resource)) {
|
||||
if (stream == null) {
|
||||
Grasscutter.getLogger().warn("Could not find resource: " + resource);
|
||||
return false;
|
||||
}
|
||||
|
||||
Files.copy(stream, new File(destination).toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
return true;
|
||||
} catch (Exception exception) {
|
||||
Grasscutter.getLogger()
|
||||
.warn("Unable to copy resource " + resource + " to " + destination, exception);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an object to the console.
|
||||
*
|
||||
* @param object The object to log.
|
||||
*/
|
||||
public static void logObject(Object object) {
|
||||
Grasscutter.getLogger().info(JsonUtils.encode(object));
|
||||
}
|
||||
|
||||
/** Checks for required files and folders before startup. */
|
||||
public static void startupCheck() {
|
||||
ConfigContainer config = Grasscutter.getConfig();
|
||||
Logger logger = Grasscutter.getLogger();
|
||||
boolean exit = false;
|
||||
|
||||
String dataFolder = config.folderStructure.data;
|
||||
|
||||
// Check for resources folder.
|
||||
if (!Files.exists(getResourcePath(""))) {
|
||||
logger.info(translate("messages.status.create_resources"));
|
||||
logger.info(translate("messages.status.resources_error"));
|
||||
createFolder(config.folderStructure.resources);
|
||||
exit = true;
|
||||
}
|
||||
|
||||
// Check for BinOutput + ExcelBinOutput.
|
||||
if (!Files.exists(getResourcePath("BinOutput"))
|
||||
|| !Files.exists(getResourcePath("ExcelBinOutput"))) {
|
||||
logger.info(translate("messages.status.resources_error"));
|
||||
exit = true;
|
||||
}
|
||||
|
||||
// Check for game data.
|
||||
if (!fileExists(dataFolder)) createFolder(dataFolder);
|
||||
|
||||
// Make sure the data folder is populated, if there are any missing files copy them from
|
||||
// resources
|
||||
DataLoader.checkAllFiles();
|
||||
|
||||
if (exit) System.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the next hour.
|
||||
*
|
||||
* @return The timestamp in UNIX seconds.
|
||||
*/
|
||||
public static int getNextTimestampOfThisHour(int hour, String timeZone, int param) {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
|
||||
for (int i = 0; i < param; i++) {
|
||||
if (zonedDateTime.getHour() < hour) {
|
||||
zonedDateTime = zonedDateTime.withHour(hour).withMinute(0).withSecond(0);
|
||||
} else {
|
||||
zonedDateTime = zonedDateTime.plusDays(1).withHour(hour).withMinute(0).withSecond(0);
|
||||
}
|
||||
}
|
||||
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the next hour in a week.
|
||||
*
|
||||
* @return The timestamp in UNIX seconds.
|
||||
*/
|
||||
public static int getNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
|
||||
for (int i = 0; i < param; i++) {
|
||||
if (zonedDateTime.getDayOfWeek() == DayOfWeek.MONDAY && zonedDateTime.getHour() < hour) {
|
||||
zonedDateTime =
|
||||
ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0);
|
||||
} else {
|
||||
zonedDateTime =
|
||||
zonedDateTime
|
||||
.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
|
||||
.withHour(hour)
|
||||
.withMinute(0)
|
||||
.withSecond(0);
|
||||
}
|
||||
}
|
||||
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the next hour in a month.
|
||||
*
|
||||
* @return The timestamp in UNIX seconds.
|
||||
*/
|
||||
public static int getNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
|
||||
for (int i = 0; i < param; i++) {
|
||||
if (zonedDateTime.getDayOfMonth() == 1 && zonedDateTime.getHour() < hour) {
|
||||
zonedDateTime =
|
||||
ZonedDateTime.now(ZoneId.of(timeZone)).withHour(hour).withMinute(0).withSecond(0);
|
||||
} else {
|
||||
zonedDateTime =
|
||||
zonedDateTime
|
||||
.with(TemporalAdjusters.firstDayOfNextMonth())
|
||||
.withHour(hour)
|
||||
.withMinute(0)
|
||||
.withSecond(0);
|
||||
}
|
||||
}
|
||||
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a string from an input stream.
|
||||
*
|
||||
* @param stream The input stream.
|
||||
* @return The string.
|
||||
*/
|
||||
public static String readFromInputStream(@Nullable InputStream stream) {
|
||||
if (stream == null) return "empty";
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
try (BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
stringBuilder.append(line);
|
||||
}
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
Grasscutter.getLogger().warn("Failed to read from input stream.");
|
||||
} catch (NullPointerException ignored) {
|
||||
return "empty";
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a linear interpolation using a table of fixed points to create an effective piecewise
|
||||
* f(x) = y function.
|
||||
*
|
||||
* @param x The x value.
|
||||
* @param xyArray Array of points in [[x0,y0], ... [xN, yN]] format
|
||||
* @return f(x) = y
|
||||
*/
|
||||
public static int lerp(int x, int[][] xyArray) {
|
||||
try {
|
||||
if (x <= xyArray[0][0]) { // Clamp to first point
|
||||
return xyArray[0][1];
|
||||
} else if (x >= xyArray[xyArray.length - 1][0]) { // Clamp to last point
|
||||
return xyArray[xyArray.length - 1][1];
|
||||
}
|
||||
// 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++) {
|
||||
if (x == xyArray[i + 1][0]) {
|
||||
return xyArray[i + 1][1];
|
||||
}
|
||||
if (x < xyArray[i + 1][0]) {
|
||||
// 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
|
||||
// careful with order of operations.
|
||||
int position = x - xyArray[i][0];
|
||||
int fullDist = xyArray[i + 1][0] - xyArray[i][0];
|
||||
int prevValue = xyArray[i][1];
|
||||
int fullDelta = xyArray[i + 1][1] - prevValue;
|
||||
return prevValue + ((position * fullDelta) / fullDist);
|
||||
}
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
Grasscutter.getLogger()
|
||||
.error("Malformed lerp point array. Must be of form [[x0, y0], ..., [xN, yN]].");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an int is in an int[]
|
||||
*
|
||||
* @param key int to look for
|
||||
* @param array int[] to look in
|
||||
* @return key in array
|
||||
*/
|
||||
public static boolean intInArray(int key, int[] array) {
|
||||
for (int i : array) {
|
||||
if (i == key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of minuend without any elements found in subtrahend.
|
||||
*
|
||||
* @param minuend The array we want elements from
|
||||
* @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
|
||||
*/
|
||||
public static int[] setSubtract(int[] minuend, int[] subtrahend) {
|
||||
IntList temp = new IntArrayList();
|
||||
for (int i : minuend) {
|
||||
if (!intInArray(i, subtrahend)) {
|
||||
temp.add(i);
|
||||
}
|
||||
}
|
||||
return temp.toIntArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the language code from a given locale.
|
||||
*
|
||||
* @param locale A locale.
|
||||
* @return A string in the format of 'XX-XX'.
|
||||
*/
|
||||
public static String getLanguageCode(Locale locale) {
|
||||
return String.format("%s-%s", locale.getLanguage(), locale.getCountry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 encodes a given byte array.
|
||||
*
|
||||
* @param toEncode An array of bytes.
|
||||
* @return A base64 encoded string.
|
||||
*/
|
||||
public static String base64Encode(byte[] toEncode) {
|
||||
return Base64.getEncoder().encodeToString(toEncode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 decodes a given string.
|
||||
*
|
||||
* @param toDecode A base64 encoded string.
|
||||
* @return An array of bytes.
|
||||
*/
|
||||
public static byte[] base64Decode(String toDecode) {
|
||||
return Base64.getDecoder().decode(toDecode);
|
||||
}
|
||||
|
||||
/***
|
||||
* 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 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.
|
||||
*/
|
||||
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
|
||||
// the size of the list, we assume uniform distribution.
|
||||
if (probabilities == null || probabilities.size() <= 1 || probabilities.size() != list.size()) {
|
||||
int index = ThreadLocalRandom.current().nextInt(0, list.size());
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
// Otherwise, we roll with the given distribution.
|
||||
int totalProbabilityMass = probabilities.stream().reduce(Integer::sum).get();
|
||||
int roll = ThreadLocalRandom.current().nextInt(1, totalProbabilityMass + 1);
|
||||
|
||||
int currentTotalChance = 0;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
currentTotalChance += probabilities.get(i);
|
||||
|
||||
if (roll <= currentTotalChance) {
|
||||
return list.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Should never happen.
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
/***
|
||||
* Draws a random element from the given list, following a uniform probability distribution.
|
||||
* @param list The list from which to draw the element.
|
||||
* @return A randomly drawn element from the given list.
|
||||
*/
|
||||
public static <T> T drawRandomListElement(List<T> list) {
|
||||
return drawRandomListElement(list, null);
|
||||
}
|
||||
|
||||
/***
|
||||
* Splits a string by a character, into a list
|
||||
* @param input The string to split
|
||||
* @param separator The character to use as the split points
|
||||
* @return A list of all the substrings
|
||||
*/
|
||||
public static List<String> nonRegexSplit(String input, int separator) {
|
||||
var output = new ArrayList<String>();
|
||||
int start = 0;
|
||||
for (int next = input.indexOf(separator); next > 0; next = input.indexOf(separator, start)) {
|
||||
output.add(input.substring(start, next));
|
||||
start = next + 1;
|
||||
}
|
||||
if (start < input.length()) output.add(input.substring(start));
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,28 @@
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class WeightedList<E> {
|
||||
private final NavigableMap<Double, E> map = new TreeMap<Double, E>();
|
||||
private double total = 0;
|
||||
|
||||
public WeightedList() {}
|
||||
|
||||
public WeightedList<E> add(double weight, E result) {
|
||||
if (weight <= 0) return this;
|
||||
total += weight;
|
||||
map.put(total, result);
|
||||
return this;
|
||||
}
|
||||
|
||||
public E next() {
|
||||
double value = ThreadLocalRandom.current().nextDouble() * total;
|
||||
return map.higherEntry(value).getValue();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.utils;
|
||||
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class WeightedList<E> {
|
||||
private final NavigableMap<Double, E> map = new TreeMap<Double, E>();
|
||||
private double total = 0;
|
||||
|
||||
public WeightedList() {}
|
||||
|
||||
public WeightedList<E> add(double weight, E result) {
|
||||
if (weight <= 0) return this;
|
||||
total += weight;
|
||||
map.put(total, result);
|
||||
return this;
|
||||
}
|
||||
|
||||
public E next() {
|
||||
double value = ThreadLocalRandom.current().nextDouble() * total;
|
||||
return map.higherEntry(value).getValue();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +1,60 @@
|
||||
package io.grasscutter;
|
||||
|
||||
import com.mchange.util.AssertException;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.config.Configuration;
|
||||
import java.io.IOException;
|
||||
import lombok.Getter;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Testing entrypoint for {@link Grasscutter}. */
|
||||
public final class GrasscutterTest {
|
||||
@Getter private static final OkHttpClient httpClient = new OkHttpClient();
|
||||
|
||||
@Getter private static int httpPort = -1;
|
||||
@Getter private static int gamePort = -1;
|
||||
|
||||
/**
|
||||
* Creates an HTTP URL.
|
||||
*
|
||||
* @param route The route to use.
|
||||
* @return The URL.
|
||||
*/
|
||||
public static String http(String route) {
|
||||
return "http://127.0.0.1:" + GrasscutterTest.httpPort + "/" + route;
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void main() {
|
||||
try {
|
||||
// Start Grasscutter.
|
||||
Grasscutter.main(new String[] {"-test"});
|
||||
} catch (Exception ignored) {
|
||||
throw new AssertException("Grasscutter failed to start.");
|
||||
}
|
||||
|
||||
// Set the ports.
|
||||
GrasscutterTest.httpPort = Configuration.SERVER.http.bindPort;
|
||||
GrasscutterTest.gamePort = Configuration.SERVER.game.bindPort;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("HTTP server check")
|
||||
public void checkHttpServer() {
|
||||
// Create a request.
|
||||
var request = new Request.Builder().url(GrasscutterTest.http("")).build();
|
||||
|
||||
// Perform the request.
|
||||
try (var response = GrasscutterTest.httpClient.newCall(request).execute()) {
|
||||
// Check the response.
|
||||
Assertions.assertTrue(response.isSuccessful());
|
||||
} catch (IOException exception) {
|
||||
throw new AssertionError(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
package io.grasscutter;
|
||||
|
||||
import com.mchange.util.AssertException;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.config.Configuration;
|
||||
import java.io.IOException;
|
||||
import lombok.Getter;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Testing entrypoint for {@link Grasscutter}. */
|
||||
public final class GrasscutterTest {
|
||||
@Getter private static final OkHttpClient httpClient = new OkHttpClient();
|
||||
|
||||
@Getter private static int httpPort = -1;
|
||||
@Getter private static int gamePort = -1;
|
||||
|
||||
/**
|
||||
* Creates an HTTP URL.
|
||||
*
|
||||
* @param route The route to use.
|
||||
* @return The URL.
|
||||
*/
|
||||
public static String http(String route) {
|
||||
return "http://127.0.0.1:" + GrasscutterTest.httpPort + "/" + route;
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void main() {
|
||||
try {
|
||||
// Start Grasscutter.
|
||||
Grasscutter.main(new String[] {"-test"});
|
||||
} catch (Exception ignored) {
|
||||
throw new AssertException("Grasscutter failed to start.");
|
||||
}
|
||||
|
||||
// Set the ports.
|
||||
GrasscutterTest.httpPort = Configuration.SERVER.http.bindPort;
|
||||
GrasscutterTest.gamePort = Configuration.SERVER.game.bindPort;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("HTTP server check")
|
||||
public void checkHttpServer() {
|
||||
// Create a request.
|
||||
var request = new Request.Builder().url(GrasscutterTest.http("")).build();
|
||||
|
||||
// Perform the request.
|
||||
try (var response = GrasscutterTest.httpClient.newCall(request).execute()) {
|
||||
// Check the response.
|
||||
Assertions.assertTrue(response.isSuccessful());
|
||||
} catch (IOException exception) {
|
||||
throw new AssertionError(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user