mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-12 15:04:36 +01:00
新增功能:实现包含简易模式特性的模拟房间功能 (#66)
feat: Implement SimRoom functionality with Simple Mode features - Added SimRoomHelper class to manage SimRoom events and logic. - Implemented SimpleModeSelectBuff handler for buff selection in Simple Mode. - Implemented SimpleModeSetSkipOption to enable/disable skip options. - Implemented SimpleModeSkipAll to handle skipping all Simple Mode stages and reward retrieval. - Implemented SimpleModeSkipBuffSelection for skipping buff selection. - Implemented SimpleModeStart to initiate Simple Mode with event handling. - Updated SimRoom data models to include buffs, legacy buffs, and event tracking. - Updated GameData add SimRoom data tables -Added JsonStaticDataReplenish add SimRoom data models - Enhanced User model to manage weekly reset logic and retain legacy buffs. - Added DateTimeHelper utility for managing time zone specific date calculations. - Updated game configuration for static data and resource URLs.
This commit is contained in:
@@ -291,6 +291,24 @@ namespace EpinelPS.Data
|
||||
[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 = [];
|
||||
|
||||
static async Task<GameData> BuildAsync()
|
||||
{
|
||||
await Load();
|
||||
|
||||
155
EpinelPS/Data/JsonStaticDataReplenish.cs
Normal file
155
EpinelPS/Data/JsonStaticDataReplenish.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using MemoryPack;
|
||||
|
||||
namespace EpinelPS.Data;
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class SimulationRoomStageLocationRecord
|
||||
{
|
||||
public int Id;
|
||||
public int ScheduleGroupId;
|
||||
public int ChapterId;
|
||||
public int StageGroupId;
|
||||
public int StageEssentialValue;
|
||||
public int EventSelectionValue;
|
||||
public SimulationRoomLocation Location;
|
||||
public int Weight;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class SimulationRoomBattleEventRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public SimulationRoomEvent EventType { get; set; }
|
||||
public SimulationRoomScalingType ScalingType { get; set; }
|
||||
public SimulationRoomConditionType DifficultyConditionType { get; set; }
|
||||
public int DifficultyConditionValue { get; set; }
|
||||
public SimulationRoomConditionType ChapterConditionType { get; set; }
|
||||
public int ChapterConditionValue { get; set; }
|
||||
public int Weight { get; set; }
|
||||
public bool SpotAutocontrol { get; set; }
|
||||
public int MonsterStageLv { get; set; }
|
||||
public int DynamicObjectStageLv { get; set; }
|
||||
public int StandardBattlePower { get; set; }
|
||||
public int StageStatIncreaseGroupId { get; set; }
|
||||
public bool IsUseQuickBattle { get; set; }
|
||||
public int SpotId { get; set; }
|
||||
public bool UseOcMode { get; set; }
|
||||
public int UseSeasonId { get; set; }
|
||||
public int BattleEventGroup { get; set; }
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class SimulationRoomBuffPreviewRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public SimulationRoomEvent EventType { get; set; }
|
||||
public PreviewType PreviewType { get; set; }
|
||||
public string? PreviewTarget { get; set; }
|
||||
public int Weight { get; set; }
|
||||
public string? DescriptionLocalkey { get; set; }
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class SimulationRoomBuffRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int GroupId { get; set; }
|
||||
public SimulationRoomBuffMainTarget MainTarget { get; set; }
|
||||
public List<SimulationRoomBuffSubTarget>? SubTarget { get; set; }
|
||||
public SimulationRoomBuffGrade Grade { get; set; }
|
||||
public int Weight { get; set; }
|
||||
public SimulationRoomBubbleType BubbleType { get; set; }
|
||||
public string? NameLocalkey { get; set; }
|
||||
public string? DescriptionLocalkey { get; set; }
|
||||
public List<string>? ParameterLocalkey { get; set; }
|
||||
public string? ResourceId { get; set; }
|
||||
public SimulationRoomBuffFunctionType FunctionType { get; set; }
|
||||
public List<SimulationRoomBuffValueData>? BuffValue { get; set; }
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class SimulationRoomSelectionEventRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public SimulationRoomEvent EventType { get; set; }
|
||||
public SimulationRoomConditionType ChapterConditionType { get; set; }
|
||||
public int ChapterConditionValue { get; set; }
|
||||
public int SelectionGroupId { get; set; }
|
||||
public int SelectionValue { get; set; }
|
||||
public string? NameLocalkey { get; set; }
|
||||
public string? DescriptionLocalkey { get; set; }
|
||||
public int Weight { get; set; }
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class SimulationRoomBuffValueData
|
||||
{
|
||||
public int FunctionValueLevel { get; set; }
|
||||
public int BattlePowerLevel { get; set; }
|
||||
}
|
||||
|
||||
public enum SimulationRoomScalingType
|
||||
{
|
||||
DataRef = 0,
|
||||
None = 1,
|
||||
}
|
||||
|
||||
public enum SimulationRoomConditionType
|
||||
{
|
||||
Range = 0, // 0x0
|
||||
Select = 1, // 0x0
|
||||
}
|
||||
|
||||
public enum PreviewType
|
||||
{
|
||||
MainTarget = 0, // 0x0
|
||||
Bubble = 1, // 0x0
|
||||
Grade = 2
|
||||
}
|
||||
|
||||
public enum SimulationRoomBuffMainTarget
|
||||
{
|
||||
Shoot = 0, // 0x0
|
||||
Attack = 1, // 0x0
|
||||
Survive = 2
|
||||
}
|
||||
|
||||
public enum SimulationRoomBuffSubTarget
|
||||
{
|
||||
AR = 0,
|
||||
RL = 1,
|
||||
SR = 2,
|
||||
MG = 3,
|
||||
SG = 4,
|
||||
SMG = 5,
|
||||
ELYSION = 6,
|
||||
MISSILIS = 7,
|
||||
TETRA = 8,
|
||||
PILGRIM = 9,
|
||||
Attacker = 10,
|
||||
Defender = 11,
|
||||
Supporter = 12,
|
||||
ALL = 13
|
||||
}
|
||||
|
||||
public enum SimulationRoomBuffGrade
|
||||
{
|
||||
R = 0,
|
||||
SR = 1,
|
||||
SSR = 2,
|
||||
EPIC = 3
|
||||
}
|
||||
|
||||
public enum SimulationRoomBubbleType
|
||||
{
|
||||
TypeA = 0,
|
||||
TypeB = 1,
|
||||
TypeC = 2,
|
||||
TypeD = 3
|
||||
}
|
||||
|
||||
public enum SimulationRoomBuffFunctionType
|
||||
{
|
||||
Function = 0,
|
||||
HealAfterBattle = 1
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
56
EpinelPS/LobbyServer/Simroom/ClearBattleFast.cs
Normal file
56
EpinelPS/LobbyServer/Simroom/ClearBattleFast.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/fastclearbattle")]
|
||||
public class ClearBattleFast : 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"}}
|
||||
ReqFastClearSimRoomBattle req = await ReadData<ReqFastClearSimRoomBattle>();
|
||||
User user = GetUser();
|
||||
|
||||
ResFastClearSimRoomBattle response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success
|
||||
};
|
||||
|
||||
// OverclockOptionChangedHps
|
||||
|
||||
// Teams
|
||||
try
|
||||
{
|
||||
var team = SimRoomHelper.GetTeamData(user, req.TeamNumber, null);
|
||||
if (team is not null) response.Teams.Add(team);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error($"ClearBattleFast Response Team Exception :{e.Message}");
|
||||
}
|
||||
|
||||
SimRoomHelper.UpdateUserRemainingHps(user, teamNumber: req.TeamNumber);
|
||||
|
||||
// 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($"ClearBattleFast Response BuffOptions Exception: {e.Message}");
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
EpinelPS/LobbyServer/Simroom/EnterBattle.cs
Normal file
20
EpinelPS/LobbyServer/Simroom/EnterBattle.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/enterbattle")]
|
||||
public class EnterBattle : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
await ReadData<ReqEnterSimRoomBattle>();
|
||||
|
||||
ResEnterSimRoomBattle response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
@@ -8,63 +9,192 @@ namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetSimRoom req = await ReadData<ReqGetSimRoom>();
|
||||
await ReadData<ReqGetSimRoom>();
|
||||
User user = GetUser();
|
||||
|
||||
// ResGetSimRoom Fields
|
||||
// SimRoomStatus Status
|
||||
// int CurrentDifficulty
|
||||
// long NextRenewAt
|
||||
// RepeatedField<NetSimRoomChapterInfo> ClearInfos
|
||||
// RepeatedField<NetSimRoomEvent> Events
|
||||
// RepeatedField<NetSimRoomCharacterHp> RemainingHps
|
||||
// RepeatedField<int> Buffs
|
||||
// RepeatedField<int> LegacyBuffs
|
||||
// RepeatedField<int> OverclockOptionList
|
||||
// NetSimRoomOverclockData OverclockData
|
||||
// Timestamp NextLegacyBuffResetDate
|
||||
// NetSimRoomSimpleModeBuffSelectionInfo NextSimpleModeBuffSelectionInfo
|
||||
// NetSimRoomChapterInfo LastPlayedChapter
|
||||
// bool IsSimpleModeSkipEnabled
|
||||
|
||||
|
||||
var CurrentDifficulty = user.ResetableData.SimRoomData.CurrentDifficulty;
|
||||
var currentChapter = user.ResetableData.SimRoomData.CurrentChapter;
|
||||
|
||||
ResGetSimRoom response = new()
|
||||
{
|
||||
OverclockData = new NetSimRoomOverclockData
|
||||
{
|
||||
CurrentSeasonData = new NetSimRoomOverclockSeasonData
|
||||
{
|
||||
SeasonStartDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow),
|
||||
SeasonEndDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow.AddDays(7)),
|
||||
IsSeasonOpen = true,
|
||||
Season = 1,
|
||||
SubSeason = 1,
|
||||
SeasonWeekCount = 1
|
||||
},
|
||||
|
||||
CurrentSeasonHighScore = new NetSimRoomOverclockHighScoreData
|
||||
{
|
||||
CreatedAt = Timestamp.FromDateTimeOffset(DateTime.UtcNow),
|
||||
OptionLevel = 1,
|
||||
Season = 1,
|
||||
SubSeason = 1,
|
||||
OptionList = {}
|
||||
},
|
||||
|
||||
CurrentSubSeasonHighScore = new NetSimRoomOverclockHighScoreData
|
||||
{
|
||||
CreatedAt = Timestamp.FromDateTimeOffset(DateTime.UtcNow),
|
||||
OptionLevel = 1,
|
||||
Season = 1,
|
||||
SubSeason = 1,
|
||||
OptionList = {}
|
||||
},
|
||||
|
||||
LatestOption = new NetSimRoomOverclockOptionSettingData
|
||||
{
|
||||
Season = 1,
|
||||
OptionList = {}
|
||||
}
|
||||
},
|
||||
|
||||
Status = SimRoomStatus.Ready,
|
||||
CurrentDifficulty = 1,
|
||||
NextRenewAt = DateTime.UtcNow.AddDays(7).Ticks,
|
||||
NextLegacyBuffResetDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow.AddDays(7))
|
||||
CurrentDifficulty = CurrentDifficulty,
|
||||
// NextRenewAt: Resets at 2 AM daily
|
||||
NextRenewAt = DateTimeHelper.GetNextDayAtTime("China Standard Time", 2).Ticks,
|
||||
// NextLegacyBuffResetDate: Resets at 2 AM every Tuesday
|
||||
NextLegacyBuffResetDate = DateTimeHelper.GetNextWeekdayAtTime("China Standard Time", DayOfWeek.Tuesday, 2).ToTimestamp(),
|
||||
IsSimpleModeSkipEnabled = user.ResetableData.SimRoomData.IsSimpleModeSkipEnabled,
|
||||
|
||||
};
|
||||
|
||||
// LegacyBuffs
|
||||
response.LegacyBuffs.AddRange(user.ResetableData.SimRoomData.LegacyBuffs);
|
||||
|
||||
// OverclockData
|
||||
response.OverclockData = GetOverclockData(user: user);
|
||||
|
||||
// ClearInfos
|
||||
response.ClearInfos.AddRange(GetClearInfos(user));
|
||||
|
||||
// OverclockOptionList
|
||||
// response.OverclockOptionList.Add([]);
|
||||
|
||||
// check if user is in sim room
|
||||
if (user.ResetableData.SimRoomData.Entered)
|
||||
{
|
||||
response.Status = SimRoomStatus.Progress;
|
||||
response.Events.AddRange(SimRoomHelper.GetSimRoomEvents(user));
|
||||
|
||||
// TODO: Get RemainingHps
|
||||
response.RemainingHps.AddRange(GetCharacterHp(user));
|
||||
|
||||
response.LastPlayedChapter = new NetSimRoomChapterInfo()
|
||||
{
|
||||
Chapter = currentChapter,
|
||||
Difficulty = CurrentDifficulty,
|
||||
};
|
||||
|
||||
// Buffs = Buffs + LegacyBuffs
|
||||
response.Buffs.AddRange(user.ResetableData.SimRoomData.Buffs);
|
||||
response.Buffs.AddRange(user.ResetableData.SimRoomData.LegacyBuffs);
|
||||
|
||||
// response.NextSimpleModeBuffSelectionInfo = new()
|
||||
// {
|
||||
// RemainingBuffSelectCount = 8 - response.Buffs.Count,
|
||||
// BuffOptions = { user.ResetableData.SimRoomData.Buffs }
|
||||
// };
|
||||
|
||||
response.CurrentDifficulty = user.ResetableData.SimRoomData.CurrentDifficulty;
|
||||
}
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get clear infos
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns>List of cleared chapters</returns>
|
||||
private static List<NetSimRoomChapterInfo> GetClearInfos(User user)
|
||||
{
|
||||
List<NetSimRoomChapterInfo> clearInfos = [];
|
||||
try
|
||||
{
|
||||
var receivedRewards = user.ResetableData.SimRoomData.ReceivedRewardChapters;
|
||||
if (receivedRewards.Count > 0)
|
||||
{
|
||||
// Get the last received reward chapter
|
||||
var lastReceivedReward = receivedRewards.OrderBy(x => x.Difficulty).ThenBy(x => x.Chapter).LastOrDefault();
|
||||
if (lastReceivedReward is not null)
|
||||
{
|
||||
var CurrentDifficulty = lastReceivedReward.Difficulty;
|
||||
var CurrentChapter = lastReceivedReward.Chapter;
|
||||
|
||||
// Get all chapters where difficulty is less than or equal to current difficulty
|
||||
var ChapterRecords = GameData.Instance.SimulationRoomChapterTable.Values.Where(x => x.DifficultyId <= CurrentDifficulty).ToList();
|
||||
|
||||
foreach (var chapterRecord in ChapterRecords)
|
||||
{
|
||||
// check if chapter is less than or equal to current chapter
|
||||
if (chapterRecord.DifficultyId == CurrentDifficulty && chapterRecord.Chapter <= CurrentChapter)
|
||||
{
|
||||
clearInfos.Add(new NetSimRoomChapterInfo()
|
||||
{
|
||||
Chapter = chapterRecord.Chapter,
|
||||
Difficulty = chapterRecord.DifficultyId,
|
||||
});
|
||||
}
|
||||
// check if difficulty is less than current difficulty
|
||||
else if (chapterRecord.DifficultyId < CurrentDifficulty)
|
||||
{
|
||||
clearInfos.Add(new NetSimRoomChapterInfo()
|
||||
{
|
||||
Chapter = chapterRecord.Chapter,
|
||||
Difficulty = chapterRecord.DifficultyId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.WriteLine($"Get ClearInfos Exception: {e.Message}", LogType.Error);
|
||||
}
|
||||
return clearInfos;
|
||||
}
|
||||
|
||||
public static List<NetSimRoomCharacterHp> GetCharacterHp(User user)
|
||||
{
|
||||
List<NetSimRoomCharacterHp> hps = [];
|
||||
if (user.UserTeams.TryGetValue((int)TeamType.SimulationRoom, out var userTeamData))
|
||||
{
|
||||
if (userTeamData.Teams.Count > 0 && userTeamData.Teams[0].Slots.Count > 0)
|
||||
{
|
||||
foreach (var slot in userTeamData.Teams[0].Slots)
|
||||
{
|
||||
hps.Add(new() { Csn = slot.Value, Hp = 100000 });
|
||||
}
|
||||
}
|
||||
}
|
||||
return hps;
|
||||
}
|
||||
|
||||
private static NetSimRoomOverclockData GetOverclockData(User user)
|
||||
{
|
||||
return new NetSimRoomOverclockData
|
||||
{
|
||||
CurrentSeasonData = new NetSimRoomOverclockSeasonData
|
||||
{
|
||||
SeasonStartDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow.Date.AddDays(-1)),
|
||||
SeasonEndDate = Timestamp.FromDateTimeOffset(DateTime.UtcNow.Date.AddDays(7)),
|
||||
IsSeasonOpen = true,
|
||||
Season = 1,
|
||||
SubSeason = 1,
|
||||
SeasonWeekCount = 1
|
||||
},
|
||||
|
||||
CurrentSeasonHighScore = new NetSimRoomOverclockHighScoreData
|
||||
{
|
||||
CreatedAt = Timestamp.FromDateTimeOffset(DateTime.UtcNow.Date.AddDays(-1)),
|
||||
OptionLevel = 1,
|
||||
Season = 1,
|
||||
SubSeason = 1,
|
||||
OptionList = { 1 }
|
||||
},
|
||||
|
||||
CurrentSubSeasonHighScore = new NetSimRoomOverclockHighScoreData
|
||||
{
|
||||
CreatedAt = Timestamp.FromDateTimeOffset(DateTime.UtcNow.Date.AddDays(-1)),
|
||||
OptionLevel = 1,
|
||||
Season = 1,
|
||||
SubSeason = 1,
|
||||
OptionList = { 1 }
|
||||
},
|
||||
|
||||
LatestOption = new NetSimRoomOverclockOptionSettingData
|
||||
{
|
||||
Season = 1,
|
||||
OptionList = { 1 }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
EpinelPS/LobbyServer/Simroom/ProceedBuffFunction.cs
Normal file
42
EpinelPS/LobbyServer/Simroom/ProceedBuffFunction.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/proceedbufffunction")]
|
||||
public class ProceedBuffFunction : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "location": { "chapter": 3, "stage": 6, "order": 1 }, "event": 22116, "selectionNumber": 2, "selectionGroupElementId": 221162, "buffToDelete": 2030608 }
|
||||
ReqProceedSimRoomBuffFunction req = await ReadData<ReqProceedSimRoomBuffFunction>();
|
||||
// ReqProceedSimRoomBuffFunction Field NetSimRoomEventLocationInfo location, int event, int selectionNumber, int selectionGroupElementId, int buffToDelete
|
||||
User user = GetUser();
|
||||
|
||||
// ReqProceedSimRoomBuffFunction Field SimRoomResult Result, RepeatedField<int> AcquiredBuff, RepeatedField<int> DeletedBuff
|
||||
ResProceedSimRoomBuffFunction response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success
|
||||
};
|
||||
if (req.BuffToDelete > 0)
|
||||
{
|
||||
response.DeletedBuff.Add(req.BuffToDelete);
|
||||
}
|
||||
|
||||
// Update
|
||||
var location = req.Location;
|
||||
// Check
|
||||
var events = user.ResetableData.SimRoomData.Events;
|
||||
var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order);
|
||||
if (simRoomEventIndex < 0)
|
||||
{
|
||||
Logging.Warn("Not Fond UserSimRoomEvent");
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
SimRoomHelper.UpdateUserSimRoomEvent(user, index: simRoomEventIndex, events: events, selectionNumber: req.SelectionNumber, isDone: true);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
113
EpinelPS/LobbyServer/Simroom/ProceedNikkeFunction.cs
Normal file
113
EpinelPS/LobbyServer/Simroom/ProceedNikkeFunction.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/proceednikkefunction")]
|
||||
public class ProceedNikkeFunction : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "location": { "chapter": 3, "stage": 6, "order": 1 }, "event": 10040, "selectionNumber": 1, "selectionGroupElementId": 100401 }
|
||||
ReqProceedSimRoomNikkeFunction req = await ReadData<ReqProceedSimRoomNikkeFunction>();
|
||||
User user = GetUser();
|
||||
|
||||
ResProceedSimRoomNikkeFunction response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success
|
||||
};
|
||||
|
||||
var location = req.Location;
|
||||
// Check
|
||||
var events = user.ResetableData.SimRoomData.Events;
|
||||
var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order);
|
||||
if (simRoomEventIndex < 0)
|
||||
{
|
||||
Logging.Warn("Not Fond UserSimRoomEvent");
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
// changedHps
|
||||
try
|
||||
{
|
||||
if (GameData.Instance.SimulationRoomSelectionGroupTable.TryGetValue(req.SelectionGroupElementId, out var selectionGroup))
|
||||
{
|
||||
if (selectionGroup.EventFunctionType == SimulationRoomEventFunctionType.Heal)
|
||||
{
|
||||
if (selectionGroup.EventFunctionTargetType == SimulationRoomEventfunctionTargetType.All)
|
||||
{
|
||||
var changedRemainingHps = UpdateUserRemainingHps(user, selectionGroup.EventFunctionValue, type: SimulationRoomEventFunctionType.Heal);
|
||||
response.ChangedHps.AddRange(changedRemainingHps.Select(x => new NetSimRoomCharacterHp { Csn = x.Csn, Hp = x.Hp }));
|
||||
|
||||
SimRoomHelper.UpdateUserSimRoomEvent(user, index: simRoomEventIndex, events, selectionNumber: req.SelectionNumber, isDone: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warn($"Not implement EventFunctionTargetType: {selectionGroup.EventFunctionTargetType}");
|
||||
}
|
||||
}
|
||||
else if (selectionGroup.EventFunctionType == SimulationRoomEventFunctionType.Resurrection)
|
||||
{
|
||||
if (selectionGroup.EventFunctionTargetType == SimulationRoomEventfunctionTargetType.All)
|
||||
{
|
||||
var changedRemainingHps = UpdateUserRemainingHps(user, selectionGroup.EventFunctionValue, type: SimulationRoomEventFunctionType.Resurrection);
|
||||
response.ChangedHps.AddRange(changedRemainingHps.Select(x => new NetSimRoomCharacterHp { Csn = x.Csn, Hp = x.Hp }));
|
||||
|
||||
SimRoomHelper.UpdateUserSimRoomEvent(user, index: simRoomEventIndex, events, selectionNumber: req.SelectionNumber, isDone: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warn($"Not implement EventFunctionTargetType: {selectionGroup.EventFunctionTargetType}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warn($"Not implement EventFunctionType: {selectionGroup.EventFunctionType}");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warn("Not Fond SimulationRoomSelectionGroup");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Warn($"ProceedNikkeFunction ChangedHps Exception {e.Message}");
|
||||
}
|
||||
|
||||
// Teams
|
||||
var team = SimRoomHelper.GetTeamData(user, 1, null);
|
||||
if (team is not null) response.Teams.Add(team);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
public static List<SimRoomCharacterHp> UpdateUserRemainingHps(User user, int HpValue, SimulationRoomEventFunctionType type = SimulationRoomEventFunctionType.Heal)
|
||||
{
|
||||
var remainingHps = user.ResetableData.SimRoomData.RemainingHps;
|
||||
if (remainingHps is not null && remainingHps.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < remainingHps.Count; i++)
|
||||
{
|
||||
if (type is SimulationRoomEventFunctionType.Heal && remainingHps[i].Hp >= 0)
|
||||
{
|
||||
// Heal
|
||||
remainingHps[i] = new SimRoomCharacterHp { Csn = remainingHps[i].Csn, Hp = HpValue };
|
||||
}
|
||||
else if (type is SimulationRoomEventFunctionType.Resurrection && remainingHps[i].Hp < 0)
|
||||
{
|
||||
// Resurrection
|
||||
remainingHps[i] = new SimRoomCharacterHp { Csn = remainingHps[i].Csn, Hp = HpValue };
|
||||
}
|
||||
}
|
||||
user.ResetableData.SimRoomData.RemainingHps = remainingHps;
|
||||
return remainingHps;
|
||||
}
|
||||
return remainingHps;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,35 @@ namespace EpinelPS.LobbyServer.Simroom
|
||||
Result = SimRoomResult.Success,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var item in req.BuffsToAdd)
|
||||
{
|
||||
if (!user.ResetableData.SimRoomData.LegacyBuffs.Contains(item))
|
||||
user.ResetableData.SimRoomData.LegacyBuffs.Add(item);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Warn($"QuitSimRoom BuffsToAdd Exception {e.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var item in req.BuffsToDelete)
|
||||
{
|
||||
user.ResetableData.SimRoomData.LegacyBuffs.Remove(item);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Warn($"QuitSimRoom BuffsToDelete Exception {e.Message}");
|
||||
}
|
||||
|
||||
user.ResetableData.SimRoomData.Entered = false;
|
||||
user.ResetableData.SimRoomData.Events = [];
|
||||
user.ResetableData.SimRoomData.RemainingHps = [];
|
||||
user.ResetableData.SimRoomData.Buffs = [];
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
|
||||
76
EpinelPS/LobbyServer/Simroom/SelectBuff.cs
Normal file
76
EpinelPS/LobbyServer/Simroom/SelectBuff.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/selectbuff")]
|
||||
public class SelectBuff : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// {"location":{"chapter":3,"stage":4,"order":3},"event":111011152,"buffToAdd":1030504}
|
||||
ReqSelectSimRoomBuff req = await ReadData<ReqSelectSimRoomBuff>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSelectSimRoomBuff response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success
|
||||
};
|
||||
|
||||
// Update User SimRoomData Buffs
|
||||
try
|
||||
{
|
||||
var buffs = user.ResetableData.SimRoomData.Buffs;
|
||||
if (req.BuffToDelete > 0)
|
||||
{
|
||||
if (buffs.Contains(req.BuffToDelete)) buffs.Remove(req.BuffToDelete);
|
||||
}
|
||||
|
||||
if (req.BuffToAdd > 0)
|
||||
{
|
||||
if (!buffs.Contains(req.BuffToDelete)) buffs.Add(req.BuffToAdd);
|
||||
}
|
||||
user.ResetableData.SimRoomData.Buffs = buffs;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Warn($"Update User SimRoomData Buffs Exception: {e.Message}");
|
||||
}
|
||||
|
||||
// NetRewardData Reward
|
||||
// NetRewardData RewardByRewardUpEvent
|
||||
// NetRewardData RewardByOverclock
|
||||
|
||||
var location = req.Location;
|
||||
if (location is not null)
|
||||
{
|
||||
var events = user.ResetableData.SimRoomData.Events;
|
||||
var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order);
|
||||
if (simRoomEventIndex > -1)
|
||||
{
|
||||
// Update User SimRoomData Events
|
||||
SimRoomHelper.UpdateUserSimRoomEvent(user, simRoomEventIndex, events, battleProgress: (int)SimRoomBattleEventProgress.RewardReceived);
|
||||
|
||||
// Reward
|
||||
var sorted = events.OrderBy(x => x.Location.Stage).ThenBy(x => x.Location.Order).ToList(); // Sort by Stage, Order
|
||||
var last = sorted[^1]; // Get last event
|
||||
if (last.Location.Chapter == location.Chapter && last.Location.Stage == location.Stage && last.Location.Order == location.Order)
|
||||
{
|
||||
var difficulty = user.ResetableData.SimRoomData.CurrentDifficulty;
|
||||
var reward = SimRoomHelper.SimRoomReceivedReward(user, difficulty, location.Chapter);
|
||||
if (reward is not null) response.Reward = reward;
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,18 @@ namespace EpinelPS.LobbyServer.Simroom
|
||||
Result = SimRoomResult.Success,
|
||||
};
|
||||
|
||||
|
||||
user.ResetableData.SimRoomData.Entered = true;
|
||||
user.ResetableData.SimRoomData.CurrentDifficulty = req.Difficulty;
|
||||
user.ResetableData.SimRoomData.CurrentChapter = req.StartingChapter;
|
||||
|
||||
// TODO: generate buffs
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
List<NetSimRoomEvent> events = SimRoomHelper.GetSimRoomEvents(user);
|
||||
user.ResetableData.SimRoomData.Events = [.. events.Select(SimRoomHelper.NetToM)];
|
||||
JsonDb.Save();
|
||||
|
||||
response.Events.AddRange(events);
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
23
EpinelPS/LobbyServer/Simroom/SelectEvent.cs
Normal file
23
EpinelPS/LobbyServer/Simroom/SelectEvent.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/selectevent")]
|
||||
public class SelectEvent : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "location": { "chapter": 3, "stage": 1, "order": 3 }, "event": 111021115 }
|
||||
ReqSelectSimRoomEvent req = await ReadData<ReqSelectSimRoomEvent>();
|
||||
// User user = GetUser();
|
||||
|
||||
ResSelectSimRoomEvent response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success,
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
EpinelPS/LobbyServer/Simroom/SelectSelectionEvent.cs
Normal file
34
EpinelPS/LobbyServer/Simroom/SelectSelectionEvent.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/selectselectionevent")]
|
||||
public class SelectSelectionEvent : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSelectSimRoomSelectionEvent req = await ReadData<ReqSelectSimRoomSelectionEvent>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSelectSimRoomSelectionEvent response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success
|
||||
};
|
||||
|
||||
var location = req.Location;
|
||||
// Check
|
||||
var events = user.ResetableData.SimRoomData.Events;
|
||||
var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order);
|
||||
if (simRoomEventIndex < 0)
|
||||
{
|
||||
Logging.Warn("Not Fond UserSimRoomEvent");
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
SimRoomHelper.UpdateUserSimRoomEvent(user, index: simRoomEventIndex, events: events, selectionNumber: req.SelectionNumber);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
633
EpinelPS/LobbyServer/Simroom/SimRoomHelper.cs
Normal file
633
EpinelPS/LobbyServer/Simroom/SimRoomHelper.cs
Normal file
@@ -0,0 +1,633 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
public static class SimRoomHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(SimRoomHelper));
|
||||
private static readonly Random _rand = new();
|
||||
|
||||
/// <summary>
|
||||
/// Get SimRoomEvent By User or DifficultyId and ChapterId
|
||||
/// </summary>
|
||||
/// <param name="user"> User </param>
|
||||
/// <param name="difficultyId"> DifficultyId </param>
|
||||
/// <param name="chapterId"> ChapterId </param>
|
||||
/// <returns>The list of NetSimRoomEvent</returns>
|
||||
public static List<NetSimRoomEvent> GetSimRoomEvents(User user, int difficultyId = 0, int chapterId = 0)
|
||||
{
|
||||
List<NetSimRoomEvent> netSimRooms = [];
|
||||
int currentDifficulty = user.ResetableData.SimRoomData.CurrentDifficulty;
|
||||
int currentChapter = user.ResetableData.SimRoomData.CurrentChapter;
|
||||
|
||||
if (difficultyId > 0) currentDifficulty = difficultyId;
|
||||
if (chapterId > 0) currentChapter = chapterId;
|
||||
|
||||
var events = user.ResetableData.SimRoomData.Events;
|
||||
if (events.Count > 1)
|
||||
{
|
||||
return [.. events.Select(MToNet)];
|
||||
}
|
||||
|
||||
var simRoomChapter = GameData.Instance.SimulationRoomChapterTable.Values.FirstOrDefault(x => x.Chapter == currentChapter && x.DifficultyId == currentDifficulty);
|
||||
log.Debug($"Fond SimulationRoomChapter Chapter: {currentChapter}, DifficultyId: {currentDifficulty} Data: {JsonConvert.SerializeObject(simRoomChapter)}");
|
||||
|
||||
var buffPreviewRecords = GameData.Instance.SimulationRoomBuffPreviewTable.Values.ToList();
|
||||
var simRoomBattleEventRecords = GameData.Instance.SimulationRoomBattleEventTable.Values.ToList();
|
||||
var simRoomSelectionEventRecords = GameData.Instance.SimulationRoomSelectionEventTable.Values.ToList();
|
||||
var simRoomSelectionEventGroupRecords = GameData.Instance.SimulationRoomSelectionGroupTable.Values.ToList();
|
||||
|
||||
if (simRoomChapter is null) return netSimRooms;
|
||||
|
||||
for (int i = 1; i <= simRoomChapter.StageValue; i++)
|
||||
{
|
||||
if (i == simRoomChapter.StageValue) // BossBattle
|
||||
{
|
||||
var battleBuffPreviews = GameData.Instance.SimulationRoomBuffPreviewTable.Values.Where(x => x.EventType is SimulationRoomEvent.BossBattle).ToList();
|
||||
var randomBuffPreview = GetRandomItems(battleBuffPreviews, 1);
|
||||
var simRoomBattleEvent = CreateSimRoomBattleEvent(chapter: simRoomChapter, stage: i, order: 1, simRoomBattleEventRecords, randomBuffPreview[0]);
|
||||
events.Add(NetToM(simRoomBattleEvent));
|
||||
netSimRooms.Add(simRoomBattleEvent);
|
||||
}
|
||||
else if (i == simRoomChapter.StageValue - 1) // Selection
|
||||
{
|
||||
// Maintenance Selection
|
||||
var simRoomSelectionEventRecord = simRoomSelectionEventRecords.OrderBy(x => x.Id).ToList()
|
||||
.FindLast(x => x.EventType is SimulationRoomEvent.Maintenance);
|
||||
|
||||
var simRoomEvent = new NetSimRoomEvent()
|
||||
{
|
||||
Location = new NetSimRoomEventLocationInfo { Chapter = simRoomChapter.Chapter, Stage = i, Order = 1 },
|
||||
};
|
||||
|
||||
if (simRoomSelectionEventRecord is not null)
|
||||
{
|
||||
simRoomEvent.Selection = new NetSimRoomSelectionEvent
|
||||
{
|
||||
Id = simRoomSelectionEventRecord.Id,
|
||||
SelectedNumber = 1
|
||||
};
|
||||
var groupRecordsBySelectionGroupId = simRoomSelectionEventGroupRecords.FindAll(x => x.SelectionGroupId == simRoomSelectionEventRecord.SelectionGroupId);
|
||||
foreach (var groupRecord in groupRecordsBySelectionGroupId)
|
||||
{
|
||||
simRoomEvent.Selection.Group.Add(new NetSimRoomSelectionGroupElement { SelectionNumber = groupRecord.SelectionNumber, Id = groupRecord.Id });
|
||||
}
|
||||
}
|
||||
|
||||
events.Add(NetToM(simRoomEvent));
|
||||
netSimRooms.Add(simRoomEvent);
|
||||
log.Debug($"stage: {i}, NetSimRoomEvent: {JsonConvert.SerializeObject(simRoomEvent)}");
|
||||
|
||||
// Random
|
||||
var RandomSimRoomSelectionEventRecords = simRoomSelectionEventRecords.FindAll(x
|
||||
=> x.EventType is SimulationRoomEvent.RandomSelection or SimulationRoomEvent.EnhanceBuff);
|
||||
|
||||
var RandomSimRoomSelectionEventRecord = GetRandomItems(RandomSimRoomSelectionEventRecords, 1);
|
||||
|
||||
var RandomSimRoomEvent = new NetSimRoomEvent
|
||||
{
|
||||
Location = new NetSimRoomEventLocationInfo { Chapter = simRoomChapter.Chapter, Stage = i, Order = 2 },
|
||||
};
|
||||
if (RandomSimRoomSelectionEventRecord != null && RandomSimRoomSelectionEventRecord.Count >= 1)
|
||||
{
|
||||
RandomSimRoomEvent.Selection = new NetSimRoomSelectionEvent
|
||||
{
|
||||
Id = RandomSimRoomSelectionEventRecord[0].Id,
|
||||
SelectedNumber = 2
|
||||
};
|
||||
|
||||
var groupRecordsBySelectionGroupId = simRoomSelectionEventGroupRecords.FindAll(x
|
||||
=> x.SelectionGroupId == RandomSimRoomSelectionEventRecord[0].SelectionGroupId);
|
||||
|
||||
foreach (var groupRecord in groupRecordsBySelectionGroupId)
|
||||
{
|
||||
RandomSimRoomEvent.Selection.Group.Add(new NetSimRoomSelectionGroupElement { SelectionNumber = groupRecord.SelectionNumber, Id = groupRecord.Id });
|
||||
}
|
||||
}
|
||||
|
||||
events.Add(NetToM(RandomSimRoomEvent));
|
||||
netSimRooms.Add(RandomSimRoomEvent);
|
||||
log.Debug($"stage: {i}, NetSimRoomEvent: {JsonConvert.SerializeObject(RandomSimRoomEvent)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var battleBuffPreviews = buffPreviewRecords.FindAll(x
|
||||
=> x.EventType is SimulationRoomEvent.NormalBattle or SimulationRoomEvent.EliteBattle);
|
||||
var randomBuffPreview = GetRandomItems(battleBuffPreviews, 3);
|
||||
int order = 0;
|
||||
foreach (var simRoomBuffPreview in randomBuffPreview)
|
||||
{
|
||||
order += 1;
|
||||
var simRoomBattleEvent = CreateSimRoomBattleEvent(chapter: simRoomChapter, stage: i, order: order, simRoomBattleEventRecords, simRoomBuffPreview);
|
||||
events.Add(NetToM(simRoomBattleEvent));
|
||||
netSimRooms.Add(simRoomBattleEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
// user.AddTrigger()
|
||||
user.ResetableData.SimRoomData.Events = events;
|
||||
JsonDb.Save();
|
||||
return netSimRooms;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get BuffOptions By User And SimRoomEventLocationInfo
|
||||
/// </summary>
|
||||
/// <param name="user"> User </param>
|
||||
/// <param name="location"> NetSimRoomEventLocationInfo </param>
|
||||
/// <returns>List<int></returns>
|
||||
public static List<int> GetBuffOptions(User user, NetSimRoomEventLocationInfo location)
|
||||
{
|
||||
var events = user.ResetableData.SimRoomData.Events;
|
||||
var simRoomEventIndex = events.FindIndex(x => x.Location.Chapter == location.Chapter && x.Location.Stage == location.Stage && x.Location.Order == location.Order);
|
||||
if (simRoomEventIndex > -1)
|
||||
{
|
||||
var simRoomEvent = events[simRoomEventIndex];
|
||||
if (GameData.Instance.SimulationRoomBuffPreviewTable.TryGetValue(simRoomEvent.Battle.BuffPreviewId, out var buffPreview))
|
||||
{
|
||||
log.Debug($"SimRoomBuffPreview: {JsonConvert.SerializeObject(buffPreview)}");
|
||||
List<SimulationRoomBuffRecord> buffRecords = [];
|
||||
if (buffPreview.PreviewType is PreviewType.Bubble)
|
||||
{
|
||||
var bubbleType = GetBubbleType(buffPreview.PreviewTarget);
|
||||
buffRecords = [.. GameData.Instance.SimulationRoomBuffTable.Values
|
||||
.Where(x => x.BubbleType == bubbleType
|
||||
&& x.Grade is SimulationRoomBuffGrade.SR or SimulationRoomBuffGrade.SSR )];
|
||||
}
|
||||
else if (buffPreview.PreviewType is PreviewType.MainTarget)
|
||||
{
|
||||
var mainTarget = GetMainTarget(buffPreview.PreviewTarget);
|
||||
buffRecords = [.. GameData.Instance.SimulationRoomBuffTable.Values
|
||||
.Where(x => x.MainTarget == mainTarget
|
||||
&& x.Grade is SimulationRoomBuffGrade.SR or SimulationRoomBuffGrade.SSR)];
|
||||
}
|
||||
else if (buffPreview.PreviewType is PreviewType.Grade)
|
||||
{
|
||||
buffRecords = [.. GameData.Instance.SimulationRoomBuffTable.Values
|
||||
.Where(x => x.Grade == SimulationRoomBuffGrade.EPIC)];
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warn($"Not Implement SimulationRoomBuffPreview.PreviewType: {buffPreview.PreviewType}");
|
||||
}
|
||||
if (buffRecords.Count > 0)
|
||||
{
|
||||
var selectedBuffs = buffRecords.GetRandomItems(3);
|
||||
log.Debug($"Selected Buffs: {JsonConvert.SerializeObject(selectedBuffs)}");
|
||||
|
||||
// user SimRoomEvent update
|
||||
UpdateUserSimRoomEvent(user, simRoomEventIndex, events, battleProgress: (int)SimRoomBattleEventProgress.RewardWaiting, BuffOptions: [.. selectedBuffs.Select(x => x.Id)]);
|
||||
return [.. selectedBuffs.Select(x => x.Id)];
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warn($"Not Font SimulationRoomBuff");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warn($"Not Font User.ResetableData.SimRoomData.Events");
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get TeamData By User Or SimRoomCharacterHp
|
||||
/// </summary>
|
||||
/// <param name="user"> User </param>
|
||||
/// <param name="teamNumber"> Int </param>
|
||||
/// <param name="remainingHps"> List<NetSimRoomCharacterHp> </param>
|
||||
/// <returns> NetTeamData </returns>
|
||||
public static NetTeamData GetTeamData(User user, int teamNumber, List<NetSimRoomCharacterHp>? remainingHps)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (remainingHps is not null && remainingHps.Count > 0)
|
||||
{
|
||||
var team = new NetTeamData()
|
||||
{
|
||||
TeamNumber = teamNumber
|
||||
};
|
||||
int slot = 1;
|
||||
foreach (var item in remainingHps)
|
||||
{
|
||||
team.Slots.Add(new NetTeamSlot() { Slot = slot, Value = item.Csn });
|
||||
slot += 1;
|
||||
}
|
||||
return team;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user.UserTeams.TryGetValue((int)TeamType.SimulationRoom, out var teamData))
|
||||
{
|
||||
var team = teamData.Teams.FirstOrDefault(t => t.TeamNumber == teamNumber);
|
||||
if (team is not null) return team;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error($"Get User Teams Exception: {e.Message}");
|
||||
}
|
||||
|
||||
// default value is null
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomly select a specified number of elements from the list.
|
||||
/// </summary>
|
||||
public static List<T> GetRandomItems<T>(this List<T>? list, int count)
|
||||
{
|
||||
if (list is null || list.Count == 0)
|
||||
return [];
|
||||
|
||||
// If the number of requests exceeds the list length, retrieve all.
|
||||
count = Math.Min(count, list.Count);
|
||||
|
||||
return [.. list.OrderBy(x => _rand.Next()).Take(count)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create SimRoomBattleEvent
|
||||
/// </summary>
|
||||
/// <param name="chapter"> SimulationRoomChapterRecord </param>
|
||||
/// <param name="stage"></param>
|
||||
/// <param name="order"></param>
|
||||
/// <param name="simRoomBattleEventRecords"></param>
|
||||
/// <param name="randomBuffPreview"></param>
|
||||
/// <returns>NetSimRoomEvent</returns>
|
||||
private static NetSimRoomEvent CreateSimRoomBattleEvent(SimulationRoomChapterRecord chapter, int stage, int order,
|
||||
List<SimulationRoomBattleEventRecord> simRoomBattleEventRecords, SimulationRoomBuffPreviewRecord randomBuffPreview)
|
||||
{
|
||||
var simRoomEvent = new NetSimRoomEvent();
|
||||
var location = new NetSimRoomEventLocationInfo();
|
||||
var battle = new NetSimRoomBattleEvent();
|
||||
|
||||
location.Chapter = chapter.Chapter;
|
||||
location.Stage = stage;
|
||||
location.Order = order;
|
||||
var simRoomBuffPreviewBattleEvents = simRoomBattleEventRecords.FindAll(x
|
||||
=> x.EventType == randomBuffPreview.EventType && x.DifficultyConditionValue <= chapter.DifficultyId);
|
||||
log.Debug($"EventType: {randomBuffPreview.EventType}, SimRoomBattleEventRecord Count: {simRoomBuffPreviewBattleEvents.Count}");
|
||||
var randomBattleEvents = GetRandomItems(simRoomBuffPreviewBattleEvents, 1);
|
||||
foreach (var battleEvent in randomBattleEvents)
|
||||
{
|
||||
battle.Id = battleEvent.Id;
|
||||
battle.RemainingTargetHealth = 10000;
|
||||
battle.BuffPreviewId = randomBuffPreview.Id;
|
||||
}
|
||||
|
||||
simRoomEvent.Location = location;
|
||||
simRoomEvent.Battle = battle;
|
||||
log.Debug($"stage: {stage}, NetSimRoomEvent: {JsonConvert.SerializeObject(simRoomEvent)}");
|
||||
return simRoomEvent;
|
||||
}
|
||||
|
||||
public static NetSimRoomEvent MToNet(SimRoomEvent simRoomEvent)
|
||||
{
|
||||
var netSimRoomEvent = new NetSimRoomEvent
|
||||
{
|
||||
Selected = simRoomEvent.Selected,
|
||||
};
|
||||
if (simRoomEvent.Location is not null && simRoomEvent.Location.Chapter > 0)
|
||||
{
|
||||
netSimRoomEvent.Location = new NetSimRoomEventLocationInfo
|
||||
{
|
||||
Chapter = simRoomEvent.Location.Chapter,
|
||||
Stage = simRoomEvent.Location.Stage,
|
||||
Order = simRoomEvent.Location.Order
|
||||
};
|
||||
}
|
||||
|
||||
if (simRoomEvent.Battle is not null && simRoomEvent.Battle.Id > 0)
|
||||
{
|
||||
netSimRoomEvent.Battle = new NetSimRoomBattleEvent
|
||||
{
|
||||
Id = simRoomEvent.Battle.Id,
|
||||
BuffOptions = { simRoomEvent.Battle.BuffOptions },
|
||||
Progress = (SimRoomBattleEventProgress)simRoomEvent.Battle.Progress,
|
||||
RemainingTargetHealth = simRoomEvent.Battle.RemainingTargetHealth,
|
||||
BuffPreviewId = simRoomEvent.Battle.BuffPreviewId,
|
||||
};
|
||||
}
|
||||
|
||||
if (simRoomEvent.Selection is not null && simRoomEvent.Selection.Id > 0)
|
||||
{
|
||||
netSimRoomEvent.Selection = new NetSimRoomSelectionEvent
|
||||
{
|
||||
Id = simRoomEvent.Selection.Id,
|
||||
SelectedNumber = simRoomEvent.Selection.SelectedNumber,
|
||||
};
|
||||
simRoomEvent.Selection.Group.ForEach(g =>
|
||||
{
|
||||
netSimRoomEvent.Selection.Group.Add(new NetSimRoomSelectionGroupElement
|
||||
{
|
||||
SelectionNumber = g.SelectionNumber,
|
||||
Id = g.Id,
|
||||
IsDone = g.IsDone,
|
||||
RandomBuff = g.RandomBuff,
|
||||
});
|
||||
});
|
||||
}
|
||||
return netSimRoomEvent;
|
||||
}
|
||||
|
||||
public static SimRoomEvent NetToM(NetSimRoomEvent simRoomEvent)
|
||||
{
|
||||
var netSimRoomEvent = new SimRoomEvent
|
||||
{
|
||||
Selected = simRoomEvent.Selected,
|
||||
EventCase = (int)simRoomEvent.EventCase,
|
||||
};
|
||||
if (simRoomEvent.Location is not null && simRoomEvent.Location.Chapter > 0)
|
||||
{
|
||||
netSimRoomEvent.Location = new SimRoomEventLocationInfo
|
||||
{
|
||||
Chapter = simRoomEvent.Location.Chapter,
|
||||
Stage = simRoomEvent.Location.Stage,
|
||||
Order = simRoomEvent.Location.Order
|
||||
};
|
||||
}
|
||||
|
||||
if (simRoomEvent.Battle is not null && simRoomEvent.Battle.Id > 0)
|
||||
{
|
||||
netSimRoomEvent.Battle = new SimRoomBattleEvent
|
||||
{
|
||||
Id = simRoomEvent.Battle.Id,
|
||||
Progress = (int)simRoomEvent.Battle.Progress,
|
||||
RemainingTargetHealth = simRoomEvent.Battle.RemainingTargetHealth,
|
||||
BuffPreviewId = simRoomEvent.Battle.BuffPreviewId,
|
||||
};
|
||||
foreach (var item in simRoomEvent.Battle.BuffOptions)
|
||||
{
|
||||
netSimRoomEvent.Battle.BuffOptions.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (simRoomEvent.Selection is not null && simRoomEvent.Selection.Id > 0)
|
||||
{
|
||||
netSimRoomEvent.Selection = new SimRoomSelectionEvent
|
||||
{
|
||||
Id = simRoomEvent.Selection.Id,
|
||||
SelectedNumber = simRoomEvent.Selection.SelectedNumber,
|
||||
};
|
||||
foreach (var g in simRoomEvent.Selection.Group)
|
||||
{
|
||||
netSimRoomEvent.Selection.Group.Add(new SimRoomSelectionGroupElement
|
||||
{
|
||||
SelectionNumber = g.SelectionNumber,
|
||||
Id = g.Id,
|
||||
IsDone = g.IsDone,
|
||||
RandomBuff = g.RandomBuff,
|
||||
});
|
||||
}
|
||||
}
|
||||
return netSimRoomEvent;
|
||||
}
|
||||
|
||||
public static SimulationRoomBubbleType GetBubbleType(string previewTarget)
|
||||
{
|
||||
// Type_C
|
||||
switch (previewTarget)
|
||||
{
|
||||
case "Type_A":
|
||||
return SimulationRoomBubbleType.TypeA;
|
||||
case "Type_B":
|
||||
return SimulationRoomBubbleType.TypeB;
|
||||
case "Type_C":
|
||||
return SimulationRoomBubbleType.TypeC;
|
||||
case "Type_D":
|
||||
return SimulationRoomBubbleType.TypeD;
|
||||
default:
|
||||
log.Warn("Unknown preview target: " + previewTarget);
|
||||
return SimulationRoomBubbleType.TypeA;
|
||||
}
|
||||
}
|
||||
|
||||
public static SimulationRoomBuffMainTarget GetMainTarget(string previewTarget)
|
||||
{
|
||||
// Shoot = 0, Attack = 1, Survive = 2
|
||||
switch (previewTarget)
|
||||
{
|
||||
case "Shoot":
|
||||
return SimulationRoomBuffMainTarget.Shoot;
|
||||
case "Attack":
|
||||
return SimulationRoomBuffMainTarget.Attack;
|
||||
case "Survive":
|
||||
return SimulationRoomBuffMainTarget.Survive;
|
||||
default:
|
||||
log.Warn("Unknown preview target: " + previewTarget);
|
||||
return SimulationRoomBuffMainTarget.Attack;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update User SimRoomEvent Events
|
||||
/// </summary>
|
||||
public static void UpdateUserSimRoomEvent(User user, int index, List<SimRoomEvent> events, int selectionNumber = 0,
|
||||
bool isDone = false, int battleProgress = 0, List<int>? BuffOptions = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var simRoomEvent = events[index];
|
||||
// user SimRoomEvent update
|
||||
simRoomEvent.Selected = true;
|
||||
|
||||
// Update Selection Group
|
||||
var groupIndex = simRoomEvent.Selection.Group.FindIndex(x => x.SelectionNumber == selectionNumber);
|
||||
if (groupIndex > -1 && isDone)
|
||||
{
|
||||
var group = simRoomEvent.Selection.Group[groupIndex];
|
||||
group.IsDone = isDone;
|
||||
simRoomEvent.Selection.Group[groupIndex] = group;
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warn("Not Fond SimRoomSelectionEvent.Group");
|
||||
}
|
||||
|
||||
// Udapte Battle Progress
|
||||
if (battleProgress > 0) simRoomEvent.Battle.Progress = battleProgress;
|
||||
|
||||
// Update BuffOptions
|
||||
if (BuffOptions is not null && BuffOptions.Count > 0)
|
||||
{
|
||||
simRoomEvent.Battle.BuffOptions = BuffOptions;
|
||||
}
|
||||
|
||||
events[index] = simRoomEvent;
|
||||
user.ResetableData.SimRoomData.Events = events;
|
||||
log.Debug($"UpdateUserSimRoomEvent After UserSimRoomEventData: {JsonConvert.SerializeObject(user.ResetableData.SimRoomData.Events)}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error($"Update UserSimRoomEvent Events Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<SimRoomCharacterHp> UpdateUserRemainingHps(User user, List<NetSimRoomCharacterHp>? remainingHps = null, int teamNumber = 1, int HpValue = 10000)
|
||||
{
|
||||
var userRemainingHps = user.ResetableData.SimRoomData.RemainingHps;
|
||||
try
|
||||
{
|
||||
// req remainingHps
|
||||
if (remainingHps is not null && remainingHps.Count > 0)
|
||||
{
|
||||
foreach (var characterHp in remainingHps)
|
||||
{
|
||||
var userCharacterHpIndex = userRemainingHps.FindIndex(x => x.Csn == characterHp.Csn);
|
||||
if (userCharacterHpIndex > -1)
|
||||
{
|
||||
userRemainingHps[userCharacterHpIndex] = new SimRoomCharacterHp { Csn = characterHp.Csn, Hp = characterHp.Hp };
|
||||
}
|
||||
else
|
||||
{
|
||||
userRemainingHps.Add(new SimRoomCharacterHp { Csn = characterHp.Csn, Hp = characterHp.Hp });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get user team
|
||||
if (user.UserTeams.TryGetValue((int)TeamType.SimulationRoom, out var userTeam))
|
||||
{
|
||||
var team = userTeam.Teams.FirstOrDefault(x => x.TeamNumber == teamNumber);
|
||||
if (team is not null)
|
||||
{
|
||||
foreach (var slot in team.Slots)
|
||||
{
|
||||
if (userRemainingHps.FindIndex(x => x.Csn == slot.Value) < 0) userRemainingHps.Add(new SimRoomCharacterHp { Csn = slot.Value, Hp = HpValue });
|
||||
}
|
||||
}
|
||||
}
|
||||
// update user
|
||||
user.ResetableData.SimRoomData.RemainingHps = userRemainingHps;
|
||||
return userRemainingHps;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error($"Update UserRemainingHps Exception: {e.Message}");
|
||||
}
|
||||
return userRemainingHps;
|
||||
}
|
||||
|
||||
|
||||
public static NetRewardData? SimRoomReceivedReward(User user, int difficultyId, int chapterId)
|
||||
{
|
||||
// check if reward is received
|
||||
NetRewardData? reward = null;
|
||||
if (!IsRewardReceived(user, difficultyId, chapterId))
|
||||
{
|
||||
|
||||
var allChapterRecords = GameData.Instance.SimulationRoomChapterTable.Values;
|
||||
var chapter = allChapterRecords
|
||||
.FirstOrDefault(x => x.DifficultyId == difficultyId && x.Chapter == chapterId);
|
||||
|
||||
if (chapter is not null && chapter.RewardId > 0)
|
||||
{
|
||||
var receivedRewardChapters = user.ResetableData.SimRoomData.ReceivedRewardChapters;
|
||||
var receivedRewardChapter = receivedRewardChapters
|
||||
.OrderBy(x => x.Difficulty)
|
||||
.ThenBy(x => x.Chapter)
|
||||
.LastOrDefault();
|
||||
SimulationRoomChapterRecord? receivedRewardChapterRecord = null;
|
||||
if (receivedRewardChapter is not null)
|
||||
{
|
||||
receivedRewardChapterRecord = allChapterRecords
|
||||
.FirstOrDefault(x => x.DifficultyId == receivedRewardChapter.Difficulty
|
||||
&& x.Chapter == receivedRewardChapter.Chapter);
|
||||
}
|
||||
|
||||
reward = new NetRewardData();
|
||||
if (receivedRewardChapterRecord is null)
|
||||
{
|
||||
// Claiming the reward for the first time
|
||||
reward = RewardUtils.RegisterRewardsForUser(user, rewardId: chapter.RewardId);
|
||||
AddRewardChapter(user, difficultyId, chapterId);
|
||||
}
|
||||
// Check if the received reward chapter is lower than the current chapter
|
||||
else if (receivedRewardChapterRecord.DifficultyId == difficultyId && receivedRewardChapterRecord.Chapter < chapter.Chapter)
|
||||
{
|
||||
// Claiming the reward for a higher chapter
|
||||
reward = CalculateIncrementalReward(user, chapter, receivedRewardChapterRecord);
|
||||
AddRewardChapter(user, difficultyId, chapterId);
|
||||
}
|
||||
// Check if the received reward chapter is lower than the current difficulty
|
||||
else if (receivedRewardChapterRecord.DifficultyId < difficultyId)
|
||||
{
|
||||
// Claiming the reward for a higher difficulty
|
||||
reward = RewardUtils.RegisterRewardsForUser(user, rewardId: chapter.RewardId);
|
||||
AddRewardChapter(user, difficultyId, chapterId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reward;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if reward is received
|
||||
/// </summary>
|
||||
/// <returns>True if reward is received, otherwise false</returns>
|
||||
private static bool IsRewardReceived(User user, int difficultyId, int chapterId)
|
||||
{
|
||||
var isReceived = user.ResetableData.SimRoomData.ReceivedRewardChapters.Any(x => x.Chapter == chapterId && x.Difficulty == difficultyId);
|
||||
log.Debug($"IsRewardReceived (diff: {difficultyId}, chapter: {chapterId}): {isReceived}");
|
||||
return isReceived;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add reward chapter
|
||||
/// </summary>
|
||||
private static void AddRewardChapter(User user, int difficulty, int chapter)
|
||||
{
|
||||
user.ResetableData.SimRoomData.ReceivedRewardChapters.Add(new SimRoomChapterInfo()
|
||||
{
|
||||
Difficulty = difficulty,
|
||||
Chapter = chapter
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate incremental reward
|
||||
/// </summary>
|
||||
/// <returns>Incremental reward data</returns>
|
||||
private static NetRewardData CalculateIncrementalReward(User user,
|
||||
SimulationRoomChapterRecord chapter, SimulationRoomChapterRecord receivedChapterRecord)
|
||||
{
|
||||
var reward = new NetRewardData();
|
||||
var rewardRecord = GameData.Instance.GetRewardTableEntry(chapter.RewardId);
|
||||
var receivedRewardRecord = GameData.Instance.GetRewardTableEntry(receivedChapterRecord.RewardId);
|
||||
|
||||
if (rewardRecord?.Rewards == null || receivedRewardRecord?.Rewards == null)
|
||||
{
|
||||
// If rewardRecord or receivedRewardRecord is empty, return the complete reward record
|
||||
reward = RewardUtils.RegisterRewardsForUser(user, rewardId: chapter.RewardId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var receivedRewardIds = receivedRewardRecord.Rewards
|
||||
.Where(x => x != null)
|
||||
.ToDictionary(x => x.RewardId, x => x.RewardValue);
|
||||
|
||||
foreach (var rewardItem in rewardRecord.Rewards.Where(x => x != null))
|
||||
{
|
||||
int receivedAmount = receivedRewardIds.GetValueOrDefault(rewardItem.RewardId, 0);
|
||||
int remainingAmount = Math.Max(0, rewardItem.RewardValue - receivedAmount);
|
||||
|
||||
if (remainingAmount > 0)
|
||||
{
|
||||
RewardUtils.AddSingleObject(user, ref reward, rewardItem.RewardId,
|
||||
rewardType: rewardItem.RewardType, remainingAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reward;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
EpinelPS/LobbyServer/Simroom/SimpleModeSelectBuff.cs
Normal file
47
EpinelPS/LobbyServer/Simroom/SimpleModeSelectBuff.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/simplemode/selectbuff")]
|
||||
public class SimpleModeSelectBuff : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// { "buffToAdd": 1010106, "buffToDelete": 1010105 }
|
||||
ReqSelectSimRoomSimpleModeBuff req = await ReadData<ReqSelectSimRoomSimpleModeBuff>();
|
||||
User user = GetUser();
|
||||
|
||||
|
||||
List<int> buffs = user.ResetableData.SimRoomData.Buffs;
|
||||
List<int> legacyBuffs = user.ResetableData.SimRoomData.LegacyBuffs;
|
||||
if (req.BuffToDelete > 0)
|
||||
{
|
||||
if (buffs.Contains(req.BuffToDelete)) buffs.Remove(req.BuffToDelete);
|
||||
if (legacyBuffs.Contains(req.BuffToDelete)) legacyBuffs.Remove(req.BuffToDelete);
|
||||
}
|
||||
|
||||
if (req.BuffToAdd > 0)
|
||||
{
|
||||
if (!buffs.Contains(req.BuffToDelete)) buffs.Add(req.BuffToDelete);
|
||||
if (!legacyBuffs.Contains(req.BuffToDelete)) legacyBuffs.Add(req.BuffToDelete);
|
||||
}
|
||||
|
||||
ResSelectSimRoomSimpleModeBuff response = new()
|
||||
{
|
||||
Result = SimRoomResult.Reset,
|
||||
NextSimpleModeBuffSelectionInfo = new()
|
||||
{
|
||||
RemainingBuffSelectCount = 8 - buffs.Count,
|
||||
BuffOptions = { buffs },
|
||||
},
|
||||
};
|
||||
|
||||
user.ResetableData.SimRoomData.Entered = false;
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Simroom/SimpleModeSetSkipOption.cs
Normal file
24
EpinelPS/LobbyServer/Simroom/SimpleModeSetSkipOption.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/simplemode/setskipoption")]
|
||||
public class SimpleModeSetSkipOption : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqSetSimRoomSimpleModeSkipOption>();
|
||||
// ReqSetSimRoomSimpleModeSkipOption Fields
|
||||
// bool Enabled
|
||||
User user = GetUser();
|
||||
|
||||
ResSetSimRoomSimpleModeSkipOption response = new();
|
||||
|
||||
user.ResetableData.SimRoomData.IsSimpleModeSkipEnabled = req.Enabled;
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
EpinelPS/LobbyServer/Simroom/SimpleModeSkipAll.cs
Normal file
41
EpinelPS/LobbyServer/Simroom/SimpleModeSkipAll.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/simplemode/skipall")]
|
||||
public class SimpleModeSkipAll : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
await ReadData<ReqSkipAllSimRoomSimpleMode>();
|
||||
var user = GetUser();
|
||||
|
||||
// ResSkipAllSimRoomSimpleMode Fields
|
||||
// SimRoomResult Result
|
||||
// NetRewardData Reward
|
||||
// NetRewardData RewardByRewardUpEvent
|
||||
|
||||
ResSkipAllSimRoomSimpleMode response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success
|
||||
};
|
||||
|
||||
// Reward
|
||||
try
|
||||
{
|
||||
user.ResetableData.SimRoomData.CurrentDifficulty = 5;
|
||||
user.ResetableData.SimRoomData.CurrentChapter = 3;
|
||||
var reward = SimRoomHelper.SimRoomReceivedReward(user, 5, 3); // 5 = difficulty, 3 = stage
|
||||
if (reward is not null) response.Reward = reward;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"SkipAllSimpleMode Reward Exception: {ex.Message}", LogType.Error);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
EpinelPS/LobbyServer/Simroom/SimpleModeSkipBuffSelection.cs
Normal file
25
EpinelPS/LobbyServer/Simroom/SimpleModeSkipBuffSelection.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/simplemode/skipbuffselection")]
|
||||
public class SimpleModeSkipBuffSelection : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
await ReadData<ReqSkipSimRoomSimpleModeBuffSelection>();
|
||||
User user = GetUser();
|
||||
ResSkipSimRoomSimpleModeBuffSelection response = new()
|
||||
{
|
||||
Result = SimRoomResult.Reset,
|
||||
};
|
||||
|
||||
user.ResetableData.SimRoomData.Entered = false;
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
EpinelPS/LobbyServer/Simroom/SimpleModeStart.cs
Normal file
65
EpinelPS/LobbyServer/Simroom/SimpleModeStart.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Simroom
|
||||
{
|
||||
[PacketPath("/simroom/simplemode/start")]
|
||||
public class SimpleModeStart : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
await ReadData<ReqStartSimRoomSimpleMode>();
|
||||
User user = GetUser();
|
||||
|
||||
// ResStartSimRoomSimpleMode Fields
|
||||
// SimRoomResult Result
|
||||
// RepeatedField<NetSimRoomEvent> Events
|
||||
// NextSimpleModeBuffSelectionInfo
|
||||
|
||||
ResStartSimRoomSimpleMode response = new()
|
||||
{
|
||||
Result = SimRoomResult.Success,
|
||||
// NextSimpleModeBuffSelectionInfo = new()
|
||||
// {
|
||||
// BuffOptions = { user.ResetableData.SimRoomData.LegacyBuffs }
|
||||
// }
|
||||
};
|
||||
|
||||
|
||||
// Events
|
||||
try
|
||||
{
|
||||
user.ResetableData.SimRoomData.Entered = true;
|
||||
var simRoomEvents = SimRoomHelper.GetSimRoomEvents(user, 5, 3); // 5 = difficulty, 3 = stage
|
||||
if (simRoomEvents is not null)
|
||||
{
|
||||
foreach (var simRoomEvent in simRoomEvents)
|
||||
{
|
||||
// Check if event is battle and is first order
|
||||
if (simRoomEvent.EventCase == NetSimRoomEvent.EventOneofCase.Battle && simRoomEvent.Location.Order == 1)
|
||||
{
|
||||
var location = simRoomEvent.Location;
|
||||
if (location is null) continue;
|
||||
SimRoomHelper.GetBuffOptions(user, location);
|
||||
}
|
||||
}
|
||||
JsonDb.Save();
|
||||
|
||||
var userSimRoomEvents = user.ResetableData.SimRoomData.Events;
|
||||
if (userSimRoomEvents is not null)
|
||||
{
|
||||
simRoomEvents = [.. userSimRoomEvents.Select(SimRoomHelper.MToNet)];
|
||||
}
|
||||
// Add events to response
|
||||
response.Events.AddRange(simRoomEvents);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"SimpleModeStart Events Exception: {ex.Message}", LogType.Error);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,12 +115,66 @@ namespace EpinelPS.Models
|
||||
public int Defense;
|
||||
public int Hp;
|
||||
}
|
||||
public class SimroomData
|
||||
|
||||
// Simroom Data
|
||||
public class SimRoomData
|
||||
{
|
||||
public int CurrentDifficulty;
|
||||
public int CurrentChapter;
|
||||
public List<int> Buffs = [];
|
||||
public List<int> LegacyBuffs = [];
|
||||
public List<SimRoomEvent> Events = [];
|
||||
public List<SimRoomCharacterHp> RemainingHps = [];
|
||||
public List<SimRoomChapterInfo> ReceivedRewardChapters = [];
|
||||
public bool IsSimpleModeSkipEnabled = false;
|
||||
public bool Entered = false;
|
||||
}
|
||||
public class SimRoomEvent
|
||||
{
|
||||
public SimRoomEventLocationInfo Location = new();
|
||||
public bool Selected;
|
||||
public SimRoomBattleEvent Battle = new();
|
||||
public SimRoomSelectionEvent Selection = new();
|
||||
public int EventCase;
|
||||
}
|
||||
public class SimRoomEventLocationInfo
|
||||
{
|
||||
public int Chapter;
|
||||
public int Stage;
|
||||
public int Order;
|
||||
}
|
||||
public class SimRoomChapterInfo
|
||||
{
|
||||
public int Difficulty;
|
||||
public int Chapter;
|
||||
}
|
||||
public class SimRoomBattleEvent
|
||||
{
|
||||
public int Id;
|
||||
public List<int> BuffOptions = [];
|
||||
public int Progress;
|
||||
public int RemainingTargetHealth;
|
||||
public int BuffPreviewId;
|
||||
}
|
||||
public class SimRoomSelectionEvent
|
||||
{
|
||||
public int Id;
|
||||
public int SelectedNumber;
|
||||
public List<SimRoomSelectionGroupElement> Group = [];
|
||||
}
|
||||
public class SimRoomSelectionGroupElement
|
||||
{
|
||||
public int SelectionNumber;
|
||||
public int Id;
|
||||
public bool IsDone;
|
||||
public int RandomBuff;
|
||||
}
|
||||
public class SimRoomCharacterHp
|
||||
{
|
||||
public long Csn;
|
||||
public int Hp;
|
||||
}
|
||||
|
||||
public class ResetableData
|
||||
{
|
||||
public int WipeoutCount = 0;
|
||||
@@ -128,7 +182,7 @@ namespace EpinelPS.Models
|
||||
public int InterceptionTickets = 3;
|
||||
public List<int> CompletedDailyMissions = [];
|
||||
public int DailyMissionPoints;
|
||||
public SimroomData SimRoomData = new();
|
||||
public SimRoomData SimRoomData = new();
|
||||
|
||||
public Dictionary<int, int> DailyCounselCount = [];
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.Models;
|
||||
|
||||
public class ClearedTutorialData
|
||||
{
|
||||
public int Id;
|
||||
@@ -34,6 +35,7 @@ public class User
|
||||
public DateTime BanEnd;
|
||||
public int BanId = 0;
|
||||
public DateTime LastReset = DateTime.MinValue;
|
||||
public DateTime LastWeeklyReset = DateTime.MinValue;
|
||||
|
||||
// Game data
|
||||
public List<string> CompletedScenarios = [];
|
||||
@@ -403,7 +405,36 @@ public class User
|
||||
return LastReset < todayResetTime;
|
||||
}
|
||||
|
||||
public void ResetDataIfNeeded()
|
||||
private bool ShouldResetWeekly()
|
||||
{
|
||||
var nowLocal = DateTime.UtcNow;
|
||||
|
||||
// Calculate the most recent Tuesday reset time
|
||||
DayOfWeek currentDay = nowLocal.DayOfWeek;
|
||||
int daysSinceTuesday = ((int)currentDay - (int)DayOfWeek.Tuesday + 7) % 7;
|
||||
|
||||
// Get the date of the most recent Tuesday
|
||||
DateTime thisTuesday = nowLocal.Date.AddDays(-daysSinceTuesday);
|
||||
|
||||
// Compute the weekly reset time
|
||||
DateTime weeklyResetTime = new(
|
||||
thisTuesday.Year,
|
||||
thisTuesday.Month,
|
||||
thisTuesday.Day,
|
||||
JsonDb.Instance.ResetHourUtcTime, 0, 0
|
||||
);
|
||||
|
||||
// If nowLocal is before the weekly reset time, subtract a week
|
||||
if (nowLocal < weeklyResetTime)
|
||||
{
|
||||
weeklyResetTime = weeklyResetTime.AddDays(-7);
|
||||
}
|
||||
|
||||
// If user's last reset was before the last scheduled 2 PM, they need reset
|
||||
return LastWeeklyReset < weeklyResetTime;
|
||||
}
|
||||
|
||||
/*public void ResetDataIfNeeded()
|
||||
{
|
||||
if (!ShouldResetUser()) return;
|
||||
|
||||
@@ -412,5 +443,41 @@ public class User
|
||||
LastReset = DateTime.UtcNow;
|
||||
ResetableData = new();
|
||||
JsonDb.Save();
|
||||
}*/
|
||||
|
||||
public void ResetDataIfNeeded()
|
||||
{
|
||||
bool needsSave = false;
|
||||
|
||||
// Check daily reset
|
||||
if (ShouldResetUser())
|
||||
{
|
||||
Logging.WriteLine("Resetting daily user data...", LogType.Warning);
|
||||
|
||||
LastReset = DateTime.UtcNow;
|
||||
ResetableData = new()
|
||||
{
|
||||
SimRoomData = new()
|
||||
{
|
||||
LegacyBuffs = ResetableData.SimRoomData.LegacyBuffs // Retain old LegacyBuffs data
|
||||
}
|
||||
};
|
||||
needsSave = true;
|
||||
}
|
||||
|
||||
// Check weekly reset
|
||||
if (ShouldResetWeekly())
|
||||
{
|
||||
Logging.WriteLine("Resetting weekly user data...", LogType.Warning);
|
||||
|
||||
LastWeeklyReset = DateTime.UtcNow;
|
||||
ResetableData = new();
|
||||
needsSave = true;
|
||||
}
|
||||
|
||||
if (needsSave)
|
||||
{
|
||||
JsonDb.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
90
EpinelPS/Utils/DateTimeHelper.cs
Normal file
90
EpinelPS/Utils/DateTimeHelper.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
namespace EpinelPS.Utils
|
||||
{
|
||||
public static class DateTimeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the specified time of the next day in the specified time zone
|
||||
/// </summary>
|
||||
/// <param name="timeZoneId">Time zone ID, such as "China Standard Time", "Eastern Standard Time", "UTC"</param>
|
||||
/// <param name="hour">Hours (0-23)</param>
|
||||
/// <param name="minute">Minutes (0-59)</param>
|
||||
/// <returns>The specified time of the next day</returns>
|
||||
public static DateTime GetNextDayAtTime(string timeZoneId = "", int hour = 0, int minute = 0)
|
||||
{
|
||||
// Get the current time of the target time zone and the current time zone
|
||||
(DateTime currentTime, TimeZoneInfo tz) = GetCurrentTimeWithZone(timeZoneId);
|
||||
|
||||
DateTime nextDay = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, hour, minute, 0).AddDays(1);
|
||||
|
||||
return TimeZoneInfo.ConvertTimeToUtc(nextDay, tz);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the next specified day of the week + specified time in the specified time zone
|
||||
/// </summary>
|
||||
/// <param name="timeZoneId">Time zone ID, such as "China Standard Time"、"Eastern Standard Time"、"UTC"</param>
|
||||
/// <param name="targetDay">Target day of the week, for example DayOfWeek.Monday</param>
|
||||
/// <param name="hour">Hours (0-23)</param>
|
||||
/// <param name="minute">Minutes (0-59)</param>
|
||||
/// <returns>Next specified day of the week Specified time </returns>
|
||||
public static DateTime GetNextWeekdayAtTime(string timeZoneId, DayOfWeek targetDay, int hour, int minute = 0)
|
||||
{
|
||||
// Get the current time of the target time zone and the current time zone
|
||||
(DateTime currentTime, TimeZoneInfo tz) = GetCurrentTimeWithZone(timeZoneId);
|
||||
|
||||
// Calculate the number of days until the target weekday
|
||||
int daysUntilTarget = ((int)targetDay - (int)currentTime.DayOfWeek + 7) % 7;
|
||||
if (daysUntilTarget == 0) daysUntilTarget = 7; // If today is the target day of the week, take next week
|
||||
|
||||
DateTime nextWeekday = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, hour, minute, 0).AddDays(daysUntilTarget);
|
||||
|
||||
return TimeZoneInfo.ConvertTimeToUtc(nextWeekday, tz);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific day of next month in the specified time zone at a specified time
|
||||
/// </summary>
|
||||
/// <param name="timeZoneId">Time zone ID, such as "China Standard Time"、"Eastern Standard Time"、"UTC"</param>
|
||||
/// <param name="day">Target date (1-31, ensure that this date exists in the month)</param>
|
||||
/// <param name="hour">Hours (0-23)</param>
|
||||
/// <param name="minute">Minutes (0-59)</param>
|
||||
/// <returns>Specified date and time next month</returns>
|
||||
public static DateTime GetNextMonthDayAtTime(string timeZoneId, int day, int hour, int minute = 0)
|
||||
{
|
||||
// Get the current time of the target time zone and the current time zone
|
||||
(DateTime currentTime, TimeZoneInfo tz) = GetCurrentTimeWithZone(timeZoneId);
|
||||
|
||||
// Calculate the year and month of next month
|
||||
int year = currentTime.Month == 12 ? currentTime.Year + 1 : currentTime.Year;
|
||||
int month = currentTime.Month == 12 ? 1 : currentTime.Month + 1;
|
||||
|
||||
// Ensure the date is valid (avoid situations like 30th February)
|
||||
int daysInMonth = DateTime.DaysInMonth(year, month);
|
||||
int targetDay = Math.Min(day, daysInMonth);
|
||||
|
||||
// Construct target time
|
||||
DateTime target = new(year, month, targetDay, hour, minute, 0);
|
||||
|
||||
return TimeZoneInfo.ConvertTimeToUtc(target, tz);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current time and timezone object for a specified timezone
|
||||
/// </summary>
|
||||
/// <param name="timeZoneId">Time zone ID, such as "China Standard Time"、"Eastern Standard Time"、"UTC"</param>
|
||||
/// <returns>(currentTime, tz) tuple</returns>
|
||||
public static (DateTime currentTime, TimeZoneInfo tz) GetCurrentTimeWithZone(string timeZoneId)
|
||||
{
|
||||
|
||||
// Get the target time zone
|
||||
TimeZoneInfo tz = TimeZoneInfo.Local;
|
||||
if (timeZoneId != null && timeZoneId != "")
|
||||
tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
|
||||
DateTime currentTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
|
||||
return (currentTime, tz);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"StaticDataMpk": {
|
||||
"Url": "https://cloud.nikke-kr.com/prdenv/139-cf218bd245/staticdata/data/qa-251030-10b/473550/mpk/StaticData.pack",
|
||||
"Version": "data/qa-251030-10b/473550",
|
||||
"Salt1": "UfBN5TYtYG7pAY6lxoZXyA7tBOf1rdoPKfxbdB/6n0M=",
|
||||
"Salt2": "zgmjiq+i9OdM6TjDHKav1JepFUaWLXRtismYpUk7lt4="
|
||||
"Url": "https://cloud.nikke-kr.com/prdenv/139-c596af726e/staticdata/data/qa-251120-10b-p1/477874/mpk/StaticData.pack",
|
||||
"Version": "data/qa-251120-10b-p1/477874",
|
||||
"Salt1": "t8iCAEGhWUzYFk6umjwwxY6Y4IrqGQcil/rAG6M/ofw=",
|
||||
"Salt2": "eBWJaf2wR/WdkfhYEc9x7gHmZX8inyrDf6sA5l7N4zk="
|
||||
},
|
||||
"ResourceBaseURL": "https://cloud.nikke-kr.com/prdenv/139-bfaa7caf86/{Platform}",
|
||||
"ResourceBaseURL": "https://cloud.nikke-kr.com/prdenv/139-b131234cad/{Platform}",
|
||||
"GameMinVer": "100.0.1",
|
||||
"GameMaxVer": "150.0.2",
|
||||
"TargetVersion": "139.8.13"
|
||||
|
||||
Reference in New Issue
Block a user