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