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

127 lines
4.4 KiB
Go

package gamedata
import (
"database/sql"
"fmt"
"slices"
)
type QuestBattleDeck struct {
DeckID uint64
QuestIDs []uint64
}
// ResolveQuestBattleDeck applies story difficulty only to monsters bound to
// main quests. Side quests use their authored deck regardless of story level.
func ResolveQuestBattleDeck(root, version string, packID int, monsterID, deckID, difficulty uint64) (QuestBattleDeck, error) {
if monsterID == 0 || monsterID > 2147483647 || deckID == 0 || deckID > 2147483647 || difficulty > 2 {
return QuestBattleDeck{}, fmt.Errorf("gamedata: invalid quest monster/deck/difficulty %d/%d/%d", monsterID, deckID, difficulty)
}
db, cleanup, err := openPackDatabase(root, version, packID)
if err != nil {
return QuestBattleDeck{}, err
}
defer cleanup()
var raw []byte
if err := db.QueryRow("SELECT ProtoBuf FROM FieldMonsterTable WHERE id=?", monsterID).Scan(&raw); err != nil {
return QuestBattleDeck{}, fmt.Errorf("gamedata: quest monster %d: %w", monsterID, err)
}
quests, err := packedInts(raw, 22)
if err != nil {
return QuestBattleDeck{}, err
}
if len(quests) == 0 {
return QuestBattleDeck{}, fmt.Errorf("gamedata: quest monster %d has no quest range", monsterID)
}
decks, err := packedInts(raw, 2)
if err != nil {
return QuestBattleDeck{}, err
}
group, err := phaseScalar(raw, 21)
if err != nil {
return QuestBattleDeck{}, err
}
common, release, err := OpenDatabase(root, version, "common")
if err != nil {
return QuestBattleDeck{}, err
}
defer release()
var kind uint64
for i, quest := range quests {
if quest == 0 || quest > 2147483647 {
return QuestBattleDeck{}, fmt.Errorf("gamedata: quest monster %d has invalid quest %d", monsterID, quest)
}
if err := common.QueryRow(fmt.Sprintf("SELECT ProtoBuf FROM QuestTable%d WHERE id=?", packID), quest).Scan(&raw); err != nil {
return QuestBattleDeck{}, fmt.Errorf("gamedata: monster %d quest %d: %w", monsterID, quest, err)
}
questKind, err := phaseScalar(raw, 63)
if err != nil {
return QuestBattleDeck{}, err
}
if questKind > 1 || (i > 0 && questKind != kind) {
return QuestBattleDeck{}, fmt.Errorf("gamedata: quest monster %d has unsupported or mixed quest types", monsterID)
}
kind = questKind
}
if kind == 1 {
difficulty = 0
}
selected, err := battleDeckForDifficultyFromDB(db, deckID, difficulty)
if err != nil {
return QuestBattleDeck{}, err
}
if group == 0 {
allowed := slices.Contains(decks, deckID)
if kind == 0 && selected > difficulty*100000 {
allowed = allowed || slices.Contains(decks, selected-difficulty*100000)
}
if !allowed {
return QuestBattleDeck{}, fmt.Errorf("gamedata: quest monster %d does not contain deck %d", monsterID, deckID)
}
} else if _, err := battleDeckPhasesFromDB(db, monsterID, selected); err != nil {
return QuestBattleDeck{}, err
}
return QuestBattleDeck{DeckID: selected, QuestIDs: quests}, nil
}
func openPackDatabase(root, version string, packID int) (*sql.DB, func(), error) {
if packID <= 0 {
return nil, nil, fmt.Errorf("gamedata: invalid battle pack %d", packID)
}
return OpenDatabase(root, version, fmt.Sprintf("pack%d", packID))
}
func battleDeckForDifficultyFromDB(db *sql.DB, deckID, difficulty uint64) (uint64, error) {
if deckID == 0 || deckID > 2147483647 || difficulty > 2 {
return 0, fmt.Errorf("gamedata: unsupported battle deck/difficulty %d/%d", deckID, difficulty)
}
var data []byte
if err := db.QueryRow("SELECT ProtoBuf FROM BattleDeckTable WHERE id=?", deckID).Scan(&data); err != nil {
return 0, fmt.Errorf("gamedata: battle deck %d: %w", deckID, err)
}
actual, err := phaseScalar(data, 24)
if err != nil {
return 0, err
}
if actual == difficulty {
return deckID, nil
}
// Only an ordinary deck can be promoted. Changing a previously selected
// difficulty deck would conceal stale client quest state.
if actual != 0 || difficulty == 0 {
return 0, fmt.Errorf("gamedata: deck %d difficulty %d differs from selected %d", deckID, actual, difficulty)
}
selected := deckID + difficulty*100000
if err := db.QueryRow("SELECT ProtoBuf FROM BattleDeckTable WHERE id=?", selected).Scan(&data); err != nil {
return 0, fmt.Errorf("gamedata: difficulty deck %d: %w", selected, err)
}
actual, err = phaseScalar(data, 24)
if err != nil {
return 0, err
}
if actual != difficulty {
return 0, fmt.Errorf("gamedata: difficulty deck %d has difficulty %d, want %d", selected, actual, difficulty)
}
return selected, nil
}