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.

271 lines
6.9 KiB
Go

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.30,
CooldownDuration: 15 * time.Minute,
},
BuffRage: {
Type: BuffRage, Name: "Rage",
Duration: 3 * time.Minute, Magnitude: 0.50,
CooldownDuration: 10 * time.Minute,
},
BuffShield: {
Type: BuffShield, Name: "Shield",
Duration: 5 * time.Minute, Magnitude: 0.35,
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.40,
CooldownDuration: 30 * time.Minute,
},
BuffHeal: {
Type: BuffHeal, Name: "Heal",
Duration: 1 * time.Second, Magnitude: 0.35,
CooldownDuration: 5 * time.Minute,
},
BuffPowerPotion: {
Type: BuffPowerPotion, Name: "Power Potion",
Duration: 5 * time.Minute, Magnitude: 0.85,
CooldownDuration: 20 * time.Minute,
},
BuffWarCry: {
Type: BuffWarCry, Name: "War Cry",
Duration: 3 * time.Minute, Magnitude: 0.50,
CooldownDuration: 10 * time.Minute,
},
}
}
func seedDebuffMap() map[DebuffType]Debuff {
return map[DebuffType]Debuff{
DebuffPoison: {
Type: DebuffPoison, Name: "Poison",
Duration: 5 * time.Second, Magnitude: 0.02,
},
DebuffFreeze: {
Type: DebuffFreeze, Name: "Freeze",
Duration: 3 * time.Second, Magnitude: 0.50,
},
DebuffBurn: {
Type: DebuffBurn, Name: "Burn",
Duration: 4 * time.Second, Magnitude: 0.03,
},
DebuffStun: {
Type: DebuffStun, Name: "Stun",
Duration: 2 * time.Second, Magnitude: 1.0,
},
DebuffSlow: {
Type: DebuffSlow, Name: "Slow",
Duration: 4 * time.Second, Magnitude: 0.40,
},
DebuffWeaken: {
Type: DebuffWeaken, Name: "Weaken",
Duration: 5 * time.Second, Magnitude: 0.30,
},
DebuffIceSlow: {
Type: DebuffIceSlow, Name: "Ice Slow",
Duration: 4 * 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
}
// 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
}