Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/main/java/com/chromascape/base/BaseScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,9 @@ public abstract class BaseScript {
private volatile boolean running = true;
private Thread scriptThread;

/**
* Constructs a BaseScript.
*
* @param isFixed whether the client UI is fixed or resizable
*/
public BaseScript(final boolean isFixed) {
controller = new Controller(isFixed);
/** Constructs a BaseScript. */
public BaseScript() {
controller = new Controller();
hotkeyListener = new HotkeyListener(this);
}

Expand Down
13 changes: 3 additions & 10 deletions src/main/java/com/chromascape/controller/Controller.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ private enum ControllerState {
}

private ControllerState state;
private final boolean isFixed;

private Kinput kinput;
private VirtualMouseUtils virtualMouseUtils;
Expand All @@ -37,15 +36,9 @@ private enum ControllerState {
private Walker walker;
private static final Logger logger = LogManager.getLogger(Controller.class);

/**
* Constructs a new Controller instance.
*
* @param isFixed Indicates whether the user's client is classic resizable or classic fixed. The
* user selects this in the UI.
*/
public Controller(boolean isFixed) {
/** Constructs a new Controller instance. */
public Controller() {
this.state = ControllerState.STOPPED;
this.isFixed = isFixed;
}

/**
Expand All @@ -64,7 +57,7 @@ public void init() {
virtualKeyboardUtils = new VirtualKeyboardUtils(kinput);

// Initialize zone management with fixed mode option
zoneManager = new ZoneManager(isFixed);
zoneManager = new ZoneManager();

state = ControllerState.RUNNING;

Expand Down
10 changes: 3 additions & 7 deletions src/main/java/com/chromascape/scripts/DemoMiningScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,9 @@ public class DemoMiningScript extends BaseScript {
private static final Logger logger = LogManager.getLogger(DemoMiningScript.class);
private static final String ironOre = "/images/user/Iron_ore.png";

/**
* Constructs a new mining script.
*
* @param isFixed whether the client UI is fixed or resizable
*/
public DemoMiningScript(boolean isFixed) {
super(isFixed);
/** Constructs a new mining script. */
public DemoMiningScript() {
super();
}

/**
Expand Down
10 changes: 3 additions & 7 deletions src/main/java/com/chromascape/scripts/DemoWineScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,9 @@ public class DemoWineScript extends BaseScript {

private boolean bankFlag = true;

/**
* Constructs a BaseScript.
*
* @param isFixed whether the client UI is classic fixed or classic resizable
*/
public DemoWineScript(boolean isFixed) {
super(isFixed);
/** Constructs a BaseScript. */
public DemoWineScript() {
super();
}

/**
Expand Down
10 changes: 3 additions & 7 deletions src/main/java/com/chromascape/scripts/Screenshotter.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ public class Screenshotter extends BaseScript {

public static final String ORIGINAL_IMAGE_PATH = "output/original.png";

/**
* Same constructor as super (BaseScript).
*
* @param isFixed whether the client is in classic fixed or classic resizable
*/
public Screenshotter(boolean isFixed) {
super(isFixed);
/** Same constructor as super (BaseScript). */
public Screenshotter() {
super();
}

/**
Expand Down
19 changes: 14 additions & 5 deletions src/main/java/com/chromascape/utils/actions/PointSelector.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,28 @@ public static Point getRandomPointInColour(
return getRandomPointByColour(image, ColourInstances.getByName(colourName), maxAttempts);
}

/**
* Attempts to find a random point inside the contour of the first object of the specified
* ColourObj.
*
* @param image the image to search in (e.g. game view from controller)
* @param colour the name of the colour (must match ColourInstances key, e.g. "Purple")
* @param maxAttempts maximum number of attempts to find a point inside the contour
* @return a random Point inside the contour, or null if not found/error
*/
public static Point getRandomPointByColour(
BufferedImage image, ColourObj colourName, int maxAttempts) {
BufferedImage image, ColourObj colour, int maxAttempts) {
List<ChromaObj> objs;
try {
objs = ColourContours.getChromaObjsInColour(image, colourName);
objs = ColourContours.getChromaObjsInColour(image, colour);
} catch (Exception e) {
logger.error(e.getMessage());
logger.error(e.getStackTrace());
return null;
}

if (objs.isEmpty()) {
logger.error("No objects found for colour: {}", colourName);
logger.error("No objects found for colour: {}", colour);
return null;
}

Expand All @@ -108,10 +117,10 @@ public static Point getRandomPointByColour(
p = ClickDistribution.generateRandomPoint(obj.boundingBox());
attempts++;
}
logger.info("Attempts to find point in colour '{}': {}", colourName, attempts);
logger.info("Attempts to find point in colour '{}': {}", colour, attempts);
if (attempts >= maxAttempts) {
logger.error(
"Failed to find a valid point in {} contour after {} attempts.", colourName, maxAttempts);
"Failed to find a valid point in {} contour after {} attempts.", colour, maxAttempts);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class TemplateMatching {

private static final Logger logger = LogManager.getLogger(TemplateMatching.class.getName());

private static double minVal;

/**
* Performs template matching to locate a smaller image (template) within a larger image (base),
* using normalized squared difference matching with an alpha channel mask to ignore transparent
Expand Down Expand Up @@ -161,9 +163,10 @@ public static Rectangle match(
+ ")",
debugMsg);

TemplateMatching.minVal = minVal.get();

if (minVal.get() > threshold) {
System.out.println(
"No match: minVal above threshold (" + minVal.get() + " > " + threshold + ")");
logger.error("No match: minVal above threshold ({} > {})", minVal.get(), threshold);
return null;
}
// offset for screen cords
Expand Down Expand Up @@ -227,4 +230,14 @@ public static Mat loadMatFromResource(String resourcePath) throws IOException {

return mat;
}

/**
* Returns the minVal of the last matched object. The minVal dictates how strongly an object
* matches onscreen. (lower = better)
*
* @return the minVal.
*/
public static double getMinVal() {
return minVal;
}
}
74 changes: 40 additions & 34 deletions src/main/java/com/chromascape/utils/domain/zones/ZoneManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public class ZoneManager {
/** Rectangle defining the location of the mouse-over text. */
private Rectangle mouseOver;

/** Default template matching threshold to verify that an image is matched successfully. */
private static final double THRESHOLD = 0.15;

/** File paths to template images used for UI element detection. */
private final String[] zoneTemplates = {
"/images/ui/minimap.png",
Expand All @@ -47,62 +50,66 @@ public class ZoneManager {
"/images/ui/minimap_fixed.png"
};

/** Threshold values corresponding to template matching sensitivity for each UI element. */
private final double[] zoneThresholds = {0.025, 0.100, 0.035, 0.020};

private static final Logger logger = LogManager.getLogger(ZoneManager.class.getName());

/**
* Constructs a new ZoneManager configured for either fixed or resizable mode.
*
* @param isFixed true if the client is in fixed mode, false otherwise.
*/
public ZoneManager(boolean isFixed) {
this.isFixed = isFixed;
/** Constructs a new ZoneManager configured for either fixed or resizable mode. */
public ZoneManager() {
this.isFixed = checkIfFixed();
mapper();
}

/**
* Performs template matching to locate UI elements and maps their respective zones.
*
* <p>Populates the minimap, control panel, chat tabs, and inventory slots with their bounding
* rectangles based on current window mode.
*
* <p>Any exceptions during mapping are caught and logged to standard error.
*/
public void mapper() {
try {
// The chat's location is used to define the location of certain elements
Rectangle chatLocation = locateUiElement(zoneTemplates[2], zoneThresholds[2]);
chatTabs = SubZoneMapper.mapChat(chatLocation);
chatTabs = SubZoneMapper.mapChat(locateUiElement(zoneTemplates[2]));
ctrlPanel = SubZoneMapper.mapCtrlPanel(locateUiElement(zoneTemplates[1]));
inventorySlots = SubZoneMapper.mapInventory(locateUiElement(zoneTemplates[1]));

ctrlPanel = SubZoneMapper.mapCtrlPanel(locateUiElement(zoneTemplates[1], zoneThresholds[1]));
Rectangle windowBounds = ScreenManager.getWindowBounds();

inventorySlots =
SubZoneMapper.mapInventory(locateUiElement(zoneTemplates[1], zoneThresholds[1]));
// The minimap's location is used in conjunction to the chat's location to define certain
// elements
if (isFixed) {
Rectangle minimapLocation = locateUiElement(zoneTemplates[3], zoneThresholds[3]);
minimap = SubZoneMapper.mapFixedMinimap(minimapLocation);
mouseOver = new Rectangle(chatLocation.x + 1, minimapLocation.y + 3, 407, 26);
minimap = SubZoneMapper.mapFixedMinimap(locateUiElement(zoneTemplates[3]));
mouseOver = new Rectangle(windowBounds.x, windowBounds.y, 407, 26);
gridInfo =
SubZoneMapper.mapGridInfo(
new Rectangle(chatLocation.x + 6, minimapLocation.y + 23, 129, 56));
new Rectangle(windowBounds.x + 9, windowBounds.y + 24, 129, 56));
} else {
Rectangle minimapLocation = locateUiElement(zoneTemplates[0], zoneThresholds[0]);
minimap = SubZoneMapper.mapMinimap(minimapLocation);
mouseOver = new Rectangle(chatLocation.x - 3, minimapLocation.y - 2, 407, 26);
minimap = SubZoneMapper.mapMinimap(locateUiElement(zoneTemplates[0]));
mouseOver = new Rectangle(windowBounds.x, windowBounds.y, 407, 26);
gridInfo =
SubZoneMapper.mapGridInfo(
new Rectangle(chatLocation.x + 2, minimapLocation.y + 18, 129, 56));
new Rectangle(windowBounds.x + 5, windowBounds.y + 20, 129, 56));
}
} catch (Exception e) {
logger.error("[ZoneManager] Mapping failed: {}", e.getMessage());
logger.debug(e.getStackTrace());
}
}

/**
* Checks the two minimap images against the client window, compares them based on accuracy.
*
* @return {@code boolean} True if Fixed classic, false if Resizable classic.
*/
private boolean checkIfFixed() {
BufferedImage screen = ScreenManager.captureWindow();
double fixedMinVal = 0;
double resizableMinVal = 0;
try {
TemplateMatching.match(zoneTemplates[0], screen, THRESHOLD, false);
resizableMinVal = TemplateMatching.getMinVal();
TemplateMatching.match(zoneTemplates[3], screen, THRESHOLD, false);
fixedMinVal = TemplateMatching.getMinVal();
} catch (Exception e) {
logger.error("[ZoneManager] Check if fixed failed: {}", e.getMessage());
}
return fixedMinVal < resizableMinVal;
}

/**
* Captures a screenshot of the current game viewport area.
*
Expand All @@ -121,14 +128,14 @@ public BufferedImage getGameView() throws Exception {
// inv (1), chat (2), minimap_fixed (3)
int[] fixedIndices = {1, 2, 3};
for (int i : fixedIndices) {
Rectangle element = locateUiElement(zoneTemplates[i], zoneThresholds[i]);
Rectangle element = locateUiElement(zoneTemplates[i]);
gameViewMask = MaskZones.maskZones(gameViewMask, ScreenManager.toClientBounds(element));
}
} else {
// inv (1), chat (2), minimap (0)
int[] resizableIndices = {1, 2, 0};
for (int i : resizableIndices) {
Rectangle element = locateUiElement(zoneTemplates[i], zoneThresholds[i]);
Rectangle element = locateUiElement(zoneTemplates[i]);
gameViewMask = MaskZones.maskZones(gameViewMask, ScreenManager.toClientBounds(element));
}
}
Expand All @@ -141,12 +148,11 @@ public BufferedImage getGameView() throws Exception {
* game window capture.
*
* @param templatePath The file path to the template image to match.
* @param threshold The matching threshold (lower values mean stricter matching).
* @return A {@link Rectangle} representing the bounds of the matched UI element.
* @throws Exception if the template matching fails or no match is found.
*/
public Rectangle locateUiElement(String templatePath, double threshold) throws Exception {
return TemplateMatching.match(templatePath, ScreenManager.captureWindow(), threshold, false);
public Rectangle locateUiElement(String templatePath) throws Exception {
return TemplateMatching.match(templatePath, ScreenManager.captureWindow(), THRESHOLD, false);
}

/**
Expand Down
23 changes: 4 additions & 19 deletions src/main/java/com/chromascape/web/instance/RunConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,22 @@
* <p>Contains the duration the script should run, the script identifier, and a flag indicating
* whether the client UI is fixed or resizable.
*/
public class RunConfig {

private final String script;
private final Boolean fixed;
public record RunConfig(String script) {

/**
* Constructs a new RunConfig with the specified duration, script, and fixed flag.
*
* @param script the identifier or name of the script to run
* @param fixed true if the UI layout is fixed; false otherwise
*/
public RunConfig(String script, boolean fixed) {
this.script = script;
this.fixed = fixed;
}
public RunConfig {}

/**
* Returns the identifier or name of the script to run.
*
* @return the script name or ID
*/
public String getScript() {
@Override
public String script() {
return script;
}

/**
* Indicates whether the UI layout is fixed.
*
* @return true if the UI layout is fixed, false otherwise
*/
public Boolean isFixed() {
return fixed;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,11 @@ public ScriptControl(WebSocketStateHandler webSocketStateHandler) {
public ResponseEntity<Object> getRunConfig(@RequestBody RunConfig config) {
try {
// Validation checks
if (config.getScript() == null || config.getScript().isEmpty()) {
if (config.script() == null || config.script().isEmpty()) {
logger.error("No script is selected");
return ResponseEntity.badRequest().body("Script must be specified.");
}

if (config.isFixed() == null) {
logger.error("No window style selected");
return ResponseEntity.badRequest().body("Window style (Fixed?) must be specified.");
}

logger.info("Config valid: attempting to run script");

// Instantiate and start the script instance
Expand Down
Loading