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,393 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/dnfenum"
|
||||
dnfproto "longheng.io/server/internal/modules/dnf/protocol"
|
||||
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
||||
dnfrepomemory "longheng.io/server/internal/modules/dnf/repository/memory"
|
||||
)
|
||||
|
||||
func TestEmotionBridgePersistsThroughOwnerAndKeepsAck(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos := dnfrepomemory.NewMemoryGroup()
|
||||
if err := repos.Character.Save(ctx, dnfrepo.CharacterRecord{
|
||||
CharacterID: "19",
|
||||
AccountID: "account-1",
|
||||
}); err != nil {
|
||||
t.Fatalf("save character: %v", err)
|
||||
}
|
||||
service, session, connection := newGameplayDomainBridgeFixture(repos)
|
||||
var body [2]byte
|
||||
binary.LittleEndian.PutUint16(body[:], 7)
|
||||
if err := service.handleCurrentChangeEmotion(session, body[:]); err != nil {
|
||||
t.Fatalf("handle emotion: %v", err)
|
||||
}
|
||||
character, _, err := repos.Character.Load(ctx, "19")
|
||||
if err != nil {
|
||||
t.Fatalf("load character: %v", err)
|
||||
}
|
||||
if character.Stats["mood_value"] != 7 {
|
||||
t.Fatalf("mood_value = %d", character.Stats["mood_value"])
|
||||
}
|
||||
ack, rest := splitGameServerUpperPacket(t, connection.write.Bytes())
|
||||
if ack.Header.MsgID != uint16(dnfenum.CmdPacketChangeEmotion) ||
|
||||
len(ack.Body) != 3 ||
|
||||
ack.Body[0] != 1 ||
|
||||
binary.LittleEndian.Uint16(ack.Body[1:3]) != 7 {
|
||||
t.Fatalf("ack header=%+v body=%x rest=%d", ack.Header, ack.Body, len(rest))
|
||||
}
|
||||
projection, trailing := splitGameServerUpperPacket(t, rest)
|
||||
if projection.Header.Classification != 0 ||
|
||||
projection.Header.MsgID != currentMoodProjectionMsgID ||
|
||||
!bytes.Equal(projection.Body, buildCurrentMoodProjectionBody(7)) ||
|
||||
len(trailing) != 0 {
|
||||
t.Fatalf("mood projection header=%+v body=%x trailing=%x", projection.Header, projection.Body, trailing)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneTitleBridgePersistsThroughOwnerAndRefreshesAppearance(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos := dnfrepomemory.NewMemoryGroup()
|
||||
if err := repos.Character.Save(ctx, dnfrepo.CharacterRecord{
|
||||
CharacterID: "19",
|
||||
AccountID: "account-1",
|
||||
Name: "Actor19",
|
||||
}); err != nil {
|
||||
t.Fatalf("save character: %v", err)
|
||||
}
|
||||
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{
|
||||
CharacterID: "19",
|
||||
Slots: map[string]dnfrepo.ItemStack{
|
||||
"100:0": {ItemID: 123456, Count: 1},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("save inventory: %v", err)
|
||||
}
|
||||
if err := repos.Equipment.Save(ctx, dnfrepo.EquipmentRecord{
|
||||
CharacterID: "19",
|
||||
Entries: map[string]dnfrepo.EquipmentEntry{
|
||||
"13": {SlotIndex: 13, ItemID: 654321},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("save equipment: %v", err)
|
||||
}
|
||||
service, session, connection := newGameplayDomainBridgeFixture(repos)
|
||||
var body [4]byte
|
||||
binary.LittleEndian.PutUint32(body[:], 123456)
|
||||
if err := service.handleCurrentSetCloneTitle(session, body[:]); err != nil {
|
||||
t.Fatalf("handle clone title: %v", err)
|
||||
}
|
||||
character, _, err := repos.Character.Load(ctx, "19")
|
||||
if err != nil {
|
||||
t.Fatalf("load character: %v", err)
|
||||
}
|
||||
if character.Stats["clone_title_item_id"] != 123456 {
|
||||
t.Fatalf("clone title = %d", character.Stats["clone_title_item_id"])
|
||||
}
|
||||
mode0, rest := splitGameServerUpperPacket(t, connection.write.Bytes())
|
||||
if mode0.Header.Classification != 0 ||
|
||||
mode0.Header.MsgID != uint16(dnfenum.CmdPacketSetUDPIPPort) ||
|
||||
len(mode0.Body) == 0 ||
|
||||
mode0.Body[0] != 0 {
|
||||
t.Fatalf("mode0 refresh header=%+v body=%x rest=%d", mode0.Header, mode0.Body, len(rest))
|
||||
}
|
||||
assertCurrentTypedMode0FullAppearanceForTest(t, mode0.Body, "Actor19", 13, 654321)
|
||||
mode1, trailing := splitGameServerUpperPacket(t, rest)
|
||||
if mode1.Header.Classification != 0 ||
|
||||
mode1.Header.MsgID != uint16(dnfenum.CmdPacketSetUDPIPPort) ||
|
||||
len(mode1.Body) <= currentMode1CreateCountOffset ||
|
||||
mode1.Body[0] != 1 ||
|
||||
binary.LittleEndian.Uint16(mode1.Body[21:23]) != 19 {
|
||||
t.Fatalf("full mode1 rebind header=%+v body=%x", mode1.Header, mode1.Body)
|
||||
}
|
||||
cloneTitleOffset := currentMode1CreateRowsOffset + currentMode1EquipmentCreateRowBaseWireSize
|
||||
if got := binary.LittleEndian.Uint32(mode1.Body[cloneTitleOffset : cloneTitleOffset+4]); got != 123456 {
|
||||
t.Fatalf("full mode1 rebind clone-title state=%d want=123456 body=%x", got, mode1.Body)
|
||||
}
|
||||
if len(trailing) != 0 {
|
||||
t.Fatalf("unexpected clone-title op13/op357/trailing=%x", trailing)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTitleBookBridgeMovesThroughOwnerAndKeepsPacketOrder(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos := dnfrepomemory.NewMemoryGroup()
|
||||
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{
|
||||
CharacterID: "19",
|
||||
Slots: map[string]dnfrepo.ItemStack{
|
||||
"0:42": {ItemID: 9001, Count: 1},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("save inventory: %v", err)
|
||||
}
|
||||
service, session, connection := newGameplayDomainBridgeFixture(repos)
|
||||
body := make([]byte, 20)
|
||||
binary.LittleEndian.PutUint32(body[0:4], 0)
|
||||
binary.LittleEndian.PutUint32(body[4:8], 42)
|
||||
binary.LittleEndian.PutUint32(body[8:12], 9001)
|
||||
binary.LittleEndian.PutUint32(body[12:16], 2)
|
||||
binary.LittleEndian.PutUint32(body[16:20], 7)
|
||||
if err := service.handleCurrentTitleBookPut(session, body); err != nil {
|
||||
t.Fatalf("handle title put: %v", err)
|
||||
}
|
||||
inventory, _, err := repos.Inventory.Load(ctx, "19")
|
||||
if err != nil {
|
||||
t.Fatalf("load inventory: %v", err)
|
||||
}
|
||||
if _, found := inventory.Slots["0:42"]; found ||
|
||||
inventory.Slots["100:2007"].ItemID != 9001 {
|
||||
t.Fatalf("slots = %+v", inventory.Slots)
|
||||
}
|
||||
|
||||
ack, rest := splitGameServerUpperPacket(t, connection.write.Bytes())
|
||||
if ack.Header.Classification != dnfproto.DefaultChannelClassification ||
|
||||
ack.Header.MsgID != currentTitleBookPutMsgID ||
|
||||
len(ack.Body) != 17 ||
|
||||
ack.Body[0] != 1 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[1:5]) != 0 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[5:9]) != 42 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[9:13]) != 2 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[13:17]) != 7 {
|
||||
t.Fatalf("ack header=%+v body=%x", ack.Header, ack.Body)
|
||||
}
|
||||
update, rest := splitGameServerUpperPacket(t, rest)
|
||||
if update.Header.Classification != 0 ||
|
||||
update.Header.MsgID != uint16(dnfenum.CmdPacketWalkoutPartyMember) {
|
||||
t.Fatalf("update header=%+v", update.Header)
|
||||
}
|
||||
list, trailing := splitGameServerUpperPacket(t, rest)
|
||||
if list.Header.Classification != 0 ||
|
||||
list.Header.MsgID != currentTitleBookMsgID ||
|
||||
len(trailing) != 0 ||
|
||||
len(list.Body) != 37 ||
|
||||
binary.LittleEndian.Uint32(list.Body[33:37]) != 0 {
|
||||
t.Fatalf("list header=%+v trailing=%d", list.Header, len(trailing))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTitleBookBridgeEquipSendsAckAndBookListOnly 钉住 2026-09-18 的闪退修复:
|
||||
// 称号簿右键使用(op413)曾经在 ack 之后补发 22 行 op14 list-3 装备全量刷新,
|
||||
// 客户端 op14 处理器因此栈溢出闪退(同日 23:41:17 客户端 90CN_trace:
|
||||
// dispatch opcode=14 packet_len=2637 后立即 0xC0000005 eip=0x03B25D03,
|
||||
// WER 同址 0xC0000409)。官方参考服务端 CTitleBook::getTitle(0x086425C4) 在装备
|
||||
// 分支末尾只调 CUser::send_equip(0x865DD14),而该函数对称号槽 13 直接 return,
|
||||
// 所以这条路径必须一条物品列表 op14 都不发,客户端靠 ack 里的 (space, slot) 自行穿上。
|
||||
func TestTitleBookBridgeEquipSendsAckAndBookListOnly(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos := dnfrepomemory.NewMemoryGroup()
|
||||
bookRow := make([]byte, 0x77)
|
||||
bookRow[0], bookRow[1] = 0xEA, 0x03 // list100 slot 1000+10 = 1010
|
||||
copy(bookRow[2:6], []byte{0xC8, 0xEC, 0x29, 0x00})
|
||||
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{
|
||||
CharacterID: "19",
|
||||
Slots: map[string]dnfrepo.ItemStack{
|
||||
"100:4010": {ItemID: 2747592, Count: 1, RawEntry: bookRow},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("save inventory: %v", err)
|
||||
}
|
||||
if err := repos.Equipment.Save(ctx, dnfrepo.EquipmentRecord{
|
||||
CharacterID: "19",
|
||||
Entries: map[string]dnfrepo.EquipmentEntry{},
|
||||
}); err != nil {
|
||||
t.Fatalf("save equipment: %v", err)
|
||||
}
|
||||
service, session, connection := newGameplayDomainBridgeFixture(repos)
|
||||
body := make([]byte, 20)
|
||||
binary.LittleEndian.PutUint32(body[0:4], 3) // ENUM_ITEMSPACE:装备
|
||||
binary.LittleEndian.PutUint32(body[4:8], 13) // 客户端指定的称号槽
|
||||
binary.LittleEndian.PutUint32(body[8:12], 2747592)
|
||||
binary.LittleEndian.PutUint32(body[12:16], 4) // category
|
||||
binary.LittleEndian.PutUint32(body[16:20], 10) // book index
|
||||
if err := service.handleCurrentTitleBookGet(session, body); err != nil {
|
||||
t.Fatalf("handle title get: %v", err)
|
||||
}
|
||||
|
||||
inventory, _, err := repos.Inventory.Load(ctx, "19")
|
||||
if err != nil {
|
||||
t.Fatalf("load inventory: %v", err)
|
||||
}
|
||||
if stack, found := inventory.Slots["100:4010"]; found {
|
||||
t.Fatalf("book entry must move out when equipped: %+v", stack)
|
||||
}
|
||||
for key, stack := range inventory.Slots {
|
||||
if stack.ItemID == 2747592 {
|
||||
t.Fatalf("title must not leak into slot %s: %+v", key, stack)
|
||||
}
|
||||
}
|
||||
equipment, found, err := repos.Equipment.Load(ctx, "19")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("load equipment found=%t err=%v", found, err)
|
||||
}
|
||||
entry := equipment.Entries["13"]
|
||||
if entry.ItemID != 2747592 || entry.Extra["source"] != "title_book_equip" ||
|
||||
entry.RawEntry[0] != 13 {
|
||||
t.Fatalf("equipment slot 13 = %+v", entry)
|
||||
}
|
||||
|
||||
ack, rest := splitGameServerUpperPacket(t, connection.write.Bytes())
|
||||
if ack.Header.Classification != dnfproto.DefaultChannelClassification ||
|
||||
ack.Header.MsgID != currentTitleBookGetMsgID ||
|
||||
len(ack.Body) != 17 ||
|
||||
ack.Body[0] != 1 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[1:5]) != 3 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[5:9]) != 13 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[9:13]) != 4 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[13:17]) != 10 {
|
||||
t.Fatalf("ack header=%+v body=%x", ack.Header, ack.Body)
|
||||
}
|
||||
packets := 0
|
||||
for len(rest) > 0 {
|
||||
var packet dnfproto.ChannelPacket
|
||||
packet, rest = splitGameServerUpperPacket(t, rest)
|
||||
packets++
|
||||
if packet.Header.MsgID == uint16(dnfenum.CmdPacketWalkoutPartyMember) {
|
||||
t.Fatalf("equip path must not push an item list op14: body=%x", packet.Body)
|
||||
}
|
||||
if packet.Header.MsgID != currentTitleBookMsgID {
|
||||
t.Fatalf("unexpected packet after ack: %+v", packet.Header)
|
||||
}
|
||||
// 称号已经穿在身上,这一页里不许再出现它(否则玩家看到「簿里一个、身上一个」)。
|
||||
if bytes.Contains(packet.Body, []byte{0xC8, 0xEC, 0x29, 0x00}) {
|
||||
t.Fatalf("worn title must not stay in the book page: body=%x", packet.Body)
|
||||
}
|
||||
}
|
||||
if packets != 1 {
|
||||
t.Fatalf("expected exactly one book list after the ack, got %d packets", packets)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTitleBookBridgeTakeOutPutsTitleInBagAtRequestedSlot 覆盖 2026-09-18 玩家报障
|
||||
// 「拿出来后显示在背包里,小退再上却自动穿戴了」:客户端「拿出来」发的是
|
||||
// op413 (space=0, slot=<期望背包格>),服务端必须回背包、并把**真实落点**写进 ack
|
||||
// (客户端按 ack 里的 space/slot 摆放物品),不能再当成装备处理。
|
||||
func TestTitleBookBridgeTakeOutPutsTitleInBagAtRequestedSlot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos := dnfrepomemory.NewMemoryGroup()
|
||||
bookRow := make([]byte, 0x77)
|
||||
bookRow[0], bookRow[1] = 0xEA, 0x03 // list100 slot 1000+10 = 1010
|
||||
copy(bookRow[2:6], []byte{0xC8, 0xEC, 0x29, 0x00})
|
||||
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{
|
||||
CharacterID: "19",
|
||||
Slots: map[string]dnfrepo.ItemStack{
|
||||
"100:4010": {ItemID: 2747592, Count: 1, RawEntry: bookRow},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("save inventory: %v", err)
|
||||
}
|
||||
if err := repos.Equipment.Save(ctx, dnfrepo.EquipmentRecord{
|
||||
CharacterID: "19",
|
||||
Entries: map[string]dnfrepo.EquipmentEntry{},
|
||||
}); err != nil {
|
||||
t.Fatalf("save equipment: %v", err)
|
||||
}
|
||||
service, session, connection := newGameplayDomainBridgeFixture(repos)
|
||||
body := make([]byte, 20)
|
||||
binary.LittleEndian.PutUint32(body[0:4], 0) // ENUM_ITEMSPACE:背包
|
||||
binary.LittleEndian.PutUint32(body[4:8], 12) // 客户端期望落在背包 12 号格
|
||||
binary.LittleEndian.PutUint32(body[8:12], 2747592)
|
||||
binary.LittleEndian.PutUint32(body[12:16], 4)
|
||||
binary.LittleEndian.PutUint32(body[16:20], 10)
|
||||
if err := service.handleCurrentTitleBookGet(session, body); err != nil {
|
||||
t.Fatalf("handle title get: %v", err)
|
||||
}
|
||||
|
||||
inventory, _, err := repos.Inventory.Load(ctx, "19")
|
||||
if err != nil {
|
||||
t.Fatalf("load inventory: %v", err)
|
||||
}
|
||||
if stack, found := inventory.Slots["100:4010"]; found {
|
||||
t.Fatalf("book entry must move out: %+v", stack)
|
||||
}
|
||||
if stack := inventory.Slots["0:12"]; stack.ItemID != 2747592 {
|
||||
t.Fatalf("title must land in the requested bag slot 12: %+v", inventory.Slots)
|
||||
}
|
||||
equipment, _, err := repos.Equipment.Load(ctx, "19")
|
||||
if err != nil {
|
||||
t.Fatalf("load equipment: %v", err)
|
||||
}
|
||||
if entry, found := equipment.Entries["13"]; found {
|
||||
t.Fatalf("take-out must not equip the title: %+v", entry)
|
||||
}
|
||||
|
||||
ack, rest := splitGameServerUpperPacket(t, connection.write.Bytes())
|
||||
if ack.Header.Classification != dnfproto.DefaultChannelClassification ||
|
||||
ack.Header.MsgID != currentTitleBookGetMsgID ||
|
||||
len(ack.Body) != 17 ||
|
||||
ack.Body[0] != 1 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[1:5]) != 0 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[5:9]) != 12 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[9:13]) != 4 ||
|
||||
binary.LittleEndian.Uint32(ack.Body[13:17]) != 10 {
|
||||
t.Fatalf("ack must echo the real destination (space=0 slot=12): header=%+v body=%x", ack.Header, ack.Body)
|
||||
}
|
||||
update, rest := splitGameServerUpperPacket(t, rest)
|
||||
if update.Header.Classification != 0 ||
|
||||
update.Header.MsgID != uint16(dnfenum.CmdPacketWalkoutPartyMember) ||
|
||||
len(update.Body) < 9 ||
|
||||
update.Body[0] != dnfrepo.MainInventoryListType ||
|
||||
binary.LittleEndian.Uint16(update.Body[1:3]) != 1 ||
|
||||
binary.LittleEndian.Uint16(update.Body[3:5]) != 12 ||
|
||||
binary.LittleEndian.Uint32(update.Body[5:9]) != 2747592 {
|
||||
t.Fatalf("expected one list-0 op14 row for bag slot 12: header=%+v body=%x", update.Header, update.Body)
|
||||
}
|
||||
list, trailing := splitGameServerUpperPacket(t, rest)
|
||||
if list.Header.MsgID != currentTitleBookMsgID || len(trailing) != 0 {
|
||||
t.Fatalf("expected the book list last: header=%+v trailing=%d", list.Header, len(trailing))
|
||||
}
|
||||
if bytes.Contains(list.Body, []byte{0xC8, 0xEC, 0x29, 0x00}) {
|
||||
t.Fatalf("taken-out title must not stay in the book page: body=%x", list.Body)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTitleBookPageKeepsItemSealState 回归 2026-09-18「已解封的称号放进称号簿、小退
|
||||
// 重登又变成封装」:称号簿页条目里 0x0D 那一位是封装标记(0=解封,1=封装),必须跟
|
||||
// 背包行同源读物品自己的状态,不能写死 1。
|
||||
func TestTitleBookPageKeepsItemSealState(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos := dnfrepomemory.NewMemoryGroup()
|
||||
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{
|
||||
CharacterID: "19",
|
||||
Slots: map[string]dnfrepo.ItemStack{
|
||||
// 解封的称号:没有 seal extra、Bind=false → 0
|
||||
"100:4010": {ItemID: 2747592, Count: 1},
|
||||
// 再封装过的称号:reseal 会写 seal 标记 → 1(见 itemgrade/reseal.go)
|
||||
"100:3001": {ItemID: 2747593, Count: 1, Extra: map[string]string{"seal_flag": "1", "seal": "1"}},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("save inventory: %v", err)
|
||||
}
|
||||
unsealedRows := currentRequestOverseerRowsFromInventory(ctx, repos.Inventory, "19", 0, 4)
|
||||
if len(unsealedRows) != 1 || unsealedRows[0].itemID != 2747592 || unsealedRows[0].createFlag != 0 {
|
||||
t.Fatalf("unsealed title page row = %+v, want seal byte 0", unsealedRows)
|
||||
}
|
||||
sealedRows := currentRequestOverseerRowsFromInventory(ctx, repos.Inventory, "19", 0, 3)
|
||||
if len(sealedRows) != 1 || sealedRows[0].itemID != 2747593 || sealedRows[0].createFlag != 1 {
|
||||
t.Fatalf("resealed title page row = %+v, want seal byte 1", sealedRows)
|
||||
}
|
||||
}
|
||||
|
||||
func newGameplayDomainBridgeFixture(repos dnfrepo.Group) (*Service, *gameSession, *bufferConn) {
|
||||
connection := &bufferConn{}
|
||||
service := &Service{
|
||||
options: options{
|
||||
accountID: "account-1",
|
||||
gameUpperHeader: gameUpperHeaderChannel13,
|
||||
gameUpperBodyCodec: gameUpperBodyCodecPlain,
|
||||
},
|
||||
repositoryProvider: func() (dnfrepo.Group, bool) {
|
||||
return repos, true
|
||||
},
|
||||
}
|
||||
session := &gameSession{
|
||||
conn: connection,
|
||||
connID: "gameplay-domain-bridge-test",
|
||||
selectedCharacterID: 19,
|
||||
}
|
||||
return service, session, connection
|
||||
}
|
||||
Reference in new issue
Block a user