implement interception rewards, random item boxes

This commit is contained in:
Mikhail Tyukin
2025-06-24 21:28:57 +04:00
parent cb1fb566af
commit 7dab32c5be
6 changed files with 310 additions and 146 deletions

View File

@@ -178,6 +178,10 @@ namespace EpinelPS.Data
[LoadRecord("ConditionRewardTable.json", "id", typeof(ConditionRewardTable))]
public readonly Dictionary<int, ConditionRewardRecord> ConditionRewards = [];
[LoadRecord("ItemConsumeTable.json", "id", typeof(ItemConsumeTable))]
public readonly Dictionary<int, ItemConsumeRecord> ConsumableItems = [];
[LoadRecord("ItemRandomTable.json", "id", typeof(RandomItemTable))]
public readonly Dictionary<int, RandomItemRecord> RandomItem = [];
static async Task<GameData> BuildAsync()
{
await Load();

View File

@@ -794,5 +794,41 @@
{
public List<ConditionRewardRecord> 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<ItemConsumeRecord> 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<RandomItemRecord> records = [];
}
}

View File

@@ -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<ReqUseRandomBox>();
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);
}
}
}

View File

@@ -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<NetUserItemData> GetUserItems(User user)
{
List<NetUserItemData> 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;
}
}
}

View File

@@ -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;
}
/// <summary>
/// Adds a single item to users inventory, and also adds it to ret parameter.
/// </summary>
/// <param name="user"></param>
/// <param name="ret"></param>
/// <param name="rewardId"></param>
/// <param name="rewardType"></param>
/// <param name="rewardCount"></param>
/// <exception cref="Exception"></exception>
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);
}
}
}
}
}

View File

@@ -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();
} /// <summary>
/// Picks a random item. weights is a list of numbers which represents probability, table ids represent ID for weight
/// </summary>
/// <param name="weights"></param>
/// <param name="tableIds"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
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.");
}
}
}