121 lines
5.8 KiB
C#
121 lines
5.8 KiB
C#
using Ananta.SDK.Rpc;
|
||
using Ananta.Server.Configuration;
|
||
using Ananta.Server.Protocol.Client4229938;
|
||
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
|
||
|
||
namespace Ananta.Server.Handlers.Game;
|
||
|
||
/// <summary>
|
||
/// 行人路网的**问路应答** —— `AskClientNpcFindPath`(67974714),C→S INVOKE。
|
||
///
|
||
/// 客户端在它自己要走路时会来问(我们服务端驱动的行人走 TaskWayPointMove,但由客户端
|
||
/// 寻路的单位 —— 比如某些 NPC / 被撞墙打断的单位 —— 会真的来问)。
|
||
///
|
||
/// ⛑️ 之前这条包走 `DefaultReturnCatalog` 兜底 = 永远回 `0xFF 01 FF 01`
|
||
/// (两个空 list7)⇒ 等于告诉客户端"没有路" ⇒ 那条单位就站着不动。
|
||
///
|
||
/// 契约真源(硬规则 22,双源吻合):
|
||
/// ① `RPCDeserializeAuto.lua` `midToReturnMessageReader[67974714]`
|
||
/// → `Base.ReadStruct(reader, Auto.Reader[688])`
|
||
/// ② `Auto.Reader[688]` = `Points:list7<UXVector3>` + `Flags:list7<byte>`
|
||
/// ③ 兜底字节 `FF 01 FF 01` = 两个空 list7,正好对上 ①②。
|
||
///
|
||
/// 规划不出来(路网没覆盖 / 不连通 / 太远)就照旧回空路径 —— 宁可不动,也不要给穿墙直线。
|
||
/// </summary>
|
||
internal sealed partial class GameRouter
|
||
{
|
||
[Handler(MethodId.AskClientNpcFindPath, HandlerPacketKind.Invoke)]
|
||
private Task AskClientNpcFindPath(Connection conn, UxRpcMessage msg)
|
||
=> AnswerNpcFindPathAsync(conn, msg);
|
||
|
||
private static Task AnswerNpcFindPathAsync(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var cfg = PrivateServerConfigStore.Current.Gameplay.Aether;
|
||
|
||
SceneMethods.AskClientNpcFindPath4229938? args = null;
|
||
if (cfg.NpcPathPlanEnabled)
|
||
{
|
||
try
|
||
{
|
||
args = msg.GetArgs<SceneMethods.AskClientNpcFindPath4229938>();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 请求体不是 28 字节(ulong + 两个 UXVector3)就当没问,回空路径。
|
||
msg.Context.Session?.Log.Warn($"[PEDPATH] args decode failed: {ex.Message}");
|
||
args = null;
|
||
}
|
||
}
|
||
|
||
if (args is null)
|
||
return conn.ReturnAsync(msg, new SceneMethods.FindPathResult4229938());
|
||
|
||
// ⛑️ 顺手把这一个包当成位置回馈用:`From` 是客户端手里那个单位的真实坐标。
|
||
// 服务端推完 TaskWayPointMove 就不知道人走到哪了,账本里的 ped.X/Z 是"应该到的地方"。
|
||
// 两边偏差一大就说明客户端那边真的被卡住了 ⇒ 校正账本 + 清掉旧行程,
|
||
// 行人才能从真实位置续走(否则会一直站在原地不动)。
|
||
var session = msg.Context.Session;
|
||
if (session is not null &&
|
||
GameRouter.TryResyncAmbientPed(session, args.Id, args.From.X, args.From.Z,
|
||
out var drift, out var hardReset))
|
||
session.Log.Info($"[PEDPATH] resync id={args.Id} drift={drift:F1}m " +
|
||
$"mode={(hardReset ? "hard" : "soft")}");
|
||
|
||
var points = PedLaneGraph.PlanRoute(
|
||
args.From.X, args.From.Z, args.To.X, args.To.Z,
|
||
cfg.NpcPathMaxPoints, cfg.NpcPathMinMetres);
|
||
|
||
// ⛑️⛑️ 2026-09-23:规划不出路时**不要**回空路径 —— 空路径 = 告诉客户端"没有路" = 站着不动。
|
||
// 实测 376 次 points=0 里有 335 次是 from≈to(spawn 后第一个包就是 From==To;
|
||
// 卡在花坛 / 门框里的单位也只会这样问)。给一个最近车道上的"脱困点",它就能起步 / 挪出来。
|
||
var escaped = false;
|
||
if (points is null && cfg.NpcPathEscapeEnabled &&
|
||
PedLaneGraph.TryPickEscape(args.From.X, args.From.Z, cfg.NpcPathEscapeMetres,
|
||
out var escX, out var escY, out var escZ))
|
||
{
|
||
points = [(escX, escY, escZ)];
|
||
escaped = true;
|
||
}
|
||
|
||
var result = new SceneMethods.FindPathResult4229938();
|
||
if (points is not null)
|
||
{
|
||
foreach (var p in points)
|
||
{
|
||
result.Points.Add(new SceneMethods.UxVector3(p.X, p.Y, p.Z));
|
||
result.Flags.Add(0);
|
||
}
|
||
}
|
||
|
||
msg.Context.Session?.Log.Info($"[PEDPATH] id={args.Id} " +
|
||
$"from=({args.From.X:F1},{args.From.Z:F1}) to=({args.To.X:F1},{args.To.Z:F1}) " +
|
||
$"points={result.Points.Count}{(escaped ? " (escape)" : "")}");
|
||
|
||
return conn.ReturnAsync(msg, result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// `AskAetherNpcUpdatePathForObstacle`(67228857) —— 行人撞墙求助。
|
||
///
|
||
/// 这一步先只做「接住 + 打日志」:donor 整场只来 1 次,我们要先确认我们这里到底来不来、
|
||
/// entityId 是不是我们发出去的那些行人。确认之后再给重规划(沿路网避开障碍点重发一条
|
||
/// TaskWayPointMove)—— 现在盲写重规划,等于拿没验证过的触发条件去改走路逻辑。
|
||
/// ⛑️ 不回执会让客户端一直等 ⇒ 无论如何都回一个空 OK。
|
||
/// </summary>
|
||
[Handler(MethodId.AskAetherNpcUpdatePathForObstacle, HandlerPacketKind.Invoke)]
|
||
private Task AskAetherNpcUpdatePathForObstacle(Connection conn, UxRpcMessage msg)
|
||
{
|
||
try
|
||
{
|
||
var args = msg.GetArgs<SceneMethods.AskAetherNpcUpdatePathForObstacle4229938>();
|
||
msg.Context.Session?.Log.Info($"[PEDPATH] OBSTACLE entity={args.EntityId} " +
|
||
$"at=({args.Point.X:F1},{args.Point.Y:F1},{args.Point.Z:F1}) r={args.Radius:F2}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
msg.Context.Session?.Log.Warn($"[PEDPATH] OBSTACLE args decode failed: {ex.Message}");
|
||
}
|
||
return conn.ReturnEmptyOkAsync(msg);
|
||
}
|
||
}
|