feat: Upgrade server with HyNB v2.4 modular architecture, minigames, admin GM web panel, companion summons, and car radio
This commit is contained in:
1 parent
74e2963a12
commit
3e5adf1dc5
1571 files changed
+336468
-6348
No files matched your search
@@ -0,0 +1,104 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user