yuanshen/Yuanshen.Gameserver/Controllers/GadgetController.cs
wisedev a4b3b669a8 Use the named retcode and reason constants
Half the handlers were still answering with a bare 0 or -1 even though the whole
retcode table is generated. Values on the wire are unchanged, they just read
properly now.

Same for the item hint reasons. ActionReasonType is a generated enum and we were
redeclaring six of its members as private consts in four different files.
2026-09-04 11:21:12 +03:00

184 lines
7.1 KiB
C#

using Yuanshen.Common.Data.Excel;
using Yuanshen.Gameserver.Controllers.Attributes;
using Yuanshen.Gameserver.Controllers.Result;
using Yuanshen.Gameserver.Database;
using Yuanshen.Gameserver.Game;
using Yuanshen.Gameserver.Network.Session;
using Yuanshen.Protocol;
using Microsoft.Extensions.Logging;
namespace Yuanshen.Gameserver.Controllers;
[NetController]
internal sealed class GadgetController : ControllerBase
{
private const uint ChestOpenedState = 102;
private const uint MoraItemId = 202;
private const uint PrimogemItemId = 201;
private static readonly Random Random = new();
[NetCommand(CmdType.GadgetInteractReq)]
public async ValueTask<IResult> OnGadgetInteractReq(NetSession session, Player player, SceneManager scene,
DropTable drops, ExcelTableCollection excel, PlayerPersistence persistence, ILogger<GadgetController> logger)
{
GadgetInteractReq req = Packet!.DecodeBody<GadgetInteractReq>();
GameGadget? gadget = player.Gadgets.FirstOrDefault(candidate => candidate.EntityId == req.GadgetEntityId);
if (gadget is null)
return Reply(req, Retcode.RET_GADGET_NOT_EXIST);
if (gadget.IsChest)
return await OpenChestAsync(session, player, scene, drops, excel, persistence, logger, gadget, req);
return await GatherAsync(session, player, scene, drops, excel, persistence, logger, gadget, req);
}
private async ValueTask<IResult> OpenChestAsync(NetSession session, Player player, SceneManager scene,
DropTable drops, ExcelTableCollection excel, PlayerPersistence persistence, ILogger logger,
GameGadget gadget, GadgetInteractReq req)
{
if (req.OpType == InterOpType.InterOpStart)
{
if (player.OpenedGadgetIds.Contains(gadget.NodeId))
return Reply(req, Retcode.RET_CHEST_IS_LOCKED);
return Reply(req, Retcode.RET_SUCC, InteractType.InteractOpenChest);
}
if (!player.OpenedGadgetIds.Add(gadget.NodeId))
return Reply(req, Retcode.RET_CHEST_IS_LOCKED);
await session.NotifyAsync(CmdType.GadgetInteractRsp, Build(req, Retcode.RET_SUCC, InteractType.InteractOpenChest));
await scene.SetGadgetStateAsync(gadget, ChestOpenedState, false);
Dictionary<uint, uint> rolled = drops.Roll(gadget.Node.DropId, Random);
await GrantAsync(session, player, drops, excel, gadget, rolled, (uint)ActionReasonType.ActionReasonOpenChest);
await persistence.SaveAsync();
logger.LogInformation("Chest {gadgetId} node {node} opened, drop {dropId} gave {count} stacks",
gadget.GadgetId, gadget.NodeId, gadget.Node.DropId, rolled.Count);
return Ok();
}
private async ValueTask<IResult> GatherAsync(NetSession session, Player player, SceneManager scene,
DropTable drops, ExcelTableCollection excel, PlayerPersistence persistence, ILogger logger,
GameGadget gadget, GadgetInteractReq req)
{
if (req.OpType != InterOpType.InterOpStart)
return Reply(req, Retcode.RET_SUCC, InteractType.InteractGather);
if (!player.OpenedGadgetIds.Add(gadget.NodeId))
return Reply(req, Retcode.RET_GADGET_NOT_EXIST);
await session.NotifyAsync(CmdType.GadgetInteractRsp, Build(req, Retcode.RET_SUCC, InteractType.InteractGather));
Dictionary<uint, uint> rolled = new();
if (gadget.Node.SubfieldId != 0)
{
uint dropId = drops.SubfieldDropId(gadget.Node.SubfieldId, gadget.Node.Level);
if (dropId != 0)
rolled = drops.Roll(dropId, Random);
}
if (rolled.Count == 0 && gadget.Node.GatherItemId != 0)
rolled[gadget.Node.GatherItemId] = 1;
await GrantAsync(session, player, drops, excel, gadget, rolled, (uint)ActionReasonType.ActionReasonGather);
await scene.DespawnGadgetAsync(gadget, VisionType.VisionGatherEscape);
await persistence.SaveAsync();
logger.LogInformation("Gathered {gadgetId} node {node}, gave {count} stacks",
gadget.GadgetId, gadget.NodeId, rolled.Count);
return Ok();
}
private static async ValueTask GrantAsync(NetSession session, Player player, DropTable drops,
ExcelTableCollection excel, GameGadget gadget, Dictionary<uint, uint> rolled, uint reason)
{
if (rolled.Count == 0)
return;
StoreItemChangeNotify store = new() { StoreType = StoreType.StorePack };
ItemAddHintNotify hint = new()
{
Reason = reason,
Position = gadget.Node.Pos,
IsPositionValid = true
};
bool currencyChanged = false;
foreach ((uint itemId, uint count) in rolled)
{
switch (drops.TypeOf(itemId))
{
case "weapon":
for (uint made = 0; made < Math.Min(count, 10u); made++)
store.ItemList.Add(player.CreateWeapon(itemId).AsItem());
break;
case "virtual" when itemId == MoraItemId:
player.Scoin += count;
currencyChanged = true;
break;
case "virtual" when itemId == PrimogemItemId:
player.Hcoin += count;
currencyChanged = true;
break;
case "virtual":
continue;
case "reliquary":
for (uint made = 0; made < Math.Min(count, 5u); made++)
{
GameReliquary? reliquary = player.CreateReliquary(itemId, Random);
if (reliquary is not null)
store.ItemList.Add(reliquary.AsItem());
}
break;
default:
player.GiveMaterial(itemId, count);
store.ItemList.Add(new Item
{
ItemId = itemId,
Guid = player.MaterialGuid(itemId),
Material = new Material { Count = player.Materials.GetValueOrDefault(itemId) }
});
break;
}
hint.ItemList.Add(new ItemHint { ItemId = itemId, Count = count, IsNew = false });
}
if (hint.ItemList.Count == 0)
return;
if (store.ItemList.Count != 0)
await session.NotifyAsync(CmdType.StoreItemChangeNotify, store);
if (currencyChanged)
await session.NotifyAsync(CmdType.PlayerPropNotify, player.BuildCurrencyProps());
await session.NotifyAsync(CmdType.ItemAddHintNotify, hint);
}
private IResult Reply(GadgetInteractReq req, int retcode, InteractType interactType = InteractType.InteractNone)
=> Response(CmdType.GadgetInteractRsp, Build(req, retcode, interactType));
private static GadgetInteractRsp Build(GadgetInteractReq req, int retcode, InteractType interactType) => new()
{
Retcode = retcode,
GadgetEntityId = req.GadgetEntityId,
GadgetId = req.GadgetId,
InteractType = interactType,
OpType = req.OpType
};
}