new irc command system, raids

This commit is contained in:
raphaeIl 2024-05-14 01:59:49 -04:00
parent 735db02c50
commit 1f697b42d5
20 changed files with 1508 additions and 36 deletions

View File

@ -209,6 +209,9 @@ namespace SCHALE.Common.Database
[JsonIgnore] [JsonIgnore]
public virtual ICollection<WeaponDB> Weapons { get; } public virtual ICollection<WeaponDB> Weapons { get; }
[JsonIgnore]
public long RaidSeasonId { get; set; } // idk where to store this
public AccountDB() { } public AccountDB() { }
public AccountDB(long publisherAccountId) public AccountDB(long publisherAccountId)

View File

@ -0,0 +1,472 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<long>("ServerId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
b.Property<DateTime?>("BirthDay")
.HasColumnType("datetime2");
b.Property<string>("CallName")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CallNameUpdateTime")
.HasColumnType("datetime2");
b.Property<string>("Comment")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreateDate")
.HasColumnType("datetime2");
b.Property<string>("DevId")
.HasColumnType("nvarchar(max)");
b.Property<long>("Exp")
.HasColumnType("bigint");
b.Property<DateTime>("LastConnectTime")
.HasColumnType("datetime2");
b.Property<int>("Level")
.HasColumnType("int");
b.Property<DateTime?>("LinkRewardDate")
.HasColumnType("datetime2");
b.Property<int>("LobbyMode")
.HasColumnType("int");
b.Property<long>("MemoryLobbyUniqueId")
.HasColumnType("bigint");
b.Property<string>("Nickname")
.HasColumnType("nvarchar(max)");
b.Property<long>("PublisherAccountId")
.HasColumnType("bigint");
b.Property<long>("RaidSeasonId")
.HasColumnType("bigint");
b.Property<int>("RepresentCharacterServerId")
.HasColumnType("int");
b.Property<int?>("RetentionDays")
.HasColumnType("int");
b.Property<int>("State")
.HasColumnType("int");
b.Property<int?>("UnReadMailCount")
.HasColumnType("int");
b.Property<int?>("VIPLevel")
.HasColumnType("int");
b.HasKey("ServerId");
b.ToTable("Accounts");
});
modelBuilder.Entity("SCHALE.Common.Database.CharacterDB", b =>
{
b.Property<long>("ServerId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
b.Property<long>("AccountServerId")
.HasColumnType("bigint");
b.Property<string>("EquipmentServerIds")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("EquipmentSlotAndDBIds")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("ExSkillLevel")
.HasColumnType("int");
b.Property<long>("Exp")
.HasColumnType("bigint");
b.Property<int>("ExtraPassiveSkillLevel")
.HasColumnType("int");
b.Property<long>("FavorExp")
.HasColumnType("bigint");
b.Property<int>("FavorRank")
.HasColumnType("int");
b.Property<bool>("IsFavorite")
.HasColumnType("bit");
b.Property<bool>("IsLocked")
.HasColumnType("bit");
b.Property<int>("LeaderSkillLevel")
.HasColumnType("int");
b.Property<int>("Level")
.HasColumnType("int");
b.Property<int>("PassiveSkillLevel")
.HasColumnType("int");
b.Property<string>("PotentialStats")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("PublicSkillLevel")
.HasColumnType("int");
b.Property<int>("StarGrade")
.HasColumnType("int");
b.Property<long>("UniqueId")
.HasColumnType("bigint");
b.HasKey("ServerId");
b.HasIndex("AccountServerId");
b.ToTable("Characters");
});
modelBuilder.Entity("SCHALE.Common.Database.EchelonDB", b =>
{
b.Property<long>("ServerId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
b.Property<long>("AccountServerId")
.HasColumnType("bigint");
b.Property<long>("EchelonNumber")
.HasColumnType("bigint");
b.Property<int>("EchelonType")
.HasColumnType("int");
b.Property<int>("ExtensionType")
.HasColumnType("int");
b.Property<long>("LeaderServerId")
.HasColumnType("bigint");
b.Property<string>("MainSlotServerIds")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("SkillCardMulliganCharacterIds")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("SupportSlotServerIds")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<long>("TSSInteractionServerId")
.HasColumnType("bigint");
b.Property<int>("UsingFlag")
.HasColumnType("int");
b.HasKey("ServerId");
b.HasIndex("AccountServerId");
b.ToTable("Echelons");
});
modelBuilder.Entity("SCHALE.Common.Database.EquipmentDB", b =>
{
b.Property<long>("ServerId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
b.Property<long>("AccountServerId")
.HasColumnType("bigint");
b.Property<long>("BoundCharacterServerId")
.HasColumnType("bigint");
b.Property<long>("Exp")
.HasColumnType("bigint");
b.Property<bool>("IsLocked")
.HasColumnType("bit");
b.Property<int>("Level")
.HasColumnType("int");
b.Property<long>("StackCount")
.HasColumnType("bigint");
b.Property<int>("Tier")
.HasColumnType("int");
b.Property<long>("UniqueId")
.HasColumnType("bigint");
b.HasKey("ServerId");
b.HasIndex("AccountServerId");
b.ToTable("Equipment");
});
modelBuilder.Entity("SCHALE.Common.Database.ItemDB", b =>
{
b.Property<long>("ServerId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
b.Property<long>("AccountServerId")
.HasColumnType("bigint");
b.Property<bool>("IsLocked")
.HasColumnType("bit");
b.Property<long>("StackCount")
.HasColumnType("bigint");
b.Property<long>("UniqueId")
.HasColumnType("bigint");
b.HasKey("ServerId");
b.HasIndex("AccountServerId");
b.ToTable("Items");
});
modelBuilder.Entity("SCHALE.Common.Database.MissionProgressDB", b =>
{
b.Property<long>("ServerId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
b.Property<long>("AccountServerId")
.HasColumnType("bigint");
b.Property<bool>("Complete")
.HasColumnType("bit");
b.Property<long>("MissionUniqueId")
.HasColumnType("bigint");
b.Property<string>("ProgressParameters")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("StartTime")
.HasColumnType("datetime2");
b.HasKey("ServerId");
b.HasIndex("AccountServerId");
b.ToTable("MissionProgresses");
});
modelBuilder.Entity("SCHALE.Common.Database.Models.AccountTutorial", b =>
{
b.Property<long>("AccountServerId")
.HasColumnType("bigint");
b.Property<string>("TutorialIds")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("AccountServerId");
b.ToTable("AccountTutorials");
});
modelBuilder.Entity("SCHALE.Common.Database.Models.GuestAccount", b =>
{
b.Property<long>("Uid")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Uid"));
b.Property<string>("DeviceId")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Uid");
b.ToTable("GuestAccounts");
});
modelBuilder.Entity("SCHALE.Common.Database.WeaponDB", b =>
{
b.Property<long>("ServerId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ServerId"));
b.Property<long>("AccountServerId")
.HasColumnType("bigint");
b.Property<long>("BoundCharacterServerId")
.HasColumnType("bigint");
b.Property<long>("Exp")
.HasColumnType("bigint");
b.Property<bool>("IsLocked")
.HasColumnType("bit");
b.Property<int>("Level")
.HasColumnType("int");
b.Property<int>("StarGrade")
.HasColumnType("int");
b.Property<long>("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
}
}
}

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SCHALE.Common.Migrations
{
/// <inheritdoc />
public partial class Account_RaidSeasonId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "RaidSeasonId",
table: "Accounts",
type: "bigint",
nullable: false,
defaultValue: 0L);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "RaidSeasonId",
table: "Accounts");
}
}
}

View File

@ -75,6 +75,9 @@ namespace SCHALE.Common.Migrations
b.Property<long>("PublisherAccountId") b.Property<long>("PublisherAccountId")
.HasColumnType("bigint"); .HasColumnType("bigint");
b.Property<long>("RaidSeasonId")
.HasColumnType("bigint");
b.Property<int>("RepresentCharacterServerId") b.Property<int>("RepresentCharacterServerId")
.HasColumnType("int"); .HasColumnType("int");

View File

@ -8795,7 +8795,7 @@ namespace SCHALE.Common.NetworkProtocol
{ {
get get
{ {
return NetworkProtocol.Protocol.None; return NetworkProtocol.Protocol.MultiFloorRaid_Sync;
} }
} }
public List<MultiFloorRaidDB> MultiFloorRaidDBs { get; set; } public List<MultiFloorRaidDB> MultiFloorRaidDBs { get; set; }

View File

@ -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<string, string> args, IrcConnection connection)
{
base.Execute(args);
// TODO: finish this
if (Unlock is null)
{
connection.SendChatMessage($"Usage: /character unlock=<all|clear|shipId>");
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<CharacterExcelTable>().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();
}
}
}

View File

@ -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<string, string>), typeof(IrcConnection)])?.DeclaringType == GetType())
usage |= CommandUsage.User;
if (GetType().GetMethod(nameof(Execute), [typeof(Dictionary<string, string>)])?.DeclaringType == GetType())
usage |= CommandUsage.Console;
return usage;
}
}
readonly Dictionary<string, PropertyInfo> 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<string, string> args)
{
foreach (var arg in args)
{
if (argsProperties.TryGetValue(arg.Key, out var prop))
prop.SetValue(this, arg.Value);
}
}
public virtual void Execute(Dictionary<string, string> 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<T>(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<Command> Commands = new List<Command>();
static readonly Dictionary<string, Action<Dictionary<string, string>>> commandFunctions;
static readonly Dictionary<string, Action<Dictionary<string, string>, IrcConnection>> commandFunctionsConn;
private static readonly char[] separator = new[] { ' ' };
static CommandHandlerFactory()
{
commandFunctions = new Dictionary<string, Action<Dictionary<string, string>>>(StringComparer.OrdinalIgnoreCase);
commandFunctionsConn = new Dictionary<string, Action<Dictionary<string, string>, 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<string, string>(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);
}
}
}
}

View File

@ -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<Type, CommandHandler?> commandAttributes = new Dictionary<Type, CommandHandler?>();
// doesnt support console yet
public override void Execute(Dictionary<string, string> 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<string, string> 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);
}
}
}

View File

@ -224,6 +224,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
//AddAllEquipment(account); //AddAllEquipment(account);
//AddAllItems(account); //AddAllItems(account);
//AddAllWeapons(account); //AddAllWeapons(account);
SetRaidSeason(account, 63);
return new AccountLoginSyncResponse() return new AccountLoginSyncResponse()
{ {
@ -327,6 +328,14 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
EquipmentDBs = [.. account.Equipment] EquipmentDBs = [.. account.Equipment]
}, },
ClanLoginResponse = new ClanLoginResponse()
{
AccountClanMemberDB = new()
{
AccountId = account.ServerId
}
},
FriendCode = "SCHALE", FriendCode = "SCHALE",
}; };
} }
@ -444,36 +453,8 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{ {
return new MiniGameMissionListResponse(); return new MiniGameMissionListResponse();
} }
// these will probably be commands // these will probably be commands
private void AddAllCharacters(AccountDB account)
{
var characterExcel = excelTableService.GetTable<CharacterExcelTable>().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) private void AddAllEquipment(AccountDB account)
{ {
var equipmentExcel = excelTableService.GetTable<EquipmentExcelTable>().UnPack().DataList; var equipmentExcel = excelTableService.GetTable<EquipmentExcelTable>().UnPack().DataList;
@ -519,7 +500,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
BoundCharacterServerId = x.ServerId, BoundCharacterServerId = x.ServerId,
IsLocked = false, IsLocked = false,
StarGrade = 5, StarGrade = 5,
Level = 70 Level = 200
}; };
}); });
@ -527,6 +508,11 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
context.SaveChanges(); context.SaveChanges();
} }
private void SetRaidSeason(AccountDB account, long seasonId)
{
account.RaidSeasonId = seasonId;
context.SaveChanges();
}
} }
} }

View File

@ -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 namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{ {
public class Clan : ProtocolHandlerBase 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)] [ProtocolHandler(Protocol.Clan_Check)]
public ResponsePacket CheckHandler(ClanCheckRequest req) public ResponsePacket CheckHandler(ClanCheckRequest req)

View File

@ -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();
}
}
}

View File

@ -1,5 +1,6 @@
using SCHALE.Common.Database; using SCHALE.Common.Database;
using SCHALE.Common.NetworkProtocol; using SCHALE.Common.NetworkProtocol;
using SCHALE.GameServer.Services;
using Serilog; using Serilog;
using System.Reflection; using System.Reflection;

View File

@ -1,4 +1,5 @@
using SCHALE.Common.Database; using SCHALE.Common.Database;
using SCHALE.Common.FlatData;
using SCHALE.Common.NetworkProtocol; using SCHALE.Common.NetworkProtocol;
using SCHALE.GameServer.Services; using SCHALE.GameServer.Services;
@ -20,9 +21,26 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
[ProtocolHandler(Protocol.Raid_Lobby)] [ProtocolHandler(Protocol.Raid_Lobby)]
public ResponsePacket LobbyHandler(RaidLobbyRequest req) public ResponsePacket LobbyHandler(RaidLobbyRequest req)
{ {
var account = sessionKeyService.GetAccount(req.SessionKey);
var raidSeasonInfo = excelTableService.GetTable<RaidSeasonManageExcelTable>().UnPack().DataList;
var targetSeason = raidSeasonInfo.FirstOrDefault(x => x.SeasonId == account.RaidSeasonId);
return new RaidLobbyResponse() 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)] [ProtocolHandler(Protocol.Raid_CreateBattle)]
public ResponsePacket CreateBattleHandller(RaidCreateBattleRequest req) public ResponsePacket CreateBattleHandller(RaidCreateBattleRequest req)
{ {
var account = sessionKeyService.GetAccount(req.SessionKey);
var raidStageTable = excelTableService.GetTable<RaidStageExcelTable>().UnPack().DataList;
var currentRaid = raidStageTable.FirstOrDefault(x => x.Id == req.RaidUniqueId);
return new RaidCreateBattleResponse() 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() return new RaidEndBattleResponse()
{ {
}; };
} }
} }

View File

@ -103,7 +103,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
Character = new() // hardcoded util proper db Character = new() // hardcoded util proper db
{ {
ServerId = req.AccountId, ServerId = req.AccountId,
UniqueId = 20007, UniqueId = id,
StarGrade = 3, StarGrade = 3,
Level = 1, Level = 1,
FavorRank = 1, FavorRank = 1,
@ -120,6 +120,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
return new ShopBuyGacha3Response() return new ShopBuyGacha3Response()
{ {
GachaResults = gachaResults,
UpdateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow,
GemBonusRemain = long.MaxValue, GemBonusRemain = long.MaxValue,
ConsumedItems = [], ConsumedItems = [],

View File

@ -6,6 +6,7 @@ using SCHALE.Common.Database;
using SCHALE.GameServer.Controllers.Api.ProtocolHandlers; using SCHALE.GameServer.Controllers.Api.ProtocolHandlers;
using SCHALE.GameServer.Services; using SCHALE.GameServer.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SCHALE.GameServer.Services.Irc;
namespace SCHALE.GameServer namespace SCHALE.GameServer
{ {
@ -53,6 +54,7 @@ namespace SCHALE.GameServer
builder.Services.AddProtocolHandlerFactory(); builder.Services.AddProtocolHandlerFactory();
builder.Services.AddMemorySessionKeyService(); builder.Services.AddMemorySessionKeyService();
builder.Services.AddExcelTableService(); builder.Services.AddExcelTableService();
builder.Services.AddIrcService();
// Add all Handler Groups // Add all Handler Groups
var handlerGroups = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(ProtocolHandlerBase))); var handlerGroups = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(ProtocolHandlerBase)));

View File

@ -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();
}
}
}

View File

@ -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<TcpClient, IrcConnection> clients = new ConcurrentDictionary<TcpClient, IrcConnection>(); // most irc commands doesn't even send over the player uid so imma just use TcpClient as key
private ConcurrentDictionary<string, List<long>> channels = new ConcurrentDictionary<string, List<long>>();
private readonly TcpListener listener;
private readonly ILogger<IrcService> logger;
private readonly SCHALEContext context;
private readonly ExcelTableService excelTableService;
public IrcServer(IPAddress host, int port, ILogger<IrcService> _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<IrcCommand>(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<string> 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<long>();
}
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<string> 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; }
}
}

View File

@ -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<IrcService> _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<IrcService>();
}
}
}

View File

@ -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<string> 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}";
}
}
}

View File

@ -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,
}
}