From 12146ff09cd82ac4991c18614053cff8257eb21e Mon Sep 17 00:00:00 2001
From: Akka <104902222+Akka0@users.noreply.github.com>
Date: Sun, 26 Jun 2022 20:16:50 +0800
Subject: [PATCH] implement the music game

---
 .../java/emu/grasscutter/data/GameData.java   |   1 +
 .../data/excels/ActivityWatcherData.java      |   4 +
 .../data/excels/MusicGameBasicData.java       |  21 ++++
 .../grasscutter/database/DatabaseHelper.java  |  10 ++
 .../grasscutter/database/DatabaseManager.java |   7 +-
 .../game/activity/ActivityHandler.java        |  27 +++--
 .../game/activity/ActivityManager.java        | 103 ++++++++++-------
 ...cherType.java => ActivityWatcherType.java} |   2 +-
 .../game/activity/DefaultActivityHandler.java |  17 +++
 .../game/activity/DefaultWatcher.java         |   2 +-
 .../{ActivityType.java => GameActivity.java}  |   6 +-
 .../game/activity/PlayerActivityData.java     |  46 +++++++-
 .../musicgame/MusicGameActivityHandler.java   |  76 +++++++++++-
 .../activity/musicgame/MusicGameBeatmap.java  | 109 ++++++++++++++++++
 .../musicgame/MusicGamePlayerData.java        |  76 ++++++++++++
 .../musicgame/MusicGameScoreTrigger.java      |   4 +-
 .../grasscutter/game/props/ActivityType.java  |  38 ++++++
 .../grasscutter/net/packet/PacketOpcodes.java |   6 +
 .../HandlerActivityTakeWatcherRewardReq.java  |  25 ++++
 .../HandlerMusicGameCreateBeatmapReq.java     |  47 ++++++++
 .../recv/HandlerMusicGameGetBeatmapReq.java   |  31 +++++
 .../HandlerMusicGameSearchBeatmapReq.java     |  28 +++++
 .../recv/HandlerMusicGameSettleReq.java       |  41 ++++++-
 .../packet/recv/HandlerMusicGameStartReq.java |   4 +-
 .../PacketActivityTakeWatcherRewardRsp.java   |  20 ++++
 .../packet/send/PacketGetActivityInfoRsp.java |   2 +-
 .../send/PacketMusicGameCreateBeatmapRsp.java |  20 ++++
 .../send/PacketMusicGameGetBeatmapRsp.java    |  27 +++++
 .../send/PacketMusicGameSearchBeatmapRsp.java |  34 ++++++
 .../packet/send/PacketMusicGameSettleRsp.java |  15 +--
 .../packet/send/PacketMusicGameStartRsp.java  |   5 +-
 .../defaults/data/ActivityConfig.json         |   6 +-
 32 files changed, 780 insertions(+), 80 deletions(-)
 create mode 100644 src/main/java/emu/grasscutter/data/excels/MusicGameBasicData.java
 rename src/main/java/emu/grasscutter/game/activity/{WatcherType.java => ActivityWatcherType.java} (89%)
 create mode 100644 src/main/java/emu/grasscutter/game/activity/DefaultActivityHandler.java
 rename src/main/java/emu/grasscutter/game/activity/{ActivityType.java => GameActivity.java} (71%)
 create mode 100644 src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameBeatmap.java
 create mode 100644 src/main/java/emu/grasscutter/game/activity/musicgame/MusicGamePlayerData.java
 create mode 100644 src/main/java/emu/grasscutter/game/props/ActivityType.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerActivityTakeWatcherRewardReq.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameCreateBeatmapReq.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameGetBeatmapReq.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSearchBeatmapReq.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketActivityTakeWatcherRewardRsp.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameCreateBeatmapRsp.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameGetBeatmapRsp.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSearchBeatmapRsp.java

diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java
index 780564cb..da95b1bc 100644
--- a/src/main/java/emu/grasscutter/data/GameData.java
+++ b/src/main/java/emu/grasscutter/data/GameData.java
@@ -101,6 +101,7 @@ public class GameData {
 
 	@Getter private static final Int2ObjectMap<ActivityData> activityDataMap = new Int2ObjectOpenHashMap<>();
     @Getter private static final Int2ObjectMap<ActivityWatcherData> activityWatcherDataMap = new Int2ObjectOpenHashMap<>();
+    @Getter private static final Int2ObjectMap<MusicGameBasicData> musicGameBasicDataMap = new Int2ObjectOpenHashMap<>();
 
 	// Cache
 	private static Map<Integer, List<Integer>> fetters = new HashMap<>();
diff --git a/src/main/java/emu/grasscutter/data/excels/ActivityWatcherData.java b/src/main/java/emu/grasscutter/data/excels/ActivityWatcherData.java
index e784693f..4ff176fa 100644
--- a/src/main/java/emu/grasscutter/data/excels/ActivityWatcherData.java
+++ b/src/main/java/emu/grasscutter/data/excels/ActivityWatcherData.java
@@ -2,6 +2,7 @@ package emu.grasscutter.data.excels;
 
 import emu.grasscutter.data.GameResource;
 import emu.grasscutter.data.ResourceType;
+import emu.grasscutter.game.props.WatcherTriggerType;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.experimental.FieldDefaults;
@@ -24,6 +25,7 @@ public class ActivityWatcherData extends GameResource {
     @Override
     public void onLoad() {
         triggerConfig.paramList = triggerConfig.paramList.stream().filter(x -> !x.isBlank()).toList();
+        triggerConfig.watcherTriggerType = WatcherTriggerType.getTypeByName(triggerConfig.triggerType);
     }
 
     @Getter
@@ -31,6 +33,8 @@ public class ActivityWatcherData extends GameResource {
     public static class WatcherTrigger{
         String triggerType;
         List<String> paramList;
+
+        transient WatcherTriggerType watcherTriggerType;
     }
 
 }
diff --git a/src/main/java/emu/grasscutter/data/excels/MusicGameBasicData.java b/src/main/java/emu/grasscutter/data/excels/MusicGameBasicData.java
new file mode 100644
index 00000000..d73d74ce
--- /dev/null
+++ b/src/main/java/emu/grasscutter/data/excels/MusicGameBasicData.java
@@ -0,0 +1,21 @@
+package emu.grasscutter.data.excels;
+
+import emu.grasscutter.data.GameResource;
+import emu.grasscutter.data.ResourceType;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.experimental.FieldDefaults;
+
+@ResourceType(name = "MusicGameBasicConfigData.json")
+@Getter
+@FieldDefaults(level = AccessLevel.PRIVATE)
+public class MusicGameBasicData extends GameResource {
+    int id;
+    int musicID;
+    int musicLevel;
+
+    @Override
+    public int getId() {
+        return this.id;
+    }
+}
diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java
index 55afc435..1178ddc4 100644
--- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java
+++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java
@@ -11,6 +11,7 @@ import emu.grasscutter.GameConstants;
 import emu.grasscutter.Grasscutter;
 import emu.grasscutter.game.Account;
 import emu.grasscutter.game.activity.PlayerActivityData;
+import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap;
 import emu.grasscutter.game.avatar.Avatar;
 import emu.grasscutter.game.battlepass.BattlePassManager;
 import emu.grasscutter.game.friends.Friendship;
@@ -337,4 +338,13 @@ public final class DatabaseHelper {
     public static void savePlayerActivityData(PlayerActivityData playerActivityData) {
         DatabaseManager.getGameDatastore().save(playerActivityData);
     }
+    public static MusicGameBeatmap getMusicGameBeatmap(long musicShareId) {
+        return DatabaseManager.getGameDatastore().find(MusicGameBeatmap.class)
+            .filter(Filters.eq("musicShareId", musicShareId))
+            .first();
+    }
+
+    public static void saveMusicGameBeatmap(MusicGameBeatmap musicGameBeatmap) {
+        DatabaseManager.getGameDatastore().save(musicGameBeatmap);
+    }
 }
diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java
index 19618c27..d0c32286 100644
--- a/src/main/java/emu/grasscutter/database/DatabaseManager.java
+++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java
@@ -14,6 +14,7 @@ import emu.grasscutter.Grasscutter;
 import emu.grasscutter.Grasscutter.ServerRunMode;
 import emu.grasscutter.game.Account;
 import emu.grasscutter.game.activity.PlayerActivityData;
+import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap;
 import emu.grasscutter.game.avatar.Avatar;
 import emu.grasscutter.game.battlepass.BattlePassManager;
 import emu.grasscutter.game.friends.Friendship;
@@ -33,12 +34,14 @@ public final class DatabaseManager {
 
 	private static final Class<?>[] mappedClasses = new Class<?>[] {
 		DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class,
-		GachaRecord.class, Mail.class, GameMainQuest.class, GameHome.class, BattlePassManager.class, PlayerActivityData.class
+		GachaRecord.class, Mail.class, GameMainQuest.class, GameHome.class, BattlePassManager.class,
+        PlayerActivityData.class, MusicGameBeatmap.class
 	};
+
     public static Datastore getGameDatastore() {
     	return gameDatastore;
     }
-    
+
     public static MongoDatabase getGameDatabase() {
     	return getGameDatastore().getDatabase();
     }
diff --git a/src/main/java/emu/grasscutter/game/activity/ActivityHandler.java b/src/main/java/emu/grasscutter/game/activity/ActivityHandler.java
index 386533d5..5a8709cd 100644
--- a/src/main/java/emu/grasscutter/game/activity/ActivityHandler.java
+++ b/src/main/java/emu/grasscutter/game/activity/ActivityHandler.java
@@ -26,12 +26,15 @@ public abstract class ActivityHandler {
     ActivityData activityData;
     Map<WatcherTriggerType, List<ActivityWatcher>> watchersMap = new HashMap<>();
 
-    public void initWatchers(HashMap<String, ConstructorAccess<?>> activityWatcherTypeMap){
+    abstract public void onProtoBuild(PlayerActivityData playerActivityData, ActivityInfoOuterClass.ActivityInfo.Builder activityInfo);
+    abstract public void onInitPlayerActivityData(PlayerActivityData playerActivityData);
+
+    public void initWatchers(Map<WatcherTriggerType, ConstructorAccess<?>> activityWatcherTypeMap){
         activityData = GameData.getActivityDataMap().get(activityConfigItem.getActivityId());
 
         // add watcher to map by id
         activityData.getWatcherDataList().forEach(watcherData -> {
-            var watcherType = activityWatcherTypeMap.get(watcherData.getTriggerConfig().getTriggerType());
+            var watcherType = activityWatcherTypeMap.get(watcherData.getTriggerConfig().getWatcherTriggerType());
             ActivityWatcher watcher;
             if(watcherType != null){
                 watcher = (ActivityWatcher) watcherType.newInstance();
@@ -42,8 +45,8 @@ public abstract class ActivityHandler {
             watcher.setWatcherId(watcherData.getId());
             watcher.setActivityHandler(this);
             watcher.setActivityWatcherData(watcherData);
-            watchersMap.computeIfAbsent(WatcherTriggerType.getTypeByName(watcherData.getTriggerConfig().getTriggerType()), k -> new ArrayList<>());
-            watchersMap.get(WatcherTriggerType.getTypeByName(watcherData.getTriggerConfig().getTriggerType())).add(watcher);
+            watchersMap.computeIfAbsent(watcherData.getTriggerConfig().getWatcherTriggerType(), k -> new ArrayList<>());
+            watchersMap.get(watcherData.getTriggerConfig().getWatcherTriggerType()).add(watcher);
         });
     }
 
@@ -55,16 +58,19 @@ public abstract class ActivityHandler {
     }
 
     public PlayerActivityData initPlayerActivityData(Player player){
-        return PlayerActivityData.of()
+        PlayerActivityData playerActivityData = PlayerActivityData.of()
             .activityId(activityConfigItem.getActivityId())
             .uid(player.getUid())
             .watcherInfoMap(initWatchersDataForPlayer())
             .build();
-    }
 
+        onInitPlayerActivityData(playerActivityData);
+        return playerActivityData;
+    }
 
-    public void buildProto(PlayerActivityData playerActivityData, ActivityInfoOuterClass.ActivityInfo.Builder activityInfo){
-        activityInfo.setActivityId(activityConfigItem.getActivityId())
+    public ActivityInfoOuterClass.ActivityInfo toProto(PlayerActivityData playerActivityData){
+        var proto = ActivityInfoOuterClass.ActivityInfo.newBuilder();
+        proto.setActivityId(activityConfigItem.getActivityId())
             .setActivityType(activityConfigItem.getActivityType())
             .setScheduleId(activityConfigItem.getScheduleId())
             .setBeginTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
@@ -73,9 +79,12 @@ public abstract class ActivityHandler {
             .addAllMeetCondList(activityConfigItem.getMeetCondList());
 
         if (playerActivityData != null){
-            activityInfo.addAllWatcherInfoList(playerActivityData.getAllWatcherInfoList());
+            proto.addAllWatcherInfoList(playerActivityData.getAllWatcherInfoList());
         }
 
+        onProtoBuild(playerActivityData, proto);
+
+        return proto.build();
     }
 
 }
diff --git a/src/main/java/emu/grasscutter/game/activity/ActivityManager.java b/src/main/java/emu/grasscutter/game/activity/ActivityManager.java
index b8b4db16..b511a262 100644
--- a/src/main/java/emu/grasscutter/game/activity/ActivityManager.java
+++ b/src/main/java/emu/grasscutter/game/activity/ActivityManager.java
@@ -6,6 +6,7 @@ import emu.grasscutter.Grasscutter;
 import emu.grasscutter.data.DataLoader;
 import emu.grasscutter.data.GameData;
 import emu.grasscutter.game.player.Player;
+import emu.grasscutter.game.props.ActivityType;
 import emu.grasscutter.game.props.WatcherTriggerType;
 import emu.grasscutter.net.proto.ActivityInfoOuterClass;
 import emu.grasscutter.server.packet.send.PacketActivityScheduleInfoNotify;
@@ -25,41 +26,22 @@ public class ActivityManager {
 
     static {
         activityConfigItemMap = new HashMap<>();
-
         loadActivityConfigData();
     }
 
-    public ActivityManager(Player player){
-        this.player = player;
-
-        playerActivityDataMap = new ConcurrentHashMap<>();
-        // load data for player
-        activityConfigItemMap.values().forEach(item -> {
-            var data = PlayerActivityData.getByPlayer(player, item.getActivityId());
-            if(data == null){
-                data = item.getActivityHandler().initPlayerActivityData(player);
-                data.save();
-            }
-            data.setPlayer(player);
-            playerActivityDataMap.put(item.getActivityId(), data);
-        });
-
-        player.sendPacket(new PacketActivityScheduleInfoNotify(activityConfigItemMap.values()));
-    }
-
     private static void loadActivityConfigData() {
         // scan activity type handler & watcher type
-        var activityHandlerTypeMap = new HashMap<String, ConstructorAccess<?>>();
-        var activityWatcherTypeMap = new HashMap<String, ConstructorAccess<?>>();
+        var activityHandlerTypeMap = new HashMap<ActivityType, ConstructorAccess<?>>();
+        var activityWatcherTypeMap = new HashMap<WatcherTriggerType, ConstructorAccess<?>>();
         var reflections = new Reflections(ActivityManager.class.getPackage().getName());
 
         reflections.getSubTypesOf(ActivityHandler.class).forEach(item -> {
-            var typeName = item.getAnnotation(ActivityType.class);
+            var typeName = item.getAnnotation(GameActivity.class);
             activityHandlerTypeMap.put(typeName.value(), ConstructorAccess.get(item));
         });
         reflections.getSubTypesOf(ActivityWatcher.class).forEach(item -> {
-            var typeName = item.getAnnotation(WatcherType.class);
-            activityWatcherTypeMap.put(typeName.value().name(), ConstructorAccess.get(item));
+            var typeName = item.getAnnotation(ActivityWatcherType.class);
+            activityWatcherTypeMap.put(typeName.value(), ConstructorAccess.get(item));
         });
 
         try(InputStream is = DataLoader.load("ActivityConfig.json"); InputStreamReader isr = new InputStreamReader(is)) {
@@ -74,39 +56,49 @@ public class ActivityManager {
                     Grasscutter.getLogger().warn("activity {} not exist.", item.getActivityId());
                     return;
                 }
-                var activityHandlerType = activityHandlerTypeMap.get(activityData.getActivityType());
+                var activityHandlerType = activityHandlerTypeMap.get(ActivityType.getTypeByName(activityData.getActivityType()));
+                ActivityHandler activityHandler;
 
                 if(activityHandlerType != null) {
-                    var activityHandler = (ActivityHandler) activityHandlerType.newInstance();
-                    activityHandler.setActivityConfigItem(item);
-                    activityHandler.initWatchers(activityWatcherTypeMap);
-                    item.setActivityHandler(activityHandler);
+                    activityHandler = (ActivityHandler) activityHandlerType.newInstance();
+                }else{
+                    activityHandler = new DefaultActivityHandler();
                 }
+                activityHandler.setActivityConfigItem(item);
+                activityHandler.initWatchers(activityWatcherTypeMap);
+                item.setActivityHandler(activityHandler);
 
                 activityConfigItemMap.putIfAbsent(item.getActivityId(), item);
             });
 
-            Grasscutter.getLogger().error("Enable {} activities.", activityConfigItemMap.size());
+            Grasscutter.getLogger().info("Enable {} activities.", activityConfigItemMap.size());
         } catch (Exception e) {
-            Grasscutter.getLogger().error("Unable to load chest reward config.", e);
+            Grasscutter.getLogger().error("Unable to load activities config.", e);
         }
 
     }
 
-    public ActivityInfoOuterClass.ActivityInfo getInfoProto(int activityId){
-        var activityHandler = activityConfigItemMap.get(activityId).getActivityHandler();
-        var activityData = playerActivityDataMap.get(activityId);
+    public ActivityManager(Player player){
+        this.player = player;
 
-        var proto = ActivityInfoOuterClass.ActivityInfo.newBuilder();
-        activityHandler.buildProto(activityData, proto);
+        playerActivityDataMap = new ConcurrentHashMap<>();
+        // load data for player
+        activityConfigItemMap.values().forEach(item -> {
+            var data = PlayerActivityData.getByPlayer(player, item.getActivityId());
+            if(data == null){
+                data = item.getActivityHandler().initPlayerActivityData(player);
+                data.save();
+            }
+            data.setPlayer(player);
+            data.setActivityHandler(item.getActivityHandler());
+            playerActivityDataMap.put(item.getActivityId(), data);
+        });
 
-        return proto.build();
+        player.sendPacket(new PacketActivityScheduleInfoNotify(activityConfigItemMap.values()));
     }
 
     /**
      * trigger activity watcher
-     * @param watcherTriggerType
-     * @param params
      */
     public void triggerWatcher(WatcherTriggerType watcherTriggerType, String... params) {
         var watchers = activityConfigItemMap.values().stream()
@@ -122,4 +114,37 @@ public class ActivityManager {
             playerActivityDataMap.get(watcher.getActivityHandler().getActivityConfigItem().getActivityId()),
             params));
     }
+
+    public ActivityInfoOuterClass.ActivityInfo getInfoProtoByActivityId(int activityId){
+        var activityHandler = activityConfigItemMap.get(activityId).getActivityHandler();
+        var activityData = playerActivityDataMap.get(activityId);
+
+        return activityHandler.toProto(activityData);
+    }
+
+    public Optional<ActivityHandler> getActivityHandler(ActivityType type){
+        return activityConfigItemMap.values().stream()
+            .map(ActivityConfigItem::getActivityHandler)
+            .filter(x -> type == x.getClass().getAnnotation(GameActivity.class).value())
+            .findFirst();
+    }
+
+    public <T extends ActivityHandler> Optional<T> getActivityHandlerAs(ActivityType type, Class<T> clazz){
+        return getActivityHandler(type).map(x -> (T)x);
+    }
+
+    public Optional<Integer> getActivityIdByActivityType(ActivityType type){
+        return getActivityHandler(type)
+            .map(ActivityHandler::getActivityConfigItem)
+            .map(ActivityConfigItem::getActivityId);
+    }
+    public Optional<PlayerActivityData> getPlayerActivityDataByActivityType(ActivityType type){
+        return getActivityIdByActivityType(type)
+            .map(playerActivityDataMap::get);
+    }
+    public Optional<ActivityInfoOuterClass.ActivityInfo> getInfoProtoByActivityType(ActivityType type){
+       return getActivityIdByActivityType(type)
+           .map(this::getInfoProtoByActivityId);
+    }
+
 }
diff --git a/src/main/java/emu/grasscutter/game/activity/WatcherType.java b/src/main/java/emu/grasscutter/game/activity/ActivityWatcherType.java
similarity index 89%
rename from src/main/java/emu/grasscutter/game/activity/WatcherType.java
rename to src/main/java/emu/grasscutter/game/activity/ActivityWatcherType.java
index 7d1f9a26..4c68e11f 100644
--- a/src/main/java/emu/grasscutter/game/activity/WatcherType.java
+++ b/src/main/java/emu/grasscutter/game/activity/ActivityWatcherType.java
@@ -9,6 +9,6 @@ import java.lang.annotation.Target;
 
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
-public @interface WatcherType {
+public @interface ActivityWatcherType {
     WatcherTriggerType value();
 }
diff --git a/src/main/java/emu/grasscutter/game/activity/DefaultActivityHandler.java b/src/main/java/emu/grasscutter/game/activity/DefaultActivityHandler.java
new file mode 100644
index 00000000..69387832
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/activity/DefaultActivityHandler.java
@@ -0,0 +1,17 @@
+package emu.grasscutter.game.activity;
+
+import emu.grasscutter.game.props.ActivityType;
+import emu.grasscutter.net.proto.ActivityInfoOuterClass;
+
+@GameActivity(ActivityType.NONE)
+public class DefaultActivityHandler extends ActivityHandler{
+    @Override
+    public void onProtoBuild(PlayerActivityData playerActivityData, ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
+
+    }
+
+    @Override
+    public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
+
+    }
+}
diff --git a/src/main/java/emu/grasscutter/game/activity/DefaultWatcher.java b/src/main/java/emu/grasscutter/game/activity/DefaultWatcher.java
index 8c6e2464..bd4c93fe 100644
--- a/src/main/java/emu/grasscutter/game/activity/DefaultWatcher.java
+++ b/src/main/java/emu/grasscutter/game/activity/DefaultWatcher.java
@@ -2,7 +2,7 @@ package emu.grasscutter.game.activity;
 
 import emu.grasscutter.game.props.WatcherTriggerType;
 
-@WatcherType(WatcherTriggerType.TRIGGER_NONE)
+@ActivityWatcherType(WatcherTriggerType.TRIGGER_NONE)
 public class DefaultWatcher extends ActivityWatcher{
     @Override
     protected boolean isMeet(String... param) {
diff --git a/src/main/java/emu/grasscutter/game/activity/ActivityType.java b/src/main/java/emu/grasscutter/game/activity/GameActivity.java
similarity index 71%
rename from src/main/java/emu/grasscutter/game/activity/ActivityType.java
rename to src/main/java/emu/grasscutter/game/activity/GameActivity.java
index da57637e..7d70a653 100644
--- a/src/main/java/emu/grasscutter/game/activity/ActivityType.java
+++ b/src/main/java/emu/grasscutter/game/activity/GameActivity.java
@@ -1,5 +1,7 @@
 package emu.grasscutter.game.activity;
 
+import emu.grasscutter.game.props.ActivityType;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -7,6 +9,6 @@ import java.lang.annotation.Target;
 
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
-public @interface ActivityType {
-    String value();
+public @interface GameActivity {
+    ActivityType value();
 }
diff --git a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java
index 4a251a62..3a4b207f 100644
--- a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java
+++ b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java
@@ -3,8 +3,14 @@ package emu.grasscutter.game.activity;
 import dev.morphia.annotations.Entity;
 import dev.morphia.annotations.Id;
 import dev.morphia.annotations.Transient;
+import emu.grasscutter.Grasscutter;
+import emu.grasscutter.data.GameData;
+import emu.grasscutter.data.common.ItemParamData;
+import emu.grasscutter.data.excels.ActivityWatcherData;
 import emu.grasscutter.database.DatabaseHelper;
+import emu.grasscutter.game.inventory.GameItem;
 import emu.grasscutter.game.player.Player;
+import emu.grasscutter.game.props.ActionReason;
 import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
 import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
 import lombok.AccessLevel;
@@ -12,8 +18,10 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.experimental.FieldDefaults;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 @Entity("activities")
 @Data
@@ -25,9 +33,12 @@ public class PlayerActivityData {
     int uid;
     int activityId;
     Map<Integer, WatcherInfo> watcherInfoMap;
+    /**
+     * the detail data of each type of activity (Json format)
+     */
     String detail;
     @Transient Player player;
-
+    @Transient ActivityHandler activityHandler;
     public void save(){
         DatabaseHelper.savePlayerActivityData(this);
     }
@@ -56,6 +67,35 @@ public class PlayerActivityData {
             .toList();
     }
 
+    public void setDetail(Object detail){
+        this.detail = Grasscutter.getGsonFactory().toJson(detail);
+    }
+
+    public void takeWatcherReward(int watcherId) {
+        var watcher = watcherInfoMap.get(watcherId);
+        if(watcher == null || watcher.isTakenReward()){
+            return;
+        }
+
+        var reward = Optional.of(watcher)
+            .map(WatcherInfo::getMetadata)
+            .map(ActivityWatcherData::getRewardID)
+            .map(id -> GameData.getRewardDataMap().get(id.intValue()));
+
+        if(reward.isEmpty()){
+            return;
+        }
+
+        List<GameItem> rewards = new ArrayList<>();
+        for (ItemParamData param : reward.get().getRewardItemList()) {
+            rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
+        }
+
+        player.getInventory().addItems(rewards, ActionReason.ActivityWatcher);
+        watcher.setTakenReward(true);
+        save();
+    }
+
     @Entity
     @Data
     @FieldDefaults(level = AccessLevel.PRIVATE)
@@ -66,6 +106,10 @@ public class PlayerActivityData {
         int curProgress;
         boolean isTakenReward;
 
+        public ActivityWatcherData getMetadata(){
+            return GameData.getActivityWatcherDataMap().get(watcherId);
+        }
+
         public static WatcherInfo init(ActivityWatcher watcher){
             return WatcherInfo.of()
                 .watcherId(watcher.getWatcherId())
diff --git a/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameActivityHandler.java b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameActivityHandler.java
index c3d5ad53..0dcb0ae5 100644
--- a/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameActivityHandler.java
+++ b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameActivityHandler.java
@@ -1,17 +1,85 @@
 package emu.grasscutter.game.activity.musicgame;
 
+import emu.grasscutter.Grasscutter;
 import emu.grasscutter.game.activity.ActivityHandler;
-import emu.grasscutter.game.activity.ActivityType;
+import emu.grasscutter.game.activity.GameActivity;
 import emu.grasscutter.game.activity.PlayerActivityData;
+import emu.grasscutter.game.props.ActivityType;
 import emu.grasscutter.net.proto.ActivityInfoOuterClass;
+import emu.grasscutter.net.proto.MusicBriefInfoOuterClass;
+import emu.grasscutter.net.proto.MusicGameActivityDetailInfoOuterClass;
 
-@ActivityType("NEW_ACTIVITY_MUSIC_GAME")
+import java.util.stream.Collectors;
+
+@GameActivity(ActivityType.NEW_ACTIVITY_MUSIC_GAME)
 public class MusicGameActivityHandler extends ActivityHandler {
 
     @Override
-    public void buildProto(PlayerActivityData playerActivityData, ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
-        super.buildProto(playerActivityData, activityInfo);
+    public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
+        var musicGamePlayerData = MusicGamePlayerData.create();
+
+        playerActivityData.setDetail(musicGamePlayerData);
+    }
+
+    @Override
+    public void onProtoBuild(PlayerActivityData playerActivityData, ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
+        MusicGamePlayerData musicGamePlayerData = getMusicGameRecord(playerActivityData);
+
+        activityInfo.setMusicGameInfo(MusicGameActivityDetailInfoOuterClass.MusicGameActivityDetailInfo.newBuilder()
+            .putAllMusicGameRecordMap(
+                musicGamePlayerData.getMusicGameRecord().values().stream()
+                    .collect(Collectors.toMap(MusicGamePlayerData.MusicGameRecord::getMusicId, MusicGamePlayerData.MusicGameRecord::toProto)))
+
+            .addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream()
+                .map(MusicGamePlayerData.CustomBeatmapRecord::toProto)
+                .map(MusicBriefInfoOuterClass.MusicBriefInfo.Builder::build)
+                .toList())
+
+            .addAllPersonCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream()
+                .map(MusicGamePlayerData.CustomBeatmapRecord::toProto)
+                .map(MusicBriefInfoOuterClass.MusicBriefInfo.Builder::build)
+                .toList())
+            .build());
+    }
+
+    public MusicGamePlayerData getMusicGameRecord(PlayerActivityData playerActivityData){
+        if(playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()){
+            onInitPlayerActivityData(playerActivityData);
+            playerActivityData.save();
+        }
+
+        return Grasscutter.getGsonFactory().fromJson(playerActivityData.getDetail(),
+            MusicGamePlayerData.class);
+    }
+
+    public boolean setMusicGameRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord){
+        var musicGamePlayerData = getMusicGameRecord(playerActivityData);
+        var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId());
+
+        saveRecord.setMaxCombo(Math.max(newRecord.getMaxCombo(), saveRecord.getMaxCombo()));
+        saveRecord.setMaxScore(Math.max(newRecord.getMaxScore(), saveRecord.getMaxScore()));
+
+        playerActivityData.setDetail(musicGamePlayerData);
+        playerActivityData.save();
+
+        return newRecord.getMaxScore() > saveRecord.getMaxScore();
+    }
+    public void setMusicGameCustomBeatmapRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord){
+        var musicGamePlayerData = getMusicGameRecord(playerActivityData);
+        musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord);
+
+        playerActivityData.setDetail(musicGamePlayerData);
+        playerActivityData.save();
+    }
 
+    public void addPersonalBeatmap(PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
+        var musicGamePlayerData = getMusicGameRecord(playerActivityData);
+        musicGamePlayerData.getPersonalCustomBeatmapRecord().put(musicGameBeatmap.getMusicShareId(),
+            MusicGamePlayerData.CustomBeatmapRecord.of()
+                .musicShareId(musicGameBeatmap.getMusicShareId())
+                .build());
 
+        playerActivityData.setDetail(musicGamePlayerData);
+        playerActivityData.save();
     }
 }
diff --git a/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameBeatmap.java b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameBeatmap.java
new file mode 100644
index 00000000..0a5baaf3
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameBeatmap.java
@@ -0,0 +1,109 @@
+package emu.grasscutter.game.activity.musicgame;
+
+import dev.morphia.annotations.Entity;
+import dev.morphia.annotations.Id;
+import emu.grasscutter.database.DatabaseHelper;
+import emu.grasscutter.net.proto.MusicBeatmapListOuterClass;
+import emu.grasscutter.net.proto.MusicBeatmapNoteOuterClass;
+import emu.grasscutter.net.proto.MusicBeatmapOuterClass;
+import emu.grasscutter.net.proto.MusicBriefInfoOuterClass;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.FieldDefaults;
+
+import java.util.List;
+import java.util.Random;
+
+@Entity("music_game_beatmaps")
+@Data
+@FieldDefaults(level = AccessLevel.PRIVATE)
+@Builder(builderMethodName = "of")
+public class MusicGameBeatmap {
+
+    @Id
+    long musicShareId;
+    int authorUid;
+    int musicId;
+    int musicNoteCount;
+    int savePosition;
+    int maxScore;
+    int createTime;
+
+    List<List<BeatmapNote>> beatmap;
+
+    public static MusicGameBeatmap getByShareId(long musicShareId){
+        return DatabaseHelper.getMusicGameBeatmap(musicShareId);
+    }
+
+    public void save(){
+        if(musicShareId == 0){
+            musicShareId = new Random().nextLong(100000000000000L,999999999999999L);
+        }
+        DatabaseHelper.saveMusicGameBeatmap(this);
+    }
+
+    public static List<List<BeatmapNote>> parse(List<MusicBeatmapListOuterClass.MusicBeatmapList> beatmapItemListList) {
+        return beatmapItemListList.stream()
+            .map(item -> item.getBeatmapNoteListList().stream()
+                .map(BeatmapNote::parse)
+                .toList())
+            .toList();
+    }
+
+    public MusicBeatmapOuterClass.MusicBeatmap toProto(){
+        return MusicBeatmapOuterClass.MusicBeatmap.newBuilder()
+            .setMusicId(musicId)
+            .addAllBeatmapItemList(beatmap.stream()
+                .map(this::musicBeatmapListToProto)
+                .toList())
+            .build();
+    }
+
+    public MusicBriefInfoOuterClass.MusicBriefInfo.Builder toBriefProto(){
+        var player = DatabaseHelper.getPlayerByUid(authorUid);
+
+        return MusicBriefInfoOuterClass.MusicBriefInfo.newBuilder()
+            .setCanShare(true)
+            .setMusicId(musicId)
+            .setMusicNoteCount(musicNoteCount)
+            .setMusicShareId(musicShareId)
+            .setMaxScore(maxScore)
+            .setCreateTime(createTime)
+            .setShareTime(createTime)
+            .setAuthorNickname(player.getNickname())
+            .setVersion(1)
+            ;
+    }
+
+    private MusicBeatmapListOuterClass.MusicBeatmapList musicBeatmapListToProto(List<BeatmapNote> beatmapNoteList){
+        return MusicBeatmapListOuterClass.MusicBeatmapList.newBuilder()
+            .addAllBeatmapNoteList(beatmapNoteList.stream()
+                .map(BeatmapNote::toProto)
+                .toList())
+            .build();
+    }
+
+    @Data
+    @FieldDefaults(level = AccessLevel.PRIVATE)
+    @Builder(builderMethodName = "of")
+    @Entity
+    public static class BeatmapNote{
+        int startTime;
+        int endTime;
+
+        public static BeatmapNote parse(MusicBeatmapNoteOuterClass.MusicBeatmapNote note){
+            return BeatmapNote.of()
+                .startTime(note.getStartTime())
+                .endTime(note.getEndTime())
+                .build();
+        }
+
+        public MusicBeatmapNoteOuterClass.MusicBeatmapNote toProto(){
+            return MusicBeatmapNoteOuterClass.MusicBeatmapNote.newBuilder()
+                .setStartTime(startTime)
+                .setEndTime(endTime)
+                .build();
+        }
+    }
+}
diff --git a/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGamePlayerData.java b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGamePlayerData.java
new file mode 100644
index 00000000..1492d93f
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGamePlayerData.java
@@ -0,0 +1,76 @@
+package emu.grasscutter.game.activity.musicgame;
+
+import emu.grasscutter.data.GameData;
+import emu.grasscutter.data.excels.MusicGameBasicData;
+import emu.grasscutter.net.proto.MusicBriefInfoOuterClass;
+import emu.grasscutter.net.proto.MusicGameRecordOuterClass;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.FieldDefaults;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Data
+@FieldDefaults(level = AccessLevel.PRIVATE)
+@Builder(builderMethodName = "of")
+public class MusicGamePlayerData {
+    Map<Integer, MusicGameRecord> musicGameRecord;
+    Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord;
+    Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord;
+
+    public static MusicGamePlayerData create(){
+        return MusicGamePlayerData.of()
+            .musicGameRecord(GameData.getMusicGameBasicDataMap().values().stream()
+                .collect(Collectors.toMap(MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create)))
+            .personalCustomBeatmapRecord(new HashMap<>())
+            .othersCustomBeatmapRecord(new HashMap<>())
+            .build();
+    }
+
+    @Data
+    @FieldDefaults(level = AccessLevel.PRIVATE)
+    @Builder(builderMethodName = "of")
+    public static class MusicGameRecord {
+        int musicId;
+        int maxCombo;
+        int maxScore;
+
+        public static MusicGameRecord create(MusicGameBasicData musicGameBasicData){
+            return MusicGameRecord.of()
+                .musicId(musicGameBasicData.getId())
+                .build();
+        }
+
+        public MusicGameRecordOuterClass.MusicGameRecord toProto(){
+            return MusicGameRecordOuterClass.MusicGameRecord.newBuilder()
+                .setIsUnlock(true)
+                .setMaxCombo(maxCombo)
+                .setMaxScore(maxScore)
+                .build();
+        }
+    }
+
+    @Data
+    @FieldDefaults(level = AccessLevel.PRIVATE)
+    @Builder(builderMethodName = "of")
+    public static class CustomBeatmapRecord {
+        long musicShareId;
+        int score;
+        boolean settle;
+
+        public MusicBriefInfoOuterClass.MusicBriefInfo.Builder toProto(){
+            var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
+
+            return musicGameBeatmap.toBriefProto()
+                .setScore(score)
+                .setSettle(settle)
+                ;
+        }
+
+    }
+}
+
+
diff --git a/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameScoreTrigger.java b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameScoreTrigger.java
index 8ee92027..34cfbe25 100644
--- a/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameScoreTrigger.java
+++ b/src/main/java/emu/grasscutter/game/activity/musicgame/MusicGameScoreTrigger.java
@@ -1,10 +1,10 @@
 package emu.grasscutter.game.activity.musicgame;
 
 import emu.grasscutter.game.activity.ActivityWatcher;
-import emu.grasscutter.game.activity.WatcherType;
+import emu.grasscutter.game.activity.ActivityWatcherType;
 import emu.grasscutter.game.props.WatcherTriggerType;
 
-@WatcherType(WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE)
+@ActivityWatcherType(WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE)
 public class MusicGameScoreTrigger extends ActivityWatcher {
     @Override
     protected boolean isMeet(String... param) {
diff --git a/src/main/java/emu/grasscutter/game/props/ActivityType.java b/src/main/java/emu/grasscutter/game/props/ActivityType.java
new file mode 100644
index 00000000..b91538cb
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/props/ActivityType.java
@@ -0,0 +1,38 @@
+package emu.grasscutter.game.props;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+@Getter
+@AllArgsConstructor
+public enum ActivityType {
+    NONE(0),
+    NEW_ACTIVITY_MUSIC_GAME(2202),
+
+    ;
+
+    private final int value;
+    private static final Int2ObjectMap<ActivityType> map = new Int2ObjectOpenHashMap<>();
+    private static final Map<String, ActivityType> stringMap = new HashMap<>();
+
+    static {
+        Stream.of(values()).forEach(e -> {
+            map.put(e.getValue(), e);
+            stringMap.put(e.name(), e);
+        });
+    }
+
+    public static ActivityType getTypeByValue(int value) {
+        return map.getOrDefault(value, NONE);
+    }
+
+    public static ActivityType getTypeByName(String name) {
+        return stringMap.getOrDefault(name, NONE);
+    }
+}
diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java
index af37f7a0..9582b6fe 100644
--- a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java
+++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java
@@ -899,6 +899,12 @@ public class PacketOpcodes {
     public static final int MultistagePlayInfoNotify = 5309;
     public static final int MultistagePlaySettleNotify = 5314;
     public static final int MultistagePlayStageEndNotify = 5340;
+    public static final int MusicGameCreateBeatmapReq = 6326;
+    public static final int MusicGameCreateBeatmapRsp = 6347;
+    public static final int MusicGameGetBeatmapReq = 6318;
+    public static final int MusicGameGetBeatmapRsp = 6309;
+    public static final int MusicGameSearchBeatmapReq = 6343;
+    public static final int MusicGameSearchBeatmapRsp = 6304;
     public static final int MusicGameSettleReq = 8745;
     public static final int MusicGameSettleRsp = 8288;
     public static final int MusicGameStartReq = 8927;
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerActivityTakeWatcherRewardReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerActivityTakeWatcherRewardReq.java
new file mode 100644
index 00000000..3ea3b75e
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerActivityTakeWatcherRewardReq.java
@@ -0,0 +1,25 @@
+package emu.grasscutter.server.packet.recv;
+
+import emu.grasscutter.net.packet.Opcodes;
+import emu.grasscutter.net.packet.PacketHandler;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.ActivityTakeWatcherRewardReqOuterClass;
+import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketActivityTakeWatcherRewardRsp;
+
+import java.util.Optional;
+
+@Opcodes(PacketOpcodes.ActivityTakeWatcherRewardReq)
+public class HandlerActivityTakeWatcherRewardReq extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+		var req = ActivityTakeWatcherRewardReqOuterClass.ActivityTakeWatcherRewardReq.parseFrom(payload);
+
+        Optional.ofNullable(session.getPlayer().getActivityManager().getPlayerActivityDataMap().get(req.getActivityId()))
+            .ifPresent(x -> x.takeWatcherReward(req.getWatcherId()));
+
+        session.send(new PacketActivityTakeWatcherRewardRsp(req.getActivityId(), req.getWatcherId()));
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameCreateBeatmapReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameCreateBeatmapReq.java
new file mode 100644
index 00000000..b3ac7ef8
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameCreateBeatmapReq.java
@@ -0,0 +1,47 @@
+package emu.grasscutter.server.packet.recv;
+
+import emu.grasscutter.game.activity.musicgame.MusicGameActivityHandler;
+import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap;
+import emu.grasscutter.game.props.ActivityType;
+import emu.grasscutter.net.packet.Opcodes;
+import emu.grasscutter.net.packet.PacketHandler;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.MusicGameCreateBeatmapReqOuterClass;
+import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketActivityInfoNotify;
+import emu.grasscutter.server.packet.send.PacketMusicGameCreateBeatmapRsp;
+import emu.grasscutter.utils.Utils;
+
+@Opcodes(PacketOpcodes.MusicGameCreateBeatmapReq)
+public class HandlerMusicGameCreateBeatmapReq extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+		var req = MusicGameCreateBeatmapReqOuterClass.MusicGameCreateBeatmapReq.parseFrom(payload);
+
+        var musicGameBeatmap = MusicGameBeatmap.of()
+            .musicId(req.getMusicBriefInfo().getMusicId())
+            .musicNoteCount(req.getMusicBriefInfo().getMusicNoteCount())
+            .savePosition(req.getMusicBriefInfo().getSavePosition())
+            .maxScore(req.getMusicBriefInfo().getMaxScore())
+            .authorUid(session.getPlayer().getUid())
+            .beatmap(MusicGameBeatmap.parse(req.getMusicRecord().getBeatmapItemListList()))
+            .createTime(Utils.getCurrentSeconds())
+            .build();
+
+        // TODO avoid player save too much to make server down
+        musicGameBeatmap.save();
+
+        var playerData = session.getPlayer().getActivityManager().getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME);
+        if(playerData.isEmpty()){
+            return;
+        }
+
+        var handler = (MusicGameActivityHandler) playerData.get().getActivityHandler();
+        handler.addPersonalBeatmap(playerData.get(), musicGameBeatmap);
+
+        session.send(new PacketActivityInfoNotify(handler.toProto(playerData.get())));
+        session.send(new PacketMusicGameCreateBeatmapRsp(musicGameBeatmap.getMusicShareId(), req.getUnknownEnum1()));
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameGetBeatmapReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameGetBeatmapReq.java
new file mode 100644
index 00000000..8d18c352
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameGetBeatmapReq.java
@@ -0,0 +1,31 @@
+package emu.grasscutter.server.packet.recv;
+
+import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap;
+import emu.grasscutter.net.packet.Opcodes;
+import emu.grasscutter.net.packet.PacketHandler;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.MusicGameGetBeatmapReqOuterClass;
+import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketMusicGameGetBeatmapRsp;
+
+@Opcodes(PacketOpcodes.MusicGameGetBeatmapReq)
+public class HandlerMusicGameGetBeatmapReq extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+		var req = MusicGameGetBeatmapReqOuterClass.MusicGameGetBeatmapReq.parseFrom(payload);
+
+		var musicGameBeatmap = MusicGameBeatmap.getByShareId(req.getMusicShareId());
+
+		if(musicGameBeatmap == null){
+			return;
+		}
+
+        session.send(new PacketMusicGameGetBeatmapRsp(
+            musicGameBeatmap.toBriefProto().build(),
+            musicGameBeatmap.toProto(),
+            req
+        ));
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSearchBeatmapReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSearchBeatmapReq.java
new file mode 100644
index 00000000..82c6f45f
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSearchBeatmapReq.java
@@ -0,0 +1,28 @@
+package emu.grasscutter.server.packet.recv;
+
+import emu.grasscutter.game.activity.musicgame.MusicGameBeatmap;
+import emu.grasscutter.net.packet.Opcodes;
+import emu.grasscutter.net.packet.PacketHandler;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.MusicGameSearchBeatmapReqOuterClass;
+import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketMusicGameSearchBeatmapRsp;
+
+@Opcodes(PacketOpcodes.MusicGameSearchBeatmapReq)
+public class HandlerMusicGameSearchBeatmapReq extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+		var req = MusicGameSearchBeatmapReqOuterClass.MusicGameSearchBeatmapReq.parseFrom(payload);
+
+		var musicGameBeatmap = MusicGameBeatmap.getByShareId(req.getMusicShareId());
+
+		if(musicGameBeatmap == null){
+			session.send(new PacketMusicGameSearchBeatmapRsp(11153, req.getUnknownEnum1()));
+			return;
+		}
+
+        session.send(new PacketMusicGameSearchBeatmapRsp(musicGameBeatmap.toBriefProto().build(), req.getUnknownEnum1()));
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSettleReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSettleReq.java
index e4e83950..b73a867e 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSettleReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameSettleReq.java
@@ -1,11 +1,15 @@
 package emu.grasscutter.server.packet.recv;
 
+import emu.grasscutter.game.activity.musicgame.MusicGameActivityHandler;
+import emu.grasscutter.game.activity.musicgame.MusicGamePlayerData;
+import emu.grasscutter.game.props.ActivityType;
 import emu.grasscutter.game.props.WatcherTriggerType;
 import emu.grasscutter.net.packet.Opcodes;
 import emu.grasscutter.net.packet.PacketHandler;
 import emu.grasscutter.net.packet.PacketOpcodes;
 import emu.grasscutter.net.proto.MusicGameSettleReqOuterClass;
 import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketActivityInfoNotify;
 import emu.grasscutter.server.packet.send.PacketMusicGameSettleRsp;
 
 @Opcodes(PacketOpcodes.MusicGameSettleReq)
@@ -15,15 +19,40 @@ public class HandlerMusicGameSettleReq extends PacketHandler {
 	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
 		var req = MusicGameSettleReqOuterClass.MusicGameSettleReq.parseFrom(payload);
 
-        session.getPlayer().getActivityManager().triggerWatcher(
-            WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE,
-            String.valueOf(req.getMusicBasicId()),
-            String.valueOf(req.getScore())
+        var playerData = session.getPlayer().getActivityManager().getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_MUSIC_GAME);
+        if(playerData.isEmpty()){
+            return;
+        }
+        var handler = (MusicGameActivityHandler) playerData.get().getActivityHandler();
+        boolean isNewRecord = false;
+        // check if custom beatmap
+        if(req.getMusicShareId() == 0){
+            session.getPlayer().getActivityManager().triggerWatcher(
+                WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE,
+                String.valueOf(req.getMusicBasicId()),
+                String.valueOf(req.getScore())
             );
 
+            isNewRecord = handler.setMusicGameRecord(playerData.get(),
+                MusicGamePlayerData.MusicGameRecord.of()
+                    .musicId(req.getMusicBasicId())
+                    .maxCombo(req.getMaxCombo())
+                    .maxScore(req.getScore())
+                    .build());
 
-		//session.send(new PacketMusicGameSettleRsp(req.getMusicBasicId()));
-		session.send(new PacketMusicGameSettleRsp(req.getMusicBasicId()));
+            // update activity info
+            session.send(new PacketActivityInfoNotify(handler.toProto(playerData.get())));
+        }else{
+            handler.setMusicGameCustomBeatmapRecord(playerData.get(),
+                MusicGamePlayerData.CustomBeatmapRecord.of()
+                    .musicShareId(req.getMusicShareId())
+                    .score(req.getMaxCombo())
+                    .settle(req.getSuccess())
+                    .build());
+        }
+
+
+		session.send(new PacketMusicGameSettleRsp(req.getMusicBasicId(), req.getMusicShareId(), isNewRecord));
 	}
 
 }
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameStartReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameStartReq.java
index a7bb22cf..a5f92f53 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameStartReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerMusicGameStartReq.java
@@ -9,12 +9,12 @@ import emu.grasscutter.server.packet.send.PacketMusicGameStartRsp;
 
 @Opcodes(PacketOpcodes.MusicGameStartReq)
 public class HandlerMusicGameStartReq extends PacketHandler {
-	
+
 	@Override
 	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
 		var req = MusicGameStartReqOuterClass.MusicGameStartReq.parseFrom(payload);
 
-		session.send(new PacketMusicGameStartRsp(req.getMusicBasicId()));
+		session.send(new PacketMusicGameStartRsp(req.getMusicBasicId(), req.getMusicShareId()));
 	}
 
 }
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketActivityTakeWatcherRewardRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketActivityTakeWatcherRewardRsp.java
new file mode 100644
index 00000000..5968198e
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketActivityTakeWatcherRewardRsp.java
@@ -0,0 +1,20 @@
+package emu.grasscutter.server.packet.send;
+
+import emu.grasscutter.net.packet.BasePacket;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.ActivityTakeWatcherRewardRspOuterClass;
+
+public class PacketActivityTakeWatcherRewardRsp extends BasePacket {
+
+	public PacketActivityTakeWatcherRewardRsp(int activityId, int watcherId) {
+		super(PacketOpcodes.ActivityTakeWatcherRewardRsp);
+
+        var proto = ActivityTakeWatcherRewardRspOuterClass.ActivityTakeWatcherRewardRsp.newBuilder();
+
+        proto.setActivityId(activityId)
+            .setWatcherId(watcherId);
+
+        this.setData(proto);
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetActivityInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetActivityInfoRsp.java
index 36e35dcb..d0172bfc 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetActivityInfoRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetActivityInfoRsp.java
@@ -14,7 +14,7 @@ public class PacketGetActivityInfoRsp extends BasePacket {
 		var proto = GetActivityInfoRsp.newBuilder();
 
         activityIdList.stream()
-            .map(activityManager::getInfoProto)
+            .map(activityManager::getInfoProtoByActivityId)
             .forEach(proto::addActivityInfoList);
 
 		this.setData(proto);
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameCreateBeatmapRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameCreateBeatmapRsp.java
new file mode 100644
index 00000000..401ff44c
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameCreateBeatmapRsp.java
@@ -0,0 +1,20 @@
+package emu.grasscutter.server.packet.send;
+
+import emu.grasscutter.net.packet.BasePacket;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.MusicGameCreateBeatmapRspOuterClass;
+import emu.grasscutter.net.proto.MusicGameUnknown1EnumOuterClass;
+
+public class PacketMusicGameCreateBeatmapRsp extends BasePacket {
+
+	public PacketMusicGameCreateBeatmapRsp(long musicShareId, MusicGameUnknown1EnumOuterClass.MusicGameUnknown1Enum unknownEnum1) {
+		super(PacketOpcodes.MusicGameCreateBeatmapRsp);
+
+        var proto = MusicGameCreateBeatmapRspOuterClass.MusicGameCreateBeatmapRsp.newBuilder();
+
+        proto.setMusicShareId(musicShareId)
+            .setUnknownEnum1(unknownEnum1);
+
+        this.setData(proto);
+	}
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameGetBeatmapRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameGetBeatmapRsp.java
new file mode 100644
index 00000000..35cbdfb3
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameGetBeatmapRsp.java
@@ -0,0 +1,27 @@
+package emu.grasscutter.server.packet.send;
+
+import emu.grasscutter.net.packet.BasePacket;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.MusicBeatmapOuterClass;
+import emu.grasscutter.net.proto.MusicBriefInfoOuterClass;
+import emu.grasscutter.net.proto.MusicGameGetBeatmapReqOuterClass;
+import emu.grasscutter.net.proto.MusicGameGetBeatmapRspOuterClass;
+
+public class PacketMusicGameGetBeatmapRsp extends BasePacket {
+
+    public PacketMusicGameGetBeatmapRsp(MusicBriefInfoOuterClass.MusicBriefInfo briefInfo, MusicBeatmapOuterClass.MusicBeatmap musicRecord, MusicGameGetBeatmapReqOuterClass.MusicGameGetBeatmapReq req) {
+        super(PacketOpcodes.MusicGameGetBeatmapRsp);
+
+        var proto = MusicGameGetBeatmapRspOuterClass.MusicGameGetBeatmapRsp.newBuilder();
+
+        proto.setMusicBriefInfo(briefInfo)
+            .setMusicShareId(briefInfo.getMusicShareId())
+            .setMusicRecord(musicRecord)
+            .setUnknownEnum1(req.getUnknownEnum1())
+            .setReqType(req.getReqType())
+        ;
+
+
+        this.setData(proto);
+    }
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSearchBeatmapRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSearchBeatmapRsp.java
new file mode 100644
index 00000000..e5dc218f
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSearchBeatmapRsp.java
@@ -0,0 +1,34 @@
+package emu.grasscutter.server.packet.send;
+
+import emu.grasscutter.net.packet.BasePacket;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.MusicBriefInfoOuterClass;
+import emu.grasscutter.net.proto.MusicGameSearchBeatmapRspOuterClass;
+import emu.grasscutter.net.proto.MusicGameUnknown1EnumOuterClass;
+
+public class PacketMusicGameSearchBeatmapRsp extends BasePacket {
+
+	public PacketMusicGameSearchBeatmapRsp(int ret, MusicGameUnknown1EnumOuterClass.MusicGameUnknown1Enum unknownEnum1) {
+		super(PacketOpcodes.MusicGameSearchBeatmapRsp);
+
+        var proto = MusicGameSearchBeatmapRspOuterClass.MusicGameSearchBeatmapRsp.newBuilder();
+
+        proto.setRetcode(ret)
+            .setUnknownEnum1(unknownEnum1);
+
+        this.setData(proto);
+	}
+
+    public PacketMusicGameSearchBeatmapRsp(MusicBriefInfoOuterClass.MusicBriefInfo briefInfo, MusicGameUnknown1EnumOuterClass.MusicGameUnknown1Enum unknownEnum1) {
+        super(PacketOpcodes.MusicGameSearchBeatmapRsp);
+
+        var proto = MusicGameSearchBeatmapRspOuterClass.MusicGameSearchBeatmapRsp.newBuilder();
+
+        proto.setMusicBriefInfo(briefInfo)
+            .setUnknownEnum1(unknownEnum1);
+
+        this.setData(proto);
+    }
+
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSettleRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSettleRsp.java
index c64e44f7..6651f8c0 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSettleRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameSettleRsp.java
@@ -6,14 +6,15 @@ import emu.grasscutter.net.proto.MusicGameSettleRspOuterClass;
 
 public class PacketMusicGameSettleRsp extends BasePacket {
 
-	public PacketMusicGameSettleRsp(int musicBasicId) {
-		super(PacketOpcodes.MusicGameSettleRsp);
+    public PacketMusicGameSettleRsp(int musicBasicId, long musicShareId, boolean isNewRecord) {
+        super(PacketOpcodes.MusicGameSettleRsp);
 
-		var proto = MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder();
+        var proto = MusicGameSettleRspOuterClass.MusicGameSettleRsp.newBuilder();
 
-		proto.setMusicBasicId(musicBasicId)
-				.setIsNewRecord(true);
+        proto.setMusicBasicId(musicBasicId)
+            .setMusicShareId(musicShareId)
+            .setIsNewRecord(isNewRecord);
 
-		this.setData(proto);
-	}
+        this.setData(proto);
+    }
 }
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameStartRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameStartRsp.java
index 2dce1ba7..86e11442 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameStartRsp.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMusicGameStartRsp.java
@@ -6,12 +6,13 @@ import emu.grasscutter.net.proto.MusicGameStartRspOuterClass;
 
 public class PacketMusicGameStartRsp extends BasePacket {
 
-	public PacketMusicGameStartRsp(int musicBasicId) {
+	public PacketMusicGameStartRsp(int musicBasicId, long musicShareId) {
 		super(PacketOpcodes.MusicGameStartRsp);
 
 		var proto = MusicGameStartRspOuterClass.MusicGameStartRsp.newBuilder();
 
-		proto.setMusicBasicId(musicBasicId);
+		proto.setMusicBasicId(musicBasicId)
+            .setMusicShareId(musicShareId);
 
 		this.setData(proto);
 	}
diff --git a/src/main/resources/defaults/data/ActivityConfig.json b/src/main/resources/defaults/data/ActivityConfig.json
index 75314fdb..6291aea4 100644
--- a/src/main/resources/defaults/data/ActivityConfig.json
+++ b/src/main/resources/defaults/data/ActivityConfig.json
@@ -2,6 +2,7 @@
     {
         "activityId" : 5072,
         "activityType" : 2202,
+        "scheduleId": 5072001,
         "meetCondList" : [
             5072001,
             5072002,
@@ -9,7 +10,10 @@
             5072004,
             5072005,
             5072006,
-            5072007
+            5072007,
+            5072008,
+            5072009,
+            5072013
         ],
         "beginTime" : "2022-05-01T00:00:00+08:00",
         "endTime" : "2023-05-01T00:00:00+08:00"
-- 
GitLab