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,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
|
||||
}
|
||||
Reference in New Issue
Block a user