diff --git a/README.md b/README.md index 9f40986..239570e 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,14 @@ Latest nightly compiled server jar: [Nightly](https://nightly.link/Melledy/Nebul - Commissions - Heartlink - Achievements +- Tower (dorm system) - Monoliths - Bounty Trials - Menance Arena - Proving Grounds - Catacylsm Survivor (talents not fully working) - Boss Blitz +- Shadow Siege - Events (Only tower defense and trials. Other events may work) ### Supported regions diff --git a/src/main/java/emu/nebula/GameConstants.java b/src/main/java/emu/nebula/GameConstants.java index bf56278..9387f22 100644 --- a/src/main/java/emu/nebula/GameConstants.java +++ b/src/main/java/emu/nebula/GameConstants.java @@ -71,6 +71,25 @@ public class GameConstants { public static final int[] TRACE_HUNT_TRACE_IDS = {21, 22, 23, 31, 41}; public static final int TRACE_HUNT_MAX_TRACE_PROGRESS = 10_000; public static final int TRACE_HUNT_MAX_HUNT_PROGRESS = 10_000; + + // Dorm + public static final int DORM_MAX_PRESET_NUM = 20; + public static final int DORM_DEFAULT_PRESET_NUM = 10; + public static final int DORM_MAX_ROOM_NUM = 4; + public static final int DORM_INVITATION_ROLE_NORMAL_NUM = 3; + public static final int DORM_INVITATION_ROLE_RAND_NUM = 1; + public static final int DORM_ROLE_INTERACTION_MAX_NUM = 5; + public static final int DORM_ROLE_INTERACTION_DROP_ID = 4002; + public static final int DORM_ROLE_FAVORABILITY = 300; + public static final int DORM_ROLE_AFFINITY_GIFT_MAX_NUM = 5; + public static final int DORM_ROLE_AFFINITY_GIFT_DAILY_NUM = 1; + public static final int DORM_NAME_RESET_TIME_LIMIT = 604_800; + public static final int DORM_LIKE_LIMIT = 3; + public static final int DORM_BLOCKS_CONVERT_PROP = 1; + public static final int DORM_DEFAULT_ROOM_ID = 701001; + public static final int DORM_JADE_ITEM_ID = 2; + public static final int DORM_FURNITURE_COIN_ITEM_ID = 701; + public static final int DORM_MAX_NAME_LENGTH = 20; // Etc public static final int REFRESH_TYPE_DAILY = 1; diff --git a/src/main/java/emu/nebula/data/GameData.java b/src/main/java/emu/nebula/data/GameData.java index cdcd1a6..36d9db5 100644 --- a/src/main/java/emu/nebula/data/GameData.java +++ b/src/main/java/emu/nebula/data/GameData.java @@ -146,6 +146,21 @@ public class GameData { @Getter private static DataTable TraceHuntLevelDataTable = new DataTable<>(); @Getter private static DataTable TraceHuntSelfHuntExtraCostDataTable = new DataTable<>(); + // ===== Dorm ===== + @Getter private static DataTable DormRoomDataTable = new DataTable<>(); + @Getter private static DataTable DormFurnitureDataTable = new DataTable<>(); + @Getter private static DataTable DormLayoutRewardDataTable = new DataTable<>(); + @Getter private static DataTable DormRoomLayoutDataTable = new DataTable<>(); + @Getter private static DataTable DormRewardDataTable = new DataTable<>(); + @Getter private static DataTable DormAchievementDataTable = new DataTable<>(); + @Getter private static DataTable DormHonorTitleDataTable = new DataTable<>(); + @Getter private static DataTable DormRoleDataTable = new DataTable<>(); + @Getter private static DataTable DormRoleSkinDataTable = new DataTable<>(); + @Getter private static DataTable DormInteractiveDataTable = new DataTable<>(); + @Getter private static DataTable DormQuestDataTable = new DataTable<>(); + @Getter private static DataTable DormQuestGroupDataTable = new DataTable<>(); + @Getter private static DataTable DormRoomSceneDataTable = new DataTable<>(); + // ===== Misc ===== @Getter private static DataTable WorldClassDataTable = new DataTable<>(); @Getter private static DataTable GuideGroupDataTable = new DataTable<>(); diff --git a/src/main/java/emu/nebula/data/resources/DormAchievementDef.java b/src/main/java/emu/nebula/data/resources/DormAchievementDef.java new file mode 100644 index 0000000..1ba0fcc --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormAchievementDef.java @@ -0,0 +1,24 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormAchievement.json") +public class DormAchievementDef extends BaseDef { + private int Id; + private int CompleteCond; + private int[] CompleteParams; + private int Tid1; + private int Qty1; + private int Tid2; + private int Qty2; + private int Tid3; + private int Qty3; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormFurnitureDef.java b/src/main/java/emu/nebula/data/resources/DormFurnitureDef.java new file mode 100644 index 0000000..12b41a5 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormFurnitureDef.java @@ -0,0 +1,17 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormFurniture.json") +public class DormFurnitureDef extends BaseDef { + private int Id; + private int CollectionValue; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormHonorTitleDef.java b/src/main/java/emu/nebula/data/resources/DormHonorTitleDef.java new file mode 100644 index 0000000..93cf4dd --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormHonorTitleDef.java @@ -0,0 +1,17 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormHonorTitle.json") +public class DormHonorTitleDef extends BaseDef { + private int Id; + private int NeedExp; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormInteractiveDef.java b/src/main/java/emu/nebula/data/resources/DormInteractiveDef.java new file mode 100644 index 0000000..4d11dbf --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormInteractiveDef.java @@ -0,0 +1,20 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormInteractive.json") +public class DormInteractiveDef extends BaseDef { + private int Id; + private int CharId; + private int FurniturnId; + private int DormInterType; + private String AnimationConfig; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormLayoutRewardDef.java b/src/main/java/emu/nebula/data/resources/DormLayoutRewardDef.java new file mode 100644 index 0000000..2ffc54b --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormLayoutRewardDef.java @@ -0,0 +1,20 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormLayoutReward.json") +public class DormLayoutRewardDef extends BaseDef { + private int Id; + private int RoomType; + private int FurnitureCount; + private int RewardId1; + private int Qty1; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormQuestDef.java b/src/main/java/emu/nebula/data/resources/DormQuestDef.java new file mode 100644 index 0000000..6a34fa8 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormQuestDef.java @@ -0,0 +1,22 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormQuest.json") +public class DormQuestDef extends BaseDef { + private int Id; + private int DormQuestGroup; + private String Title; + private int Reward1; + private int RewardQty1; + private int Reward2; + private int RewardQty2; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormQuestGroupDef.java b/src/main/java/emu/nebula/data/resources/DormQuestGroupDef.java new file mode 100644 index 0000000..69a9189 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormQuestGroupDef.java @@ -0,0 +1,24 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormQuestGroup.json") +public class DormQuestGroupDef extends BaseDef { + private int Id; + private int PreGroup; + private String Title; + private int Item1; + private int Qty1; + private int Item2; + private int Qty2; + private int Item3; + private int Qty3; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormRewardDef.java b/src/main/java/emu/nebula/data/resources/DormRewardDef.java new file mode 100644 index 0000000..74f0a49 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormRewardDef.java @@ -0,0 +1,56 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import emu.nebula.util.JsonUtils; +import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormReward.json") +public class DormRewardDef extends BaseDef { + private int Id; + private int DropId; + private String Conditions; + + private transient int[][] parsedConditions; + + @Override + public int getId() { + return Id; + } + + @Override + public void onLoad() { + this.parsedConditions = JsonUtils.decode(this.Conditions, int[][].class); + } + + /** + * Checks the requirements for this dorm reward. + * Condition type 1 = the player needs to have the room unlocked. + */ + public boolean isUnlocked(IntSet unlockedRoomIds) { + if (this.parsedConditions == null) { + return false; + } + + for (int[] condition : this.parsedConditions) { + if (condition.length < 2) { + return false; + } + + switch (condition[0]) { + case 1 -> { + if (!unlockedRoomIds.contains(condition[1])) { + return false; + } + } + default -> { + return false; + } + } + } + + return true; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormRoleDef.java b/src/main/java/emu/nebula/data/resources/DormRoleDef.java new file mode 100644 index 0000000..b0f7add --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormRoleDef.java @@ -0,0 +1,18 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormRole.json") +public class DormRoleDef extends BaseDef { + private int Id; + private int RoleType; + private int[] Exclusions; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormRoleSkinDef.java b/src/main/java/emu/nebula/data/resources/DormRoleSkinDef.java new file mode 100644 index 0000000..015d39c --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormRoleSkinDef.java @@ -0,0 +1,19 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormRoleSkin.json") +public class DormRoleSkinDef extends BaseDef { + private int Id; + private int TrId; + private int TrQty; + private int UnlockChar; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormRoomDef.java b/src/main/java/emu/nebula/data/resources/DormRoomDef.java new file mode 100644 index 0000000..9398858 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormRoomDef.java @@ -0,0 +1,24 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormRoom.json") +public class DormRoomDef extends BaseDef { + private int Id; + private int RoomType; + private int RoomSizeType; + private int SceneRes; + private String DefaultName; + private int DefaultLayoutId; + private boolean DefaultCanUse; + private int RoleNum; + private int FloorRatio; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormRoomLayoutDef.java b/src/main/java/emu/nebula/data/resources/DormRoomLayoutDef.java new file mode 100644 index 0000000..5f2eb19 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormRoomLayoutDef.java @@ -0,0 +1,21 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormRoomLayout.json") +public class DormRoomLayoutDef extends BaseDef { + private int Id; + private int RoomLayoutId; + private int FurnitureId; + private int[] Pos; + private int Rot; + private int ApplyRoomType; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/data/resources/DormRoomSceneDef.java b/src/main/java/emu/nebula/data/resources/DormRoomSceneDef.java new file mode 100644 index 0000000..940ce32 --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/DormRoomSceneDef.java @@ -0,0 +1,16 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import lombok.Getter; + +@Getter +@ResourceType(name = "DormRoomScene.json") +public class DormRoomSceneDef extends BaseDef { + private int Id; + + @Override + public int getId() { + return Id; + } +} diff --git a/src/main/java/emu/nebula/game/dorm/DormFurniture.java b/src/main/java/emu/nebula/game/dorm/DormFurniture.java new file mode 100644 index 0000000..7b3a1cf --- /dev/null +++ b/src/main/java/emu/nebula/game/dorm/DormFurniture.java @@ -0,0 +1,49 @@ +package emu.nebula.game.dorm; + +import dev.morphia.annotations.Entity; +import emu.nebula.proto.PublicDorm.FurnitureInfo; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity(useDiscriminator = false) +public class DormFurniture { + private int furnitureId; + private int x; + private int y; + private int z; + private int rotation; + + @Deprecated // Morphia only + public DormFurniture() { + + } + + public DormFurniture(int furnitureId, int x, int y, int z, int rotation) { + this.furnitureId = furnitureId; + this.x = x; + this.y = y; + this.z = z; + this.rotation = rotation; + } + + public DormFurniture(FurnitureInfo info) { + this.furnitureId = info.getFurnitureId(); + this.x = info.getX(); + this.y = info.getY(); + this.z = info.getZ(); + this.rotation = info.getRotation(); + } + + // Proto + + public FurnitureInfo toProto() { + return FurnitureInfo.newInstance() + .setFurnitureId(this.furnitureId) + .setX(this.x) + .setY(this.y) + .setZ(this.z) + .setRotation(this.rotation); + } +} diff --git a/src/main/java/emu/nebula/game/dorm/DormManager.java b/src/main/java/emu/nebula/game/dorm/DormManager.java new file mode 100644 index 0000000..5431c58 --- /dev/null +++ b/src/main/java/emu/nebula/game/dorm/DormManager.java @@ -0,0 +1,1000 @@ +package emu.nebula.game.dorm; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; + +import emu.nebula.GameConstants; +import emu.nebula.Nebula; +import emu.nebula.data.GameData; +import emu.nebula.data.resources.DormRoomDef; +import emu.nebula.data.resources.DropPkgDef; +import emu.nebula.database.GameDatabaseObject; +import emu.nebula.game.character.GameCharacter; +import emu.nebula.game.inventory.ItemParamMap; +import emu.nebula.game.player.Player; +import emu.nebula.game.player.PlayerChangeInfo; +import emu.nebula.game.player.PlayerManager; +import emu.nebula.game.quest.QuestCondition; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormInfo.DormInfoListResp; +import emu.nebula.proto.Public; +import emu.nebula.proto.PublicDorm.AchievementComplete; +import emu.nebula.proto.PublicDorm.AchievementInfo; +import emu.nebula.proto.PublicDorm.DormAffinityGiftInfo; +import emu.nebula.proto.PublicDorm.DormHot; +import emu.nebula.proto.PublicDorm.DormInfoData; +import emu.nebula.proto.PublicDorm.DormRoomUnlock; +import emu.nebula.proto.PublicDorm.RoleInteraction; +import emu.nebula.util.Utils; + +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +import lombok.Getter; + +@Getter +@Entity(value = "dorm", useDiscriminator = false) +public class DormManager extends PlayerManager implements GameDatabaseObject { + @Id + private int uid; + + // Dorm details + private String dormName; + private long dormNameRestTime; + + // Furniture collection + private Int2IntMap furnitureCollection; + + // Saved room state + private Map rooms; + private Map presets; + + // Rewards / achievements + private IntSet receivedLayoutRewardIds; + private IntSet receivedRewardIds; + private Map completedAchievements; + + // Social stats + private int likeNum; + private int dayVisitNum; + private int visitTotalNum; + + // Daily state + private long lastResetDay; + private IntSet likedFriendUids; + private IntSet dailyVisitorUids; + private IntSet dailyInteractionRoleIds; + private Int2IntMap dailyAffinityGiftCounts; + private int dailyAffinityGiftTotal; + + private transient boolean queueSave; + + @Deprecated // Morphia only + public DormManager() { + + } + + public DormManager(Player player) { + super(player); + + this.uid = player.getUid(); + this.dormName = ""; + this.furnitureCollection = new Int2IntOpenHashMap(); + this.presets = new HashMap<>(); + this.rooms = new HashMap<>(); + this.receivedLayoutRewardIds = new IntOpenHashSet(); + this.receivedRewardIds = new IntOpenHashSet(); + this.completedAchievements = new HashMap<>(); + this.likedFriendUids = new IntOpenHashSet(); + this.dailyVisitorUids = new IntOpenHashSet(); + this.dailyInteractionRoleIds = new IntOpenHashSet(); + this.dailyAffinityGiftCounts = new Int2IntOpenHashMap(); + this.lastResetDay = Utils.getResetEpochDay(); + + this.save(); + } + + public int getState() { + return this.getReceivedRewardIds().isEmpty() ? 1 : 0; + } + + // Daily reset + + public synchronized void resetDailies() { + long currentDay = Utils.getResetEpochDay(); + + if (currentDay <= this.lastResetDay) { + return; + } + + this.likedFriendUids.clear(); + this.dailyVisitorUids.clear(); + this.dailyInteractionRoleIds.clear(); + this.dailyAffinityGiftCounts.clear(); + this.dailyAffinityGiftTotal = 0; + this.dayVisitNum = 0; + this.lastResetDay = currentDay; + this.queueSave = true; + } + + // Rooms + + /** + * Returns the ids of all rooms the player has access to. Rooms are unlocked + * by acquiring the room item, which creates the room state in {@link #rooms}. + */ + public IntSet getUnlockedRoomIds() { + var result = new IntOpenHashSet(); + + // Rooms that are available by default + for (var room : GameData.getDormRoomDataTable()) { + if (room.isDefaultCanUse()) { + result.add(room.getId()); + } + } + + // Rooms that have been unlocked by the player + if (this.rooms != null) { + result.addAll(this.rooms.keySet()); + } + + return result; + } + + public boolean isRoomUnlocked(int roomId) { + return this.getUnlockedRoomIds().contains(roomId); + } + + public synchronized DormRoomState getOrCreateRoom(int roomId) { + var room = this.rooms.get(roomId); + if (room != null) { + return room; + } + + var data = GameData.getDormRoomDataTable().get(roomId); + if (data == null) { + return null; + } + + room = new DormRoomState(roomId); + room.setFurnitureList(createDefaultFurniture(data)); + + this.rooms.put(roomId, room); + this.queueSave = true; + + return room; + } + + public synchronized boolean unlockRoom(int roomId, PlayerChangeInfo change) { + if (!GameData.getDormRoomDataTable().containsKey(roomId)) { + return false; + } + + if (this.rooms.containsKey(roomId)) { + return false; + } + + this.getOrCreateRoom(roomId); + + if (change != null) { + this.getPlayer().trigger(QuestCondition.DormUnlockRoom, 1); + + if (this.getPlayer().hasSession()) { + var proto = DormRoomUnlock.newInstance() + .setDormRoomId(roomId) + .setChangeInfo(change.toProto()); + + this.getPlayer().addNextPackage(NetMsgId.dorm_room_unlock_notify, proto); + } + } + + return true; + } + + // Furniture + + /** + * Adds furniture to the collection + */ + public synchronized void addFurniture(int id, int amount) { + this.furnitureCollection.put(id, this.furnitureCollection.get(id) + amount); + this.queueSave = true; + } + + /** + * Returns how many of the given furniture the player owns + */ + public int getFurnitureCount(int id) { + if (this.furnitureCollection == null) { + return 0; + } + + return this.furnitureCollection.get(id); + } + + /** + * Returns true if the player owns at least one of the given furniture + */ + public boolean hasFurniture(int id) { + return this.getFurnitureCount(id) > 0; + } + + public static List createDefaultFurniture(DormRoomDef room) { + var result = new ArrayList(); + + if (room == null || room.getDefaultLayoutId() <= 0) { + return result; + } + + var layoutReward = GameData.getDormLayoutRewardDataTable().get(room.getDefaultLayoutId()); + if (layoutReward == null) { + return result; + } + + int baseId = room.getDefaultLayoutId() * 100; + + for (int i = 1; i <= layoutReward.getFurnitureCount(); i++) { + var layout = GameData.getDormRoomLayoutDataTable().get(baseId + i); + if (layout == null || layout.getPos() == null || layout.getPos().length < 3) { + continue; + } + + int[] pos = layout.getPos(); + result.add(new DormFurniture(layout.getFurnitureId(), pos[0], pos[1], pos[2], layout.getRot())); + } + + return result; + } + + public static boolean isValidFurnitureList(List furnitureList) { + if (furnitureList == null || furnitureList.size() > 1000) { + return false; + } + + for (var furniture : furnitureList) { + if (furniture == null || furniture.getFurnitureId() <= 0) { + return false; + } + if (!GameData.getDormFurnitureDataTable().containsKey(furniture.getFurnitureId())) { + return false; + } + } + + return true; + } + + public synchronized Long saveFurnitureLayout(int roomId, List furnitureList, int buildId) { + if (!this.isRoomUnlocked(roomId)) { + return null; + } + + var roomData = GameData.getDormRoomDataTable().get(roomId); + if (roomData == null || !isValidFurnitureList(furnitureList)) { + return null; + } + + long now = Nebula.getCurrentServerTime(); + + if (buildId == 0) { + var room = this.getOrCreateRoom(roomId); + if (room == null) { + return null; + } + + room.setFurnitureList(new ArrayList<>(furnitureList)); + this.queueSave = true; + + this.getPlayer().trigger(QuestCondition.DormPlaceFurniture, furnitureList.size()); + this.getPlayer().trigger(QuestCondition.DormSaveRoom, 1); + + return 0L; + } + + if (buildId < 1 || buildId > GameConstants.DORM_DEFAULT_PRESET_NUM) { + return null; + } + + var preset = this.presets.get(buildId); + + if (preset == null) { + if (this.presets.size() >= GameConstants.DORM_DEFAULT_PRESET_NUM) { + return null; + } + + preset = new DormPreset(buildId, roomData.getRoomType(), now); + this.presets.put(buildId, preset); + } else { + preset.setCreateTime(now); + preset.setDormType(roomData.getRoomType()); + } + + preset.setFurnitureList(new ArrayList<>(furnitureList)); + this.queueSave = true; + + return now; + } + + public synchronized boolean deletePreset(int buildId) { + if (this.presets.remove(buildId) == null) { + return false; + } + + this.queueSave = true; + return true; + } + + public synchronized boolean renamePreset(int buildId, String name) { + // Sanity check name + if (name == null || name.length() > GameConstants.DORM_MAX_NAME_LENGTH) { + return false; + } + + var preset = this.presets.get(buildId); + if (preset == null) { + return false; + } + + preset.setBuildName(name); + this.queueSave = true; + + return true; + } + + public synchronized boolean renameRoom(int roomId, String name) { + // Sanity check name + if (name == null || name.length() > GameConstants.DORM_MAX_NAME_LENGTH) { + return false; + } + + // Check rename cooldown + long now = Nebula.getCurrentServerTime(); + + if (roomId == 0) { + if (now < this.dormNameRestTime + GameConstants.DORM_NAME_RESET_TIME_LIMIT) { + return false; + } + + this.dormName = name; + this.dormNameRestTime = now; + this.queueSave = true; + + return true; + } + + if (!this.isRoomUnlocked(roomId)) { + return false; + } + + var room = this.getOrCreateRoom(roomId); + if (room == null || now < room.getRestNameTime() + GameConstants.DORM_NAME_RESET_TIME_LIMIT) { + return false; + } + + room.setRoomName(name); + room.setRestNameTime(now); + this.queueSave = true; + + return true; + } + + public synchronized boolean setRoomBgm(int roomId, int discId) { + if (discId < 0 || !this.isRoomUnlocked(roomId)) { + return false; + } + + var room = this.getOrCreateRoom(roomId); + if (room == null) { + return false; + } + + room.setDiscId(discId); + this.queueSave = true; + + return true; + } + + public synchronized boolean setRoomScene(int roomId, int sceneId) { + if (sceneId < 0 || !this.isRoomUnlocked(roomId)) { + return false; + } + + var roomData = GameData.getDormRoomDataTable().get(roomId); + + if (sceneId != 0) { + if (!GameData.getDormRoomSceneDataTable().containsKey(sceneId)) { + return false; + } + + // The default scene of the room is always available + if (roomData == null || roomData.getSceneRes() != sceneId) { + if (this.getPlayer().getInventory().getItemCount(sceneId) <= 0) { + return false; + } + } + } + + var room = this.getOrCreateRoom(roomId); + if (room == null) { + return false; + } + + room.setRoomScene(sceneId); + this.queueSave = true; + + return true; + } + + public synchronized boolean updateInvitationRoles(int roomId, List roleIds) { + if (!this.isRoomUnlocked(roomId) || roleIds == null) { + return false; + } + + var roomData = GameData.getDormRoomDataTable().get(roomId); + var room = this.getOrCreateRoom(roomId); + + if (roomData == null || room == null) { + return false; + } + + // Validate role ids + var requested = new IntOpenHashSet(); + for (int roleId : roleIds) { + var roleData = GameData.getDormRoleDataTable().get(roleId); + if (roleData == null) { + return false; + } + + // Playable characters need to be owned by the player. + if (roleData.getRoleType() == 1 && this.getPlayer().getCharacters().getCharacterById(roleId) == null) { + return false; + } + + requested.add(roleId); + } + + // Keep existing invitation NPCs, mirroring the client + var newRoles = new ArrayList(); + for (var role : room.getRoleList()) { + var roleData = GameData.getDormRoleDataTable().get(role.getRoleId()); + if (role.isInvitationRole() && roleData != null && roleData.getRoleType() == 2) { + newRoles.add(role); + } + } + + // Add requested roles + var existing = new IntOpenHashSet(); + for (var role : newRoles) { + existing.add(role.getRoleId()); + } + + for (int roleId : requested) { + if (existing.contains(roleId)) { + continue; + } + + var role = new DormRole(roleId); + role.setVisit(true); + role.setInvitationRole(false); + newRoles.add(role); + } + + if (newRoles.size() > roomData.getRoleNum()) { + return false; + } + + room.setRoleList(newRoles); + + // Remove the requested roles from all other rooms + for (var other : this.rooms.values()) { + if (other.getDormRoomId() == roomId) { + continue; + } + + other.getRoleList().removeIf(role -> requested.contains(role.getRoleId())); + } + this.queueSave = true; + return true; + } + + // Interactions + + public boolean hasInteracted(int roleId) { + this.resetDailies(); + return this.dailyInteractionRoleIds.contains(roleId); + } + + public int getDailyInteractionCount() { + this.resetDailies(); + return this.dailyInteractionRoleIds.size(); + } + + public synchronized void addInteractionRole(int roleId) { + if (this.dailyInteractionRoleIds.add(roleId)) { + this.queueSave = true; + } + } + + public boolean isParticipatingInRoom(int roomId, int roleId) { + var room = this.rooms.get(roomId); + if (room == null) { + return false; + } + + for (var role : room.getRoleList()) { + if (role.getRoleId() == roleId) { + return true; + } + } + + return false; + } + + public synchronized int interactRole(GameCharacter character, int roleId, PlayerChangeInfo change) { + if (character == null) { + return -1; + } + + // Affinity + character.addAffinityExp(GameConstants.DORM_ROLE_FAVORABILITY, change); + + // Random interaction reward + int dropItemId = emu.nebula.data.resources.DropPkgDef.getRandomDrop(GameConstants.DORM_ROLE_INTERACTION_DROP_ID); + if (dropItemId > 0) { + this.getPlayer().getInventory().addItem(dropItemId, 1, change); + } + + this.addInteractionRole(roleId); + this.getPlayer().trigger(QuestCondition.DormRoleInteractionReward, 1); + + return dropItemId; + } + + /** + * Interacts with the roles of the given rooms. + * + * @param interactions the requested role interactions + * @return the result of the interactions, or null if no interaction was possible + */ + public synchronized DormRoleInteractionResult interactRoles(Iterable interactions) { + var change = new PlayerChangeInfo(); + var results = new ArrayList(); + var processed = new IntOpenHashSet(); + + for (var interaction : interactions) { + int roomId = interaction.getDormRoomId(); + int roleId = interaction.getRoleId(); + + // Sanity checks + if (roleId <= 0 || !processed.add(roleId)) { + continue; + } + if (!this.isRoomUnlocked(roomId) || !this.isParticipatingInRoom(roomId, roleId)) { + continue; + } + if (this.getDailyInteractionCount() >= GameConstants.DORM_ROLE_INTERACTION_MAX_NUM) { + break; + } + if (this.hasInteracted(roleId)) { + continue; + } + + var character = this.getPlayer().getCharacters().getCharacterById(roleId); + if (character == null) { + continue; + } + + int dropItemId = this.interactRole(character, roleId, change); + if (dropItemId < 0) { + continue; + } + + results.add(new DormRoleInteractionResult.Interaction(character, dropItemId)); + } + + if (results.isEmpty()) { + return null; + } + + return new DormRoleInteractionResult(change, results); + } + + // Affinity gifts + + public int getDailyGiftTotal() { + return this.dailyAffinityGiftTotal; + } + + public int getDailyGiftCount(int roleId) { + return this.dailyAffinityGiftCounts.get(roleId); + } + + public synchronized void addGift(int roleId, int count) { + this.dailyAffinityGiftCounts.put(roleId, this.dailyAffinityGiftCounts.get(roleId) + count); + this.dailyAffinityGiftTotal += count; + this.queueSave = true; + } + + /** + * Sends affinity gifts to a character. One gift action per character per day, + * with a shared daily item limit. + * + * @return the changes of the gift, or null if the gifts could not be sent + */ + public synchronized PlayerChangeInfo sendAffinityGift(GameCharacter character, int roomId, ItemParamMap items) { + // Validate room and gifts + if (character == null || !this.isRoomUnlocked(roomId) || items == null || items.isEmpty()) { + return null; + } + + int charId = character.getCharId(); + + // Validate gift items + int giftCount = 0; + + for (var entry : items.int2IntEntrySet()) { + if (entry.getIntValue() <= 0) { + return null; + } + + giftCount += entry.getIntValue(); + + if (giftCount > GameConstants.DORM_ROLE_AFFINITY_GIFT_MAX_NUM) { + return null; + } + } + + // One gift action per character per day, with a shared daily item limit + if (this.getDailyGiftCount(charId) > 0) { + return null; + } + if (this.getDailyGiftTotal() + giftCount > GameConstants.DORM_ROLE_AFFINITY_GIFT_MAX_NUM) { + return null; + } + + // Remove items and add affinity + var change = character.sendGift(items); + + if (change == null) { + return null; + } + + this.addGift(charId, giftCount); + this.getPlayer().trigger(QuestCondition.DormSendGift, giftCount); + + return change; + } + + // Blocks + + /** + * Converts dorm blocks into furniture coins. + * + * @return the changes of the conversion, or null if the conversion failed + */ + public synchronized PlayerChangeInfo convertBlocks(int count) { + if (count <= 0 || count > Integer.MAX_VALUE / GameConstants.DORM_BLOCKS_CONVERT_PROP) { + return null; + } + + var inventory = this.getPlayer().getInventory(); + + if (inventory.getItemCount(GameConstants.DORM_JADE_ITEM_ID) < count) { + return null; + } + + var change = new PlayerChangeInfo(); + + // Remove jade and add furniture coins + inventory.removeItem(GameConstants.DORM_JADE_ITEM_ID, count, change); + inventory.addItem( + GameConstants.DORM_FURNITURE_COIN_ITEM_ID, + count * GameConstants.DORM_BLOCKS_CONVERT_PROP, + change + ); + + return change; + } + + // Rewards + + public IntSet getClaimableRewardIds() { + var result = new IntOpenHashSet(); + var unlocked = this.getUnlockedRoomIds(); + + for (var reward : GameData.getDormRewardDataTable()) { + if (this.receivedRewardIds.contains(reward.getId())) { + continue; + } + + if (reward.isUnlocked(unlocked)) { + result.add(reward.getId()); + } + } + + return result; + } + + public boolean isLayoutRewardClaimable(int layoutId) { + var data = GameData.getDormLayoutRewardDataTable().get(layoutId); + if (data == null || data.getRewardId1() <= 0 || data.getQty1() <= 0) { + return false; + } + + for (int i = 1; i <= data.getFurnitureCount(); i++) { + var layout = GameData.getDormRoomLayoutDataTable().get(layoutId * 100 + i); + if (layout == null) { + continue; + } + + int required = 0; + for (int j = 1; j <= data.getFurnitureCount(); j++) { + var other = GameData.getDormRoomLayoutDataTable().get(layoutId * 100 + j); + if (other != null && other.getFurnitureId() == layout.getFurnitureId()) { + required++; + } + } + + if (this.getFurnitureCount(layout.getFurnitureId()) < required) { + return false; + } + } + + return true; + } + + public synchronized PlayerChangeInfo receiveLayoutRewards(int[] layoutIds) { + var change = new PlayerChangeInfo(); + var rewards = new ItemParamMap(); + boolean received = false; + + for (int layoutId : layoutIds) { + if (this.receivedLayoutRewardIds.contains(layoutId) || !this.isLayoutRewardClaimable(layoutId)) { + continue; + } + + var data = GameData.getDormLayoutRewardDataTable().get(layoutId); + rewards.add(data.getRewardId1(), data.getQty1()); + this.receivedLayoutRewardIds.add(layoutId); + received = true; + } + + if (!received) { + return null; + } + + this.getPlayer().getInventory().addItems(rewards, change); + this.getPlayer().trigger(QuestCondition.DormRewardReceive, 1); + this.queueSave = true; + + return change; + } + + public synchronized PlayerChangeInfo receiveRewards(int[] rewardIds) { + var change = new PlayerChangeInfo(); + var unlocked = this.getUnlockedRoomIds(); + boolean received = false; + + for (int rewardId : rewardIds) { + if (this.receivedRewardIds.contains(rewardId)) { + continue; + } + + var data = GameData.getDormRewardDataTable().get(rewardId); + if (data == null || !data.isUnlocked(unlocked)) { + continue; + } + + int itemId = DropPkgDef.getRandomDrop(data.getDropId()); + if (itemId <= 0) { + continue; + } + + this.getPlayer().getInventory().addItem(itemId, 1, change); + this.receivedRewardIds.add(rewardId); + received = true; + } + + if (!received) { + return null; + } + + this.getPlayer().trigger(QuestCondition.DormRewardReceive, 1); + this.queueSave = true; + + return change; + } + + public int getCollectionValue() { + if (this.furnitureCollection == null) { + return 0; + } + + int total = 0; + + for (var entry : this.furnitureCollection.int2IntEntrySet()) { + if (entry.getIntValue() <= 0) { + continue; + } + + var furniture = GameData.getDormFurnitureDataTable().get(entry.getIntKey()); + if (furniture != null) { + total += furniture.getCollectionValue(); + } + } + + return total; + } + + public synchronized AchievementComplete receiveAchievements(int[] achievementIds) { + int collectionValue = this.getCollectionValue(); + var change = new PlayerChangeInfo(); + var proto = AchievementComplete.newInstance(); + var rewards = new ItemParamMap(); + boolean completed = false; + + for (int achievementId : achievementIds) { + if (this.completedAchievements.containsKey(achievementId)) { + continue; + } + + var data = GameData.getDormAchievementDataTable().get(achievementId); + if (data == null || data.getCompleteCond() != 1) { + continue; + } + + int required = data.getCompleteParams() != null && data.getCompleteParams().length > 0 + ? data.getCompleteParams()[0] + : 0; + + if (collectionValue < required) { + continue; + } + + long completeTime = Nebula.getCurrentServerTime(); + this.completedAchievements.put(achievementId, completeTime); + + if (data.getTid1() > 0 && data.getQty1() > 0) { + rewards.add(data.getTid1(), data.getQty1()); + } + if (data.getTid2() > 0 && data.getQty2() > 0) { + rewards.add(data.getTid2(), data.getQty2()); + } + if (data.getTid3() > 0 && data.getQty3() > 0) { + rewards.add(data.getTid3(), data.getQty3()); + } + + var info = AchievementInfo.newInstance() + .setAchievementId(achievementId) + .setCompleteTime(completeTime); + + proto.addAchievementList(info); + completed = true; + } + + if (!completed) { + return null; + } + + this.getPlayer().getInventory().addItems(rewards, change); + proto.setChangeInfo(change.toProto()); + this.queueSave = true; + + return proto; + } + + // Social + + public boolean hasLikedFriend(int uid) { + return this.likedFriendUids.contains(uid); + } + + public synchronized void addLikedFriend(int uid) { + this.likedFriendUids.add(uid); + } + + public synchronized void addLike() { + this.likeNum++; + } + + public synchronized void recordVisit(int visitorUid) { + if (this.dailyVisitorUids.add(visitorUid)) { + this.visitTotalNum++; + this.dayVisitNum = this.dailyVisitorUids.size(); + } + } + + // Helpers for friend dorm data + + public DormManager getFriendDorm(int uid) { + var cached = Nebula.getGameContext().getPlayerModule().getPlayer(uid); + + if (cached == null) { + return null; + } + + if (cached.getDormManager() == null) { + cached.loadManagerFromDatabase(DormManager.class); + } + + return cached.getDormManager(); + } + + // Proto + + public synchronized DormInfoListResp toProto() { + var proto = DormInfoListResp.newInstance(); + + var unlocked = this.getUnlockedRoomIds(); + var sortedRooms = new ArrayList<>(unlocked); + sortedRooms.sort(Integer::compareTo); + + for (int roomId : sortedRooms) { + var room = this.getOrCreateRoom(roomId); + if (room != null) { + proto.addDormList(room.toProto()); + } + } + + var info = DormInfoData.newInstance() + .setDormName(this.dormName == null ? "" : this.dormName) + .setLikeNum(this.likeNum) + .setDayVisitNum(this.dayVisitNum) + .setVisitTotalNum(this.visitTotalNum) + .setRestNameTime(this.dormNameRestTime) + .setTaskGroup(Public.UI32V.newInstance().setValue(0).setVersion(0)); + + var rewardIds = new ArrayList<>(this.getClaimableRewardIds()); + rewardIds.sort(Integer::compareTo); + for (int rewardId : rewardIds) { + info.addRewardIds(rewardId); + } + + for (var entry : this.completedAchievements.entrySet()) { + info.addAchievementList( + AchievementInfo.newInstance() + .setAchievementId(entry.getKey()) + .setCompleteTime(entry.getValue()) + ); + } + + proto.setDormInfo(info); + + for (int layoutId : this.receivedLayoutRewardIds) { + proto.addReceivedRewardIds(layoutId); + } + + for (int roleId : this.dailyInteractionRoleIds) { + proto.addInteractionRoleIds(roleId); + } + + for (var entry : this.dailyAffinityGiftCounts.int2IntEntrySet()) { + proto.addAffinityGiftRoleInfos( + DormAffinityGiftInfo.newInstance() + .setRoleId(entry.getIntKey()) + .setSendGiftCnt(entry.getIntValue()) + ); + } + + return proto; + } + + public DormHot getDormHotProto() { + return DormHot.newInstance() + .setCollectionValue(this.getCollectionValue()); + } + + // Database + + @Override + public void save() { + Nebula.getGameDatabase().save(this); + this.queueSave = false; + } +} diff --git a/src/main/java/emu/nebula/game/dorm/DormPreset.java b/src/main/java/emu/nebula/game/dorm/DormPreset.java new file mode 100644 index 0000000..4a7d71d --- /dev/null +++ b/src/main/java/emu/nebula/game/dorm/DormPreset.java @@ -0,0 +1,49 @@ +package emu.nebula.game.dorm; + +import java.util.ArrayList; +import java.util.List; + +import dev.morphia.annotations.Entity; +import emu.nebula.proto.PublicDorm.FurniturePresetInfo; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity(useDiscriminator = false) +public class DormPreset { + private int buildId; + private int dormType; + private String buildName; + private long createTime; + private List furnitureList; + + @Deprecated // Morphia only + public DormPreset() { + + } + + public DormPreset(int buildId, int dormType, long createTime) { + this.buildId = buildId; + this.dormType = dormType; + this.createTime = createTime; + this.buildName = ""; + this.furnitureList = new ArrayList<>(); + } + + public FurniturePresetInfo toProto() { + var proto = FurniturePresetInfo.newInstance() + .setBuildId(this.buildId) + .setDormType(this.dormType) + .setBuildName(this.buildName == null ? "" : this.buildName) + .setCreateTime(this.createTime); + + if (this.furnitureList != null) { + for (var furniture : this.furnitureList) { + proto.addFurnitureList(furniture.toProto()); + } + } + + return proto; + } +} diff --git a/src/main/java/emu/nebula/game/dorm/DormRole.java b/src/main/java/emu/nebula/game/dorm/DormRole.java new file mode 100644 index 0000000..c74d09d --- /dev/null +++ b/src/main/java/emu/nebula/game/dorm/DormRole.java @@ -0,0 +1,31 @@ +package emu.nebula.game.dorm; + +import dev.morphia.annotations.Entity; +import emu.nebula.proto.PublicDorm.DormRoleInfo; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity(useDiscriminator = false) +public class DormRole { + private int roleId; + private boolean isVisit; + private boolean isInvitationRole; + + @Deprecated // Morphia only + public DormRole() { + + } + + public DormRole(int roleId) { + this.roleId = roleId; + } + + public DormRoleInfo toProto() { + return DormRoleInfo.newInstance() + .setRoleId(this.roleId) + .setIsVisit(this.isVisit) + .setIsInvitationRole(this.isInvitationRole); + } +} diff --git a/src/main/java/emu/nebula/game/dorm/DormRoleInteractionResult.java b/src/main/java/emu/nebula/game/dorm/DormRoleInteractionResult.java new file mode 100644 index 0000000..6f5673d --- /dev/null +++ b/src/main/java/emu/nebula/game/dorm/DormRoleInteractionResult.java @@ -0,0 +1,32 @@ +package emu.nebula.game.dorm; + +import java.util.List; + +import emu.nebula.game.character.GameCharacter; +import emu.nebula.game.player.PlayerChangeInfo; + +import lombok.Getter; + +/** + * The result of interacting with roles in a dorm room. + */ +@Getter +public class DormRoleInteractionResult { + // The changes of the interactions + private final PlayerChangeInfo change; + + // The interactions that were performed + private final List interactions; + + public DormRoleInteractionResult(PlayerChangeInfo change, List interactions) { + this.change = change; + this.interactions = interactions; + } + + /** + * A single role interaction. + */ + public record Interaction(GameCharacter character, int itemId) { + + } +} diff --git a/src/main/java/emu/nebula/game/dorm/DormRoomState.java b/src/main/java/emu/nebula/game/dorm/DormRoomState.java new file mode 100644 index 0000000..9fecb3c --- /dev/null +++ b/src/main/java/emu/nebula/game/dorm/DormRoomState.java @@ -0,0 +1,59 @@ +package emu.nebula.game.dorm; + +import java.util.ArrayList; +import java.util.List; + +import dev.morphia.annotations.Entity; +import emu.nebula.proto.PublicDorm.DormRoomInfo; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity(useDiscriminator = false) +public class DormRoomState { + private int dormRoomId; + private List furnitureList; + private List roleList; + private int discId; + private String roomName; + private int roomScene; + private long restNameTime; + + @Deprecated // Morphia only + public DormRoomState() { + + } + + public DormRoomState(int dormRoomId) { + this.dormRoomId = dormRoomId; + this.furnitureList = new ArrayList<>(); + this.roleList = new ArrayList<>(); + this.roomName = ""; + } + + // Proto + + public DormRoomInfo toProto() { + var proto = DormRoomInfo.newInstance() + .setDormRoomId(this.getDormRoomId()) + .setDiscId(this.getDiscId()) + .setRoomName(this.getRoomName() == null ? "" : this.getRoomName()) + .setRoomScene(this.getRoomScene()) + .setRestNameTime(this.getRestNameTime()); + + if (this.getFurnitureList() != null) { + for (var furniture : this.getFurnitureList()) { + proto.addFurnitureList(furniture.toProto()); + } + } + + if (this.getRoleList() != null) { + for (var role : this.getRoleList()) { + proto.addRoleList(role.toProto()); + } + } + + return proto; + } +} diff --git a/src/main/java/emu/nebula/game/inventory/Inventory.java b/src/main/java/emu/nebula/game/inventory/Inventory.java index 92923ef..c63d8e6 100644 --- a/src/main/java/emu/nebula/game/inventory/Inventory.java +++ b/src/main/java/emu/nebula/game/inventory/Inventory.java @@ -459,6 +459,43 @@ public class Inventory extends PlayerManager implements GameDatabaseObject { change.add(proto); } } + case DormFurniture -> { + // Do not allow removing dorm furniture + if (amount <= 0) { + break; + } + + // Dorm furniture sets/kits contain their furniture in UseArgs. + // Unpack them instead of adding the set itself to the bag. + if (data.getUseParams() != null && !data.getUseParams().isEmpty()) { + // Add items from the set + for (var entry : data.getUseParams()) { + this.addItem(entry.getIntKey(), entry.getIntValue() * amount, change); + } + + // End early + break; + } + + if (data.getItemSubType() == ItemSubType.DormRoom) { + // Unlock dorm rooms when the room item is acquired. + this.getPlayer().getDormManager().unlockRoom(id, change); + } else if (data.getItemSubType() == ItemSubType.DormFurniture) { + // Add furniture to the dorm furniture collection + var dormManager = this.getPlayer().getDormManager(); + boolean isFirst = dormManager.getFurnitureCount(id) <= 0; + + dormManager.addFurniture(id, amount); + + // Add to change info (to notify the game client) + var proto = DormItem.newInstance() + .setTid(id) + .setQty(amount) + .setIsFirst(isFirst); + + change.add(proto); + } + } case Disc -> { // Cannot remove discs if (amount <= 0) break; diff --git a/src/main/java/emu/nebula/game/player/Player.java b/src/main/java/emu/nebula/game/player/Player.java index 9cd4440..0b3aa03 100644 --- a/src/main/java/emu/nebula/game/player/Player.java +++ b/src/main/java/emu/nebula/game/player/Player.java @@ -17,6 +17,7 @@ import emu.nebula.game.agent.AgentManager; import emu.nebula.game.battlepass.BattlePassManager; import emu.nebula.game.character.CharacterStorage; import emu.nebula.game.dating.DatingManager; +import emu.nebula.game.dorm.DormManager; import emu.nebula.game.formation.FormationManager; import emu.nebula.game.friends.FriendList; import emu.nebula.game.gacha.GachaManager; @@ -113,6 +114,7 @@ public class Player implements GameDatabaseObject { private transient AgentManager agentManager; private transient TraceHuntManager traceHuntManager; private transient ActivityManager activityManager; + private transient DormManager dormManager; // Extra private transient Stack nextPackages; @@ -733,6 +735,9 @@ public class Player implements GameDatabaseObject { // Reset daily trace hunt items this.getTraceHuntManager().resetDailyQuota(); + // Reset dorm dailies + this.getDormManager().resetDailies(); + // Check to reset weeklies if (resetWeekly) { // Add weekly boss entry item @@ -780,7 +785,7 @@ public class Player implements GameDatabaseObject { // Login - private T loadManagerFromDatabase(Class cls) { + public T loadManagerFromDatabase(Class cls) { var manager = Nebula.getGameDatabase().getObjectByField(cls, "_id", this.getUid()); if (manager != null) { @@ -824,6 +829,7 @@ public class Player implements GameDatabaseObject { this.agentManager = this.loadManagerFromDatabase(AgentManager.class); this.traceHuntManager = this.loadManagerFromDatabase(TraceHuntManager.class); this.activityManager = this.loadManagerFromDatabase(ActivityManager.class); + this.dormManager = this.loadManagerFromDatabase(DormManager.class); // Database fixes if (this.showChars == null) { @@ -1105,6 +1111,11 @@ public class Player implements GameDatabaseObject { if (this.getAchievementManager().isQueueSave()) { this.getAchievementManager().save(); } + + // Check if we need save dorm data + if (this.getDormManager().isQueueSave()) { + this.getDormManager().save(); + } } /** @@ -1146,7 +1157,8 @@ public class Player implements GameDatabaseObject { .setDailyShopRewardStatus(this.getQuestManager().hasDailyShopReward()) .setDailyMallRewardStatus(this.getQuestManager().hasDailyMallReward()) .setMusicInfo(this.getMusic()) - .setAchievements(new byte[64]); + .setAchievements(new byte[64]) + .setDorm(this.getDormManager().getDormHotProto()); var acc = proto.getMutableAcc() .setNickName(this.getName()) @@ -1262,6 +1274,7 @@ public class Player implements GameDatabaseObject { state.getMutableScoreBoss(); state.getMutableCharAffinityRewards(); state.getMutableTraceHunt().setBossRewardCanReceive(this.getTraceHuntManager().isHuntComplete()); + state.setDormStateFlag(this.getDormManager().getState()); // Star tower potential book state var book = state.getMutableStarTowerBook(); diff --git a/src/main/java/emu/nebula/game/player/PlayerModule.java b/src/main/java/emu/nebula/game/player/PlayerModule.java index 681ce3b..fbc541b 100644 --- a/src/main/java/emu/nebula/game/player/PlayerModule.java +++ b/src/main/java/emu/nebula/game/player/PlayerModule.java @@ -16,6 +16,7 @@ import emu.nebula.game.agent.AgentManager; import emu.nebula.game.battlepass.BattlePass; import emu.nebula.game.character.GameCharacter; import emu.nebula.game.character.GameDisc; +import emu.nebula.game.dorm.DormManager; import emu.nebula.game.formation.FormationManager; import emu.nebula.game.friends.Friendship; import emu.nebula.game.gacha.GachaManager; @@ -187,6 +188,7 @@ public class PlayerModule extends GameContextModule { datastore.getCollection(TraceHuntManager.class).deleteOne(idFilter); datastore.getCollection(AchievementManager.class).deleteOne(idFilter); datastore.getCollection(ActivityManager.class).deleteOne(idFilter); + datastore.getCollection(DormManager.class).deleteOne(idFilter); datastore.getCollection(BattlePass.class).deleteOne(idFilter); datastore.getCollection(ScoreBossRankEntry.class).deleteOne(idFilter); diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormAchievementRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormAchievementRewardReceiveReq.java new file mode 100644 index 0000000..8c7cac3 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormAchievementRewardReceiveReq.java @@ -0,0 +1,28 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormAchievementRewardReceive.DormAchievementRewardReceiveReq; + +@HandlerId(NetMsgId.dorm_achievement_reward_receive_req) +public class HandlerDormAchievementRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormAchievementRewardReceiveReq.parseFrom(message); + + // Receive achievements + var rsp = session.getPlayer().getDormManager().receiveAchievements(req.getAchievementIds().toArray()); + + if (rsp == null) { + return session.encodeMsg(NetMsgId.dorm_achievement_reward_receive_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_achievement_reward_receive_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormAffinityGiftSendReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormAffinityGiftSendReq.java new file mode 100644 index 0000000..8aca9c9 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormAffinityGiftSendReq.java @@ -0,0 +1,47 @@ +package emu.nebula.server.handlers; + +import emu.nebula.game.inventory.ItemParamMap; +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormAffinityGiftSend.DormAffinityGiftSendReq; +import emu.nebula.proto.DormAffinityGiftSend.DormAffinityGiftSendResp; + +@HandlerId(NetMsgId.dorm_affinity_gift_send_req) +public class HandlerDormAffinityGiftSendReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormAffinityGiftSendReq.parseFrom(message); + + var player = session.getPlayer(); + var manager = player.getDormManager(); + + // Get character + var character = player.getCharacters().getCharacterById(req.getCharId()); + if (character == null) { + return session.encodeMsg(NetMsgId.dorm_affinity_gift_send_failed_ack); + } + + // Parse gift items + var items = ItemParamMap.fromTemplates(req.getItems()); + + // Send gifts + var change = manager.sendAffinityGift(character, req.getDormRoomId(), items); + if (change == null) { + return session.encodeMsg(NetMsgId.dorm_affinity_gift_send_failed_ack); + } + + // Build response + var rsp = DormAffinityGiftSendResp.newInstance() + .setInfo(character.getAffinityProto()) + .setChange(change.toProto()) + .setSendGiftCnt(manager.getDailyGiftCount(character.getCharId())); + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_affinity_gift_send_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormBlocksConvertReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormBlocksConvertReq.java new file mode 100644 index 0000000..ba0fc09 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormBlocksConvertReq.java @@ -0,0 +1,27 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.UI32; + +@HandlerId(NetMsgId.dorm_blocks_convert_req) +public class HandlerDormBlocksConvertReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = UI32.parseFrom(message); + + // Convert blocks + var change = session.getPlayer().getDormManager().convertBlocks(req.getValue()); + if (change == null) { + return session.encodeMsg(NetMsgId.dorm_blocks_convert_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_blocks_convert_succeed_ack, change.toProto()); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormFriendLikeReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormFriendLikeReq.java new file mode 100644 index 0000000..ec0a57e --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormFriendLikeReq.java @@ -0,0 +1,52 @@ +package emu.nebula.server.handlers; + +import emu.nebula.GameConstants; +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormFriendLike.DormFriendLikeReq; + +@HandlerId(NetMsgId.dorm_friend_like_req) +public class HandlerDormFriendLikeReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormFriendLikeReq.parseFrom(message); + + long friendId = req.getFriendId(); + + if (friendId <= 0 || friendId > Integer.MAX_VALUE) { + return session.encodeMsg(NetMsgId.dorm_friend_like_failed_ack); + } + + int uid = (int) friendId; + var player = session.getPlayer(); + var manager = player.getDormManager(); + + // Friends only + if (!player.getFriendList().getFriends().containsKey(uid)) { + return session.encodeMsg(NetMsgId.dorm_friend_like_failed_ack); + } + + // Daily limit and duplicate check + if (manager.hasLikedFriend(uid) || manager.getLikedFriendUids().size() >= GameConstants.DORM_LIKE_LIMIT) { + return session.encodeMsg(NetMsgId.dorm_friend_like_failed_ack); + } + + manager.addLikedFriend(uid); + + // Increment the friend's like counter + var dorm = session.getPlayer().getDormManager().getFriendDorm(uid); + + if (dorm != null) { + dorm.addLike(); + dorm.save(); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_friend_like_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormFriendListReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormFriendListReq.java new file mode 100644 index 0000000..7d51693 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormFriendListReq.java @@ -0,0 +1,42 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormFriendList.DormFriendListResp; +import emu.nebula.proto.PublicDorm.DormFriendDetail; + +@HandlerId(NetMsgId.dorm_friend_list_req) +public class HandlerDormFriendListReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + var player = session.getPlayer(); + var manager = player.getDormManager(); + + var rsp = DormFriendListResp.newInstance(); + + for (var friendship : player.getFriendList().getFriends().values()) { + int uid = friendship.getFriendUid(); + + var dorm = session.getPlayer().getDormManager().getFriendDorm(uid); + + var detail = DormFriendDetail.newInstance() + .setUid(uid) + .setLikeNum(dorm == null ? 0 : dorm.getLikeNum()) + .setCollectionValue(dorm == null ? 0 : dorm.getCollectionValue()) + .setIsLike(manager.hasLikedFriend(uid)); + + if (dorm != null && dorm.getDormName() != null) { + detail.setDormName(dorm.getDormName()); + } + + rsp.addFriends(detail); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_friend_list_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormFriendVisitReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormFriendVisitReq.java new file mode 100644 index 0000000..a1bee1f --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormFriendVisitReq.java @@ -0,0 +1,70 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormFriendVisit.DormFriendVisitReq; +import emu.nebula.proto.DormFriendVisit.DormFriendVisitResp; +import emu.nebula.proto.PublicDorm.DormFriendInfo; + +@HandlerId(NetMsgId.dorm_friend_visit_req) +public class HandlerDormFriendVisitReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormFriendVisitReq.parseFrom(message); + + // Validate friend + long friendId = req.getFriendId(); + + if (friendId <= 0 || friendId > Integer.MAX_VALUE) { + return session.encodeMsg(NetMsgId.dorm_friend_visit_failed_ack); + } + + int uid = (int) friendId; + + if (!session.getPlayer().getFriendList().getFriends().containsKey(uid)) { + return session.encodeMsg(NetMsgId.dorm_friend_visit_failed_ack); + } + + // Load friend dorm data + var dorm = session.getPlayer().getDormManager().getFriendDorm(uid); + + if (dorm == null) { + return session.encodeMsg(NetMsgId.dorm_friend_visit_failed_ack); + } + + // Build response + var rsp = DormFriendVisitResp.newInstance(); + + var unlockedRoomIds = dorm.getUnlockedRoomIds(); + var sortedRoomIds = new java.util.ArrayList<>(unlockedRoomIds); + sortedRoomIds.sort(Integer::compareTo); + + for (int roomId : sortedRoomIds) { + var room = dorm.getOrCreateRoom(roomId); + + if (room != null) { + rsp.addDormList(room.toProto()); + } + } + + var dormInfo = DormFriendInfo.newInstance(); + + if (dorm.getDormName() != null) { + dormInfo.setDormName(dorm.getDormName()); + } + + rsp.setDormInfo(dormInfo); + + // Record the visit on the friend's dorm + dorm.recordVisit(session.getPlayer().getUid()); + dorm.save(); + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_friend_visit_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormFurnitureListReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormFurnitureListReq.java new file mode 100644 index 0000000..2d3fef8 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormFurnitureListReq.java @@ -0,0 +1,42 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormFurnitureList.DormBagListResp; +import emu.nebula.proto.Public.ItemTpl; +import emu.nebula.proto.PublicDorm.DormBagInfo; + +@HandlerId(NetMsgId.dorm_bag_list_req) +public class HandlerDormFurnitureListReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get dorm manager + var dorm = session.getPlayer().getDormManager(); + + // Build response + var rsp = DormBagListResp.newInstance(); + + // Encode dorm furniture inventory + for (var entry : dorm.getFurnitureCollection().int2IntEntrySet()) { + if (entry.getIntValue() <= 0) { + continue; + } + + var item = ItemTpl.newInstance() + .setTid(entry.getIntKey()) + .setQty(entry.getIntValue()); + + var info = DormBagInfo.newInstance() + .setItemTplInfo(item); + + rsp.addDormBagList(info); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_bag_list_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormFurniturePresetDeleteReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormFurniturePresetDeleteReq.java new file mode 100644 index 0000000..8c42cf2 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormFurniturePresetDeleteReq.java @@ -0,0 +1,34 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.UI32; + +@HandlerId(NetMsgId.dorm_furniture_preset_delete_req) +public class HandlerDormFurniturePresetDeleteReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // The client does not send a request body for this packet in the current client version. + // If a body is present, treat field 1 as the preset/build id. + int buildId = 0; + + if (message != null && message.length > 0) { + try { + buildId = UI32.parseFrom(message).getValue(); + } catch (Exception ignored) { + buildId = 0; + } + } + + if (buildId > 0) { + session.getPlayer().getDormManager().deletePreset(buildId); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_furniture_preset_delete_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormFurniturePresetListReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormFurniturePresetListReq.java new file mode 100644 index 0000000..8cea7a6 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormFurniturePresetListReq.java @@ -0,0 +1,41 @@ +package emu.nebula.server.handlers; + +import java.util.ArrayList; +import java.util.Comparator; + +import emu.nebula.GameConstants; +import emu.nebula.game.dorm.DormPreset; +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormFurniturePreset.DormFurniturePresetListResp; + +@HandlerId(NetMsgId.dorm_furniture_preset_list_req) +public class HandlerDormFurniturePresetListReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get player dorm + var dorm = session.getPlayer().getDormManager(); + + // Build response + var rsp = DormFurniturePresetListResp.newInstance() + .setNum(GameConstants.DORM_DEFAULT_PRESET_NUM); + + // Get preset list + var list = new ArrayList<>(dorm.getPresets().values()); + + // Sort preset list + list.sort(Comparator.comparingInt(DormPreset::getBuildId)); + + // Encode presets to proto + for (var preset : list) { + rsp.addPresetList(preset.toProto()); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_furniture_preset_list_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormFurnitureSaveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormFurnitureSaveReq.java new file mode 100644 index 0000000..356b21f --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormFurnitureSaveReq.java @@ -0,0 +1,47 @@ +package emu.nebula.server.handlers; + +import java.util.ArrayList; + +import emu.nebula.game.dorm.DormFurniture; +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormFurnitureSave.DormFurnitureSaveReq; +import emu.nebula.proto.Public.I64; + +@HandlerId(NetMsgId.dorm_furniture_save_req) +public class HandlerDormFurnitureSaveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormFurnitureSaveReq.parseFrom(message); + + // Build furniture list + var furnitureList = new ArrayList(); + + for (var info : req.getFurniturePosInfoList()) { + furnitureList.add(new DormFurniture(info)); + } + + // Save room or preset + var createTime = session.getPlayer().getDormManager().saveFurnitureLayout( + req.getDormRoomId(), + furnitureList, + req.getBuildId() + ); + + if (createTime == null) { + return session.encodeMsg(NetMsgId.dorm_furniture_save_failed_ack); + } + + // The client reads the preset creation time from Value + var rsp = I64.newInstance() + .setValue(createTime); + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_furniture_save_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormGetChangeDataReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormGetChangeDataReq.java new file mode 100644 index 0000000..57847f4 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormGetChangeDataReq.java @@ -0,0 +1,32 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormGetChangeData.DormChangeDataResp; +import emu.nebula.proto.PublicDorm.DormChangeDataInfo; + +@HandlerId(NetMsgId.dorm_get_change_data_req) +public class HandlerDormGetChangeDataReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Get player dorm + var dorm = session.getPlayer().getDormManager(); + + // Build response + var info = DormChangeDataInfo.newInstance() + .setDormName(dorm.getDormName()) + .setLikeNum(dorm.getLikeNum()) + .setDayVisitNum(dorm.getDayVisitNum()) + .setVisitTotalNum(dorm.getVisitTotalNum()); + + var rsp = DormChangeDataResp.newInstance() + .setDormChangeData(info); + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_get_change_data_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormInfoListReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormInfoListReq.java new file mode 100644 index 0000000..ccd3c27 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormInfoListReq.java @@ -0,0 +1,20 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; + +@HandlerId(NetMsgId.dorm_info_list_req) +public class HandlerDormInfoListReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Build response + var rsp = session.getPlayer().getDormManager().toProto(); + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_info_list_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormInfoUpdateNameReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormInfoUpdateNameReq.java new file mode 100644 index 0000000..a2370cd --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormInfoUpdateNameReq.java @@ -0,0 +1,26 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.StringV; + +@HandlerId(NetMsgId.dorm_info_update_name_req) +public class HandlerDormInfoUpdateNameReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = StringV.parseFrom(message); + + // Version = 0 means the dorm name, otherwise the room id + if (!session.getPlayer().getDormManager().renameRoom(req.getVersion(), req.getValue())) { + return session.encodeMsg(NetMsgId.dorm_info_update_name_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_info_update_name_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormInvitationRoleReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormInvitationRoleReq.java new file mode 100644 index 0000000..e2e138b --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormInvitationRoleReq.java @@ -0,0 +1,35 @@ +package emu.nebula.server.handlers; + +import java.util.ArrayList; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormInvitationRole.DormInvitationRoleReq; +import emu.nebula.proto.Public; + +@HandlerId(NetMsgId.dorm_invitation_role_req) +public class HandlerDormInvitationRoleReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormInvitationRoleReq.parseFrom(message); + + // Get role ids + var roleIds = new ArrayList(); + for (var info : req.getInvitationRoleList()) { + roleIds.add(info.getRoleId()); + } + + // Update invitation roles + if (!session.getPlayer().getDormManager().updateInvitationRoles(req.getDormRoomId(), roleIds)) { + return session.encodeMsg(NetMsgId.dorm_invitation_role_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_invitation_role_succeed_ack, Public.Nil.newInstance()); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormLayoutRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormLayoutRewardReceiveReq.java new file mode 100644 index 0000000..b6091ca --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormLayoutRewardReceiveReq.java @@ -0,0 +1,28 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormLayoutRewardReceive.DormLayoutRewardReceiveReq; + +@HandlerId(NetMsgId.dorm_layout_reward_receive_req) +public class HandlerDormLayoutRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormLayoutRewardReceiveReq.parseFrom(message); + + // Receive layout rewards + var change = session.getPlayer().getDormManager().receiveLayoutRewards(req.getLayoutIds().toArray()); + + if (change == null) { + return session.encodeMsg(NetMsgId.dorm_layout_reward_receive_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_layout_reward_receive_succeed_ack, change.toProto()); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormLearnReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormLearnReq.java new file mode 100644 index 0000000..50c44d8 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormLearnReq.java @@ -0,0 +1,17 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; + +@HandlerId(NetMsgId.dorm_learn_req) +public class HandlerDormLearnReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Dorm quests are not implemented yet, so acknowledge the request without changing state. + return session.encodeMsg(NetMsgId.dorm_learn_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormRewardReceiveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormRewardReceiveReq.java new file mode 100644 index 0000000..755d2ab --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormRewardReceiveReq.java @@ -0,0 +1,28 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormRewardReceive.DormRewardReceiveReq; + +@HandlerId(NetMsgId.dorm_reward_receive_req) +public class HandlerDormRewardReceiveReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormRewardReceiveReq.parseFrom(message); + + // Receive rewards + var change = session.getPlayer().getDormManager().receiveRewards(req.getRewardIds().toArray()); + + if (change == null) { + return session.encodeMsg(NetMsgId.dorm_reward_receive_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_reward_receive_succeed_ack, change.toProto()); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormRoleInteractionReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormRoleInteractionReq.java new file mode 100644 index 0000000..17841b4 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormRoleInteractionReq.java @@ -0,0 +1,49 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormRoleInteraction.DormRoleInteractionReq; +import emu.nebula.proto.DormRoleInteraction.DormRoleInteractionResp; +import emu.nebula.proto.Public; +import emu.nebula.proto.PublicDorm.AffinityData; + +@HandlerId(NetMsgId.dorm_role_interaction_req) +public class HandlerDormRoleInteractionReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormRoleInteractionReq.parseFrom(message); + + // Interact with roles + var result = session.getPlayer().getDormManager().interactRoles(req.getRoleInteractions()); + + if (result == null) { + return session.encodeMsg(NetMsgId.dorm_role_interaction_failed_ack); + } + + // Build response + var rsp = DormRoleInteractionResp.newInstance() + .setChange(result.getChange().toProto()); + + for (var interaction : result.getInteractions()) { + int itemId = interaction.itemId(); + + var itemInfo = Public.ItemTpl.newInstance() + .setTid(Math.max(itemId, 0)) + .setQty(itemId > 0 ? 1 : 0); + + rsp.addInfoList( + AffinityData.newInstance() + .setAffinityInfo(interaction.character().getAffinityProto()) + .setItemInfo(itemInfo) + ); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_role_interaction_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormRoomBgmReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormRoomBgmReq.java new file mode 100644 index 0000000..8e983c4 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormRoomBgmReq.java @@ -0,0 +1,34 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.DormRoomBgm.DormRoomBGMReq; + +@HandlerId(NetMsgId.dorm_room_bgm_req) +public class HandlerDormRoomBgmReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = DormRoomBGMReq.parseFrom(message); + + var manager = session.getPlayer().getDormManager(); + + // Validate the whole request before applying it + for (var bgm : req.getBGMList()) { + if (!manager.isRoomUnlocked(bgm.getDormRoomId()) || bgm.getDiscId() < 0) { + return session.encodeMsg(NetMsgId.dorm_room_bgm_failed_ack); + } + } + + for (var bgm : req.getBGMList()) { + manager.setRoomBgm(bgm.getDormRoomId(), bgm.getDiscId()); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_room_bgm_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormSetRoomSceneReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormSetRoomSceneReq.java new file mode 100644 index 0000000..0342617 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormSetRoomSceneReq.java @@ -0,0 +1,26 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.UI32V; + +@HandlerId(NetMsgId.dorm_set_room_scene_req) +public class HandlerDormSetRoomSceneReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = UI32V.parseFrom(message); + + // Version = room id, Value = scene item id (0 clears the scene) + if (!session.getPlayer().getDormManager().setRoomScene(req.getVersion(), req.getValue())) { + return session.encodeMsg(NetMsgId.dorm_set_room_scene_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_set_room_scene_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerDormUpdatePresetNameReq.java b/src/main/java/emu/nebula/server/handlers/HandlerDormUpdatePresetNameReq.java new file mode 100644 index 0000000..d4aacf8 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerDormUpdatePresetNameReq.java @@ -0,0 +1,26 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.GameSession; +import emu.nebula.net.HandlerId; +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.Public.StringV; + +@HandlerId(NetMsgId.dorm_update_preset_name_req) +public class HandlerDormUpdatePresetNameReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = StringV.parseFrom(message); + + // Version = preset/build id, Value = new name + if (!session.getPlayer().getDormManager().renamePreset(req.getVersion(), req.getValue())) { + return session.encodeMsg(NetMsgId.dorm_update_preset_name_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.dorm_update_preset_name_succeed_ack); + } + +} diff --git a/src/main/resources/versions.json b/src/main/resources/versions.json index 4396446..6c2896a 100644 --- a/src/main/resources/versions.json +++ b/src/main/resources/versions.json @@ -1,7 +1,7 @@ { - "global": 141, - "kr": 147, - "jp": 143, - "tw": 149, - "cn": 140 + "global": 143, + "kr": 150, + "jp": 145, + "tw": 151, + "cn": 142 } \ No newline at end of file