package dnfbridge import ( "os" "path/filepath" "testing" "time" platformpvf "longheng.io/server/internal/platform/pvf" ) // TestCompanionPureServerInjectionResolvesRealPVFAPC 验证「不改客户端」这条路的 // **最可能失败点**:服务端自建 APC 时需要从真实 Script.pvf 里解析 // AICharacter code 5405。解析不到就会让整次 planRuntimeDungeonExtendedActors // 失败 —— 那会直接导致**副本进不去**,而不是"支援兵不出现"。 // // 这是一个 opt-in 的真实资源测试(与树里其他 real-PVF 测试同一约定): // // set DNF_WORLDMAP_REAL_PVF_SMOKE= func TestCompanionPureServerInjectionResolvesRealPVFAPC(t *testing.T) { pvfPath := os.Getenv("DNF_WORLDMAP_REAL_PVF_SMOKE") if pvfPath == "" { t.Skip("set DNF_WORLDMAP_REAL_PVF_SMOKE to the Script.pvf path to verify APC5405 resolution") } archive, err := platformpvf.Open(pvfPath) if err != nil { t.Fatal(err) } catalog, err := newPVFDungeonAICharacterCatalog(archive) if err != nil { t.Fatal(err) } definition, found, err := catalog.Find(currentDungeonCompanionP0APCCode) if err != nil { t.Fatalf("resolve companion APC %d: %v", currentDungeonCompanionP0APCCode, err) } if !found { t.Fatalf("companion APC %d is absent from the deployed PVF", currentDungeonCompanionP0APCCode) } if definition.Level <= 0 { t.Fatalf("companion APC %d has non-positive level %d", currentDungeonCompanionP0APCCode, definition.Level) } t.Logf("companion APC resolved code=%d level=%d", currentDungeonCompanionP0APCCode, definition.Level) } // TestCompanionPureServerInjectionAppendsFriendlyActorToStartMap 验证开关打开后 // 真的会往 START_MAP 计划里追加一条**友方** APC 行。 // // 这不是重复 plan 层的测试:这里走的是「开关 → 计划」的完整接线, // 也就是"支援兵到底会不会进副本"的决定性一步。 func TestCompanionPureServerInjectionAppendsFriendlyActorToStartMap(t *testing.T) { // 用文件开关打开这条通道(不依赖环境变量,因为环境变量必须在进程 // 启动前设置,而本部署的登录器会复用已运行的服务端)。 // // 这里**不改** TestMain 钉的共享路径之外的东西:换成本用例专属的 // 临时文件,跑完恢复,避免影响同包其他用例。 flagPath := filepath.Join(t.TempDir(), currentDungeonCompanionP0StartMapFlagFile) companionStartMapFlagMu.Lock() previousOverride := companionStartMapFlagPathOverride companionStartMapFlagPathOverride = flagPath companionStartMapFlagChecked = time.Time{} companionStartMapFlagMu.Unlock() t.Cleanup(func() { companionStartMapFlagMu.Lock() companionStartMapFlagPathOverride = previousOverride companionStartMapFlagChecked = time.Time{} companionStartMapFlagMu.Unlock() }) if err := os.WriteFile(flagPath, []byte("1"), 0o644); err != nil { t.Fatalf("create switch file %s: %v", flagPath, err) } if !currentDungeonCompanionP0StartMapEnabled() { t.Fatalf("switch file %s did not enable the companion APC channel", flagPath) } plan := runtimeDungeonExtendedActorPlan{NextObjectKey: 7} if err := appendCurrentDungeonCompanionP0StartMapActorForRun( &plan, &pvfDungeonAICharacterCatalog{}, true, ); err == nil { // 空目录必须报错(找不到 5405),说明这条路真的在解析定义、 // 而不是无条件追加一行。 t.Fatal("empty AI character catalog must not silently add an APC row") } // 关掉开关后不得再追加。 planOff := runtimeDungeonExtendedActorPlan{NextObjectKey: 7} if err := appendCurrentDungeonCompanionP0StartMapActorForRun( &planOff, &pvfDungeonAICharacterCatalog{}, false, ); err != nil { t.Fatalf("disabled channel must be a no-op, got %v", err) } if len(planOff.Actors) != 0 || planOff.NextObjectKey != 7 { t.Fatalf("disabled channel mutated the plan: actors=%d next=%d", len(planOff.Actors), planOff.NextObjectKey) } }