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.
184 lines
7.1 KiB
C#
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
|
|
};
|
|
}
|