Jump to content

Multi Threaded Eating Handler


dubai

Recommended Posts

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;
    }
}

 

  • Heart 2
Link to comment
Share on other sites

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 by Fanny
Link to comment
Share on other sites

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.

 

Link to comment
Share on other sites

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??! :D

Link to comment
Share on other sites

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 😛

Link to comment
Share on other sites

1 hour ago, dubai said:

I found I honestly HAD to do that,

Just a thought...

You could put the whole onloop method inside a while loop on a volatile marked boolean

private volatile boolean isHealthOK = true;

while(isHealthOK){
	//logic
}

This would break it for food logic, which could mark it true again after eating

Should be able to control this boolean from the other thread too because volatile

 

I'm trying to collect data like this food healing amounts etc, to make scripts more dynamic with less GUI/setup generally

I'm not a 1337 programmer either :D We are all learning, even the best still learn!
 

Edited by Fanny
Link to comment
Share on other sites

24 minutes ago, Fanny said:

Just a thought...

You could put the whole onloop method inside a while loop on a volatile marked boolean

Holy crap I've never thought of touching the onloop like that, is this against the rules? 😛 Jokes, but thats actually a good idea I might experiment something like that maybe not just for eating but for pvp detection or something aswell..

Link to comment
Share on other sites

32 minutes ago, dubai said:

Holy crap I've never thought of touching the onloop like that, is this against the rules? 😛 Jokes, but thats actually a good idea I might experiment something like that maybe not just for eating but for pvp detection or something aswell..

Well, when I say inside a while, I mean the other way around (contents of onloop inside while) :D

So you have 

public class FightScript extends Script {

    private volatile boolean isHealthOK = true;
    private volatile boolean isPvPDetected = false;

    @Override
    public int onLoop() throws InterruptedException {

        while (isHealthOK && !isPvPDetected) {
            // While health is okay, perform main script logic
	}

        if (!isHealthOK) {
	    // Eat logic
	}
		
	if (isPvPDetected) {
	    // PvP logic
	}
    }
}

Should work I think? (untested)

It is good learning for me to be honest, I'd have just extended my sleep class to include a health check by default and auto-eat, but I like this multithreaded approach, plus it's more learning :D

Edited by Fanny
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...