Files

121 lines
5.8 KiB
C#
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.
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&lt;UXVector3&gt;` + `Flags:list7&lt;byte&gt;`
/// ③ 兜底字节 `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);
}
}