Improve live and random live handling
- Add random live handlers, schemas and persistence for lot/play/continue/gameover/reward flow - Update live status and schedule generation, including special rotation handling and CN timezone alignment - Add config support for unlocking all daily special rotation songs and document the option - Refresh live play, reward, precise score and session logic plus bundled main.db updates Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package live
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"honoka-chan/config"
|
||||
liveapischema "honoka-chan/internal/schema/api/live"
|
||||
"honoka-chan/internal/session"
|
||||
"os"
|
||||
@@ -15,7 +16,7 @@ const (
|
||||
randomLiveEndDate = "2038-01-19 12:14:07"
|
||||
)
|
||||
|
||||
var jst = time.FixedZone("JST", 9*3600)
|
||||
var cst = time.FixedZone("CST", 8*3600)
|
||||
|
||||
type liveAvailabilityRow struct {
|
||||
LiveDifficultyID int `xorm:"live_difficulty_id"`
|
||||
@@ -33,6 +34,12 @@ type specialLiveRotationRow struct {
|
||||
BaseDate string `xorm:"base_date"`
|
||||
}
|
||||
|
||||
type specialLiveRotationSchedule struct {
|
||||
LiveDifficultyID int
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
}
|
||||
|
||||
func listAvailableNormalLiveDifficultyIDs(ss *session.Session) ([]int, error) {
|
||||
rows, err := listAvailableNormalLiveRows(ss)
|
||||
if err != nil {
|
||||
@@ -46,25 +53,6 @@ func listAvailableNormalLiveDifficultyIDs(ss *session.Session) ([]int, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func listAvailableNormalLiveTrackIDs(ss *session.Session) ([]int, error) {
|
||||
rows, err := listAvailableNormalLiveRows(ss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trackSet := map[int]struct{}{}
|
||||
for _, row := range rows {
|
||||
trackSet[row.LiveTrackID] = struct{}{}
|
||||
}
|
||||
|
||||
result := make([]int, 0, len(trackSet))
|
||||
for id := range trackSet {
|
||||
result = append(result, id)
|
||||
}
|
||||
sort.Ints(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func listAvailableNormalLiveRows(ss *session.Session) ([]liveAvailabilityRow, error) {
|
||||
rows := []liveAvailabilityRow{}
|
||||
err := ss.MainEng.Table("normal_live_m").Alias("live").
|
||||
@@ -155,11 +143,11 @@ func listTodaySpecialRotationDifficultyIDs(ss *session.Session, now time.Time) (
|
||||
dayModulo int64
|
||||
}{
|
||||
liveDifficultyID: row.LiveDifficultyID,
|
||||
dayModulo: baseDate.Unix() / 86400,
|
||||
dayModulo: dayIndexInCST(baseDate),
|
||||
})
|
||||
}
|
||||
|
||||
currentDay := now.In(jst).Unix() / 86400
|
||||
currentDay := dayIndexInCST(now)
|
||||
groupIDs := make([]int, 0, len(grouped))
|
||||
for groupID := range grouped {
|
||||
groupIDs = append(groupIDs, groupID)
|
||||
@@ -183,54 +171,174 @@ func listTodaySpecialRotationDifficultyIDs(ss *session.Session, now time.Time) (
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func listAllSpecialRotationDifficultyIDs(ss *session.Session) (map[int]struct{}, error) {
|
||||
ids := []int{}
|
||||
func listCurrentAndNextSpecialRotationSchedules(ss *session.Session, now time.Time) ([]specialLiveRotationSchedule, error) {
|
||||
if config.Conf.Settings.UnlockAllSpecialRotation {
|
||||
return listAllSpecialRotationSchedules(ss)
|
||||
}
|
||||
|
||||
rows := []specialLiveRotationRow{}
|
||||
err := ss.MainEng.Table("special_live_rotation_m").
|
||||
Cols("live_difficulty_id").
|
||||
Find(&ids)
|
||||
OrderBy("rotation_group_id ASC, base_date ASC").
|
||||
Find(&rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[int]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
result[id] = struct{}{}
|
||||
type rotationEntry struct {
|
||||
liveDifficultyID int
|
||||
baseTime time.Time
|
||||
dayModulo int64
|
||||
}
|
||||
|
||||
grouped := map[int][]rotationEntry{}
|
||||
groupIDs := []int{}
|
||||
for _, row := range rows {
|
||||
baseDate, err := parseRotationBaseDate(row.BaseDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := grouped[row.RotationGroupID]; !ok {
|
||||
groupIDs = append(groupIDs, row.RotationGroupID)
|
||||
}
|
||||
grouped[row.RotationGroupID] = append(grouped[row.RotationGroupID], rotationEntry{
|
||||
liveDifficultyID: row.LiveDifficultyID,
|
||||
baseTime: baseDate,
|
||||
dayModulo: dayIndexInCST(baseDate),
|
||||
})
|
||||
}
|
||||
sort.Ints(groupIDs)
|
||||
|
||||
currentDay := dayIndexInCST(now)
|
||||
result := make([]specialLiveRotationSchedule, 0, len(groupIDs)*2)
|
||||
for _, groupID := range groupIDs {
|
||||
liveList := grouped[groupID]
|
||||
if len(liveList) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
currentIndex := -1
|
||||
currentDayModulo := currentDay % int64(len(liveList))
|
||||
for i, live := range liveList {
|
||||
if live.dayModulo%int64(len(liveList)) == currentDayModulo {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if currentIndex == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
periodDays := int64(1)
|
||||
if len(liveList) >= 2 {
|
||||
diff := int64(liveList[1].baseTime.Sub(liveList[0].baseTime) / (24 * time.Hour))
|
||||
if diff > 0 {
|
||||
periodDays = diff
|
||||
}
|
||||
}
|
||||
|
||||
currentEntry := liveList[currentIndex]
|
||||
offsetDays := (currentDay - currentEntry.dayModulo) % periodDays
|
||||
if offsetDays < 0 {
|
||||
offsetDays += periodDays
|
||||
}
|
||||
currentStartTime := startOfDay(now.In(cst)).AddDate(0, 0, -int(offsetDays))
|
||||
nextStartTime := currentStartTime.AddDate(0, 0, int(periodDays))
|
||||
duration := time.Duration(periodDays)*24*time.Hour - time.Second
|
||||
nextIndex := (currentIndex + 1) % len(liveList)
|
||||
|
||||
for i, live := range liveList {
|
||||
switch i {
|
||||
case currentIndex:
|
||||
result = append(result, specialLiveRotationSchedule{
|
||||
LiveDifficultyID: live.liveDifficultyID,
|
||||
StartTime: currentStartTime,
|
||||
EndTime: currentStartTime.Add(duration),
|
||||
})
|
||||
case nextIndex:
|
||||
result = append(result, specialLiveRotationSchedule{
|
||||
LiveDifficultyID: live.liveDifficultyID,
|
||||
StartTime: nextStartTime,
|
||||
EndTime: nextStartTime.Add(duration),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func listAllSpecialRotationSchedules(ss *session.Session) ([]specialLiveRotationSchedule, error) {
|
||||
rows := []specialLiveRotationRow{}
|
||||
err := ss.MainEng.Table("special_live_rotation_m").
|
||||
OrderBy("rotation_group_id ASC, base_date ASC").
|
||||
Find(&rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type rotationEntry struct {
|
||||
liveDifficultyID int
|
||||
baseTime time.Time
|
||||
}
|
||||
|
||||
grouped := map[int][]rotationEntry{}
|
||||
groupIDs := []int{}
|
||||
for _, row := range rows {
|
||||
baseDate, err := parseRotationBaseDate(row.BaseDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := grouped[row.RotationGroupID]; !ok {
|
||||
groupIDs = append(groupIDs, row.RotationGroupID)
|
||||
}
|
||||
grouped[row.RotationGroupID] = append(grouped[row.RotationGroupID], rotationEntry{
|
||||
liveDifficultyID: row.LiveDifficultyID,
|
||||
baseTime: baseDate,
|
||||
})
|
||||
}
|
||||
sort.Ints(groupIDs)
|
||||
|
||||
result := make([]specialLiveRotationSchedule, 0, len(rows))
|
||||
for _, groupID := range groupIDs {
|
||||
liveList := grouped[groupID]
|
||||
if len(liveList) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
periodDays := int64(1)
|
||||
if len(liveList) >= 2 {
|
||||
diff := int64(liveList[1].baseTime.Sub(liveList[0].baseTime) / (24 * time.Hour))
|
||||
if diff > 0 {
|
||||
periodDays = diff
|
||||
}
|
||||
}
|
||||
if periodDays != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, live := range liveList {
|
||||
result = append(result, specialLiveRotationSchedule{
|
||||
LiveDifficultyID: live.liveDifficultyID,
|
||||
StartTime: time.Unix(0, 0).In(cst),
|
||||
EndTime: time.Unix(2147483647, 0).In(cst),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func listAvailableTrainingLiveDifficultyIDs(ss *session.Session) ([]int, error) {
|
||||
normalTrackIDs, err := listAvailableNormalLiveTrackIDs(ss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
specialRows, err := listAvailableSpecialLiveRows(ss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rotationIDs, err := listAllSpecialRotationDifficultyIDs(ss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
normalTrackIDSet := make(map[int]struct{}, len(normalTrackIDs))
|
||||
for _, id := range normalTrackIDs {
|
||||
normalTrackIDSet[id] = struct{}{}
|
||||
}
|
||||
|
||||
result := make([]int, 0)
|
||||
for _, row := range specialRows {
|
||||
if _, ok := normalTrackIDSet[row.LiveTrackID]; !ok {
|
||||
continue
|
||||
}
|
||||
if row.Difficulty <= 5 || row.AcFlag != 0 || row.ExcludeClearCountFlag != 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := rotationIDs[row.LiveDifficultyID]; ok {
|
||||
continue
|
||||
}
|
||||
result = append(result, row.LiveDifficultyID)
|
||||
}
|
||||
|
||||
@@ -244,7 +352,7 @@ func parseRotationBaseDate(value string) (time.Time, error) {
|
||||
"2006/01/02 3:04:05",
|
||||
}
|
||||
for _, layout := range layouts {
|
||||
t, err := time.ParseInLocation(layout, value, jst)
|
||||
t, err := time.ParseInLocation(layout, value, cst)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
@@ -256,6 +364,10 @@ func startOfDay(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
func dayIndexInCST(t time.Time) int64 {
|
||||
return startOfDay(t.In(cst)).Unix() / 86400
|
||||
}
|
||||
|
||||
func nextDayStart(t time.Time) time.Time {
|
||||
return startOfDay(t).Add(24 * time.Hour)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user