├── .eslintrc.js ├── .github └── FUNDING.yml ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── app ├── bot │ ├── createBot.js │ ├── createBots.js │ ├── fishingZone.js │ ├── lootExitZone.js │ ├── lootZone.js │ ├── notificationZone.js │ ├── redButtonZone.js │ ├── runBot.js │ └── stats.js ├── config │ ├── bot.json │ ├── defaults.json │ └── settings.json ├── game │ ├── createGame.js │ ├── nut.js │ └── winSwitch.js ├── img │ ├── add.png │ ├── fish.png │ ├── fold.png │ ├── grass.png │ ├── grate.png │ ├── hint.png │ ├── icon-premium.ico │ ├── icon.ico │ ├── icon.png │ ├── install.gif │ ├── lock.png │ ├── logo.png │ ├── premium.png │ ├── remove.png │ ├── start.png │ ├── stop.png │ ├── unfold.png │ └── youtube_icon.png ├── index.html ├── main.js ├── renderer.js ├── style.css ├── ui │ ├── autoFish.js │ ├── renderSettings.js │ ├── settings.js │ ├── startButton.js │ └── utils │ │ ├── elt.js │ │ └── wrapInLabel.js ├── utils │ ├── eventLine.js │ ├── fishingQuotes.json │ ├── generateName.js │ ├── getBitmap.js │ ├── handleSquirrel.js │ ├── keySupport.js │ ├── logger.js │ ├── rgb.js │ ├── tests │ │ ├── vec.test.js │ │ └── zone.test.js │ ├── textReader.js │ ├── time.js │ ├── vec.js │ └── zone.js └── wins │ ├── advsettings │ ├── img │ │ ├── hint.png │ │ └── icon.png │ ├── index.html │ ├── main.js │ └── renderer.js │ └── fishingzone │ ├── chat.html │ ├── fishing.html │ ├── img │ ├── bad.png │ ├── cancel.png │ ├── good.png │ ├── icon.png │ └── ok.png │ ├── main.js │ ├── rendererChat.js │ ├── rendererFishing.js │ └── style.css ├── guide_img ├── Premium_UI.jpg ├── UI.jpg ├── additionalactions.jpg ├── advancedSettings.jpg ├── aiResponse.jpg ├── arduino.jpg ├── chooseFishKey.jpg ├── chooseFishZone.jpg ├── chooseGame.jpg ├── icons │ ├── bad.png │ ├── good.png │ └── question.png ├── intkey.jpg ├── lures.jpg ├── mammoth.jpg ├── motiondetection.jpg ├── rngMove.jpg ├── sound-detection.jpg ├── streaming.jpg ├── tmmenu.jpg └── tmtoken.jpg ├── package.json └── prettierrc.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es2021": true 6 | }, 7 | "globals": { 8 | "__dirname": "readonly", 9 | "process": "readonly", 10 | "_": "readonly" 11 | }, 12 | "extends": "eslint:recommended", 13 | "overrides": [ 14 | { 15 | "env": { 16 | "node": true 17 | }, 18 | "files": [ 19 | ".eslintrc.{js,cjs}" 20 | ], 21 | "parserOptions": { 22 | "sourceType": "script" 23 | } 24 | } 25 | ], 26 | "parserOptions": { 27 | "ecmaVersion": "latest" 28 | }, 29 | "ignorePatterns": ["node_modules/", "out/", "img/", "tests/", "**/*.json", "**/*.html", "**/*.css"], 30 | "rules": { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: buymeacoffee.com/jsbots 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | forge.config.js 3 | random.js 4 | cache/ 5 | eng.traineddata 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | .pnpm-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # Snowpack dependency directory (https://snowpack.dev/) 51 | web_modules/ 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache/ 64 | .rts2_cache_cjs/ 65 | .rts2_cache_es/ 66 | .rts2_cache_umd/ 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | .env.test 80 | .env.production 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # Serverless directories 104 | .serverless/ 105 | 106 | # FuseBox cache 107 | .fusebox/ 108 | 109 | # DynamoDB Local files 110 | .dynamodb/ 111 | 112 | # TernJS port file 113 | .tern-port 114 | 115 | # Stores VSCode versions used for testing VSCode extensions 116 | .vscode-test 117 | 118 | # yarn v2 119 | .yarn/cache 120 | .yarn/unplugged 121 | .yarn/build-state.yml 122 | .yarn/install-state.gz 123 | .pnp.* 124 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | forge.config.js 3 | random.js 4 | eng.traineddata 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .pnpm-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | .env.production 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | .parcel-cache 84 | 85 | # Next.js build output 86 | .next 87 | out 88 | 89 | # Nuxt.js build / generate output 90 | .nuxt 91 | dist 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and not Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | 114 | # Stores VSCode versions used for testing VSCode extensions 115 | .vscode-test 116 | 117 | # yarn v2 118 | .yarn/cache 119 | .yarn/unplugged 120 | .yarn/build-state.yml 121 | .yarn/install-state.gz 122 | .pnp.* 123 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 jsbots 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | 4 | 5 | 6 |
7 | 8 | ## Table of Contents :page_with_curl: 9 | 10 | - [Fishing bot](#fishing-bot-fish) 11 | - [Disclaimer](#disclaimer-warning) 12 | - [Guide](#guide-blue_book) 13 | - [Hints and Issues](#hints-and-issues) 14 | - [Intensity](#intensity) 15 | - [Sensitivity](#sensitivity) 16 | - [Interactive Key](#interactive-key) 17 | - [Applying Lures](#applying-lures-pushpin) 18 | - [Interactive key](#interactive-key) 19 | - [Download](#download-open_file_folder) 20 | - [AutoFish Premium](#autofish-premium-gem) 21 | - [Remote Control / Chat Detection](#remote-control--chat-detection-iphone) 22 | - [AI](#ai-speech_balloon) 23 | - [Multiple Fishing Mode](#multiple-fishing-mode-rocket) 24 | - [Alt-Tab Fishing Mode](#alt-tab-fishing-mode-sleeping) 25 | - [Sound Detection](#sound-detection-loud_sound) 26 | - [Additional Actions](#additional-actions-mage) 27 | - [Random camera/character movements](#random-cameracharacter-movements-robot) 28 | - [Hardware Control](#hardware-control-joystick) 29 | - [Streaming Mode](#streaming-mode-tv) 30 | - [Mount Selling](#mount-selling-elephant) 31 | - [Motion Detection](#motion-detection-runner) 32 | - [Check For Players Around (unstable)](#check-for-players-around-telescope-unstable) 33 | - [Attacking / Running away (unstable)](#attackingrunning-away-crossed_swords-unstable) 34 | 35 | ## Fishing bot :fish: 36 | 37 | This is a fishing bot for wow-like fishing logic (when a model of a bobber has red/blue feather and plunging animation, for example with some tweaks in the code it can work even with Minecraft). It is built using the [Electron](https://github.com/electron/electron), [keysender](https://github.com/Krombik/keysender) and [nut.js](https://github.com/nut-tree/nut.js) libraries to analyze the screen and automate the fishing process in a manner that mimics human behavior, and also [tesseract.js](https://github.com/naptha/tesseract.js) for loot filtering. 38 | 39 | This is a so-called "pixel bot": it works with pixels only, without modifying the game's memory, addons or additional programs. 40 | 41 |

42 | 43 |

44 | 45 | For video review you can watch this (pretty old) [AutoFish 1.12](https://youtu.be/A3W8UuVIZTo) 46 | 47 | **Important!** The bot works only on **Windows 10/11**. 48 | 49 | ## Disclaimer :warning: 50 | 51 | **This small project was developed for educational purposes ONLY, aiming to explore the feasibility of creating a functional gaming bot using web-development technologies.** 52 | 53 | **The software provided should NEVER be used with real-life applications, games, servers etc.** 54 | 55 | **This software is not designed to be "undetectable" in any way, nor was it ever intended for such purposes. All randomness functionality is added for educational purposes only.** 56 | 57 | **No guarantees or assurances can be made regarding the functionality or outcomes of the bot, you assume full responsibility for any outcomes that may arise from using this software.**. 58 | 59 | ## Guide :blue_book: 60 | 61 | 1. Launch the game and the bot. 62 | 2. Choose the game you want the bot to work with. 63 | 64 |

65 | 66 |

67 | 68 | 3. Set up your **Fishing Zone** by clicking the **Set Fishing Zone** button. Adjust the size and position of the **Fishing Zone** window to exclude any reddish or bluish elements (depending on the color you selected). Keep in mind that **Fishing Zone** is an overlay window, so it will also recognize colors from your character and the game's user interface, including texts and health bars. 69 | 70 |

71 | 72 |

73 | 74 | 4. Set up your **Fishing Key** by clicking on Fishing Key section and then pressing the key you want to bind. Your Fishing Key is the same key you bind your fishing skill to in the game. 75 | 76 |

77 | 78 |

79 | 80 | 5. You can press **Advanced Settings** and find there a lot of useful features and options. 81 | 82 |

83 | 84 |

85 | 86 | 6. Press the Start button and don't use your mouse and keyboard. 87 | 7. To stop the bot, press your designated stop key (default: delete). 88 | 89 | ### Hints and Issues 90 | 91 | - Avoid aggro mobs (with red names) in the Fishing Zone, if you use red color. Or turn them off in the settings (Interface -> Names -> NPC Names -> None) 92 | - If the bot can't find the bobber, try from the 1st person view. 93 | - If the bot doesn't react to animation, try from the 3rd person view. 94 | - The bot works only with default UI: no UI scaling, no UI addons and so on. (Especially ElvUI) 95 | - If the bot doesn't cast, though key is correct, launch it as admin. 96 | - The bot works only on a primary monitor. 97 | - Different camera directions can affect the brightness, size, and visibility of the bobber. 98 | - You can Adjust gamma, brightness, and contrast settings to enhance the brightness of the bobber. 99 | - The bot will auto-confirm lures application if such confirmation is needed. 100 | - The bot can auto-confirm *Soulbound* items. For that go to Advanced Settings and turn this option on. 101 | - If you use an addon that removes loot window (like Fishing Buddy), you can set *Loot Window Closing Delay* value to 0 to make it work faster. 102 | - The bot works at 50% efficiency on Turtle WoW and won't work better unless you use sound detection available in Premium. 103 | - Any features that print text will use English keyboard layout, so make sure you switched to English before using them. 104 | 105 | ### Intensity 106 | 107 | *If in manual mode* 108 | 109 | Intensity value serves as a color threshold below which the bot will ignore all the corresponding colors. 110 | 111 | Increasing the intensity value will make the bot recognize fewer red colors, while decreasing this value will cause the bot to recognize more red colors on the screen. 112 | 113 | Simply put: decrease this value, if the bot can't find the bobber (e.g. at night, bad weather). Increase this value if you want the bot to ignore some reddish/bluish elements in the Fishing Zone and recognize only the bobber.` 114 | 115 | ### Sensitivity 116 | 117 | *If in manual mode* 118 | 119 | If the bot clicks too early, decrease this value. If the bot clicks too late or doesn't click at all, increase this value. 120 | 121 | ### Interactive key 122 | 123 | You can fish with interactive key in the game. If you want the bot to use it instead of mouse movement, turn on Int. Key option and assign the same key you use for interactive key in the game. 124 | 125 |

126 | 127 |

128 | 129 | To make the interactive key work, you should use this commands (write them in the chat and press enter, one by one): 130 | ``` 131 | /console SoftTargetInteractArc 2 - This will allow you to interact with the bobber no matter which way you are facing. 132 | /console SoftTargetInteractRange 30 - This increases the interaction range to 30 yards. Adjust to your needs 133 | ``` 134 | 135 | ## Applying Lures :pushpin: 136 | 137 | Go to **Advanced Settings** and check **Use Lures**. Bind your key to the same key you bind your lures or **macro**. 138 | 139 |

140 | 141 |

142 | 143 | For **Retail** and **Classic/Vanilla** you need to use a special macro that will apply the lures onto your fishing pole. The names of the lures and fishing pole here only an example, you need to substitute them for your names: 144 | 145 | **Retail**: 146 | 147 | ``` 148 | /use Aquadynamic Fish Attractor 149 | /use Big Iron Fishing Pole 150 | ``` 151 | 152 | **Classic**: 153 | 154 | ``` 155 | /equip Big Iron Fishing Pole 156 | /use Aquadynamic Fish Attractor 157 | /use 16 158 | /click StaticPopup1Button1 159 | ``` 160 | 161 | **Vanilla**: 162 | 163 | ``` 164 | /script UseAction(your lures key); 165 | /script PickupInventoryItem(16); 166 | /script ReplaceEnchant(); 167 | ``` 168 | Or 169 | ``` 170 | /script UseContainerItem(0,2); PickupInventoryItem(16); 171 | ``` 172 | 173 | ## AutoFish Premium :gem: 174 | 175 | AutoFish Premium is an updated version of the bot with many additional features. 176 | 177 |

178 | 179 |

180 | 181 | **Premium Features/Content:** 182 | - [Remote Control / Chat Detection](#remote-control--chat-detection-iphone) 183 | - [AI](#ai-speech_balloon) 184 | - [Multiple Fishing Mode](#multiple-fishing-mode-rocket) 185 | - [Alt-Tab Fishing Mode](#alt-tab-fishing-mode-sleeping) 186 | - [Sound Detection](#sound-detection-loud_sound) 187 | - [Additional Actions](#additional-actions-mage) 188 | - [Random camera/character movements](#random-cameracharacter-movements-robot) 189 | - [Hardware Control](#hardware-control-joystick) 190 | - [Streaming Mode](#streaming-mode-tv) 191 | - [Mount Selling](#mount-selling-elephant) 192 | - [Motion Detection](#motion-detection-runner) 193 | - [Check For Players Around](#check-for-players-around-telescope-unstable) 194 | - [Attacking / Running away](#attackingrunning-away-crossed_swords-unstable) 195 | - Profiles 196 | 197 | ## Remote Control / Chat Detection :iphone: 198 | 199 | You can control the bot via Telegram and it also can detect messages in your chat in the game and notify you. 200 | 201 | ### Guide: 202 | 203 | 1. Get the token from [BotFather](https://t.me/BotFather) by using /newbot command and following the instruction. Imagine some long and random name for the bot so that someone won't accidentally join your bot and gain control over your fishing process. 204 | 2. Paste the token to **Telegram token** input field in **Remote Control** section in the **Advanced Settings** and press enter. 205 | 206 |

207 | 208 |

209 | 210 | 3. Press **Connect** button and wait until the name of the button changes to either **done** or **error* (*might take awhile*). 211 | 4. Open the bot in your Telegram and either press /start or write /start command. 212 | 5. If everything is OK, the bot will reply with: 213 | 214 |

215 | 216 |

217 | 218 | 6. Create a separate tab in your chat and set its background to solid black for better visual detection, especially if you set the bot detect messages **by text**. 219 | 7. Now set your **Chat Zone** as on the screenshot below by pressing **Set Chat Zone** button on the main window of the AutoFish. 220 | 8. Start the bot from Telegram. 221 | 222 | Watch [Telegram remote control Test Video](https://youtu.be/aKulvFK6ubg) 223 | 224 | ## AI :speech_balloon: 225 | 226 |

227 | 228 |

229 | 230 | The bot can use one of **OpenRouter** AI models to generate a response to the messages it detects in the **Chat Zone** (it uses settings from **Remote Control** section). 231 | 232 | ### Guide: 233 | 234 | 1. Sign In to [OpenRouter](https://openrouter.ai/) and get an API key: *Profile -> Keys -> Create Key*. 235 | 2. Paste it into **API Key** field. 236 | 3. Choose a model from the list. 237 | 4. You can change **AI Prompt* field to your own context and test the result in **AI Test** section. 238 | 239 | ## Multiple Fishing Mode :rocket: 240 | 241 | In **Multiple Fishing** mode you can use the bot simultaneously with multiple game windows, the bot will switch between the windows and can work with up to 10 windows at the same time. 242 | 243 | 244 | **Guide(normal mode):** 245 | Ensure each window is properly launched and configured, and **every game instance is set to DirectX 11 or lower** *(older games doesn't need it)*. 246 | 247 | In this mode, the bot will use special profiles specific to each window: WIN1, WIN2, WIN3 and so on *(check in profiles list)*. Each "WIN" profile must have a **"Custom Window"** configured *(found under Advanced Settings -> Window)*. Use the **"Focus"** button to check which window you've selected. 248 | 249 | **Simple guide:** choose WIN1 in profiles above -> Go to Advanced Settings -> Custom Window -> Choose the window for the first game -> Repeat the same for every next window. 250 | 251 | 252 | Watch [Multiple Windows Test Video](https://youtu.be/ih-xoQcByz8) 253 | 254 | **Guide(streaming mode):** 255 | Similar logic as with normal mode, but instead each *WIN1, WIN2, WIN3* and so on will use */live1, /live2, /live3* addresses respectively you need to stream to from your OBS (and because you can't stream to multiple sources in OBS you need to have multiple applications running). 256 | 257 | Check [Streaming Mode](#streaming-mode-tv) how to stream to the bot from OBS and use it for every window, but instead of */live* rtps address use */live1*, */live2* and so on, as described above. Also check important notes. 258 | 259 | Because there's no way for the bot to control the windows on the gaming PC, after starting the bot you need to focus them in right order to make the bot use simple *alt+tab* logic to switch between them. 260 | 261 | For example you have 2 windows: after you pressed "Start" you focus *first window* (/live1(WIN1)) then *second window* (/live2(WIN2)) and then back to first window. This way the bot will be able to switch between them in a *blind* way. 262 | 263 | Another example if you have 3 windows: after you pressed "Start" you focus *first window* (/live1(WIN1)) then *second window* (/live2(WIN2)) then *third window* (/live3(WIN3)) then back to *second window* and then back to *first window*. Again, this way the bot will be able to switch between them in a *blind* way. 264 | 265 | You can use [Sound Detection](#sound-detection-loud_sound) with Multiple Fishing mode while streaming, but don't forget to add audio source (check Sound Detection section). 266 | 267 | - **Important!** If in **Streaming Mode + Multiple Fishing** you get video encoder overloading try to lower the bitrate (somewhere 2500-4000) and fps (30 works well as well) for each stream. 268 | - **Important!** [Sound Detection](#sound-detection-loud_sound) won't work in Multiple Fishing **normal** mode, but you can use **Streaming Mode** and just stream to your own computer. 269 | - **Important!** For every WIN you need to turn on Streaming Mode checkbox (in Advanced Settings) separately, otherwise the bot will ignore it. 270 | - **Important!** You can start the bot from any WIN window, it doesn't matter, the bot will use settings of every WIN respectively. But if you start from some other profile don't forget to check **Multiple Fishing** checkbox. 271 | - **Important** You can try to increase mouse speed for multiple windows in case the bot can't catch up. 272 | 273 | 274 | ## Alt-Tab Fishing Mode :sleeping: 275 | 276 | Alt-Tab Fishing Mode will simulate so-called "afk fishing": the bot will focus the window only when your character needs to cast, catch or perform any action and then it will lose focus and return you to the previous window by pressing alt-tab. 277 | 278 | **Important!** Doesn't work in [Streaming Mode](#streaming-mode-tv). 279 | 280 | Watch [Alt-Tab Fishing Test Video](https://youtu.be/lQi6fSxMyL0) 281 | 282 | ## Sound Detection :loud_sound: 283 | 284 |

285 | 286 |

287 | 288 | Sound Detection is an alternative to pixel recognition logic. The bot will hook the bobber only after "splash" sound and won't rely on checking the animation of the bobber plunging. 289 | 290 | Before using sound detection turn off Music and Ambient Sounds in the game, leave only Sound Effects. Try to find a place secluded from the sounds made by other players to avoid false detections. 291 | 292 | You can also use Alt-Tab Fishing Mode with Sound Detection, with Int.Key + Sound Detection the bot will focus the window only when it needs to cast and when it detects splash sound (turn on **Sound in Background** for that). 293 | 294 | Use **"Listen"** button to determine the amplitude of the splashing sound when your bobber plunges in the water: start fishing then press "Listen" button and check for "Max" value when the fish is caught. Do it couple of times to determine the stable value. 295 | 296 | If you want to make the bot listen only to the sounds of the game you can use [Virtual Audio Cable](https://vb-audio.com/Cable/) to "pipe" the sound from the game to an input device and then choose it in the settings of the bot. If your game doesn't support in-game choice for sound output device, you can manually set Output device for your game in **"App volume and device preferences"** settings of your Windows to **CABLE input (VB-Audio Virtual Cable)** and then choose **CABLE output (VB-Audio Virtual Cable)** in the settings of the bot. 297 | 298 | For Sound Detection to work in [Streaming Mode](#streaming-mode-tv), you need to stream sound as well. So add **Application Audio Capture (BETA)** source in addition to the video source in your OBS. 299 | 300 | Watch [Sound Detection Test Video](https://youtu.be/ZggOy8tA32A) 301 | 302 | ## Motion Detection :runner: 303 | 304 |

305 | 306 |

307 | 308 | You can set a zone for motion detection and the bot will notifiy you via Telegram of any events happening in this zone/area. It will also send a screenshot of the motion occured. This feature might help against griefing. 309 | 310 | ## Additional Actions :mage: 311 | 312 |

313 | 314 |

315 | 316 | With Additional Actions module you can perform basically any automation you need during the fishing process. You can automate any mouse or keyboard movements as well as add some additional pixel conditions. It can be used for different things: applying additional lures, opening or deleting some items, automating sending caught fish in the process and so on. 317 | 318 | ## Mount Selling :elephant: 319 | 320 |

321 | 322 |

323 | 324 | As an alternative to filtering you can use a trader on your mammoth mount to sell all the junk items during the fishing. The bot will summon your mount, target your trader, interact with it by using interaction key in the game, unsummon the mount and go on fishing. 325 | 326 | Because of the novelty of the interaction key this feature is available only for Retail. 327 | 328 | Depends on the mount the name of your trader might be different, so change the default value. 329 | 330 | Watch [Mammoth Selling Test Video](https://youtu.be/zY2LqAPszdg) 331 | 332 | ## Random Camera/Character Movements :robot: 333 | 334 |

335 | 336 |

337 | 338 | The bot will randomly move and change your camera direction from time to time within the provided radius. 339 | 340 | Watch [Random Camera/Character Movements Test Video](https://youtu.be/o1hU3fNn4uk) 341 | 342 | ## Hardware Control :joystick: 343 | 344 |

345 | 346 |

347 | 348 | The bot is able to connect to your Arduino Board/Raspberry Pico W and use it to emulate a mouse/keyboard device, it will look like a real keyboard or mouse to the OS and the game. What you need to do to make it possible: 349 | 350 | 1. Get an Arduino with an ATmega32U4 on board (any cheap copies for 2-3$ will do too, you can find them on Chinese online markets). 351 | 2. Connect it to your computer. 352 | 3. Install [Arduino IDE](https://www.arduino.cc/en/software). 353 | 4. Click **New Sketch** and replace everything there with this sketch: [Arduino Sketch](https://github.com/jsbots/Clicker/blob/main/clicker_sketch/clicker_sketch.ino). 354 | 5. Click **Tools** -> **Port** and choose the port your Arduino Board connected to. 355 | 6. Click **Sketch** -> **Upload** and wait until the code uploads to your board. 356 | 7. Launch AutoFish, click **Advanced Settings** turn on **Use Arduino Board** option and choose the port your Arduino Board connected to, press **Connect** button. 357 | 358 | For **Raspberry Pico W** guide check [Streaming Mode](#streaming-mode-tv). 359 | 360 | Watch [Hardware Control Test Video](https://youtu.be/yE-qARS73oo) 361 | 362 | ## Streaming Mode :tv: 363 | 364 |

365 | 366 |

367 | 368 | Streaming Mode is used to run the bot and the game on different computers, you can use either hardware device for capturing your screen or stream it with OBS to the MediaMTX Server provided by the bot *(recommended)*. 369 | 370 | In both cases you need a **Raspberry Pico W** device to simulate hardware input, how to configure and connect it you can find in this video: [AutoFish 3.1 - Streaming Mode](https://youtu.be/Kacworq8j8Q) *The files for Pico W will come with the bot archive* 371 | 372 | - **Important!** In Streaming Mode after you press start the bot waits 5 seconds before starting *(by default)*. If you use VM on your host computer this time is given for you to focus the game manually, the bot won't do it on its own, because well... it's not on your host computer so it can't control your windows. 373 | 374 | ### Hardware 375 | 376 | How to use hardware device: [AutoFish 3.1 - Streaming Mode](https://youtu.be/Kacworq8j8Q) 377 | 378 | - **Important** Turn off [Enhance Pointer Precision](https://www.wikihow.com/Enhance-Pointer-Precision#:~:text=Enhance%20Pointer%20Precision%20changes%20your,gaming%2C%20consider%20disabling%20this%20feature.) in your Windows. 379 | - **Important!** If you run into any issues with Pico W (Like why it can't connect to your wifi and so on), you can install [Mu Editor](https://codewith.mu/) and check the errors in **Serial** section. 380 | 381 | ### Streaming Server 382 | 383 | 1. Choose **Streaming Server** as streaming source and then press **Launch** button. The bot will start RTSP server and will show (in the log) your RTSP address. 384 | 2. Install [OBS](https://obsproject.com/) on your gaming pc (where the game will run). 385 | 3. In OBS navigate to Settings -> Output -> Output Mode: Advanced -> Recording. Then configure it like this: 386 | 387 | - **Type**: **Custom Output(FFmpeg)** 388 | - **FFmpeg Output Type:** Output to URL 389 | - **File Path or URL**: Put here the address given by the bot, *e.g. rtsp://192.168.1.2:8554/live* 390 | - **Container Format**: rtsp 391 | - **Video Bitrate**: 6000 Kbps (experiment with this to get a better quality) 392 | - Check **show all codecs (even if potentically incompatible)** 393 | - If you have **AMD** GPU choose **h264_amf - AMD AMF H.265 Encoder** or If you have **NVIDIA** GPU choose **h264_nvenc** 394 | - **Video encoder settings (if any)**: bf=0 395 | - **Audio Track**: 1 396 | - **Audio Encoder**: libopus - libopus Opus *(don't confuse it with Opus)* 397 | 398 | 4. **Add Scene**. Then **Add Source** -> **Game Capture**. Then right-click on it and choose **Properties**. Turn off **Capture Cursor**. If you use [Sound Detection](#sound-detection-loud_sound) add **Application Audio Capture (BETA)** source in addition to the video source in your OBS. 399 | 5. If you have some additional networks (VPN and so on) make sure your OBS is connected to the same one the machine with the bot is connected to. (Advanced -> Network) 400 | 6. Press **"Start Recording"** *(Yes, Start Recording, not Start Streaming)*. If the log has a green line "Stream is being published to /live" then you successfully started streaming to the server. 401 | 7. Now press **Set Fishing Zone** and check if you can see your stream. 402 | 8. Press **Start Button** and wait until the bot start counting *(you can change counting time in Advanced Settings)*, during this time you need to focus your game on the PC where the game is running (or focus windows in a proper order if you run it in [Multiple Fishing Mode](#multiple-fishing-mode-rocket)) 403 | 404 | - **Important** Turn off [Enhance Pointer Precision](https://www.wikihow.com/Enhance-Pointer-Precision#:~:text=Enhance%20Pointer%20Precision%20changes%20your,gaming%2C%20consider%20disabling%20this%20feature.) in your Windows. 405 | - **Important!** Streaming Mode will work only for games in Fullscreen, that's why Game PC Resolution is the setting both for your gaming PC and the game. 406 | - **Important!** If you use any of the *color pickers* in Streaming Mode make the tolerance value lower, because the image you stream from OBS could be very dynamic. 407 | - **Important!** Make sure your firewall doesn't block 8554 port. 408 | 409 | ### VM 410 | 411 | Instead of another PC you can use a Virtual Machine for either Hardware or Streaming Server approach. There are a lot of guides how to install and use VM, so I won't cover it here (tested it on VMware). 412 | 413 | - **Important!** Use Windows 10 on your VM for the most stable results. 414 | - **Important!** Make sure your VM uses **Bridge** type of **Network Connection**. 415 | - **Important!** When setting Fishing Zone while using Streaming Server the stream image might be green, just minimize your VM window then focus it again. 416 | - **Important!** If you want to use it with Hardware capture device, make sure to connect it to the VM instead of host PC *(you connect it to your GPU and then to your USB and after it detects a new hardware you choose it work for VM only)*. 417 | 418 | 419 | ## Check For Players Around :telescope: (unstable) 420 | 421 | With some additional configuration you can set the bot to check for other players around. If it finds any it will notify you via telegram and sends a screenshot of the player found. It will search for players in front of you every second and players around you every minute (default). 422 | 423 | *For now it works only with players within 40 yd. range, because it relies on a target key.* 424 | 425 | Before using it you need to make some simple preliminary configuration: 426 | 427 | 1. Click on color picker icon from "Target HP" option and set it somewhere on the green field of your target health bar. You can target yourself before setting if there's no other player/NPC around. 428 | *vanilla* - If you play on Vanilla server you might need to set "Target HP Exception" value, because on Vanilla servers "Target Nearest Friend" also targets your own character, it should be a unique to your character target window pixel (some pixel on your name or your avatar). 429 | 2. Bind your "Target Key" to the same targeting key you use in the game (if you wish to target other players, it should be something like "Target Nearest Friendly Player"). 430 | 3. Choose what the bot should do after it finds a player in "Do After Player Found" option. 431 | 432 | ### Hints and issues 433 | - Tweak rotation time if the 360 degree turn isn't correct. 434 | - You can turn on "Target of target", in that case you will see on the screenshot recieved whether the found player is targeting you specifically. 435 | - This functionality won't work for non-hostile enemy players on Retail, because target friendly/enemy key doesn't target them in the game nor they have any names above if they aren't targeted first. 436 | 437 | ## Attacking/Running away :crossed_swords: (unstable) 438 | 439 | This module isn't "plug&play" and requires proper initial configuration and testing before using. 440 | 441 | *It definitely won't defeat anyone except for mobs, so choose a simple class for that role and make your rotation as simple as possible (some range class with simple spells that works both in melee and in range would do perfectly).* 442 | 443 | *For now it works only with players within 40 yd. range, because it relies on a target key.* 444 | 445 | The bot will notify you via telegram of any aggro events. 446 | 447 | Guide: 448 | 1. Turn off the nameplates (ctrl + v) and turn on Enemy Names (Options -> Interface -> Enemy Players), you can also set NPC names to "None" to make the bot avoid mobs names. 449 | 2. Click on color picker icon from Combat Indication Pixel and point it at the place which changes when your character switches to combat mode. Usually it should be a place which changes when the character is in combat mode: for example the place where character level is, which usually changes to "double swords" or the rim around the avatar which changes to red when in the combat mode. 450 | 3. Choose what to do after the bot is being attacked: Attack, Run Away or Stop. (Run Away is the most stable choice for now). 451 | 4. Click on color picker icon from Enemy HP Pixel and point it somewhere on the green field of your target health bar. 452 | 5. Bind your "Target Key" to the same targeting key you use in the game (if you wish to target other enemy players, it should be something like "Target Nearest Enemy Player", or "Target Nearest Enemy" if you want to target mobs too). 453 | 6. Click "+" button, if you chose "Attack" mode: 454 | 1. If your skill works only at range but doesn't work in melee, turn "Range Only" on. (But better choose another, more universal and simpler skill) 455 | 2. Bind the same key the skill is bound to in the game. 456 | 3. Click on color picker icon from Skill Position and point it exactly at the number of the skill (the color should be whitish/greyish), this number is usually used for range indication and the bot will check it to determine how far it is from the enemy and how much it should come up to it. 457 | 4. Execution time and cooldown you can find in the description of the skill and set them in the respective values. 458 | 459 | Use "Test Rotation" button to see what will happen if you are attacked during fishing and check whether your rotation and the bot works properly for you. 460 | 461 | ### Hints and issues: 462 | - You can change the size of enemy names in Accessibility section -> "Minimum Character Name Size". You can make it bigger to make the bot recognize enemies easier. 463 | 464 | 465 | ## Download :open_file_folder: 466 | 467 |

468 | 469 |

470 | 471 | Check again [Disclaimer](#disclaimer-warning) before downloading or running any of these (*I'm serious, the bot has been there for 3 years and people are still doing stupid things with it. Don't launch/use it if you are new to this stuff.*): 472 | 473 | AutoFish 2.8.3: [Download](https://github.com/jsbots/AutoFish/releases/tag/v2.8.3) 474 | 475 | AutoFish 3.3.0 Premium (trial version): [Download](https://buymeacoffee.com/jsbots/e/258390) 476 | 477 | AutoFish 3.3.0 Premium: [Download](https://www.buymeacoffee.com/jsbots/e/96734) 478 | -------------------------------------------------------------------------------- /app/bot/createBot.js: -------------------------------------------------------------------------------- 1 | const Zone = require("../utils/zone.js"); 2 | const createFishingZone = require("./fishingZone.js"); 3 | const createNotificationZone = require("./notificationZone.js"); 4 | const createLootZone = require("./lootZone.js"); 5 | const createLootExitZone = require("./lootExitZone.js"); 6 | const createRedButtonZone = require("./redButtonZone.js"); 7 | const {app} = require(`electron`); 8 | const Vec = require('../utils/vec.js'); 9 | const nutMouse = require("../game/nut.js").mouse; 10 | 11 | const { screen, Region, Point } = require("@nut-tree/nut-js"); 12 | 13 | let once = (fn, done) => (...args) => { 14 | if(!done) { 15 | done = true; 16 | return fn(...args); 17 | } 18 | } 19 | 20 | const { 21 | percentComparison, 22 | readTextFrom, 23 | sortWordsByItem, 24 | } = require("../utils/textReader.js"); 25 | 26 | const { createTimer } = require("../utils/time.js"); 27 | 28 | const sleep = (time) => { 29 | return new Promise((resolve) => { 30 | setTimeout(resolve, time); 31 | }); 32 | }; 33 | 34 | const random = (from, to) => { 35 | return from + Math.random() * (to - from); 36 | }; 37 | 38 | const createBot = (game, { config, settings }, winSwitch, state) => { 39 | if(settings.game == `Vanilla (splash)` && settings.autoTh == true) { 40 | settings.autoTh = false; 41 | }; 42 | 43 | const { keyboard, mouse, workwindow } = game; 44 | const delay = [config.delay.from, config.delay.to]; 45 | 46 | const action = async (callback) => { 47 | if(state.status == `stop`) return; 48 | 49 | await winSwitch.execute(workwindow); 50 | await callback(); 51 | winSwitch.finished(); 52 | }; 53 | 54 | const screenSize = workwindow.getView(); 55 | 56 | const actionOnce = once(action); 57 | 58 | const missOnPurposeValue = config.missOnPurpose ? random(config.missOnPurposeRandom.from, config.missOnPurposeRandom.to) : 0; 59 | const getDataFrom = async (zone) => { 60 | if(zone.x < 0) zone.x = 0; 61 | if(zone.y < 0) zone.y = 0; 62 | if(zone.width > screenSize.width) zone.width = screenSize.width; 63 | if(zone.height > screenSize.height) zone.height = screenSize.height; 64 | 65 | switch(true) { 66 | case config.libraryType == 'nut.js': { 67 | await actionOnce(() => {}); 68 | let grabbed = await(await screen.grabRegion(new Region(zone.x + screenSize.x, zone.y + screenSize.y, zone.width, zone.height))).toRGB(); 69 | return grabbed; 70 | } 71 | 72 | case config.libraryType == 'keysender': { 73 | return workwindow.capture(zone); 74 | } 75 | } 76 | }; 77 | 78 | let fishingZone = createFishingZone(getDataFrom, Zone.from(screenSize).toRel(config.relZone), screenSize, settings, config); 79 | const notificationZoneRel = Zone.from({ 80 | x: Math.round((screenSize.width / 2) - (screenSize.width * config.notificationPos.width)), 81 | y: Math.round(screenSize.height * config.notificationPos.y), 82 | width: Math.round((screenSize.width * config.notificationPos.width) * 2), 83 | height: Math.round(screenSize.height * config.notificationPos.height) 84 | }); 85 | const notificationZone = createNotificationZone({ 86 | getDataFrom, 87 | zone: notificationZoneRel, 88 | }); 89 | 90 | let lootWinResType; 91 | switch(true) { 92 | case screenSize.height <= 768: { 93 | lootWinResType = `1536` 94 | break; 95 | } 96 | 97 | case screenSize.height > 768 && screenSize.height <= 1080: { 98 | lootWinResType = `1920`; 99 | break; 100 | } 101 | 102 | case screenSize.height > 1080 && screenSize.height <= 1440: { 103 | lootWinResType = `2560`; 104 | break; 105 | } 106 | 107 | case screenSize.height > 1440: { 108 | lootWinResType = `3840`; 109 | } 110 | } 111 | let ultrawide = (screenSize.width / screenSize.height) > 2; 112 | 113 | const lootWindowPatch = config.lootWindow[lootWinResType] ? config.lootWindow[lootWinResType] : config.lootWindow[`1920`]; 114 | 115 | const confirmationWindowPatch = 116 | config.confirmationWindow[screenSize.width <= 1536 ? `1536` : `1920`]; 117 | 118 | let ignoreInterruptedPatch; 119 | if(config.ignoreInterrupted) { 120 | ignoreInterruptedPatch = config.ignoreInterrupted[screenSize.width <= 1536 ? `1536` : `1920`]; 121 | } 122 | 123 | const lootWindow = { 124 | upperLimit: lootWindowPatch.upperLimit * screenSize.height, 125 | toItemX: lootWindowPatch.toItemX * (ultrawide ? Number(lootWinResType) : screenSize.width), 126 | toItemY: lootWindowPatch.toItemY * screenSize.height, 127 | width: lootWindowPatch.width * (ultrawide ? Number(lootWinResType) : screenSize.width), 128 | height: lootWindowPatch.height * screenSize.height, 129 | itemHeight: lootWindowPatch.itemHeight * screenSize.height, 130 | }; 131 | 132 | if(lootWindowPatch.itemHeightAdd) { 133 | lootWindow.itemHeightAdd = lootWindowPatch.itemHeightAdd * screenSize.height; 134 | } 135 | 136 | const confirmationWindow = { 137 | x: (screenSize.width / 2) - (confirmationWindowPatch.x * screenSize.width), 138 | y: confirmationWindowPatch.y * screenSize.height, 139 | width: confirmationWindowPatch.width * screenSize.width, 140 | height: confirmationWindowPatch.height * screenSize.height 141 | }; 142 | 143 | if(lootWindowPatch.cursorPos) { 144 | lootWindow.cursorPos = { 145 | x: lootWindowPatch.cursorPos.x * (ultrawide ? Number(lootWinResType) : screenSize.width), 146 | y: lootWindowPatch.cursorPos.y * screenSize.height 147 | } 148 | } 149 | 150 | if(lootWindowPatch.exitButton) { 151 | lootWindow.exitButton = { 152 | x: lootWindowPatch.exitButton.x * (ultrawide ? Number(lootWinResType) : screenSize.width), 153 | y: lootWindowPatch.exitButton.y * screenSize.height 154 | } 155 | } 156 | 157 | const lootExitZone = createLootExitZone({getDataFrom, lootWindow, size: 10 * (screenSize.width / 1920)}); 158 | 159 | const whitelist = config.whitelistWords 160 | .split(",") 161 | .map((word) => word.trim()); 162 | 163 | 164 | const generateMovePastPos = (pos) => { 165 | let posFromCurrent = {x: pos.x - mouse.getPos().x, y: pos.y - mouse.getPos().y}; 166 | let movePastDir = {x: posFromCurrent.x > 0 ? 1 : -1, y: posFromCurrent.y > 0 ? 1 : -1}; 167 | let movePastMaxMain = Math.abs(posFromCurrent.x) > Math.abs(posFromCurrent.y) ? "x" : "y"; 168 | let movePastMaxSub = Math.abs(posFromCurrent.x) < Math.abs(posFromCurrent.y) ? "x" : "y"; 169 | 170 | const movePastDist = Math.sqrt(Math.pow(Math.abs(posFromCurrent.x), 2) + Math.pow(Math.abs(posFromCurrent.y), 2)) * .5; 171 | 172 | posFromCurrent[movePastMaxMain] += (movePastDist * random(0.15, 0.20)) * movePastDir[movePastMaxMain]; 173 | posFromCurrent[movePastMaxSub] += (movePastDist * random(0.05, 0.10)) * movePastDir[movePastMaxSub]; 174 | 175 | return {x: posFromCurrent.x + mouse.getPos().x, y: posFromCurrent.y + mouse.getPos().y}; 176 | } 177 | 178 | const moveTo = async ({ pos, randomRange, fineTune = {offset: randomRange || 5, steps: [1, 3]}, forcedNutMouse}) => { 179 | if (randomRange) { 180 | pos.x = pos.x + random(-randomRange, randomRange); 181 | pos.y = pos.y + random(-randomRange, randomRange); 182 | } 183 | 184 | if (config.likeHuman) { 185 | let convertedSpeed = config.mouseMoveSpeed / 100; 186 | let speedByRes = convertedSpeed * (screenSize.width / 1920); 187 | let speedCoofByRes = 0.15 * (screenSize.width / 1920); 188 | let randomSpeed = {from: speedByRes - speedCoofByRes, to: speedByRes + speedCoofByRes} 189 | if(randomSpeed.from < 0) { 190 | randomSpeed.from = 0; 191 | } 192 | 193 | let deviationCoof = config.libraryTypeInput === 'nut.js' ? 30 : 15; 194 | let randomDeviation = {from: config.mouseCurvatureStrength - deviationCoof, to: config.mouseCurvatureStrength + deviationCoof}; 195 | if(randomDeviation.from < 0) { 196 | randomDeviation.from = 0; 197 | } 198 | 199 | if(config.libraryTypeInput === 'nut.js' || forcedNutMouse) { 200 | if(!(pos instanceof Vec)) { 201 | pos = new Vec(pos.x, pos.y); 202 | } 203 | let curPos = mouse.getPos(); 204 | curPos = new Vec(curPos.x, curPos.y); 205 | await nutMouse.humanMoveTo({ 206 | from: curPos.plus(screenSize), 207 | to: pos.plus(screenSize), 208 | speed: random(randomSpeed.from, randomSpeed.to), 209 | deviation: random(randomDeviation.from, randomDeviation.to), 210 | fishingZone: Zone.from(screenSize).toRel(config.relZone) 211 | }); 212 | } else { 213 | await mouse.humanMoveTo(pos.x, pos.y, random(randomSpeed.from, randomSpeed.to), random(randomDeviation.from, randomDeviation.to)); 214 | } 215 | 216 | if(config.likeHumanFineTune && fineTune && state.status != "stop") { 217 | let times = random(fineTune.steps[0], fineTune.steps[1]); 218 | for(let i = 1; i <= times; i++) { 219 | await mouse.humanMoveTo( 220 | pos.x + random(-fineTune.offset / i, fineTune.offset / i), 221 | pos.y + random(-fineTune.offset / i, fineTune.offset / i), 222 | random(randomSpeed.from / 3, randomSpeed.to / 3), 223 | random(randomDeviation.from, randomDeviation.to) 224 | ); 225 | await sleep(random(25, 150)); 226 | } 227 | } 228 | } else { 229 | await mouse.moveTo(pos.x, pos.y, delay); 230 | } 231 | }; 232 | 233 | const checkRedButton = async (buttonPos) => { 234 | const redButtonZone = createRedButtonZone({getDataFrom, zone: buttonPos}); 235 | const colorPositions = await redButtonZone.isOn(mouse.getPos()); 236 | if(colorPositions){ 237 | let colorPos = colorPositions.yellow || colorPositions.red; 238 | await action(async () => { 239 | await moveTo({pos: { 240 | x: buttonPos.x + colorPos.x, 241 | y: buttonPos.y + colorPos.y}, 242 | randomRange: 2, 243 | }); 244 | }); 245 | 246 | return await redButtonZone.isOnAfterHighlight() 247 | } 248 | }; 249 | 250 | const checkBobberTimer = createTimer(() => config.maxFishTime * 1000); 251 | const missOnPurposeTimer = createTimer(() => random(config.missOnPurposeRandomDelay.from, config.missOnPurposeRandomDelay.to) * 1000); 252 | const logOutTimer = createTimer(() => random(config.logOutEvery.from * 1000 * 60, config.logOutEvery.to * 1000 * 60)); 253 | 254 | const logOut = async (state) => { 255 | await action(async () => { 256 | if(config.logOutUseMacro) { 257 | await keyboard.toggleKey(config.logOutMacroKey, true, delay); 258 | await keyboard.toggleKey(config.logOutMacroKey, false, delay); 259 | } else { 260 | await keyboard.toggleKey(`enter`, true, delay); 261 | await keyboard.toggleKey(`enter`, false, delay); 262 | await keyboard.printText(`/logout`, delay); 263 | await keyboard.toggleKey(`enter`, true, delay); 264 | await keyboard.toggleKey(`enter`, false, delay); 265 | } 266 | }); 267 | await sleep(20000); 268 | 269 | const addTime = applyLures.timer.timeRemains(); 270 | 271 | await sleep(random(config.logOutFor.from * 1000, config.logOutFor.to * 1000)); 272 | if(state.status == 'stop') { 273 | return; 274 | } 275 | await action(async () => { 276 | await keyboard.sendKey(`enter`); 277 | }); 278 | await sleep(random(config.logOutAfter.from * 1000, config.logOutAfter.from * 1000)); 279 | 280 | if(config.lures) { 281 | applyLures.timer.update(() => addTime); 282 | } 283 | 284 | }; 285 | logOut.timer = logOutTimer; 286 | logOut.on = config.logOut > 0; 287 | 288 | 289 | const preliminaryChecks = async (log) => { 290 | if(config.ignorePreliminary) return; 291 | 292 | if (screenSize.x == -32000 && screenSize.y == -32000) { 293 | throw new Error("The window is either in fullscreen mode or minimized. Switch to windowed or windowed(maximized)."); 294 | } 295 | 296 | if(settings.autoColor) { 297 | let colorPercent = await fishingZone.checkColor(); 298 | if(colorPercent > 25) { 299 | fishingZone.changeColor(); 300 | } 301 | } 302 | 303 | if(!settings.autoTh) { 304 | let bobber = await fishingZone.findBobber(null, log); 305 | if (bobber) { 306 | screen.config.highlightOpacity = 1; 307 | screen.config.highlightDurationMs = 1000; 308 | const highlightRegion = new Region(screenSize.x + (bobber.x - 30), screenSize.y + (bobber.y - 30), 30, 30); 309 | await screen.highlight(highlightRegion); 310 | 311 | throw new Error( 312 | `Found ${settings.bobberColor == `red` ? `red` : `blue`} colors before casting. Change your Fishing Zone or increase the intensity value or change the fishing place.` 313 | ); 314 | } 315 | } 316 | }; 317 | 318 | const applyLures = async () => { 319 | await action(async () => { 320 | await keyboard.sendKey(config.luresKey, delay); 321 | }); 322 | 323 | if(config.confirmLures) { 324 | if (config.reaction) { 325 | await sleep(random(config.reactionDelay.from, config.reactionDelay.to)); 326 | } else { 327 | await sleep(250) // wait for the window to appear 328 | } 329 | const needsConfirm = await checkRedButton(confirmationWindow); 330 | if(needsConfirm) { 331 | await action(async () => { 332 | await mouse.toggle('left', true, delay); 333 | await mouse.toggle('left', false, delay); 334 | }); 335 | } 336 | } 337 | 338 | await sleep(config.luresDelay); 339 | }; 340 | 341 | applyLures.on = config.lures; 342 | applyLures.timer = createTimer(() => { 343 | return config.luresDelayMin * 60 * 1000; 344 | }); 345 | 346 | if(config.luresOmitInitial) { 347 | applyLures.timer.start(); 348 | } 349 | 350 | const randomSleep = async () => { 351 | let sleepFor = random( 352 | config.randomSleepDelay.from * 1000, 353 | config.randomSleepDelay.to * 1000 354 | ); 355 | await sleep(sleepFor); 356 | }; 357 | 358 | randomSleep.on = config.randomSleep; 359 | randomSleep.timer = createTimer(() => { 360 | return ( 361 | random(config.randomSleepEvery.from, config.randomSleepEvery.to) * 362 | 60 * 363 | 1000 364 | ); 365 | }); 366 | 367 | const cutOutNotification = (zone) => { 368 | let noteZone = Zone.from(screenSize).toRel(zone); 369 | let fZone = Zone.from(screenSize).toRel(config.relZone) 370 | let result = []; 371 | for(let y = noteZone.y - fZone.y; y < (noteZone.y - fZone.y) + noteZone.height; y++) { 372 | for(let x = noteZone.x - fZone.x; x < (noteZone.x - fZone.x) + noteZone.width; x++) { 373 | result.push(new Vec(x, y)); 374 | } 375 | } 376 | return result; 377 | } 378 | 379 | const findAllBobberColors = async () => { 380 | if(settings.game != `Retail` && settings.game != `Cata Classic` && settings.game != `Classic`) { 381 | let bobber = await fishingZone.getBobberPrint(10); 382 | 383 | if(!bobber) { 384 | return; 385 | } 386 | 387 | if(config.ignoreInterrupted) { 388 | let interruptedArea = cutOutNotification(ignoreInterruptedPatch); 389 | bobber = [...bobber, ...interruptedArea]; 390 | } 391 | 392 | return bobber; 393 | } else { 394 | return null; 395 | } 396 | }; 397 | 398 | const castFishing = async (state) => { 399 | await action(async () => { 400 | await keyboard.sendKey(settings.fishingKey, delay); 401 | }); 402 | 403 | if (state.status == "initial") { 404 | await sleep(250); 405 | if (!config.ignorePreliminary && await notificationZone.check("error")) { 406 | throw new Error(`Game error notification occured on casting fishing.`); 407 | } else { 408 | state.status = "working"; 409 | } 410 | } 411 | 412 | if(settings.checkLogic == `pixelmatch`) { 413 | await sleep(2500); 414 | } else { 415 | await sleep(random(config.castDelay, config.castDelay + 500)); 416 | } 417 | }; 418 | 419 | const highlightBobber = async (pos, log) => { 420 | if (settings.checkLogic == `pixelmatch` || 421 | settings.useInt || 422 | (config.likeHuman && random(0, 100) > config.highlightPercent)) { 423 | return pos; 424 | } 425 | 426 | if (config.reaction) { 427 | await sleep(random(config.reactionDelay.from, config.reactionDelay.to)); 428 | } 429 | 430 | await action(async () => { 431 | if(config.likeHumanFineTune) { 432 | let pastPost = generateMovePastPos(pos); 433 | await moveTo({pos: pastPost, fineTune: null}) 434 | } 435 | 436 | await moveTo({ pos, randomRange: 5, fineTune: {offset: 5, steps: [1, 5]}}); 437 | }); 438 | 439 | return await findBobber(log, pos); 440 | }; 441 | 442 | const findBobber = async (log, highlight) => { 443 | const pos = await fishingZone.findBobber(findBobber.memory, log, highlight); 444 | 445 | if(pos && process.env.NODE_ENV == `dev`) { 446 | screen.config.highlightOpacity = 1; 447 | screen.config.highlightDurationMs = 250; 448 | const highlightRegion = new Region(screenSize.x + (pos.x - 5), screenSize.y + (pos.y - 5), 10, 10); 449 | await screen.highlight(highlightRegion); 450 | } 451 | 452 | return pos; 453 | }; 454 | findBobber.memory = null; 455 | findBobber.maxAttempts = config.maxAttempts; 456 | 457 | const checkBobber = async (pos, state) => { 458 | checkBobberTimer.start(); 459 | const startTime = Date.now(); 460 | const missOnPurpose = random(0, 100) < missOnPurposeValue; 461 | if(missOnPurpose) { 462 | missOnPurposeTimer.update(); 463 | } 464 | 465 | while (state.status == "working") { 466 | if (checkBobberTimer.isElapsed()) { 467 | switch(config.maxFishTimeAfter) { 468 | case `stop`: { 469 | throw new Error( 470 | `Something is wrong. The bot had been checking the bobber for more than ${config.maxFishTime} ms. The server might be down or your character is dead.` 471 | ); 472 | } 473 | case `recast`: { 474 | state.status = `working`; 475 | return false; 476 | } 477 | } 478 | } 479 | 480 | if(missOnPurpose && missOnPurposeTimer.isElapsed()) { 481 | pos.missedIntentionally = true; 482 | return pos; 483 | } 484 | 485 | if(settings.checkLogic == `pixelmatch`) { 486 | if(await fishingZone.checkPixelMatch(pos, startTime)) { 487 | return pos; 488 | }; 489 | } else if(settings.game == `Retail`) { 490 | if(!(await fishingZone.checkBobberPrint(pos))) { 491 | return pos; 492 | } 493 | } else if (settings.game == `Vanilla (splash)`) { 494 | if(await fishingZone.checkBobberPrintSplash(pos)) { 495 | return pos; 496 | } 497 | } else { 498 | if (!(await fishingZone.isBobber(pos))) { 499 | const newPos = settings.autoTh ? await fishingZone.checkBelow(pos) : await fishingZone.checkAroundBobber(pos); 500 | if (!newPos) { 501 | return pos; 502 | } else { 503 | pos = newPos; 504 | } 505 | } 506 | 507 | if(settings.autoTh) { 508 | pos = await fishingZone.checkAbove(pos); 509 | } 510 | } 511 | await sleep(config.checkingDelay); 512 | } 513 | 514 | }; 515 | 516 | const pickLoot = async () => { 517 | let cursorPos = config.atMouse || !lootWindow.cursorPos ? mouse.getPos() : lootWindow.cursorPos; 518 | if (cursorPos.y - lootWindow.upperLimit < 0) { 519 | cursorPos.y = lootWindow.upperLimit; 520 | } 521 | 522 | if (config.reaction) { 523 | await sleep(random(config.reactionDelay.from, config.reactionDelay.to)); // react to opening loot win 524 | } else { 525 | await sleep(150); // open loot window 526 | } 527 | 528 | if(settings.game != `Retail`) { 529 | let pos = { 530 | x: cursorPos.x + random(-(lootWindow.width * .25), lootWindow.width * .25), 531 | y: cursorPos.y - lootWindow.toItemY - 10, 532 | }; 533 | await moveTo({ pos, randomRange: 5 }); 534 | } 535 | 536 | if (config.reaction) { 537 | await sleep(random(config.reactionDelay.from, config.reactionDelay.to)); // hint disappearing and smooth text analyzing time with random value 538 | } else { 539 | await sleep(150); // hint dissappearing 540 | } 541 | 542 | if(lootWindow.exitButton) { 543 | for(let times = 0; times <= 20; times++) { // Wait for 2 seconds max until loot appears 544 | await sleep(100); 545 | if(await lootExitZone.isLootOpened(cursorPos)) { 546 | break; 547 | } 548 | 549 | if(times == 20) { 550 | return []; 551 | } 552 | } 553 | } 554 | 555 | const lootWindowDim = { 556 | x: cursorPos.x + lootWindow.toItemX, 557 | y: cursorPos.y - lootWindow.toItemY, 558 | width: lootWindow.width, 559 | height: lootWindow.height, 560 | }; 561 | 562 | let recognizedWords = await readTextFrom(await getDataFrom(lootWindowDim), screenSize.width <= 1536 ? 3 : 2); 563 | let items = sortWordsByItem(recognizedWords, lootWindow, settings.game == `Retail`); 564 | let itemPos = 0; 565 | 566 | let itemsPicked = []; 567 | for (let item of items) { 568 | let isInList = whitelist.find((word) => percentComparison(word, item) > 90); 569 | 570 | if(config.filterType == `blacklist`) { 571 | if(isInList) { 572 | isInList = false; 573 | } else { 574 | isInList = true; 575 | } 576 | } 577 | 578 | if (!isInList && config.whiteListBlueGreen) { 579 | let lootZone = createLootZone({ 580 | getDataFrom, 581 | zone: { 582 | x: lootWindowDim.x, 583 | y: lootWindowDim.y + itemPos, 584 | width: lootWindow.width, 585 | height: lootWindow.itemHeight, 586 | }, 587 | }); 588 | 589 | isInList = await lootZone.findItems("blue", "green", "purple"); 590 | } 591 | 592 | if (isInList) { 593 | await moveTo({ 594 | pos: { 595 | x: cursorPos.x, 596 | y: cursorPos.y + itemPos, 597 | }, 598 | randomRange: 5, 599 | }); 600 | 601 | if (config.reaction && random(0, 10) > 8) { // sometimes we think 602 | await sleep(random(config.reactionDelay.from, config.reactionDelay.to)); 603 | } else { 604 | await sleep(random(50, 150)); 605 | } 606 | 607 | await mouse.toggle("right", true, delay); 608 | await mouse.toggle("right", false, delay); 609 | 610 | if(typeof isInList == `boolean` && config.confirmSoulbound) { 611 | if (config.reaction) { 612 | await sleep(random(config.reactionDelay.from, config.reactionDelay.to)); 613 | } else { 614 | await sleep(250) // wait for the window to appear 615 | } 616 | const needsConfirm = await checkRedButton(confirmationWindow); 617 | if(needsConfirm) { 618 | await action(async () => { 619 | await mouse.toggle('left', true, delay); 620 | await mouse.toggle('left', false, delay); 621 | }); 622 | } 623 | } 624 | 625 | itemsPicked.push(item); 626 | } 627 | 628 | itemPos += settings.game == `Retail` ? lootWindow.itemHeight + lootWindow.itemHeightAdd : lootWindow.itemHeight; 629 | } 630 | 631 | if(settings.useInt && itemsPicked.length > 1) { 632 | await moveTo({pos: cursorPos, randomRange: 5}); 633 | } 634 | 635 | await sleep(350); // disappearing loot window delay 636 | if (settings.game == 'Leg' ? items.length != itemsPicked.length : await lootExitZone.isLootOpened(cursorPos)) { 637 | 638 | if (config.reaction) { 639 | await sleep(random(config.reactionDelay.from, config.reactionDelay.to)); 640 | } 641 | 642 | if((config.closeLoot == `mouse` || config.closeLoot == `mouse+esc`) && lootWindow.exitButton) { 643 | if(config.closeLoot == `mouse`) { 644 | await moveTo({ pos: { 645 | x: cursorPos.x + lootWindow.exitButton.x, 646 | y: cursorPos.y - lootWindow.exitButton.y 647 | }, randomRange: 2}); 648 | await mouse.toggle("left", true, delay); 649 | await mouse.toggle("left", false, delay); 650 | 651 | if(settings.useInt) { 652 | await moveTo({ pos: cursorPos, randomRange: 2 }); 653 | } 654 | } 655 | 656 | if(config.closeLoot == `mouse+esc`) { 657 | if(random(0, 100) > 50) { 658 | await keyboard.sendKey("escape", delay); 659 | } else { 660 | await moveTo({ pos: { 661 | x: cursorPos.x + lootWindow.exitButton.x, 662 | y: cursorPos.y - lootWindow.exitButton.y 663 | }, randomRange: 2}); 664 | await mouse.toggle("left", true, delay); 665 | await mouse.toggle("left", false, delay); 666 | 667 | if(settings.useInt) { 668 | await moveTo({ pos: cursorPos, randomRange: 2 }); 669 | } 670 | } 671 | } 672 | } else { 673 | await keyboard.sendKey("escape", delay); 674 | } 675 | } 676 | 677 | 678 | return itemsPicked; 679 | }; 680 | 681 | const dynamicThreshold = () => { 682 | settings.threshold = settings.threshold - config.dynamicThresholdValue; 683 | fishingZone = createFishingZone(getDataFrom, Zone.from(screenSize).toRel(config.relZone), screenSize, settings, config); 684 | } 685 | 686 | dynamicThreshold.on = config.dynamicThreshold && !settings.autoTh; 687 | dynamicThreshold.limit = () => settings.threshold < 20; 688 | 689 | const hookBobber = async (pos) => { 690 | if (config.reaction) { 691 | await sleep(random(config.reactionDelay.from, config.reactionDelay.to)); 692 | } 693 | let caught = false; 694 | 695 | if(!pos) { // in case recasting if it's stated in maxFishTimeAfter 696 | return caught; 697 | } 698 | 699 | await action(async () => { 700 | if(settings.useInt) { 701 | await keyboard.toggleKey(settings.intKey, true, delay); 702 | await keyboard.toggleKey(settings.intKey, false, delay); 703 | } else { 704 | await moveTo({ pos, randomRange: 5}); 705 | 706 | if (config.shiftClick) { 707 | await keyboard.toggleKey("shift", true, delay); 708 | await mouse.toggle(config.catchFishButton, true, delay); 709 | await mouse.toggle(config.catchFishButton, false, delay); 710 | await keyboard.toggleKey("shift", false, delay); 711 | } else { 712 | await mouse.toggle(config.catchFishButton, true, delay); 713 | await mouse.toggle(config.catchFishButton, false, delay); 714 | } 715 | } 716 | 717 | await sleep(250); 718 | if (!(await notificationZone.check("warning")) && !pos.missedIntentionally) { 719 | caught = true; 720 | if (config.whitelist) { 721 | let itemsPicked = await pickLoot(); 722 | if(itemsPicked.length > 0) { 723 | caught = itemsPicked.toString(); 724 | } 725 | } 726 | } 727 | }); 728 | if(!config.whitelist) { 729 | await sleep(config.closeLootDelay); 730 | } else { 731 | await sleep(150); 732 | } 733 | 734 | if (config.sleepAfterHook) { 735 | await sleep(random(config.afterHookDelay.from, config.afterHookDelay.to)); 736 | } 737 | 738 | return caught; 739 | }; 740 | 741 | 742 | const doAfterTimer = async (onError, wins) => { 743 | if(config.afterTimer == `HS` || config.afterTimer == `HS + Quit`) { 744 | await action(async () => { 745 | await keyboard.sendKey(config.hsKey, delay); 746 | }, true); 747 | await sleep(config.hsKeyDelay * 1000); 748 | } 749 | 750 | state.status = `stop`; 751 | 752 | if(config.afterTimer == `HS + Quit` || config.afterTimer == `Quit`) { 753 | if(wins.every(win => win.state.status == `stop`)) { 754 | if(config.timerShutDown) { 755 | await keyboard.sendKey(`lWin`, delay); 756 | await sleep(random(1000, 2000)); 757 | await keyboard.printText(`cmd`, delay); 758 | await sleep(random(1000, 2000)); 759 | await keyboard.sendKey(`enter`, delay); 760 | await sleep(random(1000, 2000)); 761 | await keyboard.printText(`shutdown -s -t 10`, delay); 762 | await keyboard.sendKey(`enter`, delay); 763 | } 764 | app.quit(); 765 | } 766 | workwindow.close(); 767 | } 768 | 769 | if(wins.every(win => win.state.status == `stop`)) { 770 | onError(); 771 | } 772 | } 773 | 774 | doAfterTimer.on = config.timer; 775 | doAfterTimer.timer = createTimer(() => config.timerTime * 1000 * 60); 776 | 777 | const checkConfirm = async () => { 778 | if(config.checkConfirm && !config.whitelist) { 779 | await sleep(250) // wait for the window to appear 780 | const needsConfirm = await checkRedButton(confirmationWindow); 781 | if(needsConfirm) { 782 | await action(async () => { 783 | await mouse.toggle('left', true, delay); 784 | await mouse.toggle('left', false, delay); 785 | }); 786 | } 787 | } 788 | } 789 | 790 | return { 791 | checkConfirm, 792 | doAfterTimer, 793 | dynamicThreshold, 794 | logOut, 795 | preliminaryChecks, 796 | findAllBobberColors, 797 | randomSleep, 798 | applyLures, 799 | castFishing, 800 | findBobber, 801 | highlightBobber, 802 | checkBobber, 803 | hookBobber 804 | }; 805 | }; 806 | 807 | module.exports = createBot; 808 | -------------------------------------------------------------------------------- /app/bot/createBots.js: -------------------------------------------------------------------------------- 1 | const runBot = require("./runBot.js"); 2 | const createBot = require("./createBot.js"); 3 | 4 | const { convertMs } = require('../utils/time.js'); 5 | const Stats = require('./stats.js'); 6 | 7 | const { createIdLog } = require("../utils/logger.js"); 8 | const EventLine = require("../utils/eventLine.js"); 9 | const { setWorker } = require("../utils/textReader.js"); 10 | 11 | const createWinSwitch = require("../game/winSwitch.js"); 12 | let properLanguages = {eng: `English`, spa: "Spanish", spa_old: "Spanish Old", deu: "Deutsch", por: "Português", fra: "Français", ita: "Italiano", chi_sim: "Simplified Chinese", chi_tra: "Traditional Chinese", kor: "Korean", rus: "Russian"}; 13 | 14 | const createBots = async (games, settings, config, log) => { 15 | const winSwitch = createWinSwitch(new EventLine()); 16 | 17 | if(config.patch[settings.game].whitelist) { 18 | log.send(`Downloading data for ${properLanguages[config.patch[settings.game].whitelistLanguage]} language, it might take a while...`); 19 | await setWorker(config.patch[settings.game].whitelistLanguage); 20 | } 21 | 22 | games = [games[0]]; 23 | 24 | const bots = games.map((game, i) => { 25 | let state = { status: "initial", startTime: Date.now() }; 26 | return { 27 | bot: createBot(game, {config: config.patch[settings.game], settings}, winSwitch, state), 28 | log: createIdLog(log, ++i), 29 | state, 30 | stats: new Stats() 31 | } 32 | }); 33 | 34 | return { 35 | startBots(onError) { 36 | log.send("Starting the bot...") 37 | 38 | bots.forEach((bot) => { 39 | runBot(bot, onError, bots) 40 | .then(() => { 41 | log.setState(true); 42 | bot.stats.show().forEach((stat) => bot.log.ok(stat)); 43 | bot.log.ok(`Time Passed: ${convertMs(Date.now() - bot.state.startTime)}`); 44 | }) 45 | .catch((error) => { 46 | if(bot.state.status == `stop`) { 47 | return; 48 | } else { 49 | bot.state.status = "stop"; 50 | } 51 | 52 | if (bots.every(({state}) => state.status == "stop")) { 53 | onError(); 54 | } 55 | log.setState(true); 56 | if(process.env.NODE_ENV == `dev`) { 57 | bot.log.err(`${error.message, error.stack}`); 58 | } else { 59 | bot.log.err(`${error.message}`); 60 | } 61 | 62 | 63 | bot.stats.show().forEach((stat) => bot.log.ok(stat)); 64 | bot.log.ok(`Time Passed: ${convertMs(Date.now() - bot.state.startTime)}`); 65 | }); 66 | }) 67 | }, 68 | stopBots() { 69 | log.send('Stopping the bot...') 70 | log.setState(false); 71 | bots.forEach(({state}) => state.status = "stop"); 72 | }, 73 | }; 74 | }; 75 | 76 | module.exports = createBots; 77 | -------------------------------------------------------------------------------- /app/bot/fishingZone.js: -------------------------------------------------------------------------------- 1 | const createRgb = require('../utils/rgb.js'); 2 | const Vec = require('../utils/vec.js'); 3 | const Jimp = require('jimp'); 4 | 5 | const pixelmatch = require('pixelmatch'); 6 | 7 | const isInLimits = ({ x, y }, { width, height }) => { 8 | return x >= 0 && y >= 0 && x < width && y < height; 9 | }; 10 | 11 | const isOverThreshold = ([r, g, b], threshold) => (r - Math.max(g, b)) > threshold; 12 | const isCloseEnough = ([_, g, b], closeness) => Math.abs(g - b) <= closeness; 13 | 14 | const isRed = (threshold, closeness = 255, size = 255, upperLimit = 335) => ([r, g, b]) => isOverThreshold([r, g, b], threshold) && 15 | isCloseEnough([r, g, b], closeness) && 16 | g < size && b < size && r <= upperLimit && g != 0 && b != 0; 17 | 18 | const isBlue = (threshold, closeness = 255, size = 255, upperLimit = 335) => ([r, g, b]) => isOverThreshold([b, g, r], threshold) && 19 | isCloseEnough([b, g, r], closeness) && 20 | r < size && g < size && b <= upperLimit && r != 0 && g != 0; 21 | 22 | let imgAroundBobberPrev; 23 | let pixelMatchMax; 24 | 25 | const createFishingZone = (getDataFrom, zone, screenSize, { game, checkLogic, autoSens, threshold, bobberColor, autoTh, bobberSensitivity: sensitivity}, {findBobberDirection: direction, splashColor}) => { 26 | let checkAboveCompensateValue = 0; 27 | const doubleZoneSize = Math.round((screenSize.height / 1080) * 50); // 25 28 | sensitivity = (game == `Retail` || game == `Vanilla (splash)` ? 30 - sensitivity[game] : 10 - sensitivity[game]) || 1; 29 | 30 | if(checkLogic == 'pixelmatch') { 31 | if(autoSens) { 32 | sensitivity = 0.25; 33 | } else { 34 | sensitivity = game == `Retail` || game == `Vanilla (splash)` ? sensitivity / 30 : sensitivity / 10; 35 | } 36 | } 37 | 38 | let isBobber = bobberColor == `red` ? isRed(threshold, 50) : isBlue(threshold, 50); 39 | let saturation = bobberColor == `red` ? [80, 0, 0] : [0, 0, 80]; 40 | const looksLikeBobber = (size) => (pos, color, rgb) => { 41 | let pointsFound = pos.getPointsAround(Math.round(size * (screenSize.height / 1080)) || 1).filter((pos) => isBobber(rgb.colorAt(pos))); 42 | if(pointsFound.length >= Math.round(8 * (screenSize.height / 1080))) { 43 | return true; 44 | } 45 | } 46 | let filledBobber; 47 | 48 | return { 49 | async findBobber(exception, log, highlight) { 50 | 51 | checkAboveCompensateValue = 0; 52 | imgAroundBobberPrev = null; 53 | pixelMatchMax = 0; 54 | 55 | let rgbZone = zone; 56 | if(highlight && (game == `Classic` || game == `Cata Classic` || game == `Retail`)) { 57 | rgbZone = {x: highlight.x - doubleZoneSize, y: highlight.y - doubleZoneSize, width: doubleZoneSize * 2, height: doubleZoneSize * 2}; 58 | if(rgbZone.x < zone.x) rgbZone.x = zone.x; 59 | if(rgbZone.y < zone.y) rgbZone.y = zone.y; 60 | if(rgbZone.x + rgbZone.width > zone.x + zone.width) rgbZone.x = zone.x + zone.width - rgbZone.width; 61 | if(rgbZone.y + rgbZone.height > zone.y + zone.height) rgbZone.y = zone.y + zone.height - rgbZone.height; 62 | } 63 | 64 | let rgb = createRgb(await getDataFrom(rgbZone)); 65 | rgb.saturate(...saturation) 66 | 67 | if(exception) { 68 | rgb.cutOut(exception); 69 | } 70 | 71 | let bobber; 72 | if(autoTh) { 73 | bobber = this._findMost(rgb); 74 | if(bobber && process.env.NODE_ENV == `dev`) { 75 | log.err(`[DEBUG] Color Found: ${bobber.color}, at: ${bobber.pos.x},${bobber.pos.y}`); 76 | } 77 | if(!bobber) return; 78 | } else { 79 | bobber = rgb.findColors({ 80 | isColor: isBobber, 81 | atFirstMet: true, 82 | saveColor: true, 83 | task: looksLikeBobber(1), 84 | direction 85 | }); 86 | } 87 | 88 | if(checkLogic == `pixelmatch`) { 89 | return bobber.pos.plus(rgbZone); 90 | } 91 | 92 | if(!bobber) return; // In case the bobber wasn't found in either _findMost or manually - recast. 93 | 94 | if(autoTh || direction == `center` || autoSens) { 95 | const doubleZoneDims = {x: rgbZone.x + bobber.pos.x - doubleZoneSize, 96 | y: rgbZone.y + bobber.pos.y - doubleZoneSize, 97 | width: doubleZoneSize * 2, 98 | height: doubleZoneSize * 2}; 99 | 100 | if(doubleZoneDims.x < rgbZone.x) doubleZoneDims.x = rgbZone.x; 101 | if(doubleZoneDims.y < rgbZone.y) doubleZoneDims.y = rgbZone.y; 102 | if(doubleZoneDims.x + doubleZoneDims.width > rgbZone.x + rgbZone.width) doubleZoneDims.x = rgbZone.x + rgbZone.width - doubleZoneDims.width; 103 | if(doubleZoneDims.y + doubleZoneDims.height > rgbZone.y + rgbZone.height) doubleZoneDims.y = rgbZone.y + rgbZone.height - doubleZoneDims.height; 104 | 105 | let doubleZoneData = await getDataFrom(doubleZoneDims); 106 | 107 | if(process.env.NODE_ENV == `dev`) { 108 | const img = await Jimp.read(doubleZoneData); 109 | const date = new Date() 110 | const name = `doubleZoneData-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}.png` 111 | img.write(`${__dirname}/../debug/${name}`); 112 | } 113 | 114 | let rgbAroundBobber = createRgb(doubleZoneData); 115 | rgbAroundBobber.saturate(...saturation); 116 | 117 | if(autoTh) { 118 | let colorPrev = null; 119 | const mostRedPoints = []; 120 | let errorStartTime = Date.now(); 121 | for(;Date.now() - errorStartTime < 2000;) { 122 | let mostRedPoint = this._findMost(rgbAroundBobber); 123 | 124 | if(!mostRedPoint) { 125 | return; 126 | } 127 | 128 | let [r, g, b] = mostRedPoint.color; 129 | let colorNow = r - Math.max(g, b) - (g + b); 130 | 131 | if(!colorPrev) { 132 | colorPrev = colorNow; 133 | } 134 | 135 | let colorDiffPercent = (colorNow / colorPrev) * 100; 136 | 137 | if(colorDiffPercent < 50) { 138 | break; 139 | } 140 | 141 | rgbAroundBobber.cutOut([mostRedPoint.pos]); 142 | mostRedPoints.push(mostRedPoint); 143 | } 144 | 145 | if(mostRedPoints.length == 0) { 146 | return; 147 | } 148 | 149 | filledBobber = mostRedPoints.map((point) => ({ 150 | color: point.color, 151 | pos: new Vec(point.pos.x + doubleZoneDims.x - rgbZone.x, point.pos.y + doubleZoneDims.y - rgbZone.y) 152 | })); 153 | 154 | let mostLeft = mostRedPoints.reduce((a, b) => a.pos.x < b.pos.x ? a : b); 155 | let mostRight = mostRedPoints.reduce((a, b) => a.pos.x > b.pos.x ? a : b); 156 | let middleValue = (mostLeft.pos.x + mostRight.pos.x) / (bobberColor == `red` ? 2 : 1.5); // position on the feather 157 | let mostTop = mostRedPoints.reduce((a, b) => a.pos.y < b.pos.y ? a : b); 158 | let mostTopMiddle = mostRedPoints.reduce((a, b) => { 159 | if( 160 | Math.abs(a.pos.x - middleValue) + (a.pos.y - mostTop.pos.y) 161 | < Math.abs(b.pos.x - middleValue) + (b.pos.y - mostTop.pos.y) 162 | ) { 163 | return a; 164 | } else { 165 | return b; 166 | } 167 | }); 168 | 169 | bobber.color = mostTopMiddle.color; 170 | bobber.pos = mostTopMiddle.pos.plus({x: doubleZoneDims.x, y: doubleZoneDims.y}); 171 | this._findThreshold(bobber); 172 | } else { 173 | /* if direction == center */ 174 | filledBobber = rgbAroundBobber.findColors({isColor: isBobber, saveColor: true}); 175 | bobber = rgbAroundBobber.findColors({ 176 | isColor: isBobber, 177 | atFirstMet: true, 178 | saveColor: true, 179 | direction: `normal`, 180 | task: game == `Vanilla` ? null : looksLikeBobber(1) 181 | }) 182 | 183 | if(!bobber) { 184 | return; 185 | } 186 | 187 | bobber.pos = bobber.pos.plus({x: doubleZoneDims.x, y: doubleZoneDims.y}); 188 | } 189 | 190 | if(autoSens) { 191 | await this.adjustSensitivity(filledBobber.length); 192 | } 193 | 194 | return bobber.pos; 195 | } 196 | 197 | return bobber.pos.plus(rgbZone); 198 | }, 199 | 200 | _findMost(rgb) { 201 | let initialThColors = rgb.findColors({ 202 | isColor: bobberColor == `red` ? isRed(0) : isBlue(0), 203 | saveColor: true 204 | }); 205 | 206 | if(!initialThColors) return; 207 | 208 | let bobber = initialThColors.reduce((a, b) => { 209 | let [rA, gA, bA] = a.color; 210 | let [rB, gB, bB] = b.color; 211 | 212 | let maxARed = gA + bA; 213 | let maxBRed = gB + bB; 214 | 215 | let maxABlue = rA + gA; 216 | let maxBBlue = rB + gB; 217 | 218 | let colorA = bobberColor == `red` ? (rA - Math.max(gA, bA)) - maxARed : (bA - Math.max(gA, rA)) - maxABlue; 219 | let colorB = bobberColor == `red` ? (rB - Math.max(gB, bB)) - maxBRed : (bB - Math.max(gB, rB)) - maxBBlue; 220 | 221 | if(colorA > colorB) { 222 | return a; 223 | } else { 224 | return b; 225 | } 226 | }); 227 | 228 | return bobber; 229 | }, 230 | 231 | _findThreshold(bobber, thCoof = .75) { // .5 232 | let newThreshold = (([r, g, b]) => bobberColor == `red` ? (r - (Math.max(g, b))) * thCoof : (b - Math.max(g, r)) * thCoof)(bobber.color); // for doubleZoneSearching searching half of the color foundo on threshold 233 | isBobber = bobberColor == `red` ? isRed(newThreshold, 50) : isBlue(newThreshold, 50); // 50? 234 | }, 235 | 236 | async adjustSensitivity(bobberSize) { 237 | if(game == `Retail`) { 238 | let calculatedSens = Math.round(Math.sqrt(bobberSize / (bobberColor == `red` ? 3 : 2.5))); // 4 2.5 239 | if(calculatedSens < 3) calculatedSens = 3; 240 | sensitivity = calculatedSens; 241 | } else { 242 | if(game == `Vanilla`) { 243 | sensitivity = 2; 244 | } else { 245 | sensitivity = Math.max(Math.round((screenSize.height / 1080) * (bobberColor == `red` ? 3 : 2)), 2); 246 | } 247 | } 248 | }, 249 | 250 | async checkBobberPrint(pos) { 251 | let rgb = createRgb(await getDataFrom({x: pos.x - sensitivity, y: pos.y - sensitivity, width: sensitivity * 2, height: sensitivity * 2})); 252 | rgb.saturate(...saturation); 253 | let bobber = rgb.findColors({ 254 | isColor: isBobber, 255 | atFirstMet: true 256 | }); 257 | if(bobber) { 258 | return true; 259 | } 260 | }, 261 | 262 | async checkBobberPrintSplash(pos) { 263 | let rgb = createRgb(await getDataFrom({x: pos.x - sensitivity, y: pos.y - sensitivity, width: sensitivity * 2, height: sensitivity * 2})); 264 | rgb.saturate(...saturation); 265 | let whiteColors = rgb.findColors({ 266 | isColor: ([r, g, b]) => r > splashColor && g > splashColor && b > splashColor, 267 | }); 268 | if((whiteColors && whiteColors.length > 10) || !(await this.checkBobberPrint(pos))) { 269 | return true; 270 | } 271 | }, 272 | 273 | async checkPixelMatch(bobber, startTime) { 274 | let imgAroundBobber = await getDataFrom({x: bobber.x - doubleZoneSize, 275 | y: bobber.y - doubleZoneSize, 276 | width: doubleZoneSize * 2, 277 | height: doubleZoneSize * 2}); 278 | 279 | if(imgAroundBobberPrev) { 280 | let pixelMatchTh = pixelmatch(imgAroundBobberPrev.data, imgAroundBobber.data, null, imgAroundBobber.width, imgAroundBobber.height, {threshold: 0.1}); // 0.1 for classic 281 | if(Date.now() - startTime < 1250) { 282 | if(pixelMatchTh > pixelMatchMax) { 283 | pixelMatchMax = pixelMatchTh; 284 | } 285 | 286 | } else { 287 | if(pixelMatchTh > pixelMatchMax + (pixelMatchMax * sensitivity)) { 288 | return true; 289 | } 290 | } 291 | } else { 292 | imgAroundBobberPrev = imgAroundBobber; 293 | } 294 | }, 295 | 296 | async checkAroundBobber(bobberPos) { 297 | for(let pos of bobberPos.getPointsAround()) { 298 | if(await this.isBobber(pos)) { 299 | return pos; 300 | } 301 | } 302 | }, 303 | 304 | async checkBelow(pos) { 305 | for(let y = 1; y < sensitivity; y++) { 306 | let pointsBelow = [pos.plus({x: -1, y}), pos.plus({x: 0, y}), pos.plus({x: 1, y})]; 307 | for(let point of pointsBelow) { 308 | if(await this.isBobber(point)) { 309 | checkAboveCompensateValue++; 310 | return pointsBelow[1]; 311 | } 312 | } 313 | } 314 | }, 315 | 316 | async checkAbove(pos) { 317 | let previous = pos; 318 | for(let i = 0; i < checkAboveCompensateValue; i++) { 319 | let posAbove = previous.plus({x: 0, y: -1}); 320 | if(!(await this.isBobber(posAbove))) { 321 | return previous; 322 | } else { 323 | checkAboveCompensateValue--; 324 | previous = posAbove; 325 | } 326 | } 327 | return previous; 328 | }, 329 | 330 | async isBobber(pos) { 331 | if(!isInLimits({ x: pos.x - zone.x, y: pos.y - zone.y }, zone)) { 332 | return; 333 | } 334 | let pointRgb = createRgb(await getDataFrom({x: pos.x, y: pos.y, width: 1, height: 1})); 335 | pointRgb.saturate(...saturation) 336 | if(isBobber(pointRgb.colorAt({ x: 0, y: 0 }))) { 337 | return true; 338 | } 339 | }, 340 | 341 | async checkColor() { 342 | let rgb = createRgb(await getDataFrom(zone)); 343 | let colors = rgb.findColors({ 344 | isColor: bobberColor == `red` ? isRed(0) : isBlue(0), 345 | }); 346 | 347 | return colors ? (colors.length / (zone.width * zone.height)) * 100 : 0; 348 | }, 349 | 350 | async changeColor() { 351 | bobberColor = bobberColor == `red` ? `blue` : `red`; 352 | isBobber = bobberColor == `red` ? isRed(threshold, 50) : isBlue(threshold, 50); // 50? 353 | saturation = bobberColor == `red` ? [80, 0, 0] : [0, 0, 80]; 354 | }, 355 | 356 | async getBobberPrint(wobble) { 357 | let rest = []; 358 | if(autoTh) { 359 | if(!filledBobber) { 360 | return; 361 | } 362 | rest = filledBobber.map(({pos}) => pos); 363 | } else { 364 | let rgb = createRgb(await getDataFrom(zone)); 365 | rgb.saturate(...saturation); 366 | rest = rgb.findColors({ isColor: isBobber, limit: 5000}); 367 | } 368 | 369 | if(!rest) return; 370 | 371 | let result = [...rest]; 372 | rest.forEach(restPoint => { 373 | restPoint.getPointsAround(wobble).forEach(aroundPoint => { 374 | if(!result.some(resultPoint => resultPoint.x == aroundPoint.x && resultPoint.y == aroundPoint.y)) { 375 | result.push(aroundPoint); 376 | } 377 | }) 378 | }); 379 | 380 | return result; 381 | } 382 | } 383 | }; 384 | 385 | module.exports = createFishingZone; 386 | -------------------------------------------------------------------------------- /app/bot/lootExitZone.js: -------------------------------------------------------------------------------- 1 | const createRgb = require('../utils/rgb.js'); 2 | const Jimp = require('jimp'); 3 | 4 | const createLootExitZone = ({ getDataFrom, lootWindow, size }) => { 5 | const isYellow = ([r, g, b]) => r - b > 135 && g - b > 135; 6 | return { 7 | async isLootOpened(cursorPos) { 8 | const zone = {x: cursorPos.x + lootWindow.exitButton.x - size, 9 | y: cursorPos.y - lootWindow.exitButton.y - size, 10 | width: size * 2, 11 | height: size * 2} 12 | 13 | let rgb = createRgb(await getDataFrom(zone)); 14 | 15 | if(process.env.NODE_ENV == `dev`) { 16 | const img = await Jimp.read(rgb.getBitmap()); 17 | const date = new Date() 18 | const name = `test-lootExitZone-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}.png` 19 | img.write(`${__dirname}/../debug/${name}`); 20 | } 21 | 22 | let result = rgb.findColors({ 23 | isColor: isYellow, 24 | atFirstMet: true 25 | }); 26 | 27 | return result; 28 | } 29 | } 30 | }; 31 | 32 | module.exports = createLootExitZone; 33 | -------------------------------------------------------------------------------- /app/bot/lootZone.js: -------------------------------------------------------------------------------- 1 | const createRgb = require('../utils/rgb.js'); 2 | const Jimp = require('jimp'); 3 | 4 | const createLootZone = ({ getDataFrom, zone }) => { 5 | let colors = { 6 | blue: ([r, g, b]) => b - r > 80 && b - g > 80, 7 | green: ([r, g, b]) => g - r > 80 && g - b > 80, 8 | purple: ([r, g, b]) => r - g > 80 && b - g > 80 9 | }; 10 | 11 | return { 12 | async findItems(...types) { 13 | let rgb = createRgb(await getDataFrom(zone)); 14 | 15 | if(process.env.NODE_ENV == `dev`) { 16 | const img = await Jimp.read(rgb.getBitmap()); 17 | const date = new Date() 18 | const name = `test-lootZone-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}.png` 19 | img.write(`${__dirname}/../debug/${name}`); 20 | } 21 | 22 | return types.some(type => rgb.findColors({ 23 | isColor: colors[type], 24 | atFirstMet: true 25 | })); 26 | } 27 | } 28 | }; 29 | 30 | module.exports = createLootZone; 31 | -------------------------------------------------------------------------------- /app/bot/notificationZone.js: -------------------------------------------------------------------------------- 1 | const createRgb = require("../utils/rgb.js"); 2 | const Jimp = require('jimp'); 3 | 4 | const createNotificationZone = ({ getDataFrom, zone }) => { 5 | const notifications = { 6 | isWarning: ([r, g, b]) => r - b > 200 && g - b > 200, 7 | isError: ([r, g, b]) => r - g > 220 && r - b > 220 8 | } 9 | 10 | return { 11 | async check(...type) { 12 | const colors = type.map((type) => { 13 | switch (type) { 14 | case "warning": { 15 | return notifications.isWarning; 16 | } 17 | case "error": { 18 | return notifications.isError; 19 | } 20 | } 21 | }); 22 | 23 | for(const color of colors) { 24 | let data = await getDataFrom(zone); 25 | let rgb = createRgb(data); 26 | 27 | if(process.env.NODE_ENV == `dev`) { 28 | const img = await Jimp.read(rgb.getBitmap()); 29 | const date = new Date() 30 | const name = `test-notificationZone-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}.png` 31 | img.write(`${__dirname}/../debug/${name}`); 32 | } 33 | 34 | let foundColor = rgb.findColors({ 35 | isColor: color, 36 | atFirstMet: true 37 | }); 38 | if(foundColor) { 39 | return true; 40 | } 41 | } 42 | }, 43 | }; 44 | }; 45 | 46 | module.exports = createNotificationZone; 47 | -------------------------------------------------------------------------------- /app/bot/redButtonZone.js: -------------------------------------------------------------------------------- 1 | const createRgb = require("../utils/rgb.js"); 2 | const Jimp = require('jimp'); 3 | 4 | const mouseWithinZone = (mouse, zone) => { 5 | return ( 6 | mouse.x - zone.x > 0 && 7 | mouse.y - zone.y > 0 && 8 | mouse.x < zone.x + zone.width && 9 | mouse.y < zone.y + zone.height 10 | ); 11 | }; 12 | 13 | 14 | const createRedButtonZone = ({getDataFrom, zone}) => { 15 | const isRed = ([r, g, b]) => r - g > 125 && r - b > 125; 16 | const isYellow = ([r, g, b]) => r - b > 175 && g - b > 175; 17 | const isWhite = ([r, g, b]) => r > 220 && g > 220 && b > 220; 18 | return { 19 | async isOn(mousePos) { 20 | const rgb = createRgb(await getDataFrom(zone)); 21 | 22 | if(process.env.NODE_ENV == `dev`) { 23 | const img = await Jimp.read(rgb.getBitmap()); 24 | const date = new Date() 25 | const name = `test-redButtonZone-before-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}.png` 26 | img.write(`${__dirname}/../debug/${name}`); 27 | } 28 | 29 | let red = rgb.findColors({isColor: isRed, atFirstMet: true}); 30 | let yellow = mouseWithinZone(mousePos, zone) || rgb.findColors({isColor: isYellow, atFirstMet: true}); 31 | if(red && yellow) { 32 | return {red, yellow}; 33 | } 34 | }, 35 | 36 | async isOnAfterHighlight() { 37 | const rgb = createRgb(await getDataFrom(zone)); 38 | 39 | if(process.env.NODE_ENV == `dev`) { 40 | const img = await Jimp.read(rgb.getBitmap()); 41 | const date = new Date() 42 | const name = `test-redButtonZone-after-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}.png` 43 | img.write(`${__dirname}/../debug/${name}`); 44 | } 45 | 46 | return rgb.findColors({isColor: isWhite, atFirstMet: true}) 47 | } 48 | } 49 | }; 50 | 51 | module.exports = createRedButtonZone; 52 | -------------------------------------------------------------------------------- /app/bot/runBot.js: -------------------------------------------------------------------------------- 1 | const runBot = async ({ bot, log, state, stats }, onError, wins) => { 2 | const { 3 | dynamicThreshold, 4 | logOut, 5 | preliminaryChecks, 6 | findAllBobberColors, 7 | randomSleep, 8 | applyLures, 9 | castFishing, 10 | findBobber, 11 | highlightBobber, 12 | checkBobber, 13 | hookBobber, 14 | doAfterTimer, 15 | checkConfirm 16 | } = bot; 17 | 18 | const sleep = (time) => { 19 | return new Promise((resolve) => { 20 | setTimeout(resolve, time); 21 | }); 22 | }; 23 | 24 | const random = (from, to) => { 25 | return from + Math.random() * (to - from); 26 | }; 27 | 28 | let failedCast = false; 29 | let attempts = 0; 30 | do { 31 | if (state.status == "initial") { 32 | log.send(`Preliminary checks...`); 33 | await preliminaryChecks(log); 34 | log.ok(`Everything is fine!`); 35 | if(randomSleep.on) { 36 | randomSleep.timer.start(); 37 | } 38 | 39 | if(logOut.on) { 40 | logOut.timer.start(); 41 | } 42 | 43 | if(doAfterTimer.on) { 44 | doAfterTimer.timer.start(); 45 | } 46 | } 47 | 48 | if(doAfterTimer.on && state.status == "working" && doAfterTimer.timer.isElapsed()) { 49 | await doAfterTimer(onError, wins); 50 | } 51 | 52 | if(logOut.on && logOut.timer.isElapsed()) { 53 | log.send(`Logging out...`) 54 | await logOut(state); 55 | if(state.status == 'stop') { 56 | return; 57 | } 58 | log.send(`Logged back!`); 59 | logOut.timer.update(); 60 | } 61 | 62 | if (randomSleep.on && randomSleep.timer.isElapsed()) { 63 | log.send(`Sleeping...`); 64 | await randomSleep(); 65 | if(state.status == 'stop') { 66 | return; 67 | } 68 | randomSleep.timer.update(); 69 | } 70 | 71 | if (applyLures.on && applyLures.timer.isElapsed()) { 72 | log.send(`Applying lures...`); 73 | await applyLures(); 74 | applyLures.timer.update(); 75 | } 76 | 77 | findBobber.memory = await findAllBobberColors(); 78 | 79 | 80 | if(failedCast) { 81 | let randomFailed = random(500, 5000); 82 | log.warn(`Sleeping for ${Math.floor(randomFailed)} ms.`) 83 | await sleep(randomFailed); 84 | } 85 | log.send(`Casting fishing...`); 86 | await castFishing(state); 87 | 88 | log.send(`Looking for the bobber...`); 89 | let bobber = await findBobber(log); 90 | 91 | if(bobber) { 92 | bobber = await highlightBobber(bobber, log); 93 | } 94 | 95 | if (bobber) { 96 | failedCast = false; 97 | log.ok(`Found the bobber!`); 98 | attempts = 0; 99 | } else { 100 | failedCast = true; 101 | stats.confused++; 102 | log.err(`Can't find the bobber, recast.`); 103 | if (++attempts == findBobber.maxAttempts) { 104 | if(dynamicThreshold.on && !dynamicThreshold.limit()) { 105 | dynamicThreshold(); 106 | attempts = 0; 107 | } else { 108 | throw new Error( 109 | `Have tried ${findBobber.maxAttempts} attempts to find the bobber and failed: decrease the red color "threshold" value or change the fishing place.` 110 | ); 111 | } 112 | } 113 | continue; 114 | } 115 | 116 | log.send(`Checking the hook...`); 117 | if ((bobber = await checkBobber(bobber, state))) { 118 | let isHooked = await hookBobber(bobber); 119 | 120 | if(bobber.missedIntentionally) { 121 | stats.misspurpose++; 122 | log.warn(`Missed the fish on purpose!`); 123 | continue; 124 | } 125 | 126 | if (isHooked) { 127 | stats.caught++; 128 | log.ok(`Caught ${typeof isHooked == `boolean` ? `the fish!` : isHooked}`); 129 | 130 | await checkConfirm(); 131 | 132 | continue; 133 | } 134 | } 135 | 136 | if (state.status == "working") { 137 | stats.miss++; 138 | log.warn(`Missed the fish!`); 139 | } 140 | } while (state.status == "working"); 141 | }; 142 | 143 | module.exports = runBot; 144 | -------------------------------------------------------------------------------- /app/bot/stats.js: -------------------------------------------------------------------------------- 1 | const getPercent = (value, total) => { 2 | return Math.ceil((value / (total || 1)) * 100 * 100) / 100; 3 | }; 4 | 5 | class Stats { 6 | constructor() { 7 | this.caught = 0; 8 | this.miss = 0; 9 | this.confused = 0; 10 | this.misspurpose = 0; 11 | } 12 | 13 | get total() { 14 | return this.caught + this.miss + this.misspurpose; 15 | } 16 | 17 | show() { 18 | return [ 19 | `Total: ${this.total}`, 20 | `Caught: ${this.caught} (${getPercent(this.caught, this.total)}%)`, 21 | `Missed: ${this.miss} (${getPercent(this.miss, this.total)}%)`, 22 | `Missed on purpose: ${this.misspurpose} (${getPercent(this.misspurpose, this.total)}%)`, 23 | `Couldn't recognize: ${this.confused} (${getPercent(this.confused, this.total)}%)` 24 | ]; 25 | } 26 | } 27 | 28 | module.exports = Stats; 29 | -------------------------------------------------------------------------------- /app/config/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "game": "Retail", 3 | "fishingKey": "1", 4 | "intKey": "f", 5 | "useInt": false, 6 | "stopKey": "delete", 7 | "threshold": 100, 8 | "bobberSensitivity": { 9 | "Retail": 25, 10 | "Cata Classic": 7, 11 | "Classic": 7, 12 | "Leg": 7, 13 | "MoP": 7, 14 | "Cata": 7, 15 | "LK Private": 7, 16 | "TBC": 7, 17 | "Vanilla": 7, 18 | "Vanilla (splash)": 10 19 | }, 20 | "checkLogic": "default", 21 | "autoSens": true, 22 | "autoTh": true, 23 | "autoColor": false, 24 | "bobberColor": "red", 25 | "initial": true, 26 | "initialZone": true 27 | } 28 | -------------------------------------------------------------------------------- /app/game/createGame.js: -------------------------------------------------------------------------------- 1 | const { Hardware, getAllWindows } = require("keysender"); 2 | 3 | const findGameWindows = ({ names, classNames }) => { 4 | 5 | const wins = getAllWindows().filter( 6 | ({ title, className }) => 7 | names.some(name => new RegExp(`${name}`).test(title)) && classNames.includes(className) 8 | ); 9 | 10 | if (wins.length > 0) { 11 | return wins.map((win) => new Hardware(win.handle)); 12 | } 13 | }; 14 | 15 | module.exports = { 16 | findGameWindows, 17 | getAllWindows 18 | }; 19 | -------------------------------------------------------------------------------- /app/game/nut.js: -------------------------------------------------------------------------------- 1 | let { mouse, Point } = require("@nut-tree/nut-js"); 2 | 3 | function getRandomControlPoint(start, end, range) { 4 | const deltaX = end.x - start.x; 5 | const deltaY = end.y - start.y; 6 | 7 | const controlPoint1 = { 8 | x: start.x + range * deltaX + Math.random() * range * deltaX, 9 | y: start.y + range * deltaY + Math.random() * range * deltaY, 10 | }; 11 | 12 | const controlPoint2 = { 13 | x: start.x + (1 - range) * deltaX + Math.random() * range * deltaX, 14 | y: start.y + (1 - range) * deltaY + Math.random() * range * deltaY, 15 | }; 16 | 17 | const controlPoint3 = { 18 | x: start.x + Math.random() * deltaX, 19 | y: start.y + Math.random() * deltaY, 20 | }; 21 | 22 | return [controlPoint1, controlPoint2, controlPoint3]; 23 | } 24 | 25 | function generateBezierPath(startPoint, endPoint, steps, range) { 26 | const controlPoints = getRandomControlPoint(startPoint, endPoint, range); 27 | const path = []; 28 | 29 | for (let t = 0; t <= 1; t += 1 / steps) { 30 | const x = Math.pow(1 - t, 3) * startPoint.x + 31 | 3 * Math.pow(1 - t, 2) * t * controlPoints[0].x + 32 | 3 * (1 - t) * Math.pow(t, 2) * controlPoints[1].x + 33 | Math.pow(t, 3) * endPoint.x; 34 | 35 | const y = Math.pow(1 - t, 3) * startPoint.y + 36 | 3 * Math.pow(1 - t, 2) * t * controlPoints[0].y + 37 | 3 * (1 - t) * Math.pow(t, 2) * controlPoints[1].y + 38 | Math.pow(t, 3) * endPoint.y; 39 | 40 | path.push({ x, y }); 41 | } 42 | 43 | return path; 44 | } 45 | 46 | 47 | const random = (from, to) => { 48 | return from + Math.random() * (to - from); 49 | }; 50 | 51 | const sleep = (time) => { 52 | return new Promise((resolve) => { 53 | setTimeout(resolve, time); 54 | }); 55 | }; 56 | 57 | function randomHumanLikeCursorEasing(x) { 58 | const oscillation = Math.sin(x * Math.PI) + (Math.random() - 0.5) * 0.1; 59 | const increasedEasingOut = 1 - Math.pow(1 - x, 4) + (Math.random() - 0.5) * 0.1; 60 | const result = 0.7 * oscillation + 0.3 * increasedEasingOut; 61 | return Math.max(0, Math.min(1, result)); 62 | } 63 | 64 | module.exports = { 65 | mouse: { 66 | async humanMoveTo({from, to, speed, deviation, fishingZone}) { 67 | const distance = Math.sqrt(Math.pow(Math.round(from.x - to.x), 2) + Math.pow(Math.round(from.y - to.y), 2)); 68 | const fZoneSize = Math.sqrt(Math.pow(fishingZone.width, 2) + Math.pow(fishingZone.height, 2)) * .25; 69 | /* apply distance relation to zone size only if distance is more than 5% */ 70 | mouse.config.mouseSpeed = (speed * 100 * 20) * (distance > fZoneSize * .05 ? distance / fZoneSize : 0.25); 71 | const bezierPath = generateBezierPath(from, to, distance, deviation / 150); 72 | await mouse.move(bezierPath, randomHumanLikeCursorEasing); 73 | }, 74 | async toggle(button, type, delay) { 75 | let buttonNumber = button == `left` ? 1 : button == `right` ? 2 : 3; 76 | if(type == true) { 77 | await mouse.pressButton(buttonNumber); 78 | } else { 79 | await mouse.releaseButton(buttonNumber); 80 | } 81 | 82 | if(Array.isArray(delay)) { 83 | await sleep(random(delay[0], delay[1])) 84 | } else { 85 | await sleep(delay); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/game/winSwitch.js: -------------------------------------------------------------------------------- 1 | const { createTimer } = require('../utils/time.js'); 2 | 3 | const windowStuck = createTimer(() => 3000); 4 | 5 | const sleep = (time) => { 6 | return new Promise((resolve) => { 7 | setTimeout(resolve, time); 8 | }); 9 | }; 10 | 11 | const createWinSwitch = (eventLine) => { 12 | return { 13 | execute(workwindow) { 14 | return new Promise((resolve, reject) => { 15 | eventLine.add(async () => { 16 | workwindow.setForeground(); 17 | windowStuck.start(); 18 | while (!workwindow.isForeground()) { 19 | if(windowStuck.isElapsed()) { 20 | this.finished(); 21 | reject(new Error(`Can't set the window to foreground`)); 22 | } 23 | await sleep(); 24 | } 25 | resolve(); 26 | }); 27 | }); 28 | }, 29 | finished() { 30 | eventLine.remove(); 31 | }, 32 | }; 33 | }; 34 | 35 | module.exports = createWinSwitch; 36 | -------------------------------------------------------------------------------- /app/img/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/add.png -------------------------------------------------------------------------------- /app/img/fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/fish.png -------------------------------------------------------------------------------- /app/img/fold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/fold.png -------------------------------------------------------------------------------- /app/img/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/grass.png -------------------------------------------------------------------------------- /app/img/grate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/grate.png -------------------------------------------------------------------------------- /app/img/hint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/hint.png -------------------------------------------------------------------------------- /app/img/icon-premium.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/icon-premium.ico -------------------------------------------------------------------------------- /app/img/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/icon.ico -------------------------------------------------------------------------------- /app/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/icon.png -------------------------------------------------------------------------------- /app/img/install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/install.gif -------------------------------------------------------------------------------- /app/img/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/lock.png -------------------------------------------------------------------------------- /app/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/logo.png -------------------------------------------------------------------------------- /app/img/premium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/premium.png -------------------------------------------------------------------------------- /app/img/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/remove.png -------------------------------------------------------------------------------- /app/img/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/start.png -------------------------------------------------------------------------------- /app/img/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/stop.png -------------------------------------------------------------------------------- /app/img/unfold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/unfold.png -------------------------------------------------------------------------------- /app/img/youtube_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/img/youtube_icon.png -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | /* Electron modules*/ 2 | const { 3 | app, 4 | BrowserWindow, 5 | ipcMain, 6 | Menu, 7 | dialog, 8 | shell, 9 | powerSaveBlocker, 10 | globalShortcut, 11 | screen, 12 | crashReporter 13 | } = require("electron"); 14 | const path = require("path"); 15 | 16 | const { readFileSync, writeFileSync, writeFile } = require("fs"); 17 | const createAdvSettings = require(`./wins/advsettings/main.js`); 18 | const createFishingZone = require(`./wins/fishingzone/main.js`); 19 | 20 | const getJson = (jsonPath) => { 21 | return JSON.parse(readFileSync(path.join(__dirname, jsonPath), "utf8")); 22 | }; 23 | /* Electron modules end */ 24 | 25 | /* Bot modules */ 26 | const generateName = require('./utils/generateName.js'); 27 | const { createLog } = require("./utils/logger.js"); 28 | const { findGameWindows, getAllWindows } = require("./game/createGame.js"); 29 | const createBots = require("./bot/createBots.js"); 30 | const getBitmapAsync = require("./utils/getBitmap.js"); 31 | /* Bot modules end */ 32 | 33 | const fishQuotes = getJson(`./utils/fishingQuotes.json`); 34 | 35 | /* Squirrel */ 36 | const handleSquirrelEvent = require(`./utils/handleSquirrel.js`); 37 | if (require("electron-squirrel-startup")) return app.quit(); 38 | if (handleSquirrelEvent(app)) { 39 | // squirrel event handled and app will exit in 1000ms, so don't do anything else 40 | return; 41 | } 42 | /* Squirrel end */ 43 | 44 | app.setPath('sessionData', path.resolve(app.getAppPath(), `cache`)); // Set cache folder in the app folder 45 | 46 | 47 | const showChoiceWarning = (win, warning, title, ...buttons) => { 48 | return result = dialog.showMessageBoxSync(win, { 49 | type: "warning", 50 | title: `${title}`, 51 | message: warning, 52 | buttons: buttons, 53 | defaultId: 0, 54 | cancelId: 1, 55 | }); 56 | }; 57 | 58 | 59 | const showWarning = (win, warning) => { 60 | return dialog.showMessageBoxSync(win, { 61 | type: "warning", 62 | title: `Warning`, 63 | message: warning, 64 | buttons: [`Ok`] 65 | }); 66 | }; 67 | 68 | const random = (from, to) => { 69 | return from + Math.random() * (to - from); 70 | }; 71 | 72 | const setFishingZone = async ({workwindow}, relZone, type, config, settings) => { 73 | workwindow.setForeground(); 74 | while(!workwindow.isForeground()) { 75 | workwindow.setForeground(); 76 | } 77 | const screenSize = workwindow.getView(); 78 | const scale = screen.getPrimaryDisplay().scaleFactor || 1; 79 | 80 | const pos = { 81 | x: (screenSize.x + relZone.x * screenSize.width) / scale, 82 | y: (screenSize.y + relZone.y * screenSize.height) / scale, 83 | width: (relZone.width * screenSize.width) / scale, 84 | height: (relZone.height * screenSize.height) / scale 85 | } 86 | 87 | const result = await createFishingZone({pos, screenSize, type, config, settings, scale}); 88 | if(!result) return; 89 | 90 | const convertedResult = { 91 | x: (result.x * scale - screenSize.x) / screenSize.width, 92 | y: (result.y * scale - screenSize.y) / screenSize.height, 93 | width: (result.width * scale) / screenSize.width, 94 | height: (result.height * scale) / screenSize.height 95 | }; 96 | 97 | return convertedResult 98 | } 99 | 100 | let win; 101 | const createWindow = async () => { 102 | win = new BrowserWindow({ 103 | title: generateName(Math.floor(random(5, 15))), 104 | width: 340, 105 | height: 678, 106 | show: false, 107 | resizable: true, 108 | webPreferences: { 109 | spellcheck: false, 110 | contextIsolation: false, 111 | nodeIntegration: true, 112 | }, 113 | icon: "./app/img/icon.png", 114 | }); 115 | 116 | win.loadFile("./app/index.html"); 117 | 118 | win.on("closed", () => { 119 | if (process.platform === "darwin") { 120 | return false; 121 | } 122 | powerSaveBlocker.stop(powerBlocker); 123 | app.quit(); 124 | }); 125 | 126 | 127 | let log; 128 | win.once("ready-to-show", async () => { 129 | //win.openDevTools({mode: `detach`}); 130 | win.show(); 131 | await new Promise(function(resolve, reject) { 132 | setTimeout(resolve, 350); 133 | }); 134 | const settings = getJson("./config/settings.json"); 135 | if(settings.initial) { 136 | 137 | if(showChoiceWarning(win, `This project was developed for educational purposes, aiming to explore the feasibility of creating a functional gaming bot using web-development technologies only. The software provided should never be used with real-life applications, games and servers outside private "sandbox". 138 | 139 | You assume full responsibility for any outcomes that may arise from using this software. It's essential to acknowledge that this software is not designed to be "undetectable" in any way, nor was it ever intended for such purposes as stated above. As a result, no guarantees or assurances can be made regarding the functionality or outcomes of the bot. 140 | 141 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 142 | 143 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 144 | 145 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 146 | 147 | By pressing "Accept" you agree to everything stated above.`, 148 | `MIT License | Copyright (c) 2023 jsbots`, `Accept`, `Decline`)) { 149 | app.quit(); 150 | } else { 151 | settings.initial = false; 152 | } 153 | 154 | let games = [`Retail`, `Cata Classic`, `Classic`, "Leg", "MoP", "Cata", "LK Private", "TBC", "Vanilla"]; 155 | let initialGameChoice = showChoiceWarning(win, `The shortcut to AutoFish was created on you desktop!\n\nChoose your game:`, `Initial configuration`, 156 | ...games 157 | ); 158 | win.webContents.send('set-game', games[initialGameChoice]) 159 | settings.game = games[initialGameChoice]; 160 | 161 | writeFile(path.join(__dirname, `./config/settings.json`), JSON.stringify(settings), () => {}) 162 | } 163 | }); 164 | 165 | ipcMain.on(`onload`, () => { 166 | let { version } = getJson('../package.json'); 167 | win.webContents.send('set-version', version); 168 | log = createLog((data) => { 169 | win.webContents.send("log-data", data); 170 | }); 171 | log.msg(fishQuotes[Math.floor(Math.random() * fishQuotes.length)]); 172 | 173 | const config = getJson("./config/bot.json"); 174 | const settings = getJson("./config/settings.json"); 175 | 176 | if(config.patch[settings.game].startByFishingKey) { 177 | globalShortcut.register(settings.fishingKey, () => { 178 | win.webContents.send('start-by-fishing-key'); 179 | }); 180 | } 181 | 182 | if(screen.getAllDisplays().length > 1) { 183 | log.warn("The bot detected more than 1 display: use both the game and the bot on the primary one.") 184 | } 185 | }) 186 | 187 | 188 | ipcMain.on("start-bot", async (event, type) => { 189 | const config = getJson("./config/bot.json"); 190 | const settings = getJson("./config/settings.json"); 191 | 192 | 193 | log.send(`Looking for the windows of the game...`); 194 | 195 | const useCustomWindow = config.patch[settings.game].useCustomWindow; 196 | if(useCustomWindow) { 197 | const customWindow = config.patch[settings.game].customWindow; 198 | const name = getAllWindows().find(({title}) => title == customWindow); 199 | if(!name) { 200 | log.err(`Can't access this window`); 201 | win.webContents.send("stop-bot"); 202 | return; 203 | } 204 | const {title, className} = name; 205 | config.game.names.push(title); 206 | config.game.classNames.push(className); 207 | } 208 | 209 | const games = findGameWindows(config.game); 210 | 211 | if (!games) { 212 | log.err(`Can't find any window of the game! Go to the Advanced Settings and choose the window of the game manually.`); 213 | win.webContents.send("stop-bot"); 214 | return; 215 | } else { 216 | log.ok(`Found ${games.length} window${games.length > 1 ? `s` : ``} of the game!`); 217 | } 218 | /* 219 | if(type != `relZone` && settings.initialZone){ 220 | await new Promise(function(resolve, reject) { 221 | setTimeout(resolve, 50); 222 | }); 223 | if(!(showChoiceWarning(win, `This is your first launch. Do you want to set your Fishing Zone first? (recommended)`, `Fishing Zone`, `Yes`, `No`))) { 224 | type = `relZone`; 225 | win.webContents.send("stop-bot"); 226 | } 227 | } 228 | */ 229 | 230 | if(settings.initialZone) { 231 | settings.initialZone = false; 232 | writeFileSync(path.join(__dirname, "./config/settings.json"), JSON.stringify(settings)); 233 | } 234 | 235 | if(type == `relZone` || type == `chatZone`) { 236 | log.send(`Setting ${type == `relZone` ? `Fishing` : `Chat`} Zone...`); 237 | let data = await setFishingZone(games[0], config.patch[settings.game][type], type, config.patch[settings.game], settings); 238 | if(data) { 239 | config.patch[settings.game][type] = data; 240 | writeFileSync(path.join(__dirname, "./config/bot.json"), JSON.stringify(config)); 241 | log.ok(`Set ${type == `relZone` ? `Fishing` : `Chat`} Zone Succesfully!`); 242 | } else { 243 | log.send(`Canceled.`) 244 | } 245 | 246 | win.focus(); 247 | return; 248 | } 249 | 250 | if((settings.game == `Retail` || settings.game == `Classic` || settings.game == `Cata Classic`)) { 251 | await new Promise(function(resolve, reject) { 252 | setTimeout(resolve, 50); 253 | }); 254 | if((showChoiceWarning(win, `You are about to start the bot in a default mode on official servers.\n\nIt's not just a disclaimer, your account WILL be banned sooner or later.\n\nYou can use Streaming Mode for a much safer approach.\n\nAre you sure you want to continue?`, `Warning`, `Yes `, `No (recommended)`))) { 255 | win.webContents.send("stop-bot"); 256 | return; 257 | } 258 | } 259 | 260 | if(config.patch[settings.game].startByFishingKey) { 261 | globalShortcut.unregister(settings.fishingKey); 262 | } 263 | 264 | const {startBots, stopBots} = await createBots(games, settings, config, log); 265 | 266 | const stopAppAndBots = () => { 267 | games.forEach(({mouse, keyboard}) => { 268 | mouse.humanMoveTo.cancelCurrent(); 269 | keyboard.sendKeys.cancelCurrent(); 270 | keyboard.printText.cancelCurrent(); 271 | }); 272 | 273 | stopBots(); 274 | 275 | if(config.patch[settings.game].hideWin) { 276 | win.show(); 277 | } 278 | 279 | shell.beep(); 280 | 281 | if (!win.isFocused()) { 282 | win.flashFrame(true); 283 | win.once("focus", () => win.flashFrame(false)); 284 | } 285 | 286 | globalShortcut.unregister(settings.stopKey); 287 | 288 | if(config.patch[settings.game].startByFishingKey) { 289 | globalShortcut.register(settings.fishingKey, () => { 290 | win.webContents.send('start-by-fishing-key'); 291 | }); 292 | } 293 | 294 | win.webContents.send("stop-bot"); 295 | }; 296 | 297 | ipcMain.on("stop-bot", stopAppAndBots); 298 | globalShortcut.register(settings.stopKey, stopAppAndBots); 299 | 300 | win.blur(); 301 | if(config.patch[settings.game].hideWin) { 302 | setTimeout(() => { 303 | win.hide(); 304 | }, 500 + Math.random() * 1500); 305 | } 306 | startBots(stopAppAndBots); 307 | }); 308 | 309 | 310 | ipcMain.on("open-link", (event, link) => 311 | shell.openExternal(link) 312 | ); 313 | 314 | ipcMain.on('reg-start-by-fishing-key', () => { 315 | const config = getJson("./config/bot.json"); 316 | const settings = getJson("./config/settings.json"); 317 | 318 | globalShortcut.register(settings.fishingKey, () => { 319 | win.webContents.send('start-by-fishing-key'); 320 | }); 321 | }) 322 | 323 | ipcMain.on('unreg-start-by-fishing-key', () => { 324 | const config = getJson("./config/bot.json"); 325 | const settings = getJson("./config/settings.json"); 326 | globalShortcut.unregister(settings.fishingKey); 327 | }) 328 | 329 | ipcMain.on("dx11-warn", () => { 330 | showWarning(win, `Don't use this if you don't know what you are doing. This is an alternative pixel recognition logic that requires DirectX 11 turned on in the game.`); 331 | }); 332 | 333 | ipcMain.on("splash-warn", () => { 334 | showWarning(win, `The bot will try to detect splash animation instead of the bobber animation. If possible, increase the visual quality of the water either by installing modded textures or in the settings of the game.\n\nIf the splash isn't detected, you can increase Sensitivity and Splash Color values (You can find the Splash Color value in the Advanced Settings).`); 335 | }); 336 | 337 | ipcMain.on("open-link-donate", () => 338 | shell.openExternal("https://www.buymeacoffee.com/jsbots/e/96734") 339 | ); 340 | 341 | ipcMain.on("save-settings", (event, settings) => 342 | writeFileSync(path.join(__dirname, "./config/settings.json"), JSON.stringify(settings)) 343 | ); 344 | 345 | ipcMain.on("unsupported-key", () => { 346 | showWarning(win, `The key you pressed is not supported by AutoFish.`); 347 | }); 348 | 349 | ipcMain.on(`resize-win`, (event, size) => { 350 | win.setSize(size.width, size.height); 351 | }); 352 | 353 | ipcMain.on("ascension-warn", () => { 354 | return showWarning(win, `If you play on some custom servers like Ascension, don't forget to run the bot as admin, otherwise it won't work.`); 355 | }) 356 | 357 | let settWin; 358 | ipcMain.on("advanced-settings", () => { 359 | if(!settWin || settWin.isDestroyed()) { 360 | settWin = createAdvSettings(__dirname) 361 | } else { 362 | settWin.focus(); 363 | } 364 | }); 365 | 366 | ipcMain.handle("get-bitmap", getBitmapAsync); 367 | ipcMain.handle("get-all-windows", getAllWindows); 368 | ipcMain.handle("get-settings", () => getJson("./config/settings.json")); 369 | } 370 | 371 | let powerBlocker = powerSaveBlocker.start("prevent-display-sleep"); 372 | app.whenReady().then(() => { 373 | const menu = Menu.buildFromTemplate([{label: `Help`, submenu: [ 374 | { label: `AutoFish ver. 2.8.3 Public` }, 375 | { type: 'separator' }, 376 | { label: "📘 Read Me", click: () => shell.openExternal("https://github.com/jsbots/AutoFish#guide-blue_book")}, 377 | { label: 'Video', click: () => shell.openExternal("https://youtu.be/A3W8UuVIZTo")}, 378 | { type: 'separator' }, 379 | { label: 'Report issue', click: () => shell.openExternal("https://github.com/jsbots/AutoFish/issues")}, 380 | { type: 'separator' }, 381 | { label: 'Discord Server', click: () => shell.openExternal("https://discord.gg/4sHFUtZ8tC")}, 382 | { label: 'Donate', click: () => shell.openExternal("https://www.buymeacoffee.com/jsbots")}, 383 | { type: 'separator' }, 384 | { role: 'quit' } 385 | ]}, 386 | { 387 | label: `Cache`, 388 | submenu: [ 389 | { 390 | label: "Open Cache", 391 | click: () => { 392 | shell.openExternal(win.webContents.session.storagePath); 393 | }, 394 | }, 395 | { 396 | label: "Clear Cache", 397 | click: () => { 398 | win.webContents.session.clearStorageData(); 399 | showWarning(win, `Cache Cleared. Application Reload May Be Required`); 400 | }, 401 | }, 402 | { type: "separator" }, 403 | ], 404 | }, 405 | { 406 | label: `Debug`, 407 | submenu: [ 408 | { 409 | label: "Debugging On", 410 | type: `checkbox`, 411 | checked: false, 412 | click: () => { 413 | if(process.env.NODE_ENV != `dev`) { 414 | process.env.NODE_ENV = `dev` 415 | } else { 416 | process.env.NODE_ENV = `prod` 417 | } 418 | }, 419 | }, 420 | { 421 | label: "Open Debugging Folder", 422 | click: () => { 423 | shell.openExternal(`${__dirname}/debug`); 424 | }, 425 | }, 426 | { role: 'toggleDevTools' }, 427 | { type: "separator" }, 428 | ], 429 | }, 430 | ]) 431 | 432 | Menu.setApplicationMenu(menu); 433 | createWindow(); 434 | }); 435 | 436 | crashReporter.start({uploadToServer: false}); 437 | -------------------------------------------------------------------------------- /app/renderer.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require("electron"); 2 | 3 | const Settings = require("./ui/settings.js"); 4 | const StartButton = require("./ui/startButton.js"); 5 | const AutoFish = require("./ui/autoFish.js"); 6 | 7 | const runApp = async () => { 8 | const settings = await ipcRenderer.invoke("get-settings"); 9 | let autoFish = new AutoFish( 10 | new Settings(settings), 11 | new StartButton() 12 | ); 13 | document.body.append(autoFish.dom); 14 | ipcRenderer.send(`onload`); 15 | }; 16 | 17 | runApp(); 18 | -------------------------------------------------------------------------------- /app/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, 6 | *:before, 7 | *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body, 12 | input, 13 | pre, 14 | button, 15 | p, 16 | select { 17 | font-family: "Segoe UI"; 18 | font-size: 12px; 19 | } 20 | 21 | select { 22 | border-radius: 5px; 23 | } 24 | 25 | html, 26 | body { 27 | width: 100%; 28 | height: 100%; 29 | overflow: hidden; 30 | } 31 | 32 | p, 33 | h1, 34 | h3 { 35 | margin: 0; 36 | padding: 0; 37 | } 38 | 39 | body { 40 | margin: 0; 41 | padding: 0; 42 | } 43 | 44 | button { 45 | width: 100%; 46 | height: 80px; 47 | border-radius: 10px; 48 | border: 1px solid grey; 49 | } 50 | 51 | button:hover { 52 | border: 1px solid black; 53 | background-color: rgb(230, 230, 230); 54 | cursor: pointer; 55 | } 56 | 57 | button:active { 58 | background-color: rgb(240, 240, 240); 59 | } 60 | 61 | section { 62 | margin-bottom: 5px; 63 | } 64 | 65 | 66 | .AutoFish { 67 | padding: 5px; 68 | width: 325px; 69 | height: 678px; 70 | } 71 | .logo { 72 | margin-top: 10px; 73 | margin-bottom: 10px; 74 | height: 50px; 75 | background-image: url("img/logo.png"); 76 | background-repeat: no-repeat; 77 | background-size: contain; 78 | } 79 | 80 | .logo_name { 81 | background: url("img/logo-icon.png") no-repeat center left; 82 | background-size: 80px; 83 | background-position: right 15px; 84 | font-size: 56px; 85 | margin: 0 auto; 86 | } 87 | 88 | .logo_link { 89 | display: flex; 90 | justify-content: flex-end; 91 | align-items: flex-end; 92 | margin-top: -10px; 93 | margin-right: 80px; 94 | } 95 | 96 | .premium_crown { 97 | position: absolute; 98 | width: 30px; 99 | transform: rotate(-8deg); 100 | top: 0; 101 | left: 133px; 102 | } 103 | 104 | 105 | .logo_link_img { 106 | height: 11px; 107 | margin-right: 5px; 108 | margin-left: 5px; 109 | } 110 | 111 | .premium_icon { 112 | height: 10px; 113 | margin-left: 2px; 114 | } 115 | 116 | .section_name { 117 | margin: 0; 118 | font-size: 12px; 119 | color: grey; 120 | } 121 | 122 | .settings_header { 123 | display: inline-block; 124 | text-align: center; 125 | background-color: white; 126 | z-index: 1000; 127 | margin-left: 5px; 128 | margin-bottom: -1px; 129 | padding: 3px; 130 | padding-right: 8px; 131 | padding-left: 8px; 132 | border-top: 1px solid black; 133 | border-top-right-radius: 5px; 134 | border-top-left-radius: 5px; 135 | border-left: 1px solid black; 136 | border-right: 1px solid black; 137 | margin-top: 7px; 138 | font-size: 1.1em; 139 | font-weight: 500; 140 | position: relative; 141 | } 142 | 143 | .settings_header_main { 144 | padding-right: 5px; 145 | padding-left: 5px; 146 | width: 40px; 147 | } 148 | 149 | .settings_header_log { 150 | margin-top: 2px; 151 | background-color: #ffffe5; 152 | } 153 | 154 | .settings_critical { 155 | background-color: rgb(255, 221, 221); 156 | } 157 | 158 | .logger { 159 | height: 105px; 160 | background-image: url("./img/fish.png"); 161 | background-size: contain; 162 | background-repeat: no-repeat; 163 | background-position: center; 164 | background-color: rgba(255, 255, 0, 0.1); 165 | border: 1px inset grey; 166 | border-radius: 10px; 167 | border-top-left-radius: 7px; 168 | overflow-y: scroll; 169 | padding: 5px; 170 | } 171 | 172 | .settings { 173 | display: flex; 174 | flex-flow: row wrap; 175 | } 176 | 177 | .settings .settings_section { 178 | display: flex; 179 | flex-flow: column; 180 | justify-content: space-between; 181 | border: 1px inset grey; 182 | border-radius: 10px; 183 | border-top-left-radius: 7px; 184 | padding: 5px; 185 | flex: 1 1 0; 186 | z-index: 1; 187 | } 188 | 189 | .settings .settings_section:first-child { 190 | margin-right: 5px; 191 | } 192 | 193 | .settings .whiteList { 194 | margin-top: 5px; 195 | } 196 | 197 | .settings label { 198 | display: flex; 199 | justify-content: space-between; 200 | margin-bottom: 5px; 201 | } 202 | 203 | .settings label div { 204 | display: flex; 205 | } 206 | 207 | .settings .option_hint { 208 | cursor: help; 209 | height: 13px; 210 | margin-left: 2px; 211 | margin-top: 3px; 212 | display: inline-block; 213 | transition: 0.7s all; 214 | } 215 | 216 | .settings .option_hint:hover { 217 | transform: rotate(1turn); 218 | } 219 | 220 | .settings_section.threshold_settings { 221 | border: 1px solid black; 222 | padding-left: 2px; 223 | position: relative; 224 | } 225 | 226 | .threshold_settings .option { 227 | display: flex; 228 | align-items: flex-end; 229 | } 230 | 231 | .threshold_settings .option .option_hint { 232 | margin-bottom: 3px; 233 | margin-left: 5px; 234 | } 235 | 236 | .settings select, 237 | .settings input[type="number"], .settings input[type="text"] { 238 | width: 42px; 239 | margin-right: 3px; 240 | height: 20px; 241 | } 242 | 243 | 244 | 245 | .settings input[name="stopKey"], 246 | .settings input[name="luresKey"], 247 | .settings input[name="fishingKey"], 248 | .settings input[name="intKey"], 249 | .settings input[name="spareKey"], 250 | .settings input[name="mammothKey"], 251 | .settings input[name="hsKey"], 252 | .settings .spares-key, 253 | .settings input[name="logOutMacroKey"], 254 | .settings input[name="mammothMacroKey"] { 255 | text-align: center; 256 | border-radius: 2px; 257 | border: 1px solid grey; 258 | cursor: pointer; 259 | transition: 0.2s all; 260 | } 261 | 262 | .settings input[name="stopKey"]:active, 263 | .settings input[name="luresKey"]:active, 264 | .settings input[name="fishingKey"]:active, 265 | .settings input[name="intKey"]:active, 266 | .settings input[name="mammothKey"]:active, 267 | .settings input[name="hsKey"]:active, 268 | .settings .spares-key:active, 269 | .settings input[name="logOutMacroKey"]:active, 270 | .settings input[name="mammothMacroKey"]:active { 271 | cursor: default; 272 | } 273 | 274 | .settings input[name="stopKey"]:focus, 275 | .settings input[name="luresKey"]:focus, 276 | .settings input[name="fishingKey"]:focus, 277 | .settings input[name="intKey"]:focus, 278 | .settings input[name="mammothKey"]:focus, 279 | .settings input[name="hsKey"]:focus, 280 | .settings .spares-key:focus, 281 | .settings input[name="logOutMacroKey"]:focus, 282 | .settings input[name="mammothMacroKey"]:focus { 283 | outline: none; 284 | } 285 | 286 | .settings input[name="stopKey"]:disabled, 287 | .settings input[name="luresKey"]:disabled, 288 | .settings input[name="fishingKey"]:disabled, 289 | .settings input[name="intKey"]:disabled, 290 | .settings input[name="mammothKey"]:disabled, 291 | .settings input[name="hsKey"]:disabled, 292 | .settings .spares-key:disabled, 293 | .settings input[name="logOutMacroKey"]:disabled, 294 | .settings input[name="mammothMacroKey"]:disabled { 295 | cursor: default; 296 | border: 1px solid rgb(215, 215, 215); 297 | background-color: rgb(245, 245, 245); 298 | } 299 | 300 | .settings textarea.whitelist_input { 301 | width: 409px !important; 302 | height: 50px; 303 | max-width: 409px; 304 | min-height: 50px; 305 | } 306 | 307 | .advanced_settings_button { 308 | height: 33px; 309 | font-size: 1.1em; 310 | width: 140px; 311 | margin-right: 3px; 312 | text-align: center; 313 | border-radius: 5px; 314 | border: 1px solid grey; 315 | } 316 | 317 | .advanced_settings_button_disabled { 318 | height: 33px; 319 | font-size: 1.1em; 320 | width: 140px; 321 | margin-right: 3px; 322 | text-align: center; 323 | border-radius: 5px; 324 | border: 1px solid rgb(230, 230, 230); 325 | } 326 | 327 | .advanced_settings_button:hover { 328 | border: 1px solid black; 329 | background-color: rgb(230, 230, 230); 330 | cursor: pointer; 331 | } 332 | 333 | .advanced_settings_button:active { 334 | background-color: rgb(240, 240, 240); 335 | } 336 | 337 | .option.game-option { 338 | width: 125px; 339 | outline: 2px solid #FFDBC5; 340 | } 341 | 342 | .startButton { 343 | background-image: url('img/start.png'); 344 | background-repeat: no-repeat; 345 | background-position: center center; 346 | display: block; 347 | height: 75px; 348 | font-size: 44px; 349 | font-weight: bold; 350 | } 351 | 352 | .version { 353 | text-align: center; 354 | } 355 | 356 | .advanced_settings { 357 | padding: 5px; 358 | } 359 | 360 | .donateLink { 361 | display: inline; 362 | } 363 | 364 | /* AdvSettings window */ 365 | 366 | .advSettings { 367 | padding: 5px; 368 | } 369 | 370 | .advSettings_settings { 371 | height: 558px; 372 | padding-right: 3px; 373 | margin-bottom: 7px; 374 | overflow-y: scroll; 375 | } 376 | 377 | .advSettings .buttons { 378 | text-align: center; 379 | } 380 | 381 | .advSettings .buttons input[type=button] { 382 | outline: 2px solid #FFDBC5; 383 | } 384 | 385 | .advSettings .option_text { 386 | margin-left: 5px; 387 | margin-right: 5px; 388 | } 389 | 390 | .advSettings input[type=button] { 391 | margin: 0 3px; 392 | width: 70px; 393 | border-radius: 5px; 394 | border: 1px solid grey; 395 | 396 | } 397 | 398 | .advSettings input[type=button]:hover { 399 | cursor: pointer; 400 | 401 | background-color: rgb(230, 230, 230); 402 | } 403 | 404 | .advSettings input[type=button].dummy_button:hover { 405 | border: 1px solid grey; 406 | background-color: rgb(240, 240, 240); 407 | cursor: default; 408 | } 409 | 410 | .advSettings input[type=button]:active { 411 | background-color: rgb(240, 240, 240); 412 | } 413 | 414 | 415 | .advSettings .settings_section input[type=number], .advSettings .settings_section input[type=text] { 416 | width: 40px; 417 | } 418 | 419 | .advSettings .settings_section input[name=mammothTraderName] { 420 | width: 160px; 421 | } 422 | 423 | .advSettings .settings { 424 | flex-flow: column; 425 | } 426 | 427 | .advSettings .settings .settings_section { 428 | margin: 0; 429 | } 430 | 431 | .advanced_warning { 432 | text-align: center; 433 | color: red; 434 | margin: 5px 0; 435 | } 436 | 437 | .advSettings .redThresholdColor { 438 | height: 20px; 439 | width: 20px; 440 | border: 1px solid grey; 441 | } 442 | 443 | .advSettings .settings select { 444 | width: 107px; 445 | } 446 | 447 | .thresholdRange { 448 | position: relative; 449 | } 450 | 451 | .thresholdRange input[type=range] { 452 | width: 239px; 453 | height: 10px; 454 | margin-bottom: 5px; 455 | z-index: 10; 456 | } 457 | 458 | .thresholdRange .bobberContainer { 459 | display: flex; 460 | flex-flow: column; 461 | align-items: center; 462 | } 463 | 464 | .threshold_canvas { 465 | display: block; 466 | margin-left: -55px; 467 | } 468 | 469 | .bobberColorSwitch { 470 | transition: 0.1s all; 471 | margin-right: 3px; 472 | width: 42px; 473 | height: 20px; 474 | border: 1px solid black; 475 | border-radius: 3px; 476 | margin-right: 38px; 477 | background-color: white; 478 | background-size: 50px 22px; 479 | background-position: -1px -1px; 480 | background-repeat: no-repeat; 481 | cursor: pointer; 482 | } 483 | 484 | 485 | .settings_section input[type=text].tmApiKey { 486 | width: 194px; 487 | } 488 | 489 | .settings_section input[type=range].whisperRange { 490 | width: 230px; 491 | } 492 | 493 | .whisperColorBox { 494 | height: 18px; 495 | width: 49px; 496 | border: 1px solid grey; 497 | color: white; 498 | text-align: center; 499 | padding-left: 3px; 500 | margin-right: 4px; 501 | background-color: rgba(252, 119, 187, 0.8); 502 | } 503 | 504 | 505 | /* AdvSettings window end */ 506 | 507 | .threshold_settings input[type=range] { 508 | width: 139px; 509 | margin-right: 6px; 510 | } 511 | 512 | input[type=range][name=threshold] { 513 | -webkit-appearance: none; 514 | background: transparent; 515 | } 516 | 517 | .threshold_settings input[type=range]:focus { 518 | outline: none; 519 | } 520 | 521 | .threshold_settings input[type=range]::-ms-track { 522 | width: 100%; 523 | cursor: pointer; 524 | background: transparent; 525 | border-color: transparent; 526 | color: transparent; 527 | } 528 | 529 | input[type=checkbox] { 530 | width: 13px; 531 | height: 13px; 532 | } 533 | 534 | .threshold_settings input[type=range]::-webkit-slider-thumb { 535 | -webkit-appearance: none; 536 | border: 1px solid grey; 537 | width: 16px; 538 | border-radius: 3px; 539 | background: rgb(240,240,240); 540 | background-image: url("img/grate.png"); 541 | background-position: center center; 542 | background-size: 10px; 543 | background-repeat: no-repeat; 544 | cursor: pointer; 545 | margin-top: -6px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ 546 | } 547 | 548 | .threshold_settings input[type=range]::-webkit-slider-thumb:hover { 549 | border: 1px solid black; 550 | } 551 | 552 | .threshold_settings input[type=range]::-webkit-slider-runnable-track { 553 | width: 100%; 554 | height: 10px; 555 | cursor: pointer; 556 | background-image: linear-gradient(to right, black, red); 557 | border-radius: 3px; 558 | border: 0.2px solid #010101; 559 | } 560 | 561 | 562 | input[type=range].threshold_disabled::-webkit-slider-runnable-track { 563 | opacity: 0.8; 564 | } 565 | 566 | input[type=range][name=threshold]:focus::-webkit-slider-runnable-track { 567 | opacity: 0.9; 568 | } 569 | 570 | input[type=range].threshold_disabled::-webkit-slider-thumb { 571 | visibility: hidden; 572 | } 573 | 574 | /* Profiles */ 575 | .settings_premium { 576 | background-color: #FFDBC5; 577 | } 578 | 579 | .settings_header_premium { 580 | background-color: #FFDBC5; 581 | } 582 | 583 | .settings_header_critical { 584 | background-color: rgb(255, 221, 221); 585 | } 586 | 587 | .settings_advSettings { 588 | margin-top: -7px; 589 | display: block; 590 | } 591 | 592 | 593 | input[type=range] { 594 | -webkit-appearance: none; 595 | background: transparent; 596 | } 597 | 598 | input[type=range]::-webkit-slider-thumb { 599 | -webkit-appearance: none; 600 | } 601 | 602 | input[type=range]:focus { 603 | outline: none; 604 | } 605 | 606 | input[type=range]::-ms-track { 607 | width: 100%; 608 | cursor: pointer; 609 | background: transparent; 610 | border-color: transparent; 611 | color: transparent; 612 | } 613 | 614 | input[type=checkbox] { 615 | width: 13px; 616 | height: 13px; 617 | } 618 | 619 | input[type=range]::-webkit-slider-thumb { 620 | -webkit-appearance: none; 621 | border: 1px solid grey; 622 | height: 20px; 623 | width: 16px; 624 | border-radius: 3px; 625 | background: rgb(240,240,240); 626 | background-image: url("img/grate.png"); 627 | background-position: center center; 628 | background-size: 10px; 629 | background-repeat: no-repeat; 630 | cursor: pointer; 631 | margin-top: -7px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ 632 | } 633 | 634 | input[type=range]::-webkit-slider-thumb:hover { 635 | border: 1px solid black; 636 | } 637 | 638 | input[type=range]::-webkit-slider-runnable-track { 639 | width: 100%; 640 | height: 8px; 641 | cursor: pointer; 642 | background-image: linear-gradient(to left, rgba(0, 117, 255, 0.6), rgba(0, 117, 255, 0.8)); 643 | border-radius: 3px; 644 | border: 0.2px solid #010101; 645 | } 646 | 647 | .switch_thumb { 648 | width: 17px; 649 | height: 16px; 650 | position: relative; 651 | top: 1px; 652 | background-color: rgb(240,240,240); 653 | background-image: url("img/grate.png"); 654 | background-position: 3px center; 655 | background-size: 9px; 656 | background-repeat: no-repeat; 657 | border-radius: 3px; 658 | border: 1px solid grey; 659 | } 660 | 661 | .switch_thumb_left { 662 | left: 1px; 663 | } 664 | 665 | .switch_thumb_right { 666 | left: 22px; 667 | } 668 | 669 | .switch_thumb:hover { 670 | border: 1px solid black; 671 | } 672 | 673 | .settingsFolder{ 674 | height: 16px; 675 | position: absolute; 676 | left: 53px; 677 | top: 86px; 678 | cursor: pointer; 679 | opacity: 0.5; 680 | border: 1px solid black; 681 | border-radius: 5px; 682 | } 683 | 684 | 685 | .settings_premium { 686 | background-color: #FFDBC5; 687 | opacity: 0.7; 688 | } 689 | 690 | .settings_header_premium { 691 | background-color: #ffe9dc; 692 | border-color: grey; 693 | color: rgba(0,0,0,0.7); 694 | } 695 | 696 | .premium_lock { 697 | background-image: url('img/youtube_icon.png'); 698 | background-repeat: no-repeat; 699 | background-position: 0; 700 | background-size: contain; 701 | background-position: center center; 702 | margin-left: 5px; 703 | margin-right: 4px; 704 | margin-top: 2px; 705 | width: 15px; 706 | height: 15px; 707 | display: inline-block; 708 | cursor: pointer; 709 | } 710 | 711 | .advSettings .premium_lock { 712 | position: relative; 713 | top: 3px; 714 | } 715 | 716 | .advSettings input[type=range] { 717 | width: 180px; 718 | } 719 | 720 | .threshold_number_input { 721 | z-index: 1; 722 | } 723 | 724 | .advSettings .settings .settings_section .spares-addButton { 725 | display: block; 726 | margin: 0 auto; 727 | background-image: url('img/add.png'); 728 | background-size: 12px; 729 | background-position: center center; 730 | background-repeat: no-repeat; 731 | height: 20px; 732 | width: 75px; 733 | } 734 | 735 | .advanced_settings_header { 736 | width: 40px; 737 | } 738 | 739 | .advanced_settings_header_text { 740 | font-size: 1.1em; 741 | font-weight: 500; 742 | margin-left: 5px 743 | } 744 | 745 | .advanced_settings_header_text_threshold { 746 | margin-top: 10px; 747 | margin-left: 5px; 748 | } 749 | 750 | .premium_label { 751 | background-image: linear-gradient(to right, rgba(255, 219, 197, 0.6), white); 752 | border-radius: 5px; 753 | padding-left: 2px; 754 | padding-top: 2px; 755 | padding-bottom: 2px; 756 | margin-left: -2px; 757 | margin-top: -2px; 758 | } 759 | /* Track */ 760 | .advSettings_settings::-webkit-scrollbar { 761 | width: 12px; /* Set the width of the scrollbar */ 762 | } 763 | 764 | /* Handle */ 765 | .advSettings_settings::-webkit-scrollbar-thumb { 766 | background-color: rgba(255, 209, 187); /* Set the color of the scrollbar handle */ 767 | border-radius: 5px; /* Round the corners of the scrollbar handle */ 768 | border: 1px solid rgb(130, 130, 130); 769 | } 770 | 771 | /* Handle on hover */ 772 | .advSettings_settings::-webkit-scrollbar-thumb:hover { 773 | background-color: rgba(255, 189, 167); /* Change color on hover */ 774 | border: 1px solid grey; 775 | } 776 | 777 | /* Track */ 778 | .advSettings_settings::-webkit-scrollbar-track { 779 | border-radius: 5px; 780 | background: rgb(211, 211, 211); /* Set the color of the scrollbar track */ 781 | } 782 | 783 | /* Handle when scrolling */ 784 | .advSettings_settings::-webkit-scrollbar-thumb:active { 785 | background-color: rgba(255, 169, 147); /* Change color when the user is actively scrolling */ 786 | } 787 | 788 | /* Hide the number input arrows */ 789 | input[type=number]::-webkit-inner-spin-button, 790 | input[type=number]::-webkit-outer-spin-button { 791 | -webkit-appearance: none; 792 | margin: 0; 793 | } 794 | input[type=number] { 795 | -moz-appearance: textfield; /* Firefox */ 796 | } 797 | 798 | 799 | .auto_button { 800 | width: 30px; 801 | height: 15px; 802 | cursor: pointer; 803 | position: absolute; 804 | font-size: 0.7em; 805 | padding: 0; 806 | z-index: 100; 807 | } 808 | 809 | .auto_button.autoTh { 810 | top: 33px; 811 | right: 24px; 812 | } 813 | 814 | .auto_button.autoSens { 815 | top: 58px; 816 | right: 24px; 817 | } 818 | 819 | .auto_button.autoColor { 820 | top: 8px; 821 | right: 24px; 822 | } 823 | 824 | .auto_button_on { 825 | cursor: pointer; 826 | border: 1px solid rgb(130, 130, 130); 827 | border-radius: 2px; 828 | background-color: rgba(255, 219, 197, .9); 829 | } 830 | 831 | .auto_button_on:hover { 832 | border: 1px solid rgba(70, 70, 70); 833 | background-color: #ffc19b; 834 | } 835 | 836 | .rangeContainer input[type=range] { 837 | margin-top: 5px; 838 | } 839 | 840 | .sensitivityContainer, .rangeContainer { 841 | margin-right: 35px; 842 | } 843 | 844 | .checkLogicSelect { 845 | width: 135px !important; 846 | margin-right: 8px !important; 847 | outline: 2px solid #FFDBC5; 848 | font-size: 0.9em; 849 | cursor: help; 850 | } 851 | 852 | .thLabel { 853 | padding-left: 3px; 854 | } 855 | -------------------------------------------------------------------------------- /app/ui/autoFish.js: -------------------------------------------------------------------------------- 1 | const elt = require("./utils/elt.js"); 2 | const { ipcRenderer } = require("electron"); 3 | 4 | const renderLogo = () => { 5 | return elt( 6 | "div", 7 | { className: "logo" }, 8 | ); 9 | }; 10 | 11 | const loggerMemory = []; 12 | const renderLogger = () => { 13 | return { 14 | dom: elt("section", { className: `logger` }), 15 | show({ text, type }) { 16 | let row = elt("p", null, text); 17 | loggerMemory.push(row); 18 | if(loggerMemory.length > 100) { 19 | loggerMemory.shift().remove() 20 | } 21 | row.style.color = type; 22 | this.dom.append(row); 23 | this.dom.scrollTop += 30; 24 | }, 25 | }; 26 | }; 27 | 28 | class AutoFish { 29 | constructor(settings, startButton) { 30 | this.settings = settings; 31 | this.button = startButton; 32 | this.logger = renderLogger(); 33 | const premiumIcon = elt(`img`, { className: `premium_icon`, src: `img/premium.png` }); 34 | const versionNode = elt("span"); 35 | const donateLink = elt( 36 | "a", 37 | { 38 | href: `#`, 39 | className: "donateLink", 40 | onclick: () => ipcRenderer.send("open-link-donate"), 41 | }, 42 | `AutoFish Premium` 43 | ); 44 | const footer = elt(`p`, { className: "version" }, versionNode, donateLink, premiumIcon); 45 | 46 | ipcRenderer.on("set-version", (event, version) => { 47 | versionNode.textContent = `ver. 2.8.3 Public | `; 48 | }); 49 | 50 | ipcRenderer.on('start-by-fishing-key', () => { 51 | if(!this.button.state) { 52 | this.button.dom.click(); 53 | } 54 | }); 55 | 56 | this.settings.regOnChange((config) => { 57 | ipcRenderer.send("save-settings", config); 58 | }); 59 | 60 | this.settings.regOnClick((config) => { 61 | ipcRenderer.send("advanced-settings", config); 62 | }); 63 | 64 | this.settings.regOnFishingZoneClick(() => { 65 | ipcRenderer.send("start-bot", `relZone`); 66 | }); 67 | 68 | this.settings.regOnChatZoneClick(() => { 69 | ipcRenderer.send("start-bot", `chatZone`); 70 | }); 71 | 72 | this.settings.regOnDx11(() => { 73 | ipcRenderer.send("dx11-warn"); 74 | }); 75 | 76 | this.settings.regOnWhitelistWarn(() => { 77 | ipcRenderer.send("whitelist-warn"); 78 | }); 79 | 80 | this.button.regOnStart(() => { 81 | ipcRenderer.send("start-bot"); 82 | }); 83 | 84 | this.button.regOnStop(() => { 85 | ipcRenderer.send("stop-bot"); 86 | }); 87 | 88 | ipcRenderer.on("settings-change", (settings) => { 89 | this.settings.config = settings; 90 | this.settings.render(); 91 | }); 92 | 93 | ipcRenderer.on("set-game", (event, game) => { 94 | this.settings.config.game = game; 95 | this.settings.reRender(); 96 | }); 97 | 98 | ipcRenderer.on("stop-bot", () => { 99 | this.button.onError(); 100 | }); 101 | 102 | ipcRenderer.on("log-data", (event, data) => { 103 | this.logger.show(data); 104 | }); 105 | 106 | let settingsVisibility = true; 107 | let foldSettingsContainer = elt(`img`, {src: `img/unfold.png`, className: `settingsFolder`}) 108 | foldSettingsContainer.addEventListener(`click`, (event) => { 109 | if(settingsVisibility) { 110 | this.settings.dom.style = `display: none;`; 111 | ipcRenderer.send(`resize-win`, {width: 341, height: 395}) 112 | event.target.src = `img/fold.png`; 113 | document.querySelector(`.settings_header_fold`).style = `border-bottom: 1px solid grey; border-radius: 5px`; 114 | } else { 115 | this.settings.dom.style = `display: block`; 116 | ipcRenderer.send(`resize-win`, {width: 341, height: 678}) 117 | event.target.src = `img/unfold.png` 118 | document.querySelector(`.settings_header_fold`).style = ``; 119 | } 120 | settingsVisibility = !settingsVisibility; 121 | }) 122 | 123 | this.dom = elt( 124 | "div", 125 | { className: "AutoFish" }, 126 | renderLogo(), 127 | elt(`div`, {className: `settings_profile`}, elt("p", { className: "settings_header settings_header_main settings_header_fold"}, "⚙️"), foldSettingsContainer), 128 | this.settings.dom, 129 | elt("p", { className: "settings_header settings_header_log settings_header_main" }, "📋"), 130 | this.logger.dom, 131 | this.button.dom, 132 | footer 133 | ); 134 | } 135 | } 136 | 137 | module.exports = AutoFish; 138 | -------------------------------------------------------------------------------- /app/ui/renderSettings.js: -------------------------------------------------------------------------------- 1 | const elt = require("./utils/elt.js"); 2 | const wrapInLabel = require("./utils/wrapInLabel.js"); 3 | 4 | const renderColorSwitch = ({bobberColor, checkLogic, autoColor, soundDetection}) => { 5 | 6 | const checkLogicTypes = ['default', 'pixelmatch']; 7 | 8 | const modeSelect = elt(`select`, {name: 'checkLogic', title: `Alternative modes for detecting bobber animation.`, className: `checkLogicSelect`}, ...checkLogicTypes.map((logic) => elt('option', {selected: checkLogic == logic, value: logic}, logic[0].toUpperCase() + logic.slice(1)))); 9 | 10 | const bobberColorSwitch = elt(`radio`, { className: `bobberColorSwitch`, 11 | name: `bobberColor`, 12 | title: `Switch between blue and red feathers.`, 13 | value: bobberColor, 14 | style: `background-image: linear-gradient(to right, ${bobberColor == `red` ? `rgba(100, 0, 0, .8), rgba(255, 0, 0, .8)` : `rgba(0, 90, 200, .8), rgba(0, 0, 100, .8)`}); ${soundDetection ? `display: none` : ``}` 15 | }, elt(`div`, {className: `switch_thumb ${bobberColor == `red` ? `switch_thumb_left` : `switch_thumb_right`}`})); 16 | return elt(`div`, null, modeSelect, bobberColorSwitch); 17 | } 18 | 19 | 20 | const renderBobberSensitivity = ({game, bobberSensitivity, autoSens}) => { 21 | let min = 1; 22 | let max = 10; 23 | 24 | if(game == `Retail` || game == `Vanilla (splash)`) { 25 | min = 1; 26 | max = 30; 27 | } 28 | 29 | if(game == `Vanilla (splash)`) { 30 | autoSens = false; 31 | } 32 | 33 | if(bobberSensitivity > max) bobberSensitivity = max; 34 | if(bobberSensitivity < min) bobberSensitivity = min; 35 | 36 | let bobberSensitivityWin = elt(`input`, {type: `number`, name: `bobberSensitivity`, value: bobberSensitivity[game], disabled: autoSens}); 37 | 38 | return elt(`div`, {className: `sensitivityContainer`}, elt('input', {type: `range`, min, max, value: bobberSensitivity[game], disabled: autoSens, className: `${autoSens ? `threshold_disabled` : ``}` , oninput: function() {bobberSensitivityWin.value = this.value}, name: `bobberSensitivity`}), bobberSensitivityWin); 39 | }; 40 | 41 | const renderThreshold = ({ threshold, bobberColor, autoTh, game }) => { 42 | if(threshold < 1) threshold = 1; 43 | else if(threshold > 250) threshold = 250; 44 | 45 | if(game == `Vanilla (splash)`) autoTh = false; 46 | 47 | const range = elt(`input`, { type: `range`, min: 1, max: 250, value: threshold, name: `threshold`, disabled: autoTh, className: `${autoTh ? `threshold_disabled` : ``}` }); 48 | const number = elt(`input`, { type: `number`, className: `threshold_number_input`, value: threshold, disabled: autoTh, name: `threshold` }); 49 | 50 | const bobberContainer = elt(`div`, null, number); 51 | const rangeContainer = elt(`div`, { className: `rangeContainer`}, range, bobberContainer) 52 | 53 | if(bobberColor == `blue`) { 54 | document.styleSheets[0].rules[80].style.backgroundImage = "linear-gradient(to right, rgb(0, 0, 100), rgb(0, 90, 200))" 55 | } else { 56 | document.styleSheets[0].rules[80].style.backgroundImage = "linear-gradient(to right, rgb(100, 0, 0), rgb(250, 0, 0))" 57 | } 58 | 59 | 60 | return elt(`div`, { className: `thresholdRange` }, rangeContainer); // autoThSwitch 61 | }; 62 | 63 | const renderGameNames = ({game}) => { 64 | const gamesOfficial = [ 65 | `Retail`, 66 | `Cata Classic`, 67 | `Classic` 68 | ]; 69 | 70 | const gamesPrivate = [ 71 | "Leg", 72 | "MoP", 73 | "Cata", 74 | "LK Private", 75 | "TBC", 76 | "Vanilla", 77 | "Vanilla (splash)" 78 | ]; 79 | 80 | return elt( 81 | "select", 82 | { name: "game", className: "option game-option" }, 83 | elt(`optgroup`, {label: `Official-like`}, ...gamesOfficial.map((name) => 84 | elt("option", { selected: name == game }, name) 85 | )), 86 | elt(`optgroup`, {label: `Private-like`}, ...gamesPrivate.map((name) => 87 | elt("option", { selected: name == game }, name) 88 | )) 89 | ); 90 | }; 91 | 92 | 93 | const renderTimer = ({timer}) => { 94 | return elt( 95 | "input", 96 | { type: "number", min: "0", value: timer, name: "timer", title: ""}, 97 | `(min)` 98 | ); 99 | }; 100 | 101 | const renderLures = ({lures}) => { 102 | return elt("input", { 103 | type: "checkbox", 104 | className: "option", 105 | checked: lures, 106 | name: "lures", 107 | }); 108 | }; 109 | const renderLuresKey = ({lures, luresKey}) => { 110 | let key = elt('input', {type: 'text', value: luresKey, disabled: !lures, name: "luresKey"}); 111 | key.setAttribute(`readonly`, `true`); 112 | return key; 113 | }; 114 | 115 | const renderStopKey = ({stopKey}) => { 116 | let key = elt('input', {type: 'text', value: stopKey, name: "stopKey"}); 117 | key.setAttribute(`readonly`, `true`); 118 | return key; 119 | }; 120 | 121 | const renderPoleKey = ({lures, game, intKey, useInt}) => { 122 | let key = elt('input', {type: 'text', value: intKey, disabled: !useInt, name: "intKey"}); 123 | key.setAttribute(`readonly`, `true`); 124 | 125 | const checkbox = elt(`input`, { 126 | type: `checkbox`, 127 | disabled: !(game == `Retail` || game == `Classic` || game == `Cata Classic`), 128 | checked: !(game == `Retail` || game == `Classic` || game == `Cata Classic`) ? false : useInt, 129 | style: `margin-right: 7px`, name: "useInt" 130 | }); 131 | 132 | const container = elt(`div`, null, checkbox, key) 133 | return container; 134 | }; 135 | 136 | const renderLuresDelay = ({lures, luresDelayMin}) => { 137 | return elt('input', {type: 'number', value: luresDelayMin, disabled: !lures, name: "luresDelayMin"}); 138 | }; 139 | const renderFishingKey = ({fishingKey}) => { 140 | let key = elt('input', {type: 'text', value: fishingKey, name: "fishingKey"}); 141 | key.setAttribute(`readonly`, `true`); 142 | return key; 143 | }; 144 | 145 | const renderAdvancedSettings = () => { 146 | return elt('input', {type: 'button', name:"advancedSettings", value: "Advanced Settings", className: "advanced_settings_button"}); 147 | }; 148 | 149 | const renderFishingZone = () => { 150 | return elt('input', {type: 'button', name:"fishingZone", value: "Fishing Zone", className: "advanced_settings_button"}); 151 | }; 152 | 153 | const renderChatZone = () => { 154 | return elt('input', {type: 'button', disabled: true, value: "Chat Zone", className: "advanced_settings_button_disabled"}); 155 | }; 156 | 157 | const renderDetectionZone = () => { 158 | return elt('input', {type: 'button', disabled: true, value: "Detection Zone", className: "advanced_settings_button_disabled"}); 159 | }; 160 | 161 | const renderMultipleWindows = () => { 162 | return elt(`div`, {className: `premium_lock premium_lock_main`, id: `link`, url: `https://youtu.be/ih-xoQcByz8`}) 163 | }; 164 | 165 | const renderAfkmode = () => { 166 | return elt(`div`, {className: `premium_lock premium_lock_main`, id: `link`, url: `https://youtu.be/lQi6fSxMyL0`}) 167 | }; 168 | 169 | const renderSettings = (config) => { 170 | return elt( 171 | "section", 172 | { className: "settings" }, 173 | elt( 174 | "div", 175 | { className: "settings_section" }, 176 | wrapInLabel( 177 | "", 178 | renderGameNames(config), 179 | `Choose the version of the game you want the bot to work on.` 180 | ), 181 | wrapInLabel( 182 | "Fishing Key: ", 183 | renderFishingKey(config), 184 | `Assign the same key you use for fishing. If you use /castFishing instead, then you should assign a key for fishing.` 185 | ), 186 | wrapInLabel( 187 | "Int. Key: ", 188 | renderPoleKey(config), 189 | `Exclusively for Retail. Use interaction key instead of mouse for catching.` 190 | ), 191 | wrapInLabel( 192 | "Stop Key: ", 193 | renderStopKey(config), 194 | `Assign a key that you will use to stop the bot.` 195 | ), 196 | wrapInLabel( 197 | "Alt-Tab Fishing: ", 198 | elt(`div`, {className: `premium_option`}, renderAfkmode()), 199 | `ONLY ON DIRECTX 11. The bot will automatically alt+tab after it casts (bringing back the previous window) and automatically focus the window of the game when it needs to catch. If you use your mouse too much during Alt-Tab Fishing the whitelist feature might be unstable. `, 200 | 'premium_label' 201 | ), 202 | wrapInLabel( 203 | "Multiple Fishing: ", 204 | elt(`div`, {className: `premium_option`}, renderMultipleWindows()), 205 | `ONLY ON DIRECTX 11. If you want to use multiple windows check this option. You need to launch every window and configure them properly, make sure every window is in DirectX 11 mode. This option uses a different library to analyze your screen, you can check it even for one window if for some reason the default way doesn't work for you.`, 206 | 'premium_label' 207 | ), 208 | ), 209 | elt( 210 | "div", 211 | { className: "settings_section" }, 212 | wrapInLabel("", renderFishingZone(config)), 213 | wrapInLabel("", renderChatZone(config)), 214 | wrapInLabel("", renderDetectionZone(config)), 215 | wrapInLabel("", renderAdvancedSettings(config))), 216 | 217 | elt("p", {className: 'settings_header'}, "🎣"), 218 | 219 | elt( 220 | "div", 221 | { className: "settings_section threshold_settings" }, 222 | elt('input', {type: `button`, disabled: !config.autoTh || config.game == `Vanilla (splash)` || config.checkLogic == `pixelmatch`, name: `autoColor`, checked: config.autoTh && config.autoColor, className: `auto_button autoColor ${config.autoColor && config.autoTh && config.game != `Vanilla (splash)` && config.checkLogic != `pixelmatch` ? `auto_button_on` : ``}`, value: `Auto`}), 223 | elt('input', {type: `button`, disabled: config.game == `Vanilla (splash)`, name: `autoTh`, checked: config.autoTh && config.game != `Vanilla (splash)`, className: `auto_button autoTh ${config.autoTh && config.game != `Vanilla (splash)` ? `auto_button_on` : ``}`, value: `Auto`}), 224 | elt('input', {type: `button`, disabled: config.game == `Vanilla (splash)`, name: `autoSens`, checked: config.autoSens && config.game != `Vanilla (splash)`, className: `auto_button autoSens ${config.autoSens && config.game != `Vanilla (splash)` ? `auto_button_on` : ``}`, value: `Auto`}), 225 | 226 | wrapInLabel("Mode: ", renderColorSwitch(config), `The color the bot will search within Fishing Zone (${config.bobberColor}, in your case). If the water and environment is bluish, choose red color. If the water and environment is reddish, choose blue color.`, 'thLabel'), 227 | wrapInLabel(`Intensity: `, 228 | renderThreshold(config),`Decrease this value, if the bot can't find the bobber (e.g. at night, bad weather). Increase this value if you want the bot to ignore more ${config.bobberColor} colors.`, 'thLabel'), 229 | wrapInLabel("Sensitivity: ", renderBobberSensitivity(config), config.game == `Vanilla (splash)` ? 230 | `The size of the zone which will be checked for splash, if the bot doesn't react to "plunging" animation - increase this value. If in Auto mode: The bot will auto-adjust both sensitivity value per each cast.` 231 | : `How sensitive the bot is to any movements (jerking, plunging) of the bobber. If the bot clicks too early, decrease this value (don't confuse it with when the bot missclicks on purpose). If the bot clicks on the bobber too late (or doesn't click at all), increase this value.`, 'thLabel') 232 | ) 233 | ); 234 | } 235 | 236 | module.exports = renderSettings; 237 | -------------------------------------------------------------------------------- /app/ui/settings.js: -------------------------------------------------------------------------------- 1 | const elt = require("./utils/elt.js"); 2 | const renderSettings = require("./renderSettings.js"); 3 | const keySupport = require("./../utils/keySupport.js"); 4 | const { ipcRenderer } = require("electron"); 5 | 6 | const convertValue = (node) => { 7 | let value = node.value; 8 | if (node.type == "checkbox" || node.name == `autoTh` || node.name == `autoSens` || node.name == `autoColor`) { 9 | value = node.checked; 10 | } 11 | 12 | if (node.type == "number") { 13 | value = Number(node.value) || 0; 14 | } 15 | 16 | return value; 17 | }; 18 | 19 | 20 | 21 | 22 | class Settings { 23 | constructor(config) { 24 | this.config = config; 25 | this.dom = elt('form', null , renderSettings(config)); 26 | 27 | const saveSettings = (event) => { 28 | if(Object.keys(this.config).includes(event.target.name)) { 29 | if(event.target.name == `bobberSensitivity`) { 30 | this.config[event.target.name][this.config.game] = convertValue(event.target); 31 | } else { 32 | this.config[event.target.name] = convertValue(event.target); 33 | } 34 | 35 | } 36 | this.dom.innerHTML = ``; 37 | this.dom.append(renderSettings(this.config)); 38 | 39 | [...this.dom.elements].forEach(option => { 40 | if(Object.keys(this.config).includes(option.name)) { 41 | if(option.name == `bobberSensitivity`) { 42 | this.config[option.name][this.config.game] = convertValue(option); 43 | } else { 44 | this.config[option.name] = convertValue(option); 45 | } 46 | } 47 | }); 48 | 49 | this.onChange(this.config); 50 | } 51 | 52 | this.dom.addEventListener("change", saveSettings); 53 | this.dom.addEventListener('change', (event) => { 54 | 55 | if(event.target.name == 'game' && event.target.value == "LK Private") { 56 | ipcRenderer.send("ascension-warn"); 57 | } 58 | 59 | if(event.target.name == `game` && event.target.value == `Vanilla (splash)`) { 60 | ipcRenderer.send(`splash-warn`); 61 | } 62 | }) 63 | 64 | this.dom.addEventListener("input", (event) => { 65 | if(event.target.name == `threshold`) { 66 | this.dom.querySelectorAll('input[name=threshold]').forEach(node => { 67 | node.value = event.target.value 68 | }); 69 | } 70 | }); 71 | 72 | const keyAssigning = (event) => { 73 | if(event.key == ` `) { 74 | event.target.value = `space`; 75 | } else { 76 | let firstChar = event.key[0].toLowerCase(); 77 | let resultKey = firstChar + event.key.slice(1); 78 | if(keySupport.isSupported(resultKey)) { 79 | event.target.value = resultKey; 80 | } else { 81 | ipcRenderer.send(`unsupported-key`); 82 | } 83 | } 84 | 85 | saveSettings(event); 86 | document.removeEventListener(`keydown`, keyAssigning); 87 | event.target.blur(); 88 | event.preventDefault(); 89 | } 90 | 91 | this.dom.addEventListener('mousedown', (event) => { 92 | 93 | if(event.target.name == `autoTh` || event.target.name == `autoSens` || event.target.name == `autoColor`) { 94 | this.config[event.target.name] = !this.config[event.target.name] 95 | this.onChange(this.config); 96 | this.reRender(); 97 | } 98 | 99 | if((event.target.name == `stopKey` || event.target.name == `fishingKey` || event.target.name == `luresKey` || event.target.name == `intKey`) && !event.target.disabled) { 100 | event.target.style.backgroundColor = `rgb(255, 219, 197)`; 101 | const activeKeyAnimation = (alter) => () => { 102 | if(alter) { 103 | event.target.style.backgroundColor = `rgb(255, 219, 197)`; 104 | } else { 105 | event.target.style.backgroundColor = `white`; 106 | } 107 | alter = !alter; 108 | } 109 | const flashKeyAnimation = setInterval(activeKeyAnimation(false), 300); 110 | event.target.addEventListener(`blur`, function bluring(event) { 111 | clearInterval(flashKeyAnimation) 112 | event.target.style.backgroundColor = `white`; 113 | event.target.removeEventListener(`blur`, bluring); 114 | event.target.removeEventListener(`keydown`, keyAssigning); 115 | }); 116 | 117 | event.target.addEventListener(`keydown`, keyAssigning); 118 | } 119 | }); 120 | 121 | this.dom.addEventListener('click', (event) => { 122 | 123 | if(event.target.id == "link") { 124 | ipcRenderer.send('open-link', (event.target.url)); 125 | } 126 | 127 | if(event.target.name == `bobberColor` || event.target.parentNode.name == `bobberColor`) { 128 | let bobberColorNode = this.dom.querySelector(`.bobberColorSwitch`); 129 | bobberColorNode.value = bobberColorNode.value == `red` ? `blue` : `red`; 130 | let eventDummy = {target: bobberColorNode}; 131 | saveSettings(eventDummy); 132 | this.reRender(); 133 | } 134 | 135 | if(event.target.name == `autoTh` || event.target.parentNode.name == `autoTh`) { 136 | let node = this.dom.querySelector(`.autoTh`); 137 | node.value = node.value ? false : true; 138 | let eventDummy = {target: node}; 139 | saveSettings(eventDummy); 140 | this.reRender(); 141 | } 142 | 143 | if(event.target.name == `multipleWindows` && event.target.checked == true) { 144 | this.onDx11(); 145 | } 146 | 147 | if((event.target.name == `lures` && event.target.checked) && (config.game == `Retail` || config.game == `Classic` || config.game == `Vanilla` || config.game == `Vanilla (splash)`)) { 148 | this.onLures(); 149 | } 150 | 151 | if(event.target.name == `whitelist` && event.target.checked == true) { 152 | this.onWhitelistWarn(); 153 | } 154 | 155 | if(event.target.name == 'advancedSettings') { 156 | this.onClick(this.config); 157 | } 158 | 159 | if(event.target.name == `fishingZone`) { 160 | this.onFishingZoneClick(); 161 | } 162 | }) 163 | } 164 | 165 | reRender() { 166 | this.dom.innerHTML = ``; 167 | this.dom.append(renderSettings(this.config)); 168 | } 169 | 170 | regOnClick(callback) { 171 | this.onClick = callback; 172 | } 173 | 174 | regOnDx11(callback) { 175 | this.onDx11 = callback; 176 | } 177 | 178 | regOnLures(callback) { 179 | this.onLures = callback; 180 | } 181 | 182 | regOnWhitelistWarn(callback) { 183 | this.onWhitelistWarn = callback; 184 | } 185 | 186 | regOnFishingZoneClick(callback) { 187 | this.onFishingZoneClick = callback; 188 | } 189 | 190 | regOnChatZoneClick(callback) { 191 | this.onChatZoneClick = callback; 192 | } 193 | 194 | regOnChange(callback) { 195 | this.onChange = callback; 196 | } 197 | } 198 | 199 | module.exports = Settings; 200 | -------------------------------------------------------------------------------- /app/ui/startButton.js: -------------------------------------------------------------------------------- 1 | const elt = require("./utils/elt.js"); 2 | 3 | class StartButton { 4 | constructor() { 5 | this.state = false; 6 | this.dom = elt( 7 | "button", 8 | { 9 | className: "startButton", 10 | onclick: (event) => { 11 | event.target.innerHTML = ``; 12 | if ((this.state = !this.state)) { 13 | this.onStart(); 14 | } else { 15 | this.onStop(); 16 | } 17 | 18 | if(this.state) { 19 | this.dom.style.backgroundImage = `url('img/stop.png')`; 20 | } else { 21 | this.dom.style.backgroundImage = `url('img/start.png')`; 22 | } 23 | }, 24 | }, 25 | ); 26 | } 27 | 28 | onError() { 29 | this.state = false; 30 | this.dom.style.backgroundImage = `url('img/start.png')`; 31 | } 32 | 33 | regOnStart(callback) { 34 | this.onStart = callback; 35 | } 36 | 37 | regOnStop(callback) { 38 | this.onStop = callback; 39 | } 40 | } 41 | 42 | module.exports = StartButton; 43 | -------------------------------------------------------------------------------- /app/ui/utils/elt.js: -------------------------------------------------------------------------------- 1 | function elt(type, props, ...children) { 2 | let dom = document.createElement(type); 3 | if (props) Object.assign(dom, props); 4 | for (let child of children) { 5 | if (typeof child != "string") dom.appendChild(child); 6 | else dom.appendChild(document.createTextNode(child)); 7 | } 8 | return dom; 9 | } 10 | 11 | module.exports = elt; 12 | -------------------------------------------------------------------------------- /app/ui/utils/wrapInLabel.js: -------------------------------------------------------------------------------- 1 | const elt = require("./elt.js"); 2 | 3 | const wrapInLabel = (name, inner, hint, classname) => { 4 | return elt( 5 | "label", 6 | {className: classname}, 7 | name, 8 | elt( 9 | "div", 10 | { className: "option" }, 11 | inner, 12 | hint ? (elt("img", { 13 | src: "./img/hint.png", 14 | className: "option_hint", 15 | title: hint, 16 | })) : `` 17 | ) 18 | ); 19 | }; 20 | 21 | module.exports = wrapInLabel; 22 | -------------------------------------------------------------------------------- /app/utils/eventLine.js: -------------------------------------------------------------------------------- 1 | class EventLine { 2 | constructor() { 3 | this.eventLine = []; 4 | } 5 | 6 | add(callback) { 7 | this.eventLine.push(callback); 8 | if (this.eventLine.length == 1) { 9 | callback(); 10 | } 11 | } 12 | 13 | remove() { 14 | this.eventLine.shift(); 15 | if (this.eventLine[0]) { 16 | this.eventLine[0](); 17 | } 18 | } 19 | } 20 | 21 | module.exports = EventLine; 22 | -------------------------------------------------------------------------------- /app/utils/fishingQuotes.json: -------------------------------------------------------------------------------- 1 | [ 2 | "'A bad day of fishing is better than a good day at work.' - Unknown", 3 | "'Fishing is a passion that can never be fully explained.' - Unknown ", 4 | "'Fishing provides that connection with the whole living world. It gives you the opportunity of being totally immersed, turning back into yourself in a good way.' - Ted Hughes", 5 | "'The solution to any problem—work, love, money, whatever—is to go fishing, and the worse the problem, the longer the trip should be.' - John Gierach", 6 | "'There are always new places to go fishing. For any fisherman, there's always a new place, always a new horizon.' - Jack Nicklaus", 7 | "'Fishing is not just a hobby; it's a state of mind.' - Mariano Rivera", 8 | "'Fishing is an affirmation of life's richness and our profound interconnectedness to the natural world.' - Carl Safina", 9 | "'Fishing is not just about catching fish; it's about understanding the delicate balance of nature.' - Aldo Leopold", 10 | "'Angling is a way of calming the soul and setting the spirit free.' - Dave Hughes", 11 | "'The act of fishing takes us to the hidden corners of the world, revealing the beauty that lies beneath the surface.' - James Prosek", 12 | "'The wise fisherman knows that satisfaction is not always measured in the size of the catch.' - Lao Tzu", 13 | "'There's certainly something in fishing that makes a man feel he is doing right; I can't explain it, but it's very pleasant.' - Norman Maclean", 14 | "'To fish is to hold a mirror to nature.' - Paul Schullery", 15 | "'The best way to catch a fish is to let him think he's escaping.' - Unknown", 16 | "'There is no such thing as too much equipment.' - Unknown (Every fisherman's mantra!)", 17 | "'Every time I cast my line, I hope to catch a moment with you.' - Lila Monroe", 18 | "'There he stands, draped in more equipment than a telephone lineman, trying to outwit an organism with a brain no bigger than a breadcrumb, and getting licked in the process.' - Paul O'Neil", 19 | "'The best time to go fishing is when you can get away.' - Robert Traver", 20 | "'Give a man a fish, and he has food for a day; teach him how to fish, and you can get rid of him for the entire weekend.' - Zenna Schaffer", 21 | "'Fishing is a quest for knowledge and wonder as much as a pursuit of fish; it is as much an acquaintance with beavers, dippers, and other fishermen as it is a challenge of trout.' - Paul Schullery", 22 | "'If you wish to fish, you must venture your bait.' - Latin Proverb", 23 | "'The fisherman is the biggest liar in the world. Except for writers, of course.' - Unknown", 24 | "'A bad day fishing still beats a good day working.' - Unknown", 25 | "'If wishes were fishes, we'd all cast nets.' - Scottish Proverb", 26 | "'A woman who has never seen her husband fishing doesn't know what a patient man she married!' - Unknown", 27 | "'Fish or cut bait.' - American Proverb", 28 | "'A fish and a guest smell after three days.' - English Proverb", 29 | "'The fish trap exists because of the fish. Once you've gotten the fish you can forget the trap.' - Chinese Proverb" 30 | ] 31 | -------------------------------------------------------------------------------- /app/utils/generateName.js: -------------------------------------------------------------------------------- 1 | const range = (start, end) => new Array(end - start).fill(true).map((a, i) => start + i); 2 | 3 | const generateName = (size) => { 4 | let combined = [...range(`a`.charCodeAt(), `z`.charCodeAt()), 5 | ...range(`A`.charCodeAt(), `Z`.charCodeAt()), 6 | ...range(`0`.charCodeAt(), `9`.charCodeAt())]; 7 | 8 | return new Array(size) 9 | .fill(true) 10 | .map(() => combined[Math.floor(Math.random() * (combined.length - 1))]) 11 | .map((random) => String.fromCharCode(random)) 12 | .join(``); 13 | }; 14 | 15 | module.exports = generateName; 16 | -------------------------------------------------------------------------------- /app/utils/getBitmap.js: -------------------------------------------------------------------------------- 1 | const Jimp = require(`jimp`); 2 | const path = require("path"); 3 | 4 | const once = (fn) => { 5 | let result; 6 | return (...args) => { 7 | if (!result) { 8 | return (result = fn(...args)); 9 | } else { 10 | return result instanceof Promise ? Promise.resolve(result) : result; 11 | } 12 | }; 13 | }; 14 | 15 | const getBitmap = async (bobberImgPath, reader = Jimp) => { 16 | const { bitmap } = await reader.read(path.join(__dirname, bobberImgPath)); 17 | return bitmap; 18 | }; 19 | 20 | const getBitmapOnce = once(getBitmap); 21 | 22 | const constructBitmap = ({ data, width, height }) => { 23 | let pixels = []; 24 | for (let y = 0, i = 0; y < height; y++) { 25 | for (let x = 0; x < width; x++, i += 4) { 26 | let r = data[i]; 27 | let g = data[i + 1]; 28 | let b = data[i + 2]; 29 | pixels.push({ 30 | pos: { x, y }, 31 | color: [r, g, b], 32 | }); 33 | } 34 | } 35 | return pixels; 36 | }; 37 | 38 | const getBitmapAsync = (event, path) => { 39 | return getBitmapOnce(path).then(constructBitmap); 40 | }; 41 | 42 | module.exports = getBitmapAsync; 43 | -------------------------------------------------------------------------------- /app/utils/handleSquirrel.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | function handleSquirrelEvent(app) { 3 | if (process.argv.length === 1) { 4 | return false; 5 | } 6 | 7 | const ChildProcess = require("child_process"); 8 | 9 | const appFolder = path.resolve(process.execPath, ".."); 10 | const rootAtomFolder = path.resolve(appFolder, ".."); 11 | const updateDotExe = path.resolve(path.join(rootAtomFolder, "Update.exe")); 12 | const exeName = path.basename(process.execPath); 13 | 14 | const spawn = function (command, args) { 15 | let spawnedProcess, error; 16 | 17 | try { 18 | spawnedProcess = ChildProcess.spawn(command, args, { detached: true }); 19 | } catch (error) {} 20 | 21 | return spawnedProcess; 22 | }; 23 | 24 | const spawnUpdate = function (args) { 25 | return spawn(updateDotExe, args); 26 | }; 27 | 28 | const squirrelEvent = process.argv[1]; 29 | switch (squirrelEvent) { 30 | case "--squirrel-install": 31 | case "--squirrel-updated": 32 | // Optionally do things such as: 33 | // - Add your .exe to the PATH 34 | // - Write to the registry for things like file associations and 35 | // explorer context menus 36 | 37 | // Install desktop and start menu shortcuts 38 | spawnUpdate(["--createShortcut", exeName]); 39 | 40 | setTimeout(app.quit, 1000); 41 | return true; 42 | 43 | case "--squirrel-uninstall": 44 | // Undo anything you did in the --squirrel-install and 45 | // --squirrel-updated handlers 46 | 47 | // Remove desktop and start menu shortcuts 48 | spawnUpdate(["--removeShortcut", exeName]); 49 | 50 | setTimeout(app.quit, 1000); 51 | return true; 52 | 53 | case "--squirrel-obsolete": 54 | // This is called on the outgoing version of your app before 55 | // we update to the new version - it's the opposite of 56 | // --squirrel-updated 57 | 58 | app.quit(); 59 | return true; 60 | } 61 | } 62 | 63 | module.exports = handleSquirrelEvent; 64 | -------------------------------------------------------------------------------- /app/utils/keySupport.js: -------------------------------------------------------------------------------- 1 | const supportedKeys = [ 2 | "backspace", 3 | "tab", 4 | "enter", 5 | "pause", 6 | "capsLock", 7 | "escape", 8 | "space", 9 | "pageUp", 10 | "pageDown", 11 | "end", 12 | "home", 13 | "left", 14 | "up", 15 | "right", 16 | "down", 17 | "printScreen", 18 | "insert", 19 | "delete", 20 | "0", 21 | "1", 22 | "2", 23 | "3", 24 | "4", 25 | "5", 26 | "6", 27 | "7", 28 | "8", 29 | "9", 30 | "a", 31 | "b", 32 | "c", 33 | "d", 34 | "e", 35 | "f", 36 | "g", 37 | "h", 38 | "i", 39 | "j", 40 | "k", 41 | "l", 42 | "m", 43 | "n", 44 | "o", 45 | "p", 46 | "q", 47 | "r", 48 | "s", 49 | "t", 50 | "u", 51 | "v", 52 | "w", 53 | "x", 54 | "y", 55 | "z", 56 | "num0", 57 | "num1", 58 | "num2", 59 | "num3", 60 | "num4", 61 | "num5", 62 | "num6", 63 | "num7", 64 | "num8", 65 | "num9", 66 | "num*", 67 | "num+", 68 | "num,", 69 | "num-", 70 | "num.", 71 | "num/", 72 | "f1", 73 | "f2", 74 | "f3", 75 | "f4", 76 | "f5", 77 | "f6", 78 | "f7", 79 | "f8", 80 | "f9", 81 | "f10", 82 | "f11", 83 | "f12", 84 | "f13", 85 | "f14", 86 | "f15", 87 | "f16", 88 | "f17", 89 | "f18", 90 | "f19", 91 | "f20", 92 | "f21", 93 | "f22", 94 | "f23", 95 | "f24", 96 | "numLock", 97 | "scrollLock", 98 | ";", 99 | "=", 100 | ",", 101 | "-", 102 | ".", 103 | "/", 104 | "`", 105 | "[", 106 | "\\", 107 | "]", 108 | "'" 109 | ]; 110 | 111 | 112 | module.exports = { 113 | isSupported(key) { 114 | return supportedKeys.includes(key); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/utils/logger.js: -------------------------------------------------------------------------------- 1 | const { getCurrentTime } = require("./time.js"); 2 | const { createWriteStream } = require('fs'); 3 | 4 | const createLog = (sendToWindow) => { 5 | let state = true; 6 | return { 7 | send(text, type = "black") { 8 | if(state) { 9 | const { hr, min, sec } = getCurrentTime(); 10 | text = `[${hr}:${min}:${sec}] ${text}`; 11 | sendToWindow({ text, type }); 12 | } 13 | }, 14 | 15 | msg(text) { 16 | sendToWindow({ text, type: "green" }); 17 | }, 18 | 19 | ok(text) { 20 | this.send(text, "green"); 21 | }, 22 | 23 | warn(text) { 24 | this.send(text, "orange"); 25 | }, 26 | 27 | err(text) { 28 | this.send(text, "red"); 29 | }, 30 | 31 | setState(value) { 32 | state = value; 33 | } 34 | }; 35 | }; 36 | 37 | const createIdLog = (log, id) => { 38 | let logData; 39 | if(process.env.NODE_ENV == `dev`) { 40 | logData = createWriteStream(`${__dirname}/../debug/log.txt`); 41 | } 42 | 43 | return Object.assign({}, log, { 44 | send(text, type) { 45 | if(process.env.NODE_ENV == `dev`) { 46 | const { hr, min, sec } = getCurrentTime(); 47 | logData.write(`[${hr}:${min}:${sec}] ${text}\n`); 48 | } 49 | log.send(`[WIN${id}] ${text}`, type); 50 | } 51 | }); 52 | }; 53 | 54 | 55 | module.exports = { 56 | createLog, 57 | createIdLog, 58 | }; 59 | -------------------------------------------------------------------------------- /app/utils/rgb.js: -------------------------------------------------------------------------------- 1 | const Vec = require("./vec.js"); 2 | 3 | const isInLimits = ({ x, y }, { width, height }) => { 4 | return x >= 0 && y >= 0 && x < width && y < height; 5 | }; 6 | 7 | const createRgb = ({ data, width, height }) => { 8 | let pixelData = Array.from(data.values()); 9 | let bitmap = []; 10 | for (let y = 0, i = 0; y < height; y++) { 11 | let row = []; 12 | for (let x = 0; x < width; x++, i += 4) { 13 | let r = pixelData[i]; 14 | let g = pixelData[i + 1]; 15 | let b = pixelData[i + 2]; 16 | row[x] = [r, g, b]; 17 | } 18 | bitmap[y] = row; 19 | } 20 | 21 | return { 22 | getBitmap() { 23 | return {data: Buffer.from(bitmap.flatMap((v) => v).flatMap(v => v)), width, height}; 24 | }, 25 | 26 | colorAt(pos) { 27 | if (isInLimits(pos, { width, height })) { 28 | return bitmap[pos.y][pos.x]; 29 | } else { 30 | return [0, 0, 0]; 31 | } 32 | }, 33 | 34 | saturate(rs, gs, bs) { 35 | bitmap = bitmap.map(y => y.map(([r, g, b]) => [r + rs, g + gs, b + bs])); 36 | }, 37 | 38 | cutOut(exception) { 39 | exception.forEach(({ x, y }) => { 40 | if(isInLimits({ x, y }, { width, height })) { 41 | bitmap[y][x] = [0, 0, 0]; 42 | } 43 | }) 44 | }, 45 | 46 | findColors({ isColor, atFirstMet, task, limit, direction, saveColor }) { 47 | let colors = []; 48 | 49 | if(!direction) direction = `normal`; 50 | 51 | switch (direction) { 52 | case `normal`: { 53 | for (let y = 0; y < height; y++) { 54 | for (let x = 0; x < width; x++) { 55 | let pos = new Vec(x, y); 56 | let color = bitmap[y][x]; 57 | if (isColor(color)) { 58 | if (limit != null) { 59 | limit--; 60 | if (limit < 0) { 61 | return null; 62 | } 63 | } 64 | 65 | if (!task || task(pos, color, this)) { 66 | if (atFirstMet) { 67 | if(saveColor) { 68 | return {pos, color} 69 | } else { 70 | return pos; 71 | } 72 | } else { 73 | if(saveColor) { 74 | colors.push({pos, color}) 75 | } else { 76 | colors.push(pos); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | break; 84 | } 85 | 86 | case `normalright`: { 87 | for (let y = 0; y < height; y++) { 88 | for (let x = width - 1; x > -1; x--) { 89 | let pos = new Vec(x, y); 90 | let color = bitmap[y][x]; 91 | if (isColor(color)) { 92 | if (limit != null) { 93 | limit--; 94 | if (limit < 0) { 95 | return null; 96 | } 97 | } 98 | 99 | if (!task || task(pos, color, this)) { 100 | if (atFirstMet) { 101 | if(saveColor) { 102 | return {pos, color} 103 | } else { 104 | return pos; 105 | } 106 | } else { 107 | if(saveColor) { 108 | colors.push({pos, color}) 109 | } else { 110 | colors.push(pos); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | } 117 | break; 118 | } 119 | 120 | case `reverse`: { 121 | for (let y = height - 1; y > -1; y--) { 122 | for (let x = 0; x < width; x++) { 123 | let pos = new Vec(x, y); 124 | let color = bitmap[y][x]; 125 | if (isColor(color)) { 126 | if (limit != null) { 127 | limit--; 128 | if (limit < 0) { 129 | return null; 130 | } 131 | } 132 | 133 | if (!task || task(pos, color, this)) { 134 | if (atFirstMet) { 135 | if(saveColor) { 136 | return {pos, color} 137 | } else { 138 | return pos; 139 | } 140 | } else { 141 | if(saveColor) { 142 | colors.push({pos, color}) 143 | } else { 144 | colors.push(pos); 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | break; 152 | } 153 | case `center`: { 154 | let center = {x: Math.floor(width / 2), y: Math.floor(height / 2)}; 155 | let stepDiffX = 1; 156 | let stepDiffY = 1; 157 | 158 | if(height > width) stepDiffX = width / height; 159 | if(width > height) stepDiffY = height / width; 160 | for (let step = 1; step < Math.floor(Math.max(height, width) / 2); step++) { 161 | for(let angle = 0; angle < Math.PI * 2; angle += ((Math.PI * 2 / 8) / step)) { 162 | let x = center.x + Math.round(Math.cos(angle) * (step * stepDiffX)); 163 | let y = center.y + Math.round(Math.sin(angle) * (step * stepDiffY)); 164 | 165 | let pos = new Vec(x, y); 166 | if(!bitmap[y] || !bitmap[y][x]) { 167 | continue; 168 | } 169 | let color = bitmap[y][x]; 170 | if (isColor(color)) { 171 | if (limit != null) { 172 | limit--; 173 | if (limit < 0) { 174 | return null; 175 | } 176 | } 177 | 178 | if (!task || task(pos, color, this)) { 179 | if (atFirstMet) { 180 | if(saveColor) { 181 | return {pos, color} 182 | } else { 183 | return pos; 184 | } 185 | } else { 186 | if(saveColor) { 187 | colors.push({pos, color}) 188 | } else { 189 | colors.push(pos); 190 | } 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | break; 198 | } 199 | } 200 | 201 | 202 | return colors.length ? colors : null; 203 | }, 204 | }; 205 | }; 206 | 207 | module.exports = createRgb; 208 | -------------------------------------------------------------------------------- /app/utils/tests/vec.test.js: -------------------------------------------------------------------------------- 1 | const Vec = require('../vec.js'); 2 | 3 | describe('Vec', () => { 4 | const vec = new Vec(10, 10); 5 | describe('plus(vec)', () => { 6 | it('Adds x, y of two vectors', () => { 7 | let result = vec.plus(new Vec(10, 10)); 8 | expect(result.x).toBe(20); 9 | expect(result.y).toBe(20); 10 | }) 11 | }); 12 | describe('get dist()', () => { 13 | it('Calculates distance from 0 to vector point using pythagorean theorem', () => { 14 | let result = Math.floor((new Vec(2, 2).dist) * 10); 15 | expect(result).toBe(28); 16 | }) 17 | }); 18 | describe('getPointsAround(size)', () => { 19 | it('Returns vectors around the vector at given size', () => { 20 | let result = vec.getPointsAround(); 21 | expect(result).toHaveLength(8); 22 | expect(result[0]).toMatchObject({ 23 | x: 9, 24 | y: 9 25 | }); 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /app/utils/tests/zone.test.js: -------------------------------------------------------------------------------- 1 | const Zone = require('../zone.js'); 2 | 3 | describe('Zone', () => { 4 | const zone = Zone.from({x: 10, y: 10, width: 10, height: 10}); 5 | 6 | describe('zone.toRel(relPoints)', () => { 7 | const relPoints = {x: .5, y: .5, width: .5, height: .5}; 8 | it('converts zone to the provided relative points (percentage)', () => { 9 | expect(zone.toRel(relPoints)).toMatchObject({ 10 | x: 5, 11 | y: 5, 12 | width: 5, 13 | height: 5 14 | }) 15 | }) 16 | }) 17 | }); 18 | -------------------------------------------------------------------------------- /app/utils/textReader.js: -------------------------------------------------------------------------------- 1 | const Jimp = require("jimp"); 2 | const { createWorker } = require("tesseract.js"); 3 | 4 | let worker; 5 | 6 | const setWorker = async (language) => { 7 | worker = await createWorker(language); 8 | }; 9 | 10 | const readTextFrom = async (buffer, scale) => { 11 | let img = await Jimp.read(buffer); 12 | if(process.env.NODE_ENV == `dev`) { 13 | const date = new Date() 14 | const name = `test-lootZone-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}.png` 15 | img.write(name); 16 | } 17 | img.greyscale().contrast(0.3).invert().scale(scale); 18 | let result = await worker.recognize(await img.getBase64Async(Jimp.MIME_PNG)); 19 | let words = result.data.words.map(({ text, baseline }) => ({ 20 | text, 21 | y: baseline.y0 / scale, 22 | })); 23 | return words; 24 | }; 25 | 26 | const getField = (attr) => (obj) => obj[attr]; 27 | const percentComparison = (first, second) => { 28 | if (second.length > first.length) { 29 | [first, second] = [second, first]; 30 | } 31 | 32 | let mainLetters = Array.from(new Set([...first])).map(letter => ({letter, index: []})); 33 | [...first].forEach(letter => { 34 | let prevIndexes = mainLetters.find((mainLetter) => mainLetter.letter == letter).index; 35 | let lastPrevIndex = prevIndexes[prevIndexes.length - 1]; 36 | let index = second.indexOf(letter, lastPrevIndex ? lastPrevIndex + 1 : 0); 37 | if(index != -1) { 38 | prevIndexes.push(index); 39 | } 40 | }); 41 | 42 | return mainLetters.flatMap(getField(`index`)).length / first.length * 100 43 | }; 44 | 45 | const sortWordsByItem = (words, lootWindow, isRetail) => { 46 | let itemHeight = lootWindow.itemHeight + (isRetail ? lootWindow.itemHeightAdd : 0); 47 | let gaps; 48 | if(isRetail) { 49 | gaps = new Array(4).fill(0).map((gap, i) => ({from: i == 0 ? lootWindow.itemHeight * (i + 1) : itemHeight * i + lootWindow.itemHeight, 50 | to: (i == 0 ? lootWindow.itemHeight * (i + 1) : itemHeight * i + lootWindow.itemHeight) + lootWindow.itemHeightAdd + (i + 1)})); 51 | } 52 | let selected = []; 53 | words.forEach((word) => { 54 | if (!word.text || word.text.length < 2) return; 55 | if(isRetail && gaps.some(gap => word.y > gap.from && word.y < gap.to)) return; 56 | let pos = selected[Math.floor(word.y / itemHeight)]; 57 | if (!pos) { 58 | selected[Math.floor(word.y / itemHeight)] = word.text; 59 | } else { 60 | selected[Math.floor(word.y / itemHeight)] += ` ${word.text}`; 61 | } 62 | }); 63 | return selected.flat(); 64 | }; 65 | 66 | module.exports = { 67 | setWorker, 68 | percentComparison, 69 | readTextFrom, 70 | sortWordsByItem, 71 | }; 72 | -------------------------------------------------------------------------------- /app/utils/time.js: -------------------------------------------------------------------------------- 1 | const convertMs = (ms) => { 2 | let sec = Math.floor(ms / 1000); 3 | let min = Math.floor(sec / 60); 4 | let hour = Math.floor(min / 60); 5 | 6 | switch (true) { 7 | case sec < 1: { 8 | return `${ms} ms`; 9 | } 10 | 11 | case min < 1: { 12 | return `${sec} sec`; 13 | } 14 | 15 | case hour < 1: { 16 | return `${min} min ${sec % 60} sec`; 17 | } 18 | } 19 | 20 | return `${hour} hour ${min % 60} min ${sec % 60} sec`; 21 | }; 22 | 23 | const getCurrentTime = () => { 24 | let date = new Date(); 25 | let times = { 26 | hr: date.getHours(), 27 | min: date.getMinutes(), 28 | sec: date.getSeconds(), 29 | }; 30 | for (let time of Object.keys(times)) { 31 | times[time] = times[time].toString(); 32 | if (times[time].length < 2) { 33 | times[time] = times[time].padStart(2, `0`); 34 | } 35 | } 36 | 37 | return times; 38 | }; 39 | 40 | 41 | const createTimer = (callback) => { 42 | let time = 0; 43 | let interval = callback(); 44 | return { 45 | start() { 46 | time = Date.now(); 47 | }, 48 | isElapsed() { 49 | return Date.now() - time > interval; 50 | }, 51 | update(cb) { 52 | interval = cb ? cb() : callback(); 53 | this.start(); 54 | }, 55 | timeRemains() { 56 | return interval - (Date.now() - time); 57 | } 58 | } 59 | }; 60 | 61 | 62 | module.exports = { 63 | convertMs, 64 | getCurrentTime, 65 | createTimer 66 | }; 67 | -------------------------------------------------------------------------------- /app/utils/vec.js: -------------------------------------------------------------------------------- 1 | module.exports = class Vec { 2 | constructor(x, y) { 3 | this.x = x; 4 | this.y = y; 5 | } 6 | 7 | plus(vec) { 8 | return new Vec(this.x + vec.x, this.y + vec.y); 9 | } 10 | 11 | get dist() { 12 | return Math.sqrt( 13 | Math.pow(Math.abs(this.x), 2) + Math.pow(Math.abs(this.y), 2) 14 | ); 15 | } 16 | 17 | isEqual(vec) { 18 | if(this.x === vec.x && this.y === vec.y) { 19 | return true 20 | } 21 | } 22 | 23 | getPointsAround(size = 1) { 24 | let pointsAround = []; 25 | for (let y = this.y - size; y <= this.y + size; y++) { 26 | for (let x = this.x - size; x <= this.x + size; x++) { 27 | let point = new Vec(x, y); 28 | if (point.x == this.x && point.y == this.y) { 29 | continue; 30 | } else { 31 | pointsAround.push(point); 32 | } 33 | } 34 | } 35 | 36 | return pointsAround; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /app/utils/zone.js: -------------------------------------------------------------------------------- 1 | class Zone { 2 | constructor({ x, y, width, height }) { 3 | this.x = x; 4 | this.y = y; 5 | this.width = width; 6 | this.height = height; 7 | } 8 | 9 | toRel({ x, y, width, height }) { 10 | return new Zone({ 11 | x: Math.floor(this.width * x), 12 | y: Math.floor(this.height * y), 13 | width: Math.floor(this.width * width), 14 | height: Math.floor(this.height * height), 15 | }); 16 | } 17 | 18 | static from(scr) { 19 | return new Zone(scr); 20 | } 21 | } 22 | 23 | module.exports = Zone; 24 | -------------------------------------------------------------------------------- /app/wins/advsettings/img/hint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/wins/advsettings/img/hint.png -------------------------------------------------------------------------------- /app/wins/advsettings/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/wins/advsettings/img/icon.png -------------------------------------------------------------------------------- /app/wins/advsettings/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/wins/advsettings/main.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow, ipcMain, dialog } = require("electron"); 2 | const { readFileSync, writeFileSync } = require("fs"); 3 | const path = require("path"); 4 | 5 | const getJson = (path) => JSON.parse(readFileSync(path), "utf8"); 6 | 7 | const showWarning = (win, warning, title) => { 8 | return result = dialog.showMessageBoxSync(win, { 9 | type: "warning", 10 | title: `Warning!`, 11 | message: warning, 12 | buttons: [`Ok`] 13 | }); 14 | }; 15 | 16 | const createAdvSettings = (appPath) => { 17 | let win = new BrowserWindow({ 18 | title: 'Advanced Settings', 19 | width: 455, 20 | height: 627, 21 | show: false, 22 | resizable: false, 23 | webPreferences: { 24 | contextIsolation: false, 25 | nodeIntegration: true, 26 | }, 27 | icon: "./app/img/icon.png" 28 | }); 29 | 30 | win.loadFile(path.join(__dirname, `index.html`)); 31 | win.removeMenu(); 32 | win.on("closed", () => { 33 | ipcMain.removeAllListeners(`advanced-click`); 34 | ipcMain.removeAllListeners(`unsupported-key-win`); 35 | ipcMain.removeAllListeners(`lures-warn`); 36 | ipcMain.removeAllListeners(`whitelist-warn`); 37 | ipcMain.removeAllListeners(`start-by-fishing-key-warn`); 38 | ipcMain.removeHandler(`advanced-defaults`); 39 | ipcMain.removeHandler(`get-game-config`); 40 | }); 41 | 42 | win.once("ready-to-show", () => { 43 | //win.openDevTools({mode: `detach`}); 44 | win.show(); 45 | }); 46 | 47 | ipcMain.on("advanced-click", (event, newConfig) => { 48 | if(newConfig) { 49 | const settings = getJson(path.join(appPath, "./config/settings.json")); 50 | const config = getJson(path.join(appPath, "./config/bot.json")); 51 | config.patch[settings.game] = newConfig; 52 | writeFileSync(path.join(appPath, "./config/bot.json"), JSON.stringify(config)); 53 | } 54 | win.close(); 55 | }); 56 | 57 | ipcMain.on("unsupported-key-win", () => { 58 | showWarning(win, `The key you pressed is not supported by AutoFish.`); 59 | }) 60 | 61 | ipcMain.on("lures-warn", () => { 62 | showWarning(win, `Don't forget to make a macros as described in the Guide and assign it to the same key you have assigned for Lures Key.`); 63 | }); 64 | 65 | ipcMain.on("start-by-fishing-key-warn", () => { 66 | showWarning(win, `The key you assigned for Fishing Key will be blocked on your machine and if used will start the bot even if you are not in the game!\n\nTurn this feature on only after you have configured all the settings and turn it off before making any changes.`); 67 | }); 68 | 69 | ipcMain.on("whitelist-warn", () => { 70 | showWarning(win, `Turn off AutoLoot option in the game.\n\nTurn off UI addons and UI scaling in the game.\n\nTurn on Open Loot Window at Mouse option in the game. (optional for Retail, Vanilla, Vanilla(splash), but if you do then check respective option in this section).\n\nBest works with standard resolutions like: 1366x768, 1920x1080 and 3840x2160.`); 71 | }); 72 | 73 | ipcMain.handle("advanced-defaults", () => { 74 | const settings = getJson(path.join(appPath, "./config/settings.json")); 75 | const defaults = getJson(path.join(appPath, "./config/defaults.json")); 76 | return defaults.patch[settings.game]; 77 | }) 78 | 79 | ipcMain.handle("get-game-config", () => { 80 | const settings = getJson(path.join(appPath, "./config/settings.json")); 81 | const config = getJson(path.join(appPath, "./config/bot.json")); 82 | return config.patch[settings.game]; 83 | }); 84 | 85 | return win; 86 | }; 87 | 88 | module.exports = createAdvSettings; 89 | -------------------------------------------------------------------------------- /app/wins/fishingzone/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | Chat Zone 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/wins/fishingzone/fishing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/wins/fishingzone/img/bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/wins/fishingzone/img/bad.png -------------------------------------------------------------------------------- /app/wins/fishingzone/img/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/wins/fishingzone/img/cancel.png -------------------------------------------------------------------------------- /app/wins/fishingzone/img/good.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/wins/fishingzone/img/good.png -------------------------------------------------------------------------------- /app/wins/fishingzone/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/wins/fishingzone/img/icon.png -------------------------------------------------------------------------------- /app/wins/fishingzone/img/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/app/wins/fishingzone/img/ok.png -------------------------------------------------------------------------------- /app/wins/fishingzone/main.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow, ipcMain } = require("electron"); 2 | const path = require("path"); 3 | const { screen, Region } = require("@nut-tree/nut-js"); 4 | const fishZone = require("../../bot/fishingZone.js"); 5 | 6 | const promisify = fn => (...args) => new Promise((resolve, reject) => 7 | fn(...args, (err, data) => (err ? reject(err) : resolve(data)))); 8 | 9 | const getDataFrom = async (zone) => { 10 | let grabbed = await(await screen.grabRegion(new Region(zone.x, zone.y, zone.width, zone.height))).toRGB(); 11 | return grabbed; 12 | }; 13 | 14 | const createFishingZone = ({pos, screenSize, type, config, settings, scale}, finished) => { 15 | let win = new BrowserWindow({ 16 | title: `Fishing Zone`, 17 | x: Math.round(pos.x), 18 | y: Math.round(pos.y), 19 | width: Math.round(pos.width), 20 | height: Math.round(pos.height), 21 | show: true, 22 | resizable: true, 23 | opacity: 0.3, 24 | frame: false, 25 | webPreferences: { 26 | contextIsolation: false, 27 | nodeIntegration: true, 28 | }, 29 | icon: `./img/icon.png` 30 | }); 31 | 32 | win.loadFile(path.join(__dirname, `${type == `relZone` ? `fishing.html` : type == `chatZone` ? `chat.html` : `detect.html`}`)); 33 | 34 | win.once("ready-to-show", () => { 35 | win.show(); 36 | }); 37 | 38 | win.once('closed', () => { 39 | ipcMain.removeAllListeners(`fishingZone-cancel`); 40 | ipcMain.removeAllListeners(`fishingZone-ok`); 41 | ipcMain.removeHandler(`fishingZone-check`); 42 | ipcMain.removeHandler(`fishingZone-bobberColor`); 43 | }); 44 | 45 | ipcMain.handle(`fishingZone-bobberColor`, () => settings.bobberColor) 46 | 47 | ipcMain.on(`fishingZone-cancel`, () => { 48 | finished(); 49 | win.close(); 50 | }); 51 | 52 | ipcMain.handle(`fishingZone-check`, async () => { 53 | let pos = win.getBounds(); 54 | 55 | pos.x = pos.x * scale; 56 | pos.y = pos.y * scale; 57 | pos.width = pos.width * scale; 58 | pos.height = pos.height * scale; 59 | 60 | if(pos.x < 0) pos.x = 0; 61 | if(pos.y < 0) pos.y = 0; 62 | if(pos.x + pos.width > screenSize.width) pos.width = screenSize.width - pos.x; 63 | if(pos.y + pos.height > screenSize.height) pos.height = screenSize.height - pos.y; 64 | 65 | if(type != `relZone`) return; 66 | 67 | win.setOpacity(0); 68 | let zone = fishZone( 69 | getDataFrom, 70 | pos, 71 | screenSize, 72 | settings, 73 | config 74 | ); 75 | let colorPercent = await zone.checkColor(); 76 | win.setOpacity(0.3); 77 | 78 | if(colorPercent > 25) { 79 | return `rgb(255, 70, 68)`; 80 | } else { 81 | return `rgb(70, 255, 68)`; 82 | } 83 | }); 84 | 85 | ipcMain.on(`fishingZone-ok`, () => { 86 | win.close(); 87 | finished(null, win.getBounds()); 88 | }) 89 | 90 | return win; 91 | }; 92 | 93 | 94 | module.exports = promisify(createFishingZone); 95 | -------------------------------------------------------------------------------- /app/wins/fishingzone/rendererChat.js: -------------------------------------------------------------------------------- 1 | const elt = require("../../ui/utils/elt.js"); 2 | const { ipcRenderer } = require('electron'); 3 | const buttonOk = elt(`div`, {className: `buttonOk`}, `Save`); 4 | const buttonCancel = elt(`div`, {className: `buttonCancel`}, `Cancel`); 5 | 6 | buttonOk.addEventListener(`click`, () => { 7 | ipcRenderer.send(`fishingZone-ok`); 8 | }); 9 | 10 | buttonCancel.addEventListener(`click`, () => { 11 | ipcRenderer.send(`fishingZone-cancel`); 12 | }); 13 | 14 | document.body.append(buttonOk); 15 | document.body.append(buttonCancel); 16 | -------------------------------------------------------------------------------- /app/wins/fishingzone/rendererFishing.js: -------------------------------------------------------------------------------- 1 | const elt = require("../../ui/utils/elt.js"); 2 | const { ipcRenderer } = require('electron'); 3 | const buttonOk = elt(`div`, {className: `buttonOk`}, `Save`); 4 | const buttonCancel = elt(`div`, {className: `buttonCancel`}, `Cancel`); 5 | /* 6 | const buttonCheck = elt(`div`, {className: `buttonCheck`}, `Check`); 7 | */ 8 | ipcRenderer.invoke(`fishingZone-bobberColor`) 9 | .then((color) => { 10 | document.body.append(elt('p', {style: `font-size: 6vw; margin-bottom: 0; `}, `Water is ${color == `red` ? `blue` : `red`}`)); 11 | document.body.append(elt('p', {style: `font-size: 6vw`}, `Avoid elements with ${color} color!`)); 12 | }); 13 | 14 | 15 | buttonOk.addEventListener(`click`, () => { 16 | ipcRenderer.send(`fishingZone-ok`); 17 | }); 18 | 19 | buttonCancel.addEventListener(`click`, () => { 20 | ipcRenderer.send(`fishingZone-cancel`); 21 | }); 22 | /* 23 | buttonCheck.addEventListener(`click`, async () => { 24 | let result = await ipcRenderer.invoke(`fishingZone-check`); 25 | document.body.style.backgroundColor = result; 26 | setTimeout(() => { 27 | document.body.style.backgroundColor = `white`; 28 | }, 1000); 29 | }) 30 | */ 31 | 32 | document.body.append(buttonOk); 33 | document.body.append(buttonCancel); 34 | /* 35 | document.body.append(buttonCheck); 36 | */ 37 | -------------------------------------------------------------------------------- /app/wins/fishingzone/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | flex-flow: column; 4 | justify-content: center; 5 | align-items: center; 6 | position: absolute; 7 | border: 1px solid black; 8 | margin: 0; 9 | top: 0; 10 | bottom: 0; 11 | right: 0; 12 | left: 0; 13 | -webkit-app-region: drag; 14 | text-align: center; 15 | font: menu; 16 | font-size: 2em; 17 | } 18 | 19 | div { 20 | -webkit-app-region: no-drag; 21 | border: 1px solid black; 22 | width: 60px; 23 | border-radius: 15px; 24 | margin: 15px; 25 | padding: 5px; 26 | font-size: 0.5em; 27 | color: white; 28 | text-align: center; 29 | cursor: pointer; 30 | } 31 | 32 | .buttonOk { 33 | background-color: green; 34 | position: absolute; 35 | right: 0; 36 | bottom: 0; 37 | } 38 | 39 | .buttonCancel { 40 | background-color: red; 41 | position: absolute; 42 | right: 0; 43 | top: 0; 44 | } 45 | 46 | .buttonCheck { 47 | background-color: blue; 48 | position: absolute; 49 | left: 0; 50 | bottom: 0; 51 | } 52 | 53 | .buttonCheck:active { 54 | cursor: progress; 55 | } 56 | -------------------------------------------------------------------------------- /guide_img/Premium_UI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/Premium_UI.jpg -------------------------------------------------------------------------------- /guide_img/UI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/UI.jpg -------------------------------------------------------------------------------- /guide_img/additionalactions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/additionalactions.jpg -------------------------------------------------------------------------------- /guide_img/advancedSettings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/advancedSettings.jpg -------------------------------------------------------------------------------- /guide_img/aiResponse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/aiResponse.jpg -------------------------------------------------------------------------------- /guide_img/arduino.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/arduino.jpg -------------------------------------------------------------------------------- /guide_img/chooseFishKey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/chooseFishKey.jpg -------------------------------------------------------------------------------- /guide_img/chooseFishZone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/chooseFishZone.jpg -------------------------------------------------------------------------------- /guide_img/chooseGame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/chooseGame.jpg -------------------------------------------------------------------------------- /guide_img/icons/bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/icons/bad.png -------------------------------------------------------------------------------- /guide_img/icons/good.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/icons/good.png -------------------------------------------------------------------------------- /guide_img/icons/question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/icons/question.png -------------------------------------------------------------------------------- /guide_img/intkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/intkey.jpg -------------------------------------------------------------------------------- /guide_img/lures.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/lures.jpg -------------------------------------------------------------------------------- /guide_img/mammoth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/mammoth.jpg -------------------------------------------------------------------------------- /guide_img/motiondetection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/motiondetection.jpg -------------------------------------------------------------------------------- /guide_img/rngMove.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/rngMove.jpg -------------------------------------------------------------------------------- /guide_img/sound-detection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/sound-detection.jpg -------------------------------------------------------------------------------- /guide_img/streaming.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/streaming.jpg -------------------------------------------------------------------------------- /guide_img/tmmenu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/tmmenu.jpg -------------------------------------------------------------------------------- /guide_img/tmtoken.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbots/AutoFish/8f21eff011ac763b20557240f37f4bbe10d27fb5/guide_img/tmtoken.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AutoFish", 3 | "version": "2.8.3", 4 | "description": "An easy-to-use fishing bot for games with wow-like fishing logic.", 5 | "main": "./app/main.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "startf": "electron-forge start", 9 | "test": "jest", 10 | "package": "electron-forge package", 11 | "premake": "node random.js", 12 | "make": "electron-forge make" 13 | }, 14 | "keywords": [ 15 | "wow", 16 | "wow-classic", 17 | "wotlk-wow", 18 | "wotlk", 19 | "retail", 20 | "tbc", 21 | "vanilla", 22 | "mop", 23 | "bot", 24 | "fishing", 25 | "fishbot", 26 | "fishing bot", 27 | "fishing script" 28 | ], 29 | "author": "jsbots", 30 | "license": "MIT", 31 | "dependencies": { 32 | "@nut-tree/nut-js": "^2.3.0", 33 | "electron-squirrel-startup": "^1.0.0", 34 | "jimp": "^0.16.1", 35 | "keysender": "^2.3.0", 36 | "tesseract.js": "^5.0.2" 37 | }, 38 | "devDependencies": { 39 | "@electron-forge/cli": "^6.0.4", 40 | "@electron-forge/maker-deb": "^6.0.4", 41 | "@electron-forge/maker-rpm": "^6.0.4", 42 | "@electron-forge/maker-squirrel": "^6.0.4", 43 | "@electron-forge/maker-zip": "^6.0.4", 44 | "electron": "^20.0.0", 45 | "electron-rebuild": "^3.2.9", 46 | "eslint": "^8.43.0", 47 | "jest": "^28.1.0", 48 | "prettier": "^2.8.8" 49 | }, 50 | "config": { 51 | "forge": "./forge.config.js" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | --------------------------------------------------------------------------------