diff --git a/pom.xml b/pom.xml
index dac38e6..cc65f0a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
4.0.0
bh.bot
99bot
- 3.5.1
+ 3.6.0
diff --git a/sample-scripts/MultiCharacter.AFK.web.bat b/sample-scripts/MultiCharacter.AFK.web.bat
new file mode 100644
index 0000000..d6bebaf
--- /dev/null
+++ b/sample-scripts/MultiCharacter.AFK.web.bat
@@ -0,0 +1,24 @@
+:: Do chaining tasks Quest, PVP, WB, GVG/Invasion/Expedition, TG, Raid and then exit after completed them all
+:: https://github.com/9-9-9-9/Bit-Heroes-bot/wiki/Function-%22afk%22
+:: Consider adding `--profile=YourProfileName` so you no longer needed to select profile manually
+:: You can remove flag `--ear` to keep game online all day (but still gone if you got Disconnected)
+:: With steam version of BH, refer to AFK.steam.bat file
+
+:: Comment Out or Delete Unused Characters
+
+:: Switch to Character #1
+echo 'e' | call web.bot.bat change-character 1
+echo 'e' | call web.bot.bat afk a --ear --profile=name1
+
+:: Switch to Character #3
+echo 'e' | call web.bot.bat change-character 2
+echo 'e' | call web.bot.bat afk a --ear --profile=name2
+
+:: Switch to Character #3
+echo 'e' | call web.bot.bat change-character 3
+echo 'e' | call web.bot.bat afk a --ear --profile=name3
+
+:: Call itself to go in an infinite loop
+call MultiCharacter.AFK.web.bat
+
+:: Move this file to bot's folder in order to use (this file was distributed within `sample-script` folder so it unable to run)
\ No newline at end of file
diff --git a/src/main/java/bh/bot/Main.java b/src/main/java/bh/bot/Main.java
index db7f1aa..8d3226c 100644
--- a/src/main/java/bh/bot/Main.java
+++ b/src/main/java/bh/bot/Main.java
@@ -69,6 +69,7 @@ public static void main(String[] args) {
WorldBossTeamApp.class, //
RaidApp.class, //
PvpApp.class, //
+ QuestApp.class, //
InvasionApp.class, //
ExpeditionApp.class, //
TrialsApp.class, //
diff --git a/src/main/java/bh/bot/app/AbstractApplication.java b/src/main/java/bh/bot/app/AbstractApplication.java
index ddb6d7f..e2f993d 100644
--- a/src/main/java/bh/bot/app/AbstractApplication.java
+++ b/src/main/java/bh/bot/app/AbstractApplication.java
@@ -62,14 +62,14 @@ public void run(ParseArgumentsResult launchInfo) throws Exception {
if (this.argumentInfo.enableSavingDebugImages)
info("Enabled saving debug images");
-
+
initOutputDirectories();
-
+
if (isRequiredToLoadImages())
BwMatrixMeta.load();
-
+
Telegram.setAppName(getAppName());
-
+
warn(getLimitationExplain());
if (launchInfo.shutdownAfterExit) {
@@ -149,8 +149,10 @@ private void wrappedInternalRun3ForWindows(ParseArgumentsResult launchInfo) {
// mutex acquired
debug("Acquired Mutex handle");
} else {
- err("'%s' of %s is not allowed to run multiple instances at the same time because it may causes unexpected issues, please close previous process first!!!", this.getClass().getAnnotation(AppMeta.class).name(), Main.botName);
- err("If this message is wrong and you are only running a single instance of %s, please relaunch with flag `%s`", Main.botName, new FlagDisableMutex().getCode());
+ err("'%s' of %s is not allowed to run multiple instances at the same time because it may causes unexpected issues, please close previous process first!!!",
+ this.getClass().getAnnotation(AppMeta.class).name(), Main.botName);
+ err("If this message is wrong and you are only running a single instance of %s, please relaunch with flag `%s`",
+ Main.botName, new FlagDisableMutex().getCode());
Main.exit(Main.EXIT_CODE_MULTIPLE_INSTANCE_DETECTED);
}
} catch (Throwable t) {
@@ -186,7 +188,7 @@ private void launchThreadCheckVersion(final String appCode) {
if (skipCheckVersion())
return;
- //noinspection CodeBlock2Expr
+ // noinspection CodeBlock2Expr
Rad.exec(33, () -> {
CompletableFuture.runAsync(() -> {
if (!VersionUtil.checkForLatestVersion())
@@ -213,7 +215,7 @@ private void tryToCloseGameWindow(boolean closeGameWindowAfterExit) {
long timeLeft;
while ((timeLeft = deadline - System.currentTimeMillis()) > 0) {
- info(ColorizeUtil.formatError, "Game window is going to be closed after %d minute%s", waitXMinutes, waitXMinutes > 1 ? "s" : "");
+ info(ColorizeUtil.formatError, "Game window is going to be closed after %d minute%s", waitXMinutes, waitXMinutes > 1 ? "s" : "");
waitXMinutes--;
sleep((int) Math.min(60_000, timeLeft + 1));
}
@@ -354,7 +356,7 @@ public String getHelp() {
sb.append(" milliseconds)");
} else {
sb.append("around ");
- sb.append(Math.round((double)defaultMainLoopInterval / 1_000));
+ sb.append(Math.round((double) defaultMainLoopInterval / 1_000));
sb.append(" seconds (");
sb.append(defaultMainLoopInterval);
sb.append(" milliseconds)");
@@ -417,8 +419,7 @@ protected Point findImageBasedOnLastClick(BwMatrixMeta im) {
blackPixelDRgb, //
sc.getRGB(px[0], px[1]) & 0xFFFFFF, //
Configuration.Tolerant.color, //
- im.getOriginalPixelPart(px[0], px[1])
- )) {
+ im.getOriginalPixelPart(px[0], px[1]))) {
return null;
}
}
@@ -605,9 +606,8 @@ protected Tuple2 detectRadioButtons(Rectangle scanRect) {
try (ScreenCapturedResult screenCapturedResult = captureElementInEstimatedArea(
new Offset(Math.max(0, scanRect.x - positionTolerant), Math.max(0, scanRect.y - positionTolerant)),
- scanRect.width + positionTolerant * 2,
- scanRect.height + positionTolerant * 2
- )) {
+ scanRect.width + positionTolerant * 2,
+ scanRect.height + positionTolerant * 2)) {
BufferedImage sc = screenCapturedResult.image;
saveDebugImage(sc, "detectRadioButtons");
@@ -677,8 +677,9 @@ protected Tuple2 detectRadioButtons(Rectangle scanRect) {
else {
if (selectedRadioButtonIndex != curRadioButtonIndex)
throw new InvalidDataException(
- String.format("Found more than one selected radio button which is absolutely wrong! (no.%d conflicts with no.%d)", curRadioButtonIndex + 1, selectedRadioButtonIndex + 1)
- );
+ String.format(
+ "Found more than one selected radio button which is absolutely wrong! (no.%d conflicts with no.%d)",
+ curRadioButtonIndex + 1, selectedRadioButtonIndex + 1));
}
break;
}
@@ -686,8 +687,7 @@ protected Tuple2 detectRadioButtons(Rectangle scanRect) {
optionalDebug(debug, "detectRadioButtons captured at %3d,%3d with size %3dx%3d, match at %3d,%3d",
screenCapturedResult.x, screenCapturedResult.y,
screenCapturedResult.w, screenCapturedResult.h,
- x, y
- );
+ x, y);
startingCoords.add(new Point(x, y));
x += Math.max(0, im.getWidth() - 2);
}
@@ -699,16 +699,16 @@ protected Tuple2 detectRadioButtons(Rectangle scanRect) {
return new Tuple2<>(
startingCoords.stream()
- .map(c -> new Point(screenCapturedResult.x + c.x, screenCapturedResult.y + c.y))
- .toArray(Point[]::new),
- (byte) selectedRadioButtonIndex
- );
+ .map(c -> new Point(screenCapturedResult.x + c.x, screenCapturedResult.y + c.y))
+ .toArray(Point[]::new),
+ (byte) selectedRadioButtonIndex);
}
}
private static final int smallTalkSleepSecs = 60;
private static final int smallTalkSleepSecsWhenClicked = 3;
private static final int detectDcSleepSecs = 60;
+ private static final int leaveDungeonSleepSecs = 10;
private static final int reactiveAutoSleepSecs = 5;
private static final int closeEnterGameDialogNewsSleepSecs = 60;
private static final int persuadeSleepSecs = 60;
@@ -721,6 +721,7 @@ protected void internalDoSmallTasks(AtomicBoolean masterSwitch, SmallTasks st) {
try {
long nextSmallTalkEpoch = addSec(smallTalkSleepSecs);
long nextDetectDcEpoch = addSec(detectDcSleepSecs);
+ long nextLeaveDungeonEpoch = addSec(leaveDungeonSleepSecs);
long nextReactiveAuto = addSec(reactiveAutoSleepSecs);
final AtomicInteger continousRed = new AtomicInteger(0);
long nextCloseEnterGameDialogNews = addSec(closeEnterGameDialogNewsSleepSecs);
@@ -733,24 +734,30 @@ protected void internalDoSmallTasks(AtomicBoolean masterSwitch, SmallTasks st) {
boolean closeChatBoxDirectMessage = Configuration.closeChatBoxDirectMessage;
if (persuade) {
- info("Auto persuade had been turned on, that means in case of any persuade screen appears, bot will automatically persuade the monster with Gold. If you want to disable this feature, you can use '%s' flag", FlagDisablePersuade.FlagName);
+ info("Auto persuade had been turned on, that means in case of any persuade screen appears, bot will automatically persuade the monster with Gold. If you want to disable this feature, you can use '%s' flag",
+ FlagDisablePersuade.FlagName);
if (Configuration.enableDevFeatures) {
final String fileBribeName = "bribe.txt";
File fBribe = new File(fileBribeName);
if (fBribe.exists() && fBribe.isFile()) {
- List familiarsToBribe = Files.readAllLines(fBribe.toPath()).stream().filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toList());
+ List familiarsToBribe = Files.readAllLines(fBribe.toPath()).stream()
+ .filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toList());
if (familiarsToBribe.size() > 0) {
FlagBribe flagBribe = new FlagBribe();
ArrayList familiars = new ArrayList<>();
boolean ignoreFile = false;
for (String famName : familiarsToBribe)
try {
- familiars.add(flagBribe.parseParam(String.format("%s=%s", flagBribe.getCode(), famName)));
+ familiars.add(
+ flagBribe.parseParam(String.format("%s=%s", flagBribe.getCode(), famName)));
} catch (NotSupportedException ignored) {
- throw new InvalidDataException("Familiar's name '%s' within file %s is not supported!", famName, fileBribeName);
+ throw new InvalidDataException(
+ "Familiar's name '%s' within file %s is not supported!", famName,
+ fileBribeName);
} catch (Exception ex2) {
dev(ex2);
- err("Error occurs while trying to parse %s file. Content within that file will be ignored. Please raise an issue on my GitHub repo", fileBribeName);
+ err("Error occurs while trying to parse %s file. Content within that file will be ignored. Please raise an issue on my GitHub repo",
+ fileBribeName);
ignoreFile = true;
break;
}
@@ -761,10 +768,11 @@ protected void internalDoSmallTasks(AtomicBoolean masterSwitch, SmallTasks st) {
}
}
- /* // TODO enable this if Bribe feature to be enabled again
- for (Familiar f : argumentInfo.familiarToBribeWithGems)
- warn("Will persuade %s with gems", f.name());
- */
+ /*
+ * // TODO enable this if Bribe feature to be enabled again
+ * for (Familiar f : argumentInfo.familiarToBribeWithGems)
+ * warn("Will persuade %s with gems", f.name());
+ */
}
if (st.persuade && argumentInfo.disablePersuade)
@@ -776,14 +784,15 @@ protected void internalDoSmallTasks(AtomicBoolean masterSwitch, SmallTasks st) {
.yellow(" for all kind of familiars or ")
.red("always stay in front of your computer")
.yellow(" to persuade/bribe manually, otherwise bot can't continue your tasks and becomes waste of resources")
- .reset()
- );
-
+ .reset());
+
if (st.detectChatboxDirectMessage) {
if (closeChatBoxDirectMessage) {
- warn("You had configured to close chatbox direct message, screenshot of the message will be saved in folder %s", saveChatboxDirectMessageImageTo);
+ warn("You had configured to close chatbox direct message, screenshot of the message will be saved in folder %s",
+ saveChatboxDirectMessageImageTo);
} else {
- warn("You did not configure to close chatbox direct message, so the income messages will stay as is and you might be disconnected after amount of time. Screenshot of the message will be saved in folder %s", saveChatboxDirectMessageImageTo);
+ warn("You did not configure to close chatbox direct message, so the income messages will stay as is and you might be disconnected after amount of time. Screenshot of the message will be saved in folder %s",
+ saveChatboxDirectMessageImageTo);
}
}
@@ -793,6 +802,9 @@ protected void internalDoSmallTasks(AtomicBoolean masterSwitch, SmallTasks st) {
if (st.clickTalk && nextSmallTalkEpoch <= System.currentTimeMillis())
nextSmallTalkEpoch = doClickTalk();
+ if (st.preventLeaveDungeon && nextLeaveDungeonEpoch <= System.currentTimeMillis())
+ nextLeaveDungeonEpoch = detectLeaveDungeon();
+
if (st.clickDisconnect && nextDetectDcEpoch <= System.currentTimeMillis())
nextDetectDcEpoch = detectDisconnected(masterSwitch);
@@ -808,15 +820,14 @@ protected void internalDoSmallTasks(AtomicBoolean masterSwitch, SmallTasks st) {
if (persuade && nextPersuade <= System.currentTimeMillis())
nextPersuade = doPersuade(continousPersuadeScreen);
- if (showWarningWorldBossTeam && nextShowWarningWorldBossTeam <= System.currentTimeMillis())
- {
+ if (showWarningWorldBossTeam && nextShowWarningWorldBossTeam <= System.currentTimeMillis()) {
warningWatchWorldBossTeam(ColorizeUtil.formatWarning);
nextShowWarningWorldBossTeam = addSec(showWarningWorldBossTeamSleepSecs);
}
-
if (st.detectChatboxDirectMessage && nextDetectChatBoxDirectMessage <= System.currentTimeMillis())
- nextDetectChatBoxDirectMessage = detectChatBoxDirectMessage(closeChatBoxDirectMessage, masterSwitch);
+ nextDetectChatBoxDirectMessage = detectChatBoxDirectMessage(closeChatBoxDirectMessage,
+ masterSwitch);
}
} catch (Exception ex) {
ex.printStackTrace();
@@ -832,6 +843,7 @@ private long addSec(int secs) {
protected static class SmallTasks {
public final boolean clickTalk;
public final boolean clickDisconnect;
+ public final boolean preventLeaveDungeon;
public final boolean reactiveAuto;
public final boolean autoExit;
public final boolean closeEnterGameNewsDialog;
@@ -848,6 +860,7 @@ private SmallTasks(Builder b) {
this.persuade = b.f(5);
this.showWarningWorldBossTeam = b.f(6);
this.detectChatboxDirectMessage = b.f(7);
+ this.preventLeaveDungeon = b.f(8);
}
public static Builder builder() {
@@ -901,11 +914,16 @@ public Builder warningWorldBossTeam() {
public Builder detectChatboxDirectMessage() {
return this.set(7);
}
+
+ public Builder preventLeaveDungeon() {
+ return this.set(8);
+ }
}
}
private List> persuadeTargets = null;
private boolean anyManuallyPersuade = false;
+
private long doPersuade(AtomicInteger continousPersuadeScreen) {
Point pBribeButton = findImage(BwMatrixMeta.Metas.Globally.Buttons.persuadeBribe);
Point pPersuadeButton = pBribeButton != null ? null : findImage(BwMatrixMeta.Metas.Globally.Buttons.persuade);
@@ -928,26 +946,27 @@ private long doPersuade(AtomicInteger continousPersuadeScreen) {
new Tuple2<>(BwMatrixMeta.Metas.Persuade.Labels.quirrel, Familiar.Quirrel),
new Tuple2<>(BwMatrixMeta.Metas.Persuade.Labels.gobby, Familiar.Gobby),
new Tuple2<>(BwMatrixMeta.Metas.Persuade.Labels.rugumz, Familiar.Rugumz),
- new Tuple2<>(BwMatrixMeta.Metas.Persuade.Labels.moghur, Familiar.Moghur)
- );
+ new Tuple2<>(BwMatrixMeta.Metas.Persuade.Labels.moghur, Familiar.Moghur));
boolean doPersuadeGold = true;
- /* // TODO enable this if Bribe feature to be enabled again
- if (argumentInfo.familiarToBribeWithGems.size() > 0) {
- for (Tuple2 target : persuadeTargets) {
- PersuadeState ps = persuade(target._1, target._2, pPersuadeButton, pBribeButton);
- if (ps == PersuadeState.NotAvailable) {
- doPersuadeGold = false;
- break;
- }
- if (ps == PersuadeState.SuccessGem || ps == PersuadeState.SuccessGold) {
- doPersuadeGold = false;
- break;
- }
- }
- }
- */
+ /*
+ * // TODO enable this if Bribe feature to be enabled again
+ * if (argumentInfo.familiarToBribeWithGems.size() > 0) {
+ * for (Tuple2 target : persuadeTargets) {
+ * PersuadeState ps = persuade(target._1, target._2, pPersuadeButton,
+ * pBribeButton);
+ * if (ps == PersuadeState.NotAvailable) {
+ * doPersuadeGold = false;
+ * break;
+ * }
+ * if (ps == PersuadeState.SuccessGem || ps == PersuadeState.SuccessGold) {
+ * doPersuadeGold = false;
+ * break;
+ * }
+ * }
+ * }
+ */
if (doPersuadeGold) {
dev("doPersuadeGold");
@@ -1034,13 +1053,15 @@ private void persuade(boolean gold, Point pPersuadeButton, Point pBribeButton) {
if (pPersuadeButton != null) {
p = pPersuadeButton;
} else if (pBribeButton != null) {
- p = fromRelativeToAbsoluteBasedOnPreviousResult(BwMatrixMeta.Metas.Globally.Buttons.persuadeBribe, pBribeButton, Configuration.screenResolutionProfile.getOffsetButtonPersuade());
+ p = fromRelativeToAbsoluteBasedOnPreviousResult(BwMatrixMeta.Metas.Globally.Buttons.persuadeBribe,
+ pBribeButton, Configuration.screenResolutionProfile.getOffsetButtonPersuade());
}
} else {
if (pBribeButton != null) {
p = pBribeButton;
} else if (pPersuadeButton != null) {
- p = fromRelativeToAbsoluteBasedOnPreviousResult(BwMatrixMeta.Metas.Globally.Buttons.persuade, pPersuadeButton, Configuration.screenResolutionProfile.getOffsetButtonBribePersuade());
+ p = fromRelativeToAbsoluteBasedOnPreviousResult(BwMatrixMeta.Metas.Globally.Buttons.persuade,
+ pPersuadeButton, Configuration.screenResolutionProfile.getOffsetButtonBribePersuade());
}
}
@@ -1076,7 +1097,16 @@ private long detectDisconnected(AtomicBoolean masterSwitch) {
return addSec(detectDcSleepSecs);
}
+ private long detectLeaveDungeon() {
+ if (clickImage(BwMatrixMeta.Metas.Globally.Dialogs.leaveThisDungeon)) {
+ debug("Prevented leaving dungeon");
+ sendEscape();
+ }
+ return addSec(leaveDungeonSleepSecs);
+ }
+
private static final String saveChatboxDirectMessageImageTo = "out\\chatbox";
+
private long detectChatBoxDirectMessage(boolean closeChatBoxDirectMessage, AtomicBoolean masterSwitch) {
if (clickImage(BwMatrixMeta.Metas.Globally.Buttons.sendMessage)) {
try {
@@ -1088,7 +1118,7 @@ private long detectChatBoxDirectMessage(boolean closeChatBoxDirectMessage, Atomi
if (!closeChatBoxDirectMessage)
masterSwitch.set(true);
}
-
+
try {
int x = Configuration.gameScreenOffset.X.get();
int y = Configuration.gameScreenOffset.Y.get();
@@ -1101,7 +1131,8 @@ private long detectChatBoxDirectMessage(boolean closeChatBoxDirectMessage, Atomi
.get("out", "chatbox", "direct_message_" + System.currentTimeMillis() + ".bmp")
.toFile();
saveImage(sc, file);
- warn("Someone sent you a direct message, screenshot of the message has been saved into folder %s", saveChatboxDirectMessageImageTo);
+ warn("Someone sent you a direct message, screenshot of the message has been saved into folder %s",
+ saveChatboxDirectMessageImageTo);
} finally {
freeMem(sc);
}
@@ -1109,11 +1140,11 @@ private long detectChatBoxDirectMessage(boolean closeChatBoxDirectMessage, Atomi
err("Failed to capture and save direct message");
err(t);
}
-
+
if (closeChatBoxDirectMessage) {
clickImage(BwMatrixMeta.Metas.Globally.Buttons.closeChatBox);
}
-
+
}
return addSec(detectChatboxDirectMessageSleepSecs);
}
@@ -1179,7 +1210,7 @@ private long closeEnterGameDialogNews() {
if (clickImage(BwMatrixMeta.Metas.Globally.Dialogs.news)) {
sendEscape();
info("Ya I'm here");
-
+
}
return addSec(closeEnterGameDialogNewsSleepSecs);
}
@@ -1315,6 +1346,117 @@ protected boolean tryEnterWorldBoss(boolean doWorldBoss, UserConfig userConfig,
return clickImage(BwMatrixMeta.Metas.WorldBoss.Buttons.summonOnListingWorldBosses);
}
+ protected boolean tryEnterQuest(boolean doQuest, UserConfig userConfig, Supplier isBlocked,
+ InteractionUtil.Screen.Game game) {
+ Point coord = findImage(BwMatrixMeta.Metas.Dungeons.Labels.zones);
+ if (coord == null)
+ return false;
+
+ if (isBlocked.get() || !doQuest) {
+ spamEscape(1);
+ return false;
+ }
+ BwMatrixMeta.Metas.Dungeons.Labels.zones.setLastMatchPoint(coord.x, coord.y);
+ debug("Trying to detect a valid level");
+ boolean triedBossCoords = false;
+ boolean triedStarCoords = false;
+ boolean triedEmptyStarCoords = false;
+ boolean triedNextLevelCoords = false;
+ // First check for the Boss level
+ // Second check for any place with a star
+ // Third check for any place without a star
+ // Fourth check for any level
+ // Todo: Scan in a few areas
+ boolean foundBossCoords = false;
+ boolean foundStarCoords = false;
+ boolean foundEmptyStarCoords = false;
+ boolean foundNextLevelCoords = false;
+
+ ArrayList levelCoords = null;
+ boolean enteredLevel = false;
+
+ while (!enteredLevel && (!triedBossCoords || !triedStarCoords || !triedEmptyStarCoords || !triedNextLevelCoords)) {
+ if (!triedBossCoords) {
+ debug("Looking for boss level");
+ ArrayList searchCoords = game.findByScanScreen(BwMatrixMeta.Metas.Dungeons.Buttons.bossLevel, 63, 96, 36, 115);
+ try {
+ foundBossCoords = searchCoords.get(0) != null;
+ } catch (IndexOutOfBoundsException e) {}
+ if (foundBossCoords) {
+ levelCoords = searchCoords;
+ }
+ triedBossCoords = true;
+ } else if (!triedStarCoords) {
+ debug("Looking for stars for a level");
+ ArrayList searchCoords = game.findByScanScreen(BwMatrixMeta.Metas.Dungeons.Buttons.star, 63, 96, 36, 115);
+ try {
+ foundStarCoords = searchCoords.get(0) != null;
+ } catch (IndexOutOfBoundsException e) {}
+ if (foundStarCoords) {
+ levelCoords = searchCoords;
+ }
+ triedStarCoords = true;
+ } else if (!triedEmptyStarCoords) {
+ debug("Looking for empty stars for a level");
+ ArrayList searchCoords = game.findByScanScreen(BwMatrixMeta.Metas.Dungeons.Buttons.emptyStar, 63, 96, 36, 115);
+ try {
+ foundEmptyStarCoords = searchCoords.get(0) != null;
+ } catch (IndexOutOfBoundsException e) {}
+ if (foundEmptyStarCoords) {
+ levelCoords = searchCoords;
+ }
+ triedEmptyStarCoords = true;
+ } else if (!triedNextLevelCoords) {
+ debug("Looking for next level");
+ ArrayList searchCoords = game.findByScanScreen(BwMatrixMeta.Metas.Dungeons.Buttons.questLevel, 63, 96, 36, 115);
+ try {
+ foundNextLevelCoords = searchCoords.get(0) != null;
+ } catch (IndexOutOfBoundsException e) {}
+ if (foundNextLevelCoords) {
+ levelCoords = searchCoords;
+ }
+ triedNextLevelCoords = true;
+ }
+ if (levelCoords == null) {
+ debug("No levels found");
+ continue;
+ }
+ debug("Found some valid possible levels");
+ debug(levelCoords);
+ for (int i = 0; i < levelCoords.size(); i++) {
+ Point level = levelCoords.get(i);
+ debug("Trying to enter level " + level);
+ mouseMoveAndClickAndHide(level);
+ sleep(2_000);
+ // TODO: If (quest header of some sort clickable) breakLoop
+ if (clickImage(BwMatrixMeta.Metas.Dungeons.Buttons.energy) || clickImage(BwMatrixMeta.Metas.Dungeons.Buttons.heroicEnergy)) {
+ debug("Found level to enter");
+ enteredLevel = true;
+ break;
+ }
+ }
+ }
+
+ debug("Clicking to enter level");
+ if (clickImage(BwMatrixMeta.Metas.Dungeons.Labels.enterLevel)) {
+ return true;
+ }
+ BwMatrixMeta im = null;
+ if (UserConfig.isNormalMode(userConfig.questMode)) {
+ im = BwMatrixMeta.Metas.Dungeons.Buttons.difficultyNormal;
+ } else if (UserConfig.isHardMode(userConfig.questMode)) {
+ im = BwMatrixMeta.Metas.Dungeons.Buttons.difficultyHard;
+ } else if (UserConfig.isHeroicMode(userConfig.questMode)) {
+ im = BwMatrixMeta.Metas.Dungeons.Buttons.difficultyHeroic;
+ } else {
+ throw new InvalidDataException("Unknown quest mode value: %d", userConfig.questMode);
+ }
+ if (clickImage(im)) {
+ return true;
+ }
+ return false;
+ }
+
protected boolean tryEnterRaid(boolean doRaid, UserConfig userConfig, Supplier isBlocked) {
Point coord = findImage(BwMatrixMeta.Metas.Raid.Labels.labelInSummonDialog);
if (coord == null)
@@ -1327,8 +1469,7 @@ protected boolean tryEnterRaid(boolean doRaid, UserConfig userConfig, Supplier result = detectRadioButtons(
- Configuration.screenResolutionProfile.getRectangleRadioButtonsOfRaid()
- );
+ Configuration.screenResolutionProfile.getRectangleRadioButtonsOfRaid());
Point[] points = result._1;
int selectedLevel = result._2 + 1;
info("Found %d raid levels, selected %s", points.length, UserConfig.getRaidLevelDesc(selectedLevel));
@@ -1350,34 +1491,26 @@ protected boolean tryEnterRaid(boolean doRaid, UserConfig userConfig, Supplier {
s = s.trim().toLowerCase();
if (!ValidationUtil.isValidUserProfileName(s))
@@ -1604,11 +1738,17 @@ protected UserConfig getPredefinedUserConfigFromProfileName(String ask) throws I
}
protected void warningPvpTargetSelectionCase() {
- info(Cu.i().yellow("** WARNING ** ").red("about selecting PVP target").yellow(" feature, to prevent wrong targeting and un-expected loss on other target-selectable ranking like GVG... (which having the same target-selection method), ").cyan("while doing AFK").yellow(", this feature works and ").cyan("only works when bot itself attends to PVP").yellow(" by selecting the PVP icon (top left of game screen). That means if you select the PVP icon yourself or enter PVP before bot click etc.., it only select the first line as target as default").reset());
+ info(Cu.i().yellow("** WARNING ** ").red("about selecting PVP target").yellow(
+ " feature, to prevent wrong targeting and un-expected loss on other target-selectable ranking like GVG... (which having the same target-selection method), ")
+ .cyan("while doing AFK").yellow(", this feature works and ")
+ .cyan("only works when bot itself attends to PVP")
+ .yellow(" by selecting the PVP icon (top left of game screen). That means if you select the PVP icon yourself or enter PVP before bot click etc.., it only select the first line as target as default")
+ .reset());
}
protected void warningWatchWorldBossTeam(Function ansiFormat) {
- info(ansiFormat, "*** NOTICE: REMEMBER YOU HAVE TO WATCH/CHECK THE GAME SOMETIME TO PREVENT UN-EXPECTED HANG/LOSS DUE TO UN-MANAGED BEHAVIORS LIKE MISSING MEMBERS, RE-GROUP FAILED, INCORRECT GROUP MATCHING...ETC ***");
+ info(ansiFormat,
+ "*** NOTICE: REMEMBER YOU HAVE TO WATCH/CHECK THE GAME SOMETIME TO PREVENT UN-EXPECTED HANG/LOSS DUE TO UN-MANAGED BEHAVIORS LIKE MISSING MEMBERS, RE-GROUP FAILED, INCORRECT GROUP MATCHING...ETC ***");
}
protected int getDefaultMainLoopInterval() {
diff --git a/src/main/java/bh/bot/app/AfkApp.java b/src/main/java/bh/bot/app/AfkApp.java
index 3a9e540..715d073 100644
--- a/src/main/java/bh/bot/app/AfkApp.java
+++ b/src/main/java/bh/bot/app/AfkApp.java
@@ -48,6 +48,7 @@ public class AfkApp extends AbstractApplication {
private final AtomicLong blockPvpUntil = new AtomicLong(0);
private final AtomicLong blockWorldBossUntil = new AtomicLong(0);
private final AtomicLong blockRaidUntil = new AtomicLong(0);
+ private final AtomicLong blockQuestUntil = new AtomicLong(0);
private final AtomicLong blockGvgAndInvasionAndExpeditionUntil = new AtomicLong(0);
private final AtomicLong blockTrialsAndGauntletUntil = new AtomicLong(0);
private final AtomicBoolean isOnPvp = new AtomicBoolean(false);
@@ -63,10 +64,11 @@ protected void internalRun(String[] args) {
eventList = getAttendablePlaces(getEventListFromArg(args));
boolean doRaid = eventList.contains(AttendablePlaces.raid);
+ boolean doQuest = eventList.contains(AttendablePlaces.quest);
boolean doWorldBoss = eventList.contains(AttendablePlaces.worldBoss);
boolean doExpedition = eventList.contains(AttendablePlaces.expedition);
boolean doPVP = eventList.contains(AttendablePlaces.pvp);
- if (doRaid || doWorldBoss || doExpedition || doPVP) {
+ if (doRaid || doWorldBoss || doExpedition || doPVP || doQuest) {
userConfig = getPredefinedUserConfigFromProfileName("You want to do Raid/World Boss (Solo)/Expedition/PVP so you have to specific profile name first!\nSelect an existing profile:");
try {
@@ -75,6 +77,10 @@ protected void internalRun(String[] args) {
userConfig.getRaidLevelDesc());
}
+ if (doQuest) {
+ info(ColorizeUtil.formatInfo, "You have selected %s mode", userConfig.getQuestModeDesc());
+ }
+
if (doWorldBoss) {
info(ColorizeUtil.formatInfo, "You have selected world boss %s", userConfig.getWorldBossLevelDesc());
warn("World Boss is solo only and does not support select mode of World Boss (Normal/Hard/Heroic), only select by default So which boss do you want to hit? Choose it before turn this on");
@@ -113,6 +119,7 @@ protected void internalRun(String[] args) {
eventList.contains(AttendablePlaces.pvp), //
eventList.contains(AttendablePlaces.worldBoss), //
eventList.contains(AttendablePlaces.raid), //
+ eventList.contains(AttendablePlaces.quest), //
eventList.contains(AttendablePlaces.gvg), //
eventList.contains(AttendablePlaces.invasion), //
eventList.contains(AttendablePlaces.expedition), //
@@ -130,6 +137,7 @@ protected void internalRun(String[] args) {
.closeEnterGameNewsDialog() //
.persuade() //
.detectChatboxDirectMessage() //
+ .preventLeaveDungeon() //
.build() //
), //
() -> doCheckGameScreenOffset(masterSwitch) //
@@ -142,6 +150,7 @@ private void doLoop(//
boolean doPvp, //
boolean doWorldBoss, //
boolean doRaid, //
+ boolean doQuest, //
boolean doGvg, //
boolean doInvasion, //
boolean doExpedition, //
@@ -149,6 +158,7 @@ private void doLoop(//
boolean doGauntlet //
) {
try {
+
info(ColorizeUtil.formatInfo, "\n\nStarting AFK");
boolean isUnknownGvgOrInvasionOrExpedition = (doGvg && doInvasion) || (doGvg && doExpedition)
|| (doInvasion && doExpedition);
@@ -156,6 +166,10 @@ private void doLoop(//
int continuousNotFound = 0;
final Point coordinateHideMouse = new Point(0, 0);
final ArrayList>> taskList = new ArrayList<>();
+ // Add Questing as first task
+ if (doQuest)
+ taskList.add(new Tuple3<>(AttendablePlaces.quest, blockQuestUntil, QuestApp.getPredefinedImageActions()));
+
NextAction naBtnFightPvp = null;
if (doPvp) {
List pvpPia = PvpApp.getPredefinedImageActions();
@@ -206,6 +220,7 @@ private void doLoop(//
}
ArrayList outOfTurnNextActionList = new ArrayList<>();
+ addOutOfTurnActionsToList(outOfTurnNextActionList, QuestApp.getPredefinedImageActions());
addOutOfTurnActionsToList(outOfTurnNextActionList, PvpApp.getPredefinedImageActions());
addOutOfTurnActionsToList(outOfTurnNextActionList, WorldBossApp.getPredefinedImageActions());
addOutOfTurnActionsToList(outOfTurnNextActionList, RaidApp.getPredefinedImageActions());
@@ -226,11 +241,14 @@ private void doLoop(//
final Supplier isWorldBossBlocked = () -> !isNotBlocked(blockWorldBossUntil);
final Supplier isRaidBlocked = () -> !isNotBlocked(blockRaidUntil);
+ final Supplier isQuestBlocked = () -> !isNotBlocked(blockQuestUntil);
Main.warningSupport();
if (doRaid)
info(ColorizeUtil.formatInfo, "Raid: %s of %s", userConfig.getRaidModeDesc(), userConfig.getRaidLevelDesc());
+ if (doQuest)
+ info(ColorizeUtil.formatInfo, "Quest: %s", userConfig.getQuestModeDesc());
if (doWorldBoss)
info(ColorizeUtil.formatInfo, "World Boss: %s", userConfig.getWorldBossLevelDesc());
if (doExpedition)
@@ -325,6 +343,13 @@ private void doLoop(//
continue ML;
}
+ if (tryEnterQuest(doQuest, userConfig, isQuestBlocked, this.gameScreenInteractor)) {
+ debug("tryEnterQuest");
+ continuousNotFound = 0;
+ moveCursor(coordinateHideMouse);
+ continue ML;
+ }
+
if (tryEnterWorldBoss(doWorldBoss, userConfig, isWorldBossBlocked)) {
debug("tryEnterWorldBoss");
continuousNotFound = 0;
@@ -461,6 +486,8 @@ else if (attendablePlace == AttendablePlaces.worldBoss)
x = blockWorldBossUntil;
else if (attendablePlace == AttendablePlaces.raid)
x = blockRaidUntil;
+ else if (attendablePlace == AttendablePlaces.quest)
+ x = blockQuestUntil;
else if (attendablePlace == AttendablePlaces.gvg || attendablePlace == AttendablePlaces.invasion
|| attendablePlace == AttendablePlaces.expedition)
x = blockGvgAndInvasionAndExpeditionUntil;
@@ -480,6 +507,7 @@ private ArrayList getAttendablePlaces(AfkBatch afkBatch) {
AttendablePlaces.gvg, //
AttendablePlaces.gauntlet, //
+ AttendablePlaces.quest, //
AttendablePlaces.pvp, //
AttendablePlaces.worldBoss, //
AttendablePlaces.raid //
@@ -502,12 +530,15 @@ private ArrayList getAttendablePlaces(AfkBatch afkBatch) {
eventList.add(AttendablePlaces.worldBoss);
if (argumentInfo.eRaid || afkBatch.doRaid)
eventList.add(AttendablePlaces.raid);
+ if (argumentInfo.eQuest || afkBatch.doQuest)
+ eventList.add(AttendablePlaces.quest);
//
if (eventList.size() == 0) {
final List