From 6dfec4ca0b8069dabda29a2780533a90edbbe8e8 Mon Sep 17 00:00:00 2001 From: StopWuyu Date: Sun, 15 Dec 2024 13:40:39 +0800 Subject: [PATCH] feat: offering system --- Common/Data/Excel/OfferingLevelConfigExcel.cs | 22 +++ Common/Data/Excel/OfferingTypeConfigExcel.cs | 23 +++ Common/Data/GameData.cs | 7 + Common/Database/Inventory/OfferingData.cs | 37 +++++ GameServer/Game/Inventory/InventoryManager.cs | 5 +- GameServer/Game/Inventory/OfferingManager.cs | 140 ++++++++++++++++++ GameServer/Game/Player/PlayerInstance.cs | 6 + .../Offering/HandlerGetOfferingInfoCsReq.cs | 20 +++ .../HandlerSubmitOfferingItemCsReq.cs | 18 +++ .../HandlerTakeOfferingRewardCsReq.cs | 18 +++ .../Offering/PacketGetOfferingInfoScRsp.cs | 18 +++ .../Offering/PacketOfferingInfoScNotify.cs | 18 +++ .../Offering/PacketSubmitOfferingItemScRsp.cs | 23 +++ .../Offering/PacketTakeOfferingRewardScRsp.cs | 27 ++++ 14 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 Common/Data/Excel/OfferingLevelConfigExcel.cs create mode 100644 Common/Data/Excel/OfferingTypeConfigExcel.cs create mode 100644 Common/Database/Inventory/OfferingData.cs create mode 100644 GameServer/Game/Inventory/OfferingManager.cs create mode 100644 GameServer/Server/Packet/Recv/Offering/HandlerGetOfferingInfoCsReq.cs create mode 100644 GameServer/Server/Packet/Recv/Offering/HandlerSubmitOfferingItemCsReq.cs create mode 100644 GameServer/Server/Packet/Recv/Offering/HandlerTakeOfferingRewardCsReq.cs create mode 100644 GameServer/Server/Packet/Send/Offering/PacketGetOfferingInfoScRsp.cs create mode 100644 GameServer/Server/Packet/Send/Offering/PacketOfferingInfoScNotify.cs create mode 100644 GameServer/Server/Packet/Send/Offering/PacketSubmitOfferingItemScRsp.cs create mode 100644 GameServer/Server/Packet/Send/Offering/PacketTakeOfferingRewardScRsp.cs diff --git a/Common/Data/Excel/OfferingLevelConfigExcel.cs b/Common/Data/Excel/OfferingLevelConfigExcel.cs new file mode 100644 index 00000000..5a00bd6a --- /dev/null +++ b/Common/Data/Excel/OfferingLevelConfigExcel.cs @@ -0,0 +1,22 @@ +namespace EggLink.DanhengServer.Data.Excel; + +[ResourceEntity("OfferingLevelConfig.json")] +public class OfferingLevelConfigExcel : ExcelResource +{ + public int ItemCost { get; set; } + public int Level { get; set; } + public int RewardID { get; set; } + public int TypeID { get; set; } + public int UnlockID { get; set; } + + public override int GetId() + { + return TypeID * 1000 + Level; + } + + public override void Loaded() + { + GameData.OfferingLevelConfigData.TryAdd(TypeID, []); + GameData.OfferingLevelConfigData[TypeID].Add(Level, this); + } +} \ No newline at end of file diff --git a/Common/Data/Excel/OfferingTypeConfigExcel.cs b/Common/Data/Excel/OfferingTypeConfigExcel.cs new file mode 100644 index 00000000..2e646f61 --- /dev/null +++ b/Common/Data/Excel/OfferingTypeConfigExcel.cs @@ -0,0 +1,23 @@ +namespace EggLink.DanhengServer.Data.Excel; + +[ResourceEntity("OfferingTypeConfig.json")] +public class OfferingTypeConfigExcel : ExcelResource +{ + public int MaxLevel { get; set; } + public int ItemID { get; set; } + public int ActivityModuleID { get; set; } + public int LongTailLimit { get; set; } + public int ID { get; set; } + public int UnlockID { get; set; } + public bool IsAutoOffer { get; set; } + + public override int GetId() + { + return ID; + } + + public override void Loaded() + { + GameData.OfferingTypeConfigData.TryAdd(ID, this); + } +} \ No newline at end of file diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index f5fcd309..ddba0149 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -111,6 +111,13 @@ public static class GameData #endregion + #region Offering + + public static Dictionary OfferingTypeConfigData { get; private set; } = []; + public static Dictionary> OfferingLevelConfigData { get; private set; } = []; + + #endregion + #region Maze public static Dictionary NpcDataData { get; private set; } = []; diff --git a/Common/Database/Inventory/OfferingData.cs b/Common/Database/Inventory/OfferingData.cs new file mode 100644 index 00000000..d753cc52 --- /dev/null +++ b/Common/Database/Inventory/OfferingData.cs @@ -0,0 +1,37 @@ +using EggLink.DanhengServer.Data; +using EggLink.DanhengServer.Data.Excel; +using EggLink.DanhengServer.Proto; +using SqlSugar; + +namespace EggLink.DanhengServer.Database.Inventory; + +[SugarTable("offering_data")] +public class OfferingData : BaseDatabaseDataHelper +{ + [SugarColumn(IsJson = true, ColumnDataType = "TEXT")] + public Dictionary Offerings { get; set; } = []; +} + +public class OfferingTypeData +{ + public OfferingState State { get; set; } = OfferingState.Open; + public int CurExp { get; set; } + public int OfferingId { get; set; } + public int Level { get; set; } + public List TakenReward { get; set; } = []; + + public OfferingInfo ToProto() + { + var totalExp = CurExp + Enumerable.Range(1, Level).Select(level => GameData.OfferingLevelConfigData.GetValueOrDefault(OfferingId)?.GetValueOrDefault(level)).OfType().Sum(config => config.ItemCost); + + return new OfferingInfo + { + OfferingState = State, + HasTakenRewardIdList = { TakenReward.Select(x => (uint)x) }, + LevelExp = (uint)CurExp, + OfferingId = (uint)OfferingId, + OfferingLevel = (uint)Level, + TotalExp = (uint)totalExp + }; + } +} \ No newline at end of file diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index 4569c212..247ab738 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -414,7 +414,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) } } - public async ValueTask> HandleReward(int rewardId, bool notify = false) + public async ValueTask> HandleReward(int rewardId, bool notify = false, bool sync = true) { GameData.RewardDataData.TryGetValue(rewardId, out var rewardData); if (rewardData == null) return []; @@ -426,7 +426,8 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) if (i != null) items.Add(i); } - await Player.SendPacket(new PacketPlayerSyncScNotify(items)); + if (sync) + await Player.SendPacket(new PacketPlayerSyncScNotify(items)); var hCoin = await AddItem(1, rewardData.Hcoin, notify, sync: false); if (hCoin != null) diff --git a/GameServer/Game/Inventory/OfferingManager.cs b/GameServer/Game/Inventory/OfferingManager.cs new file mode 100644 index 00000000..e6b424c2 --- /dev/null +++ b/GameServer/Game/Inventory/OfferingManager.cs @@ -0,0 +1,140 @@ +using EggLink.DanhengServer.Data; +using EggLink.DanhengServer.Database; +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.GameServer.Game.Player; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Offering; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.PlayerSync; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Game.Inventory; + +public class OfferingManager(PlayerInstance player) : BasePlayerManager(player) +{ + public OfferingData Data = DatabaseHelper.Instance!.GetInstanceOrCreateNew(player.Uid); + + public OfferingTypeData? GetOfferingData(int offeringId) + { + if (Data.Offerings.TryGetValue(offeringId, out var offeringData)) + { + return offeringData; + } + + var gameData = GameData.OfferingTypeConfigData.GetValueOrDefault(offeringId); // create a new one + if (gameData == null) return null; + + var unlockId = gameData.UnlockID; + var data = new OfferingTypeData + { + OfferingId = offeringId, + State = OfferingState.Lock + }; + + if (Player.QuestManager!.UnlockHandler.GetUnlockStatus(unlockId)) + { + data.State = OfferingState.Open; + } + + Data.Offerings[offeringId] = data; + + return data; + } + + public async ValueTask UpdateOfferingData() + { + List syncData = []; + foreach (var offering in Data.Offerings.Values) + { + var gameData = GameData.OfferingTypeConfigData.GetValueOrDefault(offering.OfferingId); // create a new one + if (gameData == null) continue; + + if (Player.QuestManager!.UnlockHandler.GetUnlockStatus(gameData.UnlockID) && + offering.State != OfferingState.Open) + { + offering.State = OfferingState.Open; + syncData.Add(offering); + continue; + } + + if (Player.QuestManager!.UnlockHandler.GetUnlockStatus(gameData.UnlockID) || + offering.State == OfferingState.Lock) continue; + + offering.State = OfferingState.Lock; + syncData.Add(offering); + } + + foreach (var data in syncData) + { + await Player.SendPacket(new PacketOfferingInfoScNotify(data)); + } + } + + public async ValueTask<(Retcode, OfferingTypeData? data)> SubmitOfferingItem(int offeringId) + { + var offering = GetOfferingData(offeringId); + if (offering is not { State: OfferingState.Open }) return (Retcode.RetOfferingNotUnlock, null); + + var gameData = GameData.OfferingTypeConfigData.GetValueOrDefault(offeringId); + if (gameData == null) return (Retcode.RetOfferingNotUnlock, null); + + if (offering.Level >= gameData.MaxLevel) return (Retcode.RetOfferingReachMaxLevel, offering); + + var item = Player.InventoryManager!.GetItem(gameData.ItemID); + if (item is not { Count: >= 1 }) return (Retcode.RetOfferingItemNotEnough, offering); + + var exp = item.Count; + while (true) + { + if (offering.Level >= gameData.MaxLevel) break; + var config = GameData.OfferingLevelConfigData.GetValueOrDefault(offeringId)?.GetValueOrDefault(offering.Level + 1); + if (config == null) break; + + if (exp + offering.CurExp < config.ItemCost) + { + offering.CurExp += exp; + exp = 0; + break; + } + + exp -= config.ItemCost - offering.CurExp; + offering.Level++; + offering.CurExp = 0; + } + + await Player.InventoryManager!.RemoveItem(item.ItemId, item.Count - exp); + + return (Retcode.RetSucc, offering); + } + + public async ValueTask<(Retcode, OfferingTypeData? data, List reward)> TakeOfferingReward(int offeringId, List takeList) + { + var offering = GetOfferingData(offeringId); + if (offering is not { State: OfferingState.Open }) return (Retcode.RetOfferingNotUnlock, null, []); + + var gameData = GameData.OfferingTypeConfigData.GetValueOrDefault(offeringId); + if (gameData == null) return (Retcode.RetOfferingNotUnlock, offering, []); + + List rewardIdList = []; + foreach (var excel in takeList.Select(take => GameData.OfferingLevelConfigData.GetValueOrDefault(offeringId)?.GetValueOrDefault(take))) + { + if (excel != null && excel.Level <= offering.Level && !offering.TakenReward.Contains(excel.Level)) + { + rewardIdList.Add(excel.RewardID); + } + else + { + return (Retcode.RetOfferingLevelNotUnlock, offering, []); + } + } + + offering.TakenReward.AddRange(takeList); + List reward = []; + foreach (var id in rewardIdList) + { + reward.AddRange(await Player.InventoryManager!.HandleReward(id, sync:false)); + } + + await Player.SendPacket(new PacketPlayerSyncScNotify(reward)); + + return (Retcode.RetSucc, offering, reward); + } +} \ No newline at end of file diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index 096c3f4a..eb3965c2 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -41,6 +41,7 @@ using EggLink.DanhengServer.Kcp; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Util; using static EggLink.DanhengServer.GameServer.Plugin.Event.PluginEvent; +using OfferingManager = EggLink.DanhengServer.GameServer.Game.Inventory.OfferingManager; namespace EggLink.DanhengServer.GameServer.Game.Player; @@ -62,6 +63,7 @@ public class PlayerInstance(PlayerData data) public GachaManager? GachaManager { get; private set; } public ShopService? ShopService { get; private set; } + public OfferingManager? OfferingManager { get; private set; } #endregion @@ -184,6 +186,7 @@ public class PlayerInstance(PlayerData data) QuestManager = new QuestManager(this); TrainPartyManager = new TrainPartyManager(this); MatchThreeManager = new MatchThreeManager(this); + OfferingManager = new OfferingManager(this); PlayerUnlockData = InitializeDatabase(); SceneData = InitializeDatabase(); @@ -416,6 +419,9 @@ public class PlayerInstance(PlayerData data) if (SceneInstance != null) await SceneInstance.OnHeartBeat(); + if (OfferingManager != null) + await OfferingManager.UpdateOfferingData(); + DatabaseHelper.ToSaveUidList.SafeAdd(Uid); } diff --git a/GameServer/Server/Packet/Recv/Offering/HandlerGetOfferingInfoCsReq.cs b/GameServer/Server/Packet/Recv/Offering/HandlerGetOfferingInfoCsReq.cs new file mode 100644 index 00000000..1c760995 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Offering/HandlerGetOfferingInfoCsReq.cs @@ -0,0 +1,20 @@ +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Offering; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Offering; + +[Opcode(CmdIds.GetOfferingInfoCsReq)] +public class HandlerGetOfferingInfoCsReq : Handler +{ + public override async Task OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = GetOfferingInfoCsReq.Parser.ParseFrom(data); + + List dataList = []; + dataList.AddRange(req.OfferingIdList.Select(id => connection.Player!.OfferingManager!.GetOfferingData((int)id)).OfType()); + + await connection.SendPacket(new PacketGetOfferingInfoScRsp(dataList)); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/Offering/HandlerSubmitOfferingItemCsReq.cs b/GameServer/Server/Packet/Recv/Offering/HandlerSubmitOfferingItemCsReq.cs new file mode 100644 index 00000000..5e5c67d3 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Offering/HandlerSubmitOfferingItemCsReq.cs @@ -0,0 +1,18 @@ +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Offering; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Offering; + +[Opcode(CmdIds.SubmitOfferingItemCsReq)] +public class HandlerSubmitOfferingItemCsReq : Handler +{ + public override async Task OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = SubmitOfferingItemCsReq.Parser.ParseFrom(data); + + var res = await connection.Player!.OfferingManager!.SubmitOfferingItem((int)req.OfferingId); + + await connection.SendPacket(new PacketSubmitOfferingItemScRsp(res.Item1, res.data)); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/Offering/HandlerTakeOfferingRewardCsReq.cs b/GameServer/Server/Packet/Recv/Offering/HandlerTakeOfferingRewardCsReq.cs new file mode 100644 index 00000000..63702b9c --- /dev/null +++ b/GameServer/Server/Packet/Recv/Offering/HandlerTakeOfferingRewardCsReq.cs @@ -0,0 +1,18 @@ +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Offering; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Offering; + +[Opcode(CmdIds.TakeOfferingRewardCsReq)] +public class HandlerTakeOfferingRewardCsReq : Handler +{ + public override async Task OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = TakeOfferingRewardCsReq.Parser.ParseFrom(data); + var res = await connection.Player!.OfferingManager!.TakeOfferingReward((int)req.OfferingId, + req.TakeOfferingRewardIdList.Select(x => (int)x).ToList()); + + await connection.SendPacket(new PacketTakeOfferingRewardScRsp(res.Item1, res.data, res.reward)); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Offering/PacketGetOfferingInfoScRsp.cs b/GameServer/Server/Packet/Send/Offering/PacketGetOfferingInfoScRsp.cs new file mode 100644 index 00000000..23215fc7 --- /dev/null +++ b/GameServer/Server/Packet/Send/Offering/PacketGetOfferingInfoScRsp.cs @@ -0,0 +1,18 @@ +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Offering; + +public class PacketGetOfferingInfoScRsp : BasePacket +{ + public PacketGetOfferingInfoScRsp(List dataList) : base(CmdIds.GetOfferingInfoScRsp) + { + var proto = new GetOfferingInfoScRsp + { + OfferingInfoList = { dataList.Select(data => data.ToProto()).ToList() } + }; + + SetData(proto); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Offering/PacketOfferingInfoScNotify.cs b/GameServer/Server/Packet/Send/Offering/PacketOfferingInfoScNotify.cs new file mode 100644 index 00000000..1c32c215 --- /dev/null +++ b/GameServer/Server/Packet/Send/Offering/PacketOfferingInfoScNotify.cs @@ -0,0 +1,18 @@ +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Offering; + +public class PacketOfferingInfoScNotify : BasePacket +{ + public PacketOfferingInfoScNotify(OfferingTypeData data) : base(CmdIds.OfferingInfoScNotify) + { + var proto = new OfferingInfoScNotify + { + OfferingInfo = data.ToProto() + }; + + SetData(proto); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Offering/PacketSubmitOfferingItemScRsp.cs b/GameServer/Server/Packet/Send/Offering/PacketSubmitOfferingItemScRsp.cs new file mode 100644 index 00000000..f571555b --- /dev/null +++ b/GameServer/Server/Packet/Send/Offering/PacketSubmitOfferingItemScRsp.cs @@ -0,0 +1,23 @@ +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Offering; + +public class PacketSubmitOfferingItemScRsp : BasePacket +{ + public PacketSubmitOfferingItemScRsp(Retcode ret, OfferingTypeData? data) : base(CmdIds.SubmitOfferingItemScRsp) + { + var proto = new SubmitOfferingItemScRsp + { + Retcode = (uint)ret + }; + + if (data != null) + { + proto.OfferingInfo = data.ToProto(); + } + + SetData(proto); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Offering/PacketTakeOfferingRewardScRsp.cs b/GameServer/Server/Packet/Send/Offering/PacketTakeOfferingRewardScRsp.cs new file mode 100644 index 00000000..abd8cfa8 --- /dev/null +++ b/GameServer/Server/Packet/Send/Offering/PacketTakeOfferingRewardScRsp.cs @@ -0,0 +1,27 @@ +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Offering; + +public class PacketTakeOfferingRewardScRsp : BasePacket +{ + public PacketTakeOfferingRewardScRsp(Retcode ret, OfferingTypeData? data, List reward) : base(CmdIds.TakeOfferingRewardScRsp) + { + var proto = new TakeOfferingRewardScRsp + { + Retcode = (uint)ret, + }; + + if (data != null) + { + proto.OfferingInfo = data.ToProto(); + proto.Reward = new ItemList + { + ItemList_ = { reward.Select(x => x.ToProto()) } + }; + } + + SetData(proto); + } +} \ No newline at end of file