├── .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 |
--------------------------------------------------------------------------------