KCP gameserver with packet dispatch, scene streaming, combat, gacha and the shop, plus a small HTTP server for the login flow and the wish history page. Game data under assets is pulled out of the client instead of being typed by hand. The excel tables come from the packed bin container and the spawn points from the compiled scene scripts.
158 lines
6.4 KiB
C#
158 lines
6.4 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
|
|
{
|
|
private const uint ActionReasonShop = 4;
|
|
|
|
[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 = 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) }
|
|
};
|
|
}
|