[Bot Developers] Object searching, the easy way! menu

Shout-Out

User Tag List

Results 1 to 4 of 4
  1. #1
    Apoc's Avatar Angry Penguin
    Reputation
    1388
    Join Date
    Jan 2008
    Posts
    2,750
    Thanks G/R
    0/13
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    [Bot Developers] Object searching, the easy way!

    Well, it's time for yet another installment of my bot writing guides/tutorials or whatever you want to call them.

    This time; object searching!

    People seem to be having issues writing good ways to search for objects, and filter them at the same time. In all honesty, this is made incredibly easy by .NET, among other things.

    Throughout this post, I'll explain a few different concepts of object searching, and some 'better' ways to do it.

    I won't lie, having a *good* API wrapper for object searching, will look incredibly ugly internally. However, to anything actually using the API, it's very, very, clean. Unfortunately, it's the trade-off for speed, and accuracy.

    There are quite a few different ways to go about object searching. (Obviously I'm talking about WoW in this case, but it's portable between games very easily.)

    They are;

    1. Iterating over each object, and reading data every time. [Very accurate, and will always retrieve correct information.]
    2. Iterating over each object, and caching data that is read. [Not accurate at all, but is a major speed increase]
    3. Others that I won't bother going in to, as they are just plain horrible to use.


    In other words; we'll be taking the performance hit to do a PROPER search. Sorry folks, caching just simply isn't viable for critical information such as dynamic object searching.

    Now, with that said, lets get into the nitty gritty.

    Requirements for the object search:

    1. A set of available search parameters you can use to actually search for an object. (IsDead, Distance, Level, Name, etc)
    2. A small 'parser' to parse the incoming parameters. (In this case, we will be omitting it. Explained below.)
    3. An iterating method to actually do the search.
    4. A way to actually retrieve the object data we need. (This is not covered here, as it's up to you to write your interop wrappers!)


    First things first, we need to go over a bit of terminology I'll be using throughout the rest of this post. If any of you read any C# books, or had even a 1/2 decent C# teacher, you will have at least heard of 'Nullable Types'. (Nullable Generic Structure (System) for a decent overview) In other words, it allows us to give values not only their normal type values (eg; int a = 1, bool b = true), but also a null value. (int a = null, bool b = null). Note: strings are already nullable, so this syntax will not work for strings! (I cover how to work around this issue below)

    In short; you can mark a type as nullable by assigning it a ? after the type declaration.

    Example:

    Code:
    public int? Level;
    Level can now take anything from int.MinValue - int.MaxValue, and also null. This comes in INCREDIBLY handy when creating a parameters struct/class that may not always have everything set. (In other words, you'll never assign every value in the params class.)

    There is another class in the .NET framework, called 'System.Nullable'. Feel free to look through it, however, we won't be using it in this guide, as we need a different equality check.

    First things first... lets set up a quick SearchParams class.

    Code:
    namespace ObjectSearcher
    {
        public class SearchParams
        {
            public string Name;
    
            public int? MinLevel;
            public int? MaxLevel;
            public int? LevelExact;
    
            public int? ObjectType;
    
            public bool? Dead;
            public bool? LineOfSight;
            public bool? CanAttack;
    
            public bool? IncludeMe;
    
            public double? MinDistance;
            public double? MaxDistance;
    
            public ulong? Guid;
        }
    }
    Fairly simple. Just a *very* basic set of params to search for. Notice how string isn't decorated with the nullable type '?'. (The actual name of the '?' escapes me at the moment, sorry guys!) So now we can actually start setting up some things to start searching!

    Why use a class over a struct? Simple; if we don't explicitly set each value of the the struct, it will default to the non-nullable default(T). (Not default(T?)) So we just use a class here to handle our params, and have everything that can be set to null, actually set to null.

    First things first, lets create some quick wrapper stuff to feed our searcher.

    Code:
    namespace ObjectSearcher
    {
        public class WoWObject
        {
            public double Distance;
            public ulong Guid;
            public bool IsDead;
            public bool IsInLineOfSight;
            public string Name;
            public virtual int Type { get { return 1; } }
            public bool CanAttack;
            public int Level;
            public bool IsMe;
        }
    
        public class WoWUnit : WoWObject
        {
            public override int Type { get { return 2; } }
        }
    }
    Again, nothing really functional. That's up to you as the bot writer to implement!

    So now we have some stuff we can use to check. Now lets get into actually searching!

    Now, here's a choice we have to make. Do we want to be able to instantiate a class, and that instance will hold all of our 'found' values for the search, or create a 'static' search method to return a List<WoWObject> of return types.

    Personally, I like being able to create a search class, then use it consistently as it's own collection, but still able to 'refresh' the search.

    However, to appease those who like the other way, we'll kill two birds with one stone.

    Here we go!

    Code:
    using System.Collections;
    using System.Collections.Generic;
    
    namespace ObjectSearcher
    {
        public class ObjectSearch<T> : IEnumerable<T> where T : WoWObject
        {
            private List<T> _objList = new List<T>();
    
            public ObjectSearch(SearchParams searchParams)
            {
                Params = searchParams;
                Refresh();
            }
    
            public ObjectSearch()
            {
                // Empty param set
                Params = new SearchParams();
            }
    
            public SearchParams Params { get; set; }
    
            #region IEnumerable<T> Members
    
            public IEnumerator<T> GetEnumerator()
            {
                return _objList.GetEnumerator();
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
    
            #endregion
    
            public void Refresh()
            {
                // Note: ObjectManager.ObjectList is just boilerplate code
                // to make us remember that we need to get objects from somewhere!
                _objList = DoSearch<T>(ObjectManager.ObjectList, Params);
            }
    
            public void Refresh(SearchParams searchParams)
            {
                _objList = DoSearch<T>(ObjectManager.ObjectList, searchParams);
            }
    
            /// <summary>
            /// Searches the specified objects using the search params passed and filters
            /// out an non-matching objects.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="objects">An enumerable list of objects to search through</param>
            /// <param name="searchParams">The search filters. (Params)</param>
            /// <returns>A list of filtered (searched) objects.</returns>
            public static List<T> DoSearch<T>(IEnumerable<WoWObject> objects, SearchParams searchParams) where T : WoWObject
            {
                var ret = new List<T>();
    
                // This is where we will do all of our search logic
    
                return ret;
            }
        }
    }
    Notice how ObjectSearch is GENERIC. This comes in very handy. I'll explain why later.

    Also note, that I'm being incredibly lazy here, and just using a List<T> as the backing enumerator. This is perfectly viable, and works just fine.

    And yet another note; we're restraining T to type WoWObject. If you understood OOP properly, you'd know that WoWUnit can be passed as T. (Or any other descendant of WoWObject)

    We need to do this in the static DoSearch function as well, or some of the code below will fail... miserably...

    Finally... we can start moving towards our searching...

    Well... now we're at a stand still... which method should we use to search?

    foreach loop?
    LINQ?
    Some other crappy way?

    Well, LINQ would be the easiest to read (and sort), so lets go with that.

    Again, as in the last guide I posted, if you don't understand LINQ, or have never heard of it, please, go look it up. I won't be explaining it here. (The specific type of LINQ we're using, is LINQ to Objects FYI. We used LINQ to XML (or XLINQ) in the last guide.)

    Here's the 'template' query we'll be using.

    Code:
                var filtered = from o in objects
                               // All of our 'where' logic goes here
                               select o;
    Yes, I know, it's terribly confusing. If you've ever worked with SQL, LINQ is basically SQL, in C# syntax. (But faster )

    Now we can start adding in our 'where' clauses.

    But wait! We can't! Not yet at least... Remember at the beginning when I told you about not using System.Nullable? Well, that's because we want to make sure we're taking into account that x type CAN be null, and if it is, we should ignore it.

    Now, since a LINQ where statement is a very (very) long if statement, we have to return true/false based on some comparisons. For now, I'll just show you the finished LINQ statement, then explain the equality checking code.

    Code:
                var filtered = from o in objects
                               let lvl = o.Level // Cache it as we don't want to make excessive calls
                               let dist = o.Distance // Cache
                               where
                                   Compare.Equal(searchParams.Name, o.Name) &&
                                   Compare.Equal(searchParams.CanAttack, o.CanAttack) &&
                                   Compare.Equal(searchParams.Dead, o.IsDead) && 
                                   Compare.Equal(searchParams.Guid, o.Guid) &&
                                   Compare.Equal(searchParams.LevelExact, lvl) &&
                                   Compare.Equal(searchParams.ObjectType, o.Type) &&
                                   Compare.Equal(searchParams.LineOfSight, o.IsInLineOfSight) &&
                                   // End of 'basic' checks, now we get into conditional <= >= < > checks
                                   Compare.LessOrEqual(searchParams.MinLevel, lvl) &&
                                   Compare.GreaterOrEqual(searchParams.MaxLevel, lvl) &&
                                   Compare.LessOrEqual(searchParams.MinDistance, dist) &&
                                   Compare.GreaterOrEqual(searchParams.MaxDistance, dist) && 
                                   // End of condition comparers
                                   // This check is to make sure we're constraining results to 'T'
                                   (o is T) && 
                                   // Finally check if we should include ourselves.
                                   Compare.Equal(searchParams.IncludeMe, o.IsMe)
                               select o;
    This is why I told you to go read up on LINQ yourselves.

    In short, we're doing some nullable type based equality operations.

    I'm not going to bother explaining the following class. Just read the comments.

    Code:
    namespace ObjectSearcher
    {
        internal static class Compare
        {
            public static bool Equal<T>(T? searchParam, T val) where T : struct
            {
                // If 'searchParam' has no value, return true (assume user doesn't want this param used)
                // Otherwise, make sure the actual values are equal.
                return !searchParam.HasValue || searchParam.Value.Equals(val);
            }
    
            public static bool Equal(string searchParam, string val)
            {
                // Same as the other Equal method. Except we need to adjust for strings.
                if (searchParam==null)
                    return true;
                return val == searchParam;
            }
    
            public static bool LessOrEqual(int? param, int val)
            {
                if (!param.HasValue)
                    return true;
                return param.Value <= val;
            }
    
            public static bool LessOrEqual(double? param, double val)
            {
                if (!param.HasValue)
                    return true;
                return param.Value <= val;
            }
    
            public static bool GreaterOrEqual(int? param, int val)
            {
                if (!param.HasValue)
                {
                    return true;
                }
                return param.Value >= val;
            }
            public static bool GreaterOrEqual(double? param, double val)
            {
                if (!param.HasValue)
                    return true;
                return param.Value >= val;
            }
        }
    }
    You may need to add more GreaterOrEqual and LessOrEqual methods according to the data type you need. Feel free to add Greater/Less methods. I had no use for them in this guide, so I opted to omit them for brevity. (And character limits)

    Ok, so now we've got an enumerable object of filtered WoWObject's. Now comes the issue most of you probably just ran into. (If you've actually been following along in code.) You can't just plop a WoWObject into a List<T>. The compiler bitches until you fix it. Well, unfortunately there isn't a whole lot here we can do... or is there?

    Ok, so there isn't really a whole lot to this next bit. It's fairly simple stuff.

    Code:
                foreach (WoWObject o in filtered)
                {
                    ret.Add(o as T);
                }
    I know. I'm so creative! Seriously though, this just converts 'WoWObject to a safe T'. In other words, if the conversion failed, the object that is actually added, is 'null'. Keep that in mind! (However, that should never happen here.)

    Do keep in mind folks; this doesn't cover 'non castable' implementations. Eg; those that can't be casted properly to the type you need. If you need more information on that, I'll be more than happy to explain it. However, this is all you should need in most cases.

    Finally! We're done!

    Here's the full code for the ObjectSearch class.

    Code:
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace ObjectSearcher
    {
        public class ObjectSearch<T> : IEnumerable<T> where T : WoWObject
        {
            private List<T> _objList = new List<T>();
    
            public ObjectSearch(SearchParams searchParams)
            {
                Params = searchParams;
                Refresh();
            }
    
            public ObjectSearch()
            {
                // Empty param set
                Params = new SearchParams();
            }
    
            public SearchParams Params { get; set; }
    
            #region IEnumerable<T> Members
    
            public IEnumerator<T> GetEnumerator()
            {
                return _objList.GetEnumerator();
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
    
            #endregion
    
            public void Refresh()
            {
                // Note: ObjectManager.ObjectList is just boilerplate code
                // to make us remember that we need to get objects from somewhere!
                _objList = DoSearch<T>(ObjectManager.ObjectList, Params);
            }
    
            public void Refresh(SearchParams searchParams)
            {
                _objList = DoSearch<T>(ObjectManager.ObjectList, searchParams);
            }
    
            /// <summary>
            /// Searches the specified objects using the search params passed and filters
            /// out an non-matching objects.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="objects">An enumerable list of objects to search through</param>
            /// <param name="searchParams">The search filters. (Params)</param>
            /// <returns>A list of filtered (searched) objects.</returns>
            public static List<T> DoSearch<T>(IEnumerable<WoWObject> objects, SearchParams searchParams) where T : WoWObject
            {
                var ret = new List<T>();
    
                IEnumerable<WoWObject> filtered = from o in objects
                                                  let lvl = o.Level
                                                  // Cache it as we don't want to make excessive calls
                                                  let dist = o.Distance
                                                  // Cache
                                                  where
                                                      Compare.Equal(searchParams.Name, o.Name) &&
                                                      Compare.Equal(searchParams.CanAttack, o.CanAttack) &&
                                                      Compare.Equal(searchParams.Dead, o.IsDead) &&
                                                      Compare.Equal(searchParams.Guid, o.Guid) &&
                                                      Compare.Equal(searchParams.LevelExact, lvl) &&
                                                      Compare.Equal(searchParams.ObjectType, o.Type) &&
                                                      Compare.Equal(searchParams.LineOfSight, o.IsInLineOfSight) &&
                                                      // End of 'basic' checks, now we get into conditional <= >= < > checks
                                                      Compare.LessOrEqual(searchParams.MinLevel, lvl) &&
                                                      Compare.GreaterOrEqual(searchParams.MaxLevel, lvl) &&
                                                      Compare.LessOrEqual(searchParams.MinDistance, dist) &&
                                                      Compare.GreaterOrEqual(searchParams.MaxDistance, dist) &&
                                                      // End of condition comparers
                                                      // This check is to make sure we're constraining results to 'T'
                                                      (o is T) && // Finally check if we should include ourselves.
                                                      Compare.Equal(searchParams.IncludeMe, o.IsMe)
                                                  select o;
    
                foreach (WoWObject o in filtered)
                {
                    ret.Add(o as T);
                }
    
                return ret;
            }
        }
    }
    This is a fairly basic setup, and will work for most requirements. It has plenty of room for expansion, so please, feel free to make changes, and add things. If you're so inclined, post your changes here for everyone to use!

    Enjoy folks. (Sorry, no source this time around, I've posted all of it!)

    (Pre-post edit: I just noticed how short this one is. If you guys need any clarity, let me know, and I'll edit this post. I'll probably be editing this post as I find issues anyway!)

    [Bot Developers] Object searching, the easy way!
  2. #2
    barthen's Avatar Contributor Authenticator enabled
    Reputation
    94
    Join Date
    Apr 2007
    Posts
    112
    Thanks G/R
    4/4
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Great post. I just found your "Bot Developers" series and they are a great reading.

  3. #3
    Cypher's Avatar Kynox's Sister's Pimp
    Reputation
    1358
    Join Date
    Apr 2006
    Posts
    5,368
    Thanks G/R
    0/6
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Kynox's sister is easier.

  4. #4
    Neffarian's Avatar Member
    Reputation
    -5
    Join Date
    Sep 2006
    Posts
    53
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Awesome job

Similar Threads

  1. Making an anti-afk bot with AutoIT the easy way.
    By Tsai in forum World of Warcraft Bots and Programs
    Replies: 13
    Last Post: 10-02-2007, 04:22 PM
  2. Kiting the easy way
    By Kalen24 in forum World of Warcraft Guides
    Replies: 2
    Last Post: 11-17-2006, 09:45 AM
  3. Enchanting 1-300 - The Easy Way!
    By Tory in forum World of Warcraft Guides
    Replies: 5
    Last Post: 09-05-2006, 11:15 PM
  4. Sell items the easy way
    By KenshinHimora in forum World of Warcraft Guides
    Replies: 3
    Last Post: 08-10-2006, 01:25 AM
  5. Prepping for Vaelastrasz the easy way
    By Matt in forum World of Warcraft Guides
    Replies: 0
    Last Post: 04-01-2006, 10:11 AM
All times are GMT -5. The time now is 06:49 AM. Powered by vBulletin® Version 4.2.3
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2025 DragonByte Technologies Ltd.
Google Authenticator verification provided by Two-Factor Authentication (Free) - vBulletin Mods & Addons Copyright © 2025 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search