服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
115 lines
3.6 KiB
Go
115 lines
3.6 KiB
Go
package companion
|
|
|
|
import "testing"
|
|
|
|
func retirementInstance(t *testing.T, withActor bool) (*Instance, RunKey) {
|
|
t.Helper()
|
|
key := runKey()
|
|
i, err := NewInstance(key, prepared(t), 1, 1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := i.Apply(Event{Run: key, Sequence: 1, Kind: "room_ready", RoomGeneration: 1}, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if withActor {
|
|
for n, kind := range []string{"native_created", "owner_bound", "activate"} {
|
|
if err := i.Apply(Event{Run: key, Sequence: uint64(n + 2), Kind: kind, RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}, map[uint16]bool{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
seq := i.Status().LastSequence + 1
|
|
if err := i.Apply(Event{Run: key, Sequence: seq, Kind: "retire", RoomGeneration: 1}, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return i, key
|
|
}
|
|
|
|
func TestCompanionDisposedRequiresExactActorAndRoom(t *testing.T) {
|
|
i, key := retirementInstance(t, true)
|
|
before := i.Status()
|
|
valid := Event{Run: key, Sequence: before.LastSequence + 1, Kind: "disposed", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}
|
|
for _, field := range []string{"key", "generation", "room", "empty"} {
|
|
e := valid
|
|
switch field {
|
|
case "key":
|
|
e.ObjectKey++
|
|
case "generation":
|
|
e.ObjectGeneration++
|
|
case "room":
|
|
e.RoomGeneration++
|
|
case "empty":
|
|
e.ObjectKey = 0
|
|
e.ObjectGeneration = 0
|
|
}
|
|
if err := i.Apply(e, nil); err == nil {
|
|
t.Fatalf("accepted mismatched %s disposal", field)
|
|
}
|
|
if i.Status() != before || !i.Owns(key, 24, 7) {
|
|
t.Fatalf("invalid disposal revoked owner: %s", field)
|
|
}
|
|
}
|
|
if err := i.Apply(valid, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if i.Owns(key, 24, 7) || i.Status().Phase != Disposed {
|
|
t.Fatal("valid disposal did not close")
|
|
}
|
|
}
|
|
|
|
func TestCompanionLateCreationTracksCleanupWithoutAuthorization(t *testing.T) {
|
|
i, key := retirementInstance(t, false)
|
|
e := Event{Run: key, Sequence: 3, Kind: "native_created", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}
|
|
if err := i.Apply(e, map[uint16]bool{}); err != nil {
|
|
t.Fatal("late creation lost:", err)
|
|
}
|
|
if i.Status().Phase != Retiring || i.Owns(key, 24, 7) {
|
|
t.Fatal("late creation resurrected cancelled run")
|
|
}
|
|
if err := i.Apply(e, map[uint16]bool{}); err != nil {
|
|
t.Fatal("exact duplicate should be idempotent", err)
|
|
}
|
|
if err := i.Apply(Event{Run: key, Sequence: 4, Kind: "owner_bound", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}, nil); err == nil {
|
|
t.Fatal("late actor authorized")
|
|
}
|
|
if err := i.Apply(Event{Run: key, Sequence: 4, Kind: "disposed", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if i.Status().Phase != Disposed {
|
|
t.Fatal(i.Status())
|
|
}
|
|
}
|
|
|
|
func TestCompanionLateCreationCannotReplaceExistingActor(t *testing.T) {
|
|
i, key := retirementInstance(t, true)
|
|
before := i.Status()
|
|
e := Event{Run: key, Sequence: before.LastSequence + 1, Kind: "native_created", RoomGeneration: 1, ObjectKey: 25, ObjectGeneration: 8}
|
|
if err := i.Apply(e, map[uint16]bool{}); err == nil {
|
|
t.Fatal("replaced retiring actor")
|
|
}
|
|
if i.Status() != before {
|
|
t.Fatal("rejected late creation changed state")
|
|
}
|
|
}
|
|
|
|
func TestCompanionCancellationWithoutActorRequiresEmptyDisposal(t *testing.T) {
|
|
i, key := retirementInstance(t, false)
|
|
before := i.Status()
|
|
e := Event{Run: key, Sequence: 3, Kind: "disposed", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}
|
|
if err := i.Apply(e, nil); err == nil {
|
|
t.Fatal("undisclosed actor disposal accepted")
|
|
}
|
|
if i.Status() != before {
|
|
t.Fatal("bad disposal changed state")
|
|
}
|
|
e.ObjectKey = 0
|
|
e.ObjectGeneration = 0
|
|
if err := i.Apply(e, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if i.Status().Phase != Disposed {
|
|
t.Fatal(i.Status())
|
|
}
|
|
}
|