diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index a2d23a26f..3238de22e 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -104,7 +104,7 @@ public class Player { private Position rotation; private PlayerBirthday birthday; private PlayerCodex codex; - + @Getter private PlayerOpenStateManager openStateManager; private Map properties; private Set nameCardList; private Set flyCloakList; @@ -187,7 +187,7 @@ public class Player { @Transient private FurnitureManager furnitureManager; @Transient private BattlePassManager battlePassManager; @Transient private CookingManager cookingManager; - // @Transient private + // @Transient private @Getter @Transient private ActivityManager activityManager; @Transient private CollectionManager collectionManager; @@ -248,7 +248,7 @@ public class Player { this.rewardedLevels = new HashSet<>(); this.moonCardGetTimes = new HashSet<>(); this.codex = new PlayerCodex(this); - + this.openStateManager = new PlayerOpenStateManager(this); this.shopLimit = new ArrayList<>(); this.expeditionInfo = new HashMap<>(); this.messageHandler = null; @@ -468,7 +468,7 @@ public class Player { public int getWorldLevel() { return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL); } - + public boolean setWorldLevel(int level) { if (this.setProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL, level)) { if (this.world.getHost() == this) // Don't update World's WL if we are in someone else's world @@ -1445,6 +1445,7 @@ public class Player { @PostLoad private void onLoad() { this.getCodex().setPlayer(this); + this.getOpenStateManager().setPlayer(this); this.getTeamManager().setPlayer(this); this.getTowerManager().setPlayer(this); } @@ -1465,6 +1466,9 @@ public class Player { if (this.getCodex() == null) { this.codex = new PlayerCodex(this); } + if (this.getOpenStateManager() == null) { + this.openStateManager = new PlayerOpenStateManager(this); + } if (this.getProfile().getUid() == 0) { this.getProfile().syncWithCharacter(this); } @@ -1525,6 +1529,7 @@ public class Player { this.forgingManager.sendForgeDataNotify(); this.resinManager.onPlayerLogin(); this.cookingManager.sendCookDataNofity(); + this.openStateManager.onPlayerLogin(); getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. // Battle Pass trigger @@ -1539,7 +1544,7 @@ public class Player { session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world session.send(new PacketPlayerLevelRewardUpdateNotify(rewardedLevels)); - session.send(new PacketOpenStateUpdateNotify()); + // First notify packets sent this.setHasSentAvatarDataNotify(true); diff --git a/src/main/java/emu/grasscutter/game/player/PlayerOpenStateManager.java b/src/main/java/emu/grasscutter/game/player/PlayerOpenStateManager.java new file mode 100644 index 000000000..3bf1bf8cc --- /dev/null +++ b/src/main/java/emu/grasscutter/game/player/PlayerOpenStateManager.java @@ -0,0 +1,246 @@ +package emu.grasscutter.game.player; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Transient; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.OpenState; +import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify; +import emu.grasscutter.server.packet.send.PacketOpenStateUpdateNotify; +import lombok.Getter; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static emu.grasscutter.game.props.OpenState.*; + +@Entity +public class PlayerOpenStateManager { + + @Transient private Player player; + @Getter private Map openStateMap; + /* + //DO NOT MODIFY. Based on conversation of official server and client, game version 2.7 + private static Set newPlayerOpenStates = Set.of(OPEN_STATE_DERIVATIVE_MALL,OPEN_STATE_PHOTOGRAPH,OPEN_STATE_BATTLE_PASS,OPEN_STATE_SHOP_TYPE_GENESISCRYSTAL,OPEN_STATE_SHOP_TYPE_RECOMMANDED, + OPEN_STATE_SHOP_TYPE_GIFTPACKAGE,OPEN_STATE_GUIDE_RELIC_PROM,OPEN_STATE_GUIDE_TALENT,OPEN_STATE_SHOP_TYPE_BLACKSMITH,OPEN_STATE_SHOP_TYPE_PAIMON,OPEN_STATE_WEAPON_AWAKEN, + OPEN_STATE_WEAPON_PROMOTE,OPEN_STATE_AVATAR_PROMOTE,OPEN_STATE_AVATAR_TALENT,OPEN_STATE_WEAPON_UPGRADE,OPEN_STATE_RESIN,OPEN_STATE_RELIQUARY_UPGRADE, + OPEN_STATE_SHOP_TYPE_VIRTUAL_SHOP,OPEN_STATE_RELIQUARY_PROMOTE); + */ + //For development. Remove entry when properly implemented + private static Set devOpenStates = Set.of( + OPEN_STATE_PAIMON, + OPEN_STATE_PAIMON_NAVIGATION, + OPEN_STATE_AVATAR_PROMOTE, + OPEN_STATE_AVATAR_TALENT, + OPEN_STATE_WEAPON_PROMOTE, + OPEN_STATE_WEAPON_AWAKEN, + OPEN_STATE_QUEST_REMIND, + OPEN_STATE_GAME_GUIDE, + OPEN_STATE_COOK, + OPEN_STATE_WEAPON_UPGRADE, + OPEN_STATE_RELIQUARY_UPGRADE, + OPEN_STATE_RELIQUARY_PROMOTE, + OPEN_STATE_WEAPON_PROMOTE_GUIDE, + OPEN_STATE_WEAPON_CHANGE_GUIDE, + OPEN_STATE_PLAYER_LVUP_GUIDE, + OPEN_STATE_FRESHMAN_GUIDE, + OPEN_STATE_SKIP_FRESHMAN_GUIDE, + OPEN_STATE_GUIDE_MOVE_CAMERA, + OPEN_STATE_GUIDE_SCALE_CAMERA, + OPEN_STATE_GUIDE_KEYBOARD, + OPEN_STATE_GUIDE_MOVE, + OPEN_STATE_GUIDE_JUMP, + OPEN_STATE_GUIDE_SPRINT, + OPEN_STATE_GUIDE_MAP, + OPEN_STATE_GUIDE_ATTACK, + OPEN_STATE_GUIDE_FLY, + OPEN_STATE_GUIDE_TALENT, + OPEN_STATE_GUIDE_RELIC, + OPEN_STATE_GUIDE_RELIC_PROM, + OPEN_STATE_COMBINE, + OPEN_STATE_GACHA, + OPEN_STATE_GUIDE_GACHA, + OPEN_STATE_GUIDE_TEAM, + OPEN_STATE_GUIDE_PROUD, + OPEN_STATE_GUIDE_AVATAR_PROMOTE, + OPEN_STATE_GUIDE_ADVENTURE_CARD, + OPEN_STATE_FORGE, + OPEN_STATE_GUIDE_BAG, + OPEN_STATE_EXPEDITION, + OPEN_STATE_GUIDE_ADVENTURE_DAILYTASK, + OPEN_STATE_GUIDE_ADVENTURE_DUNGEON, + OPEN_STATE_TOWER, + OPEN_STATE_WORLD_STAMINA, + OPEN_STATE_TOWER_FIRST_ENTER, + OPEN_STATE_RESIN, + OPEN_STATE_LIMIT_REGION_FRESHMEAT, + OPEN_STATE_LIMIT_REGION_GLOBAL, + OPEN_STATE_MULTIPLAYER, + OPEN_STATE_GUIDE_MOUSEPC, + OPEN_STATE_GUIDE_MULTIPLAYER, + OPEN_STATE_GUIDE_DUNGEONREWARD, + OPEN_STATE_GUIDE_BLOSSOM, + OPEN_STATE_AVATAR_FASHION, + OPEN_STATE_PHOTOGRAPH, + OPEN_STATE_GUIDE_KSLQUEST, + OPEN_STATE_PERSONAL_LINE, + OPEN_STATE_GUIDE_PERSONAL_LINE, + OPEN_STATE_GUIDE_APPEARANCE, + OPEN_STATE_GUIDE_PROCESS, + OPEN_STATE_GUIDE_PERSONAL_LINE_KEY, + OPEN_STATE_GUIDE_WIDGET, + OPEN_STATE_GUIDE_ACTIVITY_SKILL_ASTER, + OPEN_STATE_GUIDE_COLDCLIMATE, + OPEN_STATE_DERIVATIVE_MALL, + OPEN_STATE_GUIDE_EXITMULTIPLAYER, + OPEN_STATE_GUIDE_THEATREMACHANICUS_BUILD, + OPEN_STATE_GUIDE_THEATREMACHANICUS_REBUILD, + OPEN_STATE_GUIDE_THEATREMACHANICUS_CARD, + OPEN_STATE_GUIDE_THEATREMACHANICUS_MONSTER, + OPEN_STATE_GUIDE_THEATREMACHANICUS_MISSION_CHECK, + OPEN_STATE_GUIDE_THEATREMACHANICUS_BUILD_SELECT, + OPEN_STATE_GUIDE_THEATREMACHANICUS_CHALLENGE_START, + OPEN_STATE_GUIDE_CONVERT, + OPEN_STATE_GUIDE_THEATREMACHANICUS_MULTIPLAYER, + OPEN_STATE_GUIDE_COOP_TASK, + OPEN_STATE_GUIDE_HOMEWORLD_ADEPTIABODE, + OPEN_STATE_GUIDE_HOMEWORLD_DEPLOY, + OPEN_STATE_GUIDE_CHANNELLERSLAB_EQUIP, + OPEN_STATE_GUIDE_CHANNELLERSLAB_MP_SOLUTION, + OPEN_STATE_GUIDE_CHANNELLERSLAB_POWER, + OPEN_STATE_GUIDE_HIDEANDSEEK_SKILL, + OPEN_STATE_GUIDE_HOMEWORLD_MAPLIST, + OPEN_STATE_GUIDE_RELICRESOLVE, + OPEN_STATE_GUIDE_GGUIDE, + OPEN_STATE_GUIDE_GGUIDE_HINT, + OPEN_STATE_GUIDE_RIGHT_TEAM, // mobile phone only! + OPEN_STATE_CITY_REPUATION_MENGDE, + OPEN_STATE_CITY_REPUATION_LIYUE, + OPEN_STATE_CITY_REPUATION_UI_HINT, + OPEN_STATE_CITY_REPUATION_INAZUMA, + OPEN_STATE_SHOP_TYPE_MALL, + OPEN_STATE_SHOP_TYPE_RECOMMANDED, + OPEN_STATE_SHOP_TYPE_GENESISCRYSTAL, + OPEN_STATE_SHOP_TYPE_GIFTPACKAGE, + OPEN_STATE_SHOP_TYPE_PAIMON, + OPEN_STATE_SHOP_TYPE_CITY, + OPEN_STATE_SHOP_TYPE_BLACKSMITH, + OPEN_STATE_SHOP_TYPE_GROCERY, + OPEN_STATE_SHOP_TYPE_FOOD, + OPEN_STATE_SHOP_TYPE_SEA_LAMP, + OPEN_STATE_SHOP_TYPE_VIRTUAL_SHOP, + OPEN_STATE_SHOP_TYPE_LIYUE_GROCERY, + OPEN_STATE_SHOP_TYPE_LIYUE_SOUVENIR, + OPEN_STATE_SHOP_TYPE_LIYUE_RESTAURANT, + OPEN_STATE_SHOP_TYPE_INAZUMA_SOUVENIR, + OPEN_STATE_SHOP_TYPE_NPC_TOMOKI, + OPEN_ADVENTURE_MANUAL, + OPEN_ADVENTURE_MANUAL_CITY_MENGDE, + OPEN_ADVENTURE_MANUAL_CITY_LIYUE, + OPEN_ADVENTURE_MANUAL_MONSTER, + OPEN_ADVENTURE_MANUAL_BOSS_DUNGEON, + OPEN_STATE_ACTIVITY_SEALAMP, + OPEN_STATE_ACTIVITY_SEALAMP_TAB2, + OPEN_STATE_ACTIVITY_SEALAMP_TAB3, + OPEN_STATE_BATTLE_PASS, + OPEN_STATE_BATTLE_PASS_ENTRY, + OPEN_STATE_ACTIVITY_CRUCIBLE, + OPEN_STATE_ACTIVITY_NEWBEEBOUNS_OPEN, + OPEN_STATE_ACTIVITY_NEWBEEBOUNS_CLOSE, + OPEN_STATE_ACTIVITY_ENTRY_OPEN, + OPEN_STATE_MENGDE_INFUSEDCRYSTAL, + OPEN_STATE_LIYUE_INFUSEDCRYSTAL, + OPEN_STATE_SNOW_MOUNTAIN_ELDER_TREE, + OPEN_STATE_MIRACLE_RING, + OPEN_STATE_COOP_LINE, + OPEN_STATE_INAZUMA_INFUSEDCRYSTAL, + OPEN_STATE_FISH, + OPEN_STATE_GUIDE_SUMO_TEAM_SKILL, + OPEN_STATE_GUIDE_FISH_RECIPE, + OPEN_STATE_HOME, + OPEN_STATE_ACTIVITY_HOMEWORLD, + OPEN_STATE_ADEPTIABODE, + OPEN_STATE_HOME_AVATAR, + OPEN_STATE_HOME_EDIT, + OPEN_STATE_HOME_EDIT_TIPS, + OPEN_STATE_RELIQUARY_DECOMPOSE, + OPEN_STATE_ACTIVITY_H5, + OPEN_STATE_ORAIONOKAMI, + OPEN_STATE_GUIDE_CHESS_MISSION_CHECK, + OPEN_STATE_GUIDE_CHESS_BUILD, + OPEN_STATE_GUIDE_CHESS_WIND_TOWER_CIRCLE, + OPEN_STATE_GUIDE_CHESS_CARD_SELECT, + OPEN_STATE_INAZUMA_MAINQUEST_FINISHED, + OPEN_STATE_PAIMON_LVINFO, + OPEN_STATE_TELEPORT_HUD, + OPEN_STATE_GUIDE_MAP_UNLOCK, + OPEN_STATE_GUIDE_PAIMON_LVINFO, + OPEN_STATE_GUIDE_AMBORTRANSPORT, + OPEN_STATE_GUIDE_FLY_SECOND, + OPEN_STATE_GUIDE_KAEYA_CLUE, + OPEN_STATE_CAPTURE_CODEX, + OPEN_STATE_ACTIVITY_FISH_OPEN, + OPEN_STATE_ACTIVITY_FISH_CLOSE, + OPEN_STATE_GUIDE_ROGUE_MAP, + OPEN_STATE_GUIDE_ROGUE_RUNE, + OPEN_STATE_GUIDE_BARTENDER_FORMULA, + OPEN_STATE_GUIDE_BARTENDER_MIX, + OPEN_STATE_GUIDE_BARTENDER_CUP, + OPEN_STATE_GUIDE_MAIL_FAVORITES, + OPEN_STATE_GUIDE_POTION_CONFIGURE, + OPEN_STATE_GUIDE_LANV2_FIREWORK, + OPEN_STATE_LOADINGTIPS_ENKANOMIYA, + OPEN_STATE_MICHIAE_CASKET, + OPEN_STATE_MAIL_COLLECT_UNLOCK_RED_POINT, + OPEN_STATE_LUMEN_STONE, + OPEN_STATE_GUIDE_CRYSTALLINK_BUFF + ); + + public PlayerOpenStateManager(Player player) { + this.openStateMap = new HashMap<>(); + this.player = player; + } + + public void setPlayer(Player player) { + this.player = player; + } + + public Integer getOpenState(OpenState openState) { + return this.openStateMap.getOrDefault(openState.getValue(), 0); + } + public void setOpenState(OpenState openState, Integer value) { + Integer previousValue = this.openStateMap.getOrDefault(openState.getValue(),0); + if(!(value == previousValue)) { + this.openStateMap.put(openState.getValue(), value); + player.getSession().send(new PacketOpenStateChangeNotify(openState.getValue(),value)); + } else { + Grasscutter.getLogger().debug("Warning: OpenState {} is already set to {}!", openState, value); + } + } + + public void setOpenStates(Map openStatesChanged) { + for(Map.Entry entry : openStatesChanged.entrySet()) { + setOpenState(entry.getKey(), entry.getValue()); + } + } + + public void onNewPlayerCreate() { + //newPlayerOpenStates.forEach(os -> this.setOpenState(os, 1)); + //setAllOpenStates(); + devOpenStates.forEach(os -> this.setOpenState(os, 1)); + } + public void onPlayerLogin() { + /* + //little hack to give all openStates on second login + if(openStateMap.containsKey(OPEN_STATE_FRESHMAN_GUIDE.getValue())) { + setAllOpenStates(); + } + */ + player.getSession().send(new PacketOpenStateUpdateNotify(player)); + } + + public void setAllOpenStates() { + Stream.of(OpenState.values()).forEach(os -> this.setOpenState(os, 1)); + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java index d5aea0db1..4578e7d1f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java @@ -17,7 +17,7 @@ import static emu.grasscutter.Configuration.ACCOUNT; @Opcodes(PacketOpcodes.PlayerLoginReq) // Sends initial data packets public class HandlerPlayerLoginReq extends PacketHandler { - + @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { // Check @@ -28,18 +28,20 @@ public class HandlerPlayerLoginReq extends PacketHandler { // Parse request PlayerLoginReq req = PlayerLoginReq.parseFrom(payload); - + // Authenticate session if (!req.getToken().equals(session.getAccount().getToken())) { session.close(); return; } - + // Load character from db Player player = session.getPlayer(); - + // Show opening cutscene if player has no avatars if (player.getAvatars().getAvatarCount() == 0) { + // Set New Player OpenStates + player.getOpenStateManager().onNewPlayerCreate(); // Pick character session.setState(SessionState.PICKING_CHARACTER); session.send(new BasePacket(PacketOpcodes.DoSetPlayerBornDataNotify)); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetOpenStateReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetOpenStateReq.java new file mode 100644 index 000000000..f7f259a32 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetOpenStateReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.props.OpenState; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetOpenStateReqOuterClass.SetOpenStateReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSetOpenStateRsp; + +@Opcodes(PacketOpcodes.SetOpenStateReq) +public class HandlerSetOpenStateReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + var req = SetOpenStateReq.parseFrom(payload); + int openState = req.getKey(); + int value = req.getValue(); + + session.getPlayer().getOpenStateManager().setOpenState(OpenState.getTypeByValue(openState), value); + //Client Automatically Updates its OpenStateMap, no need to send OpenStateUpdateNotify + + session.send(new PacketSetOpenStateRsp(openState,value)); + } + +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateChangeNotify.java new file mode 100644 index 000000000..fafc5dc8f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateChangeNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.OpenStateChangeNotifyOuterClass.OpenStateChangeNotify; + +//Sets openState to value +public class PacketOpenStateChangeNotify extends BasePacket { + + public PacketOpenStateChangeNotify(int openState, int value) { + super(PacketOpcodes.OpenStateChangeNotify); + + OpenStateChangeNotify proto = OpenStateChangeNotify.newBuilder() + .putOpenStateMap(openState,value).build(); + + this.setData(proto); + } + +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateUpdateNotify.java index df74264ca..62ec01ea7 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketOpenStateUpdateNotify.java @@ -1,22 +1,25 @@ package emu.grasscutter.server.packet.send; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.player.PlayerOpenStateManager; import emu.grasscutter.game.props.OpenState; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.OpenStateUpdateNotifyOuterClass.OpenStateUpdateNotify; +import java.util.Map; +/* + Must be sent on login for openStates to work + Tells the client to update its openStateMap for the keys sent. value is irrelevant + */ public class PacketOpenStateUpdateNotify extends BasePacket { - - public PacketOpenStateUpdateNotify() { + + public PacketOpenStateUpdateNotify(Player player) { super(PacketOpcodes.OpenStateUpdateNotify); - + OpenStateUpdateNotify.Builder proto = OpenStateUpdateNotify.newBuilder(); - - for (OpenState type : OpenState.values()) { - if (type.getValue() > 0) { - proto.putOpenStateMap(type.getValue(), 1); - } - } + + proto.putAllOpenStateMap(player.getOpenStateManager().getOpenStateMap()).build(); this.setData(proto); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetOpenStateRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetOpenStateRsp.java new file mode 100644 index 000000000..de94fdc90 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetOpenStateRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetOpenStateRspOuterClass.SetOpenStateRsp; + +public class PacketSetOpenStateRsp extends BasePacket { + + public PacketSetOpenStateRsp(int openState, int value) { + super(PacketOpcodes.SetOpenStateRsp); + + SetOpenStateRsp proto = SetOpenStateRsp.newBuilder() + .setKey(openState).setValue(value).build(); + + this.setData(proto); + } + +} \ No newline at end of file