Implement world-map entities
Build and Push Docker images to Docker Hub / build-and-push (push) Has been cancelled

This commit is contained in:
Ilya Groshev
2026-05-21 14:15:11 +03:00
parent ab5a999ffe
commit b65c1c5fce
12 changed files with 998 additions and 50 deletions
+4 -2
View File
@@ -287,10 +287,12 @@ func ChangedTables(before, after *store.UserState) []string {
}
if !gimmickStateEqual(before.Gimmick, after.Gimmick) {
if !mapsEqualStruct(before.Gimmick.Progress, after.Gimmick.Progress) {
if !mapsEqualStruct(before.Gimmick.Progress, after.Gimmick.Progress) ||
!mapsEqualStruct(before.Gimmick.Sequences, after.Gimmick.Sequences) {
add("IUserGimmick")
}
if !mapsEqualStruct(before.Gimmick.OrnamentProgress, after.Gimmick.OrnamentProgress) {
if !mapsEqualStruct(before.Gimmick.OrnamentProgress, after.Gimmick.OrnamentProgress) ||
!mapsEqualStruct(before.Gimmick.Sequences, after.Gimmick.Sequences) {
add("IUserGimmickOrnamentProgress")
}
if !mapsEqualStruct(before.Gimmick.Sequences, after.Gimmick.Sequences) {
+129 -17
View File
@@ -2,11 +2,21 @@ package userdata
import (
"sort"
"sync"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
"lunar-tear/server/internal/utils"
)
var gimmickOrnamentRefs = sync.OnceValue(masterdata.LoadGimmickOrnamentRefs)
var gimmickSequenceChains = sync.OnceValue(masterdata.LoadGimmickSequenceChains)
var hiddenSequenceSet = sync.OnceValue(masterdata.LoadHiddenGimmickSequenceIDs)
var gimmickSequenceRanks = sync.OnceValue(masterdata.LoadGimmickSequenceRanks)
var birdGimmicks = sync.OnceValue(masterdata.LoadBirdGimmickIDs)
const birdDefaultBaseDatetime int64 = 1577836800000 // 2020-01-01 00:00:00 UTC in ms
func init() {
register("IUserGimmick", func(user store.UserState) string {
s, _ := utils.EncodeJSONMaps(sortedGimmickRecords(user)...)
@@ -26,9 +36,65 @@ func init() {
})
}
func projectActiveChainOrnaments(
user store.UserState,
addKey func(seqKey store.GimmickSequenceKey, seqId int32, ref masterdata.GimmickOrnamentRef),
sizeFn func() int,
cap int,
) {
refs := gimmickOrnamentRefs()
chains := gimmickSequenceChains()
hiddenSeq := hiddenSequenceSet()
walkChain := func(seqKey store.GimmickSequenceKey) {
chain := chains[seqKey.GimmickSequenceId]
if len(chain) == 0 {
chain = []int32{seqKey.GimmickSequenceId}
}
for _, seqId := range chain {
for _, ref := range refs[seqId] {
addKey(seqKey, seqId, ref)
}
}
}
var nonHidden []store.GimmickSequenceKey
for seqKey := range user.Gimmick.Sequences {
if hiddenSeq[seqKey.GimmickSequenceId] {
walkChain(seqKey)
} else {
nonHidden = append(nonHidden, seqKey)
}
}
for _, seqKey := range nonHidden {
if sizeFn() >= cap {
break
}
walkChain(seqKey)
}
}
func sortedGimmickRecords(user store.UserState) []map[string]any {
keys := make([]store.GimmickKey, 0, len(user.Gimmick.Progress))
keySet := make(map[store.GimmickKey]struct{})
// Real progress rows (genuine user data) — always kept.
for key := range user.Gimmick.Progress {
keySet[key] = struct{}{}
}
projectActiveChainOrnaments(user,
func(seqKey store.GimmickSequenceKey, seqId int32, ref masterdata.GimmickOrnamentRef) {
keySet[store.GimmickKey{
GimmickSequenceScheduleId: seqKey.GimmickSequenceScheduleId,
GimmickSequenceId: seqId,
GimmickId: ref.GimmickId,
}] = struct{}{}
},
func() int { return len(keySet) },
masterdata.MaxUserGimmickRows,
)
keys := make([]store.GimmickKey, 0, len(keySet))
for key := range keySet {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
@@ -37,57 +103,103 @@ func sortedGimmickRecords(user store.UserState) []map[string]any {
records := make([]map[string]any, 0, len(keys))
for _, key := range keys {
row := user.Gimmick.Progress[key]
isGimmickCleared := false
startDatetime := user.GameStartDatetime
latestVersion := user.GameStartDatetime
if row, ok := user.Gimmick.Progress[key]; ok {
isGimmickCleared = row.IsGimmickCleared
startDatetime = row.StartDatetime
latestVersion = row.LatestVersion
}
records = append(records, map[string]any{
"userId": user.UserId,
"gimmickSequenceScheduleId": row.Key.GimmickSequenceScheduleId,
"gimmickSequenceId": row.Key.GimmickSequenceId,
"gimmickId": row.Key.GimmickId,
"isGimmickCleared": row.IsGimmickCleared,
"startDatetime": row.StartDatetime,
"latestVersion": row.LatestVersion,
"gimmickSequenceScheduleId": key.GimmickSequenceScheduleId,
"gimmickSequenceId": key.GimmickSequenceId,
"gimmickId": key.GimmickId,
"isGimmickCleared": isGimmickCleared,
"startDatetime": startDatetime,
"latestVersion": latestVersion,
})
}
return records
}
func sortedGimmickOrnamentProgressRecords(user store.UserState) []map[string]any {
keys := make([]store.GimmickOrnamentKey, 0, len(user.Gimmick.OrnamentProgress))
keySet := make(map[store.GimmickOrnamentKey]struct{})
// Real progress rows (genuine user data) — always kept.
for key := range user.Gimmick.OrnamentProgress {
keySet[key] = struct{}{}
}
projectActiveChainOrnaments(user,
func(seqKey store.GimmickSequenceKey, seqId int32, ref masterdata.GimmickOrnamentRef) {
keySet[store.GimmickOrnamentKey{
GimmickSequenceScheduleId: seqKey.GimmickSequenceScheduleId,
GimmickSequenceId: seqId,
GimmickId: ref.GimmickId,
GimmickOrnamentIndex: ref.OrnamentIndex,
}] = struct{}{}
},
func() int { return len(keySet) },
masterdata.MaxUserGimmickRows,
)
keys := make([]store.GimmickOrnamentKey, 0, len(keySet))
for key := range keySet {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
return compareGimmickOrnamentKey(keys[i], keys[j]) < 0
})
birdG := birdGimmicks()
records := make([]map[string]any, 0, len(keys))
for _, key := range keys {
row := user.Gimmick.OrnamentProgress[key]
progressValueBit := int32(0)
baseDatetime := user.GameStartDatetime
latestVersion := user.GameStartDatetime
if row, ok := user.Gimmick.OrnamentProgress[key]; ok {
progressValueBit = row.ProgressValueBit
baseDatetime = row.BaseDatetime
latestVersion = row.LatestVersion
} else if birdG[key.GimmickId] {
baseDatetime = birdDefaultBaseDatetime
}
records = append(records, map[string]any{
"userId": user.UserId,
"gimmickSequenceScheduleId": row.Key.GimmickSequenceScheduleId,
"gimmickSequenceId": row.Key.GimmickSequenceId,
"gimmickId": row.Key.GimmickId,
"gimmickOrnamentIndex": row.Key.GimmickOrnamentIndex,
"progressValueBit": row.ProgressValueBit,
"baseDatetime": row.BaseDatetime,
"latestVersion": row.LatestVersion,
"gimmickSequenceScheduleId": key.GimmickSequenceScheduleId,
"gimmickSequenceId": key.GimmickSequenceId,
"gimmickId": key.GimmickId,
"gimmickOrnamentIndex": key.GimmickOrnamentIndex,
"progressValueBit": progressValueBit,
"baseDatetime": baseDatetime,
"latestVersion": latestVersion,
})
}
return records
}
func sortedGimmickSequenceRecords(user store.UserState) []map[string]any {
ranks := gimmickSequenceRanks()
keys := make([]store.GimmickSequenceKey, 0, len(user.Gimmick.Sequences))
for key := range user.Gimmick.Sequences {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
ri, rj := ranks[keys[i].GimmickSequenceId], ranks[keys[j].GimmickSequenceId]
if ri != rj {
return ri < rj
}
if keys[i].GimmickSequenceScheduleId != keys[j].GimmickSequenceScheduleId {
return keys[i].GimmickSequenceScheduleId < keys[j].GimmickSequenceScheduleId
}
return keys[i].GimmickSequenceId < keys[j].GimmickSequenceId
})
if len(keys) > masterdata.MaxUserGimmickRows {
keys = keys[:masterdata.MaxUserGimmickRows]
}
records := make([]map[string]any, 0, len(keys))
for _, key := range keys {
+21 -3
View File
@@ -33,8 +33,26 @@ func sortedQuestRecords(user store.UserState) []map[string]any {
}
func sortedQuestMissionRecords(user store.UserState) []map[string]any {
keys := make([]store.QuestMissionKey, 0, len(user.QuestMissions))
for key := range user.QuestMissions {
questMissions := make(map[store.QuestMissionKey]store.UserQuestMissionState, len(user.QuestMissions))
for key, qm := range user.QuestMissions {
questMissions[key] = qm
}
// Force-clear hidden-story quest-missions so their report gimmicks unlock.
for _, key := range hiddenStoryRequirements().QuestMissions {
if existing, ok := questMissions[key]; ok && existing.IsClear {
continue
}
questMissions[key] = store.UserQuestMissionState{
QuestId: key.QuestId,
QuestMissionId: key.QuestMissionId,
IsClear: true,
LatestClearDatetime: user.GameStartDatetime,
LatestVersion: user.GameStartDatetime,
}
}
keys := make([]store.QuestMissionKey, 0, len(questMissions))
for key := range questMissions {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
@@ -45,7 +63,7 @@ func sortedQuestMissionRecords(user store.UserState) []map[string]any {
})
records := make([]map[string]any, 0, len(keys))
for _, key := range keys {
row := user.QuestMissions[key]
row := questMissions[key]
records = append(records, map[string]any{
"userId": user.UserId,
"questId": row.QuestId,
+25 -3
View File
@@ -2,8 +2,11 @@ package userdata
import (
"sort"
"sync"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
"lunar-tear/server/internal/utils"
)
@@ -192,16 +195,35 @@ func sortedTutorialRecords(user store.UserState) []map[string]any {
return records
}
var hiddenStoryRequirements = sync.OnceValue(masterdata.LoadHiddenStoryRequirements)
func sortedMissionRecords(user store.UserState) []map[string]any {
ids := make([]int, 0, len(user.Missions))
for id := range user.Missions {
missions := make(map[int32]store.UserMissionState, len(user.Missions))
for id, m := range user.Missions {
missions[id] = m
}
for _, missionId := range hiddenStoryRequirements().MissionIds {
if existing, ok := missions[missionId]; ok && existing.MissionProgressStatusType >= int32(model.MissionProgressStatusTypeClear) {
continue
}
missions[missionId] = store.UserMissionState{
MissionId: missionId,
StartDatetime: user.GameStartDatetime,
MissionProgressStatusType: int32(model.MissionProgressStatusTypeClear),
ClearDatetime: user.GameStartDatetime,
LatestVersion: user.GameStartDatetime,
}
}
ids := make([]int, 0, len(missions))
for id := range missions {
ids = append(ids, int(id))
}
sort.Ints(ids)
records := make([]map[string]any, 0, len(ids))
for _, id := range ids {
row := user.Missions[int32(id)]
row := missions[int32(id)]
records = append(records, map[string]any{
"userId": user.UserId,
"missionId": row.MissionId,