diff --git a/Source/Game/Chat/CommandArgs.cs b/Source/Game/Chat/CommandArgs.cs
index fe202dfb1..04ea1c562 100644
--- a/Source/Game/Chat/CommandArgs.cs
+++ b/Source/Game/Chat/CommandArgs.cs
@@ -15,6 +15,10 @@
* along with this program. If not, see .
*/
+using Framework.Constants;
+using Game.DataStorage;
+using Game.Entities;
+using Game.Spells;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -24,375 +28,344 @@ namespace Game.Chat
{
class CommandArgs
{
- static Dictionary> Parsers = new()
+ public static ChatCommandResult ConsumeFromOffset(dynamic[] tuple, int offset, ParameterInfo[] parameterInfos, CommandHandler handler, string args)
{
- { typeof(sbyte), args => args.NextSByte() },
- { typeof(short), args => args.NextInt16() },
- { typeof(int), args => args.NextInt32() },
- { typeof(long), args => args.NextInt64() },
- { typeof(byte), args => args.NextByte() },
- { typeof(ushort), args => args.NextUInt16() },
- { typeof(uint), args => args.NextUInt32() },
- { typeof(ulong), args => args.NextUInt64() },
- { typeof(float), args => args.NextSingle() },
- { typeof(string), args => args.NextString() },
- { typeof(bool), args => args.NextBoolean() },
- { typeof(PlayerIdentifier), args => PlayerIdentifier.ParseFromString(args) },
- { typeof(AccountIdentifier), args => AccountIdentifier.ParseFromString(args) },
- { typeof(Tail), args => Tail.ParseFromString(args) },
- };
-
- public static bool Parse(out dynamic[] parsedArgs, CommandHandler handler, ParameterInfo[] parameterInfos, string tailCmd)
- {
- parsedArgs = new dynamic[parameterInfos.Length];
- parsedArgs[0] = handler;
-
- CommandArguments args = new CommandArguments(tailCmd);
-
- for (var i = 1; i < parameterInfos.Length; i++)
- {
- if (!ParseArgument(out dynamic value, parameterInfos[i], args))
- return false;
-
- parsedArgs[i] = value;
- }
-
- return true;
+ if (offset < tuple.Length)
+ return TryConsumeTo(tuple, offset, parameterInfos, handler, args);
+ else if (!args.IsEmpty()) /* the entire string must be consumed */
+ return default;
+ else
+ return new ChatCommandResult(args);
}
- static bool ParseArgument(out dynamic value, ParameterInfo parameterInfo, CommandArguments args)
+ static ChatCommandResult TryConsumeTo(dynamic[] tuple, int offset, ParameterInfo[] parameterInfos, CommandHandler handler, string args)
{
- var parameterType = parameterInfo.ParameterType;
-
- if (Hyperlink.TryParse(out value, parameterType, args))
- return true;
-
- bool isOptional = false;
-
- var optionalArgAttribute = parameterInfo.GetCustomAttribute(true);
- if (optionalArgAttribute != null)
- isOptional = true;
-
- if (parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(Nullable<>))
+ var optionalArgAttribute = parameterInfos[offset].GetCustomAttribute(true);
+ if (optionalArgAttribute != null || parameterInfos[offset].ParameterType.IsGenericType && parameterInfos[offset].ParameterType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
- parameterType = Nullable.GetUnderlyingType(parameterType);
- isOptional = true;
- }
+ // try with the argument
+ Type myArg = Nullable.GetUnderlyingType(parameterInfos[offset].ParameterType) ?? parameterInfos[offset].ParameterType;
- if (parameterType.IsEnum)
- parameterType = parameterType.GetEnumUnderlyingType();
+ ChatCommandResult result1 = TryConsume(out tuple[offset], myArg, handler, args);
+ if (result1.IsSuccessful())
+ if ((result1 = ConsumeFromOffset(tuple, offset + 1, parameterInfos, handler, result1)).IsSuccessful())
+ return result1;
- var pos = args.GetCurrentPosition();
-
- var result = Parsers[parameterType](args);
- if (result != ParseResult.Ok)
- {
- if (!isOptional)
- return false;
+ // try again omitting the argument
+ tuple[offset] = default;
+ ChatCommandResult result2 = ConsumeFromOffset(tuple, offset + 1, parameterInfos, handler, args);
+ if (result2.IsSuccessful())
+ return result2;
+ if (result1.HasErrorMessage() && result2.HasErrorMessage())
+ {
+ return ChatCommandResult.FromErrorMessage($"{handler.GetCypherString(CypherStrings.CmdparserEither)} \"{result2.GetErrorMessage()}\"\n{handler.GetCypherString(CypherStrings.CmdparserOr)} \"{result1.GetErrorMessage()}\"");
+ }
+ else if (result1.HasErrorMessage())
+ return result1;
else
- args.SetCurrentPosition(pos);
+ return result2;
+ }
+ else
+ {
+ ChatCommandResult next;
+
+ var variantArgAttribute = parameterInfos[offset].GetCustomAttribute(true);
+ if (variantArgAttribute != null)
+ next = TryConsumeVariant(out tuple[offset], variantArgAttribute.Types, handler, args);
+ else
+ next = TryConsume(out tuple[offset], parameterInfos[offset].ParameterType, handler, args);
+
+ if (next.IsSuccessful())
+ return ConsumeFromOffset(tuple, offset + 1, parameterInfos, handler, next);
+ else
+ return next;
+ }
+ }
+
+ public static ChatCommandResult TryConsume(out dynamic val, Type type, CommandHandler handler, string args)
+ {
+ val = default;
+
+ var hyperlinkResult = Hyperlink.TryParse(out val, type, handler, args);
+ if (hyperlinkResult.IsSuccessful())
+ return hyperlinkResult;
+
+ if (type.IsEnum)
+ type = type.GetEnumUnderlyingType();
+
+ var (token, tail) = Tokenize(args);
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.SByte:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (sbyte.TryParse(token, out sbyte tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.Int16:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (short.TryParse(token, out short tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.Int32:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (int.TryParse(token, out int tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.Int64:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (long.TryParse(token, out long tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.Byte:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (byte.TryParse(token, out byte tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.UInt16:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (ushort.TryParse(token, out ushort tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.UInt32:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (uint.TryParse(token, out uint tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.UInt64:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (ulong.TryParse(token, out ulong tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.Single:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ if (float.TryParse(token, out float tempValue))
+ val = tempValue;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ if (!float.IsFinite(val))
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserStringValueInvalid, token, Type.GetTypeCode(type)));
+
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.String:
+ {
+ if (token.IsEmpty())
+ return default;
+
+ val = token;
+ return new ChatCommandResult(tail);
+ }
+ case TypeCode.Object:
+ {
+ switch (type.Name)
+ {
+ case nameof(Tail):
+ val = new Tail();
+ return val.TryConsume(handler, args);
+ case nameof(PlayerIdentifier):
+ val = new PlayerIdentifier();
+ return val.TryConsume(handler, args);
+ case nameof(AccountIdentifier):
+ val = new AccountIdentifier();
+ return val.TryConsume(handler, args);
+ case nameof(AchievementRecord):
+ {
+ ChatCommandResult result = TryConsume(out dynamic tempVal, typeof(uint), handler, args);
+ if (!result.IsSuccessful() || (val = CliDB.AchievementStorage.LookupByKey((uint)tempVal)) != null)
+ return result;
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserAchievementNoExist, tempVal));
+ }
+ case nameof(CurrencyTypesRecord):
+ {
+ ChatCommandResult result = TryConsume(out dynamic tempVal, typeof(uint), handler, args);
+ if (!result.IsSuccessful() || (val = CliDB.CurrencyTypesStorage.LookupByKey((uint)tempVal)) != null)
+ return result;
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserCurrencyNoExist, tempVal));
+ }
+ case nameof(GameTele):
+ {
+ ChatCommandResult result = TryConsume(out dynamic tempVal, typeof(uint), handler, args);
+ if (!result.IsSuccessful())
+ result = TryConsume(out tempVal, typeof(string), handler, args);
+
+ if (!result.IsSuccessful() || (val = Global.ObjectMgr.GetGameTele(tempVal)) != null)
+ return result;
+ if (tempVal is uint)
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserGameTeleIdNoExist, tempVal));
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserGameTeleNoExist, tempVal));
+ }
+ case nameof(ItemTemplate):
+ {
+ ChatCommandResult result = TryConsume(out dynamic tempVal, typeof(uint), handler, args);
+ if (!result.IsSuccessful() || (val = Global.ObjectMgr.GetItemTemplate(tempVal)))
+ return result;
+
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserItemNoExist, tempVal));
+ }
+ case nameof(SpellInfo):
+ {
+ ChatCommandResult result = TryConsume(out dynamic tempVal, typeof(uint), handler, args);
+ if (!result.IsSuccessful() || (val = Global.SpellMgr.GetSpellInfo(tempVal, Difficulty.None)) != null)
+ return result;
+
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserSpellNoExist, tempVal));
+ }
+ }
+ break;
+ }
}
- value = result.GetValue();
- return true;
+ return default;
+ }
+
+ public static ChatCommandResult TryConsumeVariant(out dynamic val, Type[] types, CommandHandler handler, string args)
+ {
+ ChatCommandResult result = TryAtIndex(out val, types, 0, handler, args);
+ if (result.HasErrorMessage() && (result.GetErrorMessage().IndexOf('\n') != -1))
+ return ChatCommandResult.FromErrorMessage($"{handler.GetCypherString(CypherStrings.CmdparserEither)} {result.GetErrorMessage()}");
+ return result;
+ }
+
+ static ChatCommandResult TryAtIndex(out dynamic val, Type[] types, int index, CommandHandler handler, string args)
+ {
+ val = default;
+
+ if (index < types.Length)
+ {
+ ChatCommandResult thisResult = TryConsume(out val, types[index], handler, args);
+ if (thisResult.IsSuccessful())
+ return thisResult;
+ else
+ {
+ ChatCommandResult nestedResult = TryAtIndex(out val, types, index+1, handler, args);
+ if (nestedResult.IsSuccessful() || !thisResult.HasErrorMessage())
+ return nestedResult;
+ if (!nestedResult.HasErrorMessage())
+ return thisResult;
+ if (nestedResult.GetErrorMessage().StartsWith("\""))
+ return ChatCommandResult.FromErrorMessage($"\"{thisResult.GetErrorMessage()}\"\n{handler.GetCypherString(CypherStrings.CmdparserOr)} {nestedResult.GetErrorMessage()}");
+ else
+ return ChatCommandResult.FromErrorMessage($"\"{thisResult.GetErrorMessage()}\"\n{handler.GetCypherString(CypherStrings.CmdparserOr)} \"{nestedResult.GetErrorMessage()}\"");
+ }
+ }
+ else
+ return default;
+ }
+
+ public static (string token, string tail) Tokenize(string args)
+ {
+ (string token, string tail) result = new ("", "");
+ int delimPos = args.IndexOf(' ');
+ if (delimPos != -1)
+ {
+ result.token = args.Substring(0, delimPos);
+ int tailPos = args.FindFirstNotOf(" ", delimPos);
+ if (tailPos != -1)
+ result.tail = args.Substring(tailPos);
+ }
+ else
+ result.token = args;
+
+ return result;
}
}
- public enum ParseResult
+ public struct ChatCommandResult
{
- Ok,
- Error,
- EndOfString
- }
-
- public struct ParseStringResult
- {
- ParseResult result;
+ bool result;
dynamic value;
+ string errorMessage;
- public ParseStringResult(ParseResult _result, dynamic _value = default)
+ public ChatCommandResult(string _value = "")
{
- result = _result;
+ result = true;
value = _value;
+ errorMessage = null;
}
- public dynamic GetValue() { return value; }
+ public bool IsSuccessful() { return result; }
- public static implicit operator ParseResult(ParseStringResult stringResult)
+ public bool HasErrorMessage() { return !errorMessage.IsEmpty(); }
+
+ public string GetErrorMessage() { return errorMessage; }
+
+ public void SetErrorMessage(string _value)
{
- return stringResult.result;
+ result = false;
+ errorMessage = _value;
}
- public static implicit operator string(ParseStringResult stringResult)
+ public static ChatCommandResult FromErrorMessage(string str)
+ {
+ var result = new ChatCommandResult();
+ result.SetErrorMessage(str);
+ return result;
+ }
+
+ public static implicit operator string(ChatCommandResult stringResult)
{
return stringResult.value;
}
}
-
- public sealed class CommandArguments
- {
- static ParseStringResult ErrorResult = new(ParseResult.Error);
- static ParseStringResult EndOfStringResult = new(ParseResult.EndOfString);
-
- public CommandArguments(string args)
- {
- if (!args.IsEmpty())
- activestring = args.TrimStart(' ');
- activeposition = -1;
- }
-
- public CommandArguments(CommandArguments args)
- {
- activestring = args.activestring;
- activeposition = args.activeposition;
- Current = args.Current;
- }
-
- public bool Empty()
- {
- return activestring.IsEmpty();
- }
-
- public void MoveToNextChar(char c)
- {
- for (var i = activeposition; i < activestring.Length; ++i)
- if (activestring[i] == c)
- break;
- }
-
- public ParseStringResult NextString(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return new ParseStringResult(ParseResult.EndOfString, "");
-
- if (Current.Any(c => char.IsLetter(c)))
- return new ParseStringResult(ParseResult.Ok, Current);
-
- return new ParseStringResult(ParseResult.Error, "");
- }
-
- public ParseStringResult NextBoolean(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- bool value;
- if (bool.TryParse(Current, out value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- if ((Current == "1") || Current.Equals("y", StringComparison.OrdinalIgnoreCase) || Current.Equals("on", StringComparison.OrdinalIgnoreCase) || Current.Equals("yes", StringComparison.OrdinalIgnoreCase) || Current.Equals("true", StringComparison.OrdinalIgnoreCase))
- return new ParseStringResult(ParseResult.Ok, true);
- if ((Current == "0") || Current.Equals("n", StringComparison.OrdinalIgnoreCase) || Current.Equals("off", StringComparison.OrdinalIgnoreCase) || Current.Equals("no", StringComparison.OrdinalIgnoreCase) || Current.Equals("false", StringComparison.OrdinalIgnoreCase))
- return new ParseStringResult(ParseResult.Ok, false);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextChar(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (char.TryParse(Current, out char value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextByte(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (byte.TryParse(Current, out byte value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextSByte(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (sbyte.TryParse(Current, out sbyte value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextUInt16(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (ushort.TryParse(Current, out ushort value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextInt16(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (short.TryParse(Current, out short value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextUInt32(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (uint.TryParse(Current, out uint value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextInt32(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (int.TryParse(Current, out int value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextUInt64(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (ulong.TryParse(Current, out ulong value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextInt64(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (long.TryParse(Current, out long value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextSingle(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (float.TryParse(Current, out float value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextDouble(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (double.TryParse(Current, out double value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public ParseStringResult NextDecimal(string delimiters = " ")
- {
- if (!MoveNext(delimiters))
- return EndOfStringResult;
-
- if (decimal.TryParse(Current, out decimal value))
- return new ParseStringResult(ParseResult.Ok, value);
-
- return ErrorResult;
- }
-
- public void AlignToNextChar()
- {
- while (activeposition < activestring.Length && activestring[activeposition] != ' ')
- activeposition++;
- }
-
- public char this[int index]
- {
- get { return activestring[index]; }
- }
-
- public string GetString()
- {
- return activestring;
- }
-
- public void Reset()
- {
- activeposition = -1;
- Current = null;
- }
-
- public bool IsAtEnd()
- {
- return activestring.IsEmpty() || activeposition == activestring.Length;
- }
-
- public int GetCurrentPosition()
- {
- return activeposition;
- }
-
- public void SetCurrentPosition(int currentPosition)
- {
- activeposition = currentPosition;
- }
-
- bool MoveNext(string delimiters)
- {
- //the stringtotokenize was never set:
- if (activestring == null)
- return false;
-
- //all tokens have already been extracted:
- if (activeposition == activestring.Length)
- return false;
-
- //bypass delimiters:
- activeposition++;
- while (activeposition < activestring.Length && delimiters.IndexOf(activestring[activeposition]) > -1)
- {
- activeposition++;
- }
-
- //only delimiters were left, so return null:
- if (activeposition == activestring.Length)
- return false;
-
- //get starting position of string to return:
- int startingposition = activeposition;
-
- //read until next delimiter:
- do
- {
- activeposition++;
- } while (activeposition < activestring.Length && delimiters.IndexOf(activestring[activeposition]) == -1);
-
- Current = activestring.Substring(startingposition, activeposition - startingposition);
- return true;
- }
-
- private string activestring;
- private int activeposition;
- private string Current;
- }
}
diff --git a/Source/Game/Chat/CommandAttribute.cs b/Source/Game/Chat/CommandAttribute.cs
index d0f278c61..152dc0964 100644
--- a/Source/Game/Chat/CommandAttribute.cs
+++ b/Source/Game/Chat/CommandAttribute.cs
@@ -79,4 +79,15 @@ namespace Game.Chat
[AttributeUsage(AttributeTargets.Parameter)]
public class OptionalArgAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public class VariantArgAttribute : Attribute
+ {
+ public Type[] Types { get; set; }
+
+ public VariantArgAttribute(params Type[] types)
+ {
+ Types = types;
+ }
+ }
}
diff --git a/Source/Game/Chat/CommandHandler.cs b/Source/Game/Chat/CommandHandler.cs
index ff8b5a723..a7256e1a3 100644
--- a/Source/Game/Chat/CommandHandler.cs
+++ b/Source/Game/Chat/CommandHandler.cs
@@ -417,9 +417,9 @@ namespace Game.Chat
return searcher.GetTarget();
}
- public string PlayerLink(string name, bool console = false)
+ public string PlayerLink(string name)
{
- return console ? name : "|cffffffff|Hplayer:" + name + "|h[" + name + "]|h|r";
+ return _session != null ? "|cffffffff|Hplayer:" + name + "|h[" + name + "]|h|r" : name;
}
public virtual string GetNameLink()
{
@@ -622,8 +622,9 @@ namespace Game.Chat
}
public bool HasSentErrorMessage() { return _sentErrorMessage; }
+ public void SetSentErrorMessage(bool val) { _sentErrorMessage = val; }
- internal bool _sentErrorMessage;
+ bool _sentErrorMessage;
WorldSession _session;
}
@@ -739,8 +740,7 @@ namespace Game.Chat
public override void SendSysMessage(string str, bool escapeCharacters)
{
- _sentErrorMessage = true;
-
+ SetSentErrorMessage(true);
Log.outInfo(LogFilter.Server, str);
}
@@ -798,8 +798,7 @@ namespace Game.Chat
public override void SendSysMessage(string str, bool escapeCharacters)
{
- _sentErrorMessage = true;
-
+ SetSentErrorMessage(true);
_reportToRA(str);
}
diff --git a/Source/Game/Chat/CommandManager.cs b/Source/Game/Chat/CommandManager.cs
index b76eda723..b814b4bbb 100644
--- a/Source/Game/Chat/CommandManager.cs
+++ b/Source/Game/Chat/CommandManager.cs
@@ -223,7 +223,7 @@ namespace Game.Chat
if (cmd != null)
{ /* if we matched a command at some point, invoke it */
- handler._sentErrorMessage = false;
+ handler.SetSentErrorMessage(false);
if (cmd.IsInvokerVisible(handler) && cmd.Invoke(handler, oldTail))
{ /* invocation succeeded, log this */
if (!handler.IsConsole())
@@ -413,10 +413,20 @@ namespace Game.Chat
return (bool)_methodInfo.Invoke(null, new object[] { handler, new StringArguments(args) });
else
{
- if (CommandArgs.Parse(out dynamic[] parseArgs, handler, parameters, args))
+ var parseArgs = new dynamic[parameters.Length];
+ parseArgs[0] = handler;
+ var result = CommandArgs.ConsumeFromOffset(parseArgs, 1, parameters, handler, args);
+ if (result.IsSuccessful())
return (bool)_methodInfo.Invoke(null, parseArgs);
-
- return false;
+ else
+ {
+ if (result.HasErrorMessage())
+ {
+ handler.SendSysMessage(result.GetErrorMessage());
+ handler.SetSentErrorMessage(true);
+ }
+ return false;
+ }
}
}
}
diff --git a/Source/Game/Chat/CommandTags.cs b/Source/Game/Chat/CommandTags.cs
index 51278ca9d..a9bca569d 100644
--- a/Source/Game/Chat/CommandTags.cs
+++ b/Source/Game/Chat/CommandTags.cs
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+using Framework.Constants;
using Game.Entities;
using System;
@@ -26,11 +27,7 @@ namespace Game.Chat
ObjectGuid _guid;
Player _player;
- public PlayerIdentifier(string name, ObjectGuid guid)
- {
- _name = name;
- _guid = guid;
- }
+ public PlayerIdentifier() { }
public PlayerIdentifier(Player player)
{
@@ -75,40 +72,37 @@ namespace Game.Chat
return FromSelf(handler);
}
- public static ParseStringResult ParseFromString(CommandArguments args)
+ public ChatCommandResult TryConsume(CommandHandler handler, string args)
{
- var result = args.NextString();
- if (result != ParseResult.Ok)
- return new ParseStringResult(ParseResult.Error);
+ ChatCommandResult next = CommandArgs.TryConsume(out dynamic tempVal, typeof(ulong), handler, args);
+ if (!next.IsSuccessful())
+ next = CommandArgs.TryConsume(out tempVal, typeof(string), handler, args);
+ if (!next.IsSuccessful())
+ return next;
- string arg = result.GetValue();
-
- ulong guid = 0;
- string name;
- if (!Hyperlink.TryParse(out name, arg) || !ulong.TryParse(arg, out guid))
- name = arg;
-
- if (!name.IsEmpty())
+ if (tempVal is ulong)
{
- ObjectManager.NormalizePlayerName(ref name);
- var player = Global.ObjAccessor.FindPlayerByName(name);
- if (player != null)
- return new ParseStringResult(ParseResult.Ok, new PlayerIdentifier(player));
+ _guid = ObjectGuid.Create(HighGuid.Player, tempVal);
+ if ((_player = Global.ObjAccessor.FindPlayerByLowGUID(_guid.GetCounter())))
+ _name = _player.GetName();
else
- {
- var objectGuid = Global.CharacterCacheStorage.GetCharacterGuidByName(name);
- if (objectGuid.IsEmpty())
- return new ParseStringResult(ParseResult.Ok, new PlayerIdentifier(name, objectGuid));
- }
+ if (!Global.CharacterCacheStorage.GetCharacterNameByGuid(_guid, out _name))
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserCharGuidNoExist, _guid.ToString()));
+ return next;
}
- else if (guid != 0)
+ else
{
- var player = Global.ObjAccessor.FindPlayerByLowGUID(guid);
- if (player != null)
- return new ParseStringResult(ParseResult.Ok, new PlayerIdentifier(player));
- }
+ _name = tempVal;
- return new ParseStringResult(ParseResult.Error);
+ if (!ObjectManager.NormalizePlayerName(ref _name))
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserCharNameInvalid, _name));
+
+ if ((_player = Global.ObjAccessor.FindPlayerByName(_name)))
+ _guid = _player.GetGUID();
+ else if ((_guid = Global.CharacterCacheStorage.GetCharacterGuidByName(_name)).IsEmpty())
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserCharNameNoExist, _name));
+ return next;
+ }
}
}
@@ -118,6 +112,7 @@ namespace Game.Chat
string _name;
WorldSession _session;
+ public AccountIdentifier() { }
public AccountIdentifier(WorldSession session)
{
_id = session.GetAccountId();
@@ -130,26 +125,29 @@ namespace Game.Chat
public bool IsConnected() { return _session != null; }
public WorldSession GetConnectedSession() { return _session; }
- public static ParseStringResult ParseFromString(CommandArguments args)
+ public ChatCommandResult TryConsume(CommandHandler handler, string args)
{
- var result = args.NextString();
- if (result != ParseResult.Ok)
- return new ParseStringResult(ParseResult.Error);
+ ChatCommandResult next = CommandArgs.TryConsume(out dynamic text, typeof(string), handler, args);
+ if (!next.IsSuccessful())
+ return next;
- // try parsing as account name
- var session = Global.WorldMgr.FindSession(Global.AccountMgr.GetId(result.GetValue()));
- if (session != null) // account with name exists, we are done
- return new ParseStringResult(ParseResult.Ok, new AccountIdentifier(session));
+ // first try parsing as account name
+ _name = text;
+ _id = Global.AccountMgr.GetId(_name);
+ _session = Global.WorldMgr.FindSession(_id);
+ if (_id != 0) // account with name exists, we are done
+ return next;
- // try parsing as account id
- if (!uint.TryParse(result.GetValue(), out uint id))
- return new ParseStringResult(ParseResult.Error);
+ // try parsing as account id instead
+ if (uint.TryParse(text, out uint id))
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserAccountNameNoExist, _name));
+ _id = id;
+ _session = Global.WorldMgr.FindSession(_id);
- session = Global.WorldMgr.FindSession(id);
- if (session != null)
- return new ParseStringResult(ParseResult.Ok, new AccountIdentifier(session));
-
- return new ParseStringResult(ParseResult.Error);
+ if (Global.AccountMgr.GetName(_id, out _name))
+ return next;
+ else
+ return ChatCommandResult.FromErrorMessage(handler.GetParsedString(CypherStrings.CmdparserAccountIdNoExist, _id));
}
public static AccountIdentifier FromTarget(CommandHandler handler)
@@ -174,11 +172,6 @@ namespace Game.Chat
{
string str;
- public Tail(CommandArguments args)
- {
- str = args.NextString("").GetValue();
- }
-
public bool IsEmpty() { return str.IsEmpty(); }
public static implicit operator string(Tail tail)
@@ -186,9 +179,10 @@ namespace Game.Chat
return tail.str;
}
- public static ParseStringResult ParseFromString(CommandArguments args)
+ public ChatCommandResult TryConsume(CommandHandler handler, string args)
{
- return new ParseStringResult(ParseResult.Ok, new Tail(args));
+ str = args;
+ return new ChatCommandResult(str);
}
}
}
diff --git a/Source/Game/Chat/Commands/AccountCommands.cs b/Source/Game/Chat/Commands/AccountCommands.cs
index f20d8b9d4..cabfa756a 100644
--- a/Source/Game/Chat/Commands/AccountCommands.cs
+++ b/Source/Game/Chat/Commands/AccountCommands.cs
@@ -95,7 +95,7 @@ namespace Game.Chat
return false;
}
- AccountOpResult result = Global.AccountMgr.CreateAccount(accountName, password, email);
+ AccountOpResult result = Global.AccountMgr.CreateAccount(accountName, password, email ?? "");
switch (result)
{
case AccountOpResult.Ok:
@@ -105,7 +105,7 @@ namespace Game.Chat
Log.outInfo(LogFilter.Player, "Account: {0} (IP: {1}) Character:[{2}] (GUID: {3}) created Account {4} (Email: '{5}')",
handler.GetSession().GetAccountId(), handler.GetSession().GetRemoteAddress(),
handler.GetSession().GetPlayer().GetName(), handler.GetSession().GetPlayer().GetGUID().ToString(),
- accountName, email);
+ accountName, email ?? "");
}
break;
case AccountOpResult.NameTooLong:
diff --git a/Source/Game/Chat/Commands/AchievementCommands.cs b/Source/Game/Chat/Commands/AchievementCommands.cs
index 3cc6e4f95..d6828cb9a 100644
--- a/Source/Game/Chat/Commands/AchievementCommands.cs
+++ b/Source/Game/Chat/Commands/AchievementCommands.cs
@@ -27,12 +27,8 @@ namespace Game.Chat.Commands
class AchievementCommand
{
[Command("add", CypherStrings.CommandAchievementAddHelp, RBACPermissions.CommandAchievementAdd)]
- static bool HandleAchievementAddCommand(CommandHandler handler, uint achievemntId)
+ static bool HandleAchievementAddCommand(CommandHandler handler, AchievementRecord achievementEntry)
{
- AchievementRecord achievementEntry = CliDB.AchievementStorage.LookupByKey(achievemntId);
- if (achievementEntry == null)
- return false;
-
Player target = handler.GetSelectedPlayer();
if (!target)
{
diff --git a/Source/Game/Chat/Commands/DebugCommands.cs b/Source/Game/Chat/Commands/DebugCommands.cs
index 5bc2905a4..8b8651718 100644
--- a/Source/Game/Chat/Commands/DebugCommands.cs
+++ b/Source/Game/Chat/Commands/DebugCommands.cs
@@ -453,7 +453,7 @@ namespace Game.Chat
}
[Command("instancespawn", RBACPermissions.CommandDebug)]
- static bool HandleDebugInstanceSpawns(CommandHandler handler, string optArg)
+ static bool HandleDebugInstanceSpawns(CommandHandler handler, [VariantArg(typeof(uint), typeof(string))] object optArg)
{
Player player = handler.GetPlayer();
if (player == null)
@@ -461,10 +461,10 @@ namespace Game.Chat
bool explain = false;
uint groupID = 0;
- if (optArg.Equals("explain", StringComparison.OrdinalIgnoreCase))
+ if (optArg is string && (optArg as string).Equals("explain", StringComparison.OrdinalIgnoreCase))
explain = true;
else
- groupID = uint.Parse(optArg);
+ groupID = (uint)optArg;
if (groupID != 0 && Global.ObjectMgr.GetSpawnGroupData(groupID) == null)
{
@@ -786,6 +786,36 @@ namespace Game.Chat
return true;
}
+ [Command("pvp warmode", RBACPermissions.CommandDebug, true)]
+ static bool HandleDebugWarModeBalanceCommand(CommandHandler handler, string command, int? rewardValue)
+ {
+ // USAGE: .debug pvp fb [pct]
+ // neutral Sets faction balance off.
+ // alliance Set faction balance to alliance.
+ // horde Set faction balance to horde.
+ // off Reset the faction balance and use the calculated value of it
+ switch (command)
+ {
+ case "alliance":
+ Global.WorldMgr.SetForcedWarModeFactionBalanceState(TeamId.Alliance, rewardValue.GetValueOrDefault(0));
+ break;
+ case "horde":
+ Global.WorldMgr.SetForcedWarModeFactionBalanceState(TeamId.Horde, rewardValue.GetValueOrDefault(0));
+ break;
+ case "neutral":
+ Global.WorldMgr.SetForcedWarModeFactionBalanceState(TeamId.Neutral);
+ break;
+ case "off":
+ Global.WorldMgr.DisableForcedWarModeFactionBalanceState();
+ break;
+ default:
+ handler.SendSysMessage(CypherStrings.BadValue);
+ return false;
+ }
+
+ return true;
+ }
+
[Command("questreset", RBACPermissions.CommandDebug)]
static bool HandleDebugQuestResetCommand(CommandHandler handler, string arg)
{
diff --git a/Source/Game/Chat/Commands/GameObjectCommands.cs b/Source/Game/Chat/Commands/GameObjectCommands.cs
index 450186f61..ccc1d150c 100644
--- a/Source/Game/Chat/Commands/GameObjectCommands.cs
+++ b/Source/Game/Chat/Commands/GameObjectCommands.cs
@@ -115,7 +115,7 @@ namespace Game.Chat
}
[Command("info", RBACPermissions.CommandGobjectInfo)]
- static bool HandleGameObjectInfoCommand(CommandHandler handler, string isGuid, uint data)
+ static bool HandleGameObjectInfoCommand(CommandHandler handler, [OptionalArg] string isGuid, ulong data)
{
GameObject thisGO = null;
GameObjectData spawnData = null;
@@ -137,7 +137,7 @@ namespace Game.Chat
}
else
{
- entry = data;
+ entry = (uint)data;
}
GameObjectTemplate gameObjectInfo = Global.ObjectMgr.GetGameObjectTemplate(entry);
diff --git a/Source/Game/Chat/Commands/ListCommands.cs b/Source/Game/Chat/Commands/ListCommands.cs
index 8678f7ea9..52b573a4e 100644
--- a/Source/Game/Chat/Commands/ListCommands.cs
+++ b/Source/Game/Chat/Commands/ListCommands.cs
@@ -442,7 +442,7 @@ namespace Game.Chat.Commands
}
[Command("respawns", RBACPermissions.CommandListRespawns)]
- static bool HandleListRespawnsCommand(CommandHandler handler, uint range)
+ static bool HandleListRespawnsCommand(CommandHandler handler, uint? range)
{
Player player = handler.GetSession().GetPlayer();
Map map = player.GetMap();
@@ -454,8 +454,8 @@ namespace Game.Chat.Commands
string zoneName = GetZoneName(zoneId, locale);
for (SpawnObjectType type = 0; type < SpawnObjectType.NumSpawnTypes; type++)
{
- if (range != 0)
- handler.SendSysMessage(CypherStrings.ListRespawnsRange, type, range);
+ if (range.HasValue)
+ handler.SendSysMessage(CypherStrings.ListRespawnsRange, type, range.Value);
else
handler.SendSysMessage(CypherStrings.ListRespawnsZone, type, zoneName, zoneId);
@@ -473,9 +473,9 @@ namespace Game.Chat.Commands
if (edata != null)
{
respawnZoneId = map.GetZoneId(PhasingHandler.EmptyPhaseShift, edata.SpawnPoint);
- if (range != 0)
+ if (range.HasValue)
{
- if (!player.IsInDist(edata.SpawnPoint, range))
+ if (!player.IsInDist(edata.SpawnPoint, range.Value))
continue;
}
else
@@ -615,9 +615,9 @@ namespace Game.Chat.Commands
aura.GetCasterGUID().ToString());
}
- for (ushort i = 0; i < (int)AuraType.Total; ++i)
+ for (AuraType auraType = 0; auraType < AuraType.Total; ++auraType)
{
- var auraList = unit.GetAuraEffectsByType((AuraType)i);
+ var auraList = unit.GetAuraEffectsByType(auraType);
if (auraList.Empty())
continue;
@@ -631,7 +631,7 @@ namespace Game.Chat.Commands
if (!sizeLogged)
{
sizeLogged = true;
- handler.SendSysMessage(CypherStrings.CommandTargetListauratype, auraList.Count, i);
+ handler.SendSysMessage(CypherStrings.CommandTargetListauratype, auraList.Count, auraType);
}
handler.SendSysMessage(CypherStrings.CommandTargetAurasimple, effect.GetId(), effect.GetEffIndex(), effect.GetAmount());
diff --git a/Source/Game/Chat/Commands/TeleCommands.cs b/Source/Game/Chat/Commands/TeleCommands.cs
index 1b894e060..f5525cf55 100644
--- a/Source/Game/Chat/Commands/TeleCommands.cs
+++ b/Source/Game/Chat/Commands/TeleCommands.cs
@@ -31,9 +31,8 @@ namespace Game.Chat
class TeleCommands
{
[Command("", RBACPermissions.CommandTele)]
- static bool HandleTeleCommand(CommandHandler handler, string name)
+ static bool HandleTeleCommand(CommandHandler handler, GameTele tele)
{
- var tele = Global.ObjectMgr.GetGameTele(name);
if (tele == null)
{
handler.SendSysMessage(CypherStrings.CommandTeleNotfound);
@@ -71,7 +70,7 @@ namespace Game.Chat
if (player == null)
return false;
- if (Global.ObjectMgr.GetGameTele(name) != null)
+ if (Global.ObjectMgr.GetGameTeleExactName(name) != null)
{
handler.SendSysMessage(CypherStrings.CommandTpAlreadyexist);
return false;
@@ -100,9 +99,8 @@ namespace Game.Chat
}
[Command("del", RBACPermissions.CommandTeleDel, true)]
- static bool HandleTeleDelCommand(CommandHandler handler, string name)
+ static bool HandleTeleDelCommand(CommandHandler handler, GameTele tele)
{
- var tele = Global.ObjectMgr.GetGameTele(name);
if (tele == null)
{
handler.SendSysMessage(CypherStrings.CommandTeleNotfound);
@@ -115,9 +113,8 @@ namespace Game.Chat
}
[Command("group", RBACPermissions.CommandTeleGroup)]
- static bool HandleTeleGroupCommand(CommandHandler handler, string name)
+ static bool HandleTeleGroupCommand(CommandHandler handler, GameTele tele)
{
- var tele = Global.ObjectMgr.GetGameTele(name);
if (tele == null)
{
handler.SendSysMessage(CypherStrings.CommandTeleNotfound);
@@ -240,7 +237,7 @@ namespace Game.Chat
class TeleNameCommands
{
[Command("", RBACPermissions.CommandTeleName, true)]
- static bool HandleTeleNameCommand(CommandHandler handler, PlayerIdentifier player, string where)
+ static bool HandleTeleNameCommand(CommandHandler handler, [OptionalArg] PlayerIdentifier player, [VariantArg(typeof(GameTele), typeof(string))] object where)
{
if (player == null)
player = PlayerIdentifier.FromTargetOrSelf(handler);
@@ -249,7 +246,7 @@ namespace Game.Chat
Player target = player.GetConnectedPlayer();
- if (where.Equals("home", StringComparison.OrdinalIgnoreCase)) // References target's homebind
+ if (where is string && where.Equals("$home")) // References target's homebind
{
if (target)
target.TeleportTo(target.GetHomebind());
@@ -272,10 +269,7 @@ namespace Game.Chat
}
// id, or string, or [name] Shift-click form |color|Htele:id|h[name]|h|r
- GameTele tele = Global.ObjectMgr.GetGameTele(where);
- if (tele == null)
- return false;
-
+ GameTele tele = where as GameTele;
return DoNameTeleport(handler, player, tele.mapId, new Position(tele.posX, tele.posY, tele.posZ, tele.orientation), tele.name);
}
diff --git a/Source/Game/Chat/Hyperlink.cs b/Source/Game/Chat/Hyperlink.cs
index 075e48aa7..36635ed3e 100644
--- a/Source/Game/Chat/Hyperlink.cs
+++ b/Source/Game/Chat/Hyperlink.cs
@@ -15,33 +15,23 @@
* along with this program. If not, see .
*/
+using Framework.Constants;
using System;
namespace Game.Chat
{
class Hyperlink
{
- public static bool TryParse(out string value, string arg)
+ public static ChatCommandResult TryParse(out dynamic value, Type type, CommandHandler handler, string arg)
{
- value = null;
+ value = default;
HyperlinkInfo info = ParseHyperlink(arg);
// invalid hyperlinks cannot be consumed
if (info == null)
- return false;
+ return default;
- value = info.Data;
- return true;
- }
-
- public static bool TryParse(out dynamic value, Type type, CommandArguments args)
- {
- value = default;
-
- HyperlinkInfo info = ParseHyperlink(args.GetString());
- // invalid hyperlinks cannot be consumed
- if (info == null)
- return false;
+ ChatCommandResult errorResult = ChatCommandResult.FromErrorMessage(handler.GetCypherString(CypherStrings.CmdparserLinkdataInvalid));
// store value
switch (Type.GetTypeCode(type))
@@ -49,42 +39,34 @@ namespace Game.Chat
case TypeCode.UInt32:
{
if (!uint.TryParse(info.Data, out uint tempValue))
- return false;
+ return errorResult;
value = tempValue;
- return true;
+ break;
}
case TypeCode.UInt64:
{
if (!ulong.TryParse(info.Data, out ulong tempValue))
- return false;
+ return errorResult;
value = tempValue;
- return true;
+ break;
}
case TypeCode.String:
{
value = info.Data;
- return true;
- }
- case TypeCode.Object:
- {
- switch (type.Name)
- {
- case nameof(PlayerIdentifier):
- value = PlayerIdentifier.ParseFromString(args);
- break;
- case nameof(AccountIdentifier):
- value = AccountIdentifier.ParseFromString(args);
- break;
- default:
- return false;
- }
- return true;
+ break;
}
+ default:
+ return errorResult;
}
- return false;
+ // finally, skip any potential delimiters
+ var (token, next) = CommandArgs.Tokenize(info.Tail);
+ if (token.IsEmpty()) /* empty token = first character is delimiter, skip past it */
+ return new ChatCommandResult(next);
+ else
+ return new ChatCommandResult(info.Tail);
}
public static bool CheckAllLinks(string str)
@@ -123,7 +105,7 @@ namespace Game.Chat
return false;
// tag is fine, find the next one
- pos = str.Length - info.next.Length;
+ pos = str.Length - info.Tail.Length;
}
}
@@ -204,16 +186,16 @@ namespace Game.Chat
class HyperlinkInfo
{
- public HyperlinkInfo(string n = null, uint c = 0, string tag = null, string data = null, string text = null)
+ public HyperlinkInfo(string t = null, uint c = 0, string tag = null, string data = null, string text = null)
{
- next = n;
+ Tail = t;
color = new(c);
Tag = tag;
Data = data;
Text = text;
}
- public string next;
+ public string Tail;
public HyperlinkColor color;
public string Tag;
public string Data;
diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs
index fb13c7e34..5ed2828c4 100644
--- a/Source/Game/Globals/ObjectManager.cs
+++ b/Source/Game/Globals/ObjectManager.cs
@@ -5766,7 +5766,30 @@ namespace Game
}
public GameTele GetGameTele(string name)
{
- return gameTeleStorage.Values.FirstOrDefault(p => p.nameLow == name.ToLower());
+ name = name.ToLower();
+
+ // Alternative first GameTele what contains wnameLow as substring in case no GameTele location found
+ GameTele alt = null;
+ foreach (var (_, tele) in gameTeleStorage)
+ {
+ if (tele.nameLow == name)
+ return tele;
+ else if (alt == null && tele.nameLow.Contains(name))
+ alt = tele;
+ }
+
+ return alt;
+ }
+ public GameTele GetGameTeleExactName(string name)
+ {
+ name = name.ToLower();
+ foreach (var (_, tele) in gameTeleStorage)
+ {
+ if (tele.nameLow == name)
+ return tele;
+ }
+
+ return null;
}
public bool AddGameTele(GameTele tele)
{