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

502 lines
10 KiB
Go

package datatable
import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"sync"
)
var (
ErrDataSourceRequired = errors.New("data table source is required")
ErrIndexKeyRequired = errors.New("data table index key function is required")
ErrDuplicateIndexKey = errors.New("data table index key is duplicated")
ErrMismatchedTableView = errors.New("data table view name does not match row set")
ErrPreparedViewInvalid = errors.New("prepared data table view is invalid")
)
type RowSource interface {
Decode(name string, out any) error
Get(name string) (Table, bool)
Manifest() Manifest
}
type KeyFunc[T any, K comparable] func(T) (K, bool)
type ViewBinding interface {
TableName() string
Prepare(context.Context, RowSource) (PreparedView, error)
}
type PreparedView struct {
Table string `json:"table"`
Rows int `json:"rows"`
apply func()
commit func() error
rollback func()
}
func NewPreparedView(table string, rows int, apply func()) (PreparedView, error) {
return NewViewWithRollback(table, rows, apply, nil)
}
func NewViewWithRollback(table string, rows int, apply func(), rollback func()) (PreparedView, error) {
var commit func() error
if apply != nil {
commit = func() error { apply(); return nil }
}
view := PreparedView{
Table: strings.TrimSpace(table),
Rows: rows,
apply: apply,
commit: commit,
rollback: rollback,
}
if err := view.Validate(); err != nil {
return PreparedView{}, err
}
return view, nil
}
func (p PreparedView) Validate() error {
if strings.TrimSpace(p.Table) == "" || p.commit == nil {
return ErrPreparedViewInvalid
}
return nil
}
func (p PreparedView) Commit() error {
if err := p.Validate(); err != nil {
return err
}
return p.commit()
}
func (p PreparedView) Rollback() {
if p.rollback != nil {
p.rollback()
}
}
type RowSet[T any] struct {
Table Table `json:"table"`
Manifest Manifest `json:"manifest"`
Rows []T `json:"rows,omitempty"`
}
func (s RowSet[T]) Clone() RowSet[T] {
s.Rows = cloneRows(s.Rows)
return s
}
type IndexedRowSet[T any, K comparable] struct {
RowSet[T]
Index map[K]T `json:"-"`
}
func (s IndexedRowSet[T, K]) Clone() IndexedRowSet[T, K] {
out := IndexedRowSet[T, K]{
RowSet: s.RowSet.Clone(),
Index: make(map[K]T, len(s.Index)),
}
for key, row := range s.Index {
out.Index[key] = cloneValue(row)
}
return out
}
func LoadRows[T any](source RowSource, name string) (RowSet[T], error) {
name = strings.TrimSpace(name)
if source == nil {
return RowSet[T]{}, ErrDataSourceRequired
}
if name == "" {
return RowSet[T]{}, ErrTableRequired
}
table, ok := source.Get(name)
if !ok {
return RowSet[T]{}, fmt.Errorf("%w: %s", ErrTableNotFound, name)
}
var rows []T
if err := source.Decode(name, &rows); err != nil {
var wrapped struct {
Items []T `json:"items"`
}
if wrappedErr := source.Decode(name, &wrapped); wrappedErr != nil || wrapped.Items == nil {
return RowSet[T]{}, err
}
rows = wrapped.Items
}
return RowSet[T]{
Table: table,
Manifest: source.Manifest(),
Rows: append([]T(nil), rows...),
}, nil
}
func UniqueIndex[T any, K comparable](rows []T, keyFn KeyFunc[T, K]) (map[K]T, error) {
if keyFn == nil {
return nil, ErrIndexKeyRequired
}
index := make(map[K]T, len(rows))
for _, row := range rows {
key, ok := keyFn(row)
if !ok {
continue
}
if _, exists := index[key]; exists {
return nil, fmt.Errorf("%w: %v", ErrDuplicateIndexKey, key)
}
index[key] = row
}
return index, nil
}
func MultiIndex[T any, K comparable](rows []T, keyFn KeyFunc[T, K]) (map[K][]T, error) {
if keyFn == nil {
return nil, ErrIndexKeyRequired
}
index := make(map[K][]T)
for _, row := range rows {
key, ok := keyFn(row)
if !ok {
continue
}
index[key] = append(index[key], row)
}
return cloneMultiIndex(index), nil
}
type TableView[T any] struct {
name string
mu sync.RWMutex
set RowSet[T]
}
func NewTableView[T any](name string) *TableView[T] {
return &TableView[T]{name: strings.TrimSpace(name)}
}
func (v *TableView[T]) Name() string {
if v == nil {
return ""
}
return v.name
}
func (v *TableView[T]) TableName() string {
return v.Name()
}
func (v *TableView[T]) Prepare(ctx context.Context, source RowSource) (PreparedView, error) {
if err := contextErr(ctx); err != nil {
return PreparedView{}, err
}
if v == nil {
return PreparedView{}, ErrTableRequired
}
next, err := LoadRows[T](source, v.name)
if err != nil {
return PreparedView{}, err
}
v.mu.RLock()
previous := v.set.Clone()
v.mu.RUnlock()
return NewViewWithRollback(v.name, len(next.Rows), func() {
v.mu.Lock()
v.set = next.Clone()
v.mu.Unlock()
}, func() {
v.mu.Lock()
v.set = previous.Clone()
v.mu.Unlock()
})
}
func (v *TableView[T]) Reload(ctx context.Context, source RowSource) (RowSet[T], error) {
if err := contextErr(ctx); err != nil {
return RowSet[T]{}, err
}
if v == nil {
return RowSet[T]{}, ErrTableRequired
}
next, err := LoadRows[T](source, v.name)
if err != nil {
return RowSet[T]{}, err
}
v.mu.Lock()
v.set = next.Clone()
v.mu.Unlock()
return next.Clone(), nil
}
func (v *TableView[T]) Replace(set RowSet[T]) error {
if v == nil {
return ErrTableRequired
}
if strings.TrimSpace(v.name) != strings.TrimSpace(set.Table.Name) {
return fmt.Errorf("%w: view=%s rows=%s", ErrMismatchedTableView, v.name, set.Table.Name)
}
v.mu.Lock()
v.set = set.Clone()
v.mu.Unlock()
return nil
}
func (v *TableView[T]) Snapshot() RowSet[T] {
if v == nil {
return RowSet[T]{}
}
v.mu.RLock()
set := v.set.Clone()
v.mu.RUnlock()
return set
}
func (v *TableView[T]) Rows() []T {
return v.Snapshot().Rows
}
func (v *TableView[T]) Clear() {
if v == nil {
return
}
v.mu.Lock()
v.set = RowSet[T]{Table: Table{Name: v.name}}
v.mu.Unlock()
}
type UniqueIndexView[T any, K comparable] struct {
name string
keyFn KeyFunc[T, K]
mu sync.RWMutex
set IndexedRowSet[T, K]
}
func NewUniqueIndexView[T any, K comparable](name string, keyFn KeyFunc[T, K]) *UniqueIndexView[T, K] {
return &UniqueIndexView[T, K]{
name: strings.TrimSpace(name),
keyFn: keyFn,
}
}
func (v *UniqueIndexView[T, K]) Name() string {
if v == nil {
return ""
}
return v.name
}
func (v *UniqueIndexView[T, K]) TableName() string {
return v.Name()
}
func (v *UniqueIndexView[T, K]) Prepare(ctx context.Context, source RowSource) (PreparedView, error) {
if err := contextErr(ctx); err != nil {
return PreparedView{}, err
}
if v == nil {
return PreparedView{}, ErrTableRequired
}
next, err := v.build(source)
if err != nil {
return PreparedView{}, err
}
v.mu.RLock()
previous := v.set.Clone()
v.mu.RUnlock()
return NewViewWithRollback(v.name, len(next.Rows), func() {
v.mu.Lock()
v.set = next.Clone()
v.mu.Unlock()
}, func() {
v.mu.Lock()
v.set = previous.Clone()
v.mu.Unlock()
})
}
func (v *UniqueIndexView[T, K]) Reload(ctx context.Context, source RowSource) (IndexedRowSet[T, K], error) {
if err := contextErr(ctx); err != nil {
return IndexedRowSet[T, K]{}, err
}
if v == nil {
return IndexedRowSet[T, K]{}, ErrTableRequired
}
next, err := v.build(source)
if err != nil {
return IndexedRowSet[T, K]{}, err
}
v.mu.Lock()
v.set = next.Clone()
v.mu.Unlock()
return next.Clone(), nil
}
func (v *UniqueIndexView[T, K]) Snapshot() IndexedRowSet[T, K] {
if v == nil {
return IndexedRowSet[T, K]{}
}
v.mu.RLock()
set := v.set.Clone()
v.mu.RUnlock()
return set
}
func (v *UniqueIndexView[T, K]) Rows() []T {
return v.Snapshot().Rows
}
func (v *UniqueIndexView[T, K]) Clear() {
if v == nil {
return
}
v.mu.Lock()
v.set = IndexedRowSet[T, K]{
RowSet: RowSet[T]{Table: Table{Name: v.name}},
Index: make(map[K]T),
}
v.mu.Unlock()
}
func (v *UniqueIndexView[T, K]) Get(key K) (T, bool) {
var zero T
if v == nil {
return zero, false
}
v.mu.RLock()
row, ok := v.set.Index[key]
v.mu.RUnlock()
if !ok {
return zero, false
}
return cloneValue(row), true
}
func (v *UniqueIndexView[T, K]) build(source RowSource) (IndexedRowSet[T, K], error) {
rows, err := LoadRows[T](source, v.name)
if err != nil {
return IndexedRowSet[T, K]{}, err
}
index, err := UniqueIndex(rows.Rows, v.keyFn)
if err != nil {
return IndexedRowSet[T, K]{}, err
}
return IndexedRowSet[T, K]{
RowSet: rows.Clone(),
Index: index,
}, nil
}
func cloneMultiIndex[T any, K comparable](in map[K][]T) map[K][]T {
out := make(map[K][]T, len(in))
for key, rows := range in {
out[key] = cloneRows(rows)
}
return out
}
func cloneRows[T any](rows []T) []T {
if len(rows) == 0 {
return nil
}
out := make([]T, len(rows))
for i, row := range rows {
out[i] = cloneValue(row)
}
return out
}
func cloneValue[T any](value T) T {
cloned := cloneReflect(reflect.ValueOf(value))
if !cloned.IsValid() {
return value
}
out, ok := cloned.Interface().(T)
if !ok {
return value
}
return out
}
func cloneReflect(v reflect.Value) reflect.Value {
if !v.IsValid() {
return v
}
switch v.Kind() {
case reflect.Pointer:
if v.IsNil() {
return v
}
out := reflect.New(v.Type().Elem())
elem := cloneReflect(v.Elem())
if elem.IsValid() && elem.Type().AssignableTo(v.Type().Elem()) {
out.Elem().Set(elem)
} else {
out.Elem().Set(v.Elem())
}
return out
case reflect.Slice:
if v.IsNil() {
return v
}
out := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
for i := 0; i < v.Len(); i++ {
setClonedValue(out.Index(i), v.Index(i))
}
return out
case reflect.Map:
if v.IsNil() {
return v
}
out := reflect.MakeMapWithSize(v.Type(), v.Len())
iter := v.MapRange()
for iter.Next() {
value := cloneReflect(iter.Value())
if value.IsValid() && value.Type().AssignableTo(v.Type().Elem()) {
out.SetMapIndex(iter.Key(), value)
} else {
out.SetMapIndex(iter.Key(), iter.Value())
}
}
return out
case reflect.Struct:
out := reflect.New(v.Type()).Elem()
for i := 0; i < v.NumField(); i++ {
if !out.Field(i).CanSet() {
return v
}
setClonedValue(out.Field(i), v.Field(i))
}
return out
case reflect.Interface:
if v.IsNil() {
return v
}
elem := cloneReflect(v.Elem())
if elem.IsValid() && elem.Type().AssignableTo(v.Type()) {
return elem
}
out := reflect.New(v.Type()).Elem()
if elem.IsValid() && elem.Type().AssignableTo(out.Type()) {
out.Set(elem)
return out
}
return v
default:
return v
}
}
func setClonedValue(dst, src reflect.Value) {
value := cloneReflect(src)
if value.IsValid() && value.Type().AssignableTo(dst.Type()) {
dst.Set(value)
return
}
dst.Set(src)
}