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

@@ -0,0 +1,246 @@
package leaderboard
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"strings"
"sync/atomic"
"time"
"longheng.io/server/internal/platform/eventlog"
)
var (
ErrNilEventLogHistoryStore = errors.New("leaderboard eventlog history store is required")
ErrNilEventLog = errors.New("leaderboard eventlog is required")
)
const (
LeaderboardEventStream = "leaderboard"
DefaultLeaderboardHistoryEventType = "history.appended"
histUndoTimeout = 3 * time.Second
)
type HistoryStoreOptions struct {
Stream string
Type string
Strict bool
}
type EventLogHistoryStore struct {
next HistoryStore
log *eventlog.Log
stream string
typ string
strict bool
errors uint64
}
type evErrorReporter interface {
EventLogErrors() uint64
}
type histErrorReporter interface {
HistoryErrors() uint64
}
type histUndo func(context.Context) error
type histUndoer interface {
appendWithRollback(context.Context, HistoryEntry) (histUndo, error)
}
func NewEventLogHistoryStore(next HistoryStore, log *eventlog.Log, options HistoryStoreOptions) (*EventLogHistoryStore, error) {
if next == nil {
return nil, ErrNilEventLogHistoryStore
}
if log == nil {
return nil, ErrNilEventLog
}
stream := strings.TrimSpace(options.Stream)
if stream == "" {
stream = LeaderboardEventStream
}
typ := strings.TrimSpace(options.Type)
if typ == "" {
typ = DefaultLeaderboardHistoryEventType
}
return &EventLogHistoryStore{
next: next,
log: log,
stream: stream,
typ: typ,
strict: options.Strict,
}, nil
}
type eventHistoryStore struct {
log *eventlog.Log
stream string
typ string
}
func newEventHistoryStore(log *eventlog.Log, stream string, typ string) *eventHistoryStore {
return &eventHistoryStore{
log: log,
stream: normalizeEventStream(stream),
typ: normHistoryEventType(typ),
}
}
func (s *EventLogHistoryStore) Append(ctx context.Context, entry HistoryEntry) error {
if err := ctxErr(ctx); err != nil {
return err
}
if s == nil || s.next == nil {
return ErrNilEventLogHistoryStore
}
if s.log == nil {
return ErrNilEventLog
}
entry = normHistoryEntry(entry)
payload, err := json.Marshal(entry)
if err != nil {
return err
}
rollback, err := appendHist(ctx, s.next, entry)
if err != nil {
return err
}
_, err = s.log.Append(ctx, eventlog.Event{
Stream: s.stream,
Type: s.typ,
AggregateID: entry.LeaderboardID,
IdempotencyKey: historyIdemKey(entry, payload),
Payload: payload,
Headers: map[string]string{
"action": entry.Action,
"leaderboard_id": entry.LeaderboardID,
"owner_id": entry.OwnerID,
},
})
if err == nil {
return nil
}
atomic.AddUint64(&s.errors, 1)
if s.strict {
if rollbackErr := undoHist(ctx, rollback); rollbackErr != nil {
return errors.Join(err, rollbackErr)
}
return err
}
return nil
}
func (s *EventLogHistoryStore) List(ctx context.Context, leaderboardID string, limit int) ([]HistoryEntry, error) {
if s == nil || s.next == nil {
return nil, ErrNilEventLogHistoryStore
}
return s.next.List(ctx, leaderboardID, limit)
}
func (s *EventLogHistoryStore) EventLogErrors() uint64 {
if s == nil {
return 0
}
return atomic.LoadUint64(&s.errors)
}
func (s *eventHistoryStore) Append(ctx context.Context, entry HistoryEntry) error {
if err := ctxErr(ctx); err != nil {
return err
}
if s == nil || s.log == nil {
return ErrNilEventLog
}
entry = normHistoryEntry(entry)
payload, err := json.Marshal(entry)
if err != nil {
return err
}
_, err = s.log.Append(ctx, eventlog.Event{
Stream: s.stream,
Type: s.typ,
AggregateID: entry.LeaderboardID,
IdempotencyKey: historyIdemKey(entry, payload),
Payload: payload,
Headers: map[string]string{
"action": entry.Action,
"leaderboard_id": entry.LeaderboardID,
"owner_id": entry.OwnerID,
},
})
return err
}
func (s *eventHistoryStore) List(context.Context, string, int) ([]HistoryEntry, error) {
return nil, ErrNilEventLogHistoryStore
}
func historyStoreErrors(store HistoryStore) uint64 {
reporter, ok := store.(evErrorReporter)
if !ok {
return 0
}
return reporter.EventLogErrors()
}
func historyErrors(store HistoryStore) uint64 {
reporter, ok := store.(histErrorReporter)
if !ok {
return 0
}
return reporter.HistoryErrors()
}
func appendHist(ctx context.Context, store HistoryStore, entry HistoryEntry) (histUndo, error) {
if rollbacker, ok := store.(histUndoer); ok {
return rollbacker.appendWithRollback(ctx, entry)
}
if err := store.Append(ctx, entry); err != nil {
return nil, err
}
return nil, nil
}
func undoHist(parent context.Context, rollback histUndo) error {
if rollback == nil {
return nil
}
base := context.Background()
if parent != nil {
base = context.WithoutCancel(parent)
}
ctx, cancel := context.WithTimeout(base, histUndoTimeout)
defer cancel()
return rollback(ctx)
}
func normHistoryEntry(entry HistoryEntry) HistoryEntry {
entry = cloneHistoryEntry(entry)
entry.Action = strings.TrimSpace(entry.Action)
entry.LeaderboardID = strings.TrimSpace(entry.LeaderboardID)
entry.OwnerID = strings.TrimSpace(entry.OwnerID)
if entry.At.IsZero() {
entry.At = time.Now().UTC()
} else {
entry.At = entry.At.UTC()
}
return entry
}
func historyIdemKey(entry HistoryEntry, payload []byte) string {
raw := fmt.Sprintf("%s|%s|%s|%d|%x",
entry.LeaderboardID,
entry.Action,
entry.OwnerID,
entry.At.UTC().UnixNano(),
payload,
)
hash := sha256.Sum256([]byte(raw))
return "leaderboard:history:" + hex.EncodeToString(hash[:16])
}