From 00817684ef31b1621d723daa420d9c234e020dfc Mon Sep 17 00:00:00 2001 From: Ilya Groshev Date: Wed, 13 May 2026 11:30:56 +0300 Subject: [PATCH] Implement Subjugation Quests rewards and battle report --- server/internal/masterdata/bighunt.go | 63 +++---- server/internal/masterdata/quest.go | 1 - server/internal/service/quest_bighunt.go | 173 ++++++++++++++++-- server/internal/service/reward.go | 37 +++- server/internal/store/sqlite/load.go | 11 +- server/internal/store/sqlite/save.go | 42 ++++- server/internal/store/types.go | 17 +- server/internal/userdata/proj_bighunt.go | 11 +- ...064604_add_bighunt_battle_report_state.sql | 18 ++ 9 files changed, 297 insertions(+), 76 deletions(-) create mode 100644 server/migrations/20260513064604_add_bighunt_battle_report_state.sql diff --git a/server/internal/masterdata/bighunt.go b/server/internal/masterdata/bighunt.go index 6d24c4e..87f5296 100644 --- a/server/internal/masterdata/bighunt.go +++ b/server/internal/masterdata/bighunt.go @@ -49,22 +49,17 @@ type RewardItem struct { Count int32 } -type BigHuntWeeklyRewardKey struct { - ScheduleId int32 - AttributeType int32 -} - type BigHuntCatalog struct { - BossQuestById map[int32]BigHuntBossQuestRow - QuestById map[int32]BigHuntQuestRow - ScoreCoefficients map[int32]int32 - BossByBossId map[int32]BigHuntBossRow - GradeThresholds map[int32][]GradeThreshold - ActiveScheduleId int32 - ScoreRewardSchedules map[int32][]ScoreRewardScheduleEntry - ScoreRewardThresholds map[int32][]ScoreRewardThreshold - RewardItems map[int32][]RewardItem - WeeklyRewardSchedules map[BigHuntWeeklyRewardKey][]ScoreRewardScheduleEntry + BossQuestById map[int32]BigHuntBossQuestRow + QuestById map[int32]BigHuntQuestRow + ScoreCoefficients map[int32]int32 + BossByBossId map[int32]BigHuntBossRow + GradeThresholds map[int32][]GradeThreshold + ActiveScheduleId int32 + ScoreRewardSchedules map[int32][]ScoreRewardScheduleEntry + ScoreRewardThresholds map[int32][]ScoreRewardThreshold + RewardItems map[int32][]RewardItem + WeeklyRewardSchedulesByAttr map[int32][]ScoreRewardScheduleEntry } func (c *BigHuntCatalog) ResolveActiveScoreRewardGroupId(scheduleId int32, nowMillis int64) int32 { @@ -80,8 +75,8 @@ func (c *BigHuntCatalog) ResolveActiveScoreRewardGroupId(scheduleId int32, nowMi return 0 } -func (c *BigHuntCatalog) ResolveActiveWeeklyRewardGroupId(key BigHuntWeeklyRewardKey, nowMillis int64) int32 { - entries := c.WeeklyRewardSchedules[key] +func (c *BigHuntCatalog) ResolveActiveWeeklyRewardGroupIdByAttr(attributeType int32, nowMillis int64) int32 { + entries := c.WeeklyRewardSchedulesByAttr[attributeType] for _, e := range entries { if nowMillis >= e.StartDatetime { return e.BigHuntScoreRewardGroupId @@ -264,20 +259,16 @@ func LoadBigHuntCatalog() *BigHuntCatalog { if err != nil { log.Fatalf("load big hunt weekly attribute score reward group schedule table: %v", err) } - weeklyRewardSchedules := make(map[BigHuntWeeklyRewardKey][]ScoreRewardScheduleEntry) + weeklyRewardSchedulesByAttr := make(map[int32][]ScoreRewardScheduleEntry) for _, r := range weeklySchedRows { - key := BigHuntWeeklyRewardKey{ - ScheduleId: r.BigHuntWeeklyAttributeScoreRewardGroupScheduleId, - AttributeType: r.AttributeType, - } - weeklyRewardSchedules[key] = append(weeklyRewardSchedules[key], ScoreRewardScheduleEntry{ + weeklyRewardSchedulesByAttr[r.AttributeType] = append(weeklyRewardSchedulesByAttr[r.AttributeType], ScoreRewardScheduleEntry{ BigHuntScoreRewardGroupId: r.BigHuntScoreRewardGroupId, StartDatetime: r.StartDatetime, }) } - for k := range weeklyRewardSchedules { - sort.Slice(weeklyRewardSchedules[k], func(i, j int) bool { - return weeklyRewardSchedules[k][i].StartDatetime > weeklyRewardSchedules[k][j].StartDatetime + for k := range weeklyRewardSchedulesByAttr { + sort.Slice(weeklyRewardSchedulesByAttr[k], func(i, j int) bool { + return weeklyRewardSchedulesByAttr[k][i].StartDatetime > weeklyRewardSchedulesByAttr[k][j].StartDatetime }) } @@ -285,15 +276,15 @@ func LoadBigHuntCatalog() *BigHuntCatalog { len(bossQuestById), len(questById), len(bossByBossId), len(scoreCoefficients), len(rewardItems), activeScheduleId) return &BigHuntCatalog{ - BossQuestById: bossQuestById, - QuestById: questById, - ScoreCoefficients: scoreCoefficients, - BossByBossId: bossByBossId, - GradeThresholds: gradeThresholds, - ActiveScheduleId: activeScheduleId, - ScoreRewardSchedules: scoreRewardSchedules, - ScoreRewardThresholds: scoreRewardThresholds, - RewardItems: rewardItems, - WeeklyRewardSchedules: weeklyRewardSchedules, + BossQuestById: bossQuestById, + QuestById: questById, + ScoreCoefficients: scoreCoefficients, + BossByBossId: bossByBossId, + GradeThresholds: gradeThresholds, + ActiveScheduleId: activeScheduleId, + ScoreRewardSchedules: scoreRewardSchedules, + ScoreRewardThresholds: scoreRewardThresholds, + RewardItems: rewardItems, + WeeklyRewardSchedulesByAttr: weeklyRewardSchedulesByAttr, } } diff --git a/server/internal/masterdata/quest.go b/server/internal/masterdata/quest.go index 70aba9a..f8a1e87 100644 --- a/server/internal/masterdata/quest.go +++ b/server/internal/masterdata/quest.go @@ -561,4 +561,3 @@ func (q *QuestCatalog) BattleOnlyTargetSceneIdFor(questId int32) (int32, bool) { v, ok := q.BattleOnlyTargetSceneByQuestId[questId] return v, ok } - diff --git a/server/internal/service/quest_bighunt.go b/server/internal/service/quest_bighunt.go index 6f48d58..1f71c02 100644 --- a/server/internal/service/quest_bighunt.go +++ b/server/internal/service/quest_bighunt.go @@ -44,6 +44,8 @@ func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.St log.Printf("[BigHuntService] StartBigHuntQuest: unknown bigHuntQuestId=%d", req.BigHuntQuestId) } + today := gametime.StartOfDayMillis() + s.users.UpdateUser(userId, func(user *store.UserState) { if ok { engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis) @@ -60,6 +62,9 @@ func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.St user.BigHuntDeckNumber = req.UserDeckNumber st := user.BigHuntStatuses[req.BigHuntBossQuestId] + if st.LatestChallengeDatetime < today { + st.DailyChallengeCount = 0 + } st.DailyChallengeCount++ st.LatestChallengeDatetime = nowMillis st.LatestVersion = nowMillis @@ -98,12 +103,15 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F var scoreInfo *pb.BigHuntScoreInfo var scoreRewards []*pb.BigHuntReward + var battleReportWaves []*pb.BigHuntBattleReportWave s.users.UpdateUser(userId, func(user *store.UserState) { engine.HandleBigHuntQuestFinish(user, bhQuest.QuestId, req.IsRetired, false, nowMillis) if req.IsRetired || user.BigHuntProgress.IsDryRun { user.BigHuntProgress = store.BigHuntProgress{LatestVersion: nowMillis} + user.BigHuntBattleBinary = nil + user.BigHuntBattleDetail = store.BigHuntBattleDetail{} return } @@ -129,11 +137,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F userScore := baseScore * int64(1000+difficultyBonusPermil+aliveBonusPermil+maxComboBonusPermil) / 1000 - isHighScore := false - oldMaxBoss := user.BigHuntMaxScores[bossQuest.BigHuntBossId] - oldMax := oldMaxBoss.MaxScore - if userScore > oldMax { - isHighScore = true + if userScore > user.BigHuntMaxScores[bossQuest.BigHuntBossId].MaxScore { user.BigHuntMaxScores[bossQuest.BigHuntBossId] = store.BigHuntMaxScore{ MaxScore: userScore, MaxScoreUpdateDatetime: nowMillis, @@ -146,7 +150,8 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F BigHuntBossId: bossQuest.BigHuntBossId, } oldSchedMax := user.BigHuntScheduleMaxScores[schedKey].MaxScore - if userScore > oldSchedMax { + isHighScore := userScore > oldSchedMax + if isHighScore { user.BigHuntScheduleMaxScores[schedKey] = store.BigHuntScheduleMaxScore{ MaxScore: userScore, MaxScoreUpdateDatetime: nowMillis, @@ -184,7 +189,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F rewardGroupId := catalog.ResolveActiveScoreRewardGroupId( bossQuest.BigHuntScoreRewardGroupScheduleId, nowMillis) if rewardGroupId > 0 { - newItems := catalog.CollectNewRewards(rewardGroupId, oldMax, userScore) + newItems := catalog.CollectNewRewards(rewardGroupId, oldSchedMax, userScore) for _, item := range newItems { engine.Granter.GrantFull(user, model.PossessionType(item.PossessionType), item.PossessionId, item.Count, nowMillis) scoreRewards = append(scoreRewards, &pb.BigHuntReward{ @@ -196,6 +201,31 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F } } + if len(detail.CostumeBattleInfo) > 0 { + wavesByIndex := map[int32]*pb.BigHuntBattleReportWave{} + var waveOrder []int32 + for _, ci := range detail.CostumeBattleInfo { + wave, ok := wavesByIndex[ci.WaveIndex] + if !ok { + wave = &pb.BigHuntBattleReportWave{} + wavesByIndex[ci.WaveIndex] = wave + waveOrder = append(waveOrder, ci.WaveIndex) + } + wave.BattleReportCostume = append(wave.BattleReportCostume, &pb.BigHuntBattleReportCostume{ + CostumeId: ci.CostumeId, + TotalDamage: ci.TotalDamage, + HitCount: ci.HitCount, + BattleReportRandomDisplay: &pb.BattleReportRandomDisplay{ + RandomDisplayValueType: ci.RandomDisplayValueType, + RandomDisplayValue: ci.RandomDisplayValue, + }, + }) + } + for _, idx := range waveOrder { + battleReportWaves = append(battleReportWaves, wavesByIndex[idx]) + } + } + user.BigHuntProgress = store.BigHuntProgress{LatestVersion: nowMillis} user.BigHuntBattleBinary = nil user.BigHuntBattleDetail = store.BigHuntBattleDetail{} @@ -208,12 +238,17 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F scoreRewards = []*pb.BigHuntReward{} } + if battleReportWaves == nil { + battleReportWaves = []*pb.BigHuntBattleReportWave{} + } + battleReport := &pb.BigHuntBattleReport{ + BattleReportWave: battleReportWaves, + } + return &pb.FinishBigHuntQuestResponse{ - ScoreInfo: scoreInfo, - ScoreReward: scoreRewards, - BattleReport: &pb.BigHuntBattleReport{ - BattleReportWave: []*pb.BigHuntBattleReportWave{}, - }, + ScoreInfo: scoreInfo, + ScoreReward: scoreRewards, + BattleReport: battleReport, }, nil } @@ -231,6 +266,8 @@ func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb. var battleBinary []byte var deckNumber int32 + today := gametime.StartOfDayMillis() + s.users.UpdateUser(userId, func(user *store.UserState) { engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, user.BigHuntDeckNumber, nowMillis) @@ -238,6 +275,9 @@ func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb. user.BigHuntProgress.LatestVersion = nowMillis st := user.BigHuntStatuses[req.BigHuntBossQuestId] + if st.LatestChallengeDatetime < today { + st.DailyChallengeCount = 0 + } st.DailyChallengeCount++ st.LatestChallengeDatetime = nowMillis st.LatestVersion = nowMillis @@ -256,19 +296,58 @@ func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb. func (s *BigHuntServiceServer) SkipBigHuntQuest(ctx context.Context, req *pb.SkipBigHuntQuestRequest) (*pb.SkipBigHuntQuestResponse, error) { log.Printf("[BigHuntService] SkipBigHuntQuest: bossQuestId=%d skipCount=%d", req.BigHuntBossQuestId, req.SkipCount) + cat := s.holder.Get() + catalog := cat.BigHunt + granter := cat.QuestHandler.Granter userId := CurrentUserId(ctx, s.users, s.sessions) nowMillis := gametime.NowMillis() + today := gametime.StartOfDayMillis() + + bossQuest, hasBossQuest := catalog.BossQuestById[req.BigHuntBossQuestId] + var scoreRewards []*pb.BigHuntReward s.users.UpdateUser(userId, func(user *store.UserState) { st := user.BigHuntStatuses[req.BigHuntBossQuestId] + if st.LatestChallengeDatetime < today { + st.DailyChallengeCount = 0 + } st.DailyChallengeCount += req.SkipCount st.LatestChallengeDatetime = nowMillis st.LatestVersion = nowMillis user.BigHuntStatuses[req.BigHuntBossQuestId] = st + + if !hasBossQuest || req.SkipCount <= 0 { + return + } + rewardGroupId := catalog.ResolveActiveScoreRewardGroupId(bossQuest.BigHuntScoreRewardGroupScheduleId, nowMillis) + if rewardGroupId == 0 { + return + } + maxScore := user.BigHuntScheduleMaxScores[store.BigHuntScheduleScoreKey{ + BigHuntScheduleId: catalog.ActiveScheduleId, + BigHuntBossId: bossQuest.BigHuntBossId, + }].MaxScore + if maxScore <= 0 { + return + } + items := catalog.CollectNewRewards(rewardGroupId, 0, maxScore) + for n := int32(0); n < req.SkipCount; n++ { + for _, item := range items { + granter.GrantFull(user, model.PossessionType(item.PossessionType), item.PossessionId, item.Count, nowMillis) + scoreRewards = append(scoreRewards, &pb.BigHuntReward{ + PossessionType: item.PossessionType, + PossessionId: item.PossessionId, + Count: item.Count, + }) + } + } }) + if scoreRewards == nil { + scoreRewards = []*pb.BigHuntReward{} + } return &pb.SkipBigHuntQuestResponse{ - ScoreReward: []*pb.BigHuntReward{}, + ScoreReward: scoreRewards, }, nil } @@ -291,12 +370,35 @@ func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *p user.BigHuntBattleBinary = req.BattleBinary if req.BigHuntBattleDetail != nil { + existingCostumes := user.BigHuntBattleDetail.CostumeBattleInfo + nextWaveIndex := int32(bigHuntWaveCount(existingCostumes)) + newCostumes := make([]store.BigHuntCostumeBattleInfo, 0, len(req.BigHuntBattleDetail.CostumeBattleInfo)) + for _, ci := range req.BigHuntBattleDetail.CostumeBattleInfo { + if ci == nil { + continue + } + var rdType int32 + var rdValue int64 + if rd := ci.BattleReportRandomDisplay; rd != nil { + rdType = rd.RandomDisplayValueType + rdValue = rd.RandomDisplayValue + } + newCostumes = append(newCostumes, store.BigHuntCostumeBattleInfo{ + WaveIndex: nextWaveIndex, + CostumeId: resolveBigHuntCostumeId(user, ci.UserDeckNumber, ci.DeckCharacterNumber), + TotalDamage: ci.TotalDamage, + HitCount: ci.HitCount, + RandomDisplayValueType: rdType, + RandomDisplayValue: rdValue, + }) + } user.BigHuntBattleDetail = store.BigHuntBattleDetail{ DeckType: req.BigHuntBattleDetail.DeckType, UserTripleDeckNumber: req.BigHuntBattleDetail.UserTripleDeckNumber, BossKnockDownCount: req.BigHuntBattleDetail.BossKnockDownCount, MaxComboCount: req.BigHuntBattleDetail.MaxComboCount, TotalDamage: totalDamage, + CostumeBattleInfo: append(existingCostumes, newCostumes...), } } @@ -351,14 +453,49 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb }, nil } +func bigHuntWaveCount(infos []store.BigHuntCostumeBattleInfo) int { + if len(infos) == 0 { + return 0 + } + return int(infos[len(infos)-1].WaveIndex) + 1 +} + +func resolveBigHuntCostumeId(user *store.UserState, userDeckNumber, deckCharacterNumber int32) int32 { + if userDeckNumber == 0 { + userDeckNumber = user.BigHuntDeckNumber + } + for _, dt := range []model.DeckType{model.DeckTypeBigHunt, model.DeckTypeQuest} { + deck, ok := user.Decks[store.DeckKey{DeckType: dt, UserDeckNumber: userDeckNumber}] + if !ok { + continue + } + var dcUuid string + switch deckCharacterNumber { + case 1: + dcUuid = deck.UserDeckCharacterUuid01 + case 2: + dcUuid = deck.UserDeckCharacterUuid02 + case 3: + dcUuid = deck.UserDeckCharacterUuid03 + } + if dcUuid == "" { + continue + } + dc, ok := user.DeckCharacters[dcUuid] + if !ok || dc.UserCostumeUuid == "" { + continue + } + if costume, ok := user.Costumes[dc.UserCostumeUuid]; ok { + return costume.CostumeId + } + } + return 0 +} + func resolveBigHuntWeeklyRewards(catalog *masterdata.BigHuntCatalog, user store.UserState, weeklyVersion, nowMillis int64) []*pb.BigHuntReward { var rewards []*pb.BigHuntReward for _, boss := range catalog.BossByBossId { - rewardKey := masterdata.BigHuntWeeklyRewardKey{ - ScheduleId: 1, - AttributeType: boss.AttributeType, - } - rewardGroupId := catalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis) + rewardGroupId := catalog.ResolveActiveWeeklyRewardGroupIdByAttr(boss.AttributeType, nowMillis) if rewardGroupId == 0 { continue } diff --git a/server/internal/service/reward.go b/server/internal/service/reward.go index da94a60..760a76b 100644 --- a/server/internal/service/reward.go +++ b/server/internal/service/reward.go @@ -6,7 +6,6 @@ import ( pb "lunar-tear/server/gen/proto" "lunar-tear/server/internal/gametime" - "lunar-tear/server/internal/masterdata" "lunar-tear/server/internal/model" "lunar-tear/server/internal/runtime" "lunar-tear/server/internal/store" @@ -38,12 +37,42 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty userId := CurrentUserId(ctx, s.users, s.sessions) nowMillis := gametime.NowMillis() weeklyVersion := gametime.WeeklyVersion(nowMillis) + today := gametime.StartOfDayMillis() var weeklyScoreResults []*pb.WeeklyScoreResult var weeklyRewards []*pb.BigHuntReward isReceived := false s.users.UpdateUser(userId, func(user *store.UserState) { + for bossQuestId, bossQuest := range bhCatalog.BossQuestById { + st := user.BigHuntStatuses[bossQuestId] + if st.LastDailyRewardReceivedDayVersion >= today { + continue + } + rewardGroupId := bhCatalog.ResolveActiveScoreRewardGroupId(bossQuest.BigHuntScoreRewardGroupScheduleId, nowMillis) + if rewardGroupId == 0 { + continue + } + maxScore := user.BigHuntScheduleMaxScores[store.BigHuntScheduleScoreKey{ + BigHuntScheduleId: bhCatalog.ActiveScheduleId, + BigHuntBossId: bossQuest.BigHuntBossId, + }].MaxScore + if maxScore <= 0 { + continue + } + items := bhCatalog.CollectNewRewards(rewardGroupId, 0, maxScore) + for _, item := range items { + granter.GrantFull(user, model.PossessionType(item.PossessionType), item.PossessionId, item.Count, nowMillis) + } + if len(items) > 0 { + log.Printf("[RewardService] ReceiveBigHuntReward: bossQuestId=%d granted %d daily rewards (maxScore=%d, group=%d)", + bossQuestId, len(items), maxScore, rewardGroupId) + } + st.LastDailyRewardReceivedDayVersion = today + st.LatestVersion = nowMillis + user.BigHuntStatuses[bossQuestId] = st + } + ws := user.BigHuntWeeklyStatuses[weeklyVersion] isReceived = ws.IsReceivedWeeklyReward @@ -67,11 +96,7 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty if !isReceived { for _, boss := range bhCatalog.BossByBossId { - rewardKey := masterdata.BigHuntWeeklyRewardKey{ - ScheduleId: 1, - AttributeType: boss.AttributeType, - } - rewardGroupId := bhCatalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis) + rewardGroupId := bhCatalog.ResolveActiveWeeklyRewardGroupIdByAttr(boss.AttributeType, nowMillis) if rewardGroupId == 0 { continue } diff --git a/server/internal/store/sqlite/load.go b/server/internal/store/sqlite/load.go index 224b3f5..8a193bf 100644 --- a/server/internal/store/sqlite/load.go +++ b/server/internal/store/sqlite/load.go @@ -173,6 +173,13 @@ func load1to1(db *sql.DB, uid int64, u *store.UserState) { &u.BigHuntBattleDetail.TotalDamage, &u.BigHuntDeckNumber, &u.BigHuntBattleBinary) u.BigHuntProgress.IsDryRun = isDryRun != 0 + queryRows(db, `SELECT wave_index, costume_id, total_damage, hit_count, random_display_value_type, random_display_value + FROM user_big_hunt_costume_battle_infos WHERE user_id=? ORDER BY wave_index, sort_order`, uid, func(rows *sql.Rows) { + var ci store.BigHuntCostumeBattleInfo + rows.Scan(&ci.WaveIndex, &ci.CostumeId, &ci.TotalDamage, &ci.HitCount, &ci.RandomDisplayValueType, &ci.RandomDisplayValue) + u.BigHuntBattleDetail.CostumeBattleInfo = append(u.BigHuntBattleDetail.CostumeBattleInfo, ci) + }) + var isActive, isUnread int _ = db.QueryRow(`SELECT is_active, start_count, finish_count, last_started_at, last_finished_at, last_user_party_count, last_npc_party_count, last_battle_binary_size, last_elapsed_frame_count @@ -688,11 +695,11 @@ func loadMapTables(db *sql.DB, uid int64, u *store.UserState) { u.BigHuntMaxScores[id] = v }) - queryRows(db, `SELECT big_hunt_boss_id, daily_challenge_count, latest_challenge_datetime, latest_version + queryRows(db, `SELECT big_hunt_boss_id, daily_challenge_count, latest_challenge_datetime, last_daily_reward_received_day_version, latest_version FROM user_big_hunt_statuses WHERE user_id=?`, uid, func(rows *sql.Rows) { var id int32 var v store.BigHuntStatus - rows.Scan(&id, &v.DailyChallengeCount, &v.LatestChallengeDatetime, &v.LatestVersion) + rows.Scan(&id, &v.DailyChallengeCount, &v.LatestChallengeDatetime, &v.LastDailyRewardReceivedDayVersion, &v.LatestVersion) u.BigHuntStatuses[id] = v }) diff --git a/server/internal/store/sqlite/save.go b/server/internal/store/sqlite/save.go index 86601b5..3617e63 100644 --- a/server/internal/store/sqlite/save.go +++ b/server/internal/store/sqlite/save.go @@ -83,6 +83,12 @@ func writeUserState(tx *sql.Tx, uid int64, u *store.UserState) error { u.BigHuntBattleDetail.MaxComboCount, u.BigHuntBattleDetail.TotalDamage, u.BigHuntDeckNumber, u.BigHuntBattleBinary); err != nil { return err } + for i, ci := range u.BigHuntBattleDetail.CostumeBattleInfo { + if err := exec(`INSERT INTO user_big_hunt_costume_battle_infos (user_id, wave_index, sort_order, costume_id, total_damage, hit_count, random_display_value_type, random_display_value) VALUES (?,?,?,?,?,?,?,?)`, + uid, ci.WaveIndex, i, ci.CostumeId, ci.TotalDamage, ci.HitCount, ci.RandomDisplayValueType, ci.RandomDisplayValue); err != nil { + return err + } + } if err := exec(`INSERT INTO user_battle (user_id, is_active, start_count, finish_count, last_started_at, last_finished_at, last_user_party_count, last_npc_party_count, last_battle_binary_size, last_elapsed_frame_count) VALUES (?,?,?,?,?,?,?,?,?,?)`, uid, boolToInt(u.Battle.IsActive), u.Battle.StartCount, u.Battle.FinishCount, u.Battle.LastStartedAt, u.Battle.LastFinishedAt, u.Battle.LastUserPartyCount, u.Battle.LastNpcPartyCount, @@ -479,8 +485,8 @@ func writeUserState(tx *sql.Tx, uid int64, u *store.UserState) error { } } for id, v := range u.BigHuntStatuses { - if err := exec(`INSERT INTO user_big_hunt_statuses (user_id, big_hunt_boss_id, daily_challenge_count, latest_challenge_datetime, latest_version) VALUES (?,?,?,?,?)`, - uid, id, v.DailyChallengeCount, v.LatestChallengeDatetime, v.LatestVersion); err != nil { + if err := exec(`INSERT INTO user_big_hunt_statuses (user_id, big_hunt_boss_id, daily_challenge_count, latest_challenge_datetime, last_daily_reward_received_day_version, latest_version) VALUES (?,?,?,?,?,?)`, + uid, id, v.DailyChallengeCount, v.LatestChallengeDatetime, v.LastDailyRewardReceivedDayVersion, v.LatestVersion); err != nil { return err } } @@ -600,7 +606,7 @@ func diffAndSave(tx *sql.Tx, uid int64, before, after *store.UserState) error { return err } } - if before.BigHuntProgress != after.BigHuntProgress || before.BigHuntBattleDetail != after.BigHuntBattleDetail || before.BigHuntDeckNumber != after.BigHuntDeckNumber { + if before.BigHuntProgress != after.BigHuntProgress || !bigHuntBattleDetailEqual(before.BigHuntBattleDetail, after.BigHuntBattleDetail) || before.BigHuntDeckNumber != after.BigHuntDeckNumber { if err := exec(`UPDATE user_big_hunt_state SET current_big_hunt_boss_quest_id=?, current_big_hunt_quest_id=?, current_quest_scene_id=?, is_dry_run=?, latest_version=?, deck_type=?, user_triple_deck_number=?, boss_knock_down_count=?, max_combo_count=?, total_damage=?, deck_number=?, battle_binary=? WHERE user_id=?`, after.BigHuntProgress.CurrentBigHuntBossQuestId, after.BigHuntProgress.CurrentBigHuntQuestId, after.BigHuntProgress.CurrentQuestSceneId, boolToInt(after.BigHuntProgress.IsDryRun), after.BigHuntProgress.LatestVersion, @@ -608,6 +614,15 @@ func diffAndSave(tx *sql.Tx, uid int64, before, after *store.UserState) error { after.BigHuntBattleDetail.MaxComboCount, after.BigHuntBattleDetail.TotalDamage, after.BigHuntDeckNumber, after.BigHuntBattleBinary, uid); err != nil { return err } + if err := exec(`DELETE FROM user_big_hunt_costume_battle_infos WHERE user_id=?`, uid); err != nil { + return err + } + for i, ci := range after.BigHuntBattleDetail.CostumeBattleInfo { + if err := exec(`INSERT INTO user_big_hunt_costume_battle_infos (user_id, wave_index, sort_order, costume_id, total_damage, hit_count, random_display_value_type, random_display_value) VALUES (?,?,?,?,?,?,?,?)`, + uid, ci.WaveIndex, i, ci.CostumeId, ci.TotalDamage, ci.HitCount, ci.RandomDisplayValueType, ci.RandomDisplayValue); err != nil { + return err + } + } } if before.Battle != after.Battle { if err := exec(`UPDATE user_battle SET is_active=?, start_count=?, finish_count=?, last_started_at=?, last_finished_at=?, last_user_party_count=?, last_npc_party_count=?, last_battle_binary_size=?, last_elapsed_frame_count=? WHERE user_id=?`, @@ -1017,9 +1032,9 @@ func diffAndSave(tx *sql.Tx, uid int64, before, after *store.UserState) error { "big_hunt_boss_id, max_score, max_score_update_datetime, latest_version") diffMapInt32(tx, uid, before.BigHuntStatuses, after.BigHuntStatuses, "user_big_hunt_statuses", "big_hunt_boss_id", func(v store.BigHuntStatus) []any { - return []any{0, v.DailyChallengeCount, v.LatestChallengeDatetime, v.LatestVersion} + return []any{0, v.DailyChallengeCount, v.LatestChallengeDatetime, v.LastDailyRewardReceivedDayVersion, v.LatestVersion} }, - "big_hunt_boss_id, daily_challenge_count, latest_challenge_datetime, latest_version") + "big_hunt_boss_id, daily_challenge_count, latest_challenge_datetime, last_daily_reward_received_day_version, latest_version") for k, v := range after.BigHuntScheduleMaxScores { if old, ok := before.BigHuntScheduleMaxScores[k]; !ok || old != v { @@ -1058,6 +1073,23 @@ func diffAndSave(tx *sql.Tx, uid int64, before, after *store.UserState) error { return nil } +func bigHuntBattleDetailEqual(a, b store.BigHuntBattleDetail) bool { + if a.DeckType != b.DeckType || a.UserTripleDeckNumber != b.UserTripleDeckNumber || + a.BossKnockDownCount != b.BossKnockDownCount || a.MaxComboCount != b.MaxComboCount || + a.TotalDamage != b.TotalDamage { + return false + } + if len(a.CostumeBattleInfo) != len(b.CostumeBattleInfo) { + return false + } + for i := range a.CostumeBattleInfo { + if a.CostumeBattleInfo[i] != b.CostumeBattleInfo[i] { + return false + } + } + return true +} + func diffMapInt32[V comparable](tx *sql.Tx, uid int64, before, after map[int32]V, table, keyCol string, vals func(V) []any, cols string) { for k, v := range after { if old, ok := before[k]; !ok || old != v { diff --git a/server/internal/store/types.go b/server/internal/store/types.go index b978f8e..2726906 100644 --- a/server/internal/store/types.go +++ b/server/internal/store/types.go @@ -592,9 +592,10 @@ type BigHuntMaxScore struct { } type BigHuntStatus struct { - DailyChallengeCount int32 - LatestChallengeDatetime int64 - LatestVersion int64 + DailyChallengeCount int32 + LatestChallengeDatetime int64 + LastDailyRewardReceivedDayVersion int64 + LatestVersion int64 } type BigHuntScheduleScoreKey struct { @@ -657,6 +658,16 @@ type BigHuntBattleDetail struct { BossKnockDownCount int32 MaxComboCount int32 TotalDamage int64 + CostumeBattleInfo []BigHuntCostumeBattleInfo +} + +type BigHuntCostumeBattleInfo struct { + WaveIndex int32 + CostumeId int32 + TotalDamage int64 + HitCount int32 + RandomDisplayValueType int32 + RandomDisplayValue int64 } type BattleState struct { diff --git a/server/internal/userdata/proj_bighunt.go b/server/internal/userdata/proj_bighunt.go index 235d1c7..ba58ef0 100644 --- a/server/internal/userdata/proj_bighunt.go +++ b/server/internal/userdata/proj_bighunt.go @@ -57,11 +57,12 @@ func init() { for _, id := range ids { st := user.BigHuntStatuses[int32(id)] records = append(records, map[string]any{ - "userId": user.UserId, - "bigHuntBossQuestId": int32(id), - "dailyChallengeCount": st.DailyChallengeCount, - "latestChallengeDatetime": st.LatestChallengeDatetime, - "latestVersion": st.LatestVersion, + "userId": user.UserId, + "bigHuntBossQuestId": int32(id), + "dailyChallengeCount": st.DailyChallengeCount, + "latestChallengeDatetime": st.LatestChallengeDatetime, + "lastDailyRewardReceivedDayVersion": st.LastDailyRewardReceivedDayVersion, + "latestVersion": st.LatestVersion, }) } s, _ := utils.EncodeJSONMaps(records...) diff --git a/server/migrations/20260513064604_add_bighunt_battle_report_state.sql b/server/migrations/20260513064604_add_bighunt_battle_report_state.sql new file mode 100644 index 0000000..d90ac3a --- /dev/null +++ b/server/migrations/20260513064604_add_bighunt_battle_report_state.sql @@ -0,0 +1,18 @@ +-- +goose Up +ALTER TABLE user_big_hunt_statuses ADD COLUMN last_daily_reward_received_day_version INTEGER NOT NULL DEFAULT 0; + +CREATE TABLE user_big_hunt_costume_battle_infos ( + user_id INTEGER NOT NULL REFERENCES users(user_id), + wave_index INTEGER NOT NULL DEFAULT 0, + sort_order INTEGER NOT NULL, + costume_id INTEGER NOT NULL DEFAULT 0, + total_damage INTEGER NOT NULL DEFAULT 0, + hit_count INTEGER NOT NULL DEFAULT 0, + random_display_value_type INTEGER NOT NULL DEFAULT 0, + random_display_value INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (user_id, wave_index, sort_order) +); + +-- +goose Down +DROP TABLE IF EXISTS user_big_hunt_costume_battle_infos; +ALTER TABLE user_big_hunt_statuses DROP COLUMN last_daily_reward_received_day_version;