127 lines
6.1 KiB
Go
127 lines
6.1 KiB
Go
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"`
|
||
}
|
||
|