mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Initial commit
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type bigHuntBossQuestRow struct {
|
||||
BigHuntBossQuestId int32 `json:"BigHuntBossQuestId"`
|
||||
BigHuntBossId int32 `json:"BigHuntBossId"`
|
||||
BigHuntQuestGroupId int32 `json:"BigHuntQuestGroupId"`
|
||||
BigHuntBossQuestScoreCoefficientId int32 `json:"BigHuntBossQuestScoreCoefficientId"`
|
||||
BigHuntScoreRewardGroupScheduleId int32 `json:"BigHuntScoreRewardGroupScheduleId"`
|
||||
DailyChallengeCount int32 `json:"DailyChallengeCount"`
|
||||
}
|
||||
|
||||
type BigHuntBossQuestRow struct {
|
||||
BigHuntBossQuestId int32
|
||||
BigHuntBossId int32
|
||||
BigHuntQuestGroupId int32
|
||||
BigHuntScoreRewardGroupScheduleId int32
|
||||
DailyChallengeCount int32
|
||||
}
|
||||
|
||||
type bigHuntQuestRow struct {
|
||||
BigHuntQuestId int32 `json:"BigHuntQuestId"`
|
||||
QuestId int32 `json:"QuestId"`
|
||||
BigHuntQuestScoreCoefficientId int32 `json:"BigHuntQuestScoreCoefficientId"`
|
||||
}
|
||||
|
||||
type BigHuntQuestRow struct {
|
||||
BigHuntQuestId int32
|
||||
QuestId int32
|
||||
BigHuntQuestScoreCoefficientId int32
|
||||
}
|
||||
|
||||
type bigHuntQuestScoreCoefficientRow struct {
|
||||
BigHuntQuestScoreCoefficientId int32 `json:"BigHuntQuestScoreCoefficientId"`
|
||||
ScoreDifficultBonusPermil int32 `json:"ScoreDifficultBonusPermil"`
|
||||
}
|
||||
|
||||
type bigHuntBossRow struct {
|
||||
BigHuntBossId int32 `json:"BigHuntBossId"`
|
||||
BigHuntBossGradeGroupId int32 `json:"BigHuntBossGradeGroupId"`
|
||||
AttributeType int32 `json:"AttributeType"`
|
||||
}
|
||||
|
||||
type BigHuntBossRow struct {
|
||||
BigHuntBossId int32
|
||||
BigHuntBossGradeGroupId int32
|
||||
AttributeType int32
|
||||
}
|
||||
|
||||
type bigHuntBossGradeGroupRow struct {
|
||||
BigHuntBossGradeGroupId int32 `json:"BigHuntBossGradeGroupId"`
|
||||
NecessaryScore int64 `json:"NecessaryScore"`
|
||||
AssetGradeIconId int32 `json:"AssetGradeIconId"`
|
||||
}
|
||||
|
||||
type GradeThreshold struct {
|
||||
NecessaryScore int64
|
||||
AssetGradeIconId int32
|
||||
}
|
||||
|
||||
type bigHuntScheduleRow struct {
|
||||
BigHuntScheduleId int32 `json:"BigHuntScheduleId"`
|
||||
ChallengeStartDatetime int64 `json:"ChallengeStartDatetime"`
|
||||
ChallengeEndDatetime int64 `json:"ChallengeEndDatetime"`
|
||||
}
|
||||
|
||||
type scoreRewardScheduleRow struct {
|
||||
BigHuntScoreRewardGroupScheduleId int32 `json:"BigHuntScoreRewardGroupScheduleId"`
|
||||
GroupIndex int32 `json:"GroupIndex"`
|
||||
BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
|
||||
StartDatetime int64 `json:"StartDatetime"`
|
||||
}
|
||||
|
||||
type ScoreRewardScheduleEntry struct {
|
||||
BigHuntScoreRewardGroupId int32
|
||||
StartDatetime int64
|
||||
}
|
||||
|
||||
type scoreRewardGroupRow struct {
|
||||
BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
|
||||
NecessaryScore int64 `json:"NecessaryScore"`
|
||||
BigHuntRewardGroupId int32 `json:"BigHuntRewardGroupId"`
|
||||
}
|
||||
|
||||
type ScoreRewardThreshold struct {
|
||||
NecessaryScore int64
|
||||
BigHuntRewardGroupId int32
|
||||
}
|
||||
|
||||
type rewardGroupRow struct {
|
||||
BigHuntRewardGroupId int32 `json:"BigHuntRewardGroupId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
PossessionType int32 `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type RewardItem struct {
|
||||
PossessionType int32
|
||||
PossessionId int32
|
||||
Count int32
|
||||
}
|
||||
|
||||
type weeklyRewardScheduleRow struct {
|
||||
BigHuntWeeklyAttributeScoreRewardGroupScheduleId int32 `json:"BigHuntWeeklyAttributeScoreRewardGroupScheduleId"`
|
||||
AttributeType int32 `json:"AttributeType"`
|
||||
GroupIndex int32 `json:"GroupIndex"`
|
||||
BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
|
||||
StartDatetime int64 `json:"StartDatetime"`
|
||||
}
|
||||
|
||||
type BigHuntWeeklyRewardKey struct {
|
||||
ScheduleId int32
|
||||
AttributeType int32
|
||||
}
|
||||
|
||||
type BigHuntCatalog struct {
|
||||
BossQuestById map[int32]BigHuntBossQuestRow
|
||||
QuestById map[int32]BigHuntQuestRow
|
||||
ScoreCoefficients map[int32]int32
|
||||
BossByBossId map[int32]BigHuntBossRow
|
||||
GradeThresholds map[int32][]GradeThreshold
|
||||
ActiveScheduleId int32
|
||||
ScoreRewardSchedules map[int32][]ScoreRewardScheduleEntry
|
||||
ScoreRewardThresholds map[int32][]ScoreRewardThreshold
|
||||
RewardItems map[int32][]RewardItem
|
||||
WeeklyRewardSchedules map[BigHuntWeeklyRewardKey][]ScoreRewardScheduleEntry
|
||||
}
|
||||
|
||||
func (c *BigHuntCatalog) ResolveActiveScoreRewardGroupId(scheduleId int32, nowMillis int64) int32 {
|
||||
entries := c.ScoreRewardSchedules[scheduleId]
|
||||
for _, e := range entries {
|
||||
if nowMillis >= e.StartDatetime {
|
||||
return e.BigHuntScoreRewardGroupId
|
||||
}
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[len(entries)-1].BigHuntScoreRewardGroupId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *BigHuntCatalog) ResolveActiveWeeklyRewardGroupId(key BigHuntWeeklyRewardKey, nowMillis int64) int32 {
|
||||
entries := c.WeeklyRewardSchedules[key]
|
||||
for _, e := range entries {
|
||||
if nowMillis >= e.StartDatetime {
|
||||
return e.BigHuntScoreRewardGroupId
|
||||
}
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[len(entries)-1].BigHuntScoreRewardGroupId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *BigHuntCatalog) ResolveGradeIconId(bossId int32, score int64) int32 {
|
||||
boss, ok := c.BossByBossId[bossId]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
thresholds := c.GradeThresholds[boss.BigHuntBossGradeGroupId]
|
||||
var iconId int32
|
||||
for _, t := range thresholds {
|
||||
if score >= t.NecessaryScore {
|
||||
iconId = t.AssetGradeIconId
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iconId
|
||||
}
|
||||
|
||||
func (c *BigHuntCatalog) CollectNewRewards(scoreRewardGroupId int32, oldMax, newMax int64) []RewardItem {
|
||||
thresholds := c.ScoreRewardThresholds[scoreRewardGroupId]
|
||||
var items []RewardItem
|
||||
for _, t := range thresholds {
|
||||
if t.NecessaryScore > oldMax && t.NecessaryScore <= newMax {
|
||||
items = append(items, c.RewardItems[t.BigHuntRewardGroupId]...)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func LoadBigHuntCatalog() *BigHuntCatalog {
|
||||
bossQuestRows, err := utils.ReadJSON[bigHuntBossQuestRow]("EntityMBigHuntBossQuestTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt boss quest table: %v", err)
|
||||
}
|
||||
bossQuestById := make(map[int32]BigHuntBossQuestRow, len(bossQuestRows))
|
||||
for _, r := range bossQuestRows {
|
||||
bossQuestById[r.BigHuntBossQuestId] = BigHuntBossQuestRow{
|
||||
BigHuntBossQuestId: r.BigHuntBossQuestId,
|
||||
BigHuntBossId: r.BigHuntBossId,
|
||||
BigHuntQuestGroupId: r.BigHuntQuestGroupId,
|
||||
BigHuntScoreRewardGroupScheduleId: r.BigHuntScoreRewardGroupScheduleId,
|
||||
DailyChallengeCount: r.DailyChallengeCount,
|
||||
}
|
||||
}
|
||||
|
||||
questRows, err := utils.ReadJSON[bigHuntQuestRow]("EntityMBigHuntQuestTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt quest table: %v", err)
|
||||
}
|
||||
questById := make(map[int32]BigHuntQuestRow, len(questRows))
|
||||
for _, r := range questRows {
|
||||
questById[r.BigHuntQuestId] = BigHuntQuestRow{
|
||||
BigHuntQuestId: r.BigHuntQuestId,
|
||||
QuestId: r.QuestId,
|
||||
BigHuntQuestScoreCoefficientId: r.BigHuntQuestScoreCoefficientId,
|
||||
}
|
||||
}
|
||||
|
||||
coeffRows, err := utils.ReadJSON[bigHuntQuestScoreCoefficientRow]("EntityMBigHuntQuestScoreCoefficientTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt quest score coefficient table: %v", err)
|
||||
}
|
||||
scoreCoefficients := make(map[int32]int32, len(coeffRows))
|
||||
for _, r := range coeffRows {
|
||||
scoreCoefficients[r.BigHuntQuestScoreCoefficientId] = r.ScoreDifficultBonusPermil
|
||||
}
|
||||
|
||||
bossRows, err := utils.ReadJSON[bigHuntBossRow]("EntityMBigHuntBossTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt boss table: %v", err)
|
||||
}
|
||||
bossByBossId := make(map[int32]BigHuntBossRow, len(bossRows))
|
||||
for _, r := range bossRows {
|
||||
bossByBossId[r.BigHuntBossId] = BigHuntBossRow{
|
||||
BigHuntBossId: r.BigHuntBossId,
|
||||
BigHuntBossGradeGroupId: r.BigHuntBossGradeGroupId,
|
||||
AttributeType: r.AttributeType,
|
||||
}
|
||||
}
|
||||
|
||||
gradeRows, err := utils.ReadJSON[bigHuntBossGradeGroupRow]("EntityMBigHuntBossGradeGroupTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt boss grade group table: %v", err)
|
||||
}
|
||||
gradeThresholds := make(map[int32][]GradeThreshold)
|
||||
for _, r := range gradeRows {
|
||||
gradeThresholds[r.BigHuntBossGradeGroupId] = append(gradeThresholds[r.BigHuntBossGradeGroupId], GradeThreshold{
|
||||
NecessaryScore: r.NecessaryScore,
|
||||
AssetGradeIconId: r.AssetGradeIconId,
|
||||
})
|
||||
}
|
||||
for k := range gradeThresholds {
|
||||
sort.Slice(gradeThresholds[k], func(i, j int) bool {
|
||||
return gradeThresholds[k][i].NecessaryScore < gradeThresholds[k][j].NecessaryScore
|
||||
})
|
||||
}
|
||||
|
||||
scheduleRows, err := utils.ReadJSON[bigHuntScheduleRow]("EntityMBigHuntScheduleTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt schedule table: %v", err)
|
||||
}
|
||||
nowMillis := time.Now().UnixMilli()
|
||||
var activeScheduleId int32
|
||||
var latestEndDatetime int64
|
||||
for _, r := range scheduleRows {
|
||||
if nowMillis >= r.ChallengeStartDatetime && nowMillis <= r.ChallengeEndDatetime {
|
||||
activeScheduleId = r.BigHuntScheduleId
|
||||
break
|
||||
}
|
||||
if r.ChallengeEndDatetime > latestEndDatetime {
|
||||
latestEndDatetime = r.ChallengeEndDatetime
|
||||
activeScheduleId = r.BigHuntScheduleId
|
||||
}
|
||||
}
|
||||
|
||||
rewardSchedRows, err := utils.ReadJSON[scoreRewardScheduleRow]("EntityMBigHuntScoreRewardGroupScheduleTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt score reward group schedule table: %v", err)
|
||||
}
|
||||
scoreRewardSchedules := make(map[int32][]ScoreRewardScheduleEntry)
|
||||
for _, r := range rewardSchedRows {
|
||||
scoreRewardSchedules[r.BigHuntScoreRewardGroupScheduleId] = append(
|
||||
scoreRewardSchedules[r.BigHuntScoreRewardGroupScheduleId],
|
||||
ScoreRewardScheduleEntry{
|
||||
BigHuntScoreRewardGroupId: r.BigHuntScoreRewardGroupId,
|
||||
StartDatetime: r.StartDatetime,
|
||||
},
|
||||
)
|
||||
}
|
||||
for k := range scoreRewardSchedules {
|
||||
sort.Slice(scoreRewardSchedules[k], func(i, j int) bool {
|
||||
return scoreRewardSchedules[k][i].StartDatetime > scoreRewardSchedules[k][j].StartDatetime
|
||||
})
|
||||
}
|
||||
|
||||
rewardGroupRows, err := utils.ReadJSON[scoreRewardGroupRow]("EntityMBigHuntScoreRewardGroupTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt score reward group table: %v", err)
|
||||
}
|
||||
scoreRewardThresholds := make(map[int32][]ScoreRewardThreshold)
|
||||
for _, r := range rewardGroupRows {
|
||||
scoreRewardThresholds[r.BigHuntScoreRewardGroupId] = append(
|
||||
scoreRewardThresholds[r.BigHuntScoreRewardGroupId],
|
||||
ScoreRewardThreshold{
|
||||
NecessaryScore: r.NecessaryScore,
|
||||
BigHuntRewardGroupId: r.BigHuntRewardGroupId,
|
||||
},
|
||||
)
|
||||
}
|
||||
for k := range scoreRewardThresholds {
|
||||
sort.Slice(scoreRewardThresholds[k], func(i, j int) bool {
|
||||
return scoreRewardThresholds[k][i].NecessaryScore < scoreRewardThresholds[k][j].NecessaryScore
|
||||
})
|
||||
}
|
||||
|
||||
rewardItemRows, err := utils.ReadJSON[rewardGroupRow]("EntityMBigHuntRewardGroupTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt reward group table: %v", err)
|
||||
}
|
||||
rewardItems := make(map[int32][]RewardItem)
|
||||
for _, r := range rewardItemRows {
|
||||
rewardItems[r.BigHuntRewardGroupId] = append(rewardItems[r.BigHuntRewardGroupId], RewardItem{
|
||||
PossessionType: r.PossessionType,
|
||||
PossessionId: r.PossessionId,
|
||||
Count: r.Count,
|
||||
})
|
||||
}
|
||||
|
||||
weeklySchedRows, err := utils.ReadJSON[weeklyRewardScheduleRow]("EntityMBigHuntWeeklyAttributeScoreRewardGroupScheduleTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load big hunt weekly attribute score reward group schedule table: %v", err)
|
||||
}
|
||||
weeklyRewardSchedules := make(map[BigHuntWeeklyRewardKey][]ScoreRewardScheduleEntry)
|
||||
for _, r := range weeklySchedRows {
|
||||
key := BigHuntWeeklyRewardKey{
|
||||
ScheduleId: r.BigHuntWeeklyAttributeScoreRewardGroupScheduleId,
|
||||
AttributeType: r.AttributeType,
|
||||
}
|
||||
weeklyRewardSchedules[key] = append(weeklyRewardSchedules[key], ScoreRewardScheduleEntry{
|
||||
BigHuntScoreRewardGroupId: r.BigHuntScoreRewardGroupId,
|
||||
StartDatetime: r.StartDatetime,
|
||||
})
|
||||
}
|
||||
for k := range weeklyRewardSchedules {
|
||||
sort.Slice(weeklyRewardSchedules[k], func(i, j int) bool {
|
||||
return weeklyRewardSchedules[k][i].StartDatetime > weeklyRewardSchedules[k][j].StartDatetime
|
||||
})
|
||||
}
|
||||
|
||||
log.Printf("big hunt catalog loaded: %d boss quests, %d quests, %d bosses, %d score coefficients, %d reward groups, schedule=%d",
|
||||
len(bossQuestById), len(questById), len(bossByBossId), len(scoreCoefficients), len(rewardItems), activeScheduleId)
|
||||
|
||||
return &BigHuntCatalog{
|
||||
BossQuestById: bossQuestById,
|
||||
QuestById: questById,
|
||||
ScoreCoefficients: scoreCoefficients,
|
||||
BossByBossId: bossByBossId,
|
||||
GradeThresholds: gradeThresholds,
|
||||
ActiveScheduleId: activeScheduleId,
|
||||
ScoreRewardSchedules: scoreRewardSchedules,
|
||||
ScoreRewardThresholds: scoreRewardThresholds,
|
||||
RewardItems: rewardItems,
|
||||
WeeklyRewardSchedules: weeklyRewardSchedules,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"log"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type cageOrnament struct {
|
||||
CageOrnamentId int32 `json:"CageOrnamentId"`
|
||||
CageOrnamentRewardId int32 `json:"CageOrnamentRewardId"`
|
||||
}
|
||||
|
||||
type cageOrnamentRewardRow struct {
|
||||
CageOrnamentRewardId int32 `json:"CageOrnamentRewardId"`
|
||||
PossessionType int32 `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type CageOrnamentReward struct {
|
||||
PossessionType int32
|
||||
PossessionId int32
|
||||
Count int32
|
||||
}
|
||||
|
||||
type CageOrnamentCatalog struct {
|
||||
ornamentToRewardId map[int32]int32
|
||||
rewards map[int32]CageOrnamentReward
|
||||
}
|
||||
|
||||
func (c *CageOrnamentCatalog) LookupReward(cageOrnamentId int32) (CageOrnamentReward, bool) {
|
||||
rewardId, ok := c.ornamentToRewardId[cageOrnamentId]
|
||||
if !ok || rewardId == 0 {
|
||||
return CageOrnamentReward{}, false
|
||||
}
|
||||
entry, ok := c.rewards[rewardId]
|
||||
return entry, ok
|
||||
}
|
||||
|
||||
func LoadCageOrnamentCatalog() *CageOrnamentCatalog {
|
||||
ornaments, err := utils.ReadJSON[cageOrnament]("EntityMCageOrnamentTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load cage ornament table: %v", err)
|
||||
}
|
||||
rewards, err := utils.ReadJSON[cageOrnamentRewardRow]("EntityMCageOrnamentRewardTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load cage ornament reward table: %v", err)
|
||||
}
|
||||
|
||||
cat := &CageOrnamentCatalog{
|
||||
ornamentToRewardId: make(map[int32]int32, len(ornaments)),
|
||||
rewards: make(map[int32]CageOrnamentReward, len(rewards)),
|
||||
}
|
||||
for _, o := range ornaments {
|
||||
cat.ornamentToRewardId[o.CageOrnamentId] = o.CageOrnamentRewardId
|
||||
}
|
||||
for _, r := range rewards {
|
||||
cat.rewards[r.CageOrnamentRewardId] = CageOrnamentReward{
|
||||
PossessionType: r.PossessionType,
|
||||
PossessionId: r.PossessionId,
|
||||
Count: r.Count,
|
||||
}
|
||||
}
|
||||
return cat
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type CharacterRebirthRow struct {
|
||||
CharacterId int32 `json:"CharacterId"`
|
||||
CharacterRebirthStepGroupId int32 `json:"CharacterRebirthStepGroupId"`
|
||||
}
|
||||
|
||||
type CharacterRebirthStepRow struct {
|
||||
CharacterRebirthStepGroupId int32 `json:"CharacterRebirthStepGroupId"`
|
||||
BeforeRebirthCount int32 `json:"BeforeRebirthCount"`
|
||||
CostumeLevelLimitUp int32 `json:"CostumeLevelLimitUp"`
|
||||
CharacterRebirthMaterialGroupId int32 `json:"CharacterRebirthMaterialGroupId"`
|
||||
}
|
||||
|
||||
type CharacterRebirthMaterialRow struct {
|
||||
CharacterRebirthMaterialGroupId int32 `json:"CharacterRebirthMaterialGroupId"`
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type StepKey struct {
|
||||
GroupId int32
|
||||
BeforeRebirthCount int32
|
||||
}
|
||||
|
||||
type CharacterRebirthCatalog struct {
|
||||
StepGroupByCharacterId map[int32]int32
|
||||
StepByGroupAndCount map[StepKey]CharacterRebirthStepRow
|
||||
MaterialsByGroupId map[int32][]CharacterRebirthMaterialRow
|
||||
}
|
||||
|
||||
func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) {
|
||||
rebirthRows, err := utils.ReadJSON[CharacterRebirthRow]("EntityMCharacterRebirthTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character rebirth table: %w", err)
|
||||
}
|
||||
|
||||
stepRows, err := utils.ReadJSON[CharacterRebirthStepRow]("EntityMCharacterRebirthStepGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character rebirth step group table: %w", err)
|
||||
}
|
||||
|
||||
materialRows, err := utils.ReadJSON[CharacterRebirthMaterialRow]("EntityMCharacterRebirthMaterialGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character rebirth material group table: %w", err)
|
||||
}
|
||||
|
||||
stepGroupByCharacterId := make(map[int32]int32, len(rebirthRows))
|
||||
for _, r := range rebirthRows {
|
||||
stepGroupByCharacterId[r.CharacterId] = r.CharacterRebirthStepGroupId
|
||||
}
|
||||
|
||||
stepByGroupAndCount := make(map[StepKey]CharacterRebirthStepRow, len(stepRows))
|
||||
for _, s := range stepRows {
|
||||
stepByGroupAndCount[StepKey{GroupId: s.CharacterRebirthStepGroupId, BeforeRebirthCount: s.BeforeRebirthCount}] = s
|
||||
}
|
||||
|
||||
materialsByGroupId := make(map[int32][]CharacterRebirthMaterialRow)
|
||||
for _, m := range materialRows {
|
||||
materialsByGroupId[m.CharacterRebirthMaterialGroupId] = append(materialsByGroupId[m.CharacterRebirthMaterialGroupId], m)
|
||||
}
|
||||
|
||||
return &CharacterRebirthCatalog{
|
||||
StepGroupByCharacterId: stepGroupByCharacterId,
|
||||
StepByGroupAndCount: stepByGroupAndCount,
|
||||
MaterialsByGroupId: materialsByGroupId,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"lunar-tear/server/internal/store"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type CharacterBoardPanelRow struct {
|
||||
CharacterBoardPanelId int32 `json:"CharacterBoardPanelId"`
|
||||
CharacterBoardId int32 `json:"CharacterBoardId"`
|
||||
CharacterBoardPanelUnlockConditionGroupId int32 `json:"CharacterBoardPanelUnlockConditionGroupId"`
|
||||
CharacterBoardPanelReleasePossessionGroupId int32 `json:"CharacterBoardPanelReleasePossessionGroupId"`
|
||||
CharacterBoardPanelReleaseRewardGroupId int32 `json:"CharacterBoardPanelReleaseRewardGroupId"`
|
||||
CharacterBoardPanelReleaseEffectGroupId int32 `json:"CharacterBoardPanelReleaseEffectGroupId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
ParentCharacterBoardPanelId int32 `json:"ParentCharacterBoardPanelId"`
|
||||
PlaceIndex int32 `json:"PlaceIndex"`
|
||||
}
|
||||
|
||||
type CharacterBoardReleasePossessionRow struct {
|
||||
CharacterBoardPanelReleasePossessionGroupId int32 `json:"CharacterBoardPanelReleasePossessionGroupId"`
|
||||
PossessionType int32 `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
}
|
||||
|
||||
type CharacterBoardReleaseEffectRow struct {
|
||||
CharacterBoardPanelReleaseEffectGroupId int32 `json:"CharacterBoardPanelReleaseEffectGroupId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
CharacterBoardEffectType int32 `json:"CharacterBoardEffectType"`
|
||||
CharacterBoardEffectId int32 `json:"CharacterBoardEffectId"`
|
||||
EffectValue int32 `json:"EffectValue"`
|
||||
}
|
||||
|
||||
type CharacterBoardRow struct {
|
||||
CharacterBoardId int32 `json:"CharacterBoardId"`
|
||||
CharacterBoardGroupId int32 `json:"CharacterBoardGroupId"`
|
||||
CharacterBoardUnlockConditionGroupId int32 `json:"CharacterBoardUnlockConditionGroupId"`
|
||||
ReleaseRank int32 `json:"ReleaseRank"`
|
||||
}
|
||||
|
||||
type CharacterBoardStatusUpRow struct {
|
||||
CharacterBoardStatusUpId int32 `json:"CharacterBoardStatusUpId"`
|
||||
CharacterBoardStatusUpType int32 `json:"CharacterBoardStatusUpType"`
|
||||
CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
|
||||
}
|
||||
|
||||
type CharacterBoardAbilityRow struct {
|
||||
CharacterBoardAbilityId int32 `json:"CharacterBoardAbilityId"`
|
||||
CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
|
||||
AbilityId int32 `json:"AbilityId"`
|
||||
}
|
||||
|
||||
type CharacterBoardAbilityMaxLevelRow struct {
|
||||
CharacterId int32 `json:"CharacterId"`
|
||||
AbilityId int32 `json:"AbilityId"`
|
||||
MaxLevel int32 `json:"MaxLevel"`
|
||||
}
|
||||
|
||||
type CharacterBoardEffectTargetRow struct {
|
||||
CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
|
||||
GroupIndex int32 `json:"GroupIndex"`
|
||||
CharacterBoardEffectTargetType int32 `json:"CharacterBoardEffectTargetType"`
|
||||
TargetValue int32 `json:"TargetValue"`
|
||||
}
|
||||
|
||||
type CharacterBoardAssignmentRow struct {
|
||||
CharacterId int32 `json:"CharacterId"`
|
||||
CharacterBoardCategoryId int32 `json:"CharacterBoardCategoryId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
CharacterBoardAssignmentType int32 `json:"CharacterBoardAssignmentType"`
|
||||
}
|
||||
|
||||
type CharacterBoardGroupRow struct {
|
||||
CharacterBoardGroupId int32 `json:"CharacterBoardGroupId"`
|
||||
CharacterBoardCategoryId int32 `json:"CharacterBoardCategoryId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
CharacterBoardGroupType int32 `json:"CharacterBoardGroupType"`
|
||||
TextAssetId int32 `json:"TextAssetId"`
|
||||
}
|
||||
|
||||
type CharacterBoardCatalog struct {
|
||||
PanelById map[int32]CharacterBoardPanelRow
|
||||
PanelsByBoardId map[int32][]CharacterBoardPanelRow
|
||||
ReleaseCostsByGroupId map[int32][]CharacterBoardReleasePossessionRow
|
||||
ReleaseEffectsByGroupId map[int32][]CharacterBoardReleaseEffectRow
|
||||
StatusUpById map[int32]CharacterBoardStatusUpRow
|
||||
AbilityById map[int32]CharacterBoardAbilityRow
|
||||
AbilityMaxLevel map[store.CharacterBoardAbilityKey]int32
|
||||
EffectTargetsByGroupId map[int32][]CharacterBoardEffectTargetRow
|
||||
BoardById map[int32]CharacterBoardRow
|
||||
}
|
||||
|
||||
func LoadCharacterBoardCatalog() (*CharacterBoardCatalog, error) {
|
||||
panels, err := utils.ReadJSON[CharacterBoardPanelRow]("EntityMCharacterBoardPanelTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board panel table: %w", err)
|
||||
}
|
||||
|
||||
costs, err := utils.ReadJSON[CharacterBoardReleasePossessionRow]("EntityMCharacterBoardPanelReleasePossessionGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board release possession table: %w", err)
|
||||
}
|
||||
|
||||
effects, err := utils.ReadJSON[CharacterBoardReleaseEffectRow]("EntityMCharacterBoardPanelReleaseEffectGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board release effect table: %w", err)
|
||||
}
|
||||
|
||||
boards, err := utils.ReadJSON[CharacterBoardRow]("EntityMCharacterBoardTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board table: %w", err)
|
||||
}
|
||||
|
||||
statusUps, err := utils.ReadJSON[CharacterBoardStatusUpRow]("EntityMCharacterBoardStatusUpTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board status up table: %w", err)
|
||||
}
|
||||
|
||||
abilities, err := utils.ReadJSON[CharacterBoardAbilityRow]("EntityMCharacterBoardAbilityTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board ability table: %w", err)
|
||||
}
|
||||
|
||||
abilityMaxLevels, err := utils.ReadJSON[CharacterBoardAbilityMaxLevelRow]("EntityMCharacterBoardAbilityMaxLevelTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board ability max level table: %w", err)
|
||||
}
|
||||
|
||||
targets, err := utils.ReadJSON[CharacterBoardEffectTargetRow]("EntityMCharacterBoardEffectTargetGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board effect target table: %w", err)
|
||||
}
|
||||
|
||||
catalog := &CharacterBoardCatalog{
|
||||
PanelById: make(map[int32]CharacterBoardPanelRow, len(panels)),
|
||||
PanelsByBoardId: make(map[int32][]CharacterBoardPanelRow),
|
||||
ReleaseCostsByGroupId: make(map[int32][]CharacterBoardReleasePossessionRow),
|
||||
ReleaseEffectsByGroupId: make(map[int32][]CharacterBoardReleaseEffectRow),
|
||||
StatusUpById: make(map[int32]CharacterBoardStatusUpRow, len(statusUps)),
|
||||
AbilityById: make(map[int32]CharacterBoardAbilityRow, len(abilities)),
|
||||
AbilityMaxLevel: make(map[store.CharacterBoardAbilityKey]int32, len(abilityMaxLevels)),
|
||||
EffectTargetsByGroupId: make(map[int32][]CharacterBoardEffectTargetRow),
|
||||
BoardById: make(map[int32]CharacterBoardRow, len(boards)),
|
||||
}
|
||||
|
||||
for _, p := range panels {
|
||||
catalog.PanelById[p.CharacterBoardPanelId] = p
|
||||
catalog.PanelsByBoardId[p.CharacterBoardId] = append(catalog.PanelsByBoardId[p.CharacterBoardId], p)
|
||||
}
|
||||
for _, c := range costs {
|
||||
catalog.ReleaseCostsByGroupId[c.CharacterBoardPanelReleasePossessionGroupId] = append(
|
||||
catalog.ReleaseCostsByGroupId[c.CharacterBoardPanelReleasePossessionGroupId], c)
|
||||
}
|
||||
for _, e := range effects {
|
||||
catalog.ReleaseEffectsByGroupId[e.CharacterBoardPanelReleaseEffectGroupId] = append(
|
||||
catalog.ReleaseEffectsByGroupId[e.CharacterBoardPanelReleaseEffectGroupId], e)
|
||||
}
|
||||
for _, b := range boards {
|
||||
catalog.BoardById[b.CharacterBoardId] = b
|
||||
}
|
||||
for _, s := range statusUps {
|
||||
catalog.StatusUpById[s.CharacterBoardStatusUpId] = s
|
||||
}
|
||||
for _, a := range abilities {
|
||||
catalog.AbilityById[a.CharacterBoardAbilityId] = a
|
||||
}
|
||||
for _, m := range abilityMaxLevels {
|
||||
catalog.AbilityMaxLevel[store.CharacterBoardAbilityKey{
|
||||
CharacterId: m.CharacterId,
|
||||
AbilityId: m.AbilityId,
|
||||
}] = m.MaxLevel
|
||||
}
|
||||
for _, t := range targets {
|
||||
catalog.EffectTargetsByGroupId[t.CharacterBoardEffectTargetGroupId] = append(
|
||||
catalog.EffectTargetsByGroupId[t.CharacterBoardEffectTargetGroupId], t)
|
||||
}
|
||||
|
||||
return catalog, nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/store"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type characterViewerField struct {
|
||||
CharacterViewerFieldId int32 `json:"CharacterViewerFieldId"`
|
||||
ReleaseEvaluateConditionId int32 `json:"ReleaseEvaluateConditionId"`
|
||||
}
|
||||
|
||||
type characterViewerFieldEntry struct {
|
||||
FieldId int32
|
||||
RequiredQuestId int32
|
||||
}
|
||||
|
||||
type CharacterViewerCatalog struct {
|
||||
fields []characterViewerFieldEntry
|
||||
}
|
||||
|
||||
func (c *CharacterViewerCatalog) ReleasedFieldIds(user store.UserState) []int32 {
|
||||
var released []int32
|
||||
for _, f := range c.fields {
|
||||
if f.RequiredQuestId == 0 {
|
||||
released = append(released, f.FieldId)
|
||||
continue
|
||||
}
|
||||
q, ok := user.Quests[f.RequiredQuestId]
|
||||
if ok && q.QuestStateType == model.UserQuestStateTypeCleared {
|
||||
released = append(released, f.FieldId)
|
||||
}
|
||||
}
|
||||
return released
|
||||
}
|
||||
|
||||
func LoadCharacterViewerCatalog(resolver *ConditionResolver) *CharacterViewerCatalog {
|
||||
fields, err := utils.ReadJSON[characterViewerField]("EntityMCharacterViewerFieldTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load character viewer field table: %v", err)
|
||||
}
|
||||
|
||||
cat := &CharacterViewerCatalog{}
|
||||
for _, f := range fields {
|
||||
entry := characterViewerFieldEntry{FieldId: f.CharacterViewerFieldId}
|
||||
if qid, ok := resolver.RequiredQuestId(f.ReleaseEvaluateConditionId); ok {
|
||||
entry.RequiredQuestId = qid
|
||||
}
|
||||
cat.fields = append(cat.fields, entry)
|
||||
}
|
||||
|
||||
sort.Slice(cat.fields, func(i, j int) bool {
|
||||
return cat.fields[i].FieldId < cat.fields[j].FieldId
|
||||
})
|
||||
|
||||
log.Printf("character viewer catalog loaded: %d fields", len(cat.fields))
|
||||
return cat
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type companionRow struct {
|
||||
CompanionId int32 `json:"CompanionId"`
|
||||
CompanionCategoryType int32 `json:"CompanionCategoryType"`
|
||||
}
|
||||
|
||||
type companionCategoryRow struct {
|
||||
CompanionCategoryType int32 `json:"CompanionCategoryType"`
|
||||
EnhancementCostNumericalFunctionId int32 `json:"EnhancementCostNumericalFunctionId"`
|
||||
}
|
||||
|
||||
type companionEnhancementMaterialRow struct {
|
||||
CompanionCategoryType int32 `json:"CompanionCategoryType"`
|
||||
Level int32 `json:"Level"`
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type CompanionLevelKey struct {
|
||||
CategoryType int32
|
||||
Level int32
|
||||
}
|
||||
|
||||
type CompanionMaterialCost struct {
|
||||
MaterialId int32
|
||||
Count int32
|
||||
}
|
||||
|
||||
type CompanionCatalog struct {
|
||||
CompanionById map[int32]companionRow
|
||||
GoldCostByCategory map[int32]NumericalFunc
|
||||
MaterialsByKey map[CompanionLevelKey]CompanionMaterialCost
|
||||
}
|
||||
|
||||
func LoadCompanionCatalog() (*CompanionCatalog, error) {
|
||||
companions, err := utils.ReadJSON[companionRow]("EntityMCompanionTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load companion table: %w", err)
|
||||
}
|
||||
|
||||
categories, err := utils.ReadJSON[companionCategoryRow]("EntityMCompanionCategoryTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load companion category table: %w", err)
|
||||
}
|
||||
|
||||
materials, err := utils.ReadJSON[companionEnhancementMaterialRow]("EntityMCompanionEnhancementMaterialTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load companion enhancement material table: %w", err)
|
||||
}
|
||||
|
||||
funcResolver, err := LoadFunctionResolver()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||
}
|
||||
|
||||
companionById := make(map[int32]companionRow, len(companions))
|
||||
for _, c := range companions {
|
||||
companionById[c.CompanionId] = c
|
||||
}
|
||||
|
||||
goldCostByCategory := make(map[int32]NumericalFunc, len(categories))
|
||||
for _, cat := range categories {
|
||||
if f, ok := funcResolver.Resolve(cat.EnhancementCostNumericalFunctionId); ok {
|
||||
goldCostByCategory[cat.CompanionCategoryType] = f
|
||||
}
|
||||
}
|
||||
|
||||
materialsByKey := make(map[CompanionLevelKey]CompanionMaterialCost, len(materials))
|
||||
for _, m := range materials {
|
||||
key := CompanionLevelKey{CategoryType: m.CompanionCategoryType, Level: m.Level}
|
||||
materialsByKey[key] = CompanionMaterialCost{MaterialId: m.MaterialId, Count: m.Count}
|
||||
}
|
||||
|
||||
return &CompanionCatalog{
|
||||
CompanionById: companionById,
|
||||
GoldCostByCategory: goldCostByCategory,
|
||||
MaterialsByKey: materialsByKey,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type evaluateCondition struct {
|
||||
EvaluateConditionId int32 `json:"EvaluateConditionId"`
|
||||
EvaluateConditionFunctionType model.EvaluateConditionFunctionType `json:"EvaluateConditionFunctionType"`
|
||||
EvaluateConditionEvaluateType model.EvaluateConditionEvaluateType `json:"EvaluateConditionEvaluateType"`
|
||||
EvaluateConditionValueGroupId int32 `json:"EvaluateConditionValueGroupId"`
|
||||
}
|
||||
|
||||
type evaluateConditionValueGroup struct {
|
||||
EvaluateConditionValueGroupId int32 `json:"EvaluateConditionValueGroupId"`
|
||||
GroupIndex int32 `json:"GroupIndex"`
|
||||
Value int64 `json:"Value"`
|
||||
}
|
||||
|
||||
const defaultGroupIndex = 1
|
||||
|
||||
type ConditionResolver struct {
|
||||
requiredQuestByCondId map[int32]int32
|
||||
}
|
||||
|
||||
func LoadConditionResolver() (*ConditionResolver, error) {
|
||||
conditions, err := utils.ReadJSON[evaluateCondition]("EntityMEvaluateConditionTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load evaluate condition table: %w", err)
|
||||
}
|
||||
valueGroups, err := utils.ReadJSON[evaluateConditionValueGroup]("EntityMEvaluateConditionValueGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load evaluate condition value group table: %w", err)
|
||||
}
|
||||
|
||||
condById := make(map[int32]evaluateCondition, len(conditions))
|
||||
for _, c := range conditions {
|
||||
condById[c.EvaluateConditionId] = c
|
||||
}
|
||||
|
||||
type vgKey struct {
|
||||
GroupId int32
|
||||
GroupIndex int32
|
||||
}
|
||||
vgByKey := make(map[vgKey]int64, len(valueGroups))
|
||||
for _, vg := range valueGroups {
|
||||
vgByKey[vgKey{vg.EvaluateConditionValueGroupId, vg.GroupIndex}] = vg.Value
|
||||
}
|
||||
|
||||
resolved := make(map[int32]int32)
|
||||
for _, c := range conditions {
|
||||
if c.EvaluateConditionFunctionType == model.EvaluateConditionFunctionTypeQuestClear &&
|
||||
c.EvaluateConditionEvaluateType == model.EvaluateConditionEvaluateTypeIdContain {
|
||||
if questId, ok := vgByKey[vgKey{c.EvaluateConditionValueGroupId, defaultGroupIndex}]; ok {
|
||||
resolved[c.EvaluateConditionId] = int32(questId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &ConditionResolver{requiredQuestByCondId: resolved}, nil
|
||||
}
|
||||
|
||||
func (r *ConditionResolver) RequiredQuestId(conditionId int32) (int32, bool) {
|
||||
qid, ok := r.requiredQuestByCondId[conditionId]
|
||||
return qid, ok
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type configRow struct {
|
||||
ConfigKey string `json:"ConfigKey"`
|
||||
Value string `json:"Value"`
|
||||
}
|
||||
|
||||
type GameConfig struct {
|
||||
ConsumableItemIdForGold int32
|
||||
ConsumableItemIdForMedal int32
|
||||
ConsumableItemIdForRareMedal int32
|
||||
ConsumableItemIdForArenaCoin int32
|
||||
ConsumableItemIdForExploreTicket int32
|
||||
ConsumableItemIdForMomPoint int32
|
||||
ConsumableItemIdForPremiumGachaTicket int32
|
||||
ConsumableItemIdForQuestSkipTicket int32
|
||||
|
||||
CharacterRebirthAvailableCount int32
|
||||
CharacterRebirthConsumeGold int32
|
||||
|
||||
CostumeAwakenAvailableCount int32
|
||||
CostumeLimitBreakAvailableCount int32
|
||||
|
||||
MaterialSameWeaponExpCoefficientPermil int32
|
||||
|
||||
UserStaminaRecoverySecond int32
|
||||
RewardGachaDailyMaxCount int32
|
||||
QuestSkipMaxCountAtOnce int32
|
||||
|
||||
WeaponLimitBreakAvailableCount int32
|
||||
}
|
||||
|
||||
func LoadGameConfig() (*GameConfig, error) {
|
||||
rows, err := utils.ReadJSON[configRow]("EntityMConfigTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load config table: %w", err)
|
||||
}
|
||||
|
||||
kv := make(map[string]string, len(rows))
|
||||
for _, r := range rows {
|
||||
kv[r.ConfigKey] = r.Value
|
||||
}
|
||||
|
||||
cfg := &GameConfig{}
|
||||
|
||||
cfg.ConsumableItemIdForGold = parseInt32(kv, "CONSUMABLE_ITEM_ID_FOR_GOLD")
|
||||
cfg.ConsumableItemIdForMedal = parseInt32(kv, "CONSUMABLE_ITEM_ID_FOR_MEDAL")
|
||||
cfg.ConsumableItemIdForRareMedal = parseInt32(kv, "CONSUMABLE_ITEM_ID_FOR_RARE_MEDAL")
|
||||
cfg.ConsumableItemIdForArenaCoin = parseInt32(kv, "CONSUMABLE_ITEM_ID_FOR_ARENA_COIN")
|
||||
cfg.ConsumableItemIdForExploreTicket = parseInt32(kv, "CONSUMABLE_ITEM_ID_FOR_EXPLORE_TICKET")
|
||||
cfg.ConsumableItemIdForMomPoint = parseInt32(kv, "CONSUMABLE_ITEM_ID_FOR_MOM_POINT")
|
||||
cfg.ConsumableItemIdForPremiumGachaTicket = parseInt32(kv, "CONSUMABLE_ITEM_ID_FOR_PREMIUM_GACHA_TICKET")
|
||||
cfg.ConsumableItemIdForQuestSkipTicket = parseInt32(kv, "CONSUMABLE_ITEM_ID_FOR_QUEST_SKIP_TICKET")
|
||||
|
||||
cfg.CharacterRebirthAvailableCount = parseInt32(kv, "CHARACTER_REBIRTH_AVAILABLE_COUNT")
|
||||
cfg.CharacterRebirthConsumeGold = parseInt32(kv, "CHARACTER_REBIRTH_CONSUME_GOLD")
|
||||
|
||||
cfg.CostumeAwakenAvailableCount = parseInt32(kv, "COSTUME_AWAKEN_AVAILABLE_COUNT")
|
||||
cfg.CostumeLimitBreakAvailableCount = parseInt32(kv, "COSTUME_LIMIT_BREAK_AVAILABLE_COUNT")
|
||||
|
||||
cfg.MaterialSameWeaponExpCoefficientPermil = parseInt32(kv, "MATERIAL_SAME_WEAPON_EXP_COEFFICIENT_PERMIL")
|
||||
|
||||
cfg.UserStaminaRecoverySecond = parseInt32(kv, "USER_STAMINA_RECOVERY_SECOND")
|
||||
cfg.RewardGachaDailyMaxCount = parseInt32(kv, "REWARD_GACHA_DAILY_MAX_COUNT")
|
||||
cfg.QuestSkipMaxCountAtOnce = parseInt32(kv, "QUEST_SKIP_MAX_COUNT_AT_ONCE")
|
||||
|
||||
cfg.WeaponLimitBreakAvailableCount = parseInt32(kv, "WEAPON_LIMIT_BREAK_AVAILABLE_COUNT")
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func parseInt32(kv map[string]string, key string) int32 {
|
||||
s, ok := kv[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
v, err := strconv.ParseInt(s, 10, 32)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return int32(v)
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type CostumeMasterRow struct {
|
||||
CostumeId int32 `json:"CostumeId"`
|
||||
CharacterId int32 `json:"CharacterId"`
|
||||
SkillfulWeaponType int32 `json:"SkillfulWeaponType"`
|
||||
RarityType int32 `json:"RarityType"`
|
||||
CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
|
||||
CostumeActiveSkillGroupId int32 `json:"CostumeActiveSkillGroupId"`
|
||||
}
|
||||
|
||||
type costumeRarityRow struct {
|
||||
RarityType int32 `json:"RarityType"`
|
||||
CostumeLimitBreakMaterialRarityGroupId int32 `json:"CostumeLimitBreakMaterialRarityGroupId"`
|
||||
RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
|
||||
EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
|
||||
LimitBreakCostNumericalFunctionId int32 `json:"LimitBreakCostNumericalFunctionId"`
|
||||
MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
|
||||
ActiveSkillMaxLevelNumericalFunctionId int32 `json:"ActiveSkillMaxLevelNumericalFunctionId"`
|
||||
ActiveSkillEnhancementCostNumericalFunctionId int32 `json:"ActiveSkillEnhancementCostNumericalFunctionId"`
|
||||
}
|
||||
|
||||
type CostumeAwakenRow struct {
|
||||
CostumeId int32 `json:"CostumeId"`
|
||||
CostumeAwakenEffectGroupId int32 `json:"CostumeAwakenEffectGroupId"`
|
||||
CostumeAwakenStepMaterialGroupId int32 `json:"CostumeAwakenStepMaterialGroupId"`
|
||||
CostumeAwakenPriceGroupId int32 `json:"CostumeAwakenPriceGroupId"`
|
||||
}
|
||||
|
||||
type costumeAwakenPriceRow struct {
|
||||
CostumeAwakenPriceGroupId int32 `json:"CostumeAwakenPriceGroupId"`
|
||||
AwakenStepLowerLimit int32 `json:"AwakenStepLowerLimit"`
|
||||
Gold int32 `json:"Gold"`
|
||||
}
|
||||
|
||||
type CostumeAwakenEffectRow struct {
|
||||
CostumeAwakenEffectGroupId int32 `json:"CostumeAwakenEffectGroupId"`
|
||||
AwakenStep int32 `json:"AwakenStep"`
|
||||
CostumeAwakenEffectType int32 `json:"CostumeAwakenEffectType"`
|
||||
CostumeAwakenEffectId int32 `json:"CostumeAwakenEffectId"`
|
||||
}
|
||||
|
||||
type CostumeAwakenStatusUpRow struct {
|
||||
CostumeAwakenStatusUpGroupId int32 `json:"CostumeAwakenStatusUpGroupId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
StatusKindType int32 `json:"StatusKindType"`
|
||||
StatusCalculationType int32 `json:"StatusCalculationType"`
|
||||
EffectValue int32 `json:"EffectValue"`
|
||||
}
|
||||
|
||||
type CostumeAwakenItemAcquireRow struct {
|
||||
CostumeAwakenItemAcquireId int32 `json:"CostumeAwakenItemAcquireId"`
|
||||
PossessionType int32 `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type CostumeActiveSkillGroupRow struct {
|
||||
CostumeActiveSkillGroupId int32 `json:"CostumeActiveSkillGroupId"`
|
||||
CostumeLimitBreakCountLowerLimit int32 `json:"CostumeLimitBreakCountLowerLimit"`
|
||||
CostumeActiveSkillId int32 `json:"CostumeActiveSkillId"`
|
||||
CostumeActiveSkillEnhancementMaterialId int32 `json:"CostumeActiveSkillEnhancementMaterialId"`
|
||||
}
|
||||
|
||||
type CostumeActiveSkillEnhanceMaterialRow struct {
|
||||
CostumeActiveSkillEnhancementMaterialId int32 `json:"CostumeActiveSkillEnhancementMaterialId"`
|
||||
SkillLevel int32 `json:"SkillLevel"`
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
Count int32 `json:"Count"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
}
|
||||
|
||||
type CostumeCatalog struct {
|
||||
Costumes map[int32]CostumeMasterRow
|
||||
Materials map[int32]MaterialRow
|
||||
ExpByRarity map[int32][]int32
|
||||
EnhanceCostByRarity map[int32]NumericalFunc
|
||||
MaxLevelByRarity map[int32]NumericalFunc
|
||||
LimitBreakCostByRarity map[int32]NumericalFunc
|
||||
|
||||
AwakenByCostumeId map[int32]CostumeAwakenRow
|
||||
AwakenPriceByGroup map[int32]int32
|
||||
AwakenEffectsByGroupAndStep map[int32]map[int32]CostumeAwakenEffectRow
|
||||
AwakenStatusUpByGroup map[int32][]CostumeAwakenStatusUpRow
|
||||
AwakenItemAcquireById map[int32]CostumeAwakenItemAcquireRow
|
||||
|
||||
ActiveSkillGroupsByGroupId map[int32][]CostumeActiveSkillGroupRow // sorted by CostumeLimitBreakCountLowerLimit desc
|
||||
ActiveSkillEnhanceMats map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel]
|
||||
ActiveSkillMaxLevelByRarity map[int32]NumericalFunc
|
||||
ActiveSkillCostByRarity map[int32]NumericalFunc
|
||||
}
|
||||
|
||||
func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
||||
costumes, err := utils.ReadJSON[CostumeMasterRow]("EntityMCostumeTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume table: %w", err)
|
||||
}
|
||||
|
||||
rarities, err := utils.ReadJSON[costumeRarityRow]("EntityMCostumeRarityTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume rarity table: %w", err)
|
||||
}
|
||||
|
||||
paramMapRows, err := LoadParameterMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
funcResolver, err := LoadFunctionResolver()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||
}
|
||||
|
||||
awakenRows, err := utils.ReadJSON[CostumeAwakenRow]("EntityMCostumeAwakenTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume awaken table: %w", err)
|
||||
}
|
||||
awakenPriceRows, err := utils.ReadJSON[costumeAwakenPriceRow]("EntityMCostumeAwakenPriceGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume awaken price table: %w", err)
|
||||
}
|
||||
awakenEffectRows, err := utils.ReadJSON[CostumeAwakenEffectRow]("EntityMCostumeAwakenEffectGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume awaken effect table: %w", err)
|
||||
}
|
||||
awakenStatusUpRows, err := utils.ReadJSON[CostumeAwakenStatusUpRow]("EntityMCostumeAwakenStatusUpGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume awaken status up table: %w", err)
|
||||
}
|
||||
awakenItemAcquireRows, err := utils.ReadJSON[CostumeAwakenItemAcquireRow]("EntityMCostumeAwakenItemAcquireTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume awaken item acquire table: %w", err)
|
||||
}
|
||||
|
||||
activeSkillGroupRows, err := utils.ReadJSON[CostumeActiveSkillGroupRow]("EntityMCostumeActiveSkillGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume active skill group table: %w", err)
|
||||
}
|
||||
activeSkillMatRows, err := utils.ReadJSON[CostumeActiveSkillEnhanceMaterialRow]("EntityMCostumeActiveSkillEnhancementMaterialTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume active skill enhancement material table: %w", err)
|
||||
}
|
||||
|
||||
catalog := &CostumeCatalog{
|
||||
Costumes: make(map[int32]CostumeMasterRow, len(costumes)),
|
||||
Materials: matCatalog.ByType[model.MaterialTypeCostumeEnhancement],
|
||||
ExpByRarity: make(map[int32][]int32, len(rarities)),
|
||||
EnhanceCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||
MaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||
LimitBreakCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||
|
||||
AwakenByCostumeId: make(map[int32]CostumeAwakenRow, len(awakenRows)),
|
||||
AwakenPriceByGroup: make(map[int32]int32, len(awakenPriceRows)),
|
||||
AwakenEffectsByGroupAndStep: make(map[int32]map[int32]CostumeAwakenEffectRow),
|
||||
AwakenStatusUpByGroup: make(map[int32][]CostumeAwakenStatusUpRow),
|
||||
AwakenItemAcquireById: make(map[int32]CostumeAwakenItemAcquireRow, len(awakenItemAcquireRows)),
|
||||
|
||||
ActiveSkillGroupsByGroupId: make(map[int32][]CostumeActiveSkillGroupRow),
|
||||
ActiveSkillEnhanceMats: make(map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow),
|
||||
ActiveSkillMaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||
ActiveSkillCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||
}
|
||||
|
||||
for _, row := range costumes {
|
||||
catalog.Costumes[row.CostumeId] = row
|
||||
}
|
||||
|
||||
for _, r := range rarities {
|
||||
if _, ok := catalog.ExpByRarity[r.RarityType]; !ok {
|
||||
catalog.ExpByRarity[r.RarityType] = BuildExpThresholds(paramMapRows, r.RequiredExpForLevelUpNumericalParameterMapId)
|
||||
}
|
||||
if _, ok := catalog.EnhanceCostByRarity[r.RarityType]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.EnhancementCostByMaterialNumericalFunctionId); found {
|
||||
catalog.EnhanceCostByRarity[r.RarityType] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.MaxLevelByRarity[r.RarityType]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.MaxLevelNumericalFunctionId); found {
|
||||
catalog.MaxLevelByRarity[r.RarityType] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.LimitBreakCostByRarity[r.RarityType]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.LimitBreakCostNumericalFunctionId); found {
|
||||
catalog.LimitBreakCostByRarity[r.RarityType] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.ActiveSkillMaxLevelByRarity[r.RarityType]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.ActiveSkillMaxLevelNumericalFunctionId); found {
|
||||
catalog.ActiveSkillMaxLevelByRarity[r.RarityType] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.ActiveSkillCostByRarity[r.RarityType]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.ActiveSkillEnhancementCostNumericalFunctionId); found {
|
||||
catalog.ActiveSkillCostByRarity[r.RarityType] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, row := range awakenRows {
|
||||
catalog.AwakenByCostumeId[row.CostumeId] = row
|
||||
}
|
||||
for _, row := range awakenPriceRows {
|
||||
catalog.AwakenPriceByGroup[row.CostumeAwakenPriceGroupId] = row.Gold
|
||||
}
|
||||
for _, row := range awakenEffectRows {
|
||||
m, ok := catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId]
|
||||
if !ok {
|
||||
m = make(map[int32]CostumeAwakenEffectRow)
|
||||
catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId] = m
|
||||
}
|
||||
m[row.AwakenStep] = row
|
||||
}
|
||||
for _, row := range awakenStatusUpRows {
|
||||
catalog.AwakenStatusUpByGroup[row.CostumeAwakenStatusUpGroupId] = append(
|
||||
catalog.AwakenStatusUpByGroup[row.CostumeAwakenStatusUpGroupId], row)
|
||||
}
|
||||
for _, row := range awakenItemAcquireRows {
|
||||
catalog.AwakenItemAcquireById[row.CostumeAwakenItemAcquireId] = row
|
||||
}
|
||||
|
||||
for _, row := range activeSkillGroupRows {
|
||||
gid := row.CostumeActiveSkillGroupId
|
||||
catalog.ActiveSkillGroupsByGroupId[gid] = append(catalog.ActiveSkillGroupsByGroupId[gid], row)
|
||||
}
|
||||
for gid, rows := range catalog.ActiveSkillGroupsByGroupId {
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
return rows[i].CostumeLimitBreakCountLowerLimit > rows[j].CostumeLimitBreakCountLowerLimit
|
||||
})
|
||||
catalog.ActiveSkillGroupsByGroupId[gid] = rows
|
||||
}
|
||||
|
||||
for _, row := range activeSkillMatRows {
|
||||
key := [2]int32{row.CostumeActiveSkillEnhancementMaterialId, row.SkillLevel}
|
||||
catalog.ActiveSkillEnhanceMats[key] = append(catalog.ActiveSkillEnhanceMats[key], row)
|
||||
}
|
||||
|
||||
return catalog, nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type costumeDupRow struct {
|
||||
CostumeId int32 `json:"CostumeId"`
|
||||
PossessionType int32 `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
func LoadDupExchange() (map[int32][]model.DupExchangeEntry, error) {
|
||||
result := make(map[int32][]model.DupExchangeEntry)
|
||||
|
||||
costumeRows, err := utils.ReadJSON[costumeDupRow]("EntityMCostumeDuplicationExchangePossessionGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range costumeRows {
|
||||
result[r.CostumeId] = append(result[r.CostumeId], model.DupExchangeEntry{
|
||||
PossessionType: r.PossessionType,
|
||||
PossessionId: r.PossessionId,
|
||||
Count: r.Count,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type lbMaterialRow struct {
|
||||
CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
}
|
||||
|
||||
type costumeLBRef struct {
|
||||
CostumeId int32 `json:"CostumeId"`
|
||||
CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
|
||||
}
|
||||
|
||||
const dupExchangeFallbackCount int32 = 10
|
||||
|
||||
func EnrichDupExchange(dupMap map[int32][]model.DupExchangeEntry, pool *GachaCatalog) (int, error) {
|
||||
lbRows, err := utils.ReadJSON[lbMaterialRow]("EntityMCostumeLimitBreakMaterialGroupTable.json")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
groupToMaterial := make(map[int32]int32, len(lbRows))
|
||||
for _, r := range lbRows {
|
||||
groupToMaterial[r.CostumeLimitBreakMaterialGroupId] = r.MaterialId
|
||||
}
|
||||
|
||||
costumeRows, err := utils.ReadJSON[costumeLBRef]("EntityMCostumeTable.json")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
costumeLBGroup := make(map[int32]int32, len(costumeRows))
|
||||
for _, r := range costumeRows {
|
||||
costumeLBGroup[r.CostumeId] = r.CostumeLimitBreakMaterialGroupId
|
||||
}
|
||||
|
||||
added := 0
|
||||
for costumeId := range pool.CostumeById {
|
||||
if _, exists := dupMap[costumeId]; exists {
|
||||
continue
|
||||
}
|
||||
matId := groupToMaterial[costumeLBGroup[costumeId]]
|
||||
if matId == 0 {
|
||||
continue
|
||||
}
|
||||
dupMap[costumeId] = []model.DupExchangeEntry{{
|
||||
PossessionType: int32(model.PossessionTypeMaterial),
|
||||
PossessionId: matId,
|
||||
Count: dupExchangeFallbackCount,
|
||||
}}
|
||||
added++
|
||||
}
|
||||
return added, nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type ExploreRow struct {
|
||||
ExploreId int32 `json:"ExploreId"`
|
||||
ConsumeItemCount int32 `json:"ConsumeItemCount"`
|
||||
RewardLotteryCount int32 `json:"RewardLotteryCount"`
|
||||
}
|
||||
|
||||
type ExploreGradeScoreRow struct {
|
||||
ExploreId int32 `json:"ExploreId"`
|
||||
NecessaryScore int32 `json:"NecessaryScore"`
|
||||
ExploreGradeId int32 `json:"ExploreGradeId"`
|
||||
}
|
||||
|
||||
type ExploreGradeAssetRow struct {
|
||||
ExploreGradeId int32 `json:"ExploreGradeId"`
|
||||
AssetGradeIconId int32 `json:"AssetGradeIconId"`
|
||||
}
|
||||
|
||||
type ExploreCatalog struct {
|
||||
Explores map[int32]ExploreRow
|
||||
GradeScores map[int32][]ExploreGradeScoreRow // keyed by ExploreId, sorted desc by NecessaryScore
|
||||
GradeAssets map[int32]int32 // gradeId -> assetGradeIconId
|
||||
}
|
||||
|
||||
func LoadExploreCatalog() (*ExploreCatalog, error) {
|
||||
explores, err := utils.ReadJSON[ExploreRow]("EntityMExploreTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load explore table: %w", err)
|
||||
}
|
||||
|
||||
gradeScores, err := utils.ReadJSON[ExploreGradeScoreRow]("EntityMExploreGradeScoreTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load explore grade score table: %w", err)
|
||||
}
|
||||
|
||||
gradeAssets, err := utils.ReadJSON[ExploreGradeAssetRow]("EntityMExploreGradeAssetTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load explore grade asset table: %w", err)
|
||||
}
|
||||
|
||||
catalog := &ExploreCatalog{
|
||||
Explores: make(map[int32]ExploreRow, len(explores)),
|
||||
GradeScores: make(map[int32][]ExploreGradeScoreRow),
|
||||
GradeAssets: make(map[int32]int32, len(gradeAssets)),
|
||||
}
|
||||
|
||||
for _, e := range explores {
|
||||
catalog.Explores[e.ExploreId] = e
|
||||
}
|
||||
|
||||
for _, gs := range gradeScores {
|
||||
catalog.GradeScores[gs.ExploreId] = append(catalog.GradeScores[gs.ExploreId], gs)
|
||||
}
|
||||
for eid := range catalog.GradeScores {
|
||||
rows := catalog.GradeScores[eid]
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
return rows[i].NecessaryScore > rows[j].NecessaryScore
|
||||
})
|
||||
catalog.GradeScores[eid] = rows
|
||||
}
|
||||
|
||||
for _, ga := range gradeAssets {
|
||||
catalog.GradeAssets[ga.ExploreGradeId] = ga.AssetGradeIconId
|
||||
}
|
||||
|
||||
return catalog, nil
|
||||
}
|
||||
|
||||
// GradeForScore returns the AssetGradeIconId for the given explore and score.
|
||||
// Returns 0 if no matching grade is found.
|
||||
func (c *ExploreCatalog) GradeForScore(exploreId, score int32) int32 {
|
||||
rows, ok := c.GradeScores[exploreId]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
for _, r := range rows {
|
||||
if score >= r.NecessaryScore {
|
||||
return c.GradeAssets[r.ExploreGradeId]
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/store"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type gachaMedalRow struct {
|
||||
GachaMedalId int32 `json:"GachaMedalId"`
|
||||
ShopTransitionGachaId int32 `json:"ShopTransitionGachaId"`
|
||||
ConsumableItemId int32 `json:"ConsumableItemId"`
|
||||
AutoConvertDatetime int64 `json:"AutoConvertDatetime"`
|
||||
ConversionRate int32 `json:"ConversionRate"`
|
||||
}
|
||||
|
||||
type momBannerRow struct {
|
||||
MomBannerId int32 `json:"MomBannerId"`
|
||||
SortOrderDesc int32 `json:"SortOrderDesc"`
|
||||
DestinationDomainType int32 `json:"DestinationDomainType"`
|
||||
DestinationDomainId int32 `json:"DestinationDomainId"`
|
||||
BannerAssetName string `json:"BannerAssetName"`
|
||||
StartDatetime int64 `json:"StartDatetime"`
|
||||
EndDatetime int64 `json:"EndDatetime"`
|
||||
}
|
||||
|
||||
type GachaMedalInfo struct {
|
||||
GachaMedalId int32
|
||||
ConsumableItemId int32
|
||||
AutoConvertDatetime int64
|
||||
ConversionRate int32
|
||||
}
|
||||
|
||||
const chapterGachaIdBase int32 = 200000
|
||||
|
||||
func LoadGachaCatalog() ([]store.GachaCatalogEntry, map[int32]GachaMedalInfo, error) {
|
||||
medals, err := utils.ReadJSON[gachaMedalRow]("EntityMGachaMedalTable.json")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("load gacha medal table: %w", err)
|
||||
}
|
||||
banners, err := utils.ReadJSON[momBannerRow]("EntityMMomBannerTable.json")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("load mom banner table: %w", err)
|
||||
}
|
||||
|
||||
gachaToMedal := make(map[int32]gachaMedalRow)
|
||||
medalInfoByGacha := make(map[int32]GachaMedalInfo)
|
||||
for _, m := range medals {
|
||||
gachaToMedal[m.ShopTransitionGachaId] = m
|
||||
medalInfoByGacha[m.ShopTransitionGachaId] = GachaMedalInfo{
|
||||
GachaMedalId: m.GachaMedalId,
|
||||
ConsumableItemId: m.ConsumableItemId,
|
||||
AutoConvertDatetime: m.AutoConvertDatetime,
|
||||
ConversionRate: m.ConversionRate,
|
||||
}
|
||||
}
|
||||
|
||||
stepupSteps := make(map[int32][]momBannerRow)
|
||||
var entries []store.GachaCatalogEntry
|
||||
|
||||
for _, b := range banners {
|
||||
if b.DestinationDomainType != model.MomBannerDomainGacha {
|
||||
continue
|
||||
}
|
||||
gachaId := b.DestinationDomainId
|
||||
|
||||
if strings.HasPrefix(b.BannerAssetName, model.BannerPrefixStepUp) {
|
||||
if _, hasMedal := gachaToMedal[gachaId]; !hasMedal {
|
||||
continue
|
||||
}
|
||||
groupId := gachaId / model.StepUpGroupDivisor
|
||||
stepupSteps[groupId] = append(stepupSteps[groupId], b)
|
||||
continue
|
||||
}
|
||||
|
||||
labelType := model.GachaLabelPremium
|
||||
modeType := model.GachaModeBasic
|
||||
decoration := model.GachaDecorationNormal
|
||||
|
||||
isChapter := strings.HasPrefix(b.BannerAssetName, model.BannerPrefixCommon)
|
||||
|
||||
if strings.HasPrefix(b.BannerAssetName, model.BannerPrefixLimited) {
|
||||
decoration = model.GachaDecorationFestival
|
||||
}
|
||||
if isChapter {
|
||||
labelType = model.GachaLabelChapter
|
||||
modeType = model.GachaModeBox
|
||||
}
|
||||
|
||||
medal, hasMedal := gachaToMedal[gachaId]
|
||||
if !hasMedal && !isChapter {
|
||||
continue
|
||||
}
|
||||
var medalId int32
|
||||
var medalConsumableId int32
|
||||
var ceilingCount int32
|
||||
if hasMedal {
|
||||
medalId = medal.GachaMedalId
|
||||
medalConsumableId = medal.ConsumableItemId
|
||||
ceilingCount = model.PityCeilingCount
|
||||
}
|
||||
|
||||
var pricePhases []store.GachaPricePhaseEntry
|
||||
if isChapter {
|
||||
pricePhases = buildChapterPricePhases(gachaId)
|
||||
} else {
|
||||
pricePhases = buildPremiumBasicPricePhases(gachaId)
|
||||
}
|
||||
|
||||
relMainQuest := int32(0)
|
||||
if isChapter {
|
||||
relMainQuest = gachaId - chapterGachaIdBase
|
||||
}
|
||||
|
||||
var descriptionTextId int32
|
||||
if isChapter {
|
||||
descriptionTextId = gachaId
|
||||
}
|
||||
|
||||
entries = append(entries, store.GachaCatalogEntry{
|
||||
GachaId: gachaId,
|
||||
GachaLabelType: labelType,
|
||||
GachaModeType: modeType,
|
||||
GachaAutoResetType: model.GachaAutoResetNone,
|
||||
IsUserGachaUnlock: true,
|
||||
StartDatetime: b.StartDatetime,
|
||||
EndDatetime: b.EndDatetime,
|
||||
RelatedMainQuestChapterId: relMainQuest,
|
||||
GachaMedalId: medalId,
|
||||
MedalConsumableItemId: medalConsumableId,
|
||||
GachaDecorationType: decoration,
|
||||
SortOrder: b.SortOrderDesc,
|
||||
BannerAssetName: b.BannerAssetName,
|
||||
GroupId: gachaId,
|
||||
CeilingCount: ceilingCount,
|
||||
PricePhases: pricePhases,
|
||||
DescriptionTextId: descriptionTextId,
|
||||
})
|
||||
}
|
||||
|
||||
for groupId, steps := range stepupSteps {
|
||||
first := steps[0]
|
||||
gachaId := groupId
|
||||
|
||||
medal := gachaToMedal[first.DestinationDomainId]
|
||||
medalId := medal.GachaMedalId
|
||||
medalConsumableId := medal.ConsumableItemId
|
||||
|
||||
pricePhases := buildStepUpPricePhases(gachaId, len(steps))
|
||||
|
||||
var maxStep int32
|
||||
for _, p := range pricePhases {
|
||||
if p.StepNumber > maxStep {
|
||||
maxStep = p.StepNumber
|
||||
}
|
||||
}
|
||||
|
||||
entries = append(entries, store.GachaCatalogEntry{
|
||||
GachaId: gachaId,
|
||||
GachaLabelType: model.GachaLabelPremium,
|
||||
GachaModeType: model.GachaModeStepup,
|
||||
GachaAutoResetType: model.GachaAutoResetNone,
|
||||
IsUserGachaUnlock: true,
|
||||
StartDatetime: first.StartDatetime,
|
||||
EndDatetime: first.EndDatetime,
|
||||
GachaMedalId: medalId,
|
||||
MedalConsumableItemId: medalConsumableId,
|
||||
GachaDecorationType: model.GachaDecorationFestival,
|
||||
SortOrder: first.SortOrderDesc,
|
||||
BannerAssetName: first.BannerAssetName,
|
||||
GroupId: groupId,
|
||||
CeilingCount: model.PityCeilingCount,
|
||||
PricePhases: pricePhases,
|
||||
MaxStepNumber: maxStep,
|
||||
})
|
||||
}
|
||||
|
||||
return entries, medalInfoByGacha, nil
|
||||
}
|
||||
|
||||
const chapterPromoMaxItems = 4
|
||||
const maxSlideFeatured = 13
|
||||
|
||||
func EnrichCatalogPromotions(entries []store.GachaCatalogEntry, pool *GachaCatalog) {
|
||||
for i := range entries {
|
||||
if entries[i].GachaLabelType == model.GachaLabelChapter {
|
||||
entries[i].PromotionItems = buildChapterPromotionItems(pool.Materials)
|
||||
continue
|
||||
}
|
||||
|
||||
featured := pool.FeaturedByGacha[entries[i].GachaId]
|
||||
|
||||
maxRarity := int32(0)
|
||||
for _, c := range featured.Costumes {
|
||||
if c.RarityType > maxRarity {
|
||||
maxRarity = c.RarityType
|
||||
}
|
||||
}
|
||||
for _, w := range featured.Weapons {
|
||||
if w.RarityType > maxRarity {
|
||||
maxRarity = w.RarityType
|
||||
}
|
||||
}
|
||||
|
||||
var topCostumes []GachaPoolItem
|
||||
for _, c := range featured.Costumes {
|
||||
if c.RarityType == maxRarity {
|
||||
topCostumes = append(topCostumes, c)
|
||||
}
|
||||
}
|
||||
var topWeapons []GachaPoolItem
|
||||
for _, w := range featured.Weapons {
|
||||
if w.RarityType == maxRarity {
|
||||
topWeapons = append(topWeapons, w)
|
||||
}
|
||||
}
|
||||
|
||||
if len(topCostumes)+len(topWeapons) > maxSlideFeatured {
|
||||
topCostumes = topCostumes[:min(3, len(topCostumes))]
|
||||
topWeapons = topWeapons[:min(2, len(topWeapons))]
|
||||
}
|
||||
|
||||
var items []store.GachaPromotionItem
|
||||
if entries[i].GachaModeType == model.GachaModeStepup && len(topCostumes) > 0 {
|
||||
items = append(items, toPromoItemWithBonus(topCostumes[0], pool))
|
||||
wid := pool.CostumeWeaponMap[topCostumes[0].PossessionId]
|
||||
items = append(items, toPromoItem(pool.WeaponById[wid]))
|
||||
} else {
|
||||
for _, c := range topCostumes {
|
||||
items = append(items, toPromoItemWithBonus(c, pool))
|
||||
}
|
||||
for _, w := range topWeapons {
|
||||
items = append(items, toPromoItemWithBonus(w, pool))
|
||||
}
|
||||
}
|
||||
|
||||
entries[i].PromotionItems = items
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
if entries[i].SortOrder != entries[j].SortOrder {
|
||||
return entries[i].SortOrder < entries[j].SortOrder
|
||||
}
|
||||
return entries[i].GachaId < entries[j].GachaId
|
||||
})
|
||||
}
|
||||
|
||||
func toPromoItem(item GachaPoolItem) store.GachaPromotionItem {
|
||||
return store.GachaPromotionItem{
|
||||
PossessionType: item.PossessionType,
|
||||
PossessionId: item.PossessionId,
|
||||
IsTarget: true,
|
||||
}
|
||||
}
|
||||
|
||||
func toPromoItemWithBonus(item GachaPoolItem, pool *GachaCatalog) store.GachaPromotionItem {
|
||||
pi := store.GachaPromotionItem{
|
||||
PossessionType: item.PossessionType,
|
||||
PossessionId: item.PossessionId,
|
||||
IsTarget: true,
|
||||
}
|
||||
if item.PossessionType == int32(model.PossessionTypeCostume) {
|
||||
pi.BonusPossessionType = int32(model.PossessionTypeWeapon)
|
||||
pi.BonusPossessionId = pool.CostumeWeaponMap[item.PossessionId]
|
||||
}
|
||||
return pi
|
||||
}
|
||||
|
||||
func buildChapterPromotionItems(materials []GachaPoolItem) []store.GachaPromotionItem {
|
||||
limit := min(chapterPromoMaxItems, len(materials))
|
||||
items := make([]store.GachaPromotionItem, 0, limit)
|
||||
for _, m := range materials[:limit] {
|
||||
items = append(items, toPromoItem(m))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func buildPremiumBasicPricePhases(gachaId int32) []store.GachaPricePhaseEntry {
|
||||
return []store.GachaPricePhaseEntry{
|
||||
{
|
||||
PhaseId: gachaId*model.PhaseIdMultiplier + 1,
|
||||
PriceType: model.PriceTypeGem,
|
||||
Price: model.PremiumSinglePullPrice,
|
||||
RegularPrice: model.PremiumSinglePullPrice,
|
||||
DrawCount: 1,
|
||||
},
|
||||
{
|
||||
PhaseId: gachaId*model.PhaseIdMultiplier + 2,
|
||||
PriceType: model.PriceTypeGem,
|
||||
Price: model.PremiumMultiPullPrice,
|
||||
RegularPrice: model.PremiumMultiPullPrice,
|
||||
DrawCount: model.PremiumMultiPullCount,
|
||||
FixedRarityMin: model.RaritySRare,
|
||||
FixedCount: 1,
|
||||
},
|
||||
{
|
||||
PhaseId: gachaId*model.PhaseIdMultiplier + 3,
|
||||
PriceType: model.PriceTypeConsumableItem,
|
||||
PriceId: model.ConsumableIdPremiumTicket,
|
||||
Price: 1,
|
||||
RegularPrice: 1,
|
||||
DrawCount: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildStepUpPricePhases(gachaId int32, totalSteps int) []store.GachaPricePhaseEntry {
|
||||
stepCosts := []int32{model.StepUpStep1Cost, model.StepUpFreeCost, model.StepUpStep3Cost, model.StepUpFreeCost, model.StepUpStep5Cost}
|
||||
stepCosts = stepCosts[:min(totalSteps, len(stepCosts))]
|
||||
|
||||
var phases []store.GachaPricePhaseEntry
|
||||
for i, cost := range stepCosts {
|
||||
step := int32(i + 1)
|
||||
priceType := model.PriceTypePaidGem
|
||||
if cost == 0 {
|
||||
priceType = model.PriceTypeGem
|
||||
}
|
||||
|
||||
fixedRarityMin := int32(0)
|
||||
fixedCount := int32(0)
|
||||
if step == int32(len(stepCosts)) {
|
||||
fixedRarityMin = model.RaritySSRare
|
||||
fixedCount = 1
|
||||
}
|
||||
|
||||
phases = append(phases, store.GachaPricePhaseEntry{
|
||||
PhaseId: gachaId*model.PhaseIdMultiplier + step,
|
||||
PriceType: priceType,
|
||||
Price: cost,
|
||||
RegularPrice: model.PremiumMultiPullPrice,
|
||||
DrawCount: model.PremiumMultiPullCount,
|
||||
FixedRarityMin: fixedRarityMin,
|
||||
FixedCount: fixedCount,
|
||||
LimitExecCount: 1,
|
||||
StepNumber: step,
|
||||
})
|
||||
}
|
||||
return phases
|
||||
}
|
||||
|
||||
func buildChapterPricePhases(gachaId int32) []store.GachaPricePhaseEntry {
|
||||
return []store.GachaPricePhaseEntry{
|
||||
{
|
||||
PhaseId: gachaId*model.PhaseIdMultiplier + 1,
|
||||
PriceType: model.PriceTypeConsumableItem,
|
||||
PriceId: model.ConsumableIdChapterTicket,
|
||||
Price: 1,
|
||||
RegularPrice: 1,
|
||||
DrawCount: 1,
|
||||
},
|
||||
{
|
||||
PhaseId: gachaId*model.PhaseIdMultiplier + 2,
|
||||
PriceType: model.PriceTypeConsumableItem,
|
||||
PriceId: model.ConsumableIdChapterTicket,
|
||||
Price: 10,
|
||||
RegularPrice: 10,
|
||||
DrawCount: model.PremiumMultiPullCount,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/store"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type GachaPoolItem struct {
|
||||
PossessionType int32
|
||||
PossessionId int32
|
||||
RarityType model.RarityType
|
||||
CharacterId int32
|
||||
}
|
||||
|
||||
type FeaturedSet struct {
|
||||
Costumes []GachaPoolItem
|
||||
Weapons []GachaPoolItem
|
||||
}
|
||||
|
||||
type BannerPool struct {
|
||||
CostumesByRarity map[int32][]GachaPoolItem
|
||||
WeaponsByRarity map[int32][]GachaPoolItem
|
||||
Featured []GachaPoolItem
|
||||
}
|
||||
|
||||
type ShopFeaturedEntry struct {
|
||||
CostumeId int32
|
||||
WeaponId int32
|
||||
}
|
||||
|
||||
type GachaCatalog struct {
|
||||
CostumesByRarity map[int32][]GachaPoolItem
|
||||
WeaponsByRarity map[int32][]GachaPoolItem
|
||||
Materials []GachaPoolItem
|
||||
CostumeById map[int32]GachaPoolItem
|
||||
WeaponById map[int32]GachaPoolItem
|
||||
CostumeWeaponMap map[int32]int32 // costumeId -> paired weaponId
|
||||
FeaturedByGacha map[int32]FeaturedSet
|
||||
BannerPools map[int32]*BannerPool
|
||||
ShopFeaturedByMedal map[int32][]ShopFeaturedEntry // consumableId -> paired entries
|
||||
}
|
||||
|
||||
type costumePoolRow struct {
|
||||
CostumeId int32 `json:"CostumeId"`
|
||||
CharacterId int32 `json:"CharacterId"`
|
||||
SkillfulWeaponType int32 `json:"SkillfulWeaponType"`
|
||||
RarityType int32 `json:"RarityType"`
|
||||
}
|
||||
|
||||
type weaponPoolRow struct {
|
||||
WeaponId int32 `json:"WeaponId"`
|
||||
WeaponType int32 `json:"WeaponType"`
|
||||
RarityType int32 `json:"RarityType"`
|
||||
IsRestrictDiscard bool `json:"IsRestrictDiscard"`
|
||||
}
|
||||
|
||||
type catalogCostumeRow struct {
|
||||
CostumeId int32 `json:"CostumeId"`
|
||||
CatalogTermId int32 `json:"CatalogTermId"`
|
||||
}
|
||||
|
||||
type catalogWeaponRow struct {
|
||||
WeaponId int32 `json:"WeaponId"`
|
||||
CatalogTermId int32 `json:"CatalogTermId"`
|
||||
}
|
||||
|
||||
type materialPoolRow struct {
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
MaterialType int32 `json:"MaterialType"`
|
||||
RarityType int32 `json:"RarityType"`
|
||||
}
|
||||
|
||||
func LoadGachaPool() (*GachaCatalog, error) {
|
||||
costumes, err := utils.ReadJSON[costumePoolRow]("EntityMCostumeTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume table: %w", err)
|
||||
}
|
||||
weapons, err := utils.ReadJSON[weaponPoolRow]("EntityMWeaponTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon table: %w", err)
|
||||
}
|
||||
catalogCostumes, err := utils.ReadJSON[catalogCostumeRow]("EntityMCatalogCostumeTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load catalog costume table: %w", err)
|
||||
}
|
||||
catalogWeapons, err := utils.ReadJSON[catalogWeaponRow]("EntityMCatalogWeaponTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load catalog weapon table: %w", err)
|
||||
}
|
||||
materials, err := utils.ReadJSON[materialPoolRow]("EntityMMaterialTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load material table: %w", err)
|
||||
}
|
||||
evoGroupRows, err := utils.ReadJSON[WeaponEvolutionGroupRow]("EntityMWeaponEvolutionGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon evolution group table: %w", err)
|
||||
}
|
||||
evolvedWeapons := buildEvolvedWeaponSet(evoGroupRows)
|
||||
|
||||
catalogCostumeSet := make(map[int32]bool, len(catalogCostumes))
|
||||
costumeTermId := make(map[int32]int32, len(catalogCostumes))
|
||||
for _, c := range catalogCostumes {
|
||||
catalogCostumeSet[c.CostumeId] = true
|
||||
costumeTermId[c.CostumeId] = c.CatalogTermId
|
||||
}
|
||||
catalogWeaponSet := make(map[int32]bool, len(catalogWeapons))
|
||||
for _, w := range catalogWeapons {
|
||||
catalogWeaponSet[w.WeaponId] = true
|
||||
}
|
||||
|
||||
costumeWeaponType := make(map[int32]int32, len(costumes))
|
||||
for _, c := range costumes {
|
||||
costumeWeaponType[c.CostumeId] = c.SkillfulWeaponType
|
||||
}
|
||||
|
||||
weaponTypeById := make(map[int32]int32, len(weapons))
|
||||
weaponRarityById := make(map[int32]int32, len(weapons))
|
||||
restrictedWeapons := make(map[int32]bool)
|
||||
for _, w := range weapons {
|
||||
weaponTypeById[w.WeaponId] = w.WeaponType
|
||||
weaponRarityById[w.WeaponId] = w.RarityType
|
||||
if w.IsRestrictDiscard {
|
||||
restrictedWeapons[w.WeaponId] = true
|
||||
}
|
||||
}
|
||||
|
||||
pool := &GachaCatalog{
|
||||
CostumesByRarity: make(map[int32][]GachaPoolItem),
|
||||
WeaponsByRarity: make(map[int32][]GachaPoolItem),
|
||||
CostumeById: make(map[int32]GachaPoolItem),
|
||||
WeaponById: make(map[int32]GachaPoolItem),
|
||||
CostumeWeaponMap: make(map[int32]int32),
|
||||
FeaturedByGacha: make(map[int32]FeaturedSet),
|
||||
}
|
||||
|
||||
for _, c := range costumes {
|
||||
if !catalogCostumeSet[c.CostumeId] {
|
||||
continue
|
||||
}
|
||||
if c.RarityType < model.RaritySRare {
|
||||
continue
|
||||
}
|
||||
item := GachaPoolItem{
|
||||
PossessionType: int32(model.PossessionTypeCostume),
|
||||
PossessionId: c.CostumeId,
|
||||
RarityType: c.RarityType,
|
||||
CharacterId: c.CharacterId,
|
||||
}
|
||||
pool.CostumesByRarity[c.RarityType] = append(pool.CostumesByRarity[c.RarityType], item)
|
||||
pool.CostumeById[c.CostumeId] = item
|
||||
}
|
||||
|
||||
restrictedCount := 0
|
||||
for _, w := range weapons {
|
||||
if !catalogWeaponSet[w.WeaponId] {
|
||||
continue
|
||||
}
|
||||
if evolvedWeapons[w.WeaponId] {
|
||||
continue
|
||||
}
|
||||
item := GachaPoolItem{
|
||||
PossessionType: int32(model.PossessionTypeWeapon),
|
||||
PossessionId: w.WeaponId,
|
||||
RarityType: w.RarityType,
|
||||
}
|
||||
pool.WeaponById[w.WeaponId] = item
|
||||
if w.IsRestrictDiscard {
|
||||
restrictedCount++
|
||||
continue
|
||||
}
|
||||
pool.WeaponsByRarity[w.RarityType] = append(pool.WeaponsByRarity[w.RarityType], item)
|
||||
}
|
||||
|
||||
log.Printf("[GachaPool] excluded %d evolved weapons, %d restricted weapons from pool", len(evolvedWeapons), restrictedCount)
|
||||
|
||||
type weaponKey struct {
|
||||
TermId int32
|
||||
WeaponType int32
|
||||
Rarity int32
|
||||
}
|
||||
weaponsByKey := make(map[weaponKey][]int32)
|
||||
for _, cw := range catalogWeapons {
|
||||
if evolvedWeapons[cw.WeaponId] || restrictedWeapons[cw.WeaponId] {
|
||||
continue
|
||||
}
|
||||
wt := weaponTypeById[cw.WeaponId]
|
||||
r := weaponRarityById[cw.WeaponId]
|
||||
if wt == 0 || r < model.RaritySRare {
|
||||
continue
|
||||
}
|
||||
k := weaponKey{TermId: cw.CatalogTermId, WeaponType: wt, Rarity: r}
|
||||
weaponsByKey[k] = append(weaponsByKey[k], cw.WeaponId)
|
||||
}
|
||||
for k, ids := range weaponsByKey {
|
||||
slices.Sort(ids)
|
||||
weaponsByKey[k] = ids
|
||||
}
|
||||
|
||||
exact, pattern, bestGuess := 0, 0, 0
|
||||
for costumeId, item := range pool.CostumeById {
|
||||
tid := costumeTermId[costumeId]
|
||||
wt := costumeWeaponType[costumeId]
|
||||
k := weaponKey{TermId: tid, WeaponType: wt, Rarity: item.RarityType}
|
||||
candidates := weaponsByKey[k]
|
||||
if len(candidates) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(candidates) == 1 {
|
||||
pool.CostumeWeaponMap[costumeId] = candidates[0]
|
||||
exact++
|
||||
continue
|
||||
}
|
||||
idPattern := costumeId*10 + 1
|
||||
found := false
|
||||
for _, wid := range candidates {
|
||||
if wid == idPattern {
|
||||
pool.CostumeWeaponMap[costumeId] = wid
|
||||
pattern++
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
pool.CostumeWeaponMap[costumeId] = candidates[0]
|
||||
bestGuess++
|
||||
}
|
||||
}
|
||||
log.Printf("[GachaPool] costume-weapon pairing: %d exact, %d id-pattern, %d best-guess, %d total",
|
||||
exact, pattern, bestGuess, len(pool.CostumeWeaponMap))
|
||||
|
||||
for _, m := range materials {
|
||||
pool.Materials = append(pool.Materials, GachaPoolItem{
|
||||
PossessionType: int32(model.PossessionTypeMaterial),
|
||||
PossessionId: m.MaterialId,
|
||||
RarityType: m.RarityType,
|
||||
})
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (pool *GachaCatalog) BuildShopFeatured(shop *ShopCatalog) {
|
||||
pool.ShopFeaturedByMedal = make(map[int32][]ShopFeaturedEntry)
|
||||
shopPairs := 0
|
||||
for _, cells := range shop.ExchangeShopCells {
|
||||
consumableId := shop.Items[cells[0].ShopItemId].PriceId
|
||||
|
||||
var entries []ShopFeaturedEntry
|
||||
for _, cell := range cells {
|
||||
contents := shop.Contents[cell.ShopItemId]
|
||||
var costumeId, weaponId int32
|
||||
for _, c := range contents {
|
||||
switch c.PossessionType {
|
||||
case int32(model.PossessionTypeCostume):
|
||||
costumeId = c.PossessionId
|
||||
case int32(model.PossessionTypeWeapon):
|
||||
weaponId = c.PossessionId
|
||||
}
|
||||
}
|
||||
if costumeId == 0 && weaponId == 0 {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, ShopFeaturedEntry{CostumeId: costumeId, WeaponId: weaponId})
|
||||
if costumeId != 0 && weaponId != 0 {
|
||||
pool.CostumeWeaponMap[costumeId] = weaponId
|
||||
shopPairs++
|
||||
}
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
pool.ShopFeaturedByMedal[consumableId] = entries
|
||||
}
|
||||
}
|
||||
log.Printf("[GachaPool] shop featured: %d consumables, %d costume-weapon pairs overridden", len(pool.ShopFeaturedByMedal), shopPairs)
|
||||
}
|
||||
|
||||
func (pool *GachaCatalog) PruneUnpairedCostumes() {
|
||||
pruned := 0
|
||||
for costumeId := range pool.CostumeById {
|
||||
if _, ok := pool.CostumeWeaponMap[costumeId]; !ok {
|
||||
delete(pool.CostumeById, costumeId)
|
||||
pruned++
|
||||
}
|
||||
}
|
||||
for rarity, items := range pool.CostumesByRarity {
|
||||
filtered := items[:0]
|
||||
for _, item := range items {
|
||||
if _, ok := pool.CostumeWeaponMap[item.PossessionId]; ok {
|
||||
filtered = append(filtered, item)
|
||||
}
|
||||
}
|
||||
pool.CostumesByRarity[rarity] = filtered
|
||||
}
|
||||
log.Printf("[GachaPool] pruned %d unpaired costumes", pruned)
|
||||
}
|
||||
|
||||
func (pool *GachaCatalog) BuildFeaturedMapping(entries []store.GachaCatalogEntry) {
|
||||
matched := 0
|
||||
for _, entry := range entries {
|
||||
if entry.MedalConsumableItemId == 0 {
|
||||
continue
|
||||
}
|
||||
shopEntries, ok := pool.ShopFeaturedByMedal[entry.MedalConsumableItemId]
|
||||
if !ok || len(shopEntries) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
seenCostume := make(map[int32]bool)
|
||||
linkedWeapons := make(map[int32]bool)
|
||||
var costumes []GachaPoolItem
|
||||
for _, se := range shopEntries {
|
||||
if se.CostumeId != 0 && !seenCostume[se.CostumeId] {
|
||||
costumes = append(costumes, pool.CostumeById[se.CostumeId])
|
||||
seenCostume[se.CostumeId] = true
|
||||
linkedWeapons[se.WeaponId] = true
|
||||
}
|
||||
}
|
||||
|
||||
seenWeapon := make(map[int32]bool)
|
||||
var weapons []GachaPoolItem
|
||||
for _, se := range shopEntries {
|
||||
if se.WeaponId != 0 && !linkedWeapons[se.WeaponId] && !seenWeapon[se.WeaponId] {
|
||||
if item, ok := pool.WeaponById[se.WeaponId]; ok {
|
||||
weapons = append(weapons, item)
|
||||
seenWeapon[se.WeaponId] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pool.FeaturedByGacha[entry.GachaId] = FeaturedSet{Costumes: costumes, Weapons: weapons}
|
||||
matched++
|
||||
}
|
||||
log.Printf("[GachaPool] featured mapping: %d/%d banners matched via shop", matched, len(entries))
|
||||
}
|
||||
|
||||
func (pool *GachaCatalog) BuildBannerPools(entries []store.GachaCatalogEntry) {
|
||||
allFeaturedCostumes := make(map[int32]bool)
|
||||
allFeaturedWeapons := make(map[int32]bool)
|
||||
for _, fs := range pool.FeaturedByGacha {
|
||||
for _, c := range fs.Costumes {
|
||||
allFeaturedCostumes[c.PossessionId] = true
|
||||
allFeaturedWeapons[pool.CostumeWeaponMap[c.PossessionId]] = true
|
||||
}
|
||||
for _, w := range fs.Weapons {
|
||||
allFeaturedWeapons[w.PossessionId] = true
|
||||
}
|
||||
}
|
||||
|
||||
commonCostumes := make(map[int32][]GachaPoolItem)
|
||||
for rarity, items := range pool.CostumesByRarity {
|
||||
for _, item := range items {
|
||||
if !allFeaturedCostumes[item.PossessionId] {
|
||||
commonCostumes[rarity] = append(commonCostumes[rarity], item)
|
||||
}
|
||||
}
|
||||
}
|
||||
commonWeapons := make(map[int32][]GachaPoolItem)
|
||||
for rarity, items := range pool.WeaponsByRarity {
|
||||
for _, item := range items {
|
||||
if !allFeaturedWeapons[item.PossessionId] {
|
||||
commonWeapons[rarity] = append(commonWeapons[rarity], item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commonPool := &BannerPool{
|
||||
CostumesByRarity: commonCostumes,
|
||||
WeaponsByRarity: commonWeapons,
|
||||
}
|
||||
|
||||
pool.BannerPools = make(map[int32]*BannerPool)
|
||||
for _, entry := range entries {
|
||||
fs, hasFeatured := pool.FeaturedByGacha[entry.GachaId]
|
||||
if !hasFeatured {
|
||||
pool.BannerPools[entry.GachaId] = commonPool
|
||||
continue
|
||||
}
|
||||
|
||||
var allFeatured []GachaPoolItem
|
||||
bannerCostumes := make(map[int32][]GachaPoolItem)
|
||||
for rarity, items := range commonCostumes {
|
||||
bannerCostumes[rarity] = append(bannerCostumes[rarity], items...)
|
||||
}
|
||||
bannerWeapons := make(map[int32][]GachaPoolItem)
|
||||
for rarity, items := range commonWeapons {
|
||||
bannerWeapons[rarity] = append(bannerWeapons[rarity], items...)
|
||||
}
|
||||
for _, c := range fs.Costumes {
|
||||
bannerCostumes[c.RarityType] = append(bannerCostumes[c.RarityType], c)
|
||||
allFeatured = append(allFeatured, c)
|
||||
wid := pool.CostumeWeaponMap[c.PossessionId]
|
||||
w := pool.WeaponById[wid]
|
||||
bannerWeapons[w.RarityType] = append(bannerWeapons[w.RarityType], w)
|
||||
allFeatured = append(allFeatured, w)
|
||||
}
|
||||
for _, w := range fs.Weapons {
|
||||
bannerWeapons[w.RarityType] = append(bannerWeapons[w.RarityType], w)
|
||||
allFeatured = append(allFeatured, w)
|
||||
}
|
||||
|
||||
pool.BannerPools[entry.GachaId] = &BannerPool{
|
||||
CostumesByRarity: bannerCostumes,
|
||||
WeaponsByRarity: bannerWeapons,
|
||||
Featured: allFeatured,
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[GachaPool] banner pools: %d banners, %d featured costumes stripped, %d featured weapons stripped",
|
||||
len(pool.BannerPools), len(allFeaturedCostumes), len(allFeaturedWeapons))
|
||||
}
|
||||
|
||||
func buildEvolvedWeaponSet(rows []WeaponEvolutionGroupRow) map[int32]bool {
|
||||
grouped := make(map[int32][]WeaponEvolutionGroupRow)
|
||||
for _, r := range rows {
|
||||
grouped[r.WeaponEvolutionGroupId] = append(grouped[r.WeaponEvolutionGroupId], r)
|
||||
}
|
||||
evolved := make(map[int32]bool)
|
||||
for _, chain := range grouped {
|
||||
sort.Slice(chain, func(i, j int) bool {
|
||||
return chain[i].EvolutionOrder < chain[j].EvolutionOrder
|
||||
})
|
||||
for i := 1; i < len(chain); i++ {
|
||||
evolved[chain[i].WeaponId] = true
|
||||
}
|
||||
}
|
||||
return evolved
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/store"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type gimmickScheduleRow struct {
|
||||
GimmickSequenceScheduleId int32 `json:"GimmickSequenceScheduleId"`
|
||||
StartDatetime int64 `json:"StartDatetime"`
|
||||
EndDatetime int64 `json:"EndDatetime"`
|
||||
FirstGimmickSequenceId int32 `json:"FirstGimmickSequenceId"`
|
||||
ReleaseEvaluateConditionId int32 `json:"ReleaseEvaluateConditionId"`
|
||||
}
|
||||
|
||||
type gimmickScheduleEntry struct {
|
||||
ScheduleId int32
|
||||
StartDatetime int64
|
||||
EndDatetime int64
|
||||
FirstSequenceId int32
|
||||
RequiredQuestId int32 // 0 = always active
|
||||
}
|
||||
|
||||
type GimmickCatalog struct {
|
||||
schedules []gimmickScheduleEntry
|
||||
}
|
||||
|
||||
func LoadGimmickCatalog(resolver *ConditionResolver) (*GimmickCatalog, error) {
|
||||
rows, err := utils.ReadJSON[gimmickScheduleRow]("EntityMGimmickSequenceScheduleTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load gimmick sequence schedule table: %w", err)
|
||||
}
|
||||
|
||||
entries := make([]gimmickScheduleEntry, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
entry := gimmickScheduleEntry{
|
||||
ScheduleId: r.GimmickSequenceScheduleId,
|
||||
StartDatetime: r.StartDatetime,
|
||||
EndDatetime: r.EndDatetime,
|
||||
FirstSequenceId: r.FirstGimmickSequenceId,
|
||||
}
|
||||
if r.ReleaseEvaluateConditionId != 0 {
|
||||
if qid, ok := resolver.RequiredQuestId(r.ReleaseEvaluateConditionId); ok {
|
||||
entry.RequiredQuestId = qid
|
||||
}
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
log.Printf("gimmick catalog loaded: %d schedules", len(entries))
|
||||
return &GimmickCatalog{schedules: entries}, nil
|
||||
}
|
||||
|
||||
func (c *GimmickCatalog) ActiveScheduleKeys(user store.UserState, nowMillis int64) []store.GimmickSequenceKey {
|
||||
var keys []store.GimmickSequenceKey
|
||||
for _, s := range c.schedules {
|
||||
if nowMillis < s.StartDatetime || nowMillis > s.EndDatetime {
|
||||
continue
|
||||
}
|
||||
if s.RequiredQuestId != 0 {
|
||||
q, ok := user.Quests[s.RequiredQuestId]
|
||||
if !ok || q.QuestStateType != model.UserQuestStateTypeCleared {
|
||||
continue
|
||||
}
|
||||
}
|
||||
keys = append(keys, store.GimmickSequenceKey{
|
||||
GimmickSequenceScheduleId: s.ScheduleId,
|
||||
GimmickSequenceId: s.FirstSequenceId,
|
||||
})
|
||||
}
|
||||
return keys
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"log"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type loginBonusStamp struct {
|
||||
LoginBonusId int32 `json:"LoginBonusId"`
|
||||
LowerPageNumber int32 `json:"LowerPageNumber"`
|
||||
StampNumber int32 `json:"StampNumber"`
|
||||
RewardPossessionType int32 `json:"RewardPossessionType"`
|
||||
RewardPossessionId int32 `json:"RewardPossessionId"`
|
||||
RewardCount int32 `json:"RewardCount"`
|
||||
}
|
||||
|
||||
type loginBonusStampKey struct {
|
||||
LoginBonusId int32
|
||||
LowerPageNumber int32
|
||||
StampNumber int32
|
||||
}
|
||||
|
||||
type LoginBonusReward struct {
|
||||
PossessionType int32
|
||||
PossessionId int32
|
||||
Count int32
|
||||
}
|
||||
|
||||
type LoginBonusCatalog struct {
|
||||
stamps map[loginBonusStampKey]LoginBonusReward
|
||||
}
|
||||
|
||||
func (c *LoginBonusCatalog) LookupStampReward(loginBonusId, pageNumber, stampNumber int32) (LoginBonusReward, bool) {
|
||||
entry, ok := c.stamps[loginBonusStampKey{loginBonusId, pageNumber, stampNumber}]
|
||||
return entry, ok
|
||||
}
|
||||
|
||||
func LoadLoginBonusCatalog() *LoginBonusCatalog {
|
||||
stamps, err := utils.ReadJSON[loginBonusStamp]("EntityMLoginBonusStampTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load login bonus stamp table: %v", err)
|
||||
}
|
||||
|
||||
cat := &LoginBonusCatalog{
|
||||
stamps: make(map[loginBonusStampKey]LoginBonusReward, len(stamps)),
|
||||
}
|
||||
for _, s := range stamps {
|
||||
cat.stamps[loginBonusStampKey{s.LoginBonusId, s.LowerPageNumber, s.StampNumber}] = LoginBonusReward{
|
||||
PossessionType: s.RewardPossessionType,
|
||||
PossessionId: s.RewardPossessionId,
|
||||
Count: s.RewardCount,
|
||||
}
|
||||
}
|
||||
return cat
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type MaterialRow struct {
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
MaterialType model.MaterialType `json:"MaterialType"`
|
||||
WeaponType int32 `json:"WeaponType"`
|
||||
EffectValue int32 `json:"EffectValue"`
|
||||
SellPrice int32 `json:"SellPrice"`
|
||||
}
|
||||
|
||||
type numericalParameterMapRow struct {
|
||||
NumericalParameterMapId int32 `json:"NumericalParameterMapId"`
|
||||
ParameterKey int32 `json:"ParameterKey"`
|
||||
ParameterValue int32 `json:"ParameterValue"`
|
||||
}
|
||||
|
||||
func LoadParameterMap() ([]numericalParameterMapRow, error) {
|
||||
rows, err := utils.ReadJSON[numericalParameterMapRow]("EntityMNumericalParameterMapTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load numerical parameter map table: %w", err)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func BuildExpThresholds(paramMapRows []numericalParameterMapRow, mapId int32) []int32 {
|
||||
maxKey := int32(0)
|
||||
for _, r := range paramMapRows {
|
||||
if r.NumericalParameterMapId == mapId && r.ParameterKey > maxKey {
|
||||
maxKey = r.ParameterKey
|
||||
}
|
||||
}
|
||||
thresholds := make([]int32, maxKey+1)
|
||||
for _, r := range paramMapRows {
|
||||
if r.NumericalParameterMapId == mapId {
|
||||
thresholds[r.ParameterKey] = r.ParameterValue
|
||||
}
|
||||
}
|
||||
return thresholds
|
||||
}
|
||||
|
||||
type MaterialCatalog struct {
|
||||
All map[int32]MaterialRow
|
||||
ByType map[model.MaterialType]map[int32]MaterialRow
|
||||
}
|
||||
|
||||
func LoadMaterialCatalog() (*MaterialCatalog, error) {
|
||||
rows, err := utils.ReadJSON[MaterialRow]("EntityMMaterialTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load material table: %w", err)
|
||||
}
|
||||
|
||||
catalog := &MaterialCatalog{
|
||||
All: make(map[int32]MaterialRow, len(rows)),
|
||||
ByType: make(map[model.MaterialType]map[int32]MaterialRow),
|
||||
}
|
||||
for _, row := range rows {
|
||||
catalog.All[row.MaterialId] = row
|
||||
if catalog.ByType[row.MaterialType] == nil {
|
||||
catalog.ByType[row.MaterialType] = make(map[int32]MaterialRow)
|
||||
}
|
||||
catalog.ByType[row.MaterialType][row.MaterialId] = row
|
||||
}
|
||||
return catalog, nil
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type numericalFunctionRow struct {
|
||||
NumericalFunctionId int32 `json:"NumericalFunctionId"`
|
||||
NumericalFunctionType int32 `json:"NumericalFunctionType"`
|
||||
NumericalFunctionParameterGroupId int32 `json:"NumericalFunctionParameterGroupId"`
|
||||
}
|
||||
|
||||
type numericalFunctionParameterRow struct {
|
||||
NumericalFunctionParameterGroupId int32 `json:"NumericalFunctionParameterGroupId"`
|
||||
ParameterIndex int32 `json:"ParameterIndex"`
|
||||
ParameterValue int32 `json:"ParameterValue"`
|
||||
}
|
||||
|
||||
type NumericalFunc struct {
|
||||
Type model.NumericalFunctionType
|
||||
Params []int32
|
||||
}
|
||||
|
||||
func (f NumericalFunc) Evaluate(value int32) int32 {
|
||||
p := f.Params
|
||||
switch f.Type {
|
||||
case model.NumericalFunctionTypeLinear:
|
||||
return p[1] + p[0]*value
|
||||
case model.NumericalFunctionTypeMonomial:
|
||||
v := value - 1
|
||||
result := v
|
||||
counter := p[1]
|
||||
if counter > 1 {
|
||||
counter--
|
||||
for counter > 0 {
|
||||
counter--
|
||||
result *= v
|
||||
}
|
||||
}
|
||||
return result * p[0]
|
||||
case model.NumericalFunctionTypeLinearPermil:
|
||||
return p[0]*value/1000 + p[1]
|
||||
case model.NumericalFunctionTypePolynomialThird:
|
||||
return p[3] + (p[2]+(p[1]+p[0]*value)*value)*value
|
||||
case model.NumericalFunctionTypePolynomialThirdPermil:
|
||||
return p[0]*value*value*value/1000 +
|
||||
p[1]*value*value/1000 +
|
||||
p[2]*value/1000 +
|
||||
p[3]
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
type FunctionResolver struct {
|
||||
functions map[int32]NumericalFunc
|
||||
}
|
||||
|
||||
func LoadFunctionResolver() (*FunctionResolver, error) {
|
||||
funcRows, err := utils.ReadJSON[numericalFunctionRow]("EntityMNumericalFunctionTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load numerical function table: %w", err)
|
||||
}
|
||||
|
||||
paramRows, err := utils.ReadJSON[numericalFunctionParameterRow]("EntityMNumericalFunctionParameterGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load numerical function parameter group table: %w", err)
|
||||
}
|
||||
|
||||
paramsByGroup := make(map[int32][]numericalFunctionParameterRow, len(paramRows))
|
||||
for _, r := range paramRows {
|
||||
paramsByGroup[r.NumericalFunctionParameterGroupId] = append(
|
||||
paramsByGroup[r.NumericalFunctionParameterGroupId], r)
|
||||
}
|
||||
for _, group := range paramsByGroup {
|
||||
sort.Slice(group, func(i, j int) bool {
|
||||
return group[i].ParameterIndex < group[j].ParameterIndex
|
||||
})
|
||||
}
|
||||
|
||||
functions := make(map[int32]NumericalFunc, len(funcRows))
|
||||
for _, fr := range funcRows {
|
||||
group := paramsByGroup[fr.NumericalFunctionParameterGroupId]
|
||||
params := make([]int32, len(group))
|
||||
for _, pr := range group {
|
||||
if int(pr.ParameterIndex) < len(params) {
|
||||
params[pr.ParameterIndex] = pr.ParameterValue
|
||||
}
|
||||
}
|
||||
functions[fr.NumericalFunctionId] = NumericalFunc{
|
||||
Type: model.NumericalFunctionType(fr.NumericalFunctionType),
|
||||
Params: params,
|
||||
}
|
||||
}
|
||||
|
||||
return &FunctionResolver{functions: functions}, nil
|
||||
}
|
||||
|
||||
func (r *FunctionResolver) Resolve(functionId int32) (NumericalFunc, bool) {
|
||||
f, ok := r.functions[functionId]
|
||||
return f, ok
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"log"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type omikujiEntry struct {
|
||||
OmikujiId int32 `json:"OmikujiId"`
|
||||
OmikujiAssetId int32 `json:"OmikujiAssetId"`
|
||||
}
|
||||
|
||||
type OmikujiCatalog struct {
|
||||
assetIds map[int32]int32
|
||||
}
|
||||
|
||||
func (c *OmikujiCatalog) LookupAssetId(omikujiId int32) int32 {
|
||||
if id, ok := c.assetIds[omikujiId]; ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func LoadOmikujiCatalog() *OmikujiCatalog {
|
||||
entries, err := utils.ReadJSON[omikujiEntry]("EntityMOmikujiTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load omikuji table: %v", err)
|
||||
}
|
||||
|
||||
cat := &OmikujiCatalog{
|
||||
assetIds: make(map[int32]int32, len(entries)),
|
||||
}
|
||||
for _, e := range entries {
|
||||
cat.assetIds[e.OmikujiId] = e.OmikujiAssetId
|
||||
}
|
||||
return cat
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type PartsRow struct {
|
||||
PartsId int32 `json:"PartsId"`
|
||||
RarityType model.RarityType `json:"RarityType"`
|
||||
PartsGroupId int32 `json:"PartsGroupId"`
|
||||
PartsStatusMainLotteryGroupId int32 `json:"PartsStatusMainLotteryGroupId"`
|
||||
}
|
||||
|
||||
type PartsRarityRow struct {
|
||||
RarityType model.RarityType `json:"RarityType"`
|
||||
PartsLevelUpRateGroupId int32 `json:"PartsLevelUpRateGroupId"`
|
||||
PartsLevelUpPriceGroupId int32 `json:"PartsLevelUpPriceGroupId"`
|
||||
SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
|
||||
}
|
||||
|
||||
type partsLevelUpRateRow struct {
|
||||
PartsLevelUpRateGroupId int32 `json:"PartsLevelUpRateGroupId"`
|
||||
LevelLowerLimit int32 `json:"LevelLowerLimit"`
|
||||
SuccessRatePermil int32 `json:"SuccessRatePermil"`
|
||||
}
|
||||
|
||||
type partsLevelUpPriceRow struct {
|
||||
PartsLevelUpPriceGroupId int32 `json:"PartsLevelUpPriceGroupId"`
|
||||
LevelLowerLimit int32 `json:"LevelLowerLimit"`
|
||||
Gold int32 `json:"Gold"`
|
||||
}
|
||||
|
||||
type PartsCatalog struct {
|
||||
PartsById map[int32]PartsRow
|
||||
DefaultPartsStatusMainByLotteryGroup map[int32]int32
|
||||
RarityByRarityType map[model.RarityType]PartsRarityRow
|
||||
RateByGroupAndLevel map[int32]map[int32]int32
|
||||
PriceByGroupAndLevel map[int32]map[int32]int32
|
||||
SellPriceByRarity map[model.RarityType]NumericalFunc
|
||||
}
|
||||
|
||||
func LoadPartsCatalog() (*PartsCatalog, error) {
|
||||
partsRows, err := utils.ReadJSON[PartsRow]("EntityMPartsTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load parts table: %w", err)
|
||||
}
|
||||
|
||||
rarityRows, err := utils.ReadJSON[PartsRarityRow]("EntityMPartsRarityTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load parts rarity table: %w", err)
|
||||
}
|
||||
|
||||
rateRows, err := utils.ReadJSON[partsLevelUpRateRow]("EntityMPartsLevelUpRateGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load parts level up rate table: %w", err)
|
||||
}
|
||||
|
||||
priceRows, err := utils.ReadJSON[partsLevelUpPriceRow]("EntityMPartsLevelUpPriceGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load parts level up price table: %w", err)
|
||||
}
|
||||
|
||||
partsById := make(map[int32]PartsRow, len(partsRows))
|
||||
for _, p := range partsRows {
|
||||
partsById[p.PartsId] = p
|
||||
}
|
||||
|
||||
// Lottery group ID encodes tier (first digit 1-4) and stat category
|
||||
// (second digit 1-6). Formula: mainStatId = (category - 1) * 4 + tier.
|
||||
defaultPartsStatusMainByLotteryGroup := make(map[int32]int32, 24)
|
||||
for tier := int32(1); tier <= 4; tier++ {
|
||||
for cat := int32(1); cat <= 6; cat++ {
|
||||
groupId := tier*10 + cat
|
||||
mainStatId := (cat-1)*4 + tier
|
||||
defaultPartsStatusMainByLotteryGroup[groupId] = mainStatId
|
||||
}
|
||||
}
|
||||
|
||||
funcResolver, err := LoadFunctionResolver()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||
}
|
||||
|
||||
rarityByRarityType := make(map[model.RarityType]PartsRarityRow, len(rarityRows))
|
||||
sellPriceByRarity := make(map[model.RarityType]NumericalFunc, len(rarityRows))
|
||||
for _, r := range rarityRows {
|
||||
rarityByRarityType[r.RarityType] = r
|
||||
if f, ok := funcResolver.Resolve(r.SellPriceNumericalFunctionId); ok {
|
||||
sellPriceByRarity[r.RarityType] = f
|
||||
}
|
||||
}
|
||||
|
||||
rateByGroupAndLevel := make(map[int32]map[int32]int32)
|
||||
for _, r := range rateRows {
|
||||
if rateByGroupAndLevel[r.PartsLevelUpRateGroupId] == nil {
|
||||
rateByGroupAndLevel[r.PartsLevelUpRateGroupId] = make(map[int32]int32)
|
||||
}
|
||||
rateByGroupAndLevel[r.PartsLevelUpRateGroupId][r.LevelLowerLimit] = r.SuccessRatePermil
|
||||
}
|
||||
|
||||
priceByGroupAndLevel := make(map[int32]map[int32]int32)
|
||||
for _, p := range priceRows {
|
||||
if priceByGroupAndLevel[p.PartsLevelUpPriceGroupId] == nil {
|
||||
priceByGroupAndLevel[p.PartsLevelUpPriceGroupId] = make(map[int32]int32)
|
||||
}
|
||||
priceByGroupAndLevel[p.PartsLevelUpPriceGroupId][p.LevelLowerLimit] = p.Gold
|
||||
}
|
||||
|
||||
return &PartsCatalog{
|
||||
PartsById: partsById,
|
||||
DefaultPartsStatusMainByLotteryGroup: defaultPartsStatusMainByLotteryGroup,
|
||||
RarityByRarityType: rarityByRarityType,
|
||||
RateByGroupAndLevel: rateByGroupAndLevel,
|
||||
PriceByGroupAndLevel: priceByGroupAndLevel,
|
||||
SellPriceByRarity: sellPriceByRarity,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type QuestSceneRow struct {
|
||||
QuestSceneId int32 `json:"QuestSceneId"`
|
||||
QuestId int32 `json:"QuestId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
QuestSceneType model.QuestSceneType `json:"QuestSceneType"`
|
||||
AssetBackgroundId int32 `json:"AssetBackgroundId"`
|
||||
EventMapNumberUpper int32 `json:"EventMapNumberUpper"`
|
||||
EventMapNumberLower int32 `json:"EventMapNumberLower"`
|
||||
IsMainFlowQuestTarget bool `json:"IsMainFlowQuestTarget"`
|
||||
IsBattleOnlyTarget bool `json:"IsBattleOnlyTarget"`
|
||||
QuestResultType model.QuestResultType `json:"QuestResultType"`
|
||||
IsStorySkipTarget bool `json:"IsStorySkipTarget"`
|
||||
}
|
||||
|
||||
type QuestRow struct {
|
||||
QuestId int32 `json:"QuestId"`
|
||||
NameQuestTextId int32 `json:"NameQuestTextId"`
|
||||
PictureBookNameQuestTextId int32 `json:"PictureBookNameQuestTextId"`
|
||||
QuestReleaseConditionListId int32 `json:"QuestReleaseConditionListId"`
|
||||
StoryQuestTextId int32 `json:"StoryQuestTextId"`
|
||||
QuestDisplayAttributeGroupId int32 `json:"QuestDisplayAttributeGroupId"`
|
||||
RecommendedDeckPower int32 `json:"RecommendedDeckPower"`
|
||||
QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
|
||||
QuestPickupRewardGroupId int32 `json:"QuestPickupRewardGroupId"`
|
||||
QuestDeckRestrictionGroupId int32 `json:"QuestDeckRestrictionGroupId"`
|
||||
QuestMissionGroupId int32 `json:"QuestMissionGroupId"`
|
||||
Stamina int32 `json:"Stamina"`
|
||||
UserExp int32 `json:"UserExp"`
|
||||
CharacterExp int32 `json:"CharacterExp"`
|
||||
CostumeExp int32 `json:"CostumeExp"`
|
||||
Gold int32 `json:"Gold"`
|
||||
DailyClearableCount int32 `json:"DailyClearableCount"`
|
||||
IsRunInTheBackground bool `json:"IsRunInTheBackground"`
|
||||
IsCountedAsQuest bool `json:"IsCountedAsQuest"`
|
||||
QuestBonusId int32 `json:"QuestBonusId"`
|
||||
IsNotShowAfterClear bool `json:"IsNotShowAfterClear"`
|
||||
IsBigWinTarget bool `json:"IsBigWinTarget"`
|
||||
IsUsableSkipTicket bool `json:"IsUsableSkipTicket"`
|
||||
QuestReplayFlowRewardGroupId int32 `json:"QuestReplayFlowRewardGroupId"`
|
||||
InvisibleQuestMissionGroupId int32 `json:"InvisibleQuestMissionGroupId"`
|
||||
FieldEffectGroupId int32 `json:"FieldEffectGroupId"`
|
||||
}
|
||||
|
||||
type QuestMissionRow struct {
|
||||
QuestMissionId int32 `json:"QuestMissionId"`
|
||||
QuestMissionConditionType model.QuestMissionConditionType `json:"QuestMissionConditionType"`
|
||||
QuestMissionRewardId int32 `json:"QuestMissionRewardId"`
|
||||
QuestMissionConditionValueGroupId int32 `json:"QuestMissionConditionValueGroupId"`
|
||||
}
|
||||
|
||||
type QuestMissionGroupRow struct {
|
||||
QuestMissionGroupId int32 `json:"QuestMissionGroupId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
QuestMissionId int32 `json:"QuestMissionId"`
|
||||
}
|
||||
|
||||
type QuestMissionRewardRow struct {
|
||||
QuestMissionRewardId int32 `json:"QuestMissionRewardId"`
|
||||
PossessionType model.PossessionType `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type MainQuestSequenceRow struct {
|
||||
MainQuestSequenceId int32 `json:"MainQuestSequenceId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
QuestId int32 `json:"QuestId"`
|
||||
}
|
||||
|
||||
type MainQuestRouteRow struct {
|
||||
MainQuestRouteId int32 `json:"MainQuestRouteId"`
|
||||
MainQuestSeasonId int32 `json:"MainQuestSeasonId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
CharacterId int32 `json:"CharacterId"`
|
||||
}
|
||||
|
||||
type MainQuestChapterRow struct {
|
||||
MainQuestChapterId int32 `json:"MainQuestChapterId"`
|
||||
MainQuestRouteId int32 `json:"MainQuestRouteId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
MainQuestSequenceGroupId int32 `json:"MainQuestSequenceGroupId"`
|
||||
PortalCageCharacterGroupId int32 `json:"PortalCageCharacterGroupId"`
|
||||
StartDatetime int64 `json:"StartDatetime"`
|
||||
IsInvisibleInLibrary bool `json:"IsInvisibleInLibrary"`
|
||||
JoinLibraryChapterId int32 `json:"JoinLibraryChapterId"`
|
||||
}
|
||||
|
||||
type QuestFirstClearRewardSwitchRow struct {
|
||||
QuestId int32 `json:"QuestId"`
|
||||
QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
|
||||
SwitchConditionClearQuestId int32 `json:"SwitchConditionClearQuestId"`
|
||||
}
|
||||
|
||||
type QuestFirstClearRewardGroupRow struct {
|
||||
QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
|
||||
QuestFirstClearRewardType int32 `json:"QuestFirstClearRewardType"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
PossessionType model.PossessionType `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
IsPickup bool `json:"IsPickup"`
|
||||
}
|
||||
|
||||
type QuestReplayFlowRewardGroupRow struct {
|
||||
QuestReplayFlowRewardGroupId int32 `json:"QuestReplayFlowRewardGroupId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
PossessionType model.PossessionType `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type QuestSceneGrantRow struct {
|
||||
QuestSceneId int32 `json:"QuestSceneId"`
|
||||
PossessionType model.PossessionType `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type QuestPickupRewardGroupRow struct {
|
||||
QuestPickupRewardGroupId int32 `json:"QuestPickupRewardGroupId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
BattleDropRewardId int32 `json:"BattleDropRewardId"`
|
||||
}
|
||||
|
||||
type BattleDropRewardRow struct {
|
||||
BattleDropRewardId int32 `json:"BattleDropRewardId"`
|
||||
PossessionType model.PossessionType `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type QuestSceneBattleRow struct {
|
||||
QuestSceneId int32 `json:"QuestSceneId"`
|
||||
BattleGroupId int32 `json:"BattleGroupId"`
|
||||
}
|
||||
|
||||
type BattleGroupRow struct {
|
||||
BattleGroupId int32 `json:"BattleGroupId"`
|
||||
WaveNumber int32 `json:"WaveNumber"`
|
||||
BattleId int32 `json:"BattleId"`
|
||||
}
|
||||
|
||||
type BattleRow struct {
|
||||
BattleId int32 `json:"BattleId"`
|
||||
BattleNpcId int32 `json:"BattleNpcId"`
|
||||
DeckType model.DeckType `json:"DeckType"`
|
||||
BattleNpcDeckNumber int32 `json:"BattleNpcDeckNumber"`
|
||||
}
|
||||
|
||||
type BattleNpcDeckRow struct {
|
||||
BattleNpcId int32 `json:"BattleNpcId"`
|
||||
DeckType model.DeckType `json:"DeckType"`
|
||||
BattleNpcDeckNumber int32 `json:"BattleNpcDeckNumber"`
|
||||
BattleNpcDeckCharacterUuid01 string `json:"BattleNpcDeckCharacterUuid01"`
|
||||
BattleNpcDeckCharacterUuid02 string `json:"BattleNpcDeckCharacterUuid02"`
|
||||
BattleNpcDeckCharacterUuid03 string `json:"BattleNpcDeckCharacterUuid03"`
|
||||
}
|
||||
|
||||
type BattleNpcDropCategoryRow struct {
|
||||
BattleNpcId int32 `json:"BattleNpcId"`
|
||||
BattleNpcDeckCharacterUuid string `json:"BattleNpcDeckCharacterUuid"`
|
||||
BattleDropCategoryId int32 `json:"BattleDropCategoryId"`
|
||||
}
|
||||
|
||||
type BattleDropInfo struct {
|
||||
QuestSceneId int32
|
||||
BattleDropCategoryId int32
|
||||
}
|
||||
|
||||
type TutorialUnlockConditionRow struct {
|
||||
TutorialType int32 `json:"TutorialType"`
|
||||
TutorialUnlockConditionType int32 `json:"TutorialUnlockConditionType"`
|
||||
ConditionValue int32 `json:"ConditionValue"`
|
||||
}
|
||||
|
||||
type RentalDeckRow struct {
|
||||
BattleGroupId int32 `json:"BattleGroupId"`
|
||||
}
|
||||
|
||||
type UserLevelRow struct {
|
||||
UserLevel int32 `json:"UserLevel"`
|
||||
MaxStamina int32 `json:"MaxStamina"`
|
||||
}
|
||||
|
||||
type QuestCatalog struct {
|
||||
SceneById map[int32]QuestSceneRow
|
||||
MissionById map[int32]QuestMissionRow
|
||||
QuestById map[int32]QuestRow
|
||||
MissionIdsByQuestId map[int32][]int32
|
||||
RouteIdByQuestId map[int32]int32
|
||||
SceneIdsByQuestId map[int32][]int32
|
||||
OrderedQuestIds []int32
|
||||
FirstClearRewardsByGroupId map[int32][]QuestFirstClearRewardGroupRow
|
||||
FirstClearRewardSwitchesByQuestId map[int32][]QuestFirstClearRewardSwitchRow
|
||||
MissionRewardsByMissionId map[int32][]QuestMissionRewardRow
|
||||
WeaponIdsByReleaseConditionGroupId map[int32][]int32
|
||||
ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow
|
||||
SceneGrantsBySceneId map[int32][]QuestSceneGrantRow
|
||||
BattleDropRewardById map[int32]BattleDropRewardRow
|
||||
PickupRewardIdsByGroupId map[int32][]int32
|
||||
BattleDropsByQuestId map[int32][]BattleDropInfo
|
||||
ReplayFlowRewardsByGroupId map[int32][]QuestReplayFlowRewardGroupRow
|
||||
RentalQuestIds map[int32]bool
|
||||
TutorialUnlockConditions []TutorialUnlockConditionRow
|
||||
ChapterLastSceneByQuestId map[int32]int32
|
||||
SeasonIdByRouteId map[int32]int32
|
||||
|
||||
UserExpThresholds []int32
|
||||
CharacterExpThresholds []int32
|
||||
CostumeExpByRarity map[int32][]int32
|
||||
CostumeMaxLevelByRarity map[int32]NumericalFunc
|
||||
MaxStaminaByLevel map[int32]int32
|
||||
|
||||
CostumeById map[int32]CostumeMasterRow
|
||||
WeaponById map[int32]WeaponMasterRow
|
||||
|
||||
WeaponSkillSlots map[int32][]int32
|
||||
WeaponAbilitySlots map[int32][]int32
|
||||
|
||||
*PartsCatalog
|
||||
}
|
||||
|
||||
func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
||||
scenes, err := utils.ReadJSON[QuestSceneRow]("EntityMQuestSceneTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest scene table: %w", err)
|
||||
}
|
||||
sort.Slice(scenes, func(i, j int) bool {
|
||||
if scenes[i].QuestId != scenes[j].QuestId {
|
||||
return scenes[i].QuestId < scenes[j].QuestId
|
||||
}
|
||||
if scenes[i].SortOrder != scenes[j].SortOrder {
|
||||
return scenes[i].SortOrder < scenes[j].SortOrder
|
||||
}
|
||||
return scenes[i].QuestSceneId < scenes[j].QuestSceneId
|
||||
})
|
||||
|
||||
missions, err := utils.ReadJSON[QuestMissionRow]("EntityMQuestMissionTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest mission table: %w", err)
|
||||
}
|
||||
|
||||
quests, err := utils.ReadJSON[QuestRow]("EntityMQuestTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest table: %w", err)
|
||||
}
|
||||
|
||||
missionGroups, err := utils.ReadJSON[QuestMissionGroupRow]("EntityMQuestMissionGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest mission group table: %w", err)
|
||||
}
|
||||
sort.Slice(missionGroups, func(i, j int) bool {
|
||||
if missionGroups[i].QuestMissionGroupId != missionGroups[j].QuestMissionGroupId {
|
||||
return missionGroups[i].QuestMissionGroupId < missionGroups[j].QuestMissionGroupId
|
||||
}
|
||||
if missionGroups[i].SortOrder != missionGroups[j].SortOrder {
|
||||
return missionGroups[i].SortOrder < missionGroups[j].SortOrder
|
||||
}
|
||||
return missionGroups[i].QuestMissionId < missionGroups[j].QuestMissionId
|
||||
})
|
||||
|
||||
sequences, err := utils.ReadJSON[MainQuestSequenceRow]("EntityMMainQuestSequenceTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load main quest sequence table: %w", err)
|
||||
}
|
||||
sort.Slice(sequences, func(i, j int) bool {
|
||||
if sequences[i].MainQuestSequenceId != sequences[j].MainQuestSequenceId {
|
||||
return sequences[i].MainQuestSequenceId < sequences[j].MainQuestSequenceId
|
||||
}
|
||||
if sequences[i].SortOrder != sequences[j].SortOrder {
|
||||
return sequences[i].SortOrder < sequences[j].SortOrder
|
||||
}
|
||||
return sequences[i].QuestId < sequences[j].QuestId
|
||||
})
|
||||
|
||||
chapters, err := utils.ReadJSON[MainQuestChapterRow]("EntityMMainQuestChapterTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load main quest chapter table: %w", err)
|
||||
}
|
||||
|
||||
routes, err := utils.ReadJSON[MainQuestRouteRow]("EntityMMainQuestRouteTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load main quest route table: %w", err)
|
||||
}
|
||||
seasonIdByRouteId := make(map[int32]int32, len(routes))
|
||||
for _, r := range routes {
|
||||
seasonIdByRouteId[r.MainQuestRouteId] = r.MainQuestSeasonId
|
||||
}
|
||||
|
||||
firstClearSwitches, err := utils.ReadJSON[QuestFirstClearRewardSwitchRow]("EntityMQuestFirstClearRewardSwitchTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest first clear reward switch table: %w", err)
|
||||
}
|
||||
|
||||
firstClearRewards, err := utils.ReadJSON[QuestFirstClearRewardGroupRow]("EntityMQuestFirstClearRewardGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest first clear reward group table: %w", err)
|
||||
}
|
||||
sort.Slice(firstClearRewards, func(i, j int) bool {
|
||||
if firstClearRewards[i].QuestFirstClearRewardGroupId != firstClearRewards[j].QuestFirstClearRewardGroupId {
|
||||
return firstClearRewards[i].QuestFirstClearRewardGroupId < firstClearRewards[j].QuestFirstClearRewardGroupId
|
||||
}
|
||||
if firstClearRewards[i].SortOrder != firstClearRewards[j].SortOrder {
|
||||
return firstClearRewards[i].SortOrder < firstClearRewards[j].SortOrder
|
||||
}
|
||||
return firstClearRewards[i].QuestFirstClearRewardType < firstClearRewards[j].QuestFirstClearRewardType
|
||||
})
|
||||
|
||||
replayFlowRewards, err := utils.ReadJSON[QuestReplayFlowRewardGroupRow]("EntityMQuestReplayFlowRewardGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest replay flow reward group table: %w", err)
|
||||
}
|
||||
sort.Slice(replayFlowRewards, func(i, j int) bool {
|
||||
if replayFlowRewards[i].QuestReplayFlowRewardGroupId != replayFlowRewards[j].QuestReplayFlowRewardGroupId {
|
||||
return replayFlowRewards[i].QuestReplayFlowRewardGroupId < replayFlowRewards[j].QuestReplayFlowRewardGroupId
|
||||
}
|
||||
return replayFlowRewards[i].SortOrder < replayFlowRewards[j].SortOrder
|
||||
})
|
||||
|
||||
missionRewards, err := utils.ReadJSON[QuestMissionRewardRow]("EntityMQuestMissionRewardTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest mission reward table: %w", err)
|
||||
}
|
||||
|
||||
weapons, err := utils.ReadJSON[WeaponMasterRow]("EntityMWeaponTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon table: %w", err)
|
||||
}
|
||||
|
||||
weaponSkillGroups, err := utils.ReadJSON[WeaponSkillGroupRow]("EntityMWeaponSkillGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon skill group table: %w", err)
|
||||
}
|
||||
|
||||
weaponAbilityGroups, err := utils.ReadJSON[WeaponAbilityGroupRow]("EntityMWeaponAbilityGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon ability group table: %w", err)
|
||||
}
|
||||
|
||||
releaseConditions, err := utils.ReadJSON[WeaponStoryReleaseConditionRow]("EntityMWeaponStoryReleaseConditionGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon story release condition table: %w", err)
|
||||
}
|
||||
|
||||
costumeMasters, err := utils.ReadJSON[CostumeMasterRow]("EntityMCostumeTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume table: %w", err)
|
||||
}
|
||||
|
||||
costumeRarities, err := utils.ReadJSON[costumeRarityRow]("EntityMCostumeRarityTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume rarity table: %w", err)
|
||||
}
|
||||
|
||||
sceneGrants, err := utils.ReadJSON[QuestSceneGrantRow]("EntityMUserQuestSceneGrantPossessionTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest scene grant table: %w", err)
|
||||
}
|
||||
|
||||
battleDropRewards, err := utils.ReadJSON[BattleDropRewardRow]("EntityMBattleDropRewardTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load battle drop reward table: %w", err)
|
||||
}
|
||||
|
||||
pickupRewardGroups, err := utils.ReadJSON[QuestPickupRewardGroupRow]("EntityMQuestPickupRewardGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest pickup reward group table: %w", err)
|
||||
}
|
||||
sort.Slice(pickupRewardGroups, func(i, j int) bool {
|
||||
if pickupRewardGroups[i].QuestPickupRewardGroupId != pickupRewardGroups[j].QuestPickupRewardGroupId {
|
||||
return pickupRewardGroups[i].QuestPickupRewardGroupId < pickupRewardGroups[j].QuestPickupRewardGroupId
|
||||
}
|
||||
return pickupRewardGroups[i].SortOrder < pickupRewardGroups[j].SortOrder
|
||||
})
|
||||
|
||||
sceneBattles, err := utils.ReadJSON[QuestSceneBattleRow]("EntityMQuestSceneBattleTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest scene battle table: %w", err)
|
||||
}
|
||||
|
||||
battleGroups, err := utils.ReadJSON[BattleGroupRow]("EntityMBattleGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load battle group table: %w", err)
|
||||
}
|
||||
|
||||
battles, err := utils.ReadJSON[BattleRow]("EntityMBattleTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load battle table: %w", err)
|
||||
}
|
||||
|
||||
npcDecks, err := utils.ReadJSON[BattleNpcDeckRow]("EntityMBattleNpcDeckTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load battle npc deck table: %w", err)
|
||||
}
|
||||
|
||||
npcDropCategories, err := utils.ReadJSON[BattleNpcDropCategoryRow]("EntityMBattleNpcDeckCharacterDropCategoryTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load battle npc drop category table: %w", err)
|
||||
}
|
||||
|
||||
rentalDecks, err := utils.ReadJSON[RentalDeckRow]("EntityMBattleRentalDeckTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load battle rental deck table: %w", err)
|
||||
}
|
||||
|
||||
tutorialUnlockConds, err := utils.ReadJSON[TutorialUnlockConditionRow]("EntityMTutorialUnlockConditionTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load tutorial unlock condition table: %w", err)
|
||||
}
|
||||
|
||||
paramMapRows, err := LoadParameterMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userLevels, err := utils.ReadJSON[UserLevelRow]("EntityMUserLevelTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load user level table: %w", err)
|
||||
}
|
||||
maxStaminaByLevel := make(map[int32]int32, len(userLevels))
|
||||
for _, ul := range userLevels {
|
||||
maxStaminaByLevel[ul.UserLevel] = ul.MaxStamina
|
||||
}
|
||||
|
||||
funcResolver, err := LoadFunctionResolver()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||
}
|
||||
|
||||
costumeExpByRarity := make(map[int32][]int32, len(costumeRarities))
|
||||
costumeMaxLevelByRarity := make(map[int32]NumericalFunc, len(costumeRarities))
|
||||
for _, r := range costumeRarities {
|
||||
if _, ok := costumeExpByRarity[r.RarityType]; !ok {
|
||||
costumeExpByRarity[r.RarityType] = BuildExpThresholds(paramMapRows, r.RequiredExpForLevelUpNumericalParameterMapId)
|
||||
}
|
||||
if _, ok := costumeMaxLevelByRarity[r.RarityType]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.MaxLevelNumericalFunctionId); found {
|
||||
costumeMaxLevelByRarity[r.RarityType] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
costumeById := make(map[int32]CostumeMasterRow, len(costumeMasters))
|
||||
for _, cm := range costumeMasters {
|
||||
costumeById[cm.CostumeId] = cm
|
||||
}
|
||||
|
||||
weaponById := make(map[int32]WeaponMasterRow, len(weapons))
|
||||
for _, w := range weapons {
|
||||
weaponById[w.WeaponId] = w
|
||||
}
|
||||
|
||||
skillSlots := make(map[int32][]int32)
|
||||
for _, row := range weaponSkillGroups {
|
||||
skillSlots[row.WeaponSkillGroupId] = append(skillSlots[row.WeaponSkillGroupId], row.SlotNumber)
|
||||
}
|
||||
abilitySlots := make(map[int32][]int32)
|
||||
for _, row := range weaponAbilityGroups {
|
||||
abilitySlots[row.WeaponAbilityGroupId] = append(abilitySlots[row.WeaponAbilityGroupId], row.SlotNumber)
|
||||
}
|
||||
|
||||
sceneById := make(map[int32]QuestSceneRow, len(scenes))
|
||||
sceneIdsByQuestId := make(map[int32][]int32)
|
||||
for _, scene := range scenes {
|
||||
sceneById[scene.QuestSceneId] = scene
|
||||
sceneIdsByQuestId[scene.QuestId] = append(sceneIdsByQuestId[scene.QuestId], scene.QuestSceneId)
|
||||
}
|
||||
|
||||
missionById := make(map[int32]QuestMissionRow, len(missions))
|
||||
for _, mission := range missions {
|
||||
missionById[mission.QuestMissionId] = mission
|
||||
}
|
||||
|
||||
questById := make(map[int32]QuestRow, len(quests))
|
||||
for _, quest := range quests {
|
||||
questById[quest.QuestId] = quest
|
||||
}
|
||||
|
||||
missionIdsByGroupId := make(map[int32][]int32, len(missionGroups))
|
||||
for _, mg := range missionGroups {
|
||||
missionIdsByGroupId[mg.QuestMissionGroupId] = append(
|
||||
missionIdsByGroupId[mg.QuestMissionGroupId], mg.QuestMissionId)
|
||||
}
|
||||
missionIdsByQuestId := make(map[int32][]int32)
|
||||
for questId, quest := range questById {
|
||||
missionIds := missionIdsByGroupId[quest.QuestMissionGroupId]
|
||||
if len(missionIds) == 0 {
|
||||
continue
|
||||
}
|
||||
missionIdsByQuestId[questId] = append([]int32(nil), missionIds...)
|
||||
}
|
||||
|
||||
chapterBySequenceId := make(map[int32]MainQuestChapterRow, len(chapters))
|
||||
for _, chapter := range chapters {
|
||||
chapterBySequenceId[chapter.MainQuestSequenceGroupId] = chapter
|
||||
}
|
||||
routeIdByQuestId := make(map[int32]int32)
|
||||
for _, sequence := range sequences {
|
||||
if chapter, ok := chapterBySequenceId[sequence.MainQuestSequenceId]; ok {
|
||||
routeIdByQuestId[sequence.QuestId] = chapter.MainQuestRouteId
|
||||
}
|
||||
}
|
||||
|
||||
sortedChapters := make([]MainQuestChapterRow, len(chapters))
|
||||
copy(sortedChapters, chapters)
|
||||
sort.Slice(sortedChapters, func(i, j int) bool {
|
||||
return sortedChapters[i].SortOrder < sortedChapters[j].SortOrder
|
||||
})
|
||||
sequencesByGroupId := make(map[int32][]MainQuestSequenceRow)
|
||||
for _, seq := range sequences {
|
||||
sequencesByGroupId[seq.MainQuestSequenceId] = append(sequencesByGroupId[seq.MainQuestSequenceId], seq)
|
||||
}
|
||||
var orderedQuestIds []int32
|
||||
for _, chapter := range sortedChapters {
|
||||
for _, seq := range sequencesByGroupId[chapter.MainQuestSequenceGroupId] {
|
||||
orderedQuestIds = append(orderedQuestIds, seq.QuestId)
|
||||
}
|
||||
}
|
||||
|
||||
chapterLastSceneByQuestId := make(map[int32]int32)
|
||||
for _, chapter := range sortedChapters {
|
||||
seqs := sequencesByGroupId[chapter.MainQuestSequenceGroupId]
|
||||
var chapterLastScene int32
|
||||
for i := len(seqs) - 1; i >= 0; i-- {
|
||||
if sids := sceneIdsByQuestId[seqs[i].QuestId]; len(sids) > 0 {
|
||||
chapterLastScene = sids[len(sids)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if chapterLastScene != 0 {
|
||||
for _, seq := range seqs {
|
||||
chapterLastSceneByQuestId[seq.QuestId] = chapterLastScene
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
firstClearRewardsByGroupId := make(map[int32][]QuestFirstClearRewardGroupRow, len(firstClearRewards))
|
||||
for _, reward := range firstClearRewards {
|
||||
firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId] = append(
|
||||
firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId], reward)
|
||||
}
|
||||
|
||||
replayFlowRewardsByGroupId := make(map[int32][]QuestReplayFlowRewardGroupRow, len(replayFlowRewards))
|
||||
for _, reward := range replayFlowRewards {
|
||||
replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId] = append(
|
||||
replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId], reward)
|
||||
}
|
||||
|
||||
firstClearRewardSwitchesByQuestId := make(map[int32][]QuestFirstClearRewardSwitchRow, len(firstClearSwitches))
|
||||
for _, switchRow := range firstClearSwitches {
|
||||
firstClearRewardSwitchesByQuestId[switchRow.QuestId] = append(
|
||||
firstClearRewardSwitchesByQuestId[switchRow.QuestId], switchRow)
|
||||
}
|
||||
|
||||
missionRewardsByMissionId := make(map[int32][]QuestMissionRewardRow, len(missionRewards))
|
||||
for _, reward := range missionRewards {
|
||||
missionRewardsByMissionId[reward.QuestMissionRewardId] = append(
|
||||
missionRewardsByMissionId[reward.QuestMissionRewardId], reward)
|
||||
}
|
||||
|
||||
weaponIdsByReleaseConditionGroupId := make(map[int32][]int32)
|
||||
for _, w := range weaponById {
|
||||
if w.WeaponStoryReleaseConditionGroupId != 0 {
|
||||
weaponIdsByReleaseConditionGroupId[w.WeaponStoryReleaseConditionGroupId] = append(
|
||||
weaponIdsByReleaseConditionGroupId[w.WeaponStoryReleaseConditionGroupId], w.WeaponId)
|
||||
}
|
||||
}
|
||||
|
||||
releaseConditionsByGroupId := make(map[int32][]WeaponStoryReleaseConditionRow)
|
||||
for _, c := range releaseConditions {
|
||||
releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId] = append(
|
||||
releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId], c)
|
||||
}
|
||||
|
||||
sceneGrantsBySceneId := make(map[int32][]QuestSceneGrantRow)
|
||||
for _, sg := range sceneGrants {
|
||||
sceneGrantsBySceneId[sg.QuestSceneId] = append(sceneGrantsBySceneId[sg.QuestSceneId], sg)
|
||||
}
|
||||
|
||||
battleDropRewardById := make(map[int32]BattleDropRewardRow, len(battleDropRewards))
|
||||
for _, bdr := range battleDropRewards {
|
||||
battleDropRewardById[bdr.BattleDropRewardId] = bdr
|
||||
}
|
||||
|
||||
pickupRewardIdsByGroupId := make(map[int32][]int32)
|
||||
for _, pg := range pickupRewardGroups {
|
||||
pickupRewardIdsByGroupId[pg.QuestPickupRewardGroupId] = append(
|
||||
pickupRewardIdsByGroupId[pg.QuestPickupRewardGroupId], pg.BattleDropRewardId)
|
||||
}
|
||||
|
||||
battleGroupBySceneId := make(map[int32]int32, len(sceneBattles))
|
||||
for _, sb := range sceneBattles {
|
||||
battleGroupBySceneId[sb.QuestSceneId] = sb.BattleGroupId
|
||||
}
|
||||
|
||||
battleIdsByGroupId := make(map[int32][]int32)
|
||||
for _, bg := range battleGroups {
|
||||
battleIdsByGroupId[bg.BattleGroupId] = append(battleIdsByGroupId[bg.BattleGroupId], bg.BattleId)
|
||||
}
|
||||
|
||||
type npcDeckKey struct {
|
||||
BattleNpcId int32
|
||||
DeckType model.DeckType
|
||||
BattleNpcDeckNumber int32
|
||||
}
|
||||
npcDeckByKey := make(map[npcDeckKey]BattleNpcDeckRow, len(npcDecks))
|
||||
for _, d := range npcDecks {
|
||||
npcDeckByKey[npcDeckKey{d.BattleNpcId, d.DeckType, d.BattleNpcDeckNumber}] = d
|
||||
}
|
||||
|
||||
battleByIdMap := make(map[int32]BattleRow, len(battles))
|
||||
for _, b := range battles {
|
||||
battleByIdMap[b.BattleId] = b
|
||||
}
|
||||
|
||||
type dropCatKey struct {
|
||||
BattleNpcId int32
|
||||
Uuid string
|
||||
}
|
||||
dropCategoryByKey := make(map[dropCatKey]int32, len(npcDropCategories))
|
||||
for _, dc := range npcDropCategories {
|
||||
dropCategoryByKey[dropCatKey{dc.BattleNpcId, dc.BattleNpcDeckCharacterUuid}] = dc.BattleDropCategoryId
|
||||
}
|
||||
|
||||
battleDropsByQuestId := make(map[int32][]BattleDropInfo)
|
||||
for questId := range questById {
|
||||
sids := sceneIdsByQuestId[questId]
|
||||
seen := make(map[BattleDropInfo]bool)
|
||||
var drops []BattleDropInfo
|
||||
for _, sceneId := range sids {
|
||||
groupId, ok := battleGroupBySceneId[sceneId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, battleId := range battleIdsByGroupId[groupId] {
|
||||
b, ok := battleByIdMap[battleId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
dk := npcDeckKey{b.BattleNpcId, b.DeckType, b.BattleNpcDeckNumber}
|
||||
deck, ok := npcDeckByKey[dk]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, uuid := range []string{deck.BattleNpcDeckCharacterUuid01, deck.BattleNpcDeckCharacterUuid02, deck.BattleNpcDeckCharacterUuid03} {
|
||||
if uuid == "" {
|
||||
continue
|
||||
}
|
||||
catId, ok := dropCategoryByKey[dropCatKey{b.BattleNpcId, uuid}]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
info := BattleDropInfo{QuestSceneId: sceneId, BattleDropCategoryId: catId}
|
||||
if !seen[info] {
|
||||
seen[info] = true
|
||||
drops = append(drops, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(drops) > 0 {
|
||||
battleDropsByQuestId[questId] = drops
|
||||
}
|
||||
}
|
||||
|
||||
rentalBattleGroups := make(map[int32]bool, len(rentalDecks))
|
||||
for _, rd := range rentalDecks {
|
||||
rentalBattleGroups[rd.BattleGroupId] = true
|
||||
}
|
||||
rentalQuestIds := make(map[int32]bool)
|
||||
for questId := range questById {
|
||||
for _, sceneId := range sceneIdsByQuestId[questId] {
|
||||
if groupId, ok := battleGroupBySceneId[sceneId]; ok && rentalBattleGroups[groupId] {
|
||||
rentalQuestIds[questId] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &QuestCatalog{
|
||||
SceneById: sceneById,
|
||||
MissionById: missionById,
|
||||
QuestById: questById,
|
||||
MissionIdsByQuestId: missionIdsByQuestId,
|
||||
RouteIdByQuestId: routeIdByQuestId,
|
||||
SceneIdsByQuestId: sceneIdsByQuestId,
|
||||
OrderedQuestIds: orderedQuestIds,
|
||||
FirstClearRewardsByGroupId: firstClearRewardsByGroupId,
|
||||
FirstClearRewardSwitchesByQuestId: firstClearRewardSwitchesByQuestId,
|
||||
MissionRewardsByMissionId: missionRewardsByMissionId,
|
||||
WeaponIdsByReleaseConditionGroupId: weaponIdsByReleaseConditionGroupId,
|
||||
ReleaseConditionsByGroupId: releaseConditionsByGroupId,
|
||||
SceneGrantsBySceneId: sceneGrantsBySceneId,
|
||||
BattleDropRewardById: battleDropRewardById,
|
||||
PickupRewardIdsByGroupId: pickupRewardIdsByGroupId,
|
||||
BattleDropsByQuestId: battleDropsByQuestId,
|
||||
ReplayFlowRewardsByGroupId: replayFlowRewardsByGroupId,
|
||||
RentalQuestIds: rentalQuestIds,
|
||||
TutorialUnlockConditions: tutorialUnlockConds,
|
||||
ChapterLastSceneByQuestId: chapterLastSceneByQuestId,
|
||||
SeasonIdByRouteId: seasonIdByRouteId,
|
||||
|
||||
UserExpThresholds: BuildExpThresholds(paramMapRows, 1),
|
||||
CharacterExpThresholds: BuildExpThresholds(paramMapRows, 31),
|
||||
CostumeExpByRarity: costumeExpByRarity,
|
||||
CostumeMaxLevelByRarity: costumeMaxLevelByRarity,
|
||||
MaxStaminaByLevel: maxStaminaByLevel,
|
||||
|
||||
CostumeById: costumeById,
|
||||
WeaponById: weaponById,
|
||||
|
||||
WeaponSkillSlots: skillSlots,
|
||||
WeaponAbilitySlots: abilitySlots,
|
||||
|
||||
PartsCatalog: partsCatalog,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type ShopItemRow struct {
|
||||
ShopItemId int32 `json:"ShopItemId"`
|
||||
PriceType int32 `json:"PriceType"`
|
||||
PriceId int32 `json:"PriceId"`
|
||||
Price int32 `json:"Price"`
|
||||
ShopItemLimitedStockId int32 `json:"ShopItemLimitedStockId"`
|
||||
}
|
||||
|
||||
type ShopContentRow struct {
|
||||
ShopItemId int32 `json:"ShopItemId"`
|
||||
PossessionType int32 `json:"PossessionType"`
|
||||
PossessionId int32 `json:"PossessionId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type ShopContentEffectRow struct {
|
||||
ShopItemId int32 `json:"ShopItemId"`
|
||||
EffectTargetType int32 `json:"EffectTargetType"`
|
||||
EffectValueType int32 `json:"EffectValueType"`
|
||||
EffectValue int32 `json:"EffectValue"`
|
||||
}
|
||||
|
||||
type shopItemLimitedStockRow struct {
|
||||
ShopItemLimitedStockId int32 `json:"ShopItemLimitedStockId"`
|
||||
MaxCount int32 `json:"MaxCount"`
|
||||
}
|
||||
|
||||
type shopRow struct {
|
||||
ShopId int32 `json:"ShopId"`
|
||||
ShopGroupType int32 `json:"ShopGroupType"`
|
||||
ShopItemCellGroupId int32 `json:"ShopItemCellGroupId"`
|
||||
}
|
||||
|
||||
type shopItemCellGroupRow struct {
|
||||
ShopItemCellGroupId int32 `json:"ShopItemCellGroupId"`
|
||||
ShopItemCellId int32 `json:"ShopItemCellId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
}
|
||||
|
||||
type shopItemCellRow struct {
|
||||
ShopItemCellId int32 `json:"ShopItemCellId"`
|
||||
ShopItemId int32 `json:"ShopItemId"`
|
||||
}
|
||||
|
||||
type ExchangeShopCell struct {
|
||||
SortOrder int32
|
||||
ShopItemId int32
|
||||
}
|
||||
|
||||
type ShopCatalog struct {
|
||||
Items map[int32]ShopItemRow
|
||||
Contents map[int32][]ShopContentRow
|
||||
Effects map[int32][]ShopContentEffectRow
|
||||
MaxStaminaMillis map[int32]int32 // level -> max stamina in millis
|
||||
LimitedStock map[int32]int32 // stock id -> max count
|
||||
ItemShopPool []int32 // shop item IDs for the replaceable item shop, sorted by cell sort order
|
||||
ExchangeShopCells map[int32][]ExchangeShopCell // shopId -> sorted cells for exchange shops
|
||||
}
|
||||
|
||||
type userLevelEntry struct {
|
||||
UserLevel int32 `json:"UserLevel"`
|
||||
MaxStamina int32 `json:"MaxStamina"`
|
||||
}
|
||||
|
||||
func LoadShopCatalog() (*ShopCatalog, error) {
|
||||
items, err := utils.ReadJSON[ShopItemRow]("EntityMShopItemTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load shop item table: %w", err)
|
||||
}
|
||||
contents, err := utils.ReadJSON[ShopContentRow]("EntityMShopItemContentPossessionTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load shop content possession table: %w", err)
|
||||
}
|
||||
effects, err := utils.ReadJSON[ShopContentEffectRow]("EntityMShopItemContentEffectTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load shop content effect table: %w", err)
|
||||
}
|
||||
userLevels, err := utils.ReadJSON[userLevelEntry]("EntityMUserLevelTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load user level table: %w", err)
|
||||
}
|
||||
stockRows, err := utils.ReadJSON[shopItemLimitedStockRow]("EntityMShopItemLimitedStockTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load shop item limited stock table: %w", err)
|
||||
}
|
||||
|
||||
catalog := &ShopCatalog{
|
||||
Items: make(map[int32]ShopItemRow, len(items)),
|
||||
Contents: make(map[int32][]ShopContentRow, len(contents)),
|
||||
Effects: make(map[int32][]ShopContentEffectRow, len(effects)),
|
||||
MaxStaminaMillis: make(map[int32]int32, len(userLevels)),
|
||||
LimitedStock: make(map[int32]int32, len(stockRows)),
|
||||
}
|
||||
for _, row := range items {
|
||||
catalog.Items[row.ShopItemId] = row
|
||||
}
|
||||
for _, row := range contents {
|
||||
catalog.Contents[row.ShopItemId] = append(catalog.Contents[row.ShopItemId], row)
|
||||
}
|
||||
for _, row := range effects {
|
||||
catalog.Effects[row.ShopItemId] = append(catalog.Effects[row.ShopItemId], row)
|
||||
}
|
||||
for _, ul := range userLevels {
|
||||
catalog.MaxStaminaMillis[ul.UserLevel] = ul.MaxStamina * 1000
|
||||
}
|
||||
for _, row := range stockRows {
|
||||
catalog.LimitedStock[row.ShopItemLimitedStockId] = row.MaxCount
|
||||
}
|
||||
|
||||
shops, err := utils.ReadJSON[shopRow]("EntityMShopTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load shop table: %w", err)
|
||||
}
|
||||
cellGroups, err := utils.ReadJSON[shopItemCellGroupRow]("EntityMShopItemCellGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load shop item cell group table: %w", err)
|
||||
}
|
||||
cells, err := utils.ReadJSON[shopItemCellRow]("EntityMShopItemCellTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load shop item cell table: %w", err)
|
||||
}
|
||||
|
||||
cellIdToItemId := make(map[int32]int32, len(cells))
|
||||
for _, c := range cells {
|
||||
cellIdToItemId[c.ShopItemCellId] = c.ShopItemId
|
||||
}
|
||||
|
||||
cellGroupByCGId := make(map[int32][]shopItemCellGroupRow, len(cellGroups))
|
||||
for _, cg := range cellGroups {
|
||||
cellGroupByCGId[cg.ShopItemCellGroupId] = append(cellGroupByCGId[cg.ShopItemCellGroupId], cg)
|
||||
}
|
||||
|
||||
catalog.ExchangeShopCells = make(map[int32][]ExchangeShopCell)
|
||||
for _, s := range shops {
|
||||
entries := cellGroupByCGId[s.ShopItemCellGroupId]
|
||||
if len(entries) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch s.ShopGroupType {
|
||||
case model.ShopGroupTypeItemShop:
|
||||
var poolCells []ExchangeShopCell
|
||||
for _, cg := range entries {
|
||||
if itemId, ok := cellIdToItemId[cg.ShopItemCellId]; ok {
|
||||
poolCells = append(poolCells, ExchangeShopCell{cg.SortOrder, itemId})
|
||||
}
|
||||
}
|
||||
sort.Slice(poolCells, func(i, j int) bool { return poolCells[i].SortOrder < poolCells[j].SortOrder })
|
||||
catalog.ItemShopPool = make([]int32, len(poolCells))
|
||||
for i, pc := range poolCells {
|
||||
catalog.ItemShopPool[i] = pc.ShopItemId
|
||||
}
|
||||
|
||||
case model.ShopGroupTypeExchangeShop:
|
||||
var sc []ExchangeShopCell
|
||||
for _, cg := range entries {
|
||||
if itemId, ok := cellIdToItemId[cg.ShopItemCellId]; ok {
|
||||
sc = append(sc, ExchangeShopCell{cg.SortOrder, itemId})
|
||||
}
|
||||
}
|
||||
sort.Slice(sc, func(i, j int) bool { return sc[i].SortOrder < sc[j].SortOrder })
|
||||
catalog.ExchangeShopCells[s.ShopId] = sc
|
||||
}
|
||||
}
|
||||
|
||||
return catalog, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"log"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type sideStorySceneRow struct {
|
||||
SideStoryQuestId int32 `json:"SideStoryQuestId"`
|
||||
SideStoryQuestSceneId int32 `json:"SideStoryQuestSceneId"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
}
|
||||
|
||||
type SideStoryCatalog struct {
|
||||
FirstSceneByQuestId map[int32]int32
|
||||
}
|
||||
|
||||
func LoadSideStoryCatalog() *SideStoryCatalog {
|
||||
scenes, err := utils.ReadJSON[sideStorySceneRow]("EntityMSideStoryQuestSceneTable.json")
|
||||
if err != nil {
|
||||
log.Fatalf("load side story quest scene table: %v", err)
|
||||
}
|
||||
|
||||
firstScene := make(map[int32]int32, len(scenes)/7)
|
||||
for _, s := range scenes {
|
||||
if s.SortOrder == 1 {
|
||||
firstScene[s.SideStoryQuestId] = s.SideStoryQuestSceneId
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("side story catalog loaded: %d quests", len(firstScene))
|
||||
return &SideStoryCatalog{FirstSceneByQuestId: firstScene}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package masterdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/utils"
|
||||
)
|
||||
|
||||
type WeaponMasterRow struct {
|
||||
WeaponId int32 `json:"WeaponId"`
|
||||
RarityType int32 `json:"RarityType"`
|
||||
WeaponType int32 `json:"WeaponType"`
|
||||
WeaponSpecificEnhanceId int32 `json:"WeaponSpecificEnhanceId"`
|
||||
WeaponSkillGroupId int32 `json:"WeaponSkillGroupId"`
|
||||
WeaponAbilityGroupId int32 `json:"WeaponAbilityGroupId"`
|
||||
WeaponStoryReleaseConditionGroupId int32 `json:"WeaponStoryReleaseConditionGroupId"`
|
||||
WeaponEvolutionMaterialGroupId int32 `json:"WeaponEvolutionMaterialGroupId"`
|
||||
}
|
||||
|
||||
type WeaponStoryReleaseConditionRow struct {
|
||||
WeaponStoryReleaseConditionGroupId int32 `json:"WeaponStoryReleaseConditionGroupId"`
|
||||
StoryIndex int32 `json:"StoryIndex"`
|
||||
WeaponStoryReleaseConditionType model.WeaponStoryReleaseConditionType `json:"WeaponStoryReleaseConditionType"`
|
||||
ConditionValue int32 `json:"ConditionValue"`
|
||||
WeaponStoryReleaseConditionOperationGroupId int32 `json:"WeaponStoryReleaseConditionOperationGroupId"`
|
||||
}
|
||||
|
||||
type WeaponSkillGroupRow struct {
|
||||
WeaponSkillGroupId int32 `json:"WeaponSkillGroupId"`
|
||||
SlotNumber int32 `json:"SlotNumber"`
|
||||
SkillId int32 `json:"SkillId"`
|
||||
WeaponSkillEnhancementMaterialId int32 `json:"WeaponSkillEnhancementMaterialId"`
|
||||
}
|
||||
|
||||
type WeaponAbilityGroupRow struct {
|
||||
WeaponAbilityGroupId int32 `json:"WeaponAbilityGroupId"`
|
||||
SlotNumber int32 `json:"SlotNumber"`
|
||||
AbilityId int32 `json:"AbilityId"`
|
||||
WeaponAbilityEnhancementMaterialId int32 `json:"WeaponAbilityEnhancementMaterialId"`
|
||||
}
|
||||
|
||||
type weaponSpecificEnhanceRow struct {
|
||||
WeaponSpecificEnhanceId int32 `json:"WeaponSpecificEnhanceId"`
|
||||
BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"`
|
||||
SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
|
||||
RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
|
||||
EnhancementCostByWeaponNumericalFunctionId int32 `json:"EnhancementCostByWeaponNumericalFunctionId"`
|
||||
EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
|
||||
MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
|
||||
EvolutionCostNumericalFunctionId int32 `json:"EvolutionCostNumericalFunctionId"`
|
||||
LimitBreakCostByWeaponNumericalFunctionId int32 `json:"LimitBreakCostByWeaponNumericalFunctionId"`
|
||||
LimitBreakCostByMaterialNumericalFunctionId int32 `json:"LimitBreakCostByMaterialNumericalFunctionId"`
|
||||
MaxSkillLevelNumericalFunctionId int32 `json:"MaxSkillLevelNumericalFunctionId"`
|
||||
SkillEnhancementCostNumericalFunctionId int32 `json:"SkillEnhancementCostNumericalFunctionId"`
|
||||
MaxAbilityLevelNumericalFunctionId int32 `json:"MaxAbilityLevelNumericalFunctionId"`
|
||||
AbilityEnhancementCostNumericalFunctionId int32 `json:"AbilityEnhancementCostNumericalFunctionId"`
|
||||
}
|
||||
|
||||
type weaponConsumeExchangeRow struct {
|
||||
WeaponId int32 `json:"WeaponId"`
|
||||
ConsumableItemId int32 `json:"ConsumableItemId"`
|
||||
Count int32 `json:"Count"`
|
||||
}
|
||||
|
||||
type WeaponEvolutionGroupRow struct {
|
||||
WeaponEvolutionGroupId int32 `json:"WeaponEvolutionGroupId"`
|
||||
EvolutionOrder int32 `json:"EvolutionOrder"`
|
||||
WeaponId int32 `json:"WeaponId"`
|
||||
}
|
||||
|
||||
type WeaponEvolutionMaterialRow struct {
|
||||
WeaponEvolutionMaterialGroupId int32 `json:"WeaponEvolutionMaterialGroupId"`
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
Count int32 `json:"Count"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
}
|
||||
|
||||
type WeaponSkillEnhanceMaterialRow struct {
|
||||
WeaponSkillEnhancementMaterialId int32 `json:"WeaponSkillEnhancementMaterialId"`
|
||||
SkillLevel int32 `json:"SkillLevel"`
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
Count int32 `json:"Count"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
}
|
||||
|
||||
type WeaponAbilityEnhanceMaterialRow struct {
|
||||
WeaponAbilityEnhancementMaterialId int32 `json:"WeaponAbilityEnhancementMaterialId"`
|
||||
AbilityLevel int32 `json:"AbilityLevel"`
|
||||
MaterialId int32 `json:"MaterialId"`
|
||||
Count int32 `json:"Count"`
|
||||
SortOrder int32 `json:"SortOrder"`
|
||||
}
|
||||
|
||||
type weaponRarityEnhanceRow struct {
|
||||
RarityType int32 `json:"RarityType"`
|
||||
BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"`
|
||||
SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
|
||||
RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
|
||||
EnhancementCostByWeaponNumericalFunctionId int32 `json:"EnhancementCostByWeaponNumericalFunctionId"`
|
||||
EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
|
||||
MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
|
||||
EvolutionCostNumericalFunctionId int32 `json:"EvolutionCostNumericalFunctionId"`
|
||||
LimitBreakCostByWeaponNumericalFunctionId int32 `json:"LimitBreakCostByWeaponNumericalFunctionId"`
|
||||
LimitBreakCostByMaterialNumericalFunctionId int32 `json:"LimitBreakCostByMaterialNumericalFunctionId"`
|
||||
MaxSkillLevelNumericalFunctionId int32 `json:"MaxSkillLevelNumericalFunctionId"`
|
||||
SkillEnhancementCostNumericalFunctionId int32 `json:"SkillEnhancementCostNumericalFunctionId"`
|
||||
MaxAbilityLevelNumericalFunctionId int32 `json:"MaxAbilityLevelNumericalFunctionId"`
|
||||
AbilityEnhancementCostNumericalFunctionId int32 `json:"AbilityEnhancementCostNumericalFunctionId"`
|
||||
}
|
||||
|
||||
type WeaponCatalog struct {
|
||||
Weapons map[int32]WeaponMasterRow
|
||||
Materials map[int32]MaterialRow
|
||||
ExpByEnhanceId map[int32][]int32
|
||||
GoldCostByEnhanceId map[int32]NumericalFunc
|
||||
MaxLevelByEnhanceId map[int32]NumericalFunc
|
||||
SellPriceByEnhanceId map[int32]NumericalFunc
|
||||
MedalsByWeaponId map[int32]map[int32]int32 // WeaponId -> ConsumableItemId -> Count
|
||||
EvolutionNextWeaponId map[int32]int32
|
||||
EvolutionOrder map[int32]int32 // WeaponId -> 0-based position in evolution chain
|
||||
EvolutionMaterials map[int32][]WeaponEvolutionMaterialRow // WeaponEvolutionMaterialGroupId -> materials
|
||||
EvolutionCostByEnhanceId map[int32]NumericalFunc
|
||||
AbilitySlots map[int32][]int32 // WeaponAbilityGroupId -> slot numbers
|
||||
SkillGroupsByGroupId map[int32][]WeaponSkillGroupRow
|
||||
SkillEnhanceMats map[[2]int32][]WeaponSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel]
|
||||
SkillMaxLevelByEnhanceId map[int32]NumericalFunc
|
||||
SkillCostByEnhanceId map[int32]NumericalFunc
|
||||
AbilityGroupsByGroupId map[int32][]WeaponAbilityGroupRow
|
||||
AbilityEnhanceMats map[[2]int32][]WeaponAbilityEnhanceMaterialRow // key: [enhancementMaterialId, abilityLevel]
|
||||
AbilityMaxLevelByEnhanceId map[int32]NumericalFunc
|
||||
AbilityCostByEnhanceId map[int32]NumericalFunc
|
||||
EnhanceCostByWeaponByEnhanceId map[int32]NumericalFunc
|
||||
LimitBreakCostByWeaponByEnhanceId map[int32]NumericalFunc
|
||||
LimitBreakCostByMaterialByEnhanceId map[int32]NumericalFunc
|
||||
BaseExpByEnhanceId map[int32]int32
|
||||
ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow
|
||||
}
|
||||
|
||||
func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
|
||||
weapons, err := utils.ReadJSON[WeaponMasterRow]("EntityMWeaponTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon table: %w", err)
|
||||
}
|
||||
|
||||
enhanceRows, err := utils.ReadJSON[weaponSpecificEnhanceRow]("EntityMWeaponSpecificEnhanceTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon specific enhance table: %w", err)
|
||||
}
|
||||
|
||||
rarityEnhanceRows, err := utils.ReadJSON[weaponRarityEnhanceRow]("EntityMWeaponRarityTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon rarity table: %w", err)
|
||||
}
|
||||
|
||||
paramMapRows, err := LoadParameterMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
funcResolver, err := LoadFunctionResolver()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||
}
|
||||
|
||||
exchangeRows, err := utils.ReadJSON[weaponConsumeExchangeRow]("EntityMWeaponConsumeExchangeConsumableItemGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon consume exchange table: %w", err)
|
||||
}
|
||||
|
||||
evoGroupRows, err := utils.ReadJSON[WeaponEvolutionGroupRow]("EntityMWeaponEvolutionGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon evolution group table: %w", err)
|
||||
}
|
||||
evoMatRows, err := utils.ReadJSON[WeaponEvolutionMaterialRow]("EntityMWeaponEvolutionMaterialGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon evolution material group table: %w", err)
|
||||
}
|
||||
abilityGroupRows, err := utils.ReadJSON[WeaponAbilityGroupRow]("EntityMWeaponAbilityGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon ability group table: %w", err)
|
||||
}
|
||||
skillGroupRows, err := utils.ReadJSON[WeaponSkillGroupRow]("EntityMWeaponSkillGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon skill group table: %w", err)
|
||||
}
|
||||
skillMatRows, err := utils.ReadJSON[WeaponSkillEnhanceMaterialRow]("EntityMWeaponSkillEnhancementMaterialTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon skill enhancement material table: %w", err)
|
||||
}
|
||||
abilityMatRows, err := utils.ReadJSON[WeaponAbilityEnhanceMaterialRow]("EntityMWeaponAbilityEnhancementMaterialTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon ability enhancement material table: %w", err)
|
||||
}
|
||||
releaseConditions, err := utils.ReadJSON[WeaponStoryReleaseConditionRow]("EntityMWeaponStoryReleaseConditionGroupTable.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon story release condition table: %w", err)
|
||||
}
|
||||
|
||||
catalog := &WeaponCatalog{
|
||||
Weapons: make(map[int32]WeaponMasterRow, len(weapons)),
|
||||
Materials: matCatalog.ByType[model.MaterialTypeWeaponEnhancement],
|
||||
ExpByEnhanceId: make(map[int32][]int32, len(enhanceRows)),
|
||||
GoldCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
MaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
SellPriceByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
MedalsByWeaponId: make(map[int32]map[int32]int32),
|
||||
EvolutionNextWeaponId: make(map[int32]int32),
|
||||
EvolutionOrder: make(map[int32]int32),
|
||||
EvolutionMaterials: make(map[int32][]WeaponEvolutionMaterialRow),
|
||||
EvolutionCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
AbilitySlots: make(map[int32][]int32),
|
||||
SkillGroupsByGroupId: make(map[int32][]WeaponSkillGroupRow),
|
||||
SkillEnhanceMats: make(map[[2]int32][]WeaponSkillEnhanceMaterialRow),
|
||||
SkillMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
SkillCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
AbilityGroupsByGroupId: make(map[int32][]WeaponAbilityGroupRow),
|
||||
AbilityEnhanceMats: make(map[[2]int32][]WeaponAbilityEnhanceMaterialRow),
|
||||
AbilityMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
AbilityCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
EnhanceCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
LimitBreakCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
LimitBreakCostByMaterialByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||
BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)),
|
||||
ReleaseConditionsByGroupId: make(map[int32][]WeaponStoryReleaseConditionRow),
|
||||
}
|
||||
|
||||
for _, w := range weapons {
|
||||
catalog.Weapons[w.WeaponId] = w
|
||||
}
|
||||
|
||||
for _, r := range enhanceRows {
|
||||
if _, ok := catalog.ExpByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
catalog.ExpByEnhanceId[r.WeaponSpecificEnhanceId] = BuildExpThresholds(paramMapRows, r.RequiredExpForLevelUpNumericalParameterMapId)
|
||||
}
|
||||
if _, ok := catalog.GoldCostByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.EnhancementCostByMaterialNumericalFunctionId); found {
|
||||
catalog.GoldCostByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.MaxLevelByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.MaxLevelNumericalFunctionId); found {
|
||||
catalog.MaxLevelByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.SellPriceByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.SellPriceNumericalFunctionId); found {
|
||||
catalog.SellPriceByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.EvolutionCostByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.EvolutionCostNumericalFunctionId); found {
|
||||
catalog.EvolutionCostByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.SkillMaxLevelByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.MaxSkillLevelNumericalFunctionId); found {
|
||||
catalog.SkillMaxLevelByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.SkillCostByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.SkillEnhancementCostNumericalFunctionId); found {
|
||||
catalog.SkillCostByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.AbilityMaxLevelByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.MaxAbilityLevelNumericalFunctionId); found {
|
||||
catalog.AbilityMaxLevelByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.AbilityCostByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.AbilityEnhancementCostNumericalFunctionId); found {
|
||||
catalog.AbilityCostByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.EnhanceCostByWeaponByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.EnhancementCostByWeaponNumericalFunctionId); found {
|
||||
catalog.EnhanceCostByWeaponByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.LimitBreakCostByWeaponByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.LimitBreakCostByWeaponNumericalFunctionId); found {
|
||||
catalog.LimitBreakCostByWeaponByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.LimitBreakCostByMaterialByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
if f, found := funcResolver.Resolve(r.LimitBreakCostByMaterialNumericalFunctionId); found {
|
||||
catalog.LimitBreakCostByMaterialByEnhanceId[r.WeaponSpecificEnhanceId] = f
|
||||
}
|
||||
}
|
||||
if _, ok := catalog.BaseExpByEnhanceId[r.WeaponSpecificEnhanceId]; !ok {
|
||||
catalog.BaseExpByEnhanceId[r.WeaponSpecificEnhanceId] = r.BaseEnhancementObtainedExp
|
||||
}
|
||||
}
|
||||
|
||||
for _, ex := range exchangeRows {
|
||||
if catalog.MedalsByWeaponId[ex.WeaponId] == nil {
|
||||
catalog.MedalsByWeaponId[ex.WeaponId] = make(map[int32]int32)
|
||||
}
|
||||
catalog.MedalsByWeaponId[ex.WeaponId][ex.ConsumableItemId] = ex.Count
|
||||
}
|
||||
|
||||
grouped := make(map[int32][]WeaponEvolutionGroupRow)
|
||||
for _, row := range evoGroupRows {
|
||||
grouped[row.WeaponEvolutionGroupId] = append(grouped[row.WeaponEvolutionGroupId], row)
|
||||
}
|
||||
for _, rows := range grouped {
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
return rows[i].EvolutionOrder < rows[j].EvolutionOrder
|
||||
})
|
||||
for i, row := range rows {
|
||||
catalog.EvolutionOrder[row.WeaponId] = int32(i)
|
||||
if i < len(rows)-1 {
|
||||
catalog.EvolutionNextWeaponId[row.WeaponId] = rows[i+1].WeaponId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, row := range evoMatRows {
|
||||
catalog.EvolutionMaterials[row.WeaponEvolutionMaterialGroupId] = append(
|
||||
catalog.EvolutionMaterials[row.WeaponEvolutionMaterialGroupId], row)
|
||||
}
|
||||
|
||||
for _, row := range abilityGroupRows {
|
||||
catalog.AbilitySlots[row.WeaponAbilityGroupId] = append(
|
||||
catalog.AbilitySlots[row.WeaponAbilityGroupId], row.SlotNumber)
|
||||
}
|
||||
|
||||
for _, row := range skillGroupRows {
|
||||
catalog.SkillGroupsByGroupId[row.WeaponSkillGroupId] = append(
|
||||
catalog.SkillGroupsByGroupId[row.WeaponSkillGroupId], row)
|
||||
}
|
||||
|
||||
for _, row := range skillMatRows {
|
||||
key := [2]int32{row.WeaponSkillEnhancementMaterialId, row.SkillLevel}
|
||||
catalog.SkillEnhanceMats[key] = append(catalog.SkillEnhanceMats[key], row)
|
||||
}
|
||||
|
||||
for _, row := range abilityGroupRows {
|
||||
catalog.AbilityGroupsByGroupId[row.WeaponAbilityGroupId] = append(
|
||||
catalog.AbilityGroupsByGroupId[row.WeaponAbilityGroupId], row)
|
||||
}
|
||||
|
||||
for _, row := range abilityMatRows {
|
||||
key := [2]int32{row.WeaponAbilityEnhancementMaterialId, row.AbilityLevel}
|
||||
catalog.AbilityEnhanceMats[key] = append(catalog.AbilityEnhanceMats[key], row)
|
||||
}
|
||||
|
||||
for _, c := range releaseConditions {
|
||||
catalog.ReleaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId] = append(
|
||||
catalog.ReleaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId], c)
|
||||
}
|
||||
|
||||
// Rarity-based enhancement fallback: for weapons with WeaponSpecificEnhanceId == 0,
|
||||
// use EntityMWeaponRarityTable curves via synthetic enhance IDs (-RarityType).
|
||||
rarityByType := make(map[int32]weaponRarityEnhanceRow, len(rarityEnhanceRows))
|
||||
for _, r := range rarityEnhanceRows {
|
||||
rarityByType[r.RarityType] = r
|
||||
}
|
||||
|
||||
registeredRarity := make(map[int32]bool, len(rarityEnhanceRows))
|
||||
fallbackCount := 0
|
||||
for wid, w := range catalog.Weapons {
|
||||
if w.WeaponSpecificEnhanceId != 0 {
|
||||
continue
|
||||
}
|
||||
syntheticId := -w.RarityType
|
||||
if !registeredRarity[w.RarityType] {
|
||||
r, ok := rarityByType[w.RarityType]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
catalog.ExpByEnhanceId[syntheticId] = BuildExpThresholds(paramMapRows, r.RequiredExpForLevelUpNumericalParameterMapId)
|
||||
if f, found := funcResolver.Resolve(r.EnhancementCostByMaterialNumericalFunctionId); found {
|
||||
catalog.GoldCostByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.MaxLevelNumericalFunctionId); found {
|
||||
catalog.MaxLevelByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.SellPriceNumericalFunctionId); found {
|
||||
catalog.SellPriceByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.EvolutionCostNumericalFunctionId); found {
|
||||
catalog.EvolutionCostByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.MaxSkillLevelNumericalFunctionId); found {
|
||||
catalog.SkillMaxLevelByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.SkillEnhancementCostNumericalFunctionId); found {
|
||||
catalog.SkillCostByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.MaxAbilityLevelNumericalFunctionId); found {
|
||||
catalog.AbilityMaxLevelByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.AbilityEnhancementCostNumericalFunctionId); found {
|
||||
catalog.AbilityCostByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.EnhancementCostByWeaponNumericalFunctionId); found {
|
||||
catalog.EnhanceCostByWeaponByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.LimitBreakCostByWeaponNumericalFunctionId); found {
|
||||
catalog.LimitBreakCostByWeaponByEnhanceId[syntheticId] = f
|
||||
}
|
||||
if f, found := funcResolver.Resolve(r.LimitBreakCostByMaterialNumericalFunctionId); found {
|
||||
catalog.LimitBreakCostByMaterialByEnhanceId[syntheticId] = f
|
||||
}
|
||||
catalog.BaseExpByEnhanceId[syntheticId] = r.BaseEnhancementObtainedExp
|
||||
registeredRarity[w.RarityType] = true
|
||||
}
|
||||
w.WeaponSpecificEnhanceId = syntheticId
|
||||
catalog.Weapons[wid] = w
|
||||
fallbackCount++
|
||||
}
|
||||
log.Printf("[WeaponCatalog] rarity fallback: assigned synthetic enhance IDs to %d weapons", fallbackCount)
|
||||
|
||||
return catalog, nil
|
||||
}
|
||||
Reference in New Issue
Block a user