From 17addc15229690b0495362ef6e02fe80b56ac8a8 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Thu, 28 Apr 2022 08:20:37 -0700 Subject: [PATCH] Implement dungeon entry --- proto/PlayerEnterDungeonReq.proto | 8 +++ proto/PlayerEnterDungeonRsp.proto | 9 +++ proto/PlayerQuitDungeonReq.proto | 8 +++ proto/PlayerQuitDungeonRsp.proto | 8 +++ .../java/emu/grasscutter/data/GameData.java | 10 +++ .../emu/grasscutter/data/ResourceLoader.java | 1 + .../grasscutter/data/common/PointData.java | 59 +++++++---------- .../emu/grasscutter/data/def/DungeonData.java | 28 +++++++++ .../emu/grasscutter/data/def/SceneData.java | 7 ++- .../game/dungeons/DungeonManager.java | 63 +++++++++++++++++++ .../emu/grasscutter/game/world/Scene.java | 24 +++++++ .../emu/grasscutter/game/world/World.java | 43 ++++++++++--- .../recv/HandlerDungeonEntryInfoReq.java | 3 + .../recv/HandlerPlayerEnterDungeonReq.java | 20 ++++++ .../recv/HandlerPlayerQuitDungeonReq.java | 16 +++++ .../send/PacketDungeonEntryInfoRsp.java | 37 +++++++++++ .../send/PacketPathfindingEnterSceneRsp.java | 2 - .../send/PacketPlayerEnterDungeonRsp.java | 19 ++++++ .../java/emu/grasscutter/utils/Utils.java | 1 + 19 files changed, 318 insertions(+), 48 deletions(-) create mode 100644 proto/PlayerEnterDungeonReq.proto create mode 100644 proto/PlayerEnterDungeonRsp.proto create mode 100644 proto/PlayerQuitDungeonReq.proto create mode 100644 proto/PlayerQuitDungeonRsp.proto create mode 100644 src/main/java/emu/grasscutter/data/def/DungeonData.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerEnterDungeonReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerQuitDungeonReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterDungeonRsp.java diff --git a/proto/PlayerEnterDungeonReq.proto b/proto/PlayerEnterDungeonReq.proto new file mode 100644 index 000000000..aef860d18 --- /dev/null +++ b/proto/PlayerEnterDungeonReq.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message PlayerEnterDungeonReq { + uint32 point_id = 1; + uint32 dungeon_id = 2; +} diff --git a/proto/PlayerEnterDungeonRsp.proto b/proto/PlayerEnterDungeonRsp.proto new file mode 100644 index 000000000..ba9325eb0 --- /dev/null +++ b/proto/PlayerEnterDungeonRsp.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message PlayerEnterDungeonRsp { + int32 retcode = 1; + uint32 point_id = 2; + uint32 dungeon_id = 3; +} diff --git a/proto/PlayerQuitDungeonReq.proto b/proto/PlayerQuitDungeonReq.proto new file mode 100644 index 000000000..b94eb9975 --- /dev/null +++ b/proto/PlayerQuitDungeonReq.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message PlayerQuitDungeonReq { + uint32 point_id = 1; + bool is_quit_immediately = 2; +} diff --git a/proto/PlayerQuitDungeonRsp.proto b/proto/PlayerQuitDungeonRsp.proto new file mode 100644 index 000000000..c2538b630 --- /dev/null +++ b/proto/PlayerQuitDungeonRsp.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message PlayerQuitDungeonRsp { + int32 retcode = 1; + uint32 point_id = 2; +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 4c7363054..5ac8ae444 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -61,6 +61,7 @@ public class GameData { private static final Int2ObjectMap fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap worldLevelDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap dungeonDataMap = new Int2ObjectOpenHashMap<>(); // Cache private static Map> fetters = new HashMap<>(); @@ -97,6 +98,11 @@ public class GameData { public static Map getScenePointEntries() { return scenePointEntries; } + + // TODO optimize + public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) { + return getScenePointEntries().get(sceneId + "_" + pointId); + } public static Int2ObjectMap getAvatarDataMap() { return avatarDataMap; @@ -265,4 +271,8 @@ public class GameData { public static Int2ObjectMap getWorldLevelDataMap() { return worldLevelDataMap; } + + public static Int2ObjectMap getDungeonDataMap() { + return dungeonDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 589f6aca8..6b48645d8 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -164,6 +164,7 @@ public class ResourceLoader { for (Map.Entry entry : config.points.entrySet()) { PointData pointData = Grasscutter.getGsonFactory().fromJson(entry.getValue(), PointData.class); + pointData.setId(Integer.parseInt(entry.getKey())); ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData); scenePointList.add(sl); diff --git a/src/main/java/emu/grasscutter/data/common/PointData.java b/src/main/java/emu/grasscutter/data/common/PointData.java index 7c31d2f06..147eee81a 100644 --- a/src/main/java/emu/grasscutter/data/common/PointData.java +++ b/src/main/java/emu/grasscutter/data/common/PointData.java @@ -1,43 +1,30 @@ package emu.grasscutter.data.common; -public class PointData { - private pos tranPos; +import emu.grasscutter.utils.Position; - public pos getTranPos() { +public class PointData { + private int id; + private String $type; + private Position tranPos; + private int[] dungeonIds; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getType() { + return $type; + } + + public Position getTranPos() { return tranPos; } - public void setTranPos(pos tranPos) { - this.tranPos = tranPos; - } - - public class pos { - private float x; - private float y; - private float z; - - public float getX() { - return x; - } - - public void setX(float x) { - this.x = x; - } - - public float getY() { - return y; - } - - public void setY(float y) { - this.y = y; - } - - public float getZ() { - return z; - } - - public void setZ(float z) { - this.z = z; - } - } + public int[] getDungeonIds() { + return dungeonIds; + } } diff --git a/src/main/java/emu/grasscutter/data/def/DungeonData.java b/src/main/java/emu/grasscutter/data/def/DungeonData.java new file mode 100644 index 000000000..3239d30fb --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/DungeonData.java @@ -0,0 +1,28 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +import emu.grasscutter.game.props.SceneType; + +@ResourceType(name = "DungeonExcelConfigData.json") +public class DungeonData extends GameResource { + private int Id; + private int SceneId; + private String InvolveType; // TODO enum + + @Override + public int getId() { + return this.Id; + } + + public int getSceneId() { + return SceneId; + } + + @Override + public void onLoad() { + + } +} diff --git a/src/main/java/emu/grasscutter/data/def/SceneData.java b/src/main/java/emu/grasscutter/data/def/SceneData.java index 220579faf..cd1510e1d 100644 --- a/src/main/java/emu/grasscutter/data/def/SceneData.java +++ b/src/main/java/emu/grasscutter/data/def/SceneData.java @@ -9,8 +9,9 @@ import emu.grasscutter.game.props.SceneType; @ResourceType(name = "SceneExcelConfigData.json") public class SceneData extends GameResource { private int Id; - private SceneType SceneType; + private SceneType Type; private String ScriptData; + @Override public int getId() { @@ -18,7 +19,7 @@ public class SceneData extends GameResource { } public SceneType getSceneType() { - return SceneType; + return Type; } public String getScriptData() { @@ -27,6 +28,6 @@ public class SceneData extends GameResource { @Override public void onLoad() { - + } } diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index 33bac7d09..3c5b61903 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -1,6 +1,18 @@ package emu.grasscutter.game.dungeons; +import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.ScenePointEntry; +import emu.grasscutter.data.def.DungeonData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp; +import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp; +import emu.grasscutter.utils.Position; public class DungeonManager { private final GameServer server; @@ -12,4 +24,55 @@ public class DungeonManager { public GameServer getServer() { return server; } + + public void getEntryInfo(Player player, int pointId) { + ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId); + + if (entry == null || entry.getPointData().getDungeonIds() == null) { + // Error + player.sendPacket(new PacketDungeonEntryInfoRsp()); + return; + } + + player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData())); + } + + public void enterDungeon(Player player, int pointId, int dungeonId) { + DungeonData data = GameData.getDungeonDataMap().get(dungeonId); + + if (data == null) { + return; + } + + int sceneId = data.getSceneId(); + + player.getWorld().transferPlayerToScene(player, sceneId, data); + + player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId)); + } + + public void exitDungeon(Player player) { + if (player.getScene().getSceneType() != SceneType.SCENE_DUNGEON) { + return; + } + + // Get previous scene + int prevScene = player.getScene().getPrevScene() > 0 ? player.getScene().getPrevScene() : 3; + + // Get previous position + DungeonData dungeonData = player.getScene().getDungeonData(); + Position prevPos = new Position(GameConstants.START_POSITION); + + if (dungeonData != null) { + ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, dungeonData.getId()); + + if (entry != null) { + prevPos.set(entry.getPointData().getTranPos()); + } + } + + // Transfer player back to world + player.getWorld().transferPlayerToScene(player, prevScene, prevPos); + player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp)); + } } diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 543079153..f3ede3e73 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -3,6 +3,7 @@ package emu.grasscutter.game.world; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameDepot; +import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.data.def.WorldLevelData; @@ -41,6 +42,9 @@ public class Scene { private int time; private ClimateType climate; private int weather; + + private DungeonData dungeonData; + private int prevScene; // Id of the previous scene public Scene(World world, SceneData sceneData) { this.world = world; @@ -50,6 +54,7 @@ public class Scene { this.time = 8 * 60; this.climate = ClimateType.CLIMATE_SUNNY; + this.prevScene = 3; this.spawnedEntities = new HashSet<>(); this.deadSpawnedEntities = new HashSet<>(); @@ -111,6 +116,14 @@ public class Scene { this.weather = weather; } + public int getPrevScene() { + return prevScene; + } + + public void setPrevScene(int prevScene) { + this.prevScene = prevScene; + } + public boolean dontDestroyWhenEmpty() { return dontDestroyWhenEmpty; } @@ -127,6 +140,17 @@ public class Scene { return deadSpawnedEntities; } + public DungeonData getDungeonData() { + return dungeonData; + } + + public void setDungeonData(DungeonData dungeonData) { + if (this.dungeonData != null || this.getSceneType() != SceneType.SCENE_DUNGEON || dungeonData.getSceneId() != this.getId()) { + return; + } + this.dungeonData = dungeonData; + } + public boolean isInScene(GameEntity entity) { return this.entities.containsKey(entity.getId()); } diff --git a/src/main/java/emu/grasscutter/game/world/World.java b/src/main/java/emu/grasscutter/game/world/World.java index 292e63d32..2a9324e40 100644 --- a/src/main/java/emu/grasscutter/game/world/World.java +++ b/src/main/java/emu/grasscutter/game/world/World.java @@ -17,6 +17,7 @@ import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityClientGadget; @@ -212,6 +213,14 @@ public class World implements Iterable { } public boolean transferPlayerToScene(Player player, int sceneId, Position pos) { + return transferPlayerToScene(player, sceneId, null, pos); + } + + public boolean transferPlayerToScene(Player player, int sceneId, DungeonData data) { + return transferPlayerToScene(player, sceneId, data, null); + } + + public boolean transferPlayerToScene(Player player, int sceneId, DungeonData dungeonData, Position pos) { if (GameData.getSceneDataMap().get(sceneId) == null) { return false; } @@ -224,25 +233,45 @@ public class World implements Iterable { // Dont deregister scenes if the player is going to tp back into them if (oldScene.getId() == sceneId) { oldScene.setDontDestroyWhenEmpty(true); - } + } oldScene.removePlayer(player); } Scene newScene = this.getSceneById(sceneId); + newScene.setDungeonData(dungeonData); newScene.addPlayer(player); - player.getPos().set(pos); + // Dungeon + if (dungeonData != null) { + // TODO set position + } + + // Set player position + if (pos != null) { + player.getPos().set(pos); + } else { + pos = player.getPos(); + } + if (oldScene != null) { + newScene.setPrevScene(oldScene.getId()); oldScene.setDontDestroyWhenEmpty(false); } - // Teleport packet - if (oldScene == newScene) { - player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.ENTER_GOTO, EnterReason.TransPoint, sceneId, pos)); - } else { - player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.ENTER_JUMP, EnterReason.TransPoint, sceneId, pos)); + // Get enter types + EnterType enterType = EnterType.ENTER_JUMP; + EnterReason enterReason = EnterReason.TransPoint; + + if (dungeonData != null) { + enterType = EnterType.ENTER_DUNGEON; + enterReason = EnterReason.DungeonEnter; + } else if (oldScene == newScene) { + enterType = EnterType.ENTER_GOTO; } + + // Teleport packet + player.sendPacket(new PacketPlayerEnterSceneNotify(player, enterType, enterReason, sceneId, pos)); return true; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java index a490e9d73..286fbdc67 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java @@ -2,6 +2,7 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DungeonEntryInfoReqOuterClass.DungeonEntryInfoReq; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; @@ -10,7 +11,9 @@ public class HandlerDungeonEntryInfoReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + DungeonEntryInfoReq req = DungeonEntryInfoReq.parseFrom(payload); + session.getServer().getDungeonManager().getEntryInfo(session.getPlayer(), req.getPointId()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerEnterDungeonReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerEnterDungeonReq.java new file mode 100644 index 000000000..ce05c8ccf --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerEnterDungeonReq.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerEnterDungeonReqOuterClass.PlayerEnterDungeonReq; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.PlayerEnterDungeonReq) +public class HandlerPlayerEnterDungeonReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + // Auto template + PlayerEnterDungeonReq req = PlayerEnterDungeonReq.parseFrom(payload); + + session.getServer().getDungeonManager().enterDungeon(session.getPlayer(), req.getPointId(), req.getDungeonId()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerQuitDungeonReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerQuitDungeonReq.java new file mode 100644 index 000000000..e33190847 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerQuitDungeonReq.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.PlayerQuitDungeonReq) +public class HandlerPlayerQuitDungeonReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + session.getPlayer().getServer().getDungeonManager().exitDungeon(session.getPlayer()); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java new file mode 100644 index 000000000..a2cc052bb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonEntryInfoRsp.java @@ -0,0 +1,37 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Arrays; + +import emu.grasscutter.data.common.PointData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.DungeonEntryInfoOuterClass.DungeonEntryInfo; +import emu.grasscutter.net.proto.DungeonEntryInfoRspOuterClass.DungeonEntryInfoRsp; + +public class PacketDungeonEntryInfoRsp extends BasePacket { + + public PacketDungeonEntryInfoRsp(Player player, PointData pointData) { + super(PacketOpcodes.DungeonEntryInfoRsp); + + DungeonEntryInfoRsp.Builder proto = DungeonEntryInfoRsp.newBuilder() + .setPointId(pointData.getId()); + + for (int dungeonId : pointData.getDungeonIds()) { + DungeonEntryInfo info = DungeonEntryInfo.newBuilder().setDungeonId(dungeonId).build(); + proto.addDungeonEntryList(info); + } + + this.setData(proto); + } + + public PacketDungeonEntryInfoRsp() { + super(PacketOpcodes.DungeonEntryInfoRsp); + + DungeonEntryInfoRsp proto = DungeonEntryInfoRsp.newBuilder() + .setRetcode(1) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java index 16caca296..9c96888ea 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPathfindingEnterSceneRsp.java @@ -7,7 +7,5 @@ public class PacketPathfindingEnterSceneRsp extends BasePacket { public PacketPathfindingEnterSceneRsp(int clientSequence) { super(PacketOpcodes.PathfindingEnterSceneRsp); - - this.buildHeader(clientSequence); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterDungeonRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterDungeonRsp.java new file mode 100644 index 000000000..913ca109e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterDungeonRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerEnterDungeonRspOuterClass.PlayerEnterDungeonRsp; + +public class PacketPlayerEnterDungeonRsp extends BasePacket { + + public PacketPlayerEnterDungeonRsp(int pointId, int dungeonId) { + super(PacketOpcodes.PlayerEnterDungeonRsp); + + PlayerEnterDungeonRsp proto = PlayerEnterDungeonRsp.newBuilder() + .setPointId(pointId) + .setDungeonId(dungeonId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 8129a1188..b6c965c6b 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -65,6 +65,7 @@ public final class Utils { private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); 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;