DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,278 @@
|
||||
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()
|
||||
}
|
||||
Reference in new issue
Block a user