mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-12 15:04:36 +01:00
Compare commits
254 Commits
v0.1.4.1
...
8b8d58854f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b8d58854f | ||
|
|
5f27e2ea44 | ||
|
|
55e5cdcdb8 | ||
|
|
a45dd2305e | ||
|
|
c96ad5fa94 | ||
|
|
91cbedf46c | ||
|
|
913c1a2262 | ||
|
|
fb062eefae | ||
|
|
bc7416c44a | ||
|
|
4325a91cea | ||
|
|
24f6de8704 | ||
|
|
6d44c6eac9 | ||
|
|
23afbabfae | ||
|
|
f4e2d82978 | ||
|
|
2b92e3191b | ||
|
|
663bf58549 | ||
|
|
aac1c00715 | ||
|
|
338a769ade | ||
|
|
d877488d1f | ||
|
|
d378b68611 | ||
|
|
3ffbfca0a3 | ||
|
|
2b19500b67 | ||
|
|
5dc9945100 | ||
|
|
93470b21a4 | ||
|
|
16bd4077dd | ||
|
|
f09d959220 | ||
|
|
795f18445c | ||
|
|
b8dc78e1c9 | ||
|
|
dd2760b0c6 | ||
|
|
0aed2aff6c | ||
|
|
2ee95caf32 | ||
|
|
9a8db1c143 | ||
|
|
eea01945f9 | ||
|
|
206fa429ee | ||
|
|
cbbefeb51a | ||
|
|
076f987681 | ||
|
|
2c1bdee666 | ||
|
|
1f1ffdc034 | ||
|
|
775092b652 | ||
|
|
f470e054cf | ||
|
|
29427ab515 | ||
|
|
50d4d23e00 | ||
|
|
17e843ecdb | ||
|
|
04e661b2b7 | ||
|
|
71be17a043 | ||
|
|
de1b4081b2 | ||
|
|
996b585500 | ||
|
|
793e55f9de | ||
|
|
2e4310edf1 | ||
|
|
ee15420257 | ||
|
|
bb46cf584f | ||
|
|
216bc75f83 | ||
|
|
bb603c44f3 | ||
|
|
c464107149 | ||
|
|
74a2829773 | ||
|
|
689c568180 | ||
|
|
5725de9fd5 | ||
|
|
622c6d49ba | ||
|
|
6310916920 | ||
|
|
f217be263a | ||
|
|
ba3215745c | ||
|
|
84766e1c34 | ||
|
|
0af41389ab | ||
|
|
5fd4da9d69 | ||
|
|
a143da53e6 | ||
|
|
b4920e435b | ||
|
|
6bdcd08240 | ||
|
|
04a9fe29c4 | ||
|
|
2c5d8acc93 | ||
|
|
77b111519c | ||
|
|
5b0c2c7d92 | ||
|
|
88d3061132 | ||
|
|
bb3e245205 | ||
|
|
10f2a9ebf4 | ||
|
|
86f9328eaa | ||
|
|
b6ac385149 | ||
|
|
39ad16a2dd | ||
|
|
2b4f460087 | ||
|
|
6bf008146f | ||
|
|
027f76d745 | ||
|
|
63495aa5e7 | ||
|
|
08b644463b | ||
|
|
2b0784f4db | ||
|
|
4e568390ed | ||
|
|
bd4e0d3e3c | ||
|
|
7dab32c5be | ||
|
|
cb1fb566af | ||
|
|
bb7411643c | ||
|
|
13086f6d19 | ||
|
|
1ba1a89110 | ||
|
|
3bd955eebd | ||
|
|
7955c2f4db | ||
|
|
0133f7f55b | ||
|
|
ffbb5c792c | ||
|
|
4592f57742 | ||
|
|
f904dc50cb | ||
|
|
059821deb7 | ||
|
|
03aca327b0 | ||
|
|
c7ccb92659 | ||
|
|
b7425b6fde | ||
|
|
a0b7f33c80 | ||
|
|
24a0bc1d81 | ||
|
|
ca787a384c | ||
|
|
e4969c723d | ||
|
|
b30869953f | ||
|
|
58750facc2 | ||
|
|
0ba37e5d97 | ||
|
|
9304f86603 | ||
|
|
0b936fa6e5 | ||
|
|
4265c43b4f | ||
|
|
734349a297 | ||
|
|
2c37167fad | ||
|
|
98311f73d2 | ||
|
|
6298471c09 | ||
|
|
9539c0c440 | ||
|
|
e4e8f801ce | ||
|
|
24b4d11862 | ||
|
|
cb27bd926a | ||
|
|
2c8401fea0 | ||
|
|
ba2d725dff | ||
|
|
187326cc21 | ||
|
|
4fbfa3e8ec | ||
|
|
652b7e0598 | ||
|
|
56b7ffc4bd | ||
|
|
2709e67b6c | ||
|
|
c9c334c2ff | ||
|
|
e37f08c232 | ||
|
|
fb274baebf | ||
|
|
3769b8ef58 | ||
|
|
cd15896be1 | ||
|
|
5d16214a78 | ||
|
|
0e4ca4320b | ||
|
|
3d3c32007c | ||
|
|
d67a7d64cd | ||
|
|
2922e59e31 | ||
|
|
09ad13fabd | ||
|
|
07fab687e7 | ||
|
|
fe40ffb717 | ||
|
|
0e823a3bd4 | ||
|
|
343b93db9e | ||
|
|
87b56abca6 | ||
|
|
7237e3d4a1 | ||
|
|
8f65cacdda | ||
|
|
659d99aa0b | ||
|
|
0fbfb36dcc | ||
|
|
95d5bae671 | ||
|
|
1cb1e34674 | ||
|
|
675eda043f | ||
|
|
cbd58df62f | ||
|
|
3e21137a4c | ||
|
|
7a4542b97b | ||
|
|
7be612ab9d | ||
|
|
2f37fa3a3f | ||
|
|
df9b52565e | ||
|
|
ec4ddc46b8 | ||
|
|
9d8c47972a | ||
|
|
c8dc1ef511 | ||
|
|
5ad06556d8 | ||
|
|
506a17cf54 | ||
|
|
1fb3b922ae | ||
|
|
3b5171a37c | ||
|
|
b725d461f3 | ||
|
|
97fe2f7b3c | ||
|
|
8bb270d786 | ||
|
|
e9bcd7adfb | ||
|
|
a85b3fcc83 | ||
|
|
5ff6264e21 | ||
|
|
684be1b26a | ||
|
|
f0b13ed134 | ||
|
|
8f28585083 | ||
|
|
d6f9381607 | ||
|
|
86a8d6d78f | ||
|
|
4b157363f8 | ||
|
|
2f7c12713c | ||
|
|
e2ca6f7ec3 | ||
|
|
e2f0807ab1 | ||
|
|
3fa3915c2b | ||
|
|
c8a47a39d0 | ||
|
|
faead3eba1 | ||
|
|
0e13c96c4d | ||
|
|
b7a655c169 | ||
|
|
3b05037be5 | ||
|
|
8622eb5879 | ||
|
|
d4deb4aa49 | ||
|
|
5391a2da3a | ||
|
|
1e7de0ef8b | ||
|
|
8030745337 | ||
|
|
5028d24b5c | ||
|
|
b022eb688c | ||
|
|
6c58b3e137 | ||
|
|
9877d29e79 | ||
|
|
5e54cfb3f3 | ||
|
|
94e6c6faa0 | ||
|
|
7110cb0e50 | ||
|
|
4d2fcfeaa6 | ||
|
|
1f289c8d70 | ||
|
|
de1f688831 | ||
|
|
93dc699b1e | ||
|
|
3be38c0d2a | ||
|
|
2e790e0861 | ||
|
|
2a089a035b | ||
|
|
c8649335f6 | ||
|
|
34ce89446e | ||
|
|
f1e6899fc9 | ||
|
|
3a6cb469a6 | ||
|
|
71ef1cf174 | ||
|
|
eeab17d497 | ||
|
|
8078da421c | ||
|
|
c2a50d5682 | ||
|
|
d3a0e9dcd3 | ||
|
|
1d11cee520 | ||
|
|
419a7d0a6a | ||
|
|
d64de8e995 | ||
|
|
54e4304868 | ||
|
|
a59c9a8baa | ||
|
|
38299bbfa0 | ||
|
|
bb2132de81 | ||
|
|
7170abc770 | ||
|
|
f7574aeb3a | ||
|
|
d4b276fc6a | ||
|
|
f368e36c69 | ||
|
|
6ca8f00368 | ||
|
|
c56c65dbd3 | ||
|
|
d8fdef4e5b | ||
|
|
3c2080f393 | ||
|
|
899a56eac5 | ||
|
|
1e5c124033 | ||
|
|
dd81797327 | ||
|
|
2c8050a19b | ||
|
|
6587bb8b71 | ||
|
|
d76cd5c095 | ||
|
|
d7d1d9b240 | ||
|
|
319b169bfa | ||
|
|
74ddb93c0f | ||
|
|
c1d0292fe5 | ||
|
|
9f9ec8f5f8 | ||
|
|
70867cc95f | ||
|
|
7ac711b027 | ||
|
|
3f32e569d9 | ||
|
|
b7a9d0de32 | ||
|
|
9e49fd31ae | ||
|
|
92b702116c | ||
|
|
43abfe1aa8 | ||
|
|
bb0e0315ab | ||
|
|
f6ce7d06be | ||
|
|
32d329eafa | ||
|
|
e14ede5295 | ||
|
|
745e6ca164 | ||
|
|
10e0aa6386 | ||
|
|
84c0d18255 | ||
|
|
61075aae37 | ||
|
|
ed3c6bb6a0 | ||
|
|
a68a201c13 | ||
|
|
f199ca63e0 |
31
.editorconfig
Normal file
31
.editorconfig
Normal file
@@ -0,0 +1,31 @@
|
||||
[*.cs]
|
||||
|
||||
# CA5397: Do not use deprecated SslProtocols values
|
||||
dotnet_diagnostic.CA5397.severity = none
|
||||
|
||||
# CS8602: 解引用可能出现空引用。
|
||||
dotnet_diagnostic.CS8602.severity = none
|
||||
|
||||
# CS8619: 值中的引用类型的为 Null 性与目标类型不匹配。
|
||||
dotnet_diagnostic.CS8619.severity = none
|
||||
|
||||
# CS8604: 引用类型参数可能为 null。
|
||||
dotnet_diagnostic.CS8604.severity = none
|
||||
|
||||
# CS8981: 该类型名称仅包含小写 ascii 字符。此类名称可能会成为该语言的保留值。
|
||||
dotnet_diagnostic.CS8981.severity = none
|
||||
|
||||
# IDE0120: 简化 LINQ 表达式
|
||||
dotnet_diagnostic.IDE0120.severity = none
|
||||
|
||||
# CS8603: 可能返回 null 引用。
|
||||
dotnet_diagnostic.CS8603.severity = none
|
||||
|
||||
# SYSLIB0039: 类型或成员已过时
|
||||
dotnet_diagnostic.SYSLIB0039.severity = none
|
||||
|
||||
# CS0219: 变量已被赋值,但从未使用过它的值
|
||||
dotnet_diagnostic.CS0219.severity = none
|
||||
|
||||
# CS8618: 类型不包含 null 值的属性
|
||||
dotnet_diagnostic.CS8618.severity = none
|
||||
44
.github/workflows/dotnet-desktop.yml
vendored
44
.github/workflows/dotnet-desktop.yml
vendored
@@ -18,15 +18,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install the .NET Core workload
|
||||
- name: Install .NET 8
|
||||
uses: actions/setup-dotnet@v4
|
||||
- name: Install .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
|
||||
- name: Setup MSBuild
|
||||
@@ -42,10 +42,44 @@ jobs:
|
||||
run: dotnet publish EpinelPS
|
||||
|
||||
- name: Copy to output
|
||||
run: echo ${{ github.workspace }} && md ${{ github.workspace }}/out/ && xcopy /s /e "${{ github.workspace }}\ServerSelector.Desktop\bin\Release\net8.0\win-x64\publish\" "${{ github.workspace }}\out\" && xcopy /s /e "${{ github.workspace }}\EpinelPS\bin\Release\net8.0\win-x64\publish\" "${{ github.workspace }}\out\" && copy "${{ github.workspace }}\ServerSelector.Desktop\sodium.dll" "${{ github.workspace }}\out\sodium.dll"
|
||||
run: echo ${{ github.workspace }} && md ${{ github.workspace }}/out/ && xcopy /s /e "${{ github.workspace }}\ServerSelector.Desktop\bin\Release\net10.0\win-x64\publish\" "${{ github.workspace }}\out\" && xcopy /s /e "${{ github.workspace }}\EpinelPS\bin\Release\net10.0\win-x64\publish\" "${{ github.workspace }}\out\" && copy "${{ github.workspace }}\ServerSelector.Desktop\sodium.dll" "${{ github.workspace }}\out\sodium.dll"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Server and Server selector
|
||||
path: ${{ github.workspace }}/out/
|
||||
serverOnly:
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Release]
|
||||
|
||||
runs-on: ubuntu-latest # For a list of available runner types, refer to
|
||||
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install the .NET Core workload
|
||||
- name: Install .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Restore packages
|
||||
run: dotnet restore
|
||||
|
||||
- name: Publish Server
|
||||
run: dotnet publish EpinelPS
|
||||
|
||||
- name: Copy to output
|
||||
run: echo ${{ github.workspace }} && cp -R "${{ github.workspace }}/EpinelPS/bin/Release/net10.0/linux-x64/publish/" "${{ github.workspace }}/out/"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: EpinelPS_linux_x64
|
||||
path: ${{ github.workspace }}/out/
|
||||
29
.vscode/launch.json
vendored
Normal file
29
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/EpinelPS/bin/Debug/net9.0/linux-x64/EpinelPS.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/EpinelPS",
|
||||
"stopAtEntry": false,
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
||||
41
.vscode/tasks.json
vendored
Normal file
41
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/EpinelPS/EpinelPS.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/EpinelPS/EpinelPS.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/EpinelPS/EpinelPS.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<AvaloniaVersion>11.0.2</AvaloniaVersion>
|
||||
<AvaloniaVersion>11.3.9</AvaloniaVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
24
EpinelPS.Analyzers/EpinelPS.Analyzers.csproj
Normal file
24
EpinelPS.Analyzers/EpinelPS.Analyzers.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
108
EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs
Normal file
108
EpinelPS.Analyzers/LoadRecordInitializerGenerator.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
|
||||
namespace EpinelPS.Analyzers;
|
||||
|
||||
[Generator]
|
||||
public class LoadRecordInitializerGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// Step 1: Filter for field declarations with attributes
|
||||
IncrementalValueProvider<ImmutableArray<LoadFieldInfo?>> fieldDeclarations = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: static (node, _) => node is FieldDeclarationSyntax fds && fds.AttributeLists.Count > 0,
|
||||
transform: static (ctx, _) => GetTargetFieldInfo(ctx)
|
||||
)
|
||||
.Where(static m => m is not null)
|
||||
.Collect();
|
||||
|
||||
|
||||
// Step 2: Generate the code
|
||||
context.RegisterSourceOutput(fieldDeclarations, (spc, fieldInfos) =>
|
||||
{
|
||||
string source = GenerateInitializerCode(fieldInfos!);
|
||||
spc.AddSource("GameDataInitializer.g.cs", SourceText.From(source, Encoding.UTF8));
|
||||
});
|
||||
}
|
||||
|
||||
private static LoadFieldInfo? GetTargetFieldInfo(GeneratorSyntaxContext context)
|
||||
{
|
||||
if (context.Node is not FieldDeclarationSyntax fieldDecl)
|
||||
return null;
|
||||
|
||||
VariableDeclaratorSyntax? variable = fieldDecl.Declaration.Variables.FirstOrDefault();
|
||||
if (variable == null)
|
||||
return null;
|
||||
|
||||
if (context.SemanticModel.GetDeclaredSymbol(variable) is not IFieldSymbol symbol)
|
||||
return null;
|
||||
|
||||
if (symbol.Type is not INamedTypeSymbol namedSymbol)
|
||||
return null;
|
||||
|
||||
foreach (AttributeData attr in symbol.GetAttributes())
|
||||
{
|
||||
|
||||
if (attr.ConstructorArguments.Length == 2)
|
||||
{
|
||||
if (attr.ConstructorArguments[0].Value is not string fileName || attr.ConstructorArguments[1].Value is not string key)
|
||||
return null;
|
||||
|
||||
return new LoadFieldInfo
|
||||
{
|
||||
ContainingClass = symbol.ContainingType.ToDisplayString(),
|
||||
FieldName = symbol.Name,
|
||||
FileName = fileName,
|
||||
Key = key,
|
||||
RecordTypeName = namedSymbol.TypeArguments[1].Name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GenerateInitializerCode(ImmutableArray<LoadFieldInfo> fieldInfos)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine("using System.Collections.Generic;");
|
||||
sb.AppendLine("using System.Threading.Tasks;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("namespace EpinelPS.Data;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("public static class GameDataInitializer");
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine($"\tpublic static int TotalFiles = {fieldInfos.Length};");
|
||||
sb.AppendLine("\tpublic static async Task InitializeGameData(IProgress<double> progress = null)");
|
||||
sb.AppendLine("\t{");
|
||||
|
||||
foreach (LoadFieldInfo info in fieldInfos)
|
||||
{
|
||||
string tempVar = $"data_{info.FieldName}";
|
||||
sb.AppendLine($"\t\tvar {tempVar} = await {info.ContainingClass}.Instance.LoadZip<{info.RecordTypeName}>(\"{info.FileName}\", progress);");
|
||||
sb.AppendLine($"\t\tforeach (var obj in {tempVar})");
|
||||
sb.AppendLine("\t\t{");
|
||||
sb.AppendLine($"\t\t\t{info.ContainingClass}.Instance.{info.FieldName}.Add(obj.{info.Key}, obj);");
|
||||
sb.AppendLine("\t\t}");
|
||||
}
|
||||
|
||||
sb.AppendLine("\t}");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private class LoadFieldInfo
|
||||
{
|
||||
public string ContainingClass = "";
|
||||
public string FieldName = "";
|
||||
public string FileName = "";
|
||||
public string Key = "";
|
||||
public string RecordTypeName = "";
|
||||
}
|
||||
}
|
||||
31
EpinelPS.sln
31
EpinelPS.sln
@@ -11,6 +11,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerSelector.Desktop", "S
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{4BB2E77F-84A6-4644-9FB3-38E2A7DCDEC0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpinelPS.Analyzers", "EpinelPS.Analyzers\EpinelPS.Analyzers.csproj", "{E3B18A3D-B20B-447D-BCBE-12931E18B41E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -99,6 +106,30 @@ Global
|
||||
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x64.Build.0 = Release|Any CPU
|
||||
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x86.ActiveCfg = Release|Any CPU
|
||||
{01D0A73A-A881-439D-9318-54DB5B00D6F5}.ReleaseDLL|x86.Build.0 = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x64.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x64.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x86.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.DebugDLL|x86.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x64.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x64.Build.0 = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x86.ActiveCfg = Debug|Any CPU
|
||||
{E3B18A3D-B20B-447D-BCBE-12931E18B41E}.ReleaseDLL|x86.Build.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -14,11 +14,11 @@ namespace EpinelPS.Controllers
|
||||
[Route("login")]
|
||||
public string Login(string seq, [FromBody] LoginEndpoint2Req req)
|
||||
{
|
||||
foreach (var item in JsonDb.Instance.Users)
|
||||
foreach (User item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.Username == req.account && item.Password == req.password)
|
||||
{
|
||||
var tok = CreateLauncherTokenForUser(item);
|
||||
AccessToken tok = CreateLauncherTokenForUser(item);
|
||||
item.LastLogin = DateTime.UtcNow;
|
||||
JsonDb.Save();
|
||||
|
||||
@@ -55,6 +55,12 @@ namespace EpinelPS.Controllers
|
||||
User user = res.Item1;
|
||||
AccessToken? tok = res.Item2;
|
||||
|
||||
if (tok == null)
|
||||
{
|
||||
// TODO: better error handling
|
||||
return "{}";
|
||||
}
|
||||
|
||||
// Pretend that code is valid
|
||||
return "{\"account_type\":1,\"birthday\":\"1970-01\",\"email\":\"" + user.Username + "\",\"expire\":" + tok.ExpirationTime + ",\"is_receive_email\":1,\"is_receive_email_in_night\":0,\"is_receive_video\":-1,\"lang_type\":\"en\",\"msg\":\"Success\",\"nick_name\":\"\",\"phone\":\"\",\"phone_area_code\":\"\",\"privacy_policy\":\"1\",\"privacy_update_time\":1717783097,\"region\":\"724\",\"ret\":0,\"seq\":\"" + seq + "\",\"terms_of_service\":\"\",\"terms_update_time\":0,\"uid\":\"" + user.ID + "\",\"user_agreed_dt\":\"\",\"user_agreed_pp\":\"1\",\"user_agreed_tos\":\"\",\"user_name\":\"" + user.PlayerName + "\",\"username_pass_verify\":0}";
|
||||
}
|
||||
@@ -64,7 +70,7 @@ namespace EpinelPS.Controllers
|
||||
public string RegisterAccount(string seq, [FromBody] RegisterEPReq req)
|
||||
{
|
||||
// check if the account already exists
|
||||
foreach (var item in JsonDb.Instance.Users)
|
||||
foreach (User item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.Username == req.account)
|
||||
{
|
||||
@@ -72,10 +78,10 @@ namespace EpinelPS.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
var uid = (ulong)new Random().Next(1, int.MaxValue);
|
||||
ulong uid = (ulong)new Random().Next(1, int.MaxValue);
|
||||
|
||||
// Check if we havent generated a UID that exists
|
||||
foreach (var item in JsonDb.Instance.Users)
|
||||
foreach (User item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.ID == uid)
|
||||
{
|
||||
@@ -83,20 +89,31 @@ namespace EpinelPS.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
var user = new User() { ID = uid, Password = req.password, RegisterTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), Username = req.account, PlayerName = "Player_" + Rng.RandomString(8) };
|
||||
User user = new()
|
||||
{
|
||||
ID = uid,
|
||||
Password = req.password,
|
||||
RegisterTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
Username = req.account,
|
||||
PlayerName = "Player_" + Rng.RandomString(8),
|
||||
IsAdmin = JsonDb.Instance.Users.Count == 0
|
||||
};
|
||||
|
||||
JsonDb.Instance.Users.Add(user);
|
||||
|
||||
var tok = CreateLauncherTokenForUser(user);
|
||||
AccessToken tok = CreateLauncherTokenForUser(user);
|
||||
|
||||
return "{\"expire\":" + tok.ExpirationTime + ",\"is_login\":false,\"msg\":\"Success\",\"register_time\":" + user.RegisterTime + ",\"ret\":0,\"seq\":\"" + seq + "\",\"token\":\"" + tok.Token + "\",\"uid\":\"" + user.ID + "\"}";
|
||||
}
|
||||
public static AccessToken CreateLauncherTokenForUser(User user)
|
||||
{
|
||||
// TODO: implement access token expiration
|
||||
AccessToken token = new() { ExpirationTime = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeSeconds() };
|
||||
token.Token = Rng.RandomString(64);
|
||||
token.UserID = user.ID;
|
||||
AccessToken token = new()
|
||||
{
|
||||
ExpirationTime = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeSeconds(),
|
||||
Token = Rng.RandomString(64),
|
||||
UserID = user.ID
|
||||
};
|
||||
JsonDb.Instance.LauncherAccessTokens.Add(token);
|
||||
JsonDb.Save();
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.LobbyServer;
|
||||
using EpinelPS.Controllers.AdminPanel;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.LobbyServer.Stage;
|
||||
using EpinelPS.Models.Admin;
|
||||
using EpinelPS.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
@@ -12,8 +14,7 @@ namespace EpinelPS.Controllers
|
||||
[ApiController]
|
||||
public class AdminApiController : ControllerBase
|
||||
{
|
||||
public static Dictionary<string, User> AdminAuthTokens = new();
|
||||
private static MD5 md5 = MD5.Create();
|
||||
private static readonly MD5 md5 = MD5.Create();
|
||||
|
||||
[HttpPost]
|
||||
[Route("login")]
|
||||
@@ -23,12 +24,12 @@ namespace EpinelPS.Controllers
|
||||
bool nullusernames = false;
|
||||
if (b.Username != null && b.Password != null)
|
||||
{
|
||||
var passwordHash = Convert.ToHexString(md5.ComputeHash(Encoding.ASCII.GetBytes(b.Password))).ToLower();
|
||||
foreach (var item in JsonDb.Instance.Users)
|
||||
string passwordHash = Convert.ToHexString(md5.ComputeHash(Encoding.ASCII.GetBytes(b.Password))).ToLower();
|
||||
foreach (User item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.Username == b.Username)
|
||||
if (item.Username == b.Username && item.Password != null)
|
||||
{
|
||||
if (item.Password.ToLower() == passwordHash)
|
||||
if (item.Password.Equals(passwordHash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
user = item;
|
||||
}
|
||||
@@ -37,25 +38,20 @@ namespace EpinelPS.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
nullusernames = true;
|
||||
nullusernames = true;
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
if (nullusernames)
|
||||
{
|
||||
return new LoginApiResponse() { Message = "Please enter a username and password" };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new LoginApiResponse() { Message = "Username or password is incorrect" };
|
||||
}
|
||||
return nullusernames
|
||||
? new LoginApiResponse() { Message = "Please enter a username and password" }
|
||||
: new LoginApiResponse() { Message = "Username or password is incorrect" };
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user.IsAdmin)
|
||||
{
|
||||
var tok = CreateAuthToken(user);
|
||||
string tok = CreateAuthToken(user);
|
||||
HttpContext.Response.Cookies.Append("token", tok);
|
||||
return new LoginApiResponse() { OK = true, Token = tok };
|
||||
}
|
||||
@@ -64,35 +60,92 @@ namespace EpinelPS.Controllers
|
||||
return new LoginApiResponse() { Message = "User is not an administrator." };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("RunCmd")]
|
||||
public async Task<RunCmdResponse> RunCmd([FromBody] RunCmdRequest req)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return new RunCmdResponse() { error = "bad token" };
|
||||
|
||||
switch (req.cmdName)
|
||||
{
|
||||
case "reloadDb":
|
||||
JsonDb.Reload();
|
||||
return RunCmdResponse.OK;
|
||||
case "completestage":
|
||||
return AdminCommands.CompleteStage(ulong.Parse(req.p1), req.p2);
|
||||
case "addallcharacters":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.AddAllCharacters(user);
|
||||
}
|
||||
case "addallmaterials":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.AddAllMaterials(user, int.Parse(req.p2));
|
||||
}
|
||||
case "SetLevel":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.SetCharacterLevel(user, int.Parse(req.p2));
|
||||
}
|
||||
case "SetSkillLevel":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.SetSkillLevel(user, int.Parse(req.p2));
|
||||
}
|
||||
case "SetCoreLevel":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.SetCoreLevel(user, int.Parse(req.p2));
|
||||
}
|
||||
case "finishalltutorials":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.FinishAllTutorials(user);
|
||||
}
|
||||
case "AddCharacter":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
return AdminCommands.AddCharacter(user, int.Parse(req.p2));
|
||||
}
|
||||
case "AddItem":
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == ulong.Parse(req.p1));
|
||||
if (user == null) return new RunCmdResponse() { error = "invalid user ID" };
|
||||
|
||||
string[] s = req.p2.Split("-");
|
||||
return AdminCommands.AddItem(user, int.Parse(s[0]), int.Parse(s[1]));
|
||||
}
|
||||
case "updateServer":
|
||||
{
|
||||
return await AdminCommands.UpdateResources();
|
||||
}
|
||||
}
|
||||
return new RunCmdResponse() { error = "Not implemented" };
|
||||
}
|
||||
|
||||
private static string CreateAuthToken(User user)
|
||||
{
|
||||
var tok = RandomString(128);
|
||||
AdminAuthTokens.Add(tok, user);
|
||||
string tok = RandomString(128);
|
||||
// 只保留一个token
|
||||
JsonDb.Instance.AdminAuthTokens.Clear();
|
||||
JsonDb.Instance.AdminAuthTokens.Add(tok, user.ID);
|
||||
JsonDb.Save();
|
||||
return tok;
|
||||
}
|
||||
|
||||
public static string RandomString(int length)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
return new string(Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[new Random().Next(s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
public class LoginApiBody
|
||||
{
|
||||
[Required]
|
||||
public string Username { get; set; } = "";
|
||||
[Required]
|
||||
public string Password { get; set; } = "";
|
||||
}
|
||||
public class LoginApiResponse
|
||||
{
|
||||
public string Message { get; set; } = "";
|
||||
public bool OK { get; set; }
|
||||
public string Token { get; set; } = "";
|
||||
return new string([.. Enumerable.Repeat(chars, length).Select(static s => s[new Random().Next(s.Length)])]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using EpinelPS.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace EpinelPS.Controllers
|
||||
{
|
||||
[Route("admin")]
|
||||
public class AdminController : Controller
|
||||
{
|
||||
private readonly ILogger<AdminController> _logger;
|
||||
|
||||
public AdminController(ILogger<AdminController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
[Route("index")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[Route("dashboard")]
|
||||
public IActionResult Dashboard()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[Route("Events")]
|
||||
public IActionResult Events()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[Route("Configuration")]
|
||||
public IActionResult Configuration()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[Route("Users")]
|
||||
public IActionResult Users()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[Route("Mail")]
|
||||
public IActionResult Mail()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[Route("Database")]
|
||||
public IActionResult Database()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
94
EpinelPS/Controllers/AdminPanel/AdminController.cs
Normal file
94
EpinelPS/Controllers/AdminPanel/AdminController.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Models;
|
||||
using EpinelPS.Models.Admin;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Security;
|
||||
using System.Linq;
|
||||
|
||||
namespace EpinelPS.Controllers.AdminPanel
|
||||
{
|
||||
[Route("admin")]
|
||||
public class AdminController(ILogger<AdminController> logger) : Controller
|
||||
{
|
||||
private readonly ILogger<AdminController> _logger = logger;
|
||||
|
||||
public static bool CheckAuth(HttpContext context)
|
||||
{
|
||||
string? token = context.Request.Cookies["token"] ?? context.Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||
|
||||
// TODO better authentication
|
||||
if (JsonDb.Instance.AdminAuthTokens.TryGetValue(token, out ulong userId))
|
||||
{
|
||||
User? user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == userId);
|
||||
if (user != null && user.IsAdmin)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[Route("dashboard")]
|
||||
public IActionResult Dashboard()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View();
|
||||
}
|
||||
[Route("Events")]
|
||||
public IActionResult Events()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("Configuration")]
|
||||
public IActionResult Configuration()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
ServerConfiguration model = new()
|
||||
{
|
||||
LogType = JsonDb.Instance.LogLevel
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[Route("Configuration"), ActionName("Configuration")]
|
||||
[HttpPost]
|
||||
public IActionResult ConfigurationSave([FromForm] ServerConfiguration cfg)
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return View();
|
||||
|
||||
JsonDb.Instance.LogLevel = cfg.LogType;
|
||||
JsonDb.Save();
|
||||
|
||||
return View(new ServerConfiguration() { LogType = cfg.LogType });
|
||||
}
|
||||
|
||||
[Route("Mail")]
|
||||
public IActionResult Mail()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View();
|
||||
}
|
||||
[Route("Database")]
|
||||
public IActionResult Database()
|
||||
{
|
||||
if (!CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
132
EpinelPS/Controllers/AdminPanel/UsersController.cs
Normal file
132
EpinelPS/Controllers/AdminPanel/UsersController.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Models.Admin;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace EpinelPS.Controllers.AdminPanel
|
||||
{
|
||||
[Route("admin/Users")]
|
||||
public class UsersController(ILogger<UsersController> logger) : Controller
|
||||
{
|
||||
private readonly ILogger<UsersController> _logger = logger;
|
||||
private static readonly MD5 sha = MD5.Create();
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
return View(JsonDb.Instance.Users);
|
||||
}
|
||||
|
||||
[Route("Modify/{id}")]
|
||||
public IActionResult Modify(ulong id)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
User? user = JsonDb.Instance.Users.Where(x => x.ID == id).FirstOrDefault();
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return View(
|
||||
new ModUserModel()
|
||||
{
|
||||
IsAdmin = user.IsAdmin,
|
||||
IsBanned = user.IsBanned,
|
||||
Nickname = user.Nickname ?? "Unknown nickname",
|
||||
sickpulls = user.sickpulls,
|
||||
Username = user.Username ?? "Unknown username",
|
||||
ID = user.ID
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[Route("Modify/{id}"), ActionName("Modify")]
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult DoModifyUser(ulong id, [FromForm] ModUserModel toSet)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
if (!ModelState.IsValid) throw new Exception("model state invalid");
|
||||
|
||||
User? user = JsonDb.Instance.Users.Where(x => x.ID == id).FirstOrDefault();
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(toSet.Username))
|
||||
throw new Exception("username cannot be empty");
|
||||
|
||||
user.Username = toSet.Username;
|
||||
user.IsAdmin = toSet.IsAdmin;
|
||||
user.sickpulls = toSet.sickpulls;
|
||||
user.IsBanned = toSet.IsBanned;
|
||||
user.Nickname = toSet.Nickname;
|
||||
JsonDb.Save();
|
||||
|
||||
return View(new ModUserModel()
|
||||
{
|
||||
IsAdmin = user.IsAdmin,
|
||||
IsBanned = user.IsBanned,
|
||||
Nickname = user.Nickname,
|
||||
sickpulls = user.sickpulls,
|
||||
Username = user.Username,
|
||||
ID = user.ID
|
||||
});
|
||||
}
|
||||
|
||||
[Route("SetPassword/{id}")]
|
||||
public IActionResult SetPassword(ulong id)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
User? user = JsonDb.Instance.Users.Where(x => x.ID == id).FirstOrDefault();
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
user.Password = ""; // do not return the password
|
||||
|
||||
return View(user);
|
||||
}
|
||||
|
||||
|
||||
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
|
||||
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
|
||||
[Route("SetPassword")]
|
||||
[HttpPost, ActionName("SetPassword")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult SetPasswordConfirm(ulong? id)
|
||||
{
|
||||
if (!AdminController.CheckAuth(HttpContext)) return Redirect("/admin/");
|
||||
|
||||
if (id == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
string? newPw = Request.Form["PasswordHash"];
|
||||
if (string.IsNullOrEmpty(newPw))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
// TODO: use bcrypt
|
||||
|
||||
User? userToUpdate = JsonDb.Instance.Users.Where(s => s.ID == id).FirstOrDefault();
|
||||
if (userToUpdate == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
userToUpdate.Password = Convert.ToHexString(sha.ComputeHash(Encoding.ASCII.GetBytes(newPw))).ToLower();
|
||||
|
||||
return View(userToUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,9 +89,20 @@ namespace EpinelPS.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("fleet.repo.game.RepoSVC/GetVersion")]
|
||||
public string LauncherGetVersion()
|
||||
public string LauncherGetVersion([FromBody] LauncherVersionRequest? body)
|
||||
{
|
||||
if (body == null)
|
||||
{
|
||||
return "{}";
|
||||
}
|
||||
|
||||
return System.IO.File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "gameversion.json"));
|
||||
}
|
||||
|
||||
public class LauncherVersionRequest
|
||||
{
|
||||
public int game_id {get;set;}
|
||||
public int branch_id { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using EpinelPS.Database;
|
||||
using System.Reflection;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Org.BouncyCastle.Ocsp;
|
||||
|
||||
namespace EpinelPS.Controllers
|
||||
{
|
||||
@@ -22,16 +24,16 @@ namespace EpinelPS.Controllers
|
||||
[Route("auth/login")]
|
||||
public string AuthLogin(string seq, [FromBody] LoginEndpoint1Req req)
|
||||
{
|
||||
foreach (var tok in JsonDb.Instance.LauncherAccessTokens)
|
||||
foreach (AccessToken tok in JsonDb.Instance.LauncherAccessTokens)
|
||||
{
|
||||
if (tok.Token == req.channel_info.account_token)
|
||||
{
|
||||
var user = JsonDb.Instance.Users.Find(x => x.ID == tok.UserID);
|
||||
User? user = JsonDb.Instance.Users.Find(x => x.ID == tok.UserID);
|
||||
if (user != null)
|
||||
{
|
||||
// todo: they use another token here, but we will reuse the same one.
|
||||
// todo: use a class for this, this is a mess
|
||||
return "{\"birthday\":\"1970-01\",\"channel_info\":{\"account\":\"" + user.Username + "\",\"account_plat_type\":131,\"account_token\":\"" + req.channel_info.account_token + "\",\"account_type\":1,\"account_uid\":\"" + user.ID + "\",\"expire_ts\":1721667004,\"is_login\":true,\"lang_type\":\"en\",\"phone_area_code\":\"\",\"token\":\"" + req.channel_info.account_token + "\"},\"del_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"1719075066-0339089836-025921-1161847390\\\"}\",\"del_account_status\":0,\"del_li_account_status\":0,\"email\":\"" + user.Username + "\",\"extra_json\":{\"del_li_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"" + seq + "\\\"}\",\"get_status_rsp\":{\"adult_age\":14,\"adult_age_map\":{},\"adult_check_status\":1,\"adult_check_status_expiration\":\"0\",\"adult_status_map\":{},\"certificate_type\":3,\"email\":\"\",\"eu_user_agree_status\":0,\"game_grade\":0,\"game_grade_map\":{},\"is_dma\":true,\"is_eea\":false,\"is_need_li_cert\":false,\"msg\":\"success\",\"need_parent_control\":0,\"need_realname_auth\":0,\"parent_certificate_status\":0,\"parent_certificate_status_expiration\":\"0\",\"parent_control_map\":{},\"qr_code_ret\":0,\"realname_auth_status\":0,\"region\":\"724\",\"ret\":0,\"ts\":\"1719075065\"},\"need_notify_rsp\":{\"game_sacc_openid\":\"\",\"game_sacc_uid\":\"\",\"has_game_sacc_openid\":false,\"has_game_sacc_uid\":false,\"has_li_openid\":true,\"has_li_uid\":true,\"is_receive_email\":1,\"is_receive_email_in_night\":0,\"li_openid\":\"" + user.ID + "\",\"li_uid\":\"2752409592679849\",\"need_notify\":false,\"user_agreed_game_dma\":\"2\",\"user_agreed_game_pp\":\"1\",\"user_agreed_game_tos\":\"1\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"1\",\"user_agreed_li_tos\":\"\"}},\"first_login\":0,\"gender\":0,\"msg\":\"success\",\"need_name_auth\":false,\"openid\":\"" + user.ID + "\",\"pf\":\"LevelInfinite_LevelInfinite-Windows-windows-Windows-LevelInfinite-09af79d65d6e4fdf2d2569f0d365739d-" + user.ID + "\",\"pf_key\":\"abc\",\"picture_url\":\"\",\"reg_channel_dis\":\"Windows\",\"ret\":0,\"seq\":\"29080-2d28ea26-d71f-4822-9118-0156f1e2dba4-1719075060-99\",\"token\":\"" + tok.Token + "\",\"token_expire_time\":" + tok.ExpirationTime + ",\"uid\":\"" + user.ID + "\",\"user_name\":\"" + user.PlayerName + "\"}";
|
||||
return "{\"birthday\":\"1970-01\",\"channel_info\":{\"account\":\"" + user.Username + "\",\"account_plat_type\":131,\"account_token\":\"" + req.channel_info.account_token + "\",\"account_type\":1,\"account_uid\":\"" + user.ID + "\",\"expire_ts\":1721667004,\"is_login\":true,\"lang_type\":\"en\",\"phone_area_code\":\"\",\"token\":\"" + req.channel_info.account_token + "\"},\"del_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"1719075066-0339089836-025921-1161847390\\\"}\",\"del_account_status\":0,\"del_li_account_status\":0,\"email\":\"" + user.Username + "\",\"extra_json\":{\"del_li_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"" + seq + "\\\"}\",\"get_status_rsp\":{\"adult_age\":14,\"adult_age_map\":{},\"adult_check_status\":1,\"adult_check_status_expiration\":\"0\",\"adult_status_map\":{},\"certificate_type\":3,\"email\":\"\",\"eu_user_agree_status\":0,\"game_grade\":0,\"game_grade_map\":{},\"is_dma\":true,\"is_eea\":false,\"is_need_li_cert\":false,\"msg\":\"success\",\"need_parent_control\":0,\"need_realname_auth\":0,\"parent_certificate_status\":0,\"parent_certificate_status_expiration\":\"0\",\"parent_control_map\":{},\"qr_code_ret\":0,\"realname_auth_status\":0,\"region\":\"724\",\"ret\":0,\"ts\":\"1719075065\"},\"need_notify_rsp\":{\"game_sacc_openid\":\"\",\"game_sacc_uid\":\"\",\"has_game_sacc_openid\":false,\"has_game_sacc_uid\":false,\"has_li_openid\":true,\"has_li_uid\":true,\"is_receive_email\":1,\"is_receive_email_in_night\":0,\"li_openid\":\"" + user.ID + "\",\"li_uid\":\"2752409592679849\",\"need_notify\":false,\"user_agreed_game_dma\":\"2\",\"user_agreed_game_pp\":\"1\",\"user_agreed_game_tos\":\"1\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"1\",\"user_agreed_li_tos\":\"1\"}},\"first_login\":0,\"gender\":0,\"msg\":\"success\",\"need_name_auth\":false,\"openid\":\"" + user.ID + "\",\"pf\":\"LevelInfinite_LevelInfinite-Windows-windows-Windows-LevelInfinite-09af79d65d6e4fdf2d2569f0d365739d-" + user.ID + "\",\"pf_key\":\"abc\",\"picture_url\":\"\",\"reg_channel_dis\":\"Windows\",\"ret\":0,\"seq\":\"29080-2d28ea26-d71f-4822-9118-0156f1e2dba4-1719075060-99\",\"token\":\"" + tok.Token + "\",\"token_expire_time\":" + tok.ExpirationTime + ",\"uid\":\"" + user.ID + "\",\"user_name\":\"" + user.PlayerName + "\"}";
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -44,10 +46,14 @@ namespace EpinelPS.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("auth/auto_login")]
|
||||
public string AutoLogin(string seq)
|
||||
public string AutoLogin(string seq, [FromBody] AuthPkt2 req)
|
||||
{
|
||||
User? user;
|
||||
if ((user = NetUtils.GetUser(req.token).Item1) == null) return BadAuthToken;
|
||||
|
||||
|
||||
return "{\"del_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"" + seq + "\\\"}\",\"del_account_status\":0,\"del_li_account_status\":0,\"extra_json\":{\"del_li_account_info\":\"{\\\"ret\\\":0,\\\"msg\\\":\\\"\\\",\\\"status\\\":0,\\\"created_at\\\":\\\"0\\\",\\\"target_destroy_at\\\":\\\"0\\\",\\\"destroyed_at\\\":\\\"0\\\",\\\"err_code\\\":0,\\\"seq\\\":\\\"" + seq + "\\\"}\",\"get_status_msg\":\"success\",\"get_status_ret\":0,\"get_status_rsp\":{\"adult_age\":14,\"adult_age_map\":{},\"adult_check_status\":1,\"adult_check_status_expiration\":\"0\",\"adult_status_map\":{},\"certificate_type\":3,\"email\":\"\",\"eu_user_agree_status\":0,\"game_grade\":0,\"game_grade_map\":{},\"is_dma\":true,\"is_eea\":false,\"is_need_li_cert\":false,\"msg\":\"success\",\"need_parent_control\":0,\"need_realname_auth\":0,\"parent_certificate_status\":0,\"parent_certificate_status_expiration\":\"0\",\"parent_control_map\":{},\"qr_code_ret\":0,\"realname_auth_status\":0,\"region\":\"724\",\"ret\":0,\"ts\":\"" + DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
+ "\"},\"need_notify_msg\":\"success\",\"need_notify_ret\":0,\"need_notify_rsp\":{\"has_bind_li\":true,\"is_receive_email\":1,\"is_receive_email_in_night\":0,\"user_agreed_game_dma\":\"2\",\"user_agreed_game_pp\":\"1\",\"user_agreed_game_tos\":\"1\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"1\",\"user_agreed_li_tos\":\"\"}},\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
+ "\"},\"need_notify_msg\":\"success\",\"need_notify_ret\":0,\"need_notify_rsp\":{\"has_bind_li\":true,\"is_receive_email\":1,\"is_receive_email_in_night\":0,\"user_agreed_game_dma\":\"2\",\"user_agreed_game_pp\":\"1\",\"user_agreed_game_tos\":\"1\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"1\",\"user_agreed_li_tos\":\"1\"}},\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@@ -75,7 +81,7 @@ namespace EpinelPS.Controllers
|
||||
if ((user = NetUtils.GetUser(req.channel_info.token).Item1) == null) return BadAuthToken;
|
||||
|
||||
// Pretend that code is valid
|
||||
return "{\"game_sacc_openid\":\"\",\"game_sacc_uid\":\"\",\"has_game_sacc_openid\":false,\"has_game_sacc_uid\":false,\"has_li_openid\":false,\"has_li_uid\":true,\"is_receive_email\":-1,\"is_receive_email_in_night\":-1,\"li_openid\":\"\",\"li_uid\":\"" + user.ID + "\",\"msg\":\"success\",\"need_notify\":false,\"ret\":0,\"seq\":\"" + seq + "\",\"user_agreed_game_dma\":\"\",\"user_agreed_game_pp\":\"\",\"user_agreed_game_tos\":\"\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"\",\"user_agreed_li_tos\":\"\"}";
|
||||
return "{\"game_sacc_openid\":\"\",\"game_sacc_uid\":\"\",\"has_game_sacc_openid\":false,\"has_game_sacc_uid\":false,\"has_li_openid\":false,\"has_li_uid\":true,\"is_receive_email\":-1,\"is_receive_email_in_night\":-1,\"li_openid\":\"\",\"li_uid\":\"" + user.ID + "\",\"msg\":\"success\",\"need_notify\":false,\"ret\":0,\"seq\":\"" + seq + "\",\"user_agreed_game_dma\":\"\",\"user_agreed_game_pp\":\"\",\"user_agreed_game_tos\":\"\",\"user_agreed_li_dt\":\"\",\"user_agreed_li_pp\":\"\",\"user_agreed_li_tos\":\"1\"}";
|
||||
}
|
||||
|
||||
|
||||
@@ -94,12 +100,61 @@ namespace EpinelPS.Controllers
|
||||
// Enable encryption, not used in this server.
|
||||
return "{\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
private static IntlNotice CreateNotice(int id, NoticeType type, string contentText, string title = "", string picture = "")
|
||||
{
|
||||
IntlNotice notice = new()
|
||||
{
|
||||
app_id = "3001001",
|
||||
app_notice_id = "post-" + id,
|
||||
area_list = "[\"81\",\"82\",\"83\",\"84\",\"85\"]",
|
||||
extra_data = "{\"NoticeType\":\"" + type.ToString() + "\",\"Order\":\"11\",\"extra_reserved\":\"{\\\"Author\\\":\\\"\\\",\\\"Category\\\":\\\"\\\",\\\"CreateType\\\":\\\"4\\\",\\\"IsOpenService\\\":\\\"0\\\",\\\"IsToping\\\":true,\\\"Keyword\\\":\\\"\\\",\\\"Sort\\\":\\\"\\\",\\\"TopEnd\\\":\\\"2030-01-01 00:00:01\\\",\\\"TopStart\\\":\\\"2000-01-01 00:00:01\\\"}\"}",
|
||||
id = id,
|
||||
start_time = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
end_time = (int)DateTimeOffset.UtcNow.AddDays(1).ToUnixTimeSeconds(),
|
||||
update_time = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
status = 1
|
||||
};
|
||||
|
||||
ContentList content = new()
|
||||
{
|
||||
app_content_id = "post-" + id,
|
||||
content = contentText,
|
||||
extra_data = "{}",
|
||||
id = id,
|
||||
lang_type = "en",
|
||||
title = title,
|
||||
update_time = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(picture))
|
||||
{
|
||||
content.picture_list.Add(new PictureList()
|
||||
{
|
||||
extra_data = "{\"id\":\"TitleImage\"}",
|
||||
hash = "",
|
||||
redirect_url = "",
|
||||
url = picture
|
||||
});
|
||||
}
|
||||
|
||||
notice.content_list.Add(content);
|
||||
return notice;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("notice/get_notice_content")]
|
||||
public string GetNotices(string seq)
|
||||
public IntlNoticeListResponse GetNotices(string seq)
|
||||
{
|
||||
return "{\r\n \"msg\": \"success\",\r\n \"notice_list\": [\r\n {\r\n \"app_id\": \"3001001\",\r\n \"app_notice_id\": \"post-6rpvwgrdx1b\",\r\n \"area_list\": \"[\\\"81\\\",\\\"82\\\",\\\"83\\\",\\\"84\\\",\\\"85\\\"]\",\r\n \"content_list\": [\r\n {\r\n \"app_content_id\": \"post-9ilpu79xxzp\",\r\n \"content\": \"This isn't working\",\r\n \"extra_data\": \"{}\",\r\n \"id\": 48706,\r\n \"lang_type\": \"en\",\r\n \"picture_list\": [\r\n {\r\n \"extra_data\": \"{\\\"id\\\":\\\"TitleImage\\\"}\",\r\n \"hash\": \"44a99a61152b5b80a0466ff9f0cee2bc\",\r\n \"redirect_url\": \"\",\r\n \"url\": \"pnt-console-cdn.playernetwork.intlgame.com/prod/29080/notice/022681b1121a40259a575fbe587651b4.jpg\"\r\n }\r\n ],\r\n \"title\": \"New Character\",\r\n \"update_time\": 1717637493\r\n }\r\n ],\r\n \"end_time\": 1819431999,\r\n \"extra_data\": \"{\\\"NoticeType\\\":\\\"Event\\\",\\\"Order\\\":\\\"11\\\",\\\"extra_reserved\\\":\\\"{\\\\\\\"Author\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"Category\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"CreateType\\\\\\\":\\\\\\\"4\\\\\\\",\\\\\\\"IsOpenService\\\\\\\":\\\\\\\"0\\\\\\\",\\\\\\\"IsToping\\\\\\\":true,\\\\\\\"Keyword\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"Sort\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"TopEnd\\\\\\\":\\\\\\\"2030-01-01 00:00:01\\\\\\\",\\\\\\\"TopStart\\\\\\\":\\\\\\\"2000-01-01 00:00:01\\\\\\\"}\\\"}\",\r\n \"id\": 7560,\r\n \"picture_list\": [],\r\n \"start_time\": 1717617599,\r\n \"status\": 1,\r\n \"update_time\": 1717637494\r\n }\r\n ],\r\n \"ret\": 0,\r\n \"seq\": \"" + seq + "\"\r\n}";
|
||||
IntlNoticeListResponse rsp = new()
|
||||
{
|
||||
seq = seq,
|
||||
ret = 0,
|
||||
msg = "success"
|
||||
};
|
||||
|
||||
rsp.notice_list.Add(CreateNotice(2, NoticeType.System, "You are running EpinelPS v" + Assembly.GetExecutingAssembly().GetName().Version, "Server version"));
|
||||
|
||||
return rsp;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@@ -118,12 +173,24 @@ namespace EpinelPS.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("profile/get_bind_info")]
|
||||
public string GetProfileBindInfo(string seq, [FromBody] AuthPkt req)
|
||||
public string GetProfileBindInfo(string seq, [FromBody] AuthPkt2 req)
|
||||
{
|
||||
User? user;
|
||||
if ((user = NetUtils.GetUser(req.channel_info.token).Item1) == null) return BadAuthToken;
|
||||
if ((user = NetUtils.GetUser(req.token).Item1) == null) return BadAuthToken;
|
||||
|
||||
return "{\"bind_list\":[{\"bind_ts\":1717783095,\"channel_info\":{\"birthday\":\"1970-01\",\"email\":\"" + user.Username + "\",\"is_receive_email\":1,\"lang_type\":\"en\",\"last_login_time\":171000000,\"nick_name\":\"\",\"phone\":\"\",\"phone_area_code\":\"\",\"region\":\"724\",\"register_account\":\"" + user.Username + "\",\"register_account_type\":1,\"register_time\":" + user.RegisterTime + ",\"seq\":\"" + seq + "\",\"uid\":\"2752409592679849\",\"user_name\":\"" + user.PlayerName + "\",\"username_pass_verify\":0},\"channelid\":131,\"email\":\"" + user.Username + "\",\"history_scopes\":[],\"is_primary\":1,\"picture_url\":\"\",\"user_name\":\"" + user.PlayerName + "\"}],\"create_ts\":" + user.RegisterTime + ",\"last_login_ts\":171000000,\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("auth/refresh_sacc_token")]
|
||||
public string RefreshAuthToken(string seq, [FromBody] AuthPkt2 req)
|
||||
{
|
||||
// TODO redo auth token system
|
||||
AccessToken? user;
|
||||
if ((user = NetUtils.GetUser(req.token).Item2) == null) return BadAuthToken;
|
||||
user.ExpirationTime = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeSeconds();
|
||||
|
||||
return "{\"msg\":\"success\",\"ret\":0,\"seq\":\"" + seq + "\"}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using EpinelPS.LobbyServer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.Controllers
|
||||
{
|
||||
@@ -12,7 +14,11 @@ namespace EpinelPS.Controllers
|
||||
[Consumes("application/octet-stream+protobuf")]
|
||||
public async Task CatchAll(string all)
|
||||
{
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
await LobbyHandler.DispatchSingle(HttpContext);
|
||||
st.Stop();
|
||||
|
||||
Logging.WriteLine($"POST {HttpContext.Request.Path.Value}: {HttpContext.Response.StatusCode}", LogType.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
768
EpinelPS/Data/GameData.cs
Normal file
768
EpinelPS/Data/GameData.cs
Normal file
@@ -0,0 +1,768 @@
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using MemoryPack;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.Data
|
||||
{
|
||||
public class GameData
|
||||
{
|
||||
private static GameData? _instance;
|
||||
public static GameData Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
_instance ??= BuildAsync().Result;
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] MpkHash = [];
|
||||
public int MpkSize;
|
||||
|
||||
private ZipFile MainZip;
|
||||
private MemoryStream ZipStream;
|
||||
private int totalFiles = 1;
|
||||
private int currentFile;
|
||||
|
||||
public readonly Dictionary<string, FieldMapRecord> MapData = [];
|
||||
|
||||
[LoadRecord("MainQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, MainQuestRecord> QuestDataRecords = [];
|
||||
|
||||
[LoadRecord("CampaignStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, CampaignStageRecord> StageDataRecords = [];
|
||||
|
||||
[LoadRecord("RewardTable.json", "Id")]
|
||||
public readonly Dictionary<int, RewardRecord> RewardDataRecords = [];
|
||||
|
||||
[LoadRecord("UserExpTable.json", "Level")]
|
||||
public readonly Dictionary<int, UserExpRecord> UserExpDataRecords = [];
|
||||
|
||||
[LoadRecord("CampaignChapterTable.json", "Chapter")]
|
||||
public readonly Dictionary<int, CampaignChapterRecord> ChapterCampaignData = [];
|
||||
|
||||
[LoadRecord("ContentsOpenTable.json", "Id")]
|
||||
public readonly Dictionary<ContentsOpen, ContentsOpenRecord> ContentsOpenTable = [];
|
||||
|
||||
[LoadRecord("CharacterCostumeTable.json", "Id")]
|
||||
public readonly Dictionary<int, CharacterCostumeRecord> CharacterCostumeTable = [];
|
||||
|
||||
[LoadRecord("CharacterTable.json", "Id")]
|
||||
public readonly Dictionary<int, CharacterRecord> CharacterTable = [];
|
||||
|
||||
[LoadRecord("ContentsTutorialTable.json", "Id")]
|
||||
public readonly Dictionary<int, ContentsTutorialRecord> TutorialTable = [];
|
||||
[LoadRecord("ItemEquipTable.json", "Id")]
|
||||
|
||||
public readonly Dictionary<int, ItemEquipRecord> ItemEquipTable = [];
|
||||
|
||||
[LoadRecord("ItemMaterialTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemMaterialRecord> itemMaterialTable = [];
|
||||
|
||||
[LoadRecord("ItemEquipExpTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemEquipExpRecord> itemEquipExpTable = [];
|
||||
|
||||
[LoadRecord("ItemEquipGradeExpTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemEquipGradeExpRecord> ItemEquipGradeExpTable = [];
|
||||
|
||||
[LoadRecord("CharacterLevelTable.json", "Level")]
|
||||
public readonly Dictionary<int, CharacterLevelRecord> LevelData = [];
|
||||
|
||||
[LoadRecord("TacticAcademyFunctionTable.json", "Id")]
|
||||
public readonly Dictionary<int, TacticAcademyFunctionRecord> TacticAcademyLessons = [];
|
||||
|
||||
[LoadRecord("SIdeStoryStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, SideStoryStageRecord> SidestoryRewardTable = [];
|
||||
|
||||
[LoadRecord("FieldItemTable.json", "Id")]
|
||||
public readonly Dictionary<int, FieldItemRecord> FieldItems = [];
|
||||
|
||||
[LoadRecord("OutpostBattleTable.json", "Id")]
|
||||
public readonly Dictionary<int, OutpostBattleRecord> OutpostBattle = [];
|
||||
|
||||
[LoadRecord("JukeboxListTable.json", "Id")]
|
||||
public readonly Dictionary<int, JukeboxListRecord> jukeboxListDataRecords = [];
|
||||
|
||||
[LoadRecord("JukeboxThemeTable.json", "Id")]
|
||||
public readonly Dictionary<int, JukeboxThemeRecord> jukeboxThemeDataRecords = [];
|
||||
|
||||
[LoadRecord("GachaTypeTable.json", "Id")]
|
||||
public readonly Dictionary<int, GachaTypeRecord> gachaTypes = [];
|
||||
|
||||
[LoadRecord("EventManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventManagerRecord> eventManagers = [];
|
||||
|
||||
[LoadRecord("LiveWallpaperTable.json", "Id")]
|
||||
public readonly Dictionary<int, LiveWallpaperRecord> lwptablemgrs = [];
|
||||
|
||||
[LoadRecord("AlbumResourceTable.json", "Id")]
|
||||
public readonly Dictionary<int, AlbumResourceRecord> albumResourceRecords = [];
|
||||
|
||||
[LoadRecord("UserFrameTable.json", "Id")]
|
||||
public readonly Dictionary<int, UserFrameRecord> userFrameTable = [];
|
||||
|
||||
[LoadRecord("ArchiveRecordManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveRecordManagerRecord> archiveRecordManagerTable = [];
|
||||
|
||||
[LoadRecord("ArchiveEventStoryTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveEventStoryRecord> archiveEventStoryRecords = [];
|
||||
|
||||
[LoadRecord("ArchiveEventQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveEventQuestRecord_Raw> archiveEventQuestRecords = [];
|
||||
|
||||
[LoadRecord("ArchiveEventDungeonStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveEventDungeonStageRecord> archiveEventDungeonStageRecords = [];
|
||||
|
||||
[LoadRecord("UserTitleTable.json", "Id")]
|
||||
public readonly Dictionary<int, UserTitleRecord> userTitleRecords = [];
|
||||
|
||||
[LoadRecord("ArchiveMessengerConditionTable.json", "Id")]
|
||||
public readonly Dictionary<int, ArchiveMessengerConditionRecord> archiveMessengerConditionRecords = [];
|
||||
|
||||
[LoadRecord("CharacterStatTable.json", "Id")]
|
||||
public readonly Dictionary<int, CharacterStatRecord> characterStatTable = [];
|
||||
|
||||
[LoadRecord("SkillInfoTable.json", "Id")]
|
||||
public readonly Dictionary<int, SkillInfoRecord> skillInfoTable = [];
|
||||
|
||||
[LoadRecord("CostTable.json", "Id")]
|
||||
public readonly Dictionary<int, CostRecord> costTable = [];
|
||||
|
||||
[LoadRecord("MidasProductTable.json", "MidasProductIdProximabeta")]
|
||||
public readonly Dictionary<string, MidasProductRecord> mediasProductTable = [];
|
||||
|
||||
[LoadRecord("TowerTable.json", "Id")]
|
||||
public readonly Dictionary<int, TowerRecord> towerTable = [];
|
||||
|
||||
[LoadRecord("TriggerTable.json", "Id")]
|
||||
public readonly Dictionary<int, TriggerRecord> TriggerTable = [];
|
||||
|
||||
[LoadRecord("InfraCoreGradeTable.json", "Id")]
|
||||
public readonly Dictionary<int, InfraCoreGradeRecord> InfracoreTable = [];
|
||||
|
||||
[LoadRecord("AttractiveCounselCharacterTable.json", "NameCode")]
|
||||
public readonly Dictionary<int, AttractiveCounselCharacterRecord_Raw> AttractiveCounselCharacterTable = [];
|
||||
|
||||
[LoadRecord("AttractiveLevelRewardTable.json", "Id")]
|
||||
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
|
||||
|
||||
[LoadRecord("AttractiveLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, AttractiveLevelRecord> AttractiveLevelTable = [];
|
||||
|
||||
[LoadRecord("SubQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, SubQuestRecord> Subquests = [];
|
||||
|
||||
[LoadRecord("MessengerDialogTable.json", "Id")]
|
||||
public readonly Dictionary<string, MessengerDialogRecord> Messages = [];
|
||||
|
||||
[LoadRecord("MessengerConditionTriggerTable.json", "Id")]
|
||||
public readonly Dictionary<int, MessengerConditionTriggerRecord> MessageConditions = [];
|
||||
|
||||
[LoadRecord("ScenarioRewardsTable.json", "ConditionId")]
|
||||
public readonly Dictionary<string, ScenarioRewardsRecord> ScenarioRewards = [];
|
||||
|
||||
// Note: same data types are intentional
|
||||
[LoadRecord("ProductOfferTable.json", "Id")]
|
||||
public readonly Dictionary<int, ProductOfferRecord> ProductOffers = [];
|
||||
|
||||
[LoadRecord("PopupPackageListTable.json", "Id")]
|
||||
public readonly Dictionary<int, PopupPackageListRecord> PopupPackages = [];
|
||||
|
||||
[LoadRecord("InterceptNormalTable.json", "Id")]
|
||||
public readonly Dictionary<int, InterceptNormalRecord> InterceptNormal = [];
|
||||
|
||||
[LoadRecord("InterceptSpecialTable.json", "Id")]
|
||||
public readonly Dictionary<int, InterceptSpecialRecord> InterceptSpecial = [];
|
||||
|
||||
[LoadRecord("ConditionRewardTable.json", "Id")]
|
||||
public readonly Dictionary<int, ConditionRewardRecord> ConditionRewards = [];
|
||||
[LoadRecord("ItemConsumeTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemConsumeRecord> ConsumableItems = [];
|
||||
[LoadRecord("ItemRandomTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemRandomRecord> RandomItem = [];
|
||||
[LoadRecord("LostSectorTable.json", "Id")]
|
||||
public readonly Dictionary<int, LostSectorRecord> LostSector = [];
|
||||
[LoadRecord("LostSectorStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, LostSectorStageRecord> LostSectorStages = [];
|
||||
[LoadRecord("ItemPieceTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemPieceRecord> PieceItems = [];
|
||||
[LoadRecord("GachaGradeProbTable.json", "Id")]
|
||||
public readonly Dictionary<int, GachaGradeProbRecord> GachaGradeProb = [];
|
||||
[LoadRecord("GachaListProbTable.json", "Id")]
|
||||
public readonly Dictionary<int, GachaListProbRecord> GachaListProb = [];
|
||||
[LoadRecord("RecycleResearchStatTable.json", "Id")]
|
||||
public readonly Dictionary<int, RecycleResearchStatRecord> RecycleResearchStats = [];
|
||||
[LoadRecord("RecycleResearchLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, RecycleResearchLevelRecord> RecycleResearchLevels = [];
|
||||
|
||||
// Harmony Cube Data Tables
|
||||
[LoadRecord("ItemHarmonyCubeTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemHarmonyCubeRecord> ItemHarmonyCubeTable = [];
|
||||
|
||||
[LoadRecord("ItemHarmonyCubeLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemHarmonyCubeLevelRecord> ItemHarmonyCubeLevelTable = [];
|
||||
|
||||
// Favorite Item Data Tables
|
||||
[LoadRecord("FavoriteItemTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemRecord> FavoriteItemTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemExpTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemExpRecord> FavoriteItemExpTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemLevelRecord> FavoriteItemLevelTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemProbabilityTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemProbabilityRecord> FavoriteItemProbabilityTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemQuestRecord> FavoriteItemQuestTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemQuestStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, FavoriteItemQuestStageRecord> FavoriteItemQuestStageTable = [];
|
||||
|
||||
// Tables related to PlaySoda Arcade's event.
|
||||
|
||||
[LoadRecord("EventPlaySodaManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPlaySodaManagerRecord> EventPlaySodaManagerTable = [];
|
||||
|
||||
[LoadRecord("EventPlaySodaStoryModeTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPlaySodaStoryModeRecord> EventPlaySodaStoryModeTable = [];
|
||||
|
||||
[LoadRecord("EventPlaySodaChallengeModeTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPlaySodaChallengeModeRecord> EventPlaySodaChallengeModeTable = [];
|
||||
|
||||
[LoadRecord("EventPlaySodaPointRewardTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPlaySodaPointRewardRecord> EventPlaySodaPointRewardTable = [];
|
||||
|
||||
// Tables related to InTheMirror Arcade's event.
|
||||
|
||||
[LoadRecord("EventMvgQuestTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMVGQuestRecord_Raw> EventMvgQuestTable = [];
|
||||
|
||||
[LoadRecord("EventMvgShopTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMVGShopRecord_Raw> EventMvgShopTable = [];
|
||||
|
||||
[LoadRecord("EventMVGMissionTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMVGMissionRecord_Raw> EventMvgMissionTable = [];
|
||||
|
||||
[LoadRecord("EquipmentOptionTable.json", "Id")]
|
||||
public readonly Dictionary<int, EquipmentOptionRecord> EquipmentOptionTable = [];
|
||||
|
||||
[LoadRecord("EquipmentOptionCostTable.json", "Id")]
|
||||
public readonly Dictionary<int, EquipmentOptionCostRecord> EquipmentOptionCostTable = [];
|
||||
|
||||
[LoadRecord("ItemEquipCorpSettingTable.json", "Id")]
|
||||
public readonly Dictionary<int, ItemEquipCorpSettingRecord> ItemEquipCorpSettingTable = [];
|
||||
|
||||
[LoadRecord("LobbyPrivateBannerTable.json", "Id")]
|
||||
public readonly Dictionary<int, LobbyPrivateBannerRecord> LobbyPrivateBannerTable = [];
|
||||
|
||||
[LoadRecord("LoginEventTable.json", "Id")]
|
||||
public readonly Dictionary<int, LoginEventRecord> LoginEventTable = [];
|
||||
|
||||
// Contents Shop Data Tables
|
||||
[LoadRecord("ContentsShopTable.json", "Id")]
|
||||
public readonly Dictionary<int, ContentsShopRecord> ContentsShopTable = [];
|
||||
[LoadRecord("ContentsShopProductTable.json", "Id")]
|
||||
public readonly Dictionary<int, ContentsShopProductRecord> ContentsShopProductTable = [];
|
||||
[LoadRecord("ShopDiscountProbTable.json", "Id")]
|
||||
public readonly Dictionary<int, ShopDiscountProbRecord> ShopDiscountProbTable = [];
|
||||
|
||||
// Event Dungeon data Table
|
||||
[LoadRecord("EventDungeonTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventDungeonRecord> EventDungeonTable = [];
|
||||
[LoadRecord("EventDungeonStageTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventDungeonStageRecord> EventDungeonStageTable = [];
|
||||
[LoadRecord("EventDungeonSpotBattleTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventDungeonSpotBattleRecord> EventDungeonSpotBattleTable = [];
|
||||
[LoadRecord("EventDungeonDifficultTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventDungeonDifficultRecord> EventDungeonDifficultTable = [];
|
||||
[LoadRecord("EventStoryTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventStoryRecord> EventStoryTable = [];
|
||||
[LoadRecord("AutoChargeTable.json", "Id")]
|
||||
public readonly Dictionary<int, AutoChargeRecord> AutoChargeTable = [];
|
||||
|
||||
// Pass Data Tables
|
||||
[LoadRecord("PassManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, PassManagerRecord> PassManagerTable = [];
|
||||
[LoadRecord("EventPassManagerTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventPassManagerRecord> EventPassManagerTable = [];
|
||||
[LoadRecord("SeasonPassTable.json", "Id")]
|
||||
public readonly Dictionary<int, SeasonPassRecord> SeasonPassTable = [];
|
||||
[LoadRecord("PassMissionTable.json", "Id")]
|
||||
public readonly Dictionary<int, PassMissionRecord> PassMissionTable = [];
|
||||
|
||||
// Event Mission Data Tables
|
||||
[LoadRecord("EventMissionListTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMissionListRecord> EventMissionListTable = [];
|
||||
[LoadRecord("EventMissionCategoryTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventMissionCategoryRecord> EventMissionCategoryTable = [];
|
||||
|
||||
// Daily Mission Event Data Tables
|
||||
[LoadRecord("DailyMissionEventSettingTable.json", "Id")]
|
||||
public readonly Dictionary<int, DailyMissionEventSettingRecord_Raw> DailyMissionEventSettingTable = [];
|
||||
[LoadRecord("DailyEventTable.json", "Id")]
|
||||
public readonly Dictionary<int, DailyEventRecord> DailyEventTable = [];
|
||||
|
||||
// SimulationRoom Data Tables
|
||||
[LoadRecord("SimulationRoomChapterTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomChapterRecord> SimulationRoomChapterTable = [];
|
||||
[LoadRecord("SimulationRoomStageLocationTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomStageLocationRecord> SimulationRoomStageLocationTable = [];
|
||||
[LoadRecord("SimulationRoomSelectionEventTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomSelectionEventRecord> SimulationRoomSelectionEventTable = [];
|
||||
[LoadRecord("SimulationRoomSelectionGroupTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomSelectionGroupRecord> SimulationRoomSelectionGroupTable = [];
|
||||
[LoadRecord("SimulationRoomBattleEventTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomBattleEventRecord> SimulationRoomBattleEventTable = [];
|
||||
[LoadRecord("SimulationRoomLevelScalingTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomLevelScalingRecord> SimulationRoomLevelScalingTable = [];
|
||||
[LoadRecord("SimulationRoomBuffPreviewTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomBuffPreviewRecord> SimulationRoomBuffPreviewTable = [];
|
||||
[LoadRecord("SimulationRoomBuffTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomBuffRecord> SimulationRoomBuffTable = [];
|
||||
|
||||
// SimulationRoom Overclock Data Tables
|
||||
[LoadRecord("SimulationRoomOcLevelTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomOverclockLevelRecord> SimulationRoomOcLevelTable = [];
|
||||
[LoadRecord("SimulationRoomOcOptionGroupTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomOverclockOptionGroupRecord> SimulationRoomOcOptionGroupTable = [];
|
||||
[LoadRecord("SimulationRoomOcOptionTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomOverclockOptionRecord> SimulationRoomOcOptionTable = [];
|
||||
[LoadRecord("SimulationRoomOcSeasonTable.json", "Id")]
|
||||
public readonly Dictionary<int, SimulationRoomOverclockSeasonRecord> SimulationRoomOcSeasonTable = [];
|
||||
|
||||
// MiniGame AZX Tables
|
||||
[LoadRecord("EventAZXAppleGameMissionTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameMissionRecord_Raw> EventAZXAppleGameMissionTable = [];
|
||||
[LoadRecord("EventAZXAppleGameBoardTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameBoardRecord_Raw> EventAZXAppleGameBoardTable = [];
|
||||
[LoadRecord("EventAZXAppleGameCharacterTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameCharacterRecord_Raw> EventAZXAppleGameCharacterTable = [];
|
||||
[LoadRecord("EventAZXAppleGameSkillTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameSkillRecord_Raw> EventAZXAppleGameSkillTable = [];
|
||||
[LoadRecord("EventAZXAppleGameCutSceneTable.json", "Id")]
|
||||
public readonly Dictionary<int, EventAZXAppleGameCutSceneRecord_Raw> EventAZXAppleGameCutSceneTable = [];
|
||||
|
||||
static async Task<GameData> BuildAsync()
|
||||
{
|
||||
await Load();
|
||||
|
||||
Logging.WriteLine("Preparing");
|
||||
Stopwatch stopWatch = new();
|
||||
stopWatch.Start();
|
||||
await Instance.Parse();
|
||||
|
||||
stopWatch.Stop();
|
||||
Logging.WriteLine("Preparing took " + stopWatch.Elapsed);
|
||||
return Instance;
|
||||
}
|
||||
|
||||
public GameData(string mpkFilePath)
|
||||
{
|
||||
if (!File.Exists(mpkFilePath)) throw new ArgumentException("Static data file must exist", nameof(mpkFilePath));
|
||||
|
||||
// disable warnings
|
||||
ZipStream = new();
|
||||
|
||||
byte[] rawBytes2 = File.ReadAllBytes(mpkFilePath);
|
||||
MpkHash = SHA256.HashData(rawBytes2);
|
||||
MpkSize = rawBytes2.Length;
|
||||
|
||||
LoadGameData(mpkFilePath, GameConfig.Root.StaticDataMpk);
|
||||
if (MainZip == null) throw new Exception("failed to read zip file");
|
||||
}
|
||||
|
||||
#region Data loading
|
||||
private static byte[] PresharedValue = [0xCB, 0xC2, 0x1C, 0x6F, 0xF3, 0xF5, 0x07, 0xF5, 0x05, 0xBA, 0xCA, 0xD4, 0x98, 0x28, 0x84, 0x1F, 0xF0, 0xD1, 0x38, 0xC7, 0x61, 0xDF, 0xD6, 0xE6, 0x64, 0x9A, 0x85, 0x13, 0x3E, 0x1A, 0x6A, 0x0C, 0x68, 0x0E, 0x2B, 0xC4, 0xDF, 0x72, 0xF8, 0xC6, 0x55, 0xE4, 0x7B, 0x14, 0x36, 0x18, 0x3B, 0xA7, 0xD1, 0x20, 0x81, 0x22, 0xD1, 0xA9, 0x18, 0x84, 0x65, 0x13, 0x0B, 0xED, 0xA3, 0x00, 0xE5, 0xD9];
|
||||
private static RSAParameters LoadParameters = new()
|
||||
{
|
||||
Exponent = [0x01, 0x00, 0x01],
|
||||
Modulus = [0x89, 0xD6, 0x66, 0x00, 0x7D, 0xFC, 0x7D, 0xCE, 0x83, 0xA6, 0x62, 0xE3, 0x1A, 0x5E, 0x9A, 0x53, 0xC7, 0x8A, 0x27, 0xF3, 0x67, 0xC1, 0xF3, 0xD4, 0x37, 0xFE, 0x50, 0x6D, 0x38, 0x45, 0xDF, 0x7E, 0x73, 0x5C, 0xF4, 0x9D, 0x40, 0x4C, 0x8C, 0x63, 0x21, 0x97, 0xDF, 0x46, 0xFF, 0xB2, 0x0D, 0x0E, 0xDB, 0xB2, 0x72, 0xB4, 0xA8, 0x42, 0xCD, 0xEE, 0x48, 0x06, 0x74, 0x4F, 0xE9, 0x56, 0x6E, 0x9A, 0xB1, 0x60, 0x18, 0xBC, 0x86, 0x0B, 0xB6, 0x32, 0xA7, 0x51, 0x00, 0x85, 0x7B, 0xC8, 0x72, 0xCE, 0x53, 0x71, 0x3F, 0x64, 0xC2, 0x25, 0x58, 0xEF, 0xB0, 0xC9, 0x1D, 0xE3, 0xB3, 0x8E, 0xFC, 0x55, 0xCF, 0x8B, 0x02, 0xA5, 0xC8, 0x1E, 0xA7, 0x0E, 0x26, 0x59, 0xA8, 0x33, 0xA5, 0xF1, 0x11, 0xDB, 0xCB, 0xD3, 0xA7, 0x1F, 0xB1, 0xC6, 0x10, 0x39, 0xC8, 0x31, 0x1D, 0x60, 0xDB, 0x0D, 0xA4, 0x13, 0x4B, 0x2B, 0x0E, 0xF3, 0x6F, 0x69, 0xCB, 0xA8, 0x62, 0x03, 0x69, 0xE6, 0x95, 0x6B, 0x8D, 0x11, 0xF6, 0xAF, 0xD9, 0xC2, 0x27, 0x3A, 0x32, 0x12, 0x05, 0xC3, 0xB1, 0xE2, 0x81, 0x4B, 0x40, 0xF8, 0x8B, 0x8D, 0xBA, 0x1F, 0x55, 0x60, 0x2C, 0x09, 0xC6, 0xED, 0x73, 0x96, 0x32, 0xAF, 0x5F, 0xEE, 0x8F, 0xEB, 0x5B, 0x93, 0xCF, 0x73, 0x13, 0x15, 0x6B, 0x92, 0x7B, 0x27, 0x0A, 0x13, 0xF0, 0x03, 0x4D, 0x6F, 0x5E, 0x40, 0x7B, 0x9B, 0xD5, 0xCE, 0xFC, 0x04, 0x97, 0x7E, 0xAA, 0xA3, 0x53, 0x2A, 0xCF, 0xD2, 0xD5, 0xCF, 0x52, 0xB2, 0x40, 0x61, 0x28, 0xB1, 0xA6, 0xF6, 0x78, 0xFB, 0x69, 0x9A, 0x85, 0xD6, 0xB9, 0x13, 0x14, 0x6D, 0xC4, 0x25, 0x36, 0x17, 0xDB, 0x54, 0x0C, 0xD8, 0x77, 0x80, 0x9A, 0x00, 0x62, 0x83, 0xDD, 0xB0, 0x06, 0x64, 0xD0, 0x81, 0x5B, 0x0D, 0x23, 0x9E, 0x88, 0xBD],
|
||||
DP = null
|
||||
};
|
||||
private void LoadGameData(string file, StaticData data)
|
||||
{
|
||||
using FileStream fileStream = File.Open(file, FileMode.Open, FileAccess.Read);
|
||||
|
||||
// Rfc2898DeriveBytes a = new(PresharedValue, data.GetSalt2Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
// byte[] key2 = a.GetBytes(32);
|
||||
byte[] key2 = Rfc2898DeriveBytes.Pbkdf2(PresharedValue, data.GetSalt2Bytes(), 10000, HashAlgorithmName.SHA256, 32);
|
||||
|
||||
byte[] decryptionKey = key2[0..16];
|
||||
byte[] iv = key2[16..32];
|
||||
Aes aes = Aes.Create();
|
||||
aes.KeySize = 128;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Key = decryptionKey;
|
||||
aes.IV = iv;
|
||||
ICryptoTransform transform = aes.CreateDecryptor();
|
||||
using CryptoStream stream = new(fileStream, transform, CryptoStreamMode.Read);
|
||||
|
||||
using MemoryStream ms = new();
|
||||
stream.CopyTo(ms);
|
||||
|
||||
byte[] bytes = ms.ToArray();
|
||||
ZipFile zip = new(ms, false);
|
||||
|
||||
ZipEntry signEntry = zip.GetEntry("sign") ?? throw new Exception("error 1");
|
||||
ZipEntry dataEntry = zip.GetEntry("data") ?? throw new Exception("error 2");
|
||||
Stream signStream = zip.GetInputStream(signEntry);
|
||||
Stream dataStream = zip.GetInputStream(dataEntry);
|
||||
|
||||
using MemoryStream signMs = new();
|
||||
signStream.CopyTo(signMs);
|
||||
|
||||
using MemoryStream dataMs = new();
|
||||
dataStream.CopyTo(dataMs);
|
||||
dataMs.Position = 0;
|
||||
|
||||
RSA rsa = RSA.Create(LoadParameters);
|
||||
if (!rsa.VerifyData(dataMs, signMs.ToArray(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
|
||||
throw new Exception("error 3");
|
||||
|
||||
dataMs.Position = 0;
|
||||
// Rfc2898DeriveBytes keyDec2 = new(PresharedValue, data.GetSalt1Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
// byte[] key3 = keyDec2.GetBytes(32);
|
||||
byte[] key3 = Rfc2898DeriveBytes.Pbkdf2(PresharedValue, data.GetSalt1Bytes(), 10000, HashAlgorithmName.SHA256, 32);
|
||||
|
||||
byte[] val2 = key3[0..16];
|
||||
byte[] iv2 = key3[16..32];
|
||||
|
||||
ZipStream = new MemoryStream();
|
||||
DoTransformation(val2, iv2, dataMs, ZipStream);
|
||||
|
||||
ZipStream.Position = 0;
|
||||
|
||||
MainZip = new ZipFile(ZipStream, false);
|
||||
}
|
||||
|
||||
public static void DoTransformation(byte[] key, byte[] salt, Stream inputStream, Stream outputStream)
|
||||
{
|
||||
SymmetricAlgorithm aes = Aes.Create();
|
||||
aes.Mode = CipherMode.ECB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
|
||||
int blockSize = aes.BlockSize / 8;
|
||||
|
||||
if (salt.Length != blockSize)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Salt size must be same as block size " +
|
||||
$"(actual: {salt.Length}, expected: {blockSize})");
|
||||
}
|
||||
|
||||
byte[] counter = (byte[])salt.Clone();
|
||||
|
||||
Queue<byte> xorMask = new();
|
||||
|
||||
byte[] zeroIv = new byte[blockSize];
|
||||
ICryptoTransform counterEncryptor = aes.CreateEncryptor(key, zeroIv);
|
||||
|
||||
int b;
|
||||
while ((b = inputStream.ReadByte()) != -1)
|
||||
{
|
||||
if (xorMask.Count == 0)
|
||||
{
|
||||
byte[] counterModeBlock = new byte[blockSize];
|
||||
|
||||
counterEncryptor.TransformBlock(
|
||||
counter, 0, counter.Length, counterModeBlock, 0);
|
||||
|
||||
for (int i2 = counter.Length - 1; i2 >= 0; i2--)
|
||||
{
|
||||
if (++counter[i2] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (byte b2 in counterModeBlock)
|
||||
{
|
||||
xorMask.Enqueue(b2);
|
||||
}
|
||||
}
|
||||
|
||||
byte mask = xorMask.Dequeue();
|
||||
outputStream.WriteByte((byte)(((byte)b) ^ mask));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Load()
|
||||
{
|
||||
string? targetFile2 = await AssetDownloadUtil.DownloadOrGetFileAsync(GameConfig.Root.StaticDataMpk.Url, CancellationToken.None) ?? throw new Exception("static data download fail");
|
||||
_instance = new(targetFile2);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<X[]> LoadZip<X>(string entry, IProgress<double> bar) where X : new()
|
||||
{
|
||||
try
|
||||
{
|
||||
entry = entry.Replace(".json", ".mpk");
|
||||
|
||||
ZipEntry fileEntry = MainZip.GetEntry(entry);
|
||||
if (fileEntry == null)
|
||||
{
|
||||
Logging.WriteLine(entry + " does not exist in static data", LogType.Error);
|
||||
return [];
|
||||
}
|
||||
|
||||
Stream stream = MainZip.GetInputStream(fileEntry);
|
||||
X[] deserializedObject = await MemoryPackSerializer.DeserializeAsync<X[]>(stream) ?? throw new Exception("failed to parse " + entry);
|
||||
|
||||
currentFile++;
|
||||
bar.Report((double)currentFile / totalFiles);
|
||||
|
||||
return deserializedObject;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logging.WriteLine($"Failed to parse {entry}:\n{ex}\n", LogType.Error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Parse()
|
||||
{
|
||||
using ProgressBar progress = new();
|
||||
|
||||
totalFiles = GameDataInitializer.TotalFiles;
|
||||
if (totalFiles == 0) throw new Exception("Source generator failed.");
|
||||
|
||||
await GameDataInitializer.InitializeGameData(progress);
|
||||
|
||||
foreach (ZipEntry item in MainZip)
|
||||
{
|
||||
if (item.Name.StartsWith("FieldMapData_") && item.Name != "FieldMapData_EventMap.mpk")
|
||||
{
|
||||
FieldMapRecord[] x = await LoadZip<FieldMapRecord>(item.Name, progress);
|
||||
|
||||
foreach (FieldMapRecord map in x)
|
||||
{
|
||||
MapData.Add(map.Id, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// sanity checks
|
||||
if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty");
|
||||
}
|
||||
|
||||
public MainQuestRecord? GetMainQuestForStageClearCondition(int stage)
|
||||
{
|
||||
if (QuestDataRecords.Count == 0) throw new Exception("QuestDataRecords should not be empty");
|
||||
foreach (KeyValuePair<int, MainQuestRecord> item in QuestDataRecords)
|
||||
{
|
||||
if (item.Value.ConditionId[0].ConditionId == stage)
|
||||
{
|
||||
return item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public MainQuestRecord? GetMainQuestByTableId(int tId)
|
||||
{
|
||||
return QuestDataRecords[tId];
|
||||
}
|
||||
public CampaignStageRecord? GetStageData(int stage)
|
||||
{
|
||||
return StageDataRecords[stage];
|
||||
}
|
||||
public RewardRecord? GetRewardTableEntry(int rewardId)
|
||||
{
|
||||
return RewardDataRecords[rewardId];
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the level and its minimum value for XP value
|
||||
/// </summary>
|
||||
/// <param name="targetExp"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public (int, int) GetUserLevelFromUserExp(int targetExp)
|
||||
{
|
||||
int prevLevel = 0;
|
||||
int prevValue = 0;
|
||||
for (int i = 1; i < UserExpDataRecords.Count + 1; i++)
|
||||
{
|
||||
UserExpRecord item = UserExpDataRecords[i];
|
||||
|
||||
if (prevValue < targetExp)
|
||||
{
|
||||
prevLevel = item.Level;
|
||||
prevValue = item.Exp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (prevLevel, prevValue);
|
||||
}
|
||||
}
|
||||
return (-1, -1);
|
||||
}
|
||||
public int GetUserMinXpForLevel(int targetLevel)
|
||||
{
|
||||
for (int i = 1; i < UserExpDataRecords.Count + 1; i++)
|
||||
{
|
||||
UserExpRecord item = UserExpDataRecords[i];
|
||||
|
||||
if (targetLevel == item.Level)
|
||||
{
|
||||
return item.Exp;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public IEnumerable<int> GetAllCostumes()
|
||||
{
|
||||
foreach (KeyValuePair<int, CharacterCostumeRecord> item in CharacterCostumeTable)
|
||||
{
|
||||
yield return item.Value.Id;
|
||||
}
|
||||
}
|
||||
|
||||
internal ContentsTutorialRecord GetTutorialDataById(int TableId)
|
||||
{
|
||||
return TutorialTable[TableId];
|
||||
}
|
||||
|
||||
public ItemSubType GetItemSubType(int itemType)
|
||||
{
|
||||
// Check if it's an equipment item
|
||||
if (ItemEquipTable.TryGetValue(itemType, out ItemEquipRecord? equipRecord))
|
||||
{
|
||||
return equipRecord.ItemSubType;
|
||||
}
|
||||
|
||||
// Check if it's a harmony cube item
|
||||
if (ItemHarmonyCubeTable.TryGetValue(itemType, out ItemHarmonyCubeRecord? harmonyCubeRecord))
|
||||
{
|
||||
return harmonyCubeRecord.ItemSubType;
|
||||
}
|
||||
|
||||
// Return null if item type not found
|
||||
return ItemSubType.None;
|
||||
}
|
||||
|
||||
internal IEnumerable<int> GetStageIdsForChapter(int chapterNumber, bool normal)
|
||||
{
|
||||
ChapterMod mod = normal ? ChapterMod.Normal : ChapterMod.Hard;
|
||||
foreach (KeyValuePair<int, CampaignStageRecord> item in StageDataRecords)
|
||||
{
|
||||
CampaignStageRecord data = item.Value;
|
||||
|
||||
int chVal = data.ChapterId - 1;
|
||||
|
||||
if (chapterNumber == chVal && data.ChapterMod == mod && data.StageType == StageType.Main)
|
||||
{
|
||||
yield return data.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<int, CharacterLevelRecord> GetCharacterLevelUpData()
|
||||
{
|
||||
return LevelData;
|
||||
}
|
||||
|
||||
public TacticAcademyFunctionRecord GetTacticAcademyLesson(int lessonId)
|
||||
{
|
||||
return TacticAcademyLessons[lessonId];
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetScenarioStageIdsForChapter(int chapterNumber)
|
||||
{
|
||||
return albumResourceRecords.Values.Where(record => record.TargetChapter == chapterNumber && !string.IsNullOrEmpty(record.ScenarioGroupId)).Select(record => record.ScenarioGroupId);
|
||||
}
|
||||
public bool IsValIdScenarioStage(string scenarioGroupId, int targetChapter, int targetStage)
|
||||
{
|
||||
// Only process stages that belong to the main quest
|
||||
if (!scenarioGroupId.StartsWith("d_main_"))
|
||||
{
|
||||
return false; // Exclude stages that don't belong to the main quest
|
||||
}
|
||||
|
||||
// Example regular stage format: "d_main_26_08"
|
||||
// Example bonus stage format: "d_main_18af_06" or "d_main_39_af_01" (since chapter 39)
|
||||
// Example stage with suffix format: "d_main_01_01_s" or "d_main_01_01_e"
|
||||
|
||||
var matches = Regex.Matches(scenarioGroupId, @"\d+");
|
||||
var parts = new List<int>();
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
if (int.TryParse(match.Value, out int number))
|
||||
{
|
||||
parts.Add(number);
|
||||
}
|
||||
}
|
||||
if (parts.Count < 2) // Valid stage must have at least chapter and stage numbers
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int chapter = parts[0];
|
||||
int stage = parts[1];
|
||||
|
||||
// Only accept stages if they are:
|
||||
// 1. In a chapter less than the target chapter
|
||||
// 2. OR in the target chapter but with a stage number less than or equal to the target stage
|
||||
if (chapter < targetChapter || (chapter == targetChapter && (stage <= targetStage)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal string GetMapIdFromChapter(int chapter, ChapterMod mod)
|
||||
{
|
||||
CampaignChapterRecord data = ChapterCampaignData[chapter - 1];
|
||||
if (mod == ChapterMod.Hard)
|
||||
return data.HardFieldId;
|
||||
else if (mod == ChapterMod.Normal)
|
||||
return data.FieldId;
|
||||
else if (mod == ChapterMod.Story)
|
||||
return data.StoryFieldId;
|
||||
|
||||
throw new NotImplementedException($"difficulty {mod} not implemented");
|
||||
}
|
||||
|
||||
internal int GetConditionReward(int groupId, long damage)
|
||||
{
|
||||
IEnumerable<KeyValuePair<int, ConditionRewardRecord>> results = ConditionRewards.Where(x => x.Value.Group == groupId && x.Value.ValueMin <= damage && (x.Value.ValueMax == 0 || x.Value.ValueMax >= damage));
|
||||
if (results.Any())
|
||||
return results.FirstOrDefault().Value.RewardId;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
public FavoriteItemQuestRecord? GetFavoriteItemQuestTableData(int questId)
|
||||
{
|
||||
FavoriteItemQuestTable.TryGetValue(questId, out FavoriteItemQuestRecord?data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public FavoriteItemQuestStageRecord? GetFavoriteItemQuestStageData(int stageId)
|
||||
{
|
||||
FavoriteItemQuestStageTable.TryGetValue(stageId, out FavoriteItemQuestStageRecord? data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public class DataTable<T>
|
||||
{
|
||||
public string version { get; set; } = "";
|
||||
public List<T> records { get; set; } = [];
|
||||
}
|
||||
}
|
||||
21488
EpinelPS/Data/JsonStaticData.cs
Normal file
21488
EpinelPS/Data/JsonStaticData.cs
Normal file
File diff suppressed because it is too large
Load Diff
10
EpinelPS/Data/LoadRecordAttribute.cs
Normal file
10
EpinelPS/Data/LoadRecordAttribute.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace EpinelPS.Data
|
||||
{
|
||||
[System.AttributeUsage(System.AttributeTargets.Field)]
|
||||
|
||||
public class LoadRecordAttribute(string file, string primaryKey) : Attribute
|
||||
{
|
||||
public string File { get; set; } = file;
|
||||
public string PrimaryKey { get; set; } = primaryKey;
|
||||
}
|
||||
}
|
||||
6
EpinelPS/Database/DatabaseConnection.cs
Normal file
6
EpinelPS/Database/DatabaseConnection.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace EpinelPS.Database;
|
||||
|
||||
public class DatabaseConnection
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,291 +1,13 @@
|
||||
using EpinelPS.LobbyServer;
|
||||
using EpinelPS.StaticInfo;
|
||||
using System.Globalization;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
using Newtonsoft.Json;
|
||||
using Paseto;
|
||||
using Paseto.Builder;
|
||||
|
||||
namespace EpinelPS.Database
|
||||
{
|
||||
public class AccessToken
|
||||
{
|
||||
public string Token = "";
|
||||
public long ExpirationTime;
|
||||
public ulong UserID;
|
||||
}
|
||||
public class FieldInfo
|
||||
{
|
||||
public List<NetFieldStageData> CompletedStages = [];
|
||||
public List<NetFieldObject> CompletedObjects = [];
|
||||
}
|
||||
|
||||
public class FieldInfoNew
|
||||
{
|
||||
public List<int> CompletedStages = [];
|
||||
public List<NetFieldObject> CompletedObjects = [];
|
||||
}
|
||||
|
||||
public class Character
|
||||
{
|
||||
public int Csn = 0;
|
||||
public int Tid = 0;
|
||||
public int CostumeId = 0;
|
||||
|
||||
public int Level = 1;
|
||||
public int UltimateLevel = 1;
|
||||
public int Skill1Lvl = 1;
|
||||
public int Skill2Lvl = 1;
|
||||
public int Grade = 0;
|
||||
}
|
||||
public class MainQuestData
|
||||
{
|
||||
public int TableId = 0;
|
||||
public bool IsReceieved = false;
|
||||
}
|
||||
|
||||
public class UserPointData
|
||||
{
|
||||
public int UserLevel = 1;
|
||||
public int ExperiencePoint = 0;
|
||||
}
|
||||
|
||||
public class ItemData
|
||||
{
|
||||
public int ItemType;
|
||||
public long Csn;
|
||||
public int Count;
|
||||
public int Level;
|
||||
public int Exp;
|
||||
public int Position;
|
||||
public int Corp;
|
||||
public long Isn;
|
||||
}
|
||||
public class EventData
|
||||
{
|
||||
public List<string> CompletedScenarios = new();
|
||||
}
|
||||
|
||||
public class SynchroSlot
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of slot, 1 based
|
||||
/// </summary>
|
||||
public int Slot;
|
||||
/// <summary>
|
||||
/// Character CSN
|
||||
/// </summary>
|
||||
public long CharacterSerialNumber;
|
||||
/// <summary>
|
||||
/// Time when slot cooldown expires
|
||||
/// </summary>
|
||||
public long AvailableAt;
|
||||
}
|
||||
public class User
|
||||
{
|
||||
// User info
|
||||
public string Username = "";
|
||||
public string Password = "";
|
||||
public string PlayerName = "";
|
||||
public ulong ID;
|
||||
public long RegisterTime;
|
||||
public int LastNormalStageCleared;
|
||||
public int LastHardStageCleared;
|
||||
public string Nickname = "SomePlayer";
|
||||
public int ProfileIconId = 39900;
|
||||
public bool ProfileIconIsPrism = false;
|
||||
public int ProfileFrame = 1;
|
||||
public bool IsAdmin = false;
|
||||
|
||||
public bool IsBanned = false;
|
||||
public DateTime BanStart;
|
||||
public DateTime BanEnd;
|
||||
public int BanId = 0;
|
||||
|
||||
// Game data
|
||||
public List<string> CompletedScenarios = [];
|
||||
public Dictionary<string, FieldInfo> FieldInfo = []; // here for backwards compatibility
|
||||
|
||||
public Dictionary<string, FieldInfoNew> FieldInfoNew = [];
|
||||
public Dictionary<string, string> MapJson = [];
|
||||
public Dictionary<CurrencyType, long> Currency = new() {
|
||||
{ CurrencyType.ContentStamina, 2 }
|
||||
};
|
||||
public List<SynchroSlot> SynchroSlots = new List<SynchroSlot>();
|
||||
public bool SynchroDeviceUpgraded = false;
|
||||
public int SynchroDeviceLevel = 200;
|
||||
|
||||
public List<ItemData> Items = new();
|
||||
public List<Character> Characters = [];
|
||||
public NetWholeUserTeamData RepresentationTeamData = new();
|
||||
public Dictionary<int, ClearedTutorialData> ClearedTutorialData = [];
|
||||
public NetWallpaperData[] WallpaperList = [];
|
||||
public Dictionary<int, NetUserTeamData> UserTeams = new Dictionary<int, NetUserTeamData>();
|
||||
public Dictionary<int, bool> MainQuestData = new();
|
||||
public int InfraCoreExp = 0;
|
||||
public int InfraCoreLvl = 1;
|
||||
public UserPointData userPointData = new();
|
||||
public DateTime LastLogin = DateTime.UtcNow;
|
||||
public DateTime BattleTime = DateTime.UtcNow;
|
||||
|
||||
public NetOutpostBattleLevel OutpostBattleLevel = new() { Level = 1 };
|
||||
public int GachaTutorialPlayCount = 0;
|
||||
public List<int> CompletedTacticAcademyLessons = [];
|
||||
public List<int> CompletedSideStoryStages = new();
|
||||
|
||||
public List<int> Memorial = new();
|
||||
public List<int> JukeboxBgm = new();
|
||||
|
||||
// Event data
|
||||
public Dictionary<int, EventData> EventInfo = new();
|
||||
|
||||
public void SetQuest(int tid, bool recievedReward)
|
||||
{
|
||||
if (MainQuestData.ContainsKey(tid))
|
||||
{
|
||||
MainQuestData[tid] = recievedReward;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
MainQuestData.Add(tid, recievedReward);
|
||||
}
|
||||
}
|
||||
|
||||
public int GenerateUniqueItemId()
|
||||
{
|
||||
var num = Rng.RandomId();
|
||||
|
||||
while (Items.Any(x => x.Isn == num))
|
||||
{
|
||||
num = Rng.RandomId();
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
public int GenerateUniqueCharacterId()
|
||||
{
|
||||
var num = Rng.RandomId();
|
||||
|
||||
while (Characters.Any(x => x.Csn == num))
|
||||
{
|
||||
num = Rng.RandomId();
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
public bool IsStageCompleted(int id, bool isNorm)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public long GetCurrencyVal(CurrencyType type)
|
||||
{
|
||||
if (Currency.ContainsKey(type))
|
||||
return Currency[type];
|
||||
else
|
||||
{
|
||||
Currency.Add(type, 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public void AddCurrency(CurrencyType type, long val)
|
||||
{
|
||||
if (Currency.ContainsKey(type)) Currency[type] += val;
|
||||
else Currency.Add(type, val);
|
||||
}
|
||||
public bool SubtractCurrency(CurrencyType type, long val)
|
||||
{
|
||||
if (Currency.ContainsKey(type)) Currency[type] -= val;
|
||||
else return false;
|
||||
|
||||
if (Currency[type] < 0)
|
||||
{
|
||||
Currency[type] += val;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public bool CanSubtractCurrency(CurrencyType type, long val)
|
||||
{
|
||||
if (Currency.ContainsKey(type))
|
||||
{
|
||||
if (Currency[type] >= val) return true;
|
||||
else return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (val == 0) return true;
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCharacter(int c)
|
||||
{
|
||||
return Characters.Any(x => x.Tid == c);
|
||||
}
|
||||
|
||||
public Character? GetCharacterBySerialNumber(long value)
|
||||
{
|
||||
return Characters.Where(x => x.Csn == value).FirstOrDefault();
|
||||
}
|
||||
|
||||
internal bool GetSynchro(long csn)
|
||||
{
|
||||
return SynchroSlots.Where(x => x.CharacterSerialNumber == csn).Count() >= 1;
|
||||
}
|
||||
internal int GetCharacterLevel(int csn)
|
||||
{
|
||||
var c = GetCharacterBySerialNumber(csn);
|
||||
if (c == null) throw new Exception("failed to lookup character");
|
||||
|
||||
return GetCharacterLevel(csn, c.Level);
|
||||
}
|
||||
internal int GetCharacterLevel(int csn, int characterLevel)
|
||||
{
|
||||
foreach (var item in SynchroSlots)
|
||||
{
|
||||
if (item.CharacterSerialNumber == csn)
|
||||
{
|
||||
return GetSynchroLevel();
|
||||
}
|
||||
}
|
||||
return characterLevel;
|
||||
}
|
||||
internal int GetSynchroLevel()
|
||||
{
|
||||
if (SynchroDeviceUpgraded)
|
||||
return SynchroDeviceLevel;
|
||||
var highestLevelCharacters = Characters.OrderByDescending(x => x.Level).Take(5).ToList();
|
||||
|
||||
|
||||
if (highestLevelCharacters.Count > 0)
|
||||
{
|
||||
return highestLevelCharacters.Last().Level;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
public class CoreInfo
|
||||
{
|
||||
public int DbVersion = 3;
|
||||
public List<User> Users = [];
|
||||
|
||||
public List<AccessToken> LauncherAccessTokens = [];
|
||||
|
||||
public Dictionary<string, GameClientInfo> GameClientTokens = [];
|
||||
public string ServerName = "<color=\"green\">Private Server</color>";
|
||||
}
|
||||
internal class JsonDb
|
||||
{
|
||||
public static CoreInfo Instance { get; internal set; }
|
||||
@@ -307,79 +29,68 @@ namespace EpinelPS.Database
|
||||
if (j != null)
|
||||
{
|
||||
Instance = j;
|
||||
|
||||
if (Instance.DbVersion != 5)
|
||||
{
|
||||
Logging.Warn("!!!WARNING!!!");
|
||||
Logging.Warn("Database version is extremely out of date.");
|
||||
Logging.Warn("It is recommended to delete db.json to avoid issues.");
|
||||
}
|
||||
|
||||
if (Instance.DbVersion == 0)
|
||||
if (Instance.LauncherTokenKey.Length == 0)
|
||||
{
|
||||
Instance.DbVersion = 1;
|
||||
// In older versions, field info key used chapter number, but now difficultly is appened.
|
||||
Console.WriteLine("Starting database update...");
|
||||
Console.WriteLine("Launcher token key is null, generating new key");
|
||||
|
||||
foreach (var user in Instance.Users)
|
||||
{
|
||||
foreach (var f in user.FieldInfoNew.ToList())
|
||||
{
|
||||
var isNumeric = int.TryParse(f.Key, out int n);
|
||||
if (isNumeric)
|
||||
{
|
||||
var val = f.Value;
|
||||
user.FieldInfoNew.Remove(f.Key);
|
||||
user.FieldInfoNew.Add(n + "_Normal", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine("Database update completed");
|
||||
var pasetoKey = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
|
||||
.GenerateSymmetricKey();
|
||||
Instance.LauncherTokenKey = pasetoKey.Key.ToArray();
|
||||
}
|
||||
else if (Instance.DbVersion == 1)
|
||||
if (Instance.EncryptionTokenKey.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Starting database update...");
|
||||
// there was a bug where equipment position was not saved, so remove all items from each characters
|
||||
Instance.DbVersion = 2;
|
||||
foreach (var user in Instance.Users)
|
||||
{
|
||||
foreach (var f in user.Items.ToList())
|
||||
{
|
||||
f.Csn = 0;
|
||||
}
|
||||
}
|
||||
Console.WriteLine("Database update completed");
|
||||
}
|
||||
else if (Instance.DbVersion == 2)
|
||||
{
|
||||
Console.WriteLine("Starting database update...");
|
||||
// I used to use a class for FieldInfo cleared stages, but now int list is used
|
||||
Instance.DbVersion = 3;
|
||||
foreach (var user in Instance.Users)
|
||||
{
|
||||
foreach (var f in user.FieldInfo)
|
||||
{
|
||||
var newField = new FieldInfoNew();
|
||||
foreach (var stage in f.Value.CompletedStages)
|
||||
{
|
||||
newField.CompletedStages.Add(stage.StageId);
|
||||
}
|
||||
user.FieldInfoNew.Add(f.Key, newField);
|
||||
}
|
||||
user.FieldInfo.Clear();
|
||||
}
|
||||
Console.WriteLine("Database update completed");
|
||||
Console.WriteLine("EncryptionTokenKey is null, generating new key");
|
||||
|
||||
var pasetoKey = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
|
||||
.GenerateSymmetricKey();
|
||||
Instance.EncryptionTokenKey = pasetoKey.Key.ToArray();
|
||||
}
|
||||
|
||||
Save();
|
||||
|
||||
Logging.SetOutputLevel(Instance.LogLevel);
|
||||
|
||||
ValidateDb();
|
||||
Console.WriteLine("Loaded db");
|
||||
Console.WriteLine("JsonDb: Loaded");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Failed to read configuration json file");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void Reload()
|
||||
{
|
||||
if (!File.Exists(AppDomain.CurrentDomain.BaseDirectory + "/db.json"))
|
||||
{
|
||||
Console.WriteLine("users: warning: configuration not found, writing default data");
|
||||
Instance = new CoreInfo();
|
||||
Save();
|
||||
}
|
||||
|
||||
var j = JsonConvert.DeserializeObject<CoreInfo>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json"));
|
||||
if (j != null)
|
||||
{
|
||||
Instance = j;
|
||||
Console.WriteLine("Database reload complete.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateDb()
|
||||
{
|
||||
// check if character level is valid
|
||||
foreach (var item in Instance.Users)
|
||||
foreach (var user in Instance.Users)
|
||||
{
|
||||
foreach (var c in item.Characters)
|
||||
foreach (var c in user.Characters)
|
||||
{
|
||||
if (c.Level > 1000)
|
||||
{
|
||||
@@ -401,5 +112,43 @@ namespace EpinelPS.Database
|
||||
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json", JsonConvert.SerializeObject(Instance, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
public static int CurrentJukeboxBgm(int position)
|
||||
{
|
||||
var activeJukeboxBgm = new List<int>();
|
||||
//important first position holds lobby bgm id and second commanders room bgm id
|
||||
foreach (var user in Instance.Users)
|
||||
{
|
||||
if (user.JukeboxBgm == null || user.JukeboxBgm.Count == 0)
|
||||
{
|
||||
// this if statemet only exists becaus some weird black magic copies default value over and over
|
||||
//in the file when its set in public List<int> JukeboxBgm = new List<int>();
|
||||
//delete when or if it gets fixed
|
||||
|
||||
user.JukeboxBgm = [2, 5];
|
||||
}
|
||||
|
||||
activeJukeboxBgm.AddRange(user.JukeboxBgm);
|
||||
}
|
||||
|
||||
if (activeJukeboxBgm.Count == 0)
|
||||
{
|
||||
return 8995001;
|
||||
}
|
||||
|
||||
position = (position == 2 && activeJukeboxBgm.Count > 1) ? 2 : 1;
|
||||
return activeJukeboxBgm[position - 1];
|
||||
}
|
||||
|
||||
public static bool IsSickPulls(User selectedUser)
|
||||
{
|
||||
if (selectedUser != null)
|
||||
{
|
||||
return selectedUser.sickpulls;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"User not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,27 +2,42 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IncludeHttpRuleProtos>true</IncludeHttpRuleProtos>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<IncludeNativeLibrariesForSelfExtract>True</IncludeNativeLibrariesForSelfExtract>
|
||||
<NoWarn>$(NoWarn);SYSLIB0057</NoWarn>
|
||||
<Version>0.140.8.0</Version>
|
||||
<CETCompat>false</CETCompat>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ASodium" Version="0.6.1" />
|
||||
<PackageReference Include="ASodium" Version="0.6.4" />
|
||||
<PackageReference Include="DnsClient" Version="1.8.0" />
|
||||
<PackageReference Include="Google.Api.CommonProtos" Version="2.15.0" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.27.3" />
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.65.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Paseto.Core" Version="1.3.0" />
|
||||
<PackageReference Include="PeterO.Cbor" Version="5.0.0-alpha1" />
|
||||
<PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.33.1" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.76.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MemoryPack" Version="1.21.4" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="Paseto.Core" Version="1.4.1" />
|
||||
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="Sodium.Core" Version="1.4.0-preview.1" />
|
||||
<PackageReference Include="System.Net.Http.Formatting.Extension" Version="5.2.3" />
|
||||
<PackageReference Include="Sodium.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="log4net" Version="3.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -34,13 +49,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Views\Shared\error.cshtml" />
|
||||
<None Include="wwwroot\admin\assets\login.css" />
|
||||
<None Include="wwwroot\admin\assets\login.jpg" />
|
||||
<None Include="wwwroot\admin\assets\style.css" />
|
||||
<None Include="wwwroot\admin\dashbrd.html" />
|
||||
<None Include="wwwroot\admin\**" />
|
||||
<None Include="wwwroot\admin\index.html" />
|
||||
<None Include="wwwroot\admin\nav.html" />
|
||||
<None Include="wwwroot\nikke_launcher\index.html" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -58,4 +68,16 @@
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EpinelPS.Analyzers\EpinelPS.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\assets\js\loginpage.js" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\assets\login.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\assets\login.jpg" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\assets\style.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\admin\css\site.css" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,520 +0,0 @@
|
||||
using EpinelPS.Utils;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace EpinelPS.StaticInfo
|
||||
{
|
||||
public class GameData
|
||||
{
|
||||
private static GameData? _instance;
|
||||
public static GameData Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = BuildAsync().Result;
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
private ZipFile MainZip;
|
||||
private MemoryStream ZipStream;
|
||||
private Dictionary<int, MainQuestCompletionRecord> questDataRecords;
|
||||
private Dictionary<int, CampaignStageRecord> stageDataRecords;
|
||||
private Dictionary<int, RewardTableRecord> rewardDataRecords;
|
||||
private JArray userExpDataRecords;
|
||||
private Dictionary<int, CampaignChapterRecord> chapterCampaignData;
|
||||
private JArray characterCostumeTable;
|
||||
private Dictionary<int, CharacterRecord> characterTable;
|
||||
private Dictionary<int, ClearedTutorialData> tutorialTable;
|
||||
private Dictionary<int, ItemEquipRecord> itemEquipTable;
|
||||
private Dictionary<string, JArray> FieldMapData = [];
|
||||
private Dictionary<int, CharacterLevelData> LevelData = [];
|
||||
private Dictionary<int, TacticAcademyLessonRecord> TacticAcademyLessons = [];
|
||||
public Dictionary<int, int> SidestoryRewardTable = [];
|
||||
public Dictionary<string, int> PositionReward = new Dictionary<string, int>();
|
||||
public Dictionary<int, FieldItemRecord> FieldItems = [];
|
||||
|
||||
public byte[] Sha256Hash;
|
||||
public int Size;
|
||||
|
||||
static async Task<GameData> BuildAsync()
|
||||
{
|
||||
await Load();
|
||||
|
||||
Console.WriteLine("Preparing");
|
||||
var stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
await Instance.Parse();
|
||||
|
||||
stopWatch.Stop();
|
||||
Console.WriteLine("Preparing took " + stopWatch.Elapsed);
|
||||
return Instance;
|
||||
}
|
||||
|
||||
public GameData(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath)) throw new ArgumentException("Static data file must exist", nameof(filePath));
|
||||
|
||||
// disable warnings
|
||||
questDataRecords = new();
|
||||
stageDataRecords = new();
|
||||
rewardDataRecords = new();
|
||||
userExpDataRecords = new();
|
||||
chapterCampaignData = new();
|
||||
characterCostumeTable = new();
|
||||
characterTable = new();
|
||||
ZipStream = new();
|
||||
tutorialTable = new();
|
||||
itemEquipTable = new();
|
||||
|
||||
var rawBytes = File.ReadAllBytes(filePath);
|
||||
Sha256Hash = SHA256.HashData(rawBytes);
|
||||
Size = rawBytes.Length;
|
||||
|
||||
LoadGameData(filePath);
|
||||
if (MainZip == null) throw new Exception("failed to read zip file");
|
||||
}
|
||||
#region Data loading
|
||||
private static byte[] PresharedValue = [0xCB, 0xC2, 0x1C, 0x6F, 0xF3, 0xF5, 0x07, 0xF5, 0x05, 0xBA, 0xCA, 0xD4, 0x98, 0x28, 0x84, 0x1F, 0xF0, 0xD1, 0x38, 0xC7, 0x61, 0xDF, 0xD6, 0xE6, 0x64, 0x9A, 0x85, 0x13, 0x3E, 0x1A, 0x6A, 0x0C, 0x68, 0x0E, 0x2B, 0xC4, 0xDF, 0x72, 0xF8, 0xC6, 0x55, 0xE4, 0x7B, 0x14, 0x36, 0x18, 0x3B, 0xA7, 0xD1, 0x20, 0x81, 0x22, 0xD1, 0xA9, 0x18, 0x84, 0x65, 0x13, 0x0B, 0xED, 0xA3, 0x00, 0xE5, 0xD9];
|
||||
private static RSAParameters LoadParameters = new RSAParameters()
|
||||
{
|
||||
Exponent = [0x01, 0x00, 0x01],
|
||||
Modulus = [0x89, 0xD6, 0x66, 0x00, 0x7D, 0xFC, 0x7D, 0xCE, 0x83, 0xA6, 0x62, 0xE3, 0x1A, 0x5E, 0x9A, 0x53, 0xC7, 0x8A, 0x27, 0xF3, 0x67, 0xC1, 0xF3, 0xD4, 0x37, 0xFE, 0x50, 0x6D, 0x38, 0x45, 0xDF, 0x7E, 0x73, 0x5C, 0xF4, 0x9D, 0x40, 0x4C, 0x8C, 0x63, 0x21, 0x97, 0xDF, 0x46, 0xFF, 0xB2, 0x0D, 0x0E, 0xDB, 0xB2, 0x72, 0xB4, 0xA8, 0x42, 0xCD, 0xEE, 0x48, 0x06, 0x74, 0x4F, 0xE9, 0x56, 0x6E, 0x9A, 0xB1, 0x60, 0x18, 0xBC, 0x86, 0x0B, 0xB6, 0x32, 0xA7, 0x51, 0x00, 0x85, 0x7B, 0xC8, 0x72, 0xCE, 0x53, 0x71, 0x3F, 0x64, 0xC2, 0x25, 0x58, 0xEF, 0xB0, 0xC9, 0x1D, 0xE3, 0xB3, 0x8E, 0xFC, 0x55, 0xCF, 0x8B, 0x02, 0xA5, 0xC8, 0x1E, 0xA7, 0x0E, 0x26, 0x59, 0xA8, 0x33, 0xA5, 0xF1, 0x11, 0xDB, 0xCB, 0xD3, 0xA7, 0x1F, 0xB1, 0xC6, 0x10, 0x39, 0xC8, 0x31, 0x1D, 0x60, 0xDB, 0x0D, 0xA4, 0x13, 0x4B, 0x2B, 0x0E, 0xF3, 0x6F, 0x69, 0xCB, 0xA8, 0x62, 0x03, 0x69, 0xE6, 0x95, 0x6B, 0x8D, 0x11, 0xF6, 0xAF, 0xD9, 0xC2, 0x27, 0x3A, 0x32, 0x12, 0x05, 0xC3, 0xB1, 0xE2, 0x81, 0x4B, 0x40, 0xF8, 0x8B, 0x8D, 0xBA, 0x1F, 0x55, 0x60, 0x2C, 0x09, 0xC6, 0xED, 0x73, 0x96, 0x32, 0xAF, 0x5F, 0xEE, 0x8F, 0xEB, 0x5B, 0x93, 0xCF, 0x73, 0x13, 0x15, 0x6B, 0x92, 0x7B, 0x27, 0x0A, 0x13, 0xF0, 0x03, 0x4D, 0x6F, 0x5E, 0x40, 0x7B, 0x9B, 0xD5, 0xCE, 0xFC, 0x04, 0x97, 0x7E, 0xAA, 0xA3, 0x53, 0x2A, 0xCF, 0xD2, 0xD5, 0xCF, 0x52, 0xB2, 0x40, 0x61, 0x28, 0xB1, 0xA6, 0xF6, 0x78, 0xFB, 0x69, 0x9A, 0x85, 0xD6, 0xB9, 0x13, 0x14, 0x6D, 0xC4, 0x25, 0x36, 0x17, 0xDB, 0x54, 0x0C, 0xD8, 0x77, 0x80, 0x9A, 0x00, 0x62, 0x83, 0xDD, 0xB0, 0x06, 0x64, 0xD0, 0x81, 0x5B, 0x0D, 0x23, 0x9E, 0x88, 0xBD],
|
||||
DP = null
|
||||
};
|
||||
private void LoadGameData(string file)
|
||||
{
|
||||
using var fileStream = File.Open(file, FileMode.Open, FileAccess.Read);
|
||||
|
||||
var a = new Rfc2898DeriveBytes(PresharedValue, GameConfig.Root.StaticData.GetSalt2Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
var key2 = a.GetBytes(32);
|
||||
|
||||
byte[] decryptionKey = key2[0..16];
|
||||
byte[] iv = key2[16..32];
|
||||
var aes = Aes.Create();
|
||||
aes.KeySize = 128;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Key = decryptionKey;
|
||||
aes.IV = iv;
|
||||
var transform = aes.CreateDecryptor();
|
||||
using CryptoStream stream = new CryptoStream(fileStream, transform, CryptoStreamMode.Read);
|
||||
|
||||
using MemoryStream ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
|
||||
var bytes = ms.ToArray();
|
||||
var zip = new ZipFile(ms, false);
|
||||
|
||||
var signEntry = zip.GetEntry("sign");
|
||||
if (signEntry == null) throw new Exception("error 1");
|
||||
var dataEntry = zip.GetEntry("data");
|
||||
if (dataEntry == null) throw new Exception("error 2");
|
||||
|
||||
var signStream = zip.GetInputStream(signEntry);
|
||||
var dataStream = zip.GetInputStream(dataEntry);
|
||||
|
||||
using MemoryStream signMs = new MemoryStream();
|
||||
signStream.CopyTo(signMs);
|
||||
|
||||
using MemoryStream dataMs = new MemoryStream();
|
||||
dataStream.CopyTo(dataMs);
|
||||
dataMs.Position = 0;
|
||||
|
||||
var rsa = RSA.Create(LoadParameters);
|
||||
if (!rsa.VerifyData(dataMs, signMs.ToArray(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
|
||||
throw new Exception("error 3");
|
||||
|
||||
dataMs.Position = 0;
|
||||
var keyDecryptor2 = new Rfc2898DeriveBytes(PresharedValue, GameConfig.Root.StaticData.GetSalt1Bytes(), 10000, HashAlgorithmName.SHA256);
|
||||
var key3 = keyDecryptor2.GetBytes(32);
|
||||
|
||||
byte[] val2 = key3[0..16];
|
||||
byte[] iv2 = key3[16..32];
|
||||
|
||||
ZipStream = new MemoryStream();
|
||||
DoTransformation(val2, iv2, dataMs, ZipStream);
|
||||
|
||||
ZipStream.Position = 0;
|
||||
|
||||
MainZip = new ZipFile(ZipStream, false);
|
||||
}
|
||||
|
||||
public static void DoTransformation(byte[] key, byte[] salt, Stream inputStream, Stream outputStream)
|
||||
{
|
||||
SymmetricAlgorithm aes = Aes.Create();
|
||||
aes.Mode = CipherMode.ECB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
|
||||
int blockSize = aes.BlockSize / 8;
|
||||
|
||||
if (salt.Length != blockSize)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Salt size must be same as block size " +
|
||||
$"(actual: {salt.Length}, expected: {blockSize})");
|
||||
}
|
||||
|
||||
var counter = (byte[])salt.Clone();
|
||||
|
||||
var xorMask = new Queue<byte>();
|
||||
|
||||
var zeroIv = new byte[blockSize];
|
||||
ICryptoTransform counterEncryptor = aes.CreateEncryptor(key, zeroIv);
|
||||
|
||||
int b;
|
||||
while ((b = inputStream.ReadByte()) != -1)
|
||||
{
|
||||
if (xorMask.Count == 0)
|
||||
{
|
||||
var counterModeBlock = new byte[blockSize];
|
||||
|
||||
counterEncryptor.TransformBlock(
|
||||
counter, 0, counter.Length, counterModeBlock, 0);
|
||||
|
||||
for (var i2 = counter.Length - 1; i2 >= 0; i2--)
|
||||
{
|
||||
if (++counter[i2] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var b2 in counterModeBlock)
|
||||
{
|
||||
xorMask.Enqueue(b2);
|
||||
}
|
||||
}
|
||||
|
||||
var mask = xorMask.Dequeue();
|
||||
outputStream.WriteByte((byte)(((byte)b) ^ mask));
|
||||
}
|
||||
}
|
||||
public static async Task Load()
|
||||
{
|
||||
var targetFile = await AssetDownloadUtil.DownloadOrGetFileAsync(GameConfig.Root.StaticData.Url, CancellationToken.None);
|
||||
if (targetFile == null) throw new Exception("static data download fail");
|
||||
|
||||
_instance = new(targetFile);
|
||||
}
|
||||
#endregion
|
||||
private async Task<T> LoadZip<T>(string entry, ProgressBar bar)
|
||||
{
|
||||
var mainQuestData = MainZip.GetEntry(entry);
|
||||
if (mainQuestData == null) throw new Exception(entry + " does not exist in static data");
|
||||
|
||||
using StreamReader mainQuestReader = new StreamReader(MainZip.GetInputStream(mainQuestData));
|
||||
var mainQuestDataString = await mainQuestReader.ReadToEndAsync();
|
||||
|
||||
|
||||
var questdata = JsonConvert.DeserializeObject<T>(mainQuestDataString);
|
||||
if (questdata == null) throw new Exception("failed to parse " + entry);
|
||||
|
||||
currentFile++;
|
||||
bar.Report((double)currentFile / totalFiles);
|
||||
|
||||
return questdata;
|
||||
}
|
||||
private async Task<JArray> LoadZip(string entry, ProgressBar bar)
|
||||
{
|
||||
var mainQuestData = MainZip.GetEntry(entry);
|
||||
if (mainQuestData == null) throw new Exception(entry + " does not exist in static data");
|
||||
|
||||
using StreamReader mainQuestReader = new StreamReader(MainZip.GetInputStream(mainQuestData));
|
||||
var mainQuestDataString = await mainQuestReader.ReadToEndAsync();
|
||||
|
||||
|
||||
var questdata = JObject.Parse(mainQuestDataString);
|
||||
if (questdata == null) throw new Exception("failed to parse " + entry);
|
||||
|
||||
var records = (JArray?)questdata["records"];
|
||||
if (records == null) throw new Exception(entry + " is missing records element");
|
||||
|
||||
currentFile++;
|
||||
bar.Report((double)currentFile / totalFiles);
|
||||
|
||||
return records;
|
||||
}
|
||||
int totalFiles = 14;
|
||||
int currentFile = 0;
|
||||
|
||||
public async Task Parse()
|
||||
{
|
||||
using var progress = new ProgressBar();
|
||||
|
||||
var questDataRecords = await LoadZip<MainQuestCompletionTable>("MainQuestTable.json", progress);
|
||||
foreach (var obj in questDataRecords.records)
|
||||
{
|
||||
this.questDataRecords.Add(obj.id, obj);
|
||||
}
|
||||
|
||||
var stageDataRecords = await LoadZip<CampaignStageTable>("CampaignStageTable.json", progress);
|
||||
foreach (var obj in stageDataRecords.records)
|
||||
{
|
||||
this.stageDataRecords.Add(obj.id, obj);
|
||||
}
|
||||
|
||||
var rewardDataRecords = await LoadZip<RewardTable>("RewardTable.json", progress);
|
||||
foreach (var obj in rewardDataRecords.records)
|
||||
{
|
||||
this.rewardDataRecords.Add(obj.id, obj);
|
||||
}
|
||||
|
||||
var chapterCampaignData = await LoadZip<CampaignChapterTable>("CampaignChapterTable.json", progress);
|
||||
foreach (var obj in chapterCampaignData.records)
|
||||
{
|
||||
this.chapterCampaignData.Add(obj.chapter, obj);
|
||||
}
|
||||
|
||||
userExpDataRecords = await LoadZip("UserExpTable.json", progress);
|
||||
characterCostumeTable = await LoadZip("CharacterCostumeTable.json", progress);
|
||||
|
||||
var characterTable = await LoadZip<CharacterTable>("CharacterTable.json", progress);
|
||||
foreach (var obj in characterTable.records)
|
||||
{
|
||||
this.characterTable.Add(obj.id, obj);
|
||||
}
|
||||
|
||||
var tutorialTable = await LoadZip<TutorialTable>("ContentsTutorialTable.json", progress);
|
||||
foreach (var obj in tutorialTable.records)
|
||||
{
|
||||
this.tutorialTable.Add(obj.id, obj);
|
||||
}
|
||||
|
||||
var itemEquipTable = await LoadZip<ItemEquipTable>("ItemEquipTable.json", progress);
|
||||
foreach (var obj in itemEquipTable.records)
|
||||
{
|
||||
this.itemEquipTable.Add(obj.id, obj);
|
||||
}
|
||||
|
||||
var characterLevelTable = await LoadZip("CharacterLevelTable.json", progress);
|
||||
|
||||
foreach (JToken item in characterLevelTable)
|
||||
{
|
||||
var obj = item.ToObject<CharacterLevelData>();
|
||||
if (obj != null)
|
||||
LevelData.Add(obj.level, obj);
|
||||
else
|
||||
Console.WriteLine("failed to read character level table entry");
|
||||
}
|
||||
|
||||
var tacticLessonTable = await LoadZip("TacticAcademyFunctionTable.json", progress);
|
||||
|
||||
foreach (JToken item in tacticLessonTable)
|
||||
{
|
||||
var idRaw = item["id"];
|
||||
var groupidRaw = item["group_id"];
|
||||
var currencyIdRaw = item["currency_id"];
|
||||
var currencyValueRaw = item["currency_value"];
|
||||
|
||||
if (idRaw == null) throw new InvalidDataException();
|
||||
if (groupidRaw == null) throw new InvalidDataException();
|
||||
if (currencyIdRaw == null) throw new InvalidDataException();
|
||||
if (currencyValueRaw == null) throw new InvalidDataException();
|
||||
|
||||
var id = idRaw.ToObject<int>();
|
||||
var currencyId = currencyIdRaw.ToObject<int>();
|
||||
var currencyValue = currencyValueRaw.ToObject<int>();
|
||||
var groupid = groupidRaw.ToObject<int>();
|
||||
|
||||
var fullId = int.Parse(groupid.ToString() + id.ToString());
|
||||
TacticAcademyLessons.Add(id, new TacticAcademyLessonRecord() { CurrencyId = (CurrencyType)currencyId, CurrencyValue = currencyValue, GroupId = groupid, Id = id });
|
||||
}
|
||||
|
||||
var sideStoryTable = await LoadZip("SideStoryStageTable.json", progress);
|
||||
|
||||
foreach (JToken item in sideStoryTable)
|
||||
{
|
||||
var idRaw = item["id"];
|
||||
var rewardIdRaw = item["first_clear_reward"];
|
||||
|
||||
if (idRaw == null) throw new InvalidDataException();
|
||||
if (rewardIdRaw != null)
|
||||
{
|
||||
var id2 = idRaw.ToObject<int>();
|
||||
var reward = rewardIdRaw.ToObject<int>();
|
||||
|
||||
SidestoryRewardTable.Add(id2, reward);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (ZipEntry item in MainZip)
|
||||
{
|
||||
if (item.Name.StartsWith("CampaignMap/"))
|
||||
{
|
||||
var x = await LoadZip(item.Name, progress);
|
||||
|
||||
var items = x[0]["ItemSpawner"];
|
||||
foreach (var item2 in items)
|
||||
{
|
||||
var id = item2["positionId"].ToObject<string>();
|
||||
var reward = item2["itemId"].ToObject<int>();
|
||||
|
||||
if (!PositionReward.ContainsKey(id))
|
||||
PositionReward.Add(id, reward);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var fieldItems = await LoadZip<FieldItemTable>("FieldItemTable.json", progress);
|
||||
foreach (var obj in fieldItems.records)
|
||||
{
|
||||
FieldItems.Add(obj.id, obj);
|
||||
}
|
||||
}
|
||||
|
||||
public MainQuestCompletionRecord? GetMainQuestForStageClearCondition(int stage)
|
||||
{
|
||||
foreach (var item in questDataRecords)
|
||||
{
|
||||
if (item.Value.condition_id == stage)
|
||||
{
|
||||
return item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public MainQuestCompletionRecord? GetMainQuestByTableId(int tid)
|
||||
{
|
||||
return questDataRecords[tid];
|
||||
}
|
||||
public CampaignStageRecord? GetStageData(int stage)
|
||||
{
|
||||
return stageDataRecords[stage];
|
||||
}
|
||||
public RewardTableRecord? GetRewardTableEntry(int rewardId)
|
||||
{
|
||||
return rewardDataRecords[rewardId];
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the level and its minimum value for XP value
|
||||
/// </summary>
|
||||
/// <param name="targetExp"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public (int, int) GetUserLevelFromUserExp(int targetExp)
|
||||
{
|
||||
int prevLevel = 0;
|
||||
int prevValue = 0;
|
||||
for (int i = 0; i < userExpDataRecords.Count; i++)
|
||||
{
|
||||
var item = userExpDataRecords[i];
|
||||
|
||||
var level = item["level"];
|
||||
if (level == null) throw new Exception("expected level field in user exp table data");
|
||||
|
||||
int levelValue = level.ToObject<int>();
|
||||
|
||||
var exp = item["exp"];
|
||||
if (exp == null) throw new Exception("expected exp field in user exp table data");
|
||||
|
||||
int expValue = exp.ToObject<int>();
|
||||
|
||||
if (prevValue < targetExp)
|
||||
{
|
||||
prevLevel = levelValue;
|
||||
prevValue = expValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (prevLevel, prevValue);
|
||||
}
|
||||
}
|
||||
return (-1, -1);
|
||||
}
|
||||
public int GetUserMinXpForLevel(int targetLevel)
|
||||
{
|
||||
for (int i = 0; i < userExpDataRecords.Count; i++)
|
||||
{
|
||||
var item = userExpDataRecords[i];
|
||||
|
||||
var level = item["level"];
|
||||
if (level == null) throw new Exception("expected level field in user exp table data");
|
||||
|
||||
int levelValue = level.ToObject<int>();
|
||||
if (targetLevel == levelValue)
|
||||
{
|
||||
var exp = item["exp"];
|
||||
if (exp == null) throw new Exception("expected exp field in user exp table data");
|
||||
|
||||
int expValue = exp.ToObject<int>();
|
||||
return expValue;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public int GetNormalChapterNumberFromFieldName(string field)
|
||||
{
|
||||
foreach (var item in chapterCampaignData)
|
||||
{
|
||||
if (item.Value.field_id == field)
|
||||
{
|
||||
return item.Value.chapter;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetAllCharacterTids()
|
||||
{
|
||||
return characterTable.Keys;
|
||||
}
|
||||
public IEnumerable<int> GetAllCostumes()
|
||||
{
|
||||
foreach (JObject item in characterCostumeTable)
|
||||
{
|
||||
var id = item["id"];
|
||||
if (id == null) throw new Exception("expected id field in reward data");
|
||||
|
||||
int value = id.ToObject<int>();
|
||||
yield return value;
|
||||
}
|
||||
}
|
||||
|
||||
internal ClearedTutorialData GetTutorialDataById(int TableId)
|
||||
{
|
||||
return tutorialTable[TableId];
|
||||
}
|
||||
|
||||
public string? GetItemSubType(int itemType)
|
||||
{
|
||||
return itemEquipTable[itemType].item_sub_type;
|
||||
}
|
||||
|
||||
internal IEnumerable<int> GetStageIdsForChapter(int chapterNumber, bool normal)
|
||||
{
|
||||
string mod = normal ? "Normal" : "Hard";
|
||||
foreach (var item in stageDataRecords)
|
||||
{
|
||||
var data = item.Value;
|
||||
|
||||
int chVal = data.chapter_id - 1;
|
||||
|
||||
if (chapterNumber == chVal && data.chapter_mod == mod && data.stage_type == "Main")
|
||||
{
|
||||
yield return data.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<int, CharacterLevelData> GetCharacterLevelUpData()
|
||||
{
|
||||
return LevelData;
|
||||
}
|
||||
|
||||
public TacticAcademyLessonRecord GetTacticAcademyLesson(int lessonId)
|
||||
{
|
||||
return TacticAcademyLessons[lessonId];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
namespace EpinelPS.StaticInfo
|
||||
{
|
||||
public class MainQuestCompletionRecord
|
||||
{
|
||||
public int id;
|
||||
public int group_id;
|
||||
public string category = "";
|
||||
public int condition_id;
|
||||
public int next_main_quest_id = 0;
|
||||
public int reward_id = 0;
|
||||
public int target_chapter_id;
|
||||
}
|
||||
public class MainQuestCompletionTable
|
||||
{
|
||||
public List<MainQuestCompletionRecord> records;
|
||||
}
|
||||
public class CampaignStageRecord
|
||||
{
|
||||
public int id;
|
||||
public int chapter_id;
|
||||
public string stage_category = "";
|
||||
public int reward_id = 0;
|
||||
/// <summary>
|
||||
/// Can be Normal or Hard
|
||||
/// </summary>
|
||||
public string chapter_mod = "";
|
||||
public string stage_type = "";
|
||||
public string enter_scenario = "";
|
||||
public string exit_scenario = "";
|
||||
}
|
||||
public class CampaignStageTable
|
||||
{
|
||||
public List<CampaignStageRecord> records;
|
||||
}
|
||||
public class RewardTableRecord
|
||||
{
|
||||
public int id;
|
||||
public int user_exp;
|
||||
public int character_exp;
|
||||
public RewardEntry[]? rewards;
|
||||
}
|
||||
public class RewardTable
|
||||
{
|
||||
public List<RewardTableRecord> records;
|
||||
}
|
||||
|
||||
public class RewardEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// example: 1000000
|
||||
/// </summary>
|
||||
public int reward_percent;
|
||||
public string percent_display_type = "";
|
||||
public string reward_type = "";
|
||||
public int reward_id;
|
||||
public int reward_value;
|
||||
}
|
||||
|
||||
|
||||
public class ClearedTutorialData
|
||||
{
|
||||
public int id;
|
||||
public int VersionGroup = 0;
|
||||
public int GroupId;
|
||||
public int ClearedStageId;
|
||||
public int NextId;
|
||||
public bool SaveTutorial;
|
||||
}
|
||||
public class TutorialTable
|
||||
{
|
||||
public List<ClearedTutorialData> records;
|
||||
}
|
||||
|
||||
public class CharacterLevelData
|
||||
{
|
||||
/// <summary>
|
||||
/// level
|
||||
/// </summary>
|
||||
public int level;
|
||||
/// <summary>
|
||||
/// can be CharacterLevel or SynchroLevel
|
||||
/// </summary>
|
||||
public string type = "";
|
||||
/// <summary>
|
||||
/// amount of credits required
|
||||
/// </summary>
|
||||
public int gold = 0;
|
||||
/// <summary>
|
||||
/// amount of battle data required
|
||||
/// </summary>
|
||||
public int character_exp = 0;
|
||||
/// <summary>
|
||||
/// amount of core dust required
|
||||
/// </summary>
|
||||
public int character_exp2 = 0;
|
||||
}
|
||||
|
||||
public class TacticAcademyLessonRecord
|
||||
{
|
||||
public CurrencyType CurrencyId;
|
||||
public int CurrencyValue;
|
||||
public int Id;
|
||||
public int GroupId;
|
||||
}
|
||||
|
||||
public class CampaignChapterRecord
|
||||
{
|
||||
public int id;
|
||||
public int chapter;
|
||||
public string field_id;
|
||||
public string hard_field_id;
|
||||
}
|
||||
public class CampaignChapterTable
|
||||
{
|
||||
public List<CampaignChapterRecord> records;
|
||||
}
|
||||
|
||||
public class CharacterRecord
|
||||
{
|
||||
public int id;
|
||||
// TODO: There is more stuff here but it isn't needed yet
|
||||
}
|
||||
public class CharacterTable
|
||||
{
|
||||
public List<CharacterRecord> records;
|
||||
}
|
||||
|
||||
public class ItemEquipRecord
|
||||
{
|
||||
public int id;
|
||||
public string item_sub_type;
|
||||
}
|
||||
public class ItemEquipTable
|
||||
{
|
||||
public List<ItemEquipRecord> records;
|
||||
}
|
||||
|
||||
public class FieldItemRecord
|
||||
{
|
||||
public int id;
|
||||
public string item_type;
|
||||
public int type_value;
|
||||
public bool is_final_reward;
|
||||
public string difficulty;
|
||||
}
|
||||
public class FieldItemTable
|
||||
{
|
||||
public List<FieldItemRecord> records;
|
||||
}
|
||||
}
|
||||
1
EpinelPS/Global.cs
Normal file
1
EpinelPS/Global.cs
Normal file
@@ -0,0 +1 @@
|
||||
global using EpinelPS.Models;
|
||||
@@ -1,14 +1,14 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Antibot
|
||||
namespace EpinelPS.LobbyServer.Antibot
|
||||
{
|
||||
[PacketPath("/antibot/battlereportdata")]
|
||||
public class BattleReportData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqBattleReportData>();
|
||||
var response = new ResBattleReportData();
|
||||
ReqBattleReportData req = await ReadData<ReqBattleReportData>();
|
||||
ResBattleReportData response = new();
|
||||
|
||||
// this is responsible for server side anticheat
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Antibot
|
||||
namespace EpinelPS.LobbyServer.Antibot
|
||||
{
|
||||
[PacketPath("/antibot/recvdata")]
|
||||
public class RecieveAntibotData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqAntibotRecvData>();
|
||||
ReqAntibotRecvData req = await ReadData<ReqAntibotRecvData>();
|
||||
|
||||
// I don't really care about reimplementing the server side anticheat, so return
|
||||
|
||||
var response = new ResAntibotRecvData();
|
||||
ResAntibotRecvData response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
36
EpinelPS/LobbyServer/Archive/ArchiveClearStage.cs
Normal file
36
EpinelPS/LobbyServer/Archive/ArchiveClearStage.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/storydungeon/clearstage")]
|
||||
public class ClearArchiveStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqClearArchiveStage req = await ReadData<ReqClearArchiveStage>(); // has fields EventId, StageId, BattleResult
|
||||
int evid = req.EventId;
|
||||
int stgid = req.StageId;
|
||||
int result = req.BattleResult;
|
||||
User user = GetUser();
|
||||
|
||||
// Check if the EventInfo exists for the given EventId
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? eventData))
|
||||
{
|
||||
throw new Exception($"Event with ID {evid} not found.");
|
||||
}
|
||||
|
||||
// Update the EventData if BattleResult is 1
|
||||
if (result == 1 && !eventData.ClearedStages.Contains(stgid))
|
||||
{
|
||||
eventData.ClearedStages.Add(stgid);
|
||||
// Update the LastStage in EventData
|
||||
eventData.LastStage = stgid;
|
||||
}
|
||||
JsonDb.Save();
|
||||
ResClearArchiveStage response = new();
|
||||
|
||||
// Send the response back to the client
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
EpinelPS/LobbyServer/Archive/ArchiveEnterStage.cs
Normal file
18
EpinelPS/LobbyServer/Archive/ArchiveEnterStage.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/storydungeon/enterstage")]
|
||||
public class EnterArchiveStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqEnterArchiveStage req = await ReadData<ReqEnterArchiveStage>();// has fields EventId StageId TeamNumber
|
||||
int evid = req.EventId;
|
||||
|
||||
ResEnterArchiveStage response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Archive
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/bookmark/scenario/exist")]
|
||||
public class CheckBookmarkScenarioExists : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqExistScenarioBookmark>();
|
||||
ReqExistScenarioBookmark req = await ReadData<ReqExistScenarioBookmark>();
|
||||
|
||||
var response = new ResExistScenarioBookmark();
|
||||
ResExistScenarioBookmark response = new();
|
||||
// TODO
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
37
EpinelPS/LobbyServer/Archive/CompleteArchiveScenario.cs
Normal file
37
EpinelPS/LobbyServer/Archive/CompleteArchiveScenario.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/scenario/complete")]
|
||||
public class CompleteScenario : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCompleteArchiveScenario req = await ReadData<ReqCompleteArchiveScenario>(); // req has EventId, ScenarioId, DialogType fields
|
||||
int evid = req.EventId;
|
||||
string scenid = req.ScenarioId;
|
||||
int dialtyp = req.DialogType;
|
||||
|
||||
User user = GetUser();
|
||||
|
||||
// Ensure we are working with the user's EventInfo and not CompletedScenarios
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? evt))
|
||||
{
|
||||
// Create a new EventData if the event doesn't exist
|
||||
evt = new EventData();
|
||||
user.EventInfo[evid] = evt;
|
||||
}
|
||||
|
||||
// Ensure the CompletedScenarios list is initialized and add the ScenarioId
|
||||
if (!evt.CompletedScenarios.Contains(scenid))
|
||||
{
|
||||
evt.CompletedScenarios.Add(scenid);
|
||||
}
|
||||
JsonDb.Save();
|
||||
// Prepare and send the response
|
||||
ResCompleteArchiveScenario response = new();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
EpinelPS/LobbyServer/Archive/FastClearArchiveStage.cs
Normal file
34
EpinelPS/LobbyServer/Archive/FastClearArchiveStage.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/storydungeon/fastclearstage")]
|
||||
public class FastClearArchiveStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqFastClearArchiveStage req = await ReadData<ReqFastClearArchiveStage>();
|
||||
int evid = req.EventId;
|
||||
int stgid = req.StageId;
|
||||
|
||||
User user = GetUser();
|
||||
|
||||
// Check if the EventInfo exists for the given EventId
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? eventData))
|
||||
{
|
||||
throw new Exception($"Event with ID {evid} not found.");
|
||||
}
|
||||
if (!eventData.ClearedStages.Contains(stgid))
|
||||
{
|
||||
eventData.ClearedStages.Add(stgid);
|
||||
// Update the LastStage in EventData
|
||||
eventData.LastStage = stgid;
|
||||
}
|
||||
JsonDb.Save();
|
||||
ResFastClearArchiveStage response = new();
|
||||
|
||||
// Send the response back to the client
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
EpinelPS/LobbyServer/Archive/GetArchiveStoryDungeon.cs
Normal file
51
EpinelPS/LobbyServer/Archive/GetArchiveStoryDungeon.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/storydungeon/get")]
|
||||
public class GetArchiveStoryDungeon : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArchiveStoryDungeon req = await ReadData<ReqGetArchiveStoryDungeon>(); // has EventId field
|
||||
int evid = req.EventId;
|
||||
User user = GetUser();
|
||||
|
||||
// Ensure the EventInfo dictionary contains the requested EventId
|
||||
if (!user.EventInfo.TryGetValue(evid, out EventData? eventData))
|
||||
{
|
||||
eventData = new EventData
|
||||
{
|
||||
CompletedScenarios = [], // Initialize empty list
|
||||
Diff = 0, // Default difficulty
|
||||
LastStage = 0 // Default last cleared stage
|
||||
};
|
||||
// Create a new default entry for the missing EventId
|
||||
user.EventInfo[evid] = eventData;
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
// Prepare the response
|
||||
ResGetArchiveStoryDungeon response = new()
|
||||
{
|
||||
// Populate team data
|
||||
TeamData = new NetUserTeamData
|
||||
{
|
||||
LastContentsTeamNumber = 1,
|
||||
Type = 1
|
||||
}
|
||||
};
|
||||
|
||||
// Populate the last cleared stage
|
||||
response.LastClearedArchiveStageList.Add(new NetLastClearedArchiveStage
|
||||
{
|
||||
DifficultyId = eventData.Diff,
|
||||
StageId = eventData.LastStage
|
||||
});
|
||||
|
||||
// Send the response
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
EpinelPS/LobbyServer/Archive/GetArchives.cs
Normal file
34
EpinelPS/LobbyServer/Archive/GetArchives.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/get")]
|
||||
public class GetArchives : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArchiveRecord req = await ReadData<ReqGetArchiveRecord>();
|
||||
|
||||
ResGetArchiveRecord response = new();
|
||||
|
||||
// Explicitly select IDs from the records
|
||||
List<int> allIds = [.. GameData.Instance.archiveRecordManagerTable.Values.Select(record => record.Id)];
|
||||
|
||||
// Add the IDs to the response lists
|
||||
response.ArchiveRecordManagerList.AddRange(allIds);
|
||||
response.UnlockedArchiveRecordList.AddRange(allIds);
|
||||
|
||||
// Get entries with record_type "EventQuest"
|
||||
List<ArchiveRecordManagerRecord> eventQuestRecords = [.. GameData.Instance.archiveRecordManagerTable.Values.Where(record => record.RecordType == ArchiveRecordType.EventQuest)];
|
||||
|
||||
response.ArchiveEventQuest = new();
|
||||
response.ArchiveEventQuest.UnlockedArchiveRecordManagerEventQuestIdList.AddRange(eventQuestRecords.Select(record => record.Id));
|
||||
// TODO more fields
|
||||
|
||||
|
||||
// TODO: allow unlocking
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Archive/GetMinigameData.cs
Normal file
21
EpinelPS/LobbyServer/Archive/GetMinigameData.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/minigame/getdata")]
|
||||
public class GetMinigameData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArchiveMiniGameData req = await ReadData<ReqGetArchiveMiniGameData>();
|
||||
|
||||
ResGetArchiveMiniGameData response = new()
|
||||
{
|
||||
Json = "{}"
|
||||
};
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
EpinelPS/LobbyServer/Archive/GetNonResettable.cs
Normal file
25
EpinelPS/LobbyServer/Archive/GetNonResettable.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using EpinelPS.Utils;
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/scenario/getnonresettable")]
|
||||
public class GetNonResettable : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetNonResettableArchiveScenario req = await ReadData<ReqGetNonResettableArchiveScenario>();
|
||||
ResGetNonResettableArchiveScenario response = new();
|
||||
|
||||
User user = GetUser();
|
||||
foreach (var (evtId, evtData) in user.EventInfo)
|
||||
{
|
||||
if (evtId == req.EventId)
|
||||
{
|
||||
response.ScenarioIdList.AddRange(evtData.CompletedScenarios);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
EpinelPS/LobbyServer/Archive/GetResettable.cs
Normal file
32
EpinelPS/LobbyServer/Archive/GetResettable.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/scenario/getresettable")]
|
||||
public class GetResettable : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetResettableArchiveScenario req = await ReadData<ReqGetResettableArchiveScenario>();
|
||||
ResGetResettableArchiveScenario response = new(); // has ScenarioIdList field that takes in strings
|
||||
|
||||
GameData gameData = GameData.Instance;
|
||||
User user = GetUser();
|
||||
foreach (ArchiveEventStoryRecord record in gameData.archiveEventStoryRecords.Values)
|
||||
{
|
||||
// Add the PrologueScenario to the ScenarioIdList
|
||||
if (record.EventId == req.EventId && !string.IsNullOrEmpty(record.PrologueScenario))
|
||||
{
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? evtData) &&
|
||||
evtData.CompletedScenarios.Contains(record.PrologueScenario))
|
||||
{
|
||||
response.ScenarioIdList.Add(record.PrologueScenario);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
EpinelPS/LobbyServer/Archive/MessengerGet.cs
Normal file
38
EpinelPS/LobbyServer/Archive/MessengerGet.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Archive
|
||||
{
|
||||
[PacketPath("/archive/messenger/get")]
|
||||
public class GetArchiveMessenger : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Read the request containing ArchiveMessengerGroupId
|
||||
ReqGetArchiveMessenger req = await ReadData<ReqGetArchiveMessenger>();
|
||||
int groupId = req.ArchiveMessengerGroupId;
|
||||
|
||||
// Initialize the response object
|
||||
ResGetArchiveMessenger response = new();
|
||||
|
||||
// Get the relevant data from ArchiveMessengerConditionTable
|
||||
GameData gameData = GameData.Instance;
|
||||
|
||||
if (gameData.archiveMessengerConditionRecords.TryGetValue(groupId, out ArchiveMessengerConditionRecord? conditionRecord))
|
||||
{
|
||||
foreach (var condition in conditionRecord.ArchiveMessengerConditionList)
|
||||
{
|
||||
// Add each condition as a NetArchiveMessage in the response
|
||||
response.ArchiveMessageList.Add(new NetArchiveMessage
|
||||
{
|
||||
ConditionId = condition.ConditionId,
|
||||
MessageId = conditionRecord.Tid // Correctly using tId as MessageId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Write the response back
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Arena/ChampionBadgeData.cs
Normal file
24
EpinelPS/LobbyServer/Arena/ChampionBadgeData.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/champion/getbadgedata")]
|
||||
public class ChampionBadgeData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetChampionArenaDataByBadge req = await ReadData<ReqGetChampionArenaDataByBadge>();
|
||||
ResGetChampionArenaDataByBadge response = new()
|
||||
{
|
||||
// TODO
|
||||
Schedule = new NetChampionArenaSchedule(),
|
||||
NextSchedule = new NetChampionArenaSchedule(),
|
||||
ChampionArenaContentsState = ChampionArenaContentsState.SeasonClosed,
|
||||
CurrentOrLastSeasonStartAt = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(5))
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Arena/GetArena.cs
Normal file
24
EpinelPS/LobbyServer/Arena/GetArena.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/get")]
|
||||
public class GetArena : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArena req = await ReadData<ReqGetArena>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetArena response = new()
|
||||
{
|
||||
BanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) },
|
||||
User = new NetArenaData() { User = LobbyHandler.CreateWholeUserDataFromDbUser(user) }
|
||||
};
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
EpinelPS/LobbyServer/Arena/GetArenaBanInfo.cs
Normal file
22
EpinelPS/LobbyServer/Arena/GetArenaBanInfo.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/getbaninfo")]
|
||||
public class GetArenaBanInfo : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetArenaBanInfo req = await ReadData<ReqGetArenaBanInfo>();
|
||||
|
||||
ResGetArenaBanInfo response = new()
|
||||
{
|
||||
RookieArenaBanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) },
|
||||
SpecialArenaBanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) }
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
EpinelPS/LobbyServer/Arena/GetChampion.cs
Normal file
23
EpinelPS/LobbyServer/Arena/GetChampion.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/champion/get")]
|
||||
public class GetChampion : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetChampionArena req = await ReadData<ReqGetChampionArena>();
|
||||
|
||||
ResGetChampionArena response = new()
|
||||
{
|
||||
Schedule = new NetChampionArenaSchedule()
|
||||
};
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
EpinelPS/LobbyServer/Arena/GetSpecialArena.cs
Normal file
23
EpinelPS/LobbyServer/Arena/GetSpecialArena.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/special/get")]
|
||||
public class GetSpecialArena : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetSpecialArena req = await ReadData<ReqGetSpecialArena>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetSpecialArena response = new()
|
||||
{
|
||||
BanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) },
|
||||
User = new NetArenaData() { User = LobbyHandler.CreateWholeUserDataFromDbUser(user) }
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Arena/ShowSpecialArenaReward.cs
Normal file
21
EpinelPS/LobbyServer/Arena/ShowSpecialArenaReward.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Arena
|
||||
{
|
||||
[PacketPath("/arena/special/showreward")]
|
||||
public class ShowSpecialArenaReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqShowSpecialArenaReward req = await ReadData<ReqShowSpecialArenaReward>();
|
||||
|
||||
ResShowSpecialArenaReward response = new()
|
||||
{
|
||||
IsBan = true,
|
||||
BanInfo = new NetArenaBanInfo() { Description = "Not Implemented", StartAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), EndAt = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow.AddYears(10)) }
|
||||
};
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Auth
|
||||
namespace EpinelPS.LobbyServer.Auth
|
||||
{
|
||||
[PacketPath("/auth/logout")]
|
||||
public class AuthLogout : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqLogout>();
|
||||
ReqLogout req = await ReadData<ReqLogout>();
|
||||
|
||||
JsonDb.Instance.GameClientTokens.Remove(UsedAuthToken);
|
||||
// TODO remove UsedAuthToken
|
||||
|
||||
await WriteDataAsync(new ResLogout());
|
||||
}
|
||||
63
EpinelPS/LobbyServer/Auth/DoEnterServer.cs
Normal file
63
EpinelPS/LobbyServer/Auth/DoEnterServer.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Paseto.Builder;
|
||||
using Paseto;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Auth
|
||||
{
|
||||
[PacketPath("/auth/enterserver")]
|
||||
public class GetUserOnlineStateLog : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqEnterServer req = await ReadData<ReqEnterServer>();
|
||||
|
||||
// request has auth token
|
||||
UsedAuthToken = req.AuthToken;
|
||||
foreach (AccessToken item in JsonDb.Instance.LauncherAccessTokens)
|
||||
{
|
||||
if (item.Token == UsedAuthToken)
|
||||
{
|
||||
UserId = item.UserID;
|
||||
}
|
||||
}
|
||||
if (UserId == 0) throw new BadHttpRequestException("unknown auth token", 403);
|
||||
User user = GetUser();
|
||||
|
||||
GameClientInfo rsp = LobbyHandler.GenGameClientTok(req.ClientPublicKey, UserId);
|
||||
|
||||
string token = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
|
||||
.WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey)
|
||||
.AddClaim("userId", UserId)
|
||||
.IssuedAt(DateTime.UtcNow)
|
||||
.Expiration(DateTime.UtcNow.AddDays(2))
|
||||
.Encode();
|
||||
|
||||
string encryptionToken = new PasetoBuilder().Use(ProtocolVersion.V4, Purpose.Local)
|
||||
.WithKey(JsonDb.Instance.LauncherTokenKey, Encryption.SymmetricKey)
|
||||
.AddClaim("data", JsonSerializer.Serialize(rsp))
|
||||
.IssuedAt(DateTime.UtcNow)
|
||||
.Expiration(DateTime.UtcNow.AddDays(2))
|
||||
.Encode();
|
||||
|
||||
|
||||
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();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,18 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Auth
|
||||
namespace EpinelPS.LobbyServer.Auth
|
||||
{
|
||||
[PacketPath("/auth/intl")]
|
||||
public class DoIntlAuth : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqAuthIntl>();
|
||||
var response = new ResAuth();
|
||||
ReqAuthIntl req = await ReadData<ReqAuthIntl>();
|
||||
ResAuth response = new();
|
||||
|
||||
UsedAuthToken = req.Token;
|
||||
foreach (var item in JsonDb.Instance.LauncherAccessTokens)
|
||||
foreach (AccessToken item in JsonDb.Instance.LauncherAccessTokens)
|
||||
{
|
||||
if (item.Token == UsedAuthToken)
|
||||
{
|
||||
@@ -22,11 +22,11 @@ namespace EpinelPS.LobbyServer.Msgs.Auth
|
||||
}
|
||||
if (UserId == 0)
|
||||
{
|
||||
response.AuthError = new NetAuthError() { ErrorCode = AuthErrorCode.AuthErrorCodeError };
|
||||
response.AuthError = new NetAuthError() { ErrorCode = AuthErrorCode.Error };
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = GetUser();
|
||||
User user = GetUser();
|
||||
|
||||
if (user.IsBanned && user.BanEnd < DateTime.UtcNow)
|
||||
{
|
||||
@@ -43,7 +43,7 @@ namespace EpinelPS.LobbyServer.Msgs.Auth
|
||||
}
|
||||
else
|
||||
{
|
||||
response.AuthSuccess = new NetAuthSuccess() { AuthToken = req.Token, CentauriZoneId = "84", FirstAuth = false, PurchaseRestriction = new NetUserPurchaseRestriction() { PurchaseRestriction = PurchaseRestriction.PurchaseRestrictionChild, UpdatedAt = 638546758794611090 } };
|
||||
response.AuthSuccess = new NetAuthSuccess() { AuthToken = req.Token, CentauriZoneId = "84", FirstAuth = false, PurchaseRestriction = new NetUserPurchaseRestriction() { PurchaseRestriction = PurchaseRestriction.Child, UpdatedAt = 638546758794611090 } };
|
||||
}
|
||||
}
|
||||
|
||||
26
EpinelPS/LobbyServer/Badge/DeleteBadge.cs
Normal file
26
EpinelPS/LobbyServer/Badge/DeleteBadge.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Badge
|
||||
{
|
||||
[PacketPath("/badge/delete")]
|
||||
public class DeleteBadge : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqDeleteBadge req = await ReadData<ReqDeleteBadge>();
|
||||
User user = GetUser();
|
||||
|
||||
ResDeleteBadge response = new();
|
||||
|
||||
foreach (long badgeId in req.BadgeSeqList)
|
||||
{
|
||||
user.Badges.RemoveAll(x => x.Seq == badgeId);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Badge/PermanentContent.cs
Normal file
21
EpinelPS/LobbyServer/Badge/PermanentContent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Badge
|
||||
{
|
||||
[PacketPath("/badge/permanentcontent")]
|
||||
public class PermanentContent : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqPermanentContentBadgeData req = await ReadData<ReqPermanentContentBadgeData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResPermanentContentBadgeData response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpinelPS/LobbyServer/Badge/SyncBadge.cs
Normal file
24
EpinelPS/LobbyServer/Badge/SyncBadge.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Badge
|
||||
{
|
||||
[PacketPath("/badge/sync")]
|
||||
public class SyncBadge : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSyncBadge req = await ReadData<ReqSyncBadge>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSyncBadge response = new();
|
||||
|
||||
foreach (BadgeModel item in user.Badges)
|
||||
{
|
||||
response.BadgeList.Add(item.ToNet());
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Campaign
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/shutdownflags/campaignpackage/getall")]
|
||||
public class CampaignPackageGetAll : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqCampaignPackageGetAllShutdownFlags>();
|
||||
ReqCampaignPackageGetAllShutdownFlags req = await ReadData<ReqCampaignPackageGetAllShutdownFlags>();
|
||||
|
||||
var response = new ResCampaignPackageGetAllShutdownFlags();
|
||||
ResCampaignPackageGetAllShutdownFlags response = new();
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
@@ -1,27 +1,29 @@
|
||||
using EpinelPS.LobbyServer.Msgs.Stage;
|
||||
using EpinelPS.StaticInfo;
|
||||
using EpinelPS.LobbyServer.Stage;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Campaign
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/getfield")]
|
||||
public class GetCampaignField : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqGetCampaignFieldData>();
|
||||
var user = GetUser();
|
||||
ReqGetCampaignFieldData req = await ReadData<ReqGetCampaignFieldData>();
|
||||
User user = GetUser();
|
||||
|
||||
Console.WriteLine("Map ID: " + req.MapId);
|
||||
|
||||
var response = new ResGetCampaignFieldData();
|
||||
response.Field = GetStage.CreateFieldInfo(user, GameData.Instance.GetNormalChapterNumberFromFieldName(req.MapId), req.MapId.Contains("hard") ? "Hard" : "Normal");
|
||||
ResGetCampaignFieldData response = new()
|
||||
{
|
||||
Field = GetStage.CreateFieldInfo(user, req.MapId, out bool bossEntered),
|
||||
|
||||
// todo save this data
|
||||
response.Team = new NetUserTeamData() { LastContentsTeamNumber = 1, Type = 1 };
|
||||
// todo save this data
|
||||
Team = new NetUserTeamData() { LastContentsTeamNumber = 1, Type = 1 }
|
||||
};
|
||||
if (user.LastNormalStageCleared >= 6000003)
|
||||
{
|
||||
var team = new NetTeamData() { TeamNumber = 1 };
|
||||
NetTeamData team = new() { TeamNumber = 1 };
|
||||
team.Slots.Add(new NetTeamSlot() { Slot = 1, Value = 47263455 });
|
||||
team.Slots.Add(new NetTeamSlot() { Slot = 2, Value = 47263456 });
|
||||
team.Slots.Add(new NetTeamSlot() { Slot = 3, Value = 47263457 });
|
||||
@@ -33,14 +35,14 @@ namespace EpinelPS.LobbyServer.Msgs.Campaign
|
||||
}
|
||||
|
||||
string resultingJson;
|
||||
if (!user.MapJson.ContainsKey(req.MapId))
|
||||
if (!user.MapJson.TryGetValue(req.MapId, out string? value))
|
||||
{
|
||||
resultingJson = "";
|
||||
user.MapJson.Add(req.MapId, resultingJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultingJson = user.MapJson[req.MapId];
|
||||
resultingJson = value;
|
||||
}
|
||||
|
||||
response.Json = resultingJson;
|
||||
30
EpinelPS/LobbyServer/Campaign/GetFieldObjectsCount.cs
Normal file
30
EpinelPS/LobbyServer/Campaign/GetFieldObjectsCount.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.LobbyServer.Stage;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/getfieldobjectitemsnum")]
|
||||
public class GetFieldObjectsCount : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetCampaignFieldObjectItemsNum req = await ReadData<ReqGetCampaignFieldObjectItemsNum>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetCampaignFieldObjectItemsNum response = new();
|
||||
|
||||
foreach (KeyValuePair<string, FieldInfoNew> map in user.FieldInfoNew)
|
||||
{
|
||||
response.FieldObjectItemsNum.Add(new NetCampaignFieldObjectItemsNum()
|
||||
{
|
||||
MapId = map.Key,
|
||||
Count = map.Value.CompletedObjects.Where(x => x.Type == 1).Count()
|
||||
});
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
EpinelPS/LobbyServer/Campaign/ObtainItem.cs
Normal file
52
EpinelPS/LobbyServer/Campaign/ObtainItem.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.LobbyServer.Stage;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/obtain/item")]
|
||||
public class ObtainItem : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqObtainCampaignItem req = await ReadData<ReqObtainCampaignItem>();
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainCampaignItem response = new();
|
||||
|
||||
if (!user.FieldInfoNew.TryGetValue(req.MapId, out FieldInfoNew? field))
|
||||
{
|
||||
field = new FieldInfoNew();
|
||||
user.FieldInfoNew.Add(req.MapId, field);
|
||||
}
|
||||
|
||||
|
||||
foreach (NetFieldObject item in field.CompletedObjects)
|
||||
{
|
||||
if (item.PositionId == req.FieldObject.PositionId)
|
||||
{
|
||||
Logging.WriteLine("attempted to collect campaign field object twice!", LogType.WarningAntiCheat);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Register and return reward
|
||||
|
||||
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");
|
||||
|
||||
FieldItemRecord positionReward = GameData.Instance.FieldItems[position.ItemId];
|
||||
RewardRecord reward = GameData.Instance.GetRewardTableEntry(positionReward.TypeValue) ?? throw new Exception("failed to get reward");
|
||||
response.Reward = RewardUtils.RegisterRewardsForUser(user, reward);
|
||||
|
||||
// HIde it from the field
|
||||
field.CompletedObjects.Add(new NetFieldObject() { PositionId = req.FieldObject.PositionId, Type = req.FieldObject.Type});
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Campaign
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/savefield")]
|
||||
public class SaveField : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqSaveCampaignField>();
|
||||
var user = GetUser();
|
||||
ReqSaveCampaignField req = await ReadData<ReqSaveCampaignField>();
|
||||
User user = GetUser();
|
||||
|
||||
var response = new ResGetFieldTalkList();
|
||||
|
||||
Console.WriteLine($"save {req.MapId} with {req.Json}");
|
||||
ResSaveCampaignField response = new();
|
||||
|
||||
if (!user.MapJson.ContainsKey(req.MapId))
|
||||
{
|
||||
27
EpinelPS/LobbyServer/Campaign/SaveFieldObject.cs
Normal file
27
EpinelPS/LobbyServer/Campaign/SaveFieldObject.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Campaign
|
||||
{
|
||||
[PacketPath("/campaign/savefieldobject")]
|
||||
public class SaveFieldObject : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSaveCampaignFieldObject req = await ReadData<ReqSaveCampaignFieldObject>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSaveCampaignFieldObject response = new();
|
||||
|
||||
Logging.WriteLine($"save {req.MapId} with {req.FieldObject.PositionId}", LogType.Debug);
|
||||
|
||||
FieldInfoNew field = user.FieldInfoNew[req.MapId];
|
||||
|
||||
field.CompletedObjects.Add(new NetFieldObject() { PositionId = req.FieldObject.PositionId, Json = req.FieldObject.Json, Type = req.FieldObject.Type });
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,38 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/SynchroDevice/Change")]
|
||||
public class ChangeSynchroDevice : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqSynchroChange>();
|
||||
var user = GetUser();
|
||||
ReqSynchroChange req = await ReadData<ReqSynchroChange>();
|
||||
User user = GetUser();
|
||||
|
||||
var response = new ResSynchroChange();
|
||||
ResSynchroChange response = new();
|
||||
|
||||
var highestLevelCharacters = user.Characters.OrderByDescending(x => x.Level).Take(5).ToList();
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
int slot = 1;
|
||||
foreach (var item in highestLevelCharacters)
|
||||
foreach (CharacterModel? item in highestLevelCharacters)
|
||||
{
|
||||
if (item.Level != 200)
|
||||
{
|
||||
throw new Exception("expected level to be 200");
|
||||
}
|
||||
|
||||
response.Characters.Add(new NetUserCharacterData() { Default = new() { Csn = item.Csn, Skill1Lv = item.Skill1Lvl, Skill2Lv = item.Skill2Lvl, CostumeId = item.CostumeId, Level = item.Level, Grade = item.Grade, Tid = item.Tid, UltiSkillLv = item.UltimateLevel }, IsSynchro = user.GetSynchro(item.Csn) });
|
||||
response.Characters.Add(new NetUserCharacterData() { Default = new() { Csn = item.Csn, Skill1Lv = item.Skill1Lvl, Skill2Lv = item.Skill2Lvl, CostumeId = item.CostumeId, Lv = item.Level, Grade = item.Grade, Tid = item.Tid, UltiSkillLv = item.UltimateLevel }, IsSynchro = user.GetSynchro(item.Csn) });
|
||||
|
||||
|
||||
|
||||
foreach (var s in user.SynchroSlots)
|
||||
foreach (SynchroSlot s in user.SynchroSlots)
|
||||
{
|
||||
if (s.Slot == slot)
|
||||
{
|
||||
@@ -44,10 +45,13 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
|
||||
user.SynchroDeviceUpgraded = true;
|
||||
|
||||
foreach (var item in user.SynchroSlots)
|
||||
foreach (SynchroSlot item in user.SynchroSlots)
|
||||
{
|
||||
response.Slots.Add(new NetSynchroSlot() { Slot = item.Slot, AvailableRegisterAt = item.AvailableAt, Csn = item.CharacterSerialNumber });
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
18
EpinelPS/LobbyServer/Character/Counsel/Check.cs
Normal file
18
EpinelPS/LobbyServer/Character/Counsel/Check.cs
Normal 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()
|
||||
{
|
||||
ReqCounseledBefore req = await ReadData<ReqCounseledBefore>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCounseledBefore response = new();
|
||||
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
18
EpinelPS/LobbyServer/Character/Counsel/CheckCounsel.cs
Normal file
18
EpinelPS/LobbyServer/Character/Counsel/CheckCounsel.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel;
|
||||
|
||||
[PacketPath("/character/counsel/check")]
|
||||
public class CheckCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCounseledBefore req = await ReadData<ReqCounseledBefore>();
|
||||
|
||||
ResCounseledBefore response = new();
|
||||
|
||||
response.IsCounseledBefore = false;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
99
EpinelPS/LobbyServer/Character/Counsel/DoCounsel.cs
Normal file
99
EpinelPS/LobbyServer/Character/Counsel/DoCounsel.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
[PacketPath("/character/attractive/counsel")]
|
||||
public class DoCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterCounsel req = await ReadData<ReqCharacterCounsel>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterCounsel response = new();
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
|
||||
NetUserAttractiveData? currentBondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
|
||||
if (currentBondInfo != null)
|
||||
{
|
||||
int beforeLv = currentBondInfo.Lv;
|
||||
int beforeExp = currentBondInfo.Exp;
|
||||
|
||||
currentBondInfo.Exp += 100;
|
||||
currentBondInfo.CounseledCount++;
|
||||
currentBondInfo.CanCounselToday = true; // Always allow counseling
|
||||
UpdateAttractiveLevel(currentBondInfo);
|
||||
|
||||
response.Attractive = currentBondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = currentBondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = currentBondInfo.Lv,
|
||||
CurrentExp = currentBondInfo.Exp,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
NetUserAttractiveData data = new NetUserAttractiveData()
|
||||
{
|
||||
NameCode = req.NameCode,
|
||||
Exp = 100,
|
||||
CounseledCount = 1,
|
||||
IsFavorites = false,
|
||||
CanCounselToday = true,
|
||||
Lv = 1
|
||||
};
|
||||
UpdateAttractiveLevel(data);
|
||||
user.BondInfo.Add(data);
|
||||
response.Attractive = data;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = data.NameCode,
|
||||
BeforeLv = 1,
|
||||
BeforeExp = 0,
|
||||
CurrentLv = 1,
|
||||
CurrentExp = 100,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.FirstOrDefault(x => x.Value.AttractiveLevel == attractiveData.Lv).Value;
|
||||
|
||||
if (levelInfo == null)
|
||||
{
|
||||
// No more level data
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.AttractivePoint)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.AttractivePoint;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
EpinelPS/LobbyServer/Character/Counsel/Present.cs
Normal file
114
EpinelPS/LobbyServer/Character/Counsel/Present.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
[PacketPath("/character/attractive/present")]
|
||||
public class Present : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterPresent req = await ReadData<ReqCharacterPresent>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterPresent response = new ResCharacterPresent();
|
||||
|
||||
NetUserAttractiveData? bondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
if (bondInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int totalExpGained = 0;
|
||||
CharacterRecord? characterRecord = GameData.Instance.CharacterTable.Values.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
|
||||
foreach (NetItemData item in req.Items)
|
||||
{
|
||||
ItemMaterialRecord? materialInfo = GameData.Instance.itemMaterialTable.GetValueOrDefault(item.Tid);
|
||||
if (materialInfo != null && materialInfo.ItemSubType == ItemSubType.AttractiveMaterial)
|
||||
{
|
||||
int expGained = materialInfo.ItemValue * (int)item.Count;
|
||||
|
||||
if (characterRecord != null)
|
||||
{
|
||||
if (materialInfo.MaterialType == MaterialType.Corporation)
|
||||
{
|
||||
string corporation = materialInfo.NameLocalkey.Split('_')[2];
|
||||
if (corporation.Equals(characterRecord.Corporation.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
expGained *= 5;
|
||||
}
|
||||
}
|
||||
else if (materialInfo.MaterialType == MaterialType.Squad)
|
||||
{
|
||||
string squad = materialInfo.NameLocalkey.Split('_')[2];
|
||||
if (squad.Equals(characterRecord.Squad.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
expGained *= 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalExpGained += expGained;
|
||||
|
||||
ItemData? userItem = user.Items.FirstOrDefault(x => x.ItemType == item.Tid);
|
||||
if (userItem != null)
|
||||
{
|
||||
userItem.Count -= (int)item.Count;
|
||||
if (userItem.Count <= 0)
|
||||
{
|
||||
user.Items.Remove(userItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int beforeLv = bondInfo.Lv;
|
||||
int beforeExp = bondInfo.Exp;
|
||||
|
||||
bondInfo.Exp += totalExpGained;
|
||||
UpdateAttractiveLevel(bondInfo);
|
||||
|
||||
response.Attractive = bondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = bondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = bondInfo.Lv,
|
||||
CurrentExp = bondInfo.Exp,
|
||||
GainExp = totalExpGained
|
||||
};
|
||||
|
||||
response.Items.AddRange(NetUtils.GetUserItems(user));
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.Values.FirstOrDefault(x => x.AttractiveLevel == attractiveData.Lv);
|
||||
|
||||
if (levelInfo == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.AttractivePoint)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.AttractivePoint;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
EpinelPS/LobbyServer/Character/Counsel/QuickCounsel.cs
Normal file
99
EpinelPS/LobbyServer/Character/Counsel/QuickCounsel.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
[PacketPath("/character/counsel/quick")]
|
||||
public class QuickCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterQuickCounsel req = await ReadData<ReqCharacterQuickCounsel>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterQuickCounsel response = new ResCharacterQuickCounsel();
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
|
||||
NetUserAttractiveData? bondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
|
||||
if (bondInfo != null)
|
||||
{
|
||||
int beforeLv = bondInfo.Lv;
|
||||
int beforeExp = bondInfo.Exp;
|
||||
|
||||
bondInfo.Exp += 100;
|
||||
bondInfo.CounseledCount++;
|
||||
bondInfo.CanCounselToday = true; // Always allow counseling
|
||||
UpdateAttractiveLevel(bondInfo);
|
||||
|
||||
response.Attractive = bondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = bondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = bondInfo.Lv,
|
||||
CurrentExp = bondInfo.Exp,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
NetUserAttractiveData data = new NetUserAttractiveData()
|
||||
{
|
||||
NameCode = req.NameCode,
|
||||
Exp = 100,
|
||||
CounseledCount = 1,
|
||||
IsFavorites = false,
|
||||
CanCounselToday = true,
|
||||
Lv = 1
|
||||
};
|
||||
UpdateAttractiveLevel(data);
|
||||
user.BondInfo.Add(data);
|
||||
response.Attractive = data;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = data.NameCode,
|
||||
BeforeLv = 1,
|
||||
BeforeExp = 0,
|
||||
CurrentLv = 1,
|
||||
CurrentExp = 100,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.Values.FirstOrDefault(x => x.AttractiveLevel == attractiveData.Lv);
|
||||
|
||||
if (levelInfo == null)
|
||||
{
|
||||
// No more level data
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.AttractivePoint)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.AttractivePoint;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
EpinelPS/LobbyServer/Character/DoLimitBreak.cs
Normal file
70
EpinelPS/LobbyServer/Character/DoLimitBreak.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/upgrade")]
|
||||
public class DoLimitBreak : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Read the incoming request that contains the current CSN and ISN
|
||||
ReqCharacterUpgrade req = await ReadData<ReqCharacterUpgrade>(); // Contains csn and isn (read-only)
|
||||
ResCharacterUpgrade response = new();
|
||||
User user = GetUser();
|
||||
|
||||
// Get all character data from the game's character table
|
||||
List<CharacterRecord> fullchardata = [.. GameData.Instance.CharacterTable.Values];
|
||||
|
||||
CharacterModel targetCharacter = user.GetCharacterBySerialNumber(req.Csn) ?? throw new NullReferenceException();
|
||||
|
||||
// Find the element with the current csn from the request
|
||||
CharacterRecord currentCharacter = fullchardata.FirstOrDefault(c => c.Id == targetCharacter.Tid) ?? throw new NullReferenceException();
|
||||
|
||||
if (currentCharacter != null && targetCharacter != null)
|
||||
{
|
||||
if (currentCharacter.GradeCoreId == 103 || currentCharacter.GradeCoreId == 11 || currentCharacter.GradeCoreId == 201)
|
||||
{
|
||||
Console.WriteLine("cannot limit break any further!");
|
||||
await WriteDataAsync(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a new CSN based on the `NameCode` of the current character and `GradeCoreId + 1`
|
||||
// For some reason, there is a seperate character for each limit/core break value.
|
||||
CharacterRecord? newCharacter = fullchardata.FirstOrDefault(c => c.NameCode == currentCharacter.NameCode && c.GradeCoreId == currentCharacter.GradeCoreId + 1);
|
||||
|
||||
|
||||
if (newCharacter != null)
|
||||
{
|
||||
// replace character in DB with new character
|
||||
targetCharacter.Grade++;
|
||||
targetCharacter.Tid = newCharacter.Id;
|
||||
|
||||
response.Character = new NetUserCharacterDefaultData()
|
||||
{
|
||||
Csn = req.Csn,
|
||||
CostumeId = targetCharacter.CostumeId,
|
||||
Grade = targetCharacter.Grade,
|
||||
Lv = user.GetSynchroLevel(),
|
||||
Skill1Lv = targetCharacter.Skill1Lvl,
|
||||
Skill2Lv = targetCharacter.Skill2Lvl,
|
||||
Tid = targetCharacter.Tid,
|
||||
UltiSkillLv = targetCharacter.UltimateLevel
|
||||
};
|
||||
|
||||
// remove spare body item
|
||||
ItemData bodyItem = user.Items.FirstOrDefault(i => i.Isn == req.Isn) ?? throw new NullReferenceException();
|
||||
user.RemoveItemBySerialNumber(req.Isn, 1);
|
||||
response.Items.Add(NetUtils.ToNet(bodyItem));
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
}
|
||||
|
||||
// Send the response back to the client
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EpinelPS/LobbyServer/Character/GetCharacterAttractiveList.cs
Normal file
29
EpinelPS/LobbyServer/Character/GetCharacterAttractiveList.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/attractive/get")]
|
||||
public class GetCharacterAttractiveList : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetAttractiveList req = await ReadData<ReqGetAttractiveList>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetAttractiveList response = new()
|
||||
{
|
||||
CounselAvailableCount = 3 // TODO
|
||||
};
|
||||
|
||||
foreach (NetUserAttractiveData item in user.BondInfo)
|
||||
{
|
||||
response.Attractives.Add(item);
|
||||
item.CanCounselToday = true;
|
||||
|
||||
}
|
||||
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
using EpinelPS.StaticInfo;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/costume/get")]
|
||||
public class GetCharacterCostume : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqGetCharacterCostumeData>();
|
||||
ReqGetCharacterCostumeData req = await ReadData<ReqGetCharacterCostumeData>();
|
||||
|
||||
var response = new ResGetCharacterCostumeData();
|
||||
ResGetCharacterCostumeData response = new();
|
||||
|
||||
// return all
|
||||
response.CostumeIds.AddRange(GameData.Instance.GetAllCostumes());
|
||||
49
EpinelPS/LobbyServer/Character/GetCharacterData.cs
Normal file
49
EpinelPS/LobbyServer/Character/GetCharacterData.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/get")]
|
||||
public class GetCharacterData : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetCharacterData req = await ReadData<ReqGetCharacterData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetCharacterData response = new();
|
||||
foreach (CharacterModel item in user.Characters)
|
||||
{
|
||||
response.Character.Add(new NetUserCharacterData()
|
||||
{
|
||||
Default = new()
|
||||
{
|
||||
Csn = item.Csn,
|
||||
Skill1Lv = item.Skill1Lvl,
|
||||
Skill2Lv = item.Skill2Lvl,
|
||||
CostumeId = item.CostumeId,
|
||||
Lv = user.GetCharacterLevel(item.Csn, item.Level),
|
||||
Grade = item.Grade,
|
||||
Tid = item.Tid,
|
||||
UltiSkillLv = item.UltimateLevel
|
||||
},
|
||||
IsSynchro = user.GetSynchro(item.Csn)
|
||||
});
|
||||
|
||||
// Check if character is main force
|
||||
if (item.IsMainForce)
|
||||
{
|
||||
response.MainForceCsnList.Add(item.Csn);
|
||||
}
|
||||
}
|
||||
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
foreach (CharacterModel? c in highestLevelCharacters)
|
||||
{
|
||||
response.SynchroStandardCharacters.Add(c.Csn);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,41 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/synchrodevice/get")]
|
||||
public class GetSynchrodevice : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqGetSynchroData>();
|
||||
var user = GetUser();
|
||||
ReqGetSynchroData req = await ReadData<ReqGetSynchroData>();
|
||||
User user = GetUser();
|
||||
|
||||
if (user.SynchroSlots.Count == 0)
|
||||
{
|
||||
|
||||
user.SynchroSlots = new() {
|
||||
user.SynchroSlots = [
|
||||
new SynchroSlot() { Slot = 1 },
|
||||
new SynchroSlot() { Slot = 2},
|
||||
new SynchroSlot() { Slot = 3 },
|
||||
new SynchroSlot() { Slot = 4 },
|
||||
new SynchroSlot() { Slot = 5 },
|
||||
|
||||
new SynchroSlot() { Slot = 6 },
|
||||
new SynchroSlot() { Slot = 7 },
|
||||
new SynchroSlot() { Slot = 8 },
|
||||
new SynchroSlot() { Slot = 9 },
|
||||
new SynchroSlot() { Slot = 10 },
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
var highestLevelCharacters = user.Characters.OrderByDescending(x => x.Level).Take(5).ToList();
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
var response = new ResGetSynchroData();
|
||||
response.Synchro = new NetUserSynchroData();
|
||||
|
||||
foreach (var item in highestLevelCharacters)
|
||||
ResGetSynchroData response = new()
|
||||
{
|
||||
response.Synchro.StandardCharacters.Add(new NetUserCharacterData() { Default = new() { Csn = item.Csn, Skill1Lv = item.Skill1Lvl, Skill2Lv = item.Skill2Lvl, CostumeId = item.CostumeId, Level = item.Level, Grade = item.Grade, Tid = item.Tid, UltiSkillLv = item.UltimateLevel }, IsSynchro = user.GetSynchro(item.Csn) });
|
||||
Synchro = new NetUserSynchroData()
|
||||
};
|
||||
|
||||
foreach (CharacterModel? item in highestLevelCharacters)
|
||||
{
|
||||
response.Synchro.StandardCharacters.Add(new NetUserCharacterData() { Default = new() { Csn = item.Csn, Skill1Lv = item.Skill1Lvl, Skill2Lv = item.Skill2Lvl, CostumeId = item.CostumeId, Lv = item.Level, Grade = item.Grade, Tid = item.Tid, UltiSkillLv = item.UltimateLevel }, IsSynchro = user.GetSynchro(item.Csn) });
|
||||
}
|
||||
|
||||
foreach (var item in user.SynchroSlots)
|
||||
foreach (SynchroSlot item in user.SynchroSlots)
|
||||
{
|
||||
response.Synchro.Slots.Add(new NetSynchroSlot() { Slot = item.Slot, AvailableRegisterAt = 1, Csn = item.CharacterSerialNumber });
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.StaticInfo;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/levelup")]
|
||||
public class LevelUp : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqCharacterLevelUp>();
|
||||
var user = GetUser();
|
||||
var response = new ResCharacterLevelUp();
|
||||
var data = GameData.Instance.GetCharacterLevelUpData();
|
||||
ReqCharacterLevelUp req = await ReadData<ReqCharacterLevelUp>();
|
||||
User user = GetUser();
|
||||
ResCharacterLevelUp response = new();
|
||||
Dictionary<int, CharacterLevelRecord> data = GameData.Instance.GetCharacterLevelUpData();
|
||||
|
||||
foreach (var item in user.Characters.ToArray())
|
||||
foreach (CharacterModel item in user.Characters.ToArray())
|
||||
{
|
||||
if (item.Csn == req.Csn)
|
||||
{
|
||||
int requiredCredit = 0;
|
||||
int requiredBattleData = 0;
|
||||
int requiredCoreDust = 0;
|
||||
for (int i = item.Level; i < req.Level; i++)
|
||||
for (int i = item.Level; i < req.Lv; i++)
|
||||
{
|
||||
var levelUpData = data[i];
|
||||
requiredCredit += levelUpData.gold;
|
||||
requiredBattleData += levelUpData.character_exp;
|
||||
requiredCoreDust += levelUpData.character_exp2;
|
||||
CharacterLevelRecord levelUpData = data[i];
|
||||
requiredCredit += levelUpData.Gold;
|
||||
requiredBattleData += levelUpData.CharacterExp;
|
||||
requiredCoreDust += levelUpData.CharacterExp2;
|
||||
}
|
||||
|
||||
if (user.CanSubtractCurrency(CurrencyType.Gold, requiredCredit) &&
|
||||
@@ -36,12 +36,11 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
user.SubtractCurrency(CurrencyType.Gold, requiredCredit);
|
||||
user.SubtractCurrency(CurrencyType.CharacterExp, requiredBattleData);
|
||||
user.SubtractCurrency(CurrencyType.CharacterExp2, requiredCoreDust);
|
||||
item.Level = req.Level;
|
||||
item.Level = req.Lv;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TOOD: log this
|
||||
Console.WriteLine("ERROR: Not enough currency for upgrade");
|
||||
Logging.WriteLine("ERROR: Not enough currency for upgrade", LogType.WarningAntiCheat);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,23 +48,23 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
{
|
||||
CostumeId = item.CostumeId,
|
||||
Csn = item.Csn,
|
||||
Level = item.Level,
|
||||
Lv = item.Level,
|
||||
Skill1Lv = item.Skill1Lvl,
|
||||
Skill2Lv = item.Skill2Lvl,
|
||||
UltiSkillLv = item.UltimateLevel,
|
||||
Grade = item.Grade,
|
||||
Tid = item.Tid
|
||||
};
|
||||
var highestLevelCharacters = user.Characters.OrderByDescending(x => x.Level).Take(5).ToList();
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
response.SynchroLv = user.GetSynchroLevel();
|
||||
|
||||
foreach (var c in highestLevelCharacters)
|
||||
foreach (CharacterModel? c in highestLevelCharacters)
|
||||
{
|
||||
response.SynchroStandardCharacters.Add(c.Csn);
|
||||
}
|
||||
|
||||
foreach (var currency in user.Currency)
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
@@ -73,6 +72,8 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
user.AddTrigger(Trigger.CharacterLevelUpCount, 1);
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
39
EpinelPS/LobbyServer/Character/ObtainEpReward.cs
Normal file
39
EpinelPS/LobbyServer/Character/ObtainEpReward.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/attractive/obtainreward")]
|
||||
public class ObtainEpReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqObtainAttractiveReward req = await ReadData<ReqObtainAttractiveReward>();
|
||||
ResObtainAttractiveReward response = new();
|
||||
User user = GetUser();
|
||||
|
||||
// look up ID from name code and level
|
||||
KeyValuePair<int, AttractiveLevelRewardRecord> levelUpRecord = GameData.Instance.AttractiveLevelReward.Where(x => x.Value.AttractiveLevel == req.Lv && x.Value.NameCode == req.NameCode).FirstOrDefault();
|
||||
|
||||
foreach (NetUserAttractiveData item in user.BondInfo)
|
||||
{
|
||||
if (item.NameCode == req.NameCode)
|
||||
{
|
||||
if (!item.ObtainedRewardLevels.Contains(levelUpRecord.Value.Id))
|
||||
{
|
||||
item.ObtainedRewardLevels.Add(levelUpRecord.Value.Id);
|
||||
|
||||
RewardRecord reward = GameData.Instance.GetRewardTableEntry(levelUpRecord.Value.RewardId) ?? throw new Exception("failed to get reward");
|
||||
response.Reward = RewardUtils.RegisterRewardsForUser(user, reward);
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/SynchroDevice/Regist")]
|
||||
public class RegisterSynchroDevice : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqSynchroRegister>();
|
||||
var user = GetUser();
|
||||
var targetCharacter = user.GetCharacterBySerialNumber(req.Csn);
|
||||
if (targetCharacter == null) throw new Exception("target character does not exist");
|
||||
|
||||
var response = new ResSynchroRegister();
|
||||
foreach (var item in user.SynchroSlots)
|
||||
ReqSynchroRegister req = await ReadData<ReqSynchroRegister>();
|
||||
User user = GetUser();
|
||||
CharacterModel? targetCharacter = user.GetCharacterBySerialNumber(req.Csn) ?? throw new Exception("target character does not exist");
|
||||
ResSynchroRegister response = new();
|
||||
foreach (SynchroSlot item in user.SynchroSlots)
|
||||
{
|
||||
if (item.Slot == req.Slot)
|
||||
{
|
||||
@@ -30,7 +29,7 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
Csn = item.CharacterSerialNumber,
|
||||
CostumeId = targetCharacter.CostumeId,
|
||||
Grade = targetCharacter.Grade,
|
||||
Level = user.GetSynchroLevel(),
|
||||
Lv = user.GetSynchroLevel(),
|
||||
Skill1Lv = targetCharacter.Skill1Lvl,
|
||||
Skill2Lv = targetCharacter.Skill2Lvl,
|
||||
Tid = targetCharacter.Tid,
|
||||
@@ -41,6 +40,7 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
@@ -1,31 +1,31 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.StaticInfo;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/growreset")]
|
||||
public class ResetLevel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqCharacterGrowReset>();
|
||||
var user = GetUser();
|
||||
var response = new ResCharacterGrowReset();
|
||||
var data = GameData.Instance.GetCharacterLevelUpData();
|
||||
ReqCharacterGrowReset req = await ReadData<ReqCharacterGrowReset>();
|
||||
User user = GetUser();
|
||||
ResCharacterGrowReset response = new();
|
||||
Dictionary<int, CharacterLevelRecord> data = GameData.Instance.GetCharacterLevelUpData();
|
||||
|
||||
foreach (var item in user.Characters.ToArray())
|
||||
foreach (CharacterModel item in user.Characters.ToArray())
|
||||
{
|
||||
if (item.Csn == req.Csn)
|
||||
{
|
||||
if (item.Level == 1)
|
||||
{
|
||||
Console.WriteLine("Character level is already 1 - cannot reset");
|
||||
Logging.WriteLine("Character level is already 1 - cannot reset", LogType.WarningAntiCheat);
|
||||
return;
|
||||
}
|
||||
if (item.Level == 200)
|
||||
{
|
||||
Console.WriteLine("Character level is 200 - cannot reset");
|
||||
Logging.WriteLine("Character level is 200 - cannot reset", LogType.WarningAntiCheat);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
int requiredCoreDust = 0;
|
||||
for (int i = 1; i < item.Level; i++)
|
||||
{
|
||||
var levelUpData = data[i];
|
||||
requiredCredit += levelUpData.gold;
|
||||
requiredBattleData += levelUpData.character_exp;
|
||||
requiredCoreDust += levelUpData.character_exp2;
|
||||
CharacterLevelRecord levelUpData = data[i];
|
||||
requiredCredit += levelUpData.Gold;
|
||||
requiredBattleData += levelUpData.CharacterExp;
|
||||
requiredCoreDust += levelUpData.CharacterExp2;
|
||||
}
|
||||
|
||||
user.AddCurrency(CurrencyType.Gold, requiredCredit);
|
||||
@@ -49,23 +49,23 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
{
|
||||
CostumeId = item.CostumeId,
|
||||
Csn = item.Csn,
|
||||
Level = item.Level,
|
||||
Lv = item.Level,
|
||||
Skill1Lv = item.Skill1Lvl,
|
||||
Skill2Lv = item.Skill2Lvl,
|
||||
UltiSkillLv = item.UltimateLevel,
|
||||
Grade = item.Grade,
|
||||
Tid = item.Tid
|
||||
};
|
||||
var highestLevelCharacters = user.Characters.OrderByDescending(x => x.Level).Take(5).ToList();
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
response.SynchroLv = highestLevelCharacters.Last().Level;
|
||||
|
||||
foreach (var c in highestLevelCharacters)
|
||||
foreach (CharacterModel? c in highestLevelCharacters)
|
||||
{
|
||||
response.SynchroStandardCharacters.Add(c.Csn);
|
||||
}
|
||||
|
||||
foreach (var currency in user.Currency)
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/costume/set")]
|
||||
public class SetCharacterCostume : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqSetCharacterCostume>();
|
||||
var user = GetUser();
|
||||
ReqSetCharacterCostume req = await ReadData<ReqSetCharacterCostume>();
|
||||
User user = GetUser();
|
||||
|
||||
foreach (var item in user.Characters)
|
||||
foreach (CharacterModel item in user.Characters)
|
||||
{
|
||||
if (item.Csn == req.Csn)
|
||||
{
|
||||
@@ -21,7 +21,7 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
}
|
||||
JsonDb.Save();
|
||||
|
||||
var response = new ResSetCharacterCostume();
|
||||
ResSetCharacterCostume response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
29
EpinelPS/LobbyServer/Character/SetCharacterMainForce.cs
Normal file
29
EpinelPS/LobbyServer/Character/SetCharacterMainForce.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/mainforce/set")]
|
||||
public class SetCharacterMainForce : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetCharacterMainForce req = await ReadData<ReqSetCharacterMainForce>();
|
||||
User user = GetUser();
|
||||
|
||||
foreach (CharacterModel item in user.Characters)
|
||||
{
|
||||
if (item.Csn == req.Csn)
|
||||
{
|
||||
item.IsMainForce = req.IsMainForce;
|
||||
break;
|
||||
}
|
||||
}
|
||||
JsonDb.Save();
|
||||
|
||||
ResSetCharacterMainForce response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
EpinelPS/LobbyServer/Character/SkillLevelUp.cs
Normal file
94
EpinelPS/LobbyServer/Character/SkillLevelUp.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/skill/levelup")]
|
||||
public class SkillLevelUp : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterSkillLevelUp req = await ReadData<ReqCharacterSkillLevelUp>();
|
||||
User user = GetUser();
|
||||
ResCharacterSkillLevelUp response = new();
|
||||
|
||||
CharacterModel character = user.Characters.FirstOrDefault(c => c.Csn == req.Csn) ?? throw new Exception("cannot find character");
|
||||
|
||||
CharacterRecord charRecord = GameData.Instance.CharacterTable.Values.FirstOrDefault(c => c.Id == character.Tid) ?? throw new Exception("cannot find character record");
|
||||
Dictionary<int, int> skillIdMap = new()
|
||||
{
|
||||
{ 1, charRecord.UltiSkillId },
|
||||
{ 2, charRecord.Skill1Id },
|
||||
{ 3, charRecord.Skill2Id }
|
||||
};
|
||||
Dictionary<int, int> skillLevelMap = new()
|
||||
{
|
||||
{ 1, character.UltimateLevel },
|
||||
{ 2, character.Skill1Lvl },
|
||||
{ 3, character.Skill2Lvl }
|
||||
};
|
||||
SkillInfoRecord skillRecord = GameData.Instance.skillInfoTable.Values.FirstOrDefault(s => s.Id == skillIdMap[req.Category] + (skillLevelMap[req.Category] - 1)) ?? throw new Exception("cannot find character skill record");
|
||||
CostRecord costRecord = GameData.Instance.costTable.Values.FirstOrDefault(c => c.Id == skillRecord.LevelUpCostId) ?? throw new Exception("cannot find character cost record");
|
||||
|
||||
foreach (CostData? cost in costRecord.Costs.Where(i => i.ItemType != RewardType.None))
|
||||
{
|
||||
ItemData item = user.Items.FirstOrDefault(i => i.ItemType == cost.ItemId) ?? throw new NullReferenceException();
|
||||
|
||||
item.Count -= cost.ItemValue;
|
||||
|
||||
response.Items.Add(new NetUserItemData
|
||||
{
|
||||
Isn = item.Isn,
|
||||
Tid = cost.ItemId,
|
||||
Count = item.Count,
|
||||
Csn = item.Csn,
|
||||
Corporation = item.Corp,
|
||||
Lv = item.Level,
|
||||
Exp = item.Exp,
|
||||
Position = item.Position
|
||||
});
|
||||
}
|
||||
|
||||
NetUserCharacterDefaultData newChar = new()
|
||||
{
|
||||
CostumeId = character.CostumeId,
|
||||
Csn = character.Csn,
|
||||
Lv = character.Level,
|
||||
Grade = character.Grade,
|
||||
Tid = character.Tid,
|
||||
DispatchTid = character.Tid,
|
||||
Skill1Lv = character.Skill1Lvl,
|
||||
Skill2Lv = character.Skill2Lvl,
|
||||
UltiSkillLv = character.UltimateLevel,
|
||||
};
|
||||
|
||||
if (req.Category == 1)
|
||||
{
|
||||
character.UltimateLevel++;
|
||||
newChar.UltiSkillLv++;
|
||||
}
|
||||
else if (req.Category == 2)
|
||||
{
|
||||
character.Skill1Lvl++;
|
||||
newChar.Skill1Lv++;
|
||||
}
|
||||
else if (req.Category == 3)
|
||||
{
|
||||
character.Skill2Lvl++;
|
||||
newChar.Skill2Lv++;
|
||||
}
|
||||
|
||||
if (character.UltimateLevel == 10 && character.Skill1Lvl == 10 && character.Skill2Lvl == 10)
|
||||
{
|
||||
user.AddTrigger(Trigger.CharacterSkillLevelMax, 1);
|
||||
}
|
||||
|
||||
response.Character = newChar;
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
EpinelPS/LobbyServer/Character/SynchroAddSlot.cs
Normal file
49
EpinelPS/LobbyServer/Character/SynchroAddSlot.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/synchrodevice/addslot")]
|
||||
public class SynchroAddSlot : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSynchroAddSlot req = await ReadData<ReqSynchroAddSlot>();
|
||||
User user = GetUser();
|
||||
ResSynchroAddSlot response = new();
|
||||
|
||||
SynchroSlot? slot = user.SynchroSlots.FirstOrDefault(x => x.Slot == req.Slot);
|
||||
if (slot != null)
|
||||
{
|
||||
response.Slot = new NetSynchroSlot
|
||||
{
|
||||
Csn = slot.CharacterSerialNumber,
|
||||
Slot = slot.Slot,
|
||||
AvailableRegisterAt = slot.AvailableAt
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
NetSynchroSlot newSlot = new()
|
||||
{
|
||||
Csn = 0,
|
||||
Slot = req.Slot,
|
||||
AvailableRegisterAt = 0
|
||||
};
|
||||
|
||||
user.SynchroSlots.Add(new SynchroSlot()
|
||||
{
|
||||
Slot = newSlot.Slot,
|
||||
CharacterSerialNumber = newSlot.Csn,
|
||||
AvailableAt = newSlot.AvailableRegisterAt
|
||||
});
|
||||
|
||||
response.Slot = newSlot;
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
EpinelPS/LobbyServer/Character/SynchroAddSlotByItem.cs
Normal file
39
EpinelPS/LobbyServer/Character/SynchroAddSlotByItem.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/synchrodevice/addslotbyitem")]
|
||||
public class SynchroAddSlotByItem : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Broken protocol so we dIdn't validate request data.
|
||||
// May fix later.
|
||||
ReqSynchroAddSlot req = await ReadData<ReqSynchroAddSlot>();
|
||||
|
||||
User user = GetUser();
|
||||
ResSynchroAddSlot response = new();
|
||||
|
||||
NetSynchroSlot newSlot = new()
|
||||
{
|
||||
Csn = 0,
|
||||
Slot = user.SynchroSlots.Last().Slot + 1, // any upper bound?
|
||||
AvailableRegisterAt = 0
|
||||
};
|
||||
|
||||
user.SynchroSlots.Add(new SynchroSlot()
|
||||
{
|
||||
Slot = newSlot.Slot,
|
||||
CharacterSerialNumber = newSlot.Csn,
|
||||
AvailableAt = newSlot.AvailableRegisterAt
|
||||
});
|
||||
|
||||
response.Slot = newSlot;
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,28 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.StaticInfo;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/SynchroDevice/LevelUp")]
|
||||
public class SynchroLevelUp : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqSynchroLevelUp>();
|
||||
var user = GetUser();
|
||||
ReqSynchroLevelUp req = await ReadData<ReqSynchroLevelUp>();
|
||||
User user = GetUser();
|
||||
|
||||
var response = new ResSynchroLevelUp();
|
||||
var data = GameData.Instance.GetCharacterLevelUpData();
|
||||
ResSynchroLevelUp response = new();
|
||||
Dictionary<int, CharacterLevelRecord> data = GameData.Instance.GetCharacterLevelUpData();
|
||||
|
||||
|
||||
int requiredCredit = 0;
|
||||
int requiredBattleData = 0;
|
||||
int requiredCoreDust = 0;
|
||||
var levelUpData = data[user.SynchroDeviceLevel + 1];
|
||||
requiredCredit += levelUpData.gold;
|
||||
requiredBattleData += levelUpData.character_exp;
|
||||
requiredCoreDust += levelUpData.character_exp2;
|
||||
CharacterLevelRecord levelUpData = data[user.SynchroDeviceLevel + 1];
|
||||
requiredCredit += levelUpData.Gold;
|
||||
requiredBattleData += levelUpData.CharacterExp;
|
||||
requiredCoreDust += levelUpData.CharacterExp2;
|
||||
|
||||
if (user.CanSubtractCurrency(CurrencyType.Gold, requiredCredit) &&
|
||||
user.CanSubtractCurrency(CurrencyType.CharacterExp, requiredBattleData) &&
|
||||
@@ -41,12 +41,14 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
}
|
||||
|
||||
|
||||
foreach (var currency in user.Currency)
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
response.SynchroLv = user.SynchroDeviceLevel;
|
||||
|
||||
user.AddTrigger(Trigger.CharacterLevelUpCount, 1);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
@@ -1,39 +1,38 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/SynchroDevice/Unregist")]
|
||||
public class UnregisterSynchroDevice : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
var req = await ReadData<ReqSynchroUnregist>();
|
||||
var user = GetUser();
|
||||
ReqSynchroUnregist req = await ReadData<ReqSynchroUnregist>();
|
||||
User user = GetUser();
|
||||
|
||||
var response = new ResSynchroUnregist();
|
||||
ResSynchroUnregist response = new();
|
||||
|
||||
foreach (var item in user.SynchroSlots)
|
||||
foreach (SynchroSlot item in user.SynchroSlots)
|
||||
{
|
||||
if (item.Slot == req.Slot)
|
||||
{
|
||||
if (item.CharacterSerialNumber == 0)
|
||||
{
|
||||
Console.WriteLine("must add character from synchrodevice first");
|
||||
Logging.WriteLine("must add character from synchrodevice first", LogType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
var oldCSN = item.CharacterSerialNumber;
|
||||
long oldCSN = item.CharacterSerialNumber;
|
||||
item.CharacterSerialNumber = 0;
|
||||
var data = user.GetCharacterBySerialNumber(oldCSN);
|
||||
if (data == null) throw new Exception("failed to lookup character");
|
||||
CharacterModel data = user.GetCharacterBySerialNumber(oldCSN) ?? throw new Exception("failed to lookup character");
|
||||
|
||||
response.Character = new NetUserCharacterDefaultData()
|
||||
{
|
||||
Csn = data.Csn,
|
||||
CostumeId = data.CostumeId,
|
||||
Grade = data.Grade,
|
||||
Level = data.Level,
|
||||
Lv = data.Level,
|
||||
Skill1Lv = data.Skill1Lvl,
|
||||
Skill2Lv = data.Skill2Lvl,
|
||||
Tid = data.Tid,
|
||||
@@ -42,10 +41,10 @@ namespace EpinelPS.LobbyServer.Msgs.Character
|
||||
response.Slot = new NetSynchroSlot() { AvailableRegisterAt = item.AvailableAt, Csn = item.CharacterSerialNumber, Slot = item.Slot };
|
||||
|
||||
response.IsSynchro = false;
|
||||
var highestLevelCharacters = user.Characters.OrderByDescending(x => x.Level).Take(5).ToList();
|
||||
List<CharacterModel> highestLevelCharacters = [.. user.Characters.OrderByDescending(x => x.Level).Take(5)];
|
||||
|
||||
|
||||
foreach (var item2 in highestLevelCharacters)
|
||||
foreach (CharacterModel? item2 in highestLevelCharacters)
|
||||
{
|
||||
response.SynchroStandardCharacters.Add(item2.Csn);
|
||||
}
|
||||
76
EpinelPS/LobbyServer/Character/UpgradeCore.cs
Normal file
76
EpinelPS/LobbyServer/Character/UpgradeCore.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
[PacketPath("/character/coreupgrade")]
|
||||
public class CoreUpgrade : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// Read the incoming request that contains the current CSN and ISN
|
||||
ReqCharacterCoreUpgrade req = await ReadData<ReqCharacterCoreUpgrade>(); // Contains csn and isn (read-only)
|
||||
ResCharacterCoreUpgrade response = new();
|
||||
|
||||
User user = GetUser();
|
||||
|
||||
// Get all character data from the game's character table
|
||||
List<CharacterRecord> fullchardata = [.. GameData.Instance.CharacterTable.Values];
|
||||
|
||||
CharacterModel targetCharacter = user.GetCharacterBySerialNumber(req.Csn) ?? throw new NullReferenceException();
|
||||
|
||||
// Find the element with the current csn from the request
|
||||
CharacterRecord? currentCharacter = fullchardata.FirstOrDefault(c => c.Id == targetCharacter.Tid);
|
||||
|
||||
if (currentCharacter != null && targetCharacter != null)
|
||||
{
|
||||
if (currentCharacter.GradeCoreId == 103 || currentCharacter.GradeCoreId == 11 || currentCharacter.GradeCoreId == 201)
|
||||
{
|
||||
Console.WriteLine("warning: cannot upgrade code any further!");
|
||||
await WriteDataAsync(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a new CSN based on the `NameCode` of the current character and `GradeCoreId + 1`
|
||||
// For some reason, there is a seperate character for each limit/core break value.
|
||||
CharacterRecord? newCharacter = fullchardata.FirstOrDefault(c => c.NameCode == currentCharacter.NameCode && c.GradeCoreId == currentCharacter.GradeCoreId + 1);
|
||||
|
||||
|
||||
if (newCharacter != null)
|
||||
{
|
||||
// replace character in DB with new character
|
||||
targetCharacter.Grade++;
|
||||
targetCharacter.Tid = newCharacter.Id;
|
||||
|
||||
response.Character = new NetUserCharacterDefaultData()
|
||||
{
|
||||
Csn = req.Csn,
|
||||
CostumeId = targetCharacter.CostumeId,
|
||||
Grade = targetCharacter.Grade,
|
||||
Lv = user.GetSynchroLevel(),
|
||||
Skill1Lv = targetCharacter.Skill1Lvl,
|
||||
Skill2Lv = targetCharacter.Skill2Lvl,
|
||||
Tid = targetCharacter.Tid,
|
||||
UltiSkillLv = targetCharacter.UltimateLevel
|
||||
};
|
||||
|
||||
// remove spare body item
|
||||
ItemData bodyItem = user.Items.FirstOrDefault(i => i.Isn == req.Isn) ?? throw new NullReferenceException();
|
||||
user.RemoveItemBySerialNumber(req.Isn, 1);
|
||||
response.Items.Add(NetUtils.ToNet(bodyItem));
|
||||
|
||||
if (newCharacter.GradeCoreId == 103 || newCharacter.GradeCoreId == 11 || newCharacter.GradeCoreId == 201)
|
||||
{
|
||||
user.AddTrigger(Trigger.CharacterGradeMax, 1);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
}
|
||||
}
|
||||
|
||||
// Send the response back to the client
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
EpinelPS/LobbyServer/Client/CheckClientVersion.cs
Normal file
19
EpinelPS/LobbyServer/Client/CheckClientVersion.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Client
|
||||
{
|
||||
[PacketPath("/system/checkversion")]
|
||||
public class CheckClientVersion : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCheckClientVersion req = await ReadData<ReqCheckClientVersion>();
|
||||
ResCheckClientVersion response = new()
|
||||
{
|
||||
Availability = ResCheckClientVersion.Types.Availability.None
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
EpinelPS/LobbyServer/ContentsOpen/GetUnlocked.cs
Normal file
47
EpinelPS/LobbyServer/ContentsOpen/GetUnlocked.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.ContentsOpen
|
||||
{
|
||||
[PacketPath("/contentsopen/get/unlock")]
|
||||
public class GetUnlocked : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetContentsOpenUnlockInfo req = await ReadData<ReqGetContentsOpenUnlockInfo>();
|
||||
User user = GetUser();
|
||||
|
||||
// This request is used for showing the "Collection Item Unlocked" Popup and button unlock animation
|
||||
|
||||
ResGetContentsOpenUnlockInfo response = new();
|
||||
|
||||
if (user.ContentsOpenUnlocked.Count == 0)
|
||||
{
|
||||
// These Always returned as true by official server
|
||||
// Fixes "Recruitment unlocked" during chapter 0
|
||||
// TODO: Don't hardcode this, maybe its in GameData
|
||||
|
||||
user.ContentsOpenUnlocked.Add(3, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(4, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(6, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(15, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(16, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(18, new(true, true));
|
||||
user.ContentsOpenUnlocked.Add(19, new(true, true));
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<int, UnlockData> item in user.ContentsOpenUnlocked.OrderBy(x => x.Key))
|
||||
{
|
||||
response.ContentsOpenUnlockInfoList.Add(new NetContentsOpenUnlockInfo()
|
||||
{
|
||||
ContentsOpenTableId = item.Key,
|
||||
IsUnlockButtonPlayed = item.Value.ButtonAnimationPlayed,
|
||||
IsUnlockPopupPlayed = item.Value.PopupAnimationPlayed,
|
||||
});
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
EpinelPS/LobbyServer/ContentsOpen/SetUnlockButton.cs
Normal file
38
EpinelPS/LobbyServer/ContentsOpen/SetUnlockButton.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.ContentsOpen
|
||||
{
|
||||
[PacketPath("/contentsopen/set/unlock/button")]
|
||||
public class SetUnlockButton : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetContentsOpenUnlockButtonPlay req = await ReadData<ReqSetContentsOpenUnlockButtonPlay>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetContentsOpenUnlockButtonPlay response = new();
|
||||
|
||||
// Unlock button animation completed
|
||||
|
||||
foreach (int item in req.ContentsOpenTableIds)
|
||||
{
|
||||
if (user.ContentsOpenUnlocked.TryGetValue(item, out UnlockData? data))
|
||||
{
|
||||
data.ButtonAnimationPlayed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.ContentsOpenUnlocked.Add(item, new UnlockData()
|
||||
{
|
||||
ButtonAnimationPlayed = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
EpinelPS/LobbyServer/ContentsOpen/SetUnlockPopup.cs
Normal file
36
EpinelPS/LobbyServer/ContentsOpen/SetUnlockPopup.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.ContentsOpen
|
||||
{
|
||||
[PacketPath("/contentsopen/set/unlock/popup")]
|
||||
public class SetUnlockPopup : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetContentsOpenUnlockPopupPlay req = await ReadData<ReqSetContentsOpenUnlockPopupPlay>();
|
||||
User user = GetUser();
|
||||
|
||||
ResSetContentsOpenUnlockPopupPlay response = new();
|
||||
|
||||
foreach (int item in req.ContentsOpenTableIds)
|
||||
{
|
||||
if (user.ContentsOpenUnlocked.TryGetValue(item, out UnlockData? data))
|
||||
{
|
||||
data.PopupAnimationPlayed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.ContentsOpenUnlocked.Add(item, new UnlockData()
|
||||
{
|
||||
PopupAnimationPlayed = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
EpinelPS/LobbyServer/EmptyHandler.cs
Normal file
16
EpinelPS/LobbyServer/EmptyHandler.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer
|
||||
{
|
||||
public class EmptyHandler : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetNow req = await ReadData<ReqGetNow>();
|
||||
ResGetNow response = new();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
EpinelPS/LobbyServer/Episode/ListMission.cs
Normal file
19
EpinelPS/LobbyServer/Episode/ListMission.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Episode
|
||||
{
|
||||
[PacketPath("/episode/mission/enter")]
|
||||
public class ListMission : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqListValidEpMission req = await ReadData<ReqListValidEpMission>();
|
||||
|
||||
ResListValidEpMission response = new();
|
||||
|
||||
// TOOD
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
EpinelPS/LobbyServer/Event/CheckBookmarkScenarioExists.cs
Normal file
19
EpinelPS/LobbyServer/Event/CheckBookmarkScenarioExists.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/bookmark/event/scenario/exist")]
|
||||
public class CheckBookmarkScenarioExists : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqExistScenarioBookmark req = await ReadData<ReqExistScenarioBookmark>();
|
||||
|
||||
ResExistScenarioBookmark response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Event/CheckScenarioExists.cs
Normal file
21
EpinelPS/LobbyServer/Event/CheckScenarioExists.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/scenario/exist")]
|
||||
public class CheckScenarioExists : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqExistEventScenario req = await ReadData<ReqExistEventScenario>();
|
||||
|
||||
ResExistEventScenario response = new();
|
||||
|
||||
foreach (string? item in req.ScenarioGroupIds)
|
||||
response.ExistGroupIds.Add(item);
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
EpinelPS/LobbyServer/Event/CollectSystem/ListField.cs
Normal file
20
EpinelPS/LobbyServer/Event/CollectSystem/ListField.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.CollectSystem
|
||||
{
|
||||
[PacketPath("/event/collect-system/list-field")]
|
||||
public class ListField : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqListFieldEventCollectData req = await ReadData<ReqListFieldEventCollectData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResListFieldEventCollectData response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
EpinelPS/LobbyServer/Event/CompleteEventScenario.cs
Normal file
36
EpinelPS/LobbyServer/Event/CompleteEventScenario.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/scenario/complete")]
|
||||
public class CompleteEventScenario : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqSetEventScenarioComplete req = await ReadData<ReqSetEventScenarioComplete>();
|
||||
User user = GetUser();
|
||||
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? evt))
|
||||
{
|
||||
if (!evt.CompletedScenarios.Contains(req.ScenarioId))
|
||||
{
|
||||
evt.CompletedScenarios.Add(req.ScenarioId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
evt = new();
|
||||
evt.CompletedScenarios.Add(req.ScenarioId);
|
||||
user.EventInfo.Add(req.EventId, evt);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
ResSetEventScenarioComplete response = new();
|
||||
|
||||
// TODO reward
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
EpinelPS/LobbyServer/Event/EnterEventField.cs
Normal file
48
EpinelPS/LobbyServer/Event/EnterEventField.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/eventfield/enter")]
|
||||
public class EnterEventField : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqEnterEventField req = await ReadData<ReqEnterEventField>();
|
||||
User user = GetUser();
|
||||
|
||||
ResEnterEventField response = new()
|
||||
{
|
||||
Field = new()
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Retrieve collected objects and completed stages
|
||||
if (!user.FieldInfoNew.TryGetValue(req.MapId, out FieldInfoNew? field))
|
||||
{
|
||||
field = new FieldInfoNew();
|
||||
user.FieldInfoNew.Add(req.MapId, field);
|
||||
}
|
||||
|
||||
foreach (int stage in field.CompletedStages)
|
||||
{
|
||||
response.Field.Stages.Add(new NetFieldStageData() { StageId = stage });
|
||||
}
|
||||
foreach (NetFieldObject obj in field.CompletedObjects)
|
||||
{
|
||||
if (obj == null) continue;
|
||||
if (obj.Type == 1)
|
||||
response.Field.Objects.Add(obj);
|
||||
}
|
||||
|
||||
|
||||
// Retrieve camera data
|
||||
if (user.MapJson.TryGetValue(req.MapId, out string? mapJson))
|
||||
{
|
||||
response.Json = mapJson;
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
292
EpinelPS/LobbyServer/Event/EventHelper.cs
Normal file
292
EpinelPS/LobbyServer/Event/EventHelper.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
public class EventHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(EventHelper));
|
||||
|
||||
public static void AddEvents(User user, ref ResGetEventList response)
|
||||
{
|
||||
List<LobbyPrivateBannerRecord> lobbyPrivateBanners = GetLobbyPrivateBannerData(user);
|
||||
if (lobbyPrivateBanners.Count == 0)
|
||||
{
|
||||
// No active lobby private banners
|
||||
Logging.WriteLine("No active lobby private banners found.", LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var eventManagers = GameData.Instance.eventManagers.Values.ToList();
|
||||
foreach (var banner in lobbyPrivateBanners)
|
||||
{
|
||||
// Get all events (including child events) associated with this banner
|
||||
List<NetEventData> events = GetEventData(banner, eventManagers);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {events.Count} associated events: {JsonConvert.SerializeObject(events)}");
|
||||
AddEvents(ref response, events);
|
||||
|
||||
// Additionally, get any gacha events associated with this banner
|
||||
List<EventSystemType> systemTypes = [EventSystemType.PickupGachaEvent, EventSystemType.BoxGachaEvent, EventSystemType.LoginEvent];
|
||||
List<NetEventData> gachaEvents = GetEventDataBySystemTypes(banner, eventManagers, systemTypes);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {gachaEvents.Count} associated gacha events: {JsonConvert.SerializeObject(gachaEvents)}");
|
||||
AddEvents(ref response, gachaEvents);
|
||||
|
||||
// add challenge events
|
||||
var challengeEvents = GetChallengeEventData(banner, eventManagers);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {challengeEvents.Count} associated challenge events: {JsonConvert.SerializeObject(challengeEvents)}");
|
||||
AddEvents(ref response, challengeEvents);
|
||||
}
|
||||
// add daily mission events
|
||||
List<NetEventData> dailyMissionEvents = GetDailyMissionEventData(eventManagers);
|
||||
log.Debug($"Found {dailyMissionEvents.Count} associated daily mission events: {JsonConvert.SerializeObject(dailyMissionEvents)}");
|
||||
AddEvents(ref response, dailyMissionEvents);
|
||||
}
|
||||
|
||||
public static void AddJoinedEvents(User user, ref ResGetJoinedEvent response)
|
||||
{
|
||||
List<LobbyPrivateBannerRecord> lobbyPrivateBanners = GetLobbyPrivateBannerData(user);
|
||||
if (lobbyPrivateBanners.Count == 0)
|
||||
{
|
||||
// No active lobby private banners
|
||||
Logging.WriteLine("No active lobby private banners found.", LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var eventManagers = GameData.Instance.eventManagers.Values.ToList();
|
||||
foreach (var banner in lobbyPrivateBanners)
|
||||
{
|
||||
|
||||
// Get all events (including child events) associated with this banner
|
||||
var events = GetEventData(banner, eventManagers);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {events.Count} associated events: {JsonConvert.SerializeObject(events)}");
|
||||
AddJoinedEvents(ref response, events);
|
||||
|
||||
// add gacha events
|
||||
List<EventSystemType> systemTypes = [EventSystemType.PickupGachaEvent, EventSystemType.BoxGachaEvent, EventSystemType.LoginEvent];
|
||||
List<NetEventData> gachaEvents = GetEventDataBySystemTypes(banner, eventManagers, systemTypes);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {gachaEvents.Count} associated gacha events: {JsonConvert.SerializeObject(gachaEvents)}");
|
||||
AddJoinedEvents(ref response, gachaEvents);
|
||||
|
||||
// add challenge events
|
||||
List<NetEventData> challengeEvents = GetChallengeEventData(banner, eventManagers);
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {challengeEvents.Count} associated challenge events: {JsonConvert.SerializeObject(challengeEvents)}");
|
||||
AddJoinedEvents(ref response, challengeEvents);
|
||||
}
|
||||
// add daily mission events
|
||||
List<NetEventData> dailyMissionEvents = GetDailyMissionEventData(eventManagers);
|
||||
log.Debug($"Found {dailyMissionEvents.Count} associated daily mission events: {JsonConvert.SerializeObject(dailyMissionEvents)}");
|
||||
AddJoinedEvents(ref response, dailyMissionEvents);
|
||||
}
|
||||
|
||||
private static List<NetEventData> GetEventData(LobbyPrivateBannerRecord banner, List<EventManagerRecord> eventManagers)
|
||||
{
|
||||
List<NetEventData> events = [];
|
||||
|
||||
if (!eventManagers.Any(em => em.Id == banner.EventId))
|
||||
{
|
||||
Logging.WriteLine($"No event manager found for Banner EventId: {banner.EventId}", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
// Add the main event associated with the banner
|
||||
var mainEvent = eventManagers.First(em => em.Id == banner.EventId);
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = mainEvent.Id,
|
||||
EventSystemType = (int)mainEvent.EventSystemType,
|
||||
// EventStartDate = banner.StartDate.Ticks,
|
||||
// EventVisibleDate = banner.StartDate.Ticks,
|
||||
// EventDisableDate = banner.EndDate.Ticks,
|
||||
// EventEndDate = banner.EndDate.Ticks
|
||||
});
|
||||
// Add child events associated with the main event
|
||||
var childEvents = eventManagers.Where(em => em.ParentsEventId == banner.EventId || em.SetField == banner.EventId).ToList();
|
||||
foreach (var childEvent in childEvents)
|
||||
{
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = childEvent.Id,
|
||||
EventSystemType = (int)childEvent.EventSystemType,
|
||||
// EventStartDate = banner.StartDate.Ticks,
|
||||
// EventVisibleDate = banner.StartDate.Ticks,
|
||||
// EventDisableDate = banner.EndDate.Ticks,
|
||||
// EventEndDate = banner.EndDate.Ticks
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private static List<NetEventData> GetEventDataBySystemTypes(LobbyPrivateBannerRecord banner, List<EventManagerRecord> eventManagers, List<EventSystemType> systemTypes)
|
||||
{
|
||||
List<NetEventData> events = [];
|
||||
// Find all event banner resource tables associated with this banner's EventId
|
||||
List<string> eventBannerResourceTables = [.. eventManagers.Where(em =>
|
||||
(em.SetField == banner.EventId || em.ParentsEventId == banner.EventId)
|
||||
&& em.EventBannerResourceTable.StartsWith("event_")).Select(em => em.EventBannerResourceTable)];
|
||||
eventBannerResourceTables = [.. eventBannerResourceTables.Distinct()];
|
||||
log.Debug($"Banner EventId: {banner.EventId} has {eventBannerResourceTables.Count} associated event banner resource tables: {JsonConvert.SerializeObject(eventBannerResourceTables)}");
|
||||
if (eventBannerResourceTables.Count == 0)
|
||||
{
|
||||
Logging.WriteLine($"No associated event banner resource tables found for Banner EventId: {banner.EventId}", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
|
||||
// Find all events matching the banner resource tables and specified system types
|
||||
var gachaEvents = eventManagers.Where(em =>
|
||||
eventBannerResourceTables.Contains(em.EventBannerResourceTable)
|
||||
&& systemTypes.Contains(em.EventSystemType)).ToList();
|
||||
log.Debug($"Found {gachaEvents.Count} gacha events from banner resource tables: {JsonConvert.SerializeObject(gachaEvents)}");
|
||||
if (gachaEvents.Count == 0)
|
||||
{
|
||||
Logging.WriteLine($"No gacha events found for Banner EventId: {banner.EventId}", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
|
||||
// Add each gacha event to the list
|
||||
foreach (var gachaEvent in gachaEvents)
|
||||
{
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = gachaEvent.Id,
|
||||
EventSystemType = (int)gachaEvent.EventSystemType,
|
||||
// EventStartDate = banner.StartDate.Ticks,
|
||||
// EventVisibleDate = banner.StartDate.Ticks,
|
||||
// EventDisableDate = banner.EndDate.Ticks,
|
||||
// EventEndDate = banner.EndDate.Ticks
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private static List<NetEventData> GetChallengeEventData(LobbyPrivateBannerRecord banner, List<EventManagerRecord> eventManagers)
|
||||
{
|
||||
List<NetEventData> events = [];
|
||||
|
||||
// Find all challenge events (ChallengeModeEvent) associated with this banner's EventId
|
||||
var challengeEvents = eventManagers.Where(em =>
|
||||
em.ParentsEventId == banner.EventId && em.EventSystemType == EventSystemType.ChallengeModeEvent).ToList();
|
||||
log.Debug($"Found {challengeEvents.Count} challenge events from banner resource tables: {JsonConvert.SerializeObject(challengeEvents)}");
|
||||
if (challengeEvents.Count == 0)
|
||||
{
|
||||
Logging.WriteLine($"No challenge events found for Banner EventId: {banner.EventId}", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
|
||||
// Add each challenge event to the list
|
||||
foreach (var challengeEvent in challengeEvents)
|
||||
{
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = challengeEvent.Id,
|
||||
EventSystemType = (int)challengeEvent.EventSystemType,
|
||||
// EventStartDate = banner.StartDate.Ticks,
|
||||
// EventVisibleDate = banner.StartDate.Ticks,
|
||||
// EventDisableDate = banner.EndDate.Ticks,
|
||||
// EventEndDate = banner.EndDate.Ticks
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get active lobby private banner data
|
||||
/// </summary>
|
||||
/// <returns>List of active lobby private banners</returns>
|
||||
public static List<LobbyPrivateBannerRecord> GetLobbyPrivateBannerData(User user)
|
||||
{
|
||||
var lobbyPrivateBannerIds = user.LobbyPrivateBannerIds;
|
||||
var lobbyPrivateBannerRecords = GameData.Instance.LobbyPrivateBannerTable.Values;
|
||||
List<LobbyPrivateBannerRecord> lobbyPrivateBanners = [];
|
||||
if (lobbyPrivateBannerIds is not null && lobbyPrivateBannerIds.Count > 0)
|
||||
{
|
||||
lobbyPrivateBanners = [.. lobbyPrivateBannerRecords.Where(b => lobbyPrivateBannerIds.Contains(b.Id))];
|
||||
}
|
||||
else
|
||||
{
|
||||
lobbyPrivateBanners.Add(lobbyPrivateBannerRecords.OrderBy(b => b.Id).Last());
|
||||
}
|
||||
Logging.WriteLine($"Found {lobbyPrivateBanners.Count} active lobby private banners.", LogType.Debug);
|
||||
log.Debug($"Active lobby private banners: {JsonConvert.SerializeObject(lobbyPrivateBanners)}");
|
||||
return lobbyPrivateBanners;
|
||||
}
|
||||
|
||||
private static void AddEvents(ref ResGetEventList response, List<NetEventData> eventDatas)
|
||||
{
|
||||
foreach (var eventData in eventDatas)
|
||||
{
|
||||
// if (eventData.Id == 70115) continue;
|
||||
// Avoid adding duplicate events
|
||||
if (!response.EventList.Any(e => e.Id == eventData.Id))
|
||||
{
|
||||
if (eventData.EventStartDate == 0) eventData.EventStartDate = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
if (eventData.EventVisibleDate == 0) eventData.EventVisibleDate = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
if (eventData.EventDisableDate == 0) eventData.EventDisableDate = DateTime.UtcNow.AddDays(30).Ticks;
|
||||
if (eventData.EventEndDate == 0) eventData.EventEndDate = DateTime.UtcNow.AddDays(30).Ticks;
|
||||
response.EventList.Add(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Debug($"Skipping duplicate event Id: {eventData.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddJoinedEvents(ref ResGetJoinedEvent response, List<NetEventData> eventDatas)
|
||||
{
|
||||
foreach (var eventData in eventDatas)
|
||||
{
|
||||
if (eventData.Id == 70115) continue;
|
||||
// Avoid adding duplicate events
|
||||
if (!response.EventWithJoinData.Any(e => e.EventData.Id == eventData.Id))
|
||||
{
|
||||
if (eventData.EventStartDate == 0) eventData.EventStartDate = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
if (eventData.EventVisibleDate == 0) eventData.EventVisibleDate = DateTime.UtcNow.AddDays(-1).Ticks;
|
||||
if (eventData.EventDisableDate == 0) eventData.EventDisableDate = DateTime.UtcNow.AddDays(30).Ticks;
|
||||
if (eventData.EventEndDate == 0) eventData.EventEndDate = DateTime.UtcNow.AddDays(30).Ticks;
|
||||
response.EventWithJoinData.Add(new NetEventWithJoinData()
|
||||
{
|
||||
|
||||
EventData = eventData,
|
||||
JoinAt = 0
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Debug($"Skipping duplicate event Id: {eventData.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<NetEventData> GetDailyMissionEventData(List<EventManagerRecord> eventManagers)
|
||||
{
|
||||
List<NetEventData> events = [];
|
||||
|
||||
var dailyEventIds = GameData.Instance.DailyMissionEventSettingTable.Values.Select(de => de.EventId).ToList();
|
||||
log.Debug($"Daily Mission Event IDs: {JsonConvert.SerializeObject(dailyEventIds)}");
|
||||
var dailyEvents = eventManagers.Where(em => dailyEventIds.Contains(em.Id)).ToList();
|
||||
log.Debug($"Found {dailyEvents.Count} daily events: {JsonConvert.SerializeObject(dailyEvents)}");
|
||||
if (dailyEvents.Count == 0)
|
||||
{
|
||||
Logging.WriteLine("No daily events found.", LogType.Warning);
|
||||
return events;
|
||||
}
|
||||
|
||||
// Add each daily event to the list
|
||||
foreach (var dailyEvent in dailyEvents)
|
||||
{
|
||||
events.Add(new NetEventData()
|
||||
{
|
||||
Id = dailyEvent.Id,
|
||||
EventSystemType = (int)dailyEvent.EventSystemType,
|
||||
EventStartDate = DateTime.UtcNow.Ticks,
|
||||
EventVisibleDate = DateTime.UtcNow.Ticks,
|
||||
EventDisableDate = DateTime.UtcNow.AddDays(30).Ticks,
|
||||
EventEndDate = DateTime.UtcNow.AddDays(30).Ticks
|
||||
});
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
42
EpinelPS/LobbyServer/Event/EventLoginGet.cs
Normal file
42
EpinelPS/LobbyServer/Event/EventLoginGet.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/login/get")]
|
||||
public class EventLoginGet : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqLoginEventData req = await ReadData<ReqLoginEventData>();
|
||||
User user = GetUser();
|
||||
int evId = req.EventId;
|
||||
ResLoginEventData response = new()
|
||||
{
|
||||
EndDate = DateTime.Now.AddDays(13).Ticks,
|
||||
DisableDate = DateTime.Now.AddDays(13).Ticks,
|
||||
LastAttendance = new LoginEventAttendance()
|
||||
}; // fields "EndDate", "DisableDate", "RewardHistories", "LastAttendance"
|
||||
// Check if event exists
|
||||
if (!user.LoginEventInfo.TryGetValue(evId, out var loginEventData))
|
||||
{
|
||||
loginEventData = new LoginEventData();
|
||||
user.LoginEventInfo.Add(evId, loginEventData);
|
||||
JsonDb.Save();
|
||||
}
|
||||
// Populate response with event data
|
||||
int day = 1;
|
||||
GameData.Instance.LoginEventTable.Values.Where(ev => ev.EventId == evId).ToList().ForEach(ev =>
|
||||
{
|
||||
loginEventData.LastDay = day++;
|
||||
response.RewardHistories.Add(new LoginEventRewardHistory() { IsReceived = loginEventData.Days.Contains(ev.Day), Day = ev.Day });
|
||||
});
|
||||
|
||||
response.LastAttendance.Day = loginEventData.LastDay;
|
||||
response.LastAttendance.AttendanceDate = loginEventData.LastDate;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
EpinelPS/LobbyServer/Event/EventLoginReceive.cs
Normal file
38
EpinelPS/LobbyServer/Event/EventLoginReceive.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/login/receive")]
|
||||
public class EventLoginReceive : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqObtainLoginEventReward Fields: EventId, Day
|
||||
ReqObtainLoginEventReward req = await ReadData<ReqObtainLoginEventReward>();
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainLoginEventReward response = new();
|
||||
|
||||
if (!user.LoginEventInfo.TryGetValue(req.EventId, out var loginEventData))
|
||||
{
|
||||
loginEventData = new LoginEventData();
|
||||
user.LoginEventInfo.Add(req.EventId, loginEventData);
|
||||
}
|
||||
|
||||
GameData.Instance.LoginEventTable.Values.Where(ev => ev.EventId == req.EventId && ev.Day == req.Day).ToList().ForEach(ev =>
|
||||
{
|
||||
response.Reward = RewardUtils.RegisterRewardsForUser(user, ev.RewardId);
|
||||
});
|
||||
|
||||
if (!loginEventData.Days.Contains(req.Day))
|
||||
{
|
||||
// loginEventData.Days.Add(req.Day);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
EpinelPS/LobbyServer/Event/EventLoginReceiveAll.cs
Normal file
46
EpinelPS/LobbyServer/Event/EventLoginReceiveAll.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/login/allreceive")]
|
||||
public class EventLoginReceiveAll : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// ReqObtainLoginEventReward Fields: EventId, Day
|
||||
ReqObtainAllLoginEventReward req = await ReadData<ReqObtainAllLoginEventReward>();
|
||||
User user = GetUser();
|
||||
|
||||
ResObtainAllLoginEventReward response = new();
|
||||
|
||||
if (!user.LoginEventInfo.TryGetValue(req.EventId, out var loginEventData))
|
||||
{
|
||||
loginEventData = new LoginEventData();
|
||||
user.LoginEventInfo.Add(req.EventId, loginEventData);
|
||||
}
|
||||
response.Reward = new();
|
||||
NetRewardData rewardData = new();
|
||||
GameData.Instance.LoginEventTable.Values.Where(ev => ev.EventId == req.EventId).ToList().ForEach(ev =>
|
||||
{
|
||||
if (!loginEventData.Days.Contains(ev.Day))
|
||||
{
|
||||
loginEventData.Days.Add(ev.Day);
|
||||
RewardRecord reward = GameData.Instance.GetRewardTableEntry(ev.RewardId) ?? throw new Exception($"unknown reward Id {ev.RewardId}");
|
||||
foreach (var item in reward.Rewards)
|
||||
{
|
||||
if (item.RewardType != RewardType.None)
|
||||
{
|
||||
RewardUtils.AddSingleObject(user, ref rewardData, item.RewardId, item.RewardType, item.RewardValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
response.Reward = rewardData;
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
EpinelPS/LobbyServer/Event/ExecuteEventBoxGacha.cs
Normal file
22
EpinelPS/LobbyServer/Event/ExecuteEventBoxGacha.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/boxgacha/execute")]
|
||||
public class ExecuteEventBoxGacha : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// from client: {"EventId":10051,"CurrentCount":1}
|
||||
ReqExecuteEventBoxGacha req = await ReadData<ReqExecuteEventBoxGacha>();
|
||||
User user = GetUser();
|
||||
|
||||
ResExecuteEventBoxGacha response = new()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
EpinelPS/LobbyServer/Event/Field/ListPasswordDoor.cs
Normal file
20
EpinelPS/LobbyServer/Event/Field/ListPasswordDoor.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event.Field
|
||||
{
|
||||
[PacketPath("/event/field/password-door/list")]
|
||||
public class ListPasswordDoor : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqListFieldPasswordDoorData req = await ReadData<ReqListFieldPasswordDoorData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResListFieldPasswordDoorData response = new();
|
||||
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
EpinelPS/LobbyServer/Event/GetChallengeStage.cs
Normal file
45
EpinelPS/LobbyServer/Event/GetChallengeStage.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/challengestage/get")]
|
||||
public class GetChallengeStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqChallengeEventStageData req = await ReadData<ReqChallengeEventStageData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResChallengeEventStageData response = new()
|
||||
{
|
||||
RemainTicket = 3,
|
||||
TeamData = new NetUserTeamData
|
||||
{
|
||||
Type = (int)TeamType.StoryEvent
|
||||
},
|
||||
};
|
||||
// check if user has a team for this type
|
||||
if (user.UserTeams.TryGetValue((int)TeamType.StoryEvent, out NetUserTeamData? teamData))
|
||||
{
|
||||
response.TeamData = teamData;
|
||||
}
|
||||
if (!user.EventInfo.TryGetValue(req.EventId, out EventData? eventData))
|
||||
{
|
||||
eventData = new() { LastStage = 0 };
|
||||
user.EventInfo.Add(req.EventId, eventData);
|
||||
}
|
||||
|
||||
// placeholder response data for last cleared stage
|
||||
response.LastClearedEventStageList.Add(new NetLastClearedEventStageData()
|
||||
{
|
||||
DifficultyId = eventData.Diff,
|
||||
StageId = eventData.LastStage
|
||||
});
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
EpinelPS/LobbyServer/Event/GetEventBoxGacha.cs
Normal file
22
EpinelPS/LobbyServer/Event/GetEventBoxGacha.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/boxgacha/get")]
|
||||
public class GetEventBoxGacha : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
// from client: {"EventId":10051}
|
||||
ReqGetEventBoxGacha req = await ReadData<ReqGetEventBoxGacha>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetEventBoxGacha response = new()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EpinelPS/LobbyServer/Event/GetEventScenario.cs
Normal file
29
EpinelPS/LobbyServer/Event/GetEventScenario.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/scenario/get")]
|
||||
public class GetEventScenario : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetEventScenarioData req = await ReadData<ReqGetEventScenarioData>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetEventScenarioData response = new();
|
||||
|
||||
if (response.ScenarioIdList.Count == 0)
|
||||
{
|
||||
if (user.EventInfo.TryGetValue(req.EventId, out EventData? data))
|
||||
{
|
||||
response.ScenarioIdList.AddRange(data.CompletedScenarios);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all scenario_group_Id values from albumResourceRecords starting with "event_"
|
||||
// response.ScenarioIdList.Add(GameData.Instance.albumResourceRecords.Values.Where(record => record.ScenarioGroupId.StartsWith("event_")).Select(record => record.ScenarioGroupId).ToList());
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
EpinelPS/LobbyServer/Event/GetJoinedEvent.cs
Normal file
21
EpinelPS/LobbyServer/Event/GetJoinedEvent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
namespace EpinelPS.LobbyServer.Event
|
||||
{
|
||||
[PacketPath("/event/getjoinedevent")]
|
||||
public class GetJoinedEvent : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
await ReadData<ReqGetJoinedEvent>();
|
||||
//types are defined in EventTypes.cs
|
||||
ResGetJoinedEvent response = new();
|
||||
User user = GetUser();
|
||||
|
||||
// add gacha events from active lobby banners
|
||||
EventHelper.AddJoinedEvents(user, ref response);
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user