diff --git a/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMark.java b/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMark.java new file mode 100644 index 000000000..2dcbf7972 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMark.java @@ -0,0 +1,73 @@ +package emu.grasscutter.game.managers.MapMarkManager; + +import dev.morphia.annotations.Entity; +import emu.grasscutter.net.proto.MapMarkFromTypeOuterClass; +import emu.grasscutter.net.proto.MapMarkPointOuterClass; +import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass; +import emu.grasscutter.utils.Position; + +@Entity +public class MapMark { + private int sceneId; + private String name; + private Position position; + private MapMarkPointTypeOuterClass.MapMarkPointType pointType; + private int monsterId = 0; + private MapMarkFromTypeOuterClass.MapMarkFromType fromType; + private int questId = 7; + + public MapMark(Position position, MapMarkPointTypeOuterClass.MapMarkPointType type) { + this.position = position; + } + + public MapMark(MapMarkPointOuterClass.MapMarkPoint mapMarkPoint) { + this.sceneId = mapMarkPoint.getSceneId(); + this.name = mapMarkPoint.getName(); + this.position = new Position(mapMarkPoint.getPos().getX(), mapMarkPoint.getPos().getY(), mapMarkPoint.getPos().getZ()); + this.pointType = mapMarkPoint.getPointType(); + this.monsterId = mapMarkPoint.getMonsterId(); + this.fromType = mapMarkPoint.getFromType(); + this.questId = mapMarkPoint.getQuestId(); + } + + public int getSceneId() { + return this.sceneId; + } + + public String getName() { + return this.name; + } + + public Position getPosition() { + return this.position; + } + + public MapMarkPointTypeOuterClass.MapMarkPointType getMapMarkPointType() { + return this.pointType; + } + + public void setMapMarkPointType(MapMarkPointTypeOuterClass.MapMarkPointType pointType) { + this.pointType = pointType; + } + + public int getMonsterId() { + return this.monsterId; + } + + public void setMonsterId(int monsterId) { + this.monsterId = monsterId; + } + + public MapMarkFromTypeOuterClass.MapMarkFromType getMapMarkFromType() { + return this.fromType; + } + + public int getQuestId() { + return this.questId; + } + + public void setQuestId(int questId) { + this.questId = questId; + } + +} diff --git a/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java b/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java new file mode 100644 index 000000000..5249f59d9 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java @@ -0,0 +1,61 @@ +package emu.grasscutter.game.managers.MapMarkManager; + +import dev.morphia.annotations.Entity; +import emu.grasscutter.utils.Position; +import java.util.HashMap; + +@Entity +public class MapMarksManager { + + static final int mapMarkMaxCount = 150; + private HashMap mapMarks; + + public MapMarksManager() { + mapMarks = new HashMap(); + } + + public MapMarksManager(HashMap mapMarks) { + this.mapMarks = mapMarks; + } + + public HashMap getAllMapMarks() { + return mapMarks; + } + + public MapMark getMapMark(Position position) { + String key = getMapMarkKey(position); + if (mapMarks.containsKey(key)) { + return mapMarks.get(key); + } else { + return null; + } + } + + public String getMapMarkKey(Position position) { + return "x" + (int)position.getX()+ "z" + (int)position.getZ(); + } + + public boolean removeMapMark(Position position) { + String key = getMapMarkKey(position); + if (mapMarks.containsKey(key)) { + mapMarks.remove(key); + return true; + } + return false; + } + + public boolean addMapMark(MapMark mapMark) { + if (mapMarks.size() < mapMarkMaxCount) { + if (!mapMarks.containsKey(mapMark.getPosition())) { + mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark); + return true; + } + } + return false; + } + + public void setMapMarks(HashMap mapMarks) { + this.mapMarks = mapMarks; + } + +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 50fa517e6..7889286ef 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -25,6 +25,7 @@ import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.shop.ShopLimit; +import emu.grasscutter.game.managers.MapMarkManager.*; import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.World; import emu.grasscutter.net.packet.BasePacket; @@ -37,13 +38,12 @@ import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo; import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass; import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo; import emu.grasscutter.net.proto.PlayerWorldLocationInfoOuterClass; -import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass; import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture; +import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.net.proto.SocialShowAvatarInfoOuterClass; import emu.grasscutter.server.event.player.PlayerJoinEvent; import emu.grasscutter.server.event.player.PlayerQuitEvent; -import emu.grasscutter.server.event.player.PlayerReceiveMailEvent; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.*; @@ -53,12 +53,12 @@ import emu.grasscutter.utils.MessageHandler; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.time.Instant; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; @Entity(value = "players", useDiscriminator = false) public class Player { + @Id private int id; @Indexed(options = @IndexOptions(unique = true)) private String accountId; @@ -120,6 +120,8 @@ public class Player { @Transient private final InvokeHandler abilityInvokeHandler; @Transient private final InvokeHandler clientAbilityInitFinishHandler; + private MapMarksManager mapMarksManager; + @Deprecated @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! public Player() { @@ -158,6 +160,7 @@ public class Player { this.shopLimit = new ArrayList<>(); this.messageHandler = null; + this.mapMarksManager = new MapMarksManager(); } // On player creation @@ -183,6 +186,7 @@ public class Player { this.getPos().set(GameConstants.START_POSITION); this.getRotation().set(0, 307, 0); this.messageHandler = null; + this.mapMarksManager = new MapMarksManager(); } public int getUid() { @@ -959,6 +963,10 @@ public class Player { .build(); } + public MapMarksManager getMapMarksManager() { + return mapMarksManager; + } + public synchronized void onTick() { // Check ping if (this.getLastPingTime() > System.currentTimeMillis() + 60000) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMarkMapReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMarkMapReq.java index bd403047f..55b9fcfa6 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMarkMapReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMarkMapReq.java @@ -1,17 +1,22 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.game.props.EnterReason; -import emu.grasscutter.game.world.World; +import emu.grasscutter.game.managers.MapMarkManager.MapMark; +import emu.grasscutter.game.managers.MapMarkManager.MapMarksManager; +import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType; -import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq; -import emu.grasscutter.net.proto.OperationOuterClass.Operation; import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.*; +import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq; import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify; +import emu.grasscutter.server.packet.send.PacketMarkMapRsp; +import emu.grasscutter.server.packet.send.PacketMarkNewNotify; import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; +import emu.grasscutter.utils.Position; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; @Opcodes(PacketOpcodes.MarkMapReq) public class HandlerMarkMapReq extends PacketHandler { @@ -31,25 +36,51 @@ public class HandlerMarkMapReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { MarkMapReq req = MarkMapReq.parseFrom(payload); - - if (req.getOp() != MarkMapReq.Operation.ADD) { - return; + MarkMapReq.Operation op = req.getOp(); + Player player = session.getPlayer(); + MapMarksManager mapMarksManager = player.getMapMarksManager(); + if (op == MarkMapReq.Operation.ADD) { + MapMark newMapMark = new MapMark(req.getMark()); + // keep teleporting functionality on fishhook mark. + if (newMapMark.getMapMarkPointType() == MapMarkPointTypeOuterClass.MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) { + teleport(player, newMapMark); + return; + } + if (mapMarksManager.addMapMark(newMapMark)) { + player.save(); + } + } else if (op == MarkMapReq.Operation.MOD) { + MapMark newMapMark = new MapMark(req.getMark()); + if (mapMarksManager.removeMapMark(newMapMark.getPosition())) { + if (mapMarksManager.addMapMark(newMapMark)) { + player.save(); + } + } + } else if (op == MarkMapReq.Operation.DEL) { + MapMark newMapMark = new MapMark(req.getMark()); + if (mapMarksManager.removeMapMark(newMapMark.getPosition())) { + player.save(); + } + } else if (op == MarkMapReq.Operation.GET) { + // no-op } + // send all marks to refresh client map view. + HashMap mapMarks = mapMarksManager.getAllMapMarks(); + session.send(new PacketMarkMapRsp(player, mapMarks)); + } - session.getPlayer().getPos().setX(req.getMark().getPos().getX()); - session.getPlayer().getPos().setZ(req.getMark().getPos().getZ()); - - session.getPlayer().getPos() - .setY(isInt(req.getMark().getName()) ? Integer.parseInt(req.getMark().getName()) : 300); - - Grasscutter.getLogger().info("Player [" + session.getPlayer().getUid() + ":" + session.getPlayer().getNickname() - + "] tp to " + session.getPlayer().getPos() + " Scene id: " + req.getMark().getSceneId()); - - if (req.getMark().getSceneId() != session.getPlayer().getSceneId()) { - session.getPlayer().getWorld().transferPlayerToScene(session.getPlayer(), req.getMark().getSceneId(), - session.getPlayer().getPos()); + private void teleport(Player player, MapMark mapMark) { + // Increased height means you can fly to the top of dragonspine now, + // at the cost of slightly longer falling to your destination. + float y = 700; + float x = mapMark.getPosition().getX(); + float z = mapMark.getPosition().getZ(); + player.getPos().set(x, y, z); + if (mapMark.getSceneId() != player.getSceneId()) { + player.getWorld().transferPlayerToScene(player, mapMark.getSceneId(), + player.getPos()); } else { - session.getPlayer().getScene().broadcastPacket(new PacketSceneEntityAppearNotify(session.getPlayer())); + player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player)); } } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMarkMapRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMarkMapRsp.java new file mode 100644 index 000000000..d7ee20d61 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMarkMapRsp.java @@ -0,0 +1,43 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.managers.MapMarkManager.MapMark; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.*; + +import java.util.*; + +public class PacketMarkMapRsp extends BasePacket { + + public PacketMarkMapRsp(Player player, HashMap mapMarks) { + super(PacketOpcodes.MarkMapRsp); + + MarkMapRspOuterClass.MarkMapRsp.Builder proto = MarkMapRspOuterClass.MarkMapRsp.newBuilder(); + proto.setRetcode(0); + + if (mapMarks != null) { + for (MapMark mapMark: mapMarks.values()) { + MapMarkPointOuterClass.MapMarkPoint.Builder markPoint = MapMarkPointOuterClass.MapMarkPoint.newBuilder(); + markPoint.setSceneId(mapMark.getSceneId()); + markPoint.setName(mapMark.getName()); + + VectorOuterClass.Vector.Builder positionVector = VectorOuterClass.Vector.newBuilder(); + positionVector.setX(mapMark.getPosition().getX()); + positionVector.setY(mapMark.getPosition().getY()); + positionVector.setZ(mapMark.getPosition().getZ()); + markPoint.setPos(positionVector.build()); + + markPoint.setPointType(mapMark.getMapMarkPointType()); + markPoint.setFromType(mapMark.getMapMarkFromType()); + markPoint.setMonsterId(mapMark.getMonsterId()); + markPoint.setQuestId(mapMark.getQuestId()); + + proto.addMarkList(markPoint.build()); + } + } + + MarkMapRspOuterClass.MarkMapRsp data = proto.build(); + this.setData(data); + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMarkNewNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMarkNewNotify.java new file mode 100644 index 000000000..635f4ac30 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMarkNewNotify.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.*; + +import java.util.ArrayList; +import java.util.List; + +public class PacketMarkNewNotify extends BasePacket { + + public PacketMarkNewNotify(Player player, int markNewType, ArrayList idList) { + super(PacketOpcodes.MarkNewNotify); + + MarkNewNotifyOuterClass.MarkNewNotify.Builder proto = MarkNewNotifyOuterClass.MarkNewNotify.newBuilder(); + proto.setMarkNewType(markNewType); + for (Integer id: idList) { + proto.addIdList(id); + } + + MarkNewNotifyOuterClass.MarkNewNotify data = proto.build(); + this.setData(data); + } +} \ No newline at end of file