diff --git a/src/main/kotlin/Agent.kt b/src/main/kotlin/Agent.kt index 154c415..8f59b1a 100644 --- a/src/main/kotlin/Agent.kt +++ b/src/main/kotlin/Agent.kt @@ -1,6 +1,5 @@ import java.awt.Point import java.awt.Robot -import java.awt.event.InputEvent import java.awt.event.KeyEvent import kotlin.math.ceil @@ -10,10 +9,9 @@ class Agent { const val F6 = KeyEvent.VK_F6 const val Space = KeyEvent.VK_SPACE const val Minus = KeyEvent.VK_MINUS - const val LeftClick = InputEvent.BUTTON1_DOWN_MASK } - private val doer = Doer(Robot()) + private val doer = Doer() fun doLoop(totalVolume: Int, volumePerStep: Int, task: (Agent) -> Unit) { @@ -24,7 +22,7 @@ class Agent { "You must consume at least 1 thing per step" } fun computeTotalSteps(totalVolume: Int, volumePerStep: Int) = (ceil((totalVolume / volumePerStep).toDouble()) + 1).toInt() - val r = Doer(Robot()) + val totalSteps =computeTotalSteps(totalVolume, volumePerStep) val start = System.currentTimeMillis() @@ -35,7 +33,6 @@ class Agent { println() val finish = System.currentTimeMillis() println("Finished everything in ${prettyTimeString(finish - start)}") - r.drawStar(doer.getPointerLocationAfter(0)) } fun report(step: Int, of: Int, dur: Long) { @@ -61,25 +58,24 @@ class Agent { fun promptUserForPoint(prompt: String): Point { println(prompt) - - for (i in 5 downTo 1) { - print("\rtaking point snapshot in $i...") - doer.sleep(1000) + doer.countDown(5){ + print("\rtaking point snapshot in $it... ") + if(it == 0){ + println("\r ") + } } - print("\r ") - - return doer.getPointerLocationAfter(1000) + return doer.getPointerLocation() } - fun scrollOutToFixedHeight(){ + fun scrollOutToHeight(height: Int){ doer.sleep(1000) - for(i in 0 until 16){ - doer.scrollIn() + for(i in 0 until height){ + doer.scrollIn(16, 16) } for(i in 0 until 8){ - doer.scrollOut() + doer.scrollOut(16, 16) } } @@ -90,21 +86,33 @@ class Agent { waitDurationMillis: Long, waitDurationVariance: Long ) { - doer.moveMouseLeftClickAndSleep(doer.getAlmostPoint(chest), 900, 400) + //open the bank located by the chest parameter + doer.moveMouseLeftClickAndSleep(doer.getAlmostPoint(chest, Doer.WiggleParams()), 900, 400) + //withdraw the desired inventory preset doer.keypress(invHotkey) + //sleep for a server tick doer.sleepForNTicks(1) + //open the crafting dialog with the correct hotkey doer.keypress(hotbarHotkey) + //sleep for a server tick doer.sleepForNTicks(1) + //press the "accept" default hotkey doer.keypress(Space) + //wait for the desired time to finish doer.sleep(waitDurationMillis, waitDurationVariance) } fun bankStandWithoutDialog(chest: Point, invKey: Int, hotKey: Int) { - doer.mouseMove(chest) - doer.moveMouseLeftClickAndSleep(doer.getAlmostPoint(chest), 900, 400) + //open the bank located by the chest parameter + doer.moveMouseLeftClickAndSleep(doer.getAlmostPoint(chest, Doer.WiggleParams()), 900, 400) + //withdraw the desired inventory preset doer.keypress(invKey) + doer.sleepForNTicks(1) + + //press the hotkey that causes action without dialogue doer.keypress(hotKey) + doer.sleepForNTicks(1) } @@ -117,17 +125,27 @@ class Agent { waitDurationMillis: Long, waitDurationVariance: Long ) { - //click on chest - doer.moveMouseLeftClickAndSleep(doer.getAlmostPoint(chest), travelDurationMillis, travelDurationVariance) + //move to the bank and open the interface + doer.moveMouseLeftClickAndSleep(doer.getAlmostPoint(chest, Doer.WiggleParams()), travelDurationMillis, travelDurationVariance) - //select pre-stored inventory + //withdraw desired loadout doer.keypress(invHotkey) doer.sleepForNTicks(1) - //move to newLocation + //move to station and open the crafting dialog doer.moveMouseLeftClickAndSleep(station, travelDurationMillis, travelDurationVariance) + + //start the crafting task doer.keypress(KeyEvent.VK_SPACE) + //wait for it to complete doer.sleep(waitDurationMillis, waitDurationVariance) } + + /*============================================================================================================== + cheater 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 195316c..0f2360c 100644 --- a/src/main/kotlin/Doer.kt +++ b/src/main/kotlin/Doer.kt @@ -3,50 +3,154 @@ import java.awt.Point import java.awt.Robot import java.awt.event.InputEvent import java.util.concurrent.TimeUnit -import kotlin.math.ceil import kotlin.random.Random -class Doer(val r: Robot) { - +class Doer { + private val robot = Robot() companion object { - val xWiggle = 15; - val yWiggle = 15; + // The mouse button mask for a left click + const val LeftClick = InputEvent.BUTTON1_DOWN_MASK + // The duration in ms of one "tick" used for pacing actions + const val TICK_DURATION_MS = 600L + // Extra padding in ms added before actions to account for latency + const val LATENCY_PADDING_MS = 500L } + /** + * Data class to hold parameters for random wiggle amounts when moving the mouse. + * + * This holds the maximum x and y wiggle offset values to use when randomly + * offsetting mouse movements to make them less robotic. + * + * For example, this could be passed to [getAlmostPoint] to control the randomization. + * + * @param xWiggle The max x offset, default 25. + * @param yWiggle The max y offset, default 25. + */ + data class WiggleParams(val xWiggle: Int = 25, val yWiggle: Int = 25) - fun mouseMove(p: Point){ - r.mouseMove(p.x, p.y) + /** + * Moves the mouse cursor to the given [Point]. + * + * This uses the Robot [mouseMove] method to move the mouse cursor + * to the x and y coordinates specified by the provided [Point] p. + * + * @param p The [Point] representing the x and y coordinates to move the mouse to. + */ + fun mouseMove(p: Point) { + robot.mouseMove(p.x, p.y) } - fun click(buttons: Int) { - r.mousePress(buttons) - sleep(Random.nextInt(4, 8).toLong()) - r.mouseRelease(buttons) + /** + * Performs a mouse click with the specified mouse button. + * + * This uses the Robot [mousePress] and [mouseRelease] methods to click the + * mouse button specified by [button]. + * + * It sleeps for a small random duration between press and release to add + * variance to the click timing. + * + * @param button The mouse button to click, as a button mask from [InputEvent]. + */ + fun click(button: Int) { + robot.mousePress(button) + sleep(8, 8) + robot.mouseRelease(button) } - fun keypress(buttons: Int) { - r.keyPress(buttons) - sleep(Random.nextInt(4, 8).toLong()) - r.keyRelease(buttons) + /** + * Presses and releases a keyboard key. + * + * This uses the Robot [keyPress] and [keyRelease] methods to press + * and release the key specified by [key]. + * + * It sleeps for a small random duration in between key press and release + * to pace the keystrokes. + * + * @param key The key code of the key to press, from [java.awt.event.KeyEvent]. + */ + fun keypress(key: Int) { + robot.keyPress(key) + sleep(8, 8) + robot.keyRelease(key) } - fun moveMouseLeftClickAndSleep(p: Point, dur: Long, durRange: Long){ + /** + * 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 [LeftClick], 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) { mouseMove(p) sleep(100, 50) - click(InputEvent.BUTTON1_DOWN_MASK) + click(LeftClick) sleep(dur, durRange) } - - fun sleepForNTicks(n: Long){ - val latencyPadding = 500L - val baseWaitTime = n * 600L - sleep(latencyPadding+baseWaitTime, 150) + /** + * 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) } - fun getAlmostPoint(p: Point): Point { - val xDel = Random.nextInt(0, xWiggle) - val yDel = Random.nextInt(0, yWiggle) + /** + * 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 { @@ -60,49 +164,135 @@ class Doer(val r: Robot) { return Point(p.x + (xDel * xDir), p.y + (yDel + yDir)) } + /** + * Sleeps for the specified duration. + * + * This uses [TimeUnit.MILLISECONDS] to sleep for the given duration in milliseconds. + * + * @param dur The sleep duration in milliseconds. + */ fun sleep(dur: Long) { TimeUnit.MILLISECONDS.sleep(dur) } - fun sleep(dur: Long,variance: Long) { - val dSize = (variance)/2 + /** + * 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) { + val dSize = (variance) / 2 val r1 = Random.nextLong(dSize) val r2 = Random.nextLong(dSize) - TimeUnit.MILLISECONDS.sleep(dur+r1+r2) + sleep(dur + r1 + r2) } - fun scrollOut(){ - r.mouseWheel(1) - sleep(25, 26) - } - fun scrollIn(){ - r.mouseWheel(-1) - sleep(25, 26) + /** + * Scrolls the mouse wheel down by one unit. + * + * Uses the Robot [mouseWheel] method to scroll up and then sleeps + * for a random duration between 16-32ms to pace the scrolling. + */ + fun scrollOut(sleepDur: Long, sleepDurVariance: Long) { + robot.mouseWheel(1) + sleep(sleepDur, sleepDurVariance) } - fun getPointerLocationAsValDeclarationString(varName: String) { - TimeUnit.SECONDS.sleep(5) - val info = MouseInfo.getPointerInfo().location - println("val ${varName} = Point(${info.x}, ${info.y})") + /** + * Scrolls the mouse wheel up by one unit. + * + * Uses the Robot [mouseWheel] method to scroll up and then sleeps + * for a random duration between 16-32ms to pace the scrolling. + */ + fun scrollIn(sleepDur: Long, sleepDurVariance: Long) { + robot.mouseWheel(-1) + sleep(sleepDur, sleepDurVariance) } - fun getPointerLocationAfter(delay: Long): Point { - sleep(delay) + /** + * 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. + */ + fun getPointerLocation(): Point { return MouseInfo.getPointerInfo().location } - fun drawStar(p: Point){ + /** + * 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, p.y + offset) - val bottomright = Point(p.x+offset, p.y) - val topleft = Point(p.x-offset, p.y + offset) - val bottomleft = Point(p.x-offset, p.y) - val points = arrayListOf(top, bottomleft, topright, topleft, bottomright) - for(i in 0 until 3){ - for(point in points){ + 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) { mouseMove(point) - sleep(200) + sleep(32) } } } diff --git a/src/main/kotlin/Routines.kt b/src/main/kotlin/Routines.kt index 24a093a..ab96d00 100644 --- a/src/main/kotlin/Routines.kt +++ b/src/main/kotlin/Routines.kt @@ -6,7 +6,7 @@ object Routines { fun fullRunIncense(volHerbs: Int, volLogs: Int, volAshes: Int, volCleanHerbs: Int){ val agent = Agent() val start = System.currentTimeMillis() - val chest = agent.promptUserForPoint("Hold your mouse over the bank...") + val chest = agent.getBankPoint() //process any grimy herbs we might need if(volHerbs > 0){ @@ -47,7 +47,7 @@ object Routines { fun cleanHerbs(volume: Int, step: Int = 14){ val agent = Agent() - val chest = agent.promptUserForPoint("Put your mouse over the bank...") + val chest = agent.getBankPoint() agent.doLoop(volume, step){ agent.bankStandWithoutDialog(chest, KeyEvent.VK_F1, KeyEvent.VK_1) } @@ -55,7 +55,7 @@ object Routines { fun cutIncenseSticks(volume: Int, step: Int = 28){ val agent = Agent() - val chest = agent.promptUserForPoint("Put your mouse over the bank...") + val chest = agent.getBankPoint() agent.doLoop(volume, step){ agent.bankStandForLoop(chest, KeyEvent.VK_F2, KeyEvent.VK_2, 26000, 1200) @@ -64,7 +64,7 @@ object Routines { fun coatIncenseSticks(volume: Int, step: Int = 26){ val agent = Agent() - val chest = agent.promptUserForPoint("Put your mouse over the bank...") + val chest = agent.getBankPoint() agent.doLoop(volume, step){ agent.bankStandForLoop(chest, KeyEvent.VK_F3, KeyEvent.VK_3, 21000, 600) @@ -75,7 +75,7 @@ object Routines { val agent = Agent() - val chest = agent.promptUserForPoint("Put your mouse over the bank...") + val chest = agent.getBankPoint() agent.doLoop(volume, step){ agent.bankStandForLoop(chest, KeyEvent.VK_F4, KeyEvent.VK_4, 48600, 600) } @@ -84,8 +84,7 @@ object Routines { fun potionGrind(volume: Int, step: Int = 14){ val agent = Agent() - println("Put your mouse over the bank...") - val chest = agent.promptUserForPoint("Put your mouse over the bank...") + val chest = agent.getBankPoint() agent.doLoop(volume, step){ agent.bankStandForLoop(chest, Agent.F6, Agent.Minus, 19200, 600) } @@ -94,7 +93,7 @@ object Routines { fun potionGrindWithWell(volume: Int, step: Int = 14){ val agent = Agent() - val chest = agent.promptUserForPoint("Put your mouse over the bank...") + val chest = agent.getBankPoint() val well = agent.promptUserForPoint("Put your mouse over the well...") @@ -105,7 +104,7 @@ object Routines { fun supremeOverloadGrind(volume: Int, step: Int = 4){ val agent = Agent() - val chest = agent.promptUserForPoint("Put your mouse over the bank...") + val chest = agent.getBankPoint() val well = agent.promptUserForPoint("Put your mouse over the well...") agent.doLoop(volume, step){ @@ -117,7 +116,7 @@ object Routines { fun processInventoryAtFurnace(volume: Int, step: Int = 28){ val agent = Agent() println("Reset the camera and zoom in") - agent.scrollOutToFixedHeight() + agent.scrollOutToHeight(2) val furnaceFromChest = Point(776, 321) val chestFromFurance = Point(1713, 843) @@ -133,7 +132,7 @@ object Routines { fun processRefinedPlanksIntoFrames(volume: Int, step: 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.scrollOutToFixedHeight() + agent.scrollOutToHeight(8) val sawFromChest = Point(1079, 907) val chestFromSaw = Point(1226, 180)