mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Implement Recollections of Dusk
This commit is contained in:
@@ -2,11 +2,35 @@ package masterdata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SideStorySceneInfo struct {
|
||||||
|
SceneId int32
|
||||||
|
Type model.SideStorySceneIdType
|
||||||
|
}
|
||||||
|
|
||||||
|
type SideStoryQuestInfo struct {
|
||||||
|
SideStoryQuestId int32
|
||||||
|
Scenes []SideStorySceneInfo // the 7 scenes, one per type
|
||||||
|
Quests []int32 // ordered event quests (the chapter+difficulty sequence)
|
||||||
|
}
|
||||||
|
|
||||||
type SideStoryCatalog struct {
|
type SideStoryCatalog struct {
|
||||||
FirstSceneByQuestId map[int32]int32
|
QuestById map[int32]*SideStoryQuestInfo
|
||||||
|
ChapterByEventQuestId map[int32]int32 // event quest id -> side story chapter id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *SideStoryQuestInfo) SceneIdByType(t model.SideStorySceneIdType) (int32, bool) {
|
||||||
|
for _, s := range q.Scenes {
|
||||||
|
if s.Type == t {
|
||||||
|
return s.SceneId, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadSideStoryCatalog() *SideStoryCatalog {
|
func LoadSideStoryCatalog() *SideStoryCatalog {
|
||||||
@@ -14,14 +38,90 @@ func LoadSideStoryCatalog() *SideStoryCatalog {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load side story quest scene table: %v", err)
|
log.Fatalf("load side story quest scene table: %v", err)
|
||||||
}
|
}
|
||||||
|
limitContents, err := utils.ReadTable[EntityMSideStoryQuestLimitContent]("m_side_story_quest_limit_content")
|
||||||
firstScene := make(map[int32]int32, len(scenes)/7)
|
if err != nil {
|
||||||
for _, s := range scenes {
|
log.Fatalf("load side story quest limit content table: %v", err)
|
||||||
if s.SortOrder == 1 {
|
}
|
||||||
firstScene[s.SideStoryQuestId] = s.SideStoryQuestSceneId
|
seqGroups, err := utils.ReadTable[EntityMEventQuestSequenceGroup]("m_event_quest_sequence_group")
|
||||||
}
|
if err != nil {
|
||||||
|
log.Fatalf("load event quest sequence group table: %v", err)
|
||||||
|
}
|
||||||
|
sequences, err := utils.ReadTable[EntityMEventQuestSequence]("m_event_quest_sequence")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("load event quest sequence table: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("side story catalog loaded: %d quests", len(firstScene))
|
seqRows := make(map[int32][]EntityMEventQuestSequence)
|
||||||
return &SideStoryCatalog{FirstSceneByQuestId: firstScene}
|
for _, s := range sequences {
|
||||||
|
seqRows[s.EventQuestSequenceId] = append(seqRows[s.EventQuestSequenceId], s)
|
||||||
|
}
|
||||||
|
orderedQuestIds := make(map[int32][]int32, len(seqRows))
|
||||||
|
for seqId, rows := range seqRows {
|
||||||
|
sort.Slice(rows, func(i, j int) bool { return rows[i].SortOrder < rows[j].SortOrder })
|
||||||
|
ids := make([]int32, len(rows))
|
||||||
|
for i, r := range rows {
|
||||||
|
ids[i] = r.QuestId
|
||||||
|
}
|
||||||
|
orderedQuestIds[seqId] = ids
|
||||||
|
}
|
||||||
|
|
||||||
|
// (chapterId, difficulty) -> sequenceId. Sequence group id == chapter id.
|
||||||
|
type chapDiff struct{ chapter, difficulty int32 }
|
||||||
|
sequenceByChapterDiff := make(map[chapDiff]int32, len(seqGroups))
|
||||||
|
for _, g := range seqGroups {
|
||||||
|
sequenceByChapterDiff[chapDiff{g.EventQuestSequenceGroupId, g.DifficultyType}] = g.EventQuestSequenceId
|
||||||
|
}
|
||||||
|
|
||||||
|
// sideStoryQuestId -> limit content row. Limit content id == side story quest id.
|
||||||
|
limitByQuest := make(map[int32]EntityMSideStoryQuestLimitContent, len(limitContents))
|
||||||
|
for _, lc := range limitContents {
|
||||||
|
limitByQuest[lc.SideStoryQuestLimitContentId] = lc
|
||||||
|
}
|
||||||
|
|
||||||
|
// sideStoryQuestId -> scene rows
|
||||||
|
scenesByQuest := make(map[int32][]EntityMSideStoryQuestScene)
|
||||||
|
for _, sc := range scenes {
|
||||||
|
scenesByQuest[sc.SideStoryQuestId] = append(scenesByQuest[sc.SideStoryQuestId], sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
questById := make(map[int32]*SideStoryQuestInfo, len(scenesByQuest))
|
||||||
|
chapterByEventQuest := make(map[int32]int32)
|
||||||
|
|
||||||
|
for ssqId, rows := range scenesByQuest {
|
||||||
|
sort.Slice(rows, func(i, j int) bool { return rows[i].SortOrder < rows[j].SortOrder })
|
||||||
|
|
||||||
|
var orderedQuests []int32
|
||||||
|
var chapterId, difficulty int32
|
||||||
|
if lc, ok := limitByQuest[ssqId]; ok {
|
||||||
|
chapterId = lc.EventQuestChapterId
|
||||||
|
difficulty = lc.DifficultyType
|
||||||
|
if seqId, ok := sequenceByChapterDiff[chapDiff{chapterId, difficulty}]; ok {
|
||||||
|
orderedQuests = orderedQuestIds[seqId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chapterId != 0 {
|
||||||
|
for _, questId := range orderedQuests {
|
||||||
|
chapterByEventQuest[questId] = chapterId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info := &SideStoryQuestInfo{
|
||||||
|
SideStoryQuestId: ssqId,
|
||||||
|
Scenes: make([]SideStorySceneInfo, 0, len(rows)),
|
||||||
|
Quests: orderedQuests,
|
||||||
|
}
|
||||||
|
for _, sc := range rows {
|
||||||
|
info.Scenes = append(info.Scenes, SideStorySceneInfo{
|
||||||
|
SceneId: sc.SideStoryQuestSceneId,
|
||||||
|
Type: model.SideStorySceneIdType(sc.SortOrder),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
questById[ssqId] = info
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("side story catalog loaded: %d quests, %d scenes", len(questById), len(scenes))
|
||||||
|
return &SideStoryCatalog{
|
||||||
|
QuestById: questById,
|
||||||
|
ChapterByEventQuestId: chapterByEventQuest,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,20 @@ type SideStoryQuestStateType int32
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
SideStoryQuestStateUnknown SideStoryQuestStateType = 0
|
SideStoryQuestStateUnknown SideStoryQuestStateType = 0
|
||||||
SideStoryQuestStateActive SideStoryQuestStateType = 1
|
SideStoryQuestStateActive SideStoryQuestStateType = 2
|
||||||
SideStoryQuestStateCleared SideStoryQuestStateType = 2
|
SideStoryQuestStateCleared SideStoryQuestStateType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type SideStorySceneIdType int32
|
||||||
|
|
||||||
|
// Values mirror SideStoryTypes.SceneIdTypes in the client (dump.cs).
|
||||||
|
const (
|
||||||
|
SideStorySceneInvalid SideStorySceneIdType = 0
|
||||||
|
SideStorySceneIntroduction SideStorySceneIdType = 1
|
||||||
|
SideStoryScenePlayGeneralQuest SideStorySceneIdType = 2
|
||||||
|
SideStorySceneUnlockLastQuest SideStorySceneIdType = 3
|
||||||
|
SideStoryScenePlayLastQuest SideStorySceneIdType = 4
|
||||||
|
SideStorySceneOutroduction SideStorySceneIdType = 5
|
||||||
|
SideStorySceneShowCostumeAcquisition SideStorySceneIdType = 6
|
||||||
|
SideStoryScenePlayFreeQuest SideStorySceneIdType = 7
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ func (h *QuestHandler) HandleEventQuestFinish(user *store.UserState, eventQuestC
|
|||||||
outcome := h.evaluateFinishOutcome(user, questId)
|
outcome := h.evaluateFinishOutcome(user, questId)
|
||||||
if !isRetired {
|
if !isRetired {
|
||||||
h.applyQuestVictory(user, questId, &outcome, nowMillis, false)
|
h.applyQuestVictory(user, questId, &outcome, nowMillis, false)
|
||||||
|
h.recordSideStoryLimitContentStatus(user, questId, nowMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isRetired && !isAnnihilated && quest.Stamina > 1 {
|
if isRetired && !isAnnihilated && quest.Stamina > 1 {
|
||||||
@@ -64,6 +65,18 @@ func (h *QuestHandler) HandleEventQuestFinish(user *store.UserState, eventQuestC
|
|||||||
return outcome
|
return outcome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *QuestHandler) recordSideStoryLimitContentStatus(user *store.UserState, questId int32, nowMillis int64) {
|
||||||
|
chapterId, ok := h.SideStoryChapterByEventQuestId[questId]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st := user.QuestLimitContentStatus[questId]
|
||||||
|
st.LimitContentQuestStatusType = 1
|
||||||
|
st.EventQuestChapterId = chapterId
|
||||||
|
st.LatestVersion = nowMillis
|
||||||
|
user.QuestLimitContentStatus[questId] = st
|
||||||
|
}
|
||||||
|
|
||||||
func (h *QuestHandler) HandleEventQuestRestart(user *store.UserState, eventQuestChapterId, questId int32, nowMillis int64) {
|
func (h *QuestHandler) HandleEventQuestRestart(user *store.UserState, eventQuestChapterId, questId int32, nowMillis int64) {
|
||||||
h.HandleQuestRestart(user, questId, nowMillis)
|
h.HandleQuestRestart(user, questId, nowMillis)
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,23 @@ type FinishOutcome struct {
|
|||||||
|
|
||||||
type QuestHandler struct {
|
type QuestHandler struct {
|
||||||
*masterdata.QuestCatalog
|
*masterdata.QuestCatalog
|
||||||
Config *masterdata.GameConfig
|
Config *masterdata.GameConfig
|
||||||
Granter *store.PossessionGranter
|
Granter *store.PossessionGranter
|
||||||
|
SideStoryChapterByEventQuestId map[int32]int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQuestHandler(catalog *masterdata.QuestCatalog, config *masterdata.GameConfig) *QuestHandler {
|
func NewQuestHandler(catalog *masterdata.QuestCatalog, config *masterdata.GameConfig, sideStory *masterdata.SideStoryCatalog) *QuestHandler {
|
||||||
granter := BuildGranter(catalog)
|
granter := BuildGranter(catalog)
|
||||||
return &QuestHandler{QuestCatalog: catalog, Config: config, Granter: granter}
|
var sideStoryChapters map[int32]int32
|
||||||
|
if sideStory != nil {
|
||||||
|
sideStoryChapters = sideStory.ChapterByEventQuestId
|
||||||
|
}
|
||||||
|
return &QuestHandler{
|
||||||
|
QuestCatalog: catalog,
|
||||||
|
Config: config,
|
||||||
|
Granter: granter,
|
||||||
|
SideStoryChapterByEventQuestId: sideStoryChapters,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildGranter(catalog *masterdata.QuestCatalog) *store.PossessionGranter {
|
func BuildGranter(catalog *masterdata.QuestCatalog) *store.PossessionGranter {
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ func buildCatalogs() (*Catalogs, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest catalog: %w", err)
|
return nil, fmt.Errorf("load quest catalog: %w", err)
|
||||||
}
|
}
|
||||||
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig)
|
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
|
||||||
|
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig, sideStoryCatalog)
|
||||||
|
|
||||||
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -136,7 +137,6 @@ func buildCatalogs() (*Catalogs, error) {
|
|||||||
}
|
}
|
||||||
log.Printf("companion catalog loaded: %d companions, %d categories", len(companionCatalog.CompanionById), len(companionCatalog.GoldCostByCategory))
|
log.Printf("companion catalog loaded: %d companions, %d categories", len(companionCatalog.CompanionById), len(companionCatalog.GoldCostByCategory))
|
||||||
|
|
||||||
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
|
|
||||||
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
|
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
|
||||||
|
|
||||||
return &Catalogs{
|
return &Catalogs{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/runtime"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
@@ -22,34 +23,89 @@ func NewSideStoryQuestServiceServer(users store.UserRepository, sessions store.S
|
|||||||
return &SideStoryQuestServiceServer{users: users, sessions: sessions, holder: holder}
|
return &SideStoryQuestServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sideStoryClearedCount(info *masterdata.SideStoryQuestInfo, user *store.UserState) int {
|
||||||
|
cleared := 0
|
||||||
|
for _, questId := range info.Quests {
|
||||||
|
if user.QuestLimitContentStatus[questId].LimitContentQuestStatusType == 1 {
|
||||||
|
cleared++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleared
|
||||||
|
}
|
||||||
|
|
||||||
|
func sideStoryQuestCleared(info *masterdata.SideStoryQuestInfo, user *store.UserState) bool {
|
||||||
|
return info != nil && len(info.Quests) > 0 && sideStoryClearedCount(info, user) == len(info.Quests)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sideStoryNextSceneAfterBattle(info *masterdata.SideStoryQuestInfo, user *store.UserState) (int32, bool) {
|
||||||
|
cleared := sideStoryClearedCount(info, user)
|
||||||
|
if cleared == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
total := len(info.Quests)
|
||||||
|
var sceneType model.SideStorySceneIdType
|
||||||
|
switch {
|
||||||
|
case cleared >= total:
|
||||||
|
sceneType = model.SideStorySceneOutroduction
|
||||||
|
case cleared == total-1:
|
||||||
|
sceneType = model.SideStorySceneUnlockLastQuest
|
||||||
|
default:
|
||||||
|
sceneType = model.SideStoryScenePlayLastQuest
|
||||||
|
}
|
||||||
|
return info.SceneIdByType(sceneType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applySideStoryProgressState(progress *store.SideStoryQuestProgress, info *masterdata.SideStoryQuestInfo, user *store.UserState) {
|
||||||
|
if sideStoryQuestCleared(info, user) {
|
||||||
|
progress.SideStoryQuestStateType = model.SideStoryQuestStateCleared
|
||||||
|
} else if progress.SideStoryQuestStateType == model.SideStoryQuestStateUnknown {
|
||||||
|
progress.SideStoryQuestStateType = model.SideStoryQuestStateActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSideStoryActive(user *store.UserState, questId, sceneId int32, nowMillis int64) {
|
||||||
|
user.SideStoryActiveProgress = store.SideStoryActiveProgress{
|
||||||
|
CurrentSideStoryQuestId: questId,
|
||||||
|
CurrentSideStoryQuestSceneId: sceneId,
|
||||||
|
LatestVersion: nowMillis,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSideStoryScene(user *store.UserState, info *masterdata.SideStoryQuestInfo, questId, sceneId int32, nowMillis int64) {
|
||||||
|
progress := user.SideStoryQuests[questId]
|
||||||
|
progress.HeadSideStoryQuestSceneId = sceneId
|
||||||
|
applySideStoryProgressState(&progress, info, user)
|
||||||
|
progress.LatestVersion = nowMillis
|
||||||
|
user.SideStoryQuests[questId] = progress
|
||||||
|
setSideStoryActive(user, questId, sceneId, nowMillis)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Context, req *pb.MoveSideStoryQuestRequest) (*pb.MoveSideStoryQuestResponse, error) {
|
func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Context, req *pb.MoveSideStoryQuestRequest) (*pb.MoveSideStoryQuestResponse, error) {
|
||||||
log.Printf("[SideStoryQuestService] MoveSideStoryQuestProgress: sideStoryQuestId=%d", req.SideStoryQuestId)
|
log.Printf("[SideStoryQuestService] MoveSideStoryQuestProgress: sideStoryQuestId=%d", req.SideStoryQuestId)
|
||||||
|
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
firstSceneId := s.holder.Get().SideStory.FirstSceneByQuestId[req.SideStoryQuestId]
|
info := s.holder.Get().SideStory.QuestById[req.SideStoryQuestId]
|
||||||
|
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
|
if info == nil || len(info.Quests) == 0 {
|
||||||
|
log.Printf("[SideStoryQuestService] unknown sideStoryQuestId=%d, skipping", req.SideStoryQuestId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
existing, exists := user.SideStoryQuests[req.SideStoryQuestId]
|
existing, exists := user.SideStoryQuests[req.SideStoryQuestId]
|
||||||
|
|
||||||
var sceneId int32
|
var scene int32
|
||||||
if exists && existing.HeadSideStoryQuestSceneId > 0 {
|
var ok bool
|
||||||
sceneId = existing.HeadSideStoryQuestSceneId
|
if !exists || existing.HeadSideStoryQuestSceneId == 0 {
|
||||||
|
scene, ok = info.SceneIdByType(model.SideStorySceneIntroduction)
|
||||||
} else {
|
} else {
|
||||||
sceneId = firstSceneId
|
scene, ok = sideStoryNextSceneAfterBattle(info, user)
|
||||||
}
|
}
|
||||||
|
if !ok {
|
||||||
user.SideStoryActiveProgress.CurrentSideStoryQuestId = req.SideStoryQuestId
|
return
|
||||||
user.SideStoryActiveProgress.CurrentSideStoryQuestSceneId = sceneId
|
|
||||||
user.SideStoryActiveProgress.LatestVersion = nowMillis
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
user.SideStoryQuests[req.SideStoryQuestId] = store.SideStoryQuestProgress{
|
|
||||||
HeadSideStoryQuestSceneId: firstSceneId,
|
|
||||||
SideStoryQuestStateType: model.SideStoryQuestStateActive,
|
|
||||||
LatestVersion: nowMillis,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
setSideStoryScene(user, info, req.SideStoryQuestId, scene, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.MoveSideStoryQuestResponse{}, nil
|
return &pb.MoveSideStoryQuestResponse{}, nil
|
||||||
@@ -61,16 +117,10 @@ func (s *SideStoryQuestServiceServer) UpdateSideStoryQuestSceneProgress(ctx cont
|
|||||||
|
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
info := s.holder.Get().SideStory.QuestById[req.SideStoryQuestId]
|
||||||
user.SideStoryActiveProgress.CurrentSideStoryQuestSceneId = req.SideStoryQuestSceneId
|
|
||||||
user.SideStoryActiveProgress.LatestVersion = nowMillis
|
|
||||||
|
|
||||||
progress := user.SideStoryQuests[req.SideStoryQuestId]
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if req.SideStoryQuestSceneId > progress.HeadSideStoryQuestSceneId {
|
setSideStoryScene(user, info, req.SideStoryQuestId, req.SideStoryQuestSceneId, nowMillis)
|
||||||
progress.HeadSideStoryQuestSceneId = req.SideStoryQuestSceneId
|
|
||||||
}
|
|
||||||
progress.LatestVersion = nowMillis
|
|
||||||
user.SideStoryQuests[req.SideStoryQuestId] = progress
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.UpdateSideStoryQuestSceneProgressResponse{}, nil
|
return &pb.UpdateSideStoryQuestSceneProgressResponse{}, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user