From 910008216f1e4a00ceb8661baf1113c009a74353 Mon Sep 17 00:00:00 2001
From: Akka <104902222+Akka0@users.noreply.github.com>
Date: Tue, 5 Jul 2022 20:41:07 +0800
Subject: [PATCH] quest fix & personal line impl

---
 .../java/emu/grasscutter/data/GameData.java   |  2 +
 .../data/binout/MainQuestData.java            | 11 +--
 .../grasscutter/data/excels/ChapterData.java  | 37 ++++++++
 .../data/excels/PersonalLineData.java         | 28 ++++++
 .../grasscutter/data/excels/QuestData.java    | 93 +++++++++----------
 .../grasscutter/game/inventory/Inventory.java |  8 +-
 .../emu/grasscutter/game/player/Player.java   | 14 ++-
 .../game/props/PlayerProperty.java            |  2 +-
 .../grasscutter/game/quest/GameMainQuest.java | 47 ++++++----
 .../emu/grasscutter/game/quest/GameQuest.java | 90 ++++++++++++------
 .../game/quest/QuestGroupSuite.java           | 17 ++++
 .../grasscutter/game/quest/QuestManager.java  | 70 +++++++++-----
 .../game/quest/ServerQuestHandler.java        | 67 ++++++-------
 .../game/quest/conditions/BaseCondition.java  |  4 +-
 .../quest/conditions/ConditionLuaNotify.java  | 17 ++++
 .../ConditionPlayerLevelEqualGreater.java     |  4 +-
 .../quest/conditions/ConditionStateEqual.java | 10 +-
 .../game/quest/content/BaseContent.java       |  4 +-
 .../content/ContentAddQuestProgress.java      | 17 ++++
 .../quest/content/ContentCompleteTalk.java    |  4 +-
 .../quest/content/ContentEnterDungeon.java    |  4 +-
 .../game/quest/content/ContentEnterRoom.java  | 17 ++++
 .../game/quest/content/ContentFinishPlot.java |  4 +-
 .../quest/content/ContentGameTimeTick.java    | 23 +++++
 .../quest/content/ContentInteractGadget.java  | 17 ++++
 .../game/quest/content/ContentLuaNotify.java  | 17 ++++
 .../quest/content/ContentQuestStateEqual.java | 23 +++++
 .../game/quest/enums/QuestTrigger.java        | 35 ++++++-
 .../game/quest/exec/ExecAddQuestProgress.java | 24 +++++
 .../game/quest/exec/ExecNotifyGroupLua.java   | 34 +++++++
 .../quest/exec/ExecRefreshGroupSuite.java     | 39 ++++++++
 .../game/quest/handlers/QuestBaseHandler.java |  6 +-
 .../game/quest/handlers/QuestExecHandler.java | 10 ++
 .../emu/grasscutter/game/world/Scene.java     | 19 ++++
 .../emu/grasscutter/scripts/ScriptLib.java    |  5 +
 .../HandlerAddQuestContentProgressReq.java    | 23 +++++
 .../packet/recv/HandlerChangeGameTimeReq.java |  8 +-
 .../packet/recv/HandlerEnterSceneDoneReq.java |  6 ++
 ...andlerEvtEntityRenderersChangedNotify.java | 30 ++++++
 .../packet/recv/HandlerGadgetInteractReq.java |  6 +-
 .../recv/HandlerPersonalLineAllDataReq.java   | 17 ++++
 .../packet/recv/HandlerPostEnterSceneReq.java |  8 +-
 .../packet/recv/HandlerQueryPathReq.java      | 11 ++-
 .../recv/HandlerUnlockPersonalLineReq.java    | 28 ++++++
 .../PacketAddQuestContentProgressRsp.java     | 19 ++++
 .../packet/send/PacketChapterStateNotify.java | 20 ++++
 ...PacketEvtEntityRenderersChangedNotify.java | 14 +++
 .../packet/send/PacketGroupSuiteNotify.java   | 22 +++++
 .../send/PacketPersonalLineAllDataRsp.java    | 35 +++++++
 .../packet/send/PacketQueryPathRsp.java       | 22 +++++
 .../send/PacketUnlockPersonalLineRsp.java     | 20 ++++
 51 files changed, 918 insertions(+), 194 deletions(-)
 create mode 100644 src/main/java/emu/grasscutter/data/excels/ChapterData.java
 create mode 100644 src/main/java/emu/grasscutter/data/excels/PersonalLineData.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/QuestGroupSuite.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/conditions/ConditionLuaNotify.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentAddQuestProgress.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentEnterRoom.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentGameTimeTick.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentInteractGadget.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentLuaNotify.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateEqual.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/exec/ExecAddQuestProgress.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/exec/ExecNotifyGroupLua.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/exec/ExecRefreshGroupSuite.java
 create mode 100644 src/main/java/emu/grasscutter/game/quest/handlers/QuestExecHandler.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtEntityRenderersChangedNotify.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerPersonalLineAllDataReq.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerUnlockPersonalLineReq.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketAddQuestContentProgressRsp.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketChapterStateNotify.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketEvtEntityRenderersChangedNotify.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketPersonalLineAllDataRsp.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketQueryPathRsp.java
 create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketUnlockPersonalLineRsp.java

diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java
index 89bf06df..dd2d60a6 100644
--- a/src/main/java/emu/grasscutter/data/GameData.java
+++ b/src/main/java/emu/grasscutter/data/GameData.java
@@ -104,6 +104,8 @@ 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<>();
+    @Getter private static final Int2ObjectMap<PersonalLineData> personalLineDataMap = new Int2ObjectOpenHashMap<>();
+    @Getter private static final Int2ObjectMap<ChapterData> chapterDataMap = new Int2ObjectOpenHashMap<>();
 
 	// Cache
 	private static Map<Integer, List<Integer>> fetters = new HashMap<>();
diff --git a/src/main/java/emu/grasscutter/data/binout/MainQuestData.java b/src/main/java/emu/grasscutter/data/binout/MainQuestData.java
index 96075bd6..eda72e8a 100644
--- a/src/main/java/emu/grasscutter/data/binout/MainQuestData.java
+++ b/src/main/java/emu/grasscutter/data/binout/MainQuestData.java
@@ -1,8 +1,7 @@
 package emu.grasscutter.data.binout;
 
-import emu.grasscutter.game.quest.enums.LogicType;
-import emu.grasscutter.game.quest.enums.QuestTrigger;
 import emu.grasscutter.game.quest.enums.QuestType;
+import lombok.Data;
 
 public class MainQuestData {
 	private int id;
@@ -42,12 +41,10 @@ public class MainQuestData {
 	public SubQuestData[] getSubQuests() {
 		return subQuests;
 	}
-	
+
+    @Data
 	public static class SubQuestData {
 		private int subId;
-
-		public int getSubId() {
-			return subId;
-		}
+        private int order;
 	}
 }
diff --git a/src/main/java/emu/grasscutter/data/excels/ChapterData.java b/src/main/java/emu/grasscutter/data/excels/ChapterData.java
new file mode 100644
index 00000000..e8436d5a
--- /dev/null
+++ b/src/main/java/emu/grasscutter/data/excels/ChapterData.java
@@ -0,0 +1,37 @@
+package emu.grasscutter.data.excels;
+
+import emu.grasscutter.data.GameResource;
+import emu.grasscutter.data.ResourceType;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.FieldDefaults;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@ResourceType(name = "ChapterExcelConfigData.json")
+@Getter
+@Setter
+@FieldDefaults(level = AccessLevel.PRIVATE)
+public class ChapterData extends GameResource {
+    int id;
+    int beginQuestId;
+    int endQuestId;
+    int needPlayerLevel;
+
+    public static final Map<Integer, ChapterData> beginQuestChapterMap = new HashMap<>();
+    public static final Map<Integer, ChapterData> endQuestChapterMap = new HashMap<>();
+
+    @Override
+    public int getId() {
+        return this.id;
+    }
+
+    @Override
+    public void onLoad() {
+        beginQuestChapterMap.put(beginQuestId, this);
+        beginQuestChapterMap.put(endQuestId, this);
+    }
+}
diff --git a/src/main/java/emu/grasscutter/data/excels/PersonalLineData.java b/src/main/java/emu/grasscutter/data/excels/PersonalLineData.java
new file mode 100644
index 00000000..f7b95689
--- /dev/null
+++ b/src/main/java/emu/grasscutter/data/excels/PersonalLineData.java
@@ -0,0 +1,28 @@
+package emu.grasscutter.data.excels;
+
+import emu.grasscutter.data.GameResource;
+import emu.grasscutter.data.ResourceType;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.FieldDefaults;
+
+import java.util.List;
+
+@ResourceType(name = "PersonalLineExcelConfigData.json")
+@Getter
+@Setter
+@FieldDefaults(level = AccessLevel.PRIVATE)
+public class PersonalLineData extends GameResource {
+    int id;
+    int avatarID;
+    List<Integer> preQuestId;
+    int startQuestId;
+    int chapterId;
+
+    @Override
+    public int getId() {
+        return this.id;
+    }
+
+}
diff --git a/src/main/java/emu/grasscutter/data/excels/QuestData.java b/src/main/java/emu/grasscutter/data/excels/QuestData.java
index 7866eeb4..a0f34fc6 100644
--- a/src/main/java/emu/grasscutter/data/excels/QuestData.java
+++ b/src/main/java/emu/grasscutter/data/excels/QuestData.java
@@ -3,31 +3,36 @@ package emu.grasscutter.data.excels;
 import java.util.Arrays;
 import java.util.List;
 
+import com.google.gson.annotations.SerializedName;
 import emu.grasscutter.data.GameResource;
 import emu.grasscutter.data.ResourceType;
 import emu.grasscutter.game.quest.enums.LogicType;
 import emu.grasscutter.game.quest.enums.QuestTrigger;
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.experimental.FieldDefaults;
 
 @ResourceType(name = "QuestExcelConfigData.json")
+@Getter
+@ToString
 public class QuestData extends GameResource {
 	private int subId;
 	private int mainId;
 	private int order;
 	private long descTextMapHash;
-	
+
 	private boolean finishParent;
 	private boolean isRewind;
-	
+
 	private LogicType acceptCondComb;
-	private QuestCondition[] acceptConditons;
 	private LogicType finishCondComb;
-	private QuestCondition[] finishConditons;
 	private LogicType failCondComb;
-	private QuestCondition[] failConditons;
-	
-	private List<QuestParam> acceptCond;
-	private List<QuestParam> finishCond;
-	private List<QuestParam> failCond;
+
+	private List<QuestCondition> acceptCond;
+	private List<QuestCondition> finishCond;
+	private List<QuestCondition> failCond;
 	private List<QuestExecParam> beginExec;
 	private List<QuestExecParam> finishExec;
 	private List<QuestExecParam> failExec;
@@ -60,67 +65,57 @@ public class QuestData extends GameResource {
 		return acceptCondComb;
 	}
 
-	public QuestCondition[] getAcceptCond() {
-		return acceptConditons;
+	public List<QuestCondition> getAcceptCond() {
+		return acceptCond;
 	}
 
 	public LogicType getFinishCondComb() {
 		return finishCondComb;
 	}
 
-	public QuestCondition[] getFinishCond() {
-		return finishConditons;
+	public List<QuestCondition> getFinishCond() {
+		return finishCond;
 	}
 
 	public LogicType getFailCondComb() {
 		return failCondComb;
 	}
 
-	public QuestCondition[] getFailCond() {
-		return failConditons;
+	public List<QuestCondition> getFailCond() {
+		return failCond;
 	}
 
 	public void onLoad() {
-		this.acceptConditons = acceptCond.stream().filter(p -> p._type != null).map(QuestCondition::new).toArray(QuestCondition[]::new);
-		acceptCond = null;
-		this.finishConditons = finishCond.stream().filter(p -> p._type != null).map(QuestCondition::new).toArray(QuestCondition[]::new);
-		finishCond = null;
-		this.failConditons = failCond.stream().filter(p -> p._type != null).map(QuestCondition::new).toArray(QuestCondition[]::new);
-		failCond = null;
-	}
-	
-	public class QuestParam {
-		QuestTrigger _type;
-		int[] _param;
-		String _count;
+		this.acceptCond = acceptCond.stream().filter(p -> p.type != null).toList();
+		this.finishCond = finishCond.stream().filter(p -> p.type != null).toList();
+		this.failCond = failCond.stream().filter(p -> p.type != null).toList();
+
+        this.beginExec = beginExec.stream().filter(p -> p.type != null).toList();
+        this.finishExec = finishExec.stream().filter(p -> p.type != null).toList();
+        this.failExec = failExec.stream().filter(p -> p.type != null).toList();
 	}
-	
+
+    @Data
+    @FieldDefaults(level = AccessLevel.PRIVATE)
 	public class QuestExecParam {
-		QuestTrigger _type;
-		String[] _param;
-		String _count;
+        @SerializedName("_type")
+		QuestTrigger type;
+        @SerializedName("_param")
+		String[] param;
+        @SerializedName("_count")
+		String count;
 	}
-	
+
+    @Data
 	public static class QuestCondition {
+        @SerializedName("_type")
 		private QuestTrigger type;
+        @SerializedName("_param")
 		private int[] param;
+        @SerializedName("_param_str")
+		private String paramStr;
+        @SerializedName("_count")
 		private String count;
-		
-		public QuestCondition(QuestParam param) {
-			this.type = param._type;
-			this.param = param._param;
-		}
-		
-		public QuestTrigger getType() {
-			return type;
-		}
-		
-		public int[] getParam() {
-			return param;
-		}
-
-		public String getCount() {
-			return count;
-		}
+
 	}
 }
diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java
index 2f412fba..a40a6ac5 100644
--- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java
+++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java
@@ -271,6 +271,8 @@ public class Inventory implements Iterable<GameItem> {
                 this.player.getServer().getInventoryManager().upgradeAvatarFetterLevel(this.player, this.player.getTeamManager().getCurrentAvatarEntity().getAvatar(), count);
             case 106 -> // Resin
                 this.player.getResinManager().addResin(count);
+            case 107 ->  // Legendary Key
+                this.player.addLegendaryKey(count);
             case 201 -> // Primogem
                 this.player.setPrimogems(this.player.getPrimogems() + count);
             case 202 -> // Mora
@@ -292,6 +294,8 @@ public class Inventory implements Iterable<GameItem> {
 				return this.player.getCrystals();
 			case 106:  // Resin
 				return this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN);
+            case 107:  // Legendary Key
+                return this.player.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY);
 			case 204:  // Home Coin
 				return this.player.getHomeCoin();
 			default:
@@ -334,13 +338,15 @@ public class Inventory implements Iterable<GameItem> {
 					player.setCrystals(player.getCrystals() - (cost.getCount() * quantity));
 				case 106 ->  // Resin
 					player.getResinManager().useResin(cost.getCount() * quantity);
+                case 107 ->  // LegendaryKey
+                    player.useLegendaryKey(cost.getCount() * quantity);
 				case 204 ->  // Home Coin
 						player.setHomeCoin(player.getHomeCoin() - (cost.getCount() * quantity));
 				default ->
 					removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount() * quantity);
 			}
 		}
-		
+
 		if (reason != null) {  // Do we need these?
 			// getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason));
 		}
diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java
index 24574269..ec101ea8 100644
--- a/src/main/java/emu/grasscutter/game/player/Player.java
+++ b/src/main/java/emu/grasscutter/game/player/Player.java
@@ -4,10 +4,10 @@ import dev.morphia.annotations.*;
 import emu.grasscutter.GameConstants;
 import emu.grasscutter.Grasscutter;
 import emu.grasscutter.data.GameData;
+import emu.grasscutter.data.excels.PersonalLineData;
 import emu.grasscutter.data.excels.PlayerLevelData;
 import emu.grasscutter.data.excels.WeatherData;
 import emu.grasscutter.database.DatabaseHelper;
-import emu.grasscutter.database.DatabaseManager;
 import emu.grasscutter.game.Account;
 import emu.grasscutter.game.CoopRequest;
 import emu.grasscutter.game.ability.AbilityManager;
@@ -1597,7 +1597,17 @@ public class Player {
 		getServer().getPlayers().values().removeIf(player1 -> player1 == this);
 	}
 
-	public enum SceneLoadState {
+    public int getLegendaryKey() {
+        return this.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY);
+    }
+    public synchronized void addLegendaryKey(int count) {
+        this.setProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY, getLegendaryKey() + count);
+    }
+    public synchronized void useLegendaryKey(int count) {
+        this.setProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY, getLegendaryKey() - count);
+    }
+
+    public enum SceneLoadState {
 		NONE(0), LOADING(1), INIT(2), LOADED(3);
 
 		private final int value;
diff --git a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java
index 30f3ce14..4106a4b9 100644
--- a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java
+++ b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java
@@ -39,7 +39,7 @@ public enum PlayerProperty {
     PROP_IS_ONLY_MP_WITH_PS_PLAYER			(10024, 0, 1), // Is only MP with PlayStation players? [0, 1]
     PROP_PLAYER_MCOIN						(10025), // Genesis Crystal (-inf, +inf) see 10015
     PROP_PLAYER_WAIT_SUB_MCOIN				(10026),
-    PROP_PLAYER_LEGENDARY_KEY				(10027),
+    PROP_PLAYER_LEGENDARY_KEY				(10027,0),
     PROP_IS_HAS_FIRST_SHARE					(10028),
     PROP_PLAYER_FORGE_POINT					(10029, 0, 300_000),
     PROP_CUR_CLIMATE_METER					(10035),
diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java
index 4dc31a5a..9117be4c 100644
--- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java
+++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java
@@ -1,7 +1,6 @@
 package emu.grasscutter.game.quest;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
 import org.bson.types.ObjectId;
@@ -31,20 +30,21 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 @Entity(value = "quests", useDiscriminator = false)
 public class GameMainQuest {
 	@Id private ObjectId id;
-	
+
 	@Indexed private int ownerUid;
 	@Transient private Player owner;
-	
+
 	private Map<Integer, GameQuest> childQuests;
-	
+
 	private int parentQuestId;
 	private int[] questVars;
 	private ParentQuestState state;
 	private boolean isFinished;
-	
+    List<QuestGroupSuite> questGroupSuites;
+
 	@Deprecated // Morphia only. Do not use.
 	public GameMainQuest() {}
-	
+
 	public GameMainQuest(Player player, int parentQuestId) {
 		this.owner = player;
 		this.ownerUid = player.getUid();
@@ -52,12 +52,13 @@ public class GameMainQuest {
 		this.childQuests = new HashMap<>();
 		this.questVars = new int[5];
 		this.state = ParentQuestState.PARENT_QUEST_STATE_NONE;
+        this.questGroupSuites = new ArrayList<>();
 	}
 
 	public int getParentQuestId() {
 		return parentQuestId;
 	}
-	
+
 	public int getOwnerUid() {
 		return ownerUid;
 	}
@@ -74,7 +75,7 @@ public class GameMainQuest {
 	public Map<Integer, GameQuest> getChildQuests() {
 		return childQuests;
 	}
-	
+
 	public GameQuest getChildQuestById(int id) {
 		return this.getChildQuests().get(id);
 	}
@@ -91,26 +92,36 @@ public class GameMainQuest {
 		return isFinished;
 	}
 
-	public void finish() {
+    public List<QuestGroupSuite> getQuestGroupSuites() {
+        return questGroupSuites;
+    }
+
+    public void finish() {
 		this.isFinished = true;
 		this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED;
-		
+
 		this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this));
 		this.getOwner().getSession().send(new PacketCodexDataUpdateNotify(this));
-		
+
 		this.save();
-		
+
 		// Add rewards
 		MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(this.getParentQuestId());
 		for (int rewardId : mainQuestData.getRewardIdList()) {
 			RewardData rewardData = GameData.getRewardDataMap().get(rewardId);
-			
+
 			if (rewardData == null) {
 				continue;
 			}
-			
+
 			getOwner().getInventory().addItemParamDatas(rewardData.getRewardItemList(), ActionReason.QuestReward);
 		}
+
+        // handoff main quest
+        if(mainQuestData.getSuggestTrackMainQuestList() != null){
+            Arrays.stream(mainQuestData.getSuggestTrackMainQuestList())
+                .forEach(getOwner().getQuestManager()::startMainQuest);
+        }
 	}
 
 	public void save() {
@@ -122,16 +133,16 @@ public class GameMainQuest {
 				.setParentQuestId(getParentQuestId())
 				.setIsFinished(isFinished())
 				.setParentQuestState(getState().getValue());
-		
+
 		for (GameQuest quest : this.getChildQuests().values()) {
 			ChildQuest childQuest = ChildQuest.newBuilder()
 					.setQuestId(quest.getQuestId())
 					.setState(quest.getState().getValue())
 					.build();
-			
+
 			proto.addChildQuestList(childQuest);
 		}
-		
+
 		if (getQuestVars() != null) {
 			for (int i : getQuestVars()) {
 				proto.addQuestVar(i);
diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java
index 57d8655f..7d1fe5ad 100644
--- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java
+++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java
@@ -6,13 +6,16 @@ import emu.grasscutter.Grasscutter;
 import emu.grasscutter.data.GameData;
 import emu.grasscutter.data.binout.MainQuestData;
 import emu.grasscutter.data.binout.MainQuestData.SubQuestData;
+import emu.grasscutter.data.excels.ChapterData;
 import emu.grasscutter.data.excels.QuestData;
 import emu.grasscutter.data.excels.QuestData.QuestCondition;
 import emu.grasscutter.game.player.Player;
 import emu.grasscutter.game.quest.enums.LogicType;
 import emu.grasscutter.game.quest.enums.QuestState;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.net.proto.ChapterStateOuterClass;
 import emu.grasscutter.net.proto.QuestOuterClass.Quest;
-import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
+import emu.grasscutter.server.packet.send.PacketChapterStateNotify;
 import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
 import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
 import emu.grasscutter.utils.Utils;
@@ -21,21 +24,21 @@ import emu.grasscutter.utils.Utils;
 public class GameQuest {
 	@Transient private GameMainQuest mainQuest;
 	@Transient private QuestData questData;
-	
+
 	private int questId;
 	private int mainQuestId;
 	private QuestState state;
-	
+
 	private int startTime;
 	private int acceptTime;
 	private int finishTime;
-	
+
 	private int[] finishProgressList;
 	private int[] failProgressList;
-	
+
 	@Deprecated // Morphia only. Do not use.
 	public GameQuest() {}
-	
+
 	public GameQuest(GameMainQuest mainQuest, QuestData questData) {
 		this.mainQuest = mainQuest;
 		this.questId = questData.getId();
@@ -44,18 +47,31 @@ public class GameQuest {
 		this.acceptTime = Utils.getCurrentSeconds();
 		this.startTime = this.acceptTime;
 		this.state = QuestState.QUEST_STATE_UNFINISHED;
-		
-		if (questData.getFinishCond() != null && questData.getAcceptCond().length != 0) {
-			this.finishProgressList = new int[questData.getFinishCond().length];
+
+		if (questData.getFinishCond() != null && questData.getAcceptCond().size() != 0) {
+			this.finishProgressList = new int[questData.getFinishCond().size()];
 		}
 
-		if (questData.getFailCond() != null && questData.getFailCond().length != 0) {
-			this.failProgressList = new int[questData.getFailCond().length];
+		if (questData.getFailCond() != null && questData.getFailCond().size() != 0) {
+			this.failProgressList = new int[questData.getFailCond().size()];
 		}
-		
+
 		this.mainQuest.getChildQuests().put(this.questId, this);
+
+        this.getData().getBeginExec().forEach(e -> getOwner().getServer().getQuestHandler().triggerExec(this, e, e.getParam()));
+
+        this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.questId, this.state.getValue());
+
+        if (ChapterData.beginQuestChapterMap.containsKey(questId)){
+            mainQuest.getOwner().sendPacket(new PacketChapterStateNotify(
+                ChapterData.beginQuestChapterMap.get(questId).getId(),
+                ChapterStateOuterClass.ChapterState.CHAPTER_STATE_BEGIN
+            ));
+        }
+
+        Grasscutter.getLogger().debug("Quest {} is started", questId);
 	}
-	
+
 	public GameMainQuest getMainQuest() {
 		return mainQuest;
 	}
@@ -63,7 +79,7 @@ public class GameQuest {
 	public void setMainQuest(GameMainQuest mainQuest) {
 		this.mainQuest = mainQuest;
 	}
-	
+
 	public Player getOwner() {
 		return getMainQuest().getOwner();
 	}
@@ -116,11 +132,11 @@ public class GameQuest {
 	public void setFinishTime(int finishTime) {
 		this.finishTime = finishTime;
 	}
-	
+
 	public int[] getFinishProgressList() {
 		return finishProgressList;
 	}
-	
+
 	public void setFinishProgress(int index, int value) {
 		finishProgressList[index] = value;
 	}
@@ -128,7 +144,7 @@ public class GameQuest {
 	public int[] getFailProgressList() {
 		return failProgressList;
 	}
-	
+
 	public void setFailProgress(int index, int value) {
 		failProgressList[index] = value;
 	}
@@ -136,16 +152,16 @@ public class GameQuest {
 	public void finish() {
 		this.state = QuestState.QUEST_STATE_FINISHED;
 		this.finishTime = Utils.getCurrentSeconds();
-		
+
 		if (this.getFinishProgressList() != null) {
 			for (int i = 0 ; i < getFinishProgressList().length; i++) {
 				getFinishProgressList()[i] = 1;
 			}
 		}
-		
+
 		this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this));
 		this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this));
-		
+
 		if (this.getData().finishParent()) {
 			// This quest finishes the questline - the main quest will also save the quest to db so we dont have to call save() here
 			this.getMainQuest().finish();
@@ -154,8 +170,21 @@ public class GameQuest {
 			this.tryAcceptQuestLine();
 			this.save();
 		}
+
+        this.getData().getFinishExec().forEach(e -> getOwner().getServer().getQuestHandler().triggerExec(this, e, e.getParam()));
+
+        this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.questId, this.state.getValue());
+
+        if (ChapterData.endQuestChapterMap.containsKey(questId)){
+            mainQuest.getOwner().sendPacket(new PacketChapterStateNotify(
+                ChapterData.endQuestChapterMap.get(questId).getId(),
+                ChapterStateOuterClass.ChapterState.CHAPTER_STATE_END
+            ));
+        }
+
+        Grasscutter.getLogger().debug("Quest {} is finished", questId);
 	}
-	
+
 	public boolean tryAcceptQuestLine() {
 		try {
 			MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId());
@@ -167,16 +196,17 @@ public class GameQuest {
 					QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId());
 
 					if (questData == null || questData.getAcceptCond() == null
-							|| questData.getAcceptCond().length == 0) {
+							|| questData.getAcceptCond().size() == 0) {
 						continue;
 					}
 
-					int[] accept = new int[questData.getAcceptCond().length];
+					int[] accept = new int[questData.getAcceptCond().size()];
 
 					// TODO
-					for (int i = 0; i < questData.getAcceptCond().length; i++) {
-						QuestCondition condition = questData.getAcceptCond()[i];
+					for (int i = 0; i < questData.getAcceptCond().size(); i++) {
+						QuestCondition condition = questData.getAcceptCond().get(i);
 						boolean result = getOwner().getServer().getQuestHandler().triggerCondition(this, condition,
+                                condition.getParamStr(),
 								condition.getParam());
 
 						accept[i] = result ? 1 : 0;
@@ -195,11 +225,11 @@ public class GameQuest {
 
 		return false;
 	}
-	
+
 	public void save() {
 		getMainQuest().save();
 	}
-	
+
 	public Quest toProto() {
 		Quest.Builder proto = Quest.newBuilder()
 				.setQuestId(this.getQuestId())
@@ -208,19 +238,19 @@ public class GameQuest {
 				.setStartTime(this.getStartTime())
 				.setStartGameTime(438)
 				.setAcceptTime(this.getAcceptTime());
-		
+
 		if (this.getFinishProgressList() != null) {
 			for (int i : this.getFinishProgressList()) {
 				proto.addFinishProgressList(i);
 			}
 		}
-		
+
 		if (this.getFailProgressList() != null) {
 			for (int i : this.getFailProgressList()) {
 				proto.addFailProgressList(i);
 			}
 		}
-		
+
 		return proto.build();
 	}
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/QuestGroupSuite.java b/src/main/java/emu/grasscutter/game/quest/QuestGroupSuite.java
new file mode 100644
index 00000000..9865f6d1
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/QuestGroupSuite.java
@@ -0,0 +1,17 @@
+package emu.grasscutter.game.quest;
+
+import dev.morphia.annotations.Entity;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.FieldDefaults;
+
+@Entity
+@Data
+@Builder(builderMethodName = "of")
+@FieldDefaults(level = AccessLevel.PRIVATE)
+public class QuestGroupSuite {
+    int scene;
+    int group;
+    int suite;
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java
index 838cb301..d9858699 100644
--- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java
+++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java
@@ -1,29 +1,28 @@
 package emu.grasscutter.game.quest;
 
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
+import emu.grasscutter.Grasscutter;
 import emu.grasscutter.data.GameData;
+import emu.grasscutter.data.binout.MainQuestData;
 import emu.grasscutter.data.excels.QuestData;
 import emu.grasscutter.data.excels.QuestData.QuestCondition;
 import emu.grasscutter.database.DatabaseHelper;
 import emu.grasscutter.game.player.Player;
+import emu.grasscutter.game.quest.enums.ParentQuestState;
 import emu.grasscutter.game.quest.enums.QuestTrigger;
 import emu.grasscutter.game.quest.enums.LogicType;
 import emu.grasscutter.game.quest.enums.QuestState;
-import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
-import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
-import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
-import emu.grasscutter.server.packet.send.PacketServerCondMeetQuestListUpdateNotify;
+import emu.grasscutter.server.packet.send.*;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 
 public class QuestManager {
 	private final Player player;
 	private final Int2ObjectMap<GameMainQuest> quests;
-	
+
 	public QuestManager(Player player) {
 		this.player = player;
 		this.quests = new Int2ObjectOpenHashMap<>();
@@ -122,40 +121,56 @@ public class QuestManager {
 
 		return quest;
 	}
-	
-	public void triggerEvent(QuestTrigger condType, int... params) {
+    public void startMainQuest(int mainQuestId){
+        var mainQuestData = GameData.getMainQuestDataMap().get(mainQuestId);
+
+        if (mainQuestData == null){
+            return;
+        }
+
+        Arrays.stream(mainQuestData.getSubQuests())
+            .min(Comparator.comparingInt(MainQuestData.SubQuestData::getOrder))
+            .map(MainQuestData.SubQuestData::getSubId)
+            .ifPresent(this::addQuest);
+    }
+    public void triggerEvent(QuestTrigger condType, int... params) {
+        triggerEvent(condType, "", params);
+    }
+
+	public void triggerEvent(QuestTrigger condType, String paramStr, int... params) {
+        Grasscutter.getLogger().debug("Trigger Event {}, {}, {}", condType, paramStr, params);
 		Set<GameQuest> changedQuests = new HashSet<>();
-		
+
 		this.forEachActiveQuest(quest -> {
 			QuestData data = quest.getData();
-			
-			for (int i = 0; i < data.getFinishCond().length; i++) {
-				if (quest.getFinishProgressList() == null 
+
+			for (int i = 0; i < data.getFinishCond().size(); i++) {
+				if (quest.getFinishProgressList() == null
 					|| quest.getFinishProgressList().length == 0
 					|| quest.getFinishProgressList()[i] == 1) {
 					continue;
 				}
-				
-				QuestCondition condition = data.getFinishCond()[i];
-				
+
+				QuestCondition condition = data.getFinishCond().get(i);
+
 				if (condition.getType() != condType) {
 					continue;
 				}
-				
-				boolean result = getPlayer().getServer().getQuestHandler().triggerContent(quest, condition, params);
-				
+
+				boolean result = getPlayer().getServer().getQuestHandler().triggerContent(quest, condition, paramStr, params);
+
 				if (result) {
 					quest.getFinishProgressList()[i] = 1;
-					
+
 					changedQuests.add(quest);
 				}
 			}
 		});
-		
+
 		for (GameQuest quest : changedQuests) {
 			LogicType logicType = quest.getData().getFailCondComb();
 			int[] progress = quest.getFinishProgressList();
-			
+
 			// Handle logical comb
 			boolean finish = LogicType.calculate(logicType, progress);
 
@@ -169,6 +184,15 @@ public class QuestManager {
 		}
 	}
 
+    public List<QuestGroupSuite> getSceneGroupSuite(int sceneId) {
+        return getQuests().values().stream()
+            .filter(i -> i.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED)
+            .map(GameMainQuest::getQuestGroupSuites)
+            .filter(Objects::nonNull)
+            .flatMap(Collection::stream)
+            .filter(i -> i.getScene() == sceneId)
+            .toList();
+    }
 	public void loadFromDatabase() {
 		List<GameMainQuest> quests = DatabaseHelper.getAllQuests(getPlayer());
 		
diff --git a/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java b/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java
index b48194dd..19c77947 100644
--- a/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java
+++ b/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java
@@ -2,10 +2,12 @@ package emu.grasscutter.game.quest;
 
 import java.util.Set;
 
+import emu.grasscutter.data.excels.QuestData;
+import emu.grasscutter.game.quest.handlers.QuestExecHandler;
 import org.reflections.Reflections;
 
 import emu.grasscutter.Grasscutter;
-import emu.grasscutter.data.excels.QuestData.QuestCondition;
+import emu.grasscutter.data.excels.QuestData.*;
 import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -14,32 +16,32 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 public class ServerQuestHandler {
 	private final Int2ObjectMap<QuestBaseHandler> condHandlers;
 	private final Int2ObjectMap<QuestBaseHandler> contHandlers;
-	private final Int2ObjectMap<QuestBaseHandler> execHandlers;
-	
+	private final Int2ObjectMap<QuestExecHandler> execHandlers;
+
 	public ServerQuestHandler() {
-		this.condHandlers = new Int2ObjectOpenHashMap<>(); 
+		this.condHandlers = new Int2ObjectOpenHashMap<>();
 		this.contHandlers = new Int2ObjectOpenHashMap<>();
 		this.execHandlers = new Int2ObjectOpenHashMap<>();
-		
+
 		this.registerHandlers();
 	}
 
 	public void registerHandlers() {
-		this.registerHandlers(this.condHandlers, "emu.grasscutter.game.quest.conditions");
-		this.registerHandlers(this.contHandlers, "emu.grasscutter.game.quest.content");
-		this.registerHandlers(this.execHandlers, "emu.grasscutter.game.quest.exec");
+		this.registerHandlers(this.condHandlers, "emu.grasscutter.game.quest.conditions", QuestBaseHandler.class);
+		this.registerHandlers(this.contHandlers, "emu.grasscutter.game.quest.content", QuestBaseHandler.class);
+		this.registerHandlers(this.execHandlers, "emu.grasscutter.game.quest.exec", QuestExecHandler.class);
 	}
-	
-	public void registerHandlers(Int2ObjectMap<QuestBaseHandler> map, String packageName) {
+
+	public <T> void registerHandlers(Int2ObjectMap<T> map, String packageName, Class<T> clazz) {
 		Reflections reflections = new Reflections(packageName);
-		Set<?> handlerClasses = reflections.getSubTypesOf(QuestBaseHandler.class);
-		
-		for (Object obj : handlerClasses) {
-			this.registerPacketHandler(map, (Class<? extends QuestBaseHandler>) obj);
+		var handlerClasses = reflections.getSubTypesOf(clazz);
+
+		for (var obj : handlerClasses) {
+			this.registerPacketHandler(map, obj);
 		}
 	}
 
-	public void registerPacketHandler(Int2ObjectMap<QuestBaseHandler> map, Class<? extends QuestBaseHandler> handlerClass) {
+	public <T> void registerPacketHandler(Int2ObjectMap<T> map, Class<? extends T> handlerClass) {
 		try {
 			QuestValue opcode = handlerClass.getAnnotation(QuestValue.class);
 
@@ -47,43 +49,44 @@ public class ServerQuestHandler {
 				return;
 			}
 
-			QuestBaseHandler packetHandler = (QuestBaseHandler) handlerClass.newInstance();
-
-			map.put(opcode.value().getValue(), packetHandler);
+			map.put(opcode.value().getValue(), handlerClass.newInstance());
 		} catch (Exception e) {
 			e.printStackTrace();
 		}
 	}
-	
+
 	// TODO make cleaner
-	
-	public boolean triggerCondition(GameQuest quest, QuestCondition condition, int... params) {
+
+	public boolean triggerCondition(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		QuestBaseHandler handler = condHandlers.get(condition.getType().getValue());
 
 		if (handler == null || quest.getData() == null) {
+            Grasscutter.getLogger().debug("Could not trigger condition {} at {}", condition.getType().getValue(), quest.getData());
 			return false;
 		}
-		
-		return handler.execute(quest, condition, params);
+
+		return handler.execute(quest, condition, paramStr, params);
 	}
-	
-	public boolean triggerContent(GameQuest quest, QuestCondition condition, int... params) {
+
+	public boolean triggerContent(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		QuestBaseHandler handler = contHandlers.get(condition.getType().getValue());
 
 		if (handler == null || quest.getData() == null) {
+            Grasscutter.getLogger().debug("Could not trigger content {} at {}", condition.getType().getValue(), quest.getData());
 			return false;
 		}
-		
-		return handler.execute(quest, condition, params);
+
+		return handler.execute(quest, condition, paramStr, params);
 	}
-	
-	public boolean triggerExec(GameQuest quest, QuestCondition condition, int... params) {
-		QuestBaseHandler handler = execHandlers.get(condition.getType().getValue());
+
+	public boolean triggerExec(GameQuest quest, QuestExecParam execParam, String... params) {
+		QuestExecHandler handler = execHandlers.get(execParam.getType().getValue());
 
 		if (handler == null || quest.getData() == null) {
+            Grasscutter.getLogger().debug("Could not trigger exec {} at {}", execParam.getType().getValue(), quest.getData());
 			return false;
 		}
-		
-		return handler.execute(quest, condition, params);
+
+		return handler.execute(quest, execParam, params);
 	}
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java b/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java
index 86df72b3..e3efe6ed 100644
--- a/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java
+++ b/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java
@@ -10,9 +10,9 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
 public class BaseCondition extends QuestBaseHandler {
 
 	@Override
-	public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		// TODO Auto-generated method stub
 		return false;
 	}
-	
+
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionLuaNotify.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionLuaNotify.java
new file mode 100644
index 00000000..8d3fd541
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionLuaNotify.java
@@ -0,0 +1,17 @@
+package emu.grasscutter.game.quest.conditions;
+
+import emu.grasscutter.data.excels.QuestData.QuestCondition;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
+
+@QuestValue(QuestTrigger.QUEST_COND_LUA_NOTIFY)
+public class ConditionLuaNotify extends QuestBaseHandler {
+
+	@Override
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
+        return condition.getParam()[0] == Integer.parseInt(paramStr);
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java
index e15ce8ed..cb19e887 100644
--- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java
+++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java
@@ -10,8 +10,8 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
 public class ConditionPlayerLevelEqualGreater extends QuestBaseHandler {
 
 	@Override
-	public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		return quest.getOwner().getLevel() >= params[0];
 	}
-	
+
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java
index bcf90145..c993eebb 100644
--- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java
+++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java
@@ -10,14 +10,14 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
 public class ConditionStateEqual extends QuestBaseHandler {
 
 	@Override
-	public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]);
-		
+
 		if (checkQuest != null) {
 			return checkQuest.getState().getValue() == params[1];
 		}
-		
-		return false; 
+
+		return false;
 	}
-	
+
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java b/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java
index e2c7abdc..f0db20a5 100644
--- a/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java
+++ b/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java
@@ -10,9 +10,9 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
 public class BaseContent extends QuestBaseHandler {
 
 	@Override
-	public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		// TODO Auto-generated method stub
 		return false;
 	}
-	
+
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentAddQuestProgress.java b/src/main/java/emu/grasscutter/game/quest/content/ContentAddQuestProgress.java
new file mode 100644
index 00000000..a49d067f
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentAddQuestProgress.java
@@ -0,0 +1,17 @@
+package emu.grasscutter.game.quest.content;
+
+import emu.grasscutter.data.excels.QuestData.QuestCondition;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
+
+@QuestValue(QuestTrigger.QUEST_CONTENT_ADD_QUEST_PROGRESS)
+public class ContentAddQuestProgress extends QuestBaseHandler {
+
+	@Override
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
+		return condition.getParam()[0] == params[0];
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java
index 3c17bccd..76819f47 100644
--- a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java
@@ -10,8 +10,8 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
 public class ContentCompleteTalk extends QuestBaseHandler {
 
 	@Override
-	public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		return condition.getParam()[0] == params[0];
 	}
-	
+
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java
index e331f48e..4dc29160 100644
--- a/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java
@@ -10,8 +10,8 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
 public class ContentEnterDungeon extends QuestBaseHandler {
 
 	@Override
-	public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		return condition.getParam()[0] == params[0];
 	}
-	
+
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentEnterRoom.java b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterRoom.java
new file mode 100644
index 00000000..e140eb3c
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterRoom.java
@@ -0,0 +1,17 @@
+package emu.grasscutter.game.quest.content;
+
+import emu.grasscutter.data.excels.QuestData.QuestCondition;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
+
+@QuestValue(QuestTrigger.QUEST_CONTENT_ENTER_ROOM)
+public class ContentEnterRoom extends QuestBaseHandler {
+
+	@Override
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
+		return condition.getParam()[0] == params[0];
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java
index 30580811..f3040b18 100644
--- a/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java
@@ -10,8 +10,8 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
 public class ContentFinishPlot extends QuestBaseHandler {
 
 	@Override
-	public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
 		return condition.getParam()[0] == params[0];
 	}
-	
+
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentGameTimeTick.java b/src/main/java/emu/grasscutter/game/quest/content/ContentGameTimeTick.java
new file mode 100644
index 00000000..f28f5718
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentGameTimeTick.java
@@ -0,0 +1,23 @@
+package emu.grasscutter.game.quest.content;
+
+import emu.grasscutter.data.excels.QuestData.QuestCondition;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
+
+@QuestValue(QuestTrigger.QUEST_CONTENT_GAME_TIME_TICK)
+public class ContentGameTimeTick extends QuestBaseHandler {
+
+	@Override
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
+        var range = condition.getParamStr().split(",");
+        var min = Math.min(Integer.parseInt(range[0]), Integer.parseInt(range[1]));
+        var max = Math.max(Integer.parseInt(range[0]), Integer.parseInt(range[1]));
+
+        // params[0] is clock, params[1] is day
+		return params[0] >= min && params[0] <= max &&
+            params[1] >= condition.getParam()[0];
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentInteractGadget.java b/src/main/java/emu/grasscutter/game/quest/content/ContentInteractGadget.java
new file mode 100644
index 00000000..975a7f22
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentInteractGadget.java
@@ -0,0 +1,17 @@
+package emu.grasscutter.game.quest.content;
+
+import emu.grasscutter.data.excels.QuestData.QuestCondition;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
+
+@QuestValue(QuestTrigger.QUEST_CONTENT_INTERACT_GADGET)
+public class ContentInteractGadget extends QuestBaseHandler {
+
+	@Override
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
+		return params[0] == condition.getParam()[0];
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentLuaNotify.java b/src/main/java/emu/grasscutter/game/quest/content/ContentLuaNotify.java
new file mode 100644
index 00000000..f8ca181d
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentLuaNotify.java
@@ -0,0 +1,17 @@
+package emu.grasscutter.game.quest.content;
+
+import emu.grasscutter.data.excels.QuestData.QuestCondition;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
+
+@QuestValue(QuestTrigger.QUEST_CONTENT_LUA_NOTIFY)
+public class ContentLuaNotify extends QuestBaseHandler {
+
+	@Override
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
+		return condition.getParamStr().equals(paramStr);
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateEqual.java b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateEqual.java
new file mode 100644
index 00000000..511e7e19
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateEqual.java
@@ -0,0 +1,23 @@
+package emu.grasscutter.game.quest.content;
+
+import emu.grasscutter.data.excels.QuestData.QuestCondition;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
+
+@QuestValue(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL)
+public class ContentQuestStateEqual extends QuestBaseHandler {
+
+	@Override
+	public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
+        GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]);
+
+        if (checkQuest != null) {
+            return checkQuest.getState().getValue() == params[1];
+        }
+
+        return false;
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java
index def3a399..c9c3eb88 100644
--- a/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java
+++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java
@@ -1,5 +1,12 @@
 package emu.grasscutter.game.quest.enums;
 
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
 public enum QuestTrigger {
 	QUEST_COND_NONE (0),
 	QUEST_COND_STATE_EQUAL (1),
@@ -79,7 +86,7 @@ public enum QuestTrigger {
 	QUEST_COND_NEW_HOMEWORLD_SHOP_ITEM (75),
 	QUEST_COND_SCENE_POINT_UNLOCK (76),
 	QUEST_COND_SCENE_LEVEL_TAG_EQ (77),
-	
+
 	QUEST_CONTENT_NONE (0),
 	QUEST_CONTENT_KILL_MONSTER (1),
 	QUEST_CONTENT_COMPLETE_TALK (2),
@@ -153,7 +160,7 @@ public enum QuestTrigger {
 	QUEST_CONTENT_IRODORI_FINISH_FLOWER_COMBINATION (151),
 	QUEST_CONTENT_IRODORI_POETRY_REACH_MIN_PROGRESS (152),
 	QUEST_CONTENT_IRODORI_POETRY_FINISH_FILL_POETRY (153),
-	
+
 	QUEST_EXEC_NONE (0),
 	QUEST_EXEC_DEL_PACK_ITEM (1),
 	QUEST_EXEC_UNLOCK_POINT (2),
@@ -222,9 +229,9 @@ public enum QuestTrigger {
 	QUEST_EXEC_LOCK_PLAYER_WORLD_SCENE (66),
 	QUEST_EXEC_FAIL_MAINCOOP (67),
 	QUEST_EXEC_MODIFY_WEATHER_AREA (68);
-	
+
 	private final int value;
-	
+
 	QuestTrigger(int id) {
 		this.value = id;
 	}
@@ -232,4 +239,24 @@ public enum QuestTrigger {
 	public int getValue() {
 		return value;
 	}
+
+    private static final Int2ObjectMap<QuestTrigger> contentMap = new Int2ObjectOpenHashMap<>();
+    private static final Map<String, QuestTrigger> contentStringMap = new HashMap<>();
+
+    static {
+        Stream.of(values())
+            .filter(e -> e.name().startsWith("QUEST_CONTENT_"))
+            .forEach(e -> {
+            contentMap.put(e.getValue(), e);
+            contentStringMap.put(e.name(), e);
+        });
+    }
+
+    public static QuestTrigger getContentTriggerByValue(int value) {
+        return contentMap.getOrDefault(value, QUEST_CONTENT_NONE);
+    }
+
+    public static QuestTrigger getContentTriggerByName(String name) {
+        return contentStringMap.getOrDefault(name, QUEST_CONTENT_NONE);
+    }
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecAddQuestProgress.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecAddQuestProgress.java
new file mode 100644
index 00000000..57d1f545
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecAddQuestProgress.java
@@ -0,0 +1,24 @@
+package emu.grasscutter.game.quest.exec;
+
+import emu.grasscutter.data.excels.QuestData;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestExecHandler;
+
+import java.util.Arrays;
+
+@QuestValue(QuestTrigger.QUEST_EXEC_ADD_QUEST_PROGRESS)
+public class ExecAddQuestProgress extends QuestExecHandler {
+    @Override
+    public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
+        var param = Arrays.stream(paramStr)
+            .filter(i -> !i.isBlank())
+            .mapToInt(Integer::parseInt)
+            .toArray();
+
+        quest.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ADD_QUEST_PROGRESS, param);
+
+        return true;
+    }
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecNotifyGroupLua.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecNotifyGroupLua.java
new file mode 100644
index 00000000..df674fc4
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecNotifyGroupLua.java
@@ -0,0 +1,34 @@
+package emu.grasscutter.game.quest.exec;
+
+import emu.grasscutter.data.excels.QuestData;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestGroupSuite;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestState;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestExecHandler;
+import emu.grasscutter.scripts.constants.EventType;
+import emu.grasscutter.scripts.data.ScriptArgs;
+import emu.grasscutter.server.packet.send.PacketGroupSuiteNotify;
+
+@QuestValue(QuestTrigger.QUEST_EXEC_NOTIFY_GROUP_LUA)
+public class ExecNotifyGroupLua extends QuestExecHandler {
+
+    @Override
+    public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
+        var sceneId = Integer.parseInt(paramStr[0]);
+        var groupId = Integer.parseInt(paramStr[1]);
+
+        var scriptManager = quest.getOwner().getScene().getScriptManager();
+
+        if(quest.getOwner().getScene().getId() == sceneId){
+            scriptManager.callEvent(
+                quest.getState() == QuestState.QUEST_STATE_FINISHED ?
+                    EventType.EVENT_QUEST_FINISH : EventType.EVENT_QUEST_START
+                , new ScriptArgs());
+        }
+
+        return true;
+    }
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecRefreshGroupSuite.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecRefreshGroupSuite.java
new file mode 100644
index 00000000..c14e3c34
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecRefreshGroupSuite.java
@@ -0,0 +1,39 @@
+package emu.grasscutter.game.quest.exec;
+
+import emu.grasscutter.data.excels.QuestData;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.game.quest.QuestGroupSuite;
+import emu.grasscutter.game.quest.QuestValue;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.game.quest.handlers.QuestExecHandler;
+import emu.grasscutter.server.packet.send.PacketGroupSuiteNotify;
+
+import java.util.Arrays;
+
+@QuestValue(QuestTrigger.QUEST_EXEC_REFRESH_GROUP_SUITE)
+public class ExecRefreshGroupSuite extends QuestExecHandler {
+
+    @Override
+    public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
+        var sceneId = Integer.parseInt(paramStr[0]);
+        var groupId = Integer.parseInt(paramStr[1].split(",")[0]);
+        var suiteId = Integer.parseInt(paramStr[1].split(",")[1]);
+
+        var scriptManager = quest.getOwner().getScene().getScriptManager();
+
+        quest.getMainQuest().getQuestGroupSuites().add(QuestGroupSuite.of()
+            .scene(sceneId)
+            .group(groupId)
+            .suite(suiteId)
+            .build());
+
+        // refresh immediately if player is in this scene
+        if(quest.getOwner().getScene().getId() == sceneId){
+            scriptManager.refreshGroup(scriptManager.getGroupById(groupId), suiteId);
+            quest.getOwner().sendPacket(new PacketGroupSuiteNotify(groupId, suiteId));
+        }
+
+        return true;
+    }
+
+}
diff --git a/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java b/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java
index 815906f8..07702901 100644
--- a/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java
+++ b/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java
@@ -4,7 +4,7 @@ import emu.grasscutter.data.excels.QuestData.QuestCondition;
 import emu.grasscutter.game.quest.GameQuest;
 
 public abstract class QuestBaseHandler {
-	
-	public abstract boolean execute(GameQuest quest, QuestCondition condition, int... params);
-	
+
+	public abstract boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params);
+
 }
diff --git a/src/main/java/emu/grasscutter/game/quest/handlers/QuestExecHandler.java b/src/main/java/emu/grasscutter/game/quest/handlers/QuestExecHandler.java
new file mode 100644
index 00000000..378a5961
--- /dev/null
+++ b/src/main/java/emu/grasscutter/game/quest/handlers/QuestExecHandler.java
@@ -0,0 +1,10 @@
+package emu.grasscutter.game.quest.handlers;
+
+import emu.grasscutter.data.excels.QuestData;
+import emu.grasscutter.game.quest.GameQuest;
+
+public abstract class QuestExecHandler {
+
+	public abstract boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr);
+
+}
diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java
index ce4d9eaf..82f534e5 100644
--- a/src/main/java/emu/grasscutter/game/world/Scene.java
+++ b/src/main/java/emu/grasscutter/game/world/Scene.java
@@ -13,6 +13,7 @@ import emu.grasscutter.game.props.ClimateType;
 import emu.grasscutter.game.props.FightProperty;
 import emu.grasscutter.game.props.LifeState;
 import emu.grasscutter.game.props.SceneType;
+import emu.grasscutter.game.quest.QuestGroupSuite;
 import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
 import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
 import emu.grasscutter.net.packet.BasePacket;
@@ -798,4 +799,22 @@ public class Scene {
 		}
         return npcList;
 	}
+
+    public void loadGroupForQuest(List<QuestGroupSuite> sceneGroupSuite) {
+        if(!scriptManager.isInit()){
+            return;
+        }
+
+        sceneGroupSuite.forEach(i -> {
+            var group = scriptManager.getGroupById(i.getGroup());
+            if(group == null){
+                return;
+            }
+            var suite = group.getSuiteByIndex(i.getSuite());
+            if(suite == null){
+                return;
+            }
+            scriptManager.addGroupSuite(group, suite);
+        });
+    }
 }
diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java
index 39ee908b..946ec04e 100644
--- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java
+++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java
@@ -482,6 +482,11 @@ public class ScriptLib {
 		logger.debug("[LUA] Call AddQuestProgress with {}",
 				var1);
 
+        for(var player : getSceneScriptManager().getScene().getPlayers()){
+            player.getQuestManager().triggerEvent(QuestTrigger.QUEST_COND_LUA_NOTIFY, var1);
+            player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_LUA_NOTIFY, var1);
+        }
+
 		return 0;
 	}
 
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java
new file mode 100644
index 00000000..03bae4ac
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java
@@ -0,0 +1,23 @@
+package emu.grasscutter.server.packet.recv;
+
+import emu.grasscutter.game.quest.enums.QuestTrigger;
+import emu.grasscutter.net.packet.Opcodes;
+import emu.grasscutter.net.packet.PacketHandler;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass;
+import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp;
+
+@Opcodes(PacketOpcodes.AddQuestContentProgressReq)
+public class HandlerAddQuestContentProgressReq extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+        var req = AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq.parseFrom(payload);
+
+		session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.getContentTriggerByValue(req.getContentType()), req.getParam());
+
+        session.send(new PacketAddQuestContentProgressRsp(req.getContentType()));
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeGameTimeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeGameTimeReq.java
index dbd80ccf..17d66e60 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeGameTimeReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeGameTimeReq.java
@@ -1,5 +1,6 @@
 package emu.grasscutter.server.packet.recv;
 
+import emu.grasscutter.game.quest.enums.QuestTrigger;
 import emu.grasscutter.net.packet.Opcodes;
 import emu.grasscutter.net.packet.PacketOpcodes;
 import emu.grasscutter.net.proto.ChangeGameTimeReqOuterClass.ChangeGameTimeReq;
@@ -9,12 +10,15 @@ import emu.grasscutter.server.packet.send.PacketChangeGameTimeRsp;
 
 @Opcodes(PacketOpcodes.ChangeGameTimeReq)
 public class HandlerChangeGameTimeReq extends PacketHandler {
-	
+
 	@Override
 	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
 		ChangeGameTimeReq req = ChangeGameTimeReq.parseFrom(payload);
-		
+
 		session.getPlayer().getScene().changeTime(req.getGameTime());
+        session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_GAME_TIME_TICK,
+            req.getGameTime() / 60 , // hours
+            req.getExtraDays()); //days
 		session.getPlayer().sendPacket(new PacketChangeGameTimeRsp(session.getPlayer()));
 	}
 
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java
index be2d4ac6..b9b8c865 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java
@@ -32,6 +32,12 @@ public class HandlerEnterSceneDoneReq extends PacketHandler {
 
         // spawn NPC
         session.getPlayer().getScene().loadNpcForPlayerEnter(session.getPlayer());
+
+        // notify client to load the npc for quest
+        var questGroupSuites = session.getPlayer().getQuestManager().getSceneGroupSuite(session.getPlayer().getSceneId());
+        session.getPlayer().getScene().loadGroupForQuest(questGroupSuites);
+        session.send(new PacketGroupSuiteNotify(questGroupSuites));
+
 		// Reset timer for sending player locations
 		session.getPlayer().resetSendPlayerLocTime();
 	}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtEntityRenderersChangedNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtEntityRenderersChangedNotify.java
new file mode 100644
index 00000000..091bc00d
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtEntityRenderersChangedNotify.java
@@ -0,0 +1,30 @@
+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.EvtEntityRenderersChangedNotifyOuterClass;
+import emu.grasscutter.net.proto.ForwardTypeOuterClass;
+import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketEvtEntityRenderersChangedNotify;
+import emu.grasscutter.server.packet.send.PacketWorldPlayerLocationNotify;
+
+@Opcodes(PacketOpcodes.EvtEntityRenderersChangedNotify)
+public class HandlerEvtEntityRenderersChangedNotify extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+        var req = EvtEntityRenderersChangedNotifyOuterClass.EvtEntityRenderersChangedNotify.parseFrom(payload);
+
+        switch (req.getForwardType()) {
+            case FORWARD_TYPE_TO_ALL ->
+                session.getPlayer().getScene().broadcastPacket(new PacketEvtEntityRenderersChangedNotify(req));
+            case FORWARD_TYPE_TO_ALL_EXCEPT_CUR ->
+                session.getPlayer().getScene().broadcastPacketToOthers(session.getPlayer(), new PacketEvtEntityRenderersChangedNotify(req));
+            case FORWARD_TYPE_TO_HOST ->
+                session.getPlayer().getScene().getWorld().getHost().sendPacket(new PacketEvtEntityRenderersChangedNotify(req));
+        }
+
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java
index 9fa11697..9dadef7b 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGadgetInteractReq.java
@@ -1,5 +1,6 @@
 package emu.grasscutter.server.packet.recv;
 
+import emu.grasscutter.game.quest.enums.QuestTrigger;
 import emu.grasscutter.net.packet.Opcodes;
 import emu.grasscutter.net.packet.PacketOpcodes;
 import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
@@ -8,11 +9,12 @@ import emu.grasscutter.server.game.GameSession;
 
 @Opcodes(PacketOpcodes.GadgetInteractReq)
 public class HandlerGadgetInteractReq extends PacketHandler {
-	
+
 	@Override
 	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
 		GadgetInteractReq req = GadgetInteractReq.parseFrom(payload);
-		
+
+        session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_INTERACT_GADGET, req.getGadgetId());
 		session.getPlayer().interactWith(req.getGadgetEntityId(), req);
 	}
 
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPersonalLineAllDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPersonalLineAllDataReq.java
new file mode 100644
index 00000000..5552e63f
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPersonalLineAllDataReq.java
@@ -0,0 +1,17 @@
+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.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketPersonalLineAllDataRsp;
+
+@Opcodes(PacketOpcodes.PersonalLineAllDataReq)
+public class HandlerPersonalLineAllDataReq extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+        session.send(new PacketPersonalLineAllDataRsp(session.getPlayer().getQuestManager().getQuests().values()));
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java
index 9c74946b..746f8c1d 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java
@@ -1,5 +1,7 @@
 package emu.grasscutter.server.packet.recv;
 
+import emu.grasscutter.game.props.SceneType;
+import emu.grasscutter.game.quest.enums.QuestTrigger;
 import emu.grasscutter.net.packet.Opcodes;
 import emu.grasscutter.net.packet.PacketOpcodes;
 import emu.grasscutter.net.packet.PacketHandler;
@@ -8,9 +10,13 @@ import emu.grasscutter.server.packet.send.PacketPostEnterSceneRsp;
 
 @Opcodes(PacketOpcodes.PostEnterSceneReq)
 public class HandlerPostEnterSceneReq extends PacketHandler {
-	
+
 	@Override
 	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+        if(session.getPlayer().getScene().getSceneType() == SceneType.SCENE_ROOM){
+            session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId());
+        }
+
 		session.send(new PacketPostEnterSceneRsp(session.getPlayer()));
 	}
 
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerQueryPathReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerQueryPathReq.java
index aa8492b2..1184d5b1 100644
--- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerQueryPathReq.java
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerQueryPathReq.java
@@ -3,14 +3,21 @@ package emu.grasscutter.server.packet.recv;
 import emu.grasscutter.net.packet.Opcodes;
 import emu.grasscutter.net.packet.PacketOpcodes;
 import emu.grasscutter.net.packet.PacketHandler;
+import emu.grasscutter.net.proto.QueryPathReqOuterClass;
 import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketQueryPathRsp;
 
 @Opcodes(PacketOpcodes.QueryPathReq)
 public class HandlerQueryPathReq extends PacketHandler {
-	
+
 	@Override
 	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
-		// Auto template
+        var req = QueryPathReqOuterClass.QueryPathReq.parseFrom(payload);
+
+        /**
+         * It is not the actual work
+         */
+        session.send(new PacketQueryPathRsp(req));
 	}
 
 }
diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnlockPersonalLineReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnlockPersonalLineReq.java
new file mode 100644
index 00000000..480ebe4f
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnlockPersonalLineReq.java
@@ -0,0 +1,28 @@
+package emu.grasscutter.server.packet.recv;
+
+import emu.grasscutter.data.GameData;
+import emu.grasscutter.net.packet.Opcodes;
+import emu.grasscutter.net.packet.PacketHandler;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.UnlockPersonalLineReqOuterClass;
+import emu.grasscutter.server.game.GameSession;
+import emu.grasscutter.server.packet.send.PacketUnlockPersonalLineRsp;
+
+@Opcodes(PacketOpcodes.UnlockPersonalLineReq)
+public class HandlerUnlockPersonalLineReq extends PacketHandler {
+
+	@Override
+	public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
+        var req = UnlockPersonalLineReqOuterClass.UnlockPersonalLineReq.parseFrom(payload);
+        var data = GameData.getPersonalLineDataMap().get(req.getPersonalLineId());
+        if(data == null){
+            return;
+        }
+
+        session.getPlayer().getQuestManager().addQuest(data.getStartQuestId());
+        session.getPlayer().useLegendaryKey(1);
+
+        session.send(new PacketUnlockPersonalLineRsp(data.getId(), 1, data.getChapterId()));
+	}
+
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAddQuestContentProgressRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAddQuestContentProgressRsp.java
new file mode 100644
index 00000000..c803c046
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAddQuestContentProgressRsp.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.AddQuestContentProgressRspOuterClass;
+
+public class PacketAddQuestContentProgressRsp extends BasePacket {
+
+	public PacketAddQuestContentProgressRsp(int contentType) {
+		super(PacketOpcodes.AddQuestContentProgressRsp);
+
+        var proto = AddQuestContentProgressRspOuterClass.AddQuestContentProgressRsp.newBuilder();
+
+        proto.setContentType(contentType);
+
+        this.setData(proto);
+
+	}
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketChapterStateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketChapterStateNotify.java
new file mode 100644
index 00000000..4d60be2c
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketChapterStateNotify.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.ChapterStateNotifyOuterClass;
+import emu.grasscutter.net.proto.ChapterStateOuterClass;
+
+public class PacketChapterStateNotify extends BasePacket {
+
+	public PacketChapterStateNotify(int id, ChapterStateOuterClass.ChapterState state) {
+		super(PacketOpcodes.ChapterStateNotify);
+
+        var proto = ChapterStateNotifyOuterClass.ChapterStateNotify.newBuilder();
+
+        proto.setChapterId(id)
+            .setChapterState(state);
+
+        this.setData(proto);
+	}
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEvtEntityRenderersChangedNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEvtEntityRenderersChangedNotify.java
new file mode 100644
index 00000000..4df5c484
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEvtEntityRenderersChangedNotify.java
@@ -0,0 +1,14 @@
+package emu.grasscutter.server.packet.send;
+
+import emu.grasscutter.net.packet.BasePacket;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.EvtEntityRenderersChangedNotifyOuterClass;
+
+public class PacketEvtEntityRenderersChangedNotify extends BasePacket {
+
+	public PacketEvtEntityRenderersChangedNotify(EvtEntityRenderersChangedNotifyOuterClass.EvtEntityRenderersChangedNotify req) {
+		super(PacketOpcodes.EvtEntityRenderersChangedNotify, true);
+
+        this.setData(req);
+	}
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java
index 5a79b2ef..58adfdf0 100644
--- a/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGroupSuiteNotify.java
@@ -1,10 +1,12 @@
 package emu.grasscutter.server.packet.send;
 
 import emu.grasscutter.data.binout.SceneNpcBornEntry;
+import emu.grasscutter.game.quest.QuestGroupSuite;
 import emu.grasscutter.net.packet.BasePacket;
 import emu.grasscutter.net.packet.PacketOpcodes;
 import emu.grasscutter.net.proto.GroupSuiteNotifyOuterClass;
 
+import java.util.Collection;
 import java.util.List;
 
 public class PacketGroupSuiteNotify extends BasePacket {
@@ -27,4 +29,24 @@ public class PacketGroupSuiteNotify extends BasePacket {
 		this.setData(proto);
 
 	}
+
+    public PacketGroupSuiteNotify(int groupId, int suiteId) {
+        super(PacketOpcodes.GroupSuiteNotify);
+
+        var proto = GroupSuiteNotifyOuterClass.GroupSuiteNotify.newBuilder();
+
+        proto.putGroupMap(groupId, suiteId);
+
+        this.setData(proto);
+    }
+
+    public PacketGroupSuiteNotify(Collection<QuestGroupSuite> questGroupSuites) {
+        super(PacketOpcodes.GroupSuiteNotify);
+
+        var proto = GroupSuiteNotifyOuterClass.GroupSuiteNotify.newBuilder();
+
+        questGroupSuites.forEach(i -> proto.putGroupMap(i.getGroup(), i.getSuite()));
+
+        this.setData(proto);
+    }
 }
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPersonalLineAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPersonalLineAllDataRsp.java
new file mode 100644
index 00000000..6074c1cf
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPersonalLineAllDataRsp.java
@@ -0,0 +1,35 @@
+package emu.grasscutter.server.packet.send;
+
+import emu.grasscutter.data.GameData;
+import emu.grasscutter.game.quest.GameMainQuest;
+import emu.grasscutter.game.quest.GameQuest;
+import emu.grasscutter.net.packet.BasePacket;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.PersonalLineAllDataRspOuterClass;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class PacketPersonalLineAllDataRsp extends BasePacket {
+
+	public PacketPersonalLineAllDataRsp(Collection<GameMainQuest> gameMainQuestList) {
+		super(PacketOpcodes.PersonalLineAllDataRsp);
+
+        var proto = PersonalLineAllDataRspOuterClass.PersonalLineAllDataRsp.newBuilder();
+
+        var questList = gameMainQuestList.stream()
+            .map(GameMainQuest::getChildQuests)
+            .map(Map::values)
+            .flatMap(Collection::stream)
+            .map(GameQuest::getQuestId)
+            .collect(Collectors.toSet());
+
+        GameData.getPersonalLineDataMap().values().stream()
+            .filter(i -> !questList.contains(i.getStartQuestId()))
+            .forEach(i -> proto.addCanBeUnlockedPersonalLineList(i.getId()));
+
+        this.setData(proto);
+	}
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketQueryPathRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQueryPathRsp.java
new file mode 100644
index 00000000..6e95b5ec
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQueryPathRsp.java
@@ -0,0 +1,22 @@
+package emu.grasscutter.server.packet.send;
+
+import emu.grasscutter.net.packet.BasePacket;
+import emu.grasscutter.net.packet.PacketOpcodes;
+import emu.grasscutter.net.proto.QueryPathReqOuterClass;
+import emu.grasscutter.net.proto.QueryPathRspOuterClass;
+
+public class PacketQueryPathRsp extends BasePacket {
+
+	public PacketQueryPathRsp(QueryPathReqOuterClass.QueryPathReq req) {
+		super(PacketOpcodes.QueryPathRsp);
+
+        var proto = QueryPathRspOuterClass.QueryPathRsp.newBuilder();
+
+        proto.addCorners(req.getSourcePos())
+            .addCorners(req.getDestinationPos(0))
+            .setQueryId(req.getQueryId())
+            .setQueryStatus(QueryPathRspOuterClass.QueryPathRsp.PathStatusType.PATH_STATUS_TYPE_SUCC);
+
+        this.setData(proto);
+	}
+}
diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockPersonalLineRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockPersonalLineRsp.java
new file mode 100644
index 00000000..e25523e8
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/packet/send/PacketUnlockPersonalLineRsp.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.UnlockPersonalLineRspOuterClass;
+
+public class PacketUnlockPersonalLineRsp extends BasePacket {
+
+	public PacketUnlockPersonalLineRsp(int id, int level, int chapterId) {
+		super(PacketOpcodes.UnlockPersonalLineRsp);
+
+        var proto = UnlockPersonalLineRspOuterClass.UnlockPersonalLineRsp.newBuilder();
+
+        proto.setPersonalLineId(id)
+            .setLevel(level)
+            .setChapterId(chapterId);
+
+        this.setData(proto);
+	}
+}
-- 
GitLab