feat: split client tooling and add OAuth server login
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
package gamedata
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
PictorialCharacter = 1
|
||||
PictorialTalent = 2
|
||||
PictorialEquipment = 3
|
||||
PictorialCooking = 4
|
||||
PictorialItem = 5
|
||||
PictorialCostume = 7
|
||||
)
|
||||
|
||||
type PictorialBuff struct {
|
||||
ID uint64
|
||||
Category uint64
|
||||
StatType uint64
|
||||
Value float64
|
||||
}
|
||||
|
||||
type PictorialBuffStat struct {
|
||||
Category uint64
|
||||
StatType uint64
|
||||
Value float64
|
||||
}
|
||||
|
||||
type CharacterPictorial struct {
|
||||
ID uint64
|
||||
UniqueIDs []uint64
|
||||
BuffID uint64
|
||||
}
|
||||
|
||||
type TalentPictorial struct {
|
||||
ID uint64
|
||||
TalentIDs []uint64
|
||||
}
|
||||
|
||||
type CostumePictorial struct {
|
||||
ID uint64
|
||||
CostumeID uint64
|
||||
BuffIDs []uint64
|
||||
Thresholds []uint64
|
||||
}
|
||||
|
||||
type EquipmentPictorial struct {
|
||||
ID uint64
|
||||
EquipmentID uint64
|
||||
BuffID uint64
|
||||
}
|
||||
|
||||
type CookingPictorial struct {
|
||||
ID uint64
|
||||
RecipeID uint64
|
||||
BuffID uint64
|
||||
}
|
||||
|
||||
type ItemPictorial struct {
|
||||
ID uint64
|
||||
ItemID uint64
|
||||
BuffID uint64
|
||||
Category uint64
|
||||
}
|
||||
|
||||
type CharacterPictorialMeta struct {
|
||||
UniqueID uint64
|
||||
TalentID uint64
|
||||
UsePackTemporary bool
|
||||
}
|
||||
|
||||
// PictorialDesign is immutable 2.35.10 design data. Dynamic account state is
|
||||
// deliberately not copied here: callers match their current ownership against
|
||||
// these rows each time they build PictorialBookInfo or AllCharRefresh.
|
||||
type PictorialDesign struct {
|
||||
Root string
|
||||
Version string
|
||||
Characters []CharacterPictorial
|
||||
Talents []TalentPictorial
|
||||
Costumes []CostumePictorial
|
||||
Equipment []EquipmentPictorial
|
||||
Cooking []CookingPictorial
|
||||
Items []ItemPictorial
|
||||
Buffs map[uint64]PictorialBuff
|
||||
CharMeta map[uint64]CharacterPictorialMeta
|
||||
CharStats *CharacterStatDesign
|
||||
}
|
||||
|
||||
func LoadPictorialDesign(root, version string) (*PictorialDesign, error) {
|
||||
db, closeDB, err := openStatDatabase(root, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closeDB()
|
||||
d := &PictorialDesign{Root: root, Version: version, Buffs: map[uint64]PictorialBuff{}, CharMeta: map[uint64]CharacterPictorialMeta{}}
|
||||
if err := loadProtoRows(db, "CharacterPictorialBookTable", func(proto []byte) error {
|
||||
id, err := exactlyOne(proto, 5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buff, err := exactlyOne(proto, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ids, err := packedInts(proto, 1)
|
||||
if err != nil || len(ids) == 0 {
|
||||
return fmt.Errorf("gamedata: character pictorial %d ids: %w", id, err)
|
||||
}
|
||||
d.Characters = append(d.Characters, CharacterPictorial{ID: id, UniqueIDs: ids, BuffID: buff})
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadProtoRows(db, "TalentPictorialBookTable", func(proto []byte) error {
|
||||
id, err := exactlyOne(proto, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ids, err := packedInts(proto, 2)
|
||||
if err != nil || len(ids) == 0 {
|
||||
return fmt.Errorf("gamedata: talent pictorial %d ids: %w", id, err)
|
||||
}
|
||||
d.Talents = append(d.Talents, TalentPictorial{ID: id, TalentIDs: ids})
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadProtoRows(db, "CostumePictorialBookTable", func(proto []byte) error {
|
||||
id, err := exactlyOne(proto, 5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
costumeID, err := exactlyOne(proto, 3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffs, err := packedInts(proto, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
thresholds, err := packedInts(proto, 4)
|
||||
if err != nil || len(buffs) == 0 || len(buffs) != len(thresholds) {
|
||||
return fmt.Errorf("gamedata: costume pictorial %d has mismatched buff thresholds", id)
|
||||
}
|
||||
d.Costumes = append(d.Costumes, CostumePictorial{ID: id, CostumeID: costumeID, BuffIDs: buffs, Thresholds: thresholds})
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadProtoRows(db, "EquipmentPictorialBookTable", func(proto []byte) error {
|
||||
id, err := exactlyOne(proto, 5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
equipmentID, err := exactlyOne(proto, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buff, err := exactlyOne(proto, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Equipment = append(d.Equipment, EquipmentPictorial{ID: id, EquipmentID: equipmentID, BuffID: buff})
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadProtoRows(db, "CookingPictorialBookTable", func(proto []byte) error {
|
||||
buff, err := exactlyOne(proto, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recipe, err := exactlyOne(proto, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := exactlyOne(proto, 3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Cooking = append(d.Cooking, CookingPictorial{ID: id, RecipeID: recipe, BuffID: buff})
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadProtoRows(db, "ItemPictorialBookTable", func(proto []byte) error {
|
||||
category, err := zeroOrOne(proto, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buff, err := exactlyOne(proto, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := exactlyOne(proto, 3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
item, err := exactlyOne(proto, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Items = append(d.Items, ItemPictorial{ID: id, ItemID: item, BuffID: buff, Category: category})
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadProtoRows(db, "AdventuregroupBuffTable", func(proto []byte) error {
|
||||
id, err := exactlyOne(proto, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stat, err := exactlyOne(proto, 3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
category, err := zeroOrOne(proto, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, found, err := fixed64Double(proto, 4)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gamedata: pictorial buff %d value: %w", id, err)
|
||||
}
|
||||
if !found || value <= 0 {
|
||||
// Many collection-discovery rows have no battle-stat reward.
|
||||
return nil
|
||||
}
|
||||
d.Buffs[id] = PictorialBuff{ID: id, Category: category, StatType: stat, Value: value}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := loadProtoRows(db, "CharTable", func(proto []byte) error {
|
||||
id, err := exactlyOne(proto, 12)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uniqueID, err := exactlyOne(proto, 20)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
talentID, err := zeroOrOne(proto, 18)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
temporary, err := zeroOrOne(proto, 21)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.CharMeta[id] = CharacterPictorialMeta{UniqueID: uniqueID, TalentID: talentID, UsePackTemporary: temporary != 0}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.CharStats, err = loadCharacterStatDesign(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(d.Characters, func(i, j int) bool { return d.Characters[i].ID < d.Characters[j].ID })
|
||||
sort.Slice(d.Talents, func(i, j int) bool { return d.Talents[i].ID < d.Talents[j].ID })
|
||||
sort.Slice(d.Costumes, func(i, j int) bool { return d.Costumes[i].ID < d.Costumes[j].ID })
|
||||
sort.Slice(d.Equipment, func(i, j int) bool { return d.Equipment[i].ID < d.Equipment[j].ID })
|
||||
sort.Slice(d.Cooking, func(i, j int) bool { return d.Cooking[i].ID < d.Cooking[j].ID })
|
||||
sort.Slice(d.Items, func(i, j int) bool { return d.Items[i].ID < d.Items[j].ID })
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func loadProtoRows(db *sql.DB, table string, consume func([]byte) error) error {
|
||||
rows, err := db.Query("SELECT ProtoBuf FROM " + table)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gamedata: query %s: %w", table, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var proto []byte
|
||||
if err := rows.Scan(&proto); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := consume(proto); err != nil {
|
||||
return fmt.Errorf("gamedata: decode %s: %w", table, err)
|
||||
}
|
||||
}
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func exactlyOne(proto []byte, field int) (uint64, error) {
|
||||
values, err := packedInts(proto, field)
|
||||
if err != nil || len(values) != 1 {
|
||||
return 0, fmt.Errorf("field %d values %v: %w", field, values, err)
|
||||
}
|
||||
return values[0], nil
|
||||
}
|
||||
|
||||
func zeroOrOne(proto []byte, field int) (uint64, error) {
|
||||
values, err := packedInts(proto, field)
|
||||
if err != nil || len(values) > 1 {
|
||||
return 0, fmt.Errorf("field %d values %v: %w", field, values, err)
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return values[0], nil
|
||||
}
|
||||
Reference in New Issue
Block a user