mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-12 15:04:36 +01:00
feat: implement equipment awakening system with interception updates (#57)
- Implement comprehensive equipment awakening system with multiple new endpoints: * Awakening.cs: Handle equipment awakening process * ChangeOption.cs: Change equipment awakening options * GetAwakeningDetail.cs: Get detailed awakening information * LockOption.cs: Lock awakening options (with Disposable option) * ResetOption.cs: Reset awakening options * UpgradeOption.cs: Upgrade awakening options - Enhance interception system: * Simplify GetInterceptData to use fixed normal group ID (1) * Update InterceptionHelper to support type 1 in addition to type 0 * Modify GetInterceptData to use simplified special ID calculation - Implement new inventory system features: * Replace ClearAllEquipment with AllClearEquipment * Enhance GetInventoryData to properly handle HarmonyCubes and Awakenings * Update WearEquipmentList for improved equipment management - Update data models and game data: * Modify GetConditionReward to handle valueMax == 0 cases * Update EquipmentAwakeningData model with proper default values * Update ResetableData with DailyCounselCount as dictionary instead of struct field - Additional improvements: * Create GameAssemblyProcessor utility * Enhance level infinite controller * Update server selector UI * Organize protocol message documentation
This commit is contained in:
@@ -254,6 +254,14 @@ namespace EpinelPS.Data
|
|||||||
[LoadRecord("EventMVGMissionTable.json", "Id")]
|
[LoadRecord("EventMVGMissionTable.json", "Id")]
|
||||||
public readonly Dictionary<int, EventMVGMissionRecord_Raw> EventMvgMissionTable = [];
|
public readonly Dictionary<int, EventMVGMissionRecord_Raw> EventMvgMissionTable = [];
|
||||||
|
|
||||||
|
[LoadRecord("EquipmentOptionTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, EquipmentOptionRecord> EquipmentOptionTable = [];
|
||||||
|
|
||||||
|
[LoadRecord("EquipmentOptionCostTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, EquipmentOptionCostRecord> EquipmentOptionCostTable = [];
|
||||||
|
|
||||||
|
[LoadRecord("ItemEquipCorpSettingTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, ItemEquipCorpSettingRecord> ItemEquipCorpSettingTable = [];
|
||||||
static async Task<GameData> BuildAsync()
|
static async Task<GameData> BuildAsync()
|
||||||
{
|
{
|
||||||
await Load();
|
await Load();
|
||||||
@@ -718,17 +726,10 @@ namespace EpinelPS.Data
|
|||||||
return data.HardFieldId;
|
return data.HardFieldId;
|
||||||
else return data.FieldId;
|
else return data.FieldId;
|
||||||
}
|
}
|
||||||
internal string GetMapIdFromChapter(int chapter, string mod)
|
|
||||||
{
|
|
||||||
CampaignChapterRecord data = ChapterCampaignData[chapter - 1];
|
|
||||||
if (mod == "Hard")
|
|
||||||
return data.HardFieldId;
|
|
||||||
else return data.FieldId;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal int GetConditionReward(int groupId, long damage)
|
internal int GetConditionReward(int groupId, long damage)
|
||||||
{
|
{
|
||||||
IEnumerable<KeyValuePair<int, ConditionRewardRecord>> results = ConditionRewards.Where(x => x.Value.Group == groupId && x.Value.ValueMin <= damage && x.Value.ValueMax >= damage);
|
IEnumerable<KeyValuePair<int, ConditionRewardRecord>> results = ConditionRewards.Where(x => x.Value.Group == groupId && x.Value.ValueMin <= damage && (x.Value.ValueMax == 0 || x.Value.ValueMax >= damage));
|
||||||
if (results.Any())
|
if (results.Any())
|
||||||
return results.FirstOrDefault().Value.RewardId;
|
return results.FirstOrDefault().Value.RewardId;
|
||||||
else return 0;
|
else return 0;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 20001,
|
Id = 20001,
|
||||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
EventSystemType = (int)EventSystemType.PickupGachaEvent,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
@@ -31,7 +31,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 70077,
|
Id = 70077,
|
||||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
EventSystemType = (int)EventSystemType.PickupGachaEvent,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
@@ -44,7 +44,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 10046,
|
Id = 10046,
|
||||||
EventSystemType = (int)EventType.LoginEvent,
|
EventSystemType = (int)EventSystemType.LoginEvent,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
@@ -57,7 +57,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 40066,
|
Id = 40066,
|
||||||
EventSystemType = (int)EventType.StoryEvent,
|
EventSystemType = (int)EventSystemType.StoryEvent,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
@@ -70,7 +70,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 60066,
|
Id = 60066,
|
||||||
EventSystemType = (int)EventType.ChallengeModeEvent,
|
EventSystemType = (int)EventSystemType.ChallengeModeEvent,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
@@ -83,7 +83,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 70078,
|
Id = 70078,
|
||||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
EventSystemType = (int)EventSystemType.PickupGachaEvent,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
@@ -96,7 +96,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 70079,
|
Id = 70079,
|
||||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
EventSystemType = (int)EventSystemType.PickupGachaEvent,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
@@ -112,7 +112,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 140052,
|
Id = 140052,
|
||||||
EventSystemType = RewardUpEvent,
|
EventSystemType = (int)EventSystemType.RewardUpEvent,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
@@ -130,7 +130,7 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
EventData = new NetEventData()
|
EventData = new NetEventData()
|
||||||
{
|
{
|
||||||
Id = 170017,
|
Id = 170017,
|
||||||
EventSystemType = TriggerMissionEventReward,
|
EventSystemType = (int)EventSystemType.TriggerMissionEventReward,
|
||||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using EpinelPS.Database;
|
using EpinelPS.Database;
|
||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Intercept
|
namespace EpinelPS.LobbyServer.Intercept
|
||||||
{
|
{
|
||||||
@@ -8,17 +9,32 @@ namespace EpinelPS.LobbyServer.Intercept
|
|||||||
{
|
{
|
||||||
protected override async Task HandleAsync()
|
protected override async Task HandleAsync()
|
||||||
{
|
{
|
||||||
ReqGetInterceptData req = await ReadData<ReqGetInterceptData>();
|
ReqGetInterceptData req = await ReadData<ReqGetInterceptData>();
|
||||||
|
|
||||||
|
int specialId = GetCurrentInterceptionIds();
|
||||||
|
|
||||||
ResGetInterceptData response = new()
|
ResGetInterceptData response = new()
|
||||||
{
|
{
|
||||||
NormalInterceptGroup = 1,
|
NormalInterceptGroup = 1,
|
||||||
SpecialInterceptId = 1, // TODO switch this out each reset
|
SpecialInterceptId = specialId,
|
||||||
TicketCount = User.ResetableData.InterceptionTickets,
|
TicketCount = User.ResetableData.InterceptionTickets,
|
||||||
MaxTicketCount = JsonDb.Instance.MaxInterceptionCount
|
MaxTicketCount = JsonDb.Instance.MaxInterceptionCount
|
||||||
};
|
};
|
||||||
|
|
||||||
await WriteDataAsync(response);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int GetCurrentInterceptionIds()
|
||||||
|
{
|
||||||
|
var specialTable = GameData.Instance.InterceptSpecial;
|
||||||
|
var specialBosses = specialTable.Values.Where(x => x.Group == 1).OrderBy(x => x.Order).ToList();
|
||||||
|
|
||||||
|
var dayOfYear = DateTime.UtcNow.DayOfYear;
|
||||||
|
var specialIndex = dayOfYear % specialBosses.Count;
|
||||||
|
|
||||||
|
var specialId = specialBosses[specialIndex].Id;
|
||||||
|
return specialId;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
EpinelPS/LobbyServer/Inventory/AllClearEquipment.cs
Normal file
60
EpinelPS/LobbyServer/Inventory/AllClearEquipment.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
|
{
|
||||||
|
[PacketPath("/inventory/allclearequipment")]
|
||||||
|
public class AllClearEquipment : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
ReqAllClearEquipment req = await ReadData<ReqAllClearEquipment>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResAllClearEquipment response = new()
|
||||||
|
{
|
||||||
|
Csn = req.Csn
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (ItemData item in user.Items.ToArray())
|
||||||
|
{
|
||||||
|
if (item.Csn == req.Csn)
|
||||||
|
{
|
||||||
|
// Check if the item being unequipped is T10
|
||||||
|
if (IsT10Equipment(item.ItemType))
|
||||||
|
{
|
||||||
|
|
||||||
|
response.Items.Add(NetUtils.ToNet(item));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Csn = 0;
|
||||||
|
item.Position = 0;
|
||||||
|
response.Items.Add(NetUtils.ToNet(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsT10Equipment(int itemTypeId)
|
||||||
|
{
|
||||||
|
// Equipment ID format: 3 + Slot(1Head2Body3Arm4Leg) + Class(1Attacker2Defender3Supporter) + Rarity(01-09 T1-T9, 10 T10) + 01
|
||||||
|
// T10 equipment has rarity "10" in positions 3-4 (0-based indexing) for 7-digit IDs
|
||||||
|
string itemTypeStr = itemTypeId.ToString();
|
||||||
|
|
||||||
|
// Check if this is an equipment item (starts with 3) and has 7 digits
|
||||||
|
if (itemTypeStr.Length == 7 && itemTypeStr[0] == '3')
|
||||||
|
{
|
||||||
|
// Extract the rarity part (positions 3-4)
|
||||||
|
string rarityPart = itemTypeStr.Substring(3, 2);
|
||||||
|
return rarityPart == "10";
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using EpinelPS.Database;
|
|
||||||
using EpinelPS.Utils;
|
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Inventory
|
|
||||||
{
|
|
||||||
[PacketPath("/inventory/allclearequipment")]
|
|
||||||
public class ClearAllEquipment : LobbyMsgHandler
|
|
||||||
{
|
|
||||||
protected override async Task HandleAsync()
|
|
||||||
{
|
|
||||||
ReqAllClearEquipment req = await ReadData<ReqAllClearEquipment>();
|
|
||||||
User user = GetUser();
|
|
||||||
|
|
||||||
ResAllClearEquipment response = new()
|
|
||||||
{
|
|
||||||
Csn = req.Csn
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (ItemData item in user.Items.ToArray())
|
|
||||||
{
|
|
||||||
if (item.Csn == req.Csn)
|
|
||||||
{
|
|
||||||
// update character Id
|
|
||||||
item.Csn = 0;
|
|
||||||
|
|
||||||
response.Items.Add(NetUtils.ToNet(item));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDb.Save();
|
|
||||||
|
|
||||||
await WriteDataAsync(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
namespace EpinelPS.LobbyServer.Inventory
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
{
|
{
|
||||||
[PacketPath("/inventory/get")]
|
[PacketPath("/inventory/get")]
|
||||||
@@ -13,9 +13,36 @@ namespace EpinelPS.LobbyServer.Inventory
|
|||||||
ResGetInventoryData response = new();
|
ResGetInventoryData response = new();
|
||||||
foreach (ItemData item in user.Items)
|
foreach (ItemData item in user.Items)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
ItemSubType itemSubType = GameData.Instance.GetItemSubType(item.ItemType);
|
||||||
|
if (itemSubType == ItemSubType.HarmonyCube)
|
||||||
|
{
|
||||||
|
NetUserHarmonyCubeData harmonyCubeData = new NetUserHarmonyCubeData()
|
||||||
|
{
|
||||||
|
Tid = item.ItemType,
|
||||||
|
Lv = item.Level,
|
||||||
|
Isn = item.Isn
|
||||||
|
};
|
||||||
|
harmonyCubeData.CsnList.AddRange(item.CsnList);
|
||||||
|
response.HarmonyCubes.Add(harmonyCubeData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
response.Items.Add(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 });
|
response.Items.Add(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 });
|
||||||
|
|
||||||
}
|
}
|
||||||
// TODO: HarmonyCubes, RunAwakeningIsnList, UserRedeems
|
|
||||||
|
|
||||||
|
// Add all equipment awakenings
|
||||||
|
foreach (EquipmentAwakeningData awakening in user.EquipmentAwakenings)
|
||||||
|
{
|
||||||
|
response.Awakenings.Add(new NetEquipmentAwakening()
|
||||||
|
{
|
||||||
|
Isn = awakening.Isn,
|
||||||
|
Option = awakening.Option
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// TODO: UserRedeems
|
||||||
|
// Note: HarmonyCubes are now included in the Items list above
|
||||||
|
|
||||||
await WriteDataAsync(response);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using EpinelPS.Database;
|
using EpinelPS.Database;
|
||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Inventory
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
{
|
{
|
||||||
@@ -13,27 +15,120 @@ namespace EpinelPS.LobbyServer.Inventory
|
|||||||
|
|
||||||
ResWearEquipmentList response = new();
|
ResWearEquipmentList response = new();
|
||||||
|
|
||||||
// TODO optimize
|
|
||||||
foreach (long item2 in req.IsnList)
|
foreach (long item2 in req.IsnList)
|
||||||
{
|
{
|
||||||
int pos = NetUtils.GetItemPos(user, item2);
|
int pos = NetUtils.GetItemPos(user, item2);
|
||||||
|
|
||||||
|
// Check if the item being equipped is T10
|
||||||
|
ItemData? itemToCheck = user.Items.FirstOrDefault(x => x.Isn == item2);
|
||||||
|
if (itemToCheck != null && IsT10Equipment(itemToCheck.ItemType))
|
||||||
|
{
|
||||||
|
// If trying to equip a T10 item, check if there's already a T10 item in that position
|
||||||
|
bool hasT10InPosition = user.Items.Any(x => x.Position == pos && x.Csn == req.Csn && IsT10Equipment(x.ItemType));
|
||||||
|
if (hasT10InPosition)
|
||||||
|
{
|
||||||
|
// Don't allow replacing T10 equipment
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if item still exists after previous operations
|
||||||
|
itemToCheck = user.Items.FirstOrDefault(x => x.Isn == item2);
|
||||||
|
if (itemToCheck == null)
|
||||||
|
{
|
||||||
|
// Item no longer exists, skip this iteration
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// unequip previous items
|
// unequip previous items
|
||||||
foreach (ItemData item in user.Items.ToArray())
|
foreach (ItemData item in user.Items.ToArray())
|
||||||
{
|
{
|
||||||
if (item.Position == pos && item.Csn == req.Csn)
|
if (item.Position == pos && item.Csn == req.Csn)
|
||||||
{
|
{
|
||||||
|
// Check if the item being unequipped is T10
|
||||||
|
if (IsT10Equipment(item.ItemType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
item.Csn = 0;
|
item.Csn = 0;
|
||||||
item.Position = 0;
|
item.Position = 0;
|
||||||
|
response.Items.Add(NetUtils.ToNet(item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (ItemData item in user.Items.ToArray())
|
// Find the item to equip
|
||||||
|
ItemData? targetItem = user.Items.FirstOrDefault(x => x.Isn == item2);
|
||||||
|
if (targetItem != null)
|
||||||
{
|
{
|
||||||
if (item2 == item.Isn)
|
// Handle case where we have multiple copies of the same item
|
||||||
|
ItemData? equippedItem = null;
|
||||||
|
if (targetItem.Count > 1)
|
||||||
|
{
|
||||||
|
// Reduce count of original item
|
||||||
|
targetItem.Count--;
|
||||||
|
response.Items.Add(NetUtils.ToNet(targetItem));
|
||||||
|
|
||||||
|
// Create a new item instance to equip
|
||||||
|
equippedItem = new ItemData
|
||||||
|
{
|
||||||
|
ItemType = targetItem.ItemType,
|
||||||
|
Isn = user.GenerateUniqueItemId(),
|
||||||
|
Level = targetItem.Level,
|
||||||
|
Exp = targetItem.Exp,
|
||||||
|
Count = 1,
|
||||||
|
Corp = targetItem.Corp,
|
||||||
|
Position = pos // Set the position for the new item
|
||||||
|
};
|
||||||
|
user.Items.Add(equippedItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use the existing item
|
||||||
|
equippedItem = targetItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// equip the item
|
||||||
|
equippedItem.Csn = req.Csn;
|
||||||
|
equippedItem.Position = pos;
|
||||||
|
response.Items.Add(NetUtils.ToNet(equippedItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all requested items are in the response
|
||||||
|
// This helps the client track the specific items that were requested
|
||||||
|
foreach (long requestedIsn in req.IsnList)
|
||||||
|
{
|
||||||
|
bool requestedItemAdded = response.Items.Any(x => x.Isn == requestedIsn);
|
||||||
|
if (!requestedItemAdded)
|
||||||
|
{
|
||||||
|
ItemData? requestedItem = user.Items.FirstOrDefault(x => x.Isn == requestedIsn);
|
||||||
|
if (requestedItem != null)
|
||||||
|
{
|
||||||
|
response.Items.Add(NetUtils.ToNet(requestedItem));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If item not found, add it with count 0 to indicate it was processed
|
||||||
|
response.Items.Add(new NetUserItemData()
|
||||||
|
{
|
||||||
|
Isn = requestedIsn,
|
||||||
|
Count = 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all other equipped items for this character to the response
|
||||||
|
// This helps the client synchronize the full equipment state
|
||||||
|
foreach (ItemData item in user.Items)
|
||||||
|
{
|
||||||
|
if (item.Csn == req.Csn && item.Csn != 0)
|
||||||
|
{
|
||||||
|
// Check if this item was already added in the loop above
|
||||||
|
bool alreadyAdded = response.Items.Any(x => x.Isn == item.Isn);
|
||||||
|
if (!alreadyAdded)
|
||||||
{
|
{
|
||||||
item.Csn = req.Csn;
|
|
||||||
item.Position = pos;
|
|
||||||
response.Items.Add(NetUtils.ToNet(item));
|
response.Items.Add(NetUtils.ToNet(item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,5 +138,22 @@ namespace EpinelPS.LobbyServer.Inventory
|
|||||||
|
|
||||||
await WriteDataAsync(response);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsT10Equipment(int itemTypeId)
|
||||||
|
{
|
||||||
|
// Equipment ID format: 3 + Slot(1Head2Body3Arm4Leg) + Class(1Attacker2Defender3Supporter) + Rarity(01-09 T1-T9, 10 T10) + 01
|
||||||
|
// T10 equipment has rarity "10" in positions 3-4 (0-based indexing) for 7-digit IDs
|
||||||
|
string itemTypeStr = itemTypeId.ToString();
|
||||||
|
|
||||||
|
// Check if this is an equipment item (starts with 3) and has 7 digits
|
||||||
|
if (itemTypeStr.Length == 7 && itemTypeStr[0] == '3')
|
||||||
|
{
|
||||||
|
// Extract the rarity part (positions 3-4)
|
||||||
|
string rarityPart = itemTypeStr.Substring(3, 2);
|
||||||
|
return rarityPart == "10";
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
350
EpinelPS/LobbyServer/Inventory/equipment/Awakening.cs
Normal file
350
EpinelPS/LobbyServer/Inventory/equipment/Awakening.cs
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
|
{
|
||||||
|
[PacketPath("/inventory/equipment/awakening")]
|
||||||
|
public class AwakeningEquipment : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
ReqEquipmentAwakening req = await ReadData<ReqEquipmentAwakening>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResEquipmentAwakening response = new();
|
||||||
|
|
||||||
|
ItemData? equipmentToAwaken = user.Items.FirstOrDefault(x => x.Isn == req.Isn);
|
||||||
|
if (equipmentToAwaken == null)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int materialCost = 1;
|
||||||
|
int materialId = 7080001; // Equipment option material
|
||||||
|
|
||||||
|
ItemData? material = user.Items.FirstOrDefault(x => x.ItemType == materialId);
|
||||||
|
if (material == null || material.Count < materialCost)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EquipmentUtils.DeductMaterials(material, materialCost, user, response.Items))
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int awakenedEquipmentTypeId = GetAwakenedEquipmentTypeId(equipmentToAwaken.ItemType);
|
||||||
|
|
||||||
|
equipmentToAwaken.ItemType = awakenedEquipmentTypeId;
|
||||||
|
equipmentToAwaken.Level = 0;
|
||||||
|
equipmentToAwaken.Exp = 0;
|
||||||
|
equipmentToAwaken.Corp = 0;
|
||||||
|
|
||||||
|
Random rng = new Random();
|
||||||
|
|
||||||
|
NetEquipmentAwakeningOption awakeningOption = new NetEquipmentAwakeningOption()
|
||||||
|
{
|
||||||
|
Option1Lock = false,
|
||||||
|
IsOption1DisposableLock = false,
|
||||||
|
Option2Lock = false,
|
||||||
|
IsOption2DisposableLock = false,
|
||||||
|
Option3Lock = false,
|
||||||
|
IsOption3DisposableLock = false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GameData.Instance.ItemEquipTable.TryGetValue(awakenedEquipmentTypeId, out ItemEquipRecord? equipRecord))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GenerateAwakeningOptions(awakeningOption, equipRecord, rng);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Failed to generate awakening options: {ex.Message}", LogType.Error);
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.EquipmentAwakenings.Add(new EquipmentAwakeningData()
|
||||||
|
{
|
||||||
|
Isn = equipmentToAwaken.Isn,
|
||||||
|
Option = awakeningOption,
|
||||||
|
IsNewData = false
|
||||||
|
});
|
||||||
|
|
||||||
|
response.Awakening = new NetEquipmentAwakening()
|
||||||
|
{
|
||||||
|
Isn = equipmentToAwaken.Isn,
|
||||||
|
Option = awakeningOption
|
||||||
|
};
|
||||||
|
|
||||||
|
response.Items.Add(NetUtils.ToNet(equipmentToAwaken));
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetAwakenedEquipmentTypeId(int originalTypeId)
|
||||||
|
{
|
||||||
|
// Equipment ID format: 3 + Slot(1Head2Body3Arm4Leg) + Class(1Attacker2Defender3Supporter) + Rarity(01-09 T1-T9, 10 T10) + 01
|
||||||
|
// Awakening changes T9 equipment (09) to T10 equipment (10)
|
||||||
|
return originalTypeId switch
|
||||||
|
{
|
||||||
|
// Attacker equipment awakening
|
||||||
|
3110901 => 3111001, // Head T9 -> T10
|
||||||
|
3210901 => 3211001, // Body T9 -> T10
|
||||||
|
3310901 => 3311001, // Arm T9 -> T10
|
||||||
|
3410901 => 3411001, // Leg T9 -> T10
|
||||||
|
|
||||||
|
// Defender equipment awakening
|
||||||
|
3120901 => 3121001, // Head T9 -> T10
|
||||||
|
3220901 => 3221001, // Body T9 -> T10
|
||||||
|
3320901 => 3321001, // Arm T9 -> T10
|
||||||
|
3420901 => 3421001, // Leg T9 -> T10
|
||||||
|
|
||||||
|
// Supporter equipment awakening
|
||||||
|
3130901 => 3131001, // Head T9 -> T10
|
||||||
|
3230901 => 3231001, // Body T9 -> T10
|
||||||
|
3330901 => 3331001, // Arm T9 -> T10
|
||||||
|
3430901 => 3431001, // Leg T9 -> T10
|
||||||
|
|
||||||
|
// Default return original ID (awakening not supported)
|
||||||
|
_ => originalTypeId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateAwakeningOptions(NetEquipmentAwakeningOption option, ItemEquipRecord equipRecord, Random rng)
|
||||||
|
{
|
||||||
|
List<int> excludedStateEffectIds = new List<int>();
|
||||||
|
|
||||||
|
// 1.0 = 100% chance for slot 1, 0.5 = 50% chance for slot 2, 0.3 = 30% chance for slot 3
|
||||||
|
double[] slotActivationProbabilities = { 1.0, 0.5, 0.3 };
|
||||||
|
|
||||||
|
for (int i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
bool shouldActivateSlot = rng.NextDouble() < slotActivationProbabilities[i - 1];
|
||||||
|
|
||||||
|
if (shouldActivateSlot)
|
||||||
|
{
|
||||||
|
int selectedOptionId = GenerateNewOptionIdInit(excludedStateEffectIds, 11);
|
||||||
|
AddOptionToExclusionList(selectedOptionId, excludedStateEffectIds);
|
||||||
|
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
option.Option1Id = selectedOptionId;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
option.Option2Id = selectedOptionId;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
option.Option3Id = selectedOptionId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
option.Option1Id = 0;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
option.Option2Id = 0;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
option.Option3Id = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
option.Option1Lock = false;
|
||||||
|
option.IsOption1DisposableLock = false;
|
||||||
|
option.Option2Lock = false;
|
||||||
|
option.IsOption2DisposableLock = false;
|
||||||
|
option.Option3Lock = false;
|
||||||
|
option.IsOption3DisposableLock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GenerateNewOptionIdInit(List<int> excludedStateEffectIds, int level)
|
||||||
|
{
|
||||||
|
List<EquipmentOptionRecord> allAwakeningOptions = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.Where(x => x.EquipmentOptionGroupId == 100000 &&
|
||||||
|
x.StateEffectList?.Any(se => se.StateEffectLevel == level) == true)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
HashSet<int> excludedEffectGroupIds = new HashSet<int>();
|
||||||
|
|
||||||
|
foreach (int stateEffectId in excludedStateEffectIds)
|
||||||
|
{
|
||||||
|
EquipmentOptionRecord? excludedOption = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.FirstOrDefault(opt => opt.StateEffectList != null && opt.StateEffectList.Any(se => se.StateEffectId == stateEffectId));
|
||||||
|
if (excludedOption != null)
|
||||||
|
{
|
||||||
|
excludedEffectGroupIds.Add(excludedOption.StateEffectGroupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EquipmentOptionRecord> availableOptions = allAwakeningOptions
|
||||||
|
.Where(option => !excludedEffectGroupIds.Contains(option.StateEffectGroupId))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Dictionary<int, List<EquipmentOptionRecord>> optionsByEffectGroup = availableOptions
|
||||||
|
.GroupBy(option => option.StateEffectGroupId)
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
|
if (optionsByEffectGroup.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No available equipment options for awakening - this indicates a data consistency issue");
|
||||||
|
}
|
||||||
|
|
||||||
|
double excludedProbabilitySum = CalculateExcludedProbabilitySumByEffectGroup(excludedEffectGroupIds, level);
|
||||||
|
|
||||||
|
List<EffectGroupWithWeight> weightedEffectGroups = CalculateDynamicProbabilitiesForEffectGroups(optionsByEffectGroup, excludedProbabilitySum);
|
||||||
|
|
||||||
|
int selectedEffectGroupId = SelectWeightedRandomEffectGroup(weightedEffectGroups);
|
||||||
|
|
||||||
|
List<EquipmentOptionRecord> optionsInSelectedGroup = optionsByEffectGroup[selectedEffectGroupId];
|
||||||
|
|
||||||
|
foreach (EquipmentOptionRecord option in optionsInSelectedGroup)
|
||||||
|
{
|
||||||
|
StateEffectList? stateEffect = option.StateEffectList?.FirstOrDefault(se => se.StateEffectLevel == level);
|
||||||
|
if (stateEffect != null)
|
||||||
|
{
|
||||||
|
return stateEffect.StateEffectId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"No state effect found with level {level} in selected effect group - this indicates a data consistency issue");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the sum of base probabilities for excluded effect groups according to the Overload system rules
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="excludedEffectGroupIds">List of excluded state_effect_group_ids</param>
|
||||||
|
/// <param name="level">The level of options to consider</param>
|
||||||
|
/// <returns>Sum of base probabilities (as decimal percentage)</returns>
|
||||||
|
private double CalculateExcludedProbabilitySumByEffectGroup(HashSet<int> excludedEffectGroupIds, int level)
|
||||||
|
{
|
||||||
|
if (excludedEffectGroupIds.Count == 0)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double totalExcluded = 0.0;
|
||||||
|
|
||||||
|
foreach (int effectGroupId in excludedEffectGroupIds)
|
||||||
|
{
|
||||||
|
List<EquipmentOptionRecord> options = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.Where(opt => opt.StateEffectGroupId == effectGroupId &&
|
||||||
|
opt.EquipmentOptionGroupId == 100000 &&
|
||||||
|
opt.StateEffectList?.Any(se => se.StateEffectLevel == level) == true)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (options.Count > 0)
|
||||||
|
{
|
||||||
|
EquipmentOptionRecord firstOption = options.First();
|
||||||
|
totalExcluded += firstOption.OptionGroupRatio / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Min(totalExcluded, 99.9); // Ensure we don't reach 100% to avoid division by zero
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to store effect group with its calculated weight for probability selection
|
||||||
|
/// </summary>
|
||||||
|
public class EffectGroupWithWeight
|
||||||
|
{
|
||||||
|
public int EffectGroupId { get; set; }
|
||||||
|
public double Weight { get; set; }
|
||||||
|
public double BaseProbability { get; set; }
|
||||||
|
public double DynamicProbability { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates dynamic probabilities for available effect groups using the formula:
|
||||||
|
/// Dynamic Probability = Display Probability / (100% - Sum of Excluded Probabilities)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionsByEffectGroup">Dictionary of available options grouped by effect group ID</param>
|
||||||
|
/// <param name="excludedProbabilitySum">Sum of probabilities of excluded effects</param>
|
||||||
|
/// <returns>List of weighted effect groups for random selection</returns>
|
||||||
|
private List<EffectGroupWithWeight> CalculateDynamicProbabilitiesForEffectGroups(Dictionary<int, List<EquipmentOptionRecord>> optionsByEffectGroup, double excludedProbabilitySum)
|
||||||
|
{
|
||||||
|
List<EffectGroupWithWeight> weightedEffectGroups = new List<EffectGroupWithWeight>();
|
||||||
|
|
||||||
|
double probabilityDenominator = 100.0 - excludedProbabilitySum;
|
||||||
|
|
||||||
|
if (probabilityDenominator <= 0)
|
||||||
|
{
|
||||||
|
probabilityDenominator = 1.0;
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<int, List<EquipmentOptionRecord>> kvp in optionsByEffectGroup)
|
||||||
|
{
|
||||||
|
int effectGroupId = kvp.Key;
|
||||||
|
List<EquipmentOptionRecord> options = kvp.Value;
|
||||||
|
|
||||||
|
if (options.Count > 0)
|
||||||
|
{
|
||||||
|
EquipmentOptionRecord firstOption = options.First();
|
||||||
|
double baseProbability = (double)firstOption.OptionGroupRatio / 100.0;
|
||||||
|
|
||||||
|
double dynamicProbability = baseProbability / probabilityDenominator;
|
||||||
|
|
||||||
|
double selectionWeight = dynamicProbability * 1000000;
|
||||||
|
|
||||||
|
weightedEffectGroups.Add(new EffectGroupWithWeight
|
||||||
|
{
|
||||||
|
EffectGroupId = effectGroupId,
|
||||||
|
Weight = selectionWeight,
|
||||||
|
BaseProbability = baseProbability,
|
||||||
|
DynamicProbability = dynamicProbability
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weightedEffectGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects an effect group randomly based on calculated weights
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="weightedEffectGroups">List of weighted effect groups</param>
|
||||||
|
/// <returns>Selected effect_group_id</returns>
|
||||||
|
private int SelectWeightedRandomEffectGroup(List<EffectGroupWithWeight> weightedEffectGroups)
|
||||||
|
{
|
||||||
|
Random random = new Random();
|
||||||
|
double totalWeight = weightedEffectGroups.Sum(weg => weg.Weight);
|
||||||
|
|
||||||
|
double randomValue = random.NextDouble() * totalWeight;
|
||||||
|
|
||||||
|
double cumulativeWeight = 0.0;
|
||||||
|
foreach (EffectGroupWithWeight weightedGroup in weightedEffectGroups)
|
||||||
|
{
|
||||||
|
cumulativeWeight += weightedGroup.Weight;
|
||||||
|
if (randomValue <= cumulativeWeight)
|
||||||
|
{
|
||||||
|
return weightedGroup.EffectGroupId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weightedEffectGroups.Last().EffectGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddOptionToExclusionList(int optionId, List<int> excludedStateEffectIds)
|
||||||
|
{
|
||||||
|
if (!excludedStateEffectIds.Contains(optionId))
|
||||||
|
{
|
||||||
|
excludedStateEffectIds.Add(optionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
EpinelPS/LobbyServer/Inventory/equipment/ChangeOption.cs
Normal file
117
EpinelPS/LobbyServer/Inventory/equipment/ChangeOption.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
|
{
|
||||||
|
[PacketPath("/inventory/equipment/changeoption")]
|
||||||
|
public class ChangeOption : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
ReqAwakeningChangeOption req = await ReadData<ReqAwakeningChangeOption>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResAwakeningChangeOption response = new ResAwakeningChangeOption();
|
||||||
|
|
||||||
|
if (req.Isn <= 0)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EquipmentAwakeningData? oldAwakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn && !x.IsNewData);
|
||||||
|
|
||||||
|
if (oldAwakening == null)
|
||||||
|
{
|
||||||
|
oldAwakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn);
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EquipmentAwakeningData> duplicates = user.EquipmentAwakenings.Where(x => x.Isn == req.Isn).ToList();
|
||||||
|
|
||||||
|
if (req.IsChanged)
|
||||||
|
{
|
||||||
|
if (duplicates.Count > 1)
|
||||||
|
{
|
||||||
|
List<EquipmentAwakeningData> oldEntries = duplicates.Where(x => !x.IsNewData).ToList();
|
||||||
|
foreach (EquipmentAwakeningData oldEntry in oldEntries)
|
||||||
|
{
|
||||||
|
user.EquipmentAwakenings.Remove(oldEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
EquipmentAwakeningData? newEntry = duplicates.FirstOrDefault(x => x.IsNewData);
|
||||||
|
if (newEntry != null)
|
||||||
|
{
|
||||||
|
newEntry.IsNewData = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (duplicates.Count == 1)
|
||||||
|
{
|
||||||
|
EquipmentAwakeningData singleEntry = duplicates[0];
|
||||||
|
if (singleEntry.IsNewData)
|
||||||
|
{
|
||||||
|
singleEntry.IsNewData = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EquipmentAwakeningData? confirmedAwakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn);
|
||||||
|
if (confirmedAwakening != null)
|
||||||
|
{
|
||||||
|
response.Awakening = new NetEquipmentAwakening()
|
||||||
|
{
|
||||||
|
Isn = confirmedAwakening.Isn,
|
||||||
|
Option = new NetEquipmentAwakeningOption()
|
||||||
|
{
|
||||||
|
Option1Id = confirmedAwakening.Option.Option1Id,
|
||||||
|
Option1Lock = confirmedAwakening.Option.Option1Lock,
|
||||||
|
IsOption1DisposableLock = confirmedAwakening.Option.IsOption1DisposableLock,
|
||||||
|
Option2Id = confirmedAwakening.Option.Option2Id,
|
||||||
|
Option2Lock = confirmedAwakening.Option.Option2Lock,
|
||||||
|
IsOption2DisposableLock = confirmedAwakening.Option.IsOption2DisposableLock,
|
||||||
|
Option3Id = confirmedAwakening.Option.Option3Id,
|
||||||
|
Option3Lock = confirmedAwakening.Option.Option3Lock,
|
||||||
|
IsOption3DisposableLock = confirmedAwakening.Option.IsOption3DisposableLock
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (duplicates.Count > 1)
|
||||||
|
{
|
||||||
|
List<EquipmentAwakeningData> newEntries = duplicates.Where(x => x.IsNewData).ToList();
|
||||||
|
foreach (EquipmentAwakeningData newEntry in newEntries)
|
||||||
|
{
|
||||||
|
user.EquipmentAwakenings.Remove(newEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EquipmentAwakeningData? originalAwakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn);
|
||||||
|
if (originalAwakening != null)
|
||||||
|
{
|
||||||
|
response.Awakening = new NetEquipmentAwakening()
|
||||||
|
{
|
||||||
|
Isn = originalAwakening.Isn,
|
||||||
|
Option = new NetEquipmentAwakeningOption()
|
||||||
|
{
|
||||||
|
Option1Id = originalAwakening.Option.Option1Id,
|
||||||
|
Option1Lock = originalAwakening.Option.Option1Lock,
|
||||||
|
IsOption1DisposableLock = originalAwakening.Option.IsOption1DisposableLock,
|
||||||
|
Option2Id = originalAwakening.Option.Option2Id,
|
||||||
|
Option2Lock = originalAwakening.Option.Option2Lock,
|
||||||
|
IsOption2DisposableLock = originalAwakening.Option.IsOption2DisposableLock,
|
||||||
|
Option3Id = originalAwakening.Option.Option3Id,
|
||||||
|
Option3Lock = originalAwakening.Option.Option3Lock,
|
||||||
|
IsOption3DisposableLock = originalAwakening.Option.IsOption3DisposableLock
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
174
EpinelPS/LobbyServer/Inventory/equipment/GetAwakeningDetail.cs
Normal file
174
EpinelPS/LobbyServer/Inventory/equipment/GetAwakeningDetail.cs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
|
{
|
||||||
|
[PacketPath("/inventory/equipment/getawakeningdetail")]
|
||||||
|
public class GetAwakeningDetail : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
ReqGetAwakeningDetail req = await ReadData<ReqGetAwakeningDetail>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResGetAwakeningDetail response = new ResGetAwakeningDetail();
|
||||||
|
|
||||||
|
// Validate input parameters
|
||||||
|
if (req.Isn <= 0)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the equipment awakening data
|
||||||
|
EquipmentAwakeningData? awakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn);
|
||||||
|
if (awakening == null)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set current options
|
||||||
|
response.CurrentOption = new NetEquipmentAwakeningOption()
|
||||||
|
{
|
||||||
|
Option1Id = awakening.Option.Option1Id,
|
||||||
|
Option1Lock = awakening.Option.Option1Lock,
|
||||||
|
IsOption1DisposableLock = awakening.Option.IsOption1DisposableLock,
|
||||||
|
Option2Id = awakening.Option.Option2Id,
|
||||||
|
Option2Lock = awakening.Option.Option2Lock,
|
||||||
|
IsOption2DisposableLock = awakening.Option.IsOption2DisposableLock,
|
||||||
|
Option3Id = awakening.Option.Option3Id,
|
||||||
|
Option3Lock = awakening.Option.Option3Lock,
|
||||||
|
IsOption3DisposableLock = awakening.Option.IsOption3DisposableLock
|
||||||
|
};
|
||||||
|
|
||||||
|
NetEquipmentAwakeningOption newOption = new NetEquipmentAwakeningOption();
|
||||||
|
|
||||||
|
// Process each option slot (1, 2, 3)
|
||||||
|
for (int i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
// Get current option ID for this slot
|
||||||
|
int currentOptionId = GetOptionIdForSlot(awakening.Option, i);
|
||||||
|
bool isLocked = IsOptionLocked(awakening.Option, i);
|
||||||
|
bool isDisposableLocked = IsOptionDisposableLocked(awakening.Option, i);
|
||||||
|
|
||||||
|
// If option is permanently locked or disposable locked, keep it unchanged
|
||||||
|
if (isLocked || isDisposableLocked)
|
||||||
|
{
|
||||||
|
// Keep the current option unchanged
|
||||||
|
SetOptionForSlot(newOption, i, currentOptionId, isLocked, isDisposableLocked);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not locked, generate a new option
|
||||||
|
int newOptionId = GenerateNewOptionId(currentOptionId);
|
||||||
|
SetOptionForSlot(newOption, i, newOptionId, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.NewOption = newOption;
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetOptionIdForSlot(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.Option1Id,
|
||||||
|
2 => option.Option2Id,
|
||||||
|
3 => option.Option3Id,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsOptionLocked(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.Option1Lock,
|
||||||
|
2 => option.Option2Lock,
|
||||||
|
3 => option.Option3Lock,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsOptionDisposableLocked(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.IsOption1DisposableLock,
|
||||||
|
2 => option.IsOption2DisposableLock,
|
||||||
|
3 => option.IsOption3DisposableLock,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetOptionForSlot(NetEquipmentAwakeningOption option, int slot, int optionId, bool locked, bool disposableLocked)
|
||||||
|
{
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
option.Option1Id = optionId;
|
||||||
|
option.Option1Lock = locked;
|
||||||
|
option.IsOption1DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
option.Option2Id = optionId;
|
||||||
|
option.Option2Lock = locked;
|
||||||
|
option.IsOption2DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
option.Option3Id = optionId;
|
||||||
|
option.Option3Lock = locked;
|
||||||
|
option.IsOption3DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GenerateNewOptionId(int currentOptionId)
|
||||||
|
{
|
||||||
|
// Get the current option record
|
||||||
|
if (!GameData.Instance.EquipmentOptionTable.TryGetValue(currentOptionId, out EquipmentOptionRecord? currentOption))
|
||||||
|
{
|
||||||
|
return currentOptionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the group ID of the current option
|
||||||
|
int groupId = currentOption.EquipmentOptionGroupId;
|
||||||
|
|
||||||
|
// Find all options in the same group
|
||||||
|
List<EquipmentOptionRecord> optionsInGroup = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.Where(x => x.EquipmentOptionGroupId == groupId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (optionsInGroup.Count == 0)
|
||||||
|
{
|
||||||
|
return currentOptionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total ratio for probability calculation
|
||||||
|
long totalRatio = optionsInGroup.Sum(x => (long)x.OptionRatio);
|
||||||
|
|
||||||
|
if (totalRatio == 0)
|
||||||
|
{
|
||||||
|
return currentOptionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select a new option based on probability
|
||||||
|
Random random = new Random();
|
||||||
|
long randomValue = random.NextInt64(0, totalRatio);
|
||||||
|
long cumulativeRatio = 0;
|
||||||
|
|
||||||
|
foreach (EquipmentOptionRecord option in optionsInGroup)
|
||||||
|
{
|
||||||
|
cumulativeRatio += option.OptionRatio;
|
||||||
|
if (randomValue < cumulativeRatio)
|
||||||
|
{
|
||||||
|
return option.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentOptionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
EpinelPS/LobbyServer/Inventory/equipment/LockOption.cs
Normal file
137
EpinelPS/LobbyServer/Inventory/equipment/LockOption.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
|
{
|
||||||
|
[PacketPath("/inventory/equipment/lockoption")]
|
||||||
|
public class LockOption : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
ReqAwakeningLockOption req = await ReadData<ReqAwakeningLockOption>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResAwakeningLockOption response = new ResAwakeningLockOption();
|
||||||
|
EquipmentAwakeningData? awakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn);
|
||||||
|
if (awakening == null)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int slot = 0;
|
||||||
|
if (awakening.Option.Option1Id == req.OptionId)
|
||||||
|
slot = 1;
|
||||||
|
else if (awakening.Option.Option2Id == req.OptionId)
|
||||||
|
slot = 2;
|
||||||
|
else if (awakening.Option.Option3Id == req.OptionId)
|
||||||
|
slot = 3;
|
||||||
|
|
||||||
|
(int materialId, int materialCost) = GetMaterialInfoForAwakening(awakening.Option);
|
||||||
|
|
||||||
|
|
||||||
|
ItemData? material = user.Items.FirstOrDefault(x => x.ItemType == materialId);
|
||||||
|
if (material == null || material.Count < materialCost)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateLockStatus(awakening.Option, slot, req.IsLocked);
|
||||||
|
|
||||||
|
if (req.IsLocked)
|
||||||
|
{
|
||||||
|
if (!EquipmentUtils.DeductMaterials(material, materialCost, user, response.Items))
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CalculateMaterialCost(NetEquipmentAwakeningOption option)
|
||||||
|
{
|
||||||
|
int lockedOptionCount = 0;
|
||||||
|
int disposableLockOptionCount = 0;
|
||||||
|
// Count already permanently locked options (not disposable locks)
|
||||||
|
if (option.Option1Id != 0 && option.Option1Lock && !option.IsOption1DisposableLock)
|
||||||
|
lockedOptionCount++;
|
||||||
|
if (option.Option2Id != 0 && option.Option2Lock && !option.IsOption2DisposableLock)
|
||||||
|
lockedOptionCount++;
|
||||||
|
if (option.Option3Id != 0 && option.Option3Lock && !option.IsOption3DisposableLock)
|
||||||
|
lockedOptionCount++;
|
||||||
|
|
||||||
|
if (option.Option1Id != 0 && option.Option1Lock && option.IsOption1DisposableLock)
|
||||||
|
disposableLockOptionCount++;
|
||||||
|
if (option.Option2Id != 0 && option.Option2Lock && option.IsOption2DisposableLock)
|
||||||
|
disposableLockOptionCount++;
|
||||||
|
if (option.Option3Id != 0 && option.Option3Lock && option.IsOption3DisposableLock)
|
||||||
|
disposableLockOptionCount++;
|
||||||
|
|
||||||
|
return GetPermanentLockCostId(lockedOptionCount,disposableLockOptionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetPermanentLockCostId(int lockedOptionCount,int disposableLockOptionCount)
|
||||||
|
{
|
||||||
|
// For permanent locks, use cost_group_id 100
|
||||||
|
EquipmentOptionCostRecord? costRecord = GameData.Instance.EquipmentOptionCostTable.Values
|
||||||
|
.FirstOrDefault(x => x.CostGroupId == 100 && x.CostLevel == lockedOptionCount && x.DisposableFixCostLevel == disposableLockOptionCount);
|
||||||
|
|
||||||
|
int costId = costRecord?.CostId ?? 101001;
|
||||||
|
|
||||||
|
return costId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateLockStatus(NetEquipmentAwakeningOption option, int slot, bool isLocked)
|
||||||
|
{
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
option.Option1Lock = isLocked;
|
||||||
|
if (isLocked)
|
||||||
|
{
|
||||||
|
option.IsOption1DisposableLock = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
option.Option2Lock = isLocked;
|
||||||
|
if (isLocked)
|
||||||
|
{
|
||||||
|
option.IsOption2DisposableLock = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
option.Option3Lock = isLocked;
|
||||||
|
if (isLocked)
|
||||||
|
{
|
||||||
|
option.IsOption3DisposableLock = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int materialId, int materialCost) GetMaterialInfoForAwakening(NetEquipmentAwakeningOption option)
|
||||||
|
{
|
||||||
|
int costId = CalculateMaterialCost(option);
|
||||||
|
return GetMaterialInfo(costId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int materialId, int materialCost) GetMaterialInfo(int costId)
|
||||||
|
{
|
||||||
|
if (GameData.Instance.costTable.TryGetValue(costId, out CostRecord? costRecord) &&
|
||||||
|
costRecord?.Costs != null &&
|
||||||
|
costRecord.Costs.Count > 0)
|
||||||
|
{
|
||||||
|
return (costRecord.Costs[0].ItemId, costRecord.Costs[0].ItemValue);
|
||||||
|
}
|
||||||
|
return (7080001, 2); // Default material ID and cost
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
475
EpinelPS/LobbyServer/Inventory/equipment/ResetOption.cs
Normal file
475
EpinelPS/LobbyServer/Inventory/equipment/ResetOption.cs
Normal file
@@ -0,0 +1,475 @@
|
|||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
|
{
|
||||||
|
[PacketPath("/inventory/equipment/resetoption")]
|
||||||
|
public class ResetOption : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
ReqAwakeningResetOption req = await ReadData<ReqAwakeningResetOption>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResAwakeningResetOption response = new ResAwakeningResetOption();
|
||||||
|
|
||||||
|
EquipmentAwakeningData? awakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn);
|
||||||
|
if (awakening == null)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetEquipmentAwakeningOption resetOption = new NetEquipmentAwakeningOption();
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
(int optionId, bool isLocked, bool isDisposableLocked)[] slotLockInfo = new (int optionId, bool isLocked, bool isDisposableLocked)[3];
|
||||||
|
List<int> lockedOptionStateEffectIds = new List<int>();
|
||||||
|
|
||||||
|
int lockedOptionCount = 0;
|
||||||
|
for (int i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
int currentOptionId = GetOptionIdForSlot(awakening.Option, i);
|
||||||
|
bool isLocked = IsOptionLocked(awakening.Option, i);
|
||||||
|
bool isDisposableLocked = IsOptionDisposableLocked(awakening.Option, i);
|
||||||
|
|
||||||
|
slotLockInfo[i - 1] = (currentOptionId, isLocked, isDisposableLocked);
|
||||||
|
|
||||||
|
// Count locked options for material cost calculation
|
||||||
|
if (isLocked || isDisposableLocked)
|
||||||
|
lockedOptionCount++;
|
||||||
|
|
||||||
|
// Collect locked options for exclusion list
|
||||||
|
if (isLocked && currentOptionId != 0)
|
||||||
|
{
|
||||||
|
AddOptionToExclusionList(currentOptionId, lockedOptionStateEffectIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int costId = GetCostIdByLockedOptionCount(lockedOptionCount);
|
||||||
|
|
||||||
|
(int materialId, int materialCost) = GetMaterialInfo(costId);
|
||||||
|
|
||||||
|
// Check if user has enough materials
|
||||||
|
ItemData? material = user.Items.FirstOrDefault(x => x.ItemType == materialId);
|
||||||
|
if (material == null || material.Count < materialCost)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Insufficient materials for reset operation. Need {materialCost} of item {materialId}, but have {material?.Count ?? 0}", LogType.Warning);
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduct materials for reset
|
||||||
|
if (!EquipmentUtils.DeductMaterials(material, materialCost, user, response.Items))
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Insufficient materials for reset operation. Need {materialCost} of item {materialId}, but have {material?.Count ?? 0}", LogType.Warning);
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each option slot (1, 2, 3)
|
||||||
|
ProcessOptionSlots(awakening, resetOption, slotLockInfo, lockedOptionStateEffectIds);
|
||||||
|
|
||||||
|
// Create a new awakening entry with the same ISN to preserve the old data
|
||||||
|
EquipmentAwakeningData newAwakening = new EquipmentAwakeningData()
|
||||||
|
{
|
||||||
|
Isn = awakening.Isn,
|
||||||
|
Option = new NetEquipmentAwakeningOption()
|
||||||
|
{
|
||||||
|
Option1Id = resetOption.Option1Id,
|
||||||
|
Option1Lock = resetOption.Option1Lock,
|
||||||
|
IsOption1DisposableLock = resetOption.IsOption1DisposableLock,
|
||||||
|
Option2Id = resetOption.Option2Id,
|
||||||
|
Option2Lock = resetOption.Option2Lock,
|
||||||
|
IsOption2DisposableLock = resetOption.IsOption2DisposableLock,
|
||||||
|
Option3Id = resetOption.Option3Id,
|
||||||
|
Option3Lock = resetOption.Option3Lock,
|
||||||
|
IsOption3DisposableLock = resetOption.IsOption3DisposableLock
|
||||||
|
},
|
||||||
|
IsNewData = true
|
||||||
|
};
|
||||||
|
|
||||||
|
user.EquipmentAwakenings.Add(newAwakening);
|
||||||
|
|
||||||
|
// Add the reset options to the response
|
||||||
|
response.ResetOption = new NetEquipmentAwakeningOption()
|
||||||
|
{
|
||||||
|
Option1Id = resetOption.Option1Id,
|
||||||
|
Option1Lock = resetOption.Option1Lock,
|
||||||
|
IsOption1DisposableLock = resetOption.IsOption1DisposableLock,
|
||||||
|
Option2Id = resetOption.Option2Id,
|
||||||
|
Option2Lock = resetOption.Option2Lock,
|
||||||
|
IsOption2DisposableLock = resetOption.IsOption2DisposableLock,
|
||||||
|
Option3Id = resetOption.Option3Id,
|
||||||
|
Option3Lock = resetOption.Option3Lock,
|
||||||
|
IsOption3DisposableLock = resetOption.IsOption3DisposableLock
|
||||||
|
};
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ProcessOptionSlots(EquipmentAwakeningData awakening, NetEquipmentAwakeningOption resetOption, (int optionId, bool isLocked, bool isDisposableLocked)[] slotLockInfo, List<int> lockedOptionStateEffectIds)
|
||||||
|
{
|
||||||
|
Random random = new Random();
|
||||||
|
// 1.0 = 100% chance for slot 1, 0.5 = 50% chance for slot 2, 0.3 = 30% chance for slot 3
|
||||||
|
double[] slotActivationProbabilities = { 1.0, 0.5, 0.3 };
|
||||||
|
|
||||||
|
for (int i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
(int currentOptionId, bool isLocked, bool isDisposableLocked) = slotLockInfo[i - 1];
|
||||||
|
|
||||||
|
if (isLocked && !isDisposableLocked)
|
||||||
|
{
|
||||||
|
SetOptionForSlot(resetOption, i, currentOptionId, true, false);
|
||||||
|
}
|
||||||
|
else if (isLocked && isDisposableLocked)
|
||||||
|
{
|
||||||
|
SetOptionForSlot(resetOption, i, currentOptionId, false, false);
|
||||||
|
|
||||||
|
UnlockDisposableOption(awakening.Option, i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool shouldActivateSlot = random.NextDouble() < slotActivationProbabilities[i - 1];
|
||||||
|
|
||||||
|
if (shouldActivateSlot)
|
||||||
|
{
|
||||||
|
// Generate new option using non-repeating system with dynamic probability
|
||||||
|
int newOptionId = GenerateNewOptionIdWithDynamicProbability(lockedOptionStateEffectIds);
|
||||||
|
SetOptionForSlot(resetOption, i, newOptionId, false, false);
|
||||||
|
|
||||||
|
// Add the new option to locked list to prevent duplicates in subsequent slots
|
||||||
|
if (newOptionId != 0)
|
||||||
|
{
|
||||||
|
AddOptionToExclusionList(newOptionId, lockedOptionStateEffectIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetOptionForSlot(resetOption, i, 0, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnlockDisposableOption(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
SetOptionForSlot(option, slot, GetOptionIdForSlot(option, slot), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetCostIdByLockedOptionCount(int lockedOptionCount)
|
||||||
|
{
|
||||||
|
if (lockedOptionCount == 0)
|
||||||
|
{
|
||||||
|
return 100001;
|
||||||
|
}
|
||||||
|
|
||||||
|
EquipmentOptionCostRecord? costRecord = GameData.Instance.EquipmentOptionCostTable.Values
|
||||||
|
.FirstOrDefault(x => x.CostGroupId == 200 && x.CostLevel == lockedOptionCount);
|
||||||
|
|
||||||
|
return costRecord?.CostId ?? 100001;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int GetOptionIdForSlot(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.Option1Id,
|
||||||
|
2 => option.Option2Id,
|
||||||
|
3 => option.Option3Id,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool IsOptionLocked(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.Option1Lock,
|
||||||
|
2 => option.Option2Lock,
|
||||||
|
3 => option.Option3Lock,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsOptionDisposableLocked(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.IsOption1DisposableLock,
|
||||||
|
2 => option.IsOption2DisposableLock,
|
||||||
|
3 => option.IsOption3DisposableLock,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetOptionForSlot(NetEquipmentAwakeningOption option, int slot, int optionId, bool locked, bool disposableLocked)
|
||||||
|
{
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
option.Option1Id = optionId;
|
||||||
|
option.Option1Lock = locked;
|
||||||
|
option.IsOption1DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
option.Option2Id = optionId;
|
||||||
|
option.Option2Lock = locked;
|
||||||
|
option.IsOption2DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
option.Option3Id = optionId;
|
||||||
|
option.Option3Lock = locked;
|
||||||
|
option.IsOption3DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddOptionToExclusionList(int optionId, List<int> lockedOptionStateEffectIds)
|
||||||
|
{
|
||||||
|
// Since optionId is already a state_effect_id, we can directly add it to the exclusion list
|
||||||
|
if (!lockedOptionStateEffectIds.Contains(optionId))
|
||||||
|
{
|
||||||
|
lockedOptionStateEffectIds.Add(optionId);
|
||||||
|
|
||||||
|
// Also get the effect group ID for this state_effect_id to exclude the effect group
|
||||||
|
EquipmentOptionRecord? optionRecord = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.FirstOrDefault(opt => opt.StateEffectList != null && opt.StateEffectList.Any(se => se.StateEffectId == optionId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a new option ID using the Overload system's non-repeating effect types and dynamic probability formula
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="excludedStateEffectIds">List of state_effect_ids that are already taken and should be excluded</param>
|
||||||
|
/// <returns>A new state_effect_id or 0 if none available</returns>
|
||||||
|
private int GenerateNewOptionIdWithDynamicProbability(List<int> excludedStateEffectIds)
|
||||||
|
{
|
||||||
|
// Get all awakening options (equipment_option_group_id == 100000)
|
||||||
|
List<EquipmentOptionRecord> allAwakeningOptions = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.Where(x => x.EquipmentOptionGroupId == 100000)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Filter out options that have already been taken (non-repeating principle)
|
||||||
|
HashSet<int> excludedEffectGroupIds = new HashSet<int>();
|
||||||
|
|
||||||
|
// Get the effect group IDs for all excluded state effect IDs
|
||||||
|
foreach (int stateEffectId in excludedStateEffectIds)
|
||||||
|
{
|
||||||
|
EquipmentOptionRecord? excludedOption = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.FirstOrDefault(opt => opt.StateEffectList != null && opt.StateEffectList.Any(se => se.StateEffectId == stateEffectId));
|
||||||
|
if (excludedOption != null)
|
||||||
|
{
|
||||||
|
excludedEffectGroupIds.Add(excludedOption.StateEffectGroupId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EquipmentOptionRecord> availableOptions = allAwakeningOptions
|
||||||
|
.Where(option => !excludedEffectGroupIds.Contains(option.StateEffectGroupId))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Dictionary<int, List<EquipmentOptionRecord>> optionsByEffectGroup = availableOptions
|
||||||
|
.GroupBy(option => option.StateEffectGroupId)
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
|
double excludedProbabilitySum = CalculateExcludedProbabilitySumByEffectGroup(excludedEffectGroupIds);
|
||||||
|
|
||||||
|
List<EffectGroupWithWeight> weightedEffectGroups = CalculateDynamicProbabilitiesForEffectGroups(optionsByEffectGroup, excludedProbabilitySum);
|
||||||
|
int selectedEffectGroupId = SelectWeightedRandomEffectGroup(weightedEffectGroups);
|
||||||
|
|
||||||
|
List<EquipmentOptionRecord> optionsInSelectedGroup = optionsByEffectGroup[selectedEffectGroupId];
|
||||||
|
int selectedStateEffectId = SelectOptionFromGroup(optionsInSelectedGroup);
|
||||||
|
return selectedStateEffectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to store effect group with its calculated weight for probability selection
|
||||||
|
/// </summary>
|
||||||
|
public class EffectGroupWithWeight
|
||||||
|
{
|
||||||
|
public int EffectGroupId { get; set; }
|
||||||
|
public double Weight { get; set; }
|
||||||
|
public double BaseProbability { get; set; }
|
||||||
|
public double DynamicProbability { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the sum of base probabilities for excluded effect groups according to the Overload system rules
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="excludedEffectGroupIds">List of excluded state_effect_group_ids</param>
|
||||||
|
/// <returns>Sum of base probabilities (as decimal percentage)</returns>
|
||||||
|
private double CalculateExcludedProbabilitySumByEffectGroup(HashSet<int> excludedEffectGroupIds)
|
||||||
|
{
|
||||||
|
if (excludedEffectGroupIds.Count == 0)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double totalExcluded = 0.0;
|
||||||
|
|
||||||
|
foreach (int effectGroupId in excludedEffectGroupIds)
|
||||||
|
{
|
||||||
|
List<EquipmentOptionRecord> options = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.Where(opt => opt.StateEffectGroupId == effectGroupId && opt.EquipmentOptionGroupId == 100000)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (options.Count > 0)
|
||||||
|
{
|
||||||
|
EquipmentOptionRecord firstOption = options.First();
|
||||||
|
totalExcluded += firstOption.OptionGroupRatio / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap the excluded probability sum to maintain mathematical validity
|
||||||
|
return Math.Min(totalExcluded, 99.9); // Ensure we don't reach 100% to avoid division by zero
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates dynamic probabilities for available effect groups using the formula:
|
||||||
|
/// Dynamic Probability = Display Probability / (100% - Sum of Excluded Probabilities)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionsByEffectGroup">Dictionary of available options grouped by effect group ID</param>
|
||||||
|
/// <param name="excludedProbabilitySum">Sum of probabilities of excluded effects</param>
|
||||||
|
/// <returns>List of weighted effect groups for random selection</returns>
|
||||||
|
private List<EffectGroupWithWeight> CalculateDynamicProbabilitiesForEffectGroups(Dictionary<int, List<EquipmentOptionRecord>> optionsByEffectGroup, double excludedProbabilitySum)
|
||||||
|
{
|
||||||
|
List<EffectGroupWithWeight> weightedEffectGroups = new List<EffectGroupWithWeight>();
|
||||||
|
|
||||||
|
double probabilityDenominator = 100.0 - excludedProbabilitySum;
|
||||||
|
|
||||||
|
// Prevent division by zero or negative values (shouldn't happen due to capping in CalculateExcludedProbabilitySumByEffectGroup, but let's be safe)
|
||||||
|
if (probabilityDenominator <= 0)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Warning: probabilityDenominator is {probabilityDenominator}, using uniform distribution", LogType.Warning);
|
||||||
|
probabilityDenominator = 1.0; // Use uniform distribution as fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (KeyValuePair<int, List<EquipmentOptionRecord>> kvp in optionsByEffectGroup)
|
||||||
|
{
|
||||||
|
int effectGroupId = kvp.Key;
|
||||||
|
List<EquipmentOptionRecord> options = kvp.Value;
|
||||||
|
|
||||||
|
if (options.Count > 0)
|
||||||
|
{
|
||||||
|
EquipmentOptionRecord firstOption = options.First();
|
||||||
|
double baseProbability = firstOption.OptionGroupRatio / 100.0;
|
||||||
|
|
||||||
|
double dynamicProbability = baseProbability / probabilityDenominator;
|
||||||
|
|
||||||
|
double selectionWeight = dynamicProbability * 1000000;
|
||||||
|
|
||||||
|
weightedEffectGroups.Add(new EffectGroupWithWeight
|
||||||
|
{
|
||||||
|
EffectGroupId = effectGroupId,
|
||||||
|
Weight = selectionWeight,
|
||||||
|
BaseProbability = baseProbability,
|
||||||
|
DynamicProbability = dynamicProbability
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weightedEffectGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects an effect group randomly based on calculated weights
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="weightedEffectGroups">List of weighted effect groups</param>
|
||||||
|
/// <returns>Selected effect_group_id or 0 if none available</returns>
|
||||||
|
private int SelectWeightedRandomEffectGroup(List<EffectGroupWithWeight> weightedEffectGroups)
|
||||||
|
{
|
||||||
|
// Safety check to prevent data corruption
|
||||||
|
if (weightedEffectGroups == null || weightedEffectGroups.Count == 0)
|
||||||
|
throw new InvalidOperationException("No weighted effect groups available - this indicates a data consistency issue");
|
||||||
|
|
||||||
|
Random random = new Random();
|
||||||
|
double totalWeight = weightedEffectGroups.Sum(weg => weg.Weight);
|
||||||
|
|
||||||
|
// Prevent division by zero which could cause unexpected behavior
|
||||||
|
if (totalWeight <= 0)
|
||||||
|
throw new InvalidOperationException("Invalid group weights - this indicates a data consistency issue");
|
||||||
|
|
||||||
|
double randomValue = random.NextDouble() * totalWeight;
|
||||||
|
|
||||||
|
double cumulativeWeight = 0.0;
|
||||||
|
foreach (EffectGroupWithWeight weightedGroup in weightedEffectGroups)
|
||||||
|
{
|
||||||
|
cumulativeWeight += weightedGroup.Weight;
|
||||||
|
if (randomValue <= cumulativeWeight)
|
||||||
|
{
|
||||||
|
return weightedGroup.EffectGroupId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weightedEffectGroups.Last().EffectGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Random _random = new Random();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects an option from an effect group based on option_ratio weights and returns a state_effect_id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">List of options in the effect group</param>
|
||||||
|
/// <returns>Selected state_effect_id</returns>
|
||||||
|
private int SelectOptionFromGroup(List<EquipmentOptionRecord> options)
|
||||||
|
{
|
||||||
|
// Safety check to prevent data corruption
|
||||||
|
if (options == null || options.Count == 0)
|
||||||
|
throw new InvalidOperationException("No options available in group - this indicates a data consistency issue");
|
||||||
|
|
||||||
|
long totalRatio = options.Sum(x => (long)x.OptionRatio);
|
||||||
|
|
||||||
|
long randomValue = _random.NextInt64(0, totalRatio);
|
||||||
|
long cumulativeRatio = 0;
|
||||||
|
|
||||||
|
foreach (EquipmentOptionRecord? option in options)
|
||||||
|
{
|
||||||
|
cumulativeRatio += option.OptionRatio;
|
||||||
|
if (randomValue < cumulativeRatio)
|
||||||
|
{
|
||||||
|
// Randomly select from the StateEffectList
|
||||||
|
if (option.StateEffectList == null || option.StateEffectList.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"StateEffectList is null or empty for option {option.Id}");
|
||||||
|
}
|
||||||
|
int randomIndex = _random.Next(option.StateEffectList.Count);
|
||||||
|
return option.StateEffectList[randomIndex].StateEffectId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: randomly select from the StateEffectList of the last option
|
||||||
|
EquipmentOptionRecord? lastOption = options.Last();
|
||||||
|
if (lastOption?.StateEffectList == null || lastOption.StateEffectList.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"StateEffectList is null or empty for fallback option {lastOption?.Id}");
|
||||||
|
}
|
||||||
|
int fallbackIndex = _random.Next(lastOption.StateEffectList.Count);
|
||||||
|
return lastOption.StateEffectList[fallbackIndex].StateEffectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int materialId, int materialCost) GetMaterialInfo(int costId)
|
||||||
|
{
|
||||||
|
if (GameData.Instance.costTable.TryGetValue(costId, out CostRecord? costRecord) &&
|
||||||
|
costRecord?.Costs != null &&
|
||||||
|
costRecord.Costs.Count > 0)
|
||||||
|
{
|
||||||
|
return (costRecord.Costs[0].ItemId, costRecord.Costs[0].ItemValue);
|
||||||
|
}
|
||||||
|
return (7080001, 1); // Default material ID and cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
286
EpinelPS/LobbyServer/Inventory/equipment/UpgradeOption.cs
Normal file
286
EpinelPS/LobbyServer/Inventory/equipment/UpgradeOption.cs
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
|
{
|
||||||
|
[PacketPath("/inventory/equipment/upgradeoption")]
|
||||||
|
public class UpgradeOption : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
ReqAwakeningUpgradeOption req = await ReadData<ReqAwakeningUpgradeOption>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResAwakeningUpgradeOption response = new ResAwakeningUpgradeOption();
|
||||||
|
|
||||||
|
// Validate input parameters
|
||||||
|
if (req.Isn <= 0)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Invalid ISN: {req.Isn}", LogType.Warning);
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the equipment awakening data (prefer old data over new data)
|
||||||
|
EquipmentAwakeningData? awakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn && !x.IsNewData);
|
||||||
|
if (awakening == null)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetEquipmentAwakeningOption newOption = new NetEquipmentAwakeningOption();
|
||||||
|
|
||||||
|
(int optionId, bool isLocked, bool isDisposableLocked)[] slotLockInfo = new (int optionId, bool isLocked, bool isDisposableLocked)[3];
|
||||||
|
|
||||||
|
int lockedOptionCount = 0;
|
||||||
|
for (int i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
int currentOptionId = GetOptionIdForSlot(awakening.Option, i);
|
||||||
|
bool isLocked = IsOptionLocked(awakening.Option, i);
|
||||||
|
bool isDisposableLocked = IsOptionDisposableLocked(awakening.Option, i);
|
||||||
|
|
||||||
|
slotLockInfo[i - 1] = (currentOptionId, isLocked, isDisposableLocked);
|
||||||
|
|
||||||
|
if (isLocked || isDisposableLocked)
|
||||||
|
lockedOptionCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cost ID for upgrade based on locked option count
|
||||||
|
int costId = GetUpgradeCostId(lockedOptionCount);
|
||||||
|
|
||||||
|
// Query actual material ID and cost from CostTable.json
|
||||||
|
(int materialId, int materialCost) = GetMaterialInfo(costId);
|
||||||
|
|
||||||
|
ItemData? material = user.Items.FirstOrDefault(x => x.ItemType == materialId);
|
||||||
|
if (material == null || material.Count < materialCost)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EquipmentUtils.DeductMaterials(material, materialCost, user, response.Items))
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each option slot (1, 2, 3)
|
||||||
|
ProcessOptionSlots(awakening, newOption, slotLockInfo);
|
||||||
|
|
||||||
|
// Create a new awakening entry with the same ISN to preserve the old data
|
||||||
|
EquipmentAwakeningData newAwakening = new EquipmentAwakeningData()
|
||||||
|
{
|
||||||
|
Isn = awakening.Isn,
|
||||||
|
Option = new NetEquipmentAwakeningOption()
|
||||||
|
{
|
||||||
|
Option1Id = newOption.Option1Id,
|
||||||
|
Option1Lock = newOption.Option1Lock,
|
||||||
|
IsOption1DisposableLock = newOption.IsOption1DisposableLock,
|
||||||
|
Option2Id = newOption.Option2Id,
|
||||||
|
Option2Lock = newOption.Option2Lock,
|
||||||
|
IsOption2DisposableLock = newOption.IsOption2DisposableLock,
|
||||||
|
Option3Id = newOption.Option3Id,
|
||||||
|
Option3Lock = newOption.Option3Lock,
|
||||||
|
IsOption3DisposableLock = newOption.IsOption3DisposableLock
|
||||||
|
},
|
||||||
|
IsNewData = true // newAwakening
|
||||||
|
};
|
||||||
|
|
||||||
|
user.EquipmentAwakenings.Add(newAwakening);
|
||||||
|
|
||||||
|
response.ResetOption = new NetEquipmentAwakeningOption()
|
||||||
|
{
|
||||||
|
Option1Id = newOption.Option1Id,
|
||||||
|
Option1Lock = newOption.Option1Lock,
|
||||||
|
IsOption1DisposableLock = newOption.IsOption1DisposableLock,
|
||||||
|
Option2Id = newOption.Option2Id,
|
||||||
|
Option2Lock = newOption.Option2Lock,
|
||||||
|
IsOption2DisposableLock = newOption.IsOption2DisposableLock,
|
||||||
|
Option3Id = newOption.Option3Id,
|
||||||
|
Option3Lock = newOption.Option3Lock,
|
||||||
|
IsOption3DisposableLock = newOption.IsOption3DisposableLock
|
||||||
|
};
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ProcessOptionSlots(EquipmentAwakeningData awakening, NetEquipmentAwakeningOption newOption, (int optionId, bool isLocked, bool isDisposableLocked)[] slotLockInfo)
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
(int currentOptionId, bool isLocked, bool isDisposableLocked) = slotLockInfo[i - 1];
|
||||||
|
|
||||||
|
if (isLocked && !isDisposableLocked)
|
||||||
|
{
|
||||||
|
SetOptionForSlot(newOption, i, currentOptionId, isLocked, isDisposableLocked);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDisposableLocked)
|
||||||
|
{
|
||||||
|
SetOptionForSlot(newOption, i, currentOptionId, false, false);
|
||||||
|
|
||||||
|
UnlockDisposableOption(awakening.Option, i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentOptionId == 0)
|
||||||
|
{
|
||||||
|
SetOptionForSlot(newOption, i, 0, false, false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newOptionId = GenerateNewOptionId(currentOptionId);
|
||||||
|
SetOptionForSlot(newOption, i, newOptionId, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnlockDisposableOption(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
SetOptionForSlot(option, slot, GetOptionIdForSlot(option, slot), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetOptionIdForSlot(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.Option1Id,
|
||||||
|
2 => option.Option2Id,
|
||||||
|
3 => option.Option3Id,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsOptionLocked(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.Option1Lock,
|
||||||
|
2 => option.Option2Lock,
|
||||||
|
3 => option.Option3Lock,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsOptionDisposableLocked(NetEquipmentAwakeningOption option, int slot)
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
1 => option.IsOption1DisposableLock,
|
||||||
|
2 => option.IsOption2DisposableLock,
|
||||||
|
3 => option.IsOption3DisposableLock,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetOptionForSlot(NetEquipmentAwakeningOption option, int slot, int optionId, bool locked, bool disposableLocked)
|
||||||
|
{
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
option.Option1Id = optionId;
|
||||||
|
option.Option1Lock = locked;
|
||||||
|
option.IsOption1DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
option.Option2Id = optionId;
|
||||||
|
option.Option2Lock = locked;
|
||||||
|
option.IsOption2DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
option.Option3Id = optionId;
|
||||||
|
option.Option3Lock = locked;
|
||||||
|
option.IsOption3DisposableLock = disposableLocked;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GenerateNewOptionId(int currentStateEffectId)
|
||||||
|
{
|
||||||
|
EquipmentOptionRecord? currentOption = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.FirstOrDefault(option => option.StateEffectList != null && option.StateEffectList.Any(se => se.StateEffectId == currentStateEffectId));
|
||||||
|
|
||||||
|
if (currentOption == null|| currentOption.EquipmentOptionGroupId != 100000)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Current state_effect_id {currentStateEffectId} not found in any EquipmentOption");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int stateEffectGroupId = currentOption.StateEffectGroupId;
|
||||||
|
|
||||||
|
List<EquipmentOptionRecord> optionsInGroup = GameData.Instance.EquipmentOptionTable.Values
|
||||||
|
.Where(x => x.EquipmentOptionGroupId == 100000 && x.StateEffectGroupId == stateEffectGroupId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (optionsInGroup.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No awakening options found with state_effect_group_id {stateEffectGroupId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return SelectOptionFromGroup(optionsInGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly Random _random = new Random();
|
||||||
|
|
||||||
|
private int SelectOptionFromGroup(List<EquipmentOptionRecord> options)
|
||||||
|
{
|
||||||
|
if (options == null || options.Count == 0)
|
||||||
|
throw new InvalidOperationException("No options available in group - this indicates a data consistency issue");
|
||||||
|
|
||||||
|
long totalRatio = options.Sum(x => (long)x.OptionRatio);
|
||||||
|
|
||||||
|
long randomValue = _random.NextInt64(0, totalRatio);
|
||||||
|
long cumulativeRatio = 0;
|
||||||
|
|
||||||
|
foreach (EquipmentOptionRecord option in options)
|
||||||
|
{
|
||||||
|
cumulativeRatio += option.OptionRatio;
|
||||||
|
if (randomValue < cumulativeRatio)
|
||||||
|
{
|
||||||
|
// Randomly select from the StateEffectList
|
||||||
|
if (option.StateEffectList == null || option.StateEffectList.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"StateEffectList is null or empty for option {option.Id}");
|
||||||
|
}
|
||||||
|
int randomIndex = _random.Next(option.StateEffectList.Count);
|
||||||
|
return option.StateEffectList[randomIndex].StateEffectId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: randomly select from the StateEffectList of the last option
|
||||||
|
EquipmentOptionRecord? lastOption = options.Last();
|
||||||
|
if (lastOption?.StateEffectList == null || lastOption.StateEffectList.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"StateEffectList is null or empty for fallback option {lastOption?.Id}");
|
||||||
|
}
|
||||||
|
int fallbackIndex = _random.Next(lastOption.StateEffectList.Count);
|
||||||
|
return lastOption.StateEffectList[fallbackIndex].StateEffectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetUpgradeCostId(int lockedOptionCount)
|
||||||
|
{
|
||||||
|
|
||||||
|
// For upgrade operation, use cost_group_id 200
|
||||||
|
EquipmentOptionCostRecord? costRecord = GameData.Instance.EquipmentOptionCostTable.Values
|
||||||
|
.FirstOrDefault(x => x.CostGroupId == 200 && x.CostLevel == lockedOptionCount);
|
||||||
|
|
||||||
|
return costRecord?.CostId ?? 102001;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int materialId, int materialCost) GetMaterialInfo(int costId)
|
||||||
|
{
|
||||||
|
if (GameData.Instance.costTable.TryGetValue(costId, out CostRecord? costRecord) &&
|
||||||
|
costRecord?.Costs != null &&
|
||||||
|
costRecord.Costs.Count > 0)
|
||||||
|
{
|
||||||
|
return (costRecord.Costs[0].ItemId, costRecord.Costs[0].ItemValue);
|
||||||
|
}
|
||||||
|
return (7080001, 1); // Default material ID and cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Inventory
|
||||||
|
{
|
||||||
|
[PacketPath("/inventory/equipment/lockoption/disposable")]
|
||||||
|
public class Disposable : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
ReqAwakeningDisposableLockOption req = await ReadData<ReqAwakeningDisposableLockOption>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResAwakeningDisposableLockOption response = new ResAwakeningDisposableLockOption();
|
||||||
|
// Find the equipment awakening data
|
||||||
|
EquipmentAwakeningData? awakening = user.EquipmentAwakenings.FirstOrDefault(x => x.Isn == req.Isn);
|
||||||
|
if (awakening == null)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int slot = 0;
|
||||||
|
if (awakening.Option.Option1Id == req.OptionId)
|
||||||
|
slot = 1;
|
||||||
|
else if (awakening.Option.Option2Id == req.OptionId)
|
||||||
|
slot = 2;
|
||||||
|
else if (awakening.Option.Option3Id == req.OptionId)
|
||||||
|
slot = 3;
|
||||||
|
|
||||||
|
|
||||||
|
(int materialId, int materialCost) = GetMaterialInfoForAwakening(awakening.Option);
|
||||||
|
|
||||||
|
ItemData? material = user.Items.FirstOrDefault(x => x.ItemType == materialId);
|
||||||
|
if (material == null || material.Count < materialCost)
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
if (req.IsLocked)
|
||||||
|
{
|
||||||
|
awakening.Option.Option1Lock = true;
|
||||||
|
awakening.Option.IsOption1DisposableLock = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
awakening.Option.Option1Lock = false;
|
||||||
|
awakening.Option.IsOption1DisposableLock = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (req.IsLocked)
|
||||||
|
{
|
||||||
|
awakening.Option.Option2Lock = true;
|
||||||
|
awakening.Option.IsOption2DisposableLock = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
awakening.Option.Option2Lock = false;
|
||||||
|
awakening.Option.IsOption2DisposableLock = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (req.IsLocked)
|
||||||
|
{
|
||||||
|
awakening.Option.Option3Lock = true;
|
||||||
|
awakening.Option.IsOption3DisposableLock = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
awakening.Option.Option3Lock = false;
|
||||||
|
awakening.Option.IsOption3DisposableLock = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.IsLocked)
|
||||||
|
{
|
||||||
|
if (!EquipmentUtils.DeductMaterials(material, materialCost, user, response.Items))
|
||||||
|
{
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
private static int CalculateMaterialCost(NetEquipmentAwakeningOption option)
|
||||||
|
{
|
||||||
|
int lockedOptionCount = 0;
|
||||||
|
int disposableLockOptionCount = 0;
|
||||||
|
if (option.Option1Id != 0 && option.Option1Lock && !option.IsOption1DisposableLock)
|
||||||
|
lockedOptionCount++;
|
||||||
|
if (option.Option2Id != 0 && option.Option2Lock && !option.IsOption2DisposableLock)
|
||||||
|
lockedOptionCount++;
|
||||||
|
if (option.Option3Id != 0 && option.Option3Lock && !option.IsOption3DisposableLock)
|
||||||
|
lockedOptionCount++;
|
||||||
|
|
||||||
|
if (option.Option1Id != 0 && option.Option1Lock && option.IsOption1DisposableLock)
|
||||||
|
disposableLockOptionCount++;
|
||||||
|
if (option.Option2Id != 0 && option.Option2Lock && option.IsOption2DisposableLock)
|
||||||
|
disposableLockOptionCount++;
|
||||||
|
if (option.Option3Id != 0 && option.Option3Lock && option.IsOption3DisposableLock)
|
||||||
|
disposableLockOptionCount++;
|
||||||
|
|
||||||
|
return GetDisposableFixCostIdByLevel(lockedOptionCount,disposableLockOptionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static int GetDisposableFixCostIdByLevel(int lockedOptionCount,int disposableLockOptionCount)
|
||||||
|
{
|
||||||
|
EquipmentOptionCostRecord? costRecord = GameData.Instance.EquipmentOptionCostTable.Values
|
||||||
|
.FirstOrDefault(x => x.CostGroupId == 100 && x.CostLevel == lockedOptionCount && x.DisposableFixCostLevel == disposableLockOptionCount);
|
||||||
|
|
||||||
|
return costRecord?.DisposableFixCostId ?? 101004;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int materialId, int materialCost) GetMaterialInfoForAwakening(NetEquipmentAwakeningOption option)
|
||||||
|
{
|
||||||
|
int costId = CalculateMaterialCost(option);
|
||||||
|
return GetMaterialInfo(costId);
|
||||||
|
}
|
||||||
|
private static (int materialId, int materialCost) GetMaterialInfo(int costId)
|
||||||
|
{
|
||||||
|
if (GameData.Instance.costTable.TryGetValue(costId, out CostRecord? costRecord) &&
|
||||||
|
costRecord?.Costs != null &&
|
||||||
|
costRecord.Costs.Count > 0)
|
||||||
|
{
|
||||||
|
return (costRecord.Costs[0].ItemId, costRecord.Costs[0].ItemValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (7080002, 20); // Default material ID and cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -65,6 +65,19 @@ namespace EpinelPS.Models
|
|||||||
// For harmony cubes that can be equipped to multiple characters
|
// For harmony cubes that can be equipped to multiple characters
|
||||||
public List<long> CsnList = [];
|
public List<long> CsnList = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class EquipmentAwakeningData
|
||||||
|
{
|
||||||
|
public long Isn;
|
||||||
|
public NetEquipmentAwakeningOption Option;
|
||||||
|
public bool IsNewData;
|
||||||
|
|
||||||
|
public EquipmentAwakeningData()
|
||||||
|
{
|
||||||
|
Option = new NetEquipmentAwakeningOption();
|
||||||
|
IsNewData = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
public class EventData
|
public class EventData
|
||||||
{
|
{
|
||||||
public List<string> CompletedScenarios = [];
|
public List<string> CompletedScenarios = [];
|
||||||
@@ -110,8 +123,8 @@ namespace EpinelPS.Models
|
|||||||
public int DailyMissionPoints;
|
public int DailyMissionPoints;
|
||||||
public SimroomData SimRoomData = new();
|
public SimroomData SimRoomData = new();
|
||||||
|
|
||||||
public bool UnlimitedCounseling = false;
|
|
||||||
public Dictionary<int, int> DailyCounselCount = [];
|
public Dictionary<int, int> DailyCounselCount = [];
|
||||||
|
|
||||||
}
|
}
|
||||||
public class WeeklyResetableData
|
public class WeeklyResetableData
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public class User
|
|||||||
public WeeklyResetableData WeeklyResetableData = new();
|
public WeeklyResetableData WeeklyResetableData = new();
|
||||||
public List<ItemData> Items = [];
|
public List<ItemData> Items = [];
|
||||||
public List<CharacterModel> Characters = [];
|
public List<CharacterModel> Characters = [];
|
||||||
|
public List<EquipmentAwakeningData> EquipmentAwakenings = [];
|
||||||
public long[] RepresentationTeamDataNew = [];
|
public long[] RepresentationTeamDataNew = [];
|
||||||
public Dictionary<int, ClearedTutorialData> ClearedTutorialData = [];
|
public Dictionary<int, ClearedTutorialData> ClearedTutorialData = [];
|
||||||
|
|
||||||
|
|||||||
37
EpinelPS/Utils/EquipmentUtils.cs
Normal file
37
EpinelPS/Utils/EquipmentUtils.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using EpinelPS.Data;
|
||||||
|
using EpinelPS.Database;
|
||||||
|
|
||||||
|
namespace EpinelPS.Utils
|
||||||
|
{
|
||||||
|
public class EquipmentUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Deducts materials from user's inventory and updates the response
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="material">The material item to deduct</param>
|
||||||
|
/// <param name="materialCost">Amount of material to deduct</param>
|
||||||
|
/// <param name="user">The user whose inventory to update</param>
|
||||||
|
/// <param name="responseItems">The response items list to update</param>
|
||||||
|
/// <returns>True if deduction was successful, false otherwise</returns>
|
||||||
|
public static bool DeductMaterials(ItemData material, int materialCost, User user, IList<NetUserItemData> responseItems)
|
||||||
|
{
|
||||||
|
if (material.Count < materialCost)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
material.Count -= materialCost;
|
||||||
|
if (material.Count <= 0)
|
||||||
|
{
|
||||||
|
user.Items.Remove(material);
|
||||||
|
NetUserItemData netItem = NetUtils.ToNet(material);
|
||||||
|
netItem.Count = 0;
|
||||||
|
responseItems.Add(netItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
responseItems.Add(NetUtils.ToNet(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,11 +9,11 @@ namespace EpinelPS.Utils
|
|||||||
{
|
{
|
||||||
InterceptionClearResult response = new();
|
InterceptionClearResult response = new();
|
||||||
|
|
||||||
if (type != 1 && type != 2) throw new Exception("unknown interception type");
|
//if (type != 1 && type != 2) throw new Exception("unknown interception type");
|
||||||
|
|
||||||
int conditionReward;
|
int conditionReward;
|
||||||
int percentRewardGroup;
|
int percentRewardGroup;
|
||||||
if (type == 0)
|
if (type == 0 || type == 1)
|
||||||
{
|
{
|
||||||
conditionReward = GameData.Instance.InterceptNormal[id].ConditionRewardGroup;
|
conditionReward = GameData.Instance.InterceptNormal[id].ConditionRewardGroup;
|
||||||
percentRewardGroup = GameData.Instance.InterceptNormal[id].PercentConditionRewardGroup;
|
percentRewardGroup = GameData.Instance.InterceptNormal[id].PercentConditionRewardGroup;
|
||||||
|
|||||||
@@ -119,57 +119,77 @@ namespace EpinelPS.Utils
|
|||||||
{
|
{
|
||||||
AddSingleCurrencyObject(user, ref ret, (CurrencyType)rewardId, rewardCount);
|
AddSingleCurrencyObject(user, ref ret, (CurrencyType)rewardId, rewardCount);
|
||||||
}
|
}
|
||||||
else if (rewardType == RewardType.Item ||
|
else if (rewardType == RewardType.Item ||rewardType.ToString().StartsWith("Equipment_"))
|
||||||
rewardType.ToString().StartsWith("Equipment_"))
|
|
||||||
{
|
{
|
||||||
// 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
|
int corpId = 0; // Default to 0 (None)
|
||||||
if (user.Items.Where(x => x.ItemType == rewardId && x.Level == 1).Any())
|
|
||||||
|
if (rewardType.ToString().StartsWith("Equipment_"))
|
||||||
{
|
{
|
||||||
ItemData? newItem = user.Items.Where(x => x.ItemType == rewardId && x.Level == 1).FirstOrDefault();
|
var corpSetting = GameData.Instance.ItemEquipCorpSettingTable.Values.FirstOrDefault(x => x.Key == rewardType);
|
||||||
if (newItem != null)
|
|
||||||
{
|
|
||||||
newItem.Count += rewardCount;
|
|
||||||
|
|
||||||
// Tell the client the reward and its amount
|
if (corpSetting != null)
|
||||||
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");
|
if (corpSetting.CorpType == CorporationType.RANDOM)
|
||||||
|
{
|
||||||
|
// Use weighted random selection - all corporations have equal chance
|
||||||
|
// Weights: MISSILIS(1)=20%, ELYSION(2)=20%, TETRA(3)=20%, PILGRIM(4)=20%, ABNORMAL(7)=20%
|
||||||
|
int[] corpIds = { 1, 2, 3, 4, 7 }; // All corporations have equal chance
|
||||||
|
corpId = corpIds[Rng.Next(0, corpIds.Length)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Directly use the CorpType enum value as integer
|
||||||
|
corpId = (int)corpSetting.CorpType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
int Id = user.GenerateUniqueItemId();
|
// Check if user already has said item. If it is level 1, increase item count.
|
||||||
user.Items.Add(new ItemData() { ItemType = rewardId, Isn = Id, Level = 1, Exp = 0, Count = rewardCount });
|
ItemData? existingItem = user.Items.FirstOrDefault(x => x.ItemType == rewardId && x.Level == 1 && x.Corp == corpId);
|
||||||
|
|
||||||
|
if (existingItem != null)
|
||||||
|
{
|
||||||
|
existingItem.Count += rewardCount;
|
||||||
|
|
||||||
|
// Tell the client the reward and its amount
|
||||||
ret.Item.Add(new NetItemData()
|
ret.Item.Add(new NetItemData()
|
||||||
{
|
{
|
||||||
Count = rewardCount,
|
Count = rewardCount,
|
||||||
Tid = rewardId,
|
Tid = rewardId,
|
||||||
//Isn = Id
|
Corporation = corpId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tell the client the new amount of this item (which is the same as user dId not have item previously)
|
// Tell the client the new amount of this item
|
||||||
ret.UserItems.Add(new NetUserItemData()
|
ret.UserItems.Add(new NetUserItemData()
|
||||||
{
|
{
|
||||||
Isn = Id,
|
Isn = existingItem.Isn,
|
||||||
|
Tid = existingItem.ItemType,
|
||||||
|
Count = existingItem.Count,
|
||||||
|
Corporation = existingItem.Corp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int id = user.GenerateUniqueItemId();
|
||||||
|
var newItem = new ItemData() { ItemType = rewardId, Isn = id, Level = 0, Exp = 0, Count = rewardCount, Corp = corpId };
|
||||||
|
user.Items.Add(newItem);
|
||||||
|
|
||||||
|
ret.Item.Add(new NetItemData()
|
||||||
|
{
|
||||||
|
Count = rewardCount,
|
||||||
Tid = rewardId,
|
Tid = rewardId,
|
||||||
Count = rewardCount
|
Corporation = corpId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tell the client the new amount of this item
|
||||||
|
ret.UserItems.Add(new NetUserItemData()
|
||||||
|
{
|
||||||
|
Isn = newItem.Isn,
|
||||||
|
Tid = newItem.ItemType,
|
||||||
|
Count = newItem.Count,
|
||||||
|
Corporation = newItem.Corp
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ namespace EpinelPS.Utils
|
|||||||
{
|
{
|
||||||
private static readonly Random random = new();
|
private static readonly Random random = new();
|
||||||
|
|
||||||
|
public static int Next(int minValue, int maxValue)
|
||||||
|
{
|
||||||
|
return random.Next(minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
public static string RandomString(int length)
|
public static string RandomString(int length)
|
||||||
{
|
{
|
||||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|||||||
Reference in New Issue
Block a user