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.

152 lines
4.3 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"github.com/denisovdennis/autohero/internal/model"
)
// enemyPartial mirrors model.Enemy with pointer fields so JSON omits mean "keep DB value".
type enemyPartial struct {
ID *int64 `json:"id"`
Type *string `json:"type"`
Name *string `json:"name"`
HP *int `json:"hp"`
MaxHP *int `json:"maxHp"`
Attack *int `json:"attack"`
Defense *int `json:"defense"`
Speed *float64 `json:"speed"`
CritChance *float64 `json:"critChance"`
MinLevel *int `json:"minLevel"`
MaxLevel *int `json:"maxLevel"`
BaseLevel *int `json:"baseLevel"`
LevelVariance *float64 `json:"levelVariance"`
MaxHeroLevelDiff *int `json:"maxHeroLevelDiff"`
HPPerLevel *float64 `json:"hpPerLevel"`
AttackPerLevel *float64 `json:"attackPerLevel"`
DefensePerLevel *float64 `json:"defensePerLevel"`
XPPerLevel *float64 `json:"xpPerLevel"`
GoldPerLevel *float64 `json:"goldPerLevel"`
Level *int `json:"level"`
XPReward *int64 `json:"xpReward"`
GoldReward *int64 `json:"goldReward"`
SpecialAbilities *[]model.SpecialAbility `json:"specialAbilities"`
IsElite *bool `json:"isElite"`
}
// applyEnemyOverlayJSON reads a JSON object keyed by enemy type (string), merges each partial onto templates.
// Unknown keys log a warning and are skipped. Keys for types not present in templates log a warning.
func applyEnemyOverlayJSON(path string, templates map[model.EnemyType]model.Enemy) (map[model.EnemyType]model.Enemy, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read overlay %q: %w", path, err)
}
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, fmt.Errorf("parse overlay JSON: %w", err)
}
out := make(map[model.EnemyType]model.Enemy, len(templates))
for k, v := range templates {
out[k] = v
}
for typeKey, rawMsg := range raw {
et := model.EnemyType(typeKey)
base, ok := out[et]
if !ok {
fmt.Fprintf(os.Stderr, "balanceall overlay: skip unknown type %q (not in loaded templates)\n", typeKey)
continue
}
var p enemyPartial
if err := json.Unmarshal(rawMsg, &p); err != nil {
return nil, fmt.Errorf("overlay %q: %w", typeKey, err)
}
mergeEnemyPartial(&base, &p)
out[et] = base
}
return out, nil
}
func mergeEnemyPartial(dst *model.Enemy, p *enemyPartial) {
if p.ID != nil {
dst.ID = *p.ID
}
if p.Type != nil {
dst.Type = model.EnemyType(*p.Type)
}
if p.Name != nil {
dst.Name = *p.Name
}
if p.HP != nil {
dst.HP = *p.HP
}
if p.MaxHP != nil {
dst.MaxHP = *p.MaxHP
}
if p.Attack != nil {
dst.Attack = *p.Attack
}
if p.Defense != nil {
dst.Defense = *p.Defense
}
if p.Speed != nil {
dst.Speed = *p.Speed
}
if p.CritChance != nil {
dst.CritChance = *p.CritChance
}
if p.MinLevel != nil {
dst.MinLevel = *p.MinLevel
}
if p.MaxLevel != nil {
dst.MaxLevel = *p.MaxLevel
}
if p.BaseLevel != nil {
dst.BaseLevel = *p.BaseLevel
}
if p.LevelVariance != nil {
dst.LevelVariance = *p.LevelVariance
}
if p.MaxHeroLevelDiff != nil {
dst.MaxHeroLevelDiff = *p.MaxHeroLevelDiff
}
if p.HPPerLevel != nil {
dst.HPPerLevel = *p.HPPerLevel
}
if p.AttackPerLevel != nil {
dst.AttackPerLevel = *p.AttackPerLevel
}
if p.DefensePerLevel != nil {
dst.DefensePerLevel = *p.DefensePerLevel
}
if p.XPPerLevel != nil {
dst.XPPerLevel = *p.XPPerLevel
}
if p.GoldPerLevel != nil {
dst.GoldPerLevel = *p.GoldPerLevel
}
if p.Level != nil {
dst.Level = *p.Level
}
if p.XPReward != nil {
dst.XPReward = *p.XPReward
}
if p.GoldReward != nil {
dst.GoldReward = *p.GoldReward
}
if p.SpecialAbilities != nil {
dst.SpecialAbilities = *p.SpecialAbilities
}
if p.IsElite != nil {
dst.IsElite = *p.IsElite
}
// If only one of hp/maxHp was overridden, keep them aligned for template rows.
if p.MaxHP != nil && p.HP == nil {
dst.HP = dst.MaxHP
}
if p.HP != nil && p.MaxHP == nil {
dst.MaxHP = dst.HP
}
}