Skip to content

Commit 810adcf

Browse files
Derive main-quest season routes at projection time
1 parent ef69c54 commit 810adcf

11 files changed

Lines changed: 101 additions & 96 deletions

File tree

server/cmd/claim-account/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var childTables = []string{
4040
"user_big_hunt_max_scores",
4141
"user_quest_limit_content_status",
4242
"user_side_story_quests",
43+
"user_main_quest_season_routes",
4344
"user_missions",
4445
"user_quest_missions",
4546
"user_quests",

server/internal/masterdata/quest.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ type QuestCatalog struct {
3434
TutorialUnlockConditions []EntityMTutorialUnlockCondition
3535
ChapterLastSceneByQuestId map[int32]int32
3636
SeasonIdByRouteId map[int32]int32
37+
RoutesBySeason map[int32][]int32
38+
RouteCompletionQuestId map[int32]int32
3739
BattleOnlyTargetSceneByQuestId map[int32]int32
3840

3941
UserExpThresholds []int32
@@ -114,8 +116,53 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
114116
return nil, fmt.Errorf("load main quest route table: %w", err)
115117
}
116118
seasonIdByRouteId := make(map[int32]int32, len(routes))
119+
routesBySeason := make(map[int32][]int32, len(routes))
120+
sortOrderByRoute := make(map[int32]int32, len(routes))
117121
for _, r := range routes {
118122
seasonIdByRouteId[r.MainQuestRouteId] = r.MainQuestSeasonId
123+
routesBySeason[r.MainQuestSeasonId] = append(routesBySeason[r.MainQuestSeasonId], r.MainQuestRouteId)
124+
sortOrderByRoute[r.MainQuestRouteId] = r.SortOrder
125+
}
126+
for seasonId, ids := range routesBySeason {
127+
s := ids
128+
sort.Slice(s, func(i, j int) bool { return sortOrderByRoute[s[i]] > sortOrderByRoute[s[j]] })
129+
routesBySeason[seasonId] = s
130+
}
131+
132+
anotherReplayConds, err := utils.ReadTable[EntityMMainQuestRouteAnotherReplayFlowUnlockCondition]("m_main_quest_route_another_replay_flow_unlock_condition")
133+
if err != nil {
134+
return nil, fmt.Errorf("load main quest route another replay flow unlock condition table: %w", err)
135+
}
136+
evaluateConds, err := utils.ReadTable[EntityMEvaluateCondition]("m_evaluate_condition")
137+
if err != nil {
138+
return nil, fmt.Errorf("load evaluate condition table: %w", err)
139+
}
140+
valueGroupByConditionId := make(map[int32]int32, len(evaluateConds))
141+
for _, c := range evaluateConds {
142+
valueGroupByConditionId[c.EvaluateConditionId] = c.EvaluateConditionValueGroupId
143+
}
144+
evaluateValueGroups, err := utils.ReadTable[EntityMEvaluateConditionValueGroup]("m_evaluate_condition_value_group")
145+
if err != nil {
146+
return nil, fmt.Errorf("load evaluate condition value group table: %w", err)
147+
}
148+
valueByGroupId := make(map[int32]int32, len(evaluateValueGroups))
149+
for _, vg := range evaluateValueGroups {
150+
if _, exists := valueByGroupId[vg.EvaluateConditionValueGroupId]; exists {
151+
continue
152+
}
153+
valueByGroupId[vg.EvaluateConditionValueGroupId] = int32(vg.Value)
154+
}
155+
routeCompletionQuestId := make(map[int32]int32, len(anotherReplayConds))
156+
for _, c := range anotherReplayConds {
157+
valueGroupId, ok := valueGroupByConditionId[c.UnlockEvaluateConditionId]
158+
if !ok {
159+
continue
160+
}
161+
questId, ok := valueByGroupId[valueGroupId]
162+
if !ok {
163+
continue
164+
}
165+
routeCompletionQuestId[c.MainQuestRouteId] = questId
119166
}
120167

121168
firstClearSwitches, err := utils.ReadTable[EntityMQuestFirstClearRewardSwitch]("m_quest_first_clear_reward_switch")
@@ -539,6 +586,8 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
539586
TutorialUnlockConditions: tutorialUnlockConds,
540587
ChapterLastSceneByQuestId: chapterLastSceneByQuestId,
541588
SeasonIdByRouteId: seasonIdByRouteId,
589+
RoutesBySeason: routesBySeason,
590+
RouteCompletionQuestId: routeCompletionQuestId,
542591
BattleOnlyTargetSceneByQuestId: battleOnlyTargetSceneByQuestId,
543592

544593
UserExpThresholds: BuildExpThresholds(paramMapRows, 1),

server/internal/questflow/scene.go

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ func (h *QuestHandler) advanceMainFlowScene(user *store.UserState, questId, scen
4646
user.MainQuest.CurrentMainQuestRouteId = routeId
4747
if seasonId, ok := h.SeasonIdByRouteId[routeId]; ok {
4848
user.MainQuest.MainQuestSeasonId = seasonId
49-
RecordSeasonRoute(user, seasonId, routeId, gametime.NowMillis())
5049
}
5150
}
5251
}
@@ -59,22 +58,27 @@ func (h *QuestHandler) advanceReplayFlowScene(user *store.UserState, sceneId int
5958
user.MainQuest.ReplayFlowHeadQuestSceneId = sceneId
6059
}
6160

62-
func RecordSeasonRoute(user *store.UserState, seasonId, routeId int32, nowMillis int64) {
63-
if seasonId <= 0 || routeId <= 0 {
64-
return
65-
}
66-
if user.MainQuestSeasonRoutes == nil {
67-
user.MainQuestSeasonRoutes = make(map[store.SeasonRouteKey]store.SeasonRouteEntry)
68-
}
69-
key := store.SeasonRouteKey{MainQuestSeasonId: seasonId, MainQuestRouteId: routeId}
70-
if _, exists := user.MainQuestSeasonRoutes[key]; exists {
71-
return
61+
func (h *QuestHandler) SeasonRoutesFor(user *store.UserState) map[int32]int32 {
62+
out := make(map[int32]int32)
63+
for seasonId, routes := range h.RoutesBySeason {
64+
if seasonId <= 1 {
65+
continue
66+
}
67+
for _, routeId := range routes {
68+
finalQuestId, ok := h.RouteCompletionQuestId[routeId]
69+
if !ok {
70+
continue
71+
}
72+
if q, ok := user.Quests[finalQuestId]; ok && q.ClearCount > 0 {
73+
out[seasonId] = routeId
74+
break
75+
}
76+
}
7277
}
73-
user.MainQuestSeasonRoutes[key] = store.SeasonRouteEntry{
74-
MainQuestSeasonId: seasonId,
75-
MainQuestRouteId: routeId,
76-
LatestVersion: nowMillis,
78+
if cur := user.MainQuest.MainQuestSeasonId; cur >= 2 && user.MainQuest.CurrentMainQuestRouteId > 0 {
79+
out[cur] = user.MainQuest.CurrentMainQuestRouteId
7780
}
81+
return out
7882
}
7983

8084
func (h *QuestHandler) HandleMainFlowSceneProgress(user *store.UserState, questSceneId int32, nowMillis int64) {
@@ -177,15 +181,8 @@ func (h *QuestHandler) replayFlowTypeForRoute(user *store.UserState, routeId int
177181
if !ok {
178182
return model.QuestFlowTypeReplayFlow
179183
}
180-
for key, entry := range user.MainQuestSeasonRoutes {
181-
if key.MainQuestSeasonId == seasonId && entry.MainQuestRouteId != routeId {
182-
return model.QuestFlowTypeAnotherRouteReplayFlow
183-
}
184-
}
185-
if len(user.MainQuestSeasonRoutes) == 0 &&
186-
user.MainQuest.MainQuestSeasonId == seasonId &&
187-
user.MainQuest.CurrentMainQuestRouteId != 0 &&
188-
user.MainQuest.CurrentMainQuestRouteId != routeId {
184+
pairs := h.SeasonRoutesFor(user)
185+
if recorded, ok := pairs[seasonId]; ok && recorded != routeId {
189186
return model.QuestFlowTypeAnotherRouteReplayFlow
190187
}
191188
return model.QuestFlowTypeReplayFlow

server/internal/runtime/build.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"lunar-tear/server/internal/masterdata"
99
"lunar-tear/server/internal/masterdata/memorydb"
1010
"lunar-tear/server/internal/questflow"
11+
"lunar-tear/server/internal/userdata"
1112
)
1213

1314
// buildCatalogs runs the full Load*/Build*/Enrich* sequence against whatever
@@ -35,6 +36,7 @@ func buildCatalogs() (*Catalogs, error) {
3536
}
3637
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
3738
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig, sideStoryCatalog)
39+
userdata.SetQuestHandler(questHandler)
3840

3941
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
4042
if err != nil {

server/internal/service/quest_main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteReque
198198
user.MainQuest.CurrentMainQuestRouteId = req.MainQuestRouteId
199199
if seasonId, ok := engine.SeasonIdByRouteId[req.MainQuestRouteId]; ok {
200200
user.MainQuest.MainQuestSeasonId = seasonId
201-
questflow.RecordSeasonRoute(user, seasonId, req.MainQuestRouteId, gametime.NowMillis())
202201
}
203202
now := gametime.NowMillis()
204203
user.PortalCageStatus.IsCurrentProgress = false

server/internal/store/sqlite/load.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ func initMaps(u *store.UserState) {
8686
u.CharacterRebirths = make(map[int32]store.CharacterRebirthState)
8787
u.AutoSaleSettings = make(map[int32]store.AutoSaleSettingState)
8888
u.SideStoryQuests = make(map[int32]store.SideStoryQuestProgress)
89-
u.MainQuestSeasonRoutes = make(map[store.SeasonRouteKey]store.SeasonRouteEntry)
9089
u.QuestLimitContentStatus = make(map[int32]store.QuestLimitContentStatus)
9190
u.BigHuntMaxScores = make(map[int32]store.BigHuntMaxScore)
9291
u.BigHuntStatuses = make(map[int32]store.BigHuntStatus)
@@ -378,16 +377,6 @@ func loadMapTables(db *sql.DB, uid int64, u *store.UserState) {
378377
}
379378
})
380379

381-
queryRows(db, `SELECT main_quest_season_id, main_quest_route_id, latest_version
382-
FROM user_main_quest_season_routes WHERE user_id=?`, uid, func(rows *sql.Rows) {
383-
var seasonId, routeId int32
384-
var lv int64
385-
rows.Scan(&seasonId, &routeId, &lv)
386-
u.MainQuestSeasonRoutes[store.SeasonRouteKey{MainQuestSeasonId: seasonId, MainQuestRouteId: routeId}] = store.SeasonRouteEntry{
387-
MainQuestSeasonId: seasonId, MainQuestRouteId: routeId, LatestVersion: lv,
388-
}
389-
})
390-
391380
queryRows(db, `SELECT limit_content_id, limit_content_quest_status_type, event_quest_chapter_id, latest_version
392381
FROM user_quest_limit_content_status WHERE user_id=?`, uid, func(rows *sql.Rows) {
393382
var id int32

server/internal/store/sqlite/save.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,6 @@ func writeUserState(tx *sql.Tx, uid int64, u *store.UserState) error {
224224
return err
225225
}
226226
}
227-
for k, v := range u.MainQuestSeasonRoutes {
228-
if err := exec(`INSERT INTO user_main_quest_season_routes (user_id, main_quest_season_id, main_quest_route_id, latest_version) VALUES (?,?,?,?)`,
229-
uid, k.MainQuestSeasonId, k.MainQuestRouteId, v.LatestVersion); err != nil {
230-
return err
231-
}
232-
}
233227
for id, v := range u.QuestLimitContentStatus {
234228
if err := exec(`INSERT INTO user_quest_limit_content_status (user_id, limit_content_id, limit_content_quest_status_type, event_quest_chapter_id, latest_version) VALUES (?,?,?,?,?)`,
235229
uid, id, v.LimitContentQuestStatusType, v.EventQuestChapterId, v.LatestVersion); err != nil {
@@ -798,17 +792,6 @@ func diffAndSave(tx *sql.Tx, uid int64, before, after *store.UserState) error {
798792
return []any{0, v.HeadSideStoryQuestSceneId, int32(v.SideStoryQuestStateType), v.LatestVersion}
799793
}, "side_story_quest_id, head_side_story_quest_scene_id, side_story_quest_state_type, latest_version")
800794

801-
for k, v := range after.MainQuestSeasonRoutes {
802-
if old, ok := before.MainQuestSeasonRoutes[k]; !ok || old != v {
803-
exec(`INSERT OR REPLACE INTO user_main_quest_season_routes (user_id, main_quest_season_id, main_quest_route_id, latest_version) VALUES (?,?,?,?)`,
804-
uid, k.MainQuestSeasonId, k.MainQuestRouteId, v.LatestVersion)
805-
}
806-
}
807-
for k := range before.MainQuestSeasonRoutes {
808-
if _, ok := after.MainQuestSeasonRoutes[k]; !ok {
809-
exec(`DELETE FROM user_main_quest_season_routes WHERE user_id=? AND main_quest_season_id=? AND main_quest_route_id=?`, uid, k.MainQuestSeasonId, k.MainQuestRouteId)
810-
}
811-
}
812795
diffMapInt32(tx, uid, before.QuestLimitContentStatus, after.QuestLimitContentStatus, "user_quest_limit_content_status", "limit_content_id",
813796
func(v store.QuestLimitContentStatus) []any {
814797
return []any{0, v.LimitContentQuestStatusType, v.EventQuestChapterId, v.LatestVersion}

server/internal/store/types.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ type UserState struct {
4141
LoginBonus UserLoginBonusState
4242
Tutorials map[int32]TutorialProgressState
4343
MainQuest MainQuestState
44-
MainQuestSeasonRoutes map[SeasonRouteKey]SeasonRouteEntry
4544
EventQuest EventQuestState
4645
ExtraQuest ExtraQuestState
4746
SideStoryQuests map[int32]SideStoryQuestProgress
@@ -162,9 +161,6 @@ func (u *UserState) EnsureMaps() {
162161
if u.SideStoryQuests == nil {
163162
u.SideStoryQuests = make(map[int32]SideStoryQuestProgress)
164163
}
165-
if u.MainQuestSeasonRoutes == nil {
166-
u.MainQuestSeasonRoutes = make(map[SeasonRouteKey]SeasonRouteEntry)
167-
}
168164
if u.QuestLimitContentStatus == nil {
169165
u.QuestLimitContentStatus = make(map[int32]QuestLimitContentStatus)
170166
}
@@ -590,17 +586,6 @@ type SideStoryActiveProgress struct {
590586
LatestVersion int64
591587
}
592588

593-
type SeasonRouteKey struct {
594-
MainQuestSeasonId int32
595-
MainQuestRouteId int32
596-
}
597-
598-
type SeasonRouteEntry struct {
599-
MainQuestSeasonId int32
600-
MainQuestRouteId int32
601-
LatestVersion int64
602-
}
603-
604589
type QuestLimitContentStatus struct {
605590
LimitContentQuestStatusType int32
606591
EventQuestChapterId int32

server/internal/userdata/changed_tables.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ func ChangedTables(before, after *store.UserState) []string {
101101
add("IUserMainQuestMainFlowStatus")
102102
add("IUserMainQuestProgressStatus")
103103
add("IUserMainQuestReplayFlowStatus")
104-
}
105-
if !mapsEqualStruct(before.MainQuestSeasonRoutes, after.MainQuestSeasonRoutes) {
104+
// IUserMainQuestSeasonRoute is derived from MainQuest + Quests at projection
105+
// time (see proj_quest.go / questflow.QuestHandler.SeasonRoutesFor) — flag it
106+
// whenever either of those upstream inputs changes.
106107
add("IUserMainQuestSeasonRoute")
107108
}
108109
if before.EventQuest != after.EventQuest {
@@ -202,6 +203,7 @@ func ChangedTables(before, after *store.UserState) []string {
202203
}
203204
if !mapsEqualStruct(before.Quests, after.Quests) {
204205
add("IUserQuest")
206+
add("IUserMainQuestSeasonRoute")
205207
}
206208
if !mapsEqualStruct(before.QuestMissions, after.QuestMissions) {
207209
add("IUserQuestMission")

server/internal/userdata/proj_quest.go

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -116,38 +116,29 @@ func init() {
116116
return s
117117
})
118118
register("IUserMainQuestSeasonRoute", func(user store.UserState) string {
119-
if len(user.MainQuestSeasonRoutes) == 0 {
120-
// Fallback to current (season, route) for legacy saves with no history.
121-
s, _ := utils.EncodeJSONMaps(map[string]any{
122-
"userId": user.UserId,
123-
"mainQuestSeasonId": user.MainQuest.MainQuestSeasonId,
124-
"mainQuestRouteId": user.MainQuest.CurrentMainQuestRouteId,
125-
"latestVersion": user.MainQuest.LatestVersion,
126-
})
127-
return s
119+
if questHandler == nil {
120+
return "[]"
121+
}
122+
pairs := questHandler.SeasonRoutesFor(&user)
123+
if len(pairs) == 0 {
124+
return "[]"
128125
}
129-
keys := make([]store.SeasonRouteKey, 0, len(user.MainQuestSeasonRoutes))
130-
for k := range user.MainQuestSeasonRoutes {
131-
keys = append(keys, k)
126+
seasons := make([]int32, 0, len(pairs))
127+
for s := range pairs {
128+
seasons = append(seasons, s)
132129
}
133-
sort.Slice(keys, func(i, j int) bool {
134-
if keys[i].MainQuestSeasonId != keys[j].MainQuestSeasonId {
135-
return keys[i].MainQuestSeasonId < keys[j].MainQuestSeasonId
136-
}
137-
return keys[i].MainQuestRouteId < keys[j].MainQuestRouteId
138-
})
139-
records := make([]map[string]any, 0, len(keys))
140-
for _, k := range keys {
141-
e := user.MainQuestSeasonRoutes[k]
130+
sort.Slice(seasons, func(i, j int) bool { return seasons[i] < seasons[j] })
131+
records := make([]map[string]any, 0, len(seasons))
132+
for _, s := range seasons {
142133
records = append(records, map[string]any{
143134
"userId": user.UserId,
144-
"mainQuestSeasonId": e.MainQuestSeasonId,
145-
"mainQuestRouteId": e.MainQuestRouteId,
146-
"latestVersion": e.LatestVersion,
135+
"mainQuestSeasonId": s,
136+
"mainQuestRouteId": pairs[s],
137+
"latestVersion": user.MainQuest.LatestVersion,
147138
})
148139
}
149-
s, _ := utils.EncodeJSONMaps(records...)
150-
return s
140+
out, _ := utils.EncodeJSONMaps(records...)
141+
return out
151142
})
152143
register("IUserEventQuestProgressStatus", func(user store.UserState) string {
153144
s, _ := utils.EncodeJSONMaps(map[string]any{

0 commit comments

Comments
 (0)