275 lines
7.4 KiB
Go
275 lines
7.4 KiB
Go
package gamedata
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
)
|
|
|
|
type DispatchDesign struct {
|
|
Group, ID, Pack, Difficulty, TypeGroup, TypeID, AP, ClearTime, Growth uint64
|
|
GroundID, APType uint64
|
|
Rewards []BattleReward
|
|
boxes map[uint64]*dispatchGroup
|
|
}
|
|
type dispatchEntry struct {
|
|
Reward BattleReward
|
|
Weight uint64
|
|
Child *dispatchGroup
|
|
}
|
|
type dispatchGroup struct {
|
|
Drop, Count uint64
|
|
Entries []dispatchEntry
|
|
}
|
|
|
|
func LoadDispatchDesign(root, version string, group, id uint64) (*DispatchDesign, error) {
|
|
db, done, err := openStatDatabase(root, version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer done()
|
|
var raw []byte
|
|
if err = db.QueryRow("SELECT ProtoBuf FROM HuntDispatchTable WHERE groupId=? AND id=?", group, id).Scan(&raw); err != nil {
|
|
return nil, err
|
|
}
|
|
d := &DispatchDesign{boxes: map[uint64]*dispatchGroup{}}
|
|
for n, p := range map[int]*uint64{1: &d.AP, 4: &d.ClearTime, 5: &d.Difficulty, 7: &d.Group, 8: &d.ID, 10: &d.Pack, 12: &d.Growth, 13: &d.TypeGroup, 14: &d.TypeID} {
|
|
*p, err = optionalScalar(raw, n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if d.AP == 0 || d.Pack == 0 || d.Group != group || d.ID != id {
|
|
return nil, fmt.Errorf("gamedata: invalid dispatch")
|
|
}
|
|
pdb, closePack, err := openPackDatabase(root, version, int(d.Pack))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer closePack()
|
|
var ids []uint64
|
|
var boss uint64
|
|
if d.TypeGroup == 0 {
|
|
if err = pdb.QueryRow("SELECT ProtoBuf FROM HuntingGroundTable WHERE difficulty=?", d.Difficulty).Scan(&raw); err != nil {
|
|
return nil, err
|
|
}
|
|
ids, err = packedInts(raw, 5)
|
|
boss, _ = optionalScalar(raw, 1)
|
|
d.GroundID, _ = optionalScalar(raw, 3)
|
|
} else {
|
|
if err = db.QueryRow("SELECT ProtoBuf FROM SkyWayFieldTable WHERE groupId=? AND id=?", d.TypeGroup, d.TypeID).Scan(&raw); err != nil {
|
|
return nil, err
|
|
}
|
|
ids, err = packedInts(raw, 15)
|
|
boss, _ = optionalScalar(raw, 4)
|
|
d.APType, _ = optionalScalar(raw, 1)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ids = append(ids, boss)
|
|
for _, monster := range ids {
|
|
if err = pdb.QueryRow("SELECT ProtoBuf FROM FieldMonsterTable WHERE id=?", monster).Scan(&raw); err != nil {
|
|
return nil, err
|
|
}
|
|
decks, e := packedInts(raw, 2)
|
|
if e != nil || len(decks) == 0 {
|
|
return nil, fmt.Errorf("gamedata: dispatch monster has no deck")
|
|
}
|
|
if err = pdb.QueryRow("SELECT ProtoBuf FROM BattleDeckTable WHERE id=?", decks[0]).Scan(&raw); err != nil {
|
|
return nil, err
|
|
}
|
|
types, e := packedInts(raw, 36)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
rewardIDs, e := packedInts(raw, 34)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
counts, e := packedInts(raw, 33)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if len(types) != len(rewardIDs) || len(types) != len(counts) {
|
|
return nil, fmt.Errorf("gamedata: dispatch reward arrays mismatch")
|
|
}
|
|
rs, e := parallelRewards(raw, 36, 34, 33)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
for _, r := range rs {
|
|
d.Rewards = append(d.Rewards, BattleReward{r.Type, r.ID, r.Count}) //nolint:staticcheck // S1016
|
|
if r.Type == 9 {
|
|
g, e := loadDispatchBox(db, r.ID, map[uint64]bool{})
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
d.boxes[r.ID] = g
|
|
}
|
|
}
|
|
}
|
|
return d, nil
|
|
}
|
|
func loadDispatchBox(db *sql.DB, id uint64, seen map[uint64]bool) (*dispatchGroup, error) {
|
|
if seen[id] || len(seen) > 32 {
|
|
return nil, fmt.Errorf("gamedata: dispatch reward cycle")
|
|
}
|
|
seen[id] = true
|
|
defer delete(seen, id)
|
|
var raw []byte
|
|
if err := db.QueryRow("SELECT ProtoBuf FROM RandomBoxTable WHERE id=?", id).Scan(&raw); err != nil {
|
|
return nil, err
|
|
}
|
|
gid, err := optionalScalar(raw, 9)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return loadDispatchRewardGroup(db, gid, seen)
|
|
}
|
|
func loadDispatchRewardGroup(db *sql.DB, gid uint64, seen map[uint64]bool) (*dispatchGroup, error) {
|
|
var raw []byte
|
|
err := db.QueryRow("SELECT ProtoBuf FROM RewardGroupTable WHERE id=?", gid).Scan(&raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
g := &dispatchGroup{}
|
|
g.Drop, _ = optionalScalar(raw, 2)
|
|
g.Count, _ = optionalScalar(raw, 1)
|
|
fields := map[int][]uint64{}
|
|
for _, n := range []int{4, 5, 6, 8} {
|
|
fields[n], err = packedInts(raw, n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(fields[5]) == 0 || len(fields[4]) != len(fields[5]) || len(fields[6]) != len(fields[5]) || len(fields[8]) != len(fields[5]) || g.Drop > 1 || (g.Drop == 0 && g.Count == 0) {
|
|
return nil, fmt.Errorf("gamedata: malformed dispatch reward group %d", gid)
|
|
}
|
|
for i, item := range fields[5] {
|
|
e := dispatchEntry{Reward: BattleReward{fields[6][i], item, fields[4][i]}, Weight: fields[8][i]}
|
|
if e.Reward.Count == 0 {
|
|
return nil, fmt.Errorf("gamedata: empty dispatch reward")
|
|
}
|
|
if e.Reward.Type == 9 {
|
|
e.Child, err = loadDispatchBox(db, item, seen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
g.Entries = append(g.Entries, e)
|
|
}
|
|
return g, nil
|
|
}
|
|
|
|
// Roll uses the local server's random source; GameData supplies every weight and quantity.
|
|
func (d *DispatchDesign) Roll(count uint64, draw func(uint64) (uint64, error)) ([]BattleReward, error) {
|
|
if d == nil || count == 0 || count > 20 {
|
|
return nil, fmt.Errorf("gamedata: invalid dispatch count")
|
|
}
|
|
var budget uint64 = 100000
|
|
var out []BattleReward
|
|
var run func(*dispatchGroup) error
|
|
emit := func(e dispatchEntry) error {
|
|
if e.Child != nil {
|
|
for i := uint64(0); i < e.Reward.Count; i++ {
|
|
if err := run(e.Child); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
if len(out) >= 100000 {
|
|
return fmt.Errorf("gamedata: dispatch reward count exceeds operation limit")
|
|
}
|
|
out = append(out, e.Reward)
|
|
}
|
|
return nil
|
|
}
|
|
run = func(g *dispatchGroup) error {
|
|
if g == nil || budget == 0 {
|
|
return fmt.Errorf("gamedata: dispatch reward program exceeds operation limit")
|
|
}
|
|
budget--
|
|
if g.Drop == 1 {
|
|
for _, e := range g.Entries {
|
|
if e.Weight > 100 {
|
|
return fmt.Errorf("gamedata: direct dispatch percent exceeds 100")
|
|
}
|
|
v, err := draw(100)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v >= 100 {
|
|
return fmt.Errorf("gamedata: sampler out of range")
|
|
}
|
|
if v >= e.Weight {
|
|
continue
|
|
}
|
|
if err := emit(e); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
var sum uint64
|
|
for _, e := range g.Entries {
|
|
if e.Weight == 0 || math.MaxUint64-sum < e.Weight {
|
|
return fmt.Errorf("gamedata: invalid dispatch weight")
|
|
}
|
|
sum += e.Weight
|
|
}
|
|
for i := uint64(0); i < g.Count; i++ {
|
|
v, err := draw(sum)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v >= sum {
|
|
return fmt.Errorf("gamedata: random out of range")
|
|
}
|
|
for _, e := range g.Entries {
|
|
if v < e.Weight {
|
|
if err := emit(e); err != nil {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
v -= e.Weight
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
for range count {
|
|
for _, r := range d.Rewards {
|
|
if r.Type == 9 {
|
|
for n := uint64(0); n < r.Count; n++ {
|
|
if err := run(d.boxes[r.ID]); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
} else {
|
|
out = append(out, r)
|
|
}
|
|
}
|
|
}
|
|
aggregate := map[[2]uint64]uint64{}
|
|
for _, r := range out {
|
|
k := [2]uint64{r.Type, r.ID}
|
|
if r.Count > math.MaxInt32-aggregate[k] {
|
|
return nil, fmt.Errorf("gamedata: dispatch reward overflow")
|
|
}
|
|
aggregate[k] += r.Count
|
|
}
|
|
out = nil
|
|
for k, n := range aggregate {
|
|
out = append(out, BattleReward{k[0], k[1], n})
|
|
}
|
|
sort.Slice(out, func(i, j int) bool {
|
|
if out[i].Type != out[j].Type {
|
|
return out[i].Type < out[j].Type
|
|
}
|
|
return out[i].ID < out[j].ID
|
|
})
|
|
return out, nil
|
|
}
|