diff --git a/proto/DungeonSettleNotify.proto b/proto/DungeonSettleNotify.proto index c190b38e7..c48ab8f6f 100644 --- a/proto/DungeonSettleNotify.proto +++ b/proto/DungeonSettleNotify.proto @@ -4,8 +4,13 @@ option java_package = "emu.grasscutter.net.proto"; import "ParamList.proto"; import "StrengthenPointData.proto"; +import "TowerLevelEndNotify.proto"; message DungeonSettleNotify { + oneof Detail { + TowerLevelEndNotify tower_level_end_notify = 101; + // it has more! + } uint32 dungeon_id = 1; bool is_success = 2; repeated uint32 fail_cond_list = 3; diff --git a/proto/TowerFloorRecordChangeNotify.proto b/proto/TowerFloorRecordChangeNotify.proto new file mode 100644 index 000000000..74a7135ec --- /dev/null +++ b/proto/TowerFloorRecordChangeNotify.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "TowerFloorRecord.proto"; + +message TowerFloorRecordChangeNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 2418; + } + + repeated TowerFloorRecord tower_floor_record_list = 1; + bool is_finished_entrance_floor = 2; +} diff --git a/proto/TowerLevelEndNotify.proto b/proto/TowerLevelEndNotify.proto new file mode 100644 index 000000000..d9f2da543 --- /dev/null +++ b/proto/TowerLevelEndNotify.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; +import "ItemParam.proto"; + +message TowerLevelEndNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 2456; + } + + enum ContinueStateType { + CONTINUE_STATE_CAN_NOT_CONTINUE = 0; + CONTINUE_STATE_CAN_ENTER_NEXT_LEVEL = 1; + CONTINUE_STATE_CAN_ENTER_NEXT_FLOOR = 2; + } + + bool is_success = 1; + repeated uint32 finished_star_cond_list = 2; + repeated ItemParam reward_item_list = 3; + uint32 continue_state = 4; + uint32 next_floor_id = 5; +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/BasicDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/BasicDungeonSettleListener.java new file mode 100644 index 000000000..5b5e243bd --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/BasicDungeonSettleListener.java @@ -0,0 +1,14 @@ +package emu.grasscutter.game.dungeons; + +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; +import emu.grasscutter.utils.Utils; + +public class BasicDungeonSettleListener implements DungeonSettleListener { + + @Override + public void onDungeonSettle(Scene scene) { + scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); + scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge())); + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java index b9724f49a..2e07f0058 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java @@ -1,33 +1,25 @@ package emu.grasscutter.game.dungeons; -import java.util.ArrayList; -import java.util.List; - -import emu.grasscutter.data.GameData; import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.def.DungeonData; -import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.world.Scene; -import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; -import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify; -import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; -import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.ArrayList; +import java.util.List; + public class DungeonChallenge { private final Scene scene; private final SceneGroup group; @@ -40,7 +32,7 @@ public class DungeonChallenge { private int score; private int objective = 0; private IntSet rewardedPlayers; - + public DungeonChallenge(Scene scene, SceneGroup group) { this.scene = scene; this.group = group; @@ -129,8 +121,7 @@ public class DungeonChallenge { } private void settle() { - getScene().setAutoCloseTime(Utils.getCurrentSeconds() + 1000); - getScene().broadcastPacket(new PacketDungeonSettleNotify(this)); + getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0)); } diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index e858decf8..f4b16c811 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -14,9 +14,11 @@ import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp; import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp; import emu.grasscutter.utils.Position; +import java.util.List; + public class DungeonManager { private final GameServer server; - + private static final BasicDungeonSettleListener basicDungeonSettleObserver = new BasicDungeonSettleListener(); public DungeonManager(GameServer server) { this.server = server; } @@ -49,13 +51,32 @@ public class DungeonManager { int sceneId = data.getSceneId(); player.getScene().setPrevScene(sceneId); - player.getWorld().transferPlayerToScene(player, sceneId, data); + if(player.getWorld().transferPlayerToScene(player, sceneId, data)){ + player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver); + } player.getScene().setPrevScenePoint(pointId); player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId)); return true; } - + + /** + * used in tower dungeons handoff + */ + public boolean handoffDungeon(Player player, int dungeonId, List dungeonSettleListeners) { + DungeonData data = GameData.getDungeonDataMap().get(dungeonId); + + if (data == null) { + return false; + } + Grasscutter.getLogger().info(player.getNickname() + " is trying to enter tower dungeon " + dungeonId); + + if(player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)){ + dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver); + } + return true; + } + public void exitDungeon(Player player) { if (player.getScene().getSceneType() != SceneType.SCENE_DUNGEON) { return; @@ -77,6 +98,7 @@ public class DungeonManager { } // clean temp team if it has player.getTeamManager().cleanTemporaryTeam(); + player.getTowerManager().clearEntry(); // 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/dungeons/DungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonSettleListener.java new file mode 100644 index 000000000..2eb389e05 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonSettleListener.java @@ -0,0 +1,7 @@ +package emu.grasscutter.game.dungeons; + +import emu.grasscutter.game.world.Scene; + +public interface DungeonSettleListener { + void onDungeonSettle(Scene scene); +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java new file mode 100644 index 000000000..5b1ff7a30 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java @@ -0,0 +1,24 @@ +package emu.grasscutter.game.dungeons; + +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify; +import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify; +import emu.grasscutter.utils.Utils; + +public class TowerDungeonSettleListener implements DungeonSettleListener { + + @Override + public void onDungeonSettle(Scene scene) { + scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); + var towerManager = scene.getPlayers().get(0).getTowerManager(); + + towerManager.notifyCurLevelRecordChangeWhenDone(); + scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(towerManager.getCurrentFloorId())); + scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge(), + true, + towerManager.hasNextLevel(), + towerManager.getNextFloorId() + )); + + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 971fbf2ac..7a09f1ecb 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.managers.MotionManager.MotionManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.managers.MapMarkManager.*; import emu.grasscutter.game.tower.TowerManager; @@ -1048,6 +1049,7 @@ public class Player { @PostLoad private void onLoad() { this.getTeamManager().setPlayer(this); + this.getTowerManager().setPlayer(this); } public void save() { @@ -1117,6 +1119,10 @@ public class Player { } public void onLogout() { + // force to leave the dungeon + if(getScene().getSceneType() == SceneType.SCENE_DUNGEON){ + this.getServer().getDungeonManager().exitDungeon(this); + } // Leave world if (this.getWorld() != null) { this.getWorld().removePlayer(this); diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index e49a15cc2..144443c87 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -1,40 +1,100 @@ package emu.grasscutter.game.tower; import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Transient; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.TowerLevelData; +import emu.grasscutter.game.dungeons.DungeonSettleListener; +import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; import java.util.List; @Entity public class TowerManager { - private final Player player; + @Transient + private Player player; public TowerManager(Player player) { this.player = player; } - private int currentLevel; - private int currentFloor; + public void setPlayer(Player player) { + this.player = player; + } + private int currentFloorId; + private int currentLevel; + @Transient + private int currentLevelId; + + @Transient + private int entryScene; + + public int getCurrentFloorId() { + return currentFloorId; + } + + private static final List towerDungeonSettleListener = List.of(new TowerDungeonSettleListener()); public void teamSelect(int floor, List> towerTeams) { var floorData = GameData.getTowerFloorDataMap().get(floor); - this.currentFloor = floorData.getFloorId(); - this.currentLevel = floorData.getLevelId(); + this.currentFloorId = floorData.getFloorId(); + this.currentLevel = 0; + this.currentLevelId = GameData.getTowerLevelDataMap().values().stream() + .filter(x -> x.getLevelId() == floorData.getLevelId() && x.getLevelIndex() == 1) + .findFirst() + .map(TowerLevelData::getID) + .orElse(0); + + if (entryScene == 0){ + entryScene = player.getSceneId(); + } player.getTeamManager().setupTemporaryTeam(towerTeams); } public void enterLevel(int enterPointId) { - var levelData = GameData.getTowerLevelDataMap().get(currentLevel); + var levelData = GameData.getTowerLevelDataMap().get(currentLevelId + currentLevel); + + this.currentLevel++; var id = levelData.getDungeonId(); + + notifyCurLevelRecordChange(); // use team user choose player.getTeamManager().useTemporaryTeam(0); - player.getServer().getDungeonManager() - .enterDungeon(player, enterPointId, id); + player.getServer().getDungeonManager().handoffDungeon(player, id, + towerDungeonSettleListener); - player.getSession().send(new PacketTowerEnterLevelRsp(currentFloor, currentLevel)); + // make sure user can exit dungeon correctly + player.getScene().setPrevScene(entryScene); + player.getScene().setPrevScenePoint(enterPointId); + + player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel)); + + } + + public void notifyCurLevelRecordChange(){ + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel)); + } + public void notifyCurLevelRecordChangeWhenDone(){ + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel + 1)); + } + public boolean hasNextLevel(){ + return this.currentLevel < 3; + } + + public int getNextFloorId() { + if(hasNextLevel()){ + return 0; + } + this.currentFloorId++; + return this.currentFloorId; + } + + public void clearEntry() { + this.entryScene = 0; } } diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 7aa4d3f8a..97099c9b9 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -1,6 +1,5 @@ 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; @@ -8,6 +7,7 @@ import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.data.def.WorldLevelData; import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.entity.*; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.TeamInfo; @@ -20,11 +20,8 @@ import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; import emu.grasscutter.scripts.SceneScriptManager; -import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneBlock; -import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.server.packet.send.PacketAvatarSkillInfoNotify; import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; @@ -56,6 +53,7 @@ public class Scene { private SceneScriptManager scriptManager; private DungeonChallenge challenge; + private List dungeonSettleListeners; private DungeonData dungeonData; private int prevScene; // Id of the previous scene private int prevScenePoint; @@ -205,6 +203,17 @@ public class Scene { this.challenge = challenge; } + public void addDungeonSettleObserver(DungeonSettleListener dungeonSettleListener){ + if(dungeonSettleListeners == null){ + dungeonSettleListeners = new ArrayList<>(); + } + dungeonSettleListeners.add(dungeonSettleListener); + } + + public List getDungeonSettleObservers() { + return dungeonSettleListeners; + } + public boolean isInScene(GameEntity entity) { return this.entities.containsKey(entity.getId()); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java index 411be291a..479029243 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java @@ -4,6 +4,8 @@ import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.DungeonSettleNotifyOuterClass.DungeonSettleNotify; +import emu.grasscutter.net.proto.ItemParamOuterClass; +import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNotify; public class PacketDungeonSettleNotify extends BasePacket { @@ -19,4 +21,43 @@ public class PacketDungeonSettleNotify extends BasePacket { this.setData(proto); } + + public PacketDungeonSettleNotify(DungeonChallenge challenge, + boolean canJump, + boolean hasNextLevel, + int nextFloorId + ) { + super(PacketOpcodes.DungeonSettleNotify); + + var continueStatus = TowerLevelEndNotify.ContinueStateType.CONTINUE_STATE_CAN_NOT_CONTINUE_VALUE; + if(challenge.isSuccess() && canJump){ + continueStatus = hasNextLevel ? TowerLevelEndNotify.ContinueStateType.CONTINUE_STATE_CAN_ENTER_NEXT_LEVEL_VALUE + : TowerLevelEndNotify.ContinueStateType.CONTINUE_STATE_CAN_ENTER_NEXT_FLOOR_VALUE; + } + + var towerLevelEndNotify = TowerLevelEndNotify.newBuilder() + .setIsSuccess(challenge.isSuccess()) + .setContinueState(continueStatus) + .addFinishedStarCondList(1) + .addFinishedStarCondList(2) + .addFinishedStarCondList(3) + .addRewardItemList(ItemParamOuterClass.ItemParam.newBuilder() + .setItemId(201) + .setCount(1000) + .build()) + ; + if(nextFloorId > 0){ + towerLevelEndNotify.setNextFloorId(nextFloorId); + } + + DungeonSettleNotify proto = DungeonSettleNotify.newBuilder() + .setDungeonId(challenge.getScene().getDungeonData().getId()) + .setIsSuccess(challenge.isSuccess()) + .setCloseTime(challenge.getScene().getAutoCloseTime()) + .setResult(challenge.isSuccess() ? 1 : 0) + .setTowerLevelEndNotify(towerLevelEndNotify.build()) + .build(); + + this.setData(proto); + } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerCurLevelRecordChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerCurLevelRecordChangeNotify.java new file mode 100644 index 000000000..fdae92555 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerCurLevelRecordChangeNotify.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerCurLevelRecordChangeNotifyOuterClass.TowerCurLevelRecordChangeNotify; +import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord; + +public class PacketTowerCurLevelRecordChangeNotify extends BasePacket { + + public PacketTowerCurLevelRecordChangeNotify(int curFloorId, int curLevelIndex) { + super(PacketOpcodes.TowerCurLevelRecordChangeNotify); + + TowerCurLevelRecordChangeNotify proto = TowerCurLevelRecordChangeNotify.newBuilder() + .setCurLevelRecord(TowerCurLevelRecord.newBuilder() + .setCurFloorId(curFloorId) + .setCurLevelIndex(curLevelIndex) + // TODO team info + .build()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java new file mode 100644 index 000000000..c0ed414a8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java @@ -0,0 +1,30 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerFloorRecordChangeNotifyOuterClass.TowerFloorRecordChangeNotify; +import emu.grasscutter.net.proto.TowerFloorRecordOuterClass.TowerFloorRecord; +import emu.grasscutter.net.proto.TowerLevelRecordOuterClass.TowerLevelRecord; + +public class PacketTowerFloorRecordChangeNotify extends BasePacket { + + public PacketTowerFloorRecordChangeNotify(int floorId) { + super(PacketOpcodes.TowerFloorRecordChangeNotify); + + TowerFloorRecordChangeNotify proto = TowerFloorRecordChangeNotify.newBuilder() + .addTowerFloorRecordList(TowerFloorRecord.newBuilder() + .setFloorId(floorId) + .setFloorStarRewardProgress(3) + .addPassedLevelRecordList(TowerLevelRecord.newBuilder() + .setLevelId(1) + .addSatisfiedCondList(1) + .addSatisfiedCondList(2) + .addSatisfiedCondList(3) + .build()) + .build()) + .setIsFinishedEntranceFloor(true) + .build(); + + this.setData(proto); + } +}