commit 6a03b39f07df98e1970f3d1cfd892af12dc8ce69
Author: Naruse <71993948+DevilProMT@users.noreply.github.com>
Date: Sat Jun 14 11:15:32 2025 +0800
Init enter game
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..3d4d3e3
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,125 @@
+[*.cs]
+
+# IDE0022: 使用方法的程序块主体
+csharp_style_expression_bodied_methods = false:silent
+
+[*.cs]
+#### 命名样式 ####
+
+# 命名规则
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# 符号规范
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# 命名样式
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+csharp_using_directive_placement = outside_namespace:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_conditional_delegate_call = true:suggestion
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+csharp_style_var_elsewhere = false:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_space_around_binary_operators = before_and_after
+csharp_indent_labels = one_less_than_current
+
+[*.vb]
+#### 命名样式 ####
+
+# 命名规则
+
+dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion
+dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface
+dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始
+
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型
+dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
+
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员
+dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
+
+# 符号规范
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.类型.required_modifiers =
+
+dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
+dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.非字段成员.required_modifiers =
+
+# 命名样式
+
+dotnet_naming_style.以_i_开始.required_prefix = I
+dotnet_naming_style.以_i_开始.required_suffix =
+dotnet_naming_style.以_i_开始.word_separator =
+dotnet_naming_style.以_i_开始.capitalization = pascal_case
+
+dotnet_naming_style.帕斯卡拼写法.required_prefix =
+dotnet_naming_style.帕斯卡拼写法.required_suffix =
+dotnet_naming_style.帕斯卡拼写法.word_separator =
+dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
+
+dotnet_naming_style.帕斯卡拼写法.required_prefix =
+dotnet_naming_style.帕斯卡拼写法.required_suffix =
+dotnet_naming_style.帕斯卡拼写法.word_separator =
+dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
+
+[*.{cs,vb}]
+end_of_line = crlf
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_event = false:silent
+tab_width = 4
+indent_size = 4
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4207fbf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,370 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+.idea/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+[Ll]aunchSettings.json
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+# *.pubxml
+# *.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# Special Files
+/SdkServer/Properties
+/GameServer/OriginalProto
+*.rar
\ No newline at end of file
diff --git a/Common/Common.csproj b/Common/Common.csproj
new file mode 100644
index 0000000..068792b
--- /dev/null
+++ b/Common/Common.csproj
@@ -0,0 +1,36 @@
+
+
+
+ net9.0
+ enable
+ enable
+ false
+ KianaBH
+ KianaCommon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Common/Configuration/ConfigContainer.cs b/Common/Configuration/ConfigContainer.cs
new file mode 100644
index 0000000..0b440d9
--- /dev/null
+++ b/Common/Configuration/ConfigContainer.cs
@@ -0,0 +1,84 @@
+namespace KianaBH.Configuration;
+
+public class ConfigContainer
+{
+ public HttpServerConfig HttpServer { get; set; } = new();
+ public GameServerConfig GameServer { get; set; } = new();
+ public PathConfig Path { get; set; } = new();
+ public ServerOption ServerOption { get; set; } = new();
+}
+
+public class HttpServerConfig
+{
+ public string BindAddress { get; set; } = "0.0.0.0";
+ public string PublicAddress { get; set; } = "127.0.0.1";
+ public int Port { get; set; } = 80;
+
+ public string GetDisplayAddress()
+ {
+ return "http" + "://" + PublicAddress + ":" + Port;
+ }
+
+ public string GetBindDisplayAddress()
+ {
+ return "http" + "://" + BindAddress + ":" + Port;
+ }
+}
+
+public class GameServerConfig
+{
+ public string BindAddress { get; set; } = "0.0.0.0";
+ public string PublicAddress { get; set; } = "127.0.0.1";
+ public int Port { get; set; } = 21000;
+ public int KcpAliveMs { get; set; } = 45000;
+ public string DatabaseName { get; set; } = "kiana.db";
+ public string GameServerId { get; set; } = "KianaBH";
+ public string GameServerName { get; set; } = "KianaBH";
+ public string GetDisplayAddress()
+ {
+ return PublicAddress + ":" + Port;
+ }
+}
+
+public class PathConfig
+{
+ public string ResourcePath { get; set; } = "Resources";
+ public string ConfigPath { get; set; } = "Config";
+ public string DatabasePath { get; set; } = "Config/Database";
+ public string HandbookPath { get; set; } = "Config/Handbook";
+ public string LogPath { get; set; } = "Config/Logs";
+ public string DataPath { get; set; } = "Config/Data";
+}
+
+public class ServerOption
+{
+ public bool EnableMission { get; set; } = false;
+ public string DefaultGender { get; set; } = "Woman";
+ public string Language { get; set; } = "EN";
+ public string FallbackLanguage { get; set; } = "EN";
+ public string[] DefaultPermissions { get; set; } = ["Admin"];
+ public ServerProfile ServerProfile { get; set; } = new();
+ public bool AutoCreateUser { get; set; } = true;
+ public bool SavePersonalDebugFile { get; set; } = false;
+ public bool AutoSendResponseWhenNoHandler { get; set; } = true;
+#if DEBUG
+ public bool EnableDebug { get; set; } = true;
+#else
+ public bool EnableDebug { get; set; } = false;
+#endif
+ public bool DebugMessage { get; set; } = true;
+ public bool DebugDetailMessage { get; set; } = true;
+ public bool DebugNoHandlerPacket { get; set; } = true;
+}
+
+public class ServerProfile
+{
+ public string Name { get; set; } = "Server";
+ public int Uid { get; set; } = 80;
+ public string Signature { get; set; } = "Type /help for a list of commands";
+ public int Level { get; set; } = 1;
+ public int HeadIcon { get; set; } = 200105;
+ public int ChatBubbleId { get; set; } = 220001;
+ public int DisplayAvatarId { get; set; } = 1001;
+ public int DisplayAvatarLevel { get; set; } = 1;
+}
\ No newline at end of file
diff --git a/Common/Configuration/HotfixContainer.cs b/Common/Configuration/HotfixContainer.cs
new file mode 100644
index 0000000..0e3d60d
--- /dev/null
+++ b/Common/Configuration/HotfixContainer.cs
@@ -0,0 +1,75 @@
+using System.Text.Json.Serialization;
+using Newtonsoft.Json;
+
+namespace KianaBH.Configuration;
+
+public class HotfixContainer
+{
+ public bool UseLocalCache { get; set; } = false;
+ public Dictionary Hotfixes { get; set; } = new();
+ public Dictionary AesKeys { get; set; } = new ();
+
+ public static string ExtractVersionNumber(string? version)
+ {
+ try
+ {
+ return version == null ? "" : version[..version.IndexOf('_')];
+ }
+ catch
+ {
+ return "";
+ }
+ }
+}
+
+public class HotfixManfiset
+{
+ [JsonPropertyName("Asb")] public AsbData Asb { get; set; } = new();
+ [JsonPropertyName("AsbPreDownload")] public AsbPreDownloadData AsbPreDownload { get; set; } = new();
+ [JsonPropertyName("Audio")] public AudioData Audio { get; set; } = new();
+ [JsonPropertyName("AudioPreDownload")] public AudioPreDownloadData AudioPreDownload { get; set; } = new();
+ [JsonPropertyName("VideoEncrypt")] public VideoEncryptData VideoEncrypt { get; set; } = new();
+}
+
+public class AsbData
+{
+ [JsonPropertyName("android")] public PlatformInfo Android { get; set; } = new();
+ [JsonPropertyName("iphone")] public PlatformInfo Iphone { get; set; } = new();
+ [JsonPropertyName("pc")] public PlatformInfo Pc { get; set; } = new();
+}
+
+public class AsbPreDownloadData
+{
+ [JsonPropertyName("android")] public PlatformEncryptedInfo Android { get; set; } = new();
+ [JsonPropertyName("iphone")] public PlatformEncryptedInfo Iphone { get; set; } = new();
+}
+
+public class AudioData
+{
+ [JsonPropertyName("platform")] public Dictionary Platform { get; set; } = new();
+ [JsonPropertyName("revision")] public int Revision { get; set; }
+}
+
+public class AudioPreDownloadData
+{
+ [JsonPropertyName("enable_time")] public long EnableTime { get; set; }
+ [JsonPropertyName("platform")] public Dictionary Platform { get; set; } = new();
+ [JsonPropertyName("revision")] public int Revision { get; set; }
+}
+
+public class VideoEncryptData
+{
+ [JsonPropertyName("filename")] public string FileName { get; set; } = "";
+}
+
+public class PlatformInfo
+{
+ [JsonPropertyName("enable_time")] public long EnableTime { get; set; }
+ [JsonPropertyName("revision")] public string Revision { get; set; } = "";
+ [JsonPropertyName("suffix")] public string Suffix { get; set; } = "";
+}
+
+public class PlatformEncryptedInfo : PlatformInfo
+{
+ [JsonPropertyName("encrypt_key")] public string EncryptKey { get; set; } = "";
+}
\ No newline at end of file
diff --git a/Common/Data/Config/TimeStampConfig.cs b/Common/Data/Config/TimeStampConfig.cs
new file mode 100644
index 0000000..68d1bc6
--- /dev/null
+++ b/Common/Data/Config/TimeStampConfig.cs
@@ -0,0 +1,7 @@
+namespace KianaBH.Data.Config;
+
+public class TimestampConfig
+{
+ public uint TimeStampForBakedReader { get; set; }
+}
+
diff --git a/Common/Data/Excel/ActChallengeDataExcel.cs b/Common/Data/Excel/ActChallengeDataExcel.cs
new file mode 100644
index 0000000..4818146
--- /dev/null
+++ b/Common/Data/Excel/ActChallengeDataExcel.cs
@@ -0,0 +1,24 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("ActChallengeData.json")]
+public class ActChallengeDataExcel : ExcelResource
+{
+ [JsonPropertyName("actId")] public uint ActId { get; set; }
+ [JsonPropertyName("difficulty")] public uint Difficulty { get; set; }
+
+ public override int GetId()
+ {
+ return (int)ActId;
+ }
+
+ public override void Loaded()
+ {
+ if (!GameData.ActChallengeData.ContainsKey(GetId()))
+ {
+ GameData.ActChallengeData[GetId()] = new List();
+ }
+ GameData.ActChallengeData[GetId()].Add(this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/ActivityTowerExcel.cs b/Common/Data/Excel/ActivityTowerExcel.cs
new file mode 100644
index 0000000..eda089a
--- /dev/null
+++ b/Common/Data/Excel/ActivityTowerExcel.cs
@@ -0,0 +1,17 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("ActivityTower.json")]
+public class ActivityTowerExcel : ExcelResource
+{
+ public uint ActivityID { get; set; }
+
+ public override int GetId()
+ {
+ return (int)ActivityID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.ActivityTowerData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/AffixListExcel.cs b/Common/Data/Excel/AffixListExcel.cs
new file mode 100644
index 0000000..8bdfa69
--- /dev/null
+++ b/Common/Data/Excel/AffixListExcel.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("AffixList.json")]
+public class AffixListExcel : ExcelResource
+{
+ [JsonPropertyName("affixID")] public int AffixID { get; set; }
+ [JsonPropertyName("level")] public int Level { get; set; }
+
+ public override int GetId()
+ {
+ return AffixID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.AffixListData.Add(AffixID, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/AvatarDataExcel.cs b/Common/Data/Excel/AvatarDataExcel.cs
new file mode 100644
index 0000000..2c75718
--- /dev/null
+++ b/Common/Data/Excel/AvatarDataExcel.cs
@@ -0,0 +1,27 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("AvatarData.json")]
+public class AvatarDataExcel : ExcelResource
+{
+ [JsonPropertyName("avatarID")] public int AvatarID { get; set; }
+ [JsonPropertyName("unlockStar")] public int UnlockStar { get; set; }
+ [JsonPropertyName("initialWeapon")] public int InitialWeapon { get; set; }
+ [JsonPropertyName("skillList")] public List SkillList { get; set; } = [];
+ public int DefaultDressId { get; set; }
+
+ public override int GetId()
+ {
+ return AvatarID;
+ }
+
+ public override void Loaded()
+ {
+ if (AvatarID != 316 && (AvatarID < 9000 || AvatarID > 20000))
+ {
+ GameData.AvatarData.Add(AvatarID, this);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/AvatarSubSkillDataExcel.cs b/Common/Data/Excel/AvatarSubSkillDataExcel.cs
new file mode 100644
index 0000000..45144f9
--- /dev/null
+++ b/Common/Data/Excel/AvatarSubSkillDataExcel.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("AvatarSubSkillData.json")]
+public class AvatarSubSkillDataExcel : ExcelResource
+{
+ [JsonPropertyName("skillId")] public int SkillId { get; set; }
+ [JsonPropertyName("unlockScoin")] public int UnlockScoin { get; set; }
+ [JsonPropertyName("maxLv")] public int MaxLv { get; set; }
+ [JsonPropertyName("avatarSubSkillId")] public int AvatarSubSkillId { get; set; }
+
+ public override int GetId()
+ {
+ return AvatarSubSkillId;
+ }
+
+ public override void Loaded()
+ {
+ GameData.AvatarSubSkillData.Add(AvatarSubSkillId, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/AvatarTutorialExcel.cs b/Common/Data/Excel/AvatarTutorialExcel.cs
new file mode 100644
index 0000000..f18d4d5
--- /dev/null
+++ b/Common/Data/Excel/AvatarTutorialExcel.cs
@@ -0,0 +1,17 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("AvatarTutorial.json")]
+public class AvatarTutorialExcel : ExcelResource
+{
+ public uint ActivityID { get; set; }
+
+ public override int GetId()
+ {
+ return (int)ActivityID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.AvatarTutorialData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/ChapterGroupConfigExcel.cs b/Common/Data/Excel/ChapterGroupConfigExcel.cs
new file mode 100644
index 0000000..50e18c9
--- /dev/null
+++ b/Common/Data/Excel/ChapterGroupConfigExcel.cs
@@ -0,0 +1,25 @@
+using KianaBH.Data.Config;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("ChapterGroupConfig.json")]
+public class ChapterGroupConfigExcel : ExcelResource
+{
+ public uint ID { get; set; }
+ public uint GroupType { get; set; }
+ public TimestampConfig? BeginShowTime { get; set; }
+ public TimestampConfig? BeginTime { get; set; }
+ public uint BeginShowLevel { get; set; }
+ public List SiteList { get; set; } = [];
+ public uint UnlockLevel { get; set; }
+
+ public override int GetId()
+ {
+ return (int)ID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.ChapterGroupConfigData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/CityEventPhotoExcel.cs b/Common/Data/Excel/CityEventPhotoExcel.cs
new file mode 100644
index 0000000..c27522c
--- /dev/null
+++ b/Common/Data/Excel/CityEventPhotoExcel.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+using KianaBH.Data.Config;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("CityEventPhoto.json")]
+public class CityEventPhotoExcel : ExcelResource
+{
+ public uint PhotoID { get; set; }
+ [JsonPropertyName("photoType")] public uint PhotoType { get; set; }
+
+ public override int GetId()
+ {
+ return (int)PhotoID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.CityEventPhotoData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/CollectionExcel.cs b/Common/Data/Excel/CollectionExcel.cs
new file mode 100644
index 0000000..542fc82
--- /dev/null
+++ b/Common/Data/Excel/CollectionExcel.cs
@@ -0,0 +1,17 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("Collection.json")]
+public class CollectionExcel : ExcelResource
+{
+ public int ID { get; set; }
+
+ public override int GetId()
+ {
+ return ID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.CollectionData.Add(ID, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/CustomHeadDataExcel.cs b/Common/Data/Excel/CustomHeadDataExcel.cs
new file mode 100644
index 0000000..9382096
--- /dev/null
+++ b/Common/Data/Excel/CustomHeadDataExcel.cs
@@ -0,0 +1,19 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("CustomHeadData.json")]
+public class CustomHeadDataExcel : ExcelResource
+{
+ [JsonPropertyName("headID")] public uint HeadID { get; set; }
+
+ public override int GetId()
+ {
+ return (int)HeadID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.CustomHeadData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/DressDataExcel.cs b/Common/Data/Excel/DressDataExcel.cs
new file mode 100644
index 0000000..f64679f
--- /dev/null
+++ b/Common/Data/Excel/DressDataExcel.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("DressData.json")]
+public class DressDataExcel : ExcelResource
+{
+ [JsonPropertyName("dressID")] public uint DressID { get; set; }
+ [JsonPropertyName("avatarIDList")] public List AvatarIDList { get; set; } = [];
+
+ public override int GetId()
+ {
+ return (int)DressID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.DressData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/ElfAstraMateDataExcel.cs b/Common/Data/Excel/ElfAstraMateDataExcel.cs
new file mode 100644
index 0000000..99484dd
--- /dev/null
+++ b/Common/Data/Excel/ElfAstraMateDataExcel.cs
@@ -0,0 +1,30 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("Elf_AstraMate_Data.json")]
+public class ElfAstraMateDataExcel : ExcelResource
+{
+ public uint ElfID { get; set; }
+ public uint MaxLevel { get; set; }
+ public uint MaxRarity { get; set; }
+
+ [JsonIgnore] public List SkillList = [];
+
+ public override int GetId()
+ {
+ return (int)ElfID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.ElfAstraMateData.Add(GetId(), this);
+ }
+
+ public override void AfterAllDone()
+ {
+ GameData.ElfSkillData.TryGetValue(GetId(), out var Skills);
+ if (Skills == null || !Skills.ElfIds.Contains(ElfID)) return;
+ SkillList.Add(Skills);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/ElfSkillDataExcel.cs b/Common/Data/Excel/ElfSkillDataExcel.cs
new file mode 100644
index 0000000..4170876
--- /dev/null
+++ b/Common/Data/Excel/ElfSkillDataExcel.cs
@@ -0,0 +1,18 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("ElfSkillData.json")]
+public class ElfSkillDataExcel : ExcelResource
+{
+ public uint ElfSkillID { get; set; }
+ public uint MaxLv { get; set; }
+ public List ElfIds { get; set; } = [];
+ public override int GetId()
+ {
+ return (int)ElfSkillID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.ElfSkillData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/EntryThemeDataExcel.cs b/Common/Data/Excel/EntryThemeDataExcel.cs
new file mode 100644
index 0000000..d5ac692
--- /dev/null
+++ b/Common/Data/Excel/EntryThemeDataExcel.cs
@@ -0,0 +1,18 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("EntryThemeData.json")]
+public class EntryThemeDataExcel : ExcelResource
+{
+ public uint SpaceShipConfigId { get; set; }
+ public List ThemeBgmConfigList { get; set; } = [];
+ public List ThemeTagList { get; set; } = [];
+ public override int GetId()
+ {
+ return (int)SpaceShipConfigId;
+ }
+
+ public override void Loaded()
+ {
+ GameData.EntryThemeData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/EntryThemeItemDataExcel.cs b/Common/Data/Excel/EntryThemeItemDataExcel.cs
new file mode 100644
index 0000000..aed66b7
--- /dev/null
+++ b/Common/Data/Excel/EntryThemeItemDataExcel.cs
@@ -0,0 +1,16 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("EntryThemeItemData.json")]
+public class EntryThemeItemDataExcel : ExcelResource
+{
+ public int ThemeItemID { get; set; }
+ public override int GetId()
+ {
+ return ThemeItemID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.EntryThemeItemData.Add(ThemeItemID, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/FrameDataExcel.cs b/Common/Data/Excel/FrameDataExcel.cs
new file mode 100644
index 0000000..cf97fc2
--- /dev/null
+++ b/Common/Data/Excel/FrameDataExcel.cs
@@ -0,0 +1,19 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("FrameData.json")]
+public class FrameDataExcel : ExcelResource
+{
+ [JsonPropertyName("id")] public uint Id { get; set; }
+
+ public override int GetId()
+ {
+ return (int)Id;
+ }
+
+ public override void Loaded()
+ {
+ GameData.FrameData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/GeneralActivityExcel.cs b/Common/Data/Excel/GeneralActivityExcel.cs
new file mode 100644
index 0000000..8a1f1a6
--- /dev/null
+++ b/Common/Data/Excel/GeneralActivityExcel.cs
@@ -0,0 +1,18 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("GeneralActivity.json")]
+public class GeneralActivityExcel : ExcelResource
+{
+ public uint AcitivityID { get; set; }
+ public uint Series { get; set; }
+
+ public override int GetId()
+ {
+ return (int)AcitivityID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.GeneralActivityData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/GeneralActivityStageGroupExcel.cs b/Common/Data/Excel/GeneralActivityStageGroupExcel.cs
new file mode 100644
index 0000000..f0a8adb
--- /dev/null
+++ b/Common/Data/Excel/GeneralActivityStageGroupExcel.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("GeneralActivityStageGroup.json")]
+public class GeneralActivityStageGroupExcel : ExcelResource
+{
+ public uint AcitivityId { get; set; }
+ public uint StageGroupId { get; set; }
+
+ public override int GetId()
+ {
+ return (int)AcitivityId;
+ }
+
+ public override void Loaded()
+ {
+ if (!GameData.GeneralActivityStageGroupData.ContainsKey(GetId()))
+ {
+ GameData.GeneralActivityStageGroupData[GetId()] = new List();
+ }
+ GameData.GeneralActivityStageGroupData[GetId()].Add(this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/GodWarEventExcel.cs b/Common/Data/Excel/GodWarEventExcel.cs
new file mode 100644
index 0000000..4ab5f14
--- /dev/null
+++ b/Common/Data/Excel/GodWarEventExcel.cs
@@ -0,0 +1,20 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("GodWarEvent.json")]
+public class GodWarEventExcel : ExcelResource
+{
+ public uint EventID { get; set; }
+ public int EventType { get; set; }
+ public List ParamsVar { get; set; } = [];
+
+
+ public override int GetId()
+ {
+ return (int)EventID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.GodWarEventData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/GodWarMainAvatarExcel.cs b/Common/Data/Excel/GodWarMainAvatarExcel.cs
new file mode 100644
index 0000000..16b807f
--- /dev/null
+++ b/Common/Data/Excel/GodWarMainAvatarExcel.cs
@@ -0,0 +1,17 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("GodWarMainAvatar.json")]
+public class GodWarMainAvatarExcel : ExcelResource
+{
+ public int MainAvatarID { get; set; }
+
+ public override int GetId()
+ {
+ return MainAvatarID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.GodWarMainAvatarData.Add(MainAvatarID, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/GodWarRelationDataExcel.cs b/Common/Data/Excel/GodWarRelationDataExcel.cs
new file mode 100644
index 0000000..f964bc7
--- /dev/null
+++ b/Common/Data/Excel/GodWarRelationDataExcel.cs
@@ -0,0 +1,20 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("GodWarRelationData.json")]
+public class GodWarRelationDataExcel : ExcelResource
+{
+ public int AvatarID { get; set; }
+ public int RoleID { get; set; }
+ public int Level { get; set; }
+ public int MaxLevel { get; set; }
+
+ public override int GetId()
+ {
+ return AvatarID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.GodWarRelationData.Add(this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/GodWarSupportAvatarExcel.cs b/Common/Data/Excel/GodWarSupportAvatarExcel.cs
new file mode 100644
index 0000000..3335f70
--- /dev/null
+++ b/Common/Data/Excel/GodWarSupportAvatarExcel.cs
@@ -0,0 +1,17 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("GodWarSupportAvatar.json")]
+public class GodWarSupportAvatarExcel : ExcelResource
+{
+ public int SupportAvatarID { get; set; }
+
+ public override int GetId()
+ {
+ return SupportAvatarID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.GodWarSupportAvatarData.Add(SupportAvatarID, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/GodWarTaleScheduleExcel.cs b/Common/Data/Excel/GodWarTaleScheduleExcel.cs
new file mode 100644
index 0000000..d6f6b0e
--- /dev/null
+++ b/Common/Data/Excel/GodWarTaleScheduleExcel.cs
@@ -0,0 +1,22 @@
+using KianaBH.Data.Config;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("GodWarTaleSchedule.json")]
+public class GodWarTaleScheduleExcel : ExcelResource
+{
+ public uint TaleScheduleID { get; set; }
+ public List TaleIDList { get; set; } = [];
+ public TimestampConfig? BeginTime { get; set; }
+ public TimestampConfig? EndTime { get; set; }
+
+ public override int GetId()
+ {
+ return (int)TaleScheduleID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.GodWarTaleScheduleData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/GodWarTalentDataExcel.cs b/Common/Data/Excel/GodWarTalentDataExcel.cs
new file mode 100644
index 0000000..f468041
--- /dev/null
+++ b/Common/Data/Excel/GodWarTalentDataExcel.cs
@@ -0,0 +1,19 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("GodWarTalentData.json")]
+public class GodWarTalentDataExcel : ExcelResource
+{
+ public uint TalentID { get; set; }
+ public uint MaxLevel { get; set; }
+ public List TaleIDList { get; set; } = [];
+
+ public override int GetId()
+ {
+ return (int)TalentID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.GodWarTalentData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/MaterialDataExcel.cs b/Common/Data/Excel/MaterialDataExcel.cs
new file mode 100644
index 0000000..ecdf10c
--- /dev/null
+++ b/Common/Data/Excel/MaterialDataExcel.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("MaterialData.json")]
+public class MaterialDataExcel : ExcelResource
+{
+ [JsonPropertyName("ID")] public int Id { get; set; }
+ [JsonPropertyName("rarity")] public int Rarity { get; set; }
+ [JsonPropertyName("maxRarity")] public int MaxRarity { get; set; }
+ [JsonPropertyName("quantityLimit")] public int QuantityLimit { get; set; }
+
+ public override int GetId()
+ {
+ return Id;
+ }
+
+ public override void Loaded()
+ {
+ GameData.MaterialData.Add(Id, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/MissionDataExcel.cs b/Common/Data/Excel/MissionDataExcel.cs
new file mode 100644
index 0000000..d3c0367
--- /dev/null
+++ b/Common/Data/Excel/MissionDataExcel.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("MissionData.json")]
+public class MissionDataExcel : ExcelResource
+{
+ [JsonPropertyName("id")] public uint Id { get; set; }
+ [JsonPropertyName("totalProgress")] public uint TotalProgress { get; set; }
+ public uint Priority { get; set; }
+
+
+ public override int GetId()
+ {
+ return (int)Id;
+ }
+
+ public override void Loaded()
+ {
+ GameData.MissionData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/PhonePendantDataExcel.cs b/Common/Data/Excel/PhonePendantDataExcel.cs
new file mode 100644
index 0000000..bf44054
--- /dev/null
+++ b/Common/Data/Excel/PhonePendantDataExcel.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("PhonePendantData.json")]
+public class PhonePendantDataExcel : ExcelResource
+{
+ public uint PendantId { get; set; }
+ public uint Rarity { get; set; }
+
+ public override int GetId()
+ {
+ return (int)PendantId;
+ }
+
+ public override void Loaded()
+ {
+ GameData.PhonePendantData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/RandomPlotDataExcel.cs b/Common/Data/Excel/RandomPlotDataExcel.cs
new file mode 100644
index 0000000..3439de8
--- /dev/null
+++ b/Common/Data/Excel/RandomPlotDataExcel.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("RandomPlotData.json")]
+public class RandomPlotDataExcel : ExcelResource
+{
+ [JsonPropertyName("plotId")] public uint PlotId { get; set; }
+ [JsonPropertyName("startDialogId")] public uint StartDialogId { get; set; }
+ [JsonPropertyName("finishDialogIdList")] public List FinishDialogIdList { get; set; } = [];
+
+ public override int GetId()
+ {
+ return (int)PlotId;
+ }
+
+ public override void Loaded()
+ {
+ GameData.RandomPlotData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/RecommendPanelExcel.cs b/Common/Data/Excel/RecommendPanelExcel.cs
new file mode 100644
index 0000000..4bef085
--- /dev/null
+++ b/Common/Data/Excel/RecommendPanelExcel.cs
@@ -0,0 +1,17 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("RecommendPanel.json")]
+public class RecommendPanelExcel : ExcelResource
+{
+ public uint PanelId { get; set; }
+
+ public override int GetId()
+ {
+ return (int)PanelId;
+ }
+
+ public override void Loaded()
+ {
+ GameData.RecommendPanelData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/StageDataMainExcel.cs b/Common/Data/Excel/StageDataMainExcel.cs
new file mode 100644
index 0000000..107c71d
--- /dev/null
+++ b/Common/Data/Excel/StageDataMainExcel.cs
@@ -0,0 +1,28 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("StageData_Main.json")]
+public class StageDataMainExcel : ExcelResource
+{
+ [JsonPropertyName("levelId")] public uint LevelId { get; set; }
+ [JsonPropertyName("maxProgress")] public uint MaxProgress { get; set; }
+ [JsonPropertyName("challengeList")] public List ChallengeList { get; set; } = [];
+
+ public override int GetId()
+ {
+ return (int)LevelId;
+ }
+
+ public override void Loaded()
+ {
+ GameData.StageDataMain.Add(GetId(), this);
+ }
+}
+
+
+public class ChallengeData
+{
+ [JsonPropertyName("challengeId")] public uint ChallengeId { get; set; }
+ [JsonPropertyName("rewardId")] public uint RewardId { get; set; }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/StepMissionCompensationExcel.cs b/Common/Data/Excel/StepMissionCompensationExcel.cs
new file mode 100644
index 0000000..b55d794
--- /dev/null
+++ b/Common/Data/Excel/StepMissionCompensationExcel.cs
@@ -0,0 +1,21 @@
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("StepMissionCompensation.json")]
+public class StepMissionCompensationExcel : ExcelResource
+{
+ public uint CompensationId { get; set; }
+ public uint UnlockLevel { get; set; }
+ public List MainLineStepIdList { get; set; } = [];
+ public List NewChallengeStepIdList { get; set; } = [];
+ public List OldChallengeStepIdList { get; set; } = [];
+
+ public override int GetId()
+ {
+ return (int)CompensationId;
+ }
+
+ public override void Loaded()
+ {
+ GameData.StepMissionCompensationData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/StigmataDataExcel.cs b/Common/Data/Excel/StigmataDataExcel.cs
new file mode 100644
index 0000000..a0a434d
--- /dev/null
+++ b/Common/Data/Excel/StigmataDataExcel.cs
@@ -0,0 +1,26 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("StigmataData.json")]
+public class StigmataDataExcel : ExcelResource
+{
+ public int ID { get; set; }
+ [JsonPropertyName("maxLv")] public int MaxLv { get; set; }
+ [JsonPropertyName("rarity")] public int Rarity { get; set; }
+ [JsonPropertyName("maxRarity")] public int MaxRarity { get; set; }
+ [JsonPropertyName("evoID")] public int EvoID { get; set; }
+ [JsonPropertyName("quality")] public int Quality { get; set; }
+ [JsonPropertyName("isSecurityProtect")] public bool IsSecurityProtect { get; set; }
+ [JsonPropertyName("protect")] public bool Protect { get; set; }
+
+ public override int GetId()
+ {
+ return ID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.StigmataData.Add(ID, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/ThemeDataAvatarExcel.cs b/Common/Data/Excel/ThemeDataAvatarExcel.cs
new file mode 100644
index 0000000..1a74321
--- /dev/null
+++ b/Common/Data/Excel/ThemeDataAvatarExcel.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("ThemeData_Avatar.json")]
+public class ThemeDataAvatarExcel : ExcelResource
+{
+ public uint AvatarData { get; set; }
+ public List BuffList { get; set; } = [];
+ [JsonPropertyName("avatarIDList")] public List AvatarIDList { get; set; } = [];
+
+ public override int GetId()
+ {
+ return (int)AvatarData;
+ }
+
+ public override void Loaded()
+ {
+ GameData.ThemeDataAvatar.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/TutorialDataExcel.cs b/Common/Data/Excel/TutorialDataExcel.cs
new file mode 100644
index 0000000..3d34769
--- /dev/null
+++ b/Common/Data/Excel/TutorialDataExcel.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("TutorialData.json")]
+public class TutorialDataExcel : ExcelResource
+{
+ [JsonPropertyName("id")] public uint Id { get; set; }
+ [JsonPropertyName("index")] public uint Index { get; set; }
+
+ public override int GetId()
+ {
+ return (int)Id;
+ }
+
+ public override void Loaded()
+ {
+ GameData.TutorialData.Add(GetId(), this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Excel/WeaponDataExcel.cs b/Common/Data/Excel/WeaponDataExcel.cs
new file mode 100644
index 0000000..15c5def
--- /dev/null
+++ b/Common/Data/Excel/WeaponDataExcel.cs
@@ -0,0 +1,25 @@
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data.Excel;
+
+[ResourceEntity("WeaponData.json")]
+public class WeaponDataExcel : ExcelResource
+{
+ public int ID { get; set; }
+ [JsonPropertyName("weaponMainID")] public int WeaponMainID { get; set; }
+ [JsonPropertyName("maxLv")] public int MaxLv { get; set; }
+ [JsonPropertyName("rarity")] public int Rarity { get; set; }
+ [JsonPropertyName("maxRarity")] public int MaxRarity { get; set; }
+ [JsonPropertyName("evoID")] public int EvoID { get; set; }
+ [JsonPropertyName("protect")] public bool Protect { get; set; }
+
+ public override int GetId()
+ {
+ return ID;
+ }
+
+ public override void Loaded()
+ {
+ GameData.WeaponData.Add(ID, this);
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/ExcelResource.cs b/Common/Data/ExcelResource.cs
new file mode 100644
index 0000000..084bf7f
--- /dev/null
+++ b/Common/Data/ExcelResource.cs
@@ -0,0 +1,18 @@
+namespace KianaBH.Data;
+
+public abstract class ExcelResource
+{
+ public abstract int GetId();
+
+ public virtual void Loaded()
+ {
+ }
+
+ public virtual void Finalized()
+ {
+ }
+
+ public virtual void AfterAllDone()
+ {
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs
new file mode 100644
index 0000000..c124bbb
--- /dev/null
+++ b/Common/Data/GameData.cs
@@ -0,0 +1,45 @@
+using KianaBH.Data.Excel;
+using KianaBH.Util.Extensions;
+using System.Collections.Concurrent;
+using System.Text.Json.Serialization;
+
+namespace KianaBH.Data;
+
+public static class GameData
+{
+ public static Dictionary> ActChallengeData { get; private set; } = [];
+ public static Dictionary ActivityTowerData { get; private set; } = [];
+ public static Dictionary AffixListData { get; private set; } = [];
+ public static Dictionary AvatarData { get; private set; } = [];
+ public static Dictionary AvatarSubSkillData { get; private set; } = [];
+ public static Dictionary AvatarTutorialData { get; private set; } = [];
+ public static Dictionary CollectionData { get; private set; } = [];
+ public static Dictionary CustomHeadData { get; private set; } = [];
+ public static Dictionary DressData { get; private set; } = [];
+ public static Dictionary ElfAstraMateData { get; private set; } = [];
+ public static Dictionary ElfSkillData { get; private set; } = [];
+ public static Dictionary EntryThemeData { get; private set; } = [];
+ public static Dictionary EntryThemeItemData { get; private set; } = [];
+ public static Dictionary FrameData { get; private set; } = [];
+ public static Dictionary> GeneralActivityStageGroupData { get; private set; } = [];
+ public static Dictionary GeneralActivityData { get; private set; } = [];
+ public static Dictionary GodWarEventData { get; private set; } = [];
+ public static Dictionary GodWarMainAvatarData { get; private set; } = [];
+ public static List GodWarRelationData { get; private set; } = [];
+ public static Dictionary GodWarSupportAvatarData { get; private set; } = [];
+ public static Dictionary GodWarTaleScheduleData { get; private set; } = [];
+ public static Dictionary GodWarTalentData { get; private set; } = [];
+ public static Dictionary MaterialData { get; private set; } = [];
+ public static Dictionary MissionData { get; private set; } = [];
+ public static Dictionary RecommendPanelData { get; private set; } = [];
+ public static Dictionary StageDataMain { get; private set; } = [];
+ public static Dictionary StepMissionCompensationData { get; private set; } = [];
+ public static Dictionary StigmataData { get; private set; } = [];
+ public static Dictionary ThemeDataAvatar { get; private set; } = [];
+ public static Dictionary WeaponData { get; private set; } = [];
+ public static Dictionary ChapterGroupConfigData { get; private set; } = [];
+ public static Dictionary PhonePendantData { get; private set; } = [];
+ public static Dictionary TutorialData { get; private set; } = [];
+ public static Dictionary CityEventPhotoData { get; private set; } = [];
+ public static Dictionary RandomPlotData { get; private set; } = [];
+}
\ No newline at end of file
diff --git a/Common/Data/Models/Dispatch/DispatchQuery.cs b/Common/Data/Models/Dispatch/DispatchQuery.cs
new file mode 100644
index 0000000..ce06972
--- /dev/null
+++ b/Common/Data/Models/Dispatch/DispatchQuery.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+using Microsoft.AspNetCore.Mvc;
+
+namespace KianaBH.Data.Models.Dispatch;
+
+public class DispatchQuery
+{
+ [Required] public string? Version { get; set; }
+ [FromQuery(Name = "t")] public int Timestamp { get; set; }
+ public string? Lang { get; set; }
+ public int Uid { get; set; }
+ public string? Token { get; set; }
+}
\ No newline at end of file
diff --git a/Common/Data/Models/Dispatch/QueryDispatchModels.cs b/Common/Data/Models/Dispatch/QueryDispatchModels.cs
new file mode 100644
index 0000000..ad1655f
--- /dev/null
+++ b/Common/Data/Models/Dispatch/QueryDispatchModels.cs
@@ -0,0 +1,17 @@
+namespace KianaBH.Data.Models.Dispatch;
+
+public class QueryDispatchResponse
+{
+ public int Retcode { get; set; }
+ public List RegionList { get; set; } = [];
+
+
+ public class RegionInfo
+ {
+ public string? DispatchUrl { get; set; }
+ public object? Ext { get; set; }
+ public string? Name { get; set; }
+ public int Retcode { get; set; }
+ public string? Title { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Models/Dispatch/QueryGatewayModels.cs b/Common/Data/Models/Dispatch/QueryGatewayModels.cs
new file mode 100644
index 0000000..a2b0bc6
--- /dev/null
+++ b/Common/Data/Models/Dispatch/QueryGatewayModels.cs
@@ -0,0 +1,30 @@
+using KianaBH.Util.Extensions;
+using KianaBH.Configuration;
+
+namespace KianaBH.Data.Models.Dispatch;
+
+public class QueryGatewayResponse
+{
+ public long ServerCurTime { get; set; } = Extensions.GetUnixSec();
+ public int ServerCurTimezone { get; set; } = (int)TimeZoneInfo.Local.GetUtcOffset(DateTime.Now).TotalHours;
+ public string RegionName { get; set; } = "KianaBH";
+ public string Msg { get; set; } = "";
+ public bool IsDataReady { get; set; } = true;
+ public int Retcode { get; set; }
+
+ public string? AccountUrl { get; set; }
+ public ServerInfo? Gameserver { get; set; }
+ public ServerInfo? Gateway { get; set; }
+ public List ExResourceUrlList { get; set; } = [];
+ public List ExAudioAndVideoUrlList { get; set; } = [];
+ public List AssetBundleUrlList { get; set; } = [];
+ public HotfixManfiset? Manifest { get; set; }
+ public Dictionary Ext { get; set; } = new();
+
+ public class ServerInfo
+ {
+ public string? Ip { get; set; }
+ public bool IsKcp { get; set; }
+ public int Port { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Common/Data/Models/Sdk/ComboGranterModels.cs b/Common/Data/Models/Sdk/ComboGranterModels.cs
new file mode 100644
index 0000000..2716161
--- /dev/null
+++ b/Common/Data/Models/Sdk/ComboGranterModels.cs
@@ -0,0 +1,34 @@
+using System.Text.Json.Serialization;
+using System.ComponentModel.DataAnnotations;
+using KianaBH.Util.Extensions;
+
+namespace KianaBH.Data.Models.Sdk;
+
+public class ComboGranterData
+{
+ public string? Uid { get; set; }
+ public string? Token { get; set; }
+}
+
+public class ComboGranterRequest
+{
+ [Required]
+ [JsonConverter(typeof(JsonStringToObjectConverter))]
+ public ComboGranterData? Data { get; set; }
+}
+
+public class ComboGranterResponse : ResponseBase
+{
+ public new ComboGranterResponseData? Data { get; set; }
+
+ public class ComboGranterResponseData
+ {
+ public uint AccountType { get; set; }
+ public string? OpenId { get; set; }
+ public string? ComboId { get; set; }
+ public string? ComboToken { get; set; }
+ public bool Heartbeat { get; set; }
+ public string? Data { get; set; }
+ }
+}
+
diff --git a/Common/Data/Models/Sdk/DeviceFingerprintController.cs b/Common/Data/Models/Sdk/DeviceFingerprintController.cs
new file mode 100644
index 0000000..cf3f937
--- /dev/null
+++ b/Common/Data/Models/Sdk/DeviceFingerprintController.cs
@@ -0,0 +1,6 @@
+namespace KianaBH.Data.Models.Sdk;
+public class GetDeviceFingerprintRequest
+{
+ public string? DeviceFp { get; set; }
+}
+
diff --git a/Common/Data/Models/Sdk/GameWeatherModels.cs b/Common/Data/Models/Sdk/GameWeatherModels.cs
new file mode 100644
index 0000000..9c5ebb8
--- /dev/null
+++ b/Common/Data/Models/Sdk/GameWeatherModels.cs
@@ -0,0 +1,20 @@
+namespace KianaBH.Data.Models.Sdk;
+
+public class GetWeatherResponse : ResponseBase
+{
+ public new GetWeatherResponseData? Data { get; set; }
+
+ public class GetWeatherResponseData
+ {
+ public int Timezone { get; set; }
+ public List Hourly { get; set; } = [];
+
+ public class HourlyWeatherData
+ {
+ public int Condition { get; set; }
+ public int Hour { get; set; }
+ public string? Date { get; set; }
+ public int Temp { get; set; }
+ }
+ }
+}
diff --git a/Common/Data/Models/Sdk/MdkShieldModels.cs b/Common/Data/Models/Sdk/MdkShieldModels.cs
new file mode 100644
index 0000000..1b0945e
--- /dev/null
+++ b/Common/Data/Models/Sdk/MdkShieldModels.cs
@@ -0,0 +1,42 @@
+namespace KianaBH.Data.Models.Sdk;
+
+public class MdkShieldLoginRequest
+{
+ public string? Account { get; set; }
+ public string? Password { get; set; }
+ public bool IsCrypto { get; set; }
+}
+
+public class MdkShieldVerifyRequest
+{
+ public string? Uid { get; set; }
+ public string? Token { get; set; }
+}
+
+// TODO: Move this to DB instead
+public class MdkShieldAccountData
+{
+ public string? Token { get; set; }
+ public string? Uid { get; set; }
+
+ public string Email { get; set; } = "";
+ public string IsEmailVerify { get; set; } = "0";
+ public string AreaCode { get; set; } = "";
+ public string Country { get; set; } = "";
+ public string Name { get; set; } = "";
+ public string Realname { get; set; } = "";
+}
+
+public class MdkShieldResponse : ResponseBase
+{
+ public new MdkShieldResponseData? Data { get; set; }
+
+ public class MdkShieldResponseData
+ {
+ public MdkShieldAccountData? Account { get; set; }
+ public bool DeviceGrantRequired { get; set; }
+ public bool ReactiveRequired { get; set; }
+ public bool RealpersonRequired { get; set; }
+ public bool SafeMobileRequeired { get; set; }
+ }
+}
diff --git a/Common/Data/Models/Sdk/ResponseBase.cs b/Common/Data/Models/Sdk/ResponseBase.cs
new file mode 100644
index 0000000..c3da79a
--- /dev/null
+++ b/Common/Data/Models/Sdk/ResponseBase.cs
@@ -0,0 +1,9 @@
+namespace KianaBH.Data.Models.Sdk;
+
+public class ResponseBase
+{
+ public string Message { get; set; } = "OK";
+ public bool Success { get; set; } = true;
+ public int Retcode { get; set; }
+ public object? Data { get; set; }
+}
\ No newline at end of file
diff --git a/Common/Data/ResourceEntity.cs b/Common/Data/ResourceEntity.cs
new file mode 100644
index 0000000..44342a6
--- /dev/null
+++ b/Common/Data/ResourceEntity.cs
@@ -0,0 +1,33 @@
+namespace KianaBH.Data;
+
+[AttributeUsage(AttributeTargets.Class, Inherited = false)]
+public class ResourceEntity : Attribute
+{
+ [Obsolete("No effect")]
+ public ResourceEntity(string fileName, bool isCritical = false, bool isMultifile = false)
+ {
+ if (isMultifile)
+ FileName = new List(fileName.Split(','));
+ else
+ FileName = [fileName];
+ IsCritical = isCritical;
+ }
+
+
+ public ResourceEntity(string fileName, bool isMultifile = false)
+ {
+ if (isMultifile)
+ FileName = new List(fileName.Split(','));
+ else
+ FileName = [fileName];
+ }
+
+ public ResourceEntity(string fileName)
+ {
+ FileName = [fileName];
+ }
+
+ public List FileName { get; private set; }
+
+ [Obsolete("No effect")] public bool IsCritical { get; private set; } // deprecated
+}
\ No newline at end of file
diff --git a/Common/Data/ResourceManager.cs b/Common/Data/ResourceManager.cs
new file mode 100644
index 0000000..d285d01
--- /dev/null
+++ b/Common/Data/ResourceManager.cs
@@ -0,0 +1,169 @@
+using KianaBH.Internationalization;
+using KianaBH.Util;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System.Reflection;
+
+namespace KianaBH.Data;
+
+public class ResourceManager
+{
+ public static Logger Logger { get; } = new("ResourceManager");
+ public static bool IsLoaded { get; set; }
+
+ public static void LoadGameData()
+ {
+ LoadExcel();
+ }
+
+ public static void LoadExcel()
+ {
+ var classes = Assembly.GetExecutingAssembly().GetTypes(); // Get all classes in the assembly
+ List resList = [];
+
+ foreach (var cls in classes.Where(x => x.IsSubclassOf(typeof(ExcelResource))))
+ {
+ var res = LoadSingleExcelResource(cls);
+ if (res != null) resList.AddRange(res);
+ }
+
+ foreach (var cls in resList) cls.AfterAllDone();
+ }
+
+ public static List? LoadSingleExcel(Type cls) where T : ExcelResource, new()
+ {
+ return LoadSingleExcelResource(cls) as List;
+ }
+
+ public static List? LoadSingleExcelResource(Type cls)
+ {
+ var attribute = (ResourceEntity?)Attribute.GetCustomAttribute(cls, typeof(ResourceEntity));
+
+ if (attribute == null) return null;
+ var resource = (ExcelResource)Activator.CreateInstance(cls)!;
+ var count = 0;
+ List resList = [];
+ foreach (var fileName in attribute.FileName)
+ try
+ {
+ var path = ConfigManager.Config.Path.ResourcePath + "/ExcelOutput/" + fileName;
+ var file = new FileInfo(path);
+ if (!file.Exists)
+ {
+ Logger.Error(I18NManager.Translate("Server.ServerInfo.FailedToReadItem", fileName,
+ I18NManager.Translate("Word.NotFound")));
+ continue;
+ }
+
+ var json = file.OpenText().ReadToEnd();
+ using (var reader = new JsonTextReader(new StringReader(json)))
+ {
+ reader.Read();
+ switch (reader.TokenType)
+ {
+ case JsonToken.StartArray:
+ {
+ // array
+ var jArray = JArray.Parse(json);
+ foreach (var item in jArray)
+ {
+ var res = JsonConvert.DeserializeObject(item.ToString(), cls);
+ resList.Add((ExcelResource)res!);
+ ((ExcelResource?)res)?.Loaded();
+ count++;
+ }
+
+ break;
+ }
+ case JsonToken.StartObject:
+ {
+ // dictionary
+ var jObject = JObject.Parse(json);
+ foreach (var (_, obj) in jObject)
+ {
+ var instance = JsonConvert.DeserializeObject(obj!.ToString(), cls);
+
+ if (((ExcelResource?)instance)?.GetId() == 0 || (ExcelResource?)instance == null)
+ {
+ // Deserialize as JObject to handle nested dictionaries
+ var nestedObject = JsonConvert.DeserializeObject(obj.ToString());
+
+ foreach (var nestedItem in nestedObject ?? [])
+ {
+ var nestedInstance =
+ JsonConvert.DeserializeObject(nestedItem.Value!.ToString(), cls);
+ resList.Add((ExcelResource)nestedInstance!);
+ ((ExcelResource?)nestedInstance)?.Loaded();
+ count++;
+ }
+ }
+ else
+ {
+ resList.Add((ExcelResource)instance);
+ ((ExcelResource)instance).Loaded();
+ }
+
+ count++;
+ }
+
+ break;
+ }
+ }
+ }
+
+ resource.Finalized();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(
+ I18NManager.Translate("Server.ServerInfo.FailedToReadItem", fileName,
+ I18NManager.Translate("Word.Error")), ex);
+ }
+
+ Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadedItems", count.ToString(), cls.Name));
+
+ return resList;
+ }
+
+ public static T? LoadCustomFile(string filetype, string filename)
+ {
+ var type = I18NManager.Translate("Word." + filetype);
+ Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadingItem", type));
+ FileInfo file = new(ConfigManager.Config.Path.DataPath + $"/{filename}.json");
+ T? customFile = default;
+ if (!file.Exists)
+ {
+ Logger.Warn(I18NManager.Translate("Server.ServerInfo.ConfigMissing", type,
+ $"{ConfigManager.Config.Path.DataPath}/{filename}.json", type));
+ return customFile;
+ }
+
+ try
+ {
+ using var reader = file.OpenRead();
+ using StreamReader reader2 = new(reader);
+ var text = reader2.ReadToEnd();
+ var json = JsonConvert.DeserializeObject(text);
+ customFile = json;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Error in reading " + file.Name, ex);
+ }
+
+ switch (customFile)
+ {
+ case Dictionary d:
+ Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadedItems", d.Count.ToString(), type));
+ break;
+ case Dictionary> di:
+ Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadedItems", di.Count.ToString(), type));
+ break;
+ default:
+ Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadedItem", filetype));
+ break;
+ }
+
+ return customFile;
+ }
+}
\ No newline at end of file
diff --git a/Common/Database/Account/AccountData.cs b/Common/Database/Account/AccountData.cs
new file mode 100644
index 0000000..4ba0305
--- /dev/null
+++ b/Common/Database/Account/AccountData.cs
@@ -0,0 +1,150 @@
+using KianaBH.Enums.Player;
+using KianaBH.Util;
+using KianaBH.Util.Extensions;
+using KianaBH.Util.Security;
+using SqlSugar;
+
+namespace KianaBH.Database.Account;
+
+[SugarTable("Account")]
+public class AccountData : BaseDatabaseDataHelper
+{
+ public string Username { get; set; } = "";
+ public string Password { get; set; } = "";
+ public BanTypeEnum BanType { get; set; }
+
+ [SugarColumn(IsJson = true)] public List Permissions { get; set; } = [];
+
+ [SugarColumn(IsNullable = true)] public string? ComboToken { get; set; }
+
+ #region GetAccount
+
+ public static AccountData? GetAccountByUserName(string username)
+ {
+ AccountData? result = null;
+ DatabaseHelper.GetAllInstance()?.ForEach(account =>
+ {
+ if (account.Username == username) result = account;
+ });
+ return result;
+ }
+
+ public static AccountData? GetAccountByUid(int uid, bool force = false)
+ {
+ var result = DatabaseHelper.GetInstance(uid, force);
+ return result;
+ }
+
+ #endregion
+
+ #region Account
+
+ public static void CreateAccount(string username, int uid, string password)
+ {
+ var newUid = uid;
+ if (uid == 0)
+ {
+ newUid = 100001;
+ while (GetAccountByUid(newUid) != null) newUid++;
+ }
+
+ var account = new AccountData
+ {
+ Uid = newUid,
+ Username = username,
+ Password = "",
+ Permissions = [.. ConfigManager.Config.ServerOption.DefaultPermissions
+ .Select(perm => Enum.TryParse(perm, out PermEnum result) ? result : (PermEnum?)null)
+ .Where(result => result.HasValue).Select(result => result!.Value)]
+ };
+ SetPassword(account, password);
+
+ DatabaseHelper.CreateInstance(account);
+ }
+
+ public static void DeleteAccount(int uid)
+ {
+ if (GetAccountByUid(uid) == null) return;
+ DatabaseHelper.DeleteAllInstance(uid);
+ }
+
+ public static void SetPassword(AccountData account, string password)
+ {
+ if (password.Length > 0)
+ account.Password = Extensions.GetSha256Hash(password);
+ else
+ account.Password = "";
+ }
+
+ public static bool VerifyPassword(AccountData account, string password)
+ => account.Password == Extensions.GetSha256Hash(password);
+
+
+ #endregion
+
+ #region Permission
+
+ public static bool HasPerm(PermEnum[] perms, int uid)
+ {
+ if (uid == (int)ServerEnum.Console) return true;
+ var account = GetAccountByUid(uid);
+ if (account?.Permissions == null) return false;
+ if (account.Permissions.Contains(PermEnum.Admin)) return true;
+
+ return perms.Any(account.Permissions.Contains);
+ }
+
+ public static void AddPerm(PermEnum[] perms, int uid)
+ {
+ if (uid == (int)ServerEnum.Console) return;
+ var account = GetAccountByUid(uid);
+ if (account == null) return;
+
+ account.Permissions ??= [];
+ foreach (var perm in perms)
+ {
+ if (!account.Permissions.Contains(perm))
+ {
+ account.Permissions = [.. account.Permissions, perm];
+ }
+ }
+ }
+
+ public static void RemovePerm(PermEnum[] perms, int uid)
+ {
+ if (uid == (int)ServerEnum.Console) return;
+ var account = GetAccountByUid(uid);
+ if (account == null) return;
+ if (account.Permissions == null) return;
+
+ foreach (var perm in perms)
+ {
+ if (account.Permissions.Contains(perm))
+ {
+ account.Permissions = account.Permissions.Except([perm]).ToList();
+ }
+ }
+ }
+
+ public static void CleanPerm(int uid)
+ {
+ if (uid == (int)ServerEnum.Console) return;
+ var account = GetAccountByUid(uid);
+ if (account == null) return;
+
+ account.Permissions = [];
+ }
+
+ #endregion
+
+ #region Token
+
+ public string GenerateComboToken()
+ {
+ ComboToken = Crypto.CreateSessionKey(Uid.ToString());
+ DatabaseHelper.UpdateInstance(this);
+ return ComboToken;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Common/Database/Avatar/AvatarData.cs b/Common/Database/Avatar/AvatarData.cs
new file mode 100644
index 0000000..a0fdccd
--- /dev/null
+++ b/Common/Database/Avatar/AvatarData.cs
@@ -0,0 +1,95 @@
+using SqlSugar;
+using KianaBH.Proto;
+
+namespace KianaBH.Database.Avatar;
+
+[SugarTable("Avatar")]
+public class AvatarData : BaseDatabaseDataHelper
+{
+ [SugarColumn(IsJson = true)] public List Avatars { get; set; } = [];
+}
+
+public class AvatarInfo
+{
+ public int AvatarId { get; set; }
+ public int Star { get; set; }
+ public int Level { get; set; }
+ public int Exp { get; set; }
+ public int Fragment { get; set; }
+ public int WeaponUniqueId { get; set; }
+ public int StigmataUniqueId1 { get; set; }
+ public int StigmataUniqueId2 { get; set; }
+ public int StigmataUniqueId3 { get; set; }
+ public List SkillList { get; set; } = [];
+ public int TouchGoodFeel { get; set; }
+ public int TodayHasAddGoodFeel { get; set; }
+ public int StageGoodFeel { get; set; }
+ public List DressList { get; set; } = [];
+ public int DressId { get; set; }
+ public AvatarBindEquipMode Mode { get; set; } = AvatarBindEquipMode.AvatarBindEquipCommon;
+ public AvatarArtifactDetail? AvatarArtifact { get; set; }
+ public int SubStar { get; set; }
+ public long Timestamp { get; set; }
+ public Proto.Avatar ToProto()
+ {
+ var proto = new Proto.Avatar
+ {
+ AvatarId = (uint)AvatarId,
+ Star = (uint)Star,
+ Level = (uint)Level,
+ Exp = (uint)Exp,
+ Fragment = (uint)Fragment,
+ WeaponUniqueId = (uint)WeaponUniqueId,
+ StigmataUniqueId1 = (uint)StigmataUniqueId1,
+ StigmataUniqueId2 = (uint)StigmataUniqueId2,
+ StigmataUniqueId3 = (uint)StigmataUniqueId3,
+ TouchGoodfeel = (uint)TouchGoodFeel,
+ TodayHasAddGoodfeel = (uint)TodayHasAddGoodFeel,
+ StageGoodfeel = (uint)StageGoodFeel,
+ DressId = (uint)DressId,
+ Mode = Mode,
+ SubStar = (uint)SubStar,
+ };
+
+ foreach (var dressId in DressList)
+ {
+ proto.DressList.Add((uint)dressId);
+ }
+
+ foreach (var skill in SkillList)
+ {
+ var avatarSkill = new Proto.AvatarSkill
+ {
+ SkillId = (uint)skill.SkillId
+ };
+
+ avatarSkill.SubSkillList.AddRange(skill.SubSkillList.Select(x => new Proto.AvatarSubSkill
+ {
+ SubSkillId = (uint)x.SubSkillId,
+ Level = x.Level,
+ IsMask = x.IsMask
+ }));
+
+ proto.SkillList.Add(avatarSkill);
+ }
+ return proto;
+ }
+
+}
+public class AvatarSkill
+{
+ public int SkillId { get; set; }
+ public List SubSkillList { get; set; } = [];
+}
+public class AvatarSubSkill
+{
+ public int SubSkillId { get; set; }
+ public uint Level { get; set; }
+ public bool IsMask { get; set; }
+}
+public class AvatarArtifactDetail
+{
+ public int ArtifactId { get; set; }
+ public int ArtifactLevel { get; set; }
+ public bool IsArtifactSwitchOn { get; set; }
+}
\ No newline at end of file
diff --git a/Common/Database/BaseDatabaseDataHelper.cs b/Common/Database/BaseDatabaseDataHelper.cs
new file mode 100644
index 0000000..1ea2d77
--- /dev/null
+++ b/Common/Database/BaseDatabaseDataHelper.cs
@@ -0,0 +1,8 @@
+using SqlSugar;
+
+namespace KianaBH.Database;
+
+public abstract class BaseDatabaseDataHelper
+{
+ [SugarColumn(IsPrimaryKey = true)] public int Uid { get; set; }
+}
\ No newline at end of file
diff --git a/Common/Database/Client/ClientData.cs b/Common/Database/Client/ClientData.cs
new file mode 100644
index 0000000..4c336d5
--- /dev/null
+++ b/Common/Database/Client/ClientData.cs
@@ -0,0 +1,28 @@
+using Google.Protobuf;
+using KianaBH.Proto;
+using SqlSugar;
+
+namespace KianaBH.Database.Client;
+
+[SugarTable("client_data")]
+public class ClientData : BaseDatabaseDataHelper
+{
+ [SugarColumn(IsJson = true)] public List Clients { get; set; } = [];
+}
+
+public class ClientDBData
+{
+ public uint Id { get; set; }
+ public ClientDataType Type { get; set; } = ClientDataType.ClientDataNone;
+ public byte[] Data { get; set; } = Array.Empty();
+ public Proto.ClientData ToProto()
+ {
+ var proto = new Proto.ClientData
+ {
+ Id = Id,
+ Type = Type,
+ Data = ByteString.CopyFrom(Data)
+ };
+ return proto;
+ }
+}
\ No newline at end of file
diff --git a/Common/Database/CustomSerializeService.cs b/Common/Database/CustomSerializeService.cs
new file mode 100644
index 0000000..baf4f99
--- /dev/null
+++ b/Common/Database/CustomSerializeService.cs
@@ -0,0 +1,32 @@
+using Newtonsoft.Json;
+using SqlSugar;
+
+namespace KianaBH.Database;
+
+public class CustomSerializeService : ISerializeService
+{
+ private readonly JsonSerializerSettings _jsonSettings;
+
+ public CustomSerializeService()
+ {
+ _jsonSettings = new JsonSerializerSettings
+ {
+ DefaultValueHandling = DefaultValueHandling.Ignore // ignore default values
+ };
+ }
+
+ public string SerializeObject(object value)
+ {
+ return JsonConvert.SerializeObject(value, _jsonSettings);
+ }
+
+ public T DeserializeObject(string value)
+ {
+ return JsonConvert.DeserializeObject(value)!;
+ }
+
+ public string SugarSerializeObject(object value)
+ {
+ return JsonConvert.SerializeObject(value, _jsonSettings);
+ }
+}
\ No newline at end of file
diff --git a/Common/Database/DatabaseHelper.cs b/Common/Database/DatabaseHelper.cs
new file mode 100644
index 0000000..ea4b99d
--- /dev/null
+++ b/Common/Database/DatabaseHelper.cs
@@ -0,0 +1,307 @@
+using KianaBH.Database.Account;
+using KianaBH.Internationalization;
+using KianaBH.Util;
+using SqlSugar;
+using System.Collections.Concurrent;
+using System.Globalization;
+
+namespace KianaBH.Database;
+
+public class DatabaseHelper
+{
+ public static Logger logger = new("Database");
+ public static SqlSugarScope? sqlSugarScope;
+ public static readonly ConcurrentDictionary> UidInstanceMap = [];
+ public static readonly List ToSaveUidList = [];
+ public static long LastSaveTick = DateTime.UtcNow.Ticks;
+ public static Thread? SaveThread;
+ public static bool LoadAccount;
+ public static bool LoadAllData;
+
+ public void Initialize()
+ {
+ logger.Info(I18NManager.Translate("Server.ServerInfo.LoadingItem", I18NManager.Translate("Word.Database")));
+ var f = new FileInfo(ConfigManager.Config.Path.DatabasePath + "/" + ConfigManager.Config.GameServer.DatabaseName);
+ if (!f.Exists && f.Directory != null) f.Directory.Create();
+
+ sqlSugarScope = new SqlSugarScope(new ConnectionConfig
+ {
+ ConnectionString = $"Data Source={f.FullName};",
+ DbType = DbType.Sqlite,
+ IsAutoCloseConnection = true,
+ ConfigureExternalServices = new ConfigureExternalServices
+ {
+ SerializeService = new CustomSerializeService()
+ }
+ });
+
+ InitializeSqlite();
+
+ var baseType = typeof(BaseDatabaseDataHelper);
+ var assembly = typeof(BaseDatabaseDataHelper).Assembly;
+ var types = assembly.GetTypes().Where(t => t.IsSubclassOf(baseType));
+
+ var list = sqlSugarScope.Queryable().ToList();
+ foreach (var inst in list)
+ {
+ if (!UidInstanceMap.TryGetValue(inst.Uid, out var value))
+ {
+ value = [];
+ UidInstanceMap[inst.Uid] = value;
+ }
+
+ value.Add(inst); // add to the map
+ }
+
+ // start dispatch server
+ LoadAccount = true;
+
+ var res = Parallel.ForEach(list, account =>
+ {
+ Parallel.ForEach(types, t =>
+ {
+ if (t == typeof(AccountData)) return; // skip the account data
+
+ try
+ {
+ typeof(DatabaseHelper).GetMethod(nameof(InitializeTable))?.MakeGenericMethod(t)
+ .Invoke(null, [account.Uid]);
+ }
+ catch (Exception e)
+ {
+ logger.Error("Database initialization error: ", e);
+ }
+
+ }); // cache the data
+ });
+
+ while (!res.IsCompleted)
+ {
+ }
+
+ LastSaveTick = DateTime.UtcNow.Ticks;
+
+ SaveThread = new Thread(() =>
+ {
+ while (true) CalcSaveDatabase();
+ });
+ SaveThread.Start();
+
+ LoadAllData = true;
+ }
+
+ public static void InitializeTable(int uid) where T : BaseDatabaseDataHelper, new()
+ {
+ var list = sqlSugarScope?.Queryable()
+ .Select(x => x)
+ .Select()
+ .Where(x => x.Uid == uid)
+ .ToList();
+
+ foreach (var inst in list!.Select(instance => (instance as BaseDatabaseDataHelper)!))
+ {
+ if (!UidInstanceMap.TryGetValue(inst.Uid, out var value))
+ {
+ value = [];
+ UidInstanceMap[inst.Uid] = value;
+ }
+
+ value.Add(inst); // add to the map
+ }
+ }
+
+ public static void InitializeSqlite()
+ {
+ var baseType = typeof(BaseDatabaseDataHelper);
+ var assembly = typeof(BaseDatabaseDataHelper).Assembly;
+ var types = assembly.GetTypes().Where(t => t.IsSubclassOf(baseType));
+ foreach (var type in types)
+ typeof(DatabaseHelper).GetMethod("InitializeSqliteTable")?.MakeGenericMethod(type).Invoke(null, null);
+ }
+
+ // DO NOT DEL ReSharper disable once UnusedMember.Global
+ public static void InitializeSqliteTable() where T : BaseDatabaseDataHelper, new()
+ {
+ try
+ {
+ sqlSugarScope?.CodeFirst.InitTables();
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ public static T? GetInstance(int uid, bool forceReload = false) where T : BaseDatabaseDataHelper, new()
+ {
+ try
+ {
+ if (!forceReload && UidInstanceMap.TryGetValue(uid, out var value))
+ {
+ var instance = value.OfType().FirstOrDefault();
+ if (instance != null) return instance;
+ }
+ var t = sqlSugarScope?.Queryable()
+ .Where(x => x.Uid == uid)
+ .ToList();
+
+ if (t is { Count: > 0 })
+ {
+ var instance = t[0];
+
+ if (!UidInstanceMap.TryGetValue(uid, out var list))
+ {
+ list = new List();
+ UidInstanceMap[uid] = list;
+ }
+ else
+ {
+ list.RemoveAll(i => i is T);
+ }
+
+ list.Add(instance);
+ return instance;
+ }
+
+ return null;
+
+ return null;
+ }
+ catch (Exception e)
+ {
+ logger.Error("Unsupported type", e);
+ return null;
+ }
+ }
+
+ public static T GetInstanceOrCreateNew(int uid) where T : BaseDatabaseDataHelper, new()
+ {
+ var instance = GetInstance(uid);
+ if (instance != null) return instance;
+
+ instance = new T
+ {
+ Uid = uid
+ };
+ CreateInstance(instance);
+
+ return instance;
+ }
+
+ public static List? GetAllInstance() where T : BaseDatabaseDataHelper, new()
+ {
+ try
+ {
+ return sqlSugarScope?.Queryable()
+ .Select(x => x)
+ .ToList();
+ }
+ catch (Exception e)
+ {
+ logger.Error("Unsupported type", e);
+ return null;
+ }
+ }
+
+ public static void UpdateInstance(T instance) where T : BaseDatabaseDataHelper, new()
+ {
+ sqlSugarScope?.Updateable(instance).ExecuteCommand();
+ }
+
+ public static void CreateInstance(T instance) where T : BaseDatabaseDataHelper, new()
+ {
+ sqlSugarScope?.Insertable(instance).ExecuteCommand();
+ if (!UidInstanceMap.TryGetValue(instance.Uid, out var value))
+ {
+ value = [];
+ UidInstanceMap[instance.Uid] = value;
+ }
+ value.Add(instance);
+ }
+
+ public static void DeleteInstance(int key) where T : BaseDatabaseDataHelper, new()
+ {
+ try
+ {
+ sqlSugarScope?.Deleteable().Where(x => x.Uid == key).ExecuteCommand();
+ }
+ catch (Exception e)
+ {
+ logger.Error("An error occurred while delete the database", e);
+ }
+ }
+
+ public static void DeleteAllInstance(int key)
+ {
+
+ var value = UidInstanceMap[key];
+ var baseType = typeof(BaseDatabaseDataHelper);
+ var assembly = typeof(BaseDatabaseDataHelper).Assembly;
+ var types = assembly.GetTypes().Where(t => t.IsSubclassOf(baseType));
+ foreach (var type in types)
+ {
+ var instance = value.Find(x => x.GetType() == type);
+ if (instance != null)
+ typeof(DatabaseHelper).GetMethod("DeleteInstance")?.MakeGenericMethod(type)
+ .Invoke(null, [key]);
+ }
+
+ if (UidInstanceMap.TryRemove(key, out var instances))
+ ToSaveUidList.RemoveAll(x => x == key);
+ }
+
+ // Auto save per 5 min
+ public static void CalcSaveDatabase()
+ {
+ if (LastSaveTick + TimeSpan.TicksPerMinute * 5 > DateTime.UtcNow.Ticks) return;
+ SaveDatabase();
+ }
+
+ public static void SaveDatabase()
+ {
+ try
+ {
+ var prev = DateTime.Now;
+ var list = ToSaveUidList.ToList(); // copy the list to avoid the exception
+ foreach (var uid in list)
+ {
+ var value = UidInstanceMap[uid];
+ var baseType = typeof(BaseDatabaseDataHelper);
+ var assembly = typeof(BaseDatabaseDataHelper).Assembly;
+ var types = assembly.GetTypes().Where(t => t.IsSubclassOf(baseType));
+ foreach (var type in types)
+ {
+ var instance = value.Find(x => x.GetType() == type);
+ if (instance != null)
+ typeof(DatabaseHelper).GetMethod("SaveDatabaseType")?.MakeGenericMethod(type)
+ .Invoke(null, [instance]);
+ }
+ }
+
+ var t = (DateTime.Now - prev).TotalSeconds;
+ logger.Info(I18NManager.Translate("Server.ServerInfo.SaveDatabase",
+ Math.Round(t, 2).ToString(CultureInfo.InvariantCulture)));
+
+ ToSaveUidList.Clear();
+ }
+ catch (Exception e)
+ {
+ logger.Error("An error occurred while saving the database", e);
+ }
+
+ LastSaveTick = DateTime.UtcNow.Ticks;
+ }
+
+ // DO NOT DEL ReSharper save database from cache
+ public static void SaveDatabaseType(T instance) where T : BaseDatabaseDataHelper, new()
+ {
+ try
+ {
+ sqlSugarScope?.Updateable(instance).ExecuteCommand();
+ }
+ catch (Exception e)
+ {
+ logger.Error("An error occurred while saving the database", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs
new file mode 100644
index 0000000..8de8dfc
--- /dev/null
+++ b/Common/Database/Inventory/InventoryData.cs
@@ -0,0 +1,75 @@
+using KianaBH.Proto;
+using SqlSugar;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+
+namespace KianaBH.Database.Inventory;
+
+[SugarTable("InventoryData")]
+public class InventoryData : BaseDatabaseDataHelper
+{
+ [SugarColumn(IsJson = true)] public List MaterialItems { get; set; } = [];
+
+ [SugarColumn(IsJson = true)] public List WeaponItems { get; set; } = [];
+
+ [SugarColumn(IsJson = true)] public List StigmataItems { get; set; } = [];
+
+ public int NextUniqueId { get; set; } = 100;
+}
+
+public class ItemData
+{
+ public int UniqueId { get; set; }
+ public int ItemId { get; set; }
+ public int SubItemId { get; set; }
+ public int Count { get; set; }
+ public int Level { get; set; }
+ public int Exp { get; set; }
+ public bool Locked { get; set; }
+ public bool AffixIdentify { get; set; }
+ public uint CancelLockedTime { get; set; }
+ public bool Extracted { get; set; }
+ public int SlotNum { get; set; }
+ public int Refine { get; set; }
+ public int Promote { get; set; }
+ public int Homology { get; set; }
+ public List QuantumBranchLists { get; set; } = [];
+ public List RuneLists { get; set; } = [];
+ public List WaitSelectRuneLists { get; set; } = [];
+ public List WaitSelectRuneGroupLists { get; set; } = [];
+ public int EquipAvatar { get; set; }
+
+
+ public Material ToMaterialProto()
+ {
+ return new Material
+ {
+ Id = (uint)ItemId,
+ Num = (uint)Count
+ };
+ }
+
+ public Weapon ToWeaponProto()
+ {
+ return new Weapon
+ {
+ Id = (uint)ItemId,
+ UniqueId = (uint)UniqueId,
+ Level = (uint)Level,
+ Exp = (uint)Exp,
+ IsProtected = Locked,
+ IsExtracted = Extracted,
+ };
+ }
+}
+
+public class RuneGroup
+{
+ public int UniqueId { get; set; }
+ public List RuneLists { get; set; } = [];
+}
+
+public class Rune
+{
+ public int RuneId { get; set; }
+ public int Strength { get; set; }
+}
diff --git a/Common/Database/Lineup/LineupData.cs b/Common/Database/Lineup/LineupData.cs
new file mode 100644
index 0000000..46caae9
--- /dev/null
+++ b/Common/Database/Lineup/LineupData.cs
@@ -0,0 +1,19 @@
+using SqlSugar;
+
+namespace KianaBH.Database.Lineup;
+
+[SugarTable("Lineup")]
+public class LineupData : BaseDatabaseDataHelper
+{
+ [SugarColumn(IsJson = true)] public Dictionary Lineups { get; set; } = [];
+}
+
+public class LineupInfo
+{
+ public uint Id { get; set; }
+ public string? Name { get; set; }
+ public uint AstraMateId { get; set; }
+ public bool IsUsingAstraMate { get; set; }
+ public List AvatarIds { get; set; } = [];
+ public List ElfIds { get; set; } = [];
+}
\ No newline at end of file
diff --git a/Common/Database/Player/GuideData.cs b/Common/Database/Player/GuideData.cs
new file mode 100644
index 0000000..5bfff84
--- /dev/null
+++ b/Common/Database/Player/GuideData.cs
@@ -0,0 +1,9 @@
+using SqlSugar;
+
+namespace KianaBH.Database.Player;
+
+[SugarTable("player_guide")]
+public class GuideData : BaseDatabaseDataHelper
+{
+ [SugarColumn(IsJson = true)] public List GuideFinishList { get; set; } = [];
+}
\ No newline at end of file
diff --git a/Common/Database/Player/PlayerData.cs b/Common/Database/Player/PlayerData.cs
new file mode 100644
index 0000000..0c483d1
--- /dev/null
+++ b/Common/Database/Player/PlayerData.cs
@@ -0,0 +1,73 @@
+using System.Drawing;
+using KianaBH.Proto;
+using KianaBH.Util;
+using KianaBH.Util.Extensions;
+using SqlSugar;
+
+namespace KianaBH.Database.Player;
+
+[SugarTable("Player")]
+public class PlayerData : BaseDatabaseDataHelper
+{
+ public string? Name { get; set; } = "";
+ public string? Signature { get; set; } = "KianaPS";
+ public uint Level { get; set; } = 88;
+ public uint Exp { get; set; } = 0;
+ public uint HCoin { get; set; } = 0;
+ public uint Stamina { get; set; } = 240;
+ public uint HeadIcon { get; set; } = 161090;
+ public uint HeadFrame { get; set; } = 200001;
+ public uint WarshipId { get; set; } = 400004;
+ public uint PhonePendantId { get; set; } = 350005;
+ public uint AssistantAvatarId { get; set; } = 101;
+ public uint BirthDay { get; set; } = 0;
+ [SugarColumn(IsJson = true)] public WarshipAvatarData WarshipAvatar { get; set; } = new();
+ [SugarColumn(IsNullable = true)] public long LastActiveTime { get; set; }
+ public long RegisterTime { get; set; } = Extensions.GetUnixSec();
+
+ public static PlayerData? GetPlayerByUid(long uid)
+ {
+ var result = DatabaseHelper.GetInstance((int)uid);
+ return result;
+ }
+ public GetMainDataRsp ToProto()
+ {
+ return new GetMainDataRsp
+ {
+ IsAll = true,
+ AssistantAvatarId = 0,
+ Birthday = BirthDay,
+ Nickname = Name,
+ Level = Level,
+ Exp = Exp,
+ Hcoin = HCoin,
+ CustomHeadId = HeadIcon,
+ RegisterTime = (uint)RegisterTime,
+ WarshipAvatar = new Proto.WarshipAvatarData
+ {
+ WarshipFirstAvatarId = 0,
+ WarshipSecondAvatarId = 0,
+ },
+ SelfDesc = Signature,
+ UseFrameId = HeadFrame,
+ OnPhonePendantId = PhonePendantId,
+ Stamina = Stamina,
+ StaminaRecoverConfigTime = GameConstants.STAMINA_RECOVERY_TIME,
+ StaminaRecoverLeftTime = GameConstants.STAMINA_RECOVERY_TIME,
+ EquipmentSizeLimit = GameConstants.INVENTORY_MAX_EQUIPMENT,
+ TypeList = { Enumerable.Range(2, 38).Select(i => (uint)i) },
+ LevelLockId = 1,
+ WarshipTheme = new WarshipThemeData
+ {
+ WarshipId=0
+ },
+ TotalLoginDays = 1
+ };
+ }
+}
+
+public class WarshipAvatarData
+{
+ public uint FirstAvatarId { get; set; } = 101;
+ public uint SecondAvatarId { get; set; } = 0;
+}
\ No newline at end of file
diff --git a/Common/Enums/Item/ItemMainTypeEnum.cs b/Common/Enums/Item/ItemMainTypeEnum.cs
new file mode 100644
index 0000000..57fab53
--- /dev/null
+++ b/Common/Enums/Item/ItemMainTypeEnum.cs
@@ -0,0 +1,8 @@
+namespace KianaBH.Enums.Item;
+
+public enum ItemMainTypeEnum
+{
+ Material = 1,
+ Weapon = 2,
+ Stigmata = 3,
+}
\ No newline at end of file
diff --git a/Common/Enums/Language/ProgramLanguageTypeEnum.cs b/Common/Enums/Language/ProgramLanguageTypeEnum.cs
new file mode 100644
index 0000000..6bbf26c
--- /dev/null
+++ b/Common/Enums/Language/ProgramLanguageTypeEnum.cs
@@ -0,0 +1,9 @@
+namespace KianaBH.Enums.Language;
+
+public enum ProgramLanguageTypeEnum
+{
+ EN = 0,
+ CHS = 1,
+ CHT = 2,
+ JP = 3
+}
\ No newline at end of file
diff --git a/Common/Enums/Player/BanTypeEnum.cs b/Common/Enums/Player/BanTypeEnum.cs
new file mode 100644
index 0000000..4cad11e
--- /dev/null
+++ b/Common/Enums/Player/BanTypeEnum.cs
@@ -0,0 +1,13 @@
+namespace KianaBH.Enums.Player;
+
+public enum BanTypeEnum
+{
+ None = 0,
+ UseThirdPartySoftware = 1,
+ ThirdPartySoftware = 2,
+ AbnormalLogin = 4,
+ AbnormalAccount = 5,
+ ViolationTermsService = 6,
+ AccountRisk = 7,
+ Unknown = 8
+}
\ No newline at end of file
diff --git a/Common/Enums/Player/FriendEnum.cs b/Common/Enums/Player/FriendEnum.cs
new file mode 100644
index 0000000..919b9a9
--- /dev/null
+++ b/Common/Enums/Player/FriendEnum.cs
@@ -0,0 +1,7 @@
+namespace KianaBH.Enums.Player;
+
+public enum ServerEnum
+{
+ Console = 0,
+ Chat = 1
+}
\ No newline at end of file
diff --git a/Common/Enums/Player/OperationEnum.cs b/Common/Enums/Player/OperationEnum.cs
new file mode 100644
index 0000000..f2a7e48
--- /dev/null
+++ b/Common/Enums/Player/OperationEnum.cs
@@ -0,0 +1,9 @@
+namespace KianaBH.Enums.Player;
+
+public enum OperationEnum
+{
+ And = 0,
+ Or = 1,
+ Not = 2,
+ Unknow = 3
+}
\ No newline at end of file
diff --git a/Common/Enums/Player/PermEnum.cs b/Common/Enums/Player/PermEnum.cs
new file mode 100644
index 0000000..4453de8
--- /dev/null
+++ b/Common/Enums/Player/PermEnum.cs
@@ -0,0 +1,9 @@
+namespace KianaBH.Enums.Player;
+
+public enum PermEnum
+{
+ Trial = 0,
+ Support = 1,
+ Admin = 2,
+ Other = 10
+}
\ No newline at end of file
diff --git a/Common/Enums/Player/PlayerStatusEnum.cs b/Common/Enums/Player/PlayerStatusEnum.cs
new file mode 100644
index 0000000..2ce79c5
--- /dev/null
+++ b/Common/Enums/Player/PlayerStatusEnum.cs
@@ -0,0 +1,13 @@
+namespace KianaBH.Enums.Player;
+
+public enum PlayerStatusEnum
+{
+ Offline = 0,
+ Explore = 1,
+}
+
+public enum PlayerSubStatusEnum
+{
+ None = 0,
+ Battle = 1
+}
\ No newline at end of file
diff --git a/Common/Enums/Player/RegionEnum.cs b/Common/Enums/Player/RegionEnum.cs
new file mode 100644
index 0000000..d9722e3
--- /dev/null
+++ b/Common/Enums/Player/RegionEnum.cs
@@ -0,0 +1,29 @@
+namespace KianaBH.Enums.Player;
+
+public enum BaseRegionEnum
+{
+ None = 0,
+ CN = 11,
+ OS = 2
+}
+
+public enum RegionEnum
+{
+ PRODCN = 0,
+ SANDBOXCN = 1,
+ PRODOVERSEA = 2,
+ SANDBOXOVERSEA = 3,
+ PRODCNPRE = 4,
+ PRODOVERSEAPRE = 5,
+ TESTCN = 6,
+ TESTOVERSEA = 7,
+ PETCN = 8,
+ BETACN = 9,
+ BETACNPRE = 10,
+ BETAOVERSEA = 11,
+ PETOS = 12,
+ HOTFIXCN = 19,
+ HOTFIXOVERSEA = 20,
+ UNKNOWN = 21,
+ UNKNOWN2 = 22,
+}
\ No newline at end of file
diff --git a/Common/Internationalization/I18nManager.cs b/Common/Internationalization/I18nManager.cs
new file mode 100644
index 0000000..2bdc84b
--- /dev/null
+++ b/Common/Internationalization/I18nManager.cs
@@ -0,0 +1,102 @@
+using KianaBH.Enums.Language;
+using KianaBH.Internationalization.Message;
+using KianaBH.Util;
+using System.Reflection;
+
+namespace KianaBH.Internationalization;
+
+public static class I18NManager
+{
+ public static Logger Logger = new("I18nManager");
+
+ public static object Language { get; set; } = new LanguageEN();
+ public static Dictionary> PluginLanguages { get; } = [];
+
+ public static void LoadLanguage()
+ {
+ var languageStr = "KianaBH.Internationalization.Message.Language" +
+ ConfigManager.Config.ServerOption.Language;
+ var languageType = Type.GetType(languageStr);
+ if (languageType == null)
+ {
+ Logger.Warn("Language not found, fallback to EN");
+ // fallback to English
+ languageType = Type.GetType("KianaBH.Internationalization.Message.LanguageEN")!;
+ }
+
+ var language = Activator.CreateInstance(languageType) ?? throw new Exception("Language not found");
+ Language = language;
+
+ Logger.Info(Translate("Server.ServerInfo.LoadedItem", Translate("Word.Language")));
+ }
+
+ public static void LoadPluginLanguage(Dictionary> pluginAssemblies)
+ {
+ foreach (var (pluginName, types) in pluginAssemblies)
+ {
+ var languageType = types.FindAll(x => x.GetCustomAttribute() != null);
+ if (languageType.Count == 0) // no language to use
+ continue;
+
+ PluginLanguages.Add(pluginName, []);
+ foreach (var type in languageType)
+ {
+ var attr = type.GetCustomAttribute();
+ if (attr == null) continue;
+
+ var language = Activator.CreateInstance(type);
+ if (language == null) continue;
+ PluginLanguages[pluginName].Add(attr.LanguageType, language);
+ }
+ }
+ }
+
+ public static string Translate(string key, params string[] args)
+ {
+ var pluginLangs = PluginLanguages.Values;
+ var langs = (from pluginLang in pluginLangs
+ from o in pluginLang
+ where o.Key == Enum.Parse(ConfigManager.Config.ServerOption.Language)
+ select o.Value).ToList(); // get all plugin languages
+ langs.Add(Language); // add server language
+
+ var result = langs.Select(lang => GetNestedPropertyValue(lang, key)).OfType().FirstOrDefault() ?? key;
+
+ var index = 0;
+
+ return args.Aggregate(result, (current, arg) => current.Replace("{" + index++ + "}", arg));
+ }
+
+ public static string TranslateAsCertainLang(string langStr, string key, params string[] args)
+ {
+ var languageStr = "KianaBH.Internationalization.Message.Language" +
+ langStr;
+ var languageType = Type.GetType(languageStr) ??
+ Type.GetType("KianaBH.Internationalization.Message.LanguageEN")!;
+ var language = Activator.CreateInstance(languageType) ?? throw new Exception("Language not found");
+
+ List