Skip to content

Instantly share code, notes, and snippets.

@Hadyn
Created August 25, 2015 12:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Hadyn/7436c4a693554724bfd1 to your computer and use it in GitHub Desktop.
Save Hadyn/7436c4a693554724bfd1 to your computer and use it in GitHub Desktop.
import org.dreambot.api.methods.filter.Filter;
import org.dreambot.api.methods.map.Tile;
import org.dreambot.api.methods.skills.Skill;
import org.dreambot.api.methods.skills.Skills;
import org.dreambot.api.methods.walking.path.impl.LocalPath;
import org.dreambot.api.script.AbstractScript;
import org.dreambot.api.script.Category;
import org.dreambot.api.script.ScriptManifest;
import org.dreambot.api.wrappers.interactive.GameObject;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Hadyn Fitzgerald
*/
@ScriptManifest(category = Category.WOODCUTTING, name = "eveWoodcutting", description = "", author = "sini", version = 1.0D)
public class EveWoodcutting extends AbstractScript {
/**
* The amount of frames drawn per second. This calculation always holds true for the logic of the game.
* However if the game experiences lag then this number will be lower for the amount of times the
* picture is drawn to the screen.
*/
private static final int FPS = 50;
/**
* The names of all the tools.
*/
private List<String> TOOL_NAMES = Arrays.asList("Bronze axe", "Iron axe", "Mithril axe", "Adamante axe", "Rune axe", "Dragon axe");
/**
* The bank booth filter, the bank booth must obviously have the banking option and be on the same player plane.
*/
private Filter<GameObject> bankBoothFilter = obj -> obj.getName().equals("Bank booth") &&
obj.hasAction("Bank") && obj.getPlane() == getLocalPlayer().getZ();
enum Tool {
BRONZE("Bronze axe", 1, 1),
IRON("Iron axe", 6, 5),
BLACK("Black axe", 11, 10),
MITHRIL("Mithril axe", 21, 20),
ADAMANTE("Adamate axe", 31, 30);
private String name;
private int woodcuttingLevel;
private int attackLevel;
Tool(String name, int woodcuttingLevel, int attackLevel) {
this.name = name;
this.woodcuttingLevel = woodcuttingLevel;
this.attackLevel = attackLevel;
}
/**
* Gets the name.
*
* @return the name.
*/
public String getName() {
return name;
}
/**
* Gets the woodcutting level.
*
* @return the woodcutting level.
*/
public int getWoodcuttingLevel() {
return woodcuttingLevel;
}
/**
* Gets the attack level.
*
* @return the attack level.
*/
public int getAttackLevel() {
return attackLevel;
}
/**
* Gets the item name of all the tools.
*
* @return the names.
*/
public static List<String> getNames() {
return Stream.of(values()).map(tool -> tool.getName()).collect(Collectors.toList());
}
/**
*
* @param skills
* @return
*/
public static List<Tool> getValidTools(Skills skills) {
return Stream.of(values()).filter(tool -> skills.getRealLevel(Skill.WOODCUTTING) >= tool.getWoodcuttingLevel()).collect(Collectors.toList());
}
}
enum Tree {
WILLOW("Willow");
/**
*
*/
private String name;
/**
*
* @param name
*/
Tree(String name) {
this.name = name;
}
/**
*
* @return
*/
public String getName() {
return name;
}
}
/**
* A point cluster.
*/
class Cluster {
/**
* The tiles in the cluster.
*/
private List<Tile> points = new ArrayList<>();
/**
* Adds a point to the cluster.
*
* @param point the point to add.
*/
public void add(Tile point) {
points.add(point);
}
/**
* Gets the center of all of the points.
*
* @return the center.
*/
public Tile getCenter() {
return new Tile(getWidth() / 2 + getMinimumX(), getLength() / 2 + getMinimumY());
}
/**
* Gets the surface density of the points.
*
* @return the density.
*/
public double getDensity() {
int area = getWidth() * getLength();
return (double) size() / (double) area;
}
/**
* Gets the minimum x coordinate.
*
* @return the minimum x coordinate.
*/
public int getMinimumX() {
return points.stream().mapToInt(obj -> obj.getX()).min().orElse(-1);
}
/**
* Gets the minimum y coordinate.
*
* @return the minimum y coordinate.
*/
public int getMinimumY() {
return points.stream().mapToInt(obj -> obj.getY()).min().orElse(-1);
}
/**
* Gets the maximum x coordinate.
*
* @return the maximum x coordinate.
*/
public int getMaximumX() {
return points.stream().mapToInt(obj -> obj.getX()).max().orElse(-1);
}
/**
* Gets the maximum y coordinate.
*
* @return the maximum y coordinate.
*/
public int getMaximumY() {
return points.stream().mapToInt(obj -> obj.getY()).max().orElse(-1);
}
/**
* Gets the rectangular width of the cluster.
*
* @return the width.
*/
public int getWidth() {
return (getMaximumX() - getMinimumX()) + 1;
}
/**
* Gets the rectangular length of the cluster.
*
* @return the length.
*/
public int getLength() {
return (getMaximumY() - getMinimumY()) + 1;
}
/**
* Gets the points.
*
* @return the points.
*/
public List<Tile> getPoints() {
return points;
}
/**
* Gets the size of the cluster.
*
* @return the size.
*/
public int size() {
return points.size();
}
}
/**
* An action.
*/
interface Action {
/**
* Calls the action.
*
* @return if the action has completed.
*/
boolean call();
}
/**
* The current route to harvest materials.
*/
class Route {
/**
*
*/
Tree target;
/**
* The cluster of bank booths.
*/
Cluster bankBoothCluster;
/**
* The resource clusters.
*/
List<Cluster> resourceClusters;
/**
*
* @param bankBoothCluster
* @param resourceClusters
*/
Route(Tree target, Cluster bankBoothCluster, List<Cluster> resourceClusters) {
this.target = target;
this.bankBoothCluster = bankBoothCluster;
this.resourceClusters = resourceClusters;
}
/**
* Gets if the player is nearby any of the bank booths.
*
* @param distance the distance to consider the player as nearby.
* @return if the player is nearby the bank booths.
*/
public boolean isNearbyBankBooths(int distance) {
for(Tile tile : bankBoothCluster.getPoints()) {
if(getDistance(tile, getLocalPlayer().getTile()) < distance) {
return true;
}
}
return false;
}
/**
* Gets the closest available resource to the player.
*
* @return the closest available resource.
*/
public Tile getClosestAvailableResource() {
// Select the closest point in the clusters
Tile point = resourceClusters.stream()
.flatMap(l -> l.getPoints().stream())
.filter(p -> !hasObjectAtTile(p, obj -> obj.getName().equals("Tree Stump")))
.min((tile, compare) -> Integer.compare(getDistance(getLocalPlayer().getTile(), tile),
getDistance(getLocalPlayer().getTile(), compare))).orElseGet(null);
return point;
}
/**
* Gets the closest path to the resource clusters.
*
* @return the path.
*/
public LocalPath<Tile> getClosestPathToClusters() {
// Select the closest point in the clusters
Tile point = resourceClusters.stream().flatMap(l -> l.getPoints().stream()).min((tile, compare) ->
Integer.compare(getDistance(getLocalPlayer().getTile(), tile),
getDistance(getLocalPlayer().getTile(), compare))).get();
// Calculate a path from the players current position to the closest cluster point
LocalPath<Tile> path = getWalking()
.getAStarPathFinder()
.calculate(getLocalPlayer().getTile(), point);
return path;
}
/**
* Gets a path to the bank booth. Selects a random bank booth to walk to.
*
* @return the path.
*/
public LocalPath<Tile> getClosestPathToBankBooth() {
// Select the closest point in the cluster
List<Tile> points = bankBoothCluster.getPoints();
Tile point = points.stream().min((tile, compare) ->
Integer.compare(getDistance(getLocalPlayer().getTile(), tile),
getDistance(getLocalPlayer().getTile(), compare))).get();
// Calculate a path from the players current position to the bank booth
LocalPath<Tile> path = getWalking()
.getAStarPathFinder()
.calculate(getLocalPlayer().getTile(), point);
return path;
}
/**
* Gets a path to the bank booth. Selects a random bank booth to walk to.
*
* @return the path.
*/
public LocalPath<Tile> getRandomPathToBankBooth() {
// Select a random point from the cluster
List<Tile> points = bankBoothCluster.getPoints();
int index = (int) (Math.random() * points.size());
Tile point = points.get(index);
// Calculate a path from the players current position to the bank booth
LocalPath<Tile> path = getWalking()
.getAStarPathFinder()
.calculate(getLocalPlayer().getTile(), point);
return path;
}
/**
*
* @return
*/
public String getResourceTargetName() {
return target.getName();
}
}
/**
* Walk a path.
*/
class WalkPathAction implements Action {
/**
* The distance threshold to queue the next step.
*/
static final int NEXT_STEP_THRESHOLD = 3;
/**
* The distance threshold to find the next step, however we should randomize this a bit. The mini-map
* shows tiles visible up to 32 tiles, 16 from the player, away. This however of course is circular,
* so the rectangular edges aren't visible. Should do a better case of this.
*/
static final int MINI_MAP_THRESHOLD = 8;
/**
* The path to walk.
*/
LocalPath<Tile> path;
/**
* The current step in the path.
*/
int currentStep = -1;
/**
* Constructs a new {@link WalkPathAction};
*
* @param path the path to walk.
*/
WalkPathAction(LocalPath<Tile> path) {
this.path = path;
}
@Override
public boolean call() {
// If we reached the destination then we are done!
if(reachedDestination()) {
return true;
}
// Check if we need to walk the next step in the route
if(currentStep == -1 || currentStep != path.size() - 1 && getDistance(path.get(currentStep), getLocalPlayer().getTile()) < NEXT_STEP_THRESHOLD) {
currentStep = selectNextTile(currentStep, MINI_MAP_THRESHOLD);
// Walk the next step if we need to, if we cannot click the tile on the minimap then we can't really do
// much this is more or less a logic error.
if(!getWalking().clickTileOnMinimap(path.get(currentStep))) {
throw new IllegalStateException("Could not click next tile on minimap! PANIC");
}
}
return false;
}
/**
* Gets the next tile in the path closest to the limit.
*
* @param current the current step.
* @param limit the distance limit in tiles.
* @return the index of the tile.
*/
private int selectNextTile(int current, int limit) {
for(int i = path.size() - 1; i >= current; i--) {
if(getDistance(getLocalPlayer().getTile(), path.get(i)) < limit) {
return i;
}
}
throw new IllegalStateException("Panic! Could not find a valid tile");
}
/**
* Gets if we reached the destination.
*
* @return if we reached the destination.
*/
private boolean reachedDestination() {
if(path.size() < 1) return true;
Tile destination = path.get(path.size() - 1);
return getDistance(getLocalPlayer().getTile(), destination) <= 1;
}
}
/**
* Determines the best route to harvest local materials.
*/
class DetermineRouteAction implements Action {
/**
* The target tree name.
*/
Tree target;
/**
* Constructs a new {@link DetermineRouteAction}
*
* @param tree the target tree.
*/
DetermineRouteAction(Tree tree) {
this.target = tree;
}
@Override
public boolean call() {
// Find all of the bank booths within the scene
log("Detecting any nearby bank booths");
List<GameObject> bankBooths = getGameObjects().all(bankBoothFilter);
// Check if we found any valid bank booths
if(bankBooths.size() < 1) {
log("Fatal exception when attempting to determine route! Cannot find any nearby bank booths");
stop();
return false;
}
// Find clusters of bank booths, however this will be rare to find more than one
List<Cluster> bankBoothClusters = getClusters(bankBooths, 3, 20);
// Find all the nearby trees
log("Detecting nearby trees [target=" + target + "]");
List<GameObject> trees = getTrees(target.getName());
// Check to see if we have any nearby trees
if(trees.size() < 1) {
log("Fatal exception when attempting to determine route! Cannot find any nearby target trees");
stop();
return false;
}
// Find clusters of trees
List<Cluster> treeClusters = getClusters(trees, 5, 30);
// Find the closest bank and tree cluster
int suitableBankClusterId = -1, suitableTreeClusterId = -1;
// f(...) = len - (density * size) * coeff
double lowestScore = Double.MAX_VALUE;
for(int i = 0; i < bankBoothClusters.size(); i++) {
Cluster bankBoothCluster = bankBoothClusters.get(i);
for(int j = 0; j < treeClusters.size(); j++) {
Cluster treeCluster = treeClusters.get(j);
int distance = getDistance(treeCluster.getCenter(), bankBoothCluster.getCenter());
double score = (double) distance - (treeCluster.getDensity() * treeCluster.size()) * 1.0D;
if(score < lowestScore) {
suitableBankClusterId = i;
suitableTreeClusterId = j;
lowestScore = score;
}
}
}
// This case shouldn't happen but just as a precaution
if(suitableBankClusterId == -1 || suitableTreeClusterId == -1) {
throw new IllegalStateException("No suitable tree or bank cluster found, panic!");
}
Cluster bankBoothCluster = bankBoothClusters.get(suitableBankClusterId);
Cluster treeCluster = treeClusters.get(suitableTreeClusterId);
// Find any nearby clusters to the original cluster, up to a certain distance
List<Cluster> suitableTreeClusters = new ArrayList<>();
suitableTreeClusters.add(treeCluster);
for(int i = 0; i < treeClusters.size(); i++) {
// Skip over the suitable tree cluster
if(i == suitableTreeClusterId) {
continue;
}
Cluster cluster = treeClusters.get(i);
for(Tile tile : cluster.getPoints()) {
for(Tile compare : treeCluster.getPoints()) {
// If the distance between one of the points in the cluster is less
// than the limit we can safely assume that we can also use that cluster to chop trees from
if (getDistance(tile, compare) < 10) {
suitableTreeClusters.add(cluster);
break;
}
}
}
}
route = new Route(target, bankBoothCluster, suitableTreeClusters);
queueAction(startAction);
return true;
}
}
/**
* Equips a tool.
*/
class EquipToolAction implements Action {
/**
* The tool to equip.
*/
Tool tool;
EquipToolAction(Tool tool) {
this.tool = tool;
}
@Override
public boolean call() {
// Check if the tool is the inventory, if so then equip it
if(getInventory().contains(tool.getName())) {
getInventory().interact(tool.getName(), "Wield");
}
return getEquipment().contains(tool.getName());
}
}
/**
* Grabs the optimal tool from the bank.
*/
class GrabToolAction implements Action {
/**
* The tool to grab from the bank.
*/
Tool tool;
@Override
public boolean call() {
// Open the bank
getBank().open();
if(tool == null) {
// Check if the bank has any of the valid tools for the players woodcutting level
List<Tool> validTools = Tool.getValidTools(getSkills());
List<String> toolNames = validTools.stream().map(tool -> tool.getName()).collect(Collectors.toList());
boolean validTool = getBank().contains(item -> toolNames.contains(item.getName()));
if (!validTool) {
log("You do not have a valid tool which we can withdraw from the bank! Either your level is too low, or you do not have any");
stop();
return false;
}
// Sort tools by their descending level and if they exist in the bank
PriorityQueue<Tool> priority = new PriorityQueue<>((first, second) -> Integer.compare(first.getWoodcuttingLevel(), second.getWoodcuttingLevel()));
priority.addAll(validTools.stream().filter(tool -> getBank().contains(tool.getName())).collect(Collectors.toList()));
tool = priority.poll();
// Edge case, should not happen but just in case
if (tool == null) {
throw new IllegalArgumentException("Panic! Could not find tool");
}
}
// Attempt to deposit all the items in the inventory
if (!getInventory().isEmpty()) {
getBank().depositAllItems();
}
// Withdraw the tool from the bank, first scroll to it however
if(!getBank().isSlotVisible(getBank().get(tool.getName()))) {
getBank().scroll(tool.getName());
}
// Withdraw the tool from the bank
getBank().withdraw(tool.getName());
// We are done once the inventory contains the tool
if (!getInventory().contains(tool.getName())) {
return false;
}
// Close the bank
getBank().close();
queueAction(startAction);
return true;
}
}
class SearchClustersAction implements Action {
@Override
public boolean call() {
// Search for the closest available resource
Tile tile = route.getClosestAvailableResource();
if(tile == null) return false;
// Get the game object at the tile for the route target
GameObject gameObject = getObjectAtTile(tile, obj -> obj.getName().equals(route.getResourceTargetName()));
if(gameObject == null) return false;
// Calculate a path to the target if its out of our range
if(getDistance(getLocalPlayer().getTile(), tile) > 5) {
queueAction(new WalkPathAction(getWalking()
.getAStarPathFinder()
.calculate(getLocalPlayer().getTile(), tile)));
}
queueAction(new CollectFromResourceAction(gameObject));
return true;
}
}
class CollectFromResourceAction implements Action {
private GameObject resource;
CollectFromResourceAction(GameObject resource) {
this.resource = resource;
}
@Override
public boolean call() {
if(getInventory().isFull()) {
queueAction(new WalkPathAction(route.getRandomPathToBankBooth()));
queueAction(new DepositItemsAction());
queueAction(startAction);
return true;
}
if(hasObjectAtTile(resource.getTile(), obj -> obj.getName().equals("Tree Stump"))) {
queueAction(new SearchClustersAction());
return true;
}
// Just assume that since the player isn't animated that we need to interact with the resource x)
if(!getLocalPlayer().isAnimating()) {
getCamera().rotateToEntity(resource);
resource.interact("Chop down");
}
return false;
}
}
class DepositItemsAction implements Action {
@Override
public boolean call() {
// Open the bank
getBank().open();
// Deposit all of the items except for the tools we may be using
List<String> toolNames = Tool.getNames();
getBank().depositAllExcept(item -> toolNames.contains(item.getName()));
// Close the bank
getBank().close();
return true;
}
}
/**
* The action queue.
*/
private Queue<Action> actionQueue = new ArrayDeque<>();
/**
* The delay in ticks for the next action to be called.
*/
private int delay = -1;
/**
* The current loop cycle.
*/
private int loopCycle = 0;
/**
* The current route.
*/
private Route route;
/**
* The starting action. Performs the check to see if the player has the required tool to continue,
* if not then queues actions to grab the tool and move to the closest area to start collecting resources.
*/
Action startAction = () -> {
if(!hasValidTool()) {
log("Your character does not have the required tool to start this script, routing to nearest bank booth");
// Check if the player is nearby the bank booths, if not then we can just assume to route to the closest bank booth
if(!route.isNearbyBankBooths(5)) {
queueAction(new WalkPathAction(route.getClosestPathToBankBooth()));
}
queueAction(new GrabToolAction());
return true;
}
// Equip the optimal tool if possible
Tool tool = getOptimalTool();
if(tool != null) {
// Check if we can equip the item
if (getSkills().getRealLevel(Skill.ATTACK) >= tool.getAttackLevel()) {
queueAction(new EquipToolAction(tool));
}
}
// Walk the closest path to the clusters
queueAction(new WalkPathAction(route.getClosestPathToClusters()));
queueAction(new SearchClustersAction());
return true;
};
@Override
public void onStart() {
// Queue the action to determine the route
queueAction(new DetermineRouteAction(Tree.WILLOW));
}
@Override
public int onLoop() {
loopCycle++;
// Execute every tick, or what we'd consider a tick its in the time frame of a tick
if((loopCycle % 30) == 0) {
if (delay <= 0) {
if(!actionQueue.isEmpty()) {
do {
// Call the current action in the action queue, if the action
// is finished poll it from the queue
Action action = actionQueue.peek();
if (action.call()) actionQueue.poll();
else break;
} while(true);
} else {
log("Panic! There are currently no pending actions");
}
} else {
delay--;
}
}
// Wait the amount of time between loop cycles
int delta = 1000 / FPS;
return delta;
}
/***
* Queues an action for execution.
*
* @param action the action.
*/
private void queueAction(Action action) {
actionQueue.add(action);
}
/**
* Gets the optimal tool to use from the inventory.
*
* @return the optimal tool.
*/
private Tool getOptimalTool() {
List<Tool> validTools = Tool.getValidTools(getSkills());
// Sort tools by their descending level and if they exist in the equipment or inventory
PriorityQueue<Tool> priority = new PriorityQueue<>((first, second) -> Integer.compare(first.getWoodcuttingLevel(), second.getWoodcuttingLevel()));
priority.addAll(validTools.stream().filter(tool -> getInventory().contains(tool.getName()) || getEquipment().contains(tool.getName())).collect(Collectors.toList()));
return priority.poll();
}
/**
* Gets if the player has a valid tool fir their level.
*
* @return if the player has a valid tool in either their equipment or inventory.
*/
private boolean hasValidTool() {
List<Tool> validTools = Tool.getValidTools(getSkills());
List<String> toolNames = validTools.stream().map(tool -> tool.getName()).collect(Collectors.toList());
return getEquipment().contains(item -> toolNames.contains(item.getName())) || getInventory().contains(item -> toolNames.contains(item.getName()));
}
/**
* Gets all the trees by a certain name.
*
* @param name the name of the tree.
* @return the list of trees.
*/
private List<GameObject> getTrees(String name) {
return getGameObjects().all(obj -> name.equals(obj.getName()) && obj.hasAction("Chop down"));
}
/**
* Gets if an object exists at a tile with the specified filter.
*
* @param tile the tile.
* @param predicate the game object predicate.
* @return if the object exists at the tile for the specified filter.
*/
private boolean hasObjectAtTile(Tile tile, Predicate<GameObject> predicate) {
return Stream.of(getGameObjects().getObjectsOnTile(tile)).anyMatch(obj -> predicate.test(obj));
}
/**
*
* @param tile
* @param predicate
* @return
*/
private GameObject getObjectAtTile(Tile tile, Predicate<GameObject> predicate) {
return Stream.of(getGameObjects().getObjectsOnTile(tile)).filter(obj -> predicate.test(obj)).findFirst().orElse(null);
}
/**
* Gets the amount of accessible trees for a cluster.
*
* @param cluster the cluster of points to check.
* @return the number of accessable trees.
*/
private int getAccessibleTrees(Cluster cluster) {
return (int) cluster.getPoints().stream().map(point -> !hasObjectAtTile(point, obj -> obj.getName().equals("Tree Stump"))).count();
}
/**
* Gets the rectangular distance between two tiles.
*
* @param first the first tile.
* @param second the second tile.
* @return the distance in tiles.
*/
private int getDistance(Tile first, Tile second) {
return getDistance(first.getX(), first.getY(), second.getX(), second.getY());
}
/**
* Gets the rectangular distance between two points.
*
* @param x0 the first x coordinate.
* @param y0 the first y coordinate.
* @param x1 the second x coordinate.
* @param y1 the second y coordinate.
* @return the distance in tiles.
*/
private int getDistance(int x0, int y0, int x1, int y1) {
return Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0));
}
/**
* Creates a list of point clusters from game objects.
*
* @param objects the objects to create the clusters from.
* @param distanceLimit the distance limit of each cluster.
* @param sizeLimit the size limit of each cluster.
* @return the clusters.
*/
private List<Cluster> getClusters(List<GameObject> objects, int distanceLimit, int sizeLimit) {
List<Cluster> clusters = new ArrayList<>();
List<GameObject> copy = new ArrayList<>(objects);
while (!copy.isEmpty()) {
// Remove the first element of the list to create a new cluster from
Cluster cluster = new Cluster();
GameObject first = copy.remove(0);
cluster.add(first.getTile());
Iterator<GameObject> iterator = copy.iterator();
while (iterator.hasNext()) {
GameObject object = iterator.next();
for(Tile compare : cluster.getPoints()) {
// Check to see if the object is within the threshold distance
int distance = getDistance(compare.getX(), compare.getY(), object.getX(), object.getY());
if(distance <= distanceLimit) {
cluster.add(object.getTile());
iterator.remove();
break;
}
}
// If we pass the threshold size then move on to start a new cluster
if(cluster.size() >= sizeLimit) {
break;
}
}
clusters.add(cluster);
}
return clusters;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment