合并本地领先源码:旧UI、GM疲劳燃烧、白金徽章孔、宠物修复、支援兵普攻开关

This commit is contained in:
hanfuit committed 2026-11-04 19:20:49 +08:00
1 parent f2debaf9bf
commit 70b3c5a6dc
57 files changed
+4256 -30

No files matched your search

@@ -0,0 +1,111 @@
package dnfbridge
import (
"bytes"
"encoding/binary"
"net"
"testing"
"time"
dnfcompanion "longheng.io/server/internal/modules/dnf/companion"
)
// companionOfflineTestConn is intentionally local to this test so the file can
// be compiled without the large shared service_test.go harness.
type companionOfflineTestConn struct {
net.Conn
write bytes.Buffer
}
func (c *companionOfflineTestConn) Write(data []byte) (int, error) { return c.write.Write(data) }
func (c *companionOfflineTestConn) SetWriteDeadline(time.Time) error {
return nil
}
func TestBuildCurrentDungeonCompanionOfflineBodyMatchesPresentZeroContract(t *testing.T) {
body := buildCurrentDungeonCompanionOfflineBody(12345)
if len(body) != currentDungeonCompanionProjectionHeaderSize {
t.Fatalf("body length=%d want=%d", len(body), currentDungeonCompanionProjectionHeaderSize)
}
if got := binary.LittleEndian.Uint32(body[0:4]); got != currentDungeonCompanionProjectionMagic {
t.Fatalf("magic=%#x want=%#x", got, currentDungeonCompanionProjectionMagic)
}
if body[4] != currentDungeonCompanionProjectionVersion {
t.Fatalf("version=%d want=%d", body[4], currentDungeonCompanionProjectionVersion)
}
if body[5] != 0 {
t.Fatalf("flags=%#x want 0 (present must be clear)", body[5])
}
if got := binary.LittleEndian.Uint16(body[6:8]); got != 0 {
t.Fatalf("source_character=%d want 0", got)
}
if got := binary.LittleEndian.Uint64(body[8:16]); got != 12345 {
t.Fatalf("revision=%d want 12345", got)
}
for offset := 16; offset < len(body); offset++ {
if body[offset] != 0 {
t.Fatalf("byte %d=%#x want 0", offset, body[offset])
}
}
}
func TestSendCurrentDungeonCompanionTeamOfflineIsIdempotent(t *testing.T) {
connection := &companionOfflineTestConn{}
session := &gameSession{conn: connection, connID: "companion-team-logout"}
run := &currentDungeonCompanionRun{
Ready: true,
TeamMode: true,
TeamLeaderID: 1,
TeamSize: 2,
Online: true,
Sources: []currentDungeonCompanionSourceRun{
{Ready: true, SourceCharacterID: 2, Body: buildCurrentDungeonCompanionOfflineBody(77)},
{Ready: true, SourceCharacterID: 3, Body: buildCurrentDungeonCompanionOfflineBody(77)},
},
}
runtime := &runtimeDungeonState{Companion: run}
service := &Service{}
service.sendCurrentDungeonCompanionTeamOffline(session, runtime, "dungeon_return_town")
if !run.LogoutSent || run.Online {
t.Fatalf("run state after logout = sent:%v online:%v", run.LogoutSent, run.Online)
}
if run.OfflineAt.IsZero() || run.OfflineReason != "dungeon_return_town" {
t.Fatalf("offline evidence = at:%v reason:%q", run.OfflineAt, run.OfflineReason)
}
first := connection.write.Len()
if first == 0 {
t.Fatal("logout packet was not sent")
}
// A second boundary must not replay the packet.
service.sendCurrentDungeonCompanionTeamOffline(session, runtime, "return_select_character")
if connection.write.Len() != first {
t.Fatalf("second logout wrote %d extra bytes", connection.write.Len()-first)
}
stream := connection.write.Bytes()
headerSize := service.gameUpperHeaderSize()
if len(stream) < headerSize {
t.Fatalf("logout stream too short: %d", len(stream))
}
total := int(binary.LittleEndian.Uint32(stream[3:7]))
if total != len(stream) {
t.Fatalf("logout packet total=%d stream=%d", total, len(stream))
}
if msgID := binary.LittleEndian.Uint16(stream[1:3]); msgID != currentDungeonCompanionProjectionOpcode {
t.Fatalf("msg id=%#x want=%#x", msgID, currentDungeonCompanionProjectionOpcode)
}
body := stream[headerSize:total]
if len(body) != currentDungeonCompanionProjectionHeaderSize || body[5] != 0 {
t.Fatalf("logout body length=%d flags=%d", len(body), body[5])
}
}
func TestMatchTeamAutoFillKeepsProjectionPipelinePerSource(t *testing.T) {
// The matcher is pure; this guard pins the cap the bridge passes so a future
// change cannot silently form a five-player dungeon party.
if dnfcompanion.TeamMaxCompanions != 3 {
t.Fatalf("TeamMaxCompanions=%d want 3", dnfcompanion.TeamMaxCompanions)
}
}