mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-12 15:04:36 +01:00
Compare commits
36 Commits
2c1bdee666
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b8d58854f | ||
|
|
5f27e2ea44 | ||
|
|
55e5cdcdb8 | ||
|
|
a45dd2305e | ||
|
|
c96ad5fa94 | ||
|
|
91cbedf46c | ||
|
|
913c1a2262 | ||
|
|
fb062eefae | ||
|
|
bc7416c44a | ||
|
|
4325a91cea | ||
|
|
24f6de8704 | ||
|
|
6d44c6eac9 | ||
|
|
23afbabfae | ||
|
|
f4e2d82978 | ||
|
|
2b92e3191b | ||
|
|
663bf58549 | ||
|
|
aac1c00715 | ||
|
|
338a769ade | ||
|
|
d877488d1f | ||
|
|
d378b68611 | ||
|
|
3ffbfca0a3 | ||
|
|
2b19500b67 | ||
|
|
5dc9945100 | ||
|
|
93470b21a4 | ||
|
|
16bd4077dd | ||
|
|
f09d959220 | ||
|
|
795f18445c | ||
|
|
b8dc78e1c9 | ||
|
|
dd2760b0c6 | ||
|
|
0aed2aff6c | ||
|
|
2ee95caf32 | ||
|
|
9a8db1c143 | ||
|
|
eea01945f9 | ||
|
|
206fa429ee | ||
|
|
cbbefeb51a | ||
|
|
076f987681 |
@@ -2,3 +2,30 @@
|
||||
|
||||
# CA5397: Do not use deprecated SslProtocols values
|
||||
dotnet_diagnostic.CA5397.severity = none
|
||||
|
||||
# CS8602: 解引用可能出现空引用。
|
||||
dotnet_diagnostic.CS8602.severity = none
|
||||
|
||||
# CS8619: 值中的引用类型的为 Null 性与目标类型不匹配。
|
||||
dotnet_diagnostic.CS8619.severity = none
|
||||
|
||||
# CS8604: 引用类型参数可能为 null。
|
||||
dotnet_diagnostic.CS8604.severity = none
|
||||
|
||||
# CS8981: 该类型名称仅包含小写 ascii 字符。此类名称可能会成为该语言的保留值。
|
||||
dotnet_diagnostic.CS8981.severity = none
|
||||
|
||||
# IDE0120: 简化 LINQ 表达式
|
||||
dotnet_diagnostic.IDE0120.severity = none
|
||||
|
||||
# CS8603: 可能返回 null 引用。
|
||||
dotnet_diagnostic.CS8603.severity = none
|
||||
|
||||
# SYSLIB0039: 类型或成员已过时
|
||||
dotnet_diagnostic.SYSLIB0039.severity = none
|
||||
|
||||
# CS0219: 变量已被赋值,但从未使用过它的值
|
||||
dotnet_diagnostic.CS0219.severity = none
|
||||
|
||||
# CS8618: 类型不包含 null 值的属性
|
||||
dotnet_diagnostic.CS8618.severity = none
|
||||
44
.github/workflows/dotnet-desktop.yml
vendored
44
.github/workflows/dotnet-desktop.yml
vendored
@@ -18,15 +18,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install the .NET Core workload
|
||||
- name: Install .NET 9
|
||||
uses: actions/setup-dotnet@v4
|
||||
- name: Install .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
|
||||
- name: Setup MSBuild
|
||||
@@ -42,10 +42,44 @@ jobs:
|
||||
run: dotnet publish EpinelPS
|
||||
|
||||
- name: Copy to output
|
||||
run: echo ${{ github.workspace }} && md ${{ github.workspace }}/out/ && xcopy /s /e "${{ github.workspace }}\ServerSelector.Desktop\bin\Release\net9.0\win-x64\publish\" "${{ github.workspace }}\out\" && xcopy /s /e "${{ github.workspace }}\EpinelPS\bin\Release\net9.0\win-x64\publish\" "${{ github.workspace }}\out\" && copy "${{ github.workspace }}\ServerSelector.Desktop\sodium.dll" "${{ github.workspace }}\out\sodium.dll"
|
||||
run: echo ${{ github.workspace }} && md ${{ github.workspace }}/out/ && xcopy /s /e "${{ github.workspace }}\ServerSelector.Desktop\bin\Release\net10.0\win-x64\publish\" "${{ github.workspace }}\out\" && xcopy /s /e "${{ github.workspace }}\EpinelPS\bin\Release\net10.0\win-x64\publish\" "${{ github.workspace }}\out\" && copy "${{ github.workspace }}\ServerSelector.Desktop\sodium.dll" "${{ github.workspace }}\out\sodium.dll"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Server and Server selector
|
||||
path: ${{ github.workspace }}/out/
|
||||
serverOnly:
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Release]
|
||||
|
||||
runs-on: ubuntu-latest # For a list of available runner types, refer to
|
||||
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install the .NET Core workload
|
||||
- name: Install .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Restore packages
|
||||
run: dotnet restore
|
||||
|
||||
- name: Publish Server
|
||||
run: dotnet publish EpinelPS
|
||||
|
||||
- name: Copy to output
|
||||
run: echo ${{ github.workspace }} && cp -R "${{ github.workspace }}/EpinelPS/bin/Release/net10.0/linux-x64/publish/" "${{ github.workspace }}/out/"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: EpinelPS_linux_x64
|
||||
path: ${{ github.workspace }}/out/
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<AvaloniaVersion>11.0.2</AvaloniaVersion>
|
||||
<AvaloniaVersion>11.3.9</AvaloniaVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
@@ -22,9 +23,7 @@ namespace EpinelPS.Data
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Sha256Hash;
|
||||
public byte[] MpkHash = [];
|
||||
public int Size;
|
||||
public int MpkSize;
|
||||
|
||||
private ZipFile MainZip;
|
||||
@@ -32,9 +31,6 @@ namespace EpinelPS.Data
|
||||
private int totalFiles = 1;
|
||||
private int currentFile;
|
||||
|
||||
// TODO: all of the data types need to be changed to match the game
|
||||
private bool UseMemoryPack = true;
|
||||
|
||||
public readonly Dictionary<string, FieldMapRecord> MapData = [];
|
||||
|
||||
[LoadRecord("MainQuestTable.json", "Id")]
|
||||
@@ -52,6 +48,9 @@ namespace EpinelPS.Data
|
||||
[LoadRecord("CampaignChapterTable.json", "Chapter")]
|
||||
public readonly Dictionary<int, CampaignChapterRecord> ChapterCampaignData = [];
|
||||
|
||||
[LoadRecord("ContentsOpenTable.json", "Id")]
|
||||
public readonly Dictionary<ContentsOpen, ContentsOpenRecord> ContentsOpenTable = [];
|
||||
|
||||
[LoadRecord("CharacterCostumeTable.json", "Id")]
|
||||
public readonly Dictionary<int, CharacterCostumeRecord> CharacterCostumeTable = [];
|
||||
|
||||
@@ -254,6 +253,105 @@ namespace EpinelPS.Data
|
||||
[LoadRecord("EventMVGMissionTable.json", "Id")]
|
||||
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 = [];
|
||||
|
||||
[LoadRecord("LobbyPrivateBannerTable.json", "Id")]
|
||||
public readonly Dictionary<int, LobbyPrivateBannerRecord> LobbyPrivateBannerTable = [];
|
||||
|
||||
[LoadRecord("LoginEventTable.json", "Id")]
|
||||
public readonly Dictionary<int, LoginEventRecord> LoginEventTable = [];
|
||||
|
||||
// Contents Shop Data Tables
|
||||
[LoadRecord("ContentsShopTable.json", "Id")]
|
||||
public readonly Dictionary<int, ContentsShopRecord> ContentsShopTable = [];
|
||||
[LoadRecord("ContentsShopProductTable.json", "Id")]
|
||||
public readonly Dictionary<int, ContentsShopProductRecord> ContentsShopProductTable = [];
|
||||
[LoadRecord("ShopDiscountProbTable.json", "Id")]
|
||||
public readonly Dictionary<int, ShopDiscountProbRecord> ShopDiscountProbTable = [];
|
||||
|
||||
// Event Dungeon data Table
|
||||
[LoadRecord("EventDungeonTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventDungeonRecord> EventDungeonTable = [];
|
||||
[LoadRecord("EventDungeonStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventDungeonStageRecord> EventDungeonStageTable = [];
|
||||
[LoadRecord("EventDungeonSpotBattleTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventDungeonSpotBattleRecord> EventDungeonSpotBattleTable = [];
|
||||
[LoadRecord("EventDungeonDifficultTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventDungeonDifficultRecord> EventDungeonDifficultTable = [];
|
||||
[LoadRecord("EventStoryTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventStoryRecord> EventStoryTable = [];
|
||||
[LoadRecord("AutoChargeTable.json", "Id")]
|
||||
public readonly Dictionary<int, AutoChargeRecord> AutoChargeTable = [];
|
||||
|
||||
// Pass Data Tables
|
||||
[LoadRecord("PassManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, PassManagerRecord> PassManagerTable = [];
|
||||
[LoadRecord("EventPassManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPassManagerRecord> EventPassManagerTable = [];
|
||||
[LoadRecord("SeasonPassTable.json", "Id")]
|
||||
public readonly Dictionary<int, SeasonPassRecord> SeasonPassTable = [];
|
||||
[LoadRecord("PassMissionTable.json", "Id")]
|
||||
public readonly Dictionary<int, PassMissionRecord> PassMissionTable = [];
|
||||
|
||||
// Event Mission Data Tables
|
||||
[LoadRecord("EventMissionListTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMissionListRecord> EventMissionListTable = [];
|
||||
[LoadRecord("EventMissionCategoryTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMissionCategoryRecord> EventMissionCategoryTable = [];
|
||||
|
||||
// Daily Mission Event Data Tables
|
||||
[LoadRecord("DailyMissionEventSettingTable.json", "Id")]
|
||||
public readonly Dictionary<int, DailyMissionEventSettingRecord_Raw> DailyMissionEventSettingTable = [];
|
||||
[LoadRecord("DailyEventTable.json", "Id")]
|
||||
public readonly Dictionary<int, DailyEventRecord> DailyEventTable = [];
|
||||
|
||||
// SimulationRoom Data Tables
|
||||
[LoadRecord("SimulationRoomChapterTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomChapterRecord> SimulationRoomChapterTable = [];
|
||||
[LoadRecord("SimulationRoomStageLocationTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomStageLocationRecord> SimulationRoomStageLocationTable = [];
|
||||
[LoadRecord("SimulationRoomSelectionEventTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomSelectionEventRecord> SimulationRoomSelectionEventTable = [];
|
||||
[LoadRecord("SimulationRoomSelectionGroupTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomSelectionGroupRecord> SimulationRoomSelectionGroupTable = [];
|
||||
[LoadRecord("SimulationRoomBattleEventTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomBattleEventRecord> SimulationRoomBattleEventTable = [];
|
||||
[LoadRecord("SimulationRoomLevelScalingTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomLevelScalingRecord> SimulationRoomLevelScalingTable = [];
|
||||
[LoadRecord("SimulationRoomBuffPreviewTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomBuffPreviewRecord> SimulationRoomBuffPreviewTable = [];
|
||||
[LoadRecord("SimulationRoomBuffTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomBuffRecord> SimulationRoomBuffTable = [];
|
||||
|
||||
// SimulationRoom Overclock Data Tables
|
||||
[LoadRecord("SimulationRoomOcLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomOverclockLevelRecord> SimulationRoomOcLevelTable = [];
|
||||
[LoadRecord("SimulationRoomOcOptionGroupTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomOverclockOptionGroupRecord> SimulationRoomOcOptionGroupTable = [];
|
||||
[LoadRecord("SimulationRoomOcOptionTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomOverclockOptionRecord> SimulationRoomOcOptionTable = [];
|
||||
[LoadRecord("SimulationRoomOcSeasonTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomOverclockSeasonRecord> SimulationRoomOcSeasonTable = [];
|
||||
|
||||
// MiniGame AZX Tables
|
||||
[LoadRecord("EventAZXAppleGameMissionTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameMissionRecord_Raw> EventAZXAppleGameMissionTable = [];
|
||||
[LoadRecord("EventAZXAppleGameBoardTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameBoardRecord_Raw> EventAZXAppleGameBoardTable = [];
|
||||
[LoadRecord("EventAZXAppleGameCharacterTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameCharacterRecord_Raw> EventAZXAppleGameCharacterTable = [];
|
||||
[LoadRecord("EventAZXAppleGameSkillTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameSkillRecord_Raw> EventAZXAppleGameSkillTable = [];
|
||||
[LoadRecord("EventAZXAppleGameCutSceneTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameCutSceneRecord_Raw> EventAZXAppleGameCutSceneTable = [];
|
||||
|
||||
static async Task<GameData> BuildAsync()
|
||||
{
|
||||
await Load();
|
||||
@@ -268,30 +366,18 @@ namespace EpinelPS.Data
|
||||
return Instance;
|
||||
}
|
||||
|
||||
public GameData(string filePath, string mpkFilePath)
|
||||
public GameData(string mpkFilePath)
|
||||
{
|
||||
if (!File.Exists(filePath)) throw new ArgumentException("Static data file must exist", nameof(filePath));
|
||||
if (!File.Exists(mpkFilePath)) throw new ArgumentException("Static data file must exist", nameof(mpkFilePath));
|
||||
|
||||
// disable warnings
|
||||
ZipStream = new();
|
||||
|
||||
// process json data
|
||||
byte[] rawBytes = File.ReadAllBytes(filePath);
|
||||
Sha256Hash = SHA256.HashData(rawBytes);
|
||||
Size = rawBytes.Length;
|
||||
byte[] rawBytes2 = File.ReadAllBytes(mpkFilePath);
|
||||
MpkHash = SHA256.HashData(rawBytes2);
|
||||
MpkSize = rawBytes2.Length;
|
||||
|
||||
// process mpk data
|
||||
if (!string.IsNullOrEmpty(mpkFilePath))
|
||||
{
|
||||
byte[] rawBytes2 = File.ReadAllBytes(mpkFilePath);
|
||||
MpkHash = SHA256.HashData(rawBytes2);
|
||||
MpkSize = rawBytes2.Length;
|
||||
}
|
||||
|
||||
if (UseMemoryPack)
|
||||
LoadGameData(mpkFilePath, GameConfig.Root.StaticDataMpk);
|
||||
else
|
||||
LoadGameData(filePath, GameConfig.Root.StaticData);
|
||||
LoadGameData(mpkFilePath, GameConfig.Root.StaticDataMpk);
|
||||
if (MainZip == null) throw new Exception("failed to read zip file");
|
||||
}
|
||||
|
||||
@@ -307,8 +393,9 @@ namespace EpinelPS.Data
|
||||
{
|
||||
using FileStream fileStream = File.Open(file, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Rfc2898DeriveBytes a = new(PresharedValue, data.GetSalt2Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
byte[] key2 = a.GetBytes(32);
|
||||
// Rfc2898DeriveBytes a = new(PresharedValue, data.GetSalt2Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
// byte[] key2 = a.GetBytes(32);
|
||||
byte[] key2 = Rfc2898DeriveBytes.Pbkdf2(PresharedValue, data.GetSalt2Bytes(), 10000, HashAlgorithmName.SHA256, 32);
|
||||
|
||||
byte[] decryptionKey = key2[0..16];
|
||||
byte[] iv = key2[16..32];
|
||||
@@ -344,8 +431,9 @@ namespace EpinelPS.Data
|
||||
throw new Exception("error 3");
|
||||
|
||||
dataMs.Position = 0;
|
||||
Rfc2898DeriveBytes keyDec2 = new(PresharedValue, data.GetSalt1Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
byte[] key3 = keyDec2.GetBytes(32);
|
||||
// Rfc2898DeriveBytes keyDec2 = new(PresharedValue, data.GetSalt1Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
// byte[] key3 = keyDec2.GetBytes(32);
|
||||
byte[] key3 = Rfc2898DeriveBytes.Pbkdf2(PresharedValue, data.GetSalt1Bytes(), 10000, HashAlgorithmName.SHA256, 32);
|
||||
|
||||
byte[] val2 = key3[0..16];
|
||||
byte[] iv2 = key3[16..32];
|
||||
@@ -411,15 +499,8 @@ namespace EpinelPS.Data
|
||||
|
||||
public static async Task Load()
|
||||
{
|
||||
string? targetFile = await AssetDownloadUtil.DownloadOrGetFileAsync(GameConfig.Root.StaticData.Url, CancellationToken.None) ?? throw new Exception("static data download fail");
|
||||
if (string.IsNullOrEmpty(GameConfig.Root.StaticDataMpk.Url))
|
||||
{
|
||||
_instance = new(targetFile, "");
|
||||
return;
|
||||
}
|
||||
|
||||
string? targetFile2 = await AssetDownloadUtil.DownloadOrGetFileAsync(GameConfig.Root.StaticDataMpk.Url, CancellationToken.None) ?? throw new Exception("static data download fail");
|
||||
_instance = new(targetFile, targetFile2);
|
||||
_instance = new(targetFile2);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -427,7 +508,7 @@ namespace EpinelPS.Data
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UseMemoryPack) entry = entry.Replace(".json", ".mpk");
|
||||
entry = entry.Replace(".json", ".mpk");
|
||||
|
||||
ZipEntry fileEntry = MainZip.GetEntry(entry);
|
||||
if (fileEntry == null)
|
||||
@@ -436,22 +517,8 @@ namespace EpinelPS.Data
|
||||
return [];
|
||||
}
|
||||
|
||||
X[]? deserializedObject;
|
||||
|
||||
if (UseMemoryPack)
|
||||
{
|
||||
Stream stream = MainZip.GetInputStream(fileEntry);
|
||||
deserializedObject = await MemoryPackSerializer.DeserializeAsync<X[]>(stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var streamReader = new System.IO.StreamReader(MainZip.GetInputStream(fileEntry));
|
||||
var json = await streamReader.ReadToEndAsync();
|
||||
DataTable<X> obj = JsonConvert.DeserializeObject<DataTable<X>>(json) ?? throw new Exception("deserializeobject failed");
|
||||
deserializedObject = [.. obj.records];
|
||||
}
|
||||
|
||||
if (deserializedObject == null) throw new Exception("failed to parse " + entry);
|
||||
Stream stream = MainZip.GetInputStream(fileEntry);
|
||||
X[] deserializedObject = await MemoryPackSerializer.DeserializeAsync<X[]>(stream) ?? throw new Exception("failed to parse " + entry);
|
||||
|
||||
currentFile++;
|
||||
bar.Report((double)currentFile / totalFiles);
|
||||
@@ -499,7 +566,7 @@ namespace EpinelPS.Data
|
||||
if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty");
|
||||
foreach (KeyValuePair<int, MainQuestRecord> item in QuestDataRecords)
|
||||
{
|
||||
if (item.Value.ConditionId == stage)
|
||||
if (item.Value.ConditionId[0].ConditionId == stage)
|
||||
{
|
||||
return item.Value;
|
||||
}
|
||||
@@ -558,45 +625,6 @@ namespace EpinelPS.Data
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public string? GetMapIdFromDBFieldName(string field)
|
||||
{
|
||||
// Get game map ID from DB Field Name (ex: 1_Normal for chapter 1 normal)
|
||||
string[] keys = field.Split("_");
|
||||
if (int.TryParse(keys[0], out int chapterNum))
|
||||
{
|
||||
string difficulty = keys[1];
|
||||
|
||||
foreach (KeyValuePair<int, CampaignChapterRecord> item in ChapterCampaignData)
|
||||
{
|
||||
if (difficulty == "Normal" && item.Value.Chapter == chapterNum)
|
||||
{
|
||||
return item.Value.FieldId;
|
||||
}
|
||||
else if (difficulty == "Hard" && item.Value.Chapter == chapterNum)
|
||||
{
|
||||
return item.Value.HardFieldId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return keys[0]; // Already a Map ID
|
||||
}
|
||||
}
|
||||
public int GetNormalChapterNumberFromFieldName(string field)
|
||||
{
|
||||
foreach (KeyValuePair<int, CampaignChapterRecord> item in ChapterCampaignData)
|
||||
{
|
||||
if (item.Value.FieldId == field)
|
||||
{
|
||||
return item.Value.Chapter;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
public IEnumerable<int> GetAllCostumes()
|
||||
{
|
||||
foreach (KeyValuePair<int, CharacterCostumeRecord> item in CharacterCostumeTable)
|
||||
@@ -667,45 +695,32 @@ namespace EpinelPS.Data
|
||||
}
|
||||
|
||||
// Example regular stage format: "d_main_26_08"
|
||||
// Example bonus stage format: "d_main_18af_06"
|
||||
// Example bonus stage format: "d_main_18af_06" or "d_main_39_af_01" (since chapter 39)
|
||||
// Example stage with suffix format: "d_main_01_01_s" or "d_main_01_01_e"
|
||||
|
||||
string[] parts = scenarioGroupId.Split('_');
|
||||
|
||||
if (parts.Length < 4)
|
||||
var matches = Regex.Matches(scenarioGroupId, @"\d+");
|
||||
var parts = new List<int>();
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
return false; // If it doesn't have at least 4 parts, it's not a valId stage
|
||||
if (int.TryParse(match.Value, out int number))
|
||||
{
|
||||
parts.Add(number);
|
||||
}
|
||||
}
|
||||
if (parts.Count < 2) // Valid stage must have at least chapter and stage numbers
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string chapterPart = parts[2]; // This could be "26", "18af", "01"
|
||||
string stagePart = parts[3]; // This is the stage part, e.g., "08", "01_s", or "01_e"
|
||||
int chapter = parts[0];
|
||||
int stage = parts[1];
|
||||
|
||||
// Remove any suffixes like "_s", "_e" from the stage part for comparison
|
||||
string cleanedStagePart = stagePart.Split('_')[0]; // Removes "_s", "_e", etc.
|
||||
|
||||
// Handle bonus stages (ending in "af" or having "_s", "_e" suffix)
|
||||
bool isBonusStage = chapterPart.EndsWith("af") || stagePart.Contains("_s") || stagePart.Contains("_e");
|
||||
|
||||
// Extract chapter number (remove "af" if present)
|
||||
string chapterNumberStr = isBonusStage && chapterPart.EndsWith("af")
|
||||
? chapterPart[..^2] // Remove "af"
|
||||
: chapterPart;
|
||||
|
||||
// Parse chapter and stage numbers
|
||||
if (int.TryParse(chapterNumberStr, out int chapter) && int.TryParse(cleanedStagePart, out int stage))
|
||||
// Only accept stages if they are:
|
||||
// 1. In a chapter less than the target chapter
|
||||
// 2. OR in the target chapter but with a stage number less than or equal to the target stage
|
||||
if (chapter < targetChapter || (chapter == targetChapter && (stage <= targetStage)))
|
||||
{
|
||||
// Check if it's a bonus stage with a suffix
|
||||
bool isSpecialStage = stagePart.Contains("_s") || stagePart.Contains("_e");
|
||||
|
||||
// Only accept stages if they are:
|
||||
// 1. In a chapter less than the target chapter
|
||||
// 2. OR in the target chapter but with a stage number less than or equal to the target stage
|
||||
// 3. OR it's a special stage (with "_s" or "_e") in the target chapter and target stage
|
||||
if (chapter < targetChapter ||
|
||||
(chapter == targetChapter && (stage < targetStage || (stage == targetStage && isSpecialStage))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -716,19 +731,17 @@ namespace EpinelPS.Data
|
||||
CampaignChapterRecord data = ChapterCampaignData[chapter - 1];
|
||||
if (mod == ChapterMod.Hard)
|
||||
return data.HardFieldId;
|
||||
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;
|
||||
else if (mod == ChapterMod.Normal)
|
||||
return data.FieldId;
|
||||
else if (mod == ChapterMod.Story)
|
||||
return data.StoryFieldId;
|
||||
|
||||
throw new NotImplementedException($"difficulty {mod} not implemented");
|
||||
}
|
||||
|
||||
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())
|
||||
return results.FirstOrDefault().Value.RewardId;
|
||||
else return 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IncludeHttpRuleProtos>true</IncludeHttpRuleProtos>
|
||||
@@ -10,7 +10,7 @@
|
||||
<SelfContained>true</SelfContained>
|
||||
<IncludeNativeLibrariesForSelfExtract>True</IncludeNativeLibrariesForSelfExtract>
|
||||
<NoWarn>$(NoWarn);SYSLIB0057</NoWarn>
|
||||
<Version>0.135.4.3</Version>
|
||||
<Version>0.140.8.0</Version>
|
||||
<CETCompat>false</CETCompat>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -22,11 +22,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ASodium" Version="0.6.2" />
|
||||
<PackageReference Include="ASodium" Version="0.6.4" />
|
||||
<PackageReference Include="DnsClient" Version="1.8.0" />
|
||||
<PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.31.1" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.72.0">
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.33.1" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.76.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -36,8 +36,8 @@
|
||||
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="Sodium.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="log4net" Version="3.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace EpinelPS.LobbyServer.Archive
|
||||
ReqClearArchiveStage req = await ReadData<ReqClearArchiveStage>(); // has fields EventId, StageId, BattleResult
|
||||
int evid = req.EventId;
|
||||
int stgid = req.StageId;
|
||||
int result = req.BattleResult; // if 2 add to event info as last stage
|
||||
User user = GetUser() ?? throw new Exception("User not found.");
|
||||
int result = req.BattleResult;
|
||||
User user = GetUser();
|
||||
|
||||
// Check if the EventInfo exists for the given EventId
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? eventData))
|
||||
@@ -19,13 +19,12 @@ namespace EpinelPS.LobbyServer.Archive
|
||||
throw new Exception($"Event with ID {evid} not found.");
|
||||
}
|
||||
|
||||
// Update the EventData if BattleResult is 2
|
||||
if (result == 1)
|
||||
// Update the EventData if BattleResult is 1
|
||||
if (result == 1 && !eventData.ClearedStages.Contains(stgid))
|
||||
{
|
||||
|
||||
eventData.ClearedStages.Add(stgid);
|
||||
// Update the LastStage in EventData
|
||||
eventData.LastStage = stgid;
|
||||
|
||||
}
|
||||
JsonDb.Save();
|
||||
ResClearArchiveStage response = new();
|
||||
|
||||
@@ -11,16 +11,19 @@ namespace EpinelPS.LobbyServer.Archive
|
||||
int evid = req.EventId;
|
||||
int stgid = req.StageId;
|
||||
|
||||
User user = GetUser() ?? throw new Exception("User not found.");
|
||||
User user = GetUser();
|
||||
|
||||
// Check if the EventInfo exists for the given EventId
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? eventData))
|
||||
{
|
||||
throw new Exception($"Event with ID {evid} not found.");
|
||||
}
|
||||
// Update the LastStage in EventData
|
||||
eventData.LastStage = stgid;
|
||||
|
||||
if (!eventData.ClearedStages.Contains(stgid))
|
||||
{
|
||||
eventData.ClearedStages.Add(stgid);
|
||||
// Update the LastStage in EventData
|
||||
eventData.LastStage = stgid;
|
||||
}
|
||||
JsonDb.Save();
|
||||
ResFastClearArchiveStage response = new();
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data; // Ensure this namespace is included
|
||||
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/scenario/getnonresettable")]
|
||||
@@ -9,41 +6,16 @@ namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetNonResettableArchiveScenario req = await ReadData<ReqGetNonResettableArchiveScenario>(); // req has EventId field
|
||||
int evId = req.EventId;
|
||||
ReqGetNonResettableArchiveScenario req = await ReadData<ReqGetNonResettableArchiveScenario>();
|
||||
ResGetNonResettableArchiveScenario response = new();
|
||||
|
||||
// Access the GameData instance
|
||||
GameData gameData = GameData.Instance;
|
||||
|
||||
if (evId == 130002)
|
||||
User user = GetUser();
|
||||
foreach (var (evtId, evtData) in user.EventInfo)
|
||||
{
|
||||
// Directly use the archiveEventQuestRecords dictionary
|
||||
foreach (ArchiveEventQuestRecord_Raw record in gameData.archiveEventQuestRecords.Values)
|
||||
if (evtId == req.EventId)
|
||||
{
|
||||
if (record.EventQuestManagerId == evId)
|
||||
{
|
||||
// Add the end_scenario_Id to the ScenarioIdList
|
||||
if (!string.IsNullOrEmpty(record.EndScenarioId))
|
||||
{
|
||||
response.ScenarioIdList.Add(record.EndScenarioId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Directly use the archiveEventStoryRecords dictionary
|
||||
foreach (ArchiveEventStoryRecord record in gameData.archiveEventStoryRecords.Values)
|
||||
{
|
||||
if (record.EventId == evId)
|
||||
{
|
||||
// Add the PrologueScenario to the ScenarioIdList
|
||||
if (!string.IsNullOrEmpty(record.PrologueScenario))
|
||||
{
|
||||
response.ScenarioIdList.Add(record.PrologueScenario);
|
||||
}
|
||||
}
|
||||
response.ScenarioIdList.AddRange(evtData.CompletedScenarios);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,21 @@ namespace EpinelPS.LobbyServer.Archive
|
||||
ReqGetResettableArchiveScenario req = await ReadData<ReqGetResettableArchiveScenario>();
|
||||
ResGetResettableArchiveScenario response = new(); // has ScenarioIdList field that takes in strings
|
||||
|
||||
// Retrieve stage IDs from GameData
|
||||
List<string> stageIds = [.. GameData.Instance.archiveEventDungeonStageRecords.Values.Select(record => record.StageId.ToString())];
|
||||
|
||||
// Add them to the response
|
||||
response.ScenarioIdList.Add(stageIds);
|
||||
GameData gameData = GameData.Instance;
|
||||
User user = GetUser();
|
||||
foreach (ArchiveEventStoryRecord record in gameData.archiveEventStoryRecords.Values)
|
||||
{
|
||||
// Add the PrologueScenario to the ScenarioIdList
|
||||
if (record.EventId == req.EventId && !string.IsNullOrEmpty(record.PrologueScenario))
|
||||
{
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? evtData) &&
|
||||
evtData.CompletedScenarios.Contains(record.PrologueScenario))
|
||||
{
|
||||
response.ScenarioIdList.Add(record.PrologueScenario);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
21
EpinelPS/LobbyServer/Badge/PermanentContent.cs
Normal file
21
EpinelPS/LobbyServer/Badge/PermanentContent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Badge
|
||||
{
|
||||
[PacketPath("/badge/permanentcontent")]
|
||||
public class PermanentContent : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqPermanentContentBadgeData req = await ReadData<ReqPermanentContentBadgeData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResPermanentContentBadgeData response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace EpinelPS.LobbyServer.Campaign
|
||||
response.FieldObjectItemsNum.Add(new NetCampaignFieldObjectItemsNum()
|
||||
{
|
||||
MapId = map.Key,
|
||||
Count = map.Value.CompletedObjects.Count
|
||||
Count = map.Value.CompletedObjects.Where(x => x.Type == 1).Count()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace EpinelPS.LobbyServer.Character
|
||||
|
||||
}
|
||||
|
||||
// TODO: ValIdate response from real server and pull info from user info
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,27 @@ namespace EpinelPS.LobbyServer.Character
|
||||
ResGetCharacterData response = new();
|
||||
foreach (CharacterModel item in user.Characters)
|
||||
{
|
||||
response.Character.Add(new NetUserCharacterData() { Default = new() { Csn = item.Csn, Skill1Lv = item.Skill1Lvl, Skill2Lv = item.Skill2Lvl, CostumeId = item.CostumeId, Lv = user.GetCharacterLevel(item.Csn, item.Level), Grade = item.Grade, Tid = item.Tid, UltiSkillLv = item.UltimateLevel }, IsSynchro = user.GetSynchro(item.Csn) });
|
||||
response.Character.Add(new NetUserCharacterData()
|
||||
{
|
||||
Default = new()
|
||||
{
|
||||
Csn = item.Csn,
|
||||
Skill1Lv = item.Skill1Lvl,
|
||||
Skill2Lv = item.Skill2Lvl,
|
||||
CostumeId = item.CostumeId,
|
||||
Lv = user.GetCharacterLevel(item.Csn, item.Level),
|
||||
Grade = item.Grade,
|
||||
Tid = item.Tid,
|
||||
UltiSkillLv = item.UltimateLevel
|
||||
},
|
||||
IsSynchro = user.GetSynchro(item.Csn)
|
||||
});
|
||||
|
||||
// Check if character is main force
|
||||
if (item.IsMainForce)
|
||||
{
|
||||
response.MainForceCsnList.Add(item.Csn);
|
||||
}
|
||||
}
|
||||
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
29
EpinelPS/LobbyServer/Character/SetCharacterMainForce.cs
Normal file
29
EpinelPS/LobbyServer/Character/SetCharacterMainForce.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/mainforce/set")]
|
||||
public class SetCharacterMainForce : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetCharacterMainForce req = await ReadData<ReqSetCharacterMainForce>();
|
||||
User user = GetUser();
|
||||
|
||||
foreach (CharacterModel item in user.Characters)
|
||||
{
|
||||
if (item.Csn == req.Csn)
|
||||
{
|
||||
item.IsMainForce = req.IsMainForce;
|
||||
break;
|
||||
}
|
||||
}
|
||||
JsonDb.Save();
|
||||
|
||||
ResSetCharacterMainForce response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Broken protocol so we dIdn't valIdate request data.
|
||||
// Broken protocol so we dIdn't validate request data.
|
||||
// May fix later.
|
||||
ReqSynchroAddSlot req = await ReadData<ReqSynchroAddSlot>();
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace EpinelPS.LobbyServer.ContentsOpen
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<int, UnlockData> item in user.ContentsOpenUnlocked)
|
||||
foreach (KeyValuePair<int, UnlockData> item in user.ContentsOpenUnlocked.OrderBy(x => x.Key))
|
||||
{
|
||||
response.ContentsOpenUnlockInfoList.Add(new NetContentsOpenUnlockInfo()
|
||||
{
|
||||
|
||||
@@ -13,7 +13,10 @@ namespace EpinelPS.LobbyServer.Event
|
||||
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? evt))
|
||||
{
|
||||
evt.CompletedScenarios.Add(req.ScenarioId);
|
||||
if (!evt.CompletedScenarios.Contains(req.ScenarioId))
|
||||
{
|
||||
evt.CompletedScenarios.Add(req.ScenarioId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -21,7 +24,8 @@ namespace EpinelPS.LobbyServer.Event
|
||||
evt.CompletedScenarios.Add(req.ScenarioId);
|
||||
user.EventInfo.Add(req.EventId, evt);
|
||||
}
|
||||
|
||||
|
||||
JsonDb.Save();
|
||||
ResSetEventScenarioComplete response = new();
|
||||
|
||||
// TODO reward
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
@@ -13,14 +12,12 @@ namespace EpinelPS.LobbyServer.Event
|
||||
|
||||
ResEnterEventField response = new()
|
||||
{
|
||||
Field = new(),
|
||||
Json = "{}"
|
||||
Field = new()
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Retrieve collected objects
|
||||
|
||||
// Retrieve collected objects and completed stages
|
||||
if (!user.FieldInfoNew.TryGetValue(req.MapId, out FieldInfoNew? field))
|
||||
{
|
||||
field = new FieldInfoNew();
|
||||
@@ -33,7 +30,9 @@ namespace EpinelPS.LobbyServer.Event
|
||||
}
|
||||
foreach (NetFieldObject obj in field.CompletedObjects)
|
||||
{
|
||||
response.Field.Objects.Add(obj);
|
||||
if (obj == null) continue;
|
||||
if (obj.Type == 1)
|
||||
response.Field.Objects.Add(obj);
|
||||
}
|
||||
|
||||
|
||||
|
||||
292
EpinelPS/LobbyServer/Event/EventHelper.cs
Normal file
292
EpinelPS/LobbyServer/Event/EventHelper.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
public class EventHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(EventHelper));
|
||||
|
||||
public static void AddEvents(User user, ref ResGetEventList response)
|
||||
{
|
||||
List<LobbyPrivateBannerRecord> lobbyPrivateBanners = GetLobbyPrivateBannerData(user);
|
||||
if (lobbyPrivateBanners.Count == 0)
|
||||
{
|
||||
// No active lobby private banners
|
||||
Logging.WriteLine("No active lobby private banners found.", LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var eventManagers = GameData.Instance.eventManagers.Values.ToList();
|
||||
foreach (var banner in lobbyPrivateBanners)
|
||||
{
|
||||
// Get all events (including child events) associated with this banner
|
||||
List<NetEventData> events = GetEventData(banner, eventManagers);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {events.Count} associated events: {JsonConvert.SerializeObject(events)}");
|
||||
AddEvents(ref response, events);
|
||||
|
||||
// Additionally, get any gacha events associated with this banner
|
||||
List<EventSystemType> systemTypes = [EventSystemType.PickupGachaEvent, EventSystemType.BoxGachaEvent, EventSystemType.LoginEvent];
|
||||
List<NetEventData> gachaEvents = GetEventDataBySystemTypes(banner, eventManagers, systemTypes);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {gachaEvents.Count} associated gacha events: {JsonConvert.SerializeObject(gachaEvents)}");
|
||||
AddEvents(ref response, gachaEvents);
|
||||
|
||||
// add challenge events
|
||||
var challengeEvents = GetChallengeEventData(banner, eventManagers);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {challengeEvents.Count} associated challenge events: {JsonConvert.SerializeObject(challengeEvents)}");
|
||||
AddEvents(ref response, challengeEvents);
|
||||
}
|
||||
// add daily mission events
|
||||
List<NetEventData> dailyMissionEvents = GetDailyMissionEventData(eventManagers);
|
||||
log.Debug($"Found {dailyMissionEvents.Count} associated daily mission events: {JsonConvert.SerializeObject(dailyMissionEvents)}");
|
||||
AddEvents(ref response, dailyMissionEvents);
|
||||
}
|
||||
|
||||
public static void AddJoinedEvents(User user, ref ResGetJoinedEvent response)
|
||||
{
|
||||
List<LobbyPrivateBannerRecord> lobbyPrivateBanners = GetLobbyPrivateBannerData(user);
|
||||
if (lobbyPrivateBanners.Count == 0)
|
||||
{
|
||||
// No active lobby private banners
|
||||
Logging.WriteLine("No active lobby private banners found.", LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var eventManagers = GameData.Instance.eventManagers.Values.ToList();
|
||||
foreach (var banner in lobbyPrivateBanners)
|
||||
{
|
||||
|
||||
// Get all events (including child events) associated with this banner
|
||||
var events = GetEventData(banner, eventManagers);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {events.Count} associated events: {JsonConvert.SerializeObject(events)}");
|
||||
AddJoinedEvents(ref response, events);
|
||||
|
||||
// add gacha events
|
||||
List<EventSystemType> systemTypes = [EventSystemType.PickupGachaEvent, EventSystemType.BoxGachaEvent, EventSystemType.LoginEvent];
|
||||
List<NetEventData> gachaEvents = GetEventDataBySystemTypes(banner, eventManagers, systemTypes);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {gachaEvents.Count} associated gacha events: {JsonConvert.SerializeObject(gachaEvents)}");
|
||||
AddJoinedEvents(ref response, gachaEvents);
|
||||
|
||||
// add challenge events
|
||||
List<NetEventData> challengeEvents = GetChallengeEventData(banner, eventManagers);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {challengeEvents.Count} associated challenge events: {JsonConvert.SerializeObject(challengeEvents)}");
|
||||
AddJoinedEvents(ref response, challengeEvents);
|
||||
}
|
||||
// add daily mission events
|
||||
List<NetEventData> dailyMissionEvents = GetDailyMissionEventData(eventManagers);
|
||||
log.Debug($"Found {dailyMissionEvents.Count} associated daily mission events: {JsonConvert.SerializeObject(dailyMissionEvents)}");
|
||||
AddJoinedEvents(ref response, dailyMissionEvents);
|
||||
}
|
||||
|
||||
private static List<NetEventData> GetEventData(LobbyPrivateBannerRecord banner, List<EventManagerRecord> eventManagers)
|
||||
{
|
||||
List<NetEventData> events = [];
|
||||
|
||||
if (!eventManagers.Any(em => em.Id == banner.EventId))
|
||||
{
|
||||
Logging.WriteLine($"No event manager found for Banner EventId: {banner.EventId}", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
// Add the main event associated with the banner
|
||||
var mainEvent = eventManagers.First(em => em.Id == banner.EventId);
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = mainEvent.Id,
|
||||
EventSystemType = (int)mainEvent.EventSystemType,
|
||||
// EventStartDate = banner.StartDate.Ticks,
|
||||
// EventVisibleDate = banner.StartDate.Ticks,
|
||||
// EventDisableDate = banner.EndDate.Ticks,
|
||||
// EventEndDate = banner.EndDate.Ticks
|
||||
});
|
||||
// Add child events associated with the main event
|
||||
var childEvents = eventManagers.Where(em => em.ParentsEventId == banner.EventId || em.SetField == banner.EventId).ToList();
|
||||
foreach (var childEvent in childEvents)
|
||||
{
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = childEvent.Id,
|
||||
EventSystemType = (int)childEvent.EventSystemType,
|
||||
// EventStartDate = banner.StartDate.Ticks,
|
||||
// EventVisibleDate = banner.StartDate.Ticks,
|
||||
// EventDisableDate = banner.EndDate.Ticks,
|
||||
// EventEndDate = banner.EndDate.Ticks
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private static List<NetEventData> GetEventDataBySystemTypes(LobbyPrivateBannerRecord banner, List<EventManagerRecord> eventManagers, List<EventSystemType> systemTypes)
|
||||
{
|
||||
List<NetEventData> events = [];
|
||||
// Find all event banner resource tables associated with this banner's EventId
|
||||
List<string> eventBannerResourceTables = [.. eventManagers.Where(em =>
|
||||
(em.SetField == banner.EventId || em.ParentsEventId == banner.EventId)
|
||||
&& em.EventBannerResourceTable.StartsWith("event_")).Select(em => em.EventBannerResourceTable)];
|
||||
eventBannerResourceTables = [.. eventBannerResourceTables.Distinct()];
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {eventBannerResourceTables.Count} associated event banner resource tables: {JsonConvert.SerializeObject(eventBannerResourceTables)}");
|
||||
if (eventBannerResourceTables.Count == 0)
|
||||
{
|
||||
Logging.WriteLine($"No associated event banner resource tables found for Banner EventId: {banner.EventId}", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
|
||||
// Find all events matching the banner resource tables and specified system types
|
||||
var gachaEvents = eventManagers.Where(em =>
|
||||
eventBannerResourceTables.Contains(em.EventBannerResourceTable)
|
||||
&& systemTypes.Contains(em.EventSystemType)).ToList();
|
||||
log.Debug($"Found {gachaEvents.Count} gacha events from banner resource tables: {JsonConvert.SerializeObject(gachaEvents)}");
|
||||
if (gachaEvents.Count == 0)
|
||||
{
|
||||
Logging.WriteLine($"No gacha events found for Banner EventId: {banner.EventId}", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
|
||||
// Add each gacha event to the list
|
||||
foreach (var gachaEvent in gachaEvents)
|
||||
{
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = gachaEvent.Id,
|
||||
EventSystemType = (int)gachaEvent.EventSystemType,
|
||||
// EventStartDate = banner.StartDate.Ticks,
|
||||
// EventVisibleDate = banner.StartDate.Ticks,
|
||||
// EventDisableDate = banner.EndDate.Ticks,
|
||||
// EventEndDate = banner.EndDate.Ticks
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private static List<NetEventData> GetChallengeEventData(LobbyPrivateBannerRecord banner, List<EventManagerRecord> eventManagers)
|
||||
{
|
||||
List<NetEventData> events = [];
|
||||
|
||||
// Find all challenge events (ChallengeModeEvent) associated with this banner's EventId
|
||||
var challengeEvents = eventManagers.Where(em =>
|
||||
em.ParentsEventId == banner.EventId && em.EventSystemType == EventSystemType.ChallengeModeEvent).ToList();
|
||||
log.Debug($"Found {challengeEvents.Count} challenge events from banner resource tables: {JsonConvert.SerializeObject(challengeEvents)}");
|
||||
if (challengeEvents.Count == 0)
|
||||
{
|
||||
Logging.WriteLine($"No challenge events found for Banner EventId: {banner.EventId}", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
|
||||
// Add each challenge event to the list
|
||||
foreach (var challengeEvent in challengeEvents)
|
||||
{
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = challengeEvent.Id,
|
||||
EventSystemType = (int)challengeEvent.EventSystemType,
|
||||
// EventStartDate = banner.StartDate.Ticks,
|
||||
// EventVisibleDate = banner.StartDate.Ticks,
|
||||
// EventDisableDate = banner.EndDate.Ticks,
|
||||
// EventEndDate = banner.EndDate.Ticks
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get active lobby private banner data
|
||||
/// </summary>
|
||||
/// <returns>List of active lobby private banners</returns>
|
||||
public static List<LobbyPrivateBannerRecord> GetLobbyPrivateBannerData(User user)
|
||||
{
|
||||
var lobbyPrivateBannerIds = user.LobbyPrivateBannerIds;
|
||||
var lobbyPrivateBannerRecords = GameData.Instance.LobbyPrivateBannerTable.Values;
|
||||
List<LobbyPrivateBannerRecord> lobbyPrivateBanners = [];
|
||||
if (lobbyPrivateBannerIds is not null && lobbyPrivateBannerIds.Count > 0)
|
||||
{
|
||||
lobbyPrivateBanners = [.. lobbyPrivateBannerRecords.Where(b => lobbyPrivateBannerIds.Contains(b.Id))];
|
||||
}
|
||||
else
|
||||
{
|
||||
lobbyPrivateBanners.Add(lobbyPrivateBannerRecords.OrderBy(b => b.Id).Last());
|
||||
}
|
||||
Logging.WriteLine($"Found {lobbyPrivateBanners.Count} active lobby private banners.", LogType.Debug);
|
||||
log.Debug($"Active lobby private banners: {JsonConvert.SerializeObject(lobbyPrivateBanners)}");
|
||||
return lobbyPrivateBanners;
|
||||
}
|
||||
|
||||
private static void AddEvents(ref ResGetEventList response, List<NetEventData> eventDatas)
|
||||
{
|
||||
foreach (var eventData in eventDatas)
|
||||
{
|
||||
// if (eventData.Id == 70115) continue;
|
||||
// Avoid adding duplicate events
|
||||
if (!response.EventList.Any(e => e.Id == eventData.Id))
|
||||
{
|
||||
if (eventData.EventStartDate == 0) eventData.EventStartDate = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
if (eventData.EventVisibleDate == 0) eventData.EventVisibleDate = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
if (eventData.EventDisableDate == 0) eventData.EventDisableDate = DateTime.UtcNow.AddDays(30).Ticks;
|
||||
if (eventData.EventEndDate == 0) eventData.EventEndDate = DateTime.UtcNow.AddDays(30).Ticks;
|
||||
response.EventList.Add(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Debug($"Skipping duplicate event Id: {eventData.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddJoinedEvents(ref ResGetJoinedEvent response, List<NetEventData> eventDatas)
|
||||
{
|
||||
foreach (var eventData in eventDatas)
|
||||
{
|
||||
if (eventData.Id == 70115) continue;
|
||||
// Avoid adding duplicate events
|
||||
if (!response.EventWithJoinData.Any(e => e.EventData.Id == eventData.Id))
|
||||
{
|
||||
if (eventData.EventStartDate == 0) eventData.EventStartDate = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
if (eventData.EventVisibleDate == 0) eventData.EventVisibleDate = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
if (eventData.EventDisableDate == 0) eventData.EventDisableDate = DateTime.UtcNow.AddDays(30).Ticks;
|
||||
if (eventData.EventEndDate == 0) eventData.EventEndDate = DateTime.UtcNow.AddDays(30).Ticks;
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
|
||||
EventData = eventData,
|
||||
JoinAt = 0
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Debug($"Skipping duplicate event Id: {eventData.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<NetEventData> GetDailyMissionEventData(List<EventManagerRecord> eventManagers)
|
||||
{
|
||||
List<NetEventData> events = [];
|
||||
|
||||
var dailyEventIds = GameData.Instance.DailyMissionEventSettingTable.Values.Select(de => de.EventId).ToList();
|
||||
log.Debug($"Daily Mission Event IDs: {JsonConvert.SerializeObject(dailyEventIds)}");
|
||||
var dailyEvents = eventManagers.Where(em => dailyEventIds.Contains(em.Id)).ToList();
|
||||
log.Debug($"Found {dailyEvents.Count} daily events: {JsonConvert.SerializeObject(dailyEvents)}");
|
||||
if (dailyEvents.Count == 0)
|
||||
{
|
||||
Logging.WriteLine("No daily events found.", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
|
||||
// Add each daily event to the list
|
||||
foreach (var dailyEvent in dailyEvents)
|
||||
{
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = dailyEvent.Id,
|
||||
EventSystemType = (int)dailyEvent.EventSystemType,
|
||||
EventStartDate = DateTime.UtcNow.Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Ticks,
|
||||
EventDisableDate = DateTime.UtcNow.AddDays(30).Ticks,
|
||||
EventEndDate = DateTime.UtcNow.AddDays(30).Ticks
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
@@ -9,32 +10,32 @@ namespace EpinelPS.LobbyServer.Event
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqLoginEventData req = await ReadData<ReqLoginEventData>();
|
||||
User user = GetUser();
|
||||
int evId = req.EventId;
|
||||
ResLoginEventData response = new()
|
||||
{
|
||||
EndDate = DateTime.Now.AddDays(13).Ticks,
|
||||
DisableDate = DateTime.Now.AddDays(13).Ticks,
|
||||
LastAttendance = new LoginEventAttendance
|
||||
{
|
||||
Day = 14, // Example day value, adjust as needed
|
||||
AttendanceDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks // Assign Ticks here
|
||||
}
|
||||
LastAttendance = new LoginEventAttendance()
|
||||
}; // fields "EndDate", "DisableDate", "RewardHistories", "LastAttendance"
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 1 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 2 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 3 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 4 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 5 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 6 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 7 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 8 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 9 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 10 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 11 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 12 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 13 } );
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = true, Day = 14 } );
|
||||
|
||||
// Check if event exists
|
||||
if (!user.LoginEventInfo.TryGetValue(evId, out var loginEventData))
|
||||
{
|
||||
loginEventData = new LoginEventData();
|
||||
user.LoginEventInfo.Add(evId, loginEventData);
|
||||
JsonDb.Save();
|
||||
}
|
||||
// Populate response with event data
|
||||
int day = 1;
|
||||
GameData.Instance.LoginEventTable.Values.Where(ev => ev.EventId == evId).ToList().ForEach(ev =>
|
||||
{
|
||||
loginEventData.LastDay = day++;
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = loginEventData.Days.Contains(ev.Day), Day = ev.Day });
|
||||
});
|
||||
|
||||
response.LastAttendance.Day = loginEventData.LastDay;
|
||||
response.LastAttendance.AttendanceDate = loginEventData.LastDate;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
38
EpinelPS/LobbyServer/Event/EventLoginReceive.cs
Normal file
38
EpinelPS/LobbyServer/Event/EventLoginReceive.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/login/receive")]
|
||||
public class EventLoginReceive : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqObtainLoginEventReward Fields: EventId, Day
|
||||
ReqObtainLoginEventReward req = await ReadData<ReqObtainLoginEventReward>();
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainLoginEventReward response = new();
|
||||
|
||||
if (!user.LoginEventInfo.TryGetValue(req.EventId, out var loginEventData))
|
||||
{
|
||||
loginEventData = new LoginEventData();
|
||||
user.LoginEventInfo.Add(req.EventId, loginEventData);
|
||||
}
|
||||
|
||||
GameData.Instance.LoginEventTable.Values.Where(ev => ev.EventId == req.EventId && ev.Day == req.Day).ToList().ForEach(ev =>
|
||||
{
|
||||
response.Reward = RewardUtils.RegisterRewardsForUser(user, ev.RewardId);
|
||||
});
|
||||
|
||||
if (!loginEventData.Days.Contains(req.Day))
|
||||
{
|
||||
// loginEventData.Days.Add(req.Day);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
EpinelPS/LobbyServer/Event/EventLoginReceiveAll.cs
Normal file
46
EpinelPS/LobbyServer/Event/EventLoginReceiveAll.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/login/allreceive")]
|
||||
public class EventLoginReceiveAll : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqObtainLoginEventReward Fields: EventId, Day
|
||||
ReqObtainAllLoginEventReward req = await ReadData<ReqObtainAllLoginEventReward>();
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainAllLoginEventReward response = new();
|
||||
|
||||
if (!user.LoginEventInfo.TryGetValue(req.EventId, out var loginEventData))
|
||||
{
|
||||
loginEventData = new LoginEventData();
|
||||
user.LoginEventInfo.Add(req.EventId, loginEventData);
|
||||
}
|
||||
response.Reward = new();
|
||||
NetRewardData rewardData = new();
|
||||
GameData.Instance.LoginEventTable.Values.Where(ev => ev.EventId == req.EventId).ToList().ForEach(ev =>
|
||||
{
|
||||
if (!loginEventData.Days.Contains(ev.Day))
|
||||
{
|
||||
loginEventData.Days.Add(ev.Day);
|
||||
RewardRecord reward = GameData.Instance.GetRewardTableEntry(ev.RewardId) ?? throw new Exception($"unknown reward Id {ev.RewardId}");
|
||||
foreach (var item in reward.Rewards)
|
||||
{
|
||||
if (item.RewardType != RewardType.None)
|
||||
{
|
||||
RewardUtils.AddSingleObject(user, ref rewardData, item.RewardId, item.RewardType, item.RewardValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
response.Reward = rewardData;
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
EpinelPS/LobbyServer/Event/ExecuteEventBoxGacha.cs
Normal file
22
EpinelPS/LobbyServer/Event/ExecuteEventBoxGacha.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/boxgacha/execute")]
|
||||
public class ExecuteEventBoxGacha : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// from client: {"EventId":10051,"CurrentCount":1}
|
||||
ReqExecuteEventBoxGacha req = await ReadData<ReqExecuteEventBoxGacha>();
|
||||
User user = GetUser();
|
||||
|
||||
ResExecuteEventBoxGacha response = new()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
@@ -13,11 +15,30 @@ namespace EpinelPS.LobbyServer.Event
|
||||
ResChallengeEventStageData response = new()
|
||||
{
|
||||
RemainTicket = 3,
|
||||
TeamData = user.UserTeams[1]
|
||||
TeamData = new NetUserTeamData
|
||||
{
|
||||
Type = (int)TeamType.StoryEvent
|
||||
},
|
||||
};
|
||||
// check if user has a team for this type
|
||||
if (user.UserTeams.TryGetValue((int)TeamType.StoryEvent, out NetUserTeamData? teamData))
|
||||
{
|
||||
response.TeamData = teamData;
|
||||
}
|
||||
if (!user.EventInfo.TryGetValue(req.EventId, out EventData? eventData))
|
||||
{
|
||||
eventData = new() { LastStage = 0 };
|
||||
user.EventInfo.Add(req.EventId, eventData);
|
||||
}
|
||||
|
||||
// placeholder response data for last cleared stage
|
||||
response.LastClearedEventStageList.Add(new NetLastClearedEventStageData()
|
||||
{
|
||||
DifficultyId = eventData.Diff,
|
||||
StageId = eventData.LastStage
|
||||
});
|
||||
|
||||
// TODO implement properly
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/mission/getclear")]
|
||||
public class GetClearedMissions : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetEventMissionClear req = await ReadData<ReqGetEventMissionClear>(); //has EventIdList
|
||||
|
||||
|
||||
ResGetEventMissionClear response = new();
|
||||
// response.ResGetEventMissionClear.Add(new NetEventMissionClearData(EventId = 0, EventMissionId = 0 , CreatedAt = 0));
|
||||
|
||||
// TODO
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
EpinelPS/LobbyServer/Event/GetEventBoxGacha.cs
Normal file
22
EpinelPS/LobbyServer/Event/GetEventBoxGacha.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/boxgacha/get")]
|
||||
public class GetEventBoxGacha : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// from client: {"EventId":10051}
|
||||
ReqGetEventBoxGacha req = await ReadData<ReqGetEventBoxGacha>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetEventBoxGacha response = new()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data; // For GameData access
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
@@ -15,15 +12,16 @@ namespace EpinelPS.LobbyServer.Event
|
||||
|
||||
ResGetEventScenarioData response = new();
|
||||
|
||||
/*
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? data))
|
||||
if (response.ScenarioIdList.Count == 0)
|
||||
{
|
||||
response.ScenarioIdList.AddRange(data.CompletedScenarios);
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? data))
|
||||
{
|
||||
response.ScenarioIdList.AddRange(data.CompletedScenarios);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Get all scenario_group_Id values from albumResourceRecords starting with "event_"
|
||||
response.ScenarioIdList.Add(GameData.Instance.albumResourceRecords.Values.Where(record => record.ScenarioGroupId.StartsWith("event_")).Select(record => record.ScenarioGroupId).ToList());
|
||||
// response.ScenarioIdList.Add(GameData.Instance.albumResourceRecords.Values.Where(record => record.ScenarioGroupId.StartsWith("event_")).Select(record => record.ScenarioGroupId).ToList());
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/getjoinedevent")]
|
||||
@@ -6,138 +7,14 @@ namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetJoinedEvent req = await ReadData<ReqGetJoinedEvent>();
|
||||
await ReadData<ReqGetJoinedEvent>();
|
||||
//types are defined in EventTypes.cs
|
||||
ResGetJoinedEvent response = new();
|
||||
User user = GetUser();
|
||||
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 20001,
|
||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
// add gacha events from active lobby banners
|
||||
EventHelper.AddJoinedEvents(user, ref response);
|
||||
|
||||
// ssr rapi
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 70077,
|
||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 10046,
|
||||
EventSystemType = (int)EventType.LoginEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 40066,
|
||||
EventSystemType = (int)EventType.StoryEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 60066,
|
||||
EventSystemType = (int)EventType.ChallengeModeEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 70078,
|
||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 70079,
|
||||
EventSystemType = (int)EventType.PickupGachaEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
//full burst day
|
||||
|
||||
/*
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 140052,
|
||||
EventSystemType = RewardUpEvent,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
*/
|
||||
|
||||
//dailies reward up
|
||||
|
||||
/*
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
EventData = new NetEventData()
|
||||
{
|
||||
Id = 170017,
|
||||
EventSystemType = TriggerMissionEventReward,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks
|
||||
},
|
||||
JoinAt = 0
|
||||
});
|
||||
*/
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
@@ -12,98 +11,10 @@ namespace EpinelPS.LobbyServer.Event
|
||||
|
||||
// types are defined in EventTypes.cs
|
||||
ResGetEventList response = new();
|
||||
User user = GetUser();
|
||||
|
||||
// reborn evil collab event
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 82600,
|
||||
EventSystemType = (int)EventSystemType.FieldHubEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 82401,
|
||||
EventSystemType = (int)EventSystemType.CE007MiniGame,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 82602,
|
||||
EventSystemType = (int)EventSystemType.ShopEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 40090,
|
||||
EventSystemType = (int)EventSystemType.StoryEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 40091,
|
||||
EventSystemType = (int)EventSystemType.StoryEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 60090,
|
||||
EventSystemType = (int)EventSystemType.ChallengeModeEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 100034,
|
||||
EventSystemType = (int)EventSystemType.EventPass,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 100035,
|
||||
EventSystemType = (int)EventSystemType.EventPass,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 82603,
|
||||
EventSystemType = (int)EventSystemType.LoginEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
response.EventList.Add(new NetEventData()
|
||||
{
|
||||
Id = 30076,
|
||||
EventSystemType = (int)EventSystemType.CooperationEvent,
|
||||
EventVisibleDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(7)).Ticks,
|
||||
EventStartDate = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).Ticks,
|
||||
EventEndDate = DateTime.Now.AddDays(20).Ticks,
|
||||
EventDisableDate = DateTime.Now.AddDays(20).Ticks
|
||||
});
|
||||
// add events from active lobby banners
|
||||
EventHelper.AddEvents(user, ref response);
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/acquire/achievementmission/reward")]
|
||||
public class AcquireAzxAchievementMissionReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqAcquireMiniGameAzxAchievementMissionReward Fields
|
||||
// int AzxId
|
||||
// RepeatedField<int> AchievementMissionIdList
|
||||
ReqAcquireMiniGameAzxAchievementMissionReward req = await ReadData<ReqAcquireMiniGameAzxAchievementMissionReward>();
|
||||
User user = GetUser();
|
||||
|
||||
// ResAcquireMiniGameAzxAchievementMissionReward Fields
|
||||
// NetRewardData Reward
|
||||
ResAcquireMiniGameAzxAchievementMissionReward response = new();
|
||||
|
||||
NetRewardData reward = new();
|
||||
AzxHelper.AcquireReward(user, ref reward, req.AzxId, req.AchievementMissionIdList);
|
||||
|
||||
response.Reward = reward;
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
568
EpinelPS/LobbyServer/Event/Minigame/AZX/AzxHelper.cs
Normal file
568
EpinelPS/LobbyServer/Event/Minigame/AZX/AzxHelper.cs
Normal file
@@ -0,0 +1,568 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.Collections;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
public static class AzxHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(AzxHelper));
|
||||
|
||||
public static void AcquireReward(User user, ref NetRewardData reward, int azxId, RepeatedField<int> missionIds)
|
||||
{
|
||||
log.Debug($"Acquiring reward for user {user.ID}, azxId: {azxId}, missionIds: {JsonConvert.SerializeObject(missionIds)}");
|
||||
if (missionIds.Count == 0 || azxId == 0) return;
|
||||
try
|
||||
{
|
||||
var missions = GameData.Instance.EventAZXAppleGameMissionTable.Values.Where(x =>
|
||||
x.MissionRewardId > 0 && missionIds.Contains(x.Id)).ToList();
|
||||
if (missions.Count == 0) return;
|
||||
List<Reward_Data> rewardDatas = [];
|
||||
foreach (var mission in missions)
|
||||
{
|
||||
var rewardRecord = GameData.Instance.GetRewardTableEntry(mission.MissionRewardId);
|
||||
if (rewardRecord is null || rewardRecord.Rewards.Count == 0) continue;
|
||||
foreach (var rewardItem in rewardRecord.Rewards)
|
||||
{
|
||||
if (rewardItem.RewardValue == 0) continue;
|
||||
int itemIndex = rewardDatas.FindIndex(x => x.RewardId == rewardItem.RewardId);
|
||||
if (itemIndex >= 0)
|
||||
rewardDatas[itemIndex].RewardValue += rewardItem.RewardValue;
|
||||
else
|
||||
rewardDatas.Add(rewardItem);
|
||||
}
|
||||
}
|
||||
foreach (var rewardData in rewardDatas)
|
||||
{
|
||||
RewardUtils.AddSingleObject(user, ref reward, rewardData.RewardId, rewardData.RewardType, rewardData.RewardValue);
|
||||
}
|
||||
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
azxInfo.AchievementMissionDataList.AddRange(missionIds.Select(x =>
|
||||
new AchievementMissionData() { MissionId = x, IsReceived = true }));
|
||||
|
||||
user.MiniGameAzxInfo[azxId] = azxInfo;
|
||||
JsonDb.Save();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Acquiring reward failed: {ex.Message}", LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetRanking(User user, int azxId, ref ResGetMiniGameAzxRanking response)
|
||||
{
|
||||
// ResGetMiniGameAzxRanking Fields
|
||||
// NetMiniGameAzxRankingData UserGuildRanking
|
||||
// RepeatedField<NetMiniGameAzxRankingData> GuildRankingList
|
||||
// NetMiniGameAzxRankingData Fields
|
||||
// int Rank
|
||||
// NetMiniGameAzxScoreAndTime ScoreAndTime
|
||||
// NetWholeUserData User
|
||||
// NetMiniGameAzxScoreAndTime Fields
|
||||
// int Score
|
||||
// Google.Protobuf.WellKnownTypes.Duration TimeToScore
|
||||
|
||||
int dateDay = GetDateDay();
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
var scoreData = azxInfo.ScoreDatas.Find(x => x.DateDay == dateDay && x.AzxId == azxId);
|
||||
int score = scoreData?.HighScore ?? 0;
|
||||
Duration timeToScore = scoreData?.HighScoreTime ?? new Duration() { Seconds = 0, Nanos = 0 };
|
||||
|
||||
response.UserGuildRanking = new NetMiniGameAzxRankingData()
|
||||
{
|
||||
Rank = 1,
|
||||
ScoreAndTime = new NetMiniGameAzxScoreAndTime()
|
||||
{
|
||||
Score = score,
|
||||
TimeToScore = timeToScore
|
||||
},
|
||||
User = new NetWholeUserData()
|
||||
{
|
||||
Usn = (long)user.ID,
|
||||
Server = 10001,
|
||||
Nickname = user.Nickname,
|
||||
Lv = user.userPointData?.UserLevel ?? 99,
|
||||
Icon = user.ProfileIconId,
|
||||
IconPrism = user.ProfileIconIsPrism,
|
||||
Frame = user.ProfileFrame,
|
||||
LastActionAt = user.LastLogin.Ticks,
|
||||
UserTitleId = user.TitleId,
|
||||
GuildName = user.Nickname,
|
||||
}
|
||||
};
|
||||
response.GuildRankingList.Add(new NetMiniGameAzxRankingData()
|
||||
{
|
||||
Rank = 2,
|
||||
ScoreAndTime = new NetMiniGameAzxScoreAndTime()
|
||||
{
|
||||
Score = 80000,
|
||||
TimeToScore = new Duration() { Seconds = 118, Nanos = 432877000 }
|
||||
},
|
||||
User = new NetWholeUserData()
|
||||
{
|
||||
Usn = (long)user.ID,
|
||||
Server = 10001,
|
||||
Nickname = user.Nickname,
|
||||
Lv = user.userPointData?.UserLevel ?? 99,
|
||||
Icon = user.ProfileIconId,
|
||||
IconPrism = user.ProfileIconIsPrism,
|
||||
Frame = user.ProfileFrame,
|
||||
LastActionAt = user.LastLogin.Ticks,
|
||||
UserTitleId = user.TitleId,
|
||||
GuildName = user.Nickname,
|
||||
}
|
||||
});
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
public static void FinishAzx(User user, ReqFinishMiniGameAzx req, ref ResFinishMiniGameAzx response)
|
||||
{
|
||||
// ReqEnterMiniGameAzx Fields
|
||||
// int AzxId
|
||||
// NetMiniGameAzxScoreAndTime ScoreAndTime
|
||||
// int PlayBoardId
|
||||
// int PlayCharacterId
|
||||
// RepeatedField<NetSkillUseCountData> SkillUseCountList
|
||||
// int CutSceneId
|
||||
|
||||
// ResFinishMiniGameAzx Fields
|
||||
// NetRewardData Reward
|
||||
// NetMiniGameAzxDailyMissionData DailyMissionData
|
||||
// bool IsNewHighScore
|
||||
// bool IsNewHighScoreTime
|
||||
try
|
||||
{
|
||||
log.Debug($"Finishing AZX for user {user.ID}, data: {JsonConvert.SerializeObject(req)}");
|
||||
int dateDay = GetDateDay();
|
||||
|
||||
int score = req.ScoreAndTime.Score;
|
||||
Duration timeToScore = req.ScoreAndTime.TimeToScore;
|
||||
|
||||
NetMiniGameAzxDailyMissionData dailyMissionData = new() { IsDailyRewarded = false, DailyAccumulatedScore = 0 };
|
||||
NetRewardData reward = new();
|
||||
|
||||
var azxInfo = GetAzxInfo(user, req.AzxId);
|
||||
|
||||
if (req.CutSceneId > 0 && azxInfo.CutSceneDataList.Find(x => x.CutSceneId == req.CutSceneId) is null)
|
||||
{
|
||||
azxInfo.CutSceneDataList.Add(new CutSceneData() { CutSceneId = req.CutSceneId, IsNew = true });
|
||||
}
|
||||
|
||||
var scoreDataIndex = azxInfo.ScoreDatas.FindIndex(x => x.DateDay == dateDay && x.AzxId == req.AzxId);
|
||||
if (scoreDataIndex >= 0)
|
||||
{
|
||||
if (score > 10000 && !azxInfo.ScoreDatas[scoreDataIndex].IsDailyRewarded)
|
||||
{
|
||||
azxInfo.ScoreDatas[scoreDataIndex].IsDailyRewarded = true;
|
||||
dailyMissionData.IsDailyRewarded = true;
|
||||
RewardUtils.AddSingleCurrencyObject(user, ref reward, CurrencyType.FreeCash, 30);
|
||||
}
|
||||
azxInfo.ScoreDatas[scoreDataIndex].AccumulatedScore += score;
|
||||
dailyMissionData.DailyAccumulatedScore = azxInfo.ScoreDatas[scoreDataIndex].AccumulatedScore;
|
||||
if (azxInfo.ScoreDatas[scoreDataIndex].HighScore < score)
|
||||
{
|
||||
response.IsNewHighScore = true;
|
||||
azxInfo.ScoreDatas[scoreDataIndex].HighScore = score;
|
||||
}
|
||||
if (azxInfo.ScoreDatas[scoreDataIndex].HighScoreTime.ToTimeSpan() > timeToScore.ToTimeSpan())
|
||||
{
|
||||
response.IsNewHighScoreTime = true;
|
||||
azxInfo.ScoreDatas[scoreDataIndex].HighScoreTime = timeToScore;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isDailyRewarded = false;
|
||||
if (score > 10000)
|
||||
{
|
||||
isDailyRewarded = true;
|
||||
dailyMissionData.IsDailyRewarded = true;
|
||||
RewardUtils.AddSingleCurrencyObject(user, ref reward, CurrencyType.FreeCash, 30);
|
||||
}
|
||||
response.IsNewHighScoreTime = true;
|
||||
response.IsNewHighScore = true;
|
||||
azxInfo.ScoreDatas.Add(new MiniGameAzxScoreData
|
||||
{
|
||||
AzxId = req.AzxId,
|
||||
DateDay = dateDay,
|
||||
AccumulatedScore = score,
|
||||
HighScore = score,
|
||||
HighScoreTime = timeToScore,
|
||||
IsDailyRewarded = isDailyRewarded
|
||||
});
|
||||
dailyMissionData.DailyAccumulatedScore = score;
|
||||
}
|
||||
|
||||
// int PlayBoardId
|
||||
// int PlayCharacterId
|
||||
azxInfo.CharacterCount ??= [];
|
||||
if (azxInfo.CharacterCount.TryGetValue(req.PlayCharacterId, out var characterCount))
|
||||
azxInfo.CharacterCount[req.PlayCharacterId] = characterCount + 1;
|
||||
else
|
||||
azxInfo.CharacterCount.Add(req.PlayCharacterId, 1);
|
||||
// RepeatedField<NetSkillUseCountData> SkillUseCountList
|
||||
if (req.SkillUseCountList != null && req.SkillUseCountList.Count > 0)
|
||||
{
|
||||
azxInfo.SkillCount ??= [];
|
||||
foreach (var item in req.SkillUseCountList)
|
||||
{
|
||||
if (azxInfo.SkillCount.TryGetValue(item.SkillId, out var skillCount))
|
||||
azxInfo.SkillCount[item.SkillId] = skillCount + item.SkillUseCount;
|
||||
else
|
||||
azxInfo.SkillCount.Add(item.SkillId, item.SkillUseCount);
|
||||
}
|
||||
}
|
||||
|
||||
response.DailyMissionData = dailyMissionData;
|
||||
response.Reward = reward;
|
||||
// Save changes
|
||||
user.MiniGameAzxInfo[req.AzxId] = azxInfo;
|
||||
JsonDb.Save();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Finish AZX Error: {ex.Message}", LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnterAzx(User user, ref ResEnterMiniGameAzx response, int azxId, int playBoardId, int playCharacterId)
|
||||
{
|
||||
log.Debug($"Entering AZX AzxId: {azxId}, PlayBoardId: {playBoardId}, PlayCharacterId: {playCharacterId}");
|
||||
try
|
||||
{
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
azxInfo.SelectedBoardId = playBoardId;
|
||||
azxInfo.SelectedCharacterId = playCharacterId;
|
||||
user.MiniGameAzxInfo[azxId] = azxInfo;
|
||||
response.PreviousSRankCount = 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Enter AZX Error: {ex.Message}", LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetAzxData(User user, int azxId, ref ResGetMiniGameAzxData response)
|
||||
{
|
||||
log.Debug($"Getting AZX data for user {user.ID}");
|
||||
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
// Initialize score data if null
|
||||
azxInfo.ScoreDatas ??= [];
|
||||
// Get sum score
|
||||
int sumScore = azxInfo.ScoreDatas.Sum(x => x.AccumulatedScore);
|
||||
|
||||
log.Debug($"AZX data: {JsonConvert.SerializeObject(azxInfo)}");
|
||||
try
|
||||
{
|
||||
// AchievementMissionDataList
|
||||
var missions = GameData.Instance.EventAZXAppleGameMissionTable.Values.ToList();
|
||||
|
||||
foreach (var mission in missions)
|
||||
{
|
||||
bool isReceived = false;
|
||||
azxInfo.AchievementMissionDataList ??= [];
|
||||
var item = azxInfo.AchievementMissionDataList.Find(x => x.MissionId == mission.Id);
|
||||
if (item is not null) isReceived = item.IsReceived;
|
||||
if (mission.MissionType == EventAZXAppleGameMissionMissionType.GetScore)
|
||||
{
|
||||
AddAchievementMission(ref response, mission.Id, sumScore, isReceived);
|
||||
}
|
||||
else if (mission.MissionType == EventAZXAppleGameMissionMissionType.UseSkillCount)
|
||||
{
|
||||
int progress = 0;
|
||||
if (azxInfo.SkillCount.TryGetValue(mission.MissionConditionId, out var skillCount)) progress = skillCount;
|
||||
AddAchievementMission(ref response, mission.Id, progress, isReceived);
|
||||
}
|
||||
else if (mission.MissionType == EventAZXAppleGameMissionMissionType.PlayCharacterCount)
|
||||
{
|
||||
int progress = 0;
|
||||
if (azxInfo.CharacterCount.TryGetValue(mission.MissionConditionId, out var characterCount)) progress = characterCount;
|
||||
AddAchievementMission(ref response, mission.Id, progress, isReceived);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddAchievementMission(ref response, mission.Id, 0, isReceived);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"Get AchievementMissionDataList Error: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// ConditionalBoardDataList
|
||||
azxInfo.ConditionalBoardDataList ??= [];
|
||||
var boards = GameData.Instance.EventAZXAppleGameBoardTable.Values.ToList();
|
||||
foreach (var board in boards)
|
||||
{
|
||||
var item = azxInfo.ConditionalBoardDataList.Find(x => x.BoardId == board.Id);
|
||||
bool isUnlocked = item is not null && item.IsUnlocked;
|
||||
if (board.BoardOpenScore == 0) continue;
|
||||
response.ConditionalBoardDataList.Add(new NetMiniGameAzxConditionalBoardData()
|
||||
{
|
||||
BoardId = board.Id,
|
||||
Progress = sumScore,
|
||||
IsUnlocked = isUnlocked
|
||||
});
|
||||
}
|
||||
// SelectedBoardId
|
||||
int selectedBoardId = azxInfo.SelectedBoardId > 0 ? azxInfo.SelectedBoardId : boards.Min(x => x.Id);
|
||||
response.SelectedBoardId = selectedBoardId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"Get ConditionalBoardDataList Error: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// ConditionalCharacterDataList
|
||||
azxInfo.ConditionalCharacterDataList ??= [];
|
||||
var characters = GameData.Instance.EventAZXAppleGameCharacterTable.Values.ToList();
|
||||
foreach (var character in characters)
|
||||
{
|
||||
var item = azxInfo.ConditionalCharacterDataList.Find(x => x.CharacterId == character.Id);
|
||||
bool isUnlocked = item is not null && item.IsUnlocked;
|
||||
if (character.CharacterOpenScore == 0) continue;
|
||||
response.ConditionalCharacterDataList.Add(new NetMiniGameAzxConditionalCharacterData()
|
||||
{
|
||||
CharacterId = character.Id,
|
||||
Progress = sumScore,
|
||||
IsUnlocked = isUnlocked
|
||||
});
|
||||
}
|
||||
// SelectedCharacterId
|
||||
int selectedCharacterId = azxInfo.SelectedCharacterId > 0 ? azxInfo.SelectedCharacterId : characters.Min(x => x.Id);
|
||||
response.SelectedCharacterId = selectedCharacterId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"Get ConditionalCharacterDataList Error: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// ConditionalSkillDataList
|
||||
azxInfo.ConditionalSkillDataList ??= [];
|
||||
foreach (var skill in GameData.Instance.EventAZXAppleGameSkillTable.Values)
|
||||
{
|
||||
var item = azxInfo.ConditionalSkillDataList.Find(x => x.SkillId == skill.Id);
|
||||
bool isUnlocked = item is not null && item.IsUnlocked;
|
||||
if (skill.SkillOpenUseValue == 0) continue;
|
||||
response.ConditionalSkillDataList.Add(new NetMiniGameAzxConditionalSkillData()
|
||||
{
|
||||
SkillId = skill.Id,
|
||||
IsUnlocked = isUnlocked
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"Get ConditionalSkillDataList Error: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
azxInfo.CutSceneDataList ??= [];
|
||||
foreach (var cutScene in GameData.Instance.EventAZXAppleGameCutSceneTable.Values)
|
||||
{
|
||||
var item = azxInfo.CutSceneDataList.Find(x => x.CutSceneId == cutScene.Id);
|
||||
if (item is not null && item.CutSceneId > 0)
|
||||
{
|
||||
response.CutSceneList.Add(new NetMiniGameAzxCutSceneData { CutSceneId = cutScene.Id, IsNew = item.IsNew });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Get CutSceneList Error: {ex.Message}", LogType.Error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// NetMiniGameAzxDailyMissionData DailyMissionData
|
||||
azxInfo.ScoreDatas ??= [];
|
||||
int dateDay = GetDateDay();
|
||||
var scoreData = azxInfo.ScoreDatas.Find(x => x.DateDay == dateDay && x.AzxId == azxId);
|
||||
if (scoreData is not null)
|
||||
{
|
||||
response.DailyMissionData = new NetMiniGameAzxDailyMissionData()
|
||||
{
|
||||
DailyAccumulatedScore = scoreData.AccumulatedScore,
|
||||
IsDailyRewarded = scoreData.IsDailyRewarded,
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Get DailyMissionData Error: {ex.Message}", LogType.Error);
|
||||
}
|
||||
|
||||
response.IsTutorialConfirmed = azxInfo.IsTutorialConfirmed;
|
||||
}
|
||||
|
||||
public static void SetTutorialConfirmed(User user, int azxId)
|
||||
{
|
||||
log.Debug($"Setting tutorial confirmed for AZX {azxId}");
|
||||
try
|
||||
{
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
azxInfo.IsTutorialConfirmed = true;
|
||||
user.MiniGameAzxInfo[azxId] = azxInfo;
|
||||
log.Debug($"Tutorial data after: {azxInfo.IsTutorialConfirmed}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Setting AZX tutorial confirmed failed: {ex.Message}", LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetBoardUnlocked(User user, int azxId, int boardId)
|
||||
{
|
||||
log.Debug($"Setting board {boardId} unlocked for AZX {azxId}");
|
||||
try
|
||||
{
|
||||
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
azxInfo.ConditionalBoardDataList ??= [];
|
||||
log.Debug($"Board data before: {JsonConvert.SerializeObject(azxInfo.ConditionalBoardDataList)}");
|
||||
var itemIndex = azxInfo.ConditionalBoardDataList.FindIndex(x => x.BoardId == boardId);
|
||||
if (itemIndex >= 0)
|
||||
{
|
||||
azxInfo.ConditionalBoardDataList[itemIndex].IsUnlocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
azxInfo.ConditionalBoardDataList.Add(new ConditionalBoardData() { BoardId = boardId, IsUnlocked = true });
|
||||
}
|
||||
user.MiniGameAzxInfo[azxId] = azxInfo;
|
||||
log.Debug($"Board data after: {JsonConvert.SerializeObject(azxInfo.ConditionalBoardDataList)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Setting AZX board unlocked failed: {ex.Message}", LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetSkillUnlocked(User user, int azxId, int skillId)
|
||||
{
|
||||
log.Debug($"Setting skill {skillId} unlocked for AZX {azxId}");
|
||||
try
|
||||
{
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
azxInfo.ConditionalSkillDataList ??= [];
|
||||
log.Debug($"Skill data before: {JsonConvert.SerializeObject(azxInfo.ConditionalSkillDataList)}");
|
||||
var itemIndex = azxInfo.ConditionalSkillDataList.FindIndex(x => x.SkillId == skillId);
|
||||
if (itemIndex >= 0)
|
||||
{
|
||||
azxInfo.ConditionalSkillDataList[itemIndex].IsUnlocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
azxInfo.ConditionalSkillDataList.Add(new ConditionalSkillData() { SkillId = skillId, IsUnlocked = true });
|
||||
}
|
||||
user.MiniGameAzxInfo[azxId] = azxInfo;
|
||||
log.Debug($"Skill data after: {JsonConvert.SerializeObject(azxInfo.ConditionalSkillDataList)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Setting AZX skill unlocked failed: {ex.Message}", LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetCharacterUnlocked(User user, int azxId, int characterId)
|
||||
{
|
||||
log.Debug($"Setting character {characterId} unlocked for AZX {azxId}");
|
||||
try
|
||||
{
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
azxInfo.ConditionalCharacterDataList ??= [];
|
||||
log.Debug($"Character data before: {JsonConvert.SerializeObject(azxInfo.ConditionalCharacterDataList)}");
|
||||
var itemIndex = azxInfo.ConditionalCharacterDataList.FindIndex(x => x.CharacterId == characterId);
|
||||
if (itemIndex >= 0)
|
||||
{
|
||||
azxInfo.ConditionalCharacterDataList[itemIndex].IsUnlocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
azxInfo.ConditionalCharacterDataList.Add(new ConditionalCharacterData() { CharacterId = characterId, IsUnlocked = true });
|
||||
}
|
||||
user.MiniGameAzxInfo[azxId] = azxInfo;
|
||||
log.Debug($"Character data after: {JsonConvert.SerializeObject(azxInfo.ConditionalCharacterDataList)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Setting AZX character unlocked failed: {ex.Message}", LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetCutSceneConfirmed(User user, int azxId, List<int> cutSceneIdList)
|
||||
{
|
||||
log.Debug($"Setting cutscenes confirmed for AZX {azxId}: {string.Join(", ", cutSceneIdList)}");
|
||||
try
|
||||
{
|
||||
var azxInfo = GetAzxInfo(user, azxId);
|
||||
azxInfo.CutSceneDataList ??= [];
|
||||
log.Debug($"Cutscene data before: {JsonConvert.SerializeObject(azxInfo.CutSceneDataList)}");
|
||||
foreach (var item in cutSceneIdList)
|
||||
{
|
||||
var itemIndex = azxInfo.CutSceneDataList.FindIndex(x => x.CutSceneId == item);
|
||||
if (itemIndex >= 0)
|
||||
{
|
||||
azxInfo.CutSceneDataList[itemIndex].IsNew = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
azxInfo.CutSceneDataList.Add(new CutSceneData() { CutSceneId = item, IsNew = false });
|
||||
}
|
||||
}
|
||||
user.MiniGameAzxInfo[azxId] = azxInfo;
|
||||
log.Debug($"Cutscene data after: {JsonConvert.SerializeObject(azxInfo.CutSceneDataList)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Setting AZX cutscene confirmed failed: {ex.Message}", LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetDateDay()
|
||||
{
|
||||
// +4 每天4点重新计算
|
||||
DateTime dateTime = DateTime.UtcNow.AddHours(4);
|
||||
return dateTime.Year * 10000 + dateTime.Month * 100 + dateTime.Day;
|
||||
}
|
||||
|
||||
private static void AddAchievementMission(ref ResGetMiniGameAzxData response, int missionId, int progress, bool isReceived)
|
||||
{
|
||||
response.AchievementMissionDataList.Add(new NetMiniGameAzxAchievementMissionData()
|
||||
{
|
||||
MissionId = missionId,
|
||||
Progress = progress,
|
||||
IsReceived = isReceived
|
||||
});
|
||||
}
|
||||
|
||||
public static MiniGameAzxData GetAzxInfo(User user, int azxId)
|
||||
{
|
||||
if (!user.MiniGameAzxInfo.TryGetValue(azxId, out var azxInfo))
|
||||
{
|
||||
log.Debug($"Creating new AZX info for {azxId}");
|
||||
user.MiniGameAzxInfo.TryAdd(azxId, new MiniGameAzxData() { });
|
||||
azxInfo = new MiniGameAzxData() { };
|
||||
}
|
||||
log.Debug($"Getting AZX info for {azxId}, data: {JsonConvert.SerializeObject(azxInfo)}");
|
||||
return azxInfo;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
32
EpinelPS/LobbyServer/Event/Minigame/AZX/EnterAzx.cs
Normal file
32
EpinelPS/LobbyServer/Event/Minigame/AZX/EnterAzx.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/enter")]
|
||||
public class EnterAzx : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqEnterMiniGameAzx Fields
|
||||
// int AzxId
|
||||
// int PlayBoardId
|
||||
// int PlayCharacterId
|
||||
ReqEnterMiniGameAzx req = await ReadData<ReqEnterMiniGameAzx>();
|
||||
User user = GetUser();
|
||||
|
||||
// ResEnterMiniGameAzx Fields
|
||||
// int PreviousSRankCount
|
||||
ResEnterMiniGameAzx response = new()
|
||||
{
|
||||
PreviousSRankCount = 0
|
||||
};
|
||||
|
||||
if (req.AzxId > 0 && req.PlayBoardId > 0 && req.PlayCharacterId > 0)
|
||||
AzxHelper.EnterAzx(user, ref response, req.AzxId, req.PlayBoardId, req.PlayCharacterId);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
EpinelPS/LobbyServer/Event/Minigame/AZX/FinishAzx.cs
Normal file
22
EpinelPS/LobbyServer/Event/Minigame/AZX/FinishAzx.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/finish")]
|
||||
public class FinishAzx : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "azxId": 1, "scoreAndTime": { "score": 50000, "timeToScore": "110.122697s" },
|
||||
// "playBoardId": 101, "playCharacterId": 101, "skillUseCountList": [ { "skillId": 102 } ], "cutSceneId": 10101 }
|
||||
ReqFinishMiniGameAzx req = await ReadData<ReqFinishMiniGameAzx>();
|
||||
User user = GetUser();
|
||||
|
||||
ResFinishMiniGameAzx response = new();
|
||||
|
||||
AzxHelper.FinishAzx(user, req, ref response);
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxData.cs
Normal file
43
EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxData.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/get/data")]
|
||||
public class GetAzxData : LobbyMsgHandler
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(LobbyMsgHandler));
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// int AzxId
|
||||
ReqGetMiniGameAzxData req = await ReadData<ReqGetMiniGameAzxData>();
|
||||
User user = GetUser();
|
||||
|
||||
// ResGetMiniGameAzxData Fields
|
||||
// NetMiniGameAzxDailyMissionData DailyMissionData
|
||||
// RepeatedField<NetMiniGameAzxAchievementMissionData> AchievementMissionDataList
|
||||
// RepeatedField<NetMiniGameAzxCutSceneData> CutSceneList
|
||||
// int SelectedBoardId
|
||||
// int SelectedCharacterId
|
||||
// RepeatedField<NetMiniGameAzxConditionalBoardData> ConditionalBoardDataList
|
||||
// RepeatedField<NetMiniGameAzxConditionalCharacterData> ConditionalCharacterDataList
|
||||
// RepeatedField<NetMiniGameAzxConditionalSkillData> ConditionalSkillDataList
|
||||
// bool IsTutorialConfirmed
|
||||
ResGetMiniGameAzxData response = new()
|
||||
{
|
||||
DailyMissionData = new NetMiniGameAzxDailyMissionData()
|
||||
{
|
||||
DailyAccumulatedScore = 0,
|
||||
IsDailyRewarded = false,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Add implementation for AchievementMissionDataList, CutSceneList, etc.
|
||||
AzxHelper.GetAzxData(user, req.AzxId, ref response);
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRanking.cs
Normal file
23
EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRanking.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/get/ranking")]
|
||||
public class GetAzxRanking : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// int AzxId
|
||||
ReqGetMiniGameAzxRanking req = await ReadData<ReqGetMiniGameAzxRanking>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetMiniGameAzxRanking response = new();
|
||||
|
||||
if (req.AzxId > 0)
|
||||
AzxHelper.GetRanking(user, req.AzxId, ref response);
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRedDotData.cs
Normal file
28
EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRedDotData.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/get/reddot/data")]
|
||||
public class GetAzxRedDotData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqGetMiniGameAzxRedDotData Fields
|
||||
// int AzxId
|
||||
ReqGetMiniGameAzxRedDotData req = await ReadData<ReqGetMiniGameAzxRedDotData>();
|
||||
User user = GetUser();
|
||||
|
||||
// ResGetMiniGameAzxRedDotData Fields
|
||||
// bool IsDailyMissionAvailable
|
||||
// bool AchievementMissionRewardExists
|
||||
ResGetMiniGameAzxRedDotData response = new()
|
||||
{
|
||||
IsDailyMissionAvailable = true,
|
||||
AchievementMissionRewardExists = true
|
||||
};
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/set/board/unlocked")]
|
||||
public class SetAzxBoardUnlocked : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqSetMiniGameAzxBoardUnlocked Fields
|
||||
// int AzxId
|
||||
// int BoardId
|
||||
ReqSetMiniGameAzxBoardUnlocked req = await ReadData<ReqSetMiniGameAzxBoardUnlocked>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetMiniGameAzxBoardUnlocked response = new();
|
||||
|
||||
if (req.BoardId > 0 && req.AzxId > 0)
|
||||
AzxHelper.SetBoardUnlocked(user, req.AzxId, req.BoardId);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/set/character/unlocked")]
|
||||
public class SetAzxCharacterUnlocked : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqSetMiniGameAzxCharacterUnlocked Fields
|
||||
// int AzxId
|
||||
// int CharacterId
|
||||
ReqSetMiniGameAzxCharacterUnlocked req = await ReadData<ReqSetMiniGameAzxCharacterUnlocked>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetMiniGameAzxCharacterUnlocked response = new();
|
||||
|
||||
if (req.CharacterId > 0 && req.AzxId > 0)
|
||||
AzxHelper.SetCharacterUnlocked(user, req.AzxId, req.CharacterId);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/set/cutscene/confirmed")]
|
||||
public class SetAzxCutSceneConfirmed : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqEnterMiniGameAzx Fields
|
||||
// int AzxId
|
||||
// RepeatedField<int> ConfirmedCutSceneIdList
|
||||
ReqSetMiniGameAzxCutSceneConfirmed req = await ReadData<ReqSetMiniGameAzxCutSceneConfirmed>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetMiniGameAzxCutSceneConfirmed response = new();
|
||||
|
||||
if (req.AzxId > 0 && req.ConfirmedCutSceneIdList.Count > 0)
|
||||
AzxHelper.SetCutSceneConfirmed(user, req.AzxId, [.. req.ConfirmedCutSceneIdList]);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/set/skill/unlocked")]
|
||||
public class SetAzxSkillUnlocked : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqSetMiniGameAzxSkillUnlocked Fields
|
||||
// int AzxId
|
||||
// int SkillId
|
||||
ReqSetMiniGameAzxSkillUnlocked req = await ReadData<ReqSetMiniGameAzxSkillUnlocked>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetMiniGameAzxSkillUnlocked response = new();
|
||||
|
||||
if (req.SkillId > 0 && req.AzxId > 0)
|
||||
AzxHelper.SetSkillUnlocked(user, req.AzxId, req.SkillId);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Minigame.AZX
|
||||
{
|
||||
[PacketPath("/event/minigame/azx/set/tutorial/confirmed")]
|
||||
public class SetAzxTutorialConfirmed : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
await ReadData<ReqSetMiniGameAzxTutorialConfirmed>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetMiniGameAzxTutorialConfirmed response = new();
|
||||
|
||||
AzxHelper.SetTutorialConfirmed(user, 1);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
188
EpinelPS/LobbyServer/Event/Mission/EventMissionHelper.cs
Normal file
188
EpinelPS/LobbyServer/Event/Mission/EventMissionHelper.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.Collections;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
using static ResGetEventMissionClearList.Types;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Mission
|
||||
{
|
||||
public static class EventMissionHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(EventMissionHelper));
|
||||
|
||||
public static RepeatedField<NetEventMissionClearData> GetCleared(User user, int eventId)
|
||||
{
|
||||
var clearData = new RepeatedField<NetEventMissionClearData>();
|
||||
if (!user.EventMissionInfo.TryGetValue(eventId, out var userEvent)) return clearData;
|
||||
log.Debug($"GetClear UserEvent: {JsonConvert.SerializeObject(userEvent)}");
|
||||
int dateDay = user.GetDateDay();
|
||||
// Check if it's a new day, reset daily missions
|
||||
if (userEvent.LastDay != dateDay)
|
||||
{
|
||||
ResetUserDailyMission(user, eventId, dateDay);
|
||||
}
|
||||
|
||||
foreach (var id in userEvent.DailyMissionIdList)
|
||||
{
|
||||
clearData.Add(new NetEventMissionClearData()
|
||||
{
|
||||
EventId = eventId,
|
||||
EventMissionId = id,
|
||||
CreatedAt = userEvent.LastDate
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var id in userEvent.MissionIdList)
|
||||
{
|
||||
clearData.Add(new NetEventMissionClearData()
|
||||
{
|
||||
EventId = eventId,
|
||||
EventMissionId = id,
|
||||
CreatedAt = userEvent.LastDate
|
||||
});
|
||||
}
|
||||
return clearData;
|
||||
}
|
||||
|
||||
public static RepeatedField<NestEventMissionClear> GetClearedList(User user, RepeatedField<int> eventIds)
|
||||
{
|
||||
var clearDatas = new RepeatedField<NestEventMissionClear>();
|
||||
if (eventIds.Count == 0) return clearDatas;
|
||||
foreach (var eventId in eventIds)
|
||||
{
|
||||
var clearData = new NestEventMissionClear
|
||||
{
|
||||
EventId = eventId
|
||||
};
|
||||
clearData.EventMissionClearList.AddRange(GetCleared(user, eventId));
|
||||
}
|
||||
|
||||
return clearDatas;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Obtain reward for event mission
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="reward"></param>
|
||||
/// <param name="eventId"></param>
|
||||
/// <param name="missionIds"></param>
|
||||
/// <param name="timeStamp"></param>
|
||||
public static void ObtainReward(User user, ref NetRewardData reward, int eventId, RepeatedField<int> missionIds, Timestamp timeStamp)
|
||||
{
|
||||
EventMissionData userEvent = GetUserEventMissionData(user, eventId);
|
||||
log.Debug($"ObtainReward UserEvent Before: {JsonConvert.SerializeObject(userEvent)}");
|
||||
|
||||
int dateDay = user.GetDateDay();
|
||||
// Check if it's a new day, reset daily missions
|
||||
if (userEvent.LastDay != dateDay)
|
||||
{
|
||||
log.Debug($"ObtainReward New Day: {dateDay}");
|
||||
ResetUserDailyMission(user, eventId, dateDay);
|
||||
}
|
||||
|
||||
var userMissionIds = userEvent.MissionIdList ?? [];
|
||||
var userDailyMissionIds = userEvent.DailyMissionIdList ?? [];
|
||||
|
||||
var eventMissionRecords = GameData.Instance.EventMissionListTable.Values.Where(em =>
|
||||
missionIds.Contains(em.Id)
|
||||
&& !userMissionIds.Contains(em.Id)
|
||||
&& !userDailyMissionIds.Contains(em.Id)).ToList();
|
||||
if (eventMissionRecords.Count == 0) return;
|
||||
log.Debug($"ObtainReward Event Mission Records: {JsonConvert.SerializeObject(eventMissionRecords)}");
|
||||
|
||||
List<Reward_Data> rewards = [];
|
||||
foreach (var mission in eventMissionRecords)
|
||||
{
|
||||
if (mission.RewardId == 0)
|
||||
{
|
||||
if (mission.RewardPointValue > 0)
|
||||
{
|
||||
user.AddTrigger(Trigger.PointRewardEvent, mission.RewardPointValue, mission.Group);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
user.AddTrigger(Trigger.MissionClearEvent, 1, mission.Group);
|
||||
var rewardRecord = GameData.Instance.GetRewardTableEntry(mission.RewardId);
|
||||
if (rewardRecord is null || rewardRecord.Rewards.Count == 0) continue;
|
||||
foreach (var item in rewardRecord.Rewards)
|
||||
{
|
||||
var itemIndex = rewards.FindIndex(x => x.RewardId == item.RewardId);
|
||||
if (itemIndex >= 0)
|
||||
rewards[itemIndex].RewardValue += item.RewardValue;
|
||||
else
|
||||
rewards.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
// if (rewards.Count == 0) return;
|
||||
log.Debug($"ObtainReward Rewards: {JsonConvert.SerializeObject(rewards)}");
|
||||
// Add rewards to user
|
||||
foreach (var r in rewards)
|
||||
{
|
||||
RewardUtils.AddSingleObject(user, ref reward, r.RewardId, r.RewardType, r.RewardValue);
|
||||
}
|
||||
|
||||
// Add mission ids to user
|
||||
var groupIds = eventMissionRecords.Select(x => x.Group).Distinct();
|
||||
var categoryRecords = GameData.Instance.EventMissionCategoryTable.Values.Where(ec => groupIds.Contains(ec.MissionListGroup)).ToList();
|
||||
if (categoryRecords.Count == 0) return;
|
||||
foreach (var mission in eventMissionRecords)
|
||||
{
|
||||
var categoryRecord = categoryRecords.FirstOrDefault(ec => ec.MissionListGroup == mission.Group);
|
||||
if (categoryRecord is null) continue;
|
||||
if (categoryRecord.InitType == EventMissionInitType.Daily)
|
||||
{
|
||||
userEvent.DailyMissionIdList.Add(mission.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
userEvent.MissionIdList.Add(mission.Id);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in reward.Item)
|
||||
{
|
||||
user.AddTrigger(Trigger.ObtainEventCurrencyMaterial, item.Count, item.Tid);
|
||||
}
|
||||
|
||||
userEvent.LastDate = timeStamp.ToDateTime().Ticks;
|
||||
user.EventMissionInfo[eventId] = userEvent;
|
||||
log.Debug($"ObtainReward UserEvent After: {JsonConvert.SerializeObject(userEvent)}");
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
|
||||
private static void ResetUserDailyMission(User user, int eventId, int dateDay)
|
||||
{
|
||||
if (!user.EventMissionInfo.TryGetValue(eventId, out var userEvent)) return;
|
||||
if (userEvent.LastDay == dateDay) return;
|
||||
user.EventMissionInfo[eventId].DailyMissionIdList = [];
|
||||
user.EventMissionInfo[eventId].LastDay = dateDay;
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get user event mission data, if not exists, create a new one
|
||||
/// </summary>
|
||||
/// <param name="user">User</param>
|
||||
/// <param name="eventId">EventId</param>
|
||||
/// <returns>EventMissionData</returns>
|
||||
private static EventMissionData GetUserEventMissionData(User user, int eventId)
|
||||
{
|
||||
// Get user event mission data, if not exists, create a new one
|
||||
if (!user.EventMissionInfo.TryGetValue(eventId, out var userEvent))
|
||||
{
|
||||
userEvent = new EventMissionData();
|
||||
user.EventMissionInfo.Add(eventId, userEvent);
|
||||
}
|
||||
return userEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Mission
|
||||
{
|
||||
[PacketPath("/event/mission/getclearlist")]
|
||||
public class GetClearList : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetEventMissionClearList req = await ReadData<ReqGetEventMissionClearList>();
|
||||
|
||||
ResGetEventMissionClearList response = new(); //field ResGetEventMissionClearMap data type NestEventMissionClear field NestEventMissionClear data type NetEventMissionClearData fields EventId EventMissionId CreatedAt
|
||||
|
||||
// TOOD
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EpinelPS/LobbyServer/Event/Mission/GetMissionClear.cs
Normal file
29
EpinelPS/LobbyServer/Event/Mission/GetMissionClear.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Mission
|
||||
{
|
||||
[PacketPath("/event/mission/getclear")]
|
||||
public class GetMissionClear : LobbyMsgHandler
|
||||
{
|
||||
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqGetEventMissionClear>(); //EventId
|
||||
User user = GetUser();
|
||||
|
||||
ResGetEventMissionClear response = new();
|
||||
|
||||
try
|
||||
{
|
||||
response.EventMissionClearList.AddRange(EventMissionHelper.GetCleared(user, req.EventId));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Warn($"GetMissionClear failed: {ex.Message}");
|
||||
}
|
||||
|
||||
// TODO
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
EpinelPS/LobbyServer/Event/Mission/GetMissionClearList.cs
Normal file
27
EpinelPS/LobbyServer/Event/Mission/GetMissionClearList.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Mission
|
||||
{
|
||||
[PacketPath("/event/mission/getclearlist")]
|
||||
public class GetMissionClearList : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "eventIdList": [ 60090, 60092, 20001, 20002 ] }
|
||||
ReqGetEventMissionClearList req = await ReadData<ReqGetEventMissionClearList>();
|
||||
User user = GetUser();
|
||||
ResGetEventMissionClearList response = new();
|
||||
|
||||
try
|
||||
{
|
||||
response.ResGetEventMissionClearMap.AddRange(EventMissionHelper.GetClearedList(user, req.EventIdList));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Warn($"GetMissionClearList failed: {ex.Message}");
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
EpinelPS/LobbyServer/Event/Mission/ObtainMissionReward.cs
Normal file
40
EpinelPS/LobbyServer/Event/Mission/ObtainMissionReward.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Mission
|
||||
{
|
||||
[PacketPath("/event/mission/reward")]
|
||||
public class ObtainMissionReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "eventId": 20001, "dailyEventId": [ 200010105 ] }
|
||||
// ReqObtainEventMissionReward Fields
|
||||
// int EventId
|
||||
// RepeatedField<int> EventMissionIdList
|
||||
// Google.Protobuf.WellKnownTypes.Timestamp RequestTimeStamp
|
||||
var req = await ReadData<ReqObtainEventMissionReward>();
|
||||
User user = GetUser();
|
||||
|
||||
// ResObtainEventMissionReward Fields
|
||||
// NetRewardData Reward
|
||||
// ObtainEventMissionRewardResult Result
|
||||
ResObtainEventMissionReward response = new()
|
||||
{
|
||||
Result = ObtainEventMissionRewardResult.Success
|
||||
};
|
||||
|
||||
var reward = new NetRewardData();
|
||||
try
|
||||
{
|
||||
EventMissionHelper.ObtainReward(user, ref reward, req.EventId, req.EventMissionIdList, req.RequestTimeStamp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Warn($"ObtainMissionReward failed: {ex.Message}");
|
||||
}
|
||||
response.Reward = reward;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
EpinelPS/LobbyServer/Event/Shop/BuyProduct.cs
Normal file
26
EpinelPS/LobbyServer/Event/Shop/BuyProduct.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Shop
|
||||
{
|
||||
[PacketPath("/event/shopbuyproduct")]
|
||||
public class BuyProduct : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// int EventId
|
||||
var req = await ReadData<ReqEventShopBuyProduct>();
|
||||
ResEventShopBuyProduct response = new();
|
||||
User user = GetUser();
|
||||
try
|
||||
{
|
||||
EventShopHelper.BuyShopProduct(user, ref response, req);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Error buying shop product: {ex.Message}", LogType.Error);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
495
EpinelPS/LobbyServer/Event/Shop/EventShopHelper.cs
Normal file
495
EpinelPS/LobbyServer/Event/Shop/EventShopHelper.cs
Normal file
@@ -0,0 +1,495 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Shop
|
||||
{
|
||||
public static class EventShopHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(EventShopHelper));
|
||||
|
||||
|
||||
public static void BuyShopProduct(User user, ref ResEventShopBuyProduct response, ReqEventShopBuyProduct req)
|
||||
{
|
||||
ResEventShopMultipleBuyProduct MultipleResponse = new();
|
||||
List<NetBuyProductRequestData> buyProducts = [new() { ShopProductTid = req.ShopProductTid, Quantity = req.Quantity, Order = req.Order }];
|
||||
bool isSuccess = ExecuteBuyProduct(user, ref MultipleResponse, buyProducts);
|
||||
|
||||
if (!isSuccess) return;
|
||||
AddEventShopBuyCount(user, ref MultipleResponse, req.EventId, req.ShopProductTid, req.Quantity, req.Order);
|
||||
JsonDb.Save();
|
||||
|
||||
// Update currency data
|
||||
if (MultipleResponse.Currencies.Count > 0)
|
||||
response.Currencies.AddRange(MultipleResponse.Currencies);
|
||||
|
||||
// Update item data
|
||||
if (MultipleResponse.Items.Count > 0)
|
||||
response.Item = MultipleResponse.Items[0];
|
||||
|
||||
// Update product data
|
||||
response.Product = new();
|
||||
if (MultipleResponse.Product.UserItems.Count > 0) response.Product.UserItems.AddRange(MultipleResponse.Product.UserItems);
|
||||
|
||||
if (MultipleResponse.Product.Item.Count > 0) response.Product.Item.AddRange(MultipleResponse.Product.Item);
|
||||
|
||||
if (MultipleResponse.Product.Currency.Count > 0) response.Product.Currency.AddRange(MultipleResponse.Product.Currency);
|
||||
|
||||
if (MultipleResponse.Product.BuyCounts.Count > 0) response.Product.BuyCount = MultipleResponse.Product.BuyCounts[0].BuyCount;
|
||||
|
||||
if (MultipleResponse.Product.Character.Count > 0) response.Product.Character.AddRange(MultipleResponse.Product.Character);
|
||||
|
||||
if (MultipleResponse.Product.UserCharacters.Count > 0) response.Product.UserCharacters.AddRange(MultipleResponse.Product.UserCharacters);
|
||||
|
||||
if (MultipleResponse.Product.AutoCharge.Count > 0) response.Product.AutoCharge.AddRange(MultipleResponse.Product.AutoCharge);
|
||||
|
||||
// user.AddTrigger(Trigger.MainShopBuy, req.Quantity);
|
||||
|
||||
// Save changes to the database
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
public static void BuyShopMultipleProduct(User user, ref ResEventShopMultipleBuyProduct response, ReqEventShopMultipleBuyProduct req)
|
||||
{
|
||||
bool isSuccess = ExecuteBuyProduct(user, ref response, [.. req.Products]);
|
||||
|
||||
if (!isSuccess) return;
|
||||
foreach (var item in req.Products)
|
||||
{
|
||||
AddEventShopBuyCount(user, ref response, req.EventId, item.ShopProductTid, item.Quantity, item.Order);
|
||||
}
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
private static void AddEventShopBuyCount(User user, ref ResEventShopMultipleBuyProduct response, int eventId, int productTid, int quantity, int order)
|
||||
{
|
||||
if (!user.EventShopBuyCountInfo.TryGetValue(eventId, out var buyCountInfo))
|
||||
{
|
||||
buyCountInfo = new() { EventId = eventId, datas = [] };
|
||||
user.EventShopBuyCountInfo.TryAdd(eventId, buyCountInfo);
|
||||
}
|
||||
|
||||
var index = buyCountInfo.datas.FindIndex(x => x.ProductTid == productTid);
|
||||
if (index >= 0)
|
||||
{
|
||||
buyCountInfo.datas[index].BuyCount += quantity;
|
||||
response.Product.BuyCounts.Add(new NetBuyCountData { Order = order, BuyCount = buyCountInfo.datas[index].BuyCount });
|
||||
}
|
||||
else
|
||||
{
|
||||
buyCountInfo.datas.Add(new() { ProductTid = productTid, BuyCount = quantity });
|
||||
response.Product.BuyCounts.Add(new NetBuyCountData { Order = order, BuyCount = quantity });
|
||||
}
|
||||
user.EventShopBuyCountInfo[eventId] = buyCountInfo;
|
||||
}
|
||||
|
||||
private static bool ExecuteBuyProduct(User user, ref ResEventShopMultipleBuyProduct response, List<NetBuyProductRequestData> buyProducts)
|
||||
{
|
||||
if (buyProducts == null || buyProducts.Count == 0) return false;
|
||||
|
||||
response.Product = new();
|
||||
|
||||
var productTids = buyProducts.Select(p => p.ShopProductTid).ToList();
|
||||
var shopProducts = GameData.Instance.ContentsShopProductTable.Values.Where(x => productTids.Contains(x.Id)).ToList();
|
||||
|
||||
// Check user currency and item balance
|
||||
if (CheckUserCurrencyAndItemBalance(user, shopProducts, buyProducts, out Dictionary<int, int> totalCurrencyPrice, out Dictionary<int, int> totalItemPrice))
|
||||
{
|
||||
// Deduct user currency and item
|
||||
DeductUserCurrencyAndItems(user, totalCurrencyPrice, totalItemPrice, ref response);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process each shopProduct
|
||||
foreach (var shopProduct in shopProducts)
|
||||
{
|
||||
var buyProduct = buyProducts.FirstOrDefault(bp => bp.ShopProductTid == shopProduct.Id);
|
||||
int quantity = buyProduct.Quantity;
|
||||
int order = buyProduct.Order;
|
||||
if (shopProduct.GoodsType == RewardType.Item || shopProduct.GoodsType.ToString().StartsWith("Equipment"))
|
||||
{
|
||||
AddItemById(user, ref response, itemId: shopProduct.GoodsId, RewardType.Item, shopProduct.GoodsValue, quantity, order);
|
||||
}
|
||||
else if (shopProduct.GoodsType == RewardType.Currency)
|
||||
{
|
||||
long val = shopProduct.GoodsValue * quantity;
|
||||
user.AddCurrency((CurrencyType)shopProduct.GoodsId, val);
|
||||
// buyCounts.Add(new() { Order = order, BuyCount = quantity });
|
||||
response.Product.Currency.Add(new NetCurrencyData() { Type = shopProduct.GoodsId, Value = val, FinalValue = user.GetCurrencyVal((CurrencyType)shopProduct.GoodsId) });
|
||||
}
|
||||
else if (shopProduct.GoodsType == RewardType.Character)
|
||||
{
|
||||
AddCharacterByCharacterTid(user, ref response, shopProduct.GoodsId, shopProduct.GoodsValue, quantity, order);
|
||||
}
|
||||
else if (shopProduct.GoodsType == RewardType.UserTitle)
|
||||
{
|
||||
response.Product.UserTitleList.Add(shopProduct.GoodsId);
|
||||
}
|
||||
else if (shopProduct.GoodsType == RewardType.LiveWallpaper)
|
||||
{
|
||||
response.Product.LiveWallPapers.Add(shopProduct.GoodsId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.WriteLine($"Unsupported GoodsType: {shopProduct.GoodsType}");
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void DeductUserCurrencyAndItems(User user, Dictionary<int, int> totalCurrencyPrice, Dictionary<int, int> totalItemPrice,
|
||||
ref ResEventShopMultipleBuyProduct response)
|
||||
{
|
||||
foreach (int key in totalCurrencyPrice.Keys)
|
||||
{
|
||||
CurrencyType currencyType = (CurrencyType)key;
|
||||
user.SubtractCurrency(currencyType, totalCurrencyPrice[key]);
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = key, Value = user.GetCurrencyVal(currencyType) });
|
||||
}
|
||||
foreach (int tid in totalItemPrice.Keys)
|
||||
{
|
||||
var item = user.Items.FirstOrDefault(i => i.ItemType == tid);
|
||||
user.RemoveItemBySerialNumber(item.Isn, totalItemPrice[tid]);
|
||||
response.Items.Add(new NetUserItemData() { Tid = tid, Count = user.Items.FirstOrDefault(i => i.ItemType == tid).Count, Isn = item.Isn });
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CheckUserCurrencyAndItemBalance(User user, List<ContentsShopProductRecord> shopProducts, List<NetBuyProductRequestData> buyProducts,
|
||||
out Dictionary<int, int> totalCurrencyPrices, out Dictionary<int, int> totalItemPrices)
|
||||
{
|
||||
totalCurrencyPrices = shopProducts
|
||||
.Where(sp => sp.PriceType == PriceType.Currency)
|
||||
.GroupBy(sp => sp.PriceId)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Sum(sp => sp.PriceValue * buyProducts.FirstOrDefault(bp => bp.ShopProductTid == sp.Id).Quantity)
|
||||
);
|
||||
|
||||
totalItemPrices = shopProducts
|
||||
.Where(sp => sp.PriceType == PriceType.Item)
|
||||
.GroupBy(sp => sp.PriceId)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Sum(sp => sp.PriceValue * buyProducts.FirstOrDefault(bp => bp.ShopProductTid == sp.Id).Quantity)
|
||||
);
|
||||
|
||||
Logging.WriteLine($"totalCurrencyPrice: {JsonConvert.SerializeObject(totalCurrencyPrices)}", LogType.Debug);
|
||||
foreach (int currencyType in totalCurrencyPrices.Keys)
|
||||
{
|
||||
var userCurrency = user.Currency.FirstOrDefault(x => x.Key == (CurrencyType)currencyType).Value;
|
||||
if (userCurrency < totalCurrencyPrices[currencyType])
|
||||
{
|
||||
Logging.WriteLine($"Insufficient funds: Have {userCurrency}, need {totalCurrencyPrices[currencyType]}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Logging.WriteLine($"totalItemPrice: {JsonConvert.SerializeObject(totalItemPrices)}", LogType.Debug);
|
||||
foreach (int tid in totalItemPrices.Keys)
|
||||
{
|
||||
var item = user.Items.FirstOrDefault(i => i.ItemType == tid);
|
||||
if (item == null || item.Count < totalItemPrices[tid])
|
||||
{
|
||||
Logging.WriteLine($"Insufficient item funds: Have {item?.Count ?? 0}, need {totalItemPrices[tid]}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static int GetEventShopId(int eventId)
|
||||
{
|
||||
if (eventId <= 0) return 0;
|
||||
|
||||
if (GameData.Instance.eventManagers.TryGetValue(eventId, out var eventRecord))
|
||||
{
|
||||
if (eventRecord.EventShortcutId is not null && eventRecord.EventShortcutId != "")
|
||||
{
|
||||
return Convert.ToInt32(eventRecord.EventShortcutId);
|
||||
}
|
||||
}
|
||||
log.Warn($"EventManager not found for EventId: {eventId}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize Shop Data
|
||||
/// </summary>
|
||||
/// <param name="shopId">Shop ID</param>
|
||||
/// <returns>Shop Data</returns>
|
||||
public static NetEventShopProductData InitShopData(User user, int eventId)
|
||||
{
|
||||
NetEventShopProductData shop = new();
|
||||
|
||||
var shopId = GetEventShopId(eventId);
|
||||
if (shopId <= 0) return shop;
|
||||
|
||||
try
|
||||
{
|
||||
if (!GameData.Instance.ContentsShopTable.TryGetValue(shopId, out var tableShop))
|
||||
{
|
||||
Logging.WriteLine($"Invalid shopId: {shopId}", LogType.Warning);
|
||||
return shop;
|
||||
}
|
||||
|
||||
var userBuyCounts = new List<EventShopProductData>();
|
||||
if (user.EventShopBuyCountInfo.TryGetValue(eventId, out var userBuyCountInfo)) userBuyCounts = userBuyCountInfo.datas;
|
||||
|
||||
shop.ShopTid = tableShop.Id;
|
||||
shop.ShopCategory = (int)tableShop.ShopCategory;
|
||||
GameData.Instance.ContentsShopProductTable.Values
|
||||
.Where(csp => csp.BundleId == tableShop.BundleId).ToList().ForEach(csp =>
|
||||
{
|
||||
var buyCount = userBuyCounts.FirstOrDefault(x => x.ProductTid == csp.Id)?.BuyCount ?? 0;
|
||||
shop.List.Add(new NetShopProductInfoData
|
||||
{
|
||||
Order = csp.ProductOrder,
|
||||
ProductId = csp.Id,
|
||||
BuyLimitCount = csp.BuyLimitCount,
|
||||
BuyCount = buyCount,
|
||||
// Discount = csp.DiscountProbId,
|
||||
});
|
||||
});
|
||||
|
||||
return shop;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Error in InitShopData: {ex}");
|
||||
return shop;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> GetShopIds()
|
||||
{
|
||||
List<string> shopIds = [];
|
||||
var ContentsShopTable = GameData.Instance.ContentsShopTable;
|
||||
var eventShops = ContentsShopTable.Values.Where(s => s.ShopCategory == ShopCategoryType.ShopStoryEvent || s.ShopType == ShopType.EventShop).ToList();
|
||||
foreach (var shop in eventShops)
|
||||
{
|
||||
shopIds.Add(shop.Id.ToString());
|
||||
}
|
||||
log.Debug($"Final list of shop IDs: {JsonConvert.SerializeObject(shopIds)}");
|
||||
return shopIds;
|
||||
}
|
||||
|
||||
public static bool UpdateCurrency(User user, int priceId, int priceValue, int quantity, ref ResEventShopBuyProduct response)
|
||||
{
|
||||
long totalPrice = priceValue * quantity;
|
||||
if (!user.Currency.TryGetValue((CurrencyType)priceId, out var currentAmount))
|
||||
{
|
||||
Logging.WriteLine($"Insufficient funds: Have {currentAmount}, need {totalPrice}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentAmount < totalPrice)
|
||||
{
|
||||
Logging.WriteLine($"Insufficient funds: Have {currentAmount}, need {totalPrice}");
|
||||
return false;
|
||||
}
|
||||
CurrencyType currencyType = (CurrencyType)priceId; // Assuming PriceId maps directly to CurrencyType
|
||||
long newAmount = currentAmount - totalPrice; // calculate new amount
|
||||
user.Currency[currencyType] = newAmount; // update user currency
|
||||
response.Currencies.Add(new NetUserCurrencyData // Update response currency
|
||||
{
|
||||
Type = (int)currencyType,
|
||||
Value = newAmount
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool UpdateItem(User user, int priceId, int priceValue, int quantity, ref ResEventShopBuyProduct response)
|
||||
{
|
||||
var item = user.Items.FirstOrDefault(i => i.ItemType == priceId);
|
||||
if (item == null || item.Count < quantity)
|
||||
{
|
||||
Logging.WriteLine($"Insufficient item funds: Have {item?.Count ?? 0}, need {priceValue * quantity}");
|
||||
return false; // Not enough items
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Count -= priceValue * quantity; // Deduct the item cost
|
||||
if (item.Count <= 0)
|
||||
{
|
||||
user.Items.Remove(item); // Remove item if count is zero or less
|
||||
}
|
||||
// Update response items
|
||||
response.Item = new()
|
||||
{
|
||||
Tid = item.ItemType,
|
||||
Count = item.Count,
|
||||
Isn = item.Isn
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void AddCharacterByCharacterTid(User user, ref ResEventShopMultipleBuyProduct response, int characterTid, int goodsValue, int quantity, int order)
|
||||
{
|
||||
// Get character data from GameData.Instance.CharacterTable
|
||||
if (!GameData.Instance.CharacterTable.TryGetValue(characterTid, out var characterRecord))
|
||||
{
|
||||
return; // Character data not found, return
|
||||
}
|
||||
// Check if character already exists in user.Characters
|
||||
var userCharacter = user.GetCharacter(characterTid);
|
||||
bool isAddNewCharacter = userCharacter == null;
|
||||
if (isAddNewCharacter)
|
||||
{
|
||||
// Add new character to user.Characters
|
||||
userCharacter = new CharacterModel()
|
||||
{
|
||||
Csn = user.GenerateUniqueCharacterId(),
|
||||
Grade = 1,
|
||||
Tid = characterRecord.Id,
|
||||
};
|
||||
user.Characters.Add(userCharacter);
|
||||
response.Product.UserCharacters.Add(ToNetUserCharacter(userCharacter));
|
||||
}
|
||||
NetCharacterData netCharacter = new() { Csn = userCharacter.Csn, Tid = userCharacter.Tid };
|
||||
|
||||
// Calculate character material num
|
||||
int characterMaterialNum = isAddNewCharacter ? goodsValue * quantity - 1 : goodsValue * quantity;
|
||||
if (characterMaterialNum > 0)
|
||||
{
|
||||
var currentOriginalRare = characterRecord.OriginalRare;
|
||||
// Get max core num
|
||||
int maxCoreNum = currentOriginalRare == OriginalRareType.SSR ? 11 : currentOriginalRare == OriginalRareType.SR ? 3 : 1;
|
||||
|
||||
// Get current core num
|
||||
int currentCoreNum = currentOriginalRare == OriginalRareType.SSR ? userCharacter.Grade : currentOriginalRare == OriginalRareType.SR ? userCharacter.Grade % 100 : 1;
|
||||
// If current core num is greater than max core num, set current core num to max core num
|
||||
if (currentCoreNum > maxCoreNum) currentCoreNum = maxCoreNum;
|
||||
int currentMaterialNum = user.Items.FirstOrDefault(x => x.ItemType == characterRecord.PieceId)?.Count ?? 0;
|
||||
bool isAddMaterial = currentCoreNum < maxCoreNum;
|
||||
int addMaterialNum = characterMaterialNum - currentMaterialNum;
|
||||
int addCurrencyNum = 0;
|
||||
bool isAddCurrency = currentCoreNum + addMaterialNum > maxCoreNum;
|
||||
if (isAddCurrency)
|
||||
{
|
||||
int MaterialCurrencyNum = currentOriginalRare == OriginalRareType.SSR ? 6000 : currentOriginalRare == OriginalRareType.SR ? 200 : 150;
|
||||
addCurrencyNum = (currentCoreNum + addMaterialNum - maxCoreNum) * MaterialCurrencyNum;
|
||||
addMaterialNum = maxCoreNum - currentCoreNum;
|
||||
}
|
||||
Dictionary<CurrencyType, long> currency = [];
|
||||
if (addCurrencyNum > 0)
|
||||
{
|
||||
netCharacter.CurrencyValue = addCurrencyNum;
|
||||
user.AddCurrency(CurrencyType.DissolutionPoint, addCurrencyNum);
|
||||
currency.Add(CurrencyType.DissolutionPoint, addCurrencyNum);
|
||||
}
|
||||
List<ItemData> items = [];
|
||||
List<ItemData> userItems = [];
|
||||
if (addMaterialNum > 0)
|
||||
{
|
||||
netCharacter.PieceCount = addMaterialNum;
|
||||
AddItemById(user, ref response, characterRecord.PieceId, RewardType.Item, goodsValue, addMaterialNum, order);
|
||||
}
|
||||
response.Product.Character.Add(netCharacter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void AddItemById(User user, ref ResEventShopMultipleBuyProduct response,
|
||||
int itemId, RewardType itemType, int goodsValue, int quantity, int order)
|
||||
{
|
||||
var userItemIndex = user.Items.FindIndex(i => i.ItemType == itemId);
|
||||
var isEquip = GameData.Instance.ItemEquipTable.TryGetValue(itemId, out var equip);
|
||||
if (userItemIndex >= 0)
|
||||
{
|
||||
if (isEquip)
|
||||
{
|
||||
// the item is not stackable, we need to create new entries for each quantity
|
||||
for (int i = 0; i < goodsValue * quantity; i++)
|
||||
{
|
||||
var (tid, pos, isn) = (itemId, GetItemPos(equip.ItemSubType), user.GenerateUniqueItemId());
|
||||
ItemData newItem = new() { ItemType = tid, Count = 1, Position = pos, Isn = isn, Corp = GetEquipCorp(itemType) };
|
||||
user.Items.Add(newItem);
|
||||
response.Product.Item.Add(NetUtils.ItemDataToNet(newItem));
|
||||
response.Product.UserItems.Add(NetUtils.UserItemDataToNet(newItem));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
user.Items[userItemIndex].Count += goodsValue * quantity;
|
||||
response.Product.UserItems.Add(NetUtils.UserItemDataToNet(user.Items[userItemIndex]));
|
||||
var (tid, count, isn) = (itemId, goodsValue * quantity, user.Items[userItemIndex].Isn);
|
||||
bool isAddAutoCharge = AddAutoChargeByTid(ref response, itemId: itemId, value: count, finalValue: user.Items[userItemIndex].Count);
|
||||
if (!isAddAutoCharge)
|
||||
{
|
||||
response.Product.Item.Add(new NetItemData() { Tid = tid, Count = count, Isn = isn });
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var (tid, count, isn) = (itemId, goodsValue * quantity, user.GenerateUniqueItemId());
|
||||
ItemData itemData = new() { ItemType = tid, Count = count, Isn = isn };
|
||||
user.Items.Add(itemData);
|
||||
response.Product.UserItems.Add(NetUtils.UserItemDataToNet(itemData));
|
||||
bool isAddAutoCharge = AddAutoChargeByTid(ref response, itemId: itemId, value: count, finalValue: count);
|
||||
if (!isAddAutoCharge)
|
||||
{
|
||||
response.Product.Item.Add(NetUtils.ItemDataToNet(itemData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AddAutoChargeByTid(ref ResEventShopMultipleBuyProduct response, int itemId, int value, int finalValue)
|
||||
{
|
||||
var autoCharge = GameData.Instance.AutoChargeTable.Values.FirstOrDefault(x => x.ItemId == itemId);
|
||||
if (autoCharge is null) return false;
|
||||
response.Product.AutoCharge.Add(new NetAutoChargeData() { AutoChargeId = autoCharge.Id, Value = value, FinalValue = finalValue });
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int GetItemPos(ItemSubType subType)
|
||||
{
|
||||
return subType switch
|
||||
{
|
||||
ItemSubType.ModuleA => 0,
|
||||
ItemSubType.ModuleB => 1,
|
||||
ItemSubType.ModuleC => 2,
|
||||
ItemSubType.ModuleD => 3,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
public static int GetEquipCorp(RewardType subType)
|
||||
{
|
||||
return subType switch
|
||||
{
|
||||
RewardType.EquipmentELYSION => 1,
|
||||
RewardType.EquipmentMISSILIS => 2,
|
||||
RewardType.EquipmentTETRA => 3,
|
||||
RewardType.EquipmentPILGRIM => 4,
|
||||
RewardType.EquipmentABNORMAL => 7,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
public static NetUserCharacterDefaultData ToNetUserCharacter(CharacterModel character)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
CostumeId = character.CostumeId,
|
||||
Csn = character.Csn,
|
||||
Grade = character.Grade,
|
||||
Lv = character.Level,
|
||||
UltiSkillLv = character.UltimateLevel,
|
||||
Skill1Lv = character.Skill1Lvl,
|
||||
Skill2Lv = character.Skill2Lvl,
|
||||
Tid = character.Tid,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Shop
|
||||
{
|
||||
[PacketPath("/event/shopproductlist")]
|
||||
public class ListProductList : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqShopProductList req = await ReadData<ReqShopProductList>();
|
||||
User user = GetUser();
|
||||
|
||||
ResShopProductList response = new();
|
||||
response.Shops.Add(new NetShopProductData()
|
||||
{
|
||||
|
||||
});
|
||||
|
||||
// TODO implement properly
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
EpinelPS/LobbyServer/Event/Shop/MultipleBuyProduct.cs
Normal file
45
EpinelPS/LobbyServer/Event/Shop/MultipleBuyProduct.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Shop
|
||||
{
|
||||
[PacketPath("/event/shopmultiplebuyproduct")]
|
||||
public class MultipleBuyProduct : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqEventShopMultipleBuyProduct Fileds
|
||||
// int EventId
|
||||
// int ShopCategory
|
||||
// RepeatedField<NetBuyProductRequestData> Products
|
||||
// NetBuyProductRequestData Fileds
|
||||
// int ShopProductTid
|
||||
// int Order
|
||||
// int Quantity
|
||||
var req = await ReadData<ReqEventShopMultipleBuyProduct>();
|
||||
|
||||
// ResEventShopMultipleBuyProduct Fileds
|
||||
// EventShopBuyProductResult Result
|
||||
// NetShopBuyMultipleProductData Product
|
||||
// RepeatedField<NetUserItemData> Items
|
||||
// RepeatedField<NetUserCurrencyData> Currencies
|
||||
ResEventShopMultipleBuyProduct response = new()
|
||||
{
|
||||
Result = EventShopBuyProductResult.Success
|
||||
};
|
||||
User user = GetUser();
|
||||
|
||||
try
|
||||
{
|
||||
EventShopHelper.BuyShopMultipleProduct(user, ref response, req);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Error buying shop product: {ex.Message}", LogType.Error);
|
||||
}
|
||||
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
EpinelPS/LobbyServer/Event/Shop/ProductList.cs
Normal file
27
EpinelPS/LobbyServer/Event/Shop/ProductList.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Shop
|
||||
{
|
||||
[PacketPath("/event/shopproductlist")]
|
||||
public class ProductList : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// int EventId
|
||||
var req = await ReadData<ReqEventShopProductList>();
|
||||
ResEventShopProductList response = new();
|
||||
User user = GetUser();
|
||||
|
||||
try
|
||||
{
|
||||
response.Shops.Add(EventShopHelper.InitShopData(user, req.EventId));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Get EventShopProductList Error: {ex.Message}", LogType.Error);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
EpinelPS/LobbyServer/Event/StoryEvent/ClearEventStage.cs
Normal file
54
EpinelPS/LobbyServer/Event/StoryEvent/ClearEventStage.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.StoryEvent
|
||||
{
|
||||
[PacketPath("/event/storydungeon/clearstage")]
|
||||
public class ClearEventStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqClearEventStage req = await ReadData<ReqClearEventStage>();
|
||||
User user = GetUser();
|
||||
|
||||
ResClearEventStage response = new();
|
||||
|
||||
int difficultId = 0;
|
||||
NetRewardData reward = new();
|
||||
NetRewardData bonusReward = new();
|
||||
ClearEventStageHelper.ClearStage(user, req.StageId, ref reward, ref bonusReward, req.BattleResult, 1); // always clearCount = 1 for normal clear
|
||||
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? eventData) && req.BattleResult == 1)
|
||||
{
|
||||
if (!eventData.ClearedStages.Contains(req.StageId))
|
||||
{
|
||||
eventData.ClearedStages.Add(req.StageId);
|
||||
}
|
||||
if (eventData.LastStage < req.StageId) eventData.LastStage = req.StageId;
|
||||
eventData.Diff = difficultId;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.EventInfo.Add(req.EventId, new EventData() { LastStage = req.StageId, ClearedStages = [req.StageId] });
|
||||
}
|
||||
user.AddTrigger(Trigger.EventStageClear, 1, req.StageId);
|
||||
user.AddTrigger(Trigger.EventDungeonStageClear, 1, req.EventId);
|
||||
if (bonusReward.Item.Count > 0)
|
||||
{
|
||||
bonusReward.Item.ToList().ForEach(item =>
|
||||
{
|
||||
user.AddTrigger(Trigger.ObtainEventCurrencyMaterial, item.Count, item.Tid);
|
||||
});
|
||||
}
|
||||
|
||||
response.RemainTicket = EventStoryHelper.SubtractTicket(user, req.EventId, 1);
|
||||
|
||||
response.Reward = reward;
|
||||
response.BonusReward = bonusReward;
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
116
EpinelPS/LobbyServer/Event/StoryEvent/ClearEventStageHelper.cs
Normal file
116
EpinelPS/LobbyServer/Event/StoryEvent/ClearEventStageHelper.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.StoryEvent
|
||||
{
|
||||
public static class ClearEventStageHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(ClearEventStageHelper));
|
||||
|
||||
/// <summary>
|
||||
/// Clear event stage and get rewards
|
||||
/// </summary>
|
||||
/// <param name="user">The user clearing the stage</param>
|
||||
/// <param name="stageId">The ID of the stage being cleared</param>
|
||||
/// <param name="reward">The reward data for the stage</param>
|
||||
/// <param name="bonusReward">The bonus reward data for the stage</param>
|
||||
/// <param name="battleResult">The result of the battle</param>
|
||||
/// <param name="clearCount">The number of times the stage has been cleared</param>
|
||||
public static void ClearStage(User user, int stageId, ref NetRewardData reward, ref NetRewardData bonusReward, int battleResult = 0, int clearCount = 0)
|
||||
{
|
||||
if (battleResult != 1) return;
|
||||
if (clearCount < 1) clearCount = 1;
|
||||
GetReward(user, stageId, ref reward, clearCount);
|
||||
GetBonusReward(user, stageId, ref bonusReward, clearCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get normal reward for clearing event stage
|
||||
/// </summary>
|
||||
/// <param name="user">The user clearing the stage</param>
|
||||
/// <param name="stageId">The ID of the stage being cleared</param>
|
||||
/// <param name="reward">The reward data for the stage</param>
|
||||
/// <param name="battleResult">The result of the battle</param>
|
||||
/// <param name="clearCount">The number of times the stage has been cleared</param>
|
||||
public static void GetReward(User user, int stageId, ref NetRewardData reward, int clearCount)
|
||||
{
|
||||
int rewardId = GetRewardId(stageId);
|
||||
if (rewardId == 0) return;
|
||||
RecievedReward(user, ref reward, rewardId, clearCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get bonus reward for clearing event stage
|
||||
/// </summary>
|
||||
/// <param name="user">The user clearing the stage </param>
|
||||
/// <param name="stageId">The ID of the stage being cleared</param>
|
||||
/// <param name="bonusReward">The bonus reward data for the stage</param>
|
||||
/// <param name="battleResult">The result of the battle</param>
|
||||
/// <param name="clearCount">The number of times the stage has been cleared</param>
|
||||
public static void GetBonusReward(User user, int stageId, ref NetRewardData bonusReward, int clearCount)
|
||||
{
|
||||
int rewardId = GetBonusRewardId(stageId);
|
||||
if (rewardId == 0) return;
|
||||
RecievedReward(user, ref bonusReward, rewardId, clearCount);
|
||||
}
|
||||
|
||||
private static void RecievedReward(User user, ref NetRewardData reward,int rewardId, int clearCount)
|
||||
{
|
||||
RewardRecord? rewardData = GameData.Instance.GetRewardTableEntry(rewardId);
|
||||
if (rewardData == null)
|
||||
{
|
||||
Logging.WriteLine($"unknown reward Id {rewardId}", LogType.Error);
|
||||
return;
|
||||
}
|
||||
foreach (var item in rewardData.Rewards)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (item.RewardType == RewardType.None) continue;
|
||||
RewardUtils.AddSingleObject(user, ref reward, item.RewardId, item.RewardType, item.RewardValue * clearCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get reward Id from EventDungeonSpotBattleTable
|
||||
/// </summary>
|
||||
/// <param name="stageId">The ID of the stage being cleared</param>
|
||||
/// <returns>The reward ID for the stage</returns>
|
||||
private static int GetRewardId(int stageId)
|
||||
{
|
||||
if (GameData.Instance.EventDungeonSpotBattleTable.TryGetValue(stageId, out EventDungeonSpotBattleRecord? stageRecord))
|
||||
{
|
||||
return stageRecord.ClearRewardId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get bonus reward Id from EventDungeonTable via EventDungeonStageTable and EventDungeonDifficultTable
|
||||
/// </summary>
|
||||
/// <param name="stageId">The ID of the stage being cleared</param>
|
||||
/// <returns>The bonus reward ID for the stage</returns>
|
||||
private static int GetBonusRewardId(int stageId)
|
||||
{
|
||||
if (!GameData.Instance.EventDungeonStageTable.TryGetValue(stageId, out EventDungeonStageRecord? eventStage))
|
||||
{
|
||||
log.Error($"EventDungeonStageTable not found for StageId: {stageId}");
|
||||
return 0;
|
||||
}
|
||||
EventDungeonDifficultRecord? difficult = GameData.Instance.EventDungeonDifficultTable.Values.FirstOrDefault(x => x.StageGroup == eventStage.Group);
|
||||
if (difficult == null)
|
||||
{
|
||||
log.Error($"EventDungeonDifficultTable not found for Group: {eventStage.Group}");
|
||||
return 0;
|
||||
}
|
||||
EventDungeonRecord? dungeon = GameData.Instance.EventDungeonTable.Values.FirstOrDefault(x => x.DifficultGroup == difficult.Group);
|
||||
if (dungeon == null)
|
||||
{
|
||||
log.Error($"EventDungeonTable not found for DifficultGroup: {difficult.Group}");
|
||||
return 0;
|
||||
}
|
||||
return dungeon.BonusRewardId;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
139
EpinelPS/LobbyServer/Event/StoryEvent/EventStoryHelper.cs
Normal file
139
EpinelPS/LobbyServer/Event/StoryEvent/EventStoryHelper.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.StoryEvent
|
||||
{
|
||||
public static class EventStoryHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(EventStoryHelper));
|
||||
|
||||
/// <summary>
|
||||
/// Get user remain ticket by event id
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="eventId"></param>
|
||||
/// <returns></returns>
|
||||
public static int GetTicket(User user, int eventId)
|
||||
{
|
||||
|
||||
int freeTicket = 5; // Default ticket is 5
|
||||
|
||||
// Get user event data, if exists, get free ticket
|
||||
if (user.EventInfo.TryGetValue(eventId, out var eventData))
|
||||
{
|
||||
freeTicket = eventData.FreeTicket;
|
||||
}
|
||||
|
||||
// Get item ticket information and free ticket max
|
||||
(ItemData itemTicket, int freeTicketMax) = GetItemTicket(user, eventId);
|
||||
|
||||
// Get remain item ticket
|
||||
int remainItemTicket = itemTicket?.Count ?? 0;
|
||||
|
||||
// If free ticket is greater than free ticket max, set free ticket to free ticket max
|
||||
if (freeTicket > freeTicketMax) freeTicket = freeTicketMax;
|
||||
|
||||
int dateDay = user.GetDateDay();
|
||||
// If dateDay is greater than last day, update user free ticket and last day
|
||||
if (dateDay > eventData.LastDay)
|
||||
{
|
||||
Logging.WriteLine($"GetTicket ResetFreeTicket DateDay: {dateDay}, LastDay: {eventData.LastDay}, FreeTicketMax: {freeTicketMax}", LogType.Debug);
|
||||
freeTicket = freeTicketMax;
|
||||
user.EventInfo[eventId].FreeTicket = freeTicket;
|
||||
user.EventInfo[eventId].LastDay = dateDay;
|
||||
}
|
||||
|
||||
// Remain ticket is free ticket + item ticket
|
||||
int remainTicket = freeTicket + remainItemTicket;
|
||||
|
||||
Logging.WriteLine($"GetTicket FreeTicket: {freeTicket}, ItemTicket: {remainItemTicket}, RemainTicket: {remainTicket}", LogType.Debug);
|
||||
return remainTicket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtract user remaining ticket by event id and value
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="eventId"></param>
|
||||
/// <param name="val"></param>
|
||||
/// <returns>remaining ticket</returns>
|
||||
public static int SubtractTicket(User user, int eventId, int val)
|
||||
{
|
||||
int freeTicket = 5; // Default ticket is 5
|
||||
|
||||
// Get user event data, if exists, get free ticket
|
||||
if (user.EventInfo.TryGetValue(eventId, out var eventData))
|
||||
{
|
||||
freeTicket = eventData.FreeTicket;
|
||||
}
|
||||
|
||||
// Get item ticket information
|
||||
(ItemData itemTicket, _) = GetItemTicket(user, eventId);
|
||||
// Get remain item ticket
|
||||
int remainItemTicket = itemTicket?.Count ?? 0;
|
||||
|
||||
// If free ticket is enough to subtract
|
||||
if (freeTicket >= val)
|
||||
{
|
||||
freeTicket -= val;
|
||||
user.EventInfo[eventId].FreeTicket = freeTicket;
|
||||
|
||||
int remainTicket = freeTicket + remainItemTicket;
|
||||
Logging.WriteLine($"SubtractTicket Value: {val}, FreeTicket: {freeTicket}, ItemTicket: {remainItemTicket}, RemainTicket: {remainTicket}", LogType.Debug);
|
||||
return remainTicket;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If free ticket is not enough to subtract, subtract free ticket and subtract item ticket
|
||||
int SubtractItemTicket = val - freeTicket;
|
||||
user.EventInfo[eventId].FreeTicket = 0;
|
||||
if (itemTicket is not null)
|
||||
{
|
||||
user.RemoveItemBySerialNumber(itemTicket.Isn, SubtractItemTicket);
|
||||
}
|
||||
|
||||
freeTicket = 0;
|
||||
// Remain ticket is free ticket + item ticket
|
||||
int remainTicket = freeTicket + remainItemTicket;
|
||||
Logging.WriteLine($"SubtractTicket Value: {val}, FreeTicket: {freeTicket}, ItemTicket: {remainItemTicket}, RemainTicket: {remainTicket}", LogType.Debug);
|
||||
return remainTicket;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get user item ticket and free ticket max by event id
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="eventId"></param>
|
||||
/// <returns></returns>
|
||||
private static (ItemData itemTicket, int freeTicketMax) GetItemTicket(User user, int eventId)
|
||||
{
|
||||
int freeTicketMax = 5; // Default free ticket max is 5
|
||||
// Get event story data
|
||||
var eventStory = GameData.Instance.EventStoryTable.Values.FirstOrDefault(x => x.EventId == eventId);
|
||||
|
||||
// If event story data is null or auto charge id is 0, return null and default free ticket max
|
||||
if (eventStory is null || eventStory.AutoChargeId == 0) return (null, freeTicketMax);
|
||||
log.Debug($"GetItemTicket EventId: {eventId}, EventStory: {JsonConvert.SerializeObject(eventStory)}");
|
||||
|
||||
// If auto charge data is null, return null and default free ticket max
|
||||
if (!GameData.Instance.AutoChargeTable.TryGetValue(eventStory.AutoChargeId, out var autoCharge)) return (null, 5);
|
||||
log.Debug($"GetItemTicket AutoChargeId: {eventStory.AutoChargeId}, AutoCharge: {JsonConvert.SerializeObject(autoCharge)}");
|
||||
|
||||
// If auto charge max is 0, return null and default free ticket max
|
||||
if (autoCharge.AutoChargeMax == 0) return (null, freeTicketMax);
|
||||
|
||||
freeTicketMax = autoCharge.AutoChargeMax; // Set free ticket max to auto charge max
|
||||
|
||||
// Get user item
|
||||
var userItem = user.Items.FirstOrDefault(x => x.ItemType == autoCharge.ItemId);
|
||||
log.Debug($"GetItemTicket UserItem: {(userItem is not null ? JsonConvert.SerializeObject(userItem) : null)}");
|
||||
return (userItem, freeTicketMax); // Return user item and free ticket max
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
39
EpinelPS/LobbyServer/Event/StoryEvent/FastClearEventStage.cs
Normal file
39
EpinelPS/LobbyServer/Event/StoryEvent/FastClearEventStage.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.StoryEvent
|
||||
{
|
||||
[PacketPath("/event/storydungeon/fastclearstage")]
|
||||
public class FastClearEventStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqFastClearEventStage req = await ReadData<ReqFastClearEventStage>();
|
||||
User user = GetUser();
|
||||
|
||||
ResFastClearEventStage response = new();
|
||||
|
||||
NetRewardData reward = new();
|
||||
NetRewardData bonusReward = new();
|
||||
ClearEventStageHelper.ClearStage(user, req.StageId, ref reward, ref bonusReward, 1, req.ClearCount); // always battleResult = 1 for fast clear
|
||||
|
||||
user.AddTrigger(Trigger.EventDungeonStageClear, req.ClearCount, req.EventId);
|
||||
response.RemainTicket = EventStoryHelper.SubtractTicket(user, req.EventId, req.ClearCount);
|
||||
|
||||
if (bonusReward.Item.Count > 0)
|
||||
{
|
||||
bonusReward.Item.ToList().ForEach(item =>
|
||||
{
|
||||
user.AddTrigger(Trigger.ObtainEventCurrencyMaterial, item.Count, item.Tid);
|
||||
});
|
||||
}
|
||||
|
||||
response.Reward = reward;
|
||||
response.BonusReward = bonusReward;
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.StoryEvent
|
||||
{
|
||||
@@ -9,32 +10,43 @@ namespace EpinelPS.LobbyServer.Event.StoryEvent
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqStoryDungeonEventData req = await ReadData<ReqStoryDungeonEventData>();
|
||||
int evId = req.EventId;
|
||||
User user = GetUser();
|
||||
|
||||
|
||||
if (!user.EventInfo.TryGetValue(evId, out EventData? eventData))
|
||||
// Get user event data, if not exist, create new one
|
||||
if (!user.EventInfo.TryGetValue(req.EventId, out EventData? eventData))
|
||||
{
|
||||
eventData = new();
|
||||
eventData = new() { LastDay = user.GetDateDay(), FreeTicket = 5};
|
||||
user.EventInfo.TryAdd(req.EventId, eventData);
|
||||
}
|
||||
|
||||
|
||||
ResStoryDungeonEventData response = new()
|
||||
{
|
||||
RemainTicket = 5,
|
||||
|
||||
TeamData = new NetUserTeamData()
|
||||
{
|
||||
LastContentsTeamNumber = 1,
|
||||
Type = 20
|
||||
}
|
||||
RemainTicket = EventStoryHelper.GetTicket(user, req.EventId),
|
||||
TeamData = new NetUserTeamData
|
||||
{
|
||||
Type = (int)TeamType.StoryEvent
|
||||
},
|
||||
};
|
||||
|
||||
if (user.UserTeams.TryGetValue((int)TeamType.StoryEvent, out NetUserTeamData? teamData))
|
||||
{
|
||||
response.TeamData = teamData;
|
||||
}
|
||||
foreach (var stageId in eventData.ClearedStages)
|
||||
{
|
||||
response.LastClearedEventStageList.Add(new NetLastClearedEventStageData()
|
||||
{
|
||||
StageId = stageId
|
||||
});
|
||||
}
|
||||
response.LastClearedEventStageList.Add(new NetLastClearedEventStageData()
|
||||
{
|
||||
DifficultyId = eventData.Diff,
|
||||
StageId = eventData.LastStage
|
||||
});
|
||||
// TOOD
|
||||
// TODO
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
|
||||
if (req.ItemData == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"No material item provIded", 400);
|
||||
throw new BadHttpRequestException($"No material item provided", 400);
|
||||
}
|
||||
|
||||
ItemData? userItem = user.Items.FirstOrDefault(x => x.Isn == req.ItemData.Isn);
|
||||
@@ -59,7 +59,7 @@ namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
|
||||
if (isGreatSuccess)
|
||||
{
|
||||
targetLevel = probabilityData.GreatSuccessRate;
|
||||
targetLevel = probabilityData.GreatSuccessLevel;
|
||||
}
|
||||
|
||||
int goldCost = baseExp * 10;
|
||||
|
||||
@@ -20,13 +20,13 @@ namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
throw new BadHttpRequestException("Favorite item not found", 400);
|
||||
}
|
||||
|
||||
int srItemTId = rFavoriteItem.Tid + 1;
|
||||
int srItemTid = rFavoriteItem.Tid + 1;
|
||||
|
||||
|
||||
NetUserFavoriteItemData? srInventoryItem = user.FavoriteItems.FirstOrDefault(f => f.Tid == srItemTId && f.Csn == 0);
|
||||
NetUserFavoriteItemData? srInventoryItem = user.FavoriteItems.FirstOrDefault(f => f.Tid == srItemTid && f.Csn == 0);
|
||||
if (srInventoryItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"No SR-grade favorite item (TID: {srItemTId}) available in inventory for exchange", 400);
|
||||
throw new BadHttpRequestException($"No SR-grade favorite item (TID: {srItemTid}) available in inventory for exchange", 400);
|
||||
}
|
||||
|
||||
(int NewLevel, int RemainingExp, double ConversionRate) expConversion = CalculateExpConversion(rFavoriteItem.Lv, rFavoriteItem.Exp);
|
||||
@@ -43,7 +43,7 @@ namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
NetUserFavoriteItemData newSrFavoriteItem = new NetUserFavoriteItemData
|
||||
{
|
||||
FavoriteItemId = user.GenerateUniqueItemId(),
|
||||
Tid = srItemTId,
|
||||
Tid = srItemTid,
|
||||
Csn = equippedCharacterCsn, // Maintain equipment status
|
||||
Lv = expConversion.NewLevel,
|
||||
Exp = expConversion.RemainingExp
|
||||
|
||||
19
EpinelPS/LobbyServer/Hexacode/GetAll.cs
Normal file
19
EpinelPS/LobbyServer/Hexacode/GetAll.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Hexacode;
|
||||
|
||||
[PacketPath("/hexacode/get-all")]
|
||||
public class GetAll : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetHexaAll req = await ReadData<ReqGetHexaAll>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetHexaAll response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,8 @@ namespace EpinelPS.LobbyServer.Intercept
|
||||
ResInterceptAnomalousData response = new()
|
||||
{
|
||||
InterceptAnomalousManagerId = 101,
|
||||
RemainingTickets = 5
|
||||
TodayRemainingTickets = 5
|
||||
};
|
||||
response.ClearedInterceptAnomalousIds.Add([1, 2, 3, 4, 5]);
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Intercept
|
||||
{
|
||||
@@ -8,17 +9,32 @@ namespace EpinelPS.LobbyServer.Intercept
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetInterceptData req = await ReadData<ReqGetInterceptData>();
|
||||
ReqGetInterceptData req = await ReadData<ReqGetInterceptData>();
|
||||
|
||||
int specialId = GetCurrentInterceptionIds();
|
||||
|
||||
ResGetInterceptData response = new()
|
||||
{
|
||||
NormalInterceptGroup = 1,
|
||||
SpecialInterceptId = 1, // TODO switch this out each reset
|
||||
SpecialInterceptId = specialId,
|
||||
TicketCount = User.ResetableData.InterceptionTickets,
|
||||
MaxTicketCount = JsonDb.Instance.MaxInterceptionCount
|
||||
};
|
||||
|
||||
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.Data;
|
||||
namespace EpinelPS.LobbyServer.Inventory
|
||||
{
|
||||
[PacketPath("/inventory/get")]
|
||||
@@ -13,9 +13,35 @@ namespace EpinelPS.LobbyServer.Inventory
|
||||
ResGetInventoryData response = new();
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ namespace EpinelPS.LobbyServer.Inventory
|
||||
// we NEED to make sure the target item itself is in the delta list, or the UI won't update!
|
||||
response.Items.Add(NetUtils.ToNet(destItem));
|
||||
|
||||
// Add trigger for equipment level count - 升级装备次数
|
||||
user.AddTrigger(Trigger.EquipItemLevelCount, 1);
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using System.Linq;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Inventory
|
||||
{
|
||||
@@ -13,27 +15,120 @@ namespace EpinelPS.LobbyServer.Inventory
|
||||
|
||||
ResWearEquipmentList response = new();
|
||||
|
||||
// TODO optimize
|
||||
foreach (long item2 in req.IsnList)
|
||||
{
|
||||
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
|
||||
foreach (ItemData item in user.Items.ToArray())
|
||||
{
|
||||
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.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));
|
||||
}
|
||||
}
|
||||
@@ -43,5 +138,22 @@ namespace EpinelPS.LobbyServer.Inventory
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Jukebox
|
||||
{
|
||||
[PacketPath("/jukebox/set/tableId")]
|
||||
[PacketPath("/jukebox/set/tableid")]
|
||||
public class SetTableId : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
|
||||
@@ -116,8 +116,11 @@ namespace EpinelPS.LobbyServer
|
||||
msg2.MergeFrom(Contents);
|
||||
|
||||
Logging.WriteLine("Reading " + msg2.GetType().Name, LogType.Debug);
|
||||
PrintMessage(msg2);
|
||||
Logging.WriteLine("", LogType.Debug);
|
||||
if (msg2.GetType().Name != "ReqSyncBadge")
|
||||
{
|
||||
PrintMessage(msg2);
|
||||
Logging.WriteLine("", LogType.Debug);
|
||||
}
|
||||
|
||||
return msg2;
|
||||
}
|
||||
@@ -130,8 +133,11 @@ namespace EpinelPS.LobbyServer
|
||||
PacketDecryptResponse bin = await PacketDecryption.DecryptOrReturnContentAsync(ctx);
|
||||
msg.MergeFrom(bin.Contents);
|
||||
|
||||
PrintMessage(msg);
|
||||
Logging.WriteLine("", LogType.Debug);
|
||||
if (msg.GetType().Name != "ReqSyncBadge")
|
||||
{
|
||||
PrintMessage(msg);
|
||||
Logging.WriteLine("", LogType.Debug);
|
||||
}
|
||||
|
||||
UserId = bin.UserId;
|
||||
UsedAuthToken = bin.UsedAuthToken;
|
||||
|
||||
@@ -110,6 +110,10 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
||||
}
|
||||
|
||||
response.LastClearedNormalMainStageId = user.LastNormalStageCleared;
|
||||
response.LastClearedStoryStageId = user.LastStoryStageCleared;
|
||||
response.LastClearedHardMainStageId = user.LastHardStageCleared;
|
||||
response.LastClearedMod = user.LastClearedDifficulty;
|
||||
|
||||
response.TimeRewardBuffs.AddRange(NetUtils.GetOutpostTimeReward(user));
|
||||
|
||||
response.OwnedLobbyDecoBackgroundIdList.AddRange(user.LobbyDecoBackgroundList);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.LobbyUser
|
||||
{
|
||||
@@ -7,28 +8,69 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetContentsOpenData req = await ReadData<ReqGetContentsOpenData>();
|
||||
await ReadData<ReqGetContentsOpenData>();
|
||||
User user = GetUser();
|
||||
|
||||
// this request returns a list of "special" stages that mark when something is unlocked, ex: the shop or interception
|
||||
|
||||
List<int> specialStages = [6003003, 6002008, 6002016, 6005003, 6003021, 6011018, 6007021, 6004018, 6005013, 6003009, 6003012, 6009017, 6016039, 6001004, 6000003, 6000001, 6002001, 6004023, 6005026, 6020050, 6006004, 6006023,6022049];
|
||||
|
||||
ResGetContentsOpenData response = new();
|
||||
foreach (FieldInfoNew field in user.FieldInfoNew.Values)
|
||||
|
||||
List<int> stages = [];
|
||||
|
||||
foreach (var item in GameData.Instance.ContentsOpenTable)
|
||||
{
|
||||
foreach (int stage in field.CompletedStages)
|
||||
foreach (var condition in item.Value.OpenCondition)
|
||||
{
|
||||
if (specialStages.Contains(stage))
|
||||
response.ClearStageList.Add(stage);
|
||||
if (condition.OpenConditionType == ContentsOpenCondition.StageClear && !stages.Contains(condition.OpenConditionValue) && user.IsStageCompleted(condition.OpenConditionValue))
|
||||
{
|
||||
stages.Add(condition.OpenConditionValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
response.MaxGachaCount = 10;
|
||||
response.MaxGachaPremiumCount = 10;
|
||||
|
||||
// these stages are not present in contentsopentable but are required to show mission UI and burst sidebar UI in battle view
|
||||
List<int> specialStages = [6000001, 6000003];
|
||||
|
||||
foreach (var item in specialStages)
|
||||
{
|
||||
if (!stages.Contains(item) && user.IsStageCompleted(item)) stages.Add(item);
|
||||
}
|
||||
|
||||
response.ClearStageList.AddRange(stages);
|
||||
response.MaxGachaCount = user.GachaTutorialPlayCount;
|
||||
response.MaxGachaPremiumCount = user.GachaTutorialPlayCount;
|
||||
// todo tutorial playcount of gacha
|
||||
response.TutorialGachaPlayCount = user.GachaTutorialPlayCount;
|
||||
|
||||
// ClearSimRoomChapterList: 已通关的章节列表,用于显示超频选项 SimRoomOC
|
||||
response.ClearSimRoomChapterList.AddRange(GetClearSimRoomChapterList(user));
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private static List<int> GetClearSimRoomChapterList(User user)
|
||||
{
|
||||
var clearSimRoomChapterList = new List<int>();
|
||||
try
|
||||
{
|
||||
var currentDifficulty = user.ResetableData.SimRoomData?.CurrentDifficulty ?? 0;
|
||||
var currentChapter = user.ResetableData.SimRoomData?.CurrentChapter ?? 0;
|
||||
if (currentDifficulty > 0 && currentChapter > 0)
|
||||
{
|
||||
var chapters = GameData.Instance.SimulationRoomChapterTable.Values.Where(c => c.DifficultyId <= currentDifficulty).ToList();
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
bool isAdd = chapter.DifficultyId < currentDifficulty ||
|
||||
(chapter.DifficultyId == currentDifficulty && chapter.Chapter <= currentChapter);
|
||||
if (isAdd) clearSimRoomChapterList.Add(chapter.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Warn($"GetClearSimRoomChapterList error: {e.Message}");
|
||||
}
|
||||
return clearSimRoomChapterList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
||||
response.RepresentationTeam = NetUtils.GetDisplayedTeam(user);
|
||||
|
||||
response.LastClearedNormalMainStageId = user.LastNormalStageCleared;
|
||||
response.LastClearedStoryStageId = user.LastStoryStageCleared;
|
||||
response.LastClearedHardMainStageId = user.LastHardStageCleared;
|
||||
response.LastClearedMod = user.LastClearedDifficulty;
|
||||
|
||||
// Restore completed tutorials. GroupID is the first 4 digits of the Table ID.
|
||||
foreach (KeyValuePair<int, ClearedTutorialData> item in user.ClearedTutorialData)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.LobbyUser
|
||||
{
|
||||
[PacketPath("/user/scenario/exist")]
|
||||
@@ -13,21 +12,34 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
||||
|
||||
ResExistScenario response = new();
|
||||
|
||||
User user = GetUser();
|
||||
|
||||
foreach (string? item in req.ScenarioGroupIds)
|
||||
{
|
||||
foreach (string completed in user.CompletedScenarios)
|
||||
if (FindScenarioInMainStages(item) || FindScenarioInArchiveStages(item))
|
||||
{
|
||||
// story thingy was completed
|
||||
if (completed == item)
|
||||
{
|
||||
response.ExistGroupIds.Add(item);
|
||||
}
|
||||
response.ExistGroupIds.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private bool FindScenarioInMainStages(string scenarioGroupId)
|
||||
{
|
||||
User user = GetUser();
|
||||
return user.CompletedScenarios.Contains(scenarioGroupId);
|
||||
}
|
||||
|
||||
private bool FindScenarioInArchiveStages(string scenarioGroupId)
|
||||
{
|
||||
User user = GetUser();
|
||||
foreach (EventData evtData in user.EventInfo.Values)
|
||||
{
|
||||
if (evtData.CompletedScenarios.Contains(scenarioGroupId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,16 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetUserTitleCounterList req = await ReadData<ReqGetUserTitleCounterList>();
|
||||
await ReadData<ReqGetUserTitleCounterList>();
|
||||
|
||||
ResGetUserTitleCounterList r = new();
|
||||
ResGetUserTitleCounterList response = new();
|
||||
response.UserTitleCounterList.Add(new ResGetUserTitleCounterList.Types.NetUserTitleCounter { Condition = 23, SubCondition = 1, Count = 10});
|
||||
response.UserTitleCounterList.Add(new ResGetUserTitleCounterList.Types.NetUserTitleCounter { Condition = 23, SubCondition = 2, Count = 10});
|
||||
response.UserTitleCounterList.Add(new ResGetUserTitleCounterList.Types.NetUserTitleCounter { Condition = 23, SubCondition = 3, Count = 10});
|
||||
response.UserTitleCounterList.Add(new ResGetUserTitleCounterList.Types.NetUserTitleCounter { Condition = 23, SubCondition = 4, Count = 10});
|
||||
response.UserTitleCounterList.Add(new ResGetUserTitleCounterList.Types.NetUserTitleCounter { Condition = 23, SubCondition = 5, Count = 10});
|
||||
|
||||
await WriteDataAsync(r);
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace EpinelPS.LobbyServer.Misc
|
||||
if (user.GachaTutorialPlayCount > 0)
|
||||
response.Unavailables.Add(3);
|
||||
|
||||
// TODO: ValIdate response from real server and pull info from user info
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace EpinelPS.LobbyServer.Misc
|
||||
};
|
||||
|
||||
// Define maintenance window timestamps
|
||||
Google.Protobuf.WellKnownTypes.Timestamp maintenanceFrom = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow.AddHours(-2)); // Example: 2 hour ago
|
||||
/*Google.Protobuf.WellKnownTypes.Timestamp maintenanceFrom = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow.AddHours(-2)); // Example: 2 hour ago
|
||||
Google.Protobuf.WellKnownTypes.Timestamp maintenanceTo = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow.AddHours(-1)); // Example: 1 hour ago
|
||||
|
||||
// Add a new maintenance window
|
||||
@@ -25,7 +25,7 @@ namespace EpinelPS.LobbyServer.Misc
|
||||
{
|
||||
From = maintenanceFrom,
|
||||
To = maintenanceTo
|
||||
};
|
||||
};*/
|
||||
|
||||
await WriteDataAsync(r);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Outpost.Recycle
|
||||
{
|
||||
[PacketPath("/outpost/RecycleRoom/PersonalResearchLevelUp")]
|
||||
public class PersonalResearchLevelUp : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqPersonalResearchRecycleLevelUp req = await ReadData<ReqPersonalResearchRecycleLevelUp>();
|
||||
ResPersonalResearchRecycleLevelUp response = new();
|
||||
User user = GetUser();
|
||||
|
||||
const int personalResearchTid = 1001;
|
||||
RecycleRoomResearchProgress personalResearchProgress = user.ResearchProgress[personalResearchTid] ?? throw new Exception("PersonalRearch not found.");
|
||||
personalResearchProgress.Level += req.LevelUpCount;
|
||||
response.Recycle = new()
|
||||
{
|
||||
Tid = personalResearchTid,
|
||||
Lv = personalResearchProgress.Level,
|
||||
Exp = personalResearchProgress.Exp
|
||||
};
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
EpinelPS/LobbyServer/Pass/BuyEventPassRank.cs
Normal file
23
EpinelPS/LobbyServer/Pass/BuyEventPassRank.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
[PacketPath("/pass/event/buyrank")]
|
||||
public class BuyEventPassRank : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "passId": 1037, "targetPassRank": 2 }
|
||||
ReqBuyEventPassRank req = await ReadData<ReqBuyEventPassRank>(); //fields "PassId", "TargetPassRank"
|
||||
User user = GetUser();
|
||||
|
||||
ResBuyEventPassRank response = new(); // fields "PassRank", "PassPoint", "Currencies"
|
||||
PassHelper.BuyRank(user, req.PassId, req.TargetPassRank, out int PassPoint, out NetUserCurrencyData currencie);
|
||||
response.PassRank = req.TargetPassRank;
|
||||
response.PassPoint = PassPoint;
|
||||
response.Currencies.Add(currencie);
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,18 @@ namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "passId": 1037, "targetPassRank": 2 }
|
||||
ReqBuyPassRank req = await ReadData<ReqBuyPassRank>(); //fields "PassId", "TargetPassRank"
|
||||
User user = GetUser();
|
||||
|
||||
ResBuyPassRank response = new(); // fields "PassRank", "PassPoint", "Currencies"
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
PassHelper.BuyRank(user, req.PassId, req.TargetPassRank, out int PassPoint, out NetUserCurrencyData currencie);
|
||||
response.PassRank = req.TargetPassRank;
|
||||
response.PassPoint = PassPoint;
|
||||
response.Currencies.Add(currencie);
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
EpinelPS/LobbyServer/Pass/CompleteEventPassMission.cs
Normal file
28
EpinelPS/LobbyServer/Pass/CompleteEventPassMission.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
[PacketPath("/pass/event/completemission")]
|
||||
public class CompleteEventPassMission : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCompleteEventPassMission req = await ReadData<ReqCompleteEventPassMission>(); //fields "PassId", "PassMissionList"
|
||||
User user = GetUser();
|
||||
|
||||
ResCompleteEventPassMission response = new(); // field Reward
|
||||
|
||||
NetRewardData reward = new()
|
||||
{
|
||||
PassPoint = { }
|
||||
};
|
||||
|
||||
PassHelper.CompletePassMissions(user, ref reward, req.PassId, [.. req.PassMissionList]);
|
||||
response.Reward = reward;
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
EpinelPS/LobbyServer/Pass/CompletePassMission.cs
Normal file
28
EpinelPS/LobbyServer/Pass/CompletePassMission.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
[PacketPath("/pass/completemission")]
|
||||
public class CompletePassMission : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCompletePassMission req = await ReadData<ReqCompletePassMission>(); //fields "PassId", "PassMissionList"
|
||||
User user = GetUser();
|
||||
|
||||
ResCompletePassMission response = new(); // field Reward
|
||||
|
||||
NetRewardData reward = new()
|
||||
{
|
||||
PassPoint = { }
|
||||
};
|
||||
|
||||
PassHelper.CompletePassMissions(user, ref reward, req.PassId, [.. req.PassMissionList]);
|
||||
response.Reward = reward;
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,64 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.LobbyServer.Event;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
[PacketPath("/pass/event/getactive")]
|
||||
public class GetActiveEventPassData : LobbyMsgHandler
|
||||
{
|
||||
//broken game wont boot if not empty not sure how to implement this one
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(GetActiveEventPassData));
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetActiveEventPassData req = await ReadData<ReqGetActiveEventPassData>(); // no fields
|
||||
User user = GetUser();
|
||||
|
||||
ResGetActiveEventPassData response = new(); // fields PassList = NetPassInfo
|
||||
|
||||
List<LobbyPrivateBannerRecord> lobbyPrivateBanners = [];//[.. GameData.Instance.LobbyPrivateBannerTable.Values.Where(b => b.PrivateBannerShowDuration <= DateTime.UtcNow && b.EndDate >= DateTime.UtcNow)];
|
||||
lobbyPrivateBanners = EventHelper.GetLobbyPrivateBannerData(user);
|
||||
// TODO: PrivateBannerShowDuration
|
||||
log.Debug($"Active lobby private banners: {JsonConvert.SerializeObject(lobbyPrivateBanners)}");
|
||||
if (lobbyPrivateBanners.Count <= 0)
|
||||
{
|
||||
// No active lobby private banners
|
||||
Logging.WriteLine("No active lobby private banners found.", LogType.Warning);
|
||||
await WriteDataAsync(response);
|
||||
return;
|
||||
}
|
||||
|
||||
List<int> passIds = [];
|
||||
foreach (var banner in lobbyPrivateBanners)
|
||||
{
|
||||
passIds.AddRange(GameData.Instance.eventManagers.Values.Where(em => em.SetField == banner.EventId && em.EventSystemType == EventSystemType.EventPass).Select(em => em.Id));
|
||||
}
|
||||
log.Debug($"Active event pass IDs from banners: {JsonConvert.SerializeObject(passIds)}");
|
||||
if (passIds.Count == 0)
|
||||
{
|
||||
Logging.WriteLine("No active event pass IDs found from lobby private banners.", LogType.Warning);
|
||||
await WriteDataAsync(response);
|
||||
return;
|
||||
}
|
||||
|
||||
var passManager = GameData.Instance.EventPassManagerTable.Values.Where(p => passIds.Contains(p.EventId)).ToList();
|
||||
log.Debug($"Active event pass managers: {JsonConvert.SerializeObject(passManager)}");
|
||||
if (passManager.Count == 0)
|
||||
{
|
||||
Logging.WriteLine("No active event pass found.");
|
||||
await WriteDataAsync(response);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pm in passManager)
|
||||
{
|
||||
NetPassInfo passInfo = PassHelper.GetPassInfo(user, pm.Id, pm.PassPointId);
|
||||
if (passInfo.PassId != 0 && passInfo.PassRankList.Count > 0)
|
||||
{
|
||||
response.PassList.Add(passInfo);
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,42 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
[PacketPath("/pass/getactive")]
|
||||
public class GetActivePassData : LobbyMsgHandler
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(GetActivePassData));
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetActivePassData req = await ReadData<ReqGetActivePassData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetActivePassData response = new()
|
||||
{
|
||||
PassExist = true,
|
||||
Pass = new NetPassInfo { PassId = 1028, PassPoint = 490, PassSkipCount = 15, PremiumActive = true }
|
||||
PassExist = false,
|
||||
};
|
||||
|
||||
// Adding PassRankList using a loop
|
||||
for (int rank = 1; rank <= 15; rank++)
|
||||
var passManager = GameData.Instance.PassManagerTable.Values.FirstOrDefault(p => p.SeasonStartDate <= DateTime.UtcNow && p.SeasonEndDate >= DateTime.UtcNow);
|
||||
if (passManager != null)
|
||||
{
|
||||
response.Pass.PassRankList.Add(new NetPassRankData { PassRank = rank, IsNormalRewarded = true, IsPremiumRewarded = true });
|
||||
log.Debug($"Found active pass: {JsonConvert.SerializeObject(passManager)}");
|
||||
NetPassInfo passInfo = PassHelper.GetPassInfo(user, passManager.Id, passManager.PassPointId);
|
||||
// Simple validation to ensure we have a valid pass
|
||||
if (passInfo.PassId != 0 && passInfo.PassRankList.Count > 0)
|
||||
{
|
||||
response.PassExist = true;
|
||||
response.Pass = passInfo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.WriteLine("No active pass found.");
|
||||
}
|
||||
|
||||
int[] missionIds = new[] { 4001, 4002, 4003, 4004, 4005, 4006, 4007 };
|
||||
foreach (int missionId in missionIds) response.Pass.PassMissionList.Add(new NetPassMissionData { PassMissionId = missionId, IsComplete = true });
|
||||
|
||||
await WriteDataAsync(response);
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
EpinelPS/LobbyServer/Pass/ObtainEventPassReward.cs
Normal file
27
EpinelPS/LobbyServer/Pass/ObtainEventPassReward.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
[PacketPath("/pass/event/obtainreward")]
|
||||
public class ObtainEventPassReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "passId": 1037, "passRank": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ] }
|
||||
ReqObtainEventPassReward req = await ReadData<ReqObtainEventPassReward>(); //fields "PassId", "PassRank"
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainEventPassReward response = new(); // field Reward
|
||||
|
||||
NetRewardData reward = new()
|
||||
{
|
||||
PassPoint = { }
|
||||
};
|
||||
|
||||
PassHelper.ObtainPassRewards(user, ref reward, req.PassId, [.. req.PassRank]);
|
||||
response.Reward = reward;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
EpinelPS/LobbyServer/Pass/ObtainOneEventPassReward.cs
Normal file
27
EpinelPS/LobbyServer/Pass/ObtainOneEventPassReward.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
[PacketPath("/pass/event/obtainonereward")]
|
||||
public class ObtainOneEventPassReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "passId": 1037, "passRank": 1, "premiumReward": true }
|
||||
ReqObtainOneEventPassReward req = await ReadData<ReqObtainOneEventPassReward>(); //fields "PassId", "PassRank"
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainOneEventPassReward response = new(); // field Reward
|
||||
|
||||
NetRewardData reward = new()
|
||||
{
|
||||
PassPoint = { }
|
||||
};
|
||||
|
||||
PassHelper.ObtainOnePassRewards(user, ref reward, req.PassId, req.PassRank, req.PremiumReward);
|
||||
response.Reward = reward;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
EpinelPS/LobbyServer/Pass/ObtainOnePassReward.cs
Normal file
27
EpinelPS/LobbyServer/Pass/ObtainOnePassReward.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
[PacketPath("/pass/obtainonereward")]
|
||||
public class ObtainOnePassReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "passId": 1037, "passRank": 1, "premiumReward": true }
|
||||
ReqObtainOnePassReward req = await ReadData<ReqObtainOnePassReward>(); //fields "PassId", "PassRank"
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainOnePassReward response = new(); // field Reward
|
||||
|
||||
NetRewardData reward = new()
|
||||
{
|
||||
PassPoint = { }
|
||||
};
|
||||
|
||||
PassHelper.ObtainOnePassRewards(user, ref reward, req.PassId, req.PassRank, req.PremiumReward);
|
||||
response.Reward = reward;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,21 @@ namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "passId": 1037, "passRank": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ] }
|
||||
ReqObtainPassReward req = await ReadData<ReqObtainPassReward>(); //fields "PassId", "PassRank"
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainPassReward response = new(); // field Reward
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
NetRewardData reward = new()
|
||||
{
|
||||
PassPoint = { }
|
||||
};
|
||||
|
||||
PassHelper.ObtainPassRewards(user, ref reward, req.PassId, [.. req.PassRank]);
|
||||
response.Reward = reward;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
381
EpinelPS/LobbyServer/Pass/PassHelper.cs
Normal file
381
EpinelPS/LobbyServer/Pass/PassHelper.cs
Normal file
@@ -0,0 +1,381 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Pass
|
||||
{
|
||||
public static class PassHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(PassHelper));
|
||||
|
||||
public static NetPassInfo GetPassInfo(User user, int passId, int passPointId)
|
||||
{
|
||||
NetPassInfo passInfo = new();
|
||||
try
|
||||
{
|
||||
var userPass = user.UserPassInfo.GetValueOrDefault(passId);
|
||||
// If user does not have this pass info, create a new one
|
||||
if (userPass == null || userPass.PassId == 0)
|
||||
{
|
||||
userPass = new PassData
|
||||
{
|
||||
PassId = passId,
|
||||
PremiumActive = true,
|
||||
PassPoint = 0,
|
||||
PassRankList = [],
|
||||
PassMissionList = []
|
||||
};
|
||||
}
|
||||
log.Debug($"UserPassInfo before completing missions: {JsonConvert.SerializeObject(userPass)}");
|
||||
passInfo.PassId = userPass.PassId;
|
||||
passInfo.PremiumActive = userPass.PremiumActive;
|
||||
|
||||
// Populate PassRankList based on SeasonPassTable
|
||||
var seasonPass = GameData.Instance.SeasonPassTable.Values.Where(sp => sp.PassId == passInfo.PassId).ToList();
|
||||
if (seasonPass != null && seasonPass.Count > 0)
|
||||
{
|
||||
passInfo.PassPoint = userPass.PassPoint;
|
||||
foreach (var sp in seasonPass)
|
||||
{
|
||||
if (userPass.PassRankList.Any(pr => pr.PassRank == sp.PassRank))
|
||||
{
|
||||
// Use existing rank data
|
||||
var existingRank = userPass.PassRankList.First(pr => pr.PassRank == sp.PassRank);
|
||||
passInfo.PassRankList.Add(new NetPassRankData
|
||||
{
|
||||
PassRank = existingRank.PassRank,
|
||||
IsNormalRewarded = existingRank.IsNormalRewarded,
|
||||
IsPremiumRewarded = existingRank.IsPremiumRewarded
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the user does not have this rank yet, add it as not rewarded
|
||||
userPass.PassRankList.Add(new PassRankData { PassRank = sp.PassRank, IsNormalRewarded = false, IsPremiumRewarded = false });
|
||||
passInfo.PassRankList.Add(new NetPassRankData { PassRank = sp.PassRank, IsNormalRewarded = false, IsPremiumRewarded = false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate PassMissionList based on PassMissionTable
|
||||
var passMissions = GameData.Instance.PassMissionTable.Values.Where(pm => pm.PassPointId == passPointId).ToList();
|
||||
if (passMissions != null && passMissions.Count > 0)
|
||||
{
|
||||
foreach (var pm in passMissions)
|
||||
{
|
||||
if (userPass.PassMissionList.Any(m => m.PassMissionId == pm.Id))
|
||||
{
|
||||
// Use existing mission data
|
||||
var existingMission = userPass.PassMissionList.First(m => m.PassMissionId == pm.Id);
|
||||
existingMission.IsComplete = userPass.LastCompleteAt == DateTime.Now.ToString("yyyyMMdd") && existingMission.IsComplete;
|
||||
passInfo.PassMissionList.Add(new NetPassMissionData
|
||||
{
|
||||
PassMissionId = existingMission.PassMissionId,
|
||||
IsComplete = existingMission.IsComplete
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the user does not have this mission yet, add it as not complete
|
||||
userPass.PassMissionList.Add(new PassMissionData { PassMissionId = pm.Id, IsComplete = false });
|
||||
passInfo.PassMissionList.Add(new NetPassMissionData { PassMissionId = pm.Id, IsComplete = false });
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update user's pass info in database
|
||||
if (userPass != null && userPass.PassId != 0)
|
||||
{
|
||||
if (!user.UserPassInfo.TryAdd(userPass.PassId, userPass))
|
||||
user.UserPassInfo[userPass.PassId] = userPass;
|
||||
}
|
||||
log.Debug($"UserPassInfo after completing missions: {JsonConvert.SerializeObject(userPass)}");
|
||||
JsonDb.Save();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"Error getting pass info for user {user.ID}, PassId: {passId}, PassPointId: {passPointId}, error: {ex.Message}");
|
||||
}
|
||||
return passInfo;
|
||||
}
|
||||
|
||||
public static void RewardsForUser(User user, ref NetRewardData reward, int rewardId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rewardData = GameData.Instance.RewardDataRecords.GetValueOrDefault(rewardId);
|
||||
if (rewardData == null)
|
||||
{
|
||||
log.Warn($"No reward data found for RewardId: {rewardId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewardData.UserExp != 0)
|
||||
{
|
||||
int newXp = rewardData.UserExp + user.userPointData.ExperiencePoint;
|
||||
|
||||
int newLevelExp = GameData.Instance.GetUserMinXpForLevel(user.userPointData.UserLevel);
|
||||
int newLevel = user.userPointData.UserLevel;
|
||||
|
||||
if (newLevelExp == -1)
|
||||
{
|
||||
log.Warn("Unknown user level value for xp " + newXp);
|
||||
}
|
||||
|
||||
int newGems = 0;
|
||||
|
||||
while (newXp >= newLevelExp)
|
||||
{
|
||||
newLevel++;
|
||||
newGems += 30;
|
||||
newXp -= newLevelExp;
|
||||
if (user.Currency.ContainsKey(CurrencyType.FreeCash))
|
||||
user.Currency[CurrencyType.FreeCash] += 30;
|
||||
else
|
||||
user.Currency.Add(CurrencyType.FreeCash, 30);
|
||||
|
||||
newLevelExp = GameData.Instance.GetUserMinXpForLevel(newLevel);
|
||||
}
|
||||
|
||||
// TODO: what is the difference between IncreaseExp and GainExp
|
||||
// NOTE: Current Exp/Lv refers to after XP was added.
|
||||
|
||||
reward.UserExp = new NetIncreaseExpData()
|
||||
{
|
||||
BeforeExp = user.userPointData.ExperiencePoint,
|
||||
BeforeLv = user.userPointData.UserLevel,
|
||||
|
||||
// IncreaseExp = rewardData.UserExp,
|
||||
CurrentExp = newXp,
|
||||
CurrentLv = newLevel,
|
||||
GainExp = rewardData.UserExp,
|
||||
};
|
||||
user.userPointData.ExperiencePoint = newXp;
|
||||
user.userPointData.UserLevel = newLevel;
|
||||
}
|
||||
|
||||
foreach (var item in rewardData.Rewards)
|
||||
{
|
||||
if (item.RewardType != RewardType.None)
|
||||
{
|
||||
RewardUtils.AddSingleObject(user, ref reward, item.RewardId, item.RewardType, item.RewardValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"Error processing rewards for user {user.ID} with RewardId {rewardId}, error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateUserPassInfoRank(User user, int passId, List<int> rankList, bool IsNormalRewarded, bool IsPremiumRewarded, int key = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (user.UserPassInfo.TryGetValue(passId, out PassData? passData))
|
||||
{
|
||||
foreach (var rank in rankList)
|
||||
{
|
||||
if (passData.PassRankList.Any(pr => pr.PassRank == rank))
|
||||
{
|
||||
var existingRank = passData.PassRankList.First(pr => pr.PassRank == rank);
|
||||
if (key == 0 || key == 1) existingRank.IsNormalRewarded = IsNormalRewarded;
|
||||
if (key == 0 || key == 2) existingRank.IsPremiumRewarded = IsPremiumRewarded;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the user does not have this rank yet, add it
|
||||
passData.PassRankList.Add(new PassRankData
|
||||
{
|
||||
PassRank = rank,
|
||||
IsNormalRewarded = IsNormalRewarded,
|
||||
IsPremiumRewarded = IsPremiumRewarded
|
||||
});
|
||||
}
|
||||
log.Debug($"Updated pass rank info for user {user.ID} with PassId: {passId}, Ranks: {string.Join(", ", rankList)} userPassInfo: {JsonConvert.SerializeObject(passData)}");
|
||||
}
|
||||
JsonDb.Save();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.WriteLine($"No pass data found for user {user.ID} with PassId: {passId}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Error updating pass rank info for user {user.ID}, PassId: {passId}, error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void ObtainPassRewards(User user, ref NetRewardData reward, int passId, List<int> rankList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var seasons = GameData.Instance.SeasonPassTable.Values.Where(sp => sp.PassId == passId && rankList.Contains(sp.PassRank)).ToList();
|
||||
if (seasons.Count == 0)
|
||||
{
|
||||
Logging.WriteLine($"No such pass id: {passId} or ranks: {string.Join(", ", rankList)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var season in seasons)
|
||||
{
|
||||
bool isFreeRewarded = false;
|
||||
bool isPremiumRewarded = false;
|
||||
// check if the user has already claimed these rewards
|
||||
if (user.UserPassInfo.TryGetValue(passId, out PassData? passData))
|
||||
{
|
||||
var existingRank = passData.PassRankList.FirstOrDefault(pr => pr.PassRank == season.PassRank);
|
||||
if (existingRank != null)
|
||||
{
|
||||
isFreeRewarded = existingRank.IsNormalRewarded;
|
||||
isPremiumRewarded = existingRank.IsPremiumRewarded;
|
||||
}
|
||||
}
|
||||
// give rewards if not already claimed
|
||||
if (season.FreeReward != 0 && !isFreeRewarded)
|
||||
RewardsForUser(user, ref reward, season.FreeReward);
|
||||
if (season.PremiumReward1 != 0 && !isPremiumRewarded)
|
||||
RewardsForUser(user, ref reward, season.PremiumReward1);
|
||||
if (season.PremiumReward2 != 0 && !isPremiumRewarded)
|
||||
RewardsForUser(user, ref reward, season.PremiumReward2);
|
||||
}
|
||||
// update user pass info to mark these ranks as rewarded
|
||||
// No abnormal judgment was made here,
|
||||
UpdateUserPassInfoRank(user, passId, rankList, true, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Error obtaining pass rewards for user {user.ID}, PassId: {passId}, Ranks: {string.Join(", ", rankList)}, error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void ObtainOnePassRewards(User user, ref NetRewardData reward, int passId, int rank, bool premiumReward)
|
||||
{
|
||||
try
|
||||
{
|
||||
SeasonPassRecord? season = GameData.Instance.SeasonPassTable.Values.Where(sp => sp.PassId == passId && sp.PassRank == rank).FirstOrDefault();
|
||||
if (season == null || season.PassId == 0)
|
||||
{
|
||||
Logging.WriteLine($"No such pass id: {passId} or rank: {rank}");
|
||||
}
|
||||
else
|
||||
{
|
||||
int key = premiumReward ? 2 : 1;
|
||||
if (!premiumReward && season.FreeReward != 0)
|
||||
RewardsForUser(user, ref reward, season.FreeReward);
|
||||
if (premiumReward && season.PremiumReward1 != 0)
|
||||
RewardsForUser(user, ref reward, season.PremiumReward1);
|
||||
if (premiumReward && season.PremiumReward2 != 0)
|
||||
RewardsForUser(user, ref reward, season.PremiumReward2);
|
||||
// update user pass info to mark these ranks as rewarded
|
||||
// No abnormal judgment was made here,
|
||||
UpdateUserPassInfoRank(user, passId, [season.PassRank], !premiumReward, premiumReward, key);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Error obtaining pass rewards for user {user.ID}, PassId: {passId}, Rank: {rank}, error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void BuyRank(User user, int passId, int targetPassRank, out int passPoint, out NetUserCurrencyData currencie)
|
||||
{
|
||||
int rankPrice = 200; // each rank costs 200 currency units
|
||||
passPoint = 0;
|
||||
currencie = new();
|
||||
|
||||
if (user.Currency.TryGetValue(CurrencyType.ChargeCash, out long value) && value < rankPrice)
|
||||
{
|
||||
Logging.WriteLine($"User {user.ID} does not have enough ChargeCash to buy rank. Required: {rankPrice}, Available: {value}");
|
||||
currencie = new() { Type = (int)CurrencyType.ChargeCash, Value = value };
|
||||
return;
|
||||
}
|
||||
|
||||
SeasonPassRecord? season = GameData.Instance.SeasonPassTable.Values.Where(sp => sp.PassId == passId && sp.PassRank == targetPassRank).FirstOrDefault();
|
||||
if (season == null)
|
||||
{
|
||||
Logging.WriteLine($"No such pass id: {passId} or rank: {targetPassRank}");
|
||||
return;
|
||||
}
|
||||
|
||||
user.Currency[CurrencyType.ChargeCash] -= rankPrice;
|
||||
passPoint = season.ConditionValue;
|
||||
currencie = new() { Type = (int)CurrencyType.ChargeCash, Value = user.Currency[CurrencyType.ChargeCash] };
|
||||
if (user.UserPassInfo.TryGetValue(passId, out PassData? passData))
|
||||
{
|
||||
passData.PassPoint = passPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
passData = new PassData
|
||||
{
|
||||
PassId = passId,
|
||||
PremiumActive = true,
|
||||
PassPoint = season.ConditionValue,
|
||||
PassRankList = [],
|
||||
PassMissionList = []
|
||||
};
|
||||
user.UserPassInfo.Add(passId, passData);
|
||||
}
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
public static void CompletePassMissions(User user, ref NetRewardData reward, int passId, List<int> missionIds)
|
||||
{
|
||||
if (!user.UserPassInfo.TryGetValue(passId, out var passData))
|
||||
{
|
||||
passData = new();
|
||||
user.UserPassInfo.Add(passId, passData);
|
||||
}
|
||||
log.Debug($"UserPassInfo before completing missions: {JsonConvert.SerializeObject(passData)}");
|
||||
int completedPoints = 0;
|
||||
var completedMissions = GameData.Instance.PassMissionTable.Values
|
||||
.Where(pm => missionIds.Contains(pm.Id)).ToList();
|
||||
if (completedMissions.Count == 0)
|
||||
{
|
||||
log.Warn($"User {user.ID} has no valid pass missions to complete for pass {passId}");
|
||||
return;
|
||||
}
|
||||
foreach (var mission in completedMissions)
|
||||
{
|
||||
var existingMission = passData.PassMissionList.FirstOrDefault(m => m.PassMissionId == mission.Id);
|
||||
if (existingMission != null && existingMission.IsComplete)
|
||||
{
|
||||
log.Warn($"User {user.ID} has already completed pass mission {mission.Id} for pass {passId}");
|
||||
continue;
|
||||
}
|
||||
var rewardEntry = GameData.Instance.GetRewardTableEntry(mission.RewardId);
|
||||
if (rewardEntry == null || rewardEntry.Rewards.Count == 0)
|
||||
{
|
||||
log.Warn($"Unable to find reward entry {mission.RewardId} for pass mission {mission.Id}");
|
||||
continue;
|
||||
}
|
||||
foreach (var rewardItem in rewardEntry.Rewards)
|
||||
{
|
||||
if (rewardItem.RewardType == RewardType.PassPoint)
|
||||
{
|
||||
completedPoints += rewardItem.RewardValue;
|
||||
existingMission.IsComplete = true;
|
||||
passData.LastCompleteAt = DateTime.Now.ToString("yyyyMMdd");
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warn($"Unsupported reward type {rewardItem.RewardType} in pass mission {mission.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
user.AddTrigger(Trigger.PointRewardEvent, completedPoints, passId);
|
||||
passData.PassPoint += completedPoints;
|
||||
log.Debug($"UserPassInfo after completing missions: {JsonConvert.SerializeObject(passData)}");
|
||||
reward.PassPoint = new()
|
||||
{
|
||||
Value = completedPoints,
|
||||
FinalValue = passData.PassPoint
|
||||
};
|
||||
JsonDb.Save(); // Save user data after updating pass info
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace EpinelPS.LobbyServer.Shop.InApp
|
||||
|
||||
ResGetCustomPackageSetupData response = new();
|
||||
|
||||
// TODO: ValIdate response from real server and pull info from user info
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace EpinelPS.LobbyServer.Shop.PackageShop
|
||||
|
||||
ResGetCampaignPackage response = new();
|
||||
|
||||
// TODO: ValIdate response from real server and pull info from user info
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace EpinelPS.LobbyServer.Sidestory
|
||||
response.SideStoryStageDataList.Add(new NetSideStoryStageData() { SideStoryStageId = item, ClearedAt = Timestamp.FromDateTime(DateTime.UtcNow) });
|
||||
}
|
||||
|
||||
response.ViewedSideStoryIds.AddRange(user.ViewedSideStoryStages);
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
28
EpinelPS/LobbyServer/Sidestory/SetViewed.cs
Normal file
28
EpinelPS/LobbyServer/Sidestory/SetViewed.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Sidestory
|
||||
{
|
||||
[PacketPath("/sidestory/view/set")]
|
||||
public class SetViewed : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetViewSideStory req = await ReadData<ReqSetViewSideStory>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetViewSideStory response = new();
|
||||
|
||||
foreach (var id in req.ViewedSideStoryIds)
|
||||
{
|
||||
if (!user.ViewedSideStoryStages.Contains(id))
|
||||
{
|
||||
user.ViewedSideStoryStages.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
EpinelPS/LobbyServer/Simroom/ClearBattle.cs
Normal file
61
EpinelPS/LobbyServer/Simroom/ClearBattle.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/clearbattle")]
|
||||
public class ClearBattle : LobbyMsgHandler
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(ClearBattle));
|
||||
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// {"location":{"chapter":3,"stage":3,"order":2},"event":111011143,"teamNumber":1,"antiCheatAdditionalInfo":{"clientLocalTime":"638993283799771900"}}
|
||||
ReqClearSimRoomBattle req = await ReadData<ReqClearSimRoomBattle>();
|
||||
User user = GetUser();
|
||||
|
||||
ResClearSimRoomBattle response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success
|
||||
};
|
||||
|
||||
// OverclockOptionChangedHps
|
||||
|
||||
// Teams
|
||||
try
|
||||
{
|
||||
var team = SimRoomHelper.GetTeamData(user, req.TeamNumber, [.. req.RemainingHps]);
|
||||
if (team is not null) response.Teams.Add(team);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error($"ClearBattle Response Team Exception :{e.Message}");
|
||||
}
|
||||
|
||||
SimRoomHelper.UpdateUserRemainingHps(user, [.. req.RemainingHps], req.TeamNumber);
|
||||
|
||||
if (req.BattleResult == 1)
|
||||
{
|
||||
// BuffOptions
|
||||
try
|
||||
{
|
||||
var buffOptions = SimRoomHelper.GetBuffOptions(user, req.Location);
|
||||
if (buffOptions is not null && buffOptions.Count > 0)
|
||||
{
|
||||
response.BuffOptions.AddRange(buffOptions);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error($"ClearBattle Response BuffOptions Exception :{e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user