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

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

279 lines
6.9 KiB
Go

package leaderboard
import (
"context"
"encoding/base64"
"fmt"
"math"
"strings"
)
func normalizeSubmission(submission Submission) (Submission, error) {
submission.OwnerID = strings.TrimSpace(submission.OwnerID)
if submission.OwnerID == "" {
return Submission{}, fmt.Errorf("%w: owner_id is required", ErrInvalidSubmission)
}
submission.Metadata = cloneStringMap(submission.Metadata)
return submission, nil
}
func addScoreValue(left int64, right int64) (int64, error) {
if (right > 0 && left > math.MaxInt64-right) || (right < 0 && left < math.MinInt64-right) {
return 0, fmt.Errorf("%w: %w", ErrInvalidSubmission, ErrScoreOverflow)
}
return left + right, nil
}
func normRepairReq(request RepairRequest) (RepairRequest, error) {
request.OwnerID = strings.TrimSpace(request.OwnerID)
if request.OwnerID == "" {
return RepairRequest{}, fmt.Errorf("%w: owner_id is required", ErrInvalidSubmission)
}
request.Reason = strings.TrimSpace(request.Reason)
request.OperatorID = strings.TrimSpace(request.OperatorID)
request.RequestID = strings.TrimSpace(request.RequestID)
request.Metadata = cloneStringMap(request.Metadata)
return request, nil
}
func repairIdempotencyKey(leaderboardID string, requestID string) string {
leaderboardID = strings.TrimSpace(leaderboardID)
requestID = strings.TrimSpace(requestID)
if leaderboardID == "" || requestID == "" {
return ""
}
return strings.Join([]string{"v1", encodeKeyPart(leaderboardID), encodeKeyPart(requestID)}, ":")
}
func encodeKeyPart(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return "_"
}
return base64.RawURLEncoding.EncodeToString([]byte(value))
}
func normCaptureOpts(options CaptureOptions) CaptureOptions {
if options.Limit <= 0 {
options.Limit = defaultListLimit
}
if options.Limit > maxListLimit {
options.Limit = maxListLimit
}
options.Reason = strings.TrimSpace(options.Reason)
options.OperatorID = strings.TrimSpace(options.OperatorID)
options.RequestID = strings.TrimSpace(options.RequestID)
options.Metadata = cloneStringMap(options.Metadata)
return options
}
func normalizeSortOrder(sortOrder string) (string, error) {
sortOrder = strings.ToLower(strings.TrimSpace(sortOrder))
switch sortOrder {
case "", SortDescending:
return SortDescending, nil
case SortAscending:
return SortAscending, nil
default:
return "", fmt.Errorf("%w: unsupported sort_order %q", ErrInvalidDefinition, sortOrder)
}
}
func normalizeOperator(operator string) (string, error) {
operator = strings.ToLower(strings.TrimSpace(operator))
switch operator {
case "", OperatorBest:
return OperatorBest, nil
case OperatorSet, OperatorIncrement:
return operator, nil
default:
return "", fmt.Errorf("%w: unsupported operator %q", ErrInvalidDefinition, operator)
}
}
func normalizeListOptions(options ListOptions) (int, int) {
offset := options.Offset
if offset < 0 {
offset = 0
}
limit := options.Limit
if limit <= 0 {
limit = defaultListLimit
}
if limit > maxListLimit {
limit = maxListLimit
}
return offset, limit
}
func betterThan(candidate Record, existing Record, sortOrder string) bool {
switch {
case candidate.Score != existing.Score:
if sortOrder == SortAscending {
return candidate.Score < existing.Score
}
return candidate.Score > existing.Score
case candidate.Subscore != existing.Subscore:
if sortOrder == SortAscending {
return candidate.Subscore < existing.Subscore
}
return candidate.Subscore > existing.Subscore
default:
return false
}
}
func compareRecords(a Record, b Record, sortOrder string) int {
if a.Score != b.Score {
if sortOrder == SortAscending {
if a.Score < b.Score {
return -1
}
return 1
}
if a.Score > b.Score {
return -1
}
return 1
}
if a.Subscore != b.Subscore {
if sortOrder == SortAscending {
if a.Subscore < b.Subscore {
return -1
}
return 1
}
if a.Subscore > b.Subscore {
return -1
}
return 1
}
if !a.CreatedAt.Equal(b.CreatedAt) {
if a.CreatedAt.Before(b.CreatedAt) {
return -1
}
return 1
}
if a.OwnerID < b.OwnerID {
return -1
}
if a.OwnerID > b.OwnerID {
return 1
}
if a.LeaderboardID < b.LeaderboardID {
return -1
}
if a.LeaderboardID > b.LeaderboardID {
return 1
}
return 0
}
func cloneDefinition(definition Definition) Definition {
definition.Metadata = cloneStringMap(definition.Metadata)
return definition
}
func cloneRecord(record Record) Record {
record.Metadata = cloneStringMap(record.Metadata)
return record
}
func cloneRecords(records []Record) []Record {
out := make([]Record, len(records))
for i, record := range records {
out[i] = cloneRecord(record)
}
return out
}
func cloneDefinitionMap(definitions map[string]Definition) map[string]Definition {
out := make(map[string]Definition, len(definitions))
for id, definition := range definitions {
out[id] = cloneDefinition(definition)
}
return out
}
func cloneRecordMap(records map[string]map[string]Record) map[string]map[string]Record {
out := make(map[string]map[string]Record, len(records))
for leaderboardID, boardRecords := range records {
out[leaderboardID] = cloneRecordSet(boardRecords)
}
return out
}
func cloneRecordSet(records map[string]Record) map[string]Record {
out := make(map[string]Record, len(records))
for ownerID, record := range records {
out[ownerID] = cloneRecord(record)
}
return out
}
func cloneRepairMap(repairs map[string]RepairReceipt) map[string]RepairReceipt {
out := make(map[string]RepairReceipt, len(repairs))
for key, receipt := range repairs {
out[key] = cloneRepairReceipt(receipt)
}
return out
}
func cloneRepairReceipt(receipt RepairReceipt) RepairReceipt {
if receipt.Before != nil {
before := cloneRecord(*receipt.Before)
receipt.Before = &before
}
if receipt.After != nil {
after := cloneRecord(*receipt.After)
receipt.After = &after
}
receipt.Metadata = cloneStringMap(receipt.Metadata)
return receipt
}
func cloneCapture(capture Capture) Capture {
capture.Records = cloneRecords(capture.Records)
capture.Metadata = cloneStringMap(capture.Metadata)
return capture
}
func repairHistoryEntry(receipt RepairReceipt, record *Record) HistoryEntry {
entry := HistoryEntry{
Action: HistoryActionRepairRecord,
LeaderboardID: receipt.LeaderboardID,
OwnerID: receipt.OwnerID,
Reason: receipt.Reason,
OperatorID: receipt.OperatorID,
RequestID: receipt.RequestID,
Metadata: cloneStringMap(receipt.Metadata),
At: receipt.At,
}
if record != nil {
cloned := cloneRecord(*record)
entry.Record = &cloned
} else if receipt.Before != nil {
cloned := cloneRecord(*receipt.Before)
entry.Record = &cloned
}
return entry
}
func cloneStringMap(in map[string]string) map[string]string {
if len(in) == 0 {
return nil
}
out := make(map[string]string, len(in))
for key, value := range in {
out[key] = value
}
return out
}
func ctxErr(ctx context.Context) error {
if ctx == nil {
return nil
}
return ctx.Err()
}