From ace68414f78fdf56dbcdeb01b11b40c02c53c3e4 Mon Sep 17 00:00:00 2001 From: xeon Date: Sun, 21 Jan 2024 18:34:19 +0300 Subject: [PATCH] MUIP server, GM command system --- RPG.GameCore/Excel/AvatarRow.cs | 2 +- RPG.Network.Proto/server_only.proto | 9 +++ .../HostApplicationBuilderExtensions.cs | 5 +- .../Modules/AdventureModule.cs | 35 ++++++++++ .../Modules/Attributes/GMAliasAttribute.cs | 12 ++++ .../Modules/Attributes/GMCommandAttribute.cs | 12 ++++ .../Modules/ModuleManager.cs | 69 +++++++++++++++++-- .../Command/GameserverCommandHandler.cs | 11 +++ .../Session/PlayerSession.cs | 5 ++ RPG.Services.Gameserver/appsettings.json | 5 ++ .../RPG.Services.Gateserver.csproj | 4 ++ RPG.Services.Gateserver/appsettings.json | 5 ++ RPG.Services.MUIP/Handlers/ApiHandler.cs | 53 ++++++++++++++ .../Command/MuipserverCommandHandler.cs | 11 +++ RPG.Services.MUIP/Program.cs | 23 +++++++ .../Properties/launchSettings.json | 29 ++++++++ RPG.Services.MUIP/RPG.Services.MUIP.csproj | 13 ++++ RPG.Services.MUIP/RPGMuipserver.cs | 10 +++ .../appsettings.Development.json | 8 +++ RPG.Services.MUIP/appsettings.json | 29 ++++++++ RPG.Services.SDK/Handlers/DispatchHandler.cs | 3 +- Snowflake.sln | 19 +++-- 22 files changed, 357 insertions(+), 15 deletions(-) create mode 100644 RPG.Services.Gameserver/Modules/Attributes/GMAliasAttribute.cs create mode 100644 RPG.Services.Gameserver/Modules/Attributes/GMCommandAttribute.cs create mode 100644 RPG.Services.MUIP/Handlers/ApiHandler.cs create mode 100644 RPG.Services.MUIP/Network/Command/MuipserverCommandHandler.cs create mode 100644 RPG.Services.MUIP/Program.cs create mode 100644 RPG.Services.MUIP/Properties/launchSettings.json create mode 100644 RPG.Services.MUIP/RPG.Services.MUIP.csproj create mode 100644 RPG.Services.MUIP/RPGMuipserver.cs create mode 100644 RPG.Services.MUIP/appsettings.Development.json create mode 100644 RPG.Services.MUIP/appsettings.json diff --git a/RPG.GameCore/Excel/AvatarRow.cs b/RPG.GameCore/Excel/AvatarRow.cs index 73326dc..2538242 100644 --- a/RPG.GameCore/Excel/AvatarRow.cs +++ b/RPG.GameCore/Excel/AvatarRow.cs @@ -26,7 +26,7 @@ public class AvatarRow : ExcelRow public uint MaxRank { get; set; } // 0x50 public string[] RankUpCostList { get; set; } = []; // 0x58 public uint MaxRankRepay { get; set; } // 0x60 - public uint[] SkillList { get; set; } // 0x68 + public uint[] SkillList { get; set; } = []; // 0x68 [JsonConverter(typeof(JsonStringEnumConverter))] public AvatarBaseType AvatarBaseType { get; set; } // 0x70 public string DefaultAvatarImagePath { get; set; } = string.Empty; // 0x78 diff --git a/RPG.Network.Proto/server_only.proto b/RPG.Network.Proto/server_only.proto index ca6c727..abe9331 100644 --- a/RPG.Network.Proto/server_only.proto +++ b/RPG.Network.Proto/server_only.proto @@ -7,17 +7,26 @@ enum RPGServiceType RPG_SERVICE_TYPE_SDK = 1; RPG_SERVICE_TYPE_GATESERVER = 2; RPG_SERVICE_TYPE_GAMESERVER = 3; + RPG_SERVICE_TYPE_MUIPSERVER = 4; } enum ServiceCommandType { SERVICE_COMMAND_TYPE_NONE = 0; + SERVICE_COMMAND_TYPE_GMTALK_BY_MUIP = 10; + SERVICE_COMMAND_TYPE_BIND_CONTAINER = 100; SERVICE_COMMAND_TYPE_BIND_CONTAINER_RESULT = 101; SERVICE_COMMAND_TYPE_UNBIND_CONTAINER = 102; SERVICE_COMMAND_TYPE_FORWARD_GAME_MESSAGE = 103; } +message CmdGmtalkByMuip +{ + uint64 session_id = 1; + string msg = 2; +} + message CmdBindContainer { uint64 session_id = 1; diff --git a/RPG.Services.Core/Extensions/HostApplicationBuilderExtensions.cs b/RPG.Services.Core/Extensions/HostApplicationBuilderExtensions.cs index 4cabbc7..a576b19 100644 --- a/RPG.Services.Core/Extensions/HostApplicationBuilderExtensions.cs +++ b/RPG.Services.Core/Extensions/HostApplicationBuilderExtensions.cs @@ -9,7 +9,7 @@ using RPG.Services.Core.Session; namespace RPG.Services.Core.Extensions; public static class HostApplicationBuilderExtensions { - public static HostApplicationBuilder SetupRPGService(this HostApplicationBuilder builder) + public static IHostApplicationBuilder SetupRPGService(this IHostApplicationBuilder builder, bool stateless = false) where TService : RPGServiceBase where TCommandHandler : ServiceCommandHandler { @@ -21,10 +21,11 @@ public static class HostApplicationBuilderExtensions builder.Services.AddHostedService() .AddSingleton() - .AddSingleton() .AddSingleton() .AddSingleton(); + if (!stateless) builder.Services.AddSingleton(); + return builder; } } diff --git a/RPG.Services.Gameserver/Modules/AdventureModule.cs b/RPG.Services.Gameserver/Modules/AdventureModule.cs index 9e0d71f..722afcc 100644 --- a/RPG.Services.Gameserver/Modules/AdventureModule.cs +++ b/RPG.Services.Gameserver/Modules/AdventureModule.cs @@ -4,6 +4,8 @@ using RPG.Services.Gameserver.Modules.Attributes; using RPG.Services.Gameserver.Session; namespace RPG.Services.Gameserver.Modules; + +[GMAlias("adventure")] internal class AdventureModule : BaseModule { private readonly ExcelTables _tables; @@ -13,6 +15,39 @@ internal class AdventureModule : BaseModule _tables = excelTables; } + [GMCommand("maze")] + public Task OnGmEnterMaze(PlayerSession session, string[] args) + { + if (args.Length != 1) return Task.CompletedTask; + + if (!uint.TryParse(args[0], out uint entryId)) + return Task.CompletedTask; + + MapEntryRow? entry = _tables.GetExcelRow(ExcelType.MapEntry, entryId); + if (entry == null) return Task.CompletedTask; + + Send(session, CmdType.CmdEnterMazeByServerScNotify, new EnterMazeByServerScNotify + { + Maze = new Maze + { + MapEntryId = entry.ID, + Id = entry.PlaneID, + Floor = new MazeFloor + { + FloorId = entry.FloorID, + Scene = new SceneInfo + { + EntryId = entry.ID, + PlaneId = entry.PlaneID, + FloorId = entry.FloorID + } + } + }, + }); + + return Task.CompletedTask; + } + [OnCommand(CmdType.CmdGetCurSceneInfoCsReq)] public Task OnCmdGetCurSceneInfoCsReq(PlayerSession session, ReadOnlyMemory _) { diff --git a/RPG.Services.Gameserver/Modules/Attributes/GMAliasAttribute.cs b/RPG.Services.Gameserver/Modules/Attributes/GMAliasAttribute.cs new file mode 100644 index 0000000..94d68ff --- /dev/null +++ b/RPG.Services.Gameserver/Modules/Attributes/GMAliasAttribute.cs @@ -0,0 +1,12 @@ +namespace RPG.Services.Gameserver.Modules.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +internal class GMAliasAttribute : Attribute +{ + public string Alias { get; } + + public GMAliasAttribute(string alias) + { + Alias = alias; + } +} diff --git a/RPG.Services.Gameserver/Modules/Attributes/GMCommandAttribute.cs b/RPG.Services.Gameserver/Modules/Attributes/GMCommandAttribute.cs new file mode 100644 index 0000000..555e212 --- /dev/null +++ b/RPG.Services.Gameserver/Modules/Attributes/GMCommandAttribute.cs @@ -0,0 +1,12 @@ +namespace RPG.Services.Gameserver.Modules.Attributes; + +[AttributeUsage(AttributeTargets.Method)] +internal class GMCommandAttribute : Attribute +{ + public string Command { get; } + + public GMCommandAttribute(string command) + { + Command = command; + } +} diff --git a/RPG.Services.Gameserver/Modules/ModuleManager.cs b/RPG.Services.Gameserver/Modules/ModuleManager.cs index 75fd005..a03b0d3 100644 --- a/RPG.Services.Gameserver/Modules/ModuleManager.cs +++ b/RPG.Services.Gameserver/Modules/ModuleManager.cs @@ -11,14 +11,18 @@ namespace RPG.Services.Gameserver.Modules; internal class ModuleManager { private delegate Task ReqHandler(PlayerSession session, IServiceProvider serviceProvider, ReadOnlyMemory body); - private static readonly ImmutableDictionary s_handlers; + private static readonly ImmutableDictionary s_reqHandlers; + + private delegate Task GmCommandHandler(IServiceProvider provider, PlayerSession session, string[] args); + private static readonly ImmutableDictionary> s_gmCommandMap; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; static ModuleManager() { - s_handlers = MapHandlers(); + s_reqHandlers = MapReqHandlers(); + s_gmCommandMap = MapGmCommandHandlers(); } public ModuleManager(IServiceProvider serviceProvider, ILogger logger) @@ -27,9 +31,27 @@ internal class ModuleManager _logger = logger; } + public TModule Get() where TModule : BaseModule + { + return _serviceProvider.GetRequiredService(); + } + + public async Task HandleGmCommandAsync(PlayerSession session, string[] args) + { + if (args.Length < 2) return; + + if (s_gmCommandMap.TryGetValue(args[0], out var map)) + { + if (map.TryGetValue(args[1], out GmCommandHandler? handler)) + { + await handler(_serviceProvider, session, args[2..]); + } + } + } + public async Task HandleAsync(PlayerSession session, CmdType cmdType, ReadOnlyMemory body) { - if (s_handlers.TryGetValue(cmdType, out var handler)) + if (s_reqHandlers.TryGetValue(cmdType, out var handler)) { await handler(session, _serviceProvider, body); _logger.LogInformation("Successfully handled command of type {cmdType}", cmdType); @@ -40,7 +62,7 @@ internal class ModuleManager } } - private static ImmutableDictionary MapHandlers() + private static ImmutableDictionary MapReqHandlers() { var builder = ImmutableDictionary.CreateBuilder(); @@ -72,4 +94,43 @@ internal class ModuleManager return builder.ToImmutable(); } + + private static ImmutableDictionary> MapGmCommandHandlers() + { + var builder = ImmutableDictionary.CreateBuilder>(); + + IEnumerable types = Assembly.GetExecutingAssembly().GetTypes() + .Where(type => type.IsAssignableTo(typeof(BaseModule)) && !type.IsAbstract); + + MethodInfo getServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod("GetRequiredService", [typeof(IServiceProvider)])!; + + foreach (Type type in types) + { + GMAliasAttribute? aliasAttribute = type.GetCustomAttribute(); + if (aliasAttribute == null) continue; + + var commands = ImmutableDictionary.CreateBuilder(); + IEnumerable methods = type.GetMethods() + .Where(method => method.GetCustomAttribute() != null); + + foreach (MethodInfo method in methods) + { + GMCommandAttribute attribute = method.GetCustomAttribute()!; + + ParameterExpression serviceProviderParam = Expression.Parameter(typeof(IServiceProvider)); + ParameterExpression sessionParam = Expression.Parameter(typeof(PlayerSession)); + ParameterExpression argsParam = Expression.Parameter(typeof(string[])); + + MethodCallExpression getServiceCall = Expression.Call(getServiceMethod.MakeGenericMethod(type), serviceProviderParam); + MethodCallExpression handlerCall = Expression.Call(getServiceCall, method, sessionParam, argsParam); + + Expression lambda = Expression.Lambda(handlerCall, serviceProviderParam, sessionParam, argsParam); + commands.Add(attribute.Command, lambda.Compile()); + } + + builder.Add(aliasAttribute.Alias, commands.ToImmutable()); + } + + return builder.ToImmutable(); + } } diff --git a/RPG.Services.Gameserver/Network/Command/GameserverCommandHandler.cs b/RPG.Services.Gameserver/Network/Command/GameserverCommandHandler.cs index 85242b9..d7cbe9b 100644 --- a/RPG.Services.Gameserver/Network/Command/GameserverCommandHandler.cs +++ b/RPG.Services.Gameserver/Network/Command/GameserverCommandHandler.cs @@ -66,4 +66,15 @@ internal class GameserverCommandHandler : ServiceCommandHandler await session.HandleGameCommand((ushort)cmd.CmdType, cmd.Payload.Memory); } } + + [ServiceCommand(ServiceCommandType.GmtalkByMuip)] + public async Task OnCmdGmtalkByMuip(ServiceCommand command) + { + CmdGmtalkByMuip cmd = CmdGmtalkByMuip.Parser.ParseFrom(command.Body.Span); + + if (_sessionManager.TryGet(cmd.SessionId, out PlayerSession? session)) + { + await session.HandleGmTalkCommand(cmd.Msg); + } + } } diff --git a/RPG.Services.Gameserver/Session/PlayerSession.cs b/RPG.Services.Gameserver/Session/PlayerSession.cs index f7934e3..8445b66 100644 --- a/RPG.Services.Gameserver/Session/PlayerSession.cs +++ b/RPG.Services.Gameserver/Session/PlayerSession.cs @@ -16,6 +16,11 @@ internal class PlayerSession : RPGSession _moduleManager = _scope.ServiceProvider.GetRequiredService(); } + public async Task HandleGmTalkCommand(string cmd) + { + await _moduleManager.HandleGmCommandAsync(this, cmd.Split(' ')); + } + public async Task HandleGameCommand(ushort cmdType, ReadOnlyMemory body) { await _moduleManager.HandleAsync(this, (CmdType)cmdType, body); diff --git a/RPG.Services.Gameserver/appsettings.json b/RPG.Services.Gameserver/appsettings.json index 33622e7..fc701a4 100644 --- a/RPG.Services.Gameserver/appsettings.json +++ b/RPG.Services.Gameserver/appsettings.json @@ -3,6 +3,11 @@ "ServiceType": "Gameserver" }, "Nodes": [ + { + "Type": "Muipserver", + "Host": "127.0.0.1", + "Port": "21031" + }, { "Type": "Gateserver", "Host": "127.0.0.1", diff --git a/RPG.Services.Gateserver/RPG.Services.Gateserver.csproj b/RPG.Services.Gateserver/RPG.Services.Gateserver.csproj index 39a3637..ac5d8d2 100644 --- a/RPG.Services.Gateserver/RPG.Services.Gateserver.csproj +++ b/RPG.Services.Gateserver/RPG.Services.Gateserver.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/RPG.Services.Gateserver/appsettings.json b/RPG.Services.Gateserver/appsettings.json index dbbf968..4be2bfd 100644 --- a/RPG.Services.Gateserver/appsettings.json +++ b/RPG.Services.Gateserver/appsettings.json @@ -3,6 +3,11 @@ "ServiceType": "Gateserver" }, "Nodes": [ + { + "Type": "Muipserver", + "Host": "127.0.0.1", + "Port": "21031" + }, { "Type": "Gateserver", "Host": "127.0.0.1", diff --git a/RPG.Services.MUIP/Handlers/ApiHandler.cs b/RPG.Services.MUIP/Handlers/ApiHandler.cs new file mode 100644 index 0000000..ad65508 --- /dev/null +++ b/RPG.Services.MUIP/Handlers/ApiHandler.cs @@ -0,0 +1,53 @@ +using Google.Protobuf; +using RPG.Network.Proto; +using RPG.Services.Core.Network; +using RPG.Services.Core.Network.Command; + +namespace RPG.Services.MUIP.Handlers; + +internal static class ApiHandler +{ + private const string LogCategoryName = "MuipHttpApi"; + private const string GmTalkSessionIdQueryKey = "s_id"; + private const string GmTalkCommandQueryKey = "cmd"; + + private static ILogger? _logger; + + public static IResult OnGmTalk(HttpContext ctx, ServiceBox services, ILoggerFactory loggerFactory) + { + _logger ??= loggerFactory.CreateLogger(LogCategoryName); + + IQueryCollection query = ctx.Request.Query; + if (!query.ContainsKey(GmTalkSessionIdQueryKey) || !query.ContainsKey(GmTalkCommandQueryKey)) + return Results.BadRequest(); + + string? sessionIdTxt = query[GmTalkSessionIdQueryKey]; + string? cmd = query[GmTalkCommandQueryKey]; + + if (cmd == null || sessionIdTxt == null) + return Results.BadRequest(); + + if (!ulong.TryParse(sessionIdTxt, out ulong sessionId)) + return Results.BadRequest(); + + _logger.LogInformation("Received GM Talk request via HTTP, session id: {id}, cmd: {cmd}", sessionId, cmd); + + SendServiceCommand(services, ServiceCommandType.GmtalkByMuip, new CmdGmtalkByMuip + { + SessionId = sessionId, + Msg = cmd + }, RPGServiceType.Gameserver); + + return Results.Ok("Request forwarded to Gameserver"); + } + + private static void SendServiceCommand(ServiceBox services, ServiceCommandType commandType, TBody body, RPGServiceType target) where TBody : IMessage + { + ServiceCommand command = new(services.CurrentType, commandType, body.ToByteArray()); + + byte[] buffer = GC.AllocateUninitializedArray(command.Body.Length + 7); + ServiceCommandEncoder.EncodeCommand(command, buffer); + + services.SendToService(target, buffer); + } +} diff --git a/RPG.Services.MUIP/Network/Command/MuipserverCommandHandler.cs b/RPG.Services.MUIP/Network/Command/MuipserverCommandHandler.cs new file mode 100644 index 0000000..6d19552 --- /dev/null +++ b/RPG.Services.MUIP/Network/Command/MuipserverCommandHandler.cs @@ -0,0 +1,11 @@ +using RPG.Services.Core.Network; +using RPG.Services.Core.Network.Command; + +namespace RPG.Services.MUIP.Network.Command; + +internal class MuipserverCommandHandler : ServiceCommandHandler +{ + public MuipserverCommandHandler(ILogger logger, ServiceBox services) : base(logger, services) + { + } +} diff --git a/RPG.Services.MUIP/Program.cs b/RPG.Services.MUIP/Program.cs new file mode 100644 index 0000000..dfd3b64 --- /dev/null +++ b/RPG.Services.MUIP/Program.cs @@ -0,0 +1,23 @@ +using RPG.Services.MUIP.Handlers; +using RPG.Services.Core.Extensions; +using RPG.Services.MUIP.Network.Command; + +namespace RPG.Services.MUIP; + +internal static class Program +{ + private static async Task Main(string[] args) + { + Console.Title = "Snowflake | MUIP"; + + WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + builder.WebHost.UseUrls("http://*:1337"); + builder.SetupRPGService(true); + + WebApplication app = builder.Build(); + + app.MapGet("/api/gmtalk", ApiHandler.OnGmTalk); + + await app.RunAsync(); + } +} diff --git a/RPG.Services.MUIP/Properties/launchSettings.json b/RPG.Services.MUIP/Properties/launchSettings.json new file mode 100644 index 0000000..f61c10b --- /dev/null +++ b/RPG.Services.MUIP/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:13849", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/RPG.Services.MUIP/RPG.Services.MUIP.csproj b/RPG.Services.MUIP/RPG.Services.MUIP.csproj new file mode 100644 index 0000000..f156982 --- /dev/null +++ b/RPG.Services.MUIP/RPG.Services.MUIP.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/RPG.Services.MUIP/RPGMuipserver.cs b/RPG.Services.MUIP/RPGMuipserver.cs new file mode 100644 index 0000000..2a5807f --- /dev/null +++ b/RPG.Services.MUIP/RPGMuipserver.cs @@ -0,0 +1,10 @@ +using RPG.Services.Core; + +namespace RPG.Services.MUIP; + +internal class RPGMuipserver : RPGServiceBase +{ + public RPGMuipserver(ServiceManager serviceManager) : base(serviceManager) + { + } +} diff --git a/RPG.Services.MUIP/appsettings.Development.json b/RPG.Services.MUIP/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/RPG.Services.MUIP/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/RPG.Services.MUIP/appsettings.json b/RPG.Services.MUIP/appsettings.json new file mode 100644 index 0000000..5aa2591 --- /dev/null +++ b/RPG.Services.MUIP/appsettings.json @@ -0,0 +1,29 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Service": { + "ServiceType": "Muipserver" + }, + "Nodes": [ + { + "Type": "Muipserver", + "Host": "127.0.0.1", + "Port": "21031" + }, + { + "Type": "Gateserver", + "Host": "127.0.0.1", + "Port": "21051" + }, + { + "Type": "Gameserver", + "Host": "127.0.0.1", + "Port": "21081" + } + ] +} diff --git a/RPG.Services.SDK/Handlers/DispatchHandler.cs b/RPG.Services.SDK/Handlers/DispatchHandler.cs index c6d03bf..4327dcd 100644 --- a/RPG.Services.SDK/Handlers/DispatchHandler.cs +++ b/RPG.Services.SDK/Handlers/DispatchHandler.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Http.HttpResults; -using RPG.Services.SDK.Models; using RPG.Services.SDK.Models.Region; namespace RPG.Services.SDK.Handlers; @@ -14,7 +13,7 @@ public static class DispatchHandler { Retcode = 0, TopServerRegionName = "Snowflake", - RegionList = + RegionList = [ new RegionInfo { diff --git a/Snowflake.sln b/Snowflake.sln index f90ed67..5609beb 100644 --- a/Snowflake.sln +++ b/Snowflake.sln @@ -3,27 +3,29 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPG.Network.Proto", "RPG.Network.Proto\RPG.Network.Proto.csproj", "{7DA70126-3F73-407B-A024-5856F354FA97}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPG.Network.Proto", "RPG.Network.Proto\RPG.Network.Proto.csproj", "{7DA70126-3F73-407B-A024-5856F354FA97}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPG.GameCore", "RPG.GameCore\RPG.GameCore.csproj", "{74042D70-7EA0-4348-9BDB-D1E5D0FC868A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPG.GameCore", "RPG.GameCore\RPG.GameCore.csproj", "{74042D70-7EA0-4348-9BDB-D1E5D0FC868A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Framework", "Framework", "{0FF9D567-C413-43F0-9EDF-09D4D36154B7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{F199706E-4A92-4A2F-BDDD-25DA4691D43E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPG.Services.SDK", "RPG.Services.SDK\RPG.Services.SDK.csproj", "{855DA130-974F-4CE8-8DB5-2BD59DC2C3AA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPG.Services.SDK", "RPG.Services.SDK\RPG.Services.SDK.csproj", "{855DA130-974F-4CE8-8DB5-2BD59DC2C3AA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPG.Services.Gateserver", "RPG.Services.Gateserver\RPG.Services.Gateserver.csproj", "{EB7A2038-E2AF-4565-944C-D850D6AEAEED}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPG.Services.Gateserver", "RPG.Services.Gateserver\RPG.Services.Gateserver.csproj", "{EB7A2038-E2AF-4565-944C-D850D6AEAEED}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPG.Services.Gameserver", "RPG.Services.Gameserver\RPG.Services.Gameserver.csproj", "{565F9857-3E97-4363-9A5D-05CED8718116}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPG.Services.Gameserver", "RPG.Services.Gameserver\RPG.Services.Gameserver.csproj", "{565F9857-3E97-4363-9A5D-05CED8718116}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPG.Services.Core", "RPG.Services.Core\RPG.Services.Core.csproj", "{1B434662-DEC9-40C9-A709-CE87026191D9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPG.Services.Core", "RPG.Services.Core\RPG.Services.Core.csproj", "{1B434662-DEC9-40C9-A709-CE87026191D9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D96091AB-B78F-4092-ADEF-7A4D9F1B90C6}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPG.Services.MUIP", "RPG.Services.MUIP\RPG.Services.MUIP.csproj", "{2E029A8A-8878-4B71-B72B-2881CDE83B71}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,6 +56,10 @@ Global {1B434662-DEC9-40C9-A709-CE87026191D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B434662-DEC9-40C9-A709-CE87026191D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B434662-DEC9-40C9-A709-CE87026191D9}.Release|Any CPU.Build.0 = Release|Any CPU + {2E029A8A-8878-4B71-B72B-2881CDE83B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E029A8A-8878-4B71-B72B-2881CDE83B71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E029A8A-8878-4B71-B72B-2881CDE83B71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E029A8A-8878-4B71-B72B-2881CDE83B71}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -65,6 +71,7 @@ Global {EB7A2038-E2AF-4565-944C-D850D6AEAEED} = {F199706E-4A92-4A2F-BDDD-25DA4691D43E} {565F9857-3E97-4363-9A5D-05CED8718116} = {F199706E-4A92-4A2F-BDDD-25DA4691D43E} {1B434662-DEC9-40C9-A709-CE87026191D9} = {0FF9D567-C413-43F0-9EDF-09D4D36154B7} + {2E029A8A-8878-4B71-B72B-2881CDE83B71} = {F199706E-4A92-4A2F-BDDD-25DA4691D43E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9B5CE103-882A-419D-8FA3-89C8642687F6}