mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Implement Fate Boards
Build and Push Docker images to Docker Hub / build-and-push (push) Has been cancelled
Build and Push Docker images to Docker Hub / build-and-push (push) Has been cancelled
This commit is contained in:
@@ -124,4 +124,5 @@ func registerServices(
|
|||||||
pb.RegisterSideStoryQuestServiceServer(srv, service.NewSideStoryQuestServiceServer(userStore, userStore, holder))
|
pb.RegisterSideStoryQuestServiceServer(srv, service.NewSideStoryQuestServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, holder))
|
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, holder))
|
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, holder))
|
||||||
|
pb.RegisterLabyrinthServiceServer(srv, service.NewLabyrinthServiceServer(userStore, userStore, holder))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package masterdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LabyrinthChapter struct {
|
||||||
|
EventQuestChapterId int32
|
||||||
|
LatestSeasonNumber int32
|
||||||
|
StageOrders []int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type LabyrinthStageTier struct {
|
||||||
|
QuestMissionClearCount int32
|
||||||
|
Rewards []RewardItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type LabyrinthSeasonMilestone struct {
|
||||||
|
HeadQuestId int32
|
||||||
|
HeadStageOrder int32
|
||||||
|
Rewards []RewardItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type labyrinthStageKey struct {
|
||||||
|
ChapterId int32
|
||||||
|
StageOrder int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type LabyrinthCatalog struct {
|
||||||
|
ChaptersByOrder []LabyrinthChapter
|
||||||
|
ClearRewardsByStage map[labyrinthStageKey][]RewardItem
|
||||||
|
AccumTiersByStage map[labyrinthStageKey][]LabyrinthStageTier
|
||||||
|
SeasonMilestonesByChapter map[int32][]LabyrinthSeasonMilestone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LabyrinthCatalog) StageClearReward(chapterId, stageOrder int32) []RewardItem {
|
||||||
|
return c.ClearRewardsByStage[labyrinthStageKey{chapterId, stageOrder}]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LabyrinthCatalog) CollectAccumulationRewards(chapterId, stageOrder, oldCount, targetCount int32) ([]RewardItem, int32) {
|
||||||
|
var items []RewardItem
|
||||||
|
highest := int32(0)
|
||||||
|
for _, t := range c.AccumTiersByStage[labyrinthStageKey{chapterId, stageOrder}] {
|
||||||
|
if t.QuestMissionClearCount > oldCount && t.QuestMissionClearCount <= targetCount {
|
||||||
|
items = append(items, t.Rewards...)
|
||||||
|
if t.QuestMissionClearCount > highest {
|
||||||
|
highest = t.QuestMissionClearCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items, highest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LabyrinthCatalog) SeasonMilestones(chapterId int32) []LabyrinthSeasonMilestone {
|
||||||
|
return c.SeasonMilestonesByChapter[chapterId]
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadLabyrinthCatalog() *LabyrinthCatalog {
|
||||||
|
seasonRows, err := utils.ReadTable[EntityMEventQuestLabyrinthSeason]("m_event_quest_labyrinth_season")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[labyrinth] m_event_quest_labyrinth_season unavailable, labyrinth disabled: %v", err)
|
||||||
|
return &LabyrinthCatalog{}
|
||||||
|
}
|
||||||
|
stageRows, err := utils.ReadTable[EntityMEventQuestLabyrinthStage]("m_event_quest_labyrinth_stage")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[labyrinth] m_event_quest_labyrinth_stage unavailable, labyrinth disabled: %v", err)
|
||||||
|
return &LabyrinthCatalog{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// chapterId -> highest SeasonNumber
|
||||||
|
latestSeason := make(map[int32]int32)
|
||||||
|
for _, r := range seasonRows {
|
||||||
|
if r.SeasonNumber > latestSeason[r.EventQuestChapterId] {
|
||||||
|
latestSeason[r.EventQuestChapterId] = r.SeasonNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// chapterId -> stage orders
|
||||||
|
stagesByChapter := make(map[int32][]int32)
|
||||||
|
for _, r := range stageRows {
|
||||||
|
stagesByChapter[r.EventQuestChapterId] = append(stagesByChapter[r.EventQuestChapterId], r.StageOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
chapters := make([]LabyrinthChapter, 0, len(latestSeason))
|
||||||
|
for chapterId, season := range latestSeason {
|
||||||
|
stages := stagesByChapter[chapterId]
|
||||||
|
sort.Slice(stages, func(i, j int) bool { return stages[i] < stages[j] })
|
||||||
|
chapters = append(chapters, LabyrinthChapter{
|
||||||
|
EventQuestChapterId: chapterId,
|
||||||
|
LatestSeasonNumber: season,
|
||||||
|
StageOrders: stages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(chapters, func(i, j int) bool {
|
||||||
|
return chapters[i].EventQuestChapterId < chapters[j].EventQuestChapterId
|
||||||
|
})
|
||||||
|
|
||||||
|
clearRewards, accumTiers, seasonMilestones := loadLabyrinthRewards(seasonRows, stageRows)
|
||||||
|
|
||||||
|
log.Printf("labyrinth catalog loaded: %d chapters, %d stages with clear rewards, %d with accumulation rewards, %d chapters with season rewards",
|
||||||
|
len(chapters), len(clearRewards), len(accumTiers), len(seasonMilestones))
|
||||||
|
return &LabyrinthCatalog{
|
||||||
|
ChaptersByOrder: chapters,
|
||||||
|
ClearRewardsByStage: clearRewards,
|
||||||
|
AccumTiersByStage: accumTiers,
|
||||||
|
SeasonMilestonesByChapter: seasonMilestones,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadLabyrinthRewards(seasonRows []EntityMEventQuestLabyrinthSeason, stageRows []EntityMEventQuestLabyrinthStage) (
|
||||||
|
clearRewards map[labyrinthStageKey][]RewardItem,
|
||||||
|
accumTiers map[labyrinthStageKey][]LabyrinthStageTier,
|
||||||
|
seasonMilestones map[int32][]LabyrinthSeasonMilestone,
|
||||||
|
) {
|
||||||
|
rewardGroupRows, err := utils.ReadTable[EntityMEventQuestLabyrinthRewardGroup]("m_event_quest_labyrinth_reward_group")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[labyrinth] m_event_quest_labyrinth_reward_group unavailable, rewards disabled: %v", err)
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reward group id -> reward items
|
||||||
|
itemsByRewardGroup := make(map[int32][]RewardItem)
|
||||||
|
for _, r := range rewardGroupRows {
|
||||||
|
itemsByRewardGroup[r.EventQuestLabyrinthRewardGroupId] = append(itemsByRewardGroup[r.EventQuestLabyrinthRewardGroupId], RewardItem{
|
||||||
|
PossessionType: r.PossessionType,
|
||||||
|
PossessionId: r.PossessionId,
|
||||||
|
Count: r.Count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// per-stage one-time clear reward
|
||||||
|
clearRewards = make(map[labyrinthStageKey][]RewardItem)
|
||||||
|
for _, r := range stageRows {
|
||||||
|
if r.StageClearRewardGroupId == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if items := itemsByRewardGroup[r.StageClearRewardGroupId]; len(items) > 0 {
|
||||||
|
clearRewards[labyrinthStageKey{r.EventQuestChapterId, r.StageOrder}] = items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if accumGroupRows, err := utils.ReadTable[EntityMEventQuestLabyrinthStageAccumulationRewardGroup]("m_event_quest_labyrinth_stage_accumulation_reward_group"); err != nil {
|
||||||
|
log.Printf("[labyrinth] m_event_quest_labyrinth_stage_accumulation_reward_group unavailable, accumulation rewards disabled: %v", err)
|
||||||
|
} else {
|
||||||
|
// accumulation group id -> tiers (threshold + resolved reward items)
|
||||||
|
tiersByGroup := make(map[int32][]LabyrinthStageTier)
|
||||||
|
for _, r := range accumGroupRows {
|
||||||
|
tiersByGroup[r.EventQuestLabyrinthStageAccumulationRewardGroupId] = append(tiersByGroup[r.EventQuestLabyrinthStageAccumulationRewardGroupId], LabyrinthStageTier{
|
||||||
|
QuestMissionClearCount: r.QuestMissionClearCount,
|
||||||
|
Rewards: itemsByRewardGroup[r.EventQuestLabyrinthRewardGroupId],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
accumTiers = make(map[labyrinthStageKey][]LabyrinthStageTier)
|
||||||
|
for _, r := range stageRows {
|
||||||
|
if r.StageAccumulationRewardGroupId == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tiers := tiersByGroup[r.StageAccumulationRewardGroupId]
|
||||||
|
sort.Slice(tiers, func(i, j int) bool {
|
||||||
|
return tiers[i].QuestMissionClearCount < tiers[j].QuestMissionClearCount
|
||||||
|
})
|
||||||
|
accumTiers[labyrinthStageKey{r.EventQuestChapterId, r.StageOrder}] = tiers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// per-chapter season-reward milestones
|
||||||
|
if seasonRewardRows, err := utils.ReadTable[EntityMEventQuestLabyrinthSeasonRewardGroup]("m_event_quest_labyrinth_season_reward_group"); err != nil {
|
||||||
|
log.Printf("[labyrinth] m_event_quest_labyrinth_season_reward_group unavailable, season rewards disabled: %v", err)
|
||||||
|
} else {
|
||||||
|
seasonMilestones = buildLabyrinthSeasonMilestones(seasonRows, seasonRewardRows, itemsByRewardGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clearRewards, accumTiers, seasonMilestones
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLabyrinthSeasonMilestones(
|
||||||
|
seasonRows []EntityMEventQuestLabyrinthSeason,
|
||||||
|
seasonRewardRows []EntityMEventQuestLabyrinthSeasonRewardGroup,
|
||||||
|
itemsByRewardGroup map[int32][]RewardItem,
|
||||||
|
) map[int32][]LabyrinthSeasonMilestone {
|
||||||
|
// chapter -> SeasonRewardGroupId (all seasons of a chapter share one)
|
||||||
|
groupByChapter := make(map[int32]int32)
|
||||||
|
for _, r := range seasonRows {
|
||||||
|
groupByChapter[r.EventQuestChapterId] = r.SeasonRewardGroupId
|
||||||
|
}
|
||||||
|
// SeasonRewardGroupId -> its rows, in table order
|
||||||
|
rowsByGroup := make(map[int32][]EntityMEventQuestLabyrinthSeasonRewardGroup)
|
||||||
|
for _, r := range seasonRewardRows {
|
||||||
|
rowsByGroup[r.EventQuestLabyrinthSeasonRewardGroupId] = append(rowsByGroup[r.EventQuestLabyrinthSeasonRewardGroupId], r)
|
||||||
|
}
|
||||||
|
|
||||||
|
milestones := make(map[int32][]LabyrinthSeasonMilestone)
|
||||||
|
for chapterId, seasonGroupId := range groupByChapter {
|
||||||
|
rows := rowsByGroup[seasonGroupId]
|
||||||
|
if len(rows) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// rank distinct reward-group ids ascending -> 1-based head stage order
|
||||||
|
stageByRewardGroup := make(map[int32]int32)
|
||||||
|
var distinct []int32
|
||||||
|
for _, r := range rows {
|
||||||
|
if _, seen := stageByRewardGroup[r.EventQuestLabyrinthRewardGroupId]; !seen {
|
||||||
|
stageByRewardGroup[r.EventQuestLabyrinthRewardGroupId] = 0
|
||||||
|
distinct = append(distinct, r.EventQuestLabyrinthRewardGroupId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(distinct, func(i, j int) bool { return distinct[i] < distinct[j] })
|
||||||
|
for i, gid := range distinct {
|
||||||
|
stageByRewardGroup[gid] = int32(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
list := make([]LabyrinthSeasonMilestone, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
list = append(list, LabyrinthSeasonMilestone{
|
||||||
|
HeadQuestId: r.HeadQuestId,
|
||||||
|
HeadStageOrder: stageByRewardGroup[r.EventQuestLabyrinthRewardGroupId],
|
||||||
|
Rewards: itemsByRewardGroup[r.EventQuestLabyrinthRewardGroupId],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
milestones[chapterId] = list
|
||||||
|
}
|
||||||
|
return milestones
|
||||||
|
}
|
||||||
@@ -12,10 +12,6 @@ const (
|
|||||||
QuestFlowTypeAnotherRouteReplayFlow QuestFlowType = 4
|
QuestFlowTypeAnotherRouteReplayFlow QuestFlowType = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsReplayQuestFlowType reports whether the flow type indicates an active
|
|
||||||
// replay session — either same-route REPLAY_FLOW or cross-route
|
|
||||||
// ANOTHER_ROUTE_REPLAY_FLOW. Mirrors the client's Story.IsReplayQuestFlowType
|
|
||||||
// predicate (dump.cs:768202).
|
|
||||||
func IsReplayQuestFlowType(t int32) bool {
|
func IsReplayQuestFlowType(t int32) bool {
|
||||||
return t == int32(QuestFlowTypeReplayFlow) ||
|
return t == int32(QuestFlowTypeReplayFlow) ||
|
||||||
t == int32(QuestFlowTypeAnotherRouteReplayFlow)
|
t == int32(QuestFlowTypeAnotherRouteReplayFlow)
|
||||||
@@ -170,7 +166,6 @@ const (
|
|||||||
|
|
||||||
type SideStorySceneIdType int32
|
type SideStorySceneIdType int32
|
||||||
|
|
||||||
// Values mirror SideStoryTypes.SceneIdTypes in the client (dump.cs).
|
|
||||||
const (
|
const (
|
||||||
SideStorySceneInvalid SideStorySceneIdType = 0
|
SideStorySceneInvalid SideStorySceneIdType = 0
|
||||||
SideStorySceneIntroduction SideStorySceneIdType = 1
|
SideStorySceneIntroduction SideStorySceneIdType = 1
|
||||||
|
|||||||
@@ -141,6 +141,8 @@ func buildCatalogs() (*Catalogs, error) {
|
|||||||
|
|
||||||
towerCatalog := masterdata.LoadTowerCatalog()
|
towerCatalog := masterdata.LoadTowerCatalog()
|
||||||
|
|
||||||
|
labyrinthCatalog := masterdata.LoadLabyrinthCatalog()
|
||||||
|
|
||||||
return &Catalogs{
|
return &Catalogs{
|
||||||
GameConfig: gameConfig,
|
GameConfig: gameConfig,
|
||||||
Parts: partsCatalog,
|
Parts: partsCatalog,
|
||||||
@@ -167,6 +169,7 @@ func buildCatalogs() (*Catalogs, error) {
|
|||||||
SideStory: sideStoryCatalog,
|
SideStory: sideStoryCatalog,
|
||||||
BigHunt: bigHuntCatalog,
|
BigHunt: bigHuntCatalog,
|
||||||
Tower: towerCatalog,
|
Tower: towerCatalog,
|
||||||
|
Labyrinth: labyrinthCatalog,
|
||||||
QuestHandler: questHandler,
|
QuestHandler: questHandler,
|
||||||
GachaHandler: gachaHandler,
|
GachaHandler: gachaHandler,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ type Catalogs struct {
|
|||||||
SideStory *masterdata.SideStoryCatalog
|
SideStory *masterdata.SideStoryCatalog
|
||||||
BigHunt *masterdata.BigHuntCatalog
|
BigHunt *masterdata.BigHuntCatalog
|
||||||
Tower *masterdata.TowerCatalog
|
Tower *masterdata.TowerCatalog
|
||||||
|
Labyrinth *masterdata.LabyrinthCatalog
|
||||||
|
|
||||||
// Catalog-derived handlers must rebuild on every reload because they
|
// Catalog-derived handlers must rebuild on every reload because they
|
||||||
// embed/cache pointers to specific catalog instances.
|
// embed/cache pointers to specific catalog instances.
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
pb "lunar-tear/server/gen/proto"
|
||||||
|
"lunar-tear/server/internal/gametime"
|
||||||
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LabyrinthServiceServer struct {
|
||||||
|
pb.UnimplementedLabyrinthServiceServer
|
||||||
|
users store.UserRepository
|
||||||
|
sessions store.SessionRepository
|
||||||
|
holder *runtime.Holder
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLabyrinthServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *LabyrinthServiceServer {
|
||||||
|
if holder == nil {
|
||||||
|
panic("runtime holder is required")
|
||||||
|
}
|
||||||
|
return &LabyrinthServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LabyrinthServiceServer) ReceiveStageAccumulationReward(ctx context.Context, req *pb.ReceiveStageAccumulationRewardRequest) (*pb.ReceiveStageAccumulationRewardResponse, error) {
|
||||||
|
log.Printf("[LabyrinthService] ReceiveStageAccumulationReward: chapter=%d stage=%d questMissionClearCount=%d",
|
||||||
|
req.EventQuestChapterId, req.StageOrder, req.QuestMissionClearCount)
|
||||||
|
|
||||||
|
cat := s.holder.Get()
|
||||||
|
laby := cat.Labyrinth
|
||||||
|
granter := cat.QuestHandler.Granter
|
||||||
|
|
||||||
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
|
key := store.LabyrinthStageKey{
|
||||||
|
EventQuestChapterId: req.EventQuestChapterId,
|
||||||
|
StageOrder: req.StageOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
|
rec := user.LabyrinthStages[key]
|
||||||
|
old := rec.AccumulationRewardReceivedQuestMissionCount
|
||||||
|
|
||||||
|
items, highest := laby.CollectAccumulationRewards(req.EventQuestChapterId, req.StageOrder, old, req.QuestMissionClearCount)
|
||||||
|
if highest <= old {
|
||||||
|
log.Printf("[LabyrinthService] ReceiveStageAccumulationReward: nothing to grant for chapter=%d stage=%d (claimed=%d, target=%d)",
|
||||||
|
req.EventQuestChapterId, req.StageOrder, old, req.QuestMissionClearCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, it := range items {
|
||||||
|
granter.GrantFull(user, model.PossessionType(it.PossessionType), it.PossessionId, it.Count, nowMillis)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec.EventQuestChapterId = req.EventQuestChapterId
|
||||||
|
rec.StageOrder = req.StageOrder
|
||||||
|
rec.AccumulationRewardReceivedQuestMissionCount = highest
|
||||||
|
rec.LatestVersion = nowMillis
|
||||||
|
user.LabyrinthStages[key] = rec
|
||||||
|
|
||||||
|
log.Printf("[LabyrinthService] ReceiveStageAccumulationReward: chapter=%d stage=%d granted %d item(s), claimed %d -> %d",
|
||||||
|
req.EventQuestChapterId, req.StageOrder, len(items), old, highest)
|
||||||
|
})
|
||||||
|
|
||||||
|
return &pb.ReceiveStageAccumulationRewardResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LabyrinthServiceServer) ReceiveStageClearReward(ctx context.Context, req *pb.ReceiveStageClearRewardRequest) (*pb.ReceiveStageClearRewardResponse, error) {
|
||||||
|
log.Printf("[LabyrinthService] ReceiveStageClearReward: chapter=%d stage=%d",
|
||||||
|
req.EventQuestChapterId, req.StageOrder)
|
||||||
|
|
||||||
|
cat := s.holder.Get()
|
||||||
|
laby := cat.Labyrinth
|
||||||
|
granter := cat.QuestHandler.Granter
|
||||||
|
|
||||||
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
|
key := store.LabyrinthStageKey{
|
||||||
|
EventQuestChapterId: req.EventQuestChapterId,
|
||||||
|
StageOrder: req.StageOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
|
rec := user.LabyrinthStages[key]
|
||||||
|
if rec.IsReceivedStageClearReward {
|
||||||
|
log.Printf("[LabyrinthService] ReceiveStageClearReward: already claimed chapter=%d stage=%d",
|
||||||
|
req.EventQuestChapterId, req.StageOrder)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items := laby.StageClearReward(req.EventQuestChapterId, req.StageOrder)
|
||||||
|
for _, it := range items {
|
||||||
|
granter.GrantFull(user, model.PossessionType(it.PossessionType), it.PossessionId, it.Count, nowMillis)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec.EventQuestChapterId = req.EventQuestChapterId
|
||||||
|
rec.StageOrder = req.StageOrder
|
||||||
|
rec.IsReceivedStageClearReward = true
|
||||||
|
rec.LatestVersion = nowMillis
|
||||||
|
user.LabyrinthStages[key] = rec
|
||||||
|
|
||||||
|
log.Printf("[LabyrinthService] ReceiveStageClearReward: chapter=%d stage=%d granted %d item(s)",
|
||||||
|
req.EventQuestChapterId, req.StageOrder, len(items))
|
||||||
|
})
|
||||||
|
|
||||||
|
return &pb.ReceiveStageClearRewardResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LabyrinthServiceServer) UpdateSeasonData(ctx context.Context, req *pb.UpdateSeasonDataRequest) (*pb.UpdateSeasonDataResponse, error) {
|
||||||
|
laby := s.holder.Get().Labyrinth
|
||||||
|
|
||||||
|
var seasonResult []*pb.LabyrinthSeasonResult
|
||||||
|
for _, m := range laby.SeasonMilestones(req.EventQuestChapterId) {
|
||||||
|
rewards := make([]*pb.LabyrinthReward, 0, len(m.Rewards))
|
||||||
|
for _, it := range m.Rewards {
|
||||||
|
rewards = append(rewards, &pb.LabyrinthReward{
|
||||||
|
PossessionType: it.PossessionType,
|
||||||
|
PossessionId: it.PossessionId,
|
||||||
|
Count: it.Count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
seasonResult = append(seasonResult, &pb.LabyrinthSeasonResult{
|
||||||
|
EventQuestChapterId: req.EventQuestChapterId,
|
||||||
|
HeadQuestId: m.HeadQuestId,
|
||||||
|
SeasonReward: rewards,
|
||||||
|
HeadStageOrder: m.HeadStageOrder,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[LabyrinthService] UpdateSeasonData: chapter=%d -> %d milestone(s)",
|
||||||
|
req.EventQuestChapterId, len(seasonResult))
|
||||||
|
return &pb.UpdateSeasonDataResponse{SeasonResult: seasonResult}, nil
|
||||||
|
}
|
||||||
@@ -27,6 +27,8 @@ func CloneUserState(u UserState) UserState {
|
|||||||
}
|
}
|
||||||
out.CageOrnamentRewards = maps.Clone(u.CageOrnamentRewards)
|
out.CageOrnamentRewards = maps.Clone(u.CageOrnamentRewards)
|
||||||
out.TowerAccumulationRewards = maps.Clone(u.TowerAccumulationRewards)
|
out.TowerAccumulationRewards = maps.Clone(u.TowerAccumulationRewards)
|
||||||
|
out.LabyrinthSeasons = maps.Clone(u.LabyrinthSeasons)
|
||||||
|
out.LabyrinthStages = maps.Clone(u.LabyrinthStages)
|
||||||
out.ConsumableItems = maps.Clone(u.ConsumableItems)
|
out.ConsumableItems = maps.Clone(u.ConsumableItems)
|
||||||
out.Materials = maps.Clone(u.Materials)
|
out.Materials = maps.Clone(u.Materials)
|
||||||
out.Parts = maps.Clone(u.Parts)
|
out.Parts = maps.Clone(u.Parts)
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ func SeedUserState(userId int64, uuid string, nowMillis int64, platform model.Cl
|
|||||||
},
|
},
|
||||||
CageOrnamentRewards: make(map[int32]CageOrnamentRewardState),
|
CageOrnamentRewards: make(map[int32]CageOrnamentRewardState),
|
||||||
TowerAccumulationRewards: make(map[int32]TowerAccumulationRewardState),
|
TowerAccumulationRewards: make(map[int32]TowerAccumulationRewardState),
|
||||||
|
LabyrinthSeasons: make(map[int32]LabyrinthSeasonState),
|
||||||
|
LabyrinthStages: make(map[LabyrinthStageKey]LabyrinthStageState),
|
||||||
ConsumableItems: make(map[int32]int32),
|
ConsumableItems: make(map[int32]int32),
|
||||||
Materials: make(map[int32]int32),
|
Materials: make(map[int32]int32),
|
||||||
Thoughts: make(map[string]ThoughtState),
|
Thoughts: make(map[string]ThoughtState),
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ func initMaps(u *store.UserState) {
|
|||||||
u.ExploreScores = make(map[int32]store.ExploreScoreState)
|
u.ExploreScores = make(map[int32]store.ExploreScoreState)
|
||||||
u.CageOrnamentRewards = make(map[int32]store.CageOrnamentRewardState)
|
u.CageOrnamentRewards = make(map[int32]store.CageOrnamentRewardState)
|
||||||
u.TowerAccumulationRewards = make(map[int32]store.TowerAccumulationRewardState)
|
u.TowerAccumulationRewards = make(map[int32]store.TowerAccumulationRewardState)
|
||||||
|
u.LabyrinthSeasons = make(map[int32]store.LabyrinthSeasonState)
|
||||||
|
u.LabyrinthStages = make(map[store.LabyrinthStageKey]store.LabyrinthStageState)
|
||||||
u.CharacterBoards = make(map[int32]store.CharacterBoardState)
|
u.CharacterBoards = make(map[int32]store.CharacterBoardState)
|
||||||
u.CharacterBoardAbilities = make(map[store.CharacterBoardAbilityKey]store.CharacterBoardAbilityState)
|
u.CharacterBoardAbilities = make(map[store.CharacterBoardAbilityKey]store.CharacterBoardAbilityState)
|
||||||
u.CharacterBoardStatusUps = make(map[store.CharacterBoardStatusUpKey]store.CharacterBoardStatusUpState)
|
u.CharacterBoardStatusUps = make(map[store.CharacterBoardStatusUpKey]store.CharacterBoardStatusUpState)
|
||||||
@@ -659,6 +661,22 @@ func loadMapTables(db *sql.DB, uid int64, u *store.UserState) {
|
|||||||
u.TowerAccumulationRewards[v.EventQuestChapterId] = v
|
u.TowerAccumulationRewards[v.EventQuestChapterId] = v
|
||||||
})
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT event_quest_chapter_id, last_join_season_number, last_season_reward_received_season_number, latest_version
|
||||||
|
FROM user_event_quest_labyrinth_seasons WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.LabyrinthSeasonState
|
||||||
|
rows.Scan(&v.EventQuestChapterId, &v.LastJoinSeasonNumber, &v.LastSeasonRewardReceivedSeasonNumber, &v.LatestVersion)
|
||||||
|
u.LabyrinthSeasons[v.EventQuestChapterId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT event_quest_chapter_id, stage_order, is_received_stage_clear_reward, accumulation_reward_received_quest_mission_count, latest_version
|
||||||
|
FROM user_event_quest_labyrinth_stages WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.LabyrinthStageState
|
||||||
|
var rcvd int
|
||||||
|
rows.Scan(&v.EventQuestChapterId, &v.StageOrder, &rcvd, &v.AccumulationRewardReceivedQuestMissionCount, &v.LatestVersion)
|
||||||
|
v.IsReceivedStageClearReward = rcvd != 0
|
||||||
|
u.LabyrinthStages[store.LabyrinthStageKey{EventQuestChapterId: v.EventQuestChapterId, StageOrder: v.StageOrder}] = v
|
||||||
|
})
|
||||||
|
|
||||||
queryRows(db, `SELECT shop_item_id, bought_count, latest_bought_count_changed_datetime, latest_version
|
queryRows(db, `SELECT shop_item_id, bought_count, latest_bought_count_changed_datetime, latest_version
|
||||||
FROM user_shop_items WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
FROM user_shop_items WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
var v store.UserShopItemState
|
var v store.UserShopItemState
|
||||||
|
|||||||
@@ -460,6 +460,18 @@ func writeUserState(tx *sql.Tx, uid int64, u *store.UserState) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, v := range u.LabyrinthSeasons {
|
||||||
|
if err := exec(`INSERT INTO user_event_quest_labyrinth_seasons (user_id, event_quest_chapter_id, last_join_season_number, last_season_reward_received_season_number, latest_version) VALUES (?,?,?,?,?)`,
|
||||||
|
uid, v.EventQuestChapterId, v.LastJoinSeasonNumber, v.LastSeasonRewardReceivedSeasonNumber, v.LatestVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range u.LabyrinthStages {
|
||||||
|
if err := exec(`INSERT INTO user_event_quest_labyrinth_stages (user_id, event_quest_chapter_id, stage_order, is_received_stage_clear_reward, accumulation_reward_received_quest_mission_count, latest_version) VALUES (?,?,?,?,?,?)`,
|
||||||
|
uid, k.EventQuestChapterId, k.StageOrder, boolToInt(v.IsReceivedStageClearReward), v.AccumulationRewardReceivedQuestMissionCount, v.LatestVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, v := range u.ShopItems {
|
for _, v := range u.ShopItems {
|
||||||
if err := exec(`INSERT INTO user_shop_items (user_id, shop_item_id, bought_count, latest_bought_count_changed_datetime, latest_version) VALUES (?,?,?,?,?)`,
|
if err := exec(`INSERT INTO user_shop_items (user_id, shop_item_id, bought_count, latest_bought_count_changed_datetime, latest_version) VALUES (?,?,?,?,?)`,
|
||||||
uid, v.ShopItemId, v.BoughtCount, v.LatestBoughtCountChangedDatetime, v.LatestVersion); err != nil {
|
uid, v.ShopItemId, v.BoughtCount, v.LatestBoughtCountChangedDatetime, v.LatestVersion); err != nil {
|
||||||
@@ -1005,6 +1017,22 @@ func diffAndSave(tx *sql.Tx, uid int64, before, after *store.UserState) error {
|
|||||||
return []any{v.EventQuestChapterId, v.LatestRewardReceiveQuestMissionClearCount, v.LatestVersion}
|
return []any{v.EventQuestChapterId, v.LatestRewardReceiveQuestMissionClearCount, v.LatestVersion}
|
||||||
},
|
},
|
||||||
"event_quest_chapter_id, latest_reward_receive_quest_mission_clear_count, latest_version")
|
"event_quest_chapter_id, latest_reward_receive_quest_mission_clear_count, latest_version")
|
||||||
|
diffMapInt32(tx, uid, before.LabyrinthSeasons, after.LabyrinthSeasons, "user_event_quest_labyrinth_seasons", "event_quest_chapter_id",
|
||||||
|
func(v store.LabyrinthSeasonState) []any {
|
||||||
|
return []any{v.EventQuestChapterId, v.LastJoinSeasonNumber, v.LastSeasonRewardReceivedSeasonNumber, v.LatestVersion}
|
||||||
|
},
|
||||||
|
"event_quest_chapter_id, last_join_season_number, last_season_reward_received_season_number, latest_version")
|
||||||
|
for k, v := range after.LabyrinthStages {
|
||||||
|
if old, ok := before.LabyrinthStages[k]; !ok || old != v {
|
||||||
|
exec(`INSERT OR REPLACE INTO user_event_quest_labyrinth_stages (user_id, event_quest_chapter_id, stage_order, is_received_stage_clear_reward, accumulation_reward_received_quest_mission_count, latest_version) VALUES (?,?,?,?,?,?)`,
|
||||||
|
uid, k.EventQuestChapterId, k.StageOrder, boolToInt(v.IsReceivedStageClearReward), v.AccumulationRewardReceivedQuestMissionCount, v.LatestVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k := range before.LabyrinthStages {
|
||||||
|
if _, ok := after.LabyrinthStages[k]; !ok {
|
||||||
|
exec(`DELETE FROM user_event_quest_labyrinth_stages WHERE user_id=? AND event_quest_chapter_id=? AND stage_order=?`, uid, k.EventQuestChapterId, k.StageOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
diffMapInt32(tx, uid, before.ShopItems, after.ShopItems, "user_shop_items", "shop_item_id",
|
diffMapInt32(tx, uid, before.ShopItems, after.ShopItems, "user_shop_items", "shop_item_id",
|
||||||
func(v store.UserShopItemState) []any {
|
func(v store.UserShopItemState) []any {
|
||||||
return []any{v.ShopItemId, v.BoughtCount, v.LatestBoughtCountChangedDatetime, v.LatestVersion}
|
return []any{v.ShopItemId, v.BoughtCount, v.LatestBoughtCountChangedDatetime, v.LatestVersion}
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ func (s *SQLiteStore) ImportUser(u *store.UserState) error {
|
|||||||
|
|
||||||
// Child tables in reverse-dependency order (matches schema's goose Down).
|
// Child tables in reverse-dependency order (matches schema's goose Down).
|
||||||
childTables := []string{
|
childTables := []string{
|
||||||
|
"user_event_quest_labyrinth_stages",
|
||||||
|
"user_event_quest_labyrinth_seasons",
|
||||||
"user_event_quest_tower_accumulation_rewards",
|
"user_event_quest_tower_accumulation_rewards",
|
||||||
"user_cage_ornament_rewards",
|
"user_cage_ornament_rewards",
|
||||||
"user_shop_replaceable_lineup",
|
"user_shop_replaceable_lineup",
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ type UserState struct {
|
|||||||
Gimmick GimmickState
|
Gimmick GimmickState
|
||||||
CageOrnamentRewards map[int32]CageOrnamentRewardState
|
CageOrnamentRewards map[int32]CageOrnamentRewardState
|
||||||
TowerAccumulationRewards map[int32]TowerAccumulationRewardState
|
TowerAccumulationRewards map[int32]TowerAccumulationRewardState
|
||||||
|
LabyrinthSeasons map[int32]LabyrinthSeasonState
|
||||||
|
LabyrinthStages map[LabyrinthStageKey]LabyrinthStageState
|
||||||
ConsumableItems map[int32]int32
|
ConsumableItems map[int32]int32
|
||||||
Materials map[int32]int32
|
Materials map[int32]int32
|
||||||
Parts map[string]PartsState
|
Parts map[string]PartsState
|
||||||
@@ -196,6 +198,12 @@ func (u *UserState) EnsureMaps() {
|
|||||||
if u.TowerAccumulationRewards == nil {
|
if u.TowerAccumulationRewards == nil {
|
||||||
u.TowerAccumulationRewards = make(map[int32]TowerAccumulationRewardState)
|
u.TowerAccumulationRewards = make(map[int32]TowerAccumulationRewardState)
|
||||||
}
|
}
|
||||||
|
if u.LabyrinthSeasons == nil {
|
||||||
|
u.LabyrinthSeasons = make(map[int32]LabyrinthSeasonState)
|
||||||
|
}
|
||||||
|
if u.LabyrinthStages == nil {
|
||||||
|
u.LabyrinthStages = make(map[LabyrinthStageKey]LabyrinthStageState)
|
||||||
|
}
|
||||||
if u.ConsumableItems == nil {
|
if u.ConsumableItems == nil {
|
||||||
u.ConsumableItems = make(map[int32]int32)
|
u.ConsumableItems = make(map[int32]int32)
|
||||||
}
|
}
|
||||||
@@ -878,6 +886,27 @@ type TowerAccumulationRewardState struct {
|
|||||||
LatestVersion int64
|
LatestVersion int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LabyrinthSeasonState struct {
|
||||||
|
EventQuestChapterId int32
|
||||||
|
LastJoinSeasonNumber int32
|
||||||
|
LastSeasonRewardReceivedSeasonNumber int32
|
||||||
|
LatestVersion int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabyrinthStageKey is the composite key for UserState.LabyrinthStages.
|
||||||
|
type LabyrinthStageKey struct {
|
||||||
|
EventQuestChapterId int32
|
||||||
|
StageOrder int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type LabyrinthStageState struct {
|
||||||
|
EventQuestChapterId int32
|
||||||
|
StageOrder int32
|
||||||
|
IsReceivedStageClearReward bool
|
||||||
|
AccumulationRewardReceivedQuestMissionCount int32
|
||||||
|
LatestVersion int64
|
||||||
|
}
|
||||||
|
|
||||||
type PartsState struct {
|
type PartsState struct {
|
||||||
UserPartsUuid string
|
UserPartsUuid string
|
||||||
PartsId int32
|
PartsId int32
|
||||||
|
|||||||
@@ -266,6 +266,9 @@ func ChangedTables(before, after *store.UserState) []string {
|
|||||||
if !mapsEqualStruct(before.TowerAccumulationRewards, after.TowerAccumulationRewards) {
|
if !mapsEqualStruct(before.TowerAccumulationRewards, after.TowerAccumulationRewards) {
|
||||||
add("IUserEventQuestTowerAccumulationReward")
|
add("IUserEventQuestTowerAccumulationReward")
|
||||||
}
|
}
|
||||||
|
if !mapsEqualStruct(before.LabyrinthStages, after.LabyrinthStages) {
|
||||||
|
add("IUserEventQuestLabyrinthStage")
|
||||||
|
}
|
||||||
|
|
||||||
if !mapsEqualStruct(before.BigHuntMaxScores, after.BigHuntMaxScores) {
|
if !mapsEqualStruct(before.BigHuntMaxScores, after.BigHuntMaxScores) {
|
||||||
add("IUserBigHuntMaxScore")
|
add("IUserBigHuntMaxScore")
|
||||||
@@ -431,6 +434,8 @@ func keyFieldsForTable(table string) []string {
|
|||||||
return []string{"userId", "cageOrnamentId"}
|
return []string{"userId", "cageOrnamentId"}
|
||||||
case "IUserEventQuestTowerAccumulationReward":
|
case "IUserEventQuestTowerAccumulationReward":
|
||||||
return []string{"userId", "eventQuestChapterId"}
|
return []string{"userId", "eventQuestChapterId"}
|
||||||
|
case "IUserEventQuestLabyrinthStage":
|
||||||
|
return []string{"userId", "eventQuestChapterId", "stageOrder"}
|
||||||
case "IUserAutoSaleSettingDetail":
|
case "IUserAutoSaleSettingDetail":
|
||||||
return []string{"userId", "possessionAutoSaleItemType"}
|
return []string{"userId", "possessionAutoSaleItemType"}
|
||||||
case "IUserCharacterRebirth":
|
case "IUserCharacterRebirth":
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package userdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/masterdata"
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
"lunar-tear/server/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var labyrinthCatalog = sync.OnceValue(masterdata.LoadLabyrinthCatalog)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register("IUserEventQuestLabyrinthSeason", func(user store.UserState) string {
|
||||||
|
chapters := labyrinthCatalog().ChaptersByOrder
|
||||||
|
records := make([]map[string]any, 0, len(chapters))
|
||||||
|
for _, ch := range chapters {
|
||||||
|
if st, ok := user.LabyrinthSeasons[ch.EventQuestChapterId]; ok {
|
||||||
|
records = append(records, map[string]any{
|
||||||
|
"userId": user.UserId,
|
||||||
|
"eventQuestChapterId": st.EventQuestChapterId,
|
||||||
|
"lastJoinSeasonNumber": st.LastJoinSeasonNumber,
|
||||||
|
"lastSeasonRewardReceivedSeasonNumber": st.LastSeasonRewardReceivedSeasonNumber,
|
||||||
|
"latestVersion": st.LatestVersion,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
records = append(records, map[string]any{
|
||||||
|
"userId": user.UserId,
|
||||||
|
"eventQuestChapterId": ch.EventQuestChapterId,
|
||||||
|
"lastJoinSeasonNumber": ch.LatestSeasonNumber,
|
||||||
|
"lastSeasonRewardReceivedSeasonNumber": 0,
|
||||||
|
"latestVersion": user.GameStartDatetime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
s, _ := utils.EncodeJSONMaps(records...)
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
|
||||||
|
register("IUserEventQuestLabyrinthStage", func(user store.UserState) string {
|
||||||
|
records := make([]map[string]any, 0)
|
||||||
|
for _, ch := range labyrinthCatalog().ChaptersByOrder {
|
||||||
|
for _, stageOrder := range ch.StageOrders {
|
||||||
|
key := store.LabyrinthStageKey{
|
||||||
|
EventQuestChapterId: ch.EventQuestChapterId,
|
||||||
|
StageOrder: stageOrder,
|
||||||
|
}
|
||||||
|
if st, ok := user.LabyrinthStages[key]; ok {
|
||||||
|
records = append(records, map[string]any{
|
||||||
|
"userId": user.UserId,
|
||||||
|
"eventQuestChapterId": st.EventQuestChapterId,
|
||||||
|
"stageOrder": st.StageOrder,
|
||||||
|
"isReceivedStageClearReward": st.IsReceivedStageClearReward,
|
||||||
|
"accumulationRewardReceivedQuestMissionCount": st.AccumulationRewardReceivedQuestMissionCount,
|
||||||
|
"latestVersion": st.LatestVersion,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
records = append(records, map[string]any{
|
||||||
|
"userId": user.UserId,
|
||||||
|
"eventQuestChapterId": ch.EventQuestChapterId,
|
||||||
|
"stageOrder": stageOrder,
|
||||||
|
"isReceivedStageClearReward": false,
|
||||||
|
"accumulationRewardReceivedQuestMissionCount": 0,
|
||||||
|
"latestVersion": user.GameStartDatetime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s, _ := utils.EncodeJSONMaps(records...)
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -243,8 +243,6 @@ func init() {
|
|||||||
})
|
})
|
||||||
registerStatic(
|
registerStatic(
|
||||||
"IUserEventQuestDailyGroupCompleteReward",
|
"IUserEventQuestDailyGroupCompleteReward",
|
||||||
"IUserEventQuestLabyrinthSeason",
|
|
||||||
"IUserEventQuestLabyrinthStage",
|
|
||||||
"IUserQuestReplayFlowRewardGroup",
|
"IUserQuestReplayFlowRewardGroup",
|
||||||
"IUserQuestAutoOrbit",
|
"IUserQuestAutoOrbit",
|
||||||
"IUserQuestSceneChoice",
|
"IUserQuestSceneChoice",
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
-- +goose Up
|
||||||
|
CREATE TABLE user_event_quest_labyrinth_seasons (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
event_quest_chapter_id INTEGER NOT NULL,
|
||||||
|
last_join_season_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_season_reward_received_season_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, event_quest_chapter_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_event_quest_labyrinth_stages (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
event_quest_chapter_id INTEGER NOT NULL,
|
||||||
|
stage_order INTEGER NOT NULL,
|
||||||
|
is_received_stage_clear_reward INTEGER NOT NULL DEFAULT 0,
|
||||||
|
accumulation_reward_received_quest_mission_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, event_quest_chapter_id, stage_order)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP TABLE IF EXISTS user_event_quest_labyrinth_stages;
|
||||||
|
DROP TABLE IF EXISTS user_event_quest_labyrinth_seasons;
|
||||||
Reference in New Issue
Block a user