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 menuItems = Stream.of( MenuItem.from(AttendablePlaces.pvp), MenuItem.from(AttendablePlaces.worldBoss), MenuItem.from(AttendablePlaces.raid), + MenuItem.from(AttendablePlaces.quest), MenuItem.from("GVG/Expedition/Invasion", AttendablePlaces.gvg, AttendablePlaces.expedition, AttendablePlaces.invasion), MenuItem.from("Trials/Gauntlet", AttendablePlaces.trials, AttendablePlaces.gauntlet), MenuItem.from(AttendablePlaces.pvp, AttendablePlaces.worldBoss, AttendablePlaces.raid), @@ -590,6 +621,7 @@ protected String getLimitationExplain() { private static final char codeWorldBoss1 = 'B'; private static final char codeWorldBoss2 = 'W'; private static final char codeRaid = 'R'; + private static final char codeQuest = 'Q'; private static final char codeInvasion = 'I'; private static final char codeExpedition = 'E'; private static final char codeGVG = 'V'; @@ -600,12 +632,13 @@ protected String getLimitationExplain() { private static final char codeComboTrialsGauntlet = '3'; private static final char codeComboAll = 'A'; - private static final String shortDescArg = String.format("%s (PVP), %s (World Boss), %s (Raid), %s (Invasion), %s (Expedition), %s (GVG), %s (Gauntlet), %s (Trials), %s (PVP/World Boss/Raid), %s (Invasion/GVG/Expedition), %s (Trials/Gauntlet), %s (All)", codePvp, codeWorldBoss1, codeRaid, codeInvasion, codeExpedition, codeGVG, codeGauntlet, codeTrials, codeComboPvpWorldBossRaid, codeComboInvasionGvgExpedition, codeComboTrialsGauntlet, codeComboAll); + private static final String shortDescArg = String.format("%s (PVP), %s (World Boss), %s (Raid), %s (Quest), %s (Invasion), %s (Expedition), %s (GVG), %s (Gauntlet), %s (Trials), %s (PVP/World Boss/Raid), %s (Invasion/GVG/Expedition), %s (Trials/Gauntlet), %s (All)", codePvp, codeWorldBoss1, codeRaid, codeQuest, codeInvasion, codeExpedition, codeGVG, codeGauntlet, codeTrials, codeComboPvpWorldBossRaid, codeComboInvasionGvgExpedition, codeComboTrialsGauntlet, codeComboAll); private static class AfkBatch { public boolean doPvp; public boolean doWorldBoss; public boolean doRaid; + public boolean doQuest; public boolean doInvasion; public boolean doExpedition; public boolean doGvg; @@ -622,6 +655,7 @@ private static AfkBatch getEventListFromArg(String[] args) { result.doPvp = true; result.doWorldBoss = true; result.doRaid = true; + result.doQuest = true; result.doInvasion = true; result.doExpedition = true; result.doGvg = true; @@ -644,6 +678,8 @@ private static AfkBatch getEventListFromArg(String[] args) { result.doWorldBoss = true; } else if (c == codeRaid) { result.doRaid = true; + } else if (c == codeQuest) { + result.doQuest = true; } else if (c == codeInvasion) { result.doInvasion = true; } else if (c == codeGVG) { diff --git a/src/main/java/bh/bot/app/SettingApp.java b/src/main/java/bh/bot/app/SettingApp.java index a141a45..b2cabdd 100644 --- a/src/main/java/bh/bot/app/SettingApp.java +++ b/src/main/java/bh/bot/app/SettingApp.java @@ -29,10 +29,11 @@ protected void internalRun(String[] args) { if (file.exists() && file.isDirectory()) throw new InvalidDataException("%s is a directory", fileName); Tuple2 resultLoadUserConfig = Configuration.loadUserConfig(cfgProfileName); - int raidLevel, raidMode, worldBossLevel, expeditionPlace, pvpTarget; + int raidLevel, raidMode, worldBossLevel, expeditionPlace, pvpTarget, questMode; if (resultLoadUserConfig._1) { raidLevel = resultLoadUserConfig._2.raidLevel; raidMode = resultLoadUserConfig._2.raidMode; + questMode = resultLoadUserConfig._2.questMode; worldBossLevel = resultLoadUserConfig._2.worldBossLevel; expeditionPlace = resultLoadUserConfig._2.expeditionPlace; pvpTarget = resultLoadUserConfig._2.pvpTarget; @@ -47,6 +48,11 @@ protected void internalRun(String[] args) { else info(ColorizeUtil.formatInfo, "You haven't specified Raid mode (Normal/Hard/Heroic)"); + if (UserConfig.isValidDifficultyMode(resultLoadUserConfig._2.questMode)) + info(ColorizeUtil.formatInfo, "Selected Quest mode %s", resultLoadUserConfig._2.getQuestModeDesc()); + else + info(ColorizeUtil.formatInfo, "You haven't specified Quest mode (Normal/Hard/Heroic)"); + if (resultLoadUserConfig._2.isValidWorldBossLevel()) info(ColorizeUtil.formatInfo, "Selected World Boss (Solo) %s", resultLoadUserConfig._2.getWorldBossLevelDesc()); else @@ -67,6 +73,7 @@ protected void internalRun(String[] args) { } else { raidLevel = 0; raidMode = 0; + questMode = 0; worldBossLevel = 0; expeditionPlace = 0; pvpTarget = 0; @@ -90,6 +97,13 @@ protected void internalRun(String[] args) { tmp = readIntInput(sb.toString(), modeRange._1, modeRange._2); raidMode = tmp == null ? raidMode : tmp; // + sb = new StringBuilder("All Quest's difficulty mode:\n"); + for (byte rl = modeRange._1; rl <= modeRange._2; rl++) + sb.append(String.format(" %2d. %s\n", rl, UserConfig.getDifficultyModeDesc(rl, "Quest"))); + sb.append("Specific Quest mode? This will be used when a level with difficulties happens to be selected"); + tmp = readIntInput(sb.toString(), modeRange._1, modeRange._2); + questMode = tmp == null ? questMode : tmp; + // final Tuple2 woldBossLevelRange = UserConfig.getWorldBossLevelRange(); sb = new StringBuilder("All World Boss levels:\n"); for (int rl = woldBossLevelRange._1; rl <= woldBossLevelRange._2; rl++) @@ -115,7 +129,7 @@ protected void internalRun(String[] args) { tmp = readIntInput(sb.toString(), pvpTargetRange._1, pvpTargetRange._2); pvpTarget = tmp == null ? pvpTarget : tmp; // - UserConfig newCfg = new UserConfig(cfgProfileName, (byte) raidLevel, (byte) raidMode, (byte) worldBossLevel, (byte) expeditionPlace, (byte) pvpTarget); + UserConfig newCfg = new UserConfig(cfgProfileName, (byte) raidLevel, (byte) raidMode, (byte) worldBossLevel, (byte) expeditionPlace, (byte) pvpTarget, (byte) questMode); sb = new StringBuilder("Your setting:\n"); if (newCfg.isValidRaidLevel() && UserConfig.isValidDifficultyMode(newCfg.raidMode)) @@ -123,6 +137,11 @@ protected void internalRun(String[] args) { else sb.append(" raid has not been set"); sb.append('\n'); + if (UserConfig.isValidDifficultyMode(newCfg.questMode)) + sb.append(String.format(" %s mode of quest", UserConfig.getDifficultyModeDesc((byte) questMode, "Quest"))); + else + sb.append(" quest has not been set"); + sb.append('\n'); if (newCfg.isValidWorldBossLevel()) sb.append(String.format(" world boss (solo) %s", UserConfig.getWorldBossLevelDesc((byte) worldBossLevel))); else @@ -151,6 +170,7 @@ protected void internalRun(String[] args) { if (save) { sb = new StringBuilder(); sb.append(String.format("%s=%d\n", UserConfig.raidLevelKey, raidLevel)); + sb.append(String.format("%s=%d\n", UserConfig.questModeKey, questMode)); sb.append(String.format("%s=%d\n", UserConfig.raidModeKey, raidMode)); sb.append(String.format("%s=%d\n", UserConfig.worldBossLevelKey, worldBossLevel)); sb.append(String.format("%s=%d\n", UserConfig.expeditionPlaceKey, expeditionPlace)); diff --git a/src/main/java/bh/bot/app/farming/QuestApp.java b/src/main/java/bh/bot/app/farming/QuestApp.java new file mode 100644 index 0000000..fbc65fb --- /dev/null +++ b/src/main/java/bh/bot/app/farming/QuestApp.java @@ -0,0 +1,79 @@ +package bh.bot.app.farming; + +import bh.bot.Main; +import bh.bot.common.exceptions.InvalidDataException; +import bh.bot.common.types.AttendablePlace; +import bh.bot.common.types.AttendablePlaces; +import bh.bot.common.types.UserConfig; +import bh.bot.common.types.annotations.AppMeta; +import bh.bot.common.types.annotations.RequireSingleInstance; +import bh.bot.common.types.images.BwMatrixMeta; +import bh.bot.common.utils.ColorizeUtil; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + +import static bh.bot.common.Log.err; +import static bh.bot.common.Log.info; + +@AppMeta(code = "quest", name = "Quest", displayOrder = 1, argRequired = false, argDefault = "2", argType = "number") +@RequireSingleInstance +public class QuestApp extends AbstractDoFarmingApp { + private final Supplier isQuestBlocked = () -> false; + private UserConfig userConfig; + + @Override + protected boolean readMoreInput() throws IOException { + userConfig = getPredefinedUserConfigFromProfileName("You want to do Quests so you have to specific profile name first!\nSelect an existing profile:"); + + try { + info( + ColorizeUtil.formatInfo, + "You have selected %s mode", + userConfig.getQuestModeDesc() + ); + return true; + } catch (InvalidDataException ex2) { + err(ex2.getMessage()); + printRequiresSetting(); + Main.exit(Main.EXIT_CODE_INCORRECT_LEVEL_AND_DIFFICULTY_CONFIGURATION); + return false; + } + } + + @Override + protected AttendablePlace getAttendablePlace() { + return AttendablePlaces.quest; + } + + @Override + protected List getInternalPredefinedImageActions() { + return QuestApp.getPredefinedImageActions(); + } + + @Override + protected boolean doCustomAction() { + return tryEnterQuest(true, userConfig, isQuestBlocked, this.gameScreenInteractor); + } + + public static List getPredefinedImageActions() { + return Arrays.asList( + new NextAction(BwMatrixMeta.Metas.Dungeons.Buttons.accept, false, false), + new NextAction(BwMatrixMeta.Metas.Dungeons.Buttons.rerun, true, false), + new NextAction(BwMatrixMeta.Metas.Dungeons.Buttons.collect, false, false), + new NextAction(BwMatrixMeta.Metas.Dungeons.Dialogs.notEnoughEnergy, false, true) + ); + } + + @Override + protected String getLimitationExplain() { + return "Quest does not support level select, so choose it before turn this on"; + } + + @Override + protected int getDefaultMainLoopInterval() { + return 1_000; + } +} \ No newline at end of file diff --git a/src/main/java/bh/bot/common/Configuration.java b/src/main/java/bh/bot/common/Configuration.java index 63f9ebc..f385963 100644 --- a/src/main/java/bh/bot/common/Configuration.java +++ b/src/main/java/bh/bot/common/Configuration.java @@ -172,7 +172,7 @@ public static Tuple2 loadUserConfig(String cfgProfileName) return new Tuple2<>(false, null); } - byte raidLevel, raidMode, worldBossLevel, expeditionPlace, pvpTarget; + byte raidLevel, raidMode, questMode, worldBossLevel, expeditionPlace, pvpTarget; info("Going to load configuration from %s", fileCfg.getName()); @@ -187,6 +187,12 @@ public static Tuple2 loadUserConfig(String cfgProfileName) throw new InvalidDataException("Value of key '%s' is not a number", UserConfig.raidLevelKey); } + try { + questMode = Byte.parseByte(readKey(properties, UserConfig.questModeKey, "0", "Not specified")); + } catch (NumberFormatException ex) { + throw new InvalidDataException("Value of key '%s' is not a number", UserConfig.questModeKey); + } + try { raidMode = Byte.parseByte(readKey(properties, UserConfig.raidModeKey, "0", "Not specified")); } catch (NumberFormatException ex) { @@ -211,7 +217,7 @@ public static Tuple2 loadUserConfig(String cfgProfileName) throw new InvalidDataException("Value of key '%s' is not a number", UserConfig.pvpTargetKey); } - return new Tuple2<>(true, new UserConfig(cfgProfileName, raidLevel, raidMode, worldBossLevel, expeditionPlace, pvpTarget)); + return new Tuple2<>(true, new UserConfig(cfgProfileName, raidLevel, raidMode, worldBossLevel, expeditionPlace, pvpTarget, questMode)); } @SuppressWarnings("SameParameterValue") diff --git a/src/main/java/bh/bot/common/types/AttendablePlace.java b/src/main/java/bh/bot/common/types/AttendablePlace.java index c7c6a42..acfb044 100644 --- a/src/main/java/bh/bot/common/types/AttendablePlace.java +++ b/src/main/java/bh/bot/common/types/AttendablePlace.java @@ -9,45 +9,46 @@ public class AttendablePlace { public final String name; - public final int id; + public final long id; public final BwMatrixMeta img; public final boolean left; public final short procedureTicketMinutes; - public AttendablePlace(String name, int id, String imgName, boolean left) throws IOException { + public AttendablePlace(String name, long id, String imgName, boolean left) throws IOException { this(name, id, imgName, left, (short) 30); } - public AttendablePlace(String name, int id, String imgCode, boolean left, int procedureTicketMinutes) throws IOException { + public AttendablePlace(String name, long id, String imgCode, boolean left, int procedureTicketMinutes) + throws IOException { this.name = name; this.id = id; - + String imgFile; - switch(imgCode) { - case "pvp": - case "world-boss": - case "raid": - case "invasion": - case "trials": - case "gvg": - case "gauntlet": - case "expedition": - imgFile = imgCode + "3"; - break; - default: - imgFile = imgCode; - break; + switch (imgCode) { + case "pvp": + case "world-boss": + case "raid": + case "invasion": + case "trials": + case "gvg": + case "quest": + case "gauntlet": + case "expedition": + imgFile = imgCode + "3"; + break; + default: + imgFile = imgCode; + break; } this.img = BwMatrixMeta.from( String.format("labels/attendable-places/%s?", imgFile), new Offset(0, 0), - 0xFFFFFF - ); + 0xFFFFFF); this.left = left; this.procedureTicketMinutes = (short) procedureTicketMinutes; } - public int getId() { + public long getId() { return id; } diff --git a/src/main/java/bh/bot/common/types/AttendablePlaces.java b/src/main/java/bh/bot/common/types/AttendablePlaces.java index cca7078..462259f 100644 --- a/src/main/java/bh/bot/common/types/AttendablePlaces.java +++ b/src/main/java/bh/bot/common/types/AttendablePlaces.java @@ -5,15 +5,16 @@ public class AttendablePlaces { public static class Ids { // Right - public static final int Invasion = 0b00000001; - public static final int Trials = 0b00000010; - public static final int GvG = 0b00000100; - public static final int Gauntlet = 0b00001000; - public static final int Expedition = 0b00010000; + public static final long Invasion = 0b000000001; + public static final long Trials = 0b000000010; + public static final long GvG = 0b000000100; + public static final long Gauntlet = 0b000001000; + public static final long Expedition = 0b000010000; // Left - public static final int Pvp = 0b00100000; - public static final int WorldBoss = 0b01000000; - public static final int Raid = 0b10000000; + public static final long Pvp = 0b000100000; + public static final long WorldBoss = 0b001000000; + public static final long Raid = 0b010000000; + public static final long Quest = 0b100000000; } public static AttendablePlace invasion = null; @@ -25,6 +26,8 @@ public static class Ids { public static AttendablePlace pvp = null; public static AttendablePlace worldBoss = null; public static AttendablePlace raid = null; + public static AttendablePlace quest = null; + static { try { @@ -37,6 +40,7 @@ public static class Ids { pvp = new AttendablePlace("PVP", AttendablePlaces.Ids.Pvp, "pvp", true); worldBoss = new AttendablePlace("World Boss", AttendablePlaces.Ids.WorldBoss, "world-boss", true, 60); raid = new AttendablePlace("Raid", AttendablePlaces.Ids.Raid, "raid", true, 120); + quest = new AttendablePlace("Quest", AttendablePlaces.Ids.Quest, "quest", true, 120); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/bh/bot/common/types/ParseArgumentsResult.java b/src/main/java/bh/bot/common/types/ParseArgumentsResult.java index f10d270..fdcc1b4 100644 --- a/src/main/java/bh/bot/common/types/ParseArgumentsResult.java +++ b/src/main/java/bh/bot/common/types/ParseArgumentsResult.java @@ -30,6 +30,7 @@ public class ParseArgumentsResult { public boolean ePvp; public boolean eWorldBoss; public boolean eRaid; + public boolean eQuest; public ScreenResolutionProfile screenResolutionProfile; public String cfgProfileName; public ArrayList familiarToBribeWithGems; diff --git a/src/main/java/bh/bot/common/types/ScreenResolutionProfile.java b/src/main/java/bh/bot/common/types/ScreenResolutionProfile.java index e5355cd..b9becd1 100644 --- a/src/main/java/bh/bot/common/types/ScreenResolutionProfile.java +++ b/src/main/java/bh/bot/common/types/ScreenResolutionProfile.java @@ -22,6 +22,8 @@ public abstract class ScreenResolutionProfile { public abstract Offset getOffsetButtonDungeonReRun(); + public abstract Offset getOffsetButtonDungeonStar(); + public abstract Offset getOffsetButtonTalkRightArrow(); public abstract Offset getOffsetButtonReconnect(); @@ -164,10 +166,22 @@ public abstract class ScreenResolutionProfile { public abstract Offset getOffsetDialogNotEnoughInvasionBadges(); + public abstract Offset getOffsetDialogNotEnoughDungeonEnergy(); + + public abstract Offset getOffsetLabelDungeonEnterLevel(); + + public abstract Offset getOffsetLabelDungeonZones(); + public abstract Offset getOffsetButtonPlayTrials(); + public abstract Offset getOffsetButtonEnergyDungeon(); + + public abstract Offset getOffsetButtonHeroicEnergyDungeon(); + public abstract Offset getOffsetButtonAcceptTrials(); + public abstract Offset getOffsetButtonCollectQuest(); + public abstract Offset getOffsetButtonTownAfterCompetedTrials(); public abstract Offset getOffsetDialogNotEnoughTrialsTokens(); @@ -208,10 +222,18 @@ public abstract class ScreenResolutionProfile { public abstract Offset getOffsetButtonEnterHeroicRaid(); + public abstract Offset getOffsetButtonEnterNormalQuest(); + + public abstract Offset getOffsetButtonEnterHardQuest(); + + public abstract Offset getOffsetButtonEnterHeroicQuest(); + public abstract Offset getOffsetButtonMapOnFamiliarUi(); public abstract Offset getOffsetDialogAreYouSureWantToExit(); + public abstract Offset getOffsetDialogLeaveThisDungeon(); + public abstract Offset getOffsetDialogNews(); public abstract Offset getOffsetButtonPersuade(); @@ -286,6 +308,11 @@ public Offset getOffsetButtonDungeonReRun() { return new Offset(309, 468); } + @Override + public Offset getOffsetButtonDungeonStar() { + return new Offset(400, 260); + } + @Override public Offset getOffsetButtonTalkRightArrow() { return new Offset(718, 287); @@ -338,7 +365,7 @@ public Tuple4 getBackwardScanRightSideAttend @Override public Tuple4 getBackwardScanLeftSideAttendablePlaces() { - return new Tuple4<>(14, 96, 72, 59); + return new Tuple4<>(14, 0, 96, 59); } @Override @@ -481,16 +508,45 @@ public Offset getOffsetDialogNotEnoughInvasionBadges() { return new Offset(279, 231); } + @Override + public Offset getOffsetDialogNotEnoughDungeonEnergy() { + return new Offset(279, 231); + } + + @Override + public Offset getOffsetLabelDungeonEnterLevel() { + return new Offset(415, 433); + } + + @Override + public Offset getOffsetLabelDungeonZones() { + return new Offset(104, 62); + } + @Override public Offset getOffsetButtonPlayTrials() { return new Offset(517, 271); } + @Override + public Offset getOffsetButtonEnergyDungeon() { + return new Offset(275, 400); + } + @Override + public Offset getOffsetButtonHeroicEnergyDungeon() { + return new Offset(550, 150); + } + @Override public Offset getOffsetButtonAcceptTrials() { return new Offset(474, 457); } + @Override + public Offset getOffsetButtonCollectQuest() { + return new Offset(331, 358); + } + @Override public Offset getOffsetButtonTownAfterCompetedTrials() { return new Offset(387, 468); @@ -591,6 +647,21 @@ public Offset getOffsetButtonEnterHeroicRaid() { return new Offset(590, 233); } + @Override + public Offset getOffsetButtonEnterNormalQuest() { + return new Offset(135, 223); + } + + @Override + public Offset getOffsetButtonEnterHardQuest() { + return new Offset(327, 223); + } + + @Override + public Offset getOffsetButtonEnterHeroicQuest() { + return new Offset(520, 223); + } + @Override public Offset getOffsetButtonMapOnFamiliarUi() { return new Offset(66, 135); @@ -601,6 +672,11 @@ public Offset getOffsetDialogAreYouSureWantToExit() { return new Offset(289, 231); } + @Override + public Offset getOffsetDialogLeaveThisDungeon() { + return new Offset(289, 231); + } + @Override public Offset getOffsetButtonPlayExpedition() { return new Offset(521, 269); diff --git a/src/main/java/bh/bot/common/types/UserConfig.java b/src/main/java/bh/bot/common/types/UserConfig.java index cc02ce7..10626f2 100644 --- a/src/main/java/bh/bot/common/types/UserConfig.java +++ b/src/main/java/bh/bot/common/types/UserConfig.java @@ -6,6 +6,7 @@ public class UserConfig { public static final String raidLevelKey = "ig.user.raid.level"; public static final String raidModeKey = "ig.user.raid.mode"; + public static final String questModeKey = "ig.user.quest.mode"; public static final String worldBossLevelKey = "ig.user.world-boss.level"; public static final String expeditionPlaceKey = "ig.user.expedition.place"; public static final String pvpTargetKey = "ig.user.pvp.target"; @@ -26,16 +27,18 @@ public class UserConfig { public final String cfgProfileName; public final byte raidLevel; public final byte raidMode; + public final byte questMode; public final byte worldBossLevel; public final byte expeditionPlace; public final byte pvpTarget; - public UserConfig(String cfgProfileName, byte raidLevel, byte raidMode, byte worldBossLevel, byte expeditionPlace, byte pvpTarget) { + public UserConfig(String cfgProfileName, byte raidLevel, byte raidMode, byte worldBossLevel, byte expeditionPlace, byte pvpTarget, byte questMode) { this.cfgProfileName = cfgProfileName; this.raidLevel = raidLevel; this.raidMode = raidMode; this.worldBossLevel = worldBossLevel; this.expeditionPlace = expeditionPlace; + this.questMode = questMode; this.pvpTarget = (byte)Math.max(pvpTargetMin, pvpTarget); } @@ -72,6 +75,10 @@ public String getRaidModeDesc() { return getDifficultyModeDesc(raidMode, "Raid"); } + public String getQuestModeDesc() { + return getDifficultyModeDesc(questMode, "Quest"); + } + public boolean isValidRaidLevel() { return raidLevel >= raidLevelMin && raidLevel <= raidLevelMax; } diff --git a/src/main/java/bh/bot/common/types/flags/FlagProfileName.java b/src/main/java/bh/bot/common/types/flags/FlagProfileName.java index 9a29273..949fa70 100644 --- a/src/main/java/bh/bot/common/types/flags/FlagProfileName.java +++ b/src/main/java/bh/bot/common/types/flags/FlagProfileName.java @@ -4,6 +4,7 @@ import bh.bot.app.AfkApp; import bh.bot.app.farming.ExpeditionApp; import bh.bot.app.farming.PvpApp; +import bh.bot.app.farming.QuestApp; import bh.bot.app.farming.RaidApp; import bh.bot.app.farming.WorldBossApp; import bh.bot.common.exceptions.InvalidFlagException; @@ -43,7 +44,8 @@ protected boolean internalCheckIsSupportedByApp(AbstractApplication instance) { || instance instanceof WorldBossApp || instance instanceof RaidApp || instance instanceof ExpeditionApp - || instance instanceof PvpApp; + || instance instanceof PvpApp + || instance instanceof QuestApp; } @Override diff --git a/src/main/java/bh/bot/common/types/images/BwMatrixMeta.java b/src/main/java/bh/bot/common/types/images/BwMatrixMeta.java index 03e00ec..be9631a 100644 --- a/src/main/java/bh/bot/common/types/images/BwMatrixMeta.java +++ b/src/main/java/bh/bot/common/types/images/BwMatrixMeta.java @@ -221,6 +221,7 @@ public static class Dialogs { public static BwMatrixMeta confirmStartNotFullTeam; public static BwMatrixMeta areYouStillThere; public static BwMatrixMeta areYouSureWantToExit; + public static BwMatrixMeta leaveThisDungeon; public static BwMatrixMeta news; } } @@ -257,6 +258,28 @@ public static class Buttons { public static class Dungeons { public static class Buttons { public static BwMatrixMeta rerun; + public static BwMatrixMeta star; + public static BwMatrixMeta emptyStar; + public static BwMatrixMeta bossLevel; + public static BwMatrixMeta questLevel; + public static BwMatrixMeta accept; + public static BwMatrixMeta collect; + public static BwMatrixMeta energy; + public static BwMatrixMeta heroicEnergy; + public static BwMatrixMeta difficultyNormal; + public static BwMatrixMeta difficultyHard; + public static BwMatrixMeta difficultyHeroic; + + } + + public static class Dialogs { + public static BwMatrixMeta notEnoughEnergy; + } + + public static class Labels { + public static BwMatrixMeta zones; + public static BwMatrixMeta enterLevel; + } } @@ -457,6 +480,11 @@ public static void load() throws IOException { Configuration.screenResolutionProfile.getOffsetDialogAreYouSureWantToExit(), // 0xFFFFFF ); + Metas.Globally.Dialogs.leaveThisDungeon = BwMatrixMeta.from(// + "dialogs/globally.leave-this-dungeon2?", + Configuration.screenResolutionProfile.getOffsetDialogLeaveThisDungeon(), // + 0xFFFFFF + ); Metas.Globally.Dialogs.news = BwMatrixMeta.from(// "dialogs/globally.news2?", Configuration.screenResolutionProfile.getOffsetDialogNews(), // @@ -465,7 +493,78 @@ public static void load() throws IOException { Metas.Dungeons.Buttons.rerun = BwMatrixMeta.from(// "buttons/dungeons.rerun2?", Configuration.screenResolutionProfile.getOffsetButtonDungeonReRun(), // - 0xFFFFFF); + 0xFFFFFF + ); + Metas.Dungeons.Buttons.star = BwMatrixMeta.from(// + "buttons/dungeons.star2?", + Configuration.screenResolutionProfile.getOffsetButtonDungeonStar(), // + 0x000000 + ); + Metas.Dungeons.Buttons.emptyStar = BwMatrixMeta.from(// + "buttons/dungeons.bStar2?", + new Offset(0, 0), // + 0x000000 + ); + Metas.Dungeons.Buttons.bossLevel = BwMatrixMeta.from(// + "buttons/dungeons.boss2?", + new Offset(0, 0), // + 0x6066AD + ); + Metas.Dungeons.Buttons.questLevel = BwMatrixMeta.from(// + "buttons/dungeons.level2?", + Configuration.screenResolutionProfile.getOffsetButtonDungeonStar(), // + 0xFFFFFF + ); + Metas.Dungeons.Buttons.accept = BwMatrixMeta.from(// + "buttons/dungeons.accept2?", + Configuration.screenResolutionProfile.getOffsetButtonAcceptTrials(), // + 0xFFFFFF + ); + Metas.Dungeons.Buttons.collect = BwMatrixMeta.from(// + "buttons/dungeons.collect2?", + Configuration.screenResolutionProfile.getOffsetButtonAcceptTrials(), // + 0xFFFFFF + ); + Metas.Dungeons.Buttons.energy = BwMatrixMeta.from(// + "buttons/dungeons.energy2?", + Configuration.screenResolutionProfile.getOffsetButtonEnergyDungeon(), // + 0xFFFFFF + ); + Metas.Dungeons.Buttons.heroicEnergy = BwMatrixMeta.from(// + "buttons/dungeons.heroic-energy2?", + Configuration.screenResolutionProfile.getOffsetButtonHeroicEnergyDungeon(), // + 0xFFFFFF + ); + Metas.Dungeons.Buttons.difficultyHard = BwMatrixMeta.from(// + "buttons/dungeons.hard2?", + Configuration.screenResolutionProfile.getOffsetButtonEnterHardQuest(), // + 0xFFFFFF + ); + Metas.Dungeons.Buttons.difficultyHeroic = BwMatrixMeta.from(// + "buttons/dungeons.heroic2?", + Configuration.screenResolutionProfile.getOffsetButtonEnterHeroicQuest(), // + 0xFFFFFF + ); + Metas.Dungeons.Buttons.difficultyNormal = BwMatrixMeta.from(// + "buttons/dungeons.normal2?", + Configuration.screenResolutionProfile.getOffsetButtonEnterNormalQuest(), // + 0xFFFFFF + ); + Metas.Dungeons.Dialogs.notEnoughEnergy = BwMatrixMeta.from(// + "dialogs/dungeons.not-enough-energy2?", + Configuration.screenResolutionProfile.getOffsetDialogNotEnoughDungeonEnergy(), // + 0xFFFFFF + ); + Metas.Dungeons.Labels.enterLevel = BwMatrixMeta.from(// + "labels/dungeons.enter2?", + Configuration.screenResolutionProfile.getOffsetLabelDungeonEnterLevel(), // + 0xFFFFFF + ); + Metas.Dungeons.Labels.zones = BwMatrixMeta.from(// + "labels/dungeons.zones2?", + Configuration.screenResolutionProfile.getOffsetLabelDungeonZones(), // + 0xFFFFFF + ); // Persuade Metas.Persuade.Labels.kaleido = BwMatrixMeta.from(// diff --git a/src/main/java/bh/bot/common/utils/InteractionUtil.java b/src/main/java/bh/bot/common/utils/InteractionUtil.java index 261f70c..b62d752 100644 --- a/src/main/java/bh/bot/common/utils/InteractionUtil.java +++ b/src/main/java/bh/bot/common/utils/InteractionUtil.java @@ -6,12 +6,14 @@ import bh.bot.common.types.AttendablePlace; import bh.bot.common.types.Offset; import bh.bot.common.types.images.BwMatrixMeta; +import bh.bot.common.types.images.BwMatrixMeta.Metas; import bh.bot.common.types.tuples.Tuple4; import java.awt.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; +import java.util.ArrayList; import static bh.bot.common.Log.debug; import static bh.bot.common.Log.optionalDebug; @@ -240,6 +242,101 @@ public Point findAttendablePlace(AttendablePlace event) { } return null; } + + public ArrayList findByScanScreen(BwMatrixMeta im, int minX, int maxX, int stepY, int firstY) { + int maxScreenWidth = 800; + int maxScreenHeight = 520; + ArrayList points = new ArrayList<>(); + Point located = null; + int currentX = minX; + int currentMaxX = maxX; + int step = maxX - minX; + while ( currentMaxX < maxScreenWidth) { + int loops = (int) Math.floor((maxScreenHeight - firstY) / stepY); + located = findByScanColumn(im, currentX, currentMaxX, stepY, firstY, loops); + currentX += step; + currentMaxX += step; + if (located != null) { + points.add(located); + located = null; + } + } + return points; + } + + public Point findByScanColumn(BwMatrixMeta im, int minX, int maxX, int stepY, int firstY, int numberOfScans) { + minX += Configuration.gameScreenOffset.X.get(); + maxX += Configuration.gameScreenOffset.X.get(); + firstY += Configuration.gameScreenOffset.Y.get(); + + final boolean debug = false; + + final int positionTolerant = Math.abs(Math.min(Configuration.Tolerant.position, Math.abs(stepY))); + final int scanWidth = maxX - minX + 1 + positionTolerant * 2; + final int scanHeight = Math.abs(stepY) + positionTolerant * 2; + final int scanX = Math.max(0, minX - positionTolerant); + final int numberOfColumns = Math.min(numberOfScans, 5); + for (int i = 0; i < numberOfColumns; i++) { + final int scanY = Math.max(0, firstY + stepY * i - positionTolerant); + BufferedImage sc = captureScreen(scanX, scanY, scanWidth, scanHeight); + try { + instance.saveDebugImage(sc, String.format("scanColumn_%d_%s_", i, im.getImageNameCode())); + if (im.throwIfNotAvailable()) + continue; + // + boolean go = true; + Point p = new Point(); + final int blackPixelRgb = im.getBlackPixelRgb(); + final ImageUtil.DynamicRgb blackPixelDRgb = im.getBlackPixelDRgb(); + for (int y = sc.getHeight() - im.getHeight() - 1; y >= 0 && go; y--) { + for (int x = sc.getWidth() - im.getWidth() - 1; x >= 0 && go; x--) { + boolean allGood = true; + + for (int[] px : im.getBlackPixels()) { + int srcRgb = sc.getRGB(x + px[0], y + px[1]) & 0xFFFFFF; + if (!ImageUtil.areColorsSimilar(// + blackPixelDRgb, // + srcRgb, // + Configuration.Tolerant.color, + im.getOriginalPixelPart(px[0], px[1]))) { + allGood = false; + optionalDebug(debug, "scanColumn first match failed at %d,%d (%d,%d)", x + px[0], y + px[1], px[0], px[1]); + break; + } + } + + if (allGood) { + for (int[] px : im.getNonBlackPixels()) { + int srcRgb = sc.getRGB(x + px[0], y + px[1]) & 0xFFFFFF; + if (ImageUtil.areColorsSimilar(// + blackPixelRgb, // + srcRgb, // + Configuration.Tolerant.color)) { + allGood = false; + optionalDebug(debug, "scanColumn second match failed at %d,%d (%d,%d)", x + px[0], y + px[1], px[0], px[1]); + break; + } + } + } + + if (allGood) { + go = false; + p = new Point(scanX + x, scanY + y); + optionalDebug(debug, "scanColumn result %d, %d", p.x, p.y); + } + } + } + + if (!go) + return p; + // + + } finally { + freeMem(sc); + } + } + return null; + } } } } diff --git a/src/main/resources/game-images/800x520/buttons/character.confirm2-tp.bmp b/src/main/resources/game-images/800x520/buttons/character.confirm2-tp.bmp index 3211545..cb47e95 100644 Binary files a/src/main/resources/game-images/800x520/buttons/character.confirm2-tp.bmp and b/src/main/resources/game-images/800x520/buttons/character.confirm2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/character.select2-tp.bmp b/src/main/resources/game-images/800x520/buttons/character.select2-tp.bmp index 3a9b914..a87342b 100644 Binary files a/src/main/resources/game-images/800x520/buttons/character.select2-tp.bmp and b/src/main/resources/game-images/800x520/buttons/character.select2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.accept2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.accept2-tp.bmp new file mode 100644 index 0000000..a96f8d6 Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.accept2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.bStar2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.bStar2-tp.bmp new file mode 100644 index 0000000..1fa155c Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.bStar2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.boss2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.boss2-tp.bmp new file mode 100644 index 0000000..12152a5 Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.boss2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.collect2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.collect2-tp.bmp new file mode 100644 index 0000000..ee344e8 Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.collect2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.energy2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.energy2-tp.bmp new file mode 100644 index 0000000..d2cb30e Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.energy2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.hard2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.hard2-tp.bmp new file mode 100644 index 0000000..12d89f3 Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.hard2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.heroic-energy2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.heroic-energy2-tp.bmp new file mode 100644 index 0000000..2906194 Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.heroic-energy2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.heroic2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.heroic2-tp.bmp new file mode 100644 index 0000000..6acdcbb Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.heroic2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.level2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.level2-tp.bmp new file mode 100644 index 0000000..ceb7f38 Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.level2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.normal2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.normal2-tp.bmp new file mode 100644 index 0000000..c9e759c Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.normal2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/buttons/dungeons.star2-tp.bmp b/src/main/resources/game-images/800x520/buttons/dungeons.star2-tp.bmp new file mode 100644 index 0000000..30219f2 Binary files /dev/null and b/src/main/resources/game-images/800x520/buttons/dungeons.star2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/dialogs/character.loading2-tp.bmp b/src/main/resources/game-images/800x520/dialogs/character.loading2-tp.bmp index 8fbfe2a..867d479 100644 Binary files a/src/main/resources/game-images/800x520/dialogs/character.loading2-tp.bmp and b/src/main/resources/game-images/800x520/dialogs/character.loading2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/dialogs/dungeons.not-enough-energy2-tp.bmp b/src/main/resources/game-images/800x520/dialogs/dungeons.not-enough-energy2-tp.bmp new file mode 100644 index 0000000..4aca1e8 Binary files /dev/null and b/src/main/resources/game-images/800x520/dialogs/dungeons.not-enough-energy2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/dialogs/globally.leave-this-dungeon2-tp.bmp b/src/main/resources/game-images/800x520/dialogs/globally.leave-this-dungeon2-tp.bmp new file mode 100644 index 0000000..c8b4aac Binary files /dev/null and b/src/main/resources/game-images/800x520/dialogs/globally.leave-this-dungeon2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/labels/attendable-places/quest3-tp.bmp b/src/main/resources/game-images/800x520/labels/attendable-places/quest3-tp.bmp new file mode 100644 index 0000000..e26d9db Binary files /dev/null and b/src/main/resources/game-images/800x520/labels/attendable-places/quest3-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/labels/characters.heroes2-tp.bmp b/src/main/resources/game-images/800x520/labels/characters.heroes2-tp.bmp new file mode 100644 index 0000000..3bfa108 Binary files /dev/null and b/src/main/resources/game-images/800x520/labels/characters.heroes2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/labels/dungeons.enter2-tp.bmp b/src/main/resources/game-images/800x520/labels/dungeons.enter2-tp.bmp new file mode 100644 index 0000000..5e0698c Binary files /dev/null and b/src/main/resources/game-images/800x520/labels/dungeons.enter2-tp.bmp differ diff --git a/src/main/resources/game-images/800x520/labels/dungeons.zones2-tp.bmp b/src/main/resources/game-images/800x520/labels/dungeons.zones2-tp.bmp new file mode 100644 index 0000000..735c1b4 Binary files /dev/null and b/src/main/resources/game-images/800x520/labels/dungeons.zones2-tp.bmp differ diff --git a/web/json/reject-versions-2.json b/web/json/reject-versions-2.json index 7aed940..684d568 100644 --- a/web/json/reject-versions-2.json +++ b/web/json/reject-versions-2.json @@ -1,5 +1,5 @@ { - "bv": ["3.3.0","3.2.0","3.1.0","3.0.0","2.12.0","2.11.0","2.10.1","2.10.0","2.9.3","2.9.2","2.9.1","2.9.0","2.8.6","2.8.5","2.8.4","2.8.3","2.8.2","2.8.1","2.8.0","2.7.0","2.6.0","2.5.0","2.4.0","2.3.0","2.2.0","2.1.0","2.0.1","2.0.0"], + "bv": ["3.5.0", "3.4.0", "3.3.0","3.2.0","3.1.0","3.0.0","2.12.0","2.11.0","2.10.1","2.10.0","2.9.3","2.9.2","2.9.1","2.9.0","2.8.6","2.8.5","2.8.4","2.8.3","2.8.2","2.8.1","2.8.0","2.7.0","2.6.0","2.5.0","2.4.0","2.3.0","2.2.0","2.1.0","2.0.1","2.0.0"], "bf": { "3.4.1": ["invasion"], "3.4.0": ["invasion","gvg","gauntlet"]