diff --git a/src/main/kotlin/Agent.kt b/src/main/kotlin/Agent.kt index 8f59b1a..29c8dd7 100644 --- a/src/main/kotlin/Agent.kt +++ b/src/main/kotlin/Agent.kt @@ -1,29 +1,80 @@ import java.awt.Point -import java.awt.Robot import java.awt.event.KeyEvent -import kotlin.math.ceil +/** + * Agent represents an autonomous agent that can simulate mouse and keyboard actions. + * + * This class provides methods for the agent to perform actions like moving the mouse, + * clicking, pressing keys, prompting the user, and looping through repetitive tasks. + * + * The Agent uses a Doer instance internally to execute low-level keyboard and mouse operations. + * + * @property doer The Doer used by this Agent for simulating user input. + */ class Agent { companion object { + /** + * Constant for the F6 key code. + */ const val F6 = KeyEvent.VK_F6 + + /** + * Constant for the Spacebar key code. + */ const val Space = KeyEvent.VK_SPACE + + /** + * Constant for the Minus/Dash key code. + */ const val Minus = KeyEvent.VK_MINUS } + /** + * The Doer instance used by this Agent for simulated mouse and keyboard actions. + */ private val doer = Doer() + /** + * Calculates the total number of steps needed to process the given volume. + * + * @param totalVolume The total volume that needs to be processed. + * @param volumePerStep The volume that can be processed per step. + * + * @return The computed total steps based on the total volume and volume per step. + * + * This method calculates the total steps by dividing the total volume by the volume per step, + * rounding up to account for any remainder/partial volume, and adding 1 to get an inclusive count. + * + * For example, with totalVolume = 101 and volumePerStep = 20, it would return 6 total steps. + */ + private fun computeTotalSteps(totalVolume: Int, volumePerStep: Int) = + totalVolume / volumePerStep + 1 + + /** + * Performs a loop to repeatedly execute a task for automated steps. + * + * @param totalVolume The total number of units to process across all steps. + * @param volumePerStep The number of units to process per step. + * @param task The task function to execute on each step. It receives the Agent. + * + * This method handles iterating through the steps, reporting progress, + * timing the overall execution, and calling the task on each iteration. + * + * It uses computeTotalSteps to calculate how many iterations needed based on total + * and per step volumes. The task typically simulates some action like banking. + */ fun doLoop(totalVolume: Int, volumePerStep: Int, task: (Agent) -> Unit) { - require(totalVolume>0){ + require(totalVolume > 0) { "You must make at least 1 thing in total" } - require(volumePerStep>0){ + require(volumePerStep > 0) { "You must consume at least 1 thing per step" } - fun computeTotalSteps(totalVolume: Int, volumePerStep: Int) = (ceil((totalVolume / volumePerStep).toDouble()) + 1).toInt() - val totalSteps =computeTotalSteps(totalVolume, volumePerStep) + + val totalSteps = computeTotalSteps(totalVolume, volumePerStep) val start = System.currentTimeMillis() for (i in 0 until totalSteps) { @@ -35,11 +86,39 @@ class Agent { println("Finished everything in ${prettyTimeString(finish - start)}") } + /** + * Prints a progress report for the current step. + * + * @param step The current step number. + * @param of The total number of steps. + * @param dur The duration in milliseconds so far. + * + * This method prints a progress report to the console in the format: + * "Step {step} of {of} ({formattedDuration} complete | ~{remainingTime} remaining)" + * + * It calculates the estimated remaining time based on the current duration and + * number of steps completed vs total steps. + * + * The output is printed on the same line to dynamically update the progress. + */ fun report(step: Int, of: Int, dur: Long) { val remaining = (dur / step) * (of - step) print("\rStep $step of $of (${prettyTimeString(dur)} complete\t|\t~${prettyTimeString(remaining)} remaining) ") } + /** + * Formats the given duration in milliseconds into a human readable time string. + * + * @param durationMillis The duration to format in milliseconds. + * + * @return A formatted time string showing the duration broken down into hours, minutes, and seconds. + * + * This converts the duration into hours, minutes, and seconds based on millis per second, + * minute, and hour constants. It returns a string in the format "XhYmZs" where X, Y, and Z are + * the calculated hours, minutes, and seconds. + * + * If durationMillis is 0, it returns "No time data yet" instead. + */ fun prettyTimeString(durationMillis: Long): String { if (durationMillis == 0L) { return "No time data yet" @@ -56,11 +135,25 @@ class Agent { } } + /** + * Prompts the user to position the mouse pointer and returns the pointer location. + * + * @param prompt The message to display to the user as a prompt. + * + * @return The Point containing the mouse pointer location after prompting. + * + * This first prints the prompt message to the console. + * It then uses the Doer to count down from 5 seconds while printing a countdown. + * After the countdown, it gets the current mouse pointer location using the Doer + * and returns the Point. + * + * This allows prompting the user to move the mouse before taking a snapshot of the pointer position. + */ fun promptUserForPoint(prompt: String): Point { println(prompt) - doer.countDown(5){ + doer.countDown(5) { print("\rtaking point snapshot in $it... ") - if(it == 0){ + if (it == 0) { println("\r ") } } @@ -68,17 +161,51 @@ class Agent { return doer.getPointerLocation() } - fun scrollOutToHeight(height: Int){ + /** + * Scrolls out to the specified height by scrolling in and then back out. + * + * @param height The height in pixels to scroll out to. + * @param scrollWaitAndVariance The number of milliseconds to wait between scroll steps. Defaults to 16. + * + * This method first sleeps for 1 second. It then scrolls in by the height amount + * using repeated small scroll in steps. + * + * After scrolling in fully, it then scrolls back out using a few repeated small scroll outs. + * + * This allows smoothly animating the scroll all the way in and then back out to a specific height + * + * The scrollWaitAndVariance controls the pacing between scroll steps. The default of 16ms provides + * a reasonable scroll animation speed. + */ + fun scrollOutToHeight(height: Int, scrollWaitAndVariance: Long = 10L) { doer.sleep(1000) - for(i in 0 until height){ - doer.scrollIn(16, 16) + for (i in 0 until height*2) { + doer.scrollIn(scrollWaitAndVariance, scrollWaitAndVariance) } - for(i in 0 until 8){ - doer.scrollOut(16, 16) + + for (i in 0 until height) { + doer.scrollOut(scrollWaitAndVariance, scrollWaitAndVariance) } } + /** + * Performs automated banking steps in a loop at the given chest location. + * + * @param chest The Point location of the bank chest to click on. + * @param invHotkey The key for the inventory preset to withdraw. + * @param hotbarHotkey The key for the crafting preset to open. + * @param waitDurationMillis The time in ms to wait at each loop iteration. + * @param waitDurationVariance Random variance to add to wait time. + * + * This handles the steps to open the bank interface, withdraw a preset, + * open a crafting interface, and wait for the desired duration. + * + * It clicks the bank chest, presses the inventory hotkey, presses the crafting hotkey, + * presses spacebar to accept, and waits before repeating. + * + * Useful for automated banking behaviors like crafting or herblore. + */ fun bankStandForLoop( chest: Point, invHotkey: Int, @@ -102,6 +229,21 @@ class Agent { doer.sleep(waitDurationMillis, waitDurationVariance) } + /** + * Performs banking actions at a bank without additional dialog prompts. + * + * @param chest The Point location of the bank chest or stand to interact with. + * @param invKey The key code for the inventory preset hotkey to withdraw. + * @param hotKey The key code for the action hotkey, like crafting. + * + * This method handles clicking the chest, withdrawing a preset inventory, + * and activating a process like crafting that doesn't require additional prompts. + * + * It clicks the chest location, presses the inventory preset hotkey, waits briefly, + * then presses the action hotkey like crafting. This allows automated crafting at the bank. + * + * The sleeps provide a brief pause between actions to allow animations. + */ fun bankStandWithoutDialog(chest: Point, invKey: Int, hotKey: Int) { //open the bank located by the chest parameter doer.moveMouseLeftClickAndSleep(doer.getAlmostPoint(chest, Doer.WiggleParams()), 900, 400) @@ -116,6 +258,24 @@ class Agent { doer.sleepForNTicks(1) } + /** + * Performs processing steps between a bank and nearby crafting station. + * + * @param chest The Point location of the bank chest. + * @param station The Point location of the processing station. + * @param invHotkey The inventory preset hotkey to withdraw at bank. + * @param travelDurationMillis Base travel time between bank and station. + * @param travelDurationVariance Random variance to add to travel time. + * @param waitDurationMillis Base wait time for processing at station. + * @param waitDurationVariance Random variance to add to wait time. + * + * This handles the steps to go to the bank, withdraw a preset, go to the station, + * open the processing interface, wait, and then loop. + * + * It goes between the bank and station locations, simulating travel time. + * At the bank it withdraws using the preset hotkey. + * At the station it activates processing and waits. + */ fun processAtStationNearBank( chest: Point, station: Point, @@ -126,7 +286,11 @@ class Agent { waitDurationVariance: Long ) { //move to the bank and open the interface - doer.moveMouseLeftClickAndSleep(doer.getAlmostPoint(chest, Doer.WiggleParams()), travelDurationMillis, travelDurationVariance) + doer.moveMouseLeftClickAndSleep( + doer.getAlmostPoint(chest, Doer.WiggleParams()), + travelDurationMillis, + travelDurationVariance + ) //withdraw desired loadout doer.keypress(invHotkey) @@ -145,7 +309,18 @@ class Agent { /*============================================================================================================== cheater functions ==============================================================================================================*/ - fun getBankPoint():Point{ + + /** + * Prompts the user to position the mouse over the bank and returns the pointer location. + * + * @return The Point containing the mouse pointer location over the bank. + * + * This first prompts the user with a message to hold the mouse over the bank. + * It then uses promptUserForPoint() to display a countdown and get the mouse position. + * + * The returned Point can be used as the bank location for automated banking functions. + */ + fun getBankPoint(): Point { return promptUserForPoint("Hold your mouse over the bank...") } } \ No newline at end of file diff --git a/src/main/kotlin/Doer.kt b/src/main/kotlin/Doer.kt index 93755d9..7da505b 100644 --- a/src/main/kotlin/Doer.kt +++ b/src/main/kotlin/Doer.kt @@ -32,37 +32,31 @@ import kotlin.random.Random * * @constructor Creates a Doer instance with a Robot. */ -class Doer { +class Doer(private val robot: Robot = Robot()) { /** - * The Robot instance used to perform mouse and keyboard actions. + * The duration in milliseconds of one "tick". The duration of 600ms matches the tick duration of game servers. + * + * This defines the concept of a "tick" as a unit of time used for pacing actions. + * + * It is used in methods like [sleepForNTicks] to calculate sleep durations + * based on multiplying a number of ticks by this value. + * + * For example, 5 ticks with this value would be 5 * 600 = 3000ms sleep duration. */ - private val robot = Robot() - companion object { - /** - * The duration in milliseconds of one "tick". The duration of 600ms matches the tick duration of game servers. - * - * This defines the concept of a "tick" as a unit of time used for pacing actions. - * - * It is used in methods like [sleepForNTicks] to calculate sleep durations - * based on multiplying a number of ticks by this value. - * - * For example, 5 ticks with this value would be 5 * 600 = 3000ms sleep duration. - */ - const val TICK_DURATION_MS = 600L + private val TICK_DURATION_MS = 600L - /** - * Extra padding in milliseconds added before actions to account for latency. 500ms is entirely arbitrary. It is - * simply a value that works well during high-load periods. Better to be conservative than lossy. - * - * This defines an extra duration in milliseconds that is added to sleeps - * and waits. - * - * It is to account for latency in the system before actions like mouse moves - * and clicks actually take effect. - */ - const val LATENCY_PADDING_MS = 500L - } + /** + * Extra padding in milliseconds added before actions to account for latency. 500ms is entirely arbitrary. It is + * simply a value that works well during high-load periods. Better to be conservative than lossy. + * + * This defines an extra duration in milliseconds that is added to sleeps + * and waits. + * + * It is to account for latency in the system before actions like mouse moves + * and clicks actually take effect. + */ + private val LATENCY_PADDING_MS = 500L /** * Data class to hold parameters for random wiggle amounts when moving the mouse. This is useful for avoiding diff --git a/src/main/kotlin/Routines.kt b/src/main/kotlin/Routines.kt index ab96d00..5436e98 100644 --- a/src/main/kotlin/Routines.kt +++ b/src/main/kotlin/Routines.kt @@ -3,38 +3,61 @@ import java.awt.event.KeyEvent object Routines { - fun fullRunIncense(volHerbs: Int, volLogs: Int, volAshes: Int, volCleanHerbs: Int){ + /** + * Performs the full incense stick crafting process from start to finish. + * + * @param volHerbs The number of grimy herbs to clean. + * @param volLogs The number of magic logs to cut into sticks. + * @param volAshes The number of sticks to coat in ashes. + * @param volCleanHerbs The number of clean herbs to infuse into sticks. + * + * This handles the entire incense stick crafting process: + * - Cleaning grimy herbs + * - Cutting magic logs into sticks + * - Coating sticks in ashes + * - Infusing clean herbs into sticks + * + * It loops through each step based on the provided volumes, performing the + * actions at the bank. The bank location is prompted from the user. + * + * Progress is printed after each step. Total elapsed time is printed at the end. + */ + fun fullRunIncense(volHerbs: Int, volLogs: Int, volAshes: Int, volCleanHerbs: Int) { val agent = Agent() val start = System.currentTimeMillis() val chest = agent.getBankPoint() - //process any grimy herbs we might need - if(volHerbs > 0){ - agent.doLoop(volHerbs, 28){ + // Loop to clean grimy herbs: + // Withdraw herb preset, clean without dialog at bank + if (volHerbs > 0) { + agent.doLoop(volHerbs, 28) { agent.bankStandWithoutDialog(chest, KeyEvent.VK_F1, KeyEvent.VK_1) } } println("\rHerbs cleaned") - //cut up any magic logs we might need - if(volLogs > 0){ - agent.doLoop(volLogs, 26){ + // Loop to cut magic logs into sticks: + // Withdraw log preset, cut logs using hotkey at bank + if (volLogs > 0) { + agent.doLoop(volLogs, 26) { agent.bankStandForLoop(chest, KeyEvent.VK_F2, KeyEvent.VK_2, 26000, 1200) } } println("\rLogs cut into sticks") - //coat any sticks - if(volAshes > 0){ - agent.doLoop(volAshes, 26){ + // Loop to coat sticks in ashes: + // Withdraw ash preset, coat sticks using hotkey at bank + if (volAshes > 0) { + agent.doLoop(volAshes, 26) { agent.bankStandForLoop(chest, KeyEvent.VK_F3, KeyEvent.VK_3, 21000, 600) } } println("\rSticks coated in ashes") - //infuse any sticks - if(volCleanHerbs > 0){ - agent.doLoop(volCleanHerbs, 27){ + // Loop to infuse clean herbs into sticks: +// Withdraw herb preset, infuse sticks using hotkey at bank + if (volCleanHerbs > 0) { + agent.doLoop(volCleanHerbs, 27) { agent.bankStandForLoop(chest, KeyEvent.VK_F4, KeyEvent.VK_4, 48600, 600) } } @@ -42,101 +65,243 @@ object Routines { val finish = System.currentTimeMillis() - println("Entire chain finished in ${agent.prettyTimeString(finish-start)}") + println("Entire chain finished in ${agent.prettyTimeString(finish - start)}") } - fun cleanHerbs(volume: Int, step: Int = 14){ + /** + * Cleans a volume of grimy herbs at the bank. + * + * @param totalVolume The total number of grimy herbs to clean. + * @param volumePerStep The number of herbs to clean per loop iteration. + * + * This handles cleaning the given total volume of grimy herbs by looping + * and cleaning the volumePerStep amount each iteration at the bank. + * + * It gets the bank location point from the user with a prompt. + * + * Inside the loop, it withdraws the herb cleaning preset and activates + * the cleaning without additional dialog boxes. + */ + fun cleanHerbs(totalVolume: Int, volumePerStep: Int = 14) { val agent = Agent() val chest = agent.getBankPoint() - agent.doLoop(volume, step){ + agent.doLoop(totalVolume, volumePerStep) { agent.bankStandWithoutDialog(chest, KeyEvent.VK_F1, KeyEvent.VK_1) } } - fun cutIncenseSticks(volume: Int, step: Int = 28){ + + + /** + * Cuts a specified total volume of magic logs into incense sticks at the bank. + * + * @param totalVolume the total number of magic logs to cut into sticks + * @param volumePerStep the number of logs to cut per loop iteration. Default is 28. + * + * This handles cutting the given total volume of logs into sticks by looping + * and cutting the volumePerStep amount each iteration at the bank. + * + * It gets the bank location point by prompting the user. + * + * Inside the loop, it withdraws the log cutting preset and cuts the logs into + * sticks using the specified hotkeys. + */ + fun cutIncenseSticks(totalVolume: Int, volumePerStep: Int = 28) { val agent = Agent() val chest = agent.getBankPoint() - agent.doLoop(volume, step){ + agent.doLoop(totalVolume, volumePerStep) { agent.bankStandForLoop(chest, KeyEvent.VK_F2, KeyEvent.VK_2, 26000, 1200) } } - fun coatIncenseSticks(volume: Int, step: Int = 26){ + + + /** + * Coats a specified number of incense sticks in ashes at the bank. + * + * @param totalVolume the total number of sticks to coat in ashes + * @param volumePerStep the number of sticks to coat per loop iteration. Default is 26. + * + * This handles coating the given total number of sticks in ashes by looping + * and coating volumePerStep sticks each iteration at the bank. + * + * It gets the bank location point from the Agent. + * + * Inside the loop, it withdraws the ash coating preset and coats the sticks + * using the specified hotkeys. + */ + fun coatIncenseSticks(totalVolume: Int, volumePerStep: Int = 26) { val agent = Agent() val chest = agent.getBankPoint() - agent.doLoop(volume, step){ + agent.doLoop(totalVolume, volumePerStep) { agent.bankStandForLoop(chest, KeyEvent.VK_F3, KeyEvent.VK_3, 21000, 600) } } - fun infuseIncenseSticks(volume: Int, step: Int = 27 ){ + + /** + * Infuses a specified number of incense sticks with clean herbs at the bank. + * + * @param totalVolume the total number of sticks to infuse with herbs + * @param volumePerStep the number of sticks to infuse per loop iteration. Default is 27. + * + * This handles infusing the given total number of sticks with clean herbs by looping + * and infusing volumePerStep sticks each iteration at the bank. + * + * It gets the bank location point from the Agent. + * + * Inside the loop, it withdraws the herb infusing preset and infuses the sticks + * using the specified hotkeys. + */ + fun infuseIncenseSticks(totalVolume: Int, volumePerStep: Int = 27) { val agent = Agent() val chest = agent.getBankPoint() - agent.doLoop(volume, step){ + agent.doLoop(totalVolume, volumePerStep) { agent.bankStandForLoop(chest, KeyEvent.VK_F4, KeyEvent.VK_4, 48600, 600) } } - fun potionGrind(volume: Int, step: Int = 14){ + /** + * Crafts potions at the bank in a loop. + * + * @param totalVolume the total number of potions to craft + * @param volumePerStep the number of potions to craft per loop iteration. Default is 14. + * + * This handles crafting the given total volume of potions by looping + * and crafting the volumePerStep amount each iteration at the bank. + * + * It gets the bank location point by prompting the user. + * + * Inside the loop, it withdraws the potion crafting preset and crafts + * using the crafting hotkey which doesn't produce a dialogue box. + */ + fun craftPotionAtBank(totalVolume: Int, volumePerStep: Int = 14) { val agent = Agent() val chest = agent.getBankPoint() - agent.doLoop(volume, step){ + agent.doLoop(totalVolume, volumePerStep) { agent.bankStandForLoop(chest, Agent.F6, Agent.Minus, 19200, 600) } } - fun potionGrindWithWell(volume: Int, step: Int = 14){ + /** + * Grinds a total volume of potions using a well near the bank. + * + * @param totalVolume the total number of potions to grind + * @param volumePerStep the number of potions to grind per loop iteration. Default is 14. + * @param travelDurationInMillis the time in milliseconds for the agent to travel between the bank and the well. + * Default is 0. + * + + * This handles crafting the given total volume of potions by looping accounting for the crafting of volumePerStep + * potions amount each iteration using a well near the bank. + * + * It gets the bank location point and well location point by prompting the user. + * + * Inside the loop, it withdraws the potion grinding preset and creates potions using the well. + * + * After creating the potions, it banks again before the next iteration. + * + * The travelDurationInMillis parameter allows configuring the travel time between the bank and the well. + */ + fun potionGrindWithWell(totalVolume: Int, volumePerStep: Int = 14, travelDurationInMillis: Long = 0) { val agent = Agent() val chest = agent.getBankPoint() val well = agent.promptUserForPoint("Put your mouse over the well...") - agent.doLoop(volume, step){ - agent.processAtStationNearBank(chest, well, Agent.F6, 0, 0, 18600, 600) + agent.doLoop(totalVolume, volumePerStep) { + agent.processAtStationNearBank(chest, well, Agent.F6, travelDurationInMillis, 0, 18600, 600) } } - fun supremeOverloadGrind(volume: Int, step: Int = 4){ + + /** + * Grinds a total volume of supreme overload potions using a well near the bank. + * + * @param totalVolume the total number of potions to grind + * @param volumePerStep the number of potions to grind per loop iteration. Default is 4. + * + * This handles crafting the given total volume of supreme overload potions by looping + * and accounting for producing volumePerStep amount of potions each iteration using a well near the bank. + * + * It gets the bank location point and well location point by prompting the user. + * + * Inside the loop, it withdraws the potion grinding preset and grinds + * using the well. + * + * After crafting finishes, it banks again before the next iteration. + */ + fun supremeOverloadGrind(totalVolume: Int, volumePerStep: Int = 4) { val agent = Agent() val chest = agent.getBankPoint() val well = agent.promptUserForPoint("Put your mouse over the well...") - agent.doLoop(volume, step){ + agent.doLoop(totalVolume, volumePerStep) { agent.processAtStationNearBank(chest, well, Agent.F6, 0, 0, 6500, 1000) } } - fun processInventoryAtFurnace(volume: Int, step: Int = 28){ + /** + * Processes a total volume of inventory at a furnace near the bank. + * + * @param totalVolume the total number of inventory items to process + * @param volumePerStep the number of items to process per loop iteration. Default is 28. + * + * This handles processing the given total volume of inventory by looping + * and processing volumePerStep items each iteration at a furnace near the bank. + * + * It uses pre-defined points for the furnace location relative to the bank chest. + * + * Inside the loop, it travels to the furnace, processes the items using the + * process hotkey, and travels back to the bank before the next iteration. + * + * The camera is reset before starting. + */ + fun processInventoryAtFurnace(totalVolume: Int, volumePerStep: Int = 28) { val agent = Agent() - println("Reset the camera and zoom in") - agent.scrollOutToHeight(2) + println("Reset the camera") + agent.scrollOutToHeight(8) val furnaceFromChest = Point(776, 321) val chestFromFurance = Point(1713, 843) - agent.doLoop(volume, step){ + agent.doLoop(totalVolume, volumePerStep) { agent.processAtStationNearBank(chestFromFurance, furnaceFromChest, Agent.F6, 2500, 600, 53200, 600) } - - } + /** + * Processes refined planks into frames at a sawmill near the bank. + * + * @param totalVolume the total number of planks to process into frames + * @param volumePerStep the number of planks to process per loop iteration + * + * This handles processing the given total volume of refined planks into frames + * by looping and processing volumePerStep planks each iteration at a sawmill near the bank. + * + * It uses pre-defined points for the sawmill location relative to the bank chest. + * + * Inside the loop, it travels to the sawmill, processes the planks into frames + * using the process hotkey, and travels back to the bank before the next iteration. + * + * @deprecated Needs validation before using for real, as sawmill points are hardcoded + */ @Deprecated("Needs validation before you use it for realsies") - fun processRefinedPlanksIntoFrames(volume: Int, step: Int){ + fun processRefinedPlanksIntoFrames(totalVolume: Int, volumePerStep: Int) { println("You must zoom in all the way and have the compass pointing straight N with a completely top-down view\n you must also be standing at the touch point on the saw") val agent = Agent() agent.scrollOutToHeight(8) val sawFromChest = Point(1079, 907) val chestFromSaw = Point(1226, 180) - agent.doLoop(volume, step){ + agent.doLoop(totalVolume, volumePerStep) { // //standing at bank with full inv // d.mouseMove(sawFromChest) // d.sleep(100, 50)