Persist live status and migrate legacy live data
- Add user_live_status to store hi score, hi combo count, and clear count per chart - Expand user_live_goal to persist achieved live_goal_reward_id entries instead of only coarse goal types - Update live reward handling to refresh live status and sync newly achieved live goals on every clear - Return live status and achieved goal ids from stored user data in /api/live/status - Aggregate profile liveCnt from stored user_live_status instead of hardcoded values - Load complete-rank thresholds from live info for goal evaluation - Migrate legacy user_live_record data into user_live_status and backfill achievable live goals on startup Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
@@ -268,46 +268,49 @@ func buildRandomLiveList() []liveapischema.RandomLiveList {
|
||||
}
|
||||
}
|
||||
|
||||
func buildNormalLiveStatusList(ids []int) []liveapischema.NormalLiveStatusList {
|
||||
func buildNormalLiveStatusList(ids []int, snapshotMap map[int]session.LiveStatusSnapshot) []liveapischema.NormalLiveStatusList {
|
||||
result := make([]liveapischema.NormalLiveStatusList, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
snapshot := snapshotMap[id]
|
||||
result = append(result, liveapischema.NormalLiveStatusList{
|
||||
LiveDifficultyID: id,
|
||||
Status: 1,
|
||||
HiScore: 0,
|
||||
HiComboCount: 0,
|
||||
ClearCnt: 0,
|
||||
AchievedGoalIDList: []int{},
|
||||
Status: snapshot.Status,
|
||||
HiScore: snapshot.HiScore,
|
||||
HiComboCount: snapshot.HiComboCount,
|
||||
ClearCnt: snapshot.ClearCnt,
|
||||
AchievedGoalIDList: snapshot.AchievedGoalIDList,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func buildSpecialLiveStatusList(ids []int) []liveapischema.SpecialLiveStatusList {
|
||||
func buildSpecialLiveStatusList(ids []int, snapshotMap map[int]session.LiveStatusSnapshot) []liveapischema.SpecialLiveStatusList {
|
||||
result := make([]liveapischema.SpecialLiveStatusList, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
snapshot := snapshotMap[id]
|
||||
result = append(result, liveapischema.SpecialLiveStatusList{
|
||||
LiveDifficultyID: id,
|
||||
Status: 1,
|
||||
HiScore: 0,
|
||||
HiComboCount: 0,
|
||||
ClearCnt: 0,
|
||||
AchievedGoalIDList: []int{},
|
||||
Status: snapshot.Status,
|
||||
HiScore: snapshot.HiScore,
|
||||
HiComboCount: snapshot.HiComboCount,
|
||||
ClearCnt: snapshot.ClearCnt,
|
||||
AchievedGoalIDList: snapshot.AchievedGoalIDList,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func buildTrainingLiveStatusList(ids []int) []liveapischema.TrainingLiveStatusList {
|
||||
func buildTrainingLiveStatusList(ids []int, snapshotMap map[int]session.LiveStatusSnapshot) []liveapischema.TrainingLiveStatusList {
|
||||
result := make([]liveapischema.TrainingLiveStatusList, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
snapshot := snapshotMap[id]
|
||||
result = append(result, liveapischema.TrainingLiveStatusList{
|
||||
LiveDifficultyID: id,
|
||||
Status: 1,
|
||||
HiScore: 0,
|
||||
HiComboCount: 0,
|
||||
ClearCnt: 0,
|
||||
AchievedGoalIDList: []int{},
|
||||
Status: snapshot.Status,
|
||||
HiScore: snapshot.HiScore,
|
||||
HiComboCount: snapshot.HiComboCount,
|
||||
ClearCnt: snapshot.ClearCnt,
|
||||
AchievedGoalIDList: snapshot.AchievedGoalIDList,
|
||||
})
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -24,12 +24,20 @@ func liveStatus(ctx *gin.Context) (res any, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allIDs := make([]int, 0, len(normalIDs)+len(specialIDs)+len(trainingIDs))
|
||||
allIDs = append(allIDs, normalIDs...)
|
||||
allIDs = append(allIDs, specialIDs...)
|
||||
allIDs = append(allIDs, trainingIDs...)
|
||||
statusSnapshotMap, err := ss.BuildLiveStatusSnapshotMap(allIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = liveapischema.StatusResp{
|
||||
Result: liveapischema.StatusData{
|
||||
NormalLiveStatusList: buildNormalLiveStatusList(normalIDs),
|
||||
SpecialLiveStatusList: buildSpecialLiveStatusList(specialIDs),
|
||||
TrainingLiveStatusList: buildTrainingLiveStatusList(trainingIDs),
|
||||
NormalLiveStatusList: buildNormalLiveStatusList(normalIDs, statusSnapshotMap),
|
||||
SpecialLiveStatusList: buildSpecialLiveStatusList(specialIDs, statusSnapshotMap),
|
||||
TrainingLiveStatusList: buildTrainingLiveStatusList(trainingIDs, statusSnapshotMap),
|
||||
MarathonLiveStatusList: []any{},
|
||||
FreeLiveStatusList: []any{},
|
||||
CanResumeLive: true,
|
||||
|
||||
@@ -1,34 +1,87 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
usermodel "honoka-chan/internal/model/user"
|
||||
profileapischema "honoka-chan/internal/schema/api/profile"
|
||||
"honoka-chan/internal/session"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func liveCnt() (res any, err error) {
|
||||
type profileLiveDifficultyRow struct {
|
||||
LiveDifficultyID int `xorm:"live_difficulty_id"`
|
||||
Difficulty int `xorm:"difficulty"`
|
||||
}
|
||||
|
||||
func liveCnt(ctx *gin.Context, targetUserID int) (res any, err error) {
|
||||
ss := session.Get(ctx)
|
||||
|
||||
targetPref, err := getTargetUserPref(ss, targetUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
difficultyByLiveID := map[int]int{}
|
||||
loadDifficultyRows := func(table string) error {
|
||||
rows := []profileLiveDifficultyRow{}
|
||||
err := ss.MainEng.Table(table).Alias("live").
|
||||
Join("LEFT", "live_setting_m setting", "live.live_setting_id = setting.live_setting_id").
|
||||
Select("live.live_difficulty_id, setting.difficulty").
|
||||
Find(&rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
difficultyByLiveID[row.LiveDifficultyID] = row.Difficulty
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := loadDifficultyRows("normal_live_m"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadDifficultyRows("special_live_m"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statusRows := []usermodel.UserLiveStatus{}
|
||||
err = ss.UserEng.Table(new(usermodel.UserLiveStatus)).
|
||||
Where("user_id = ?", targetPref.UserID).
|
||||
Where("clear_cnt > 0").
|
||||
Cols("live_difficulty_id", "clear_cnt").
|
||||
Find(&statusRows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
difficultyCounts := map[int]int{}
|
||||
for _, row := range statusRows {
|
||||
difficulty, ok := difficultyByLiveID[row.LiveDifficultyID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if difficulty == 5 {
|
||||
continue
|
||||
}
|
||||
difficultyCounts[difficulty]++
|
||||
}
|
||||
|
||||
orderedDifficulties := []int{1, 2, 3, 4, 6}
|
||||
result := make([]profileapischema.LiveCntData, 0, len(orderedDifficulties))
|
||||
for _, difficulty := range orderedDifficulties {
|
||||
result = append(result, profileapischema.LiveCntData{
|
||||
Difficulty: difficulty,
|
||||
ClearCnt: difficultyCounts[difficulty],
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Difficulty < result[j].Difficulty
|
||||
})
|
||||
|
||||
res = profileapischema.LiveCntResp{
|
||||
Result: []profileapischema.LiveCntData{
|
||||
{
|
||||
Difficulty: 1,
|
||||
ClearCnt: 315,
|
||||
},
|
||||
{
|
||||
Difficulty: 2,
|
||||
ClearCnt: 310,
|
||||
},
|
||||
{
|
||||
Difficulty: 3,
|
||||
ClearCnt: 314,
|
||||
},
|
||||
{
|
||||
Difficulty: 4,
|
||||
ClearCnt: 455,
|
||||
},
|
||||
{
|
||||
Difficulty: 6,
|
||||
ClearCnt: 233,
|
||||
},
|
||||
},
|
||||
Result: result,
|
||||
Status: 200,
|
||||
CommandNum: false,
|
||||
TimeStamp: time.Now().Unix(),
|
||||
|
||||
@@ -11,7 +11,7 @@ func ProfileApi(ctx *gin.Context, action string, targetUserID int) (res any, err
|
||||
case "cardRanking":
|
||||
res, err = cardRanking()
|
||||
case "liveCnt":
|
||||
res, err = liveCnt()
|
||||
res, err = liveCnt(ctx, targetUserID)
|
||||
case "profileInfo":
|
||||
res, err = profileInfo(ctx, targetUserID)
|
||||
default:
|
||||
|
||||
@@ -42,6 +42,36 @@ func reward(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
totalScore := playRewardReq.ScoreSmile + playRewardReq.ScoreCool + playRewardReq.ScoreCute
|
||||
beforeLiveStatus, afterLiveStatus, err := ss.UpdateUserLiveStatus(difficultyID, totalScore, playRewardReq.MaxCombo)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
newGoalRewardIDs, err := ss.SyncUserLiveGoals(difficultyID, afterLiveStatus.HiScore, afterLiveStatus.HiComboCount, afterLiveStatus.ClearCnt)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
|
||||
goalAccomplishedIDs := make([]any, 0, len(newGoalRewardIDs))
|
||||
goalRewards := make([]any, 0, len(newGoalRewardIDs))
|
||||
if len(newGoalRewardIDs) > 0 {
|
||||
goalRewardRows, err := ss.GetLiveGoalRewardRowsByIDs(newGoalRewardIDs)
|
||||
if ss.CheckErr(err) {
|
||||
return
|
||||
}
|
||||
for _, goalRewardID := range newGoalRewardIDs {
|
||||
goalAccomplishedIDs = append(goalAccomplishedIDs, goalRewardID)
|
||||
}
|
||||
for _, row := range goalRewardRows {
|
||||
goalRewards = append(goalRewards, liveschema.PlayRewardList{
|
||||
ItemID: row.ItemID,
|
||||
AddType: row.AddType,
|
||||
Amount: row.Amount,
|
||||
ItemCategoryID: row.ItemCategoryID,
|
||||
RewardBoxFlag: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
playResp := liveschema.RewardResp{
|
||||
ResponseData: liveschema.RewardData{
|
||||
LiveInfo: []liveschema.RewardLiveInfo{
|
||||
@@ -53,8 +83,8 @@ func reward(ctx *gin.Context) {
|
||||
},
|
||||
},
|
||||
TotalLove: 0,
|
||||
IsHighScore: true,
|
||||
HiScore: totalScore,
|
||||
IsHighScore: totalScore >= beforeLiveStatus.HiScore,
|
||||
HiScore: afterLiveStatus.HiScore,
|
||||
BaseRewardInfo: liveschema.BaseRewardInfo{
|
||||
PlayerExp: 0,
|
||||
PlayerExpUnitMax: liveschema.PlayerExpUnitMax{
|
||||
@@ -93,8 +123,8 @@ func reward(ctx *gin.Context) {
|
||||
},
|
||||
},
|
||||
GoalAccompInfo: liveschema.GoalAccompInfo{
|
||||
AchievedIds: []any{},
|
||||
Rewards: []any{},
|
||||
AchievedIds: goalAccomplishedIDs,
|
||||
Rewards: goalRewards,
|
||||
},
|
||||
SpecialRewardInfo: []any{},
|
||||
EventInfo: []any{},
|
||||
@@ -123,25 +153,25 @@ func reward(ctx *gin.Context) {
|
||||
StatusCode: 200,
|
||||
}
|
||||
|
||||
if playRewardReq.MaxCombo > liveInfo.SRankCombo {
|
||||
if playRewardReq.MaxCombo >= liveInfo.SRankCombo {
|
||||
playResp.ResponseData.ComboRank = 1
|
||||
} else if playRewardReq.MaxCombo > liveInfo.ARankCombo {
|
||||
} else if playRewardReq.MaxCombo >= liveInfo.ARankCombo {
|
||||
playResp.ResponseData.ComboRank = 2
|
||||
} else if playRewardReq.MaxCombo > liveInfo.BRankCombo {
|
||||
} else if playRewardReq.MaxCombo >= liveInfo.BRankCombo {
|
||||
playResp.ResponseData.ComboRank = 3
|
||||
} else if playRewardReq.MaxCombo > liveInfo.CRankCombo {
|
||||
} else if playRewardReq.MaxCombo >= liveInfo.CRankCombo {
|
||||
playResp.ResponseData.ComboRank = 4
|
||||
} else {
|
||||
playResp.ResponseData.ComboRank = 5
|
||||
}
|
||||
|
||||
if totalScore > liveInfo.SRankScore {
|
||||
if totalScore >= liveInfo.SRankScore {
|
||||
playResp.ResponseData.Rank = 1
|
||||
} else if totalScore > liveInfo.ARankScore {
|
||||
} else if totalScore >= liveInfo.ARankScore {
|
||||
playResp.ResponseData.Rank = 2
|
||||
} else if totalScore > liveInfo.BRankScore {
|
||||
} else if totalScore >= liveInfo.BRankScore {
|
||||
playResp.ResponseData.Rank = 3
|
||||
} else if totalScore > liveInfo.CRankScore {
|
||||
} else if totalScore >= liveInfo.CRankScore {
|
||||
playResp.ResponseData.Rank = 4
|
||||
} else {
|
||||
playResp.ResponseData.Rank = 5
|
||||
|
||||
Reference in New Issue
Block a user