This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

158 lines
8.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// 派对 NPC「驻留行为」—— 让被邀请进派对的角色**自己会动**(2026-09-26 第 46 轮)。
// WHY THIS FILE EXISTS
// --------------------
// 用户原话:「最主要的还是邀请进派对角色的行为问题,**不互动一个也要让角色们自由行动,
// 自由做动作,不然还是死气沉沉**」。
// 现场事实(第 45/46 轮实测):被邀角色**能被正确认出来(cardId 已修)、会播 POI 预制动作,
// 但一动不动地站在原地做完那一套动作**。原因是 `SendCivilianAsync` 只发到「POI 动作」为止,
// 少了让客户端接管 locomotion 的那几步。
// ★★★★★ 官方口径(全部来自配表/客户端契约,不是猜)
// · `SyncMoveWandering`(68745518) 的契约注释原文(CrowdMethods4229938.cs:204-207):
// 「**让一个已投放的 NPC/行人"自己走起来"的唯一官方包。**
// 它**不带目的地**——只有半径、角度窗口和时间预算,路径由客户端
// ZoneGraph/AetherAI 自己挑。⇒ 服务端根本不需要路网来做"行走",
// 只需要周期续期(MaxTime 是总时长,走完就永久站住)。」
// · 项目自己的 static 路人配方(GameRouter.Ambient.cs:804-834 / 792-794):
// `SendCivilianAsync`(建人)+ **`SyncAgentForceGo`(启动行为树)**… 且
// 「crowd 通道是客户端自驱游荡,服务端发游荡包反而多余;**static 通道必须发,
// 否则只播 idle**」—— 派对 NPC 正好走 static 通道。
// · `GameRouter.Ambient.cs:16-27` 的终版配方把 `SyncLogicAgentEnter` 列为第 3 步:
// 「把 id 注册进 logic-agent 管理器(**任务从它派发**)—— 不发它,任务可能被静默丢弃」。
// ★ 为什么**不能**改用 crowd 通道(`SyncAetherAICrowdAdd` + `TargetLocationReason=1` 自驱游荡):
// `ClientCrowdInitData4229938` 的字段只有
// `NpcFormworkId / AgentPersonaId / UrbanDiversityConfigId / DesiredSpeed / ActionId /
// TargetLocationReason / FashionSuitId` + 基类 `Id/Position/Facing/EulerAngles`
// —— **没有 `AIAgentInfo`** ⇒ 带不了 `FightSpiritId` ⇒ `ClientData.cardId` 恒 0
// ⇒ 第 45 轮刚修好的「烤肉邀请面板」会**立刻退回空列表**。所以通道必须留在 static。
// ★★ 另一条被排除的路(配表级硬判据,别再回头):
// 24 个可邀角色的 `AgentConfig.AiSettingId` 指向的行为集
// (`AgentAIBehaviorSetsBattleAISBehaviorConfig`)**全部没有游荡行为**:
// 40213395 Companion_Taffy / 40215027 Companion_zhuchang_Lin / 40213369 Companion_Battle /
// 40215025 Bot_Common_SMG / 40213004 Behavior_EmptyBehavior(无战斗AI)…
// 它们的 `Behavior_Wander` 与 `Behavior_StandBy` **都是空字符串**。
// 对照:会走路的市民档是 40213001/40213002(`Main_Common_NPC`,
// `Behavior_Wander = Behavior_Common_Wander`、`Behavior_StandBy = Behavior_Common_Patrol`)。
// ⇒ 这些角色在官方场景里是「**跟着玩家的同伴**」,本来就不游荡;拿它们当驻场 NPC
// 就必须由服务端/客户端额外下发行为意图。这正是本文件的职责。
// ⚠ 已知边界(诚实标注)
// 1. `SyncMoveWandering` 对 **crowd 通道(ECS/AgentEntity 管线)** 已被证伪
// (Ambient.cs:33-34:走 WanderingActionModule = BaseUnit 管线 ⇒ 静默失败)。
// 本文件只用在 **static 通道** 上(Ambient.cs:792-794 明确说 static 必须发)。
// 若实测仍不动 ⇒ 把 `PartyNpcRoamMode` 换成 `"waypoint"`(路点巡游,同一份契约里
// 被称作「唯一能产生走路步态的通道」),无需改代码。
// 2. 游荡半径取小值(默认 2~10 m)——营地里有家具与碰撞体,绕远会走丢或穿模。
// 3. 走完 `MaxTime` 就永久站住 ⇒ 必须**周期续期**,见 `PartyNpcRoamRearmSeconds`。
// ★★★ 第 47 轮定案(用户现场):「**停走**」才是正解。
// 用户实测 waypoint 档 = 「**可以走了,不过是乱走**」⇒ 走路通道本身已跑通
// (`SyncLogicAgentCommand`(68050574) + `TaskWayPointMove`(tag 39) 有效,
// `SyncMoveWandering` 无效),但马上要求「把角色**围成一圈**、**坐在地面**」。
// ⇒ 默认 `PartyNpcRoamMode = "none"`;`SyncLogicAgentEnter` / `SyncAgentForceGo`
// 已**上移到模式门之前**(`none` 档也要发 —— 行为树没起来,连 POI 坐姿都保不住)。
// ⛔ 位置与朝向**本来就是对的**(`SpawnPartyNpcsAsync` 圆周排布 + `angle + π` 朝圆心),
// 是 walk 任务把人走散、并用行走姿态顶掉了坐姿 ⇒ 不需要改坐标,也不需要另写编排。
//
func GameRouter_PartyNpcRoam_RoamStateOf(ctx context.Context, session *GameSession) any {
// Router implementation for GameRouter_PartyNpcRoam_RoamStateOf
var zero any; return zero
}
// 重开派对(撤掉上一批人)时调用:停掉旧循环、清空账本。
func GameRouter_PartyNpcRoam_ResetPartyNpcRoam(ctx context.Context, session *GameSession) any {
// Router implementation for GameRouter_PartyNpcRoam_ResetPartyNpcRoam
var zero any; return zero
}
func GameRouter_PartyNpcRoam_PartyRoamActive(ctx context.Context, ) bool {
// Router implementation for GameRouter_PartyNpcRoam_PartyRoamActive
return true
}
//
// 把一个人登记进驻留行为,并起后台续期循环。
// ⛑ 在 SendCivilianAsync **之后**调用:单位必须先存在,任务才有人接。
//
func GameRouter_PartyNpcRoam_ArmPartyNpcRoamAsync(ctx context.Context, session *GameSession, entityId uint64, x float32, y float32, z float32, token context.Context) any {
// Router implementation for GameRouter_PartyNpcRoam_ArmPartyNpcRoamAsync
var zero any; return zero
}
// 按当前模式下发一次行为意图(并回写 PartyNpcRoamEntry.ArmedTicks)。
func GameRouter_PartyNpcRoam_PushPartyRoamIntentAsync(ctx context.Context, session *GameSession, entry any, cfg any, token context.Context) any {
// Router implementation for GameRouter_PartyNpcRoam_PushPartyRoamIntentAsync
var zero any; return zero
}
//
// `SyncMoveWandering`(68745518) —— 客户端自驱游荡意图。
// 逐字段照抄 `GameRouter.Ambient.PushAmbientWanderAsync`(同一条已验证的 static 配方),
// 只有半径/时长换成派对用的保守值。
// ⛑ `MoveId` 不是序号,是 `BattleMoveActionType`;填 0(Unknown) 等于「没有 locomotion 状态」。
//
func GameRouter_PartyNpcRoam_PushPartyWanderAsync(ctx context.Context, session *GameSession, entry any, cfg any, token context.Context) any {
// Router implementation for GameRouter_PartyNpcRoam_PushPartyWanderAsync
var zero any; return zero
}
//
// 对照/退路档:用 `SyncLogicAgentCommand`+`TaskWayPointMove`(tag 39) 让 NPC 沿一条
// **绕着营地的小多边形**巡游。`Ambient.cs` 文件头称这条通道是
// 「**唯一能产生走路步态的通道**」。
// 服务端自己记 `Cursor*`(= 上一腿终点)⇒ 不需要客户端上报位置。
//
func GameRouter_PartyNpcRoam_PushPartyWaypointAsync(ctx context.Context, session *GameSession, entry any, cfg any, token context.Context) any {
// Router implementation for GameRouter_PartyNpcRoam_PushPartyWaypointAsync
var zero any; return zero
}
//
// 续期循环。`MaxTime` 走完客户端会**永久站住**,所以必须周期性补发。
// 与 `EnsureLeftBehindLoop` 同款:session 断开后写 socket 会抛异常,循环自己退出,
// 不需要额外的停止钩子。
//
func GameRouter_PartyNpcRoam_EnsurePartyNpcRoamLoop(ctx context.Context, session *GameSession, state any) any {
// Router implementation for GameRouter_PartyNpcRoam_EnsurePartyNpcRoamLoop
var zero any; return zero
}
func GameRouter_PartyNpcRoam_RunPartyNpcRoamLoopAsync(ctx context.Context, session *GameSession, state any, token context.Context) any {
// Router implementation for GameRouter_PartyNpcRoam_RunPartyNpcRoamLoopAsync
var zero any; return zero
}
type PartyNpcRoamEntry struct {
EntityId uint64 `json:"entityId"`
HomeX float32 `json:"homeX"`
HomeY float32 `json:"homeY"`
HomeZ float32 `json:"homeZ"`
// 上一次「下发/续期」的时刻(Environment.TickCount64)。
ArmedTicks int64 `json:"armedTicks"`
// waypoint 模式:这一腿预计走完的时刻。
EtaTicks int64 `json:"etaTicks"`
// waypoint 模式:下一腿的起点(= 上一腿的终点,服务端自己记账,不依赖客户端上报)。
CursorX float32 `json:"cursorX"`
CursorY float32 `json:"cursorY"`
CursorZ float32 `json:"cursorZ"`
}
type PartyNpcRoamState struct {
Entries []PartyNpcRoamEntry `json:"entries"`
SyncRoot any `json:"syncRoot"`
Cts *context.CancelFunc `json:"cts"`
Loop *any `json:"loop"`
// ★ 第 49 轮:停走档那条日志每批只打一次(原先每人一条 ⇒ 10 条一样的话
// 把 [PARTYNPC] 生成汇总 淹掉,而汇总才是真正要看的证据)。
StopWalkLogged bool `json:"stopWalkLogged"`
}