1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Tutorial What nullity and validity mean, and how to work with them

Discussion in 'Tutorials & Resources' started by Snufalufugus, Mar 31, 2017.

  1. Snufalufugus

    Joined:
    Aug 23, 2015
    Messages:
    1,961
    Likes Received:
    757
    Introduction to null:
    In Java, null is a way to represent nothing. In other words, it is something that is unknown, has no defining characteristics, etc.

    Why is Null important at RM:
    Unless you take a precaution known as null-checking in your code as you write a bot, you WILL, sooner or later, end up with an error described as a Null Pointer Exception (NPE). If your bot produces an NPE error, it means that you tried to tell the bot to do something with a null value. Essentially, you provided nothing, pointed to it in your code, and told your bot to do something to that nothingness.

    How can you accidentally end up with something null:
    Whenever you're looking for something in-game, that something has the possibility to end up null. This can be your local character, a character around you, an in-game object, a ground item, a sprite item, etc. You can have something end up null even if it isn't related to anything in-game, but as a new developer (the primary audience for this guide), working with in-game things is an easy way to introduce the concept of nullity.

    As an example, consider looking for an in-game yew tree. Queries will be covered more extensively in a different tutorial, but basic ones are self-explanatory at a glance.
    Code (Text):
    1. GameObjects.newQuery().names("Yew tree").results().nearest();
    Stop for a moment, and think about how this query would end up finding a yew tree, and how it could end up null (finding nothing). The query will take all loaded GameObjects, look for the ones named "Yew tree", gather the results, and find the one closest to your player.
    If you are standing in a field of Yew trees, chances are this query will find the nearest one like you intended.

    If you're standing in a field of evergreen trees, this query won't find anything. If you're standing in a yew tree field, but they have all been reduced to stumps by a massive bot army, your query won't find anything.


    Any time you look for something in-game, you have to consider the possibility that it may end up null. A simple solution is the following code. Take a look, and see if you can figure out what it does. An explanation will follow.
    Code (Text):
    1. GameObject tree;
    2. tree = GameObjects.newQuery().names("Yew tree").results().nearest();
    3. if(tree != null){
    4.     tree.interact("Chop");
    5. } else {
    6.     getLogger.warn("Our tree is null");
    7. }
    8.  
    Code (Text):
    1. GameObject tree; //declares a variable named "tree" of the "GameObject" type
    2. tree = GameObjects.newQuery().names("Yew tree").results().nearest(); //Looks for a GameObject and assigns the result to our tree variable
    3. if(tree != null){ //looks to see if our tree variable has information assigned to it
    4.     tree.interact("Chop"); //if it does, we interact with the tree
    5. } else { //if our tree variable has no information
    6.     getLogger.warn("Our tree is null"); //let us know that tree had no information
    7. }
    8.  

    Understanding validity:
    At this point, you should have a good understanding of what null means, and how you can encounter it, and how to avoid it. The next step is understanding the difference between something that is null, and something that is invalid.

    Any time you find something in-game, such as a GameObject or a SpriteItem (inventory item), you are actually finding all of the information about that something such as it's location, it's name, it's model, it's actions, and more. Whenever you reference that something, you are referencing all of the information that makes that something itself.

    RuneScape is an always-changing game, and as such, something that was there at one time may not be there a moment later. When this occurs, the something would be described as invalid.

    Consider the code that we used above:
    Code (Text):
    1. GameObject tree; //declares a variable named "tree" of the "GameObject" type
    2. tree = GameObjects.newQuery().names("Yew tree").results().nearest(); //Looks for a GameObject and assigns the result to our tree variable
    3. if(tree != null){ //looks to see if our tree variable has information assigned to it
    4.     tree.interact("Chop"); //if it does, we interact with the tree
    5. } else { //if our tree variable has no information
    6.     getLogger.warn("Our tree is null"); //let us know that tree had no information
    7. }
    8.  
    What would happen if after we find and save the tree, but before we chop it, that tree gets chopped down? We don't always interact with something we find immediately after finding it - sometimes we find that something and want to save it for later use, rather than finding it again. The solution to this is checking if our tree is still valid before we interact with it:

    Code (Text):
    1. GameObject tree; //creates a variable named "tree" of the "GameObject" type
    2. tree = GameObjects.newQuery().names("Yew tree").results().nearest(); //looks for a nearby tree GameObject and saves it to our "tree" variable
    3. if(tree != null){ //if our tree has defining characteristics (A name, a position, an appearance, etc)
    4.     if(tree.isValid()){ //if our tree is still there in the live game
    5.         tree.interact("Chop"); //Chop the tree
    6.     } else {
    7.         getLogger.info("Our saved tree was not valid"); //print out that our tree was not there when we looked
    8.     }
    9. } else {
    10.     getLogger.warn("Our tree is null"); //print out that our tree was never there, using the "warn" level of logging to bring attention to the message
    11. }
    12.  
    To clarify, checking for validity as I have just done is not needed, it is just an example of how to check. Unless you keep the reference to your something in memory (saved to a variable for an extended time), or you delay in between finding something and interacting with something, a something will not become invalid immediately after getting it from the game.

    Saving a query result to a variable, and proceeding to null-check that variable, is the proper way to null-check.

    At a glance, it is easy to think that this code does the same thing:
    Code (Text):
    1.  if(GameObjects.newQuery().names("Yew tree").results().nearest() != null){
    2.             GameObjects.newQuery().names("Yew tree").results().nearest().interact("Chop");
    3.         } else {
    4.             getLogger.warn("Our tree is null");
    5.         }
    Although the result of the first query must not be null for you to chop the tree, you're not interacting with the first non-null query result. Instead, this code re-queries for the tree, and interacts with that result, which may be null. You must save the result of your query to a variable of the appropriate category and interact with that result to make sure you're still attempting to interact with the same something.
     
    #1 Snufalufugus, Mar 31, 2017
    Last edited: Aug 22, 2017
    TYG123, SlashnHax, Slex and 2 others like this.
  2. awesome123man

    awesome123man Go check out new bots and give helpful feedback.

    Joined:
    Jan 31, 2016
    Messages:
    5,413
    Likes Received:
    1,662
    Great Tutorial!

    tree = GameObjects.newQuery().names("Yew tree").results().nearest(); //looks for a nearby tree GameObject and saves it to our "tree" variable
    if(tree != null){ //if our tree has defining characteristics (A name, a position, an appearance, etc)
    if(tree.isValid()){ //if our tree is still there in the live game

    I don't personally use the .isValid check right after querying... Usually i'll do it if I cache a GameObject in the loop before and want to make sure it's still there, checking validity right after querying seems a bit inefficient, but i may be wrong.
     
    Snufalufugus likes this.
  3. Snufalufugus

    Joined:
    Aug 23, 2015
    Messages:
    1,961
    Likes Received:
    757
    You're right that it's not really necessary immediately after, but it's a good habit, and calling isValid() will have a minuscule impact on any code being executed. It's much more important when using an older result for sure.
     
    awesome123man likes this.
  4. Savior

    Savior Java Warlord

    Joined:
    Nov 17, 2014
    Messages:
    4,906
    Likes Received:
    2,748
    Just annotating that doing something like above is not needed. Unless you keep the reference to your gameobject in memory, or you delay inbetween, a game entity will not become invalid right after you got it from the game. Furthermore when calling results() on a query, the results will not contain any invalid items (unless you have a custom provider but that's another story).
     
    Snufalufugus and awesome123man like this.
  5. Snufalufugus

    Joined:
    Aug 23, 2015
    Messages:
    1,961
    Likes Received:
    757
    Edited OP to clarify when checking for validity is necessary. Thanks for the input.
     
  6. proxi

    proxi s̶c̶r̶i̶p̶t̶ bot*

    Joined:
    Aug 23, 2015
    Messages:
    2,223
    Likes Received:
    501
    Great tutorial! Definitely useful for the new bot authors coming in!
     
    Snufalufugus likes this.

Share This Page

Loading...