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
|
||||
|
||||
@@ -4,9 +4,11 @@ import "honoka-chan/internal/constant"
|
||||
|
||||
type UserLiveGoal struct {
|
||||
ID int `xorm:"id pk autoincr"`
|
||||
LiveDifficultyID int `xorm:"live_difficulty_id"`
|
||||
UserID int `xorm:"user_id"`
|
||||
LiveDifficultyID int `xorm:"live_difficulty_id index"`
|
||||
UserID int `xorm:"user_id index"`
|
||||
LiveGoalRewardID int `xorm:"live_goal_reward_id index"`
|
||||
GoalType constant.LiveGoalType `xorm:"goal_type"`
|
||||
Rank int `xorm:"rank"`
|
||||
CompletedAt int64 `xorm:"completed_at"`
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package usermodel
|
||||
|
||||
type UserLiveStatus struct {
|
||||
ID int `xorm:"id pk autoincr"`
|
||||
UserID int `xorm:"user_id unique(user_live_status_pair) index"`
|
||||
LiveDifficultyID int `xorm:"live_difficulty_id unique(user_live_status_pair) index"`
|
||||
HiScore int `xorm:"hi_score"`
|
||||
HiComboCount int `xorm:"hi_combo_count"`
|
||||
ClearCnt int `xorm:"clear_cnt"`
|
||||
InsertDate int64 `xorm:"insert_date"`
|
||||
UpdateDate int64 `xorm:"update_date"`
|
||||
}
|
||||
|
||||
func (UserLiveStatus) TableName() string {
|
||||
return "user_live_status"
|
||||
}
|
||||
@@ -39,6 +39,10 @@ type LiveInfo struct {
|
||||
BRankCombo int `json:"-"`
|
||||
CRankCombo int `json:"-"`
|
||||
SRankCombo int `json:"-"`
|
||||
ARankComplete int `json:"-"`
|
||||
BRankComplete int `json:"-"`
|
||||
CRankComplete int `json:"-"`
|
||||
SRankComplete int `json:"-"`
|
||||
AcFlag int `json:"ac_flag"`
|
||||
SwingFlag int `json:"swing_flag"`
|
||||
NotesList []NotesList `json:"notes_list"`
|
||||
|
||||
+44
-27
@@ -1,7 +1,6 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"honoka-chan/internal/constant"
|
||||
usermodel "honoka-chan/internal/model/user"
|
||||
liveschema "honoka-chan/internal/schema/live"
|
||||
liverecordschema "honoka-chan/internal/schema/liverecord"
|
||||
@@ -64,13 +63,34 @@ func (ss *Session) GetLiveInfo(LiveDifficultyID int) (bool, *liveschema.LiveInfo
|
||||
BRankCombo int `xorm:"b_rank_combo"`
|
||||
CRankCombo int `xorm:"c_rank_combo"`
|
||||
SRankCombo int `xorm:"s_rank_combo"`
|
||||
ARankComplete int `xorm:"a_rank_complete"`
|
||||
BRankComplete int `xorm:"b_rank_complete"`
|
||||
CRankComplete int `xorm:"c_rank_complete"`
|
||||
SRankComplete int `xorm:"s_rank_complete"`
|
||||
AcFlag int `xorm:"ac_flag"`
|
||||
SwingFlag int `xorm:"swing_flag"`
|
||||
}
|
||||
has, err := ss.MainEng.Table("special_live_m").Alias("a").
|
||||
Join("LEFT", "live_setting_m", "a.live_setting_id = live_setting_m.live_setting_id").
|
||||
Where("live_difficulty_id = ?", LiveDifficultyID).
|
||||
Cols("a.live_difficulty_id,live_setting_m.*").Get(&info)
|
||||
Select(`
|
||||
a.live_difficulty_id,
|
||||
a.c_rank_complete,
|
||||
a.b_rank_complete,
|
||||
a.a_rank_complete,
|
||||
a.s_rank_complete,
|
||||
live_setting_m.notes_setting_asset,
|
||||
live_setting_m.a_rank_score,
|
||||
live_setting_m.b_rank_score,
|
||||
live_setting_m.c_rank_score,
|
||||
live_setting_m.s_rank_score,
|
||||
live_setting_m.a_rank_combo,
|
||||
live_setting_m.b_rank_combo,
|
||||
live_setting_m.c_rank_combo,
|
||||
live_setting_m.s_rank_combo,
|
||||
live_setting_m.ac_flag,
|
||||
live_setting_m.swing_flag
|
||||
`).Get(&info)
|
||||
if ss.CheckErr(err) {
|
||||
return false, nil
|
||||
}
|
||||
@@ -79,7 +99,24 @@ func (ss *Session) GetLiveInfo(LiveDifficultyID int) (bool, *liveschema.LiveInfo
|
||||
has, err = ss.MainEng.Table("normal_live_m").Alias("a").
|
||||
Join("LEFT", "live_setting_m", "a.live_setting_id = live_setting_m.live_setting_id").
|
||||
Where("live_difficulty_id = ?", LiveDifficultyID).
|
||||
Cols("a.live_difficulty_id,live_setting_m.*").Get(&info)
|
||||
Select(`
|
||||
a.live_difficulty_id,
|
||||
a.c_rank_complete,
|
||||
a.b_rank_complete,
|
||||
a.a_rank_complete,
|
||||
a.s_rank_complete,
|
||||
live_setting_m.notes_setting_asset,
|
||||
live_setting_m.a_rank_score,
|
||||
live_setting_m.b_rank_score,
|
||||
live_setting_m.c_rank_score,
|
||||
live_setting_m.s_rank_score,
|
||||
live_setting_m.a_rank_combo,
|
||||
live_setting_m.b_rank_combo,
|
||||
live_setting_m.c_rank_combo,
|
||||
live_setting_m.s_rank_combo,
|
||||
live_setting_m.ac_flag,
|
||||
live_setting_m.swing_flag
|
||||
`).Get(&info)
|
||||
if ss.CheckErr(err) {
|
||||
return false, nil
|
||||
}
|
||||
@@ -105,6 +142,10 @@ func (ss *Session) GetLiveInfo(LiveDifficultyID int) (bool, *liveschema.LiveInfo
|
||||
BRankCombo: info.BRankCombo,
|
||||
CRankCombo: info.CRankCombo,
|
||||
SRankCombo: info.SRankCombo,
|
||||
ARankComplete: info.ARankComplete,
|
||||
BRankComplete: info.BRankComplete,
|
||||
CRankComplete: info.CRankComplete,
|
||||
SRankComplete: info.SRankComplete,
|
||||
AcFlag: info.AcFlag,
|
||||
SwingFlag: info.SwingFlag,
|
||||
NotesList: []liveschema.NotesList{},
|
||||
@@ -188,27 +229,3 @@ func (ss *Session) GetDeckInfo(deckID int) (bool, *liverecordschema.DeckInfo) {
|
||||
UnitList: unitData,
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *Session) GetLiveGoalList(LiveDifficultyID int) []constant.LiveGoalType {
|
||||
var goal []constant.LiveGoalType
|
||||
err := ss.MainEng.Table("live_goal_reward_m").
|
||||
Where("live_difficulty_id = ?", LiveDifficultyID).Cols("live_goal_type").Find(&goal)
|
||||
if ss.CheckErr(err) {
|
||||
return []constant.LiveGoalType{}
|
||||
}
|
||||
return goal
|
||||
}
|
||||
|
||||
func (ss *Session) GetUserLiveGoalList(LiveDifficultyID int) []constant.LiveGoalType {
|
||||
var goal []constant.LiveGoalType
|
||||
var goalData []usermodel.UserLiveGoal
|
||||
err := ss.UserEng.Table(new(usermodel.UserLiveGoal)).
|
||||
Where("user_id = ? AND live_difficulty_id = ?", ss.UserID, LiveDifficultyID).Find(&goalData)
|
||||
if ss.CheckErr(err) {
|
||||
return []constant.LiveGoalType{}
|
||||
}
|
||||
for _, g := range goalData {
|
||||
goal = append(goal, g.GoalType)
|
||||
}
|
||||
return goal
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
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
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package startup
|
||||
|
||||
import (
|
||||
"honoka-chan/internal/constant"
|
||||
ghomemodel "honoka-chan/internal/model/ghome"
|
||||
loginmodel "honoka-chan/internal/model/login"
|
||||
unitmodel "honoka-chan/internal/model/unit"
|
||||
usermodel "honoka-chan/internal/model/user"
|
||||
"honoka-chan/pkg/db"
|
||||
"log"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
@@ -25,6 +27,7 @@ func CreateTables() {
|
||||
db.UserEng.Sync2(new(usermodel.UserDeckUnit))
|
||||
db.UserEng.Sync2(new(usermodel.UserKey))
|
||||
db.UserEng.Sync2(new(usermodel.UserLiveGoal))
|
||||
db.UserEng.Sync2(new(usermodel.UserLiveStatus))
|
||||
db.UserEng.Sync2(new(usermodel.UserLiveInProgress))
|
||||
db.UserEng.Sync2(new(usermodel.UserLiveRecord))
|
||||
db.UserEng.Sync2(new(usermodel.UserFriend))
|
||||
@@ -35,6 +38,7 @@ func CreateTables() {
|
||||
db.UserEng.Sync2(new(usermodel.UserUnitSkillEquip))
|
||||
|
||||
MigrateUserPref()
|
||||
MigrateUserLiveData()
|
||||
}
|
||||
|
||||
func MigrateUserPref() {
|
||||
@@ -62,6 +66,233 @@ func MigrateUserPref() {
|
||||
}
|
||||
}
|
||||
|
||||
type liveGoalRewardMigrationRow struct {
|
||||
LiveGoalRewardID int `xorm:"live_goal_reward_id"`
|
||||
LiveDifficultyID int `xorm:"live_difficulty_id"`
|
||||
LiveGoalType int `xorm:"live_goal_type"`
|
||||
Rank int `xorm:"rank"`
|
||||
}
|
||||
|
||||
type liveGoalInfoMigrationRow struct {
|
||||
LiveDifficultyID int `xorm:"live_difficulty_id"`
|
||||
CRankScore int `xorm:"c_rank_score"`
|
||||
BRankScore int `xorm:"b_rank_score"`
|
||||
ARankScore int `xorm:"a_rank_score"`
|
||||
SRankScore int `xorm:"s_rank_score"`
|
||||
CRankCombo int `xorm:"c_rank_combo"`
|
||||
BRankCombo int `xorm:"b_rank_combo"`
|
||||
ARankCombo int `xorm:"a_rank_combo"`
|
||||
SRankCombo int `xorm:"s_rank_combo"`
|
||||
CRankComplete int `xorm:"c_rank_complete"`
|
||||
BRankComplete int `xorm:"b_rank_complete"`
|
||||
ARankComplete int `xorm:"a_rank_complete"`
|
||||
SRankComplete int `xorm:"s_rank_complete"`
|
||||
}
|
||||
|
||||
func liveRankForMigration(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 MigrateUserLiveData() {
|
||||
session := db.UserEng.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
recordRows := []usermodel.UserLiveRecord{}
|
||||
if err := session.Table(new(usermodel.UserLiveRecord)).Find(&recordRows); err != nil {
|
||||
log.Fatalln("迁移 user_live_status 失败:", err.Error())
|
||||
}
|
||||
|
||||
existingStatusRows := []usermodel.UserLiveStatus{}
|
||||
if err := session.Table(new(usermodel.UserLiveStatus)).Find(&existingStatusRows); err != nil {
|
||||
log.Fatalln("迁移 user_live_status 失败:", err.Error())
|
||||
}
|
||||
|
||||
type liveStatusKey struct {
|
||||
UserID int
|
||||
LiveDifficultyID int
|
||||
}
|
||||
|
||||
statusMap := make(map[liveStatusKey]usermodel.UserLiveStatus, len(existingStatusRows))
|
||||
for _, row := range existingStatusRows {
|
||||
statusMap[liveStatusKey{UserID: row.UserID, LiveDifficultyID: row.LiveDifficultyID}] = row
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
for _, row := range recordRows {
|
||||
key := liveStatusKey{UserID: row.UserID, LiveDifficultyID: row.LiveDifficultyID}
|
||||
status, ok := statusMap[key]
|
||||
if !ok {
|
||||
status = usermodel.UserLiveStatus{
|
||||
UserID: row.UserID,
|
||||
LiveDifficultyID: row.LiveDifficultyID,
|
||||
HiScore: row.TotalScore,
|
||||
HiComboCount: row.MaxCombo,
|
||||
ClearCnt: 1,
|
||||
InsertDate: now,
|
||||
UpdateDate: now,
|
||||
}
|
||||
if _, err := session.Insert(&status); err != nil {
|
||||
log.Fatalln("迁移 user_live_status 失败:", err.Error())
|
||||
}
|
||||
statusMap[key] = status
|
||||
continue
|
||||
}
|
||||
|
||||
updated := false
|
||||
if row.TotalScore > status.HiScore {
|
||||
status.HiScore = row.TotalScore
|
||||
updated = true
|
||||
}
|
||||
if row.MaxCombo > status.HiComboCount {
|
||||
status.HiComboCount = row.MaxCombo
|
||||
updated = true
|
||||
}
|
||||
if status.ClearCnt <= 0 {
|
||||
status.ClearCnt = 1
|
||||
updated = true
|
||||
}
|
||||
if updated {
|
||||
status.UpdateDate = now
|
||||
if _, err := session.Table(new(usermodel.UserLiveStatus)).
|
||||
Where("user_id = ? AND live_difficulty_id = ?", status.UserID, status.LiveDifficultyID).
|
||||
Cols("hi_score", "hi_combo_count", "clear_cnt", "update_date").
|
||||
Update(&status); err != nil {
|
||||
log.Fatalln("迁移 user_live_status 失败:", err.Error())
|
||||
}
|
||||
statusMap[key] = status
|
||||
}
|
||||
}
|
||||
|
||||
liveGoalInfoMap := map[int]liveGoalInfoMigrationRow{}
|
||||
loadLiveGoalInfoRows := func(table string) {
|
||||
rows := []liveGoalInfoMigrationRow{}
|
||||
err := db.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.c_rank_score,
|
||||
setting.b_rank_score,
|
||||
setting.a_rank_score,
|
||||
setting.s_rank_score,
|
||||
setting.c_rank_combo,
|
||||
setting.b_rank_combo,
|
||||
setting.a_rank_combo,
|
||||
setting.s_rank_combo,
|
||||
live.c_rank_complete,
|
||||
live.b_rank_complete,
|
||||
live.a_rank_complete,
|
||||
live.s_rank_complete
|
||||
`).
|
||||
Find(&rows)
|
||||
if err != nil {
|
||||
log.Fatalln("迁移 user_live_goal 失败:", err.Error())
|
||||
}
|
||||
for _, row := range rows {
|
||||
liveGoalInfoMap[row.LiveDifficultyID] = row
|
||||
}
|
||||
}
|
||||
loadLiveGoalInfoRows("normal_live_m")
|
||||
loadLiveGoalInfoRows("special_live_m")
|
||||
|
||||
goalRewardRows := []liveGoalRewardMigrationRow{}
|
||||
if err := db.MainEng.Table("live_goal_reward_m").
|
||||
OrderBy("live_difficulty_id ASC, live_goal_type ASC, rank ASC, live_goal_reward_id ASC").
|
||||
Find(&goalRewardRows); err != nil {
|
||||
log.Fatalln("迁移 user_live_goal 失败:", err.Error())
|
||||
}
|
||||
|
||||
goalRewardMap := map[int][]liveGoalRewardMigrationRow{}
|
||||
for _, row := range goalRewardRows {
|
||||
goalRewardMap[row.LiveDifficultyID] = append(goalRewardMap[row.LiveDifficultyID], row)
|
||||
}
|
||||
|
||||
existingGoalRows := []usermodel.UserLiveGoal{}
|
||||
if err := session.Table(new(usermodel.UserLiveGoal)).
|
||||
Where("live_goal_reward_id > 0").
|
||||
Find(&existingGoalRows); err != nil {
|
||||
log.Fatalln("迁移 user_live_goal 失败:", err.Error())
|
||||
}
|
||||
|
||||
existingGoalMap := make(map[liveStatusKey]map[int]struct{})
|
||||
for _, row := range existingGoalRows {
|
||||
key := liveStatusKey{UserID: row.UserID, LiveDifficultyID: row.LiveDifficultyID}
|
||||
if existingGoalMap[key] == nil {
|
||||
existingGoalMap[key] = map[int]struct{}{}
|
||||
}
|
||||
existingGoalMap[key][row.LiveGoalRewardID] = struct{}{}
|
||||
}
|
||||
|
||||
statusKeys := make([]liveStatusKey, 0, len(statusMap))
|
||||
for key := range statusMap {
|
||||
statusKeys = append(statusKeys, key)
|
||||
}
|
||||
sort.Slice(statusKeys, func(i, j int) bool {
|
||||
if statusKeys[i].UserID == statusKeys[j].UserID {
|
||||
return statusKeys[i].LiveDifficultyID < statusKeys[j].LiveDifficultyID
|
||||
}
|
||||
return statusKeys[i].UserID < statusKeys[j].UserID
|
||||
})
|
||||
|
||||
for _, key := range statusKeys {
|
||||
status := statusMap[key]
|
||||
liveInfo, ok := liveGoalInfoMap[key.LiveDifficultyID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
scoreRank := liveRankForMigration(status.HiScore, liveInfo.CRankScore, liveInfo.BRankScore, liveInfo.ARankScore, liveInfo.SRankScore)
|
||||
comboRank := liveRankForMigration(status.HiComboCount, liveInfo.CRankCombo, liveInfo.BRankCombo, liveInfo.ARankCombo, liveInfo.SRankCombo)
|
||||
clearRank := liveRankForMigration(status.ClearCnt, liveInfo.CRankComplete, liveInfo.BRankComplete, liveInfo.ARankComplete, liveInfo.SRankComplete)
|
||||
|
||||
for _, reward := range goalRewardMap[key.LiveDifficultyID] {
|
||||
achieved := false
|
||||
switch reward.LiveGoalType {
|
||||
case 1:
|
||||
achieved = scoreRank <= reward.Rank
|
||||
case 2:
|
||||
achieved = comboRank <= reward.Rank
|
||||
case 3:
|
||||
achieved = clearRank <= reward.Rank
|
||||
}
|
||||
if !achieved {
|
||||
continue
|
||||
}
|
||||
|
||||
if existingGoalMap[key] != nil {
|
||||
if _, ok := existingGoalMap[key][reward.LiveGoalRewardID]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := session.Insert(&usermodel.UserLiveGoal{
|
||||
UserID: key.UserID,
|
||||
LiveDifficultyID: key.LiveDifficultyID,
|
||||
LiveGoalRewardID: reward.LiveGoalRewardID,
|
||||
GoalType: constant.LiveGoalType(reward.LiveGoalType),
|
||||
Rank: reward.Rank,
|
||||
CompletedAt: now,
|
||||
}); err != nil {
|
||||
log.Fatalln("迁移 user_live_goal 失败:", err.Error())
|
||||
}
|
||||
if existingGoalMap[key] == nil {
|
||||
existingGoalMap[key] = map[int]struct{}{}
|
||||
}
|
||||
existingGoalMap[key][reward.LiveGoalRewardID] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func LoadUnitData() {
|
||||
userEng = db.UserEng.NewSession()
|
||||
defer userEng.Close()
|
||||
|
||||
Reference in New Issue
Block a user