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) {