Psychotechno Posted November 1, 2020 Share Posted November 1, 2020 (edited) Dear forum, I'm quite new to scripting and even though I'm decent in JAVA, it's been a while and I am not sure about the following behavior. I am trying to open a trap door and climb down. To that end, I'm using the following code: @Override public int onLoop() throws InterruptedException { RS2Object trapdoor = getObjects().closest("Trapdoor"); if (trapdoor != null && trapdoor.interact("Open")) { log("If was executed"); MethodProvider.sleep(7000); log("Sleep was executed"); trapdoor.interact("Climb-down"); log("Tried to climb-down"); } return 0; } It doesn't matter if I put the trapdoor.interact("Climb-down"); inside or outside the if-statement - it never seems to execute. Even though all my log messages are printed to the bot log! Opening the trapdoor goes perfectly every time. Can somebody explain to me why this is happening? Also, what would be a more "fool-proof" way of opening/climbing down a trapdoor? I was maybe thinking of adding a conditional sleep, or some kind of check to see whether the "climb down" action was successful but so far I haven't been able to come up with an elegant solution. But first I need to understand why such a simple behavior causes problems Any help is greatly appreciated Edited November 1, 2020 by Psychotechno Link to comment Share on other sites More sharing options...
Hawk Posted November 1, 2020 Share Posted November 1, 2020 It's because the closed trapdoor is not the same object as the open trapdoor. After you open the trapdoor and try to call "Climb-down" it does nothing because the only action that trapdoor object has is "Open". 2 Link to comment Share on other sites More sharing options...
Apaec Posted November 1, 2020 Share Posted November 1, 2020 Hawk has pointed out the problem. It seems that the particular object you are interacting with changes after interacting with it - this is not the case for all objects, but appears to be the case here. Regarding the structure of your code, you should always aim to make the script deterministic based on game state. This essentially means that you should be executing a single game interaction per onLoop iteration. This way, should any given interaction fail (which can happen for many reasons as we are automating a live game here), the script will always be in a state to retry. Here's what I mean in your context with pseudocode: onLoop { RS2Object trapdoor = getObjects().closest("Trapdoor"); if (trapdoor has action "open") { if (trapdoor.interact("open")) { conditional sleep until trapdoor is open; } } else if (trapdoor has action "climb-down") { if (trapdoor.interact("climb-down")) { conditional sleep until climb down is successful (e.g. correct z index) } } } Note that for every iteration of onLoop here, only one interaction can ever happen. Also note that this example doesn't take into account the particular issue you are experiencing wrt. the trapdoor object changing. You'll probably need to initialise two objects (e.g. trapdoorOpen and trapdoorClosed) and act based on which one is null. Hope that helps Apa 2 Link to comment Share on other sites More sharing options...
Psychotechno Posted November 1, 2020 Author Share Posted November 1, 2020 (edited) 40 minutes ago, Hawk said: It's because the closed trapdoor is not the same object as the open trapdoor. After you open the trapdoor and try to call "Climb-down" it does nothing because the only action that trapdoor object has is "Open". Thank you bro-beans! Knew I was overlooking something. For future readers; here is a solution in code; RS2Object closed_trapdoor = getObjects().closest("Trapdoor"); if (closed_trapdoor != null && closed_trapdoor.interact("Open")) { log("If was executed"); MethodProvider.sleep(7000); log("Sleep was executed"); RS2Object open_trapdoor = getObjects().closest("Trapdoor"); open_trapdoor.interact("Climb-down"); log("Tried to climb-down"); } Edited November 1, 2020 by Psychotechno 1 Link to comment Share on other sites More sharing options...
Psychotechno Posted November 1, 2020 Author Share Posted November 1, 2020 6 minutes ago, Apaec said: Hawk has pointed out the problem. It seems that the particular object you are interacting with changes after interacting with it - this is not the case for all objects, but appears to be the case here. Regarding the structure of your code, you should always aim to make the script deterministic based on game state. This essentially means that you should be executing a single game interaction per onLoop iteration. This way, should any given interaction fail (which can happen for many reasons as we are automating a live game here), the script will always be in a state to retry. Here's what I mean in your context with pseudocode: onLoop { RS2Object trapdoor = getObjects().closest("Trapdoor"); if (trapdoor has action "open") { if (trapdoor.interact("open")) { conditional sleep until trapdoor is open; } } else if (trapdoor has action "climb-down") { if (trapdoor.interact("climb-down")) { conditional sleep until climb down is successful (e.g. correct z index) } } } Note that for every iteration of onLoop here, only one interaction can ever happen. Also note that this example doesn't take into account the particular issue you are experiencing wrt. the trapdoor object changing. You'll probably need to initialise two objects (e.g. trapdoorOpen and trapdoorClosed) and act based on which one is null. Hope that helps Apa Thanks for the response Apa! I was assuming that one onLoop call would be a whole execution of the script (Say smithing 1 inventory of bars, Onloop call: bank - smith bars - bank. Next Onloop call: bank-smith bars - bank, and so on. If I get what you're saying, every call to onLoop is one single game interaction? Does this also apply to simpler game interactions, like walkpaths? (Assuming they don't fail too often?) You're saying: "It seems that the particular object you are interacting with changes after interacting with it - this is not the case for all objects, but appears to be the case here." The object id in the debugger of OSbot indeed changes when opening and closing the trapdoor, so I guess that's something I should look out for. Probably could also even check this with code? I was also thinking about adding a conditional sleep until trapdoor is open. But I'm not sure how to achieve such a thing; I tried to Conditional sleep untill my character was no longer animating (from opening the trapdoor) but am not sure if this is the right way to go; as in are there better ways to conditional sleep or is conditional sleeping on character animation generally a good way to go? hope you don't mind the questions, am new and willing to learn! Thx Link to comment Share on other sites More sharing options...
Khaleesi Posted November 1, 2020 Share Posted November 1, 2020 44 minutes ago, Psychotechno said: Thank you bro-beans! Knew I was overlooking something. For future readers; here is a solution in code; RS2Object closed_trapdoor = getObjects().closest("Trapdoor"); if (closed_trapdoor != null && closed_trapdoor.interact("Open")) { log("If was executed"); MethodProvider.sleep(7000); log("Sleep was executed"); RS2Object open_trapdoor = getObjects().closest("Trapdoor"); open_trapdoor.interact("Climb-down"); log("Tried to climb-down"); } That still has some flaws in it. Imagine the trapdoor already being open, you will never enter the trapdoor. You also need to null check in case the object was not found. Also some conditional sleeps makes it alot better ^^ You want to do something like this: RS2Object trapDoor = getObjects().closest("Trapdoor"); if (trapDoor != null) { if (trapDoor.hasAction("Open")) { if (trapDoor.interact("Open")) { new ConditionalSleep(7000) { @Override public boolean condition() { return trapDoor.hasAction("Climb-down"); } }.sleep(); } } else { Position prevPos = myPosition(); if (trapDoor.interact("Climb-down")) { new ConditionalSleep(5000) { @Override public boolean condition() { return myPosition().distance(prevPos) > 50 || myPosition().getZ() != prevPos.getZ(); } }.sleep(); } } } 1 Link to comment Share on other sites More sharing options...
Medusa Posted November 1, 2020 Share Posted November 1, 2020 4 minutes ago, Khaleesi said: That still has some flaws in it. Imagine the trapdoor already being open, you will never enter the trapdoor. You also need to null check in case the object was not found. Also some conditional sleeps makes it alot better ^^ You want to do something like this: RS2Object trapDoor = getObjects().closest("Trapdoor"); if (trapDoor != null) { if (trapDoor.hasAction("Open")) { if (trapDoor.interact("Open")) { new ConditionalSleep(7000) { @Override public boolean condition() { return trapDoor.hasAction("Climb-down"); } }.sleep(); } } else { Position prevPos = myPosition(); if (trapDoor.interact("Climb-down")) { new ConditionalSleep(5000) { @Override public boolean condition() { return myPosition().distance(prevPos) > 50 || myPosition().getZ() != prevPos.getZ(); } }.sleep(); } } } 1 Link to comment Share on other sites More sharing options...
Psychotechno Posted November 1, 2020 Author Share Posted November 1, 2020 How do you guys feel about the following solution? Just thinking aloud here, am using OnMessage to do some checks; public final class RandomWalker extends Script { private boolean door_open; private boolean climbed_down; private void setDoor_open(boolean door_open) { this.door_open = door_open; } private void setClimbed_down(boolean climbed_down) { this.climbed_down = climbed_down; } @Override public void onStart() throws InterruptedException { log("This will be printed to the logger when the script starts"); } @Override public void onExit() throws InterruptedException { log("This will be printed to the logger when the script exits"); } @Override public int onLoop() throws InterruptedException { RS2Object trapdoor = getObjects().closest("Trapdoor"); if (trapdoor != null && trapdoor.interact("Open")) { Sleep.sleepUntil(() -> this.door_open, 3000); RS2Object open_trapdoor = getObjects().closest("Trapdoor"); open_trapdoor.interact("Climb-down"); }else if(trapdoor != null && trapdoor.interact("Climb-down")){ trapdoor.interact("Climb-down"); } Sleep.sleepUntil(() -> this.climbed_down, 3000); List<Position> path = new ArrayList<>(); path.add(new Position(3097, 9876, 0)); path.add(new Position(3095, 9888, 0)); path.add(new Position(3096, 9902, 0)); getWalking().walkPath(path); return 0; } @Override //Use this to get bot messages public void onMessage(Message message) { log("A message arrived in the chatbox: " + message.getMessage()); if(message.getMessage().equals("The trapdoor opens...")){ setDoor_open(true); log("Set door open var to true."); }else if (message.getMessage().equals("You climb down through the trapdoor...")){ setClimbed_down(true); log("Set climbed down var to true"); } } } I'm using the onMessage to check if the trapdoor was opened, and to see if the climb-down was succesful. Also, I conditional sleep before climbing down, until I receive the message that the trapdoor is open. BTW, I'm using @Explv Conditional sleep class implementation because it keeps my code nice 'n tidy =). In general, do you guys ever use onMessage to see if an action was completed successfully? Or is checking if a position changed or smth like that always better? Since there are some downsides to the onMessage approach however, as I need fields and setters to transfer information between onMessage and onLoop, so I think Khal's solution is better. BTW @Khaleesi, I'm not sure myPosition().getZ() != prevPos.getZ() Will work in my case, since the dungeon I'm entering through the trapdoor has the same z-coordinate as the surface somehow. Also, won't RS2Object trapDoor = getObjects().closest("Trapdoor"); if (trapDoor != null) { if (trapDoor.hasAction("Open")) { if (trapDoor.interact("Open")) { new ConditionalSleep(7000) { @Override public boolean condition() { return trapDoor.hasAction("Climb-down"); } }.sleep(); } This snippet of code generate the same problem I experienced before? Since the closed trapDoor has another Object ID than the open trapdoor, return trapDoor.hasAction("Climb-down"); will always return False right? Thanks for the respones guys, appreciate it Link to comment Share on other sites More sharing options...
Apaec Posted November 1, 2020 Share Posted November 1, 2020 (edited) 6 hours ago, Psychotechno said: Thanks for the response Apa! I was assuming that one onLoop call would be a whole execution of the script (Say smithing 1 inventory of bars, Onloop call: bank - smith bars - bank. Next Onloop call: bank-smith bars - bank, and so on. If I get what you're saying, every call to onLoop is one single game interaction? Does this also apply to simpler game interactions, like walkpaths? (Assuming they don't fail too often?) You're saying: "It seems that the particular object you are interacting with changes after interacting with it - this is not the case for all objects, but appears to be the case here." The object id in the debugger of OSbot indeed changes when opening and closing the trapdoor, so I guess that's something I should look out for. Probably could also even check this with code? I was also thinking about adding a conditional sleep until trapdoor is open. But I'm not sure how to achieve such a thing; I tried to Conditional sleep untill my character was no longer animating (from opening the trapdoor) but am not sure if this is the right way to go; as in are there better ways to conditional sleep or is conditional sleeping on character animation generally a good way to go? hope you don't mind the questions, am new and willing to learn! Thx The onLoop is just a while loop which continues until the user presses the red 'stop' button, where the return value is a millisecond delay between loops. You can use it as you wish. The 'design pattern' I described, where only a single game interaction happens per loop, is safest as this makes the script deterministic. And yes - a 'walk path' call could also fail (potentially even mid-route). For example, your network connection could drop for 5 seconds. Your script needs to be able to be able to handle the possibility of this. -Apa Edit: About conditional sleeps for this particular situaton: I would sleep until the 'open trapdoor' object exists, rather than waiting for animation. Edited November 1, 2020 by Apaec 1 Link to comment Share on other sites More sharing options...
Psychotechno Posted November 1, 2020 Author Share Posted November 1, 2020 16 minutes ago, Apaec said: The onLoop is just a while loop which continues until the user presses the red 'stop' button, where the return value is a millisecond delay between loops. You can use it as you wish. The 'design pattern' I described, where only a single game interaction happens per loop, is safest as this makes the script deterministic. And yes - a 'walk path' call could also fail (potentially even mid-route). For example, your network connection could drop for 5 seconds. Your script needs to be able to be able to handle the possibility of this. -Apa Edit: About conditional sleeps for this particular situaton: I would sleep until the 'open trapdoor' object exists, rather than waiting for animation. Makes sense! Thank you Link to comment Share on other sites More sharing options...
Khaleesi Posted November 2, 2020 Share Posted November 2, 2020 (edited) 18 hours ago, Psychotechno said: How do you guys feel about the following solution? Just thinking aloud here, am using OnMessage to do some checks; public final class RandomWalker extends Script { private boolean door_open; private boolean climbed_down; private void setDoor_open(boolean door_open) { this.door_open = door_open; } private void setClimbed_down(boolean climbed_down) { this.climbed_down = climbed_down; } @Override public void onStart() throws InterruptedException { log("This will be printed to the logger when the script starts"); } @Override public void onExit() throws InterruptedException { log("This will be printed to the logger when the script exits"); } @Override public int onLoop() throws InterruptedException { RS2Object trapdoor = getObjects().closest("Trapdoor"); if (trapdoor != null && trapdoor.interact("Open")) { Sleep.sleepUntil(() -> this.door_open, 3000); RS2Object open_trapdoor = getObjects().closest("Trapdoor"); open_trapdoor.interact("Climb-down"); }else if(trapdoor != null && trapdoor.interact("Climb-down")){ trapdoor.interact("Climb-down"); } Sleep.sleepUntil(() -> this.climbed_down, 3000); List<Position> path = new ArrayList<>(); path.add(new Position(3097, 9876, 0)); path.add(new Position(3095, 9888, 0)); path.add(new Position(3096, 9902, 0)); getWalking().walkPath(path); return 0; } @Override //Use this to get bot messages public void onMessage(Message message) { log("A message arrived in the chatbox: " + message.getMessage()); if(message.getMessage().equals("The trapdoor opens...")){ setDoor_open(true); log("Set door open var to true."); }else if (message.getMessage().equals("You climb down through the trapdoor...")){ setClimbed_down(true); log("Set climbed down var to true"); } } } I'm using the onMessage to check if the trapdoor was opened, and to see if the climb-down was succesful. Also, I conditional sleep before climbing down, until I receive the message that the trapdoor is open. BTW, I'm using @Explv Conditional sleep class implementation because it keeps my code nice 'n tidy =). In general, do you guys ever use onMessage to see if an action was completed successfully? Or is checking if a position changed or smth like that always better? Since there are some downsides to the onMessage approach however, as I need fields and setters to transfer information between onMessage and onLoop, so I think Khal's solution is better. BTW @Khaleesi, I'm not sure myPosition().getZ() != prevPos.getZ() Will work in my case, since the dungeon I'm entering through the trapdoor has the same z-coordinate as the surface somehow. Also, won't RS2Object trapDoor = getObjects().closest("Trapdoor"); if (trapDoor != null) { if (trapDoor.hasAction("Open")) { if (trapDoor.interact("Open")) { new ConditionalSleep(7000) { @Override public boolean condition() { return trapDoor.hasAction("Climb-down"); } }.sleep(); } This snippet of code generate the same problem I experienced before? Since the closed trapDoor has another Object ID than the open trapdoor, return trapDoor.hasAction("Climb-down"); will always return False right? Thanks for the respones guys, appreciate it Was just giving an example. Just check if the object still exists in that case ^^ No need to make things hard, keep it simple and stupid. Never use Onmessage for this kind of things, not very reliable. Edited November 2, 2020 by Khaleesi 1 Link to comment Share on other sites More sharing options...
Camaro Posted November 2, 2020 Share Posted November 2, 2020 This can be simplified very nicely RS2Object trapdoor = getObjects().closest("Trapdoor"); if (trapdoor != null && trapdoor.interact("Open", "Climb-down")) { Sleep.sleepUntil(() -> !trapdoor.exists(), 5000); } Once you are in the dungeon, the trapdoor no longer exists, so the sleep condition covers both interactions. 1 1 Link to comment Share on other sites More sharing options...
Psychotechno Posted November 3, 2020 Author Share Posted November 3, 2020 On 11/1/2020 at 6:45 PM, Apaec said: The onLoop is just a while loop which continues until the user presses the red 'stop' button, where the return value is a millisecond delay between loops. You can use it as you wish. The 'design pattern' I described, where only a single game interaction happens per loop, is safest as this makes the script deterministic. And yes - a 'walk path' call could also fail (potentially even mid-route). For example, your network connection could drop for 5 seconds. Your script needs to be able to be able to handle the possibility of this. -Apa Edit: About conditional sleeps for this particular situaton: I would sleep until the 'open trapdoor' object exists, rather than waiting for animation. I've been thinking about what you said about your design pattern, but I'm unsure how to implement such a thing. How can I make sure only one interaction happens per OnLoop execution? Only thing I can think of is to store all my compound actions inside some data structure outside onloop and call them one by one in onLoop, only moving on to the next one when the previous one succeeds. Is that what you mean? Again, appreciate the response =) Thanks Link to comment Share on other sites More sharing options...
Psychotechno Posted November 3, 2020 Author Share Posted November 3, 2020 On 11/2/2020 at 4:50 PM, Camaro said: This can be simplified very nicely RS2Object trapdoor = getObjects().closest("Trapdoor"); if (trapdoor != null && trapdoor.interact("Open", "Climb-down")) { Sleep.sleepUntil(() -> !trapdoor.exists(), 5000); } Once you are in the dungeon, the trapdoor no longer exists, so the sleep condition covers both interactions. This does not work, the If statement will never get executed because the trapdoor does not have actions "Open" and "Climb-down" at the same time. anyways, thx for the snippet, Will use the .exists() function to check. Link to comment Share on other sites More sharing options...
Camaro Posted November 4, 2020 Share Posted November 4, 2020 1 hour ago, Psychotechno said: This does not work, the If statement will never get executed because the trapdoor does not have actions "Open" and "Climb-down" at the same time. anyways, thx for the snippet, Will use the .exists() function to check. The interaction event will attempt to execute any of them, not all of them. https://osbot.org/api/org/osbot/rs07/event/InteractionEvent.html Link to comment Share on other sites More sharing options...