From 09bbae66d20ee4b568722bb9a6428930fea14445 Mon Sep 17 00:00:00 2001 From: BobSong <605277374@qq.com> Date: Mon, 28 Jul 2025 23:45:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=A7=92=E8=89=B2=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Config/Binary/SceneConfigData.bytes | 4 +- Config/Binary/WorldConfigData.bytes | 2 +- Config/Excel/Server/SceneConfig.xlsx | Bin 16921 -> 16913 bytes Config/Excel/Server/WorldConfig.xlsx | Bin 10128 -> 9905 bytes Config/Excel/Version.txt | 2 +- Config/Json/Server/SceneConfigData.Json | 4 +- Config/Json/Server/WorldConfigData.Json | 2 +- .../NetworkProtocol/Inner/InnerMessage.proto | 11 + .../NetworkProtocol/Outer/OuterCommon.proto | 19 ++ .../NetworkProtocol/Outer/OuterMessage.proto | 11 +- Config/NetworkProtocol/RouteType.Config | 3 +- Entity/Entity.csproj | 1 - .../Child/PlayerBasic.cs} | 3 +- .../Child/PlayerDayFlags.cs} | 3 +- .../Child/PlayerStatistics.cs} | 3 +- Entity/Game/Player/Player.cs | 30 ++ Entity/Game/Player/PlayerAutoSaveComponent.cs | 8 + .../Player}/PlayerManageComponent.cs | 2 +- Entity/Game/Role/Child/RoleCurrency.cs | 15 - Entity/Game/Role/Role.cs | 8 - Entity/Game/Role/RoleComAttribute.cs | 10 - Entity/Game/Role/RoleManagerComponent.cs | 11 - Entity/Gate/Entity/Player.cs | 13 - Entity/Gate/Entity/PlayerFlagComponent.cs | 15 - Entity/Gate/SessionPlayerComponent.cs | 10 + .../Generate/NetworkProtocol/InnerMessage.cs | 49 +++- .../Generate/NetworkProtocol/InnerOpcode.cs | 2 + .../Generate/NetworkProtocol/OuterCommon.cs | 75 +++++ .../Generate/NetworkProtocol/OuterMessage.cs | 36 +-- .../Generate/NetworkProtocol/OuterOpcode.cs | 4 +- Entity/Generate/NetworkProtocol/RouteType.cs | 1 + .../Handler/G2Game_EnterRequestHandler.cs | 75 +++++ .../Game/Role/RoleManagerComponentSystem.cs | 49 ---- .../Player/PlayerAutoSaveComponentSystem.cs | 6 + .../System/Player/PlayerDestroySystem.cs | 2 +- .../System/Player/PlayerFactory.cs | 3 +- .../System/Player/PlayerHelper.cs | 3 +- .../System/PlayerManageComponentSystem.cs | 4 +- .../C2G_GetAccountInfoRequestHandler.cs | 32 +++ .../Gate/Handler/C2G_LoginRequestHandler.cs | 78 ++++++ .../Outer/C2G_GetAccountInfoRequestHandler.cs | 32 --- .../Handler/Outer/C2G_LoginRequestHandler.cs | 101 ------- .../{Player => }/PlayerFlagComponentSystem.cs | 10 +- Hotfix/OnSceneCreate_Init.cs | 7 +- Server.sln.DotSettings.user | 1 + Tools/NetworkProtocol/Template.txt | 2 +- Tools/NetworkProtocol2/CommandLine.dll | Bin 225280 -> 0 bytes Tools/NetworkProtocol2/ExporterSettings.json | 29 -- .../Fantasy.Tools.NetworkProtocol | Bin 123264 -> 0 bytes .../Fantasy.Tools.NetworkProtocol.deps.json | 264 ------------------ .../Fantasy.Tools.NetworkProtocol.dll | Bin 51200 -> 0 bytes .../Fantasy.Tools.NetworkProtocol.pdb | Bin 25840 -> 0 bytes ...y.Tools.NetworkProtocol.runtimeconfig.json | 12 - ....Extensions.Configuration.Abstractions.dll | Bin 28424 -> 0 bytes ...xtensions.Configuration.FileExtensions.dll | Bin 28432 -> 0 bytes ...icrosoft.Extensions.Configuration.Json.dll | Bin 27400 -> 0 bytes .../Microsoft.Extensions.Configuration.dll | Bin 44328 -> 0 bytes ....Extensions.FileProviders.Abstractions.dll | Bin 22832 -> 0 bytes ...soft.Extensions.FileProviders.Physical.dll | Bin 45336 -> 0 bytes ...icrosoft.Extensions.FileSystemGlobbing.dll | Bin 45832 -> 0 bytes .../Microsoft.Extensions.Primitives.dll | Bin 44336 -> 0 bytes Tools/NetworkProtocol2/Newtonsoft.Json.dll | Bin 712464 -> 0 bytes Tools/NetworkProtocol2/Run.bat | 23 -- Tools/NetworkProtocol2/Run.sh | 24 -- .../NetworkProtocol2/System.IO.Pipelines.dll | Bin 77616 -> 0 bytes .../System.Text.Encodings.Web.dll | Bin 70968 -> 0 bytes Tools/NetworkProtocol2/System.Text.Json.dll | Bin 643848 -> 0 bytes .../lib/net8.0/System.Text.Encodings.Web.dll | Bin 70936 -> 0 bytes 68 files changed, 452 insertions(+), 662 deletions(-) create mode 100644 Config/NetworkProtocol/Outer/OuterCommon.proto rename Entity/Game/{Role/Child/RoleBasic.cs => Player/Child/PlayerBasic.cs} (92%) rename Entity/Game/{Role/Child/RoleDayFlags.cs => Player/Child/PlayerDayFlags.cs} (68%) rename Entity/Game/{Role/Child/RoleStatistics.cs => Player/Child/PlayerStatistics.cs} (91%) create mode 100644 Entity/Game/Player/Player.cs create mode 100644 Entity/Game/Player/PlayerAutoSaveComponent.cs rename Entity/{Gate => Game/Player}/PlayerManageComponent.cs (88%) delete mode 100644 Entity/Game/Role/Child/RoleCurrency.cs delete mode 100644 Entity/Game/Role/Role.cs delete mode 100644 Entity/Game/Role/RoleComAttribute.cs delete mode 100644 Entity/Game/Role/RoleManagerComponent.cs delete mode 100644 Entity/Gate/Entity/Player.cs delete mode 100644 Entity/Gate/Entity/PlayerFlagComponent.cs create mode 100644 Entity/Gate/SessionPlayerComponent.cs create mode 100644 Entity/Generate/NetworkProtocol/OuterCommon.cs create mode 100644 Hotfix/Game/Handler/G2Game_EnterRequestHandler.cs delete mode 100644 Hotfix/Game/Role/RoleManagerComponentSystem.cs create mode 100644 Hotfix/Game/System/Player/PlayerAutoSaveComponentSystem.cs rename Hotfix/{Gate => Game}/System/Player/PlayerDestroySystem.cs (93%) rename Hotfix/{Gate => Game}/System/Player/PlayerFactory.cs (95%) rename Hotfix/{Gate => Game}/System/Player/PlayerHelper.cs (99%) rename Hotfix/{Gate => Game}/System/PlayerManageComponentSystem.cs (94%) create mode 100644 Hotfix/Gate/Handler/C2G_GetAccountInfoRequestHandler.cs create mode 100644 Hotfix/Gate/Handler/C2G_LoginRequestHandler.cs delete mode 100644 Hotfix/Gate/Handler/Outer/C2G_GetAccountInfoRequestHandler.cs delete mode 100644 Hotfix/Gate/Handler/Outer/C2G_LoginRequestHandler.cs rename Hotfix/Gate/System/{Player => }/PlayerFlagComponentSystem.cs (54%) delete mode 100644 Tools/NetworkProtocol2/CommandLine.dll delete mode 100644 Tools/NetworkProtocol2/ExporterSettings.json delete mode 100644 Tools/NetworkProtocol2/Fantasy.Tools.NetworkProtocol delete mode 100644 Tools/NetworkProtocol2/Fantasy.Tools.NetworkProtocol.deps.json delete mode 100644 Tools/NetworkProtocol2/Fantasy.Tools.NetworkProtocol.dll delete mode 100644 Tools/NetworkProtocol2/Fantasy.Tools.NetworkProtocol.pdb delete mode 100644 Tools/NetworkProtocol2/Fantasy.Tools.NetworkProtocol.runtimeconfig.json delete mode 100644 Tools/NetworkProtocol2/Microsoft.Extensions.Configuration.Abstractions.dll delete mode 100644 Tools/NetworkProtocol2/Microsoft.Extensions.Configuration.FileExtensions.dll delete mode 100644 Tools/NetworkProtocol2/Microsoft.Extensions.Configuration.Json.dll delete mode 100644 Tools/NetworkProtocol2/Microsoft.Extensions.Configuration.dll delete mode 100644 Tools/NetworkProtocol2/Microsoft.Extensions.FileProviders.Abstractions.dll delete mode 100644 Tools/NetworkProtocol2/Microsoft.Extensions.FileProviders.Physical.dll delete mode 100644 Tools/NetworkProtocol2/Microsoft.Extensions.FileSystemGlobbing.dll delete mode 100644 Tools/NetworkProtocol2/Microsoft.Extensions.Primitives.dll delete mode 100644 Tools/NetworkProtocol2/Newtonsoft.Json.dll delete mode 100644 Tools/NetworkProtocol2/Run.bat delete mode 100644 Tools/NetworkProtocol2/Run.sh delete mode 100644 Tools/NetworkProtocol2/System.IO.Pipelines.dll delete mode 100644 Tools/NetworkProtocol2/System.Text.Encodings.Web.dll delete mode 100644 Tools/NetworkProtocol2/System.Text.Json.dll delete mode 100644 Tools/NetworkProtocol2/runtimes/browser/lib/net8.0/System.Text.Encodings.Web.dll diff --git a/Config/Binary/SceneConfigData.bytes b/Config/Binary/SceneConfigData.bytes index 861e2d9..c4839ac 100644 --- a/Config/Binary/SceneConfigData.bytes +++ b/Config/Binary/SceneConfigData.bytes @@ -2,6 +2,6 @@ /" MultiThread* Addressable2KCP8@UH &" MultiThread* Addressable@VH (" MultiThread*Gate2KCP8@VH -" MultiThread*Map@VH -" MultiThread*Map@VH +" MultiThread*Game@VH +" MultiThread*Game@VH " MultiThread*Chat@VH \ No newline at end of file diff --git a/Config/Binary/WorldConfigData.bytes b/Config/Binary/WorldConfigData.bytes index beded13..62d8946 100644 --- a/Config/Binary/WorldConfigData.bytes +++ b/Config/Binary/WorldConfigData.bytes @@ -1,2 +1,2 @@ -$ 测试服" fantasy_main*MongoDB \ No newline at end of file +9 测试服mongodb://127.0.0.1" fantasy_main*MongoDB \ No newline at end of file diff --git a/Config/Excel/Server/SceneConfig.xlsx b/Config/Excel/Server/SceneConfig.xlsx index 5a8a5e6ead2f36518cc1da1ea231b7d30626fa97..cb288fb63d70ccc1709228b67ae6161d3ec1bc4f 100644 GIT binary patch delta 4389 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 delta 4388 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 w - diff --git a/Entity/Game/Role/Child/RoleBasic.cs b/Entity/Game/Player/Child/PlayerBasic.cs similarity index 92% rename from Entity/Game/Role/Child/RoleBasic.cs rename to Entity/Game/Player/Child/PlayerBasic.cs index 6b76c86..efed87a 100644 --- a/Entity/Game/Role/Child/RoleBasic.cs +++ b/Entity/Game/Player/Child/PlayerBasic.cs @@ -5,8 +5,7 @@ namespace NB; /// /// 角色基础数据 /// -[RoleCom] -public class RoleBasic : Entity +public class PlayerBasic { /// /// 昵称 diff --git a/Entity/Game/Role/Child/RoleDayFlags.cs b/Entity/Game/Player/Child/PlayerDayFlags.cs similarity index 68% rename from Entity/Game/Role/Child/RoleDayFlags.cs rename to Entity/Game/Player/Child/PlayerDayFlags.cs index 9fcea59..22b612a 100644 --- a/Entity/Game/Role/Child/RoleDayFlags.cs +++ b/Entity/Game/Player/Child/PlayerDayFlags.cs @@ -5,7 +5,6 @@ namespace NB; /// /// 角色状态标志量 /// -[RoleCom] -public class RoleDayFlags : Entity +public class PlayerDayFlags { } \ No newline at end of file diff --git a/Entity/Game/Role/Child/RoleStatistics.cs b/Entity/Game/Player/Child/PlayerStatistics.cs similarity index 91% rename from Entity/Game/Role/Child/RoleStatistics.cs rename to Entity/Game/Player/Child/PlayerStatistics.cs index ddd5f5b..925b363 100644 --- a/Entity/Game/Role/Child/RoleStatistics.cs +++ b/Entity/Game/Player/Child/PlayerStatistics.cs @@ -5,8 +5,7 @@ namespace NB; /// /// 角色统计数据 /// -[RoleCom] -public class RoleStatistics : Entity +public class PlayerStatistics { /// /// 登录次数 diff --git a/Entity/Game/Player/Player.cs b/Entity/Game/Player/Player.cs new file mode 100644 index 0000000..5892717 --- /dev/null +++ b/Entity/Game/Player/Player.cs @@ -0,0 +1,30 @@ +using Fantasy.Entitas; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Options; + +namespace NB.Game; + +public sealed class Player : Entity +{ + public long CreateTime; + public long LoginTime; + + public PlayerBasic Basic = new PlayerBasic(); + public PlayerStatistics Statistics = new PlayerStatistics(); + public PlayerDayFlags DayFlags = new PlayerDayFlags(); + + [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)] + public Dictionary Currency = new(); + + [BsonIgnore] public long SessionRunTimeId; + + /// + /// 需要保存数据库 + /// + [BsonIgnore] public bool NeedSave; + + /// + /// 最后保存时间 + /// + [BsonIgnore] public long LastSaveTime; +} \ No newline at end of file diff --git a/Entity/Game/Player/PlayerAutoSaveComponent.cs b/Entity/Game/Player/PlayerAutoSaveComponent.cs new file mode 100644 index 0000000..86b02e5 --- /dev/null +++ b/Entity/Game/Player/PlayerAutoSaveComponent.cs @@ -0,0 +1,8 @@ +using Fantasy.Entitas; + +namespace NB.Game; + +public class PlayerAutoSaveComponent : Entity +{ + +} \ No newline at end of file diff --git a/Entity/Gate/PlayerManageComponent.cs b/Entity/Game/Player/PlayerManageComponent.cs similarity index 88% rename from Entity/Gate/PlayerManageComponent.cs rename to Entity/Game/Player/PlayerManageComponent.cs index 6b74077..1965dca 100644 --- a/Entity/Gate/PlayerManageComponent.cs +++ b/Entity/Game/Player/PlayerManageComponent.cs @@ -1,6 +1,6 @@ using Fantasy.Entitas; -namespace NB.Gate; +namespace NB.Game; public sealed class PlayerManageComponent : Entity { diff --git a/Entity/Game/Role/Child/RoleCurrency.cs b/Entity/Game/Role/Child/RoleCurrency.cs deleted file mode 100644 index c39d7b2..0000000 --- a/Entity/Game/Role/Child/RoleCurrency.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Fantasy.Entitas; -using MongoDB.Bson.Serialization.Attributes; -using MongoDB.Bson.Serialization.Options; - -namespace NB; - -/// -/// 角色货币数据 -/// -[RoleCom] -public class RoleCurrency : Entity -{ - [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)] - public Dictionary dic = new(); -} \ No newline at end of file diff --git a/Entity/Game/Role/Role.cs b/Entity/Game/Role/Role.cs deleted file mode 100644 index 0da5d30..0000000 --- a/Entity/Game/Role/Role.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Fantasy.Entitas; - -namespace NB; - -public sealed class Role : Entity -{ - -} \ No newline at end of file diff --git a/Entity/Game/Role/RoleComAttribute.cs b/Entity/Game/Role/RoleComAttribute.cs deleted file mode 100644 index 326c94d..0000000 --- a/Entity/Game/Role/RoleComAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ -using CommandLine; - -namespace NB; - -/// -/// 挂这个 玩家Unit创建时会自动添加此组件 -/// -public class RoleComAttribute : BaseAttribute -{ -} \ No newline at end of file diff --git a/Entity/Game/Role/RoleManagerComponent.cs b/Entity/Game/Role/RoleManagerComponent.cs deleted file mode 100644 index 4cc9859..0000000 --- a/Entity/Game/Role/RoleManagerComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Fantasy.Entitas; - -namespace NB; - -public class RoleManagerComponent : Entity -{ - /// - /// 游戏列表 - /// - public readonly Dictionary Roles = new(); -} \ No newline at end of file diff --git a/Entity/Gate/Entity/Player.cs b/Entity/Gate/Entity/Player.cs deleted file mode 100644 index af984f7..0000000 --- a/Entity/Gate/Entity/Player.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Fantasy.Entitas; -using MongoDB.Bson.Serialization.Attributes; - -namespace NB.Gate; - -public sealed class Player : Entity -{ - public long CreateTime; - public long LoginTime; - - [BsonIgnore] - public long SessionRunTimeId; -} \ No newline at end of file diff --git a/Entity/Gate/Entity/PlayerFlagComponent.cs b/Entity/Gate/Entity/PlayerFlagComponent.cs deleted file mode 100644 index 68a43d5..0000000 --- a/Entity/Gate/Entity/PlayerFlagComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Fantasy.Entitas; - -namespace NB.Gate; - -public sealed class PlayerFlagComponent : Entity -{ - public bool Kick { get; set; } - - public long AccountID; - - // 有一种可能,当在Account在其他地方被销毁 - // 这时候因为这个Account是会回收到池子中,所以这个引用还是有效的 - // 那这时候就会出现这个引用的Account可能是其他用户的了。 - public EntityReference Player; -} \ No newline at end of file diff --git a/Entity/Gate/SessionPlayerComponent.cs b/Entity/Gate/SessionPlayerComponent.cs new file mode 100644 index 0000000..8090aff --- /dev/null +++ b/Entity/Gate/SessionPlayerComponent.cs @@ -0,0 +1,10 @@ +using Fantasy.Entitas; + +namespace NB.Gate; + +public sealed class SessionPlayerComponent : Entity +{ + public bool Kick { get; set; } + + public long AccountID; +} \ No newline at end of file diff --git a/Entity/Generate/NetworkProtocol/InnerMessage.cs b/Entity/Generate/NetworkProtocol/InnerMessage.cs index 586abde..f576f13 100644 --- a/Entity/Generate/NetworkProtocol/InnerMessage.cs +++ b/Entity/Generate/NetworkProtocol/InnerMessage.cs @@ -15,6 +15,53 @@ using Fantasy.Serialize; #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8618 -namespace NBC +namespace Fantasy { + /// + /// 通知游戏服角色进入该游戏服 + /// + [ProtoContract] + public partial class G2Game_EnterRequest : AMessage, IRouteRequest, IProto + { + public static G2Game_EnterRequest Create(Scene scene) + { + return scene.MessagePoolComponent.Rent(); + } + public override void Dispose() + { + AccountId = default; + GateRouteId = default; +#if FANTASY_NET || FANTASY_UNITY + GetScene().MessagePoolComponent.Return(this); +#endif + } + [ProtoIgnore] + public Game2G_EnterResponse ResponseType { get; set; } + public uint OpCode() { return InnerOpcode.G2Game_EnterRequest; } + [ProtoMember(1)] + public long AccountId { get; set; } + [ProtoMember(2)] + public long GateRouteId { get; set; } + } + [ProtoContract] + public partial class Game2G_EnterResponse : AMessage, IRouteResponse, IProto + { + public static Game2G_EnterResponse Create(Scene scene) + { + return scene.MessagePoolComponent.Rent(); + } + public override void Dispose() + { + ErrorCode = default; + RoleRouteId = default; +#if FANTASY_NET || FANTASY_UNITY + GetScene().MessagePoolComponent.Return(this); +#endif + } + public uint OpCode() { return InnerOpcode.Game2G_EnterResponse; } + [ProtoMember(1)] + public long RoleRouteId { get; set; } + [ProtoMember(2)] + public uint ErrorCode { get; set; } + } } diff --git a/Entity/Generate/NetworkProtocol/InnerOpcode.cs b/Entity/Generate/NetworkProtocol/InnerOpcode.cs index ce1590d..0dfe652 100644 --- a/Entity/Generate/NetworkProtocol/InnerOpcode.cs +++ b/Entity/Generate/NetworkProtocol/InnerOpcode.cs @@ -2,5 +2,7 @@ namespace Fantasy { public static partial class InnerOpcode { + public const uint G2Game_EnterRequest = 1073751825; + public const uint Game2G_EnterResponse = 1207969553; } } diff --git a/Entity/Generate/NetworkProtocol/OuterCommon.cs b/Entity/Generate/NetworkProtocol/OuterCommon.cs new file mode 100644 index 0000000..94e4fa2 --- /dev/null +++ b/Entity/Generate/NetworkProtocol/OuterCommon.cs @@ -0,0 +1,75 @@ +using ProtoBuf; + +using System.Collections.Generic; +using MongoDB.Bson.Serialization.Attributes; +using Fantasy; +using Fantasy.Network.Interface; +using Fantasy.Serialize; +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantUsingDirective +// ReSharper disable RedundantOverriddenMember +// ReSharper disable PartialTypeWithSinglePart +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable CheckNamespace +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 + +namespace Fantasy +{ + /// + /// GameAccount实体类 + /// + [ProtoContract] + public partial class RoleBaseInfo : AMessage, IProto + { + public static RoleBaseInfo Create(Scene scene) + { + return scene.MessagePoolComponent.Rent(); + } + public override void Dispose() + { + NickName = default; + Head = default; + Country = default; + Level = default; + Exp = default; +#if FANTASY_NET || FANTASY_UNITY + GetScene().MessagePoolComponent.Return(this); +#endif + } + [ProtoMember(1)] + public string NickName { get; set; } + [ProtoMember(2)] + public string Head { get; set; } + [ProtoMember(3)] + public string Country { get; set; } + [ProtoMember(4)] + public int Level { get; set; } + [ProtoMember(5)] + public int Exp { get; set; } + } + /// + /// 角色信息 + /// + [ProtoContract] + public partial class RoleInfo : AMessage, IProto + { + public static RoleInfo Create(Scene scene) + { + return scene.MessagePoolComponent.Rent(); + } + public override void Dispose() + { + BaseInfo = default; + RoleId = default; +#if FANTASY_NET || FANTASY_UNITY + GetScene().MessagePoolComponent.Return(this); +#endif + } + [ProtoMember(1)] + public RoleBaseInfo BaseInfo { get; set; } + [ProtoMember(2)] + public long RoleId { get; set; } + } +} diff --git a/Entity/Generate/NetworkProtocol/OuterMessage.cs b/Entity/Generate/NetworkProtocol/OuterMessage.cs index 2461e93..66e2b43 100644 --- a/Entity/Generate/NetworkProtocol/OuterMessage.cs +++ b/Entity/Generate/NetworkProtocol/OuterMessage.cs @@ -15,7 +15,7 @@ using Fantasy.Serialize; #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8618 -namespace NBC +namespace Fantasy { [ProtoContract] public partial class C2A_LoginRequest : AMessage, IRequest, IProto @@ -149,45 +149,47 @@ namespace NBC [ProtoMember(2)] public long LoginTime { get; set; } } - /// - /// 拿到当前账号的信息 - /// [ProtoContract] - public partial class C2G_GetAccountInfoRequest : AMessage, IRequest, IProto + public partial class C2Game_GetRoleInfoRequest : AMessage, ICustomRouteRequest, IProto { - public static C2G_GetAccountInfoRequest Create(Scene scene) + public static C2Game_GetRoleInfoRequest Create(Scene scene) { - return scene.MessagePoolComponent.Rent(); + return scene.MessagePoolComponent.Rent(); } public override void Dispose() { #if FANTASY_NET || FANTASY_UNITY - GetScene().MessagePoolComponent.Return(this); + GetScene().MessagePoolComponent.Return(this); #endif } [ProtoIgnore] - public G2C_GetAccountInfoResponse ResponseType { get; set; } - public uint OpCode() { return OuterOpcode.C2G_GetAccountInfoRequest; } + public Game2C_GetRoleInfoResponse ResponseType { get; set; } + public uint OpCode() { return OuterOpcode.C2Game_GetRoleInfoRequest; } + [ProtoIgnore] + public int RouteType => Fantasy.RouteType.GameRoute; } [ProtoContract] - public partial class G2C_GetAccountInfoResponse : AMessage, IResponse, IProto + public partial class Game2C_GetRoleInfoResponse : AMessage, ICustomRouteResponse, IProto { - public static G2C_GetAccountInfoResponse Create(Scene scene) + public static Game2C_GetRoleInfoResponse Create(Scene scene) { - return scene.MessagePoolComponent.Rent(); + return scene.MessagePoolComponent.Rent(); } public override void Dispose() { ErrorCode = default; - GameAccountInfo = default; + Name = default; + RoleId = default; #if FANTASY_NET || FANTASY_UNITY - GetScene().MessagePoolComponent.Return(this); + GetScene().MessagePoolComponent.Return(this); #endif } - public uint OpCode() { return OuterOpcode.G2C_GetAccountInfoResponse; } + public uint OpCode() { return OuterOpcode.Game2C_GetRoleInfoResponse; } [ProtoMember(1)] - public GameAccountInfo GameAccountInfo { get; set; } + public string Name { get; set; } [ProtoMember(2)] + public string RoleId { get; set; } + [ProtoMember(3)] public uint ErrorCode { get; set; } } } diff --git a/Entity/Generate/NetworkProtocol/OuterOpcode.cs b/Entity/Generate/NetworkProtocol/OuterOpcode.cs index 4648e95..5b83c2e 100644 --- a/Entity/Generate/NetworkProtocol/OuterOpcode.cs +++ b/Entity/Generate/NetworkProtocol/OuterOpcode.cs @@ -7,7 +7,7 @@ namespace Fantasy public const uint C2G_LoginRequest = 268445458; public const uint G2C_LoginResponse = 402663186; public const uint G2C_RepeatLogin = 134227729; - public const uint C2G_GetAccountInfoRequest = 268445459; - public const uint G2C_GetAccountInfoResponse = 402663187; + public const uint C2Game_GetRoleInfoRequest = 2281711377; + public const uint Game2C_GetRoleInfoResponse = 2415929105; } } diff --git a/Entity/Generate/NetworkProtocol/RouteType.cs b/Entity/Generate/NetworkProtocol/RouteType.cs index cdd0df0..8bd8a52 100644 --- a/Entity/Generate/NetworkProtocol/RouteType.cs +++ b/Entity/Generate/NetworkProtocol/RouteType.cs @@ -5,5 +5,6 @@ namespace Fantasy { public const int GateRoute = 1001; // Gate public const int ChatRoute = 1002; // Chat + public const int GameRoute = 1003; // Game } } diff --git a/Hotfix/Game/Handler/G2Game_EnterRequestHandler.cs b/Hotfix/Game/Handler/G2Game_EnterRequestHandler.cs new file mode 100644 index 0000000..5643ea3 --- /dev/null +++ b/Hotfix/Game/Handler/G2Game_EnterRequestHandler.cs @@ -0,0 +1,75 @@ +using Fantasy; +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Helper; +using Fantasy.Network; +using Fantasy.Network.Interface; +using NB; +using NB.Gate; + +namespace NB.Game; + +public class G2Game_EnterRequestHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, G2Game_EnterRequest request, Game2G_EnterResponse response, + Action reply) + { + Log.Debug("收到 G2Game_EnterRequestHandler"); + + var accountId = request.AccountId; + + // 在缓存中检查该账号是否存在 + var gameAccountManageComponent = scene.GetComponent(); + Log.Debug("检查账号是否在缓存中"); + if (!gameAccountManageComponent.TryGet(accountId, out var account)) + { + // 首先要先到数据库中查询是否有这个账号 + account = await PlayerHelper.LoadDataBase(scene, accountId); + // 如果有的话,就直接加入在缓存中就可以了 + if (account == null) + { + Log.Debug("检查到账号没有在数据库中,需要创建一个新的账号并且保存到数据库中"); + // 如果没有,就要创建一个新的并且保存到数据库。 + // 如果不存在,表示这是一个新的账号,需要创建一下这个账号。 + account = await PlayerFactory.Create(scene, accountId); + account.Basic.Level = 99; + account.Basic.NickName = "王麻子"; + account.Basic.Country = "cn"; + account.Basic.Exp = 999; + account.Basic.Head = "xxx.png"; + + account.NeedSave = true; + } + else + { + Log.Debug("检查到账号在数据库中"); + } + + Log.Debug("把当前账号添加到缓存中"); + // 把创建完成的Account放入到缓存中 + gameAccountManageComponent.Add(account); + } + else + { + Log.Debug("检测到当前账号已经在缓存中了"); + // 如果有延迟下线的计划任务,那就先取消一下。 + account.CancelTimeout(); + // 如果在Gate的缓存中已经存在了该账号那只能以下几种可能: + // 1、同一客户端发送了重复登录的请求数据。 + // 2、客户端经历的断线然后又重新连接到这个服务器上了(断线重连)。 + // 3、多个客户端同时登录了这个账号(顶号)。 + + if (request.GateRouteId == account.SessionRunTimeId) + { + // 如果执行到这里,说明是客户端发送了多次登录的请求,这样的情况下,直接返回就可以了,不需要做任何操作。 + return; + } + } + + account.LoginTime = TimeHelper.Now; + + response.RoleRouteId = account.RuntimeId; + + await FTask.CompletedTask; + } +} \ No newline at end of file diff --git a/Hotfix/Game/Role/RoleManagerComponentSystem.cs b/Hotfix/Game/Role/RoleManagerComponentSystem.cs deleted file mode 100644 index 9858365..0000000 --- a/Hotfix/Game/Role/RoleManagerComponentSystem.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Fantasy.Entitas.Interface; - -namespace NB; - -public sealed class RoleManagerComponentDestroySystem : DestroySystem -{ - protected override void Destroy(RoleManagerComponent self) - { - foreach (var (_, role) in self.Roles) - { - role.Dispose(); - } - - self.Roles.Clear(); - } -} - -public static class RoleManagerComponentSystem -{ - public static void Add(this RoleManagerComponent self, Role account) - { - self.Roles.Add(account.Id, account); - } - - public static Role Get(this RoleManagerComponent self, long accountId) - { - return self.Roles.GetValueOrDefault(accountId); - } - - public static bool TryGet(this RoleManagerComponent self, long accountId, out Role account) - { - return self.Roles.TryGetValue(accountId, out account); - } - - public static void Remove(this RoleManagerComponent self, long accountId, bool isDispose = true) - { - if (!self.Roles.Remove(accountId, out var account)) - { - return; - } - - if (!isDispose) - { - return; - } - - account.Dispose(); - } -} \ No newline at end of file diff --git a/Hotfix/Game/System/Player/PlayerAutoSaveComponentSystem.cs b/Hotfix/Game/System/Player/PlayerAutoSaveComponentSystem.cs new file mode 100644 index 0000000..49410e4 --- /dev/null +++ b/Hotfix/Game/System/Player/PlayerAutoSaveComponentSystem.cs @@ -0,0 +1,6 @@ +namespace NB.Game; + +public class PlayerAutoSaveComponentSystem +{ + +} \ No newline at end of file diff --git a/Hotfix/Gate/System/Player/PlayerDestroySystem.cs b/Hotfix/Game/System/Player/PlayerDestroySystem.cs similarity index 93% rename from Hotfix/Gate/System/Player/PlayerDestroySystem.cs rename to Hotfix/Game/System/Player/PlayerDestroySystem.cs index 32d32d7..90aa06a 100644 --- a/Hotfix/Gate/System/Player/PlayerDestroySystem.cs +++ b/Hotfix/Game/System/Player/PlayerDestroySystem.cs @@ -1,6 +1,6 @@ using Fantasy.Entitas.Interface; -namespace NB.Gate; +namespace NB.Game; public sealed class PlayerDestroySystem : DestroySystem { diff --git a/Hotfix/Gate/System/Player/PlayerFactory.cs b/Hotfix/Game/System/Player/PlayerFactory.cs similarity index 95% rename from Hotfix/Gate/System/Player/PlayerFactory.cs rename to Hotfix/Game/System/Player/PlayerFactory.cs index f7d9626..8203c26 100644 --- a/Hotfix/Gate/System/Player/PlayerFactory.cs +++ b/Hotfix/Game/System/Player/PlayerFactory.cs @@ -2,8 +2,9 @@ using Fantasy; using Fantasy.Async; using Fantasy.Entitas; using Fantasy.Helper; +using NB.Game; -namespace NB.Gate; +namespace NB.Game; public static class PlayerFactory { diff --git a/Hotfix/Gate/System/Player/PlayerHelper.cs b/Hotfix/Game/System/Player/PlayerHelper.cs similarity index 99% rename from Hotfix/Gate/System/Player/PlayerHelper.cs rename to Hotfix/Game/System/Player/PlayerHelper.cs index c9a4ce5..8d64f06 100644 --- a/Hotfix/Gate/System/Player/PlayerHelper.cs +++ b/Hotfix/Game/System/Player/PlayerHelper.cs @@ -1,9 +1,8 @@ -using NB.Gate.System; using Fantasy; using Fantasy.Async; using Fantasy.Network; -namespace NB.Gate; +namespace NB.Game; public static class PlayerHelper { diff --git a/Hotfix/Gate/System/PlayerManageComponentSystem.cs b/Hotfix/Game/System/PlayerManageComponentSystem.cs similarity index 94% rename from Hotfix/Gate/System/PlayerManageComponentSystem.cs rename to Hotfix/Game/System/PlayerManageComponentSystem.cs index 313bed3..c626983 100644 --- a/Hotfix/Gate/System/PlayerManageComponentSystem.cs +++ b/Hotfix/Game/System/PlayerManageComponentSystem.cs @@ -1,6 +1,6 @@ using Fantasy.Entitas.Interface; -namespace NB.Gate.System; +namespace NB.Game; public sealed class PlayerManageComponentDestroySystem : DestroySystem { @@ -27,7 +27,7 @@ public static class PlayerManageComponentSystem return self.Players.GetValueOrDefault(accountId); } - public static bool TryGet(this PlayerManageComponent self, long accountId, out Player? account) + public static bool TryGet(this PlayerManageComponent self, long accountId, out Player account) { return self.Players.TryGetValue(accountId, out account); } diff --git a/Hotfix/Gate/Handler/C2G_GetAccountInfoRequestHandler.cs b/Hotfix/Gate/Handler/C2G_GetAccountInfoRequestHandler.cs new file mode 100644 index 0000000..a286832 --- /dev/null +++ b/Hotfix/Gate/Handler/C2G_GetAccountInfoRequestHandler.cs @@ -0,0 +1,32 @@ +// using Fantasy; +// using Fantasy.Async; +// using Fantasy.Network; +// using Fantasy.Network.Interface; +// +// namespace NB.Gate; +// +// public sealed class C2G_GetAccountInfoRequestHandler : MessageRPC +// { +// protected override async FTask Run(Session session, C2G_GetAccountInfoRequest request, G2C_GetAccountInfoResponse response, Action reply) +// { +// var gameAccountFlagComponent = session.GetComponent(); +// +// if (gameAccountFlagComponent == null) +// { +// // 表示不应该访问这个接口,要先访问登录的接口。 +// // response.ErrorCode = 1; +// session.Dispose(); +// return; +// } +// +// Player account = gameAccountFlagComponent.Player; +// +// if (account == null) +// { +// // 表示这个Account已经被销毁过了。不是咱们想要的了 +// } +// +// response.GameAccountInfo = account.GetGameAccountInfo(); +// await FTask.CompletedTask; +// } +// } \ No newline at end of file diff --git a/Hotfix/Gate/Handler/C2G_LoginRequestHandler.cs b/Hotfix/Gate/Handler/C2G_LoginRequestHandler.cs new file mode 100644 index 0000000..326541d --- /dev/null +++ b/Hotfix/Gate/Handler/C2G_LoginRequestHandler.cs @@ -0,0 +1,78 @@ +using Fantasy; +using Fantasy.Async; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.Platform.Net; + +namespace NB.Gate.Handler; + +public sealed class C2G_LoginRequestHandler : MessageRPC +{ + protected override async FTask Run(Session session, C2G_LoginRequest request, G2C_LoginResponse response, + Action reply) + { + if (string.IsNullOrEmpty(request.ToKen)) + { + // 1、客户端漏传了 response.ErrorCode = 1; + // 2、恶意攻击导致的 session.Dispose(); + session.Dispose(); + return; + } + + var scene = session.Scene; + // Log.Info($"网关服场景 {scene.Id} {scene.RouteId} {scene.SceneConfigId} {scene.RouteId} {session.RouteId}"); + if (!GateJWTHelper.ValidateToken(scene, request.ToKen, out var accountId)) + { + // 如果失败,表示肯定是恶意攻击、所以毫不犹疑,直接断开当前会话。 + session.Dispose(); + return; + } + + // 首先需要找到一个需要建立Route的Scene的SceneConfig。 + // 例子演示的连接的ChatScene,所以这里我通过SceneConfigData拿到这个SceneConfig。 + // 如果是其他Scene,用法跟这个没有任何区别。 + var gameSceneConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Game)[0]; + // 通过chatSceneConfig拿到这个Scene的RouteId + var gameRouteId = gameSceneConfig.RouteId; + //连接到游戏中心服 + var gameResponse = (Game2G_EnterResponse)await session.Scene.NetworkMessagingComponent.CallInnerRoute( + gameRouteId, new G2Game_EnterRequest() + { + AccountId = accountId, + GateRouteId = session.RuntimeId + }); + + if (gameResponse.ErrorCode != 0) + { + // 如果ErrorCode不是0表示请求的协议发生错误,应该提示给客户端。 + // 这里就不做这个了。 + response.ErrorCode = gameResponse.ErrorCode; + return; + } + + // 要实现Route协议的转发,需要给Session添加一个RouteComponent,这个非常重要。 + var routeComponent = session.AddComponent(); + // 需要再Examples/Config/NetworkProtocol/RouteType.Config里添加一个ChatRoute + // 然后点击导表工具,会自动生成一个RouteType.cs文件。 + // 使用你定义的ChatRoute当routeType的参数传递进去。 + // routeResponse会返回一个ChatRouteId,这个就是Chat的RouteId。 + routeComponent.AddAddress(RouteType.GameRoute, gameResponse.RoleRouteId); + // 这些操作完成后,就完成了Route消息的建立。 + // 后面可以直接发送Route消息通过Gate自动中转给Chat了。 + + + + // 给当前Session添加一个组件,当Session销毁的时候会销毁这个组件。 + var accountFlagComponent = session.AddComponent(); + accountFlagComponent.AccountID = accountId; + + // account.SessionRunTimeId = session.RuntimeId; + // response.GameAccountInfo = account.GetGameAccountInfo(); + + Log.Debug($"当前的Gate服务器:{session.Scene.SceneConfigId} accountId:{accountId}"); + + + + Log.Debug($"网关内网连接到游戏服成功 routerId={gameResponse.RoleRouteId}"); + } +} \ No newline at end of file diff --git a/Hotfix/Gate/Handler/Outer/C2G_GetAccountInfoRequestHandler.cs b/Hotfix/Gate/Handler/Outer/C2G_GetAccountInfoRequestHandler.cs deleted file mode 100644 index 05decaf..0000000 --- a/Hotfix/Gate/Handler/Outer/C2G_GetAccountInfoRequestHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Fantasy; -using Fantasy.Async; -using Fantasy.Network; -using Fantasy.Network.Interface; - -namespace NB.Gate; - -public sealed class C2G_GetAccountInfoRequestHandler : MessageRPC -{ - protected override async FTask Run(Session session, C2G_GetAccountInfoRequest request, G2C_GetAccountInfoResponse response, Action reply) - { - var gameAccountFlagComponent = session.GetComponent(); - - if (gameAccountFlagComponent == null) - { - // 表示不应该访问这个接口,要先访问登录的接口。 - // response.ErrorCode = 1; - session.Dispose(); - return; - } - - Player account = gameAccountFlagComponent.Player; - - if (account == null) - { - // 表示这个Account已经被销毁过了。不是咱们想要的了 - } - - response.GameAccountInfo = account.GetGameAccountInfo(); - await FTask.CompletedTask; - } -} \ No newline at end of file diff --git a/Hotfix/Gate/Handler/Outer/C2G_LoginRequestHandler.cs b/Hotfix/Gate/Handler/Outer/C2G_LoginRequestHandler.cs deleted file mode 100644 index 459c91e..0000000 --- a/Hotfix/Gate/Handler/Outer/C2G_LoginRequestHandler.cs +++ /dev/null @@ -1,101 +0,0 @@ -using NB.Gate.System; -using Fantasy; -using Fantasy.Async; -using Fantasy.Network; -using Fantasy.Network.Interface; - -namespace NB.Gate.Handler; - -public sealed class C2G_LoginRequestHandler : MessageRPC -{ - protected override async FTask Run(Session session, C2G_LoginRequest request, G2C_LoginResponse response, - Action reply) - { - if (string.IsNullOrEmpty(request.ToKen)) - { - // 1、客户端漏传了 response.ErrorCode = 1; - // 2、恶意攻击导致的 session.Dispose(); - session.Dispose(); - return; - } - - var scene = session.Scene; - // Log.Info($"网关服场景 {scene.Id} {scene.RouteId} {scene.SceneConfigId} {scene.RouteId} {session.RouteId}"); - if (!GateJWTHelper.ValidateToken(scene, request.ToKen, out var accountId)) - { - // 如果失败,表示肯定是恶意攻击、所以毫不犹疑,直接断开当前会话。 - session.Dispose(); - return; - } - - // 在缓存中检查该账号是否存在 - var gameAccountManageComponent = scene.GetComponent(); - Log.Debug("检查账号是否在缓存中"); - if (!gameAccountManageComponent.TryGet(accountId, out var account)) - { - // 首先要先到数据库中查询是否有这个账号 - account = await PlayerHelper.LoadDataBase(scene, accountId); - // 如果有的话,就直接加入在缓存中就可以了 - if (account == null) - { - Log.Debug("检查到账号没有在数据库中,需要创建一个新的账号并且保存到数据库中"); - // 如果没有,就要创建一个新的并且保存到数据库。 - // 如果不存在,表示这是一个新的账号,需要创建一下这个账号。 - account = await PlayerFactory.Create(scene, accountId); - } - else - { - Log.Debug("检查到账号在数据库中"); - } - - Log.Debug("把当前账号添加到缓存中"); - // 把创建完成的Account放入到缓存中 - gameAccountManageComponent.Add(account); - } - else - { - Log.Debug("检测到当前账号已经在缓存中了"); - // 如果有延迟下线的计划任务,那就先取消一下。 - account.CancelTimeout(); - // 如果在Gate的缓存中已经存在了该账号那只能以下几种可能: - // 1、同一客户端发送了重复登录的请求数据。 - // 2、客户端经历的断线然后又重新连接到这个服务器上了(断线重连)。 - // 3、多个客户端同时登录了这个账号(顶号)。 - - if (session.RuntimeId == account.SessionRunTimeId) - { - // 如果执行到这里,说明是客户端发送了多次登录的请求,这样的情况下,直接返回就可以了,不需要做任何操作。 - return; - } - - Log.Debug("检测到当前账号的Session不是同一个"); - if (scene.TryGetEntity(account.SessionRunTimeId, out var oldSession)) - { - Log.Debug("当前账号的Session在当前的系统中,所以需要发送一个重复登录的命令,并且要断开这个Session"); - // 如果这个Session在当前框架中可以查询到。 - // 那表示就是当前的会话还是在存在的,有如下几个可能: - // 1、客户端断线重连,要给这个Session发送一个消息,通知它有人登录了。 - // 2、其他的客户端登录了这个账号,要给这个Session发送一个消息,通知它有人登录了。 - - var gameAccountFlagComponent = oldSession.GetComponent(); - gameAccountFlagComponent.AccountID = 0; - gameAccountFlagComponent.Player = null; - // 给客户端发送一个重复登录的消息,如果当前客户端是自己上次登录的,发送也不会收到。 - oldSession.Send(new G2C_RepeatLogin()); - // 给当前Session做一个定时销毁的任务,因为不做这个定时销毁,直接销毁的话,有可能消息还没有发送过去就销毁了 - oldSession.SetTimeout(3000); - } - } - - // 给当前Session添加一个组件,当Session销毁的时候会销毁这个组件。 - var accountFlagComponent = session.AddComponent(); - accountFlagComponent.AccountID = accountId; - accountFlagComponent.Player = account; - - account.SessionRunTimeId = session.RuntimeId; - response.GameAccountInfo = account.GetGameAccountInfo(); - Log.Debug($"当前的Gate服务器:{session.Scene.SceneConfigId} accountId:{accountId}"); - - //连接到游戏中心服 - } -} \ No newline at end of file diff --git a/Hotfix/Gate/System/Player/PlayerFlagComponentSystem.cs b/Hotfix/Gate/System/PlayerFlagComponentSystem.cs similarity index 54% rename from Hotfix/Gate/System/Player/PlayerFlagComponentSystem.cs rename to Hotfix/Gate/System/PlayerFlagComponentSystem.cs index 81f9028..77f0d53 100644 --- a/Hotfix/Gate/System/Player/PlayerFlagComponentSystem.cs +++ b/Hotfix/Gate/System/PlayerFlagComponentSystem.cs @@ -2,18 +2,18 @@ using Fantasy.Entitas.Interface; namespace NB.Gate; -public sealed class PlayerFlagComponentDestroySystem : DestroySystem +public sealed class SessionPlayerComponentDestroySystem : DestroySystem { - protected override void Destroy(PlayerFlagComponent self) + protected override void Destroy(SessionPlayerComponent self) { if (self.AccountID != 0) { // 执行下线过程、并且要求在5分钟后完成缓存清理。也就是5分钟后会保存数据到数据库。 // 由于5分钟太长了、咱们测试的时候,不方便测试,也可以把这个时间改短一些,比如10秒。 - PlayerHelper.Disconnect(self.Scene, self.AccountID, 1000 * 60 * 5).Coroutine(); - self.AccountID = 0; + // PlayerHelper.Disconnect(self.Scene, self.AccountID, 1000 * 60 * 5).Coroutine(); + // self.AccountID = 0; } - self.Player = null; + // self.Player = null; } } \ No newline at end of file diff --git a/Hotfix/OnSceneCreate_Init.cs b/Hotfix/OnSceneCreate_Init.cs index 9b76ede..ef991b5 100644 --- a/Hotfix/OnSceneCreate_Init.cs +++ b/Hotfix/OnSceneCreate_Init.cs @@ -2,6 +2,7 @@ using Fantasy.Async; using Fantasy.Event; using NB.Authentication; +using NB.Game; using NB.Gate; namespace NB; @@ -26,14 +27,12 @@ public class OnSceneCreate_Init : AsyncEventSystem { // 用于验证JWT是否合法的组件 scene.AddComponent(); - // 用于管理玩家的组件 - scene.AddComponent(); break; } case SceneType.Game: { - //游戏服用于管理用户的组件 - scene.AddComponent(); + //用于管理玩家的组件 + scene.AddComponent(); break; } } diff --git a/Server.sln.DotSettings.user b/Server.sln.DotSettings.user index 59e24e9..6c23b67 100644 --- a/Server.sln.DotSettings.user +++ b/Server.sln.DotSettings.user @@ -38,6 +38,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/Tools/NetworkProtocol/Template.txt b/Tools/NetworkProtocol/Template.txt index 39c10be..217dfaf 100644 --- a/Tools/NetworkProtocol/Template.txt +++ b/Tools/NetworkProtocol/Template.txt @@ -16,7 +16,7 @@ using Fantasy.Serialize; #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8618 -namespace NBC +namespace Fantasy { #else using ProtoBuf; diff --git a/Tools/NetworkProtocol2/CommandLine.dll b/Tools/NetworkProtocol2/CommandLine.dll deleted file mode 100644 index 3eab2be274359223f5640b0af04b2af375cce752..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 225280 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