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,501 @@
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user