Files
bd2/go/internal/server/gamedata/battle.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

104 lines
3.4 KiB
Go

package gamedata
import (
"encoding/binary"
"fmt"
"bd2server/internal/server/wire"
_ "modernc.org/sqlite"
)
// BattleReward is a static victory reward from BattleDeckTable in a pack DB.
// It is a design definition, not a player's owned item or server-generated ID.
type BattleReward struct {
Type uint64
ID uint64
Count uint64
}
// BattleDeckRewards uses the enemy battle deck actually chosen by the client
// (BattleEnter field 4); a monster may have several different battle decks.
func BattleDeckRewards(root, version string, packID int, deckID uint64) ([]BattleReward, error) {
if packID <= 0 || deckID == 0 || deckID > uint64(^uint(0)>>1) {
return nil, fmt.Errorf("gamedata: invalid battle pack/deck %d/%d", packID, deckID)
}
return readBattleRewards(root, version, packID, int(deckID), 0)
}
// BattleRewards is a legacy convenience for a monster with exactly one deck.
// Call BattleDeckRewards for a real battle, where the chosen deck is known.
func BattleRewards(root, version string, packID, monsterID int) ([]BattleReward, error) {
if packID <= 0 || monsterID <= 0 {
return nil, fmt.Errorf("gamedata: invalid battle pack/monster %d/%d", packID, monsterID)
}
return readBattleRewards(root, version, packID, 0, monsterID)
}
func readBattleRewards(root, version string, packID, deckID, monsterID int) ([]BattleReward, error) {
db, release, err := OpenDatabase(root, version, fmt.Sprintf("pack%d", packID))
if err != nil {
return nil, err
}
defer release()
if monsterID != 0 {
var monster []byte
if err := db.QueryRow("SELECT ProtoBuf FROM FieldMonsterTable WHERE id=?", monsterID).Scan(&monster); err != nil {
return nil, fmt.Errorf("gamedata: monster %d: %w", monsterID, err)
}
deckIDs, err := packedInts(monster, 2)
if err != nil || len(deckIDs) != 1 || deckIDs[0] == 0 || deckIDs[0] > uint64(^uint(0)>>1) {
return nil, fmt.Errorf("gamedata: monster %d requires a selected battle deck (found %v): %w", monsterID, deckIDs, err)
}
deckID = int(deckIDs[0])
}
var design []byte
if err := db.QueryRow("SELECT ProtoBuf FROM BattleDeckTable WHERE id=?", deckID).Scan(&design); err != nil {
return nil, fmt.Errorf("gamedata: battle deck %d: %w", deckID, err)
}
types, err := packedInts(design, 36)
if err != nil {
return nil, err
}
ids, err := packedInts(design, 34)
if err != nil {
return nil, err
}
counts, err := packedInts(design, 33)
if err != nil {
return nil, err
}
if len(types) != len(ids) || len(ids) != len(counts) {
return nil, fmt.Errorf("gamedata: battle deck %d reward arrays differ in length", deckID)
}
rewards := make([]BattleReward, len(types))
for i := range rewards {
if types[i] == 0 || ids[i] == 0 || counts[i] == 0 {
return nil, fmt.Errorf("gamedata: invalid battle deck %d reward", deckID)
}
rewards[i] = BattleReward{Type: types[i], ID: ids[i], Count: counts[i]}
}
return rewards, nil
}
func packedInts(proto []byte, number int) ([]uint64, error) {
var out []uint64
err := wire.Walk(proto, func(f wire.Field) error {
if f.Number != number {
return nil
}
if f.Type != 0 && f.Type != 2 {
return fmt.Errorf("gamedata: field %d has type %d", number, f.Type)
}
for data := f.Value; len(data) > 0; {
value, n := binary.Uvarint(data)
if n <= 0 {
return fmt.Errorf("gamedata: invalid packed field %d", number)
}
out = append(out, value)
data = data[n:]
}
return nil
})
return out, err
}