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 OnGadgetInteractReq(NetSession session, Player player, SceneManager scene, DropTable drops, ExcelTableCollection excel, PlayerPersistence persistence, ILogger logger) { GadgetInteractReq req = Packet!.DecodeBody(); 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 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 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 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 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 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 }; }