diff --git a/src/main/kotlin/Agent.kt b/src/main/kotlin/Agent.kt index 77e2faf..4feee65 100644 --- a/src/main/kotlin/Agent.kt +++ b/src/main/kotlin/Agent.kt @@ -1,5 +1,5 @@ import java.awt.Point -import java.awt.Robot +import java.awt.event.InputEvent import java.awt.event.KeyEvent /** @@ -19,9 +19,34 @@ import java.awt.event.KeyEvent * specific workflow logic and leverage the agent for the lower level * activities needed to craft, cook, etc. */ -class Agent(val controller: RobotController = RobotController()) { - +class Agent(val controller: DesktopInputController = 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. * @@ -183,7 +208,7 @@ class Agent(val controller: RobotController = RobotController()) { */ fun promptUserForPoint(prompt: String): Point { println(prompt) - controller.countDown(5) { + countDown(5) { print("\rtaking point snapshot in $it... ") if (it == 0) { println("\r ") @@ -246,19 +271,19 @@ class Agent(val controller: RobotController = RobotController()) { waitDurationVariance: Long ) { //open the bank located by the chest parameter - controller.moveMouseLeftClickAndSleep(controller.getAlmostPoint(chest, WiggleParams()), 900, 400) + moveMouseLeftClickAndSleep(controller.getAlmostPoint(chest, WiggleParams()), 900, 400) //withdraw the desired inventory preset controller.keyPress(bankPresetHotkey) //sleep for a server tick - controller.sleepForNTicks(1) + sleepForNTicks(1) //open the crafting dialog with the correct hotkey controller.keyPress(craftingDialogueHotkey) //sleep for a server tick - controller.sleepForNTicks(1) + sleepForNTicks(1) //press the "accept" default hotkey controller.keyPress(KeyEvent.VK_SPACE) //wait for the desired time to finish - controller.sleep(waitDurationMillis, waitDurationVariance) + controller.sleepWithVariance(waitDurationMillis, waitDurationVariance) } /** @@ -278,16 +303,16 @@ class Agent(val controller: RobotController = RobotController()) { */ fun bankStandWithoutDialog(chest: Point, invKey: Int, hotKey: Int) { //open the bank located by the chest parameter - controller.moveMouseLeftClickAndSleep(controller.getAlmostPoint(chest, WiggleParams()), 900, 400) + moveMouseLeftClickAndSleep(controller.getAlmostPoint(chest, WiggleParams()), 900, 400) //withdraw the desired inventory preset controller.keyPress(invKey) - controller.sleepForNTicks(1) + sleepForNTicks(1) //press the hotkey that causes action without dialogue controller.keyPress(hotKey) - controller.sleepForNTicks(1) + sleepForNTicks(1) } /** @@ -318,7 +343,7 @@ class Agent(val controller: RobotController = RobotController()) { waitDurationVarianceMillis: Long ) { //move to the bank and open the interface - controller.moveMouseLeftClickAndSleep( + moveMouseLeftClickAndSleep( controller.getAlmostPoint(chest, WiggleParams()), travelDurationMillis, travelDurationVarianceMillis @@ -326,33 +351,81 @@ class Agent(val controller: RobotController = RobotController()) { //withdraw desired loadout controller.keyPress(bankPresetHotkey) - controller.sleepForNTicks(1) + sleepForNTicks(1) //move to station and open the crafting dialog - controller.moveMouseLeftClickAndSleep(station, travelDurationMillis, travelDurationVarianceMillis) + moveMouseLeftClickAndSleep(station, travelDurationMillis, travelDurationVarianceMillis) //start the crafting task controller.keyPress(KeyEvent.VK_SPACE) //wait for it to complete - controller.sleep(waitDurationMillis, waitDurationVarianceMillis) + controller.sleepWithVariance(waitDurationMillis, waitDurationVarianceMillis) } /*============================================================================================================== cheater functions ==============================================================================================================*/ - /** - * 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...") } + + + 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/RobotController.kt index c3a3caf..11905df 100644 --- a/src/main/kotlin/RobotController.kt +++ b/src/main/kotlin/RobotController.kt @@ -5,34 +5,6 @@ import java.awt.event.InputEvent import java.util.concurrent.TimeUnit import kotlin.random.Random -/** - * Utility class for programmatically controlling mouse and keyboard actions. - * - * This class allows performing mouse movements, clicks, and keyboard presses - * through a [Robot] instance. It includes utility methods to add less-robotic - * variance and pacing to the actions. - * - * Basic usage: - * - * ``` - * val doer = Doer() - * - * // Move mouse - * val target = Point(100, 200) - * val nearTarget = doer.getAlmostPoint(target, WiggleParams()) - * doer.mouseMove(nearTarget) - * - * // Mouse click - * doer.click(Doer.LEFT_CLICK) - * - * // Type keys - * doer.keypress(KeyEvent.VK_A) - * doer.keypress(KeyEvent.VK_B) - * ``` - * - * @constructor Creates a Doer instance with a Robot. - */ - interface InputController { fun moveMouse(point: Point) @@ -42,46 +14,45 @@ interface InputController { fun sleep(duration: Long) + fun sleepWithVariance(duration: Long, variance: Long) + fun scrollIn(sleepDur: Long, sleepDurVariance: Long) fun scrollOut(sleepDur: Long, sleepDurVariance: Long) } -interface DesktopController{ - fun getPointerLocation(): Point +interface DesktopController { + fun getPointerLocation(): Point { + return MouseInfo.getPointerInfo().location + } + + fun getAlmostPoint(point: Point, params : WiggleParams): Point{ + val xDel = Random.nextInt(0, params.xWiggle) + val yDel = Random.nextInt(0, params.yWiggle) + val xDir = if (Random.nextDouble() > 0.5) { + 1 + } else { + -1 + } + val yDir = if (Random.nextDouble() > 0.5) { + 1 + } else { + -1 + } + return Point(point.x + (xDel * xDir), point.y + (yDel + yDir)) + } } +interface DesktopInputController : DesktopController, InputController + data class WiggleParams(val xWiggle: Int = 25, val yWiggle: Int = 25) -open class RobotController(private val robot: Robot = Robot()): InputController, DesktopController { - 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 - } +open class RobotController(private val robot: Robot = Robot()) : DesktopInputController { + - /** - * 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 /** @@ -128,7 +99,7 @@ open class RobotController(private val robot: Robot = Robot()): InputController, robot.mousePress(button) //we add in some random time variance here to appear less robotic - sleep(8, 8) + sleepWithVariance(8, 8) robot.mouseRelease(button) } @@ -154,101 +125,11 @@ open class RobotController(private val robot: Robot = Robot()): InputController, robot.keyPress(key) //we add in some random time variance here to appear less robotic - sleep(8, 8) + sleepWithVariance(8, 8) robot.keyRelease(key) } - /** - * Moves the mouse to a point, left clicks, and sleeps. - * - * This moves the mouse to the given [Point] p, sleeps for 100ms ± 50ms, - * left clicks using [LEFT_CLICK], and then sleeps for the given duration - * dur ± durRange. - * - * @param p The [Point] to move the mouse to. - * @param dur The base duration to sleep after clicking. - * @param durRange The random variance to add to the sleep duration. - */ - fun moveMouseLeftClickAndSleep(p: Point, dur: Long, durRange: Long) { - moveMouse(p) - sleep(100, 50) - //left click - mouseClick(InputEvent.BUTTON1_DOWN_MASK) - sleep(dur, durRange) - } - - /** - * Sleeps for a duration proportional to the number of "ticks". - * - * This calculates the sleep duration based on multiplying the number of ticks [n] - * by the tick duration constant [TICK_DURATION_MS]. - * - * It also adds a [LATENCY_PADDING_MS] to account for latency before actions take effect. - * - * Finally, some random variance is added by passing a range to [sleep]. - * - * @param n The number of "ticks" to calculate the sleep duration from. - */ - fun sleepForNTicks(n: Long) { - val latencyPadding = LATENCY_PADDING_MS - val baseWaitTime = n * TICK_DURATION_MS - sleep(latencyPadding + baseWaitTime, 150) - } - - /** - * Counts down from a given number of seconds, calling a function on each step. - * - * This will count down the given number of [nSeconds], decrementing the count on each step. - * - * On each step, it will call the provided [announceFn] function, passing the current count. - * - * It waits 1 second between each call using [sleep]. - * - * @param nSeconds The number of seconds to count down from. - * @param announceFn The function to call on each step, passing the current count. - */ - fun countDown(nSeconds: Int, announceFn: (step: Int) -> Unit) { - for (i in nSeconds downTo 0) { - announceFn(i) - sleep(1000) - } - } - - /** - * Gets a point near the given point with random offset. - * - * This takes in a base [Point] and returns a new point that is offset - * randomly in the x and y directions. - * - * The maximum x and y offset amounts are determined by the [xWiggle] and [yWiggle] - * values passed in [params]. - * - * The offset is calculated by: - * - Generating random ints from 0 to xWiggle and 0 to yWiggle - * - Randomly choosing 1 or -1 for the x and y offset directions - * - Adding the offset to the original x and y coordinates - * - * @param p The base [Point] to offset from - * @param params The [WiggleParams] controlling the max x and y offsets - * @return A new [Point] offset randomly from the original point - */ - fun getAlmostPoint(p: Point, params: WiggleParams): Point { - val xDel = Random.nextInt(0, params.xWiggle) - val yDel = Random.nextInt(0, params.yWiggle) - val xDir = if (Random.nextDouble() > 0.5) { - 1 - } else { - -1 - } - val yDir = if (Random.nextDouble() > 0.5) { - 1 - } else { - -1 - } - return Point(p.x + (xDel * xDir), p.y + (yDel + yDir)) - } - /** * Sleeps for the specified duration. * @@ -260,26 +141,15 @@ open class RobotController(private val robot: Robot = Robot()): InputController, TimeUnit.MILLISECONDS.sleep(dur) } - /** - * Sleeps for a duration with random variance added. - * - * This sleeps for the specified base duration plus a random amount of variance - * to avoid having the exact same sleep duration each time. - * - * The variance is calculated by taking half of the variance value as the max - * random duration to add. So a variance of 100 would add between 0 and 100ms following a normal distribution. - * - * @param dur The base duration to sleep in milliseconds. - * @param variance The amount of random variance to add in milliseconds. - */ - fun sleep(dur: Long, variance: Long) { - if (dur < 0 || variance <= 1) { + + override fun sleepWithVariance(duration: Long, variance: Long) { + if (duration < 0 || variance <= 1) { return } val dSize = (variance) / 2 - val r1 = Random.nextLong(dSize) + val r1 = Random.nextLong(dSize) val r2 = Random.nextLong(dSize) - sleep(dur + r1 + r2) + sleep(duration + r1 + r2) } /** @@ -290,7 +160,7 @@ open class RobotController(private val robot: Robot = Robot()): InputController, */ override fun scrollOut(sleepDur: Long, sleepDurVariance: Long) { robot.mouseWheel(1) - sleep(sleepDur, sleepDurVariance) + sleepWithVariance(sleepDur, sleepDurVariance) } /** @@ -301,87 +171,7 @@ open class RobotController(private val robot: Robot = Robot()): InputController, */ override fun scrollIn(sleepDur: Long, sleepDurVariance: Long) { robot.mouseWheel(-1) - sleep(sleepDur, sleepDurVariance) + sleepWithVariance(sleepDur, sleepDurVariance) } - /** - * Gets the current mouse pointer location. - * - * This uses [MouseInfo.getPointerInfo] to retrieve the mouse pointer location - * and returns it as a [Point]. - * - * @return The [Point] representing the mouse pointer's x and y coordinates. - */ - override fun getPointerLocation(): Point { - return MouseInfo.getPointerInfo().location - } - - /** - * Gets the mouse pointer location after a delay. - * - * This counts down the provided delay in seconds, printing a countdown prompt. - * After the delay, it retrieves the current mouse pointer location using - * [getPointerLocation]. - * - * @param delayInSeconds The amount of time in seconds to delay before getting the pointer location. - * @return The [Point] representing the mouse x and y coordinates after the delay. - */ - fun getPointerLocationAfter(delayInSeconds: Int): Point { - countDown(delayInSeconds) { - print("\rtaking pointer snapshot in $it...") - if (it == 0) { - println("\r ") - } - } - return getPointerLocation() - } - - /** - * Gets the current mouse pointer location and returns it as a Kotlin variable declaration string. - * - * This will wait for 5 seconds to allow the user to move the mouse to the desired location. - * - * The pointer location is retrieved using [getPointerLocationAfter] and converted to a - * Kotlin 'val' variable declaration statement assigning it to a [Point]. - * - * For example, for varName "clickLoc": - * - * val clickLoc = Point(500, 400) - * - * @param varName The desired variable name to use in the declaration string. - * @return The declaration string declaring a val with the pointer location. - */ - fun getPointerLocationAsValDeclarationString(varName: String): String { - val info = getPointerLocationAfter(5) - return "val $varName = Point(${info.x}, ${info.y})" - } - - /** - * Draws a star shape by moving the mouse to points around a center point. - * - * This calculates offset points around the provided center point - * to trace out a star shape with the mouse cursor. - * - * The offset distance from the center can be configured by changing the offset constant. - * - * It will draw the star shape 10 times by looping through the point list. - * - * @param p The center point to base the star shape around. - */ - - 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) { - moveMouse(point) - sleep(32) - } - } - } } \ No newline at end of file diff --git a/src/main/kotlin/Routines.kt b/src/main/kotlin/Routines.kt index 31f16ef..e48bad7 100644 --- a/src/main/kotlin/Routines.kt +++ b/src/main/kotlin/Routines.kt @@ -71,7 +71,7 @@ object Routines { println("\rClean herbs infused") val finish = System.currentTimeMillis() - agent.drawStar(agent.getPointerLocation()) + agent.drawStar(agent.controller.getPointerLocation()) println("Entire chain finished in ${agent.prettyTimeString(finish - start)}") } @@ -125,7 +125,7 @@ object Routines { KeyEvent.VK_F2, KeyEvent.VK_2, 26000, - RobotController.TICK_DURATION_MS, + Agent.TICK_DURATION_MS, ) Agent.doStandingTask(params) } @@ -152,7 +152,7 @@ object Routines { KeyEvent.VK_F3, KeyEvent.VK_3, 17000, - RobotController.TICK_DURATION_MS, + Agent.TICK_DURATION_MS, ) Agent.doStandingTask(params) } @@ -178,7 +178,7 @@ object Routines { KeyEvent.VK_F4, KeyEvent.VK_4, 48600, - RobotController.TICK_DURATION_MS, + Agent.TICK_DURATION_MS, ) Agent.doStandingTask(params) } @@ -193,7 +193,7 @@ object Routines { KeyEvent.VK_F6, KeyEvent.VK_MINUS, 19200, - RobotController.TICK_DURATION_MS, + Agent.TICK_DURATION_MS, ) Agent.doStandingTask(params) } @@ -215,9 +215,9 @@ object Routines { KeyEvent.VK_F6, -1, //since the travel point is also the dialogue creator, we can omit the hotkey 19200, - RobotController.TICK_DURATION_MS, + Agent.TICK_DURATION_MS, travelDurationInMillis, - RobotController.TICK_DURATION_MS + Agent.TICK_DURATION_MS ) Agent.doTravelTask(params) } @@ -270,9 +270,9 @@ object Routines { KeyEvent.VK_F6, -1, //since the travel point is also the dialogue creator, we can omit the hotkey 51000, - RobotController.TICK_DURATION_MS, + Agent.TICK_DURATION_MS, 2000, - RobotController.TICK_DURATION_MS + Agent.TICK_DURATION_MS ) println("Resetting the camera. We need to define the reset to compass button...") agent.scrollOutToHeight(8) @@ -310,4 +310,5 @@ object Routines { agent.processAtStationNearBank(chestFromSaw, sawFromChest, KeyEvent.VK_F6, 2700, 500, 64000, 1000) } } + } \ No newline at end of file