/// <summary> /// Find a paramater's target in code (methods) /// </summary> /// <param name="commandType">the system type of the command</param> /// <param name="currentNeededParm">the paramater we are after</param> public void SeekInCode(Type commandType, CommandParameterAttribute currentNeededParm) { var validTargetTypes = commandsAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(currentNeededParm.ParameterType)); var internalCommandString = CommandStringRemainder.ToList(); var parmWords = internalCommandString.Count(); Type parm = null; while (parmWords > 0) { var currentParmString = string.Join(" ", RemoveGrammaticalNiceities(internalCommandString.Take(parmWords))).ToLower(); if (!currentNeededParm.MatchesPattern(currentParmString)) { parmWords--; continue; } var validParms = validTargetTypes.Where(comm => comm.GetCustomAttributes <CommandKeywordAttribute>() .Any(att => att.Keyword.Equals(currentParmString))); if (validParms.Count() > 1) { AccessErrors.Add(string.Format("There are {0} potential targets with that name for the {1} command.", validParms.Count(), commandType.Name)); AccessErrors.AddRange(validParms.Select(typ => typ.Name)); break; } else if (validParms.Count() == 1) { parm = validParms.First(); if (parm != null) { switch (currentNeededParm.Usage) { case CommandUsage.Supporting: Supporting = Activator.CreateInstance(parm); break; case CommandUsage.Subject: Subject = Activator.CreateInstance(parm); break; case CommandUsage.Target: Target = Activator.CreateInstance(parm); break; } } CommandStringRemainder = CommandStringRemainder.Skip(parmWords); return; } parmWords--; } }
/// <summary> /// Find a parameter target in backing data /// </summary> /// <typeparam name="T">the system type of the data</typeparam> /// <param name="commandType">the system type of the command</param> /// <param name="currentNeededParm">the conditions for the parameter we're after</param> public void SeekInBackingData <T>(Type commandType, CommandParameterAttribute currentNeededParm) where T : IData { var internalCommandString = CommandStringRemainder.ToList(); var parmWords = internalCommandString.Count(); while (parmWords > 0) { var currentParmString = string.Join(" ", internalCommandString.Take(parmWords)); if (!currentNeededParm.MatchesPattern(currentParmString)) { parmWords--; continue; } var validObject = default(T); long parmID = -1; if (!long.TryParse(currentParmString, out parmID)) { validObject = DataWrapper.GetOneBySharedKey <T>("Name", currentParmString); } else { validObject = DataWrapper.GetOne <T>(parmID); } if (validObject != null && !validObject.Equals(default(T))) { switch (currentNeededParm.Usage) { case CommandUsage.Supporting: Supporting = validObject; break; case CommandUsage.Subject: Subject = validObject; break; case CommandUsage.Target: Target = validObject; break; } CommandStringRemainder = CommandStringRemainder.Skip(parmWords); return; } parmWords--; } }
/// <summary> /// Find a parameter target in reference data /// </summary> /// <typeparam name="T">the system type of the data</typeparam> /// <param name="commandType">the system type of the command</param> /// <param name="currentNeededParm">the conditions for the parameter we're after</param> public void SeekInReferenceData <T>(Type commandType, CommandParameterAttribute currentNeededParm) where T : IReferenceData { var internalCommandString = CommandStringRemainder.ToList(); var parmWords = internalCommandString.Count(); while (parmWords > 0) { var currentParmString = string.Join(" ", RemoveGrammaticalNiceities(internalCommandString.Take(parmWords))); if (!currentNeededParm.MatchesPattern(currentParmString)) { parmWords--; continue; } var validObject = ReferenceWrapper.GetOne <T>(currentParmString); if (validObject != null && !validObject.Equals(default(T))) { switch (currentNeededParm.Usage) { case CommandUsage.Supporting: Supporting = validObject; break; case CommandUsage.Subject: Subject = validObject; break; case CommandUsage.Target: Target = validObject; break; } CommandStringRemainder = CommandStringRemainder.Skip(parmWords); return; } parmWords--; } }
/// <summary> /// Find a parameter target in the live world (entity) /// </summary> /// <typeparam name="T">the system type of the entity</typeparam> /// <param name="commandType">the system type of the command</param> /// <param name="currentNeededParm">the conditions for the parameter we're after</param> public void SeekInLiveWorldContainer <T>(Type commandType, CommandParameterAttribute currentNeededParm, Type subjectType) { //Borked it here, we found nothing viable earlier if (Subject == null || !((ICollection <IEntity>)Subject).Any()) { return; } var subjectCollection = (ICollection <IEntity>)Subject; //Containers are touch range only var internalCommandString = CommandStringRemainder.ToList(); var disambiguator = -1; var parmWords = internalCommandString.Count(); while (parmWords > 0) { var currentParmString = string.Join(" ", RemoveGrammaticalNiceities(internalCommandString.Take(parmWords))).ToLower(); //We have disambiguation here, we need to pick the first object we get back in the list if (Regex.IsMatch(currentParmString, LiveWorldDisambiguationSyntax)) { disambiguator = int.Parse(currentParmString.Substring(0, currentParmString.IndexOf("."))); currentParmString = currentParmString.Substring(currentParmString.IndexOf(".") + 1); } if (!currentNeededParm.MatchesPattern(currentParmString)) { parmWords--; continue; } var validObjects = new List <T>(); validObjects.AddRange(subjectCollection.Select(sbj => sbj.CurrentLocation) .Where(cl => cl.Keywords.Any(key => key.Contains(currentParmString))).Select(ent => (T)ent)); if (validObjects.Count() > 0) { //Skip everything up to the right guy and then take the one we want so we don't have to horribly alter the following logic flows if (disambiguator > -1 && validObjects.Count() > 1) { validObjects = validObjects.Skip(disambiguator - 1).Take(1).ToList(); } if (validObjects.Count() > 1) { AccessErrors.Add(string.Format("There are {0} potential containers with that name for the {1} command. Try using one of the following disambiguators:", validObjects.Count(), commandType.Name)); int iterator = 1; foreach (var obj in validObjects) { var entityObject = (IEntity)obj; AccessErrors.Add(string.Format("{0}.{1}", iterator++, entityObject.DataTemplate.Name)); } break; } else if (validObjects.Count() == 1) { var parm = validObjects.First(); if (parm != null) { switch (currentNeededParm.Usage) { case CommandUsage.Supporting: Supporting = parm; break; case CommandUsage.Subject: Subject = parm; break; case CommandUsage.Target: Target = parm; break; } } CommandStringRemainder = CommandStringRemainder.Skip(parmWords); //Now try to set the subject var container = (IContains)parm; var validSubjects = new List <IEntity>(); validSubjects.AddRange(subjectCollection.Where(sbj => sbj.CurrentLocation.Equals(container))); if (validSubjects.Count() > 1) { AccessErrors.Add(string.Format("There are {0} potential targets with that name inside {1} for the {2} command. Try using one of the following disambiguators:" , validObjects.Count(), parmWords, commandType.Name)); int iterator = 1; foreach (var obj in validSubjects) { AccessErrors.Add(string.Format("{0}.{1}", iterator++, obj.DataTemplate.Name)); } } else if (validObjects.Count() == 1) { Subject = validSubjects.First(); } return; } } parmWords--; } }
/// <summary> /// Find a parameter target in the live world (entity) /// </summary> /// <typeparam name="T">the system type of the entity</typeparam> /// <param name="commandType">the system type of the command</param> /// <param name="currentNeededParm">the conditions for the parameter we're after</param> /// <param name="hasContainer">does the command need a container to look for things in</param> /// <param name="seekRange">how far we can look</param> public void SeekInLiveWorld <T>(Type commandType, CommandParameterAttribute currentNeededParm, CommandRangeAttribute seekRange, bool hasContainer) { var internalCommandString = CommandStringRemainder.ToList(); var disambiguator = -1; var parmWords = internalCommandString.Count(); while (parmWords > 0) { var currentParmString = string.Join(" ", RemoveGrammaticalNiceities(internalCommandString.Take(parmWords))).ToLower(); //We have disambiguation here, we need to pick the first object we get back in the list if (Regex.IsMatch(currentParmString, LiveWorldDisambiguationSyntax)) { disambiguator = int.Parse(currentParmString.Substring(0, currentParmString.IndexOf("."))); currentParmString = currentParmString.Substring(currentParmString.IndexOf(".") + 1); } if (!currentNeededParm.MatchesPattern(currentParmString)) { parmWords--; continue; } var validObjects = new List <T>(); switch (seekRange.Type) { case CommandRangeType.Self: validObjects.Add((T)Actor); break; case CommandRangeType.Touch: validObjects.AddRange(Location.GetContents <T>().Where(ent => ((IEntity)ent).Keywords.Any(key => key.Contains(currentParmString)))); if (Actor.GetType().GetInterfaces().Any(typ => typ == typeof(IContains))) { validObjects.AddRange(((IContains)Actor).GetContents <T>().Where(ent => ((IEntity)ent).Keywords.Any(key => key.Contains(currentParmString)))); } //Containers only matter for touch usage subject paramaters, actor's inventory is already handled //Don't sift through another intelligence's stuff //TODO: write "does entity have permission to another entity's inventories" function on IEntity if (hasContainer && currentNeededParm.Usage == CommandUsage.Subject) { foreach (IContains thing in Location.GetContents <T>().Where(ent => ent.GetType().GetInterfaces().Any(intf => intf == typeof(IContains)) && !ent.GetType().GetInterfaces().Any(intf => intf == typeof(IMobile)) && !ent.Equals(Actor))) { validObjects.AddRange(thing.GetContents <T>().Where(ent => ((IEntity)ent).Keywords.Any(key => key.Contains(currentParmString)))); } } break; case CommandRangeType.Local: //requires Range to be working break; case CommandRangeType.Regional: //requires range to be working break; case CommandRangeType.Global: validObjects.AddRange(LiveCache.GetAll <T>().Where(ent => ((IEntity)ent).Keywords.Any(key => key.Contains(currentParmString)))); break; } if (hasContainer && currentNeededParm.Usage == CommandUsage.Subject && validObjects.Count() > 0) { Subject = validObjects; CommandStringRemainder = CommandStringRemainder.Skip(parmWords); return; } else if (validObjects.Count() > 0) { //Skip everything up to the right guy and then take the one we want so we don't have to horribly alter the following logic flows if (disambiguator > -1 && validObjects.Count() > 1) { validObjects = validObjects.Skip(disambiguator - 1).Take(1).ToList(); } if (validObjects.Count() > 1) { AccessErrors.Add(string.Format("There are {0} potential targets with that name for the {1} command. Try using one of the following disambiguators:", validObjects.Count(), commandType.Name)); int iterator = 1; foreach (var obj in validObjects) { var entityObject = (IEntity)obj; AccessErrors.Add(string.Format("{0}.{1}", iterator++, entityObject.DataTemplate.Name)); } break; } else if (validObjects.Count() == 1) { var parm = validObjects.First(); if (parm != null) { switch (currentNeededParm.Usage) { case CommandUsage.Supporting: Supporting = parm; break; case CommandUsage.Subject: Subject = parm; break; case CommandUsage.Target: Target = parm; break; } } CommandStringRemainder = CommandStringRemainder.Skip(parmWords); return; } } parmWords--; } }
/// <summary> /// Tries to parse the parameters for the command /// </summary> /// <param name="commandType">the command's method type</param> /// <param name="neededParms">what paramaters are considered required by the command</param> private void ParseParamaters(Type commandType, IEnumerable <CommandParameterAttribute> neededParms, bool hasContainer) { //HUGE conceit for CacheReferenceType.Container usage // : We need to store all the found CommandUsage.Subjects and validate them once we get to the container Type subjectType = typeof(IEntity); //Flip through each remaining word and parse them foreach (var currentNeededParm in neededParms.OrderBy(parm => parm.Usage)) { //why continue if we ran out of stuff if (CommandStringRemainder.Count() == 0) { break; } if (hasContainer && currentNeededParm.Usage == CommandUsage.Subject) { subjectType = currentNeededParm.ParameterType; } foreach (var seekType in currentNeededParm.CacheTypes) { switch (seekType) { case CacheReferenceType.Code: SeekInCode(commandType, currentNeededParm); break; case CacheReferenceType.Entity: //So damn ugly, make this not use reflection if possible MethodInfo entityMethod = GetType().GetMethod("SeekInLiveWorld") .MakeGenericMethod(new Type[] { currentNeededParm.ParameterType }); entityMethod.Invoke(this, new object[] { commandType, currentNeededParm, commandType.GetCustomAttribute <CommandRangeAttribute>(), hasContainer }); break; case CacheReferenceType.Container: MethodInfo containerMethod = GetType().GetMethod("SeekInLiveWorldContainer") .MakeGenericMethod(new Type[] { currentNeededParm.ParameterType }); containerMethod.Invoke(this, new object[] { commandType, currentNeededParm, subjectType }); break; case CacheReferenceType.Reference: MethodInfo referenceMethod = GetType().GetMethod("SeekInReferenceData") .MakeGenericMethod(new Type[] { currentNeededParm.ParameterType }); referenceMethod.Invoke(this, new object[] { commandType, currentNeededParm }); break; case CacheReferenceType.Help: SeekInReferenceData <Help>(commandType, currentNeededParm); break; case CacheReferenceType.Data: MethodInfo dataMethod = GetType().GetMethod("SeekInBackingData") .MakeGenericMethod(new Type[] { currentNeededParm.ParameterType }); dataMethod.Invoke(this, new object[] { commandType, currentNeededParm }); break; case CacheReferenceType.Text: //Text just means grab the remainder of the command string and dump it into a param switch (currentNeededParm.Usage) { case CommandUsage.Supporting: Supporting = string.Join(" ", CommandStringRemainder); break; case CommandUsage.Subject: Subject = string.Join(" ", CommandStringRemainder); break; case CommandUsage.Target: Target = string.Join(" ", CommandStringRemainder); break; } //empty the remainder CommandStringRemainder = Enumerable.Empty <string>(); //We return here to end the parsing return; } } } }
/// <summary> /// Where we do the parsing, creates the context and parsed everything on creation /// </summary> /// <param name="fullCommand">the initial unparsed input string</param> /// <param name="actor">the entity issuing the command</param> public Context(string fullCommand, IActor actor) { commandsAssembly = Assembly.GetAssembly(typeof(CommandParameterAttribute)); entitiesAssembly = Assembly.GetAssembly(typeof(IEntity)); OriginalCommandString = fullCommand; Actor = actor; Location = (ILocation)Actor.CurrentLocation; AccessErrors = new List <string>(); CommandStringRemainder = Enumerable.Empty <string>(); LoadedCommands = commandsAssembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(ICommand))); //NPCs can't use anything player rank can't use if (Actor.GetType().GetInterfaces().Contains(typeof(IPlayer))) { LoadedCommands = LoadedCommands.Where(comm => comm.GetCustomAttributes <CommandPermissionAttribute>().Any(att => att.MinimumRank <= ((ICharacter)Actor.DataTemplate).GamePermissionsRank)); } else { LoadedCommands = LoadedCommands.Where(comm => comm.GetCustomAttributes <CommandPermissionAttribute>().Any(att => att.MinimumRank == StaffRank.Player)); } //find out command's type var commandType = ParseCommand(); if (commandType == null) { AccessErrors.Add("Unknown Command."); //TODO: Add generic errors class for rando error messages return; } //Log people using and even attempting to use admin commands in game if (commandType.GetCustomAttributes <CommandPermissionAttribute>().Any(att => att.MinimumRank == StaffRank.Admin)) { LoggingUtility.LogAdminCommandUsage(OriginalCommandString, ((ICharacter)Actor.DataTemplate).AccountHandle); } try { //find the parameters var parmList = commandType.GetCustomAttributes <CommandParameterAttribute>(); var hasContainer = parmList.Any(parm => parm.CacheTypes.Any(crt => crt == CacheReferenceType.Container)); //why bother if we have no parms to find? if (CommandStringRemainder.Count() > 0) { ParseParamaters(commandType, parmList, hasContainer); } //Did we get errors from the parameter parser? if so bail if (AccessErrors.Count > 0) { return; } Command = Activator.CreateInstance(commandType) as ICommand; if ( (parmList.Any(parm => !parm.Optional && parm.Usage == CommandUsage.Subject) && Subject == null) || (parmList.Any(parm => !parm.Optional && parm.Usage == CommandUsage.Target) && Target == null) || (parmList.Any(parm => !parm.Optional && parm.Usage == CommandUsage.Supporting) && Supporting == null) ) { AccessErrors.Add("Invalid command targets specified."); AccessErrors.AddRange(Command.RenderSyntaxHelp()); return; } //Parms we got doesn't equal parms we loaded if (CommandStringRemainder.Count() != 0) { AccessErrors.Add(string.Format("I could not find {0}.", string.Join(" ", CommandStringRemainder))); AccessErrors.AddRange(Command.RenderSyntaxHelp()); return; } //double check container stuff if (hasContainer && Subject != null && Subject.GetType().GetInterfaces().Any(typ => typ == typeof(ICollection))) { var collection = (ICollection <IEntity>)Subject; if (collection.Count() == 1) { Subject = collection.First(); } else { AccessErrors.Add("Invalid command targets specified."); AccessErrors.AddRange(Command.RenderSyntaxHelp()); return; } } Command.Actor = Actor; Command.OriginLocation = Location; Command.Surroundings = Surroundings; Command.Subject = Subject; Command.Target = Target; Command.Supporting = Supporting; } catch (MethodAccessException mEx) { Command = Activator.CreateInstance(commandType) as ICommand; AccessErrors.Add(mEx.Message); AccessErrors = AccessErrors.Concat(Command.RenderSyntaxHelp()).ToList(); } }