diff --git a/src/DiIiS-NA/D3-GameServer/CommandManager/CommandAttribute.cs b/src/DiIiS-NA/D3-GameServer/CommandManager/CommandAttribute.cs index 1aaff50..5ef8ca4 100644 --- a/src/DiIiS-NA/D3-GameServer/CommandManager/CommandAttribute.cs +++ b/src/DiIiS-NA/D3-GameServer/CommandManager/CommandAttribute.cs @@ -24,12 +24,18 @@ namespace DiIiS_NA.GameServer.CommandManager /// Minimum user level required to invoke the command. /// public Account.UserLevels MinUserLevel { get; private set; } + + /// + /// For InGame commands only. If true, the command will be available only in the game. + /// + public bool InGameOnly { get; } - public CommandGroupAttribute(string name, string help, Account.UserLevels minUserLevel = Account.UserLevels.Admin) + public CommandGroupAttribute(string name, string help, Account.UserLevels minUserLevel = Account.UserLevels.Admin, bool inGameOnly = false) { Name = name.ToLower(); Help = help; MinUserLevel = minUserLevel; + InGameOnly = inGameOnly; } } @@ -49,21 +55,27 @@ namespace DiIiS_NA.GameServer.CommandManager /// /// Minimum user level required to invoke the command. /// - public Account.UserLevels MinUserLevel { get; private set; } + public Account.UserLevels MinUserLevel { get; } + + /// + /// Whether the command is only for in-game command. + /// + public bool InGameOnly { get; } - public CommandAttribute(string command, string help, Account.UserLevels minUserLevel = Account.UserLevels.User) + public CommandAttribute(string command, string help, Account.UserLevels minUserLevel = Account.UserLevels.User, bool inGameOnly = false) { Name = command.ToLower(); Help = help; MinUserLevel = minUserLevel; + InGameOnly = inGameOnly; } } [AttributeUsage(AttributeTargets.Method)] public class DefaultCommand : CommandAttribute { - public DefaultCommand(Account.UserLevels minUserLevel = Account.UserLevels.User) - : base("", "", minUserLevel) + public DefaultCommand(Account.UserLevels minUserLevel = Account.UserLevels.User, bool inGameOnly = false) + : base("", "", minUserLevel, inGameOnly) { } } diff --git a/src/DiIiS-NA/D3-GameServer/CommandManager/CommandException.cs b/src/DiIiS-NA/D3-GameServer/CommandManager/CommandException.cs new file mode 100644 index 0000000..394641b --- /dev/null +++ b/src/DiIiS-NA/D3-GameServer/CommandManager/CommandException.cs @@ -0,0 +1,9 @@ +using System; + +namespace DiIiS_NA.GameServer.CommandManager; + +public class CommandException : Exception +{ + public CommandException(string message) : base(message) {} + public CommandException(string message, Exception ex) : base(message, ex) {} +} \ No newline at end of file diff --git a/src/DiIiS-NA/D3-GameServer/CommandManager/CommandGroup.cs b/src/DiIiS-NA/D3-GameServer/CommandManager/CommandGroup.cs index be6e049..28c3367 100644 --- a/src/DiIiS-NA/D3-GameServer/CommandManager/CommandGroup.cs +++ b/src/DiIiS-NA/D3-GameServer/CommandManager/CommandGroup.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; +using FluentNHibernate.Utils; namespace DiIiS_NA.GameServer.CommandManager { @@ -13,7 +14,7 @@ namespace DiIiS_NA.GameServer.CommandManager { private static readonly Logger Logger = LogManager.CreateLogger("CmdGrp"); - public CommandGroupAttribute Attributes { get; private set; } + private CommandGroupAttribute Attributes { get; set; } private readonly Dictionary _commands = new(); @@ -37,7 +38,7 @@ namespace DiIiS_NA.GameServer.CommandManager if (!_commands.ContainsKey(attribute)) _commands.Add(attribute, method); else - Logger.Fatal("Command '$[underline white]${0}$[/]$' already exists.", attribute.Name); + Logger.Fatal($"$[red]$Command$[/]$ '$[underline white]${attribute.Name.SafeAnsi()}$[/]$' already exists."); } } @@ -67,6 +68,8 @@ namespace DiIiS_NA.GameServer.CommandManager #else return "You don't have enough privileges to invoke that command."; #endif + if (invokerClient?.InGameClient == null && Attributes.InGameOnly) + return "You can only use this command in-game."; string[] @params = null; CommandAttribute target = null; @@ -77,7 +80,7 @@ namespace DiIiS_NA.GameServer.CommandManager @params = parameters.Split(' '); target = GetSubcommand(@params[0]) ?? GetDefaultSubcommand(); - if (target != GetDefaultSubcommand()) + if (!Equals(target, GetDefaultSubcommand())) @params = @params.Skip(1).ToArray(); } @@ -88,15 +91,28 @@ namespace DiIiS_NA.GameServer.CommandManager #else return "You don't have enough privileges to invoke that command."; #endif + if (invokerClient?.InGameClient == null && target.InGameOnly) + return "This command can only be invoked in-game."; - return (string)_commands[target].Invoke(this, new object[] { @params, invokerClient }); + try + { + return (string)_commands[target].Invoke(this, new object[] { @params, invokerClient }); + } + catch (CommandException commandException) + { + return commandException.Message; + } + catch (Exception ex) + { + Logger.ErrorException(ex, "Command Handling Error"); + return "An error occurred while executing the command."; + } } public string GetHelp(string command) { - foreach (var pair in _commands) + foreach (var pair in _commands.Where(pair => command == pair.Key.Name)) { - if (command != pair.Key.Name) continue; return pair.Key.Help; } @@ -114,14 +130,8 @@ namespace DiIiS_NA.GameServer.CommandManager return output.Substring(0, output.Length - 2) + "."; } - protected CommandAttribute GetDefaultSubcommand() - { - return _commands.Keys.First(); - } + protected CommandAttribute GetDefaultSubcommand() => _commands.Keys.First(); - protected CommandAttribute GetSubcommand(string name) - { - return _commands.Keys.FirstOrDefault(command => command.Name == name); - } + protected CommandAttribute GetSubcommand(string name) => _commands.Keys.FirstOrDefault(command => command.Name == name); } } diff --git a/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/QuestCommand.cs b/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/QuestCommand.cs index d0d90d7..5dec561 100644 --- a/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/QuestCommand.cs +++ b/src/DiIiS-NA/D3-GameServer/CommandManager/Commands/QuestCommand.cs @@ -1,25 +1,17 @@ using System; +using DiIiS_NA.LoginServer.AccountsSystem; using DiIiS_NA.LoginServer.Battle; +using FluentNHibernate.Utils; namespace DiIiS_NA.GameServer.CommandManager; [CommandGroup("quest", - "Retrieves information about quest states and manipulates quest progress.\n Usage: quest [triggers | trigger eventType eventValue | advance snoQuest]")] + "Retrieves information about quest states and manipulates quest progress.\n" + + "Usage: quest [triggers | trigger eventType eventValue | advance snoQuest]", + Account.UserLevels.Tester, inGameOnly: true)] public class QuestCommand : CommandGroup { - [DefaultCommand] - public string Quest(string[] @params, BattleClient invokerClient) - { - if (invokerClient == null) - return "You cannot invoke this command from console."; - - if (invokerClient.InGameClient == null) - return "You can only invoke this command while in-game."; - - return Info(@params, invokerClient); - } - - [Command("advance", "Advances a quest by a single step\n Usage: advance")] + [Command("advance", "Advances a quest by a single step\n Usage: advance", inGameOnly: true)] public string Advance(string[] @params, BattleClient invokerClient) { try @@ -33,7 +25,7 @@ public class QuestCommand : CommandGroup } } - [Command("sideadvance", "Advances a side-quest by a single step\n Usage: sideadvance")] + [Command("sideadvance", "Advances a side-quest by a single step\n Usage: sideadvance", inGameOnly: true)] public string SideAdvance(string[] @params, BattleClient invokerClient) { try @@ -69,7 +61,7 @@ public class QuestCommand : CommandGroup } } - [Command("timer", "Send broadcasted text message.\n Usage: public 'message'")] + [Command("timer", "Send broadcast text message.\n Usage: public 'message'")] public string Timer(string[] @params, BattleClient invokerClient) { if (@params == null) @@ -78,20 +70,42 @@ public class QuestCommand : CommandGroup if (@params.Length != 2) return "Invalid arguments. Type 'help text public' to get help."; - var eventId = int.Parse(@params[0]); - var duration = int.Parse(@params[1]); - - invokerClient.InGameClient.Game.QuestManager.LaunchQuestTimer(eventId, (float)duration, - new Action((q) => { })); + if (!int.TryParse(@params[0], out var eventId) || !int.TryParse(@params[1], out var duration)) + return "Invalid arguments. Type 'help text public' to get help."; + + invokerClient.InGameClient.Game.QuestManager.LaunchQuestTimer(eventId, (float)duration, (_) => { }); return "Message sent."; } + + [Command("set", "Advance to a specific quest step.\n Usage: quest to [questId] [step]")] + public string Set(string[] @params, BattleClient invokerClient) + { + if (@params == null) + return Fallback(); - [Command("info", "Retrieves information about quest states.\n Usage: info")] + if (@params.Length != 2) + return "Invalid arguments. Type 'help quest to' to get help."; + + if (!int.TryParse(@params[0], out var questId) || !int.TryParse(@params[1], out var step)) + return "Invalid arguments. Type 'help quest to' to get help."; + + try + { + invokerClient.InGameClient.Game.QuestManager.AdvanceTo(questId, step); + return $"Advancing to quest {questId} step {step}"; + } + catch (Exception e) + { + return e.Message; + } + } + + [Command("info", "Retrieves information about quest states.\n Usage: info", inGameOnly: true)] public string Info(string[] @params, BattleClient invokerClient) { - if (invokerClient?.InGameClient?.Game?.QuestManager is not {} questManager) - return "You can only invoke this command while in-game."; + if (invokerClient.InGameClient.Game?.QuestManager is not {} questManager) + return "No quests found."; var act = questManager.CurrentAct; var quest = questManager.Game.CurrentQuest; diff --git a/src/DiIiS-NA/D3-GameServer/CommandManager/InvalidParametersException.cs b/src/DiIiS-NA/D3-GameServer/CommandManager/InvalidParametersException.cs new file mode 100644 index 0000000..857831a --- /dev/null +++ b/src/DiIiS-NA/D3-GameServer/CommandManager/InvalidParametersException.cs @@ -0,0 +1,9 @@ +using System; + +namespace DiIiS_NA.GameServer.CommandManager; + +public class InvalidParametersException : CommandException +{ + public InvalidParametersException(string message) : base(message) {} + public InvalidParametersException(string message, Exception ex) : base(message, ex) {} +} \ No newline at end of file diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/ObjectsSystem/FixedMap.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/ObjectsSystem/FixedMap.cs index a924774..0b11061 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/ObjectsSystem/FixedMap.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/ObjectsSystem/FixedMap.cs @@ -31,12 +31,11 @@ namespace DiIiS_NA.GameServer.GSSystem.ObjectsSystem if (Contains(name)) { - _attributeMap[name] += action; - _logger.Warn($"Fixed attribute {name} already exists. Action will be added."); - return; + _attributeMap[name] = action; + _logger.Warn($"Overwrite attribute {name}"); } - - _attributeMap.Add(name, action); + else + _attributeMap.Add(name, action); } public void Remove(FixedAttribute name) diff --git a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs index 7da5bb7..423ea43 100644 --- a/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs +++ b/src/DiIiS-NA/D3-GameServer/GSSystem/PlayerSystem/Player.cs @@ -3203,14 +3203,14 @@ public class Player : Actor, IMessageConsumer, IUpdateable NecroSkeletons.Clear(); } - while (NecroSkeletons.Count < 7) + while (NecroSkeletons.Count < GameServerConfig.Instance.NecroSkeletonCount) { - var Skeleton = new NecromancerSkeleton_A(World, ActorSno._p6_necro_commandskeletons_a, this); - Skeleton.Brain.DeActivate(); - Skeleton.Scale = 1.2f; + var necroSkeleton = new NecromancerSkeleton_A(World, ActorSno._p6_necro_commandskeletons_a, this); + necroSkeleton.Brain.DeActivate(); + necroSkeleton.Scale = 1.2f; - Skeleton.EnterWorld(PowerContext.RandomDirection(Position, 3f, 8f)); - NecroSkeletons.Add(Skeleton); + necroSkeleton.EnterWorld(PowerContext.RandomDirection(Position, 3f, 8f)); + NecroSkeletons.Add(necroSkeleton); /*this.InGameClient.SendMessage(new PetMessage() { Owner = this.PlayerIndex, @@ -3219,7 +3219,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable Type = 70, }); //*/ - Skeleton.Brain.Activate(); + necroSkeleton.Brain.Activate(); } } else @@ -3540,7 +3540,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable world.Reveal(this); Unreveal(this); - if (Math.Abs(_CurrentHPValue - (-1f)) < Globals.FLOAT_TOLERANCE) + if (Math.Abs(_CurrentHPValue - -1f) < Globals.FLOAT_TOLERANCE) DefaultQueryProximityRadius = 60; InGameClient.SendMessage(new EnterWorldMessage() @@ -3571,7 +3571,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable break; } - if (Math.Abs(_CurrentHPValue - (-1f)) < Globals.FLOAT_TOLERANCE) + if (Math.Abs(_CurrentHPValue - -1f) < Globals.FLOAT_TOLERANCE) AddPercentageHP(100); DefaultQueryProximityRadius = 100; @@ -3741,7 +3741,7 @@ public class Player : Actor, IMessageConsumer, IUpdateable AllBuffs.Clear(); BetweenWorlds = false; - if (Math.Abs(_CurrentHPValue - (-1)) > Globals.FLOAT_TOLERANCE) + if (Math.Abs(_CurrentHPValue - -1) > Globals.FLOAT_TOLERANCE) { Attributes[GameAttributes.Hitpoints_Cur] = _CurrentHPValue; Attributes[GameAttributes.Resource_Cur, (int)Toon.HeroTable.PrimaryResource + 1] = _CurrentResourceValue; diff --git a/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs b/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs index 6f71361..9597611 100644 --- a/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs +++ b/src/DiIiS-NA/D3-GameServer/GameServerConfig.cs @@ -298,12 +298,21 @@ namespace DiIiS_NA.GameServer set => Set(nameof(NephalemRiftOrbsChance), value); } + /// + /// Forces the game to reveal all the map. + /// public bool ForceMinimapVisibility { get => GetBoolean(nameof(ForceMinimapVisibility), false); set => Set(nameof(ForceMinimapVisibility), value); } + public int NecroSkeletonCount + { + get => GetInt(nameof(NecroSkeletonCount), 7); + set => Set(nameof(NecroSkeletonCount), value); + } + #endregion public static GameServerConfig Instance { get; } = new();