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 ShopController : ControllerBase { private const uint ActionReasonShop = 4; [NetCommand(CmdType.GetShopmallDataReq)] public ValueTask OnGetShopmallDataReq(ExcelTableCollection excel) { GetShopmallDataRsp rsp = new() { Retcode = Retcode.RET_SUCC }; foreach (ShopmallEntranceExcel entrance in excel.GetTable(ExcelType.ShopmallEntrance) .Items.OfType().OrderBy(entrance => entrance.Order)) { rsp.ShopTypeList.Add(entrance.ShopType); } return ValueTask.FromResult(Response(CmdType.GetShopmallDataRsp, rsp)); } [NetCommand(CmdType.GetShopReq)] public ValueTask OnGetShopReq(Player player, ExcelTableCollection excel) { GetShopReq req = Packet!.DecodeBody(); Shop shop = new() { ShopType = req.ShopType }; foreach (ShopGoodsExcel goods in Goods(excel).Where(goods => goods.ShopType == req.ShopType) .OrderBy(goods => goods.SortLevel).ThenBy(goods => goods.GoodsId)) { shop.GoodsList.Add(BuildGoods(player, goods)); } return ValueTask.FromResult(Response(CmdType.GetShopRsp, new GetShopRsp { Retcode = Retcode.RET_SUCC, Shop = shop })); } [NetCommand(CmdType.BuyGoodsReq)] public async ValueTask OnBuyGoodsReq(NetSession session, Player player, ExcelTableCollection excel, PlayerPersistence persistence, ILogger logger) { BuyGoodsReq req = Packet!.DecodeBody(); uint goodsId = req.Goods?.GoodsId ?? 0; uint count = Math.Max(req.BuyCount, 1u); ShopGoodsExcel? goods = Goods(excel).FirstOrDefault(candidate => candidate.GoodsId == goodsId); if (goods is null) return Response(CmdType.BuyGoodsRsp, new BuyGoodsRsp { Retcode = Retcode.RET_SHOP_CONTENT_NOT_MATCH, ShopType = req.ShopType }); uint bought = player.BoughtGoods.GetValueOrDefault(goodsId); if (goods.BuyLimit != 0 && bought + count > goods.BuyLimit) return Response(CmdType.BuyGoodsRsp, new BuyGoodsRsp { Retcode = Retcode.RET_SHOP_CONTENT_NOT_MATCH, ShopType = req.ShopType }); if (player.Hcoin < goods.CostHcoin * count || player.Scoin < goods.CostScoin * count) { logger.LogInformation("Shop refused {goods}: not enough currency", goodsId); return Response(CmdType.BuyGoodsRsp, new BuyGoodsRsp { Retcode = Retcode.RET_SHOP_CONTENT_NOT_MATCH, ShopType = req.ShopType }); } foreach (ShopCostItem cost in goods.CostItems.Where(cost => cost.Id != 0)) { if (player.Materials.GetValueOrDefault(cost.Id) < cost.Count * count) { logger.LogInformation("Shop refused {goods}: need {n}x {item}", goodsId, cost.Count * count, cost.Id); return Response(CmdType.BuyGoodsRsp, new BuyGoodsRsp { Retcode = Retcode.RET_SHOP_CONTENT_NOT_MATCH, ShopType = req.ShopType }); } } player.Hcoin -= goods.CostHcoin * count; player.Scoin -= goods.CostScoin * count; StoreItemChangeNotify store = new() { StoreType = StoreType.StorePack }; foreach (ShopCostItem cost in goods.CostItems.Where(cost => cost.Id != 0)) { player.TakeMaterial(cost.Id, cost.Count * count); store.ItemList.Add(BuildMaterial(player, cost.Id)); } uint gained = goods.ItemCount * count; bool isWeapon = excel.GetTable(ExcelType.Weapon).Items.OfType().Any(weapon => weapon.Id == goods.ItemId); if (isWeapon) { for (uint made = 0; made < Math.Min(gained, 50u); made++) store.ItemList.Add(player.CreateWeapon(goods.ItemId).AsItem()); } else { player.GiveMaterial(goods.ItemId, gained); store.ItemList.Add(BuildMaterial(player, goods.ItemId)); } player.BoughtGoods[goodsId] = bought + count; await session.NotifyAsync(CmdType.StoreItemChangeNotify, store); await session.NotifyAsync(CmdType.PlayerPropNotify, player.BuildCurrencyProps()); ItemAddHintNotify hint = new() { Reason = ActionReasonShop }; hint.ItemList.Add(new ItemHint { ItemId = goods.ItemId, Count = gained, IsNew = false }); await session.NotifyAsync(CmdType.ItemAddHintNotify, hint); await persistence.SaveAsync(); logger.LogInformation("Shop {shop}: bought {count}x goods {goods} -> {n}x item {item}", req.ShopType, count, goodsId, gained, goods.ItemId); return Response(CmdType.BuyGoodsRsp, new BuyGoodsRsp { Retcode = Retcode.RET_SUCC, ShopType = req.ShopType, BuyCount = count, Goods = BuildGoods(player, goods) }); } private static IEnumerable Goods(ExcelTableCollection excel) => excel.GetTable(ExcelType.ShopGoods).Items.OfType(); private static ShopGoods BuildGoods(Player player, ShopGoodsExcel goods) { ShopGoods result = new() { GoodsId = goods.GoodsId, GoodsItem = new ItemParam { ItemId = goods.ItemId, Count = goods.ItemCount }, Scoin = goods.CostScoin, Hcoin = goods.CostHcoin, BoughtNum = player.BoughtGoods.GetValueOrDefault(goods.GoodsId), BuyLimit = goods.BuyLimit, MinLevel = goods.MinPlayerLevel, MaxLevel = goods.MaxPlayerLevel == 0 ? 99u : goods.MaxPlayerLevel, BeginTime = 0, EndTime = uint.MaxValue }; foreach (ShopCostItem cost in goods.CostItems.Where(cost => cost.Id != 0)) result.CostItemList.Add(new ItemParam { ItemId = cost.Id, Count = cost.Count }); return result; } private static Item BuildMaterial(Player player, uint itemId) => new() { ItemId = itemId, Guid = player.MaterialGuid(itemId), Material = new Material { Count = player.Materials.GetValueOrDefault(itemId) } }; }