package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // 随从的服务端兜底驱动:跟随 + 协战。 // ⛑⛑⛑ 2026-10-01 定位到更正的解法,本文件已不再是默认路径 // (开关 gameplay.aether.companionServerDrive,默认 false): // 随从模板用的是专属伴生行为树(晴 Companion_JiaMu_Shuangmawei、 // 盖斯 Companion_Guys),它们的行为集里没有 Behavior_Battle 和 RelatedSkillIds, // 战斗能力写在那棵树的 C# 实现里,服务端查不到 ⇒ 硬喂招只能给到 // EnemyAiFallbackAttacks(金属吉他手那两下),这就是实机上看到的 // 「只有远程射击动作、也没有武器、打不出伤害」。 // 正解 = SyncAgentCompanionAI + SyncCompanionMaster + 模板自带的阵营 // (UnitCamp.PlayerDefault = 1,见 CompanionBind.cs),然后让客户端那棵树自己跑。 // ⛑⛑ 2026-10-01 更早的实机:只推 SyncCompanionMaster / SyncAgentCompanionAI 是不够的 —— // 那两个包只登记「谁是谁的随从」,客户端不会因此给它下任何指令,实测就是站着不动的木桩。 // 当时补的就是这条用主线本来就有的敌人战斗指令驱动: // SyncBattleAiS(68555063) 开/关战斗 AI // SyncUnitLockTarget(68009754) 锁定谁 // SyncEnemyFightEdict(68882858) 给不给攻击令牌 // SyncEnemyBattleMoveTowardUnit(68792181) 朝谁移动 // 这四个包在 GameRouter.EnemyAiActions.cs:446-454 已经在用(驱动敌人追玩家)。 // 捐赠端 7k v0.5-2 那 323 行 GarmCompanionAi.cs 的核心也就是这行 —— // 所以它不需要整体移植,主线把同样的包换个目标发就行。 // 跟随 = 没有敌人时把 target 留空、移动目标填主人 uid; // 协战 = 选一个最近的敌人当 target,同时给攻击令牌。 // // 由 OnMovementReport 调用:喂一条随从的真实坐标。 func GameRouter_CompanionAssist_NoteCompanionPosition(ctx context.Context, uid uint64, pos Vec3) any { // Router implementation for GameRouter_CompanionAssist_NoteCompanionPosition var zero any; return zero } // 取随从的真实坐标;没有或过期返回 false。 func GameRouter_CompanionAssist_TryGetCompanionPosition(ctx context.Context, uid uint64, pos Vec3) bool { // Router implementation for GameRouter_CompanionAssist_TryGetCompanionPosition return true } // // 给刚绑好的随从起一个驱动 worker。失败不影响随从本身(它已经生成了)。 // func GameRouter_CompanionAssist_StartCompanionAssist(ctx context.Context, uid uint64, ownerUnitId uint64) any { // Router implementation for GameRouter_CompanionAssist_StartCompanionAssist var zero any; return zero } // 让某个随从停手(解散 / 单位消失时用)。 func GameRouter_CompanionAssist_StopCompanionAssist(ctx context.Context, uid uint64) any { // Router implementation for GameRouter_CompanionAssist_StopCompanionAssist var zero any; return zero } func GameRouter_CompanionAssist_RunCompanionAssistAsync(ctx context.Context, uid uint64, ownerUnitId uint64) any { // Router implementation for GameRouter_CompanionAssist_RunCompanionAssistAsync var zero any; return zero } func GameRouter_CompanionAssist_TickCompanionAssistAsync(ctx context.Context, session *GameSession, world any, rt any, tick int32) any { // Router implementation for GameRouter_CompanionAssist_TickCompanionAssistAsync var zero any; return zero } // // 贴身出招:SyncUnitSmoothFace 转身 + SyncUseTargetSkill 放招。 // 与主线 GameRouter.EnemyAiActions.cs:592-668 的那段逐行对齐(那套是调过的,别另写)。 // ⛑ 技能池来自 gameplay.aether.companionAttacks:随从没有自己的攻击模组 // (行为集里空),只能借通用池;换池子改配置即可,不用重编译。 // func GameRouter_CompanionAssist_CastCompanionAsync(ctx context.Context, session *GameSession, rt any, targetPos Vec3, now int64) any { // Router implementation for GameRouter_CompanionAssist_CastCompanionAsync var zero any; return zero } // // 从主线的本地敌人账本里挑一个最近的目标,并返回它当前的位置。挑不到返回 (0, 玩家坐标) = 跟随。 // ⛑ 排除:自己、玩家单位、已死单位;是否参战按离玩家的距离判断(随从离玩家太远就不打)。 // func GameRouter_CompanionAssist_SelectCompanionTarget(ctx context.Context, session *GameSession, world any, rt any) any { // Router implementation for GameRouter_CompanionAssist_SelectCompanionTarget var zero any; return zero } func GameRouter_CompanionAssist_DistanceSq(ctx context.Context, a Vec3, b Vec3) float32 { // Router implementation for GameRouter_CompanionAssist_DistanceSq return 0 } type CompanionAssistRuntime struct { UnitId uint64 `json:"unitId"` OwnerUnitId uint64 `json:"ownerUnitId"` // // 当前锁定的目标。初值必须是哨兵(不是 0):engagement 三件套只在目标变化时下发, // 而"跟随"态的目标正是 0 —— 若初值也是 0,next != Target 恒为 false ⇒ // 整局一个 engagement 包都发不出去,包括"关掉战斗 AI"那条,随从就一直保持 // spawn 时的战斗姿态举着枪(2026-10-01 实机踩到)。 // Target uint64 `json:"target"` // 单调递增的 move 序号;契约里 MoveId/InstanceId 是 byte,落盘前截断。 MoveSeq uint32 `json:"moveSeq"` NextMoveAt int64 `json:"nextMoveAt"` // // 估算锚点。服务端拿不到实体真实坐标(客户端不回传),只能这么估。 // spawn 用的是「全 0 pose = 落在玩家跟前」的约定 ⇒ 首帧拿玩家坐标当起点是准的。 // Anchor Vec3 `json:"anchor"` HasAnchor bool `json:"hasAnchor"` MoveCount int32 `json:"moveCount"` // 朝向(度)。出招前先转身用 —— 官方技能很多带 NoRotation=true。 Facing float32 `json:"facing"` // 出招冷却到期时间(TickCount64)。 CastUntil int64 `json:"castUntil"` // 下次允许出招的时间。 ActionAt int64 `json:"actionAt"` // 出招实例号;契约里 instanceId 是 int,别写成 uint。 CastInstance int32 `json:"castInstance"` CastCount int32 `json:"castCount"` }