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,98 @@
|
||||
using Ananta.SDK.Rpc;
|
||||
using Ananta.Server.ClientData.Client4229938;
|
||||
using Ananta.Server.Configuration;
|
||||
using Ananta.Server.Protocol.Client4229938;
|
||||
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
|
||||
|
||||
namespace Ananta.Server.Handlers.Game;
|
||||
|
||||
/// <summary>
|
||||
/// NPC shop shelves for build 4229938 (ShopManager: AskNpcShop -> SyncFullCommodityInfos).
|
||||
///
|
||||
/// Why this file exists: a placed shop clerk opens its panel fine but the panel is empty. The
|
||||
/// client builds the rows from tables it already owns (ShopConfig / ShopCommodityGroupConfig /
|
||||
/// ShopCommodityConfig) and only waits for the server to say what is actually on the shelf —
|
||||
/// <c>ShopManager.lua</c> aborts with "获取不到服务端的商品数据" when
|
||||
/// <c>shopCommodityDataDict[shopId]</c> has no entry, which is exactly the empty panel. That
|
||||
/// dictionary is filled by nothing but SyncFullCommodityInfos (64769566).
|
||||
///
|
||||
/// The server is not asked to price or validate anything here: it restocks every row at its
|
||||
/// configured price with unlimited stock. Charging for a purchase is a separate step
|
||||
/// (AskBuyCommodity) and is deliberately not implemented yet.
|
||||
/// </summary>
|
||||
internal sealed partial class GameRouter
|
||||
{
|
||||
/// <summary>Solution Pawn — the shop standing 1.95 m from the 典斥屋 scene preset, used as the A0 pilot.</summary>
|
||||
private const uint PilotShopId = 94100024u;
|
||||
|
||||
[Handler(MethodId.AskNpcShop, HandlerPacketKind.Invoke)]
|
||||
private async Task AskNpcShop(Connection conn, UxRpcMessage msg)
|
||||
{
|
||||
var mode = PrivateServerConfigStore.Current.Gameplay.NpcShopPayloadMode;
|
||||
|
||||
// AskNpcShop is a void INVOKE the client awaits, so it must always be answered — a shop we
|
||||
// cannot restock still has to return OK, otherwise the clerk looks like a failed interaction.
|
||||
if (string.Equals(mode, "off", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
var shopId = msg.Body.Length >= 4 ? BitConverter.ToUInt32(msg.Body, 0) : 0u;
|
||||
if (shopId == 0)
|
||||
{
|
||||
conn.Log.Warn($"[NPCSHOP] AskNpcShop without a shop id (body={msg.Body.Length}B hex={Convert.ToHexString(msg.Body)})");
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(mode, "pilot", StringComparison.OrdinalIgnoreCase) && shopId != PilotShopId)
|
||||
{
|
||||
conn.Log.Info($"[NPCSHOP] skip shop={shopId} (pilot mode only serves {PilotShopId})");
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
var loadError = NpcShopCatalog4229938.LoadError;
|
||||
if (loadError is not null)
|
||||
{
|
||||
conn.Log.Warn($"[NPCSHOP] catalog unusable: {loadError}");
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NpcShopCatalog4229938.TryGetShop(shopId, out var shop))
|
||||
{
|
||||
conn.Log.Info($"[NPCSHOP] shop={shopId} is not in NpcShopCatalog.json ({NpcShopCatalog4229938.Count} shops known)");
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
var infos = new List<GameMethods.NpcShopCommodityInfo4229938>(shop.Commodities.Count);
|
||||
foreach (var commodity in shop.Commodities)
|
||||
{
|
||||
infos.Add(new GameMethods.NpcShopCommodityInfo4229938
|
||||
{
|
||||
TemplateId = commodity.TemplateId,
|
||||
// A positive stock keeps the row out of the sold-out state; LimitNum <= 0 means the
|
||||
// official row is unlimited, in which case there is no meaningful number to print.
|
||||
Count = commodity.LimitNum > 0 ? commodity.LimitNum : 9999,
|
||||
RefreshTime = 0,
|
||||
Status = 0,
|
||||
Discount = 100, // 100 = no discount; anything else flips the client's HasDiscount.
|
||||
DiscountPrice = commodity.Price,
|
||||
MaxBuyCount = 0, // 0 = the client leaves the stock figure alone.
|
||||
DailyDiscount = 0,
|
||||
CommodityBadgeDiscountDict = new Dictionary<uint, uint>(),
|
||||
BlackMarketPriceModifier = 0,
|
||||
});
|
||||
}
|
||||
|
||||
await conn.NotifyAsync(
|
||||
MethodId.IGameToClient_SyncFullCommodityInfos,
|
||||
new GameMethods.SyncFullCommodityInfosArgs4229938 { shopId = shopId, infos = infos });
|
||||
|
||||
conn.Log.Info($"[NPCSHOP] shop={shopId} '{shop.Name}' restocked {infos.Count} rows mode={mode}");
|
||||
await conn.ReturnEmptyOkAsync(msg);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user