From 7dab32c5be235b8dce4407c49341e8fc0c589c67 Mon Sep 17 00:00:00 2001 From: Mikhail Tyukin Date: Tue, 24 Jun 2025 21:28:57 +0400 Subject: [PATCH] implement interception rewards, random item boxes --- EpinelPS/Data/GameData.cs | 4 + EpinelPS/Data/JsonStaticData.cs | 38 ++- .../LobbyServer/Inventory/UseRandomBox.cs | 32 ++ EpinelPS/Utils/NetUtils.cs | 76 +++-- EpinelPS/Utils/RewardUtils.cs | 278 ++++++++++-------- EpinelPS/Utils/Rng.cs | 28 +- 6 files changed, 310 insertions(+), 146 deletions(-) create mode 100644 EpinelPS/LobbyServer/Inventory/UseRandomBox.cs diff --git a/EpinelPS/Data/GameData.cs b/EpinelPS/Data/GameData.cs index 5280fe3..a7f5bd3 100644 --- a/EpinelPS/Data/GameData.cs +++ b/EpinelPS/Data/GameData.cs @@ -178,6 +178,10 @@ namespace EpinelPS.Data [LoadRecord("ConditionRewardTable.json", "id", typeof(ConditionRewardTable))] public readonly Dictionary ConditionRewards = []; + [LoadRecord("ItemConsumeTable.json", "id", typeof(ItemConsumeTable))] + public readonly Dictionary ConsumableItems = []; + [LoadRecord("ItemRandomTable.json", "id", typeof(RandomItemTable))] + public readonly Dictionary RandomItem = []; static async Task BuildAsync() { await Load(); diff --git a/EpinelPS/Data/JsonStaticData.cs b/EpinelPS/Data/JsonStaticData.cs index 87e8d08..9f08db5 100644 --- a/EpinelPS/Data/JsonStaticData.cs +++ b/EpinelPS/Data/JsonStaticData.cs @@ -794,5 +794,41 @@ { public List records = []; } - + public enum ItemSubType + { + BundleBox, + ItemRandomBoxList, + ItemRandomBoxNormal, + TimeReward, + Box, + ProfileRandomBox, + ArcadeItem, + EquipCombination + } + public class ItemConsumeRecord + { + public int id; + public string use_type = ""; + public string item_type = ""; + public ItemSubType item_sub_type; + public int use_id; + } + public class ItemConsumeTable + { + public List records = []; + } + public class RandomItemRecord + { + public int id; + public int group_id; + public string reward_type = ""; + public int reward_id; + public int reward_value_min; + public int reward_value_max; + public int ratio; + } + public class RandomItemTable + { + public List records = []; + } } diff --git a/EpinelPS/LobbyServer/Inventory/UseRandomBox.cs b/EpinelPS/LobbyServer/Inventory/UseRandomBox.cs new file mode 100644 index 0000000..ce27c8c --- /dev/null +++ b/EpinelPS/LobbyServer/Inventory/UseRandomBox.cs @@ -0,0 +1,32 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Inventory +{ + [PacketPath("/inventory/userandombox")] + public class UseRandomBox : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + var response = new ResUseRandomBox(); + + var box = user.Items.Where(x => x.Isn == req.Isn).FirstOrDefault() ?? throw new InvalidDataException("cannot find box with isn " + req.Isn); + if (req.Count > box.Count) throw new Exception("count mismatch"); + + box.Count -= req.Count; + if (box.Count == 0) user.Items.Remove(box); + + response.Reward = NetUtils.UseLootBox(user, box.ItemType, req.Count); + + // update client side box count + response.Reward.UserItems.Add(NetUtils.UserItemDataToNet(box)); + + JsonDb.Save(); + + await WriteDataAsync(response); + } + } +} diff --git a/EpinelPS/Utils/NetUtils.cs b/EpinelPS/Utils/NetUtils.cs index 6cace7b..f55603e 100644 --- a/EpinelPS/Utils/NetUtils.cs +++ b/EpinelPS/Utils/NetUtils.cs @@ -1,7 +1,8 @@ -using EpinelPS.Database; -using EpinelPS.Data; -using Google.Protobuf.WellKnownTypes; +using System.Collections; using System.Collections.Generic; +using EpinelPS.Data; +using EpinelPS.Database; +using Google.Protobuf.WellKnownTypes; using static Google.Rpc.Context.AttributeContext.Types; namespace EpinelPS.Utils @@ -43,6 +44,22 @@ namespace EpinelPS.Utils }; } + internal static NetUserItemData UserItemDataToNet(ItemData item) + { + return new NetUserItemData() + { + Count = item.Count, + Tid = item.ItemType, + Csn = item.Csn, + Lv = item.Level, + Exp = item.Exp, + Corporation = item.Corp, + Isn = item.Isn, + Position = item.Position + }; + } + + public static List GetUserItems(User user) { List ret = new(); @@ -58,33 +75,12 @@ namespace EpinelPS.Utils } else { - itemDictionary[item.ItemType] = new NetUserItemData() - { - Count = item.Count, - Tid = item.ItemType, - Csn = item.Csn, - Lv = item.Level, - Exp = item.Exp, - Corporation = item.Corp, - Isn = item.Isn, - Position = item.Position - }; + itemDictionary[item.ItemType] = UserItemDataToNet(item); } } else { - var newItem = new NetUserItemData() - { - Count = item.Count, - Tid = item.ItemType, - Csn = item.Csn, - Lv = item.Level, - Exp = item.Exp, - Corporation = item.Corp, - Isn = item.Isn, - Position = item.Position - }; - itemDictionary[item.ItemType] = newItem; + itemDictionary[item.ItemType] = UserItemDataToNet(item); } } @@ -375,5 +371,33 @@ namespace EpinelPS.Utils return result; } + + public static NetRewardData UseLootBox(User user, int boxId, int count) + { + ItemConsumeRecord? cItem = GameData.Instance.ConsumableItems.Where(x => x.Value.id == boxId).FirstOrDefault().Value ?? throw new Exception("cannot find box id " + boxId); + + if (cItem.use_type != "ItemRandomBox") throw new Exception("expected random box"); + + // find matching probability entries + var probabilityEntries = GameData.Instance.RandomItem.Values.Where(x => x.group_id == cItem.use_id).ToArray(); + if (!probabilityEntries.Any()) throw new Exception($"cannot find any probability entries with ID {cItem.use_id}, box ID: {cItem.id}"); + + // run probability as many times as needed + NetRewardData ret = new() { PassPoint = new() }; + for (int i = 0; i < count; i++) + { + var winningRecord = Rng.PickWeightedItem(probabilityEntries); + + if (winningRecord.reward_value_min != winningRecord.reward_value_max) + { + Logging.WriteLine("TODO: reward_value_max", LogType.Warning); + } + + RewardUtils.AddSingleObject(user, ref ret, winningRecord.reward_id, winningRecord.reward_type, winningRecord.reward_value_min); + } + JsonDb.Save(); + + return ret; + } } } \ No newline at end of file diff --git a/EpinelPS/Utils/RewardUtils.cs b/EpinelPS/Utils/RewardUtils.cs index 3079556..d2473e2 100644 --- a/EpinelPS/Utils/RewardUtils.cs +++ b/EpinelPS/Utils/RewardUtils.cs @@ -1,5 +1,6 @@ -using EpinelPS.Database; using EpinelPS.Data; +using EpinelPS.Database; +using Org.BouncyCastle.Ocsp; namespace EpinelPS.Utils { @@ -56,12 +57,12 @@ namespace EpinelPS.Utils BeforeExp = user.userPointData.ExperiencePoint, BeforeLv = user.userPointData.UserLevel, - // IncreaseExp = rewardData.user_exp, + // IncreaseExp = rewardData.user_exp, CurrentExp = newXp, CurrentLv = newLevel, GainExp = rewardData.user_exp, - + }; user.userPointData.ExperiencePoint = newXp; @@ -70,126 +71,167 @@ namespace EpinelPS.Utils foreach (var item in rewardData.rewards) { - if (item.reward_id != 0 || !string.IsNullOrEmpty(item.reward_type)) + if (item.reward_percent != 1000000) { - if (string.IsNullOrEmpty(item.reward_type) || string.IsNullOrWhiteSpace(item.reward_type)) { } - else if (item.reward_type == "Currency") - { - bool found = false; - foreach (var currentReward in user.Currency) - { - if (currentReward.Key == (CurrencyType)item.reward_id) - { - user.Currency[currentReward.Key] += item.reward_value; - - ret.Currency.Add(new NetCurrencyData() - { - FinalValue = user.Currency[currentReward.Key], - Value = item.reward_value, - Type = item.reward_id - }); - found = true; - break; - } - } - - if (!found) - { - user.Currency.Add((CurrencyType)item.reward_id, item.reward_value); - ret.Currency.Add(new NetCurrencyData() - { - FinalValue = item.reward_value, - Value = item.reward_value, - Type = item.reward_id - }); - } - } - else if (item.reward_type == "Item") - { - // Check if user already has said item. If it is level 1, increase item count. - // If user does not have item, generate a new item ID - if (user.Items.Where(x => x.ItemType == item.reward_id && x.Level == 1).Any()) - { - ItemData? newItem = user.Items.Where(x => x.ItemType == item.reward_id && x.Level == 1).FirstOrDefault(); - if (newItem != null) - { - newItem.Count += item.reward_value; - - // Tell the client the reward and its amount - ret.Item.Add(new NetItemData() - { - Count = item.reward_value, - Tid = item.reward_id, - //Isn = newItem.Isn - }); - - // Tell the client the new amount of this item - ret.UserItems.Add(new NetUserItemData() - { - Isn = newItem.Isn, - Tid = newItem.ItemType, - Count = newItem.Count - }); - } - else - { - throw new Exception("should not occur"); - } - } - else - { - - var id = user.GenerateUniqueItemId(); - user.Items.Add(new ItemData() { ItemType = item.reward_id, Isn = id, Level = 1, Exp = 0, Count = item.reward_value }); - ret.Item.Add(new NetItemData() - { - Count = item.reward_value, - Tid = item.reward_id, - //Isn = id - }); - - // Tell the client the new amount of this item (which is the same as user did not have item previously) - ret.UserItems.Add(new NetUserItemData() - { - Isn = id, - Tid = item.reward_id, - Count = item.reward_value - }); - } - } - else if (item.reward_type == "Memorial") - { - if (!user.Memorial.Contains(item.reward_id)) - { - ret.Memorial.Add(item.reward_id); - user.Memorial.Add(item.reward_id); - } - } - else if (item.reward_type == "Bgm") - { - if (!user.JukeboxBgm.Contains(item.reward_id)) - { - ret.JukeboxBgm.Add(item.reward_id); - user.JukeboxBgm.Add(item.reward_id); - } - } - else if (item.reward_type == "InfraCoreExp") - { - ret.InfraCoreExp = new NetIncreaseExpData() - { - BeforeLv = user.InfraCoreLvl, - BeforeExp = user.InfraCoreExp, - // TODO - }; - } - else - { - Console.WriteLine("TODO: Reward type " + item.reward_type); - } + Logging.WriteLine("WARNING: ignoring percent: " + item.reward_percent / 10000 + ", item will be added anyways", LogType.Warning); } + + AddSingleObject(user, ref ret, item.reward_id, item.reward_type, item.reward_value); } return ret; } + /// + /// Adds a single item to users inventory, and also adds it to ret parameter. + /// + /// + /// + /// + /// + /// + /// + public static void AddSingleObject(User user, ref NetRewardData ret, int rewardId, string rewardType, int rewardCount) + { + if (rewardId != 0 || !string.IsNullOrEmpty(rewardType)) + { + if (string.IsNullOrEmpty(rewardType) || string.IsNullOrWhiteSpace(rewardType)) { } + else if (rewardType == "Currency") + { + bool found = false; + foreach (var currentReward in user.Currency) + { + if (currentReward.Key == (CurrencyType)rewardId) + { + user.Currency[currentReward.Key] += rewardCount; + + ret.Currency.Add(new NetCurrencyData() + { + FinalValue = user.Currency[currentReward.Key], + Value = rewardCount, + Type = rewardId + }); + found = true; + break; + } + } + + if (!found) + { + user.Currency.Add((CurrencyType)rewardId, rewardCount); + ret.Currency.Add(new NetCurrencyData() + { + FinalValue = rewardCount, + Value = rewardCount, + Type = rewardId + }); + } + } + else if (rewardType == "Item") + { + // Check if user already has said item. If it is level 1, increase item count. + // If user does not have item, generate a new item ID + if (user.Items.Where(x => x.ItemType == rewardId && x.Level == 1).Any()) + { + ItemData? newItem = user.Items.Where(x => x.ItemType == rewardId && x.Level == 1).FirstOrDefault(); + if (newItem != null) + { + newItem.Count += rewardCount; + + // Tell the client the reward and its amount + ret.Item.Add(new NetItemData() + { + Count = rewardCount, + Tid = rewardId, + //Isn = newItem.Isn + }); + + // Tell the client the new amount of this item + ret.UserItems.Add(new NetUserItemData() + { + Isn = newItem.Isn, + Tid = newItem.ItemType, + Count = newItem.Count + }); + } + else + { + throw new Exception("should not occur"); + } + } + else + { + + var id = user.GenerateUniqueItemId(); + user.Items.Add(new ItemData() { ItemType = rewardId, Isn = id, Level = 1, Exp = 0, Count = rewardCount }); + ret.Item.Add(new NetItemData() + { + Count = rewardCount, + Tid = rewardId, + //Isn = id + }); + + // Tell the client the new amount of this item (which is the same as user did not have item previously) + ret.UserItems.Add(new NetUserItemData() + { + Isn = id, + Tid = rewardId, + Count = rewardCount + }); + } + } + else if (rewardType == "Memorial") + { + if (!user.Memorial.Contains(rewardId)) + { + ret.Memorial.Add(rewardId); + user.Memorial.Add(rewardId); + } + } + else if (rewardType == "Bgm") + { + if (!user.JukeboxBgm.Contains(rewardId)) + { + ret.JukeboxBgm.Add(rewardId); + user.JukeboxBgm.Add(rewardId); + } + } + else if (rewardType == "InfraCoreExp") + { + ret.InfraCoreExp = new NetIncreaseExpData() + { + BeforeLv = user.InfraCoreLvl, + BeforeExp = user.InfraCoreExp, + // TODO + }; + } + else if (rewardType == "ItemRandomBox") + { + ItemConsumeRecord? cItem = GameData.Instance.ConsumableItems.Where(x => x.Value.id == rewardId).FirstOrDefault().Value; + + if (cItem.item_sub_type == ItemSubType.ItemRandomBoxList) + { + NetRewardData reward = NetUtils.UseLootBox(user, rewardId, rewardCount); + + NetUtils.RegisterRewardsForUser(user, reward); + ret = NetUtils.MergeRewards([ret, reward], user); + } + else + { + var itm = new NetItemData() + { + Count = rewardCount, + Tid = cItem.id, + Isn = user.GenerateUniqueItemId() + }; + ret.Item.Add(itm); + user.Items.Add(new ItemData() { Count = rewardCount, Isn = itm.Isn, ItemType = itm.Tid }); + } + } + else + { + Logging.WriteLine("TODO: Reward type " + rewardType, LogType.Warning); + } + } + } } } diff --git a/EpinelPS/Utils/Rng.cs b/EpinelPS/Utils/Rng.cs index 4fec5e8..a4c230b 100644 --- a/EpinelPS/Utils/Rng.cs +++ b/EpinelPS/Utils/Rng.cs @@ -1,4 +1,6 @@ -namespace EpinelPS.Utils +using EpinelPS.Data; + +namespace EpinelPS.Utils { public class Rng { @@ -14,6 +16,30 @@ public static int RandomId() { return random.Next(); + } /// + /// Picks a random item. weights is a list of numbers which represents probability, table ids represent ID for weight + /// + /// + /// + /// + /// + public static RandomItemRecord PickWeightedItem(RandomItemRecord[] records) + { + int totalWeight = 0; + foreach (var item in records) + totalWeight += item.ratio; + + int randomNumber = random.Next(0, totalWeight); + + int runningSum = 0; + for (int i = 0; i < records.Length; i++) + { + runningSum += records[i].ratio; + if (randomNumber < runningSum) + return records[i]; + } + + throw new Exception("Weight distribution error."); } } }