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,91 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/dnfenum"
|
||||
dnfproto "longheng.io/server/internal/modules/dnf/protocol"
|
||||
"longheng.io/server/internal/modules/dnf/raid"
|
||||
)
|
||||
|
||||
// Operator's report, 2026-09-20: "攻坚结束后 这个成员列表不实时刷新 1队的两个人已经退出
|
||||
// 攻坚队了 还在攻坚队内的人看成员列表 显示这俩人还在攻坚队". Live raid 1507329:
|
||||
//
|
||||
// 13:21:29 game-raid-completed-team-leave-acknowledged char_id=1 member_count=4
|
||||
// 13:21:36 game-raid-completed-team-leave-acknowledged char_id=21 member_count=4
|
||||
//
|
||||
// Both leavers were answered, and no roster packet followed: the live raid row was already
|
||||
// gone (finalizeRuntimeRaid deletes it), so the remaining members kept drawing all four
|
||||
// rows. The reduced roster is now rebuilt from the frozen completion record and pushed.
|
||||
func TestCompletedTeamRaidLeaveRefreshesTheRemainingRoster(t *testing.T) {
|
||||
service := &Service{
|
||||
options: options{gameUpperBodyCodec: gameUpperBodyCodecPlain},
|
||||
raids: make(map[uint32]*runtimeRaidState),
|
||||
raidRewardCompleted: make(map[runtimeRaidRewardCompletionKey]runtimeRaidSettlementRewardCompletion),
|
||||
}
|
||||
teamRaidKey := uint32(1507329)
|
||||
leaver := &gameSession{conn: &bufferConn{}}
|
||||
stayer := &gameSession{conn: &bufferConn{}}
|
||||
service.bindGameSessionCharacter(leaver, 13)
|
||||
service.bindGameSessionCharacter(stayer, 1)
|
||||
completed := runtimeRaidState{
|
||||
RaidKey: teamRaidKey,
|
||||
LeaderID: 1,
|
||||
TailFlag: 0,
|
||||
FrozenMembers: []runtimeRaidMemberState{
|
||||
{CharID: 1, Name: "leader", GroupIndex: 1, SlotOrder: 0, UserState: 1, HPPercent: 100, MPPercent: 100},
|
||||
{CharID: 13, Name: "member", GroupIndex: 1, SlotOrder: 1, UserState: 1, HPPercent: 100, MPPercent: 100},
|
||||
},
|
||||
Lifecycle: runtimeRaidLifecycleState{
|
||||
Phase: runtimeRaidDungeonPhaseTwo,
|
||||
Status: runtimeRaidLifecycleFinished,
|
||||
},
|
||||
}
|
||||
key := runtimeRaidRewardCompletionKey{RaidKey: teamRaidKey, Phase: runtimeRaidDungeonPhaseTwo, Token: 11}
|
||||
service.raidRewardCompleted[key] = runtimeRaidSettlementRewardCompletion{Raid: completed}
|
||||
stayer.conn.(*bufferConn).write.Reset()
|
||||
|
||||
body := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(body, 13)
|
||||
if err := service.handleOnlineLeaveRaid(leaver, uint16(dnfenum.CmdPacketLeaveRaid), body); err != nil {
|
||||
t.Fatalf("completed team leave: %v", err)
|
||||
}
|
||||
|
||||
packets := runtimeRaidPeerTake(t, stayer)
|
||||
refresh, ok := runtimeRaidPeerFind(packets, raid.RaidMemberRefreshMsgID)
|
||||
if !ok {
|
||||
t.Fatal("the remaining member received no roster refresh after a teammate left the released raid")
|
||||
}
|
||||
// The pushed list must be the frozen roster minus the leaver.
|
||||
reduced := cloneRuntimeRaidState(completed)
|
||||
reduced.Members = []runtimeRaidMemberState{completed.FrozenMembers[0]}
|
||||
reduced.FrozenMembers = []runtimeRaidMemberState{completed.FrozenMembers[0]}
|
||||
want, err := raid.BuildMemberRefreshMode3Body(runtimeRaidMemberRefresh(reduced))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(refresh.Body, want) {
|
||||
t.Fatalf("refreshed roster body=% X, want the one-member list % X", refresh.Body, want)
|
||||
}
|
||||
fullBody, err := raid.BuildMemberRefreshMode3Body(runtimeRaidMemberRefresh(completed))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes.Equal(fullBody, want) {
|
||||
t.Fatal("the fixture's reduced list is not actually smaller than the frozen roster")
|
||||
}
|
||||
|
||||
// The leaver itself is answered as before: success ACK + its own object removed.
|
||||
leaverPackets := runtimeRaidPeerTake(t, leaver)
|
||||
ack, ok := runtimeRaidPeerFind(leaverPackets, uint16(dnfenum.CmdPacketLeaveRaid))
|
||||
if !ok || !bytes.Equal(ack.Body, raid.BuildLeaveRaidResultBody(false)) {
|
||||
t.Fatalf("leaver ack msg=%d body=% X, want the success body", ack.Header.MsgID, ack.Body)
|
||||
}
|
||||
if removal, ok := runtimeRaidPeerFind(leaverPackets, raid.RaidObjectMsgID); !ok ||
|
||||
!bytes.Equal(removal.Body, raid.BuildRaidObjectRemoveBody(teamRaidKey)) {
|
||||
t.Fatalf("leaver received msg=%d body=% X, want the raid object removal", removal.Header.MsgID, removal.Body)
|
||||
}
|
||||
var _ dnfproto.ChannelPacket = refresh
|
||||
}
|
||||
Reference in new issue
Block a user