Files
bd2/go/internal/server/gamedata/story_characters.go
T
Flechazo 5b5a219dc2 feat(server): add special loot and data-driven gameplay rules
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.
2026-10-04 19:15:59 +08:00

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
}