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
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
|
|
}
|