105 lines
5.0 KiB
C#
105 lines
5.0 KiB
C#
using Ananta.SDK.Network;
|
|
using Ananta.SDK.Rpc;
|
|
using Ananta.Server.Protocol.Client4229938;
|
|
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
|
|
|
|
namespace Ananta.Server.Handlers.Game;
|
|
|
|
/// <summary>
|
|
/// EonBug / hacker phone app — the interactive half of the BBS.
|
|
///
|
|
/// The data half lives in <c>GameRouter.Profiles.cs</c> (SyncHackerJobInfo 64189208: handle, post
|
|
/// list, rank, daily counters). This file is what happens when the player actually types in the
|
|
/// app, and the whole reason it exists is one symptom: open 千年虫 (EonBug), type a name, and the
|
|
/// UI freezes in the dialog with no way out.
|
|
///
|
|
/// Root cause (matches the reference branch's own logs): the client keeps asking
|
|
/// AskChangeHackerName(63975191) and never gets an answer, so its local "this account has no handle
|
|
/// yet" state never clears and the dialog re-arms every couple of seconds. Our tree used to have no
|
|
/// handler registered for it at all, which is the same failure with extra steps — the request
|
|
/// landed, nothing answered it, and the client stayed stuck.
|
|
/// </summary>
|
|
internal sealed partial class GameRouter
|
|
{
|
|
/// <summary>Per-session EonBug handle. Survives character switches within one login.</summary>
|
|
const string HackerHandleKey = "hacker.handle";
|
|
|
|
/// <summary>The handle used when the player never picked one.</summary>
|
|
const string DefaultHackerHandle = "Ananta";
|
|
|
|
[Handler(MethodId.AskChangeHackerName, HandlerPacketKind.Invoke)]
|
|
private async Task AskChangeHackerName(Connection conn, UxRpcMessage msg)
|
|
{
|
|
var name = string.Empty;
|
|
try
|
|
{
|
|
name = msg.GetArgs<GameMethods.AskChangeHackerName4229938>()?.name ?? string.Empty;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Still answer: an unanswered INVOKE leaves the awaited dialog armed forever, which is
|
|
// the exact symptom this handler exists to remove. A parse failure only costs us the
|
|
// new handle, so fall back to whatever we already published.
|
|
msg.Context.Session.Log.Warn($"[HACK-JOB] AskChangeHackerName 解析失败(沿用旧代号): {ex.Message}");
|
|
}
|
|
|
|
name = name.Trim();
|
|
if (name.Length > 32)
|
|
name = name[..32];
|
|
|
|
// ⛑️ Always ack, even for an empty name — see the catch above. Without the empty OK the
|
|
// client's await never lands and the dialog stays up.
|
|
await conn.ReturnEmptyOkAsync(msg);
|
|
if (name.Length == 0)
|
|
{
|
|
msg.Context.Session.Log.Warn("[HACK-JOB] 收到空的黑客代号(忽略)");
|
|
return;
|
|
}
|
|
|
|
msg.Context.Session.Items[HackerHandleKey] = name;
|
|
msg.Context.Session.Log.Info($"[HACK-JOB] 黑客代号改为 '{name}' — 立即回推 SyncHackerJobInfo");
|
|
|
|
await RepublishHackerHandleAsync(msg.Context);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Re-push the BBS backbone with whatever handle is stored. Without this the change only exists
|
|
/// server-side: nothing tells the client, so <c>HackerSpiritInfo[cardId]</c> stays empty and the
|
|
/// app reopens as a new user — back to the same dialog.
|
|
/// </summary>
|
|
async Task RepublishHackerHandleAsync(RpcContext ctx)
|
|
{
|
|
var templateId = GetWorldState(ctx).ActiveSpiritTemplateId;
|
|
await PublishHackerJobRuntime4229938(ctx, templateId, "hacker-rename",
|
|
ctx.Session.Items.TryGetValue(HackerHandleKey, out var stored) ? stored as string : null);
|
|
}
|
|
|
|
/// <summary>The EonBug handle to publish: the one the player picked, else the default.</summary>
|
|
static string HackerHandle4229938(TcpSession session)
|
|
=> session.Items.TryGetValue(HackerHandleKey, out var stored) && stored is string s && s.Length > 0
|
|
? s
|
|
: DefaultHackerHandle;
|
|
|
|
/// <summary>
|
|
/// The two remaining EonBug forum actions: read a new post (63150736) and accept a job from one
|
|
/// (63036377). Both return void per <c>DefaultReturnCatalog4229938</c>, so an empty OK is the
|
|
/// whole contract.
|
|
///
|
|
/// ⛑ The point of routing them is not content — the forum's post list already rides
|
|
/// SyncHackerJobInfo (64189208) from <c>GameRouter.Profiles.cs</c>. Both are INVOKEs, so with no
|
|
/// handler the client awaits a reply that never comes: tapping a post or 接差事 leaves the UI
|
|
/// armed forever, the same failure mode AskChangeHackerName used to have.
|
|
///
|
|
/// Nothing is persisted: the client commits the read/accept locally, and there is no server-side
|
|
/// job simulation to advance.
|
|
/// </summary>
|
|
[Handler(MethodId.AskReadHackerNewPost, HandlerPacketKind.Invoke)]
|
|
[Handler(MethodId.AskAcceptHackerPostTask, HandlerPacketKind.Invoke)]
|
|
private async Task EonBugForumAction(Connection conn, UxRpcMessage msg)
|
|
{
|
|
var action = msg.MethodId == MethodId.AskAcceptHackerPostTask ? "accept-post-task" : "read-new-post";
|
|
conn.Log.Info($"[HACK-JOB] {action} bytes={msg.Body.Length}");
|
|
await conn.ReturnEmptyOkAsync(msg);
|
|
}
|
|
}
|