- 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>
282 lines
7.6 KiB
Go
282 lines
7.6 KiB
Go
package session
|
|
|
|
import (
|
|
"honoka-chan/internal/constant"
|
|
usermodel "honoka-chan/internal/model/user"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
type liveGoalRewardRow struct {
|
|
LiveGoalRewardID int `xorm:"live_goal_reward_id"`
|
|
LiveDifficultyID int `xorm:"live_difficulty_id"`
|
|
LiveGoalType constant.LiveGoalType `xorm:"live_goal_type"`
|
|
Rank int `xorm:"rank"`
|
|
AddType int `xorm:"add_type"`
|
|
ItemID int `xorm:"item_id"`
|
|
ItemCategoryID int `xorm:"item_category_id"`
|
|
Amount int `xorm:"amount"`
|
|
}
|
|
|
|
type LiveStatusSnapshot struct {
|
|
Status int
|
|
HiScore int
|
|
HiComboCount int
|
|
ClearCnt int
|
|
AchievedGoalIDList []int
|
|
}
|
|
|
|
func liveRank(value int, cRank int, bRank int, aRank int, sRank int) int {
|
|
switch {
|
|
case value >= sRank:
|
|
return 1
|
|
case value >= aRank:
|
|
return 2
|
|
case value >= bRank:
|
|
return 3
|
|
case value >= cRank:
|
|
return 4
|
|
default:
|
|
return 5
|
|
}
|
|
}
|
|
|
|
func (ss *Session) GetUserLiveStatus(liveDifficultyID int) (bool, *usermodel.UserLiveStatus, error) {
|
|
status := usermodel.UserLiveStatus{}
|
|
has, err := ss.UserEng.Table(new(usermodel.UserLiveStatus)).
|
|
Where("user_id = ? AND live_difficulty_id = ?", ss.UserID, liveDifficultyID).
|
|
Get(&status)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
return has, &status, nil
|
|
}
|
|
|
|
func (ss *Session) GetUserLiveStatusMap(liveDifficultyIDs []int) (map[int]usermodel.UserLiveStatus, error) {
|
|
result := map[int]usermodel.UserLiveStatus{}
|
|
if len(liveDifficultyIDs) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
rows := []usermodel.UserLiveStatus{}
|
|
err := ss.UserEng.Table(new(usermodel.UserLiveStatus)).
|
|
In("live_difficulty_id", liveDifficultyIDs).
|
|
Where("user_id = ?", ss.UserID).
|
|
Find(&rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, row := range rows {
|
|
result[row.LiveDifficultyID] = row
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (ss *Session) GetUserLiveGoalRewardIDMap(liveDifficultyIDs []int) (map[int][]int, error) {
|
|
result := map[int][]int{}
|
|
if len(liveDifficultyIDs) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
rows := []usermodel.UserLiveGoal{}
|
|
err := ss.UserEng.Table(new(usermodel.UserLiveGoal)).
|
|
In("live_difficulty_id", liveDifficultyIDs).
|
|
Where("user_id = ?", ss.UserID).
|
|
Where("live_goal_reward_id > 0").
|
|
OrderBy("live_goal_reward_id ASC").
|
|
Find(&rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, row := range rows {
|
|
result[row.LiveDifficultyID] = append(result[row.LiveDifficultyID], row.LiveGoalRewardID)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (ss *Session) BuildLiveStatusSnapshotMap(liveDifficultyIDs []int) (map[int]LiveStatusSnapshot, error) {
|
|
statusMap, err := ss.GetUserLiveStatusMap(liveDifficultyIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
goalMap, err := ss.GetUserLiveGoalRewardIDMap(liveDifficultyIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make(map[int]LiveStatusSnapshot, len(liveDifficultyIDs))
|
|
for _, liveDifficultyID := range liveDifficultyIDs {
|
|
status := LiveStatusSnapshot{
|
|
Status: 1,
|
|
HiScore: 0,
|
|
HiComboCount: 0,
|
|
ClearCnt: 0,
|
|
AchievedGoalIDList: []int{},
|
|
}
|
|
|
|
if row, ok := statusMap[liveDifficultyID]; ok {
|
|
status.HiScore = row.HiScore
|
|
status.HiComboCount = row.HiComboCount
|
|
status.ClearCnt = row.ClearCnt
|
|
if row.ClearCnt > 0 {
|
|
status.Status = 2
|
|
}
|
|
}
|
|
|
|
if ids := goalMap[liveDifficultyID]; len(ids) > 0 {
|
|
status.AchievedGoalIDList = ids
|
|
}
|
|
|
|
result[liveDifficultyID] = status
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (ss *Session) UpdateUserLiveStatus(liveDifficultyID int, totalScore int, maxCombo int) (before usermodel.UserLiveStatus, after usermodel.UserLiveStatus, err error) {
|
|
now := time.Now().Unix()
|
|
has, current, err := ss.GetUserLiveStatus(liveDifficultyID)
|
|
if err != nil {
|
|
return before, after, err
|
|
}
|
|
if current == nil {
|
|
current = &usermodel.UserLiveStatus{}
|
|
}
|
|
|
|
before = *current
|
|
if !has {
|
|
after = usermodel.UserLiveStatus{
|
|
UserID: ss.UserID,
|
|
LiveDifficultyID: liveDifficultyID,
|
|
HiScore: totalScore,
|
|
HiComboCount: maxCombo,
|
|
ClearCnt: 1,
|
|
InsertDate: now,
|
|
UpdateDate: now,
|
|
}
|
|
_, err = ss.UserEng.Insert(&after)
|
|
return before, after, err
|
|
}
|
|
|
|
after = before
|
|
if totalScore > after.HiScore {
|
|
after.HiScore = totalScore
|
|
}
|
|
if maxCombo > after.HiComboCount {
|
|
after.HiComboCount = maxCombo
|
|
}
|
|
after.ClearCnt++
|
|
after.UpdateDate = now
|
|
|
|
_, err = ss.UserEng.Table(new(usermodel.UserLiveStatus)).
|
|
Where("user_id = ? AND live_difficulty_id = ?", ss.UserID, liveDifficultyID).
|
|
Cols("hi_score", "hi_combo_count", "clear_cnt", "update_date").
|
|
Update(&after)
|
|
return before, after, err
|
|
}
|
|
|
|
func (ss *Session) GetAchievedLiveGoalRewardRows(liveDifficultyID int, hiScore int, hiComboCount int, clearCnt int) ([]liveGoalRewardRow, error) {
|
|
has, liveInfo := ss.GetLiveInfo(liveDifficultyID)
|
|
if !has || liveInfo == nil {
|
|
return []liveGoalRewardRow{}, nil
|
|
}
|
|
|
|
rows := []liveGoalRewardRow{}
|
|
err := ss.MainEng.Table("live_goal_reward_m").
|
|
Where("live_difficulty_id = ?", liveDifficultyID).
|
|
OrderBy("live_goal_type ASC, rank ASC, live_goal_reward_id ASC").
|
|
Find(&rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scoreRank := liveRank(hiScore, liveInfo.CRankScore, liveInfo.BRankScore, liveInfo.ARankScore, liveInfo.SRankScore)
|
|
comboRank := liveRank(hiComboCount, liveInfo.CRankCombo, liveInfo.BRankCombo, liveInfo.ARankCombo, liveInfo.SRankCombo)
|
|
clearRank := liveRank(clearCnt, liveInfo.CRankComplete, liveInfo.BRankComplete, liveInfo.ARankComplete, liveInfo.SRankComplete)
|
|
|
|
result := make([]liveGoalRewardRow, 0, len(rows))
|
|
for _, row := range rows {
|
|
switch row.LiveGoalType {
|
|
case constant.LiveGoalTypeScore:
|
|
if scoreRank <= row.Rank {
|
|
result = append(result, row)
|
|
}
|
|
case constant.LiveGoalTypeCombo:
|
|
if comboRank <= row.Rank {
|
|
result = append(result, row)
|
|
}
|
|
case constant.LiveGoalTypeClear:
|
|
if clearRank <= row.Rank {
|
|
result = append(result, row)
|
|
}
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (ss *Session) SyncUserLiveGoals(liveDifficultyID int, hiScore int, hiComboCount int, clearCnt int) ([]int, error) {
|
|
achievedRows, err := ss.GetAchievedLiveGoalRewardRows(liveDifficultyID, hiScore, hiComboCount, clearCnt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(achievedRows) == 0 {
|
|
return []int{}, nil
|
|
}
|
|
|
|
existingRows := []usermodel.UserLiveGoal{}
|
|
err = ss.UserEng.Table(new(usermodel.UserLiveGoal)).
|
|
Where("user_id = ? AND live_difficulty_id = ?", ss.UserID, liveDifficultyID).
|
|
Where("live_goal_reward_id > 0").
|
|
Find(&existingRows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existing := make(map[int]struct{}, len(existingRows))
|
|
for _, row := range existingRows {
|
|
existing[row.LiveGoalRewardID] = struct{}{}
|
|
}
|
|
|
|
now := time.Now().Unix()
|
|
newlyAchieved := make([]int, 0)
|
|
for _, row := range achievedRows {
|
|
if _, ok := existing[row.LiveGoalRewardID]; ok {
|
|
continue
|
|
}
|
|
|
|
_, err = ss.UserEng.Insert(&usermodel.UserLiveGoal{
|
|
UserID: ss.UserID,
|
|
LiveDifficultyID: liveDifficultyID,
|
|
LiveGoalRewardID: row.LiveGoalRewardID,
|
|
GoalType: row.LiveGoalType,
|
|
Rank: row.Rank,
|
|
CompletedAt: now,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newlyAchieved = append(newlyAchieved, row.LiveGoalRewardID)
|
|
}
|
|
|
|
sort.Ints(newlyAchieved)
|
|
return newlyAchieved, nil
|
|
}
|
|
|
|
func (ss *Session) GetLiveGoalRewardRowsByIDs(goalRewardIDs []int) ([]liveGoalRewardRow, error) {
|
|
if len(goalRewardIDs) == 0 {
|
|
return []liveGoalRewardRow{}, nil
|
|
}
|
|
|
|
rows := []liveGoalRewardRow{}
|
|
err := ss.MainEng.Table("live_goal_reward_m").
|
|
In("live_goal_reward_id", goalRewardIDs).
|
|
OrderBy("live_goal_reward_id ASC").
|
|
Find(&rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|