Feature: Move Proto to Proto project, Move Kcp & Connection to DanhengKcpSharp project

This commit is contained in:
Somebody
2024-08-02 21:53:07 +08:00
parent 3d4f5aa44f
commit 98cdaded14
2860 changed files with 1131 additions and 612 deletions

View File

@@ -15,6 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\GameServer\GameServer.csproj" />
<ProjectReference Include="..\Proto\Proto.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.Command.Command;
@@ -38,10 +39,9 @@ public class CommandArg
CharacterArgs.TryGetValue("@", out var target);
if (target != null)
{
var connection = Listener.Connections.Values.ToList().Find(item => item.Player?.Uid.ToString() == target);
if (connection != null) Target = connection;
}
if (DanhengListener.Connections.Values.ToList()
.Find(item => (item as Connection)?.Player?.Uid.ToString() == target) is Connection connection)
Target = connection;
}
public string Raw { get; }

View File

@@ -1,6 +1,7 @@
using System.Reflection;
using EggLink.DanhengServer.GameServer.Server;
using EggLink.DanhengServer.Internationalization;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Util;
using Spectre.Console;
@@ -8,16 +9,16 @@ namespace EggLink.DanhengServer.Command.Command;
public class CommandManager
{
private const int MaxCommandHistory = 100;
private readonly List<string> _commandHistory = [];
private int _historyIndex = -1;
public static CommandManager? Instance { get; private set; }
public Dictionary<string, ICommand> Commands { get; } = [];
public Dictionary<string, CommandInfo> CommandInfo { get; } = [];
public Logger Logger { get; } = new("CommandManager");
public Connection? Target { get; set; }
private List<string> commandHistory = new();
private int historyIndex = -1;
private const int MaxCommandHistory = 100;
public void RegisterCommand()
{
Instance = this;
@@ -48,18 +49,12 @@ public class CommandManager
if (string.IsNullOrEmpty(input)) continue;
if (input.StartsWith("/"))
{
input = input.Substring(1);
}
if (input.StartsWith("/")) input = input.Substring(1);
if (commandHistory.Count >= MaxCommandHistory)
{
commandHistory.RemoveAt(0);
}
if (_commandHistory.Count >= MaxCommandHistory) _commandHistory.RemoveAt(0);
if (commandHistory.Count == 0 || commandHistory.Last() != input) commandHistory.Add(input);
historyIndex = commandHistory.Count;
if (_commandHistory.Count == 0 || _commandHistory.Last() != input) _commandHistory.Add(input);
_historyIndex = _commandHistory.Count;
HandleCommand(input, new ConsoleCommandSender(Logger));
}
catch
@@ -94,26 +89,26 @@ public class CommandManager
}
else if (keyInfo.Key == ConsoleKey.UpArrow)
{
if (historyIndex > 0)
if (_historyIndex > 0)
{
historyIndex--;
ReplaceInput(input, commandHistory[historyIndex]);
_historyIndex--;
ReplaceInput(input, _commandHistory[_historyIndex]);
}
}
else if (keyInfo.Key == ConsoleKey.DownArrow)
{
if (historyIndex < commandHistory.Count - 1)
if (_historyIndex < _commandHistory.Count - 1)
{
historyIndex++;
ReplaceInput(input, commandHistory[historyIndex]);
_historyIndex++;
ReplaceInput(input, _commandHistory[_historyIndex]);
}
else if (historyIndex == commandHistory.Count - 1)
else if (_historyIndex == _commandHistory.Count - 1)
{
historyIndex++;
_historyIndex++;
ReplaceInput(input, string.Empty);
}
}
else // known issue: Ctrl + (Any Key but C) or other control key will cause display error
else // known issue: Ctrl + (Any Key but C) or other control key will cause display error
{
input.Add(keyInfo.KeyChar);
Console.Write(keyInfo.KeyChar);
@@ -147,8 +142,8 @@ public class CommandManager
if (cmd.StartsWith('@'))
{
var target = cmd[1..];
var con = Listener.Connections.Values.ToList().Find(item => item.Player?.Uid.ToString() == target);
if (con != null)
if (DanhengListener.Connections.Values.ToList()
.Find(item => (item as Connection)?.Player?.Uid.ToString() == target) is Connection con)
{
Target = con;
sender.SendMsg(I18nManager.Translate("Game.Command.Notice.TargetFound", target,

View File

@@ -9,8 +9,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.25.3" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.25.3" />
<PackageReference Include="Google.Protobuf" Version="3.27.3" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.27.3" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
@@ -21,4 +21,8 @@
<PackageReference Include="System.Management" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Proto\Proto.csproj" />
</ItemGroup>
</Project>

View File

@@ -179,7 +179,8 @@ public class NoticeTextCHS
/// </summary>
public class HeroTextCHS
{
public string Desc => "切换主角的性别/形态\n当切换性别时genderId为1代表男性2代表女性\n当切换形态时8001代表毁灭命途8003代表存护命途8005代表同谐命途。\n注意切换性别时会清空所有可选命途以及行迹为不可逆操作";
public string Desc =>
"切换主角的性别/形态\n当切换性别时genderId为1代表男性2代表女性\n当切换形态时8001代表毁灭命途8003代表存护命途8005代表同谐命途。\n注意切换性别时会清空所有可选命途以及行迹为不可逆操作";
public string Usage => "用法:/hero gender [genderId]\n\n用法/hero type [typeId]";
public string GenderNotSpecified => "性别不存在!";
@@ -208,7 +209,8 @@ public class AvatarTextCHS
{
public string Desc => "设定玩家已有角色的属性\n设置行迹等级时设置X级即设置所有行迹节点至X级若大于此节点允许的最高等级设置为最高等级\n注意-1意为所有已拥有角色";
public string Usage => "用法:/avatar talent [角色ID/-1] [行迹等级]\n\n用法/avatar get [角色ID]\n\n用法/avatar rank [角色ID/-1] [星魂]\n\n用法/avatar level [角色ID/-1] [角色等级]";
public string Usage =>
"用法:/avatar talent [角色ID/-1] [行迹等级]\n\n用法/avatar get [角色ID]\n\n用法/avatar rank [角色ID/-1] [星魂]\n\n用法/avatar level [角色ID/-1] [角色等级]";
public string InvalidLevel => "{0}等级无效";
public string AllAvatarsLevelSet => "已将全部角色 {0}等级设置为 {1}";
@@ -236,7 +238,8 @@ public class GiveAllTextCHS
{
public string Desc => "给予玩家全部指定类型的物品\navatar意为角色equipment意为光锥relic意为遗器unlock意为气泡、手机壁纸、头像";
public string Usage => "用法:/giveall avatar r<星魂> l<等级>\n\n用法/giveall equipment r<叠影> l<等级> x<数量>\n\n用法/giveall relic l<等级> x<数量>\n\n用法/giveall unlock";
public string Usage =>
"用法:/giveall avatar r<星魂> l<等级>\n\n用法/giveall equipment r<叠影> l<等级> x<数量>\n\n用法/giveall relic l<等级> x<数量>\n\n用法/giveall unlock";
public string GiveAllItems => "已给予所有 {0}, 各 {1} 个";
}
@@ -284,7 +287,8 @@ public class MissionTextCHS
"使用 running 获取正在进行的任务以及可能卡住的任务,使用后可能会出现较长任务列表,请注意甄别\n" +
"使用 reaccept 可重新进行指定主任务,请浏览 handbook 来获取主任务ID";
public string Usage => "用法:/mission pass\n\n用法/mission finish [子任务ID]\n\n用法/mission running\n\n用法/mission reaccept [主任务ID]";
public string Usage =>
"用法:/mission pass\n\n用法/mission finish [子任务ID]\n\n用法/mission running\n\n用法/mission reaccept [主任务ID]";
public string AllMissionsFinished => "所有任务已完成!";
public string AllRunningMissionsFinished => "共 {0} 个进行中的任务已完成!";
@@ -306,7 +310,8 @@ public class RelicTextCHS
{
public string Desc => "管理玩家的遗器\n主词条可选副词条可选但至少存在其中之一\n等级限制1≤等级≤9999";
public string Usage => "用法:/relic <遗器ID> <主词条ID> <小词条ID1:小词条等级> <小词条ID2:小词条等级> <小词条ID3:小词条等级> <小词条ID4:小词条等级> l<等级> x<数量>";
public string Usage =>
"用法:/relic <遗器ID> <主词条ID> <小词条ID1:小词条等级> <小词条ID2:小词条等级> <小词条ID3:小词条等级> <小词条ID4:小词条等级> l<等级> x<数量>";
public string RelicNotFound => "遗器不存在!";
public string InvalidMainAffixId => "主词条ID无效";
@@ -331,7 +336,8 @@ public class RogueTextCHS
{
public string Desc => "管理玩家模拟宇宙中的数据\n-1意为所有祝福已拥有祝福\n使用 buff 来获取祝福\n使用 enhance 来强化祝福";
public string Usage => "用法:/rogue money [宇宙碎片数量]\n\n用法/rogue buff [祝福ID/-1]\n\n用法/rogue miracle [奇物ID]\n\n用法/rogue enhance [祝福ID/-1]\n\n用法/rogue unstuck - 脱离事件";
public string Usage =>
"用法:/rogue money [宇宙碎片数量]\n\n用法/rogue buff [祝福ID/-1]\n\n用法/rogue miracle [奇物ID]\n\n用法/rogue enhance [祝福ID/-1]\n\n用法/rogue unstuck - 脱离事件";
public string PlayerGainedMoney => "玩家已获得 {0} 宇宙碎片";
public string PlayerGainedAllItems => "玩家已获得所有{0}";
@@ -357,7 +363,8 @@ public class SceneTextCHS
"使用 reload 来重新加载当前场景,并回到初始位置\n" +
"使用 reset 来重置指定场景所有道具状态要获取当前FloorId请访问数据库 Player 表";
public string Usage => "用法:/scene prop [组ID] [道具ID] [状态]\n\n用法/scene remove [实体ID]\n\n用法/scene unlockall\n\n用法/scene change [entryId]\n\n用法/scene reload\n\n用法/scene reset <floorId>";
public string Usage =>
"用法:/scene prop [组ID] [道具ID] [状态]\n\n用法/scene remove [实体ID]\n\n用法/scene unlockall\n\n用法/scene change [entryId]\n\n用法/scene reload\n\n用法/scene reset <floorId>";
public string LoadedGroups => "已加载组: {0}";
public string PropStateChanged => "道具: {0} 的状态已设置为 {1}";

View File

@@ -179,7 +179,8 @@ public class NoticeTextCHT
/// </summary>
public class HeroTextCHT
{
public string Desc => "切換主角的性別/形態\n當切換性別時genderId為1代表男性2代表女性\n當切換形態時8001代表毀滅命途8003代表存護命途8005代表同諧命途。\n注意切換性別時會清空所有可選命途以及行跡為不可逆操作";
public string Desc =>
"切換主角的性別/形態\n當切換性別時genderId為1代表男性2代表女性\n當切換形態時8001代表毀滅命途8003代表存護命途8005代表同諧命途。\n注意切換性別時會清空所有可選命途以及行跡為不可逆操作";
public string Usage => "用法:/hero gender [genderId]\n\n用法/hero type [typeId]";
public string GenderNotSpecified => "性別不存在!";
@@ -208,7 +209,8 @@ public class AvatarTextCHT
{
public string Desc => "設定玩家已有角色的屬性\n設置行跡等級時設置X級即設置所有行跡節點至X級若大於此節點允許的最高等級設置為最高等級\n注意-1意為所有已擁有角色";
public string Usage => "用法:/avatar talent [角色ID/-1] [行跡等級]\n\n用法/avatar get [角色ID]\n\n用法/avatar rank [角色ID/-1] [星魂]\n\n用法/avatar level [角色ID/-1] [角色等級]";
public string Usage =>
"用法:/avatar talent [角色ID/-1] [行跡等級]\n\n用法/avatar get [角色ID]\n\n用法/avatar rank [角色ID/-1] [星魂]\n\n用法/avatar level [角色ID/-1] [角色等級]";
public string InvalidLevel => "{0}等級無效";
public string AllAvatarsLevelSet => "已將全部角色 {0}等級設置為 {1}";
@@ -236,7 +238,8 @@ public class GiveAllTextCHT
{
public string Desc => "給予玩家全部指定類型的物品\navatar意為角色equipment意為光錐relic意為遺器unlock意為氣泡、手機壁紙、頭像";
public string Usage => "用法:/giveall avatar r<星魂> l<等級>\n\n用法/giveall equipment r<疊影> l<等級> x<數量>\n\n用法/giveall relic l<等級> x<數量>\n\n用法/giveall unlock";
public string Usage =>
"用法:/giveall avatar r<星魂> l<等級>\n\n用法/giveall equipment r<疊影> l<等級> x<數量>\n\n用法/giveall relic l<等級> x<數量>\n\n用法/giveall unlock";
public string GiveAllItems => "已給予所有 {0}, 各 {1} 個";
}
@@ -284,7 +287,8 @@ public class MissionTextCHT
"使用 running 獲取正在進行的任務以及可能卡住的任務,使用後可能會出現較長任務列表,請注意甄別\n" +
"使用 reaccept 可重新進行指定主任務,請瀏覽 handbook 來獲取主任務ID";
public string Usage => "用法:/mission pass\n\n用法/mission finish [子任務ID]\n\n用法/mission running\n\n用法/mission reaccept [主任務ID]";
public string Usage =>
"用法:/mission pass\n\n用法/mission finish [子任務ID]\n\n用法/mission running\n\n用法/mission reaccept [主任務ID]";
public string AllMissionsFinished => "所有任務已完成!";
public string AllRunningMissionsFinished => "共 {0} 個進行中的任務已完成!";
@@ -306,7 +310,8 @@ public class RelicTextCHT
{
public string Desc => "管理玩家的遺器\n主詞條可選副詞條可選但至少存在其中之一\n等級限制1≤等級≤9999";
public string Usage => "用法:/relic <遺器ID> <主詞條ID> <小詞條ID1:小詞條等級> <小詞條ID2:小詞條等級> <小詞條ID3:小詞條等級> <小詞條ID4:小詞條等級> l<等級> x<數量>";
public string Usage =>
"用法:/relic <遺器ID> <主詞條ID> <小詞條ID1:小詞條等級> <小詞條ID2:小詞條等級> <小詞條ID3:小詞條等級> <小詞條ID4:小詞條等級> l<等級> x<數量>";
public string RelicNotFound => "遺器不存在!";
public string InvalidMainAffixId => "主詞條ID無效";
@@ -331,7 +336,8 @@ public class RogueTextCHT
{
public string Desc => "管理玩家模擬宇宙中的數據\n-1意為所有祝福已擁有祝福\n使用 buff 來獲取祝福\n使用 enhance 來強化祝福";
public string Usage => "用法:/rogue money [宇宙碎片數量]\n\n用法/rogue buff [祝福ID/-1]\n\n用法/rogue miracle [奇物ID]\n\n用法/rogue enhance [祝福ID/-1]\n\n用法/rogue unstuck - 脫離事件";
public string Usage =>
"用法:/rogue money [宇宙碎片數量]\n\n用法/rogue buff [祝福ID/-1]\n\n用法/rogue miracle [奇物ID]\n\n用法/rogue enhance [祝福ID/-1]\n\n用法/rogue unstuck - 脫離事件";
public string PlayerGainedMoney => "玩家已獲得 {0} 宇宙碎片";
public string PlayerGainedAllItems => "玩家已獲得所有{0}";
@@ -357,7 +363,8 @@ public class SceneTextCHT
"使用 reload 來重新加載當前場景,並回到初始位置\n" +
"使用 reset 來重置指定場景所有道具狀態要獲取當前FloorId請訪問數據庫 Player 表";
public string Usage => "用法:/scene prop [組ID] [道具ID] [狀態]\n\n用法/scene remove [實體ID]\n\n用法/scene unlockall\n\n用法/scene change [entryId]\n\n用法/scene reload\n\n用法/scene reset <floorId>";
public string Usage =>
"用法:/scene prop [組ID] [道具ID] [狀態]\n\n用法/scene remove [實體ID]\n\n用法/scene unlockall\n\n用法/scene change [entryId]\n\n用法/scene reload\n\n用法/scene reset <floorId>";
public string LoadedGroups => "已加載組: {0}";
public string PropStateChanged => "道具: {0} 的狀態已設置為 {1}";

View File

@@ -143,8 +143,12 @@ public class ServerInfoTextEN
public string LoadedItem => "Loaded {0}.";
public string LoadedItems => "Loaded {0} {1}(s).";
public string ServerRunning => "{0} server listening on {1}";
public string ServerStarted => "Startup complete! Took {0}s, better than 99% of users. Type 'help' for command help"; // This is a meme, consider localizing in English
public string MissionEnabled => "Mission system enabled. This feature is still in development and may not work as expected. Please report any bugs to the developers.";
public string ServerStarted =>
"Startup complete! Took {0}s, better than 99% of users. Type 'help' for command help"; // This is a meme, consider localizing in English
public string MissionEnabled =>
"Mission system enabled. This feature is still in development and may not work as expected. Please report any bugs to the developers.";
public string ConfigMissing => "{0} is missing. Please check your resource folder: {1}, {2} may not be available.";
public string UnloadedItems => "Unloaded all {0}.";
@@ -179,7 +183,8 @@ public class NoticeTextEN
/// </summary>
public class HeroTextEN
{
public string Desc => "Switch the gender/type of the main character\nWhen switch the gender, 1 means male, 2 means female\nWhen switch the type(path), 8001 means Destruction, 8003 means Preservation, 8005 means Harmony.\nNotice: Switch gender will clear all the paths and talents of main character, this operation is irreversible!";
public string Desc =>
"Switch the gender/type of the main character\nWhen switch the gender, 1 means male, 2 means female\nWhen switch the type(path), 8001 means Destruction, 8003 means Preservation, 8005 means Harmony.\nNotice: Switch gender will clear all the paths and talents of main character, this operation is irreversible!";
public string Usage => "Usage: /hero gender [genderId]\n\nUsage: /hero type [typeId]";
@@ -197,6 +202,7 @@ public class UnlockAllTextEN
public string Desc =>
"Unlock the objects in given category\n" +
"Use '/unlockall mission' to finish all missions, and the target player will be kicked, after re-login, the player may be stuck in tutorial, please use with caution";
public string Usage => "Usage: /unlockall mission";
public string AllMissionsUnlocked => "All missions have been unlocked!";
}
@@ -206,9 +212,11 @@ public class UnlockAllTextEN
/// </summary>
public class AvatarTextEN
{
public string Desc => "Set the properties of the avatars player owned\nWhen set talent level, set to X level means set all talent point to X level, if greater than the point max level, set to max level\nNotice: -1 means all owned avatars";
public string Desc =>
"Set the properties of the avatars player owned\nWhen set talent level, set to X level means set all talent point to X level, if greater than the point max level, set to max level\nNotice: -1 means all owned avatars";
public string Usage => "Usage: /avatar talent [Avatar ID/-1] [Talent Level]\n\nUsage: /avatar get [Avatar ID]\n\nUsage: /avatar rank [Avatar ID/-1] [Rank]\n\nUsage: /avatar level [Avatar ID/-1] [Avatar Level]";
public string Usage =>
"Usage: /avatar talent [Avatar ID/-1] [Talent Level]\n\nUsage: /avatar get [Avatar ID]\n\nUsage: /avatar rank [Avatar ID/-1] [Rank]\n\nUsage: /avatar level [Avatar ID/-1] [Avatar Level]";
public string InvalidLevel => "Invalid {0} level";
public string AllAvatarsLevelSet => "Set all characters' {0} level to {1}";
@@ -234,9 +242,11 @@ public class GiveTextEN
/// </summary>
public class GiveAllTextEN
{
public string Desc => "Give the player all specified types of items\navatar means characters, equipment means light cones, relic means relic(artifact), unlock means chatBubbles, avatar(head icon), wallpaper";
public string Desc =>
"Give the player all specified types of items\navatar means characters, equipment means light cones, relic means relic(artifact), unlock means chatBubbles, avatar(head icon), wallpaper";
public string Usage => "Usage: /giveall avatar r<rank> l<level>\n\nUsage: /giveall equipment r<rank> l<level> x<amount>\n\nUsage: /giveall relic l<level> x<amount>\n\nUsage: /giveall unlock";
public string Usage =>
"Usage: /giveall avatar r<rank> l<level>\n\nUsage: /giveall equipment r<rank> l<level> x<amount>\n\nUsage: /giveall relic l<level> x<amount>\n\nUsage: /giveall unlock";
public string GiveAllItems => "Gave all {0}, each {1} items";
}
@@ -285,7 +295,8 @@ public class MissionTextEN
"Use 'running' to get the running mission and possible stuck missions, after use, a longer mission list may appear, please note that\n" +
"Use 'reaccept' to re-accept given main mission, please find main mission id in handbook";
public string Usage => "Usage: /mission pass\n\nUsage: /mission finish [Sub mission ID]\n\nUsage: /mission running\n\nUsage: /mission reaccept [main mission id]";
public string Usage =>
"Usage: /mission pass\n\nUsage: /mission finish [Sub mission ID]\n\nUsage: /mission running\n\nUsage: /mission reaccept [main mission id]";
public string AllMissionsFinished => "All tasks have been completed!";
public string AllRunningMissionsFinished => "A total of {0} ongoing tasks have been completed!";
@@ -305,9 +316,11 @@ public class MissionTextEN
/// </summary>
public class RelicTextEN
{
public string Desc => "Manage player's relics\nmain affix optional, sub affix optional, but at least one of them exists\nLevel limit: 1≤Level≤9999";
public string Desc =>
"Manage player's relics\nmain affix optional, sub affix optional, but at least one of them exists\nLevel limit: 1≤Level≤9999";
public string Usage => "Usage: /relic <relic ID> <main affix ID> <sub affix ID1:sub affix level> <sub affix ID2:sub affix level> <sub affix ID3:sub affix level> <sub affix ID4:sub affix level> l<level> x<amount>";
public string Usage =>
"Usage: /relic <relic ID> <main affix ID> <sub affix ID1:sub affix level> <sub affix ID2:sub affix level> <sub affix ID3:sub affix level> <sub affix ID4:sub affix level> l<level> x<amount>";
public string RelicNotFound => "Relic does not exist!";
public string InvalidMainAffixId => "Invalid main affix ID";
@@ -330,9 +343,11 @@ public class ReloadTextEN
/// </summary>
public class RogueTextEN
{
public string Desc => "Manage player's data in the simulated universe\n-1 means all blessings (all owned blessings)\nUse 'buff' to get blessings\nUse 'enhance' to enhance blessings";
public string Desc =>
"Manage player's data in the simulated universe\n-1 means all blessings (all owned blessings)\nUse 'buff' to get blessings\nUse 'enhance' to enhance blessings";
public string Usage => "Usage: /rogue money [Universe Debris Amount]\n\nUsage: /rogue buff [Blessing Id/-1]\n\nUsage: /rogue miracle [Miracle ID]\n\nUsage: /rogue enhance [Blessing ID/-1]\n\nUsage: /rogue unstuck - Leave event";
public string Usage =>
"Usage: /rogue money [Universe Debris Amount]\n\nUsage: /rogue buff [Blessing Id/-1]\n\nUsage: /rogue miracle [Miracle ID]\n\nUsage: /rogue enhance [Blessing ID/-1]\n\nUsage: /rogue unstuck - Leave event";
public string PlayerGainedMoney => "Player gained {0} universe debris";
public string PlayerGainedAllItems => "Player gained all {0}";
@@ -358,7 +373,8 @@ public class SceneTextEN
"Use 'reload' to reload the current scene and return to the initial position.\n" +
"Use 'reset' to reset the state of all props in the specified scene. For the current FloorId, refer to the Player table in the database.";
public string Usage => "Usage: /scene prop [groupId] [propId] [state]\n\nUsage: /scene remove [entityId]\n\nUsage: /scene unlockall\n\nUsage: /scene change [entryId]\n\nUsage: /scene reload\n\nUsage: /scene reset <floorId>";
public string Usage =>
"Usage: /scene prop [groupId] [propId] [state]\n\nUsage: /scene remove [entityId]\n\nUsage: /scene unlockall\n\nUsage: /scene change [entryId]\n\nUsage: /scene reload\n\nUsage: /scene reset <floorId>";
public string LoadedGroups => "Loaded groups: {0}";
public string PropStateChanged => "Prop: {0} state set to {1}";

View File

@@ -1,5 +1,5 @@
using Spectre.Console;
using System.Diagnostics;
using System.Diagnostics;
using Spectre.Console;
namespace EggLink.DanhengServer.Util;
@@ -14,8 +14,8 @@ public class Logger(string moduleName)
lock (_lock)
{
AnsiConsole.Write(new Markup($"[[[bold deepskyblue3_1]{DateTime.Now:HH:mm:ss}[/]]] " +
$"[[[gray]{ModuleName}[/]]] [[[{((ConsoleColor)level)}]{level}[/]]] {message.Replace("[", "[[").Replace("]", "]]")}\n"));
$"[[[gray]{ModuleName}[/]]] [[[{(ConsoleColor)level}]{level}[/]]] {message.Replace("[", "[[").Replace("]", "]]")}\n"));
var logMessage = $"[{DateTime.Now:HH:mm:ss}] [{ModuleName}] [{level}] {message}";
PluginEventCommon.InvokeOnConsoleLog(logMessage);
WriteToFile(logMessage);

View File

@@ -1,7 +1,7 @@
using EggLink.DanhengServer.Util;
using Google.Protobuf;
namespace EggLink.DanhengServer.GameServer.Server.Packet;
namespace EggLink.DanhengServer.Kcp;
public class BasePacket(ushort cmdId)
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.Server.Packet;
namespace EggLink.DanhengServer.Kcp;
public class CmdIds
{

View File

@@ -0,0 +1,165 @@
using System.Net;
using System.Reflection;
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.Kcp.KcpSharp;
using EggLink.DanhengServer.Util;
using Google.Protobuf;
using Google.Protobuf.Reflection;
namespace EggLink.DanhengServer.Kcp;
public class DanhengConnection
{
public const int MAX_MSG_SIZE = 16384;
public const int HANDSHAKE_SIZE = 20;
public static readonly List<int> BannedPackets = [];
private static readonly Logger Logger = new("GameServer");
public static readonly Dictionary<string, string> LogMap = [];
public static readonly List<int> IgnoreLog =
[
CmdIds.PlayerHeartBeatCsReq, CmdIds.PlayerHeartBeatScRsp, CmdIds.SceneEntityMoveCsReq,
CmdIds.SceneEntityMoveScRsp, CmdIds.GetShopListCsReq, CmdIds.GetShopListScRsp
];
protected readonly CancellationTokenSource CancelToken;
protected readonly KcpConversation Conversation;
public readonly IPEndPoint RemoteEndPoint;
public string DebugFile = "";
public bool IsOnline = true;
public StreamWriter? Writer;
public DanhengConnection(KcpConversation conversation, IPEndPoint remote)
{
Conversation = conversation;
RemoteEndPoint = remote;
CancelToken = new CancellationTokenSource();
Start();
}
public long? ConversationId => Conversation.ConversationId;
public SessionStateEnum State { get; set; } = SessionStateEnum.INACTIVE;
//public PlayerInstance? Player { get; set; }
public virtual void Start()
{
Logger.Info($"New connection from {RemoteEndPoint}.");
State = SessionStateEnum.WAITING_FOR_TOKEN;
}
public virtual void Stop()
{
//Player?.OnLogoutAsync();
//Listener.UnregisterConnection(this);
Conversation.Dispose();
try
{
CancelToken.Cancel();
CancelToken.Dispose();
}
catch
{
}
IsOnline = false;
}
public void LogPacket(string sendOrRecv, ushort opcode, byte[] payload)
{
try
{
//Logger.DebugWriteLine($"{sendOrRecv}: {Enum.GetName(typeof(OpCode), opcode)}({opcode})\r\n{Convert.ToHexString(payload)}");
if (IgnoreLog.Contains(opcode)) return;
var typ = AppDomain.CurrentDomain.GetAssemblies()
.SingleOrDefault(assembly => assembly.GetName().Name == "DanhengProto")!.GetTypes()
.First(t => t.Name == $"{LogMap[opcode.ToString()]}"); //get the type using the packet name
var descriptor =
typ.GetProperty("Descriptor", BindingFlags.Public | BindingFlags.Static)?.GetValue(
null, null) as MessageDescriptor; // get the static property Descriptor
var packet = descriptor?.Parser.ParseFrom(payload);
var formatter = JsonFormatter.Default;
var asJson = formatter.Format(packet);
var output = $"{sendOrRecv}: {LogMap[opcode.ToString()]}({opcode})\r\n{asJson}";
#if DEBUG
Logger.Debug(output);
#endif
if (DebugFile != "" && ConfigManager.Config.ServerOption.SavePersonalDebugFile)
{
var sw = GetWriter();
sw.WriteLine($"[{DateTime.Now:HH:mm:ss}] [GameServer] [DEBUG] " + output);
sw.Flush();
}
}
catch
{
var output = $"{sendOrRecv}: {LogMap[opcode.ToString()]}({opcode})";
#if DEBUG
Logger.Debug(output);
#endif
if (DebugFile != "" && ConfigManager.Config.ServerOption.SavePersonalDebugFile)
{
var sw = GetWriter();
sw.WriteLine($"[{DateTime.Now:HH:mm:ss}] [GameServer] [DEBUG] " + output);
sw.Flush();
}
}
}
private StreamWriter GetWriter()
{
// Create the file if it doesn't exist
var file = new FileInfo(DebugFile);
if (!file.Exists)
{
Directory.CreateDirectory(file.DirectoryName!);
File.Create(DebugFile).Dispose();
}
Writer ??= new StreamWriter(DebugFile, true);
return Writer;
}
public async Task SendPacket(byte[] packet)
{
try
{
_ = await Conversation.SendAsync(packet, CancelToken.Token);
}
catch
{
// ignore
}
}
public async Task SendPacket(BasePacket packet)
{
// Test
if (packet.CmdId <= 0)
{
Logger.Debug("Tried to send packet with missing cmd id!");
return;
}
// DO NOT REMOVE (unless we find a way to validate code before sending to client which I don't think we can)
if (BannedPackets.Contains(packet.CmdId)) return;
LogPacket("Send", packet.CmdId, packet.Data);
// Header
var packetBytes = packet.BuildPacket();
try
{
_ = await Conversation.SendAsync(packetBytes, CancelToken.Token);
}
catch
{
// ignore
}
}
public async Task SendPacket(int cmdId)
{
await SendPacket(new BasePacket((ushort)cmdId));
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>DanhengKcpSharp</AssemblyName>
<RootNamespace>EggLink.DanhengServer.Kcp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.27.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,147 @@
using System.Net;
using System.Net.Sockets;
using EggLink.DanhengServer.Internationalization;
using EggLink.DanhengServer.Kcp.KcpSharp;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.Kcp;
public class DanhengListener
{
private static UdpClient? UDPClient;
private static IPEndPoint? ListenAddress;
private static IKcpTransport<IKcpMultiplexConnection>? KCPTransport;
private static readonly Logger Logger = new("GameServer");
public static readonly SortedList<long, DanhengConnection> Connections = [];
private static readonly KcpConversationOptions ConvOpt = new()
{
StreamMode = false,
Mtu = 1400,
ReceiveWindow = 256,
SendWindow = 256,
NoDelay = true,
UpdateInterval = 100,
KeepAliveOptions = new KcpKeepAliveOptions(1000, 30000)
};
public static Type BaseConnection { get; set; } = typeof(DanhengConnection);
private static Socket? UDPListener => UDPClient?.Client;
private static IKcpMultiplexConnection? Multiplex => KCPTransport?.Connection;
private static uint PORT => ConfigManager.Config.GameServer.PublicPort;
public static DanhengConnection? GetConnectionByEndPoint(IPEndPoint ep)
{
return Connections.Values.FirstOrDefault(c => c.RemoteEndPoint.Equals(ep));
}
public static void StartListener()
{
ListenAddress = new IPEndPoint(IPAddress.Parse(ConfigManager.Config.GameServer.PublicAddress), (int)PORT);
UDPClient = new UdpClient(ListenAddress);
if (UDPListener == null) return;
KCPTransport = KcpSocketTransport.CreateMultiplexConnection(UDPClient, 1400);
KCPTransport.Start();
Logger.Info(I18nManager.Translate("Server.ServerInfo.ServerRunning", I18nManager.Translate("Word.Game"),
ConfigManager.Config.GameServer.GetDisplayAddress()));
}
private static void RegisterConnection(DanhengConnection con)
{
if (!con.ConversationId.HasValue) return;
Connections[con.ConversationId.Value] = con;
}
public static void UnregisterConnection(DanhengConnection con)
{
if (!con.ConversationId.HasValue) return;
var convId = con.ConversationId.Value;
if (Connections.Remove(convId))
{
Multiplex?.UnregisterConversation(convId);
Logger.Info($"Connection with {con.RemoteEndPoint} has been closed");
}
}
public static async Task HandleHandshake(UdpReceiveResult rcv)
{
try
{
var con = GetConnectionByEndPoint(rcv.RemoteEndPoint);
await using MemoryStream? ms = new(rcv.Buffer);
using BinaryReader? br = new(ms);
var code = br.ReadInt32BE();
br.ReadUInt32();
br.ReadUInt32();
var enet = br.ReadInt32BE();
br.ReadUInt32();
switch (code)
{
case 0x000000FF:
if (con != null)
{
Logger.Info($"Duplicate handshake from {con.RemoteEndPoint}");
return;
}
await AcceptConnection(rcv, enet);
break;
case 0x00000194:
if (con == null)
{
Logger.Info($"Inexistent connection asked for disconnect from {rcv.RemoteEndPoint}");
return;
}
await SendDisconnectPacket(con, 5);
break;
default:
Logger.Error($"Invalid handshake code received {code}");
return;
}
}
catch (Exception ex)
{
Logger.Error($"Failed to handle handshake: {ex}");
}
}
private static async Task AcceptConnection(UdpReceiveResult rcv, int enet)
{
var convId = Connections.GetNextAvailableIndex();
var convo = Multiplex?.CreateConversation(convId, rcv.RemoteEndPoint, ConvOpt);
if (convo == null) return;
var con = (DanhengConnection)Activator.CreateInstance(BaseConnection, [convo, rcv.RemoteEndPoint])!;
RegisterConnection(con);
await SendHandshakeResponse(con, enet);
}
private static async Task SendHandshakeResponse(DanhengConnection user, int enet)
{
if (user == null || UDPClient == null || !user.ConversationId.HasValue) return;
var convId = user.ConversationId.Value;
await using MemoryStream? ms = new();
await using BinaryWriter? bw = new(ms);
bw.WriteInt32BE(0x00000145);
bw.WriteConvID(convId);
bw.WriteInt32BE(enet);
bw.WriteInt32BE(0x14514545);
var data = ms.ToArray();
await UDPClient.SendAsync(data, data.Length, user.RemoteEndPoint);
}
public static async Task SendDisconnectPacket(DanhengConnection user, int code)
{
if (user == null || UDPClient == null || !user.ConversationId.HasValue) return;
var convId = user.ConversationId.Value;
await using MemoryStream? ms = new();
await using BinaryWriter? bw = new(ms);
bw.WriteInt32BE(0x00000194);
bw.WriteConvID(convId);
bw.WriteInt32BE(code);
bw.WriteInt32BE(0x19419494);
var data = ms.ToArray();
await UDPClient.SendAsync(data, data.Length, user.RemoteEndPoint);
}
}

View File

@@ -2,7 +2,7 @@
using System.Buffers;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class ArrayMemoryOwner : IMemoryOwner<byte>
{

View File

@@ -1,7 +1,7 @@
using System.Diagnostics;
using System.Threading.Tasks.Sources;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal class AsyncAutoResetEvent<T> : IValueTaskSource<T>
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class DefaultArrayPoolBufferAllocator : IKcpBufferPool
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// The buffer pool to rent buffers from.

View File

@@ -1,6 +1,6 @@
using System.Net.Sockets;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// A conversation or a channel over the transport.

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal interface IKcpConversationUpdateNotificationSource
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// An instance that can produce exceptions in background jobs.

View File

@@ -1,6 +1,6 @@
using System.Net;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Multiplex many channels or conversations over the same transport.

View File

@@ -1,6 +1,6 @@
using System.Net;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Multiplex many channels or conversations over the same transport.

View File

@@ -1,6 +1,6 @@
using System.Net;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// A transport to send and receive packets.

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// A transport instance for upper-level connections.

View File

@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpAcknowledgeList
{

View File

@@ -1,6 +1,6 @@
using System.Diagnostics;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal readonly struct KcpBuffer
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// The options to use when renting buffers from the pool.

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal enum KcpCommand : byte
{

View File

@@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks.Sources;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
partial class KcpConversation
{

View File

@@ -3,9 +3,9 @@ using LinkedListOfBufferItem = KcpSharp.NetstandardShim.LinkedList<KcpSharp.KcpS
using LinkedListNodeOfBufferItem = KcpSharp.NetstandardShim.LinkedListNode<KcpSharp.KcpSendReceiveBufferItem>;
#else
using LinkedListOfBufferItem =
System.Collections.Generic.LinkedList<EggLink.DanhengServer.GameServer.KcpSharp.KcpSendReceiveBufferItem>;
System.Collections.Generic.LinkedList<EggLink.DanhengServer.Kcp.KcpSharp.KcpSendReceiveBufferItem>;
using LinkedListNodeOfBufferItem =
System.Collections.Generic.LinkedListNode<EggLink.DanhengServer.GameServer.KcpSharp.KcpSendReceiveBufferItem>;
System.Collections.Generic.LinkedListNode<EggLink.DanhengServer.Kcp.KcpSharp.KcpSendReceiveBufferItem>;
#endif
using System.Buffers.Binary;
using System.Net;
@@ -13,7 +13,7 @@ using System.Net.Sockets;
using System.Runtime.CompilerServices;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// A reliable channel over an unreliable transport implemented in KCP protocol.

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Options used to control the behaviors of <see cref="KcpConversation" />.

View File

@@ -1,6 +1,6 @@
using System.Globalization;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// The result of a receive or peek operation.

View File

@@ -1,7 +1,7 @@
using System.Diagnostics;
using System.Threading.Tasks.Sources;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpConversationUpdateActivation : IValueTaskSource<KcpConversationUpdateNotification>, IDisposable
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal readonly struct KcpConversationUpdateNotification : IDisposable
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Helper methods for <see cref="IKcpExceptionProducer{T}" />.

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal static class KcpGlobalVars
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Options for customized keep-alive functionality.

View File

@@ -3,7 +3,7 @@ using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Multiplex many channels or conversations over the same transport.

View File

@@ -1,7 +1,7 @@
using System.Buffers.Binary;
using System.Diagnostics;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal readonly struct KcpPacketHeader : IEquatable<KcpPacketHeader>
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
[Flags]
internal enum KcpProbeType

View File

@@ -2,7 +2,7 @@
using System.Net;
using System.Net.Sockets;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// An unreliable channel with a conversation ID.

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Options used to control the behaviors of <see cref="KcpRawChannelOptions" />.

View File

@@ -3,14 +3,14 @@ using LinkedListOfQueueItem = KcpSharp.NetstandardShim.LinkedList<KcpSharp.KcpBu
using LinkedListNodeOfQueueItem = KcpSharp.NetstandardShim.LinkedListNode<KcpSharp.KcpBuffer>;
#else
using LinkedListOfQueueItem =
System.Collections.Generic.LinkedList<EggLink.DanhengServer.GameServer.KcpSharp.KcpBuffer>;
System.Collections.Generic.LinkedList<EggLink.DanhengServer.Kcp.KcpSharp.KcpBuffer>;
using LinkedListNodeOfQueueItem =
System.Collections.Generic.LinkedListNode<EggLink.DanhengServer.GameServer.KcpSharp.KcpBuffer>;
System.Collections.Generic.LinkedListNode<EggLink.DanhengServer.Kcp.KcpSharp.KcpBuffer>;
#endif
using System.Diagnostics;
using System.Threading.Tasks.Sources;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpRawReceiveQueue : IValueTaskSource<KcpConversationReceiveResult>, IDisposable
{

View File

@@ -1,7 +1,7 @@
using System.Diagnostics;
using System.Threading.Tasks.Sources;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpRawSendOperation : IValueTaskSource<bool>, IDisposable
{

View File

@@ -3,15 +3,15 @@ using LinkedListOfQueueItem = KcpSharp.NetstandardShim.LinkedList<(KcpSharp.KcpB
using LinkedListNodeOfQueueItem = KcpSharp.NetstandardShim.LinkedListNode<(KcpSharp.KcpBuffer Data, byte Fragment)>;
#else
using LinkedListOfQueueItem =
System.Collections.Generic.LinkedList<(EggLink.DanhengServer.GameServer.KcpSharp.KcpBuffer Data, byte Fragment)>;
System.Collections.Generic.LinkedList<(EggLink.DanhengServer.Kcp.KcpSharp.KcpBuffer Data, byte Fragment)>;
using LinkedListNodeOfQueueItem =
System.Collections.Generic.LinkedListNode<(EggLink.DanhengServer.GameServer.KcpSharp.KcpBuffer Data, byte Fragment
System.Collections.Generic.LinkedListNode<(EggLink.DanhengServer.Kcp.KcpSharp.KcpBuffer Data, byte Fragment
)>;
#endif
using System.Diagnostics;
using System.Threading.Tasks.Sources;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpReceiveQueue : IValueTaskSource<KcpConversationReceiveResult>, IValueTaskSource<int>,
IValueTaskSource<bool>, IDisposable

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Options for sending receive window size notification.

View File

@@ -3,7 +3,7 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// The buffer rented and owned by KcpSharp.

View File

@@ -3,12 +3,12 @@ using LinkedListOfQueueItem = KcpSharp.NetstandardShim.LinkedList<(KcpSharp.KcpB
using LinkedListNodeOfQueueItem = KcpSharp.NetstandardShim.LinkedListNode<(KcpSharp.KcpBuffer Data, byte Fragment)>;
#else
using LinkedListOfQueueItem =
System.Collections.Generic.LinkedList<(EggLink.DanhengServer.GameServer.KcpSharp.KcpBuffer Data, byte Fragment)>;
System.Collections.Generic.LinkedList<(EggLink.DanhengServer.Kcp.KcpSharp.KcpBuffer Data, byte Fragment)>;
#endif
using System.Diagnostics;
using System.Threading.Tasks.Sources;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpSendQueue : IValueTaskSource<bool>, IValueTaskSource, IDisposable
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal struct KcpSendReceiveBufferItem
{

View File

@@ -3,12 +3,12 @@ using LinkedListOfBufferItem = KcpSharp.NetstandardShim.LinkedList<KcpSharp.KcpS
using LinkedListNodeOfBufferItem = KcpSharp.NetstandardShim.LinkedListNode<KcpSharp.KcpSendReceiveBufferItem>;
#else
using LinkedListNodeOfBufferItem =
System.Collections.Generic.LinkedListNode<EggLink.DanhengServer.GameServer.KcpSharp.KcpSendReceiveBufferItem>;
System.Collections.Generic.LinkedListNode<EggLink.DanhengServer.Kcp.KcpSharp.KcpSendReceiveBufferItem>;
using LinkedListOfBufferItem =
System.Collections.Generic.LinkedList<EggLink.DanhengServer.GameServer.KcpSharp.KcpSendReceiveBufferItem>;
System.Collections.Generic.LinkedList<EggLink.DanhengServer.Kcp.KcpSharp.KcpSendReceiveBufferItem>;
#endif
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal struct KcpSendReceiveBufferItemCache
{

View File

@@ -3,13 +3,13 @@ using LinkedListOfQueueItem = KcpSharp.NetstandardShim.LinkedList<(KcpSharp.KcpB
using LinkedListNodeOfQueueItem = KcpSharp.NetstandardShim.LinkedListNode<(KcpSharp.KcpBuffer Data, byte Fragment)>;
#else
using LinkedListNodeOfQueueItem =
System.Collections.Generic.LinkedListNode<(EggLink.DanhengServer.GameServer.KcpSharp.KcpBuffer Data, byte Fragment
System.Collections.Generic.LinkedListNode<(EggLink.DanhengServer.Kcp.KcpSharp.KcpBuffer Data, byte Fragment
)>;
using LinkedListOfQueueItem =
System.Collections.Generic.LinkedList<(EggLink.DanhengServer.GameServer.KcpSharp.KcpBuffer Data, byte Fragment)>;
System.Collections.Generic.LinkedList<(EggLink.DanhengServer.Kcp.KcpSharp.KcpBuffer Data, byte Fragment)>;
#endif
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpSendReceiveQueueItemCache
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal readonly struct KcpSendSegmentStats
{

View File

@@ -1,7 +1,7 @@
using System.Net;
using System.Net.Sockets;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Helper methods to create socket transports for KCP conversations.

View File

@@ -1,7 +1,7 @@
using System.Net;
using System.Net.Sockets;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// Socket transport for KCP conversation.

View File

@@ -1,6 +1,6 @@
using System.Net.Sockets;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpSocketTransportForMultiplexConnection<T> : KcpSocketTransport<KcpMultiplexConnection<T>>,
IKcpTransport<IKcpMultiplexConnection<T>>

View File

@@ -1,7 +1,7 @@
using System.Net;
using System.Net.Sockets;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal sealed class KcpSocketTransportForRawChannel : KcpSocketTransport<KcpRawChannel>, IKcpTransport<KcpRawChannel>
{

View File

@@ -1,10 +1,9 @@
using System.Buffers;
using System.Net;
using System.Net.Sockets;
using EggLink.DanhengServer.GameServer.Server;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// A Socket transport for upper-level connections.
@@ -123,8 +122,8 @@ public abstract class KcpSocketTransport<T> : IKcpTransport, IDisposable where T
if (bytesReceived != 0 && bytesReceived <= _mtu)
{
if (bytesReceived == Listener.HANDSHAKE_SIZE)
await Listener.HandleHandshake(result);
if (bytesReceived == DanhengConnection.HANDSHAKE_SIZE)
await DanhengListener.HandleHandshake(result);
else if (!error)
await connection.InputPakcetAsync(result, cancellationToken).ConfigureAwait(false);
}

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
/// <summary>
/// A stream wrapper of <see cref="KcpConversation" />.

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.GameServer.KcpSharp;
namespace EggLink.DanhengServer.Kcp.KcpSharp;
internal static class ThrowHelper
{

View File

@@ -1,4 +1,4 @@
namespace EggLink.DanhengServer.Enums;
namespace EggLink.DanhengServer.Kcp;
public enum SessionStateEnum
{

View File

@@ -19,9 +19,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Command", "Command\Command.csproj", "{B52B8DE8-E63E-4CE4-A75F-C17A97C86D89}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Command", "Command\Command.csproj", "{B52B8DE8-E63E-4CE4-A75F-C17A97C86D89}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Program", "Program\Program.csproj", "{71D8488F-CAED-48EE-BD5C-F325FBAB991F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Program", "Program\Program.csproj", "{71D8488F-CAED-48EE-BD5C-F325FBAB991F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto", "Proto\Proto.csproj", "{8A0ECA1A-167B-4B97-BF79-3665AF654A52}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DanhengKcpSharp", "DanhengKcpSharp\DanhengKcpSharp.csproj", "{CD7EFAA3-C655-40EE-8F6A-A8E2DA3B0FCB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -49,6 +53,14 @@ Global
{71D8488F-CAED-48EE-BD5C-F325FBAB991F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71D8488F-CAED-48EE-BD5C-F325FBAB991F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71D8488F-CAED-48EE-BD5C-F325FBAB991F}.Release|Any CPU.Build.0 = Release|Any CPU
{8A0ECA1A-167B-4B97-BF79-3665AF654A52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A0ECA1A-167B-4B97-BF79-3665AF654A52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A0ECA1A-167B-4B97-BF79-3665AF654A52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A0ECA1A-167B-4B97-BF79-3665AF654A52}.Release|Any CPU.Build.0 = Release|Any CPU
{CD7EFAA3-C655-40EE-8F6A-A8E2DA3B0FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD7EFAA3-C655-40EE-8F6A-A8E2DA3B0FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD7EFAA3-C655-40EE-8F6A-A8E2DA3B0FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD7EFAA3-C655-40EE-8F6A-A8E2DA3B0FCB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,37 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Enums.Mission;
using EggLink.DanhengServer.GameServer.Game.Player;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Game.Mission.FinishType.Handler
namespace EggLink.DanhengServer.GameServer.Game.Mission.FinishType.Handler;
[MissionFinishType(MissionFinishTypeEnum.FinishQuest)]
public class MissionHandlerFinishQuest : MissionFinishTypeHandler
{
[MissionFinishType(MissionFinishTypeEnum.FinishQuest)]
public class MissionHandlerFinishQuest : MissionFinishTypeHandler
public override async ValueTask HandleMissionFinishType(PlayerInstance player, SubMissionInfo info, object? arg)
{
public override async ValueTask HandleMissionFinishType(PlayerInstance player, SubMissionInfo info, object? arg)
{
// this type wont be used in mission
await ValueTask.CompletedTask;
}
public override async ValueTask HandleQuestFinishType(PlayerInstance player, QuestDataExcel quest,
FinishWayExcel excel, object? arg)
{
var questCount = 0;
foreach (var qid in excel.ParamIntList)
{
var status = player.QuestManager?.GetQuestStatus(qid);
if (status == QuestStatus.QuestFinish || status == QuestStatus.QuestClose)
questCount++;
}
await player.QuestManager!.UpdateQuestProgress(quest.QuestID, questCount);
}
// this type wont be used in mission
await ValueTask.CompletedTask;
}
}
public override async ValueTask HandleQuestFinishType(PlayerInstance player, QuestDataExcel quest,
FinishWayExcel excel, object? arg)
{
var questCount = 0;
foreach (var qid in excel.ParamIntList)
{
var status = player.QuestManager?.GetQuestStatus(qid);
if (status == QuestStatus.QuestFinish || status == QuestStatus.QuestClose)
questCount++;
}
await player.QuestManager!.UpdateQuestProgress(quest.QuestID, questCount);
}
}

View File

@@ -33,6 +33,7 @@ using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lineup;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Player;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Scene;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using static EggLink.DanhengServer.GameServer.Plugin.Event.PluginEvent;

View File

@@ -43,7 +43,8 @@ public class QuestManager(PlayerInstance player) : BasePlayerManager(player)
var accept2 = true;
foreach (var questId in quest.UnlockParamList)
if (GetQuestStatus(questId) != QuestStatus.QuestFinish && GetQuestStatus(questId) != QuestStatus.QuestClose)
if (GetQuestStatus(questId) != QuestStatus.QuestFinish &&
GetQuestStatus(questId) != QuestStatus.QuestClose)
{
accept2 = false;
break;

View File

@@ -18,7 +18,8 @@
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\WebServer\WebServer.csproj" />
<ProjectReference Include="..\DanhengKcpSharp\DanhengKcpSharp.csproj" />
<ProjectReference Include="..\Proto\Proto.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,4 @@
@echo off
del /s /f ..\Common\Proto\*.cs
del /s /f ..\Proto\*.cs
cd OriginalProto
protoc ".\*" --csharp_out=..\..\Common\Proto\
protoc ".\*" --csharp_out=..\..\Proto\

View File

@@ -1,130 +1,41 @@
using System.Buffers;
using System.Net;
using System.Reflection;
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.GameServer.Game.Player;
using EggLink.DanhengServer.GameServer.KcpSharp;
using EggLink.DanhengServer.GameServer.Server.Packet;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Kcp.KcpSharp;
using EggLink.DanhengServer.Util;
using Google.Protobuf;
using Google.Protobuf.Reflection;
namespace EggLink.DanhengServer.GameServer.Server;
public class Connection
public class Connection : DanhengConnection
{
public static readonly List<int> BANNED_PACKETS = [];
private static readonly Logger Logger = new("GameServer");
public static readonly Dictionary<string, string> LogMap = [];
public static readonly List<int> IgnoreLog =
[
CmdIds.PlayerHeartBeatCsReq, CmdIds.PlayerHeartBeatScRsp, CmdIds.SceneEntityMoveCsReq,
CmdIds.SceneEntityMoveScRsp, CmdIds.GetShopListCsReq, CmdIds.GetShopListScRsp
];
private readonly CancellationTokenSource CancelToken;
private readonly KcpConversation Conversation;
public readonly IPEndPoint RemoteEndPoint;
public string DebugFile = "";
public bool IsOnline = true;
public StreamWriter? writer;
public Connection(KcpConversation conversation, IPEndPoint remote)
public Connection(KcpConversation conversation, IPEndPoint remote) : base(conversation, remote)
{
Conversation = conversation;
RemoteEndPoint = remote;
CancelToken = new CancellationTokenSource();
Start();
}
public long? ConversationID => Conversation.ConversationId;
public SessionStateEnum State { get; set; } = SessionStateEnum.INACTIVE;
public PlayerInstance? Player { get; set; }
private async void Start()
public override async void Start()
{
Logger.Info($"New connection from {RemoteEndPoint}.");
State = SessionStateEnum.WAITING_FOR_TOKEN;
await ReceiveLoop();
}
public void Stop()
public override void Stop()
{
Player?.OnLogoutAsync();
Listener.UnregisterConnection(this);
Conversation.Dispose();
try
{
CancelToken.Cancel();
CancelToken.Dispose();
}
catch
{
}
DanhengListener.UnregisterConnection(this);
base.Stop();
IsOnline = false;
}
public void LogPacket(string sendOrRecv, ushort opcode, byte[] payload)
{
try
{
//Logger.DebugWriteLine($"{sendOrRecv}: {Enum.GetName(typeof(OpCode), opcode)}({opcode})\r\n{Convert.ToHexString(payload)}");
if (IgnoreLog.Contains(opcode)) return;
#pragma warning disable CS8600
var typ = AppDomain.CurrentDomain.GetAssemblies()
.SingleOrDefault(assembly => assembly.GetName().Name == "DanhengCommon")!.GetTypes()
.First(t => t.Name == $"{LogMap[opcode.ToString()]}"); //get the type using the packet name
var descriptor =
(MessageDescriptor)typ.GetProperty("Descriptor", BindingFlags.Public | BindingFlags.Static)!.GetValue(
null, null); // get the static property Descriptor
var packet = descriptor!.Parser.ParseFrom(payload);
#pragma warning restore CS8600
var formatter = JsonFormatter.Default;
var asJson = formatter.Format(packet);
var output = $"{sendOrRecv}: {LogMap[opcode.ToString()]}({opcode})\r\n{asJson}";
#if DEBUG
Logger.Debug(output);
#endif
if (DebugFile != "" && ConfigManager.Config.ServerOption.SavePersonalDebugFile)
{
var sw = GetWriter();
sw.WriteLine($"[{DateTime.Now:HH:mm:ss}] [GameServer] [DEBUG] " + output);
sw.Flush();
}
}
catch
{
var output = $"{sendOrRecv}: {LogMap[opcode.ToString()]}({opcode})";
#if DEBUG
Logger.Debug(output);
#endif
if (DebugFile != "" && ConfigManager.Config.ServerOption.SavePersonalDebugFile)
{
var sw = GetWriter();
sw.WriteLine($"[{DateTime.Now:HH:mm:ss}] [GameServer] [DEBUG] " + output);
sw.Flush();
}
}
}
private StreamWriter GetWriter()
{
// Create the file if it doesn't exist
var file = new FileInfo(DebugFile);
if (!file.Exists)
{
Directory.CreateDirectory(file.DirectoryName!);
File.Create(DebugFile).Dispose();
}
writer ??= new StreamWriter(DebugFile, true);
return writer;
}
private async Task ReceiveLoop()
protected async Task ReceiveLoop()
{
while (!CancelToken.IsCancellationRequested)
{
@@ -136,7 +47,7 @@ public class Connection
break;
}
if (result.BytesReceived > Listener.MAX_MSG_SIZE)
if (result.BytesReceived > MAX_MSG_SIZE)
{
// The message is too large.
Logger.Error("Packet too large");
@@ -175,8 +86,8 @@ public class Connection
{
var gamePacket = data.ToArray();
await using MemoryStream? ms = new(gamePacket);
using BinaryReader? br = new(ms);
await using MemoryStream ms = new(gamePacket);
using BinaryReader br = new(ms);
// Handle
try
@@ -186,10 +97,10 @@ public class Connection
// Length
if (br.BaseStream.Length - br.BaseStream.Position < 12) return;
// Packet sanity check
var Magic1 = br.ReadUInt32BE();
if (Magic1 != 0x9D74C714)
var magic1 = br.ReadUInt32BE();
if (magic1 != 0x9D74C714)
{
Logger.Error($"Bad Data Package Received: got 0x{Magic1:X}, expect 0x9D74C714");
Logger.Error($"Bad Data Package Received: got 0x{magic1:X}, expect 0x9D74C714");
return; // Bad packet
}
@@ -244,34 +155,4 @@ public class Connection
return false;
}
public async Task SendPacket(BasePacket packet)
{
// Test
if (packet.CmdId <= 0)
{
Logger.Debug("Tried to send packet with missing cmd id!");
return;
}
// DO NOT REMOVE (unless we find a way to validate code before sending to client which I don't think we can)
if (BANNED_PACKETS.Contains(packet.CmdId)) return;
LogPacket("Send", packet.CmdId, packet.Data);
// Header
var packetBytes = packet.BuildPacket();
try
{
_ = await Conversation.SendAsync(packetBytes, CancelToken.Token);
}
catch
{
// ignore
}
}
public async Task SendPacket(int cmdId)
{
await SendPacket(new BasePacket((ushort)cmdId));
}
}

View File

@@ -1,154 +1,14 @@
using System.Net;
using System.Net.Sockets;
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.GameServer.KcpSharp;
using EggLink.DanhengServer.Internationalization;
using EggLink.DanhengServer.Util;
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server;
public class Listener
public class Listener : DanhengListener
{
public const int MAX_MSG_SIZE = 16384;
public const int HANDSHAKE_SIZE = 20;
private static UdpClient? UDPClient;
private static IPEndPoint? ListenAddress;
private static IKcpTransport<IKcpMultiplexConnection>? KCPTransport;
private static readonly Logger Logger = new("GameServer");
public static readonly SortedList<long, Connection> Connections = [];
private static readonly KcpConversationOptions ConvOpt = new()
{
StreamMode = false,
Mtu = 1400,
ReceiveWindow = 256,
SendWindow = 256,
NoDelay = true,
UpdateInterval = 100,
KeepAliveOptions = new KcpKeepAliveOptions(1000, 30000)
};
private static Socket? UDPListener => UDPClient?.Client;
private static IKcpMultiplexConnection? Multiplex => KCPTransport?.Connection;
private static uint PORT => ConfigManager.Config.GameServer.PublicPort;
public static Connection? GetConnectionByEndPoint(IPEndPoint ep)
{
return Connections.Values.FirstOrDefault(c => c.RemoteEndPoint.Equals(ep));
}
public static void StartListener()
{
ListenAddress = new IPEndPoint(IPAddress.Parse(ConfigManager.Config.GameServer.PublicAddress), (int)PORT);
UDPClient = new UdpClient(ListenAddress);
if (UDPListener == null) return;
KCPTransport = KcpSocketTransport.CreateMultiplexConnection(UDPClient, 1400);
KCPTransport.Start();
Logger.Info(I18nManager.Translate("Server.ServerInfo.ServerRunning", I18nManager.Translate("Word.Game"),
ConfigManager.Config.GameServer.GetDisplayAddress()));
}
private static void RegisterConnection(Connection con)
{
if (!con.ConversationID.HasValue) return;
Connections[con.ConversationID.Value] = con;
}
public static void UnregisterConnection(Connection con)
{
if (!con.ConversationID.HasValue) return;
var convId = con.ConversationID.Value;
if (Connections.Remove(convId))
{
Multiplex?.UnregisterConversation(convId);
Logger.Info($"Connection with {con.RemoteEndPoint} has been closed");
}
}
public static Connection? GetActiveConnection(int uid)
{
var con = Connections.Values.FirstOrDefault(c => c.Player?.Uid == uid && c.State == SessionStateEnum.ACTIVE);
var con = Connections.Values.FirstOrDefault(c =>
(c as Connection)?.Player?.Uid == uid && c.State == SessionStateEnum.ACTIVE) as Connection;
return con;
}
public static async Task HandleHandshake(UdpReceiveResult rcv)
{
try
{
var con = GetConnectionByEndPoint(rcv.RemoteEndPoint);
await using MemoryStream? ms = new(rcv.Buffer);
using BinaryReader? br = new(ms);
var code = br.ReadInt32BE();
br.ReadUInt32();
br.ReadUInt32();
var enet = br.ReadInt32BE();
br.ReadUInt32();
switch (code)
{
case 0x000000FF:
if (con != null)
{
Logger.Info($"Duplicate handshake from {con.RemoteEndPoint}");
return;
}
await AcceptConnection(rcv, enet);
break;
case 0x00000194:
if (con == null)
{
Logger.Info($"Inexistent connection asked for disconnect from {rcv.RemoteEndPoint}");
return;
}
await SendDisconnectPacket(con, 5);
break;
default:
Logger.Error($"Invalid handshake code received {code}");
return;
}
}
catch (Exception ex)
{
Logger.Error($"Failed to handle handshake: {ex}");
}
}
private static async Task AcceptConnection(UdpReceiveResult rcv, int enet)
{
var convId = Connections.GetNextAvailableIndex();
var convo = Multiplex?.CreateConversation(convId, rcv.RemoteEndPoint, ConvOpt);
if (convo == null) return;
Connection? con = new(convo, rcv.RemoteEndPoint);
RegisterConnection(con);
await SendHandshakeResponse(con, enet);
}
private static async Task SendHandshakeResponse(Connection user, int enet)
{
if (user == null || UDPClient == null || !user.ConversationID.HasValue) return;
var convId = user.ConversationID.Value;
await using MemoryStream? ms = new();
using BinaryWriter? bw = new(ms);
bw.WriteInt32BE(0x00000145);
bw.WriteConvID(convId);
bw.WriteInt32BE(enet);
bw.WriteInt32BE(0x14514545);
var data = ms.ToArray();
await UDPClient.SendAsync(data, data.Length, user.RemoteEndPoint);
}
public static async Task SendDisconnectPacket(Connection user, int code)
{
if (user == null || UDPClient == null || !user.ConversationID.HasValue) return;
var convId = user.ConversationID.Value;
await using MemoryStream? ms = new();
using BinaryWriter? bw = new(ms);
bw.WriteInt32BE(0x00000194);
bw.WriteConvID(convId);
bw.WriteInt32BE(code);
bw.WriteInt32BE(0x19419494);
var data = ms.ToArray();
await UDPClient.SendAsync(data, data.Length, user.RemoteEndPoint);
}
}

View File

@@ -11,7 +11,7 @@ public static class HandlerManager
var classes = Assembly.GetExecutingAssembly().GetTypes(); // Get all classes in the assembly
foreach (var cls in classes)
{
var attribute = (Opcode)Attribute.GetCustomAttribute(cls, typeof(Opcode))!;
var attribute = (Opcode?)Attribute.GetCustomAttribute(cls, typeof(Opcode));
if (attribute != null) handlers.Add(attribute.CmdId, (Handler)Activator.CreateInstance(cls)!);
}

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Activity;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Activity;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Activity;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Activity;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Activity;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Game.Activity.Activities;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Activity;

View File

@@ -4,6 +4,7 @@ using EggLink.DanhengServer.Database.Inventory;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Activity;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Player;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Scene;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Activity;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,6 @@
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;
[Opcode(CmdIds.GetCurAssistCsReq)]
public class HandlerGetCurAssistCsReq : Handler

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,6 +1,7 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Enums.Avatar;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -1,5 +1,6 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Player;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

View File

@@ -2,6 +2,7 @@
using EggLink.DanhengServer.Enums.Mission;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Avatar;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Player;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Avatar;

Some files were not shown because too many files have changed in this diff Show More