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.
181 lines
5.6 KiB
Go
181 lines
5.6 KiB
Go
package gamedata
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
type CostumePotentialCost struct{ Type, ID, Count uint64 }
|
|
|
|
type CostumePotentialNode struct {
|
|
ID uint64
|
|
NodeType uint64
|
|
StatType uint64
|
|
StatValue float64
|
|
ConditionGrade uint64
|
|
Prerequisites []uint64
|
|
Costs []CostumePotentialCost
|
|
}
|
|
|
|
type CostumePotentialDesign struct {
|
|
Nodes map[uint64]map[uint64]CostumePotentialNode
|
|
CostumeUnique map[uint64]uint64
|
|
CharacterGrade map[uint64]uint64
|
|
CharacterUnique map[uint64]uint64
|
|
}
|
|
|
|
func LoadCostumePotentialDesign(root, version string) (*CostumePotentialDesign, error) {
|
|
db, release, err := OpenDatabase(root, version, "common")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer release()
|
|
return loadCostumePotentialDesign(db)
|
|
}
|
|
|
|
func loadCostumePotentialDesign(db *sql.DB) (*CostumePotentialDesign, error) {
|
|
d := &CostumePotentialDesign{Nodes: map[uint64]map[uint64]CostumePotentialNode{}, CostumeUnique: map[uint64]uint64{}, CharacterGrade: map[uint64]uint64{}, CharacterUnique: map[uint64]uint64{}}
|
|
rows, err := db.Query("SELECT groupId,id,ProtoBuf FROM CostumeNodeTable ORDER BY groupId,id")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for rows.Next() {
|
|
var groupID, id uint64
|
|
var proto []byte
|
|
if err := rows.Scan(&groupID, &id, &proto); err != nil {
|
|
rows.Close()
|
|
return nil, err
|
|
}
|
|
counts, _ := packedInts(proto, 1)
|
|
ids, _ := packedInts(proto, 2)
|
|
types, _ := packedInts(proto, 3)
|
|
if len(counts) == 0 || len(counts) != len(ids) || len(counts) != len(types) {
|
|
rows.Close()
|
|
return nil, fmt.Errorf("gamedata: invalid costume potential costs %d/%d", groupID, id)
|
|
}
|
|
grade, _ := packedInts(proto, 17)
|
|
conditions, _ := packedInts(proto, 18)
|
|
node := CostumePotentialNode{ID: id, Prerequisites: append([]uint64(nil), conditions...)}
|
|
nodeTypes, nodeTypeErr := packedInts(proto, 27)
|
|
statTypes, statTypeErr := packedInts(proto, 29)
|
|
statValue, _, statValueErr := fixed64Double(proto, 30)
|
|
if nodeTypeErr != nil || statTypeErr != nil || statValueErr != nil || len(nodeTypes) != 1 || len(statTypes) > 1 || math.IsNaN(statValue) || math.IsInf(statValue, 0) || statValue < 0 {
|
|
rows.Close()
|
|
return nil, fmt.Errorf("gamedata: invalid costume potential stats %d/%d", groupID, id)
|
|
}
|
|
node.NodeType = nodeTypes[0]
|
|
node.StatValue = statValue
|
|
if len(statTypes) == 1 {
|
|
node.StatType = statTypes[0]
|
|
}
|
|
if (node.NodeType == 1 || node.NodeType == 2) && (node.StatType == 0 || node.StatType > 20) {
|
|
rows.Close()
|
|
return nil, fmt.Errorf("gamedata: unsupported costume potential stat %d/%d option %d", groupID, id, node.StatType)
|
|
}
|
|
if len(grade) == 1 {
|
|
node.ConditionGrade = grade[0]
|
|
} else if len(grade) > 1 {
|
|
rows.Close()
|
|
return nil, fmt.Errorf("gamedata: invalid costume potential grade %d/%d", groupID, id)
|
|
}
|
|
for i := range counts {
|
|
if counts[i] == 0 || types[i] == 0 || (types[i] != 4 && ids[i] == 0) {
|
|
rows.Close()
|
|
return nil, fmt.Errorf("gamedata: invalid costume potential cost %d/%d", groupID, id)
|
|
}
|
|
node.Costs = append(node.Costs, CostumePotentialCost{Type: types[i], ID: ids[i], Count: counts[i]})
|
|
}
|
|
if d.Nodes[groupID] == nil {
|
|
d.Nodes[groupID] = map[uint64]CostumePotentialNode{}
|
|
}
|
|
d.Nodes[groupID][id] = node
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
rows, err = db.Query("SELECT id,ProtoBuf FROM CostumeTable")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for rows.Next() {
|
|
var id uint64
|
|
var proto []byte
|
|
if err := rows.Scan(&id, &proto); err != nil {
|
|
rows.Close()
|
|
return nil, err
|
|
}
|
|
unique, _ := packedInts(proto, 27)
|
|
if len(unique) == 1 {
|
|
d.CostumeUnique[id] = unique[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 proto []byte
|
|
if err := rows.Scan(&id, &proto); err != nil {
|
|
rows.Close()
|
|
return nil, err
|
|
}
|
|
grade, _ := packedInts(proto, 10)
|
|
unique, _ := packedInts(proto, 20)
|
|
if len(grade) == 1 && len(unique) == 1 {
|
|
d.CharacterGrade[id] = grade[0]
|
|
d.CharacterUnique[id] = unique[0]
|
|
}
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(d.Nodes) == 0 {
|
|
return nil, fmt.Errorf("gamedata: CostumeNodeTable is empty")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func (d *CostumePotentialDesign) Validate(costumeID, characterID, grade uint64, active, requested []uint64) ([]CostumePotentialCost, error) {
|
|
if d == nil || costumeID == 0 || characterID == 0 || len(requested) == 0 {
|
|
return nil, fmt.Errorf("gamedata: invalid costume potential request")
|
|
}
|
|
if d.CostumeUnique[costumeID] == 0 || d.CostumeUnique[costumeID] != d.CharacterUnique[characterID] {
|
|
return nil, fmt.Errorf("gamedata: costume %d does not belong to character %d", costumeID, characterID)
|
|
}
|
|
if grade == 0 {
|
|
grade = d.CharacterGrade[characterID]
|
|
}
|
|
known := make(map[uint64]bool, len(active)+len(requested))
|
|
for _, id := range active {
|
|
known[id] = true
|
|
}
|
|
for _, id := range requested {
|
|
if id == 0 || known[id] {
|
|
return nil, fmt.Errorf("gamedata: duplicate costume potential node %d", id)
|
|
}
|
|
known[id] = true
|
|
}
|
|
var costs []CostumePotentialCost
|
|
for _, id := range requested {
|
|
node, exists := d.Nodes[costumeID][id]
|
|
if !exists {
|
|
return nil, fmt.Errorf("gamedata: costume %d has no potential node %d", costumeID, id)
|
|
}
|
|
if node.ConditionGrade > grade {
|
|
return nil, fmt.Errorf("gamedata: potential node %d requires growth grade %d", id, node.ConditionGrade)
|
|
}
|
|
for _, prerequisite := range node.Prerequisites {
|
|
if !known[prerequisite] {
|
|
return nil, fmt.Errorf("gamedata: potential node %d requires node %d", id, prerequisite)
|
|
}
|
|
}
|
|
costs = append(costs, node.Costs...)
|
|
}
|
|
return costs, nil
|
|
}
|