Files
bd2/go/internal/server/design/gamedata/dispatch.go
T

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
}