yuanshen/Yuanshen.Gameserver/Controllers/ShopController.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

157 lines
6.3 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 ShopController : ControllerBase
{
[NetCommand(CmdType.GetShopmallDataReq)]
public ValueTask<IResult> OnGetShopmallDataReq(ExcelTableCollection excel)
{
GetShopmallDataRsp rsp = new() { Retcode = Retcode.RET_SUCC };
foreach (ShopmallEntranceExcel entrance in excel.GetTable(ExcelType.ShopmallEntrance)
.Items.OfType<ShopmallEntranceExcel>().OrderBy(entrance => entrance.Order))
{
rsp.ShopTypeList.Add(entrance.ShopType);
}
return ValueTask.FromResult(Response(CmdType.GetShopmallDataRsp, rsp));
}
[NetCommand(CmdType.GetShopReq)]
public ValueTask<IResult> OnGetShopReq(Player player, ExcelTableCollection excel)
{
GetShopReq req = Packet!.DecodeBody<GetShopReq>();
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<IResult> OnBuyGoodsReq(NetSession session, Player player, ExcelTableCollection excel,
PlayerPersistence persistence, ILogger<ShopController> logger)
{
BuyGoodsReq req = Packet!.DecodeBody<BuyGoodsReq>();
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<WeaponExcel>().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 = (uint)ActionReasonType.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<ShopGoodsExcel> Goods(ExcelTableCollection excel)
=> excel.GetTable(ExcelType.ShopGoods).Items.OfType<ShopGoodsExcel>();
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) }
};
}