Generate equipment loot atomically, grant random boxes and prevent repeat claims. Read gacha pools, pity, products, tickets, missions, presets, chapter rewards and inventory costs from GameData; extend both collaboration UR ticket pools. Sync owned recipes, configure startup state and calendars in versioned seeds, and validate unlock tickets and revival talents.
109 lines
3.6 KiB
Go
109 lines
3.6 KiB
Go
package gamedata
|
|
|
|
import "fmt"
|
|
|
|
type StoryCharacterDesign struct {
|
|
CharacterID, UniqueCharacterID, Level, CostumeID, HP, Order uint64
|
|
TemporaryPack uint64
|
|
}
|
|
|
|
// StoryCharacterCatalog contains only authored temporary character rows.
|
|
type StoryCharacterCatalog struct {
|
|
formations map[int]map[int]QuestFormation
|
|
characters map[[2]int][]StoryCharacterDesign
|
|
}
|
|
|
|
func LoadStoryCharacterCatalog(root, version string, packs []int, placeholderCostumes ...uint64) (*StoryCharacterCatalog, error) {
|
|
var placeholder uint64
|
|
if len(placeholderCostumes) > 1 {
|
|
return nil, fmt.Errorf("gamedata: multiple story placeholders")
|
|
}
|
|
if len(placeholderCostumes) == 1 {
|
|
placeholder = placeholderCostumes[0]
|
|
}
|
|
db, cleanup, err := openStatDatabase(root, version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cleanup()
|
|
stats, err := loadCharacterStatDesign(db)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
catalog := &StoryCharacterCatalog{formations: map[int]map[int]QuestFormation{}, characters: map[[2]int][]StoryCharacterDesign{}}
|
|
instances := map[[3]uint64]uint64{}
|
|
for _, pack := range packs {
|
|
formations, err := loadQuestFormationsDB(db, pack)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
catalog.formations[pack] = formations
|
|
for quest, formation := range formations {
|
|
for _, row := range formation.Characters {
|
|
if row.Banned {
|
|
continue
|
|
}
|
|
var data []byte
|
|
if err := db.QueryRow("SELECT ProtoBuf FROM CharTable WHERE id=?", row.CharacterID).Scan(&data); err != nil {
|
|
return nil, fmt.Errorf("gamedata: story character %d: %w", row.CharacterID, err)
|
|
}
|
|
unique, err := requiredScalar(data, 20)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
temporaryPack, err := optionalScalar(data, 21)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
characterType, err := optionalScalar(data, 19)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if characterType != 1 || temporaryPack == 0 {
|
|
continue
|
|
}
|
|
costume, err := requiredScalar(data, 5)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, story := range formation.StoryCostumes {
|
|
if story.UniqueCharacterID == unique && (placeholder == 0 || story.CostumeID != placeholder) {
|
|
costume = story.CostumeID
|
|
break
|
|
}
|
|
}
|
|
base, err := stats.BaseStats(row.CharacterID, row.Level)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if base.Health <= 0 {
|
|
return nil, fmt.Errorf("gamedata: story character %d has invalid health", row.CharacterID)
|
|
}
|
|
instance := [3]uint64{uint64(pack), row.CharacterID, row.Level}
|
|
if previous, exists := instances[instance]; exists && previous != costume {
|
|
return nil, fmt.Errorf("gamedata: story instance pack%d character%d level%d has conflicting costumes %d/%d", pack, row.CharacterID, row.Level, previous, costume)
|
|
}
|
|
instances[instance] = costume
|
|
key := [2]int{pack, quest}
|
|
catalog.characters[key] = append(catalog.characters[key], StoryCharacterDesign{row.CharacterID, unique, row.Level, costume, uint64(base.Health), row.Order, temporaryPack})
|
|
}
|
|
}
|
|
}
|
|
return catalog, nil
|
|
}
|
|
|
|
func (c *StoryCharacterCatalog) Formation(packID, questID int) (QuestFormation, bool) {
|
|
if c == nil {
|
|
return QuestFormation{}, false
|
|
}
|
|
f, ok := c.formations[packID][questID]
|
|
return f, ok
|
|
}
|
|
|
|
func (c *StoryCharacterCatalog) Characters(packID, questID int) ([]StoryCharacterDesign, error) {
|
|
if _, ok := c.Formation(packID, questID); !ok {
|
|
return nil, fmt.Errorf("gamedata: missing story formation pack%d quest%d", packID, questID)
|
|
}
|
|
return append([]StoryCharacterDesign(nil), c.characters[[2]int{packID, questID}]...), nil
|
|
}
|