feat: Upgrade server with HyNB v2.4 modular architecture, minigames, admin GM web panel, companion summons, and car radio

This commit is contained in:
CAtlas0784 committed 2026-10-29 02:44:24 +07:00
1 parent 74e2963a12
commit 3e5adf1dc5
1571 files changed
+336468 -6348

No files matched your search

@@ -8,17 +8,12 @@ namespace Ananta.Server.Handlers.Game;
/// <summary>
/// Time + weather. The client runs its clock/atmosphere locally; these handlers make
/// the in-game time UI work (accept + remember) and the debug panel can force it:
/// time via SyncPlayerCurrentTime (proto-verified shape). Stored per session,
/// re-pushed after every load.
/// the in-game time UI work (accept + remember) and the admin panel can force both:
/// time via SyncPlayerCurrentTime (proto-verified shape), weather via SyncPlayerWeather
/// (lua-reader-verified: 3× u32). Stored per session, re-pushed after every load.
/// </summary>
internal sealed partial class GameRouter
{
internal static WorldEntryState? GetStateIfExists(TcpSession session)
=> session.Items.TryGetValue(WorldStateKey, out var raw) && raw is WorldEntryState existing
? existing
: null;
private static void SetSessionTime(WorldEntryState state, uint hour, uint minute, bool fix, uint transition)
{
lock (state.SyncRoot)
@@ -28,6 +23,8 @@ internal sealed partial class GameRouter
state.TimeFixed = fix;
state.TimeTransitionSeconds = transition;
state.HasExplicitTime = true;
// ★ 玩家(或 GM/面板)主动推了一把时间 —— 记一次,供「赖在原地的角色该去过日子了」判据用。
state.TimeEpoch++;
}
}
@@ -129,7 +126,11 @@ internal sealed partial class GameRouter
if (args.clear)
{
lock (state.SyncRoot)
{
state.HasExplicitTime = false;
// 「解除固定」同样是玩家主动改时间(回到实时),也要记一次。
state.TimeEpoch++;
}
conn.Log.Info("[TIME] GmFixRaidTime clear");
}
else
@@ -142,115 +143,19 @@ internal sealed partial class GameRouter
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskTimePanelInfo)]
private async Task AskTimePanelInfo(Connection conn, UxRpcMessage msg)
{
var state = GetWorldState(msg.Context);
GameMethods.TimePanelInfo4229938 response;
lock (state.SyncRoot)
{
response = new GameMethods.TimePanelInfo4229938
{
PersonalTimeSettings = new List<GameMethods.PersonalTimeSetting4229938>(state.PersonalTimeSettings)
};
}
conn.Log.Info($"[TIME] AskTimePanelInfo: returning {response.PersonalTimeSettings.Count} personal time slots");
await conn.ReturnAsync(msg, response);
}
[Handler(MethodId.ChangePersonalTimeSetting)]
private async Task ChangePersonalTimeSetting(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.ChangePersonalTimeSettingArgs4229938>(out var args) && args is not null)
{
var state = GetWorldState(msg.Context);
lock (state.SyncRoot)
{
if (args.info is null)
{
if (args.index >= 0 && args.index < state.PersonalTimeSettings.Count)
{
state.PersonalTimeSettings.RemoveAt(args.index);
conn.Log.Info($"[TIME] personal delete slot={args.index}");
}
}
else
{
if (args.index >= 0 && args.index < state.PersonalTimeSettings.Count)
{
state.PersonalTimeSettings[args.index] = args.info;
conn.Log.Info($"[TIME] personal update slot={args.index} label={args.info.Label} {args.info.Hour:D2}:{args.info.Minute:D2}");
}
else
{
state.PersonalTimeSettings.Add(args.info);
conn.Log.Info($"[TIME] personal append slot={args.index} label={args.info.Label} {args.info.Hour:D2}:{args.info.Minute:D2}");
}
}
}
}
conn.Log.Info($"[TIME] personal slot={args.index} label={args.info?.Label} {args.info?.Hour:D2}:{args.info?.Minute:D2}");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AddPersonalTimeSetting)]
private async Task AddPersonalTimeSetting(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AddPersonalTimeSettingArgs4229938>(out var args) && args?.info is not null)
{
var state = GetWorldState(msg.Context);
lock (state.SyncRoot)
{
state.PersonalTimeSettings.Add(args.info);
}
conn.Log.Info($"[TIME] personal add label={args.info.Label} {args.info.Hour:D2}:{args.info.Minute:D2}");
}
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskSetMobileSkinPart)]
private async Task AskSetMobileSkinPart(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskSetMobileSkinPartArgs4229938>(out var args) && args is not null)
{
var state = GetWorldState(msg.Context);
lock (state.SyncRoot)
{
state.MobileWallpaperId = args.wallpaper;
state.MobileDecorationId = args.decoration;
state.MobilePendantId = args.pendant;
}
conn.Log.Info($"[PHONE] AskSetMobileSkinPart wallpaper={args.wallpaper} decoration={args.decoration} pendant={args.pendant}");
}
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskResetMobileSkinPart)]
private async Task AskResetMobileSkinPart(Connection conn, UxRpcMessage msg)
{
var state = GetWorldState(msg.Context);
lock (state.SyncRoot)
{
state.MobileWallpaperId = 0;
state.MobileDecorationId = 0;
state.MobilePendantId = 0;
}
conn.Log.Info("[PHONE] AskResetMobileSkinPart reset to defaults");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskInstallMobileApp)]
private async Task AskInstallMobileApp(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskInstallMobileAppArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[PHONE] AskInstallMobileApp appId={args.appId}");
await conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.AskCancelInteractionActionRedPoint)]
private async Task AskCancelInteractionActionRedPoint(Connection conn, UxRpcMessage msg)
{
if (msg.TryGetArgs<GameMethods.AskCancelInteractionActionRedPointArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[ACTION] AskCancelInteractionActionRedPoint actionId={args.actionId}");
if (msg.TryGetArgs<GameMethods.ChangePersonalTimeSettingArgs4229938>(out var args) && args is not null)
conn.Log.Info($"[TIME] personal add slot={args.index} label={args.info?.Label}");
await conn.ReturnEmptyOkAsync(msg);
}