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,205 @@
package companion
import "sync"
type RunKey struct {
Instance string
SessionGeneration uint64
OwnerCharacterID string
LifecycleToken uint64
SpawnSequence uint64
Nonce string
}
func (k RunKey) valid() bool {
return validIdentity(k.Instance) && k.SessionGeneration > 0 && validCharacterID(k.OwnerCharacterID) &&
k.LifecycleToken > 0 && k.SpawnSequence > 0 && validHash(k.Nonce)
}
type Phase string
const (
Prepared Phase = "prepared"
Ready Phase = "room_ready"
Created Phase = "spawned_inactive"
Bound Phase = "owner_bound"
Active Phase = "active"
Transitioning Phase = "transitioning"
Dead Phase = "dead"
Retiring Phase = "retiring"
Disposed Phase = "disposed"
Skipped Phase = "skipped"
)
type Event struct {
Run RunKey
Sequence uint64
Kind string
RoomGeneration uint64
ObjectKey uint16
ObjectGeneration uint64
}
type Status struct {
Run RunKey
Phase Phase
ConfigRevision uint64
SnapshotHash string
LastSequence uint64
RoomGeneration uint64
ObjectKey uint16
ObjectGeneration uint64
OwnerBound bool
}
// Instance is an isolated coordinator, not a client message handler.
// Callers must authenticate events and hold the real runtime/object admission
// boundary. An object-confirmation event is never proof of native creation by
// itself. No network endpoint or game callback currently invokes this type.
type Instance struct {
mu sync.Mutex
status Status
snapshot Snapshot
last Event
playerObjectKey uint16
}
func NewInstance(key RunKey, snapshot Snapshot, configRevision uint64, playerObjectKey uint16) (*Instance, error) {
if !key.valid() || playerObjectKey == 0 || playerObjectKey == 65535 {
return nil, ErrStaleRun
}
if snapshot.SchemaVersion != 1 || snapshot.OwnerCharacterID != key.OwnerCharacterID ||
!validCharacterID(snapshot.SourceCharacterID) || snapshot.SourceCharacterID == key.OwnerCharacterID ||
snapshot.ProfileID == "" || !validHash(snapshot.ClientSHA256) || !validHash(snapshot.PVFSHA256) {
return nil, ErrSnapshot
}
frozen := snapshot.Clone()
return &Instance{status: Status{Run: key, Phase: Prepared, ConfigRevision: configRevision,
SnapshotHash: frozen.Fingerprint()}, snapshot: frozen, playerObjectKey: playerObjectKey}, nil
}
func (i *Instance) Status() Status { i.mu.Lock(); defer i.mu.Unlock(); return i.status }
func (i *Instance) Snapshot() Snapshot { i.mu.Lock(); defer i.mu.Unlock(); return i.snapshot.Clone() }
// occupied contains other actors only, freshly inspected by the future bridge.
// nil means collision evidence missing and rejects a first native creation.
// Successful repeated messages are no-ops; a reused sequence with new content
// is an error. No transition can create another APC or respawn a dead one.
func (i *Instance) Apply(e Event, occupied map[uint16]bool) error {
i.mu.Lock()
defer i.mu.Unlock()
s := i.status
if e.Run != s.Run {
return ErrStaleRun
}
if e.Sequence == 0 {
return ErrTransition
}
if e.Sequence == s.LastSequence && e == i.last {
return nil
}
if e.Sequence <= s.LastSequence {
return ErrTransition
}
switch e.Kind {
case "room_ready":
if s.Phase != Prepared && s.Phase != Transitioning {
return ErrTransition
}
if e.RoomGeneration <= s.RoomGeneration {
return ErrRoom
}
s.RoomGeneration = e.RoomGeneration
if s.Phase == Prepared {
s.Phase = Ready
} else {
s.Phase = Active
}
case "native_created":
if (s.Phase != Ready && s.Phase != Retiring) || e.RoomGeneration != s.RoomGeneration {
return ErrRoom
}
// A native create may finish after cancellation. Track it for exact
// cleanup, but never authorize it or replace an existing actor.
if s.Phase == Retiring && s.ObjectKey != 0 {
return ErrCollision
}
if occupied == nil || e.ObjectKey == 0 || e.ObjectKey == 65535 || e.ObjectKey == i.playerObjectKey || occupied[e.ObjectKey] || e.ObjectGeneration == 0 {
return ErrCollision
}
s.ObjectKey = e.ObjectKey
s.ObjectGeneration = e.ObjectGeneration
if s.Phase == Ready {
s.Phase = Created
}
case "owner_bound", "activate":
if e.ObjectKey != s.ObjectKey || e.ObjectGeneration != s.ObjectGeneration || e.RoomGeneration != s.RoomGeneration {
return ErrCollision
}
if e.Kind == "owner_bound" {
if s.Phase != Created {
return ErrTransition
}
s.Phase = Bound
s.OwnerBound = true
} else {
if s.Phase != Bound {
return ErrTransition
}
s.Phase = Active
}
case "room_leaving":
if s.Phase != Active || e.RoomGeneration != s.RoomGeneration {
return ErrTransition
}
s.Phase = Transitioning
case "dead":
if s.Phase != Active && s.Phase != Transitioning {
return ErrTransition
}
if e.ObjectKey != s.ObjectKey || e.ObjectGeneration != s.ObjectGeneration {
return ErrCollision
}
s.Phase = Dead
case "retire":
if s.Phase == Disposed || s.Phase == Skipped {
return ErrTransition
}
s.Phase = Retiring
case "disposed":
if s.Phase != Retiring {
return ErrTransition
}
// The bridge must also prove the native operation/callbacks drained;
// a missing room manager is not proof that the actor was destroyed.
if e.ObjectKey != s.ObjectKey || e.ObjectGeneration != s.ObjectGeneration {
return ErrCollision
}
if e.RoomGeneration != s.RoomGeneration {
return ErrRoom
}
s.Phase = Disposed
s.OwnerBound = false
case "skip":
if s.Phase != Prepared && s.Phase != Ready {
return ErrTransition
}
s.Phase = Skipped
default:
return ErrTransition
}
s.LastSequence = e.Sequence
i.status = s
i.last = e
return nil
}
// Retiring/dead owners remain valid for already-in-flight combat. The bridge
// must validate targets and safely drain reports before reporting disposed.
// Object generation is internal evidence; no existing game wire is changed.
func (i *Instance) Owns(run RunKey, objectKey uint16, generation uint64) bool {
i.mu.Lock()
defer i.mu.Unlock()
s := i.status
return run == s.Run && s.OwnerBound && objectKey != 0 && objectKey == s.ObjectKey && generation == s.ObjectGeneration
}