Files

83 lines
4.2 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 System.Buffers.Binary;
using Ananta.SDK.Rpc;
using Ananta.SDK.Serialization;
using Ananta.Server.Handlers;
using Ananta.Server.Protocol.Client4229938;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// 牛奶车(gadget 版)交互的服务端那一半。
///
/// 背景(2026-10-01 实机):用 <c>debugPlace</c> 探针把 <c>T_MilkCar</c>(pathId 331844513) /
/// <c>T_Milk_Drive_mcTemp</c>(530867541) 摆到玩家面前 6 m 后,客户端给出四个交互点:
/// 车侧面=调查日报 / 联系联络员 / 道具制作,车尾=回血。
///
/// 抓包结论:
/// - 车尾「回血」客户端会发 <c>AskDrinkMilk</c>(67579042, 0x04072CA2),body 8 字节
/// = gadget uniqueId(实测 <c>0x5000000000000001</c>,与 debugPlace 的 Seq 逐位吻合)。
/// - 侧面三个里「调查日报 / 道具制作」客户端<b>一个 RPC 都不发</b> ⇒ 纯客户端本地演出,
/// 服务端无从介入(调查日报点下去还会卡在读取演出界面,属客户端 bug,重连即可解除)。
/// - 「联系联络员」走的是通用对话线 <c>AskStartDialog</c>(63754798),已在
/// <c>GameRouter.Dialog.cs</c> 里回 0u,不需要在这里重复。
///
/// 所以本文件只做一件事:接住 AskDrinkMilk 并把血回满。
///
/// 回法沿用 <c>GameAdminBridge.RestorePlayerHealthAsync</c> 的既定顺序(硬规则 52):
/// <b>先属性表(SyncUnitAttrs,带 MaxHp 分母)再绝对值(SyncUnitHp)</b>;同时把
/// <c>state.PlayerHp</c> 这本账一起改掉,否则下一次受击会从旧值结算、立刻又把人打死。
///
/// ⛑ 回滚:把 <c>GameRouter.cs</c> 白名单里的 <c>MethodId.AskDrinkMilk</c> 那一行删掉即可
/// (handler 不注册 ⇒ 回落到 typed-default 兜底,点回血只剩演出、不回血)。
/// </summary>
internal sealed partial class GameRouter
{
/// <summary>
/// 官方解锁链:徽章 <c>19000150「牛奶车解锁回血」→ Buffs:[52606182]</c>,
/// 52606182 = BuffConfig「解锁牛奶车回血功能」,Duration -1(永久)。
/// 目前<b>只记录不下发</b>:客户端并没有因为缺这个 buff 就拦住 AskDrinkMilk(实机照发),
/// 所以这里不发也不会影响可用性;将来要还原「先解锁才能回血」的门槛时,
/// 就在这里读 <c>state</c> 判定并走失败分支。
/// </summary>
private const uint MilkCarUnlockBuffId = 52606182u;
/// <summary>
/// `IGameSceneToClient.AskDrinkMilk(ulong instanceId)` — 67579042 (0x04072CA2, dump.cs:1730509)。
/// 返回 `UXRPCTask`(void)⇒ 回空 OK。请求体实测 8 字节 = 牛奶车 gadget 的 uniqueId。
/// ⛑⛑ 必须<b>先回包再推血</b>:客户端 await 的是这个 void,先推血会让回包晚一拍。
/// </summary>
[Handler(MethodId.AskDrinkMilk, HandlerPacketKind.Invoke)]
private async Task AskDrinkMilk(Connection conn, UxRpcMessage msg)
{
var body = msg.Body.ToArray();
ulong instanceId = body.Length >= 8 ? BinaryPrimitives.ReadUInt64LittleEndian(body) : 0UL;
conn.Log.Info($"[MILKCAR] drink uid=0x{instanceId:X} bytes={body.Length} hex={Convert.ToHexString(body)}");
await conn.ReturnEmptyOkAsync(msg);
var state = GetStateIfExists(conn.Session);
if (state is null)
{
conn.Log.Warn($"[MILKCAR] heal-skipped reason=no-world-state uid=0x{instanceId:X}");
return;
}
ulong unit;
lock (state.SyncRoot)
{
state.PlayerHp = CombatCodec.MaxHp;
state.PlayerDowned = false;
unit = state.ActiveSpiritUnitId != 0 ? state.ActiveSpiritUnitId : Profile.InitialUnitId;
}
// 硬规则 52:属性表(MaxHp 分母)在前,绝对值在后。
await conn.Session.NotifyAsync(MethodId.SyncUnitAttrs,
UxSerializer.Serialize(CombatCodec.UnitAttrs(unit, CombatCodec.MaxHp)), CancellationToken.None);
await conn.Session.NotifyAsync(MethodId.SyncUnitHp,
UxSerializer.Serialize(CombatCodec.UnitHp(unit, CombatCodec.MaxHp)), CancellationToken.None);
conn.Log.Info($"[MILKCAR] heal unit={unit} hp={CombatCodec.MaxHp:F0} from=uid0x{instanceId:X}");
}
}