服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
141 lines
6.1 KiB
Go
141 lines
6.1 KiB
Go
package expertjob
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
|
dnfrepomemory "longheng.io/server/internal/modules/dnf/repository/memory"
|
|
)
|
|
|
|
func TestOwnerCompoundCommitsCharacterAndInventoryAtomically(t *testing.T) {
|
|
ctx := context.Background()
|
|
repositories := dnfrepomemory.NewMemoryGroup()
|
|
if err := repositories.Character.Save(ctx, dnfrepo.CharacterRecord{CharacterID: "19", AccountID: "dnf:1", Stats: map[string]int64{"expert_job_exp": 0}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "19", Slots: map[string]dnfrepo.ItemStack{"0:9": {ItemID: 3001, Count: 2}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
owner, err := NewOwner(repositories)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = owner.Compound(ctx, Command{AccountID: "dnf:1", CharacterID: "19", Project: func(assets *Assets) (Changes, error) {
|
|
assets.Character.Stats["expert_job_exp"] = 3
|
|
delete(assets.Inventory.Slots, "0:9")
|
|
return Changes{Character: true, Inventory: true}, nil
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
character, _, _ := repositories.Character.Load(ctx, "19")
|
|
inventory, _, _ := repositories.Inventory.Load(ctx, "19")
|
|
if character.Stats["expert_job_exp"] != 3 || len(inventory.Slots) != 0 {
|
|
t.Fatalf("character=%+v inventory=%+v", character, inventory)
|
|
}
|
|
}
|
|
|
|
func TestOwnerLearnRecipeRollsBackBothAggregates(t *testing.T) {
|
|
ctx := context.Background()
|
|
repositories := dnfrepomemory.NewMemoryGroup()
|
|
if err := repositories.Character.Save(ctx, dnfrepo.CharacterRecord{CharacterID: "19", AccountID: "dnf:1", Stats: map[string]int64{}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "19", Slots: map[string]dnfrepo.ItemStack{"0:9": {ItemID: 1002, Count: 1}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
owner, _ := NewOwner(repositories)
|
|
want := errors.New("late failure")
|
|
err := owner.LearnRecipe(ctx, Command{AccountID: "dnf:1", CharacterID: "19", Project: func(assets *Assets) (Changes, error) {
|
|
if assets.Character.Stats == nil {
|
|
assets.Character.Stats = make(map[string]int64)
|
|
}
|
|
assets.Character.Stats["expert_job_recipe_2_1002"] = 1
|
|
delete(assets.Inventory.Slots, "0:9")
|
|
return Changes{Character: true, Inventory: true}, want
|
|
}})
|
|
if !errors.Is(err, want) {
|
|
t.Fatalf("error = %v", err)
|
|
}
|
|
character, _, _ := repositories.Character.Load(ctx, "19")
|
|
inventory, _, _ := repositories.Inventory.Load(ctx, "19")
|
|
if character.Stats["expert_job_recipe_2_1002"] != 0 || inventory.Slots["0:9"].Count != 1 {
|
|
t.Fatalf("rollback character=%+v inventory=%+v", character, inventory)
|
|
}
|
|
}
|
|
|
|
func TestOwnerGiveUpCommitsCharacterWalletAndQuestAtomically(t *testing.T) {
|
|
ctx := context.Background()
|
|
repositories := dnfrepomemory.NewMemoryGroup()
|
|
if err := repositories.Character.Save(ctx, dnfrepo.CharacterRecord{CharacterID: "19", AccountID: "dnf:1", Stats: map[string]int64{"expert_job_type": 3, "expert_job_exp": 20}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "19", Slots: map[string]dnfrepo.ItemStack{"0:0": {ItemID: 0, Count: 60_000}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repositories.Quest.Save(ctx, dnfrepo.QuestRecord{CharacterID: "19", States: map[int64]dnfrepo.QuestState{2702: {Status: "completed"}, 5000: {Status: "active"}}, Progress: map[int64]dnfrepo.QuestState{2708: {Status: "active"}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
owner, err := NewOwner(repositories)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = owner.GiveUp(ctx, Command{AccountID: "dnf:1", CharacterID: "19", Project: func(assets *Assets) (Changes, error) {
|
|
assets.Character.Stats["expert_job_type"] = 0
|
|
assets.Character.Stats["expert_job_exp"] = 0
|
|
wallet := assets.Inventory.Slots["0:0"]
|
|
wallet.Count = 59_000
|
|
assets.Inventory.Slots["0:0"] = wallet
|
|
delete(assets.Quest.States, 2702)
|
|
delete(assets.Quest.Progress, 2708)
|
|
return Changes{Character: true, Inventory: true, Quest: true}, nil
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
character, _, _ := repositories.Character.Load(ctx, "19")
|
|
inventory, _, _ := repositories.Inventory.Load(ctx, "19")
|
|
quest, _, _ := repositories.Quest.Load(ctx, "19")
|
|
if character.Stats["expert_job_type"] != 0 || inventory.Slots["0:0"].Count != 59_000 {
|
|
t.Fatalf("character=%+v inventory=%+v", character, inventory)
|
|
}
|
|
if _, exists := quest.States[2702]; exists {
|
|
t.Fatalf("transition state was not removed: %+v", quest)
|
|
}
|
|
if _, exists := quest.Progress[2708]; exists {
|
|
t.Fatalf("transition progress was not removed: %+v", quest)
|
|
}
|
|
if quest.States[5000].Status != "active" {
|
|
t.Fatalf("unrelated quest was changed: %+v", quest)
|
|
}
|
|
}
|
|
|
|
func TestOwnerGiveUpRollsBackQuestWithWallet(t *testing.T) {
|
|
ctx := context.Background()
|
|
repositories := dnfrepomemory.NewMemoryGroup()
|
|
_ = repositories.Character.Save(ctx, dnfrepo.CharacterRecord{CharacterID: "19", AccountID: "dnf:1", Stats: map[string]int64{"expert_job_type": 2}})
|
|
_ = repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "19", Slots: map[string]dnfrepo.ItemStack{"0:0": {ItemID: 0, Count: 60_000}}})
|
|
_ = repositories.Quest.Save(ctx, dnfrepo.QuestRecord{CharacterID: "19", States: map[int64]dnfrepo.QuestState{2702: {Status: "completed"}}})
|
|
owner, _ := NewOwner(repositories)
|
|
want := errors.New("late give-up failure")
|
|
err := owner.GiveUp(ctx, Command{AccountID: "dnf:1", CharacterID: "19", Project: func(assets *Assets) (Changes, error) {
|
|
assets.Character.Stats["expert_job_type"] = 0
|
|
wallet := assets.Inventory.Slots["0:0"]
|
|
wallet.Count = 59_000
|
|
assets.Inventory.Slots["0:0"] = wallet
|
|
delete(assets.Quest.States, 2702)
|
|
return Changes{Character: true, Inventory: true, Quest: true}, want
|
|
}})
|
|
if !errors.Is(err, want) {
|
|
t.Fatalf("error=%v", err)
|
|
}
|
|
character, _, _ := repositories.Character.Load(ctx, "19")
|
|
inventory, _, _ := repositories.Inventory.Load(ctx, "19")
|
|
quest, _, _ := repositories.Quest.Load(ctx, "19")
|
|
if character.Stats["expert_job_type"] != 2 || inventory.Slots["0:0"].Count != 60_000 || quest.States[2702].Status != "completed" {
|
|
t.Fatalf("rollback character=%+v inventory=%+v quest=%+v", character, inventory, quest)
|
|
}
|
|
}
|