Files
bd2/go/internal/server/gamedata/achievement.go
T
Flechazo aec053f7d2 fix(server): restore achievements, progress, claims and experience
Load ordinary achievements and send transactional gameplay progress notifications.
Restore main-story clears and recorded gacha history without duplicate credit.
Validate claim thresholds and prior tiers, credit wallet rewards and persist
achievement experience in login responses.
2026-10-04 19:54:26 +08:00

85 lines
2.5 KiB
Go

package gamedata
import (
"database/sql"
"fmt"
"math"
"sort"
)
// AchievementCounterDesign indexes the content groups to which each counter belongs.
// AchievementTable has several tiers with the same group ID; id is not unique.
type AchievementCondition struct{ Type, SubType, ParentGroup uint64 }
type AchievementCounterDesign struct {
Groups map[int][]int
Conditions map[int]AchievementCondition
}
func LoadAchievementCounterDesign(root, version string) (*AchievementCounterDesign, error) {
db, closeDB, err := openStatDatabase(root, version)
if err != nil {
return nil, err
}
defer closeDB()
return loadAchievementCounterDesign(db)
}
func loadAchievementCounterDesign(db *sql.DB) (*AchievementCounterDesign, error) {
rows, err := db.Query("SELECT ProtoBuf FROM AchievementTable")
if err != nil {
return nil, err
}
defer rows.Close()
sets := map[int]map[int]bool{}
conditions := map[int]AchievementCondition{}
for rows.Next() {
var raw []byte
if err := rows.Scan(&raw); err != nil {
return nil, err
}
fields := map[int]int{}
for _, field := range []int{1, 2, 4, 9, 13} {
values, err := packedInts(raw, field)
if err != nil || len(values) > 1 || (len(values) == 1 && values[0] > math.MaxInt32) {
return nil, fmt.Errorf("gamedata: invalid achievement field%d", field)
}
if len(values) > 0 {
fields[field] = int(values[0])
}
}
// Client condition lookup SQL filters parentGroupId=0.
// The wire update carries no content group.
if fields[13] != 0 {
continue
}
group, content := fields[9], fields[4]
if group <= 0 || content < 0 || content > 1 {
return nil, fmt.Errorf("gamedata: invalid achievement group %d/%d", group, content)
}
condition := AchievementCondition{Type: uint64(fields[2]), SubType: uint64(fields[1]), ParentGroup: uint64(fields[13])}
if previous, ok := conditions[group]; ok && previous != condition {
return nil, fmt.Errorf("gamedata: achievement group condition mismatch")
}
conditions[group] = condition
if sets[group] == nil {
sets[group] = map[int]bool{}
}
sets[group][content] = true
}
if err := rows.Err(); err != nil {
return nil, err
}
design := &AchievementCounterDesign{Groups: map[int][]int{}, Conditions: conditions}
for group, contents := range sets {
for content := range contents {
design.Groups[group] = append(design.Groups[group], content)
}
sort.Ints(design.Groups[group])
}
if len(design.Groups) == 0 {
return nil, fmt.Errorf("gamedata: empty achievement design")
}
return design, nil
}