fix character counsel, server reset

This commit is contained in:
Mikhail Tyukin
2025-07-19 16:08:50 -04:00
parent a143da53e6
commit 5fd4da9d69
16 changed files with 171 additions and 50 deletions

View File

@@ -450,10 +450,16 @@ namespace EpinelPS.Data
}
}
// sanity checks
if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty");
}
public MainQuestCompletionRecord? GetMainQuestForStageClearCondition(int stage)
{
if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty");
foreach (var item in QuestDataRecords)
{
if (item.Value.condition_id == stage)
@@ -581,7 +587,7 @@ namespace EpinelPS.Data
int chVal = data.chapter_id - 1;
if (chapterNumber == chVal && data.mod == mod && data.stage_type == StageType.Main)
if (chapterNumber == chVal && data.chapter_mod == mod && data.stage_type == StageType.Main)
{
yield return data.id;
}

View File

@@ -107,22 +107,22 @@ namespace EpinelPS.Data
{
public int id;
public int chapter_id;
public ChapterMod mod;
public ChapterMod chapter_mod;
public int parent_id;
public int group_id;
public string name = "";
public string name_localkey = "";
public StageCategory stage_category;
public StageType stage_type;
public bool allow_autobattle;
public bool spot_autocontrol;
public int enter_condition;
public int monster_stage_level;
public int dynobj_stage_level;
public int monster_stage_lv;
public int dynamic_object_stage_lv;
public int standard_battle_power;
public int statinc_groupid;
public bool allow_quickbattle;
public int fieldmonster_id;
public int spotid;
public int reward_id = 0;
public int stage_stat_increase_group_id;
public bool is_use_quick_battle;
public int field_monster_id;
public int spot_id;
public int reward_id;
public ScenarioType enter_scenario_type;
public string enter_scenario = "";
public ScenarioType exit_scenario_type;

View File

@@ -261,6 +261,7 @@ namespace EpinelPS.Database
public DateTime BanStart;
public DateTime BanEnd;
public int BanId = 0;
public DateTime LastReset = DateTime.MinValue;
// Game data
public List<string> CompletedScenarios = [];
@@ -411,7 +412,6 @@ namespace EpinelPS.Database
foreach (var item in FieldInfoNew)
{
if (item.Key.Contains("hard") && isNorm) continue;
if (item.Key.Contains("normal") && !isNorm) continue;
if (item.Value.CompletedStages.Contains(id))
{
return true;
@@ -601,6 +601,38 @@ namespace EpinelPS.Database
MessengerData.Add(msg);
return msg;
}
private bool ShouldResetUser()
{
var nowLocal = DateTime.Now;
// Compute the last reset threshold (most recent 2 PM before or at nowLocal)
DateTime todayResetTime = new(
nowLocal.Year,
nowLocal.Month,
nowLocal.Day,
JsonDb.Instance.ResetHourLocalTime, 0, 0
);
if (nowLocal < todayResetTime)
{
todayResetTime = todayResetTime.AddDays(-1);
}
// If user's last reset was before the last scheduled 2 PM, they need reset
return LastReset < todayResetTime;
}
public void ResetDataIfNeeded()
{
if (!ShouldResetUser()) return;
Logging.WriteLine("Resetting user...", LogType.Warning);
LastReset = DateTime.Now;
ResetableData = new();
JsonDb.Save();
}
}
public class CoreInfo
{
@@ -615,6 +647,7 @@ namespace EpinelPS.Database
public LogType LogLevel = LogType.Debug;
public int MaxInterceptionCount = 3;
public int ResetHourLocalTime = 14;
}
internal class JsonDb
{

View File

@@ -10,7 +10,7 @@
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>True</IncludeNativeLibrariesForSelfExtract>
<NoWarn>$(NoWarn);SYSLIB0057</NoWarn>
<Version>0.134.4.3</Version>
<Version>0.135.4.3</Version>
<CETCompat>false</CETCompat>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>

View File

@@ -45,14 +45,18 @@ namespace EpinelPS.LobbyServer.Auth
.Encode();
ResEnterServer response = new();
response.GameClientToken = token;
response.FeatureDataInfo = new NetFeatureDataInfo() { }; // TODO
response.Identifier = new NetLegacyUserIdentifier() { Server = 1000, Usn = (long)user.ID };
response.ShouldRestartAfter = Duration.FromTimeSpan(TimeSpan.FromSeconds(86400));
ResEnterServer response = new()
{
GameClientToken = token,
FeatureDataInfo = new NetFeatureDataInfo() { }, // TODO
Identifier = new NetLegacyUserIdentifier() { Server = 1000, Usn = (long)user.ID },
ShouldRestartAfter = Duration.FromTimeSpan(TimeSpan.FromSeconds(86400)),
EncryptionToken = ByteString.CopyFromUtf8(encryptionToken)
};
user.ResetDataIfNeeded();
response.EncryptionToken = ByteString.CopyFromUtf8(encryptionToken);
await WriteDataAsync(response);
}
}

View File

@@ -1,21 +0,0 @@
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Character
{
[PacketPath("/character/attractive/counsel")]
public class Counsel : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqCharacterCounsel>();
var user = GetUser();
ResCharacterCounsel response = new();
response.Attractive = new();
response.Exp = new();
// TODO
await WriteDataAsync(response);
}
}
}

View File

@@ -0,0 +1,18 @@
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Character.Counsel;
[PacketPath("/character/attractive/check")]
public class CheckCharacterCounsel : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqCounseledBefore>();
var user = GetUser();
ResCounseledBefore response = new();
// TODO: Validate response from real server and pull info from user info
await WriteDataAsync(response);
}
}

View File

@@ -0,0 +1,49 @@
using EpinelPS.Database;
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Character.Counsel;
[PacketPath("/character/attractive/counsel")]
public class DoCounsel : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqCharacterCounsel>();
var user = GetUser();
ResCharacterCounsel response = new();
foreach (var currency in user.Currency)
{
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
}
var currentBondInfo = user.BondInfo.Where(x => x.NameCode == req.NameCode);
NetUserAttractiveData data;
if (currentBondInfo.Any())
{
data = currentBondInfo.First();
// TODO update
response.Attractive = data;
}
else
{
data = new()
{
NameCode = req.NameCode,
// TODO
};
response.Attractive = data;
user.BondInfo.Add(data);
}
JsonDb.Save();
// TODO: Validate response from real server and pull info from user info
await WriteDataAsync(response);
}
}

View File

@@ -17,6 +17,8 @@ namespace EpinelPS.LobbyServer.Event
Json = "{}"
};
// Retrieve collected objects
if (!user.FieldInfoNew.TryGetValue(req.MapId, out FieldInfoNew? field))

View File

@@ -0,0 +1,24 @@
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Event.Shop
{
[PacketPath("/event/shopproductlist")]
public class ListProductList : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqShopProductList>();
var user = GetUser();
var response = new ResShopProductList();
response.Shops.Add(new NetShopProductData()
{
});
// TODO implement properly
await WriteDataAsync(response);
}
}
}

View File

@@ -22,8 +22,8 @@ namespace EpinelPS.LobbyServer.Mission
{
if (user.ResetableData.CompletedDailyMissions.Contains(item))
{
Console.WriteLine("already completed daily mission");
continue;
Logging.WriteLine("already completed daily mission", LogType.Warning);
continue;
}
if (!GameData.Instance.TriggerTable.TryGetValue(item, out TriggerRecord? key)) throw new Exception("unknown TID");

View File

@@ -10,6 +10,8 @@ namespace EpinelPS.LobbyServer.Mission.Rewards
await ReadData<ReqGetDailyRewardedData>();
var user = GetUser();
user.ResetDataIfNeeded();
var response = new ResGetDailyRewardedData();
response.Ids.Add(user.ResetableData.CompletedDailyMissions);

View File

@@ -10,6 +10,8 @@ namespace EpinelPS.LobbyServer.Mission.Rewards
await ReadData<ReqGetWeeklyRewardedData>();
var user = GetUser();
user.ResetDataIfNeeded();
var response = new ResGetWeeklyRewardedData();
response.Ids.Add(user.WeeklyResetableData.CompletedWeeklyMissions);

View File

@@ -10,6 +10,7 @@ namespace EpinelPS.LobbyServer.Outpost
{
var req = await ReadData<ReqGetOutpostData>();
var user = GetUser();
user.ResetDataIfNeeded();
var battleTime = DateTime.UtcNow - user.BattleTime;
var battleTimeMs = (long)(battleTime.TotalNanoseconds / 100);

View File

@@ -31,7 +31,7 @@ namespace EpinelPS.LobbyServer.Stage
var response = new ResClearStage();
var clearedStage = GameData.Instance.GetStageData(StageId) ?? throw new Exception("cleared stage cannot be null");
var stageMapId = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.mod);
var stageMapId = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_mod);
if (user.FieldInfoNew.Count == 0)
{
@@ -91,7 +91,7 @@ namespace EpinelPS.LobbyServer.Stage
if (clearedStage.stage_category == StageCategory.Normal || clearedStage.stage_category == StageCategory.Boss || clearedStage.stage_category == StageCategory.Hard)
{
if (clearedStage.mod == ChapterMod.Hard)
if (clearedStage.chapter_mod == ChapterMod.Hard)
{
if (StageId > user.LastHardStageCleared)
user.LastHardStageCleared = StageId;
@@ -135,7 +135,7 @@ namespace EpinelPS.LobbyServer.Stage
// Mark chapter as completed if boss stage was completed
if (clearedStage.stage_category == StageCategory.Boss && clearedStage.stage_type == StageType.Main)
{
if (clearedStage.mod == ChapterMod.Hard)
if (clearedStage.chapter_mod == ChapterMod.Hard)
user.AddTrigger(TriggerType.HardChapterClear, 1, clearedStage.chapter_id);
else
user.AddTrigger(TriggerType.ChapterClear, 1, clearedStage.chapter_id);
@@ -152,10 +152,11 @@ namespace EpinelPS.LobbyServer.Stage
private static void DoQuestSpecificUserOperations(User user, int clearedStageId)
{
var quest = GameData.Instance.GetMainQuestForStageClearCondition(clearedStageId);
user.AddTrigger(TriggerType.CampaignClear, 1, clearedStageId);
if (quest != null)
{
user.SetQuest(quest.id, false);
user.AddTrigger(TriggerType.CampaignClear, 1, clearedStageId);
user.AddTrigger(TriggerType.MainQuestClear, 1, quest.id);
}
@@ -181,7 +182,7 @@ namespace EpinelPS.LobbyServer.Stage
user.BondInfo.Add(new() { NameCode = 3001, Lv = 1 });
user.BondInfo.Add(new() { NameCode = 3005, Lv = 1 });
user.AddTrigger(TriggerType.ObtainCharacter, 1, 3001);
user.AddTrigger(TriggerType.ObtainCharacter, 1, 1018);
user.AddTrigger(TriggerType.ObtainCharacter, 1, 1015);

View File

@@ -15,7 +15,7 @@ namespace EpinelPS.LobbyServer.Stage
var response = new ResEnterStage();
var clearedStage = GameData.Instance.GetStageData(req.StageId) ?? throw new Exception("cleared stage cannot be null");
var map = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.mod);
var map = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_mod);
if (clearedStage.stage_category == StageCategory.Boss)
{
@@ -27,7 +27,7 @@ namespace EpinelPS.LobbyServer.Stage
info.BossEntered = true;
}
user.AddTrigger(TriggerType.CampaignStart, 1);
user.AddTrigger(TriggerType.CampaignStart, 1, req.StageId);
JsonDb.Save();