Files
bd2/go/internal/server/gamedata/field_objects.go
T
Flechazo d8771caef2 feat(all): add hunting and events, commerce and rewards, binary calendars, GameData caching and login diagnostics
Implement hunting AP and progress, event tasks, exchanges, games, stories and actions; connect battle and reward handling.

Add CashShop plugin and installation, server purchases and entitlements, recharge rules and plugin version metadata.

Distribute binary calendars independently; cache GameData in memory and fix single-connection query deadlocks.

Improve automatic login and recovery diagnostics; document configuration and release flows.
2026-10-05 11:50:31 +08:00

202 lines
6.0 KiB
Go

package gamedata
import (
"database/sql"
"fmt"
)
// FieldObjectDesign retains the client table identities, including unsupported
// reward graphs, so callers can reject them without inventing a reward.
type FieldObjectDesign struct {
Objects map[int]FieldRewardObject
Equipment *EquipmentGachaCatalog
}
type FieldRewardObject struct {
ID, MapID, GroupID, Type, ResetType, QuestID, BuffID, MonsterID int
Rewards []BattleReward
Ratios []uint64
DropCount, DropType uint64
}
func LoadFieldObjects(root, version string, pack int) (FieldObjectDesign, error) {
db, release, err := OpenDatabase(root, version, fmt.Sprintf("pack%d", pack))
if err != nil {
return FieldObjectDesign{}, err
}
defer release()
common, closeDB, err := openStatDatabase(root, version)
if err != nil {
return FieldObjectDesign{}, err
}
defer closeDB()
return loadFieldObjects(db, common)
}
func loadFieldObjects(db, common *sql.DB) (FieldObjectDesign, error) {
design := FieldObjectDesign{Objects: map[int]FieldRewardObject{}, Equipment: &EquipmentGachaCatalog{equipment: map[uint64]EquipmentDesign{}}}
groups := map[int]FieldRewardObject{}
rows, err := db.Query("SELECT id,ProtoBuf FROM FieldRewardObjectGroupTable")
if err != nil {
return design, err
}
for rows.Next() {
var id int
var raw []byte
if err = rows.Scan(&id, &raw); err != nil {
rows.Close()
return design, err
}
obj := FieldRewardObject{GroupID: id}
for f, target := range map[int]*int{1: &obj.BuffID, 7: &obj.MonsterID, 8: &obj.QuestID, 9: &obj.ResetType, 12: &obj.Type} {
v, e := packedInts(raw, f)
if e != nil || len(v) > 1 {
rows.Close()
return design, fmt.Errorf("gamedata: malformed field object group %d", id)
}
if len(v) > 0 {
*target = int(v[0])
}
}
group, e := packedInts(raw, 10)
if e != nil || len(group) > 1 {
rows.Close()
return design, fmt.Errorf("gamedata: malformed field reward group %d", id)
}
if len(group) == 1 {
var rewardRaw []byte
if err = common.QueryRow("SELECT ProtoBuf FROM RewardGroupTable WHERE id=?", group[0]).Scan(&rewardRaw); err != nil {
rows.Close()
return design, err
}
ids, e1 := packedInts(rewardRaw, 5)
types, e2 := packedInts(rewardRaw, 6)
counts, e3 := packedInts(rewardRaw, 4)
drop, e4 := packedInts(rewardRaw, 1)
ratios, e5 := packedInts(rewardRaw, 8)
dropType, e6 := packedInts(rewardRaw, 2)
if e1 != nil || e2 != nil || e3 != nil || e4 != nil || e5 != nil || e6 != nil {
rows.Close()
return design, fmt.Errorf("gamedata: malformed field loot %d", group[0])
}
if len(ids) > 0 && len(ids) == len(types) && len(ids) == len(counts) && len(ratios) == len(ids) && len(drop) <= 1 && len(dropType) <= 1 {
if len(drop) == 1 {
obj.DropCount = drop[0]
}
obj.Ratios = ratios
if len(dropType) > 0 {
obj.DropType = dropType[0]
}
for i := range ids {
obj.Rewards = append(obj.Rewards, BattleReward{ID: ids[i], Type: types[i], Count: counts[i]})
if types[i] == 10 {
if e := design.Equipment.loadEquipmentTree(common, WeightedEquipment{ID: ids[i]}); e != nil {
rows.Close()
return design, e
}
}
}
} else {
rows.Close()
return design, fmt.Errorf("gamedata: malformed field loot entries %d", group[0])
}
}
groups[id] = obj
}
if err = rows.Err(); err != nil {
rows.Close()
return design, err
}
rows.Close()
rows, err = db.Query("SELECT id,ProtoBuf FROM FieldRewardObjectTable")
if err != nil {
return design, err
}
defer rows.Close()
for rows.Next() {
var id int
var raw []byte
if err = rows.Scan(&id, &raw); err != nil {
return design, err
}
group, e1 := packedInts(raw, 3)
maps, e2 := packedInts(raw, 6)
if e1 != nil || e2 != nil || len(group) != 1 || len(maps) > 1 || id <= 0 {
return design, fmt.Errorf("gamedata: malformed field object %d", id)
}
obj, ok := groups[int(group[0])]
if !ok {
return design, fmt.Errorf("gamedata: missing object group %d", group[0])
}
obj.ID = id
if len(maps) > 0 {
obj.MapID = int(maps[0])
}
design.Objects[id] = obj
}
return design, rows.Err()
}
// DropType 0 makes DropCount weighted selections. DropType 1 checks each
// component's percentage independently; its DropCount may be proto default zero.
func (o FieldRewardObject) Draw() ([]BattleReward, error) {
return o.draw(cryptoDraw)
}
func (o FieldRewardObject) draw(draw func(uint64) (uint64, error)) ([]BattleReward, error) {
if o.DropType > 1 || (o.DropType == 0 && o.DropCount == 0) || o.DropCount > 100 || len(o.Rewards) == 0 || len(o.Rewards) != len(o.Ratios) {
return nil, fmt.Errorf("gamedata: unsupported field reward draw")
}
var total uint64
for i, r := range o.Ratios {
if o.Rewards[i].Count == 0 || o.Rewards[i].Count > uint64(^uint32(0)>>1) || (o.DropType == 1 && r > 100) {
return nil, fmt.Errorf("gamedata: malformed field reward component")
}
if ^uint64(0)-total < r {
return nil, fmt.Errorf("gamedata: reward ratio overflow")
}
total += r
}
if total == 0 && o.DropType == 0 {
return nil, fmt.Errorf("gamedata: empty field reward distribution")
}
var result []BattleReward
if o.DropType == 1 {
for i, ratio := range o.Ratios {
if ratio == 0 {
continue
}
if ratio < 100 {
value, err := draw(100)
if err != nil {
return nil, err
}
if value >= 100 {
return nil, fmt.Errorf("gamedata: field reward random out of range")
}
if value >= ratio {
continue
}
}
result = append(result, o.Rewards[i])
}
return result, nil
}
for n := uint64(0); n < o.DropCount; n++ {
value, err := draw(total)
if err != nil {
return nil, err
}
if value >= total {
return nil, fmt.Errorf("gamedata: field reward random out of range")
}
pick := value
for i, ratio := range o.Ratios {
if pick < ratio {
result = append(result, o.Rewards[i])
break
}
pick -= ratio
}
}
return result, nil
}