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

127 lines
3.9 KiB
Go

package gamedata
import (
"database/sql"
"fmt"
"bd2server/internal/server/wire"
)
// BattlePhase identifies a row in the pack's PhaseBattleTable. ID is the
// design row ID sent as current_phase_id, not a zero-based list offset.
type BattlePhase struct{ GroupID, ID, DeckID uint64 }
// BattleDeckPhases resolves a monster's phase group and verifies that the
// selected deck belongs to it. Ordinary monsters have no phase group.
func BattleDeckPhases(root, version string, packID int, monsterID, deckID uint64) ([]BattlePhase, error) {
if packID <= 0 || monsterID == 0 || deckID == 0 || monsterID > 2147483647 || deckID > 2147483647 {
return nil, fmt.Errorf("gamedata: invalid phase battle pack/monster/deck %d/%d/%d", packID, monsterID, deckID)
}
db, release, err := OpenDatabase(root, version, fmt.Sprintf("pack%d", packID))
if err != nil {
return nil, err
}
defer release()
return battleDeckPhasesFromDB(db, monsterID, deckID)
}
func battleDeckPhasesFromDB(db *sql.DB, monsterID, deckID uint64) ([]BattlePhase, error) {
var monster []byte
if err := db.QueryRow("SELECT ProtoBuf FROM FieldMonsterTable WHERE id=?", monsterID).Scan(&monster); err != nil {
return nil, fmt.Errorf("gamedata: phase monster %d: %w", monsterID, err)
}
group, err := phaseScalar(monster, 21)
if err != nil {
return nil, err
}
if group == 0 {
return nil, nil
}
if group > 2147483647 {
return nil, fmt.Errorf("gamedata: phase group exceeds int32")
}
rows, err := db.Query("SELECT id, ProtoBuf FROM PhaseBattleTable WHERE groupId=? ORDER BY id", group)
if err != nil {
return nil, err
}
defer rows.Close()
var phases []BattlePhase
selected := false
for rows.Next() {
var data []byte
var sqlID uint64
if err := rows.Scan(&sqlID, &data); err != nil {
return nil, err
}
rowGroup, err := phaseScalar(data, 3)
if err != nil {
return nil, err
}
id, err := phaseScalar(data, 4)
if err != nil {
return nil, err
}
deck, err := phaseScalar(data, 2)
if err != nil {
return nil, err
}
if rowGroup != group || id == 0 || deck == 0 || id > 2147483647 || deck > 2147483647 || id != sqlID || (len(phases) > 0 && phases[len(phases)-1].ID >= id) {
return nil, fmt.Errorf("gamedata: invalid phase group %d row %d deck %d", rowGroup, id, deck)
}
phases = append(phases, BattlePhase{GroupID: group, ID: id, DeckID: deck})
selected = selected || deck == deckID
}
if err := rows.Err(); err != nil {
return nil, err
}
if len(phases) != 0 && !selected {
// Phase rows name the normal decks. Hard story battles use the
// independently authored difficulty decks for every phase.
var data []byte
if err := db.QueryRow("SELECT ProtoBuf FROM BattleDeckTable WHERE id=?", deckID).Scan(&data); err != nil {
return nil, fmt.Errorf("gamedata: selected phase deck %d: %w", deckID, err)
}
difficulty, err := phaseScalar(data, 24)
if err != nil {
return nil, err
}
if difficulty == 0 {
return nil, fmt.Errorf("gamedata: phase group %d does not contain selected deck %d", group, deckID)
}
for i := range phases {
mapped, err := battleDeckForDifficultyFromDB(db, phases[i].DeckID, difficulty)
if err != nil {
return nil, err
}
phases[i].DeckID = mapped
selected = selected || mapped == deckID
}
}
if len(phases) == 0 || !selected {
return nil, fmt.Errorf("gamedata: phase group %d does not contain selected deck %d", group, deckID)
}
return phases, nil
}
func phaseScalar(data []byte, number int) (uint64, error) {
var value uint64
err := wire.Walk(data, func(f wire.Field) error {
if f.Number != number {
return nil
}
if f.Type != 0 {
return fmt.Errorf("gamedata: phase field %d has wire type %d", number, f.Type)
}
values, err := packedInts(data, number)
if err != nil {
return err
}
if len(values) != 1 {
return fmt.Errorf("gamedata: phase field %d is not scalar", number)
}
value = values[0]
return nil
})
return value, err
}