Files
bd2/go/internal/server/gamedata/equipment_craft.go
T

355 lines
11 KiB
Go

package gamedata
import (
"database/sql"
"fmt"
"math"
)
const equipmentMakingTalentClass = 9
type EquipmentCraftRecipe struct {
ID uint64
ResultCount uint64
TalentLevel uint64
Costs []PromotionCost
Results []WeightedEquipment
}
type equipmentMakingTalent struct {
SkillGroup uint64
GrowthGroup uint64
}
type equipmentMakingTalentSkill struct {
Experience uint64
Catalyst uint64
}
type GeneratedEquipment struct {
Design EquipmentDesign
Main []EquipmentOptionChoice
Sub []EquipmentOptionChoice
Private *EquipmentOptionChoice
}
type EquipmentCraftDesign struct {
Recipes map[uint64]EquipmentCraftRecipe
Equipment map[uint64]EquipmentDesign
CharTalent map[uint64]equipmentMakingTalent
Skills map[[2]uint64]equipmentMakingTalentSkill
TalentNeed map[[2]uint64]uint64
draw func(uint64) (uint64, error)
}
func LoadEquipmentCraftDesign(root, version string) (*EquipmentCraftDesign, error) {
db, closeDB, err := openStatDatabase(root, version)
if err != nil {
return nil, err
}
defer closeDB()
return loadEquipmentCraftDesign(db)
}
func loadEquipmentCraftDesign(db *sql.DB) (*EquipmentCraftDesign, error) {
design := &EquipmentCraftDesign{
Recipes: make(map[uint64]EquipmentCraftRecipe), Equipment: make(map[uint64]EquipmentDesign),
CharTalent: make(map[uint64]equipmentMakingTalent), Skills: make(map[[2]uint64]equipmentMakingTalentSkill),
TalentNeed: make(map[[2]uint64]uint64), draw: cryptoDraw,
}
rows, err := db.Query("SELECT id,ProtoBuf FROM EquipmentMakingTable ORDER BY id")
if err != nil {
return nil, err
}
for rows.Next() {
var id uint64
var raw []byte
if err := rows.Scan(&id, &raw); err != nil {
rows.Close()
return nil, err
}
counts, _ := packedInts(raw, 4)
materialIDs, _ := packedInts(raw, 5)
materialTypes, _ := packedInts(raw, 6)
resultCounts, _ := packedInts(raw, 7)
boxIDs, _ := packedInts(raw, 8)
resultTypes, _ := packedInts(raw, 9)
talentLevels, _ := packedInts(raw, 11)
if len(counts) == 0 || len(counts) != len(materialIDs) || len(counts) != len(materialTypes) ||
len(resultCounts) != 1 || resultCounts[0] != 1 || len(boxIDs) != 1 ||
len(resultTypes) != 1 || resultTypes[0] != 9 || len(talentLevels) != 1 || talentLevels[0] == 0 {
rows.Close()
return nil, fmt.Errorf("gamedata: equipment making recipe %d malformed", id)
}
var box []byte
if err := db.QueryRow("SELECT ProtoBuf FROM RandomBoxTable WHERE id=?", boxIDs[0]).Scan(&box); err != nil {
rows.Close()
return nil, fmt.Errorf("gamedata: equipment making recipe %d random box: %w", id, err)
}
groups, _ := packedInts(box, 9)
if len(groups) != 1 || groups[0] == 0 {
rows.Close()
return nil, fmt.Errorf("gamedata: equipment making random box %d malformed", boxIDs[0])
}
results, equipmentOnly, err := classifyEquipmentRewardPool(db, groups[0])
if err != nil || !equipmentOnly || len(results) == 0 {
rows.Close()
return nil, fmt.Errorf("gamedata: equipment making recipe %d reward group: %w", id, err)
}
recipe := EquipmentCraftRecipe{ID: id, ResultCount: resultCounts[0], TalentLevel: talentLevels[0], Results: results}
for i := range counts {
if counts[i] == 0 || materialIDs[i] == 0 || materialTypes[i] == 0 {
rows.Close()
return nil, fmt.Errorf("gamedata: equipment making recipe %d invalid material", id)
}
recipe.Costs = append(recipe.Costs, PromotionCost{Type: materialTypes[i], ID: materialIDs[i], Count: counts[i]})
}
if err := design.loadEquipmentPool(db, results); err != nil {
rows.Close()
return nil, fmt.Errorf("gamedata: equipment making recipe %d: %w", id, err)
}
design.Recipes[id] = recipe
}
if err := rows.Err(); err != nil {
rows.Close()
return nil, err
}
if err := rows.Close(); err != nil {
return nil, err
}
talents := make(map[uint64]equipmentMakingTalent)
rows, err = db.Query("SELECT id,ProtoBuf FROM TalentTable")
if err != nil {
return nil, err
}
for rows.Next() {
var id uint64
var raw []byte
if err := rows.Scan(&id, &raw); err != nil {
rows.Close()
return nil, err
}
classes, _ := packedInts(raw, 4)
growthGroups, _ := packedInts(raw, 6)
skillGroups, _ := packedInts(raw, 18)
if len(classes) == 1 && classes[0] == equipmentMakingTalentClass && len(growthGroups) == 1 && len(skillGroups) == 1 {
talents[id] = equipmentMakingTalent{SkillGroup: skillGroups[0], GrowthGroup: growthGroups[0]}
}
}
if err := rows.Close(); err != nil {
return nil, err
}
rows, err = db.Query("SELECT id,ProtoBuf FROM CharTable")
if err != nil {
return nil, err
}
for rows.Next() {
var id uint64
var raw []byte
if err := rows.Scan(&id, &raw); err != nil {
rows.Close()
return nil, err
}
talentIDs, _ := packedInts(raw, 18)
if len(talentIDs) == 1 {
if talent, ok := talents[talentIDs[0]]; ok {
design.CharTalent[id] = talent
}
}
}
if err := rows.Close(); err != nil {
return nil, err
}
rows, err = db.Query("SELECT groupId,id,ProtoBuf FROM TalentSkillTable WHERE classType=?", equipmentMakingTalentClass)
if err != nil {
return nil, err
}
for rows.Next() {
var group, level uint64
var raw []byte
if err := rows.Scan(&group, &level, &raw); err != nil {
rows.Close()
return nil, err
}
catalysts, _ := packedInts(raw, 1)
experience, _ := packedInts(raw, 5)
if len(catalysts) > 1 || len(experience) > 1 {
rows.Close()
return nil, fmt.Errorf("gamedata: equipment making talent skill %d/%d malformed", group, level)
}
var skill equipmentMakingTalentSkill
if len(experience) == 1 {
skill.Experience = experience[0]
}
if len(catalysts) == 1 {
skill.Catalyst = catalysts[0]
}
design.Skills[[2]uint64{group, level}] = skill
}
if err := rows.Close(); err != nil {
return nil, err
}
rows, err = db.Query("SELECT groupId,id,ProtoBuf FROM TalentGrowthTable")
if err != nil {
return nil, err
}
for rows.Next() {
var group, level uint64
var raw []byte
if err := rows.Scan(&group, &level, &raw); err != nil {
rows.Close()
return nil, err
}
need, _ := packedInts(raw, 6)
if len(need) == 1 {
design.TalentNeed[[2]uint64{group, level}] = need[0]
}
}
if err := rows.Close(); err != nil {
return nil, err
}
return design, nil
}
func (d *EquipmentCraftDesign) loadEquipmentPool(db *sql.DB, pool []WeightedEquipment) error {
for _, result := range pool {
if result.ID == 0 {
if len(result.Children) == 0 {
return fmt.Errorf("empty nested equipment reward")
}
if err := d.loadEquipmentPool(db, result.Children); err != nil {
return err
}
continue
}
if len(result.Children) != 0 {
return fmt.Errorf("equipment reward %d also has children", result.ID)
}
if _, exists := d.Equipment[result.ID]; exists {
continue
}
equipment, err := loadEquipmentDesign(db, result.ID)
if err != nil {
return err
}
d.Equipment[result.ID] = equipment
}
return nil
}
func (d *EquipmentCraftDesign) Recipe(id uint64) (EquipmentCraftRecipe, bool) {
if d == nil {
return EquipmentCraftRecipe{}, false
}
recipe, ok := d.Recipes[id]
return recipe, ok
}
func (d *EquipmentCraftDesign) Generate(recipeID uint64) (GeneratedEquipment, error) {
if d == nil {
return GeneratedEquipment{}, fmt.Errorf("gamedata: equipment crafting unavailable")
}
recipe, ok := d.Recipes[recipeID]
if !ok {
return GeneratedEquipment{}, fmt.Errorf("gamedata: unknown equipment making recipe %d", recipeID)
}
draw := d.draw
if draw == nil {
draw = cryptoDraw
}
id, err := rollEquipmentChoiceWith(recipe.Results, draw)
if err != nil {
return GeneratedEquipment{}, err
}
equipment, ok := d.Equipment[id]
if !ok {
return GeneratedEquipment{}, fmt.Errorf("gamedata: equipment making result %d missing", id)
}
main, sub, private, err := rollEquipmentOptionsWith(equipment, draw)
if err != nil {
return GeneratedEquipment{}, err
}
return GeneratedEquipment{Design: equipment, Main: main, Sub: sub, Private: private}, nil
}
func rollEquipmentOptionsWith(design EquipmentDesign, draw func(uint64) (uint64, error)) (main, sub []EquipmentOptionChoice, private *EquipmentOptionChoice, err error) {
pick := func(groups []OptionGroup) ([]EquipmentOptionChoice, error) {
out := make([]EquipmentOptionChoice, 0, len(groups))
for _, group := range groups {
pool := make([]WeightedEquipment, 0, len(group.Choices))
for _, choice := range group.Choices {
pool = append(pool, WeightedEquipment{ID: choice.ID, Weight: choice.Weight})
}
id, err := rollEquipmentChoiceWith(pool, draw)
if err != nil {
return nil, err
}
out = append(out, EquipmentOptionChoice{GroupID: group.ID, ID: id})
}
return out, nil
}
if main, err = pick(design.Main); err != nil {
return nil, nil, nil, err
}
if sub, err = pick(design.Sub); err != nil {
return nil, nil, nil, err
}
choices, err := pick(design.Private)
if err != nil {
return nil, nil, nil, err
}
if len(choices) > 1 {
return nil, nil, nil, fmt.Errorf("gamedata: equipment %d has %d private option groups", design.ID, len(choices))
}
if len(choices) == 1 {
choice := choices[0]
private = &choice
}
return main, sub, private, nil
}
func (d *EquipmentCraftDesign) Talent(characterID, characterLevel, recipeLevel, count, currentExperience uint64) (gain, catalyst, maximum uint64, err error) {
if d == nil || characterID == 0 || characterLevel == 0 || recipeLevel == 0 || count == 0 {
return 0, 0, 0, fmt.Errorf("gamedata: invalid equipment making talent request")
}
talent, ok := d.CharTalent[characterID]
if !ok {
return 0, 0, 0, fmt.Errorf("gamedata: character %d has no equipment making talent", characterID)
}
if characterLevel < recipeLevel {
return 0, 0, 0, fmt.Errorf("gamedata: equipment making talent level %d is below recipe level %d", characterLevel, recipeLevel)
}
recipeSkill, ok := d.Skills[[2]uint64{talent.SkillGroup, recipeLevel}]
if !ok {
return 0, 0, 0, fmt.Errorf("gamedata: equipment making talent skill %d/%d missing", talent.SkillGroup, recipeLevel)
}
currentSkill, ok := d.Skills[[2]uint64{talent.SkillGroup, characterLevel}]
if !ok {
return 0, 0, 0, fmt.Errorf("gamedata: equipment making talent skill %d/%d missing", talent.SkillGroup, characterLevel)
}
if currentSkill.Experience != 0 && count > math.MaxUint64/currentSkill.Experience {
return 0, 0, 0, fmt.Errorf("gamedata: equipment making talent experience overflow")
}
if recipeSkill.Catalyst != 0 && count > math.MaxUint64/recipeSkill.Catalyst {
return 0, 0, 0, fmt.Errorf("gamedata: equipment making catalyst overflow")
}
for level := uint64(1); level <= characterLevel; level++ {
need, exists := d.TalentNeed[[2]uint64{talent.GrowthGroup, level}]
if !exists {
return 0, 0, 0, fmt.Errorf("gamedata: equipment making talent growth %d/%d missing", talent.GrowthGroup, level)
}
if maximum > math.MaxUint64-need {
return 0, 0, 0, fmt.Errorf("gamedata: equipment making talent threshold overflow")
}
maximum += need
}
wanted := currentSkill.Experience * count
if currentExperience >= maximum {
wanted = 0
} else if wanted > maximum-currentExperience {
wanted = maximum - currentExperience
}
return wanted, recipeSkill.Catalyst * count, maximum, nil
}