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