Skip to content

Commit 9b0ebcf

Browse files
authored
Interface Revamp, Upgrade Mouse Integration & Performance Enhancements (#46)
# PR #46: Interface Revamp, Humanized Mouse & Performance Overhaul This is a massive update that updates almost every part of the bot. I wanted to make the client look better, feel more human, and run a lot smoother. ### Complete Web Interface Rehaul The frontend has been completely rebuilt with a modern **GlassMorphism** aesthetic. Theres also several components in the tech stack which have been created to utilise `WebSockets` for real time updates. * **Live Debug Viewport:** You can now see exactly what the bot "sees" in real-time. The `ColourContours` and `TemplateMatching` logic push their state directly to the browser. If the stream gets overloaded, the system discards old frames to keep the bot logic running. * **Reactive Status:** The header now displays the live sensor state (`SEARCHING`, `ACTING`, `WAITING`, `ERROR`) so you know exactly what the bot is thinking. * **Live Statistics:** Runtime, cycles, and inputs are streamed live. * **Smart Logs:** Console logs are now colour-coded by severity in the web UI. * **Better Controls:** Fixed the sliders (you can no longer break them by pushing min past max), and added a **Copy Button** to the colour picker. You can now generate the Java code for a `ColourObj` and paste it directly into your script. * **Slider performance** Slider performance has been greatly improved by no longer writing modified.png to disk. ### WindMouse Integration I’ve completely replaced the old `MousePathing` with **WindMouse**. - Instead of calculating a geometric path, WindMouse simulates a physical cursor with mass. It uses `gravity` to pull towards the target and `wind` to apply randomness making it pixel imperfect. - Movement includes natural overshoots, micro-jitters, and variable speeds, As a result, overshoot and undershoot methods are now deprecated and removed. ### Performance & Multithreading We've moved to a **Producer -> Consumer threading model** for inputs. The physics engine calculates mouse movement on one thread, while a separate background thread handles the hardware remote inputs. If the hardware input lag spikes, the physics calculation remains accurate. **OpenCV Improvements:** * **Font Caching:** OCR no longer lazy loads on each OCR call, it's cached in memory at startup. * **Viewport:** The state tracker uses a no-op interface by default and is only hooked up when Spring loads, preventing memory leaks during headless runs. ### Background Mode This is probably the biggest Quality of Life improvement **You can now hide the RuneLite.** I replaced the standard Java `Robot` capture with native Windows calls. This reads the window's device context directly and writes it into a buffer. The bot can see and use the game window even if it is covered by another window. You can finally use your laptop for other things while the bot runs in the background! > You can't minimise it, however you can maximise other things and cover it completely. ### New Actions Utility You can now call `ItemDropper.dropAll(controller());` to drop all the items in your inventory. There are also different dropping patterns to choose from, see the class. ## BREAKING CHANGES: - `VirtualMouseUtils.moveToPause()` & `VirtualMouseUtils.moveToAndOvershoot()` have been removed. - There is no longer a "fastest" option in mouse.moveTo().
1 parent 9e6c01c commit 9b0ebcf

File tree

117 files changed

+3228
-8533
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+3228
-8533
lines changed

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,40 @@ Due to the separation of domain, core and actions utilities it provides a greate
3636
## Mouse input
3737

3838

39-
https://github.com/user-attachments/assets/a7a39096-6d65-4ff6-8843-f3661cc9a92d
39+
https://github.com/user-attachments/assets/1267df86-db5c-4189-8c8d-e2f5fe047cab
4040

4141

4242
### - Remote input
4343

4444
ChromaScape uses advanced remote input techniques to function as a virtual second mouse dedicated to the client window. Unlike traditional input methods, this approach never hijacks your physical mouse, so you can continue using your PC without interruption while the bot runs in the background.
4545

46-
This is achieved through KInput, which provides utilities to send mouse and keyboard events directly into a client’s Java Canvas object. By design, it cannot click outside this canvas and requires the Process ID of the target client to operate.
46+
This is achieved through KInput.
4747

48-
ChromaScape uses the [64-bit](https://github.com/ThatOneGuyScripts/KInput) supported version of KInput. The [original](https://github.com/Kasi-R/KInput) KInput source is also available for reference.
49-
50-
Feel free to browse the code or build from source if you’d like to dive deeper into how it works.
48+
ChromaScape uses a slightly modified version of the [64-bit](https://github.com/ThatOneGuyScripts/KInput) supported version of KInput. The [original](https://github.com/Kasi-R/KInput) KInput source is also available for reference.
49+
There are instructions on how to build KInput from source within the third_party directory in `DEV_README.md`
5150

5251
### - Humanised mouse movement
53-
To further reduce bot detection risks, ChromaScape uses humanised mouse movement patterns that mimic real user behavior. Through a combination of multiple bezier paths, easing functions and the ability to overshoot/undershoot then recorrect - this produces surprisingly natural-looking behavior.
52+
To further reduce bot detection risks, ChromaScape uses an adapted version of [WindMouse](https://ben.land/post/2021/04/25/windmouse-human-mouse-movement/), a physics based calculation of gravity and wind to ensure pixel imperfections unlike bezier curves. WindMouse has been a successful staple within the community for over a decade, and provides exceedingly human mouse movements.
5453

5554
## Web-Based Control Panel
5655

57-
The UI is built with Spring Boot and served locally. This gives you a powerful way to view logs, manage scripts, and extend functionality - all from a browser tab. It's fully customizable with HTML/CSS/JS, so power users can tweak or overhaul it without modifying core bot code or needing to worry about tight coupling.
56+
57+
https://github.com/user-attachments/assets/b1c601e9-b58a-4a54-865b-739a25ddf898
58+
59+
60+
The UI is built with Spring Boot, a mature industry framework, and served locally. This gives you a powerful way to view logs, manage scripts, see the bot's sensor information, and extend functionality. It's fully customizable with basic HTML/CSS/JS, so power users can tweak or overhaul it without modifying core bot code or needing to worry about tight coupling.
61+
62+
**Newly: you can now have runelite covered by other applications while the bot runs**
63+
> You can't minimise it or put it entirely offscreen yet, but you can open other applications and have it run in the background.
5864
5965
## Colour and Image Detection
6066

6167
### - Colour Picker
6268
This utility allows you to pick exact pixel colours directly from the screen. It’s useful for identifying precise colour values needed to detect specific game elements or interface components. The picker supports real-time sampling and stores these colours for use in detection routines. Inspired by ThatOneGuy's [BCD](https://github.com/ThatOneGuyScripts/BetterColorDetection)
6369

64-
<img width="1298" height="751" alt="colourScreenshot" src="https://github.com/user-attachments/assets/b93eb66c-2a61-40ba-9abb-24fb0596d7b5" />
70+
<img width="1298" height="751" alt="Colour_Picker" src="https://github.com/user-attachments/assets/650deb75-97c1-46af-b2f7-414af9ce63e6" />
6571

66-
> Note: You will need all other colours except your desired one to be black. (This is just an example to show colour filtering)
72+
> Note: You will need all other colours except your desired one to be black.
6773
6874
### - Colour Detection
6975
Using the colours obtained from the picker, the framework scans defined screen regions to find matching outlines or clusters of pixels. This process enables the bot to recognise in-game objects, UI elements, or indicators by their unique colour signatures. The detection logic is optimized to handle slight variations in colour due to lighting or graphical effects by allowing for a lower and upper range of colours.

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies {
3939

4040
// Logging
4141
implementation("org.springframework.boot:spring-boot-starter-log4j2")
42+
annotationProcessor("org.apache.logging.log4j:log4j-core:2.24.3")
4243

4344
// Other libraries
4445
implementation("com.github.kwhat:jnativehook:2.2.2")

config/checkstyle/checkstyle-suppressions.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@
99
<suppress files=".*WindowHandler\.java" checks="MethodName"/>
1010
<suppress files=".*ScreenManager\.java" checks="MethodName"/>
1111
<suppress files=".*Similarity\.java" checks=".*"/>
12+
<suppress files=".*WindMouse\.java" checks=".*"/>
13+
<suppress files=".*TemplateMatching\.java" checks="AvoidStarImport"/>
14+
<suppress files=".*ScreenManager\.java" checks="LocalVariableName"/>
1215
</suppressions>

gradlew

100755100644
File mode changed.

src/main/java/com/chromascape/base/BaseScript.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import com.chromascape.controller.Controller;
44
import com.chromascape.utils.core.runtime.HotkeyListener;
55
import com.chromascape.utils.core.runtime.ScriptStoppedException;
6+
import com.chromascape.utils.core.state.BotState;
7+
import com.chromascape.utils.core.state.StateManager;
8+
import com.chromascape.utils.core.statistics.StatisticsManager;
69
import java.util.concurrent.ThreadLocalRandom;
710
import org.apache.logging.log4j.LogManager;
811
import org.apache.logging.log4j.Logger;
@@ -42,9 +45,11 @@ public final void run() {
4245
scriptThread = Thread.currentThread();
4346
controller.init();
4447
hotkeyListener.start();
48+
StatisticsManager.reset();
4549

4650
try {
4751
while (running) {
52+
StatisticsManager.incrementCycles();
4853
if (Thread.currentThread().isInterrupted()) {
4954
logger.info("Thread interrupted, exiting.");
5055
break;
@@ -55,6 +60,7 @@ public final void run() {
5560
logger.error("Cycle interrupted: {}", e.getMessage());
5661
break;
5762
} catch (Exception e) {
63+
StateManager.setState(BotState.ERROR);
5864
logger.error("Exception in cycle: {}", e.getMessage());
5965
break;
6066
}
@@ -95,7 +101,8 @@ public void stop() {
95101
* @param ms the duration to sleep in milliseconds
96102
* @throws ScriptStoppedException if the thread is interrupted during sleep
97103
*/
98-
public static void waitMillis(long ms) throws ScriptStoppedException {
104+
public static void waitMillis(long ms) {
105+
StateManager.setState(BotState.WAITING);
99106
try {
100107
Thread.sleep(ms);
101108
} catch (InterruptedException e) {

src/main/java/com/chromascape/controller/Controller.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.chromascape.utils.core.input.remoteinput.Kinput;
66
import com.chromascape.utils.core.screen.window.ScreenManager;
77
import com.chromascape.utils.core.screen.window.WindowHandler;
8+
import com.chromascape.utils.domain.ocr.Ocr;
89
import com.chromascape.utils.domain.walker.Walker;
910
import com.chromascape.utils.domain.zones.ZoneManager;
1011
import org.apache.logging.log4j.LogManager;
@@ -49,20 +50,36 @@ public Controller() {
4950
* internal state for running.
5051
*/
5152
public void init() {
53+
logger.info("Setting up Font masks...");
54+
// Warmup: Pre-load common fonts
55+
try {
56+
Ocr.loadFont("Plain 11");
57+
Ocr.loadFont("Plain 12");
58+
Ocr.loadFont("Bold 12");
59+
} catch (Exception e) {
60+
logger.error("Failed to pre-load fonts during init: {}", e.getMessage());
61+
}
62+
63+
logger.info("Setting up Remote Input Library...");
5264
// Obtain process ID of the target window to initialize input injection
5365
kinput = new Kinput(WindowHandler.getPid(WindowHandler.getTargetWindow()));
5466

5567
// Initialize virtual input utilities with current window bounds and fullscreen status
68+
logger.info("Initialising mouse and keyboard utils...");
5669
virtualMouseUtils = new VirtualMouseUtils(kinput, ScreenManager.getWindowBounds());
5770
virtualKeyboardUtils = new VirtualKeyboardUtils(kinput);
5871

72+
logger.info("Pre-loading and instantiating zones...");
5973
// Initialize zone management with fixed mode option
6074
zoneManager = new ZoneManager();
75+
// Initialise gameView instead of LazyLoading, to improve startup overhead
76+
zoneManager.getGameView();
6177

6278
state = ControllerState.RUNNING;
6379

6480
// Initialises a walker to provide the script with Walking functionality through the DAX API
6581
walker = new Walker(this);
82+
logger.info("Controller State: {}", state);
6683
}
6784

6885
/**
@@ -74,10 +91,46 @@ public void init() {
7491
public void shutdown() {
7592
mouse().getMouseOverlay().eraseOverlay();
7693
kinput.destroy();
94+
if (!killKinput()) {
95+
logger.warn("Kinput failed to destroy");
96+
}
7797
state = ControllerState.STOPPED;
7898
logger.info("Shutting down");
7999
}
80100

101+
/**
102+
* Uses the command prompt to forcibly delete KInput.dll and KInputCtrl.dll from the build
103+
* directory. Effectively freeing up the program to rerun.
104+
*
105+
* @return {@code true} if successful, {@code false} if not.
106+
*/
107+
public boolean killKinput() {
108+
try {
109+
String distPath = new java.io.File("build/dist").getAbsolutePath();
110+
String kinputCtrl = "\"" + distPath + "\\KInputCtrl.dll\"";
111+
String kinput = "\"" + distPath + "\\KInput.dll\"";
112+
113+
// Wait 2s, then force delete files.
114+
// We use cmd /c start "" /B to ensure it runs detached/background if possible.
115+
// Using ProcessBuilder to avoid Runtime.exec deprecation.
116+
new ProcessBuilder(
117+
"cmd.exe",
118+
"/c",
119+
"start",
120+
"/MIN",
121+
"cmd.exe",
122+
"/c",
123+
"timeout /t 2 /nobreak > NUL & del /f /q " + kinputCtrl + " " + kinput)
124+
.start();
125+
126+
logger.info("Scheduled forced Kinput DLL cleanup in 2 seconds.");
127+
return true;
128+
} catch (Exception e) {
129+
logger.error("Failed to schedule Kinput cleanup: {}", e.getMessage());
130+
return false;
131+
}
132+
}
133+
81134
/**
82135
* Provides access to the virtual mouse utility.
83136
*

src/main/java/com/chromascape/scripts/DemoMiningScript.java

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import com.chromascape.base.BaseScript;
44
import com.chromascape.utils.actions.Idler;
5+
import com.chromascape.utils.actions.ItemDropper;
56
import com.chromascape.utils.actions.PointSelector;
6-
import com.chromascape.utils.core.input.distribution.ClickDistribution;
77
import com.chromascape.utils.core.screen.topology.TemplateMatching;
88
import com.chromascape.utils.core.screen.window.ScreenManager;
99
import java.awt.Point;
@@ -45,7 +45,7 @@ public DemoMiningScript() {
4545
@Override
4646
protected void cycle() {
4747
if (isInventoryFull()) {
48-
dropIronOre();
48+
ItemDropper.dropAll(controller());
4949
}
5050
clickOre();
5151
waitRandomMillis(800, 1000);
@@ -90,28 +90,4 @@ private boolean isInventoryFull() {
9090
}
9191
return false;
9292
}
93-
94-
/**
95-
* Drops all iron ore in the inventory using shift-click.
96-
*
97-
* <p>Iterates through all 28 slots, clicking each one while the shift modifier is held down.
98-
*/
99-
private void dropIronOre() {
100-
try {
101-
controller().keyboard().sendModifierKey(401, "shift");
102-
waitRandomMillis(800, 1200);
103-
for (int i = 0; i < 28; i++) {
104-
Rectangle invSlot = controller().zones().getInventorySlots().get(i);
105-
Point clickLoc = ClickDistribution.generateRandomPoint(invSlot);
106-
controller().mouse().moveTo(clickLoc, "medium");
107-
controller().mouse().leftClick();
108-
waitRandomMillis(200, 650);
109-
}
110-
waitRandomMillis(600, 800);
111-
controller().keyboard().sendModifierKey(402, "shift");
112-
} catch (Exception e) {
113-
logger.error(e);
114-
logger.error(e.getStackTrace());
115-
}
116-
}
11793
}

src/main/java/com/chromascape/utils/actions/Idler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ public class Idler {
3636
* @param timeoutSeconds the maximum number of seconds to remain idle before continuing
3737
*/
3838
public static void waitUntilIdle(BaseScript base, int timeoutSeconds) {
39+
// Initial wait to prevent race condition to previous idle message.
40+
BaseScript.waitMillis(600);
3941
BaseScript.checkInterrupted();
4042
try {
4143
Instant start = Instant.now();
4244
Instant deadline = start.plus(Duration.ofSeconds(timeoutSeconds));
4345
while (Instant.now().isBefore(deadline)) {
46+
// Throttle wait to reduce lag, this is enough.
47+
BaseScript.waitMillis(300);
4448
Rectangle latestMessage = base.controller().zones().getChatTabs().get("Latest Message");
4549
ColourObj red = ColourInstances.getByName("ChatRed");
4650
ColourObj black = ColourInstances.getByName("Black");

0 commit comments

Comments
 (0)