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

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, "'", "''") + "'"
}