implement lost sector, fix interception

This commit is contained in:
Mikhail Tyukin
2025-06-25 13:43:01 +04:00
parent 2b0784f4db
commit 08b644463b
17 changed files with 453 additions and 84 deletions

View File

@@ -16,7 +16,10 @@ namespace EpinelPS.Controllers.AdminPanel
public static bool CheckAuth(HttpContext context)
{
string? token = context.Request.Cookies["token"];
if (token == null) return false;
if (token == null)
{
token = context.Request.Headers.Authorization.ToString().Replace("Bearer ", "");
}
// TODO better authentication
if (JsonDb.Instance.AdminAuthTokens.ContainsKey(token))

View File

@@ -31,126 +31,125 @@ namespace EpinelPS.Data
private int totalFiles = 1;
private int currentFile = 0;
public readonly Dictionary<string, MapInfo> MapData = [];
[LoadRecord("MainQuestTable.json", "id", typeof(MainQuestCompletionTable))]
public readonly Dictionary<int, MainQuestCompletionRecord> QuestDataRecords = [];
[LoadRecord("CampaignStageTable.json", "id", typeof(CampaignStageTable))]
public readonly Dictionary<int, CampaignStageRecord> StageDataRecords = [];
[LoadRecord("RewardTable.json", "id", typeof(RewardTable))]
public readonly Dictionary<int, RewardTableRecord> RewardDataRecords = [];
[LoadRecord("UserExpTable.json", "level", typeof(UserExpTable))]
public readonly Dictionary<int, UserExpRecord> UserExpDataRecords = [];
[LoadRecord("CampaignChapterTable.json", "chapter", typeof(CampaignChapterTable))]
public readonly Dictionary<int, CampaignChapterRecord> ChapterCampaignData = [];
[LoadRecord("CharacterCostumeTable.json", "id", typeof(CharacterCostumeTable))]
public readonly Dictionary<int, CharacterCostumeRecord> CharacterCostumeTable = [];
[LoadRecord("CharacterTable.json", "id", typeof(CharacterTable))]
public readonly Dictionary<int, CharacterRecord> CharacterTable = [];
[LoadRecord("ContentsTutorialTable.json", "id", typeof(TutorialTable))]
public readonly Dictionary<int, ClearedTutorialData> TutorialTable = [];
[LoadRecord("ItemEquipTable.json", "id", typeof(ItemEquipTable))]
public readonly Dictionary<int, ItemEquipRecord> ItemEquipTable = [];
[LoadRecord("ItemMaterialTable.json", "id", typeof(ItemMaterialTable))]
public readonly Dictionary<int, ItemMaterialRecord> itemMaterialTable = [];
[LoadRecord("ItemEquipExpTable.json", "id", typeof(ItemEquipExpTable))]
public readonly Dictionary<int, ItemEquipExpRecord> itemEquipExpTable = [];
[LoadRecord("ItemEquipGradeExpTable.json", "id", typeof(ItemEquipGradeExpTable))]
public readonly Dictionary<int, ItemEquipGradeExpRecord> ItemEquipGradeExpTable = [];
[LoadRecord("CharacterLevelTable.json", "level", typeof(CharacterLevelTable))]
public readonly Dictionary<int, CharacterLevelData> LevelData = [];
[LoadRecord("TacticAcademyFunctionTable.json", "id", typeof(TacticAcademyLessonTable))]
public readonly Dictionary<int, TacticAcademyLessonRecord> TacticAcademyLessons = [];
[LoadRecord("SideStoryStageTable.json", "id", typeof(SideStoryStageTable))]
public readonly Dictionary<int, SideStoryStageRecord> SidestoryRewardTable = [];
public readonly Dictionary<string, int> PositionReward = [];
[LoadRecord("FieldItemTable.json", "id", typeof(FieldItemTable))]
public readonly Dictionary<int, FieldItemRecord> FieldItems = [];
[LoadRecord("OutpostBattleTable.json", "id", typeof(OutpostBattleTable))]
public readonly Dictionary<int, OutpostBattleTableRecord> OutpostBattle = [];
[LoadRecord("JukeboxListTable.json", "id", typeof(JukeboxListTable))]
public readonly Dictionary<int, JukeboxListRecord> jukeboxListDataRecords = [];
[LoadRecord("JukeboxThemeTable.json", "id", typeof(JukeboxThemeTable))]
public readonly Dictionary<int, JukeboxThemeRecord> jukeboxThemeDataRecords = [];
[LoadRecord("GachaTypeTable.json", "id", typeof(GachaTypeTable))]
public readonly Dictionary<int, GachaType> gachaTypes = [];
[LoadRecord("EventManagerTable.json", "id", typeof(EventManagerTable))]
public readonly Dictionary<int, EventManager> eventManagers = [];
[LoadRecord("LiveWallpaperTable.json", "id", typeof(LiveWallpaperTable))]
public readonly Dictionary<int, LiveWallpaperRecord> lwptablemgrs = [];
[LoadRecord("AlbumResourceTable.json", "id", typeof(AlbumResourceTable))]
public readonly Dictionary<int, AlbumResourceRecord> albumResourceRecords = [];
[LoadRecord("UserFrameTable.json", "id", typeof(UserFrameTable))]
public readonly Dictionary<int, UserFrameTableRecord> userFrameTable = [];
[LoadRecord("ArchiveRecordManagerTable.json", "id", typeof(ArchiveRecordManagerTable))]
public readonly Dictionary<int, ArchiveRecordManagerRecord> archiveRecordManagerTable = [];
[LoadRecord("ArchiveEventStoryTable.json", "id", typeof(ArchiveEventStoryTable))]
public readonly Dictionary<int, ArchiveEventStoryRecord> archiveEventStoryRecords = [];
[LoadRecord("ArchiveEventQuestTable.json", "id", typeof(ArchiveEventQuestTable))]
public readonly Dictionary<int, ArchiveEventQuestRecord> archiveEventQuestRecords = [];
[LoadRecord("ArchiveEventDungeonStageTable.json", "id", typeof(ArchiveEventDungeonStageTable))]
public readonly Dictionary<int, ArchiveEventDungeonStageRecord> archiveEventDungeonStageRecords = [];
[LoadRecord("UserTitleTable.json", "id", typeof(UserTitleTable))]
public readonly Dictionary<int, UserTitleRecord> userTitleRecords = [];
[LoadRecord("ArchiveMessengerConditionTable.json", "id", typeof(ArchiveMessengerConditionTable))]
public readonly Dictionary<int, ArchiveMessengerConditionRecord> archiveMessengerConditionRecords = [];
[LoadRecord("CharacterStatTable.json", "id", typeof(CharacterStatTable))]
public readonly Dictionary<int, CharacterStatRecord> characterStatTable = [];
[LoadRecord("SkillInfoTable.json", "id", typeof(SkillInfoTable))]
public readonly Dictionary<int, SkillInfoRecord> skillInfoTable = [];
[LoadRecord("CostTable.json", "id", typeof(CostTable))]
public readonly Dictionary<int, CostRecord> costTable = [];
[LoadRecord("MidasProductTable.json", "midas_product_id_proximabeta", typeof(MidasProductTable))]
public readonly Dictionary<string, MidasProductRecord> mediasProductTable = [];
[LoadRecord("TowerTable.json", "id", typeof(TowerTable))]
public readonly Dictionary<int, TowerRecord> towerTable = [];
[LoadRecord("TriggerTable.json", "id", typeof(TriggerTable))]
public readonly Dictionary<int, TriggerRecord> TriggerTable = [];
[LoadRecord("InfraCoreGradeTable.json", "id", typeof(InfracoreTable))]
public readonly Dictionary<int, InfracoreRecord> InfracoreTable = [];
[LoadRecord("AttractiveCounselCharacterTable.json", "name_code", typeof(AttractiveCounselCharacterTable))]
public readonly Dictionary<int, AttractiveCounselCharacterRecord> AttractiveCounselCharacterTable = [];
[LoadRecord("AttractiveLevelRewardTable.json", "id", typeof(AttractiveLevelRewardTable))]
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
[LoadRecord("SubQuestTable.json", "id", typeof(SubquestTable))]
public readonly Dictionary<int, SubquestRecord> Subquests = [];
@@ -182,6 +181,10 @@ namespace EpinelPS.Data
public readonly Dictionary<int, ItemConsumeRecord> ConsumableItems = [];
[LoadRecord("ItemRandomTable.json", "id", typeof(RandomItemTable))]
public readonly Dictionary<int, RandomItemRecord> RandomItem = [];
[LoadRecord("LostSectorTable.json", "id", typeof(LostSectorTable))]
public readonly Dictionary<int, LostSectorRecord> LostSector = [];
[LoadRecord("LostSectorStageTable.json", "id", typeof(LostSectorStageTable))]
public readonly Dictionary<int, LostSectorStageRecord> LostSectorStages = [];
static async Task<GameData> BuildAsync()
{
await Load();
@@ -358,21 +361,6 @@ namespace EpinelPS.Data
return deserializedObject;
}
private async Task<JArray> LoadZip(string entry, ProgressBar bar)
{
var mainQuestData = MainZip.GetEntry(entry) ?? throw new Exception(entry + " does not exist in static data");
using StreamReader mainQuestReader = new(MainZip.GetInputStream(mainQuestData));
var mainQuestDataString = await mainQuestReader.ReadToEndAsync();
JObject questdata = JObject.Parse(mainQuestDataString) ?? throw new Exception("failed to parse " + entry);
JArray? records = (JArray?)questdata["records"] ?? throw new Exception(entry + " is missing records element");
currentFile++;
bar.Report((double)currentFile / totalFiles);
return records;
}
public async Task Parse()
{
using var progress = new ProgressBar();
@@ -383,28 +371,20 @@ namespace EpinelPS.Data
foreach (ZipEntry item in MainZip)
{
if (item.Name.StartsWith("CampaignMap/") || item.Name.StartsWith("EventMap/"))
if (item.Name.StartsWith("CampaignMap/") ||
item.Name.StartsWith("EventMap/") ||
item.Name.StartsWith("LostSectorMap/")
)
{
var x = await LoadZip(item.Name, progress);
var x = await LoadZip<MapInfoTable>(item.Name, progress);
var items = x[0]["ItemSpawner"];
if (items != null)
foreach (var map in x.records)
{
foreach (var item2 in items)
{
var posId = item2["positionId"] ?? throw new Exception("positionId cannot be null");
var rewardObj = item2["itemId"] ?? throw new Exception("itemId cannot be null");
var id = posId.ToObject<string>() ?? throw new Exception("positionId cannot be null");
var reward = rewardObj.ToObject<int>();
PositionReward.TryAdd(id, reward);
}
MapData.Add(map.id, map);
}
}
}
}
public MainQuestCompletionRecord? GetMainQuestForStageClearCondition(int stage)

View File

@@ -137,7 +137,7 @@
public int piece_id;
public string original_rare = "";
public string corporation = "";
public string corporation_sub_type = "";
public string corporation_sub_type = "";
public int grade_core_id;
public int name_code;
public int grow_grade;
@@ -599,7 +599,7 @@
}
public class TowerTable
{
public List<TowerRecord> records = [];
public List<TowerRecord> records = [];
}
public class ItemEquipExpRecord
@@ -831,4 +831,56 @@
{
public List<RandomItemRecord> records = [];
}
public enum ContentOpenType
{
Stage,
NonUpdate
}
public class LostSectorRecord
{
public int id;
public int sector;
public int exploration_reward;
public string field_id;
public int sector_clear_condition;
public ContentOpenType open_condition_type;
public int open_condition_value;
}
public class LostSectorTable
{
public List<LostSectorRecord> records = [];
}
public class LostSectorStageRecord
{
public int id;
public bool is_use_quick_battle;
public int sector;
}
public class LostSectorStageTable
{
public List<LostSectorStageRecord> records = [];
}
public class ItemSpawner
{
public string positionId = "";
public int itemId;
public bool isCompleteReward;
}
public class StageSpawner
{
public string positionId = "";
public int stageId;
}
public class MapInfo
{
public string id { get; set; } = "";
public List<ItemSpawner> ItemSpawner { get; set; } = [];
public List<StageSpawner> StageSpawner { get; set; } = [];
}
public class MapInfoTable
{
public List<MapInfo> records = [];
}
}

View File

@@ -1,9 +1,10 @@
using EpinelPS.Data;
using System.Globalization;
using EpinelPS.Data;
using EpinelPS.Utils;
using Newtonsoft.Json;
using Paseto.Builder;
using Paseto;
using Google.Protobuf;
using Newtonsoft.Json;
using Paseto;
using Paseto.Builder;
namespace EpinelPS.Database
{
@@ -219,6 +220,18 @@ namespace EpinelPS.Database
public string Id { get; set; } = "";
public int State { get; set; }
}
public class LostSectorData
{
public bool IsOpen { get; set; }
public bool IsPlaying { get; set; }
public string Json { get; set; } = "";
public Dictionary<string, NetLostSectorFieldObject> Objects { get; set; } = [];
public Dictionary<string, int> ClearedStages { get; set; } = [];
public List<NetLostSectorTeamPosition> TeamPositions { get; set; } = [];
public int ObtainedRewards { get; set; } = 0;
public bool RecievedFinalReward { get; set; }
public bool CompletedPerfectly { get; set; }
}
public class User
{
// User info
@@ -304,6 +317,7 @@ namespace EpinelPS.Database
public List<NetMessage> MessengerData = [];
public ulong LastMessageId = 1;
public long LastBadgeSeq = 1;
public Dictionary<int, LostSectorData> LostSectorData = [];
// Event data
public Dictionary<int, EventData> EventInfo = new();

View File

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

View File

@@ -33,8 +33,11 @@ namespace EpinelPS.LobbyServer.Campaign
// Register and return reward
if (!GameData.Instance.PositionReward.TryGetValue(req.FieldObject.PositionId, out int fieldReward)) throw new Exception("bad position id");
var positionReward = GameData.Instance.FieldItems[fieldReward];
var map = GameData.Instance.MapData[req.MapId];
var position = map.ItemSpawner.Where(x => x.positionId == req.FieldObject.PositionId).FirstOrDefault() ?? throw new Exception("bad position id");
var positionReward = GameData.Instance.FieldItems[position.itemId];
var reward = GameData.Instance.GetRewardTableEntry(positionReward.type_value) ?? throw new Exception("failed to get reward");
response.Reward = RewardUtils.RegisterRewardsForUser(user, reward);

View File

@@ -13,7 +13,7 @@ namespace EpinelPS.LobbyServer.Intercept
var response = new ResInterceptAnomalousData
{
InterceptAnomalousManagerId = 1,
InterceptAnomalousManagerId = 101,
RemainingTickets = 5
};
response.ClearedInterceptAnomalousIds.Add(new[] { 1, 2, 3, 4, 5 });

View File

@@ -0,0 +1,42 @@
using EpinelPS.Data;
using EpinelPS.Database;
using EpinelPS.Utils;
using Org.BouncyCastle.Ocsp;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/clearstage")]
public class ClearStage : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqClearLostSectorStage>();
var user = GetUser();
var response = new ResClearLostSectorStage();
if (req.BattleResult == 1)
{
ClearLostSectorStage(user, req.StageId);
JsonDb.Save();
}
await WriteDataAsync(response);
}
public static void ClearLostSectorStage(User user, int stageId)
{
// get lost sector id from stage id
var sector = GameData.Instance.LostSectorStages[stageId].sector;
// get position ID from stage id in map data
var sectorData = GameData.Instance.LostSector[sector];
MapInfo mapInfo = GameData.Instance.MapData[sectorData.field_id];
var stage = mapInfo.StageSpawner.Where(x => x.stageId == stageId).FirstOrDefault() ?? throw new Exception("cannot find stage in map data");
user.LostSectorData[sector].ClearedStages.Add(stage.positionId, stageId);
}
}
}

View File

@@ -0,0 +1,17 @@
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/enterstage")]
public class EnterStage : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqEnterLostSectorStage>();
var response = new ResEnterLostSectorStage();
await WriteDataAsync(response);
}
}
}

View File

@@ -0,0 +1,43 @@
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/getfield")]
public class GetField : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqGetLostSectorFieldData>();
var user = GetUser();
var f = user.LostSectorData[req.SectorId];
ResGetLostSectorFieldData response = new()
{
Field = new NetFieldObjectData(),
Json = f.Json
};
foreach (var item in f.Objects)
response.Field.Objects.Add(new NetFieldObject()
{
ActionAt = item.Value.ActionAt,
Json = item.Value.Json,
PositionId = item.Key,
Type = item.Value.Type
});
foreach (var item in f.ClearedStages)
response.Field.Stages.Add(new NetFieldStageData()
{
PositionId = item.Key,
StageId = item.Value
});
// 10: lost sector team
if (user.UserTeams.ContainsKey(10))
response.Team = user.UserTeams[10];
await WriteDataAsync(response);
}
}
}

View File

@@ -1,4 +1,5 @@
using EpinelPS.Utils;
using EpinelPS.Data;
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
@@ -12,7 +13,29 @@ namespace EpinelPS.LobbyServer.Lostsector
var response = new ResGetLostSectorData();
// TODO
foreach (var item in user.LostSectorData)
{
var val = item.Value;
response.LostSector.Add(new NetUserLostSectorData()
{
IsOpen = val.IsOpen,
SectorId= item.Key,
IsPlaying=val.IsPlaying,
CurrentClearStageCount = val.ClearedStages.Count,
RewardCount = val.ObtainedRewards,
IsFinalReward=val.RecievedFinalReward,
IsPerfectReward = val.CompletedPerfectly,
MaxClearStageCount = 0, // TODO
});
}
foreach (var item in GameData.Instance.LostSector)
{
if (item.Value.open_condition_type == ContentOpenType.Stage && user.IsStageCompleted(item.Value.open_condition_value, true))
{
response.ClearStages.Add(new NetFieldStageData() { StageId = item.Value.open_condition_value });
}
}
await WriteDataAsync(response);
}

View File

@@ -0,0 +1,45 @@
using EpinelPS.Data;
using EpinelPS.Database;
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/obtainitem")]
public class ObtainItem : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqObtainLostSectorItem>();
var user = GetUser();
ResObtainLostSectorItem response = new();
var lostSectorUser = user.LostSectorData[req.SectorId];
lostSectorUser.ObtainedRewards++;
if (lostSectorUser.Objects.ContainsKey(req.Object.PositionId))
lostSectorUser.Objects[req.Object.PositionId] = req.Object;
else
lostSectorUser.Objects.Add(req.Object.PositionId, req.Object);
// Get map info
MapInfo map = GameData.Instance.MapData[GameData.Instance.LostSector[req.SectorId].field_id];
// find reward
var rewardEntry = map.ItemSpawner.Where(x => x.positionId == req.Object.PositionId).FirstOrDefault() ?? throw new Exception("cannot find reward");
var positionReward = GameData.Instance.FieldItems[rewardEntry.itemId];
response.Reward = RewardUtils.RegisterRewardsForUser(user, positionReward.type_value);
if (positionReward.is_final_reward)
{
lostSectorUser.RecievedFinalReward = true;
}
JsonDb.Save();
await WriteDataAsync(response);
}
}
}

View File

@@ -0,0 +1,32 @@
using EpinelPS.Database;
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/open")]
public class Open : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqOpenLostSector>();
var user = GetUser();
var response = new ResOpenLostSector();
if (!user.LostSectorData.ContainsKey(req.SectorId))
user.LostSectorData.Add(req.SectorId, new Database.LostSectorData()
{
IsOpen = true
});
response.Lostsector = new NetUserLostSectorData()
{
IsOpen = true,
SectorId = req.SectorId,
};
JsonDb.Save();
await WriteDataAsync(response);
}
}
}

View File

@@ -0,0 +1,36 @@
using EpinelPS.Database;
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/play")]
public class Play : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqPlayLostSector>();
var user = GetUser();
var response = new ResPlayLostSector();
if (!user.LostSectorData.ContainsKey(req.SectorId))
user.LostSectorData.Add(req.SectorId, new Database.LostSectorData()
{
IsPlaying = true
});
var lostSectorData = user.LostSectorData[req.SectorId];
lostSectorData.IsPlaying = true;
response.Lostsector = new NetUserLostSectorData()
{
IsOpen = lostSectorData.IsOpen,
SectorId = req.SectorId,
IsPlaying = lostSectorData.IsPlaying
};
JsonDb.Save();
await WriteDataAsync(response);
}
}
}

View File

@@ -0,0 +1,23 @@
using EpinelPS.Database;
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/fastclearstage")]
public class QuickClearStage : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqFastClearLostSectorStage>();
var user = GetUser();
var response = new ResFastClearLostSectorStage();
ClearStage.ClearLostSectorStage(user, req.StageId);
JsonDb.Save();
await WriteDataAsync(response);
}
}
}

View File

@@ -0,0 +1,27 @@
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/savefield")]
public class SaveField : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqSaveLostSectorField>();
var user = GetUser();
var response = new ResSaveLostSectorField();
if (!user.LostSectorData.ContainsKey(req.SectorId))
user.LostSectorData.Add(req.SectorId, new Database.LostSectorData()
{
Json = req.Json
});
else
user.LostSectorData[req.SectorId].Json = req.Json;
await WriteDataAsync(response);
}
}
}

View File

@@ -0,0 +1,29 @@
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Lostsector
{
[PacketPath("/lostsector/savefieldobject")]
public class SaveFieldObject : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqSaveLostSectorFieldObject>();
var user = GetUser();
var response = new ResSaveLostSectorFieldObject();
if (user.LostSectorData[req.SectorId].Objects.ContainsKey(req.Object.PositionId))
user.LostSectorData[req.SectorId].Objects[req.Object.PositionId] = req.Object;
else
user.LostSectorData[req.SectorId].Objects.Add(req.Object.PositionId, req.Object);
if(req.Object.TeamPosition.Count != 0)
{
user.LostSectorData[req.SectorId].TeamPositions.Clear();
user.LostSectorData[req.SectorId].TeamPositions.AddRange(req.Object.TeamPosition);
}
await WriteDataAsync(response);
}
}
}