package model import ( "context" "encoding/json" "log/slog" "sync/atomic" "time" ) // BuffJSON is DB/admin JSON for one buff type (durations in ms). type BuffJSON struct { Name string `json:"name"` DurationMs int64 `json:"durationMs"` Magnitude float64 `json:"magnitude"` CooldownMs int64 `json:"cooldownMs"` } // DebuffJSON is DB/admin JSON for one debuff type (duration in ms). type DebuffJSON struct { Name string `json:"name"` DurationMs int64 `json:"durationMs"` Magnitude float64 `json:"magnitude"` } type buffDebuffPayload struct { Buffs map[string]BuffJSON `json:"buffs"` Debuffs map[string]DebuffJSON `json:"debuffs"` } type buffDebuffCatalogData struct { buffs map[BuffType]Buff debuffs map[DebuffType]Debuff } var buffDebuffCatalog atomic.Value func init() { buffDebuffCatalog.Store(&buffDebuffCatalogData{ buffs: seedBuffMap(), debuffs: seedDebuffMap(), }) } func seedBuffMap() map[BuffType]Buff { return map[BuffType]Buff{ BuffRush: { Type: BuffRush, Name: "Rush", Duration: 5 * time.Minute, Magnitude: 0.50, CooldownDuration: 15 * time.Minute, }, BuffRage: { Type: BuffRage, Name: "Rage", Duration: 3 * time.Minute, Magnitude: 1.00, CooldownDuration: 10 * time.Minute, }, BuffShield: { Type: BuffShield, Name: "Shield", Duration: 5 * time.Minute, Magnitude: 0.50, CooldownDuration: 12 * time.Minute, }, BuffLuck: { Type: BuffLuck, Name: "Luck", Duration: 30 * time.Minute, Magnitude: 1.0, CooldownDuration: 2 * time.Hour, }, BuffResurrection: { Type: BuffResurrection, Name: "Resurrection", Duration: 10 * time.Minute, Magnitude: 0.50, CooldownDuration: 30 * time.Minute, }, BuffHeal: { Type: BuffHeal, Name: "Heal", Duration: 1 * time.Second, Magnitude: 0.50, CooldownDuration: 5 * time.Minute, }, BuffPowerPotion: { Type: BuffPowerPotion, Name: "Power Potion", Duration: 5 * time.Minute, Magnitude: 1.50, CooldownDuration: 20 * time.Minute, }, BuffWarCry: { Type: BuffWarCry, Name: "War Cry", Duration: 3 * time.Minute, Magnitude: 1.00, CooldownDuration: 10 * time.Minute, }, } } func seedDebuffMap() map[DebuffType]Debuff { return map[DebuffType]Debuff{ DebuffPoison: { Type: DebuffPoison, Name: "Poison", Duration: 50 * time.Second, Magnitude: 0.02, }, DebuffFreeze: { Type: DebuffFreeze, Name: "Freeze", Duration: 30 * time.Second, Magnitude: 0.50, }, DebuffBurn: { Type: DebuffBurn, Name: "Burn", Duration: 40 * time.Second, Magnitude: 0.03, }, DebuffStun: { Type: DebuffStun, Name: "Stun", Duration: 5 * time.Second, Magnitude: 1.0, }, DebuffSlow: { Type: DebuffSlow, Name: "Slow", Duration: 40 * time.Second, Magnitude: 0.40, }, DebuffWeaken: { Type: DebuffWeaken, Name: "Weaken", Duration: 50 * time.Second, Magnitude: 0.30, }, DebuffIceSlow: { Type: DebuffIceSlow, Name: "Ice Slow", Duration: 40 * time.Second, Magnitude: 0.20, }, } } func buffFromStrictJSON(bt BuffType, j BuffJSON) Buff { return Buff{ Type: bt, Name: j.Name, Duration: time.Duration(j.DurationMs) * time.Millisecond, Magnitude: j.Magnitude, CooldownDuration: time.Duration(j.CooldownMs) * time.Millisecond, } } func debuffFromStrictJSON(dt DebuffType, j DebuffJSON) Debuff { return Debuff{ Type: dt, Name: j.Name, Duration: time.Duration(j.DurationMs) * time.Millisecond, Magnitude: j.Magnitude, } } func cloneBuffMap(src map[BuffType]Buff) map[BuffType]Buff { out := make(map[BuffType]Buff, len(src)) for k, v := range src { out[k] = v } return out } func cloneDebuffMap(src map[DebuffType]Debuff) map[DebuffType]Debuff { out := make(map[DebuffType]Debuff, len(src)) for k, v := range src { out[k] = v } return out } // BuffDebuffPayloadLoader loads raw JSON from persistence. type BuffDebuffPayloadLoader interface { LoadBuffDebuffConfigPayload(ctx context.Context) ([]byte, error) } // ReloadBuffDebuffCatalog merges DB payload into built-in seeds (same pattern as tuning). func ReloadBuffDebuffCatalog(ctx context.Context, logger *slog.Logger, loader BuffDebuffPayloadLoader) error { payload, err := loader.LoadBuffDebuffConfigPayload(ctx) if err != nil { if logger != nil { logger.Warn("buff/debuff config load failed", "error", err) } return err } buffs := cloneBuffMap(seedBuffMap()) debuffs := cloneDebuffMap(seedDebuffMap()) if len(payload) > 0 { var raw buffDebuffPayload if err := json.Unmarshal(payload, &raw); err != nil { if logger != nil { logger.Warn("buff/debuff config parse failed", "error", err) } return err } // Per-key full replace: payload must include all fields for edited types (admin UI sends full effective maps). for key, j := range raw.Buffs { bt := BuffType(key) if _, ok := buffs[bt]; ok { buffs[bt] = buffFromStrictJSON(bt, j) } } for key, j := range raw.Debuffs { dt := DebuffType(key) if _, ok := debuffs[dt]; ok { debuffs[dt] = debuffFromStrictJSON(dt, j) } } } buffDebuffCatalog.Store(&buffDebuffCatalogData{buffs: buffs, debuffs: debuffs}) return nil } func catalogData() *buffDebuffCatalogData { return buffDebuffCatalog.Load().(*buffDebuffCatalogData) } // BuffDefinition returns the active buff template (DB + defaults). func BuffDefinition(bt BuffType) (Buff, bool) { b, ok := catalogData().buffs[bt] return b, ok } // DebuffDefinition returns the active debuff template (DB + defaults). func DebuffDefinition(dt DebuffType) (Debuff, bool) { d, ok := catalogData().debuffs[dt] return d, ok } // BuffCatalogSnapshot returns copies for admin/API. func BuffCatalogSnapshot() map[BuffType]Buff { src := catalogData().buffs out := make(map[BuffType]Buff, len(src)) for k, v := range src { out[k] = v } return out } // AttachDebuffCatalogForClient fills h.DebuffCatalog from the active catalog (for JSON responses only). func AttachDebuffCatalogForClient(h *Hero) { if h == nil { return } _, deb := BuffCatalogEffectiveJSON() h.DebuffCatalog = deb } // DebuffCatalogSnapshot returns copies for admin/API. func DebuffCatalogSnapshot() map[DebuffType]Debuff { src := catalogData().debuffs out := make(map[DebuffType]Debuff, len(src)) for k, v := range src { out[k] = v } return out } // BuffToJSON converts Buff to BuffJSON (ms). func BuffToJSON(b Buff) BuffJSON { return BuffJSON{ Name: b.Name, DurationMs: b.Duration.Milliseconds(), Magnitude: b.Magnitude, CooldownMs: b.CooldownDuration.Milliseconds(), } } // DebuffToJSON converts Debuff to DebuffJSON (ms). func DebuffToJSON(d Debuff) DebuffJSON { return DebuffJSON{ Name: d.Name, DurationMs: d.Duration.Milliseconds(), Magnitude: d.Magnitude, } } // BuffCatalogEffectiveJSON builds string-keyed maps for admin/API. func BuffCatalogEffectiveJSON() (map[string]BuffJSON, map[string]DebuffJSON) { buffs := BuffCatalogSnapshot() outB := make(map[string]BuffJSON, len(buffs)) for t, b := range buffs { outB[string(t)] = BuffToJSON(b) } debuffs := DebuffCatalogSnapshot() outD := make(map[string]DebuffJSON, len(debuffs)) for t, d := range debuffs { outD[string(t)] = DebuffToJSON(d) } return outB, outD }