DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
hanfuit committed 2026-11-04 14:31:55 +08:00
commit 645b64fe21
4388 files changed
+1405457

No files matched your search

+291
View File
@@ -0,0 +1,291 @@
package reload
import (
"context"
"encoding/json"
"errors"
"log/slog"
"strings"
"sync"
"time"
)
type Trigger string
const (
TriggerManual Trigger = "manual"
TriggerWatch Trigger = "watch"
)
var ErrApplyRequired = errors.New("reload apply function is required")
type Config struct {
Enabled bool
PollInterval time.Duration
ApplyTimeout time.Duration
}
type Result struct {
Trigger string `json:"trigger"`
Reason string `json:"reason,omitempty"`
At time.Time `json:"at"`
DurationMS int64 `json:"duration_ms"`
Changed bool `json:"changed"`
Applied []string `json:"applied,omitempty"`
Skipped []string `json:"skipped,omitempty"`
RestartRequired []string `json:"restart_required,omitempty"`
Details map[string]any `json:"details,omitempty"`
Error string `json:"error,omitempty"`
}
type Snapshot struct {
Enabled bool `json:"enabled"`
PollIntervalSeconds int64 `json:"poll_interval_seconds"`
ApplyTimeoutSeconds int64 `json:"apply_timeout_seconds"`
Checks int64 `json:"checks"`
Reloads int64 `json:"reloads"`
Failures int64 `json:"failures"`
LastCheckedAt time.Time `json:"last_checked_at,omitempty"`
LastSuccessfulReload time.Time `json:"last_successful_reload,omitempty"`
LastResult Result `json:"last_result"`
}
type ApplyFunc func(context.Context, Trigger, string) (Result, error)
type Manager struct {
name string
apply ApplyFunc
logger *slog.Logger
mu sync.Mutex
applyMu sync.Mutex
cfg Config
snapshot Snapshot
cancel context.CancelFunc
done chan struct{}
}
func New(name string, cfg Config, apply ApplyFunc, logger *slog.Logger) *Manager {
cfg = normalizeConfig(cfg)
name = strings.TrimSpace(name)
if name == "" {
name = "config-reloader"
}
return &Manager{
name: name,
cfg: cfg,
apply: apply,
logger: logger,
snapshot: Snapshot{
Enabled: cfg.Enabled,
PollIntervalSeconds: int64(cfg.PollInterval / time.Second),
ApplyTimeoutSeconds: int64(cfg.ApplyTimeout / time.Second),
},
}
}
func (m *Manager) Name() string {
if m == nil {
return "config-reloader"
}
return m.name
}
func (m *Manager) Start(ctx context.Context) error {
if m == nil {
return nil
}
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return err
}
m.mu.Lock()
if m.cancel != nil || !m.cfg.Enabled {
m.mu.Unlock()
return nil
}
runCtx, cancel := context.WithCancel(ctx)
done := make(chan struct{})
interval := m.cfg.PollInterval
m.cancel = cancel
m.done = done
m.mu.Unlock()
go m.loop(runCtx, done, interval)
return nil
}
func (m *Manager) Stop(ctx context.Context) error {
if m == nil {
return nil
}
if ctx == nil {
ctx = context.Background()
}
m.mu.Lock()
cancel := m.cancel
done := m.done
m.cancel = nil
m.done = nil
m.mu.Unlock()
if cancel != nil {
cancel()
}
if done == nil {
return nil
}
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (m *Manager) Configure(cfg Config) {
if m == nil {
return
}
cfg = normalizeConfig(cfg)
m.mu.Lock()
wasEnabled := m.cfg.Enabled
changedInterval := m.cfg.PollInterval != cfg.PollInterval
m.cfg = cfg
m.snapshot.Enabled = cfg.Enabled
m.snapshot.PollIntervalSeconds = int64(cfg.PollInterval / time.Second)
m.snapshot.ApplyTimeoutSeconds = int64(cfg.ApplyTimeout / time.Second)
cancel := m.cancel
if wasEnabled && (!cfg.Enabled || changedInterval) {
m.cancel = nil
m.done = nil
}
m.mu.Unlock()
if wasEnabled && (!cfg.Enabled || changedInterval) && cancel != nil {
cancel()
}
if cfg.Enabled && (!wasEnabled || changedInterval) {
_ = m.Start(context.Background())
}
}
func (m *Manager) Reload(ctx context.Context, trigger Trigger, reason string) (Result, error) {
if m == nil {
return Result{}, ErrApplyRequired
}
m.applyMu.Lock()
defer m.applyMu.Unlock()
if ctx == nil {
ctx = context.Background()
}
m.mu.Lock()
cfg := m.cfg
m.snapshot.Checks++
m.snapshot.LastCheckedAt = time.Now().UTC()
m.mu.Unlock()
if m.apply == nil {
result := Result{Trigger: string(trigger), Reason: strings.TrimSpace(reason), At: time.Now().UTC(), Error: ErrApplyRequired.Error()}
m.record(result, ErrApplyRequired)
return result, ErrApplyRequired
}
startedAt := time.Now().UTC()
applyCtx, cancel := context.WithTimeout(ctx, cfg.ApplyTimeout)
defer cancel()
result, err := m.apply(applyCtx, trigger, strings.TrimSpace(reason))
result.Trigger = string(trigger)
result.Reason = strings.TrimSpace(reason)
if result.At.IsZero() {
result.At = startedAt
}
result.DurationMS = time.Since(startedAt).Milliseconds()
if err != nil && result.Error == "" {
result.Error = err.Error()
}
m.record(result, err)
return result, err
}
func (m *Manager) Snapshot() Snapshot {
if m == nil {
return Snapshot{}
}
m.mu.Lock()
snapshot := m.snapshot
snapshot.LastResult = cloneResult(snapshot.LastResult)
m.mu.Unlock()
return snapshot
}
func (m *Manager) loop(ctx context.Context, done chan struct{}, interval time.Duration) {
defer close(done)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if _, err := m.Reload(ctx, TriggerWatch, "watch"); err != nil && m.logger != nil {
m.logger.Warn("config reload failed", "error", err)
}
}
}
}
func (m *Manager) record(result Result, err error) {
m.mu.Lock()
m.snapshot.LastResult = cloneResult(result)
if result.Changed || len(result.Applied) > 0 {
m.snapshot.Reloads++
if err == nil {
m.snapshot.LastSuccessfulReload = time.Now().UTC()
}
}
if err != nil {
m.snapshot.Failures++
}
m.mu.Unlock()
}
func normalizeConfig(cfg Config) Config {
if cfg.PollInterval <= 0 {
cfg.PollInterval = 5 * time.Second
}
if cfg.ApplyTimeout <= 0 {
cfg.ApplyTimeout = 5 * time.Second
}
return cfg
}
func cloneResult(result Result) Result {
result.Applied = append([]string(nil), result.Applied...)
result.Skipped = append([]string(nil), result.Skipped...)
result.RestartRequired = append([]string(nil), result.RestartRequired...)
if len(result.Details) > 0 {
details := make(map[string]any, len(result.Details))
for key, value := range result.Details {
details[key] = cloneDetailValue(value)
}
result.Details = details
}
return result
}
func cloneDetailValue(value any) any {
if value == nil {
return nil
}
data, err := json.Marshal(value)
if err != nil {
return value
}
var out any
if err := json.Unmarshal(data, &out); err != nil {
return value
}
return out
}