dubai Posted yesterday at 09:30 AM Share Posted yesterday at 09:30 AM Open source eating handler, welcome to any feed back as I use this in my combat scripts: Spoiler package utils; import org.osbot.rs07.api.ui.Skill; import org.osbot.rs07.script.Script; import java.io.PrintWriter; import java.io.StringWriter; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; public class EatingHandler implements Runnable { private final Script script; private final int eatBelowHP; private final String food; private final ReentrantLock lock; private AtomicBoolean running; private volatile boolean stopped = false; private enum State { EATING_NOT_REQUIRED, NEED_TO_EAT, EATING, SUCCESSFULLY_ATE } private volatile State currentState; public EatingHandler(Script script, int eatBelowHP, String food) { this.script = script; this.eatBelowHP = eatBelowHP; this.food = food; this.currentState = State.EATING_NOT_REQUIRED; this.running = new AtomicBoolean(false); this.lock = new ReentrantLock(); } @Override public void run() { script.log("EatingHandler thread is running."); while (running.get()) { handleEating(); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); script.log("Thread was interrupted, stopping EatingHandler."); running.set(false); } } } public boolean handleEating() { try { int currentHealth = script.getSkills().getDynamic(Skill.HITPOINTS); lock.lock(); try { switch (currentState) { case EATING_NOT_REQUIRED: if (currentHealth <= eatBelowHP) { currentState = State.NEED_TO_EAT; } break; case NEED_TO_EAT: currentState = State.EATING; break; case EATING: if (eatFood()) { currentState = State.SUCCESSFULLY_ATE; } break; case SUCCESSFULLY_ATE: if (currentHealth <= eatBelowHP) { currentState = State.NEED_TO_EAT; } else { currentState = State.EATING_NOT_REQUIRED; } break; } } finally { lock.unlock(); } } catch (Exception e) { script.log("Error in EatingHandler: " + e.getMessage()); StringWriter writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); e.printStackTrace(printWriter); printWriter.flush(); script.log(writer.toString()); } return false; } public synchronized void start() { if (!running.get()) { running.set(true); stopped = false; new Thread(this).start(); script.log("EatingHandler started."); } } public void stop() { running.set(false); script.log("EatingHandler stopped."); } private boolean eatFood() throws InterruptedException { boolean ate = false; lock.lock(); try { if (script.getInventory().interact("Eat", food)) { script.log("Ate food: " + food); script.sleep(600); ate = true; } else { script.log("Failed to eat food: " + food); } } catch (Exception e) { script.log("Exception while trying to eat: " + e.getMessage()); } finally { lock.unlock(); } return ate; } } 2 Quote Link to comment Share on other sites More sharing options...
Fanny Posted 1 hour ago Share Posted 1 hour ago (edited) Really interesting, nice job I haven't really toyed with performing actions in a multi-threaded scenario and locking them out so I'm trying to interpret how the locking mechanism works! Looks well handled, error-wise I would be tempted to expand with an AvailableFood class which stores a list of foods and their healing values, so that you don't have to input what food type, can also dynamically eat mob dropped food that way I would also be tempted to take a range of values for eatBelowHP, so for 99HP you might say 75-40, each time you eat a food, you can set the eatBelowHP to a random value in that range until it next eats, then set the value again etc. You could combine both of these to check where your HP is within the range (higher or lower end of the range) and have AvailableFood.eatBestFood() or something if you are nearer 40hp, or AvailableFood.eatWorstFood() if you are near to 75hp in the example Alternatively, you could instead of taking eatBelowHP, take mobMaxHit, then you can dynamically leave a safety margin and create a range in which to eat using players max hp, and this would be more scalable for all levels and mobs Edited 1 hour ago by Fanny Quote Link to comment Share on other sites More sharing options...
dubai Posted 1 hour ago Author Share Posted 1 hour ago 19 minutes ago, Fanny said: I would be tempted to expand with an AvailableFood class which stores a list of foods and their healing values, so that you don't have to input what food type, can also dynamically eat mob dropped food that way You mean something like this? Spoiler package common; public class FoodHealingAmount { public int getFoodHealingAmount(String food) { switch (food.toLowerCase()) { case "lobster": return 12; case "swordfish": return 14; case "shark": return 20; case "monkfish": return 16; case "trout": return 7; case "salmon": return 9; default: return 0; } } } I also feel my locking block isn't perfect in the eating handler. As there are use cases where my script might crash during testing, and I've noticed the lock holds the eating handler running AFTER the script has stopped and I have to start/stop the script again. So it does need work... Thanks heaps for your comment and feedback though. Let me know if you try this out and if you run into any similar problems like I have as it does seem to be a rare bug. Quote Link to comment Share on other sites More sharing options...
Fanny Posted 52 minutes ago Share Posted 52 minutes ago 29 minutes ago, dubai said: You mean something like this? Yeah exactly that! You could filter the inventory based on the list too, so you know what foods you have available, and how much each one heals public class FoodHealingAmount { private static final Map<String, Integer> FOOD_HEALING_MAP; static { Map<String, Integer> foodMap = new HashMap<>(); foodMap.put("Lobster", 12); foodMap.put("Swordfish", 14); foodMap.put("Shark", 20); foodMap.put("Monkfish", 16); foodMap.put("Trout", 7); foodMap.put("Salmon", 9); FOOD_HEALING_MAP = Collections.unmodifiableMap(foodMap); } public static Set<String> getAllFoodNames() { return FOOD_HEALING_MAP.keySet(); } //etc } // Get all food names Set<String> foodNames = FoodHealingAmount.getAllFoodNames(); // Filter inventory items that are food Item[] foodItems = getInventory().filter(item -> foodNames.contains(item.getName())); Regards locking logic, I'd personally be tempted to contain eating logic to the main script (or any actual actions) to keep things consistent and error-free, but I can see why you would want it in a separate thread - and where is the challenge of doing it in a single thread??! Quote Link to comment Share on other sites More sharing options...
dubai Posted 29 minutes ago Author Share Posted 29 minutes ago 19 minutes ago, Fanny said: You could filter the inventory based on the list too, so you know what foods you have available, and how much each one heals I actually do have something similar in my RememberInventory class that does a check on start to determine what food/potions etc you have in the inventory on start, and then withdraws those food. I'm really loving the idea of a push start script that auto checks configuration and doesn't require any GUI UX. In regards to multithreading the eating handler, I found I honestly HAD to do that, especially for some of my wilderness scripts where it needs to be constantly checking hp and prioritizing eating over other tasks. Single threaded verisons just couldnt do what I wanted them too sadly but I'm definitely not a 1337 programmer, YET Quote Link to comment Share on other sites More sharing options...