diff --git a/SCHALE.GameServer/Commands/CharacterCommand.cs b/SCHALE.GameServer/Commands/CharacterCommand.cs index 3d95342..a884fc5 100644 --- a/SCHALE.GameServer/Commands/CharacterCommand.cs +++ b/SCHALE.GameServer/Commands/CharacterCommand.cs @@ -2,6 +2,7 @@ using SCHALE.Common.Database; using SCHALE.Common.Database.ModelExtensions; using SCHALE.Common.FlatData; +using SCHALE.Common.Utils; using SCHALE.GameServer.Controllers.Api.ProtocolHandlers; using SCHALE.GameServer.Services; using SCHALE.GameServer.Services.Irc; @@ -28,13 +29,13 @@ namespace SCHALE.GameServer.Commands case "add": if (Target == "all") { - AddAllCharacters(connection); + InventoryUtils.AddAllCharacters(connection); connection.SendChatMessage("All Characters Added!"); } else if (uint.TryParse(Target, out uint characterId)) { - var newChar = CreateMaxCharacterFromId(characterId); + var newChar = InventoryUtils.CreateMaxCharacterFromId(characterId); if (characterDB.Any(x => x.UniqueId == newChar.UniqueId)) { @@ -51,14 +52,13 @@ namespace SCHALE.GameServer.Commands } break; + case "clear": - var defaultCharacters = connection.ExcelTableService.GetTable().UnPack().DataList.Select(x => x.CharacterId).ToList(); + InventoryUtils.RemoveAllCharacters(connection); - var removed = characterDB.Where(x => x.AccountServerId == connection.AccountServerId && !defaultCharacters.Contains(x.UniqueId)); - - characterDB.RemoveRange(removed); - connection.SendChatMessage($"Removed {removed.Count()} characters!"); + connection.SendChatMessage($"Removed all characters!"); break; + default: connection.SendChatMessage($"Usage: /character unlock="); throw new InvalidOperationException("Invalid operation!"); @@ -66,42 +66,5 @@ namespace SCHALE.GameServer.Commands connection.Context.SaveChanges(); } - - 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 CreateMaxCharacterFromId(x.Id); - }).ToList(); - - account.AddCharacters(context, [.. allCharacters]); - connection.Context.SaveChanges(); - } - - private CharacterDB CreateMaxCharacterFromId(long characterId) - { - return new CharacterDB() - { - UniqueId = characterId, - StarGrade = 5, - 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] - }; - } - } } diff --git a/SCHALE.GameServer/Commands/HelpCommand.cs b/SCHALE.GameServer/Commands/HelpCommand.cs index fec0a6d..8b87839 100644 --- a/SCHALE.GameServer/Commands/HelpCommand.cs +++ b/SCHALE.GameServer/Commands/HelpCommand.cs @@ -1,19 +1,55 @@ -using SCHALE.Common.Database; +using Microsoft.Extensions.FileSystemGlobbing.Internal; +using SCHALE.Common.Database; using SCHALE.GameServer.Services.Irc; using Serilog; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; +using static System.Net.Mime.MediaTypeNames; namespace SCHALE.GameServer.Commands { - [CommandHandler("help", "Show this help.", "/help")] + [CommandHandler("help", "Show this help.", "/help [command]")] internal class HelpCommand : Command { + [Argument(0, @"^[a-zA-Z]+$", "The command to display the help message", ArgumentFlags.IgnoreCase | ArgumentFlags.Optional)] + public string Command { get; set; } = string.Empty; + public HelpCommand(IrcConnection connection, string[] args, bool validate = true) : base(connection, args, validate) { } public override void Execute() - { // can't use newline, not gonna print args help for now + { + if (Command != string.Empty) + { + if (CommandFactory.commands.ContainsKey(Command)) + { + var cmdAtr = (CommandHandlerAttribute?)Attribute.GetCustomAttribute(CommandFactory.commands[Command], typeof(CommandHandlerAttribute)); + Command? cmd = CommandFactory.CreateCommand(Command, connection, args, false); + + if (cmd is not null) + { + connection.SendChatMessage($"{Command} - {cmdAtr.Hint} (Usage: {cmdAtr.Usage})"); + + List argsProperties = cmd.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.GetCustomAttribute(typeof(ArgumentAttribute)) is not null).ToList(); + + foreach (var argProp in argsProperties) + { + ArgumentAttribute attr = (ArgumentAttribute)argProp.GetCustomAttribute(typeof(ArgumentAttribute))!; + var arg = Regex.Replace(attr.Pattern.ToString(), @"[\^\$\+]", ""); + + connection.SendChatMessage($"<{arg}> - {attr.Description}"); + } + } + } else + { + throw new ArgumentException("Invalid Argument."); + } + + return; + } + foreach (var command in CommandFactory.commands.Keys) { var cmdAtr = (CommandHandlerAttribute?)Attribute.GetCustomAttribute(CommandFactory.commands[command], typeof(CommandHandlerAttribute)); diff --git a/SCHALE.GameServer/Commands/InventoryCommand.cs b/SCHALE.GameServer/Commands/InventoryCommand.cs new file mode 100644 index 0000000..04de0c8 --- /dev/null +++ b/SCHALE.GameServer/Commands/InventoryCommand.cs @@ -0,0 +1,41 @@ +using SCHALE.Common.Database; +using SCHALE.Common.FlatData; +using SCHALE.Common.Utils; +using SCHALE.GameServer.Services; +using SCHALE.GameServer.Services.Irc; + +namespace SCHALE.GameServer.Commands +{ + [CommandHandler("inventory", "Command to manage inventory (chars, weapons, equipment, items)", "/inventory ")] + internal class InventoryCommand : Command + { + public InventoryCommand(IrcConnection connection, string[] args, bool validate = true) : base(connection, args, validate) { } + + [Argument(0, @"^addall$|^clearall$", "The operation selected (addall, clearall)", ArgumentFlags.IgnoreCase)] + public string Op { get; set; } = string.Empty; + + public override void Execute() + { + var context = connection.Context; + + switch (Op.ToLower()) + { + case "addall": + InventoryUtils.AddAllCharacters(connection); + InventoryUtils.AddAllWeapons(connection); + InventoryUtils.AddAllEquipment(connection); + InventoryUtils.AddAllItems(connection); + break; + + case "clearall": + InventoryUtils.RemoveAllCharacters(connection); + context.Weapons.RemoveRange(context.Weapons.Where(x => x.AccountServerId == connection.AccountServerId)); + context.Equipment.RemoveRange(context.Equipment.Where(x => x.AccountServerId == connection.AccountServerId)); + context.Items.RemoveRange(context.Items.Where(x => x.AccountServerId == connection.AccountServerId)); + break; + } + + context.SaveChanges(); + } + } +} diff --git a/SCHALE.GameServer/Commands/SetAccountCommand.cs b/SCHALE.GameServer/Commands/SetAccountCommand.cs new file mode 100644 index 0000000..82e4fc2 --- /dev/null +++ b/SCHALE.GameServer/Commands/SetAccountCommand.cs @@ -0,0 +1,47 @@ +using SCHALE.Common.Database; +using SCHALE.GameServer.Services.Irc; +using System.ComponentModel; +using System.Reflection; + +namespace SCHALE.GameServer.Commands +{ + [CommandHandler("setaccount", "Command to change player's account data", "/setaccount <|Level|Nickname|RaidSeasonId|Property|...> ")] + internal class SetAccountCommand : Command + { + public SetAccountCommand(IrcConnection connection, string[] args, bool validate = true) : base(connection, args, validate) { } + + [Argument(0, @"^[a-zA-Z]+$", "The Account Property you want to change.", ArgumentFlags.IgnoreCase)] + public string Property { get; set; } = string.Empty; + + [Argument(1, @"", "The value you want to change it to, must match the property type.", ArgumentFlags.IgnoreCase)] + public string Value { get; set; } = string.Empty; + + public override void Execute() + { + PropertyInfo? targetProperty = typeof(AccountDB).GetProperty(Property); + + if (targetProperty != null) + { + TypeConverter converter = TypeDescriptor.GetConverter(targetProperty.PropertyType); + + if (converter != null && converter.CanConvertFrom(typeof(string))) + { + try + { + object targetValue = converter.ConvertFromString(Value); + + targetProperty.SetValue(connection.Account, targetValue); + } catch (Exception) + { + throw new ArgumentException("Invalid Value"); + } + } + } else + { + throw new ArgumentException("Invalid Player Property!"); + } + + + } + } +} diff --git a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Account.cs b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Account.cs index 5764bcd..352edb2 100644 --- a/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Account.cs +++ b/SCHALE.GameServer/Controllers/Api/ProtocolHandlers/Account.cs @@ -218,13 +218,6 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers { var account = sessionKeyService.GetAccount(req.SessionKey); - // add everything manually - //AddAllCharacters(account); - //AddAllEquipment(account); - //AddAllItems(account); - //AddAllWeapons(account); - SetRaidSeason(account, 63); - return new AccountLoginSyncResponse() { AccountCurrencySyncResponse = new AccountCurrencySyncResponse() @@ -454,59 +447,6 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers } // these will probably be commands - private void AddAllEquipment(AccountDB account) - { - var equipmentExcel = excelTableService.GetTable().UnPack().DataList; - var allEquipment = equipmentExcel.Select(x => - { - return new EquipmentDB() - { - UniqueId = x.Id, - Level = 1, - StackCount = 100, // ~ 90,000 cap, auto converted if over - }; - }).ToList(); - - account.AddEquipment(context, [.. allEquipment]); - context.SaveChanges(); - } - - private void AddAllItems(AccountDB account) - { - var itemExcel = excelTableService.GetTable().UnPack().DataList; - var allItems = itemExcel.Select(x => - { - return new ItemDB() - { - IsNew = true, - UniqueId = x.Id, - StackCount = 5555, - }; - }).ToList(); - - account.AddItems(context, [.. allItems]); - context.SaveChanges(); - } - - private void AddAllWeapons(AccountDB account) - { - // only for current characters - var allWeapons = account.Characters.Select(x => - { - return new WeaponDB() - { - UniqueId = x.UniqueId, - BoundCharacterServerId = x.ServerId, - IsLocked = false, - StarGrade = 5, - Level = 200 - }; - }); - - account.AddWeapons(context, [.. allWeapons]); - context.SaveChanges(); - } - private void SetRaidSeason(AccountDB account, long seasonId) { account.RaidSeasonId = seasonId; diff --git a/SCHALE.GameServer/Services/Irc/IrcConnection.cs b/SCHALE.GameServer/Services/Irc/IrcConnection.cs index 2b44dfc..b64768e 100644 --- a/SCHALE.GameServer/Services/Irc/IrcConnection.cs +++ b/SCHALE.GameServer/Services/Irc/IrcConnection.cs @@ -22,10 +22,15 @@ namespace SCHALE.GameServer.Services.Irc public void SendChatMessage(string text) { - SendChatMessage(text, "Shiroko", 10010, IrcMessageType.Chat); + SendChatMessage(text, "Shiroko", 10010, 0, IrcMessageType.Chat); } - public void SendChatMessage(string text, string nickname, long pfpCharacterId, IrcMessageType messageType) + public void SendEmote(long stickerId) + { + SendChatMessage("", "Shiroko", 10010, stickerId, IrcMessageType.Sticker); + } + + public void SendChatMessage(string text, string nickname, long pfpCharacterId, long stickerId, IrcMessageType messageType) { var reply = new Reply() { @@ -38,11 +43,11 @@ namespace SCHALE.GameServer.Services.Irc AccountNickname = nickname, Text = text, SendTicks = DateTimeOffset.Now.Ticks, + StickerId = stickerId, }, typeof(IrcMessage)), }.ToString(); StreamWriter.WriteLine(reply); - StreamWriter.Flush(); } } } diff --git a/SCHALE.GameServer/Services/Irc/IrcServer.cs b/SCHALE.GameServer/Services/Irc/IrcServer.cs index c6906a0..1a88ec4 100644 --- a/SCHALE.GameServer/Services/Irc/IrcServer.cs +++ b/SCHALE.GameServer/Services/Irc/IrcServer.cs @@ -129,12 +129,16 @@ namespace SCHALE.GameServer.Services.Irc channels[channel] = new List(); } - var userClient = clients[client]; + var connection = clients[client]; - channels[channel].Add(userClient.AccountServerId); - userClient.CurrentChannel = channel; + channels[channel].Add(connection.AccountServerId); + connection.CurrentChannel = channel; - logger.LogDebug($"User {userClient.AccountServerId} joined {channel}"); + logger.LogDebug($"User {connection.AccountServerId} joined {channel}"); + + // custom welcome + connection.SendChatMessage("Welcome to SCHALE!"); + connection.SendEmote(2); } private async Task HandlePrivMsg(string parameters, TcpClient client) // player sends msg @@ -215,7 +219,7 @@ namespace SCHALE.GameServer.Services.Irc public string AccountNickname { get; set; } [JsonPropertyName("StickerId")] - public long StickerId { get; } + public long StickerId { get; set; } [JsonPropertyName("Text")] public string Text { get; set; } diff --git a/SCHALE.GameServer/Utils/InventoryUtils.cs b/SCHALE.GameServer/Utils/InventoryUtils.cs new file mode 100644 index 0000000..dfb3416 --- /dev/null +++ b/SCHALE.GameServer/Utils/InventoryUtils.cs @@ -0,0 +1,115 @@ +using SCHALE.Common.Database; +using SCHALE.Common.Database.ModelExtensions; +using SCHALE.Common.FlatData; +using SCHALE.GameServer.Services; +using SCHALE.GameServer.Services.Irc; +using System; +using System.Data; + +namespace SCHALE.Common.Utils +{ + public static class InventoryUtils + { + public static 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 CreateMaxCharacterFromId(x.Id); + }).ToList(); + + account.AddCharacters(context, [.. allCharacters]); + context.SaveChanges(); + } + + public static void AddAllEquipment(IrcConnection connection) + { + var equipmentExcel = connection.ExcelTableService.GetTable().UnPack().DataList; + var allEquipment = equipmentExcel.Select(x => + { + return new EquipmentDB() + { + UniqueId = x.Id, + Level = 1, + StackCount = 77777, // ~ 90,000 cap, auto converted if over + }; + }).ToList(); + + connection.Account.AddEquipment(connection.Context, [.. allEquipment]); + connection.Context.SaveChanges(); + } + + public static void AddAllItems(IrcConnection connection) + { + var itemExcel = connection.ExcelTableService.GetTable().UnPack().DataList; + var allItems = itemExcel.Select(x => + { + return new ItemDB() + { + IsNew = true, + UniqueId = x.Id, + StackCount = 5555, + }; + }).ToList(); + + connection.Account.AddItems(connection.Context, [.. allItems]); + connection.Context.SaveChanges(); + } + + public static void AddAllWeapons(IrcConnection connection) + { + var account = connection.Account; + var context = connection.Context; + + // only for current characters + var allWeapons = account.Characters.Select(x => + { + return new WeaponDB() + { + UniqueId = x.UniqueId, + BoundCharacterServerId = x.ServerId, + IsLocked = false, + StarGrade = 5, + Level = 200 + }; + }); + + account.AddWeapons(context, [.. allWeapons]); + context.SaveChanges(); + } + + public static void RemoveAllCharacters(IrcConnection connection) // removing default characters breaks game + { + var characterDB = connection.Context.Characters; + var defaultCharacters = connection.ExcelTableService.GetTable().UnPack().DataList.Select(x => x.CharacterId).ToList(); + + var removed = characterDB.Where(x => x.AccountServerId == connection.AccountServerId && !defaultCharacters.Contains(x.UniqueId)); + + characterDB.RemoveRange(removed); + } + + public static CharacterDB CreateMaxCharacterFromId(long characterId) + { + return new CharacterDB() + { + UniqueId = characterId, + StarGrade = 5, + Level = 200, + Exp = 0, + PublicSkillLevel = 10, + ExSkillLevel = 5, + PassiveSkillLevel = 10, + ExtraPassiveSkillLevel = 10, + LeaderSkillLevel = 1, + FavorRank = 500, + IsNew = true, + IsLocked = true, + PotentialStats = { { 1, 25 }, { 2, 25 }, { 3, 25 } }, + EquipmentServerIds = [0, 0, 0] + }; + } + } +}