Skip to content

Commit 9e6c01c

Browse files
authored
Demo fishing (#45)
- Add new DemoFishingScript. - Improve JavaDocs in PointSelector. - Change getRandomPointByColour to getRandomPointByColourObj to be more intuitive.
1 parent 88d3995 commit 9e6c01c

File tree

2 files changed

+265
-4
lines changed

2 files changed

+265
-4
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package com.chromascape.scripts;
2+
3+
import com.chromascape.base.BaseScript;
4+
import com.chromascape.utils.actions.PointSelector;
5+
import com.chromascape.utils.core.input.distribution.ClickDistribution;
6+
import com.chromascape.utils.core.screen.colour.ColourInstances;
7+
import com.chromascape.utils.core.screen.colour.ColourObj;
8+
import com.chromascape.utils.core.screen.topology.TemplateMatching;
9+
import com.chromascape.utils.core.screen.window.ScreenManager;
10+
import com.chromascape.utils.domain.ocr.Ocr;
11+
import java.awt.Point;
12+
import java.awt.Rectangle;
13+
import java.awt.image.BufferedImage;
14+
import java.io.IOException;
15+
import java.time.Duration;
16+
import java.time.Instant;
17+
import org.apache.logging.log4j.LogManager;
18+
import org.apache.logging.log4j.Logger;
19+
20+
/**
21+
* A demo script. Created to show off how to use certain utilities, namely:
22+
*
23+
* <ul>
24+
* <li>Ocr and how to read text in screen regions.
25+
* <li>How to use the Idler (although a modified version)
26+
* <li>Dropping items in a human-like way using the mouse
27+
* <li>Template matching, its use of thresholds and how to search for images on-screen
28+
* <li>Colour detection within the gameView
29+
* <li>The use of {@link PointSelector} the new actions utility
30+
* </ul>
31+
*
32+
* <p>This is a DEMO script. It's not intended to be used, but rather as a reference. ChromaScape is
33+
* not liable for any damages incurred whilst using DEMO scripts. Images not included.
34+
*/
35+
public class DemoFishingScript extends BaseScript {
36+
37+
// Only one type of rod and one bait is necessary to be downloaded
38+
private static final String fishingRod = "/images/user/Fishing_rod.png";
39+
private static final String flyFishingRod = "/images/user/Fly_fishing_rod.png";
40+
private static final String bait = "/images/user/Fishing_bait.png";
41+
private static final String feather = "/images/user/Feather.png";
42+
43+
private static final Logger logger = LogManager.getLogger(DemoFishingScript.class);
44+
45+
private static String lastMessage;
46+
47+
/** Constructs a BaseScript. */
48+
public DemoFishingScript() {
49+
super();
50+
}
51+
52+
/**
53+
* Overridden cycle. Repeats all tasks within, until stop() is called from either the Web UI, or
54+
* from within the script.
55+
*/
56+
@Override
57+
protected void cycle() {
58+
if (!checkIfCorrectInventoryLayout()) {
59+
logger.warn("Normal or fly-fishing rod must be in inventory slot 27");
60+
logger.warn("Bait/feathers must be in inventory slot 28");
61+
logger.info("The top of your bait or feather images should be cropped by 10 px");
62+
stop();
63+
}
64+
65+
clickFishingSpot();
66+
67+
// Waiting, if the player needs to walk - adjust as needed
68+
waitRandomMillis(5000, 6000);
69+
70+
// Check if stopped fishing
71+
waitUntilStoppedFishing(300);
72+
logger.info("Is idle");
73+
74+
// If ran out of bait, stop
75+
if (checkChatPopup("have")) {
76+
logger.warn("Ran out of bait!");
77+
stop();
78+
}
79+
80+
// If inventory full, drop all fish
81+
if (checkChatPopup("carry")) {
82+
logger.warn("Pop-up found");
83+
dropAllFish();
84+
}
85+
}
86+
87+
/**
88+
* Checks if the inventory layout is as expected. The inventory layout needs to be in a specific
89+
* format to ensure that the dropping of items looks human.
90+
*
91+
* @return {@code boolean} true if correct, false if not.
92+
*/
93+
private boolean checkIfCorrectInventoryLayout() {
94+
logger.info("Checking if inventory layout is valid");
95+
Rectangle invSlot27 = controller().zones().getInventorySlots().get(26);
96+
Rectangle invSlot28 = controller().zones().getInventorySlots().get(27);
97+
BufferedImage invSlot27Image = ScreenManager.captureZone(invSlot27);
98+
BufferedImage invSlot28Image = ScreenManager.captureZone(invSlot28);
99+
Rectangle slot27Match;
100+
Rectangle slot28Match;
101+
102+
try {
103+
slot27Match = TemplateMatching.match(fishingRod, invSlot27Image, 0.15, false);
104+
slot28Match = TemplateMatching.match(bait, invSlot28Image, 0.15, false);
105+
106+
if (slot27Match != null && slot28Match != null) {
107+
logger.info("Inventory layout matched");
108+
return true;
109+
}
110+
111+
slot27Match = TemplateMatching.match(flyFishingRod, invSlot27Image, 0.05, false);
112+
slot28Match = TemplateMatching.match(feather, invSlot28Image, 0.05, false);
113+
114+
if (slot27Match != null && slot28Match != null) {
115+
logger.info("Inventory layout matched");
116+
return true;
117+
} else {
118+
return false;
119+
}
120+
} catch (Exception e) {
121+
logger.error(
122+
"checkIfCorrectInventoryLayout() template matching failed: {}", String.valueOf(e));
123+
}
124+
return false;
125+
}
126+
127+
/**
128+
* Drops all fish in the inventory in a human-like manner. Designed to only be called when the
129+
* inventory is full, because it doesn't check whether there is an item in any specific slot.
130+
*/
131+
private void dropAllFish() {
132+
logger.info("Dropping all fish");
133+
int currentSlot = 0;
134+
int alternateSlot = 4;
135+
controller().keyboard().sendModifierKey(401, "shift");
136+
waitRandomMillis(100, 250);
137+
for (int i = 0; i < 3; i++) {
138+
for (int j = 0; j < 4; j++) {
139+
clickPoint(
140+
ClickDistribution.generateRandomPoint(
141+
controller().zones().getInventorySlots().get(currentSlot)));
142+
waitRandomMillis(20, 50);
143+
clickPoint(
144+
ClickDistribution.generateRandomPoint(
145+
controller().zones().getInventorySlots().get(alternateSlot)));
146+
waitRandomMillis(40, 80);
147+
currentSlot++;
148+
alternateSlot = currentSlot + 4;
149+
}
150+
waitRandomMillis(100, 200);
151+
currentSlot = currentSlot + 4;
152+
alternateSlot = currentSlot + 4;
153+
}
154+
clickPoint(
155+
ClickDistribution.generateRandomPoint(controller().zones().getInventorySlots().get(24)));
156+
clickPoint(
157+
ClickDistribution.generateRandomPoint(controller().zones().getInventorySlots().get(25)));
158+
waitRandomMillis(100, 250);
159+
controller().keyboard().sendModifierKey(402, "shift");
160+
}
161+
162+
/**
163+
* Helper method to left-click a {@link Point} location on-screen.
164+
*
165+
* @param clickPoint The point to click.
166+
*/
167+
private void clickPoint(Point clickPoint) {
168+
try {
169+
controller().mouse().moveTo(clickPoint, "medium");
170+
} catch (InterruptedException e) {
171+
logger.error("Mouse interrupted: {}", String.valueOf(e));
172+
}
173+
controller().mouse().leftClick();
174+
}
175+
176+
/**
177+
* Checks if the chat contains a specified phrase in the font {@code Quill 8}. Uses the Ocr module
178+
* to look for the phrase in the {@code Chat} zone.
179+
*
180+
* @param phrase The phrase to look for in the chat.
181+
* @return true if found, else false.
182+
*/
183+
private boolean checkChatPopup(String phrase) {
184+
Rectangle chat = controller().zones().getChatTabs().get("Chat");
185+
ColourObj black = ColourInstances.getByName("Black");
186+
String extraction = null;
187+
try {
188+
extraction = Ocr.extractText(chat, "Quill 8", black, true);
189+
} catch (IOException e) {
190+
logger.error("OCR failed while loading font: {}", String.valueOf(e));
191+
}
192+
return extraction != null && extraction.contains(phrase);
193+
}
194+
195+
/**
196+
* Clicks the {@code Cyan} colour which denotes a fishing spot within the GameView {@link
197+
* BufferedImage}. Generates a random click point to click within the contour of the found {@code
198+
* Cyan} object.
199+
*/
200+
private void clickFishingSpot() {
201+
logger.info("Clicking fishing spot");
202+
BufferedImage gameView = null;
203+
try {
204+
gameView = controller().zones().getGameView();
205+
} catch (Exception e) {
206+
logger.error("gameView not found: {}", String.valueOf(e));
207+
}
208+
209+
Point clickLocation = PointSelector.getRandomPointInColour(gameView, "Cyan", 15);
210+
if (clickLocation == null) {
211+
logger.error("clickLocation is null!");
212+
stop();
213+
}
214+
clickPoint(clickLocation);
215+
}
216+
217+
/**
218+
* A modified version of the {@link com.chromascape.utils.actions.Idler#waitUntilIdle(BaseScript,
219+
* int) waitUntilIdle} method. Altered to also check if the inventory is full and or if the user
220+
* has run out of bait/feathers.
221+
*
222+
* @param timeoutSeconds The maximum number of seconds to remain idle before continuing.
223+
*/
224+
private void waitUntilStoppedFishing(int timeoutSeconds) {
225+
logger.info("Waiting until stopped fishing");
226+
checkInterrupted();
227+
try {
228+
Instant start = Instant.now();
229+
Instant deadline = start.plus(Duration.ofSeconds(timeoutSeconds));
230+
while (Instant.now().isBefore(deadline)) {
231+
Rectangle latestMessage = controller().zones().getChatTabs().get("Latest Message");
232+
ColourObj red = ColourInstances.getByName("ChatRed");
233+
ColourObj black = ColourInstances.getByName("Black");
234+
String idleText = Ocr.extractText(latestMessage, "Plain 12", red, true);
235+
String timeStamp = Ocr.extractText(latestMessage, "Plain 12", black, true);
236+
if ((idleText.contains("moving") || idleText.contains("idle"))
237+
&& !timeStamp.equals(lastMessage)) {
238+
lastMessage = timeStamp;
239+
return;
240+
} else if (checkChatPopup("carry")) {
241+
return;
242+
} else if (checkChatPopup("have")) {
243+
return;
244+
}
245+
}
246+
} catch (Exception e) {
247+
logger.error("Error while waiting for idle", e);
248+
}
249+
}
250+
}

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ public class PointSelector {
4545

4646
/**
4747
* Searches for the provided image template within the current game view, then returns a random
48-
* point within the detected bounding box if the match exceeds the defined threshold.
48+
* point within the detected bounding box if the match exceeds the defined threshold. Uses {@link
49+
* TemplateMatching#match(String, BufferedImage, double, boolean) TemplateMatching.match} to
50+
* derive the location of the image onscreen based on the threshold (lower = stricter).
4951
*
5052
* @param templatePath the BufferedImage template to locate and click within the larger image view
5153
* @param image the larger image, what you're searching in
@@ -73,6 +75,12 @@ public static Point getRandomPointInImage(
7375

7476
/**
7577
* Attempts to find a random point inside the contour of the first object of the specified colour.
78+
* Is an abstraction of {@link #getRandomPointByColourObj(BufferedImage, ColourObj, int)
79+
* getRandomPointByColourObj} and instead searches the colour for you at runtime. Uses
80+
* ColourContours to mask out the provided buffered image by the provided ColourObj. Based on the
81+
* mask, extracts contours of the colour. Generates a random point within the bounding box of its
82+
* {@link ChromaObj} and checks a maximum of {@code int maxAttempts} whether the point lands in
83+
* the contour. Fails if exceeded.
7684
*
7785
* @param image the image to search in (e.g. game view from controller)
7886
* @param colourName the name of the colour (must match ColourInstances key, e.g. "Purple")
@@ -81,19 +89,22 @@ public static Point getRandomPointInImage(
8189
*/
8290
public static Point getRandomPointInColour(
8391
BufferedImage image, String colourName, int maxAttempts) {
84-
return getRandomPointByColour(image, ColourInstances.getByName(colourName), maxAttempts);
92+
return getRandomPointByColourObj(image, ColourInstances.getByName(colourName), maxAttempts);
8593
}
8694

8795
/**
8896
* Attempts to find a random point inside the contour of the first object of the specified
89-
* ColourObj.
97+
* ColourObj. Uses ColourContours to mask out the provided buffered image by the provided
98+
* ColourObj. Based on the mask, extracts contours of the colour. Generates a random point within
99+
* the bounding box of its {@link ChromaObj} and checks a maximum of {@code int maxAttempts}
100+
* whether the point lands in the contour. Fails if exceeded.
90101
*
91102
* @param image the image to search in (e.g. game view from controller)
92103
* @param colour the name of the colour (must match ColourInstances key, e.g. "Purple")
93104
* @param maxAttempts maximum number of attempts to find a point inside the contour
94105
* @return a random Point inside the contour, or null if not found/error
95106
*/
96-
public static Point getRandomPointByColour(
107+
public static Point getRandomPointByColourObj(
97108
BufferedImage image, ColourObj colour, int maxAttempts) {
98109
List<ChromaObj> objs;
99110
try {

0 commit comments

Comments
 (0)