From 2cb6d3aed3c73631b0b94b59a0297b2d1a19228b Mon Sep 17 00:00:00 2001 From: dtookey Date: Sun, 6 Aug 2023 19:21:53 -0400 Subject: [PATCH] Another massive architecture refactor --- src/main/kotlin/Agent.kt | 431 ------------------ .../{RobotController.kt => Controllers.kt} | 25 +- src/main/kotlin/HelperFunctions.kt | 66 +++ src/main/kotlin/Orchestrator.kt | 58 +++ src/main/kotlin/RSLogic.kt | 322 +++++++++++++ src/main/kotlin/Routines.kt | 72 +-- src/main/kotlin/TaskParams.kt | 3 - 7 files changed, 501 insertions(+), 476 deletions(-) delete mode 100644 src/main/kotlin/Agent.kt rename src/main/kotlin/{RobotController.kt => Controllers.kt} (90%) create mode 100644 src/main/kotlin/HelperFunctions.kt create mode 100644 src/main/kotlin/Orchestrator.kt create mode 100644 src/main/kotlin/RSLogic.kt diff --git a/src/main/kotlin/Agent.kt b/src/main/kotlin/Agent.kt deleted file mode 100644 index 1981b23..0000000 --- a/src/main/kotlin/Agent.kt +++ /dev/null @@ -1,431 +0,0 @@ -import java.awt.Point -import java.awt.event.InputEvent -import java.awt.event.KeyEvent - -/** - * Agent handles lower level actions like banking, navigation, and timing - * to support automation routines. - * - * This provides a common set of primitive actions that can be used across - * different automation workflows. Routines build on top of these actions. - * - * The agent handles things like: - * - Bank interaction (presets, deposits, withdrawals) - * - Navigation points and travel - * - Timing actions with randomness - * - Progress reporting and logging - * - * By encapsulating these common actions, routines can focus on their - * specific workflow logic and leverage the agent for the lower level - * activities needed to craft, cook, etc. - */ -class Agent(val controller: Automaton = RobotController()) { - companion object { - - - /** - * 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 - - /** - * 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 - - /** - * Performs a standing crafting task loop for the agent. - * - * The agent will repeatedly go to the bank, withdraw items, - * open the crafting interface, craft items, close it, - * and deposit items back until the total volume is reached. - * - * @param params the parameters configuring how to perform the standing task - */ - fun doStandingTask(params: StandingTaskParams) { - val agent = params.agent - agent.doLoop(params.totalVolume, params.volumePerStep) { - agent.bankStandForLoop( - params.bankPoint, - params.bankPresetHotkey, - params.craftingDialogHotkey, - params.craftingWaitDurationMillis, - params.craftingWaitDurationVarianceMillis - ) - } - } - - /** - * Performs a looped travel task for the agent. - * - * This handles having the agent repeatedly travel from the bank - * to a station, process items, and travel back. - * - * @param params the parameters configuring how to perform the task - */ - fun doTravelTask(params: TravelTaskParams) { - val agent = params.agent - agent.doLoop(params.totalVolume, params.volumePerStep) { - agent.processAtStationNearBank( - params.bankPoint, - params.travelPoint, - params.bankPresetHotkey, - params.travelDurationMillis, - params.travelDurationVarianceMillis, - params.craftingWaitDurationMillis, - params.craftingWaitDurationVarianceMillis - ) - } - } - } - - - /** - * Computes the total number of steps needed to process the given total volume. - * - * @param totalVolume the total amount that needs to be processed - * @param volumePerStep the amount to process per step - * @return the number of steps required to process the total volume - */ - private fun computeTotalSteps(totalVolume: Int, volumePerStep: Int) = - totalVolume / volumePerStep + if (totalVolume % volumePerStep > 0) { - 1 - } else { - 0 - } - - - /** - * 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) { - "You must make at least 1 thing in total" - } - require(volumePerStep > 0) { - "You must consume at least 1 thing per step" - } - - - val totalSteps = computeTotalSteps(totalVolume, volumePerStep) - - val start = System.currentTimeMillis() - for (i in 0 until totalSteps) { - report(i + 1, totalSteps, System.currentTimeMillis() - start) - task(this) - } - println() - val finish = System.currentTimeMillis() - 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" - } - val millisPerSecond = 1000L - val millisPerMinute = 60L * millisPerSecond - val millisPerHour = 60L * millisPerMinute - return if (durationMillis > millisPerHour) { - return "${durationMillis / millisPerHour}h${(durationMillis % millisPerHour) / millisPerMinute}m${(durationMillis % millisPerMinute) / millisPerSecond}s" - } else if (durationMillis > millisPerMinute) { - return "${(durationMillis % millisPerHour) / millisPerMinute}m${(durationMillis % millisPerMinute) / millisPerSecond}s" - } else { - "${(durationMillis % millisPerMinute) / millisPerSecond}s" - } - } - - /** - * 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) - countDown(5) { - print("\rtaking point snapshot in $it... ") - if (it == 0) { - println("\r ") - } - } - - return controller.getPointerLocation() - } - - /** - * 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) { - controller.sleep(1000) - - for (i in 0 until height * 2) { - controller.scrollIn(scrollWaitAndVariance, scrollWaitAndVariance) - } - - for (i in 0 until height) { - controller.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 bankPresetHotkey The key for the inventory preset to withdraw. - * @param craftingDialogueHotkey 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. - */ - private fun bankStandForLoop( - chest: Point, - bankPresetHotkey: Int, - craftingDialogueHotkey: Int, - waitDurationMillis: Long, - waitDurationVariance: Long - ) { - //open the bank located by the chest parameter - moveMouseLeftClickAndSleep(controller.getAlmostPoint(chest, WiggleParams()), 900, 400) - //withdraw the desired inventory preset - controller.keyPress(bankPresetHotkey) - //sleep for a server tick - sleepForNTicks(1) - //open the crafting dialog with the correct hotkey - controller.keyPress(craftingDialogueHotkey) - //sleep for a server tick - sleepForNTicks(1) - //press the "accept" default hotkey - controller.keyPress(KeyEvent.VK_SPACE) - //wait for the desired time to finish - controller.sleepWithVariance(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 - moveMouseLeftClickAndSleep(controller.getAlmostPoint(chest, WiggleParams()), 900, 400) - //withdraw the desired inventory preset - controller.keyPress(invKey) - - sleepForNTicks(1) - - //press the hotkey that causes action without dialogue - controller.keyPress(hotKey) - - 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 bankPresetHotkey The inventory preset hotkey to withdraw at bank. - * @param travelDurationMillis Base travel time between bank and station. - * @param travelDurationVarianceMillis Random variance to add to travel time. - * @param waitDurationMillis Base wait time for processing at station. - * @param waitDurationVarianceMillis 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, - bankPresetHotkey: Int, - travelDurationMillis: Long, - travelDurationVarianceMillis: Long, - waitDurationMillis: Long, - waitDurationVarianceMillis: Long - ) { - //move to the bank and open the interface - moveMouseLeftClickAndSleep( - controller.getAlmostPoint(chest, WiggleParams()), - travelDurationMillis, - travelDurationVarianceMillis - ) - - //withdraw desired loadout - controller.keyPress(bankPresetHotkey) - sleepForNTicks(1) - - //move to station and open the crafting dialog - moveMouseLeftClickAndSleep(station, travelDurationMillis, travelDurationVarianceMillis) - - //start the crafting task - controller.keyPress(KeyEvent.VK_SPACE) - - //wait for it to complete - controller.sleepWithVariance(waitDurationMillis, waitDurationVarianceMillis) - } - - /*============================================================================================================== - cheater functions - ==============================================================================================================*/ - - - fun getBankPoint(): Point { - return promptUserForPoint("Hold your mouse over the bank...") - } - - - fun countDown(nSeconds: Int, announceFn: (step: Int) -> Unit) { - for (i in nSeconds downTo 0) { - announceFn(i) - controller.sleep(1000) - } - } - - fun getPointerLocationAfter(delayInSeconds: Int): Point { - countDown(delayInSeconds) { - print("\rtaking pointer snapshot in $it...") - if (it == 0) { - println("\r ") - } - } - return controller.getPointerLocation() - } - - fun getPointerLocationAsValDeclarationString(varName: String): String { - val info = getPointerLocationAfter(5) - return "val $varName = Point(${info.x}, ${info.y})" - } - - - - fun moveMouseLeftClickAndSleep(p: Point, dur: Long, durRange: Long) { - controller.moveMouse(p) - controller.sleepWithVariance(100, 50) - //left click - controller.mouseClick(InputEvent.BUTTON1_DOWN_MASK) - controller.sleepWithVariance(dur, durRange) - } - - fun sleepForNTicks(n: Long) { - val latencyPadding = LATENCY_PADDING_MS - val baseWaitTime = n * TICK_DURATION_MS - controller.sleepWithVariance(latencyPadding + baseWaitTime, 150) - } - - - fun drawStar(p: Point) { - val offset = 100 - val top = Point(p.x, p.y - offset * 2) - val topright = Point(p.x + offset * 2, p.y + offset) - val bottomright = Point(p.x + offset * 2, p.y) - val topleft = Point(p.x - offset * 2, p.y + offset) - val bottomleft = Point(p.x - offset * 2, p.y) - val points = arrayListOf(top, bottomleft, topright, topleft, bottomright) - for (i in 0 until 10) { - for (point in points) { - controller.moveMouse(point) - controller.sleep(32) - } - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/RobotController.kt b/src/main/kotlin/Controllers.kt similarity index 90% rename from src/main/kotlin/RobotController.kt rename to src/main/kotlin/Controllers.kt index 6317742..a574285 100644 --- a/src/main/kotlin/RobotController.kt +++ b/src/main/kotlin/Controllers.kt @@ -146,17 +146,26 @@ interface DesktopController { } /** - * Interface for controllers that provide desktop input capabilities. + * Interface for full-featured desktop automation controllers. * - * This interface extends [DesktopController], [InputController], and - * [TemporalController] to create a combined interface that can: + * Automaton combines capabilities from other interfaces to create a controller that can: * - * - Get desktop/mouse info like pointer location - * - Perform mouse and keyboard input like clicks and key presses - * - Control timing and add delays + * - Get desktop and mouse state information like pointer location via [DesktopController] * - * Classes that implement this interface can serve as full featured - * controllers for desktop automation tasks. + * - Perform mouse and keyboard input like clicks, key presses, and scrolling via [InputController] + * + * - Handle timing and delays between actions using [TemporalController] + * + * By composing multiple capabilities, Automaton aims to provide a simple yet powerful interface for implementing + * desktop automation routines. + * + * Typical usage involves: + * + * 1. Obtaining an Automaton instance bound to the current OS/desktop + * 2. Calling methods like [moveMouse] and [mouseClick] to perform actions + * 3. Using [sleep] and [sleepWithVariance] to add delays + * + * This interface allows the underlying OS/desktop implementation details to be abstracted and swapped as needed. */ interface Automaton : DesktopController, InputController, TemporalController diff --git a/src/main/kotlin/HelperFunctions.kt b/src/main/kotlin/HelperFunctions.kt new file mode 100644 index 0000000..817edb7 --- /dev/null +++ b/src/main/kotlin/HelperFunctions.kt @@ -0,0 +1,66 @@ +object HelperFunctions { + /** + * Computes the total number of steps needed to process the given total volume. + * + * @param totalVolume the total amount that needs to be processed + * @param volumePerStep the amount to process per step + * @return the number of steps required to process the total volume + */ + fun calculateTotalSteps(totalVolume: Int, volumePerStep: Int) = + totalVolume / volumePerStep + if (totalVolume % volumePerStep > 0) { + 1 + } else { + 0 + } + + /** + * 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" + } + val millisPerSecond = 1000L + val millisPerMinute = 60L * millisPerSecond + val millisPerHour = 60L * millisPerMinute + return if (durationMillis > millisPerHour) { + return "${durationMillis / millisPerHour}h${(durationMillis % millisPerHour) / millisPerMinute}m${(durationMillis % millisPerMinute) / millisPerSecond}s" + } else if (durationMillis > millisPerMinute) { + return "${(durationMillis % millisPerHour) / millisPerMinute}m${(durationMillis % millisPerMinute) / millisPerSecond}s" + } else { + "${(durationMillis % millisPerMinute) / millisPerSecond}s" + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/Orchestrator.kt b/src/main/kotlin/Orchestrator.kt new file mode 100644 index 0000000..33b079d --- /dev/null +++ b/src/main/kotlin/Orchestrator.kt @@ -0,0 +1,58 @@ +import java.awt.Point + +interface Orchestrator { + + val automaton: Automaton + + fun scrollOutToHeight(height: Int, scrollWaitAndVariance: Long = 10L) { + automaton.sleep(1000) + doLoop(height * 2, 1) { + automaton.scrollIn(scrollWaitAndVariance, scrollWaitAndVariance) + } + + doLoop(height, 1){ + automaton.scrollOut(scrollWaitAndVariance, scrollWaitAndVariance) + } + } + + + /** + * 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: (Orchestrator) -> Unit) { + require(totalVolume > 0) { + "You must make at least 1 thing in total" + } + require(volumePerStep > 0) { + "You must consume at least 1 thing per step" + } + + + val totalSteps = HelperFunctions.calculateTotalSteps(totalVolume, volumePerStep) + + val start = System.currentTimeMillis() + for (i in 0 until totalSteps) { + HelperFunctions.report(i + 1, totalSteps, System.currentTimeMillis() - start) + task(this) + } + println() + val finish = System.currentTimeMillis() + println("Finished everything in ${HelperFunctions.prettyTimeString(finish - start)}") + } + + fun promptUserForPoint(prompt: String): Point + + fun drawStar(p: Point) +} + + diff --git a/src/main/kotlin/RSLogic.kt b/src/main/kotlin/RSLogic.kt new file mode 100644 index 0000000..1e34e6b --- /dev/null +++ b/src/main/kotlin/RSLogic.kt @@ -0,0 +1,322 @@ +import java.awt.Point +import java.awt.event.InputEvent +import java.awt.event.KeyEvent + +/** + * Interface for coordinating RuneScape automation routines. + * + * This interface provides methods for common in-game workflows like: + * + * - Processing actions at the bank chest + * - Traveling between bank and crafting station + * - Bank standing without dialogue boxes + * + * Implementations would integrate with a desktop automation library + * to actually perform the in-game clicks and inputs. + * + * This interface allows the game-specific logic to be separated from + * the underlying automation library. + * + */ +interface RSCoordinator { + + /** + * Perform actions at the bank chest. + * + * This typically involves: + * 1. Clicking the chest to open bank interface + * 2. Pressing preset hotkey to withdraw items + * 3. Pressing crafting hotkey to start crafting workflow + * 4. Waiting for crafting duration + * + * @param bankPoint Point location of bank chest to click + * @param bankPresetHotkey Key code for bank preset withdraw action + * @param craftingDialogueHotkey Key code to select crafting dialogue + * @param waitDurationMillis Duration in ms to wait during crafting + * @param waitDurationVariance Allowed variance in wait duration + */ + fun processAtBank( + bankPoint: Point, + bankPresetHotkey: Int, + craftingDialogueHotkey: Int, + waitDurationMillis: Long, + waitDurationVariance: Long + ) + + /** + * Perform actions between bank and nearby station. + * + * This involves: + * 1. Traveling from bankPoint to stationPoint + * 2. Clicking station to open interface + * 3. Pressing dialogue key to start crafting + * 4. Waiting for crafting duration + * 5. Waiting for crafting duration + * + * @param bankPoint Point location of bank + * @param craftingStationPoint Point location of crafting station + * @param bankPresetHotkey Key code to withdraw items + * @param travelDurationMillis Time in ms to travel between points + * @param travelDurationVariance Allowed variance in travel time + * @param waitDurationMillis Crafting duration + * @param waitDurationVariance Variance in crafting duration + */ + fun processAtStationNearBank( + bankPoint: Point, + craftingStationPoint: Point, + bankPresetHotkey: Int, + travelDurationMillis: Long, + travelDurationVarianceMillis: Long, + waitDurationMillis: Long, + waitDurationVarianceMillis: Long + ) + + /** + * Perform bank standing without random events. + * + * This involves: + * 1. Clicking the bank chest at bankPoint + * 2. Pressing inventoryHotKey to load an inventory preset + * 3. Pressing actionBarHotKey to perform an action + * + * @param bankPoint Location of bank chest + * @param inventoryHotKey Key code to clear inventory + * @param actionBarHotKey Key code to clear action bar + */ + fun bankStandWithoutDialog(bankPoint: Point, inventoryHotKey: Int, actionBarHotKey: Int) + + fun getBankPoint(): Point +} + +/** + * Interface for orchestrating and coordinating RuneScape automation routines. + * + * This interface combines the orchestration capabilities from [Orchestrator] + * and the RuneScape coordination capabilities from [RSCoordinator]. + * + * An implementation would use the orchestration methods like [doLoop] to + * define the overall workflow. + * + * It would use the RSCoordinator methods like [processAtBank] to + * encapsulate common in-game actions needed for that workflow. + * + * By combining both capabilities, this interface provides a simple + * way to implement full RuneScape automation routines. + * + */ +interface RSOrchestrator : Orchestrator, RSCoordinator{ + + companion object { + fun doStandingTask(orchestrator: RSOrchestrator, params: StandingTaskParams) { + orchestrator.doLoop(params.totalVolume, params.volumePerStep) { + orchestrator.processAtBank( + params.bankPoint, + params.bankPresetHotkey, + params.craftingDialogHotkey, + params.craftingWaitDurationMillis, + params.craftingWaitDurationVarianceMillis + ) + } + } + + fun doTravelTask(orchestrator: RSOrchestrator, params: TravelTaskParams) { + orchestrator.doLoop(params.totalVolume, params.volumePerStep) { + orchestrator.processAtStationNearBank( + params.bankPoint, + params.travelPoint, + params.bankPresetHotkey, + params.travelDurationMillis, + params.travelDurationVarianceMillis, + params.craftingWaitDurationMillis, + params.craftingWaitDurationVarianceMillis + ) + } + } + + fun getDefaultInstance(): RSOrchestrator{ + return RSAgent() + } + } //end of companion object + +} + +private class RSAgent(override val automaton: Automaton = RobotController()) : RSOrchestrator { + + companion object{ + /** + * 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 const val LATENCY_PADDING_MS: Long = 500L + + + /** + * 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 const val TICK_DURATION_MS = 600L + } + + + /*============================================================================================================== + interface implementation + ==============================================================================================================*/ + + override fun bankStandWithoutDialog(bankPoint: Point, inventoryHotKey: Int, actionBarHotKey: Int) { + //open the bank located by the chest parameter + moveMouseLeftClickAndSleep(automaton.getAlmostPoint(bankPoint, WiggleParams()), 900, 400) + //withdraw the desired inventory preset + automaton.keyPress(inventoryHotKey) + + sleepForNTicks(1) + + //press the hotkey that causes action without dialogue + automaton.keyPress(actionBarHotKey) + + sleepForNTicks(1) + } + + override fun processAtBank( + bankPoint: Point, + bankPresetHotkey: Int, + craftingDialogueHotkey: Int, + waitDurationMillis: Long, + waitDurationVariance: Long + ) { + //open the bank located by the chest parameter + moveMouseLeftClickAndSleep(automaton.getAlmostPoint(bankPoint, WiggleParams()), 900, 400) + //withdraw the desired inventory preset + automaton.keyPress(bankPresetHotkey) + //sleep for a server tick + sleepForNTicks(1) + //open the crafting dialog with the correct hotkey + automaton.keyPress(craftingDialogueHotkey) + //sleep for a server tick + sleepForNTicks(1) + //press the "accept" default hotkey + automaton.keyPress(KeyEvent.VK_SPACE) + //wait for the desired time to finish + automaton.sleepWithVariance(waitDurationMillis, waitDurationVariance) + } + + override fun processAtStationNearBank( + bankPoint: Point, + craftingStationPoint: Point, + bankPresetHotkey: Int, + travelDurationMillis: Long, + travelDurationVarianceMillis: Long, + waitDurationMillis: Long, + waitDurationVarianceMillis: Long + ) { + //move to the bank and open the interface + moveMouseLeftClickAndSleep( + automaton.getAlmostPoint(bankPoint, WiggleParams()), + travelDurationMillis, + travelDurationVarianceMillis + ) + + //withdraw desired loadout + automaton.keyPress(bankPresetHotkey) + sleepForNTicks(1) + + //move to station and open the crafting dialog + moveMouseLeftClickAndSleep(craftingStationPoint, travelDurationMillis, travelDurationVarianceMillis) + + //start the crafting task + automaton.keyPress(KeyEvent.VK_SPACE) + + //wait for it to complete + automaton.sleepWithVariance(waitDurationMillis, waitDurationVarianceMillis) + } + + + + + /*============================================================================================================== + cheater functions + ==============================================================================================================*/ + override fun promptUserForPoint(prompt: String): Point { + println(prompt) + countDown(5) { + print("\rtaking point snapshot in $it... ") + if (it == 0) { + println("\r ") + } + } + + return automaton.getPointerLocation() + } + + override fun getBankPoint(): Point { + return promptUserForPoint("Hold your mouse over the bank...") + } + + + fun countDown(nSeconds: Int, announceFn: (step: Int) -> Unit) { + for (i in nSeconds downTo 0) { + announceFn(i) + automaton.sleep(1000) + } + } + + fun getPointerLocationAfter(delayInSeconds: Int): Point { + countDown(delayInSeconds) { + print("\rtaking pointer snapshot in $it...") + if (it == 0) { + println("\r ") + } + } + return automaton.getPointerLocation() + } + + fun getPointerLocationAsValDeclarationString(varName: String): String { + val info = getPointerLocationAfter(5) + return "val $varName = Point(${info.x}, ${info.y})" + } + + + fun moveMouseLeftClickAndSleep(p: Point, dur: Long, durRange: Long) { + automaton.moveMouse(p) + automaton.sleepWithVariance(100, 50) + //left click + automaton.mouseClick(InputEvent.BUTTON1_DOWN_MASK) + automaton.sleepWithVariance(dur, durRange) + } + + fun sleepForNTicks(n: Long) { + val latencyPadding = LATENCY_PADDING_MS + val baseWaitTime = n * TICK_DURATION_MS + automaton.sleepWithVariance(latencyPadding + baseWaitTime, 150) + } + + + override fun drawStar(p: Point) { + val offset = 100 + val top = Point(p.x, p.y - offset * 2) + val topright = Point(p.x + offset * 2, p.y + offset) + val bottomright = Point(p.x + offset * 2, p.y) + val topleft = Point(p.x - offset * 2, p.y + offset) + val bottomleft = Point(p.x - offset * 2, p.y) + val points = arrayListOf(top, bottomleft, topright, topleft, bottomright) + for (i in 0 until 10) { + for (point in points) { + automaton.moveMouse(point) + automaton.sleep(32) + } + } + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/Routines.kt b/src/main/kotlin/Routines.kt index e48bad7..9e210f8 100644 --- a/src/main/kotlin/Routines.kt +++ b/src/main/kotlin/Routines.kt @@ -15,6 +15,17 @@ import java.awt.event.KeyEvent * The routines are encapsulated for pure convenience */ object Routines { + /** + * 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 /** * Performs the full incense stick crafting process from start to finish. @@ -39,7 +50,7 @@ object Routines { val start = System.currentTimeMillis() //initialize the shared agent and chest point - val agent = Agent() + val agent = RSOrchestrator.getDefaultInstance() val bankPoint = agent.getBankPoint() // Loop to clean grimy herbs: @@ -71,8 +82,8 @@ object Routines { println("\rClean herbs infused") val finish = System.currentTimeMillis() - agent.drawStar(agent.controller.getPointerLocation()) - println("Entire chain finished in ${agent.prettyTimeString(finish - start)}") + agent.drawStar(agent.automaton.getPointerLocation()) + println("Entire chain finished in ${HelperFunctions.prettyTimeString(finish - start)}") } /** @@ -89,18 +100,17 @@ object Routines { * * It performs the actions at the bank location using the provided agent. */ - fun cleanHerbs(volume: Int, agent: Agent = Agent(), bankPoint: Point = agent.getBankPoint()) { + fun cleanHerbs(volume: Int, agent: RSOrchestrator = RSOrchestrator.getDefaultInstance(), bankPoint: Point = agent.getBankPoint()) { val params = StandingTaskParams( volume, CommonVolumesPerStep.FullInventory, - agent, bankPoint, KeyEvent.VK_F1, KeyEvent.VK_1, 0, 0 ) - Agent.doStandingTask(params) + RSOrchestrator.doStandingTask(agent, params) } @@ -116,18 +126,17 @@ object Routines { * - Cutting magic logs into incense sticks without dialog * - Depositing incense sticks */ - fun cutIncenseSticks(volume: Int, agent: Agent = Agent(), bankPoint: Point = agent.getBankPoint()) { + fun cutIncenseSticks(volume: Int, agent: RSOrchestrator = RSOrchestrator.getDefaultInstance(), bankPoint: Point = agent.getBankPoint()) { val params = StandingTaskParams( volume, CommonVolumesPerStep.FullInventory, - agent, bankPoint, KeyEvent.VK_F2, KeyEvent.VK_2, 26000, - Agent.TICK_DURATION_MS, + TICK_DURATION_MS, ) - Agent.doStandingTask(params) + RSOrchestrator.doStandingTask(agent, params) } @@ -143,18 +152,17 @@ object Routines { * - Coating incense sticks with ashes using hotkey * - Depositing coated sticks */ - fun coatIncenseSticks(volume: Int, agent: Agent = Agent(), bankPoint: Point = agent.getBankPoint()) { + fun coatIncenseSticks(volume: Int, agent: RSOrchestrator = RSOrchestrator.getDefaultInstance(), bankPoint: Point = agent.getBankPoint()) { val params = StandingTaskParams( volume, CommonVolumesPerStep.CoatingIncenseWithAsh, - agent, bankPoint, KeyEvent.VK_F3, KeyEvent.VK_3, 17000, - Agent.TICK_DURATION_MS, + TICK_DURATION_MS, ) - Agent.doStandingTask(params) + RSOrchestrator.doStandingTask(agent, params) } /** @@ -169,63 +177,60 @@ object Routines { * - Infusing incense sticks with herbs using hotkey * - Depositing infused incense sticks */ - fun infuseIncenseSticks(volume: Int, agent: Agent = Agent(), bankPoint: Point = agent.getBankPoint()) { + fun infuseIncenseSticks(volume: Int, agent: RSOrchestrator = RSOrchestrator.getDefaultInstance(), bankPoint: Point = agent.getBankPoint()) { val params = StandingTaskParams( volume, CommonVolumesPerStep.InfusingIncenseWithHerb, - agent, bankPoint, KeyEvent.VK_F4, KeyEvent.VK_4, 48600, - Agent.TICK_DURATION_MS, + TICK_DURATION_MS, ) - Agent.doStandingTask(params) + RSOrchestrator.doStandingTask(agent, params) } @Deprecated("Needs validation before you use it for realsies") - fun craftPotionAtBank(volume: Int, agent: Agent = Agent(), bankPoint: Point = agent.getBankPoint()) { + fun craftPotionAtBank(volume: Int, agent: RSOrchestrator = RSOrchestrator.getDefaultInstance(), bankPoint: Point = agent.getBankPoint()) { val params = StandingTaskParams( volume, CommonVolumesPerStep.FullInventory, - agent, bankPoint, KeyEvent.VK_F6, KeyEvent.VK_MINUS, 19200, - Agent.TICK_DURATION_MS, + TICK_DURATION_MS, ) - Agent.doStandingTask(params) + RSOrchestrator.doStandingTask(agent, params) } @Deprecated("Needs validation before you use it for realsies") fun potionGrindWithWell( volume: Int, travelDurationInMillis: Long, - agent: Agent = Agent(), + agent: RSOrchestrator = RSOrchestrator.getDefaultInstance(), bankPoint: Point = agent.getBankPoint() ) { val well = agent.promptUserForPoint("Put your mouse over the well...") val params = TravelTaskParams( volume, CommonVolumesPerStep.FullInventory, - agent, bankPoint, well, KeyEvent.VK_F6, -1, //since the travel point is also the dialogue creator, we can omit the hotkey 19200, - Agent.TICK_DURATION_MS, + TICK_DURATION_MS, travelDurationInMillis, - Agent.TICK_DURATION_MS + TICK_DURATION_MS ) - Agent.doTravelTask(params) + RSOrchestrator.doTravelTask(agent, params) } @Deprecated("Needs validation before you use it for realsies") fun supremeOverloadGrind(params: TaskParams) { - val agent = Agent() + val agent = RSOrchestrator.getDefaultInstance() val chest = agent.getBankPoint() val well = agent.promptUserForPoint("Put your mouse over the well...") @@ -260,24 +265,23 @@ object Routines { //these two points are specific to my computer. we need to export these into a file or something val furnaceFromChest = Point(776, 321) val chestFromFurnance = Point(1713, 843) - val agent = Agent() + val agent = RSOrchestrator.getDefaultInstance() val params = TravelTaskParams( volume, 28, - agent, chestFromFurnance, furnaceFromChest, KeyEvent.VK_F6, -1, //since the travel point is also the dialogue creator, we can omit the hotkey 51000, - Agent.TICK_DURATION_MS, + TICK_DURATION_MS, 2000, - Agent.TICK_DURATION_MS + TICK_DURATION_MS ) println("Resetting the camera. We need to define the reset to compass button...") agent.scrollOutToHeight(8) - Agent.doTravelTask(params) + RSOrchestrator.doTravelTask(agent, params) } /** @@ -299,7 +303,7 @@ object Routines { @Deprecated("Needs validation before you use it for realsies") fun processRefinedPlanksIntoFrames(params: TaskParams) { 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() + val agent = RSOrchestrator.getDefaultInstance() agent.scrollOutToHeight(8) //the following 2 points are magic numbers from *my* setup and resolution diff --git a/src/main/kotlin/TaskParams.kt b/src/main/kotlin/TaskParams.kt index a3446e2..a2c0472 100644 --- a/src/main/kotlin/TaskParams.kt +++ b/src/main/kotlin/TaskParams.kt @@ -13,7 +13,6 @@ import java.awt.Point interface TaskParams { val totalVolume: Int val volumePerStep: Int - val agent: Agent } /** @@ -90,7 +89,6 @@ interface TravelParams { data class StandingTaskParams( override val totalVolume: Int, override val volumePerStep: Int, - override val agent: Agent, override val bankPoint: Point, override val bankPresetHotkey: Int, override val craftingDialogHotkey: Int, @@ -122,7 +120,6 @@ data class StandingTaskParams( data class TravelTaskParams( override val totalVolume: Int, override val volumePerStep: Int, - override val agent: Agent, override val bankPoint: Point, override val travelPoint: Point, override val bankPresetHotkey: Int,