You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
5.0 KiB
Go
171 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/denisovdennis/autohero/internal/model"
|
|
"github.com/denisovdennis/autohero/internal/storage"
|
|
)
|
|
|
|
func loadGearCatalog(ctx context.Context, cs *storage.ContentStore, base string) ([]model.GearFamily, error) {
|
|
switch strings.ToLower(strings.TrimSpace(base)) {
|
|
case "", "db":
|
|
return cs.LoadGearFamilies(ctx)
|
|
case "code":
|
|
return model.DefaultGearFamilies(), nil
|
|
default:
|
|
return nil, fmt.Errorf("gear-base must be db or code, got %q", base)
|
|
}
|
|
}
|
|
|
|
// gearFamilyPatchJSON holds optional overrides merged onto catalog families after DB/code load.
|
|
type gearFamilyPatchJSON struct {
|
|
BasePrimary *int `json:"basePrimary,omitempty"`
|
|
SpeedModifier *float64 `json:"speedModifier,omitempty"`
|
|
BaseCrit *float64 `json:"baseCrit,omitempty"`
|
|
AgilityBonus *int `json:"agilityBonus,omitempty"`
|
|
StatType *string `json:"statType,omitempty"`
|
|
}
|
|
|
|
// listOverlayKeys returns top-level JSON object keys (patch targets).
|
|
func listOverlayKeys(path string) ([]string, error) {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var raw map[string]json.RawMessage
|
|
if err := json.Unmarshal(b, &raw); err != nil {
|
|
return nil, fmt.Errorf("gear overlay JSON: %w", err)
|
|
}
|
|
keys := make([]string, 0, len(raw))
|
|
for k := range raw {
|
|
keys = append(keys, strings.TrimSpace(k))
|
|
}
|
|
return keys, nil
|
|
}
|
|
|
|
// applyGearOverlayJSON clones families, applies patches keyed by "name" or "slot:name" (e.g. "main_hand:Soul Reaver").
|
|
func applyGearOverlayJSON(path string, families []model.GearFamily) ([]model.GearFamily, error) {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var raw map[string]json.RawMessage
|
|
if err := json.Unmarshal(b, &raw); err != nil {
|
|
return nil, fmt.Errorf("gear overlay JSON: %w", err)
|
|
}
|
|
patches := make(map[string]gearFamilyPatchJSON, len(raw))
|
|
for k, v := range raw {
|
|
var p gearFamilyPatchJSON
|
|
if err := json.Unmarshal(v, &p); err != nil {
|
|
return nil, fmt.Errorf("gear overlay key %q: %w", k, err)
|
|
}
|
|
patches[strings.TrimSpace(k)] = p
|
|
}
|
|
out := cloneGearFamilies(families)
|
|
for i := range out {
|
|
key := overlayKey(out[i])
|
|
if p, ok := patches[key]; ok {
|
|
mergePatch(&out[i], p)
|
|
continue
|
|
}
|
|
if p, ok := patches[out[i].Name]; ok {
|
|
mergePatch(&out[i], p)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func overlayKey(g model.GearFamily) string {
|
|
return fmt.Sprintf("%s:%s", g.Slot, g.Name)
|
|
}
|
|
|
|
func cloneGearFamilies(f []model.GearFamily) []model.GearFamily {
|
|
out := make([]model.GearFamily, len(f))
|
|
copy(out, f)
|
|
return out
|
|
}
|
|
|
|
func mergePatch(g *model.GearFamily, p gearFamilyPatchJSON) {
|
|
if p.BasePrimary != nil {
|
|
g.BasePrimary = *p.BasePrimary
|
|
}
|
|
if p.SpeedModifier != nil {
|
|
g.SpeedModifier = *p.SpeedModifier
|
|
}
|
|
if p.BaseCrit != nil {
|
|
g.BaseCrit = *p.BaseCrit
|
|
}
|
|
if p.AgilityBonus != nil {
|
|
g.AgilityBonus = *p.AgilityBonus
|
|
}
|
|
if p.StatType != nil {
|
|
g.StatType = *p.StatType
|
|
}
|
|
}
|
|
|
|
// printGearOverlayMigrationSQL prints UPDATEs for gear (and equipment_items for extended slots) for each overlay key.
|
|
func printGearOverlayMigrationSQL(families []model.GearFamily, overlayKeys []string) {
|
|
bySlotName := make(map[string]model.GearFamily, len(families))
|
|
byName := make(map[string]model.GearFamily, len(families))
|
|
for _, g := range families {
|
|
bySlotName[overlayKey(g)] = g
|
|
byName[g.Name] = g
|
|
}
|
|
fmt.Printf("-- balanceall -gear-print-sql (pairs with -gear-overlay)\n\n")
|
|
for _, k := range overlayKeys {
|
|
g, ok := bySlotName[k]
|
|
if !ok {
|
|
g, ok = byName[k]
|
|
}
|
|
if !ok {
|
|
fmt.Printf("-- WARNING: no catalog family for overlay key %q\n", k)
|
|
continue
|
|
}
|
|
r := catalogRarityForName(g.Name)
|
|
ps := model.ScalePrimary(g.BasePrimary, 1, r)
|
|
fmt.Printf(`UPDATE public.gear SET base_primary = %d, primary_stat = %d WHERE slot = '%s' AND name = %s;
|
|
`, g.BasePrimary, ps, string(g.Slot), sqlQuote(g.Name))
|
|
switch g.Slot {
|
|
case model.SlotMainHand, model.SlotChest:
|
|
// Catalog is fully in public.gear above.
|
|
default:
|
|
if g.FormID != "" {
|
|
fmt.Printf(`UPDATE public.equipment_items SET base_primary = %d, primary_stat = %d WHERE form_id = %s;
|
|
`, g.BasePrimary, ps, sqlQuote(g.FormID))
|
|
}
|
|
}
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
func catalogRarityForName(name string) model.Rarity {
|
|
switch name {
|
|
case "Rusty Dagger", "Iron Sword", "Rusty Axe",
|
|
"Leather Armor", "Chainmail", "Iron Plate":
|
|
return model.RarityCommon
|
|
case "Iron Dagger", "Steel Sword", "Battle Axe",
|
|
"Ranger's Vest", "Reinforced Mail", "Steel Plate":
|
|
return model.RarityUncommon
|
|
case "Assassin's Blade", "Longsword", "War Axe",
|
|
"Shadow Cloak", "Battle Armor", "Fortress Armor", "Guardian's Plate":
|
|
return model.RarityRare
|
|
case "Phantom Edge", "Excalibur", "Infernal Axe",
|
|
"Phantom Garb", "Royal Guard", "Dragon Scale", "Guardian's Bastion":
|
|
return model.RarityEpic
|
|
case "Fang of the Void", "Soul Reaver", "Godslayer's Edge",
|
|
"Whisper of the Void", "Crown of Eternity", "Dragon Slayer", "Ancient Guardian's Aegis":
|
|
return model.RarityLegendary
|
|
default:
|
|
return model.RarityCommon
|
|
}
|
|
}
|
|
|
|
func sqlQuote(s string) string {
|
|
return "'" + strings.ReplaceAll(s, "'", "''") + "'"
|
|
}
|