diff --git a/SCHALE.Common/Database/dbs.cs b/SCHALE.Common/Database/dbs.cs index 4e3d006..49d9575 100644 --- a/SCHALE.Common/Database/dbs.cs +++ b/SCHALE.Common/Database/dbs.cs @@ -209,6 +209,9 @@ namespace SCHALE.Common.Database [JsonIgnore] public virtual ICollection Weapons { get; } + [JsonIgnore] + public long RaidSeasonId { get; set; } // idk where to store this + public AccountDB() { } public AccountDB(long publisherAccountId) diff --git a/SCHALE.Common/Migrations/20240511074956_Account_RaidSeasonId.Designer.cs b/SCHALE.Common/Migrations/20240511074956_Account_RaidSeasonId.Designer.cs new file mode 100644 index 0000000..0434d57 --- /dev/null +++ b/SCHALE.Common/Migrations/20240511074956_Account_RaidSeasonId.Designer.cs @@ -0,0 +1,472 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SCHALE.Common.Database; + +#nullable disable + +namespace SCHALE.Common.Migrations +{ + [DbContext(typeof(SCHALEContext))] + [Migration("20240511074956_Account_RaidSeasonId")] + partial class Account_RaidSeasonId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("SCHALE.Common.Database.AccountDB", b => + { + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ServerId")); + + b.Property("BirthDay") + .HasColumnType("datetime2"); + + b.Property("CallName") + .HasColumnType("nvarchar(max)"); + + b.Property("CallNameUpdateTime") + .HasColumnType("datetime2"); + + b.Property("Comment") + .HasColumnType("nvarchar(max)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("DevId") + .HasColumnType("nvarchar(max)"); + + b.Property("Exp") + .HasColumnType("bigint"); + + b.Property("LastConnectTime") + .HasColumnType("datetime2"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("LinkRewardDate") + .HasColumnType("datetime2"); + + b.Property("LobbyMode") + .HasColumnType("int"); + + b.Property("MemoryLobbyUniqueId") + .HasColumnType("bigint"); + + b.Property("Nickname") + .HasColumnType("nvarchar(max)"); + + b.Property("PublisherAccountId") + .HasColumnType("bigint"); + + b.Property("RaidSeasonId") + .HasColumnType("bigint"); + + b.Property("RepresentCharacterServerId") + .HasColumnType("int"); + + b.Property("RetentionDays") + .HasColumnType("int"); + + b.Property("State") + .HasColumnType("int"); + + b.Property("UnReadMailCount") + .HasColumnType("int"); + + b.Property("VIPLevel") + .HasColumnType("int"); + + b.HasKey("ServerId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.CharacterDB", b => + { + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ServerId")); + + b.Property("AccountServerId") + .HasColumnType("bigint"); + + b.Property("EquipmentServerIds") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EquipmentSlotAndDBIds") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExSkillLevel") + .HasColumnType("int"); + + b.Property("Exp") + .HasColumnType("bigint"); + + b.Property("ExtraPassiveSkillLevel") + .HasColumnType("int"); + + b.Property("FavorExp") + .HasColumnType("bigint"); + + b.Property("FavorRank") + .HasColumnType("int"); + + b.Property("IsFavorite") + .HasColumnType("bit"); + + b.Property("IsLocked") + .HasColumnType("bit"); + + b.Property("LeaderSkillLevel") + .HasColumnType("int"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("PassiveSkillLevel") + .HasColumnType("int"); + + b.Property("PotentialStats") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PublicSkillLevel") + .HasColumnType("int"); + + b.Property("StarGrade") + .HasColumnType("int"); + + b.Property("UniqueId") + .HasColumnType("bigint"); + + b.HasKey("ServerId"); + + b.HasIndex("AccountServerId"); + + b.ToTable("Characters"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.EchelonDB", b => + { + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ServerId")); + + b.Property("AccountServerId") + .HasColumnType("bigint"); + + b.Property("EchelonNumber") + .HasColumnType("bigint"); + + b.Property("EchelonType") + .HasColumnType("int"); + + b.Property("ExtensionType") + .HasColumnType("int"); + + b.Property("LeaderServerId") + .HasColumnType("bigint"); + + b.Property("MainSlotServerIds") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SkillCardMulliganCharacterIds") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SupportSlotServerIds") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TSSInteractionServerId") + .HasColumnType("bigint"); + + b.Property("UsingFlag") + .HasColumnType("int"); + + b.HasKey("ServerId"); + + b.HasIndex("AccountServerId"); + + b.ToTable("Echelons"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.EquipmentDB", b => + { + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ServerId")); + + b.Property("AccountServerId") + .HasColumnType("bigint"); + + b.Property("BoundCharacterServerId") + .HasColumnType("bigint"); + + b.Property("Exp") + .HasColumnType("bigint"); + + b.Property("IsLocked") + .HasColumnType("bit"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("StackCount") + .HasColumnType("bigint"); + + b.Property("Tier") + .HasColumnType("int"); + + b.Property("UniqueId") + .HasColumnType("bigint"); + + b.HasKey("ServerId"); + + b.HasIndex("AccountServerId"); + + b.ToTable("Equipment"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.ItemDB", b => + { + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ServerId")); + + b.Property("AccountServerId") + .HasColumnType("bigint"); + + b.Property("IsLocked") + .HasColumnType("bit"); + + b.Property("StackCount") + .HasColumnType("bigint"); + + b.Property("UniqueId") + .HasColumnType("bigint"); + + b.HasKey("ServerId"); + + b.HasIndex("AccountServerId"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.MissionProgressDB", b => + { + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ServerId")); + + b.Property("AccountServerId") + .HasColumnType("bigint"); + + b.Property("Complete") + .HasColumnType("bit"); + + b.Property("MissionUniqueId") + .HasColumnType("bigint"); + + b.Property("ProgressParameters") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.HasKey("ServerId"); + + b.HasIndex("AccountServerId"); + + b.ToTable("MissionProgresses"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.Models.AccountTutorial", b => + { + b.Property("AccountServerId") + .HasColumnType("bigint"); + + b.Property("TutorialIds") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("AccountServerId"); + + b.ToTable("AccountTutorials"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.Models.GuestAccount", b => + { + b.Property("Uid") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Uid")); + + b.Property("DeviceId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Uid"); + + b.ToTable("GuestAccounts"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.WeaponDB", b => + { + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ServerId")); + + b.Property("AccountServerId") + .HasColumnType("bigint"); + + b.Property("BoundCharacterServerId") + .HasColumnType("bigint"); + + b.Property("Exp") + .HasColumnType("bigint"); + + b.Property("IsLocked") + .HasColumnType("bit"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("StarGrade") + .HasColumnType("int"); + + b.Property("UniqueId") + .HasColumnType("bigint"); + + b.HasKey("ServerId"); + + b.HasIndex("AccountServerId"); + + b.ToTable("Weapons"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.CharacterDB", b => + { + b.HasOne("SCHALE.Common.Database.AccountDB", "Account") + .WithMany("Characters") + .HasForeignKey("AccountServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.EchelonDB", b => + { + b.HasOne("SCHALE.Common.Database.AccountDB", "Account") + .WithMany("Echelons") + .HasForeignKey("AccountServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.EquipmentDB", b => + { + b.HasOne("SCHALE.Common.Database.AccountDB", "Account") + .WithMany("Equipment") + .HasForeignKey("AccountServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.ItemDB", b => + { + b.HasOne("SCHALE.Common.Database.AccountDB", "Account") + .WithMany("Items") + .HasForeignKey("AccountServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.MissionProgressDB", b => + { + b.HasOne("SCHALE.Common.Database.AccountDB", "Account") + .WithMany("MissionProgresses") + .HasForeignKey("AccountServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.WeaponDB", b => + { + b.HasOne("SCHALE.Common.Database.AccountDB", "Account") + .WithMany("Weapons") + .HasForeignKey("AccountServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("SCHALE.Common.Database.AccountDB", b => + { + b.Navigation("Characters"); + + b.Navigation("Echelons"); + + b.Navigation("Equipment"); + + b.Navigation("Items"); + + b.Navigation("MissionProgresses"); + + b.Navigation("Weapons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SCHALE.Common/Migrations/20240511074956_Account_RaidSeasonId.cs b/SCHALE.Common/Migrations/20240511074956_Account_RaidSeasonId.cs new file mode 100644 index 0000000..4a71b0e --- /dev/null +++ b/SCHALE.Common/Migrations/20240511074956_Account_RaidSeasonId.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SCHALE.Common.Migrations +{ + /// + public partial class Account_RaidSeasonId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RaidSeasonId", + table: "Accounts", + type: "bigint", + nullable: false, + defaultValue: 0L); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RaidSeasonId", + table: "Accounts"); + } + } +} diff --git a/SCHALE.Common/Migrations/SCHALEContextModelSnapshot.cs b/SCHALE.Common/Migrations/SCHALEContextModelSnapshot.cs index 7c746db..26923f0 100644 --- a/SCHALE.Common/Migrations/SCHALEContextModelSnapshot.cs +++ b/SCHALE.Common/Migrations/SCHALEContextModelSnapshot.cs @@ -75,6 +75,9 @@ namespace SCHALE.Common.Migrations b.Property("PublisherAccountId") .HasColumnType("bigint"); + b.Property("RaidSeasonId") + .HasColumnType("bigint"); + b.Property("RepresentCharacterServerId") .HasColumnType("int"); diff --git a/SCHALE.Common/NetworkProtocol/protos.cs b/SCHALE.Common/NetworkProtocol/protos.cs index 2d58d92..c691fb9 100644 --- a/SCHALE.Common/NetworkProtocol/protos.cs +++ b/SCHALE.Common/NetworkProtocol/protos.cs @@ -8795,7 +8795,7 @@ namespace SCHALE.Common.NetworkProtocol { get { - return NetworkProtocol.Protocol.None; + return NetworkProtocol.Protocol.MultiFloorRaid_Sync; } } public List MultiFloorRaidDBs { get; set; } diff --git a/SCHALE.GameServer/Commands/CharacterCommand.cs b/SCHALE.GameServer/Commands/CharacterCommand.cs new file mode 100644 index 0000000..8eb22b5 --- /dev/null +++ b/SCHALE.GameServer/Commands/CharacterCommand.cs @@ -0,0 +1,78 @@ +using SCHALE.Common.Database; +using SCHALE.Common.Database.ModelExtensions; +using SCHALE.Common.FlatData; +using SCHALE.GameServer.Services.Irc; + +namespace SCHALE.GameServer.Commands +{ + [CommandHandler("character", "Unlock a character or all characters", "character unlock=all")] + public class CharacterCommand : Command + { + [Argument("unlock")] + public string? Unlock { get; set; } + + public override void Execute(Dictionary args, IrcConnection connection) + { + base.Execute(args); + + // TODO: finish this + if (Unlock is null) + { + connection.SendChatMessage($"Usage: /character unlock="); + return; + } + + if (Unlock.Equals("all", StringComparison.CurrentCultureIgnoreCase)) + { + AddAllCharacters(connection); + + connection.SendChatMessage("All Characters Added!"); + } else if (Unlock.Equals("clear", StringComparison.CurrentCultureIgnoreCase)) + { + + } else if (uint.TryParse(Unlock, out uint characterId)) + { + + } else + { + connection.SendChatMessage($"Invalid Character Id: {characterId}"); + return; + } + + connection.Context.SaveChanges(); + base.NotifySuccess(connection); + } + + private void AddAllCharacters(IrcConnection connection) + { + var account = connection.Account; + var context = connection.Context; + + var characterExcel = connection.ExcelTableService.GetTable().UnPack().DataList; + var allCharacters = characterExcel.Where(x => x.IsPlayable && x.IsPlayableCharacter && x.CollectionVisible && !account.Characters.Any(c => c.UniqueId == x.Id)).Select(x => + { + return new CharacterDB() + { + UniqueId = x.Id, + StarGrade = x.MaxStarGrade, + Level = 90, + Exp = 0, + PublicSkillLevel = 10, + ExSkillLevel = 5, + PassiveSkillLevel = 10, + ExtraPassiveSkillLevel = 10, + LeaderSkillLevel = 1, + FavorRank = 500, + IsNew = true, + IsLocked = true, + PotentialStats = { { 1, 0 }, { 2, 0 }, { 3, 0 } }, + EquipmentServerIds = [0, 0, 0] + }; + }).ToList(); + + account.AddCharacters(context, [.. allCharacters]); + connection.Context.SaveChanges(); + } + + } +} diff --git a/SCHALE.GameServer/Commands/Command.cs b/SCHALE.GameServer/Commands/Command.cs new file mode 100644 index 0000000..24e0c7d --- /dev/null +++ b/SCHALE.GameServer/Commands/Command.cs @@ -0,0 +1,177 @@ +using SCHALE.GameServer.Services.Irc; +using System.Reflection; + +namespace SCHALE.GameServer.Commands +{ + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class CommandHandler : Attribute + { + public string Name { get; } + public string Description { get; } + public string Example { get; } + + public CommandHandler(string commandName, string description, string example) + { + Name = commandName; + Description = description; + Example = example; + } + + } + + [AttributeUsage(AttributeTargets.Property)] + public class ArgumentAttribute : Attribute + { + public string Key { get; } + + public ArgumentAttribute(string key) + { + Key = key; + } + } + + [Flags] + public enum CommandUsage + { + None = 0, + Console = 1, + User = 2 + } + + public abstract class Command + { + public virtual CommandUsage Usage + { + get + { + var usage = CommandUsage.None; + if (GetType().GetMethod(nameof(Execute), [typeof(Dictionary), typeof(IrcConnection)])?.DeclaringType == GetType()) + usage |= CommandUsage.User; + if (GetType().GetMethod(nameof(Execute), [typeof(Dictionary)])?.DeclaringType == GetType()) + usage |= CommandUsage.Console; + + return usage; + } + } + + readonly Dictionary argsProperties; + + public Command() + { + argsProperties = GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(x => Attribute.IsDefined(x, typeof(ArgumentAttribute))) + .ToDictionary(x => ((ArgumentAttribute)Attribute.GetCustomAttribute(x, typeof(ArgumentAttribute))!).Key, StringComparer.OrdinalIgnoreCase); + } + + public virtual void Execute(Dictionary args) + { + foreach (var arg in args) + { + if (argsProperties.TryGetValue(arg.Key, out var prop)) + prop.SetValue(this, arg.Value); + } + } + + public virtual void Execute(Dictionary args, IrcConnection connection) + { + Execute(args); + } + + public virtual void NotifySuccess(IrcConnection connection) + { + connection.SendChatMessage($"{GetType().Name} success! Please relog for it to take effect."); + } + + protected T Parse(string? value, T fallback = default!) + { + var tryParseMethod = typeof(T).GetMethod("TryParse", [typeof(string), typeof(T).MakeByRefType()]); + + if (tryParseMethod != null) + { + var parameters = new object[] { value!, null! }; + bool success = (bool)tryParseMethod.Invoke(null, parameters)!; + + if (success) + return (T)parameters[1]; + } + + return fallback; + } + } + + public static class CommandHandlerFactory + { + public static readonly List Commands = new List(); + + static readonly Dictionary>> commandFunctions; + static readonly Dictionary, IrcConnection>> commandFunctionsConn; + private static readonly char[] separator = new[] { ' ' }; + + static CommandHandlerFactory() + { + commandFunctions = new Dictionary>>(StringComparer.OrdinalIgnoreCase); + commandFunctionsConn = new Dictionary, IrcConnection>>(StringComparer.OrdinalIgnoreCase); + + RegisterCommands(Assembly.GetExecutingAssembly()); + } + + public static void RegisterCommands(Assembly assembly) + { + var commandTypes = assembly.GetTypes() + .Where(t => Attribute.IsDefined(t, typeof(CommandHandler)) && typeof(Command).IsAssignableFrom(t)); + + foreach (var commandType in commandTypes) + { + var commandAttribute = (CommandHandler?)Attribute.GetCustomAttribute(commandType, typeof(CommandHandler)); + if (commandAttribute != null) + { + var commandInstance = (Command)Activator.CreateInstance(commandType)!; + + if (commandInstance.Usage.HasFlag(CommandUsage.Console)) + commandFunctions[commandAttribute.Name] = commandInstance.Execute; + if (commandInstance.Usage.HasFlag(CommandUsage.User)) + commandFunctionsConn[commandAttribute.Name] = commandInstance.Execute; + + Commands.Add(commandInstance); + } + } + } + + public static void HandleCommand(string commandLine, IrcConnection? connection = null) + { + var parts = commandLine.Split(separator, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0) + return; + + var arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var i = 1; i < parts.Length; i++) + { + var argParts = parts[i].Split('=', 2); + if (argParts.Length == 2) + arguments[argParts[0]] = argParts[1]; + } + + if (connection is not null) + { + if (!(commandFunctionsConn).TryGetValue(parts[0], out var command)) + { + connection.SendChatMessage($"Unknown command: {parts[0]}"); + return; + } + + command(arguments, connection); + } else + { + if (!(commandFunctions).TryGetValue(parts[0], out var command)) + { + connection.SendChatMessage($"Unknown command: {parts[0]}"); + return; + } + + command(arguments); + } + } + } + +} diff --git a/SCHALE.GameServer/Commands/HelpCommand.cs b/SCHALE.GameServer/Commands/HelpCommand.cs new file mode 100644 index 0000000..4e1ca81 --- /dev/null +++ b/SCHALE.GameServer/Commands/HelpCommand.cs @@ -0,0 +1,58 @@ +using SCHALE.Common.Database; +using SCHALE.GameServer.Services.Irc; +using Serilog; +using System.Reflection; +using System.Text; + +namespace SCHALE.GameServer.Commands +{ + [CommandHandler("help", "Print out all commands with their description and example", "help")] + public class HelpCommand : Command + { + static readonly Dictionary commandAttributes = new Dictionary(); + + // doesnt support console yet + public override void Execute(Dictionary args) + { + base.Execute(args); + + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("Available Commands: "); + foreach (var command in CommandHandlerFactory.Commands.Where(x => x.Usage.HasFlag(CommandUsage.Console))) + { + if (!commandAttributes.TryGetValue(command.GetType(), out var attr)) + { + attr = command.GetType().GetCustomAttribute(typeof(CommandHandler)) as CommandHandler; + commandAttributes[command.GetType()] = attr; + } + + if (attr != null) + sb.AppendLine($"{attr.Name} - {attr.Description} (Example: {attr.Example})"); + } + + Console.Write(sb.ToString()); + } + + public override void Execute(Dictionary args, IrcConnection connection) + { + base.Execute(args); + + connection.SendChatMessage("Available Commands: "); + foreach (var command in CommandHandlerFactory.Commands.Where(x => x.Usage.HasFlag(CommandUsage.User))) + { + if (!commandAttributes.TryGetValue(command.GetType(), out var attr)) + { + attr = command.GetType().GetCustomAttribute(typeof(CommandHandler)) as CommandHandler; + commandAttributes[command.GetType()] = attr; + } + + if (attr != null) + connection.SendChatMessage($"{attr.Name} - {attr.Description} (Example: {attr.Example})"); + } + + base.NotifySuccess(connection); + } + } + +} diff --git a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Account.cs b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Account.cs index c4af67f..04aeb16 100644 --- a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Account.cs +++ b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Account.cs @@ -224,6 +224,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers //AddAllEquipment(account); //AddAllItems(account); //AddAllWeapons(account); + SetRaidSeason(account, 63); return new AccountLoginSyncResponse() { @@ -327,6 +328,14 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers EquipmentDBs = [.. account.Equipment] }, + ClanLoginResponse = new ClanLoginResponse() + { + AccountClanMemberDB = new() + { + AccountId = account.ServerId + } + }, + FriendCode = "SCHALE", }; } @@ -444,36 +453,8 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers { return new MiniGameMissionListResponse(); } - + // these will probably be commands - private void AddAllCharacters(AccountDB account) - { - var characterExcel = excelTableService.GetTable().UnPack().DataList; - var allCharacters = characterExcel.Where(x => x.IsPlayable && x.IsPlayableCharacter && x.CollectionVisible && !account.Characters.Any(c => c.UniqueId == x.Id)).Select(x => - { - return new CharacterDB() - { - UniqueId = x.Id, - StarGrade = x.MaxStarGrade, - Level = 90, - Exp = 0, - PublicSkillLevel = 10, - ExSkillLevel = 5, - PassiveSkillLevel = 10, - ExtraPassiveSkillLevel = 10, - LeaderSkillLevel = 1, - FavorRank = 500, - IsNew = true, - IsLocked = true, - PotentialStats = { { 1, 0 }, { 2, 0 }, { 3, 0 } }, - EquipmentServerIds = [0, 0, 0] - }; - }).ToList(); - - account.AddCharacters(context, [.. allCharacters]); - context.SaveChanges(); - } - private void AddAllEquipment(AccountDB account) { var equipmentExcel = excelTableService.GetTable().UnPack().DataList; @@ -519,7 +500,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers BoundCharacterServerId = x.ServerId, IsLocked = false, StarGrade = 5, - Level = 70 + Level = 200 }; }); @@ -527,6 +508,11 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers context.SaveChanges(); } + private void SetRaidSeason(AccountDB account, long seasonId) + { + account.RaidSeasonId = seasonId; + context.SaveChanges(); + } } } diff --git a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Clan.cs b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Clan.cs index a3e1c74..37b489f 100644 --- a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Clan.cs +++ b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Clan.cs @@ -1,10 +1,73 @@ -using SCHALE.Common.NetworkProtocol; +using SCHALE.Common.Database; +using SCHALE.Common.NetworkProtocol; +using SCHALE.GameServer.Services; namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers { public class Clan : ProtocolHandlerBase { - public Clan(IProtocolHandlerFactory protocolHandlerFactory) : base(protocolHandlerFactory) { } + private readonly ISessionKeyService sessionKeyService; + private readonly SCHALEContext context; + private readonly ExcelTableService excelTableService; + + public Clan(IProtocolHandlerFactory protocolHandlerFactory, ISessionKeyService _sessionKeyService, SCHALEContext _context, ExcelTableService _excelTableService) : base(protocolHandlerFactory) + { + sessionKeyService = _sessionKeyService; + context = _context; + excelTableService = _excelTableService; + } + + [ProtocolHandler(Protocol.Clan_Lobby)] + public ResponsePacket CheckHandler(ClanLobbyRequest req) + { + var account = sessionKeyService.GetAccount(req.SessionKey); + + return new ClanLobbyResponse() + { + IrcConfig = new() + { + //HostAddress = "10.0.0.149", + HostAddress = "192.168.86.35", + Port = 6667, + Password = "" + }, + AccountClanDB = new() + { + ClanDBId = 777, + ClanName = "Nexon", + ClanChannelName = "channel_1", + ClanPresidentNickName = "Arona", + ClanPresidentRepresentCharacterUniqueId = 10000, + ClanNotice = "", + ClanMemberCount = 1, + }, + AccountClanMemberDB = new() + { + AccountId = account.ServerId, + AccountLevel = account.Level, + ClanDBId = 777, + RepresentCharacterUniqueId = 10000, + ClanSocialGrade = Common.FlatData.ClanSocialGrade.Member, + AccountNickName = account.Nickname + }, + ClanMemberDBs = [ + new() { + AccountId = account.ServerId, + AccountLevel = account.Level, + ClanDBId = 777, + RepresentCharacterUniqueId = 10000, + AttendanceCount = 33, + ClanSocialGrade = Common.FlatData.ClanSocialGrade.Member, + AccountNickName = account.Nickname, + AttachmentDB = new() { + AccountId = account.ServerId, + EmblemUniqueId = 123123 + } + } + ], + + }; + } [ProtocolHandler(Protocol.Clan_Check)] public ResponsePacket CheckHandler(ClanCheckRequest req) diff --git a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/MultiFloorRaid.cs b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/MultiFloorRaid.cs new file mode 100644 index 0000000..52b6837 --- /dev/null +++ b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/MultiFloorRaid.cs @@ -0,0 +1,47 @@ +using SCHALE.Common.Database; +using SCHALE.Common.NetworkProtocol; +using SCHALE.GameServer.Services; + +namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers +{ + public class MultiFloorRaid : ProtocolHandlerBase + { + private readonly ISessionKeyService sessionKeyService; + private readonly SCHALEContext context; + private readonly ExcelTableService excelTableService; + + public MultiFloorRaid(IProtocolHandlerFactory protocolHandlerFactory, ISessionKeyService _sessionKeyService, SCHALEContext _context, ExcelTableService _excelTableService) : base(protocolHandlerFactory) + { + sessionKeyService = _sessionKeyService; + context = _context; + excelTableService = _excelTableService; + } + + [ProtocolHandler(Protocol.MultiFloorRaid_Sync)] + public ResponsePacket SyncHandler(MultiFloorRaidSyncRequest req) + { + return new MultiFloorRaidSyncResponse() + { + MultiFloorRaidDBs = [ + new() { + SeasonId = 2, + ClearBattleFrame = -1 + } + ] + }; + } + + [ProtocolHandler(Protocol.MultiFloorRaid_EnterBattle)] + public ResponsePacket EnterBattleHandler(MultiFloorRaidEnterBattleRequest req) + { + return new MultiFloorRaidEnterBattleResponse(); + } + + [ProtocolHandler(Protocol.MultiFloorRaid_EndBattle)] + public ResponsePacket EndBattleHandler(MultiFloorRaidEndBattleRequest req) + { + return new MultiFloorRaidEndBattleResponse(); + } + + } +} diff --git a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/ProtocolHandlerFactory.cs b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/ProtocolHandlerFactory.cs index 68d1532..473b1fa 100644 --- a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/ProtocolHandlerFactory.cs +++ b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/ProtocolHandlerFactory.cs @@ -1,5 +1,6 @@ using SCHALE.Common.Database; using SCHALE.Common.NetworkProtocol; +using SCHALE.GameServer.Services; using Serilog; using System.Reflection; diff --git a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Raid.cs b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Raid.cs index 5eef9a7..a788f03 100644 --- a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Raid.cs +++ b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Raid.cs @@ -1,4 +1,5 @@ using SCHALE.Common.Database; +using SCHALE.Common.FlatData; using SCHALE.Common.NetworkProtocol; using SCHALE.GameServer.Services; @@ -20,9 +21,26 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers [ProtocolHandler(Protocol.Raid_Lobby)] public ResponsePacket LobbyHandler(RaidLobbyRequest req) { + var account = sessionKeyService.GetAccount(req.SessionKey); + + var raidSeasonInfo = excelTableService.GetTable().UnPack().DataList; + var targetSeason = raidSeasonInfo.FirstOrDefault(x => x.SeasonId == account.RaidSeasonId); + return new RaidLobbyResponse() { - + SeasonType = RaidSeasonType.Open, + RaidLobbyInfoDB = new() + { + SeasonId = account.RaidSeasonId, + Tier = 2, + Ranking = 1, + BestRankingPoint = 1_000_000, + TotalRankingPoint = 10_000_000, + ReceiveRewardIds = targetSeason.SeasonRewardId, + PlayableHighestDifficulty = new() { + { targetSeason.OpenRaidBossGroup.FirstOrDefault(), Difficulty.Torment } + } + } }; } @@ -38,9 +56,49 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers [ProtocolHandler(Protocol.Raid_CreateBattle)] public ResponsePacket CreateBattleHandller(RaidCreateBattleRequest req) { + var account = sessionKeyService.GetAccount(req.SessionKey); + + var raidStageTable = excelTableService.GetTable().UnPack().DataList; + var currentRaid = raidStageTable.FirstOrDefault(x => x.Id == req.RaidUniqueId); + return new RaidCreateBattleResponse() { + RaidDB = new() + { + Owner = new() + { + AccountId = account.ServerId, + AccountName = account.Nickname, + }, + ContentType = ContentType.Raid, + UniqueId = req.RaidUniqueId, + SeasonId = account.RaidSeasonId, + PlayerCount = 1, + RaidState = RaidStatus.Playing, + IsPractice = req.IsPractice, + RaidBossDBs = [ + new() { + ContentType = ContentType.Raid, + BossCurrentHP = long.MaxValue + } + ], + AccountLevelWhenCreateDB = account.Level, + }, + RaidBattleDB = new() + { + ContentType = ContentType.Raid, + RaidUniqueId = req.RaidUniqueId, + CurrentBossHP = long.MaxValue, + RaidMembers = [ + new() { + AccountId = account.ServerId, + AccountName = account.Nickname, + } + ] + }, + + AssistCharacterDB = new () { } }; } @@ -58,7 +116,6 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers { return new RaidEndBattleResponse() { - }; } } diff --git a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Shop.cs b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Shop.cs index 4482094..a4c41d9 100644 --- a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Shop.cs +++ b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Shop.cs @@ -103,7 +103,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers Character = new() // hardcoded util proper db { ServerId = req.AccountId, - UniqueId = 20007, + UniqueId = id, StarGrade = 3, Level = 1, FavorRank = 1, @@ -120,6 +120,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers return new ShopBuyGacha3Response() { + GachaResults = gachaResults, UpdateTime = DateTime.UtcNow, GemBonusRemain = long.MaxValue, ConsumedItems = [], diff --git a/SCHALE.GameServer/GameServer.cs b/SCHALE.GameServer/GameServer.cs index bb3af75..3fb92f5 100644 --- a/SCHALE.GameServer/GameServer.cs +++ b/SCHALE.GameServer/GameServer.cs @@ -6,6 +6,7 @@ using SCHALE.Common.Database; using SCHALE.GameServer.Controllers.Api.ProtocolHandlers; using SCHALE.GameServer.Services; using Microsoft.EntityFrameworkCore; +using SCHALE.GameServer.Services.Irc; namespace SCHALE.GameServer { @@ -53,6 +54,7 @@ namespace SCHALE.GameServer builder.Services.AddProtocolHandlerFactory(); builder.Services.AddMemorySessionKeyService(); builder.Services.AddExcelTableService(); + builder.Services.AddIrcService(); // Add all Handler Groups var handlerGroups = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(ProtocolHandlerBase))); diff --git a/SCHALE.GameServer/Services/Irc/IrcConnection.cs b/SCHALE.GameServer/Services/Irc/IrcConnection.cs new file mode 100644 index 0000000..2b44dfc --- /dev/null +++ b/SCHALE.GameServer/Services/Irc/IrcConnection.cs @@ -0,0 +1,48 @@ +using SCHALE.Common.Database; +using System.Net.Sockets; +using System.Text.Json; + +namespace SCHALE.GameServer.Services.Irc +{ + public class IrcConnection + { + public required TcpClient TcpClient { get; set; } + + public required SCHALEContext Context { get; set; } + + public required StreamWriter StreamWriter { get; set; } + + public required ExcelTableService ExcelTableService { get; set; } + + public long AccountServerId { get; set; } + + public string CurrentChannel { get; set; } + + public AccountDB Account { get => Context.Accounts.FirstOrDefault(x => x.ServerId == AccountServerId); } + + public void SendChatMessage(string text) + { + SendChatMessage(text, "Shiroko", 10010, IrcMessageType.Chat); + } + + public void SendChatMessage(string text, string nickname, long pfpCharacterId, IrcMessageType messageType) + { + var reply = new Reply() + { + Prefix = "mx_admin_bot!admin@netadmin.example.com", + Command = $"PRIVMSG {CurrentChannel}", + Trailing = JsonSerializer.Serialize(new IrcMessage() + { + CharacterId = pfpCharacterId, + MessageType = messageType, + AccountNickname = nickname, + Text = text, + SendTicks = DateTimeOffset.Now.Ticks, + }, typeof(IrcMessage)), + }.ToString(); + + StreamWriter.WriteLine(reply); + StreamWriter.Flush(); + } + } +} diff --git a/SCHALE.GameServer/Services/Irc/IrcServer.cs b/SCHALE.GameServer/Services/Irc/IrcServer.cs new file mode 100644 index 0000000..71213f9 --- /dev/null +++ b/SCHALE.GameServer/Services/Irc/IrcServer.cs @@ -0,0 +1,208 @@ +using SCHALE.Common.Database; +using SCHALE.GameServer.Commands; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SCHALE.GameServer.Services.Irc +{ + public class IrcServer + { + private ConcurrentDictionary clients = new ConcurrentDictionary(); // most irc commands doesn't even send over the player uid so imma just use TcpClient as key + private ConcurrentDictionary> channels = new ConcurrentDictionary>(); + + private readonly TcpListener listener; + + private readonly ILogger logger; + private readonly SCHALEContext context; + private readonly ExcelTableService excelTableService; + + public IrcServer(IPAddress host, int port, ILogger _logger, SCHALEContext _context, ExcelTableService _excelTableService) + { + logger = _logger; + context = _context; + excelTableService = _excelTableService; + + listener = new TcpListener(host, port); + } + + public async Task StartAsync(CancellationToken stoppingToken) + { + listener.Start(); + logger.LogDebug("Irc Server Started"); + + while (!stoppingToken.IsCancellationRequested) + { + var tcpClient = await listener.AcceptTcpClientAsync(); + + _ = HandleMessageAsync(tcpClient); + logger.LogDebug("TcpClient is trying to connect..."); + } + } + + public async Task HandleMessageAsync(TcpClient tcpClient) + { + using var reader = new StreamReader(tcpClient.GetStream()); + using var writer = new StreamWriter(tcpClient.GetStream()) { AutoFlush = true }; + + string line; + + while ((line = await reader.ReadLineAsync()) != null) + { + var splitLine = line.Split(' ', 2); + var commandStr = splitLine[0].ToUpper().Trim(); + var parameters = splitLine.Length > 1 ? splitLine[1] : ""; + + if (!Enum.TryParse(commandStr, out var command)) + { + command = IrcCommand.UNKNOWN; + } + + string result = ""; + + switch (command) + { + case IrcCommand.NICK: + result = await HandleNick(parameters); + break; + case IrcCommand.USER: + await HandleUser(parameters, tcpClient, writer); + break; + case IrcCommand.JOIN: + await HandleJoin(parameters, tcpClient); + break; + case IrcCommand.PRIVMSG: + await HandlePrivMsg(parameters, tcpClient); + break; + case IrcCommand.PING: + result = await HandlePing(parameters); + break; + } + + if (result != null || result != "") + await writer.WriteLineAsync(result); + } + + tcpClient.Close(); + } + + public void Stop() + { + listener.Stop(); + } + + private async Task HandleNick(string parameters) // welcomes + { + return new Reply() + { + Prefix = "server", + ReplyCode = ReplyCode.RPL_WELCOME, + Trailing = "Welcome to SCHALE!" + }.ToString(); + } + + private async Task HandleUser(string parameters, TcpClient client, StreamWriter writer) // sends over account server id + { + string[] args = parameters.Split(' '); + var user_serverId = long.Parse(args[0].Split("_")[1]); + + clients[client] = new IrcConnection() + { + AccountServerId = user_serverId, + Context = context, + TcpClient = client, + StreamWriter = writer, + ExcelTableService = excelTableService, + }; + + logger.LogDebug($"User {user_serverId} logged in"); + } + + private async Task HandleJoin(string parameters, TcpClient client) // sends over channel id + { + var channel = parameters; + + if (!channels.ContainsKey(channel)) + { + channels[channel] = new List(); + } + + var userClient = clients[client]; + + channels[channel].Add(userClient.AccountServerId); + userClient.CurrentChannel = channel; + + logger.LogDebug($"User {userClient.AccountServerId} joined {channel}"); + } + + private async Task HandlePrivMsg(string parameters, TcpClient client) // player sends msg + { + string[] args = parameters.Split(' ', 2); + + var channel = args[0]; + var payloadStr = args[1].TrimStart(':'); + + //logger.LogDebug("payload: " + payloadStr); + + var payload = JsonSerializer.Deserialize(payloadStr, typeof(IrcMessage)) as IrcMessage; + + if (payload.Text.StartsWith('/')) + { + CommandHandlerFactory.HandleCommand(payload.Text.Substring(1), clients[client]); + } + } + + private async Task HandlePing(string parameters) + { + return new Reply().ToString(); + } + } + + public enum IrcCommand + { + PASS, + NICK, + USER, + JOIN, + PRIVMSG, + PING, + PART, + QUIT, + UNKNOWN + } + + public enum IrcMessageType + { + None, + Notice, + Sticker, + Chat, + HistoryCount + } + + public class IrcMessage : EventArgs + { + [JsonPropertyName("MessageType")] + public IrcMessageType MessageType { get; set; } + + [JsonPropertyName("CharacterId")] + public long CharacterId { get; set; } + + [JsonPropertyName("AccountNickname")] + public string AccountNickname { get; set; } + + [JsonPropertyName("StickerId")] + public long StickerId { get; } + + [JsonPropertyName("Text")] + public string Text { get; set; } + + [JsonPropertyName("SendTicks")] + public long SendTicks { get; set; } + + [JsonPropertyName("EmblemId")] + public int EmblemId { get; set; } + } +} diff --git a/SCHALE.GameServer/Services/Irc/IrcService.cs b/SCHALE.GameServer/Services/Irc/IrcService.cs new file mode 100644 index 0000000..137c654 --- /dev/null +++ b/SCHALE.GameServer/Services/Irc/IrcService.cs @@ -0,0 +1,35 @@ +using System.Net; +using SCHALE.Common.Database; +using Serilog; + +namespace SCHALE.GameServer.Services.Irc +{ + public class IrcService : BackgroundService + { + private IrcServer server; + + public IrcService(ILogger _logger, SCHALEContext _context, ExcelTableService excelTableService) + { + server = new IrcServer(IPAddress.Any, 6667, _logger, _context, excelTableService); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await server.StartAsync(stoppingToken); + } + + public override async Task StopAsync(CancellationToken stoppingToken) + { + server.Stop(); + await base.StopAsync(stoppingToken); + } + } + + internal static class IrcServiceExtensions + { + public static void AddIrcService(this IServiceCollection services) + { + services.AddHostedService(); + } + } +} diff --git a/SCHALE.GameServer/Services/Irc/Reply.cs b/SCHALE.GameServer/Services/Irc/Reply.cs new file mode 100644 index 0000000..4d4b299 --- /dev/null +++ b/SCHALE.GameServer/Services/Irc/Reply.cs @@ -0,0 +1,22 @@ +namespace SCHALE.GameServer.Services.Irc +{ + public class Reply : EventArgs + { + public string Prefix { get; set; } + + public string Command { get; set; } = string.Empty; + + public ReplyCode ReplyCode { get; set; } + + public List Params { get; set; } + + public string Trailing { get; set; } + + public override string ToString() + { + string cmd = Command == string.Empty ? $"{(int)ReplyCode:D3}" : Command; + + return $":{Prefix} {cmd} {Params} :{Trailing}"; + } + } +} diff --git a/SCHALE.GameServer/Services/Irc/ReplyCode.cs b/SCHALE.GameServer/Services/Irc/ReplyCode.cs new file mode 100644 index 0000000..cb52626 --- /dev/null +++ b/SCHALE.GameServer/Services/Irc/ReplyCode.cs @@ -0,0 +1,182 @@ +namespace SCHALE.GameServer.Services.Irc +{ + public enum ReplyCode + { + NONE = 0, + RPL_NONE = 300, + RPL_WELCOME = 1, + RPL_YOURHOST = 2, + RPL_CREATED = 3, + RPL_MYINFO = 4, + RPL_MAP = 5, + RPL_ENDOFMAP = 7, + RPL_MOTDSTART = 375, + RPL_MOTD = 372, + RPL_MOTDALT = 377, + RPL_MOTDALT2 = 378, + RPL_MOTDEND = 376, + RPL_UMODEIS = 221, + RPL_USERHOST = 302, + RPL_ISON = 303, + RPL_AWAY = 301, + RPL_UNAWAY = 305, + RPL_NOWAWAY = 306, + RPL_WHOISHELPER = 310, + RPL_WHOISUSER = 311, + RPL_WHOISSERVER = 312, + RPL_WHOISOPERATOR = 313, + RPL_WHOISIDLE = 317, + RPL_ENDOFWHOIS = 318, + RPL_WHOISCHANNEL = 319, + RPL_WHOWASUSER = 314, + RPL_ENDOFWHOWAS = 369, + RPL_WHOREPLY = 352, + RPL_ENDOFWHO = 315, + RPL_USERIPS = 307, + RPL_USERIP = 340, + RPL_LISTSTART = 321, + RPL_LIST = 322, + RPL_LISTEND = 323, + RPL_LINKS = 364, + RPL_ENDOFLINKS = 365, + RPL_UNIQOPIS = 325, + RPL_CHANNELMODEIS = 324, + RPL_CHANNELURL = 328, + RPL_CHANNELCREATED = 329, + RPL_NOTOPIC = 331, + RPL_TOPIC = 332, + RPL_TOPICSETBY = 333, + RPL_NAMEREPLY = 353, + RPL_ENDOFNAMES = 366, + RPL_INVITING = 341, + RPL_SUMMONING = 342, + RPL_INVITELIST = 346, + RPL_ENDOFINVITELIST = 357, + RPL_EXCEPTLIST = 348, + RPL_ENDOFEXCEPTLIST = 349, + RPL_BANLIST = 367, + RPL_ENDOFBANLIST = 368, + RPL_VERSION = 351, + RPL_INFO = 371, + RPL_ENDOFINFO = 374, + RPL_YOUREOPER = 381, + RPL_REHASHING = 382, + RPL_YOURESERVICE = 383, + RPL_TIME = 391, + RPL_USERSSTART = 392, + RPL_USERS = 393, + RPL_ENDOFUSERS = 394, + RPL_NOUSERS = 395, + RPL_SERVLIST = 234, + RPL_SERVLISTEND = 235, + RPL_ADMINME = 256, + RPL_ADMINLOC1 = 257, + RPL_ADMINLOC2 = 258, + RPL_ADMINEMAIL = 259, + RPL_TRYAGAIN = 263, + RPL_TRACELINK = 200, + RPL_TRACECONNECTING = 201, + RPL_TRACEHANDSHAKE = 202, + RPL_TRACEUNKNOWN = 203, + RPL_TRACEOPERATOR = 204, + RPL_TRACEUSER = 205, + RPL_TRACESERVER = 206, + RPL_TRACESERVICE = 207, + RPL_TRACENEWTYPE = 208, + RPL_TRACECLASS = 209, + RPL_TRACERECONNECT = 210, + RPL_TRACELOG = 261, + RPL_TRACEEND = 262, + RPL_STATSLINKINFO = 211, + RPL_STATSCOMMANDS = 212, + RPL_STATSCLINE = 213, + RPL_STATSNLINE = 214, + RPL_STATSILINE = 215, + RPL_STATSKLINE = 216, + RplStatsPLine = 217, + RplStatsQLine = 222, + RplStatsELine = 223, + RplStatsDLine = 224, + RplStatsLLine = 241, + RplStatsuLine = 242, + RplStatsoLine = 243, + RplStatsHLine = 244, + RplStatsGLine = 247, + RplStatsULine = 248, + RplStatsZLine = 249, + RplStatsYLine = 218, + RPL_ENDOFSTATS = 219, + RPL_STATSUPTIME = 242, + RPL_STATSPING = 246, + RPL_STATSDLINE = 250, + RplGLineList = 280, + RplEndOfGLineList = 281, + RplSilenceList = 271, + RplEndOfSilenceList = 272, + RPL_LUSERCLIENT = 251, + RPL_LUSEROP = 252, + RPL_LUSERUNKNOWN = 253, + RPL_LUSERCHANNELS = 254, + RPL_LUSERME = 255, + RplLUserLocalUser = 265, + RplLUserGlobalUser = 266, + ERR_NOSUCHNICK = 401, + ERR_NOSUCHSERVER = 402, + ERR_NOSUCHCHANNEL = 403, + ERR_CANNOTSENDTOCHAN = 404, + ERR_TOOMANYCHANNELS = 405, + ERR_WASNOSUCHNICK = 406, + ERR_TOOMANYTARGETS = 407, + ERR_NOSUCHSERVICE = 408, + ERR_NOORIGIN = 409, + ERR_NORECIPIENT = 411, + ERR_NOTEXTTOSEND = 412, + ERR_NOTOPLEVEL = 413, + ERR_WILDTOPLEVEL = 414, + ERR_BADMASK = 415, + ErrTooMuchInfo = 416, + ERR_UNKNOWNCOMMAND = 421, + ERR_NOMOTD = 422, + ERR_NOADMININFO = 423, + ERR_FILEERROR = 424, + ERR_NONICKNAMEGIVEN = 431, + ERR_ERRONEUSNICKNAME = 432, + ERR_NICKNAMEINUSE = 433, + ERR_NICKCOLLISION = 436, + ERR_UNAVAILRESOURCE = 437, + ErrNickTooFast = 438, + ErrTargetTooFast = 439, + ERR_USERNOTINCHANNEL = 441, + ERR_NOTONCHANNEL = 442, + ERR_USERONCHANNEL = 443, + ERR_NOLOGIN = 444, + ERR_SUMMONDISABLED = 445, + ERR_USERSDISABLED = 446, + ERR_NOTREGISTERED = 451, + ERR_NEEDMOREPARAMS = 461, + ERR_ALREADYREGISTRED = 462, + ERR_NOPERMFORHOST = 463, + ERR_PASSWDMISMATCH = 464, + ERR_YOUREBANNEDCREEP = 465, + ERR_YOUWILLBEBANNED = 466, + ERR_KEYSET = 467, + ErrServerCanChange = 468, + ERR_CHANNELISFULL = 471, + ERR_UNKNOWNMODE = 472, + ERR_INVITEONLYCHAN = 473, + ERR_BANNEDFROMCHAN = 474, + ERR_BADCHANNELKEY = 475, + ERR_BADCHANMASK = 476, + ERR_NOCHANMODES = 477, + ERR_BANLISTFULL = 478, + ERR_NOPRIVILEGES = 481, + ERR_CHANOPRIVSNEEDED = 482, + ERR_CANTKILLSERVER = 483, + ERR_RESTRICTED = 484, + ERR_UNIQOPPRIVSNEEDED = 485, + ERR_NOOPERHOST = 491, + ERR_UMODEUNKNOWNFLAG = 501, + ERR_USERSDONTMATCH = 502, + ErrSilenceListFull = 511, + } +}