├── icon.png ├── readme └── img.png ├── requirements.txt ├── i18n ├── en_US │ └── LC_MESSAGES │ │ ├── ok.mo │ │ └── ok.po └── zh_CN │ └── LC_MESSAGES │ ├── ok.mo │ └── ok.po ├── main.py ├── src ├── __init__.py ├── char │ ├── Baizhi.py │ ├── Calcharo.py │ ├── Taoqi.py │ ├── Sanhua.py │ ├── HavocRover.py │ ├── Yuanwu.py │ ├── Chixia.py │ ├── Verina.py │ ├── Danjin.py │ ├── Jianxin.py │ ├── Yinlin.py │ ├── Changli.py │ ├── CharFactory.py │ ├── Encore.py │ ├── CharSkillButton.py │ ├── Jinhsi.py │ └── BaseChar.py ├── task │ ├── AutoCombatTask.py │ ├── AutoPickTask.py │ ├── SkipDialogTask.py │ ├── FarmEchoTask.py │ ├── FarmWorldBossTask.py │ └── BaseCombatTask.py └── combat │ └── CombatCheck.py ├── assets ├── 4_png.rf.180bb3f2bed38134b1d0bba90c3fcdb0.png ├── e521850f-00_37_38_458841_Encore_liberation_0-274_original_png.rf.e7703de2285a6b2c70567e891804c760.png ├── 01304a45-10_34_04_580378_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win_WnYCyUT_png.rf.7b5dfc4585195e1d846574b715468897.png ├── 0ab28c72-00_54_43_431654_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_usg73Cm_png.rf.124dc89f9b3816ecb0ce5a97c95475dc.png ├── 154eb284-17_01_16_406889_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_6yGu616_png.rf.12ebc1f4e7e5208bcbf2c7f878f691aa.png ├── 221458a6-13_25_04_962031_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_XYXLS23_png.rf.281b46703c1e3f6bd436750f6aa82cf2.png ├── 3b991d47-10_18_37_465122_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_3Hw8AdB_png.rf.7b75bb60b5b041f3c6f36e735ca3bc19.png ├── 55617870-00_55_49_536245_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_NSeTZ2S_png.rf.5947b06a7caca9b89c2e06ec7ceab8fa.png ├── 5841f84c-17_04_26_738022_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_TIDxIl1_png.rf.97a22c8e01f1fd88debc20c4c903b8b9.png ├── 64425cad-10_44_19_536997_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win_BLAxtp1_png.rf.d37538db2b2d15d6aad459dd08bb348d.png ├── 934f0ca6-17_53_47_840123_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_aJQVINN_png.rf.a107256237035eec3be4125f726b8b73.png ├── 9beed513-16_15_23_246388_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_a4tQhDg_png.rf.4e79b52b95ad46f47bc470f069a65b29.png ├── 9dd77a5d-16_57_31_014010_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_RVvCfQe_png.rf.e8397fbd133d485459bb8c30fc37353c.png ├── 9ed8b373-00_57_50_813872_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_3jLoRJh_png.rf.170fe2ae11c1d8058d8d3e4b2361357a.png ├── 9f04db98-16_20_58_825243_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_VuLpBVc_png.rf.a16ce4854c42840f44a6c0a04d44c83c.png ├── cef1b3dd-00_55_22_640294_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_NtclVno_png.rf.0eb178e9974bb9135567f91780337d55.png ├── WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_133336_1_False_original_png.rf.a2b55fa3583fef2081d49205ae9c643b.png ├── WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_1841174_1_False_original-1-_png.rf.e6c4a5734163badfbfa76780248cd2f8.png ├── WindowsGraphicsCaptureMethod_1920x1080_title_None_Client-Win64-Shipping-exe_1920x1080_329010_1_False_original_png.rf.b6f619b8fe6f0e1e8a0993a5e61e2801.png ├── WindowsGraphicsCaptureMethod_2560x1440_title_None_Client-Win64-Shipping-exe_3840x2160_465786_1_False_original_png.rf.015495d17edfa6c8bf29047cfcc19d6d.png ├── WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_10361910_1_False_original_png.rf.244ee2bd0b4de93caac3066adaae0b75.png ├── WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_12847792_1_False_original_png.rf.abeaa08198ff5a77fb70340952ba23f3.png ├── WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_1577828_1_False_original_png.rf.3ed83cbce453e93e1027f37184cff0a5.png ├── WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_4720836_1_False_original_png.rf.935bb51188f6bc516efc5a57b52fe8ad.png ├── WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_7476412_1_False_original_png.rf.a21fbbbefc2fa48f56917d19a6a443b2.png ├── 17_11_07_938917_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_396094_1_False_original_png.rf.f921e6fb49de02d7ef17e7f5f6a976c4.png └── _annotations.coco.json ├── requirements-dev.txt ├── main_debug.py ├── main_debug_console.py ├── .github ├── workflows │ ├── needs-reply.yml │ ├── needs-reply-remove.yml │ └── build.yml └── ISSUE_TEMPLATE │ └── 报告bug-.md ├── .gitignore ├── README.md ├── config.py ├── main_debug.spec ├── main.spec └── LICENSE.txt /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/icon.png -------------------------------------------------------------------------------- /readme/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/readme/img.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/requirements.txt -------------------------------------------------------------------------------- /i18n/en_US/LC_MESSAGES/ok.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/i18n/en_US/LC_MESSAGES/ok.mo -------------------------------------------------------------------------------- /i18n/zh_CN/LC_MESSAGES/ok.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/i18n/zh_CN/LC_MESSAGES/ok.mo -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from ok.OK import OK 3 | 4 | config = config 5 | ok = OK(config) 6 | ok.start() 7 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | text_white_color = { 2 | 'r': (255, 255), # Red range 3 | 'g': (255, 255), # Green range 4 | 'b': (255, 255) # Blue range 5 | } 6 | -------------------------------------------------------------------------------- /assets/4_png.rf.180bb3f2bed38134b1d0bba90c3fcdb0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/4_png.rf.180bb3f2bed38134b1d0bba90c3fcdb0.png -------------------------------------------------------------------------------- /src/char/Baizhi.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Baizhi(BaseChar): 5 | 6 | def count_base_priority(self): 7 | return - 1 8 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | adbutils 2 | numpy 3 | PyDirectInput 4 | pywin32 5 | typing-extensions 6 | opencv-python 7 | PySide6 8 | PySide6-Fluent-Widgets 9 | psutil 10 | py7zr 11 | 12 | rapidocr_openvino 13 | -------------------------------------------------------------------------------- /main_debug.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from ok.OK import OK 3 | 4 | config = config 5 | config['debug'] = True 6 | # config['click_screenshots_folder'] = "click_screenshots" # debug用 点击后截图文件夹] 7 | ok = OK(config) 8 | ok.start() 9 | -------------------------------------------------------------------------------- /main_debug_console.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from ok.OK import OK 3 | 4 | config = config 5 | config['debug'] = True 6 | config['use_gui'] = False 7 | config['onetime_tasks'][1].enable() 8 | ok = OK(config) 9 | ok.start() 10 | -------------------------------------------------------------------------------- /assets/e521850f-00_37_38_458841_Encore_liberation_0-274_original_png.rf.e7703de2285a6b2c70567e891804c760.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/e521850f-00_37_38_458841_Encore_liberation_0-274_original_png.rf.e7703de2285a6b2c70567e891804c760.png -------------------------------------------------------------------------------- /assets/01304a45-10_34_04_580378_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win_WnYCyUT_png.rf.7b5dfc4585195e1d846574b715468897.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/01304a45-10_34_04_580378_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win_WnYCyUT_png.rf.7b5dfc4585195e1d846574b715468897.png -------------------------------------------------------------------------------- /assets/0ab28c72-00_54_43_431654_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_usg73Cm_png.rf.124dc89f9b3816ecb0ce5a97c95475dc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/0ab28c72-00_54_43_431654_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_usg73Cm_png.rf.124dc89f9b3816ecb0ce5a97c95475dc.png -------------------------------------------------------------------------------- /assets/154eb284-17_01_16_406889_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_6yGu616_png.rf.12ebc1f4e7e5208bcbf2c7f878f691aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/154eb284-17_01_16_406889_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_6yGu616_png.rf.12ebc1f4e7e5208bcbf2c7f878f691aa.png -------------------------------------------------------------------------------- /assets/221458a6-13_25_04_962031_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_XYXLS23_png.rf.281b46703c1e3f6bd436750f6aa82cf2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/221458a6-13_25_04_962031_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_XYXLS23_png.rf.281b46703c1e3f6bd436750f6aa82cf2.png -------------------------------------------------------------------------------- /assets/3b991d47-10_18_37_465122_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_3Hw8AdB_png.rf.7b75bb60b5b041f3c6f36e735ca3bc19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/3b991d47-10_18_37_465122_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_3Hw8AdB_png.rf.7b75bb60b5b041f3c6f36e735ca3bc19.png -------------------------------------------------------------------------------- /assets/55617870-00_55_49_536245_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_NSeTZ2S_png.rf.5947b06a7caca9b89c2e06ec7ceab8fa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/55617870-00_55_49_536245_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_NSeTZ2S_png.rf.5947b06a7caca9b89c2e06ec7ceab8fa.png -------------------------------------------------------------------------------- /assets/5841f84c-17_04_26_738022_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_TIDxIl1_png.rf.97a22c8e01f1fd88debc20c4c903b8b9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/5841f84c-17_04_26_738022_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_TIDxIl1_png.rf.97a22c8e01f1fd88debc20c4c903b8b9.png -------------------------------------------------------------------------------- /assets/64425cad-10_44_19_536997_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win_BLAxtp1_png.rf.d37538db2b2d15d6aad459dd08bb348d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/64425cad-10_44_19_536997_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win_BLAxtp1_png.rf.d37538db2b2d15d6aad459dd08bb348d.png -------------------------------------------------------------------------------- /assets/934f0ca6-17_53_47_840123_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_aJQVINN_png.rf.a107256237035eec3be4125f726b8b73.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/934f0ca6-17_53_47_840123_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_aJQVINN_png.rf.a107256237035eec3be4125f726b8b73.png -------------------------------------------------------------------------------- /assets/9beed513-16_15_23_246388_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_a4tQhDg_png.rf.4e79b52b95ad46f47bc470f069a65b29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/9beed513-16_15_23_246388_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_a4tQhDg_png.rf.4e79b52b95ad46f47bc470f069a65b29.png -------------------------------------------------------------------------------- /assets/9dd77a5d-16_57_31_014010_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_RVvCfQe_png.rf.e8397fbd133d485459bb8c30fc37353c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/9dd77a5d-16_57_31_014010_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_RVvCfQe_png.rf.e8397fbd133d485459bb8c30fc37353c.png -------------------------------------------------------------------------------- /assets/9ed8b373-00_57_50_813872_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_3jLoRJh_png.rf.170fe2ae11c1d8058d8d3e4b2361357a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/9ed8b373-00_57_50_813872_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_3jLoRJh_png.rf.170fe2ae11c1d8058d8d3e4b2361357a.png -------------------------------------------------------------------------------- /assets/9f04db98-16_20_58_825243_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_VuLpBVc_png.rf.a16ce4854c42840f44a6c0a04d44c83c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/9f04db98-16_20_58_825243_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_VuLpBVc_png.rf.a16ce4854c42840f44a6c0a04d44c83c.png -------------------------------------------------------------------------------- /assets/cef1b3dd-00_55_22_640294_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_NtclVno_png.rf.0eb178e9974bb9135567f91780337d55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/cef1b3dd-00_55_22_640294_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_NtclVno_png.rf.0eb178e9974bb9135567f91780337d55.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_133336_1_False_original_png.rf.a2b55fa3583fef2081d49205ae9c643b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_133336_1_False_original_png.rf.a2b55fa3583fef2081d49205ae9c643b.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_1841174_1_False_original-1-_png.rf.e6c4a5734163badfbfa76780248cd2f8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_1841174_1_False_original-1-_png.rf.e6c4a5734163badfbfa76780248cd2f8.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_1920x1080_title_None_Client-Win64-Shipping-exe_1920x1080_329010_1_False_original_png.rf.b6f619b8fe6f0e1e8a0993a5e61e2801.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_1920x1080_title_None_Client-Win64-Shipping-exe_1920x1080_329010_1_False_original_png.rf.b6f619b8fe6f0e1e8a0993a5e61e2801.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_2560x1440_title_None_Client-Win64-Shipping-exe_3840x2160_465786_1_False_original_png.rf.015495d17edfa6c8bf29047cfcc19d6d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_2560x1440_title_None_Client-Win64-Shipping-exe_3840x2160_465786_1_False_original_png.rf.015495d17edfa6c8bf29047cfcc19d6d.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_10361910_1_False_original_png.rf.244ee2bd0b4de93caac3066adaae0b75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_10361910_1_False_original_png.rf.244ee2bd0b4de93caac3066adaae0b75.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_12847792_1_False_original_png.rf.abeaa08198ff5a77fb70340952ba23f3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_12847792_1_False_original_png.rf.abeaa08198ff5a77fb70340952ba23f3.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_1577828_1_False_original_png.rf.3ed83cbce453e93e1027f37184cff0a5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_1577828_1_False_original_png.rf.3ed83cbce453e93e1027f37184cff0a5.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_4720836_1_False_original_png.rf.935bb51188f6bc516efc5a57b52fe8ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_4720836_1_False_original_png.rf.935bb51188f6bc516efc5a57b52fe8ad.png -------------------------------------------------------------------------------- /assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_7476412_1_False_original_png.rf.a21fbbbefc2fa48f56917d19a6a443b2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_7476412_1_False_original_png.rf.a21fbbbefc2fa48f56917d19a6a443b2.png -------------------------------------------------------------------------------- /assets/17_11_07_938917_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_396094_1_False_original_png.rf.f921e6fb49de02d7ef17e7f5f6a976c4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjj5855/ok-wuthering-waves/master/assets/17_11_07_938917_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_396094_1_False_original_png.rf.f921e6fb49de02d7ef17e7f5f6a976c4.png -------------------------------------------------------------------------------- /.github/workflows/needs-reply.yml: -------------------------------------------------------------------------------- 1 | name: Close old issues that need reply 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Close old issues that need reply 12 | uses: dwieeb/needs-reply@v2 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-label: needs-reply -------------------------------------------------------------------------------- /src/char/Calcharo.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Calcharo(BaseChar): 5 | def do_perform(self): 6 | if self.has_intro: 7 | self.logger.debug('Calcharo wait intro animation') 8 | self.sleep(1) 9 | self.task.wait_in_team_and_world(time_out=3, raise_if_not_found=False) 10 | self.check_combat() 11 | super().do_perform() 12 | -------------------------------------------------------------------------------- /src/char/Taoqi.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Taoqi(BaseChar): 5 | def do_perform(self): 6 | if self.has_intro: 7 | self.wait_down() 8 | self.continues_normal_attack(2.5) 9 | else: 10 | self.click_liberation() 11 | self.click_resonance() 12 | self.click_echo(sleep_time=0.1) 13 | self.switch_next_char() 14 | -------------------------------------------------------------------------------- /src/char/Sanhua.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Sanhua(BaseChar): 5 | def do_perform(self): 6 | self.click_liberation() 7 | if self.click_resonance()[0]: 8 | return self.switch_next_char() 9 | if self.click_echo(): 10 | return self.switch_next_char() 11 | 12 | self.task.mouse_down() 13 | self.sleep(.7) 14 | self.task.mouse_up() 15 | self.sleep(0.3) 16 | self.switch_next_char() 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/报告bug-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '报告Bug ' 3 | about: 遇到的问题 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: ok-oldking 7 | 8 | --- 9 | 10 | 描述错误: 11 | [请清晰简洁地描述错误是什么] 12 | 13 | 复现步骤 按照以下步骤复现行为: 14 | 15 | 1. 点击 ‘…’ 16 | 2. 向下滚动到 ‘…’ 17 | 3. 看到错误 18 | 19 | 截图 : 20 | 如果适用,添加截图以帮助解释您的问题。 21 | 22 | 脚本软件版本: 23 | [如]1.1.1 24 | 25 | windows操作系统: 26 | [例如] Windows 11 Pro 23H2 22631.3593 27 | 28 | 模拟器版本: 29 | [如使用模拟器]MuMu模拟器12 V3.8.25 (2927) 30 | 31 | 上传日志: 32 | 将脚本安装目录的logs,screenshots,config,click_screenshots打包上传附件 33 | -------------------------------------------------------------------------------- /src/char/HavocRover.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class HavocRover(BaseChar): 5 | def do_perform(self): 6 | if self.is_forte_full() and self.liberation_available(): 7 | self.logger.info(f'forte_full, and liberation_available, heavy attack') 8 | self.wait_down() 9 | self.heavy_attack() 10 | self.sleep(0.4) 11 | self.click_liberation() 12 | if not self.click_resonance()[0]: 13 | self.click_echo() 14 | self.switch_next_char() 15 | -------------------------------------------------------------------------------- /src/char/Yuanwu.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Yuanwu(BaseChar): 5 | 6 | def count_resonance_priority(self): 7 | return 0 8 | 9 | def count_echo_priority(self): 10 | return 0 11 | 12 | def count_base_priority(self): 13 | return -1 14 | 15 | def do_perform(self): 16 | self.click_liberation(con_less_than=1) 17 | if self.is_forte_full(): 18 | self.send_resonance_key(down_time=0.6, post_sleep=0.2) 19 | self.click_echo() 20 | self.switch_next_char() 21 | -------------------------------------------------------------------------------- /src/char/Chixia.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Chixia(BaseChar): 5 | 6 | def __init__(self, *args): 7 | super().__init__(*args) 8 | self.bullets = 0 9 | 10 | # def do_perform(self): 11 | # if self.resonance_available(): 12 | # if self.bullets > 35: 13 | # self.task.send_key_down(self.get_resonance_key()) 14 | # self.sleep(3) 15 | # self.click 16 | # else: 17 | # self.task.send_key_up(self.get_resonance_key()) 18 | # self.switch_next_char() 19 | -------------------------------------------------------------------------------- /src/char/Verina.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Verina(BaseChar): 5 | 6 | def do_perform(self): 7 | self.click_liberation() 8 | if self.flying(): 9 | return self.switch_next_char() 10 | if self.click_resonance(send_click=False)[0]: 11 | return self.switch_next_char() 12 | if self.click_echo(): 13 | self.heavy_attack() 14 | return self.switch_next_char() 15 | self.heavy_attack() 16 | self.switch_next_char() 17 | 18 | def count_base_priority(self): 19 | return - 1 20 | -------------------------------------------------------------------------------- /src/char/Danjin.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Danjin(BaseChar): 5 | 6 | def __init__(self, *args): 7 | super().__init__(*args) 8 | # self.bullets = 0 9 | 10 | def count_resonance_priority(self): 11 | return 0 12 | 13 | def do_perform(self): 14 | self.click_liberation() 15 | if self.is_forte_full(): 16 | self.heavy_attack() 17 | self.sleep(0.2) 18 | elif self.click_echo(): 19 | pass 20 | else: 21 | self.task.send_key(self.get_resonance_key()) 22 | self.switch_next_char() 23 | -------------------------------------------------------------------------------- /src/char/Jianxin.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Jianxin(BaseChar): 5 | def do_perform(self): 6 | if self.has_intro: 7 | self.sleep(0.8) 8 | # if self.liberation_available(): 9 | # self.click_liberation() 10 | # self.sleep(2) 11 | if self.is_forte_full(): 12 | self.task.mouse_down() 13 | self.sleep(5.6) 14 | self.task.mouse_up() 15 | if self.resonance_available(): 16 | self.click_resonance() 17 | if self.echo_available(): 18 | self.sleep(0.3) 19 | self.click_echo() 20 | self.sleep(0.3) 21 | self.switch_next_char() 22 | -------------------------------------------------------------------------------- /.github/workflows/needs-reply-remove.yml: -------------------------------------------------------------------------------- 1 | name: Remove needs-reply label 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | if: | 12 | github.event.comment.author_association != 'OWNER' && 13 | github.event.comment.author_association != 'COLLABORATOR' 14 | steps: 15 | - name: Remove needs-reply label 16 | uses: octokit/request-action@v2.x 17 | continue-on-error: true 18 | with: 19 | route: DELETE /repos/:repository/issues/:issue/labels/:label 20 | repository: ${{ github.repository }} 21 | issue: ${{ github.event.issue.number }} 22 | label: needs-reply 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | updates/ 2 | tesseract 3 | models 4 | fonts 5 | _internal/ 6 | # Byte-compiled / optimized / DLL files 7 | .idea/ 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | configs/ 12 | ok/ 13 | ok 14 | *.exe 15 | md5.txt 16 | update.bat 17 | click_screenshots/ 18 | test_scripts/ 19 | screenshots/ 20 | thread_dumps.txt 21 | # C extensions 22 | *.so 23 | *.bat 24 | 25 | # Distribution / packaging 26 | .Python 27 | test/ 28 | logs/ 29 | build/ 30 | develop-eggs/ 31 | dist/ 32 | downloads/ 33 | eggs/ 34 | .eggs/ 35 | lib/ 36 | lib64/ 37 | parts/ 38 | sdist/ 39 | var/ 40 | wheels/ 41 | share/python-wheels/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | venv/ 47 | 48 | # PyInstaller 49 | # Usually these files are written by a python script from a template 50 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 51 | *.manifest 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | autohelper 57 | working_images/ 58 | -------------------------------------------------------------------------------- /src/task/AutoCombatTask.py: -------------------------------------------------------------------------------- 1 | from ok.logging.Logger import get_logger 2 | from ok.task.TriggerTask import TriggerTask 3 | from src.task.BaseCombatTask import BaseCombatTask, NotInCombatException 4 | 5 | logger = get_logger(__name__) 6 | 7 | 8 | class AutoCombatTask(BaseCombatTask, TriggerTask): 9 | 10 | def __init__(self): 11 | super().__init__() 12 | self.trigger_interval = 0.2 13 | self.name = "Auto Combat" 14 | self.description = "Enable auto combat in Abyss, Game World etc" 15 | 16 | def run(self): 17 | while self.in_combat(): 18 | try: 19 | logger.debug(f'autocombat loop {self.chars}') 20 | self.get_current_char().perform() 21 | except NotInCombatException as e: 22 | logger.info(f'auto_combat_task_out_of_combat {e}') 23 | if self.debug: 24 | self.screenshot(f'auto_combat_task_out_of_combat {e}') 25 | break 26 | 27 | def trigger(self): 28 | if self.in_combat(): 29 | self.load_chars() 30 | return True 31 | -------------------------------------------------------------------------------- /src/char/Yinlin.py: -------------------------------------------------------------------------------- 1 | from src.char.BaseChar import BaseChar 2 | 3 | 4 | class Yinlin(BaseChar): 5 | def do_perform(self): 6 | if self.has_intro: 7 | self.sleep(0.4) 8 | liberation = self.click_liberation() 9 | if self.is_forte_full(): 10 | if not self.has_intro and not liberation: 11 | self.normal_attack() 12 | self.heavy_attack() 13 | self.sleep(0.4) 14 | elif self.click_resonance(send_click=False)[0]: 15 | self.sleep(0.1) 16 | elif self.echo_available(): 17 | echo_key = self.get_echo_key() 18 | self.sleep(0.1) 19 | self.task.send_key_down(echo_key) 20 | self.sleep(.6) 21 | return self.switch_next_char(post_action=self.echo_post_action) 22 | else: 23 | self.heavy_attack() 24 | self.switch_next_char() 25 | 26 | def count_base_priority(self): 27 | return 2 28 | 29 | def count_forte_priority(self): 30 | return 20 31 | 32 | def count_liberation_priority(self): 33 | return 0 34 | 35 | def count_echo_priority(self): 36 | return 1 37 | 38 | def echo_post_action(self): # hold down the echo for 1 seconds and switch and then release the echo key 39 | self.task.send_key_up(self.get_echo_key()) 40 | self.sleep(0.01) 41 | -------------------------------------------------------------------------------- /src/task/AutoPickTask.py: -------------------------------------------------------------------------------- 1 | from ok.feature.FindFeature import FindFeature 2 | from ok.logging.Logger import get_logger 3 | from ok.task.TriggerTask import TriggerTask 4 | 5 | logger = get_logger(__name__) 6 | 7 | 8 | class AutoPickTask(TriggerTask, FindFeature): 9 | 10 | def __init__(self): 11 | super().__init__() 12 | self.name = "Auto Pick" 13 | self.description = "Auto Pick Flowers in Game World" 14 | 15 | def run(self): 16 | self.send_key('f') 17 | self.sleep(0.2) 18 | self.send_key('f') 19 | self.sleep(0.2) 20 | self.send_key('f') 21 | self.sleep(0.2) 22 | 23 | def trigger(self): 24 | f_search_box = self.get_box_by_name('pick_up_f') 25 | f_search_box = f_search_box.copy(x_offset=-f_search_box.width / 2, 26 | width_offset=f_search_box.width, 27 | height_offset=f_search_box.height * 4, 28 | y_offset=-f_search_box.height / 2, 29 | name='search_dialog') 30 | if f := self.find_one('pick_up_f', box=f_search_box, 31 | threshold=0.8): 32 | dialog_search = f.copy(x_offset=f.width * 2, width_offset=f.width * 2, height_offset=f.height * 2, 33 | y_offset=-f.height, 34 | name='search_dialog') 35 | return self.find_one('dialog_3_dots', box=dialog_search, 36 | threshold=0.8) is None 37 | -------------------------------------------------------------------------------- /src/char/Changli.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from src.char.BaseChar import BaseChar, Priority 4 | 5 | 6 | class Changli(BaseChar): 7 | 8 | def __init__(self, *args): 9 | super().__init__(*args) 10 | self.enhanced_normal = False 11 | self.last_e = 0 12 | 13 | def reset_state(self): 14 | self.enhanced_normal = False 15 | 16 | def do_get_switch_priority(self, current_char: BaseChar, has_intro=False): 17 | if time.time() - self.last_e < 3: 18 | self.logger.info( 19 | f'switch priority MIN because e not finished') 20 | return Priority.MIN 21 | else: 22 | return super().do_get_switch_priority(current_char, has_intro) 23 | 24 | def do_perform(self): 25 | # self.logger.debug( 26 | # f'Encore_perform_{self.has_intro}_{self.echo_available()}_{self.resonance_available()}_{self.liberation_available()}') 27 | if self.has_intro or self.enhanced_normal: 28 | self.normal_attack() 29 | self.sleep(0.5) 30 | self.enhanced_normal = False 31 | if self.is_forte_full(): 32 | self.heavy_attack(0.8) 33 | return self.switch_next_char() 34 | if self.click_liberation(): 35 | self.heavy_attack(0.8) 36 | return self.switch_next_char() 37 | elif self.resonance_available(): 38 | self.send_resonance_key() 39 | self.enhanced_normal = True 40 | self.normal_attack() 41 | elif self.click_echo(1.5): 42 | pass 43 | else: 44 | self.normal_attack() 45 | self.logger.info('Changli nothing is available') 46 | self.switch_next_char() 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![icon](icon.png) 2 | 3 | ## OK-WW 4 | * Automation for Wuthering Waves using Computer Vision, Auto Combat 5 | * 点击[releases](https://github.com/ok-oldking/ok-wuthering-waves/releases), 下载7z压缩包, 解压缩双击运行.exe 6 | * 下载有问题的, 也可加入腾讯QQ频道,[OK-WW](https://pd.qq.com/s/2jhl3oogp) 7 | * QQ水群,只聊游戏, 不下载不讨论软件: [970523295](https://qm.qq.com/q/qMezq2IDGU) 8 | 9 | 10 | ### 有多强? 11 | 12 | 1. 4K分辨率流畅运行,支持所有16:9分辨率,1600x900以上, 1280x720不支持是因为鸣潮bug, 它的1280x720并不是1280x720 13 | 2. 可后台运行,可窗口化,可全屏,屏幕缩放比例无要求 14 | 3. 自动战斗比大多数玩家手操都强, 深渊可满星, 演示视频: [今汐12秒轴](https://www.bilibili.com/video/BV1Hx4y1t7NP/) 15 | 4. 无需安装Cuda之类, 基本不占用显卡资源, 性能优化到支持自动战斗10毫秒左右的响应时间 16 | 5. 可高度自定义角色出招逻辑(动态合轴) [角色列表](src/char) 17 | 18 | ### 出现问题请检查 19 | 20 | 1. 关闭windows HDR, 护眼低蓝光模式, 游戏使用默认亮度, 关闭显卡滤镜,等一切改变游戏颜色的功能 21 | 2. 所有角色必须装备主声骸, 暂时不支持卡卡罗, 会跳出战斗 22 | 3. 把下载目录和解压目录, 添加到杀毒软件白名单. 23 | 4. 不要直接解压在QQ下载文件夹里运行, 不要放中文目录 24 | 5. OK-WW没更新最新版的, 更新最新版 25 | 6. 如果手动改过鸣潮或者鸣潮启动器的DPI设置, 重置 26 | 7. 最好升级Win10最新版(至少版本20348以上)或者Win11 27 | 28 | Demonstration: [https://youtu.be/N32I1aMfdqQ](https://youtu.be/N32I1aMfdqQ) 29 | 30 | * Farm Boss Echo (Dreamless, Jue and World Bosses) 31 | * Auto Combat (Beats 90% players for [Fully Supported Characters](src/char)) 32 | * Auto Skip Dialogs in Quests 33 | * Supports All Game Languages 34 | ![img.png](readme/img.png) 35 | 36 | ### How to Run 37 | 38 | * Download the 7z from [releases](https://github.com/ok-oldking/ok-wuthering-waves/releases), extract and run the exe 39 | * May need to add the app folder to Windows Defender white list. 40 | * Game must be a 16:9 ratio like 1920x1080, 3840x2160, lowest supported resolution is 1600*900 41 | * Can run while game is in background, but not minimized 42 | 43 | ### Development 44 | 45 | use Python 3.11, other versions might work but not tested 46 | 47 | ``` 48 | pip install -r requirements.txt 49 | python main_debug.py 50 | ``` 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /i18n/en_US/LC_MESSAGES/ok.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: 1.0\n" 5 | "Report-Msgid-Bugs-To: you@example.com\n" 6 | "Last-Translator: you@example.com\n" 7 | "Language-Team: English\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | 12 | msgid "(1-6) Important, Choose which level to farm, lower levels might not produce a echo" 13 | msgstr "" 14 | 15 | msgid "Boss1" 16 | msgstr "" 17 | 18 | msgid "Boss2" 19 | msgstr "" 20 | 21 | msgid "Boss3" 22 | msgstr "" 23 | 24 | msgid "Choose Forward for Dreamless, Backward for Jue" 25 | msgstr "" 26 | 27 | msgid "Click Start at the Entrance(Dreamless, Jue)" 28 | msgstr "" 29 | 30 | msgid "Click Start in Game World" 31 | msgstr "" 32 | 33 | msgid "Echo Key" 34 | msgstr "" 35 | 36 | msgid "Entrance Direction" 37 | msgstr "" 38 | 39 | msgid "Farm Echo in Dungeon" 40 | msgstr "" 41 | 42 | msgid "Farm World Boss(Must Drop a WayPoint on the Boss First)" 43 | msgstr "" 44 | 45 | msgid "Game Hotkey Config" 46 | msgstr "" 47 | 48 | msgid "In Game Hotkey for Skills" 49 | msgstr "" 50 | 51 | msgid "Level" 52 | msgstr "" 53 | 54 | msgid "Liberation Key" 55 | msgstr "" 56 | 57 | msgid "Repeat Farm Count" 58 | msgstr "" 59 | 60 | msgid "Resonance Key" 61 | msgstr "" 62 | 63 | msgid "Auto Combat" 64 | msgstr "" 65 | 66 | msgid "Backward" 67 | msgstr "" 68 | 69 | msgid "Bell-Borne Geochelone" 70 | msgstr "" 71 | 72 | msgid "Crownless" 73 | msgstr "" 74 | 75 | msgid "Enable auto combat in Abyss, Game World etc" 76 | msgstr "" 77 | 78 | msgid "Feilian Beringal" 79 | msgstr "" 80 | 81 | msgid "Forward" 82 | msgstr "" 83 | 84 | msgid "Impermanence Heron" 85 | msgstr "" 86 | 87 | msgid "Inferno Rider" 88 | msgstr "" 89 | 90 | msgid "Lampylumen Myriad" 91 | msgstr "" 92 | 93 | msgid "Mech Abomination" 94 | msgstr "" 95 | 96 | msgid "Mourning Aix" 97 | msgstr "" 98 | 99 | msgid "N/A" 100 | msgstr "" 101 | 102 | msgid "Skip Dialog during Quests" 103 | msgstr "" 104 | 105 | msgid "Tempest Mephis" 106 | msgstr "" 107 | 108 | msgid "Thundering Mephis" 109 | msgstr "" 110 | 111 | msgid "Combat Count" 112 | msgstr "" 113 | 114 | msgid "Echo Count" 115 | msgstr "" 116 | 117 | msgid "Log" 118 | msgstr "" 119 | 120 | msgid "Auto Pick" 121 | msgstr "" 122 | 123 | msgid "Auto Pick Flowers in Game World" 124 | msgstr "" 125 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Windows Executable 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | build: 11 | name: Build exe with PyInstaller 12 | runs-on: windows-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | name: Checkout code 16 | with: 17 | fetch-depth: 0 # Important: fetch all history for all tags and branches 18 | 19 | - name: Get Changes between Tags 20 | id: changes 21 | uses: simbo/changes-between-tags-action@v1 22 | with: 23 | validate-tag: false 24 | 25 | - name: Get tag name 26 | id: tagName 27 | uses: olegtarasov/get-tag@v2.1.3 28 | 29 | - name: Set up Python 30 | uses: actions/setup-python@v2 31 | with: 32 | python-version: '3.11' # Use the version of Python you need 33 | 34 | - name: Install Dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install pyinstaller # Add other dependencies if needed 38 | pip install -r requirements.txt 39 | 40 | - name: Build Executable 41 | run: | 42 | echo "tag: ${{ steps.changes.outputs.tag }}" 43 | echo "changes: ${{ steps.changes.outputs.changes }}" 44 | echo ${{ github.sha }} > Release.txt 45 | (Get-Content config.py) -replace 'version = "v\d+\.\d+\.\d+"', 'version = "${{ steps.tagName.outputs.tag }}"' | Set-Content config.py 46 | pyinstaller main.spec 47 | python -m ok.update.gen_md5 .\dist\bundle 48 | mv dist/bundle ok-ww 49 | 7z a -t7z -r "ok-ww-release-${{ steps.tagName.outputs.tag }}.7z" "ok-ww" 50 | 51 | shell: pwsh 52 | 53 | - name: Create Release 54 | id: create_release 55 | uses: actions/create-release@v1 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | with: 59 | tag_name: ${{ github.ref }} 60 | release_name: Release ${{ github.ref }} 61 | body: | 62 | Updates: 63 | ${{ steps.changes.outputs.changes }} 64 | draft: false 65 | prerelease: true 66 | 67 | - name: upload-win 68 | uses: actions/upload-release-asset@v1 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | with: 72 | upload_url: ${{ steps.create_release.outputs.upload_url }} 73 | asset_path: ./ok-ww-release-${{ steps.tagName.outputs.tag }}.7z 74 | asset_name: ok-ww-release-${{ steps.tagName.outputs.tag }}.7z 75 | asset_content_type: application/zip 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /i18n/zh_CN/LC_MESSAGES/ok.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: 1.0\n" 8 | "Report-Msgid-Bugs-To: you@example.com\n" 9 | "Last-Translator: you@example.com\n" 10 | "Language-Team: English\n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=UTF-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | 15 | msgid "(1-6) Important, Choose which level to farm, lower levels might not produce a echo" 16 | msgstr "(1-6) 重要, 选择你对应世界等级可以产出声骸的难度, 从上到下为1-6" 17 | 18 | msgid "Boss1" 19 | msgstr "第1个Boss" 20 | 21 | msgid "Boss2" 22 | msgstr "第2个Boss" 23 | 24 | msgid "Boss3" 25 | msgstr "第3个Boss" 26 | 27 | msgid "Choose Forward for Dreamless, Backward for Jue" 28 | msgstr "无妄者选前进, 角选后退" 29 | 30 | msgid "Click Start at the Entrance(Dreamless, Jue)" 31 | msgstr "在入口点击(无妄者, 角)" 32 | 33 | msgid "Click Start in Game World" 34 | msgstr "在游戏世界点击开始" 35 | 36 | msgid "Echo Key" 37 | msgstr "声骸按键(默认q)" 38 | 39 | msgid "Entrance Direction" 40 | msgstr "入口方向" 41 | 42 | msgid "Farm Echo in Dungeon" 43 | msgstr "刷副本Boss声骸" 44 | 45 | msgid "Farm World Boss(Must Drop a WayPoint on the Boss First)" 46 | msgstr "刷大世界4C Boss声骸(先在要刷的Boss脸上放锚点)" 47 | 48 | msgid "Game Hotkey Config" 49 | msgstr "游戏快捷键设置" 50 | 51 | msgid "In Game Hotkey for Skills" 52 | msgstr "声骸, 共鸣技能, 共鸣解放" 53 | 54 | msgid "Level" 55 | msgstr "等级" 56 | 57 | msgid "Liberation Key" 58 | msgstr "共鸣解放快捷键(默认r)" 59 | 60 | msgid "Repeat Farm Count" 61 | msgstr "刷多少次" 62 | 63 | msgid "Resonance Key" 64 | msgstr "共鸣技能快捷键(默认e)" 65 | 66 | msgid "Auto Combat" 67 | msgstr "自动战斗" 68 | 69 | msgid "Backward" 70 | msgstr "后退" 71 | 72 | msgid "Bell-Borne Geochelone" 73 | msgstr "乌龟" 74 | 75 | msgid "Crownless" 76 | msgstr "无冠者" 77 | 78 | msgid "Enable auto combat in Abyss, Game World etc" 79 | msgstr "在大世界,深渊,无音区等开启自动战斗" 80 | 81 | msgid "Feilian Beringal" 82 | msgstr "猴子" 83 | 84 | msgid "Forward" 85 | msgstr "前进" 86 | 87 | msgid "Impermanence Heron" 88 | msgstr "黑鸟" 89 | 90 | msgid "Inferno Rider" 91 | msgstr "摩托车" 92 | 93 | msgid "Lampylumen Myriad" 94 | msgstr "辉萤军势" 95 | 96 | msgid "Mech Abomination" 97 | msgstr "机器人" 98 | 99 | msgid "Mourning Aix" 100 | msgstr "哀声鸷" 101 | 102 | msgid "N/A" 103 | msgstr "不刷" 104 | 105 | msgid "Skip Dialog during Quests" 106 | msgstr "任务跳过对话" 107 | 108 | msgid "Tempest Mephis" 109 | msgstr "第一个雷Boss" 110 | 111 | msgid "Thundering Mephis" 112 | msgstr "第二个雷Boss" 113 | 114 | msgid "Combat Count" 115 | msgstr "战斗次数" 116 | 117 | msgid "Echo Count" 118 | msgstr "获得声骸个数" 119 | 120 | msgid "Log" 121 | msgstr "日志" 122 | 123 | msgid "Auto Pick" 124 | msgstr "自动拾取" 125 | 126 | msgid "Auto Pick Flowers in Game World" 127 | msgstr "大世界自动拾取" 128 | -------------------------------------------------------------------------------- /src/char/CharFactory.py: -------------------------------------------------------------------------------- 1 | from src.char.Baizhi import Baizhi 2 | from src.char.Calcharo import Calcharo 3 | from src.char.Changli import Changli 4 | from src.char.CharSkillButton import is_float 5 | from src.char.Chixia import Chixia 6 | from src.char.Danjin import Danjin 7 | from src.char.Jinhsi import Jinhsi 8 | from src.char.Yuanwu import Yuanwu 9 | 10 | 11 | def get_char_by_pos(task, box, index): 12 | from src.char.Verina import Verina 13 | from src.char.Yinlin import Yinlin 14 | from src.char.Taoqi import Taoqi 15 | from src.char.BaseChar import BaseChar 16 | from src.char.HavocRover import HavocRover 17 | from src.char.Sanhua import Sanhua 18 | from src.char.Jianxin import Jianxin 19 | from src.char.Encore import Encore 20 | char_dict = { 21 | 'char_yinlin': {'cls': Yinlin, 'res_cd': 12, 'echo_cd': 15}, 22 | 'char_verina': {'cls': Verina, 'res_cd': 12, 'echo_cd': 20}, 23 | 'char_taoqi': {'cls': Taoqi, 'res_cd': 15, 'echo_cd': 20}, 24 | 'char_rover': {'cls': HavocRover, 'res_cd': 12, 'echo_cd': 20}, 25 | 'char_encore': {'cls': Encore, 'res_cd': 10, 'echo_cd': 20}, 26 | 'char_jianxin': {'cls': Jianxin, 'res_cd': 12, 'echo_cd': 20}, 27 | 'char_sanhua': {'cls': Sanhua, 'res_cd': 10, 'echo_cd': 20}, 28 | 'char_jinhsi': {'cls': Jinhsi, 'res_cd': 3, 'echo_cd': 20}, 29 | 'char_yuanwu': {'cls': Yuanwu, 'res_cd': 3, 'echo_cd': 20}, 30 | 'chang_changli': {'cls': Changli, 'res_cd': 12, 'echo_cd': 20}, 31 | 'char_chixia': {'cls': Chixia, 'res_cd': 9, 'echo_cd': 20}, 32 | 'char_danjin': {'cls': Danjin, 'res_cd': 9999999, 'echo_cd': 20}, 33 | 'char_baizhi': {'cls': Baizhi, 'res_cd': 16, 'echo_cd': 20}, 34 | 'char_calcharo': {'cls': Calcharo, 'res_cd': 99999, 'echo_cd': 20} 35 | } 36 | highest_confidence = 0 37 | info = None 38 | for char_name, char_info in char_dict.items(): 39 | feature = task.find_one(char_name, box=box, threshold=0.7) 40 | if feature: 41 | task.log_info(f'found char {char_name} {feature.confidence} {highest_confidence}') 42 | if feature and feature.confidence > highest_confidence: 43 | highest_confidence = feature.confidence 44 | info = char_info 45 | if info is not None: 46 | cls = info.get('cls') 47 | return cls(task, index, info.get('res_cd'), info.get('echo_cd')) 48 | task.log_info(f'could not find char {info} {highest_confidence}') 49 | has_cd = task.ocr(box=box) 50 | if has_cd and is_float(has_cd[0].name): 51 | task.log_info(f'found char {has_cd[0]} wait and reload') 52 | task.next_frame() 53 | return get_char_by_pos(task, box, index) 54 | if task.debug: 55 | task.screenshot(f'could not find char {index}') 56 | return BaseChar(task, index) 57 | -------------------------------------------------------------------------------- /src/char/Encore.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from src.char.BaseChar import BaseChar, Priority 4 | 5 | 6 | class Encore(BaseChar): 7 | 8 | def __init__(self, *args): 9 | super().__init__(*args) 10 | self.last_heavy = 0 11 | self.liberation_time = 0 12 | self.last_resonance = 0 13 | 14 | def still_in_liberation(self): 15 | return time.time() - self.liberation_time < 10 16 | 17 | def do_perform(self): 18 | target_low_con = False 19 | if self.has_intro: 20 | self.sleep(0.7) 21 | self.wait_down() 22 | elif self.can_resonance_step2(4): 23 | if self.click_resonance()[0]: 24 | self.logger.info('try Encore resonance_step2 success') 25 | self.sleep(0.3) 26 | else: 27 | self.task.wait_until(self.resonance_available, time_out=1) 28 | wait_success = self.click_resonance()[0] 29 | self.logger.info(f'try Encore resonance_step2 wait_success:{wait_success}') 30 | 31 | if self.still_in_liberation(): 32 | # if time.time() - self.liberation_time > 7.5 and self.is_forte_full(): 33 | # self.heavy_attack() 34 | # self.last_heavy = time.time() 35 | # else: 36 | target_low_con = True 37 | self.n4() 38 | elif self.click_resonance()[0]: 39 | self.logger.debug('click_resonance') 40 | self.last_resonance = time.time() 41 | pass 42 | elif self.click_liberation(): 43 | self.liberation_time = time.time() 44 | self.n4() 45 | target_low_con = True 46 | elif self.echo_available(): 47 | self.logger.debug('click_echo') 48 | self.click_echo(duration=1.5) 49 | else: 50 | self.logger.info('Encore nothing is available') 51 | self.switch_next_char(target_low_con=target_low_con) 52 | 53 | def count_liberation_priority(self): 54 | return 40 55 | 56 | def count_resonance_priority(self): 57 | return 40 58 | 59 | def count_echo_priority(self): 60 | return 40 61 | 62 | def can_resonance_step2(self, delay=3): 63 | return time.time() - self.last_resonance < delay 64 | 65 | def do_get_switch_priority(self, current_char: BaseChar, has_intro=False): 66 | if time.time() - self.last_heavy < 3: 67 | return Priority.MIN 68 | elif self.still_in_liberation() or self.can_resonance_step2(): 69 | self.logger.info( 70 | f'switch priority MIN because still in liberation') 71 | return Priority.MAX + 1 72 | else: 73 | return super().do_get_switch_priority(current_char, has_intro) 74 | 75 | def n4(self, duration=2.0): 76 | duration = 2.6 if self.click_resonance()[0] else 2.3 77 | if time.time() - self.liberation_time < 6 or not self.is_forte_full(): 78 | self.continues_normal_attack(duration=duration) 79 | if not self.still_in_liberation(): 80 | self.click_resonance() 81 | else: 82 | self.heavy_attack() 83 | self.logger.info('encore heavy') 84 | self.last_heavy = time.time() 85 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ok.util.path import get_path_in_package 4 | from src.task.AutoCombatTask import AutoCombatTask 5 | from src.task.AutoPickTask import AutoPickTask 6 | from src.task.FarmEchoTask import FarmEchoTask 7 | from src.task.FarmWorldBossTask import FarmWorldBossTask 8 | from src.task.SkipDialogTask import AutoDialogTask 9 | 10 | version = "v0.0.11" 11 | 12 | 13 | def calculate_pc_exe_path(running_path): 14 | return running_path 15 | 16 | 17 | config = { 18 | 'debug': False, # Optional, default: False 19 | 'use_gui': True, 20 | 'config_folder': 'configs', 21 | 'gui_icon': get_path_in_package(__file__, 'icon.png'), 22 | 'ocr': { 23 | 'lib': 'RapidOCR' 24 | }, 25 | # required if using feature detection 26 | 'template_matching': { 27 | 'coco_feature_json': os.path.join('assets', '_annotations.coco.json'), 28 | 'default_horizontal_variance': 0.002, 29 | 'default_vertical_variance': 0.002, 30 | 'default_threshold': 0.9, 31 | }, 32 | 'windows': { # required when supporting windows game 33 | 'exe': 'Client-Win64-Shipping.exe', 34 | 'calculate_pc_exe_path': calculate_pc_exe_path, 35 | 'interaction': 'PostMessage', 36 | 'can_bit_blt': True, # default false, opengl games does not support bit_blt 37 | 'bit_blt_render_full': True, 38 | 'check_hdr_and_night_light': True 39 | }, 40 | 'supported_resolution': { 41 | 'ratio': '16:9', 42 | 'min_size': (1600, 900) 43 | }, 44 | 'analytics': { 45 | 'report_url': 'http://111.231.71.225/report' 46 | }, 47 | 'update': { 48 | 'releases_url': 'https://api.github.com/repos/ok-oldking/ok-wuthering-waves/releases?per_page=15', 49 | 'proxy_url': 'http://111.231.71.225/', 50 | 'exe_name': 'ok-ww.exe', 51 | 'use_proxy': True 52 | }, 53 | 'about': """ 54 |

OK-WW

55 |

GitHub https://github.com/ok-oldking/ok-wuthering-waves

56 |

Report a BUG https://github.com/ok-oldking/ok-wuthering-waves/issues/new?assignees=ok-oldking&labels=bug&projects=&template=%E6%8A%A5%E5%91%8Abug-.md&title=%5BBUG%5D

57 |

QQ群:970523295

58 |

QQ频道:OK-WW

59 |

60 | 本软件是免费开源的。 如果你被收费,请立即退款。请访问QQ频道或GitHub下载最新的官方版本。 61 |

62 |

63 | 本软件仅供个人使用,用于学习Python编程、计算机视觉、UI自动化等。 请勿将其用于任何营利性或商业用途。 64 |

65 |

66 | 使用本软件可能会导致账号被封。 请在了解风险后再使用。 67 |

68 | """, 69 | 'supported_screen_ratio': '16:9', 70 | 'screenshots_folder': "screenshots", 71 | 'gui_title': 'OK-WW', # Optional 72 | # 'coco_feature_folder': get_path(__file__, 'assets/coco_feature'), # required if using feature detection 73 | 'log_file': 'logs/ok-script.log', # Optional, auto rotating every day 74 | 'error_log_file': 'logs/ok-script_error.log', 75 | 'version': version, 76 | 'onetime_tasks': [ # tasks to execute 77 | FarmEchoTask, 78 | FarmWorldBossTask 79 | ], 'trigger_tasks': [ 80 | AutoCombatTask, 81 | AutoDialogTask, 82 | AutoPickTask 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /src/task/SkipDialogTask.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | 4 | from ok.feature.FindFeature import FindFeature 5 | from ok.logging.Logger import get_logger 6 | from ok.ocr.OCR import OCR 7 | from ok.task.TriggerTask import TriggerTask 8 | 9 | logger = get_logger(__name__) 10 | 11 | 12 | class AutoDialogTask(TriggerTask, FindFeature, OCR): 13 | 14 | def __init__(self): 15 | super().__init__() 16 | self.skip = None 17 | self.confirm_dialog_checked = False 18 | self.trigger_interval = 1 19 | self.has_eye_time = 0 20 | self.name = "Skip Dialog during Quests" 21 | 22 | def run(self): 23 | pass 24 | 25 | def trigger(self): 26 | skip = self.ocr(0.03, 0.03, 0.11, 0.10, use_grayscale=True, match=re.compile('SKIP'), threshold=0.9) 27 | if skip: 28 | logger.info('Click Skip Dialog') 29 | self.click_box(skip, move_back=True) 30 | if not self.confirm_dialog_checked: 31 | logger.info('Start checking if confirm dialog exists') 32 | self.sleep(2) 33 | if self.calculate_color_percentage(dialog_white_color, box=self.box_of_screen(0.42, 0.59, 0.56, 34 | 0.64)) > 0.9 and self.calculate_color_percentage( 35 | dialog_black_color, box=self.box_of_screen(0.61, 0.60, 0.74, 0.64)) > 0.8: 36 | logger.info('confirm dialog exists, click confirm') 37 | self.click_relative(0.44, 0.55) 38 | self.sleep(0.2) 39 | self.click_relative(0.67, 0.62) 40 | else: 41 | self.screenshot('dialog') 42 | logger.info('confirm dialog does not exist') 43 | self.confirm_dialog_checked = True 44 | if time.time() - self.has_eye_time < 2: 45 | btn_dialog_close = self.find_one('btn_dialog_close', use_gray_scale=True, threshold=0.8) 46 | if btn_dialog_close: 47 | self.click(btn_dialog_close, move_back=True) 48 | return 49 | btn_dialog_eye = self.find_one('btn_dialog_eye', use_gray_scale=True, threshold=0.8) 50 | if btn_dialog_eye: 51 | self.has_eye_time = time.time() 52 | btn_auto_play_dialog = self.find_one('btn_auto_play_dialog', use_gray_scale=True) 53 | if btn_auto_play_dialog: 54 | self.click_box(btn_auto_play_dialog, move_back=True) 55 | logger.info('toggle auto play') 56 | self.sleep(0.2) 57 | if arrow := self.find_feature('btn_dialog_arrow', x=0.59, y=0.33, to_x=0.75, to_y=0.75, 58 | use_gray_scale=True, threshold=0.7): 59 | self.click(arrow[-1]) 60 | logger.info('choose arrow') 61 | self.sleep(0.2) 62 | elif dots := self.find_feature('btn_dialog_3dots', x=0.59, y=0.33, to_x=0.75, to_y=0.75, 63 | use_gray_scale=True, threshold=0.7): 64 | if dots: 65 | self.click(dots[-1]) 66 | logger.info('choose dot') 67 | self.sleep(0.2) 68 | return 69 | 70 | 71 | dialog_white_color = { 72 | 'r': (230, 255), # Red range 73 | 'g': (230, 255), # Green range 74 | 'b': (230, 255) # Blue range 75 | } 76 | 77 | dialog_black_color = { 78 | 'r': (0, 15), # Red range 79 | 'g': (0, 15), # Green range 80 | 'b': (0, 15) # Blue range 81 | } 82 | -------------------------------------------------------------------------------- /src/char/CharSkillButton.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import cv2 4 | 5 | from ok.logging.Logger import get_logger 6 | 7 | logger = get_logger(__name__) 8 | 9 | 10 | class CharSkillButton: 11 | 12 | def __init__(self, name, task, t, white_limit=1, white_hints=[]): 13 | self.name = name 14 | self.type = t 15 | if not white_limit: 16 | white_limit = 1 17 | self.white_limit = white_limit 18 | self.white_hints = white_hints 19 | self.task = task 20 | self.white_list = [] 21 | self.white_off_percent = 0.01 22 | 23 | # def is_available(self, percent): 24 | # if percent == 0: 25 | # return True 26 | # for base in self.white_list: 27 | # if abs(base - percent) < self.white_off_percent: 28 | # return True 29 | # if len(self.white_list) == self.white_limit: 30 | # return False 31 | # for white_hint in self.white_hints: 32 | # if abs(percent - white_hint) < self.white_off_percent: 33 | # logger.info(f'{self.name} set base {self.type} to {percent:.4f} by white_hint {white_hint}') 34 | # self.white_list.append(percent) 35 | # return True 36 | # cd_text = self.task.ocr(box=self.task.get_box_by_name(f'box_{self.type}'), target_height=540, threshold=0.9) 37 | # if len(cd_text) == 0 or all(not is_float(text.name) for text in cd_text): 38 | # self.white_list.append(percent) 39 | # if self.task.debug: 40 | # self.task.screenshot(f'{self.name}_{self.type}_{percent:.4f}') 41 | # logger.info( 42 | # f' set base {self.name}_{self.type} to {percent:.4f} by ocr {self.white_list} {self.white_limit} {cd_text}') 43 | # return True 44 | # if cd_text: 45 | # logger.info(f'{self.name} set base {self.type} to has text {cd_text}') 46 | # return False 47 | 48 | def is_available(self, percent): 49 | if percent == 0: 50 | return True 51 | start = time.time() 52 | box = self.task.get_box_by_name(f'box_{self.type}') 53 | box = box.copy(x_offset=box.width / 4, y_offset=box.height * 0.6, width_offset=-box.width / 2, 54 | height_offset=-box.height * 0.5) 55 | dot = self.task.find_one('edge_echo_cd_dot', box=box, canny_lower=40, canny_higher=80, threshold=0.6) 56 | # if self.task.debug: 57 | # colored = cv2.cvtColor(self.boss_lv_edge, cv2.COLOR_GRAY2BGR) 58 | # self.frame[self.boss_lv_box.y:self.boss_lv_box.y + self.boss_lv_box.height, 59 | # self.boss_lv_box.x:self.boss_lv_box.x + self.boss_lv_box.width] = cv2.cvtColor(current, 60 | # cv2.COLOR_GRAY2BGR) 61 | if dot is None: 62 | logger.debug(f'find dot not exist cost : {time.time() - start}') 63 | return True 64 | else: 65 | logger.debug(f'find dot exist cost : {time.time() - start} {dot}') 66 | return False 67 | 68 | 69 | def is_float(s): 70 | try: 71 | float(s) 72 | return True 73 | except ValueError: 74 | return False 75 | 76 | 77 | if __name__ == '__main__': 78 | image = cv2.imread( 79 | 'assets\\images\\154eb284-17_01_16_406889_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_6yGu616.png', 80 | cv2.IMREAD_GRAYSCALE) # Load in grayscale 81 | 82 | # Apply Canny edge detection 83 | edges = cv2.Canny(image, 40, 80) 84 | 85 | # Save the result 86 | cv2.imwrite('edges.jpg', edges) 87 | -------------------------------------------------------------------------------- /main_debug.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | from pathlib import Path 3 | import rapidocr_openvino 4 | 5 | 6 | block_cipher = None 7 | 8 | package_name = 'rapidocr_openvino' 9 | install_dir = Path(rapidocr_openvino.__file__).resolve().parent 10 | 11 | onnx_paths = list(install_dir.rglob('*.onnx')) + list(install_dir.rglob('*.txt')) 12 | yaml_paths = list(install_dir.rglob('*.yaml')) 13 | 14 | onnx_add_data = [(str(v.parent), f'{package_name}/{v.parent.name}') 15 | for v in onnx_paths] 16 | 17 | yaml_add_data = [] 18 | for v in yaml_paths: 19 | if package_name == v.parent.name: 20 | yaml_add_data.append((str(v.parent / '*.yaml'), package_name)) 21 | else: 22 | yaml_add_data.append( 23 | (str(v.parent / '*.yaml'), f'{package_name}/{v.parent.name}')) 24 | 25 | import openvino 26 | 27 | block_cipher = None 28 | 29 | package_name = 'openvino' 30 | install_dir = Path(openvino.__file__).resolve().parent 31 | 32 | openvino_dll_path = list(install_dir.rglob('openvino_intel_cpu_plugin.dll')) + list(install_dir.rglob('openvino_onnx_frontend.dll')) 33 | 34 | 35 | # Modified list comprehension with a condition check 36 | openvino_add_data = [(str(v), f'{package_name}/{v.parent.name}') 37 | for v in openvino_dll_path] 38 | 39 | print(f'openvino_add_data {openvino_add_data}') 40 | add_data = list(set(yaml_add_data + onnx_add_data + openvino_add_data)) 41 | 42 | excludes = ['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter', 'resources', 'matplotlib','numpy.lib'] 43 | add_data.append(('icon.ico', '.')) 44 | 45 | def list_files(directory, prefix=''): 46 | file_list = [] 47 | for root, dirs, files in os.walk(directory): 48 | for filename in files: 49 | # Create the full filepath by joining root with the filename 50 | filepath = os.path.join(root, filename) 51 | # Create the relative path for the file to be used in the spec datas 52 | relative_path = os.path.relpath(filepath, prefix) 53 | folder_path = os.path.dirname(relative_path) 54 | # Append the tuple (full filepath, relative path) to the file list 55 | file_list.append((filepath, folder_path)) 56 | return file_list 57 | 58 | if os.path.exists('assets'): 59 | root_folder = os.getcwd() # Get the current working directory 60 | assets = list_files(os.path.join(root_folder, 'assets'), root_folder) 61 | add_data += assets 62 | 63 | print(f"add_data {add_data}") 64 | 65 | a = Analysis( 66 | ['main_debug.py'], 67 | pathex=[], 68 | binaries=[], 69 | datas=add_data, 70 | hiddenimports=[], 71 | hookspath=[], 72 | hooksconfig={}, 73 | runtime_hooks=[], 74 | excludes=[], 75 | cipher=block_cipher, 76 | noarchive=False, 77 | noconsole=True, 78 | ) 79 | 80 | 81 | # List of patterns to exclude 82 | exclude_patterns = ['opencv_videoio_ffmpeg', 'opengl32sw.dll', 'Qt6Quick.dll','Qt6Pdf.dll','Qt6Qml.dll','Qt6OpenGL.dll','Qt6Network.dll','Qt6QmlModels.dll','Qt6VirtualKeyboard.dll','QtNetwork.pyd' 83 | ,'openvino_pytorch_frontend.dll','openvino_tensorflow_frontend.dll','py_tensorflow_frontend.cp311-win_amd64.pyd','py_pytorch_frontend.cp311-win_amd64.pyd', 84 | ] 85 | 86 | 87 | # Optimized list comprehension using any() with a generator expression 88 | a.binaries = [x for x in a.binaries if not any(pattern in x[0] for pattern in exclude_patterns)] 89 | 90 | print(f'a.binaries {a.binaries}') 91 | 92 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) 93 | 94 | exe = EXE( 95 | pyz, 96 | a.scripts, 97 | [], 98 | exclude_binaries=True, 99 | name='ok-baijing', 100 | icon='icon.ico', 101 | debug=False, 102 | bootloader_ignore_signals=False, 103 | strip=False, 104 | upx=True, 105 | console=True, 106 | disable_windowed_traceback=False, 107 | argv_emulation=False, 108 | target_arch=None, 109 | codesign_identity=None, 110 | entitlements_file=None, 111 | ) 112 | 113 | coll = COLLECT( 114 | exe, 115 | a.binaries, 116 | a.datas, 117 | strip=False, 118 | upx=True, 119 | upx_exclude=[], 120 | name='bundle', 121 | ) 122 | 123 | -------------------------------------------------------------------------------- /src/char/Jinhsi.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from src.char.BaseChar import BaseChar, Priority 4 | 5 | 6 | class Jinhsi(BaseChar): 7 | 8 | def __init__(self, *args): 9 | super().__init__(*args) 10 | self.last_free_intro = 0 # free intro every 25 sec 11 | self.has_free_intro = False 12 | self.incarnation = False 13 | self.incarnation_cd = False 14 | 15 | def do_perform(self): 16 | if self.incarnation: 17 | self.handle_incarnation() 18 | return self.switch_next_char() 19 | if self.has_intro or self.incarnation_cd: 20 | self.handle_intro() 21 | return self.switch_next_char() 22 | self.click_echo() 23 | return self.switch_next_char() 24 | 25 | def reset_state(self): 26 | super().reset_state() 27 | self.incarnation = False 28 | self.has_free_intro = False 29 | self.incarnation_cd = False 30 | 31 | def switch_next_char(self, **args): 32 | super().switch_next_char(free_intro=self.has_free_intro, target_low_con=True) 33 | self.has_free_intro = False 34 | 35 | def do_get_switch_priority(self, current_char: BaseChar, has_intro=False): 36 | if has_intro or self.incarnation or self.incarnation_cd: 37 | self.logger.info( 38 | f'switch priority max because has_intro {has_intro} incarnation {self.incarnation} incarnation_cd {self.incarnation_cd}') 39 | return Priority.MAX 40 | else: 41 | return super().do_get_switch_priority(current_char, has_intro) 42 | 43 | def count_base_priority(self): 44 | return 0 45 | 46 | def count_resonance_priority(self): 47 | return 0 48 | 49 | def count_echo_priority(self): 50 | return 0 51 | 52 | def count_liberation_priority(self): 53 | return 0 54 | 55 | def handle_incarnation(self): 56 | self.incarnation = False 57 | self.logger.info(f'handle_incarnation click_resonance start') 58 | start = time.time() 59 | liberated = False 60 | while True: 61 | current_res = self.current_resonance() 62 | if current_res > 0 and not self.has_cd('resonance'): 63 | self.logger.debug(f'handle_incarnation current_res: {current_res} breaking') 64 | # if self.task.debug: 65 | # self.task.screenshot(f'handle_incarnation e available') 66 | break 67 | self.task.click(interval=0.1) 68 | if not liberated or not self.task.in_team()[0]: 69 | self.check_combat() 70 | 71 | self.click_resonance(has_animation=True, send_click=True) 72 | if not self.click_echo(): 73 | self.task.click() 74 | # if self.task.debug: 75 | # self.task.screenshot(f'handle_incarnation click_resonance end {time.time() - start}') 76 | self.logger.info(f'handle_incarnation click_resonance end {time.time() - start}') 77 | 78 | def handle_intro(self): 79 | # self.task.screenshot(f'handle_intro start') 80 | self.logger.info(f'handle_intro start') 81 | last = None 82 | start = time.time() 83 | self.send_resonance_key() 84 | while not self.has_cd('resonance'): 85 | if last != 'resonance' or time.time() - start < 1: 86 | if self.send_resonance_key(interval=0.1): 87 | last = 'resonance' 88 | else: 89 | if self.task.click(interval=0.1): 90 | last = 'click' 91 | self.check_combat() 92 | if time.time() - start < 1.2: 93 | self.logger.info(f'handle_intro fly e in_cd {time.time() - start}') 94 | self.incarnation_cd = True 95 | if not self.click_echo(): 96 | self.task.click() 97 | return 98 | if self.click_liberation(send_click=True): 99 | self.continues_normal_attack(0.3) 100 | else: 101 | self.continues_normal_attack(1.8) 102 | # self.task.screenshot(f'handle_intro end {time.time() - start}') 103 | self.logger.info(f'handle_intro end {time.time() - start}') 104 | self.incarnation = True 105 | self.incarnation_cd = False 106 | 107 | def wait_resonance(self): 108 | while not self.resonance_available(check_ready=True): 109 | self.send_resonance_key(interval=0.1) 110 | -------------------------------------------------------------------------------- /src/task/FarmEchoTask.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from ok.logging.Logger import get_logger 4 | from src.task.BaseCombatTask import BaseCombatTask 5 | 6 | logger = get_logger(__name__) 7 | 8 | 9 | class FarmEchoTask(BaseCombatTask): 10 | 11 | def __init__(self): 12 | super().__init__() 13 | self.description = "Click Start at the Entrance(Dreamless, Jue)" 14 | self.name = "Farm Echo in Dungeon" 15 | self.default_config.update({ 16 | 'Level': 1, 17 | 'Repeat Farm Count': 100, 18 | 'Entrance Direction': 'Forward' 19 | }) 20 | self.config_description = { 21 | 'Level': '(1-6) Important, Choose which level to farm, lower levels might not produce a echo', 22 | 'Entrance Direction': 'Choose Forward for Dreamless, Backward for Jue' 23 | } 24 | self.config_type["Entrance Direction"] = {'type': "drop_down", 'options': ['Forward', 'Backward']} 25 | self.crownless_pos = (0.9, 0.4) 26 | self.last_drop = False 27 | 28 | def run(self): 29 | # return self.run_in_circle_to_find_echo() 30 | self.handler.post(self.mouse_reset, 0.01) 31 | if not self.in_team()[0]: 32 | self.log_error('must be in game world and in teams', notify=True) 33 | return 34 | 35 | # loop here 36 | count = 0 37 | 38 | while count < self.config.get("Repeat Farm Count", 0): 39 | count += 1 40 | self.wait_in_team_and_world(time_out=20) 41 | self.sleep(1) 42 | self.walk_until_f(time_out=10, 43 | direction='w' if self.config.get('Entrance Direction') == 'Forward' else 's', 44 | raise_if_not_found=True) 45 | logger.info(f'enter success') 46 | stam = self.wait_ocr(0.75, 0.02, 0.85, 0.09, match=re.compile('240'), raise_if_not_found=True) 47 | logger.info(f'found stam {stam}') 48 | self.sleep(1) 49 | self.choose_level(self.config.get("Level")) 50 | 51 | self.combat_once() 52 | logger.info(f'farm echo combat end') 53 | self.wait_in_team_and_world(time_out=20) 54 | logger.info(f'farm echo move {self.config.get("Entrance Direction")} walk_until_f to find echo') 55 | if self.config.get('Entrance Direction') == 'Forward': 56 | dropped = self.walk_until_f(time_out=3, 57 | raise_if_not_found=False) # find and pick echo 58 | logger.debug(f'farm echo found echo move forward walk_until_f to find echo') 59 | else: 60 | self.sleep(2) 61 | dropped = self.run_in_circle_to_find_echo(3) 62 | self.incr_drop(dropped) 63 | self.sleep(0.5) 64 | self.send_key('esc') 65 | self.wait_click_feature('gray_confirm_exit_button', relative_x=-1, raise_if_not_found=True, 66 | use_gray_scale=True) 67 | self.wait_in_team_and_world(time_out=40) 68 | self.sleep(4) 69 | if self.config.get('Entrance Direction') == 'Backward': 70 | self.send_key('a', down_time=0.2) # Jue 71 | self.sleep(1) 72 | 73 | def incr_drop(self, dropped): 74 | if dropped: 75 | self.info['Echo Count'] = self.info.get('Echo Count', 0) + 1 76 | self.last_drop = dropped 77 | 78 | def choose_level(self, start): 79 | y = 0.17 80 | x = 0.15 81 | distance = 0.08 82 | 83 | logger.info(f'choose level {start}') 84 | self.click_relative(x, y + (start - 1) * distance) 85 | self.sleep(0.5) 86 | 87 | self.wait_click_feature('gray_button_challenge', raise_if_not_found=True, use_gray_scale=True, 88 | click_after_delay=0.5) 89 | self.wait_click_feature('gray_confirm_exit_button', relative_x=-1, raise_if_not_found=False, 90 | use_gray_scale=True, time_out=3, click_after_delay=0.5, threshold=0.8) 91 | self.wait_click_feature('gray_start_battle', relative_x=-1, raise_if_not_found=True, 92 | use_gray_scale=True, click_after_delay=0.5, threshold=0.8) 93 | 94 | 95 | echo_color = { 96 | 'r': (200, 255), # Red range 97 | 'g': (150, 220), # Green range 98 | 'b': (130, 170) # Blue range 99 | } 100 | -------------------------------------------------------------------------------- /main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: pythzon ; coding: utf-8 -*- 2 | from pathlib import Path 3 | import importlib.util 4 | block_cipher = None 5 | 6 | def check_package_exists(package_name): 7 | package_spec = importlib.util.find_spec(package_name) 8 | return package_spec is not None 9 | 10 | # Example usage: 11 | if check_package_exists('rapidocr_openvino'): 12 | print("rapidocr_openvino exists") 13 | 14 | import rapidocr_openvino 15 | 16 | 17 | 18 | 19 | package_name = 'rapidocr_openvino' 20 | install_dir = Path(rapidocr_openvino.__file__).resolve().parent 21 | 22 | onnx_paths = list(install_dir.rglob('*.onnx')) + list(install_dir.rglob('*.txt')) 23 | yaml_paths = list(install_dir.rglob('*.yaml')) 24 | 25 | onnx_add_data = [(str(v.parent), f'{package_name}/{v.parent.name}') 26 | for v in onnx_paths] 27 | 28 | yaml_add_data = [] 29 | for v in yaml_paths: 30 | if package_name == v.parent.name: 31 | yaml_add_data.append((str(v.parent / '*.yaml'), package_name)) 32 | else: 33 | yaml_add_data.append( 34 | (str(v.parent / '*.yaml'), f'{package_name}/{v.parent.name}')) 35 | 36 | import openvino 37 | 38 | 39 | package_name = 'openvino' 40 | install_dir = Path(openvino.__file__).resolve().parent 41 | 42 | openvino_dll_path = list(install_dir.rglob('openvino_intel_cpu_plugin.dll')) + list(install_dir.rglob('openvino_onnx_frontend.dll')) 43 | 44 | 45 | # Modified list comprehension with a condition check 46 | openvino_add_data = [(str(v), f'{package_name}/{v.parent.name}') 47 | for v in openvino_dll_path] 48 | 49 | print(f'openvino_add_data {openvino_add_data}') 50 | add_data = list(set(yaml_add_data + onnx_add_data + openvino_add_data)) 51 | else: 52 | add_data = [] 53 | 54 | excludes = ['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter', 'resources', 'matplotlib','numpy.lib'] 55 | add_data.append(('icon.png', '.')) 56 | 57 | 58 | def list_files(directory, prefix='', extensions=[]): 59 | file_list = [] 60 | for root, dirs, files in os.walk(directory): 61 | for filename in files: 62 | # Check the file extension 63 | _, ext = os.path.splitext(filename) 64 | if extensions and ext not in extensions: 65 | continue 66 | 67 | # Create the full filepath by joining root with the filename 68 | filepath = os.path.join(root, filename) 69 | 70 | # Create the relative path for the file to be used in the spec datas 71 | relative_path = os.path.relpath(filepath, prefix) 72 | folder_path = os.path.dirname(relative_path) 73 | 74 | # Append the tuple (full filepath, relative path) to the file list 75 | file_list.append((filepath, folder_path)) 76 | return file_list 77 | 78 | if os.path.exists('assets'): 79 | root_folder = os.getcwd() # Get the current working directory 80 | assets = list_files(os.path.join(root_folder, 'assets'), root_folder) 81 | add_data += assets 82 | 83 | if os.path.exists('i18n'): 84 | root_folder = os.getcwd() # Get the current working directory 85 | assets = list_files(os.path.join(root_folder, 'i18n'), root_folder, ['.mo']) 86 | add_data += assets 87 | 88 | print(f"add_data {add_data}") 89 | 90 | a = Analysis( 91 | ['main.py'], 92 | pathex=[], 93 | binaries=[], 94 | datas=add_data, 95 | hiddenimports=[], 96 | hookspath=[], 97 | hooksconfig={}, 98 | runtime_hooks=[], 99 | excludes=[], 100 | cipher=block_cipher, 101 | noarchive=False, 102 | noconsole=True, 103 | ) 104 | 105 | 106 | # List of patterns to exclude 107 | exclude_patterns = ['opencv_videoio_ffmpeg', 'opengl32sw.dll', 'Qt6Quick.dll','Qt6Pdf.dll','Qt6Qml.dll','Qt6OpenGL.dll','Qt6Network.dll','Qt6QmlModels.dll','Qt6VirtualKeyboard.dll','QtNetwork.pyd' 108 | ,'openvino_pytorch_frontend.dll','openvino_tensorflow_frontend.dll','py_tensorflow_frontend.cp311-win_amd64.pyd','py_pytorch_frontend.cp311-win_amd64.pyd', 109 | ] 110 | 111 | 112 | # Optimized list comprehension using any() with a generator expression 113 | a.binaries = [x for x in a.binaries if not any(pattern in x[0] for pattern in exclude_patterns)] 114 | 115 | print(f'a.binaries {a.binaries}') 116 | 117 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) 118 | 119 | from config import config 120 | 121 | exe = EXE( 122 | pyz, 123 | a.scripts, 124 | [], 125 | exclude_binaries=True, 126 | name='ok-ww', 127 | icon='icon.png', 128 | debug=False, 129 | bootloader_ignore_signals=False, 130 | strip=False, 131 | upx=True, 132 | console=False, 133 | disable_windowed_traceback=False, 134 | argv_emulation=False, 135 | target_arch=None, 136 | codesign_identity=None, 137 | entitlements_file=None, 138 | uac_admin=True, 139 | ) 140 | 141 | coll = COLLECT( 142 | exe, 143 | a.binaries, 144 | a.datas, 145 | strip=False, 146 | upx=True, 147 | upx_exclude=[], 148 | name='bundle', 149 | ) 150 | 151 | -------------------------------------------------------------------------------- /src/task/FarmWorldBossTask.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ok.feature.Feature import Feature 4 | from ok.logging.Logger import get_logger 5 | from src.task.BaseCombatTask import BaseCombatTask, CharDeadException 6 | 7 | logger = get_logger(__name__) 8 | 9 | 10 | class FarmWorldBossTask(BaseCombatTask): 11 | 12 | def __init__(self): 13 | super().__init__() 14 | self.description = "Click Start in Game World" 15 | self.name = "Farm World Boss(Must Drop a WayPoint on the Boss First)" 16 | self.boss_names = ['N/A', 'Crownless', 'Tempest Mephis', 'Thundering Mephis', 'Inferno Rider', 17 | 'Feilian Beringal', 18 | 'Mourning Aix', 'Impermanence Heron', 'Lampylumen Myriad', 'Mech Abomination', 19 | 'Bell-Borne Geochelone'] 20 | self.weekly_boss_index = {'Bell-Borne Geochelone': 3} 21 | self.weekly_boss_count = 1 # Bell-Borne Geochelone 22 | default_config = { 23 | 'Boss1': 'N/A', 24 | 'Boss2': 'N/A', 25 | 'Boss3': 'N/A', 26 | 'Repeat Farm Count': 1000 27 | } 28 | default_config.update(self.default_config) 29 | self.default_config = default_config 30 | self.config_type["Boss1"] = {'type': "drop_down", 'options': self.boss_names} 31 | self.config_type["Boss2"] = {'type': "drop_down", 'options': self.boss_names} 32 | self.config_type["Boss3"] = {'type': "drop_down", 'options': self.boss_names} 33 | self.config_description = { 34 | 'Level': '(1-6) Important, Choose which level to farm, lower levels might not produce a echo', 35 | 'Entrance Direction': 'Choose Forward for Dreamless, Backward for Jue' 36 | } 37 | self.config_type["Entrance Direction"] = {'type': "drop_down", 'options': ['Forward', 'Backward']} 38 | self.crownless_pos = (0.9, 0.4) 39 | self.last_drop = False 40 | 41 | def teleport_to_boss(self, boss_name): 42 | index = self.boss_names.index(boss_name) 43 | index -= 1 44 | self.log_info(f'teleport to {boss_name} index {index}') 45 | self.sleep(1) 46 | self.log_info('click f2 to open the book') 47 | self.send_key('f2') 48 | gray_book_boss = self.wait_book() 49 | if not gray_book_boss: 50 | self.log_error("can't find the gray_book_boss", notify=True) 51 | raise Exception("can't find gray_book_boss") 52 | 53 | self.log_info(f'click {gray_book_boss}') 54 | self.click_box(gray_book_boss) 55 | self.sleep(1.5) 56 | 57 | if index >= (len(self.boss_names) - self.weekly_boss_count - 1): # weekly turtle 58 | logger.info('click weekly boss') 59 | index = self.weekly_boss_index[boss_name] 60 | self.click_relative(0.21, 0.59) 61 | else: 62 | logger.info('click normal boss') 63 | self.click_relative(0.21, 0.36) 64 | 65 | self.sleep(1) 66 | 67 | if index > 4: 68 | self.log_info(f'click scroll bar') 69 | self.click_relative(3760 / 3840, 1852 / 2160) 70 | self.sleep(0.5) 71 | index -= 4 72 | 73 | self.log_info(f'index after scrolling down {index}') 74 | proceeds = self.find_feature('boss_proceed', vertical_variance=1, use_gray_scale=True, threshold=0.8) 75 | if self.debug: 76 | self.screenshot('proceeds') 77 | if not proceeds: 78 | raise Exception("can't find the boss proceeds") 79 | 80 | self.wait_feature('gray_teleport', raise_if_not_found=True, use_gray_scale=True, time_out=120, 81 | pre_action=lambda: self.click_box(proceeds[index], relative_x=-1), wait_until_before_delay=5) 82 | self.sleep(1) 83 | teleport = self.wait_click_feature('custom_teleport', box=self.box_of_screen(0.48, 0.45, 0.54, 0.58), 84 | raise_if_not_found=False, threshold=0.8, time_out=2) 85 | if not teleport: 86 | self.click_relative(0.5, 0.5) 87 | self.sleep(0.5) 88 | self.wait_click_feature('gray_custom_way_point', box=self.box_of_screen(0.62, 0.48, 0.70, 0.66), 89 | raise_if_not_found=True, 90 | use_gray_scale=True, threshold=0.75, time_out=2) 91 | self.click_fast_travel() 92 | 93 | def click_fast_travel(self): 94 | travel = self.wait_feature('fast_travel_custom', raise_if_not_found=True, threshold=0.75) 95 | self.click_box(travel, relative_x=1.5) 96 | 97 | def wait_book(self): 98 | gray_book_boss = self.wait_until( 99 | lambda: self.find_one('gray_book_boss', vertical_variance=1, horizontal_variance=0.05, 100 | threshold=0.7, canny_lower=50, 101 | canny_higher=150) or self.find_one( 102 | 'gray_book_boss_highlight', 103 | vertical_variance=1, horizontal_variance=0.05, 104 | threshold=0.7, 105 | canny_lower=50, 106 | canny_higher=150), 107 | time_out=3) 108 | return gray_book_boss 109 | 110 | def check_main(self): 111 | if not self.in_team()[0]: 112 | self.send_key('esc') 113 | self.sleep(1) 114 | return self.in_team()[0] 115 | return True 116 | 117 | # not current in use because not stable, right now using one click to scroll down 118 | def scroll_down_a_page(self): 119 | source_box = self.box_of_screen(0.38, 0.80, 0.42, 0.83) 120 | source_template = Feature(source_box.crop_frame(self.frame), source_box.x, source_box.y) 121 | target_box = self.box_of_screen(0.38, 0.16, 0.42, 0.31) 122 | start = time.time() 123 | 124 | self.click_relative(0.5, 0.5) 125 | self.sleep(0.1) 126 | while True: 127 | if time.time() - start > 20: 128 | raise Exception("scroll to long") 129 | self.scroll_relative(0.5, 0.5, -1) 130 | self.sleep(0.1) 131 | targets = self.find_feature('target_box', box=target_box, template=source_template) 132 | if targets: 133 | self.log_info(f'scroll to targets {targets} successfully') 134 | break 135 | 136 | def teleport_to_heal(self): 137 | self.info['Death Count'] = self.info.get('Death Count', 0) + 1 138 | self.send_key('esc') 139 | self.sleep(1) 140 | self.log_info('click m to open the map') 141 | self.send_key('m') 142 | self.sleep(2) 143 | self.click_relative(0.68, 0.05) 144 | self.sleep(1) 145 | self.click_relative(0.37, 0.42) 146 | travel = self.wait_feature('gray_teleport', raise_if_not_found=True, use_gray_scale=True, time_out=3) 147 | self.click_box(travel, relative_x=1.5) 148 | self.wait_in_team_and_world(time_out=20) 149 | self.sleep(2) 150 | 151 | def run(self): 152 | if not self.check_main(): 153 | self.log_error('must be in game world and in teams', notify=True) 154 | self.handler.post(self.mouse_reset, 0.01) 155 | count = 0 156 | while True: 157 | for i in range(1, 4): 158 | key = 'Boss' + str(i) 159 | if boss_name := self.config.get(key): 160 | if boss_name != 'N/A': 161 | count += 1 162 | self.teleport_to_boss(boss_name) 163 | logger.info(f'farm echo combat once start') 164 | if boss_name == 'Crownless': 165 | self.wait_in_team_and_world(time_out=20) 166 | self.sleep(2) 167 | logger.info('Crownless walk to f') 168 | self.walk_until_f(raise_if_not_found=True, time_out=4, backward_time=1) 169 | try: 170 | self.combat_once() 171 | except CharDeadException: 172 | logger.info(f'char dead try teleport to heal') 173 | self.teleport_to_heal() 174 | continue 175 | logger.info(f'farm echo combat end') 176 | if boss_name == 'Bell-Borne Geochelone': 177 | logger.info(f'sleep for the Boss model to disappear') 178 | self.sleep(5) 179 | self.wait_in_team_and_world(time_out=20) 180 | logger.info(f'farm echo move forward walk_until_f to find echo') 181 | if self.walk_until_f(time_out=6, backward_time=1, 182 | raise_if_not_found=False): # find and pick echo 183 | logger.debug(f'farm echo found echo move forward walk_until_f to find echo') 184 | self.incr_drop(True) 185 | 186 | if count == 0: 187 | self.log_error('must choose at least 1 Boss to Farm', notify=True) 188 | return 189 | 190 | def incr_drop(self, dropped): 191 | if dropped: 192 | self.info['Echo Count'] = self.info.get('Echo Count', 0) + 1 193 | self.last_drop = dropped 194 | -------------------------------------------------------------------------------- /src/combat/CombatCheck.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | 4 | import cv2 5 | 6 | from ok.color.Color import find_color_rectangles, get_mask_in_color_range, is_pure_black 7 | from ok.feature.Box import find_boxes_by_name 8 | from ok.logging.Logger import get_logger 9 | from src import text_white_color 10 | 11 | logger = get_logger(__name__) 12 | 13 | 14 | class CombatCheck: 15 | 16 | def __init__(self): 17 | self._in_combat = False 18 | self.boss_lv_template = None 19 | self.boss_lv_mask = None 20 | self.in_liberation = False # return True 21 | self.has_count_down = False 22 | self.last_out_of_combat_time = 0 23 | self.last_combat_check = 0 24 | self.boss_lv_box = None 25 | self.boss_health_box = None 26 | self.boss_health = None 27 | self.out_of_combat_reason = "" 28 | self.combat_check_interval = 0.8 29 | self.last_click_liberation = 0 30 | 31 | def reset_to_false(self, recheck=False, reason=""): 32 | if is_pure_black(self.frame): 33 | logger.error('getting a pure black frame for unknown reason, reset_to_false return true') 34 | return True 35 | if recheck and time.time() - self.last_out_of_combat_time > 2.1: 36 | logger.info('out of combat start double check') 37 | if self.debug: 38 | self.screenshot('out of combat start double check') 39 | self.last_out_of_combat_time = time.time() 40 | return True 41 | else: 42 | self.out_of_combat_reason = reason 43 | self._in_combat = False 44 | self.boss_lv_mask = None 45 | self.boss_lv_template = None 46 | self.in_liberation = False # return True 47 | self.has_count_down = False 48 | self.last_out_of_combat_time = 0 49 | self.last_combat_check = 0 50 | self.boss_lv_box = None 51 | self.boss_health = None 52 | self.boss_health_box = None 53 | return False 54 | 55 | def recent_liberation(self): 56 | return time.time() - self.last_click_liberation < 3 57 | 58 | def check_count_down(self): 59 | count_down_area = self.box_of_screen(1820 / 3840, 266 / 2160, 2100 / 3840, 60 | 340 / 2160, name="check_count_down") 61 | count_down = self.calculate_color_percentage(text_white_color, 62 | count_down_area) 63 | 64 | if self.has_count_down: 65 | if count_down < 0.03: 66 | numbers = self.ocr(box=count_down_area, match=count_down_re) 67 | if self.debug: 68 | self.screenshot(f'count_down disappeared {count_down:.2f}%') 69 | logger.info(f'count_down disappeared {numbers} {count_down:.2f}%') 70 | if not numbers: 71 | self.has_count_down = False 72 | return False 73 | else: 74 | return True 75 | else: 76 | return True 77 | else: 78 | if count_down > 0.03: 79 | numbers = self.ocr(box=count_down_area, match=count_down_re) 80 | if numbers: 81 | self.has_count_down = True 82 | logger.info(f'set count_down to {self.has_count_down} {numbers} {count_down:.2f}%') 83 | return self.has_count_down 84 | 85 | def check_boss(self, in_team): 86 | current = self.boss_lv_box.crop_frame(self.frame) 87 | max_val = 0 88 | if current is not None: 89 | res = cv2.matchTemplate(current, self.boss_lv_template, cv2.TM_CCOEFF_NORMED, mask=self.boss_lv_mask) 90 | min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 91 | if max_val < 0.8: 92 | if self.debug: 93 | self.screenshot_boss_lv(current, f'boss lv not detected by edge {max_val}') 94 | logger.debug(f'boss lv not detected by edge') 95 | if not self.find_boss_lv_text(): # double check by text 96 | if not in_team and not self.check_health_bar() and not self.check_count_down() and not self.find_target_enemy(): 97 | if self.debug: 98 | self.screenshot_boss_lv(current, 'out_of combat boss_health disappeared') 99 | logger.info(f'out of combat because of boss_health disappeared, res:{max_val}') 100 | return False 101 | else: 102 | self.boss_lv_template = None 103 | self.boss_lv_box = None 104 | logger.info(f'boss_health disappeared, but still in combat') 105 | return True 106 | else: 107 | return True 108 | else: 109 | logger.debug(f'check boss edge passed {max_val}') 110 | return True 111 | 112 | def screenshot_boss_lv(self, current, name): 113 | if self.debug: 114 | if self.boss_lv_box is not None and self.boss_lv_template is not None and current is not None: 115 | frame = self.frame.copy() 116 | frame[self.boss_lv_box.y:self.boss_lv_box.y + self.boss_lv_box.height, 117 | self.boss_lv_box.x:self.boss_lv_box.x + self.boss_lv_box.width] = current 118 | x, y, w, h = self.boss_lv_box.x, self.boss_lv_box.height + 50 + self.boss_lv_box.y, self.boss_lv_box.width, self.boss_lv_box.height 119 | frame[y:y + h, x:x + w] = self.boss_lv_template 120 | self.screenshot(name, frame) 121 | 122 | def find_target_enemy(self): 123 | start = time.time() 124 | target_enemy = self.find_one('target_enemy_white', box=self.box_of_screen(0.25, 0.25, 0.75, 0.75), 125 | use_gray_scale=True, threshold=0.83, 126 | frame_processor=process_target_enemy_area) 127 | # if self.debug and target_enemy is not None: 128 | # self.screenshot('find_target_enemy') 129 | logger.debug(f'find_target_enemy {target_enemy} {time.time() - start}') 130 | return target_enemy is not None 131 | 132 | def handle_monthly_card(self): 133 | monthly_card = self.find_one('monthly_card', threshold=0.8) 134 | if monthly_card is not None: 135 | self.click(monthly_card) 136 | self.sleep(2) 137 | self.click(monthly_card) 138 | self.sleep(1) 139 | logger.debug(f'check_monthly_card {monthly_card}') 140 | return monthly_card is not None 141 | 142 | def in_combat(self, rechecked=False): 143 | if self.in_liberation or self.recent_liberation(): 144 | self.last_combat_check = time.time() 145 | logger.debug('in liberation return True') 146 | return True 147 | if self._in_combat: 148 | now = time.time() 149 | if now - self.last_combat_check > self.combat_check_interval: 150 | self.last_combat_check = now 151 | in_team = self.in_team()[0] 152 | if not in_team: 153 | return self.reset_to_false(recheck=False, reason="not in team") 154 | if self.check_count_down(): 155 | return True 156 | if self.boss_lv_template is not None: 157 | if self.check_boss(in_team): 158 | return True 159 | else: 160 | return self.reset_to_false(recheck=False, reason="boss disappear") 161 | if not self.check_health_bar(): 162 | logger.debug('not in team or no health bar') 163 | if not self.target_enemy(): 164 | logger.error('target_enemy failed, break out of combat') 165 | return self.reset_to_false(reason='target enemy failed') 166 | return True 167 | else: 168 | logger.debug( 169 | 'check in combat pass') 170 | return True 171 | else: 172 | return True 173 | else: 174 | in_combat = self.in_team()[0] and self.check_health_bar() 175 | if in_combat: 176 | in_combat = self.boss_health_box is not None or self.boss_lv_template is not None or self.has_count_down 177 | if in_combat: 178 | self.target_enemy(wait=False) 179 | else: 180 | in_combat = self.target_enemy() 181 | if in_combat: 182 | logger.info( 183 | f'enter combat boss_lv_template:{self.boss_lv_template is not None} boss_health_box:{self.boss_health_box} has_count_down:{self.has_count_down}') 184 | self._in_combat = True 185 | return True 186 | 187 | def target_enemy(self, wait=True): 188 | if not wait: 189 | self.middle_click() 190 | else: 191 | if self.find_target_enemy(): 192 | return True 193 | self.middle_click() 194 | return self.wait_until(self.find_target_enemy, time_out=2) 195 | 196 | def check_health_bar(self): 197 | if self._in_combat: 198 | min_height = self.height_of_screen(10 / 2160) 199 | max_height = min_height * 3 200 | min_width = self.width_of_screen(15 / 3840) 201 | else: 202 | min_height = self.height_of_screen(12 / 2160) 203 | max_height = min_height * 3 204 | min_width = self.width_of_screen(100 / 3840) 205 | 206 | boxes = find_color_rectangles(self.frame, enemy_health_color_red, min_width, min_height, max_height=max_height) 207 | 208 | if len(boxes) > 0: 209 | self.draw_boxes('enemy_health_bar_red', boxes, color='blue') 210 | return True 211 | else: 212 | boxes = find_color_rectangles(self.frame, boss_health_color, min_width * 3, min_height * 1.3, 213 | box=self.box_of_screen(1269 / 3840, 58 / 2160, 2533 / 3840, 192 / 2160)) 214 | if len(boxes) == 1: 215 | self.boss_health_box = boxes[0] 216 | self.boss_health_box.width = 10 217 | self.boss_health_box.x += 6 218 | self.boss_health = self.boss_health_box.crop_frame(self.frame) 219 | self.draw_boxes('boss_health', boxes, color='blue') 220 | return True 221 | 222 | return self.find_boss_lv_text() 223 | 224 | def find_boss_lv_text(self): 225 | texts = self.ocr(box=self.box_of_screen(1269 / 3840, 10 / 2160, 2533 / 3840, 140 / 2160), 226 | target_height=720) 227 | boss_lv_texts = find_boxes_by_name(texts, 228 | [re.compile(r'(?i)^L[V].*')]) 229 | if len(boss_lv_texts) > 0: 230 | logger.debug(f'boss_lv_texts: {boss_lv_texts}') 231 | self.boss_lv_box = boss_lv_texts[0] 232 | self.boss_lv_template, self.boss_lv_mask = self.keep_boss_text_white() 233 | if self.boss_lv_template is None: 234 | self.boss_lv_box = None 235 | return False 236 | return True 237 | 238 | def keep_boss_text_white(self): 239 | cropped = self.boss_lv_box.crop_frame(self.frame) 240 | mask, area = get_mask_in_color_range(cropped, boss_white_text_color) 241 | if area / mask.shape[0] * mask.shape[1] < 0.05: 242 | mask, area = get_mask_in_color_range(cropped, boss_orange_text_color) 243 | if area / mask.shape[0] * mask.shape[1] < 0.05: 244 | mask, area = get_mask_in_color_range(cropped, 245 | boss_red_text_color) 246 | if area / mask.shape[0] * mask.shape[1] < 0.05: 247 | logger.error(f'keep_boss_text_white cant find text with the correct color') 248 | return None, 0 249 | return cropped, mask 250 | 251 | 252 | count_down_re = re.compile(r'\d\d') 253 | 254 | 255 | def process_target_enemy_area(frame): 256 | frame[frame != 255] = 0 257 | return frame 258 | 259 | 260 | enemy_health_color_red = { 261 | 'r': (202, 212), # Red range 262 | 'g': (70, 80), # Green range 263 | 'b': (55, 65) # Blue range 264 | } # 207,75,60 265 | 266 | enemy_health_color_black = { 267 | 'r': (10, 55), # Red range 268 | 'g': (28, 50), # Green range 269 | 'b': (18, 70) # Blue range 270 | } 271 | 272 | boss_white_text_color = { 273 | 'r': (200, 255), # Red range 274 | 'g': (200, 255), # Green range 275 | 'b': (200, 255) # Blue range 276 | } 277 | 278 | boss_orange_text_color = { 279 | 'r': (218, 218), # Red range 280 | 'g': (178, 178), # Green range 281 | 'b': (68, 68) # Blue range 282 | } 283 | 284 | boss_red_text_color = { 285 | 'r': (200, 230), # Red range 286 | 'g': (70, 90), # Green range 287 | 'b': (60, 80) # Blue range 288 | } 289 | 290 | boss_health_color = { 291 | 'r': (250, 255), # Red range 292 | 'g': (30, 180), # Green range 293 | 'b': (4, 75) # Blue range 294 | } 295 | -------------------------------------------------------------------------------- /src/task/BaseCombatTask.py: -------------------------------------------------------------------------------- 1 | import math 2 | import time 3 | 4 | import win32api 5 | 6 | from ok.config.ConfigOption import ConfigOption 7 | from ok.feature.FindFeature import FindFeature 8 | from ok.logging.Logger import get_logger 9 | from ok.ocr.OCR import OCR 10 | from ok.task.BaseTask import BaseTask 11 | from ok.task.TaskExecutor import CannotFindException 12 | from ok.util.list import safe_get 13 | from src.char import BaseChar 14 | from src.char.BaseChar import Priority 15 | from src.char.CharFactory import get_char_by_pos 16 | from src.combat.CombatCheck import CombatCheck 17 | 18 | logger = get_logger(__name__) 19 | 20 | 21 | class NotInCombatException(Exception): 22 | pass 23 | 24 | 25 | class CharDeadException(NotInCombatException): 26 | pass 27 | 28 | 29 | key_config_option = ConfigOption('Game Hotkey Config', { 30 | 'Echo Key': 'q', 31 | 'Liberation Key': 'r', 32 | 'Resonance Key': 'e', 33 | }, description='In Game Hotkey for Skills') 34 | 35 | 36 | class BaseCombatTask(BaseTask, FindFeature, OCR, CombatCheck): 37 | 38 | def __init__(self): 39 | super().__init__() 40 | CombatCheck.__init__(self) 41 | self.chars = [None, None, None] 42 | self.char_texts = ['char_1_text', 'char_2_text', 'char_3_text'] 43 | self.key_config = self.get_config(key_config_option) 44 | 45 | self.mouse_pos = None 46 | 47 | self.char_texts = ['char_1_text', 'char_2_text', 'char_3_text'] 48 | 49 | def raise_not_in_combat(self, message, exception_type=None): 50 | logger.error(message) 51 | self.reset_to_false(reason=message) 52 | if exception_type is None: 53 | exception_type = NotInCombatException 54 | raise exception_type(message) 55 | 56 | def combat_once(self, wait_combat_time=180, wait_before=2): 57 | self.wait_until(lambda: self.in_combat(), time_out=wait_combat_time, raise_if_not_found=True) 58 | self.sleep(wait_before) 59 | self.wait_until(lambda: self.in_combat(), time_out=3, raise_if_not_found=True) 60 | self.load_chars() 61 | self.info['Combat Count'] = self.info.get('Combat Count', 0) + 1 62 | while self.in_combat(): 63 | try: 64 | logger.debug(f'combat_once loop {self.chars}') 65 | self.get_current_char().perform() 66 | except CharDeadException as e: 67 | raise e 68 | except NotInCombatException as e: 69 | logger.info(f'combat_once out of combat break {e}') 70 | if self.debug: 71 | self.screenshot(f'out of combat break {self.out_of_combat_reason}') 72 | break 73 | 74 | def run_in_circle_to_find_echo(self, circle_count=3): 75 | directions = ['w', 'a', 's', 'd'] 76 | step = 1.2 77 | duration = 0.8 78 | total_index = 0 79 | for count in range(circle_count): 80 | logger.debug(f'running first circle_count{circle_count} circle {total_index} duration:{duration}') 81 | for direction in directions: 82 | if total_index > 2 and (total_index + 1) % 2 == 0: 83 | duration += step 84 | picked = self.send_key_and_wait_f(direction, False, time_out=duration, running=True) 85 | if picked: 86 | self.mouse_up(key="right") 87 | return True 88 | total_index += 1 89 | 90 | def switch_next_char(self, current_char, post_action=None, free_intro=False, target_low_con=False): 91 | max_priority = Priority.MIN 92 | switch_to = None 93 | has_intro = free_intro 94 | if not has_intro: 95 | current_con = current_char.get_current_con() 96 | if current_con > 0.8 and current_con != 1: 97 | logger.info(f'switch_next_char current_con {current_con:.2f} almost full, sleep and check again') 98 | self.sleep(0.05) 99 | self.next_frame() 100 | current_con = current_char.get_current_con() 101 | if current_con == 1: 102 | has_intro = True 103 | 104 | for i, char in enumerate(self.chars): 105 | if char == current_char: 106 | priority = Priority.CURRENT_CHAR 107 | else: 108 | priority = char.get_switch_priority(current_char, has_intro) 109 | if target_low_con: 110 | priority += (1 - char.current_con) * 1000 - Priority.SWITCH_CD 111 | logger.info( 112 | f'switch_next_char priority: {char} {priority} {char.current_con} target_low_con {target_low_con}') 113 | if priority > max_priority: 114 | max_priority = priority 115 | switch_to = char 116 | if switch_to == current_char: 117 | self.check_combat() 118 | self.click() 119 | logger.warning(f"can't find next char to switch to, maybe switching too fast click and wait") 120 | return self.switch_next_char(current_char, post_action, free_intro, target_low_con) 121 | switch_to.has_intro = has_intro 122 | logger.info(f'switch_next_char {current_char} -> {switch_to} has_intro {has_intro}') 123 | last_click = 0 124 | start = time.time() 125 | while True: 126 | now = time.time() 127 | if now - last_click > 0.1: 128 | self.send_key(switch_to.index + 1) 129 | last_click = now 130 | in_team, current_index, size = self.in_team() 131 | if not in_team: 132 | if self.debug: 133 | self.screenshot(f'not in team while switching chars_{current_char}_to_{switch_to} {now - start}') 134 | confirm = self.wait_feature('revive_confirm', threshold=0.8, time_out=3) 135 | if confirm: 136 | self.log_info(f'char dead') 137 | self.raise_not_in_combat(f'char dead', exception_type=CharDeadException) 138 | else: 139 | self.raise_not_in_combat( 140 | f'not in team while switching chars_{current_char}_to_{switch_to}') 141 | if now - start > 10: 142 | self.raise_not_in_combat( 143 | f'switch too long failed chars_{current_char}_to_{switch_to}, {now - start}') 144 | if current_index != switch_to.index: 145 | has_intro = free_intro if free_intro else current_char.is_con_full() 146 | switch_to.has_intro = has_intro 147 | if now - start > 10: 148 | if self.debug: 149 | self.screenshot(f'switch_not_detected_{current_char}_to_{switch_to}') 150 | self.raise_not_in_combat('failed switch chars') 151 | else: 152 | self.next_frame() 153 | else: 154 | self.in_liberation = False 155 | switch_time = time.time() 156 | current_char.switch_out() 157 | switch_to.is_current_char = True 158 | break 159 | 160 | if post_action: 161 | post_action() 162 | logger.info(f'switch_next_char end {(switch_time - start):.3f}s') 163 | return switch_time 164 | 165 | def click(self, x=-1, y=-1, move_back=False, name=None, interval=-1): 166 | if x == -1 and y == -1: 167 | x = self.width_of_screen(0.5) 168 | y = self.height_of_screen(0.5) 169 | return super().click(x, y, move_back, name, interval) 170 | 171 | def wait_in_team_and_world(self, time_out=10, raise_if_not_found=True): 172 | return self.wait_until(self.in_team_and_world, time_out=time_out, raise_if_not_found=raise_if_not_found) 173 | 174 | def in_team_and_world(self): 175 | return self.in_team()[ 176 | 0] # and self.find_one(f'gray_book_button', threshold=0.7, canny_lower=50, canny_higher=150) 177 | 178 | def get_current_char(self): 179 | for char in self.chars: 180 | if char.is_current_char: 181 | return char 182 | if not self.in_team()[0]: 183 | self.raise_not_in_combat('can find current char!!') 184 | self.load_chars() 185 | return self.get_current_char() 186 | 187 | def sleep_check_combat(self, timeout, check_combat=True): 188 | start = time.time() 189 | if not self.in_combat() and check_combat: 190 | self.raise_not_in_combat('sleep check not in combat') 191 | self.sleep(timeout - (time.time() - start)) 192 | 193 | def check_combat(self): 194 | if not self.in_combat(): 195 | if self.debug: 196 | self.screenshot('not_in_combat_calling_check_combat') 197 | self.raise_not_in_combat('combat check not in combat') 198 | 199 | def send_key_and_wait_f(self, direction, raise_if_not_found, time_out, running=False): 200 | if time_out <= 0: 201 | return 202 | start = time.time() 203 | if running: 204 | self.mouse_down(key='right') 205 | self.send_key_down(direction) 206 | f_found = self.wait_feature('pick_up_f', horizontal_variance=0.01, vertical_variance=0.01, threshold=0.8, 207 | wait_until_before_delay=0, time_out=time_out, raise_if_not_found=False) 208 | if f_found: 209 | self.send_key('f') 210 | self.sleep(0.1) 211 | self.send_key_up(direction) 212 | if running: 213 | self.mouse_up(key='right') 214 | if not f_found: 215 | if raise_if_not_found: 216 | raise CannotFindException('cant find the f to enter') 217 | else: 218 | logger.warning(f"can't find the f to enter") 219 | return False 220 | remaining = time.time() - start 221 | 222 | if self.handle_claim_button(): 223 | self.sleep(0.5) 224 | self.send_key_down(direction) 225 | if running: 226 | self.mouse_down(key='right') 227 | self.sleep(remaining + 0.2) 228 | if running: 229 | self.mouse_up(key='right') 230 | self.send_key_up(direction) 231 | return False 232 | return f_found 233 | 234 | def handle_claim_button(self): 235 | if self.wait_feature('cancel_button', raise_if_not_found=False, horizontal_variance=0.1, 236 | vertical_variance=0.1, 237 | use_gray_scale=True, time_out=2, threshold=0.8): 238 | self.send_key('esc') 239 | self.sleep(0.05) 240 | logger.info(f"found a claim reward") 241 | return True 242 | 243 | def walk_until_f(self, direction='w', time_out=0, raise_if_not_found=True, backward_time=0): 244 | if not self.find_one('pick_up_f', horizontal_variance=0.01, vertical_variance=0.01, threshold=0.8): 245 | if backward_time > 0: 246 | if self.send_key_and_wait_f('s', raise_if_not_found, backward_time): 247 | return True 248 | return self.send_key_and_wait_f(direction, raise_if_not_found, time_out) and self.sleep(0.5) 249 | else: 250 | self.send_key('f') 251 | if self.handle_claim_button(): 252 | return False 253 | self.sleep(0.5) 254 | return True 255 | 256 | def load_chars(self): 257 | in_team, current_index, count = self.in_team() 258 | if not in_team: 259 | return 260 | self.log_info('load chars') 261 | char = get_char_by_pos(self, self.get_box_by_name('box_char_1'), 0) 262 | old_char = safe_get(self.chars, 0) 263 | if self.should_update(char, old_char): 264 | self.chars[0] = char 265 | logger.info(f'update char1 to {char.name} {type(char)} {type(char) is not BaseChar}') 266 | 267 | char = get_char_by_pos(self, self.get_box_by_name('box_char_2'), 1) 268 | old_char = safe_get(self.chars, 1) 269 | if self.should_update(char, old_char): 270 | self.chars[1] = char 271 | logger.info(f'update char2 to {char.name}') 272 | if count == 3: 273 | char = get_char_by_pos(self, self.get_box_by_name('box_char_3'), 2) 274 | old_char = safe_get(self.chars, 2) 275 | if self.should_update(char, old_char): 276 | if len(self.chars) == 3: 277 | self.chars[2] = char 278 | else: 279 | self.chars.append(char) 280 | logger.info(f'update char3 to {char.name}') 281 | else: 282 | if len(self.chars) == 3: 283 | self.chars.pop(0) 284 | logger.info(f'team size changed to 2') 285 | 286 | for char in self.chars: 287 | char.reset_state() 288 | if char.index == current_index: 289 | char.is_current_char = True 290 | else: 291 | char.is_current_char = False 292 | 293 | self.log_info(f'load chars success {self.chars}') 294 | 295 | @staticmethod 296 | def should_update(char, old_char): 297 | return (type(char) is BaseChar and old_char is None) or (type(char) is not BaseChar and old_char != char) 298 | 299 | def box_resonance(self): 300 | return self.get_box_by_name('box_resonance_cd') 301 | 302 | def get_resonance_cd_percentage(self): 303 | return self.calculate_color_percentage(white_color, self.get_box_by_name('box_resonance_cd')) 304 | 305 | def get_resonance_percentage(self): 306 | return self.calculate_color_percentage(white_color, self.get_box_by_name('box_resonance')) 307 | 308 | def in_team(self): 309 | start = time.time() 310 | c1 = self.find_one('char_1_text', 311 | threshold=0.75) 312 | c2 = self.find_one('char_2_text', 313 | threshold=0.75) 314 | c3 = self.find_one('char_3_text', 315 | threshold=0.75) 316 | arr = [c1, c2, c3] 317 | # logger.debug(f'in_team check {arr} time: {(time.time() - start):.3f}s') 318 | current = -1 319 | exist_count = 0 320 | for i in range(len(arr)): 321 | if arr[i] is None: 322 | if current == -1: 323 | current = i 324 | else: 325 | exist_count += 1 326 | if exist_count == 2 or exist_count == 1: 327 | return True, current, exist_count + 1 328 | else: 329 | return False, -1, exist_count + 1 330 | 331 | def mouse_reset(self): 332 | # logger.debug("mouse_reset") 333 | try: 334 | current_position = win32api.GetCursorPos() 335 | if self.mouse_pos: 336 | distance = math.sqrt( 337 | (current_position[0] - self.mouse_pos[0]) ** 2 338 | + (current_position[1] - self.mouse_pos[1]) ** 2 339 | ) 340 | if distance > 400: 341 | logger.debug(f'move mouse back {self.mouse_pos}') 342 | win32api.SetCursorPos(self.mouse_pos) 343 | self.mouse_pos = None 344 | if self.enabled: 345 | self.handler.post(self.mouse_reset, 1) 346 | return 347 | self.mouse_pos = current_position 348 | if self.enabled: 349 | return self.handler.post(self.mouse_reset, 0.005) 350 | except Exception as e: 351 | logger.error('mouse_reset exception', e) 352 | 353 | 354 | white_color = { 355 | 'r': (253, 255), # Red range 356 | 'g': (253, 255), # Green range 357 | 'b': (253, 255) # Blue range 358 | } 359 | -------------------------------------------------------------------------------- /src/char/BaseChar.py: -------------------------------------------------------------------------------- 1 | import time 2 | from enum import IntEnum, StrEnum 3 | from typing import Any 4 | 5 | import cv2 6 | import numpy as np 7 | 8 | from ok.color.Color import get_connected_area_by_color, color_range_to_bound 9 | from ok.config.Config import Config 10 | from ok.logging.Logger import get_logger 11 | from src import text_white_color 12 | 13 | 14 | class Priority(IntEnum): 15 | MIN = -999999999 16 | SWITCH_CD = -1000 17 | CURRENT_CHAR = -100 18 | SKILL_AVAILABLE = 100 19 | ALL_IN_CD = 0 20 | NORMAL = 10 21 | MAX = 9999999999 22 | 23 | 24 | class Role(StrEnum): 25 | DEFAULT = 'Default' 26 | SUB_DPS = 'Sub DPS' 27 | MAIN_DPS = 'Main DPS' 28 | HEALER = 'Healer' 29 | 30 | 31 | role_values = [role for role in Role] 32 | 33 | char_lib_check_marks = ['char_1_lib_check_mark', 'char_2_lib_check_mark', 'char_3_lib_check_mark'] 34 | 35 | 36 | class BaseChar: 37 | 38 | def __init__(self, task, index, res_cd=0, echo_cd=0): 39 | self.white_off_threshold = 0.01 40 | self.echo_cd = echo_cd 41 | self.task = task 42 | self.sleep_adjust = 0 43 | self.index = index 44 | self.last_switch_time = -1 45 | self.last_res = -1 46 | self.last_echo = -1 47 | self.has_intro = False 48 | self.res_cd = res_cd 49 | self.is_current_char = False 50 | self.liberation_available_mark = False 51 | self.logger = get_logger(self.name) 52 | self.full_ring_area = 0 53 | self._is_forte_full = False 54 | self.config = {"_full_ring_area": 0, "_ring_color_index": -1} 55 | if type(self) is not BaseChar: 56 | self.config = Config(self.name, self.config) 57 | self.current_con = 0 58 | 59 | def char_config(self): 60 | return {} 61 | 62 | @property 63 | def name(self): 64 | return self.__class__.__name__ 65 | 66 | def __eq__(self, other): 67 | if isinstance(other, BaseChar): 68 | return self.name == other.name and self.index == other.index 69 | return False 70 | 71 | def perform(self): 72 | # self.wait_down() 73 | self.do_perform() 74 | self.logger.debug(f'set current char false {self.index}') 75 | 76 | def wait_down(self): 77 | start = time.time() 78 | while self.flying(): 79 | self.task.click() 80 | self.sleep(0.2) 81 | 82 | self.task.screenshot( 83 | f'{self}_down_finish_{(time.time() - start):.2f}_f:{self.is_forte_full()}_e:{self.resonance_available()}_r:{self.echo_available()}_q:{self.liberation_available()}_i{self.has_intro}') 84 | 85 | def click(self, *args: Any, **kwargs: Any): 86 | self.task.click(*args, **kwargs) 87 | 88 | def do_perform(self): 89 | self.click_liberation(con_less_than=1) 90 | if self.click_resonance()[0]: 91 | return self.switch_next_char() 92 | if self.click_echo(): 93 | return self.switch_next_char() 94 | self.task.click() 95 | self.switch_next_char() 96 | 97 | def has_cd(self, box_name): 98 | box = self.task.get_box_by_name(f'box_{box_name}') 99 | cropped = box.crop_frame(self.task.frame) 100 | num_labels, stats = get_connected_area_by_color(cropped, dot_color, connectivity=8) 101 | big_area_count = 0 102 | has_dot = False 103 | number_count = 0 104 | invalid_count = 0 105 | for i in range(1, num_labels): 106 | # Check if the connected co mponent touches the border 107 | left, top, width, height, area = stats[i] 108 | if area / self.task.frame.shape[0] / self.task.frame.shape[ 109 | 1] > 20 / 3840 / 2160: 110 | big_area_count += 1 111 | if left > 0 and top > 0 and left + width < box.width and top + height < box.height: 112 | # self.logger.debug(f"{box_name} Area of connected component {i}: {area} pixels {width}x{height}") 113 | if 16 / 3840 / 2160 <= area / self.task.frame.shape[0] / self.task.frame.shape[ 114 | 1] <= 60 / 3840 / 2160 and abs(width - height) / (width + height) < 0.3: 115 | has_dot = True 116 | elif 25 / 2160 <= height / self.task.screen_height <= 45 / 2160 and 5 / 2160 <= width / self.task.screen_height <= 35 / 2160: 117 | number_count += 1 118 | else: 119 | invalid_count += 1 120 | has_cd = invalid_count == 0 and (has_dot and 2 <= number_count <= 3) 121 | # if self.task.debug: 122 | # msg = f"{self}_{has_cd}_{box_name} number_count {number_count} big_count {big_area_count} invalid_count {invalid_count} has_dot {has_dot}" 123 | # self.task.screenshot(msg, frame=cropped) 124 | # self.logger.debug(msg) 125 | return has_cd 126 | 127 | def is_available(self, percent, box_name): 128 | return percent == 0 or not self.has_cd(box_name) 129 | 130 | def switch_out(self): 131 | self.is_current_char = False 132 | self.has_intro = False 133 | self.liberation_available_mark = self.liberation_available() 134 | if self.current_con == 1: 135 | self.logger.info(f'switch_out at full con set current_con to 0') 136 | self.current_con = 0 137 | 138 | def __repr__(self): 139 | return self.__class__.__name__ + ('_T' if self.is_current_char else '_F') 140 | 141 | def switch_next_char(self, post_action=None, free_intro=False, target_low_con=False): 142 | self.is_forte_full() 143 | self.last_switch_time = self.task.switch_next_char(self, post_action=post_action, free_intro=free_intro, 144 | target_low_con=target_low_con) 145 | 146 | def sleep(self, sec, check_combat=True): 147 | if sec > 0: 148 | self.task.sleep_check_combat(sec + self.sleep_adjust, check_combat=check_combat) 149 | 150 | def click_resonance(self, post_sleep=0, has_animation=False, send_click=True): 151 | clicked = False 152 | self.logger.debug(f'click_resonance start') 153 | last_click = 0 154 | last_op = 'click' 155 | resonance_click_time = 0 156 | animated = False 157 | while True: 158 | if resonance_click_time != 0 and time.time() - resonance_click_time > 8: 159 | self.logger.error(f'click_resonance too long, breaking {time.time() - resonance_click_time}') 160 | self.task.screenshot('click_resonance too long, breaking') 161 | break 162 | if has_animation: 163 | if not self.task.in_team()[0]: 164 | self.task.in_liberation = True 165 | animated = True 166 | if time.time() - resonance_click_time > 6: 167 | self.task.in_liberation = False 168 | self.logger.error(f'resonance animation too long, breaking') 169 | self.task.next_frame() 170 | self.check_combat() 171 | continue 172 | self.check_combat() 173 | current_resonance = self.current_resonance() 174 | if not self.resonance_available(current_resonance): 175 | self.logger.debug(f'click_resonance not available break') 176 | break 177 | self.logger.debug(f'click_resonance resonance_available click') 178 | now = time.time() 179 | if now - last_click > 0.1: 180 | if ((current_resonance == 0) and send_click) or last_op == 'resonance': 181 | self.task.click() 182 | last_op = 'click' 183 | continue 184 | if current_resonance > 0: 185 | if resonance_click_time == 0: 186 | clicked = True 187 | resonance_click_time = now 188 | self.update_res_cd() 189 | last_op = 'resonance' 190 | self.send_resonance_key() 191 | if has_animation: # sleep if there will be an animation like Jinhsi 192 | self.sleep(0.2, check_combat=False) 193 | last_click = now 194 | self.task.next_frame() 195 | self.task.in_liberation = False 196 | if clicked: 197 | self.sleep(post_sleep) 198 | duration = time.time() - resonance_click_time if resonance_click_time != 0 else 0 199 | self.logger.info(f'click_resonance end clicked {clicked} duration {duration} animated {animated}') 200 | return clicked, duration, animated 201 | 202 | def send_resonance_key(self, post_sleep=0, interval=-1, down_time=0.01): 203 | self.task.send_key(self.task.key_config.get('Resonance Key'), interval=interval, down_time=down_time) 204 | self.sleep(post_sleep) 205 | 206 | def update_res_cd(self): 207 | current = time.time() 208 | if current - self.last_res > self.res_cd: # count the first click only 209 | self.last_res = time.time() 210 | 211 | def update_echo_cd(self): 212 | current = time.time() 213 | if current - self.last_echo > self.echo_cd: # count the first click only 214 | self.last_echo = time.time() 215 | 216 | def click_echo(self, duration=0, sleep_time=0): 217 | self.logger.debug(f'click_echo start') 218 | clicked = False 219 | start = 0 220 | last_click = 0 221 | while True: 222 | self.check_combat() 223 | current = self.current_echo() 224 | if duration == 0 and not self.echo_available(current): 225 | break 226 | now = time.time() 227 | if duration > 0 and start != 0: 228 | if now - start > duration: 229 | break 230 | self.logger.debug(f'click_echo echo_available click') 231 | if now - last_click > 0.1: 232 | if current == 0: 233 | self.task.click() 234 | else: 235 | if start == 0: 236 | start = now 237 | clicked = True 238 | self.update_echo_cd() 239 | self.task.send_key(self.get_echo_key()) 240 | last_click = now 241 | self.task.next_frame() 242 | self.logger.debug(f'click_echo end {clicked}') 243 | return clicked 244 | 245 | def check_combat(self): 246 | self.task.check_combat() 247 | 248 | def reset_state(self): 249 | self.logger.info('reset state') 250 | self.has_intro = False 251 | 252 | def click_liberation(self, wait_end=True, con_less_than=-1, send_click=False): 253 | if con_less_than > 0: 254 | if self.get_current_con() > con_less_than: 255 | return False 256 | self.task.in_liberation = True 257 | self.logger.debug(f'click_liberation start') 258 | start = time.time() 259 | last_click = 0 260 | clicked = False 261 | while self.liberation_available(): 262 | self.logger.debug(f'click_liberation liberation_available click') 263 | now = time.time() 264 | if now - last_click > 0.1: 265 | self.task.last_click_liberation = now 266 | self.task.send_key(self.get_liberation_key()) 267 | self.liberation_available_mark = False 268 | clicked = True 269 | last_click = now 270 | if time.time() - start > 5: 271 | self.task.raise_not_in_combat('too long clicking a liberation') 272 | self.task.next_frame() 273 | if clicked: 274 | if self.task.wait_until(lambda: not self.task.in_team()[0], time_out=0.6): 275 | self.logger.debug(f'not in_team successfully casted liberation') 276 | else: 277 | self.task.in_liberation = False 278 | self.logger.error(f'clicked liberation but no effect') 279 | return False 280 | while not self.task.in_team()[0]: 281 | clicked = True 282 | if send_click: 283 | self.task.click(interval=0.1) 284 | if time.time() - start > 7: 285 | self.task.raise_not_in_combat('too long a liberation, the boss was killed by the liberation') 286 | self.task.next_frame() 287 | self.task.in_liberation = False 288 | if clicked: 289 | liberation_time = f'{(time.time() - start):.2f}' 290 | self.logger.info(f'click_liberation end {liberation_time}') 291 | return clicked 292 | 293 | def get_liberation_key(self): 294 | return self.task.key_config['Liberation Key'] 295 | 296 | def get_echo_key(self): 297 | return self.task.key_config['Echo Key'] 298 | 299 | def get_resonance_key(self): 300 | return self.task.key_config['Resonance Key'] 301 | 302 | def get_switch_priority(self, current_char, has_intro): 303 | priority = self.do_get_switch_priority(current_char, has_intro) 304 | if priority != Priority.MAX and time.time() - self.last_switch_time < 0.9: 305 | return Priority.SWITCH_CD # switch cd 306 | else: 307 | return priority 308 | 309 | def do_get_switch_priority(self, current_char, has_intro=False): 310 | priority = 0 311 | if self.count_liberation_priority() and self.liberation_available(): 312 | priority += self.count_liberation_priority() 313 | if self.count_resonance_priority() and self.resonance_available(): 314 | priority += self.count_resonance_priority() 315 | if self.count_forte_priority() and self._is_forte_full: 316 | priority += self.count_forte_priority() 317 | if priority > 0: 318 | priority += Priority.SKILL_AVAILABLE 319 | priority += self.count_liberation_priority() 320 | return priority 321 | 322 | def count_base_priority(self): 323 | return 0 324 | 325 | def count_liberation_priority(self): 326 | return 1 327 | 328 | def count_resonance_priority(self): 329 | return 10 330 | 331 | def count_echo_priority(self): 332 | return 1 333 | 334 | def count_forte_priority(self): 335 | return 0 336 | 337 | def resonance_available(self, current=None, check_ready=False): 338 | if self.is_current_char: 339 | snap = self.current_resonance() if current is None else current 340 | if check_ready and snap == 0: 341 | return False 342 | return self.is_available(snap, 'resonance') 343 | elif self.res_cd > 0: 344 | return time.time() - self.last_res > self.res_cd 345 | 346 | def echo_available(self, current=None): 347 | if self.is_current_char: 348 | snap = self.current_echo() if current is None else current 349 | return self.is_available(snap, 'echo') 350 | elif self.echo_cd > 0: 351 | return time.time() - self.last_echo > self.echo_cd 352 | 353 | def is_con_full(self): 354 | return self.get_current_con() == 1 355 | 356 | def get_current_con(self): 357 | box = self.task.box_of_screen(1422 / 3840, 1939 / 2160, 1566 / 3840, 2076 / 2160, name='con_full') 358 | box.confidence = 0 359 | 360 | max_area = 0 361 | percent = 0 362 | max_is_full = False 363 | color_index = -1 364 | target_index = self.config.get('_ring_color_index', -1) 365 | cropped = box.crop_frame(self.task.frame) 366 | for i in range(len(con_colors)): 367 | if target_index != -1 and i != target_index: 368 | continue 369 | color_range = con_colors[i] 370 | area, is_full = self.count_rings(cropped, color_range, 371 | 1500 / 3840 / 2160 * self.task.screen_width * self.task.screen_height) 372 | self.logger.debug(f'is_con_full test color_range {color_range} {area, is_full}') 373 | if is_full: 374 | max_is_full = is_full 375 | color_index = i 376 | if area > max_area: 377 | max_area = int(area) 378 | if max_is_full: 379 | self.logger.info( 380 | f'is_con_full found a full ring {self.config.get("_full_ring_area", 0)} -> {max_area} {color_index}') 381 | self.config['_full_ring_area'] = max_area 382 | self.config['_ring_color_index'] = color_index 383 | self.logger.info( 384 | f'is_con_full2 found a full ring {self.config.get("_full_ring_area", 0)} -> {max_area} {color_index}') 385 | if self.config.get('_full_ring_area', 0) > 0: 386 | percent = max_area / self.config['_full_ring_area'] 387 | if not max_is_full and percent >= 1: 388 | self.logger.warning( 389 | f'is_con_full not full but percent greater than 1, set to 0.99, {percent} {max_is_full}') 390 | # self.task.screenshot( 391 | # f'is_con_full not full but percent greater than 1, set to 0.99, {percent} {max_is_full}', 392 | # cropped) 393 | percent = 0.99 394 | if percent > 1: 395 | self.logger.error(f'is_con_full percent greater than 1, set to 1, {percent} {max_is_full}') 396 | self.task.screenshot(f'is_con_full percent greater than 1, set to 1, {percent} {max_is_full}', cropped) 397 | percent = 1 398 | self.logger.info( 399 | f'is_con_full {self} {percent} {max_area}/{self.config.get("_full_ring_area", 0)} {color_index} ') 400 | # if self.task.debug: 401 | # self.task.screenshot( 402 | # f'is_con_full {self} {percent} {max_area}/{self.config.get("_full_ring_area", 0)} {color_index} ', 403 | # cropped) 404 | box.confidence = percent 405 | self.current_con = percent 406 | self.task.draw_boxes(f'is_con_full_{self}', box) 407 | if percent > 1: 408 | percent = 1 409 | return percent 410 | 411 | def is_forte_full(self): 412 | box = self.task.box_of_screen(2251 / 3840, 1993 / 2160, 2311 / 3840, 2016 / 2160, name='forte_full') 413 | white_percent = self.task.calculate_color_percentage(forte_white_color, box) 414 | # num_labels, stats = get_connected_area_by_color(box.crop_frame(self.task.frame), forte_white_color, 415 | # connectivity=8) 416 | # total_area = 0 417 | # for i in range(1, num_labels): 418 | # # Check if the connected co mponent touches the border 419 | # left, top, width, height, area = stats[i] 420 | # total_area += area 421 | # white_percent = total_area / box.width / box.height 422 | # if self.task.debug: 423 | # self.task.screenshot(f'{self}_forte_{white_percent}') 424 | self.logger.debug(f'is_forte_full {white_percent}') 425 | box.confidence = white_percent 426 | self.task.draw_boxes('forte_full', box) 427 | self._is_forte_full = white_percent > 0.08 428 | return self._is_forte_full 429 | 430 | def liberation_available(self): 431 | if self.liberation_available_mark: 432 | return True 433 | if self.is_current_char: 434 | snap = self.current_liberation() 435 | if snap == 0: 436 | return False 437 | else: 438 | return self.is_available(snap, 'liberation') 439 | # else: 440 | # mark_to_check = char_lib_check_marks[self.index] 441 | # box = self.task.get_box_by_name(mark_to_check) 442 | # box = box.copy(x_offset=-box.width, y_offset=-box.height, width_offset=box.width * 2, 443 | # height_offset=box.height * 2) 444 | # for match in char_lib_check_marks: 445 | # mark = self.task.find_one(match, box=box, canny_lower=10, canny_higher=80, threshold=0.8) 446 | # if mark is not None: 447 | # self.logger.debug(f'{self.__repr__()} liberation ready by checking mark {mark}') 448 | # self.liberation_available_mark = True 449 | # return True 450 | 451 | def __str__(self): 452 | return self.__repr__() 453 | 454 | def continues_normal_attack(self, duration, interval=0.1, click_resonance_if_ready_and_return=False, 455 | until_con_full=False): 456 | start = time.time() 457 | while time.time() - start < duration: 458 | if click_resonance_if_ready_and_return and self.resonance_available(): 459 | return self.click_resonance() 460 | if until_con_full and self.is_con_full(): 461 | return 462 | self.task.click(interval=interval) 463 | 464 | def normal_attack(self): 465 | self.logger.debug('normal attack') 466 | self.check_combat() 467 | self.task.click() 468 | 469 | def heavy_attack(self, duration=0.6): 470 | self.check_combat() 471 | self.logger.debug('heavy attack start') 472 | self.task.mouse_down() 473 | self.sleep(duration) 474 | self.task.mouse_up() 475 | self.logger.debug('heavy attack end') 476 | 477 | def current_resonance(self): 478 | return self.task.calculate_color_percentage(text_white_color, 479 | self.task.get_box_by_name('box_resonance')) 480 | 481 | def current_echo(self): 482 | return self.task.calculate_color_percentage(text_white_color, 483 | self.task.get_box_by_name('box_echo')) 484 | 485 | def current_liberation(self): 486 | return self.task.calculate_color_percentage(text_white_color, self.task.get_box_by_name('box_liberation')) 487 | 488 | def flying(self): 489 | return self.current_resonance() == 0 490 | 491 | def count_rings(self, image, color_range, min_area): 492 | # Define the color range 493 | lower_bound, upper_bound = color_range_to_bound(color_range) 494 | 495 | image_with_contours = image.copy() 496 | 497 | # Create a binary mask 498 | mask = cv2.inRange(image, lower_bound, upper_bound) 499 | 500 | # Find connected components 501 | num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, connectivity=8) 502 | 503 | colors = [ 504 | (0, 255, 0), # Green 505 | (0, 0, 255), # Red 506 | (255, 0, 0), # Blue 507 | (0, 255, 255), # Yellow 508 | (255, 0, 255), # Magenta 509 | (255, 255, 0) # Cyan 510 | ] 511 | 512 | # Function to check if a component forms a ring 513 | def is_full_ring(component_mask): 514 | # Find contours 515 | contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 516 | if len(contours) != 1: 517 | return False 518 | contour = contours[0] 519 | 520 | # Check if the contour is closed by checking if the start and end points are the same 521 | # if cv2.arcLength(contour, True) > 0: 522 | # return True 523 | # Approximate the contour with polygons. 524 | epsilon = 0.05 * cv2.arcLength(contour, True) 525 | approx = cv2.approxPolyDP(contour, epsilon, True) 526 | 527 | # Check if the polygon is closed (has no gaps) and has a reasonable number of vertices for a ring. 528 | if not cv2.isContourConvex(approx) or len(approx) < 4: 529 | return False 530 | 531 | # All conditions met, likely a close ring. 532 | return True 533 | 534 | # Iterate over each component 535 | ring_count = 0 536 | is_full = False 537 | the_area = 0 538 | for label in range(1, num_labels): 539 | x, y, width, height, area = stats[label, :5] 540 | bounding_box_area = width * height 541 | component_mask = (labels == label).astype(np.uint8) * 255 542 | contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 543 | color = colors[label % len(colors)] 544 | cv2.drawContours(image_with_contours, contours, -1, color, 2) 545 | if bounding_box_area >= min_area: 546 | # Select a color from the list based on the label index 547 | if is_full_ring(component_mask): 548 | is_full = True 549 | the_area = area 550 | ring_count += 1 551 | 552 | if self.task.debug: 553 | # Save or display the image with contours 554 | cv2.imwrite(f'test\\test_{self}_{is_full}_{the_area}_{lower_bound}.jpg', image_with_contours) 555 | if ring_count > 1: 556 | is_full = False 557 | the_area = 0 558 | self.logger.warning(f'is_con_full found multiple rings {ring_count}') 559 | 560 | return the_area, is_full 561 | 562 | 563 | forte_white_color = { 564 | 'r': (244, 255), # Red range 565 | 'g': (246, 255), # Green range 566 | 'b': (250, 255) # Blue range 567 | } 568 | 569 | dot_color = { 570 | 'r': (235, 255), # Red range 571 | 'g': (235, 255), # Green range 572 | 'b': (235, 255) # Blue range 573 | } 574 | 575 | con_colors = [ 576 | { 577 | 'r': (205, 235), 578 | 'g': (190, 222), # for yellow spectro 579 | 'b': (90, 130) 580 | }, 581 | { 582 | 'r': (150, 190), # Red range 583 | 'g': (95, 140), # Green range for purple electric 584 | 'b': (210, 249) # Blue range 585 | }, 586 | { 587 | 'r': (200, 230), # Red range 588 | 'g': (100, 130), # Green range for red fire 589 | 'b': (75, 105) # Blue range 590 | }, 591 | { 592 | 'r': (60, 95), # Red range 593 | 'g': (150, 180), # Green range for blue ice 594 | 'b': (210, 245) # Blue range 595 | }, 596 | { 597 | 'r': (70, 110), # Red range 598 | 'g': (215, 250), # Green range for green wind 599 | 'b': (155, 190) # Blue range 600 | }, 601 | { 602 | 'r': (190, 220), # Red range 603 | 'g': (65, 105), # Green range for havoc 604 | 'b': (145, 175) # Blue range 605 | } 606 | ] 607 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /assets/_annotations.coco.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "year": "2024", 4 | "version": "25", 5 | "description": "Exported from roboflow.com", 6 | "contributor": "", 7 | "url": "https://public.roboflow.com/object-detection/undefined", 8 | "date_created": "2024-07-22T07:36:05+00:00" 9 | }, 10 | "licenses": [ 11 | { 12 | "id": 1, 13 | "url": "https://creativecommons.org/licenses/by/4.0/", 14 | "name": "CC BY 4.0" 15 | } 16 | ], 17 | "categories": [ 18 | { 19 | "id": 0, 20 | "name": "ww", 21 | "supercategory": "none" 22 | }, 23 | { 24 | "id": 1, 25 | "name": "boss_proceed", 26 | "supercategory": "ww" 27 | }, 28 | { 29 | "id": 2, 30 | "name": "box_char1_switch_wave", 31 | "supercategory": "ww" 32 | }, 33 | { 34 | "id": 3, 35 | "name": "box_char2_switch_wave", 36 | "supercategory": "ww" 37 | }, 38 | { 39 | "id": 4, 40 | "name": "box_char3_switch_wave", 41 | "supercategory": "ww" 42 | }, 43 | { 44 | "id": 5, 45 | "name": "box_char_1", 46 | "supercategory": "ww" 47 | }, 48 | { 49 | "id": 6, 50 | "name": "box_char_2", 51 | "supercategory": "ww" 52 | }, 53 | { 54 | "id": 7, 55 | "name": "box_char_3", 56 | "supercategory": "ww" 57 | }, 58 | { 59 | "id": 8, 60 | "name": "box_concerto_last_dot", 61 | "supercategory": "ww" 62 | }, 63 | { 64 | "id": 9, 65 | "name": "box_echo", 66 | "supercategory": "ww" 67 | }, 68 | { 69 | "id": 10, 70 | "name": "box_liberation", 71 | "supercategory": "ww" 72 | }, 73 | { 74 | "id": 11, 75 | "name": "box_resonance", 76 | "supercategory": "ww" 77 | }, 78 | { 79 | "id": 12, 80 | "name": "btn_auto_play_dialog", 81 | "supercategory": "ww" 82 | }, 83 | { 84 | "id": 13, 85 | "name": "btn_dialog_3dots", 86 | "supercategory": "ww" 87 | }, 88 | { 89 | "id": 14, 90 | "name": "btn_dialog_arrow", 91 | "supercategory": "ww" 92 | }, 93 | { 94 | "id": 15, 95 | "name": "btn_dialog_close", 96 | "supercategory": "ww" 97 | }, 98 | { 99 | "id": 16, 100 | "name": "btn_dialog_eye", 101 | "supercategory": "ww" 102 | }, 103 | { 104 | "id": 17, 105 | "name": "cancel_button", 106 | "supercategory": "ww" 107 | }, 108 | { 109 | "id": 18, 110 | "name": "chang_changli", 111 | "supercategory": "ww" 112 | }, 113 | { 114 | "id": 19, 115 | "name": "char_1_lib_check_mark", 116 | "supercategory": "ww" 117 | }, 118 | { 119 | "id": 20, 120 | "name": "char_1_text", 121 | "supercategory": "ww" 122 | }, 123 | { 124 | "id": 21, 125 | "name": "char_2_lib_check_mark", 126 | "supercategory": "ww" 127 | }, 128 | { 129 | "id": 22, 130 | "name": "char_2_text", 131 | "supercategory": "ww" 132 | }, 133 | { 134 | "id": 23, 135 | "name": "char_3_lib_check_mark", 136 | "supercategory": "ww" 137 | }, 138 | { 139 | "id": 24, 140 | "name": "char_3_text", 141 | "supercategory": "ww" 142 | }, 143 | { 144 | "id": 25, 145 | "name": "char_baizhi", 146 | "supercategory": "ww" 147 | }, 148 | { 149 | "id": 26, 150 | "name": "char_calcharo", 151 | "supercategory": "ww" 152 | }, 153 | { 154 | "id": 27, 155 | "name": "char_chixia", 156 | "supercategory": "ww" 157 | }, 158 | { 159 | "id": 28, 160 | "name": "char_danjin", 161 | "supercategory": "ww" 162 | }, 163 | { 164 | "id": 29, 165 | "name": "char_encore", 166 | "supercategory": "ww" 167 | }, 168 | { 169 | "id": 30, 170 | "name": "char_jianxin", 171 | "supercategory": "ww" 172 | }, 173 | { 174 | "id": 31, 175 | "name": "char_jinhsi", 176 | "supercategory": "ww" 177 | }, 178 | { 179 | "id": 32, 180 | "name": "char_jiyan", 181 | "supercategory": "ww" 182 | }, 183 | { 184 | "id": 33, 185 | "name": "char_mortefi", 186 | "supercategory": "ww" 187 | }, 188 | { 189 | "id": 34, 190 | "name": "char_rover", 191 | "supercategory": "ww" 192 | }, 193 | { 194 | "id": 35, 195 | "name": "char_sanhua", 196 | "supercategory": "ww" 197 | }, 198 | { 199 | "id": 36, 200 | "name": "char_taoqi", 201 | "supercategory": "ww" 202 | }, 203 | { 204 | "id": 37, 205 | "name": "char_verina", 206 | "supercategory": "ww" 207 | }, 208 | { 209 | "id": 38, 210 | "name": "char_yinlin", 211 | "supercategory": "ww" 212 | }, 213 | { 214 | "id": 39, 215 | "name": "char_yuanwu", 216 | "supercategory": "ww" 217 | }, 218 | { 219 | "id": 40, 220 | "name": "custom_teleport", 221 | "supercategory": "ww" 222 | }, 223 | { 224 | "id": 41, 225 | "name": "dialog_3_dots", 226 | "supercategory": "ww" 227 | }, 228 | { 229 | "id": 42, 230 | "name": "edge_echo_cd_dot", 231 | "supercategory": "ww" 232 | }, 233 | { 234 | "id": 43, 235 | "name": "edge_levitator", 236 | "supercategory": "ww" 237 | }, 238 | { 239 | "id": 44, 240 | "name": "fast_travel_custom", 241 | "supercategory": "ww" 242 | }, 243 | { 244 | "id": 45, 245 | "name": "gray_book_boss", 246 | "supercategory": "ww" 247 | }, 248 | { 249 | "id": 46, 250 | "name": "gray_book_boss_highlight", 251 | "supercategory": "ww" 252 | }, 253 | { 254 | "id": 47, 255 | "name": "gray_button_challenge", 256 | "supercategory": "ww" 257 | }, 258 | { 259 | "id": 48, 260 | "name": "gray_combat_count_down", 261 | "supercategory": "ww" 262 | }, 263 | { 264 | "id": 49, 265 | "name": "gray_confirm_exit_button", 266 | "supercategory": "ww" 267 | }, 268 | { 269 | "id": 50, 270 | "name": "gray_crownless_battle", 271 | "supercategory": "ww" 272 | }, 273 | { 274 | "id": 51, 275 | "name": "gray_custom_way_point", 276 | "supercategory": "ww" 277 | }, 278 | { 279 | "id": 52, 280 | "name": "gray_start_battle", 281 | "supercategory": "ww" 282 | }, 283 | { 284 | "id": 53, 285 | "name": "gray_teleport", 286 | "supercategory": "ww" 287 | }, 288 | { 289 | "id": 54, 290 | "name": "monthly_card", 291 | "supercategory": "ww" 292 | }, 293 | { 294 | "id": 55, 295 | "name": "pick_up_f", 296 | "supercategory": "ww" 297 | }, 298 | { 299 | "id": 56, 300 | "name": "revive_confirm", 301 | "supercategory": "ww" 302 | }, 303 | { 304 | "id": 57, 305 | "name": "target_enemy_white", 306 | "supercategory": "ww" 307 | } 308 | ], 309 | "images": [ 310 | { 311 | "id": 0, 312 | "license": 1, 313 | "file_name": "WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_1577828_1_False_original_png.rf.3ed83cbce453e93e1027f37184cff0a5.png", 314 | "height": 2160, 315 | "width": 3840, 316 | "date_captured": "2024-07-22T07:36:05+00:00" 317 | }, 318 | { 319 | "id": 1, 320 | "license": 1, 321 | "file_name": "154eb284-17_01_16_406889_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_6yGu616_png.rf.12ebc1f4e7e5208bcbf2c7f878f691aa.png", 322 | "height": 2160, 323 | "width": 3840, 324 | "date_captured": "2024-07-22T07:36:05+00:00" 325 | }, 326 | { 327 | "id": 2, 328 | "license": 1, 329 | "file_name": "0ab28c72-00_54_43_431654_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_usg73Cm_png.rf.124dc89f9b3816ecb0ce5a97c95475dc.png", 330 | "height": 2160, 331 | "width": 3840, 332 | "date_captured": "2024-07-22T07:36:05+00:00" 333 | }, 334 | { 335 | "id": 3, 336 | "license": 1, 337 | "file_name": "55617870-00_55_49_536245_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_NSeTZ2S_png.rf.5947b06a7caca9b89c2e06ec7ceab8fa.png", 338 | "height": 2160, 339 | "width": 3840, 340 | "date_captured": "2024-07-22T07:36:05+00:00" 341 | }, 342 | { 343 | "id": 4, 344 | "license": 1, 345 | "file_name": "WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_10361910_1_False_original_png.rf.244ee2bd0b4de93caac3066adaae0b75.png", 346 | "height": 2160, 347 | "width": 3840, 348 | "date_captured": "2024-07-22T07:36:05+00:00" 349 | }, 350 | { 351 | "id": 5, 352 | "license": 1, 353 | "file_name": "9beed513-16_15_23_246388_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_a4tQhDg_png.rf.4e79b52b95ad46f47bc470f069a65b29.png", 354 | "height": 2160, 355 | "width": 3840, 356 | "date_captured": "2024-07-22T07:36:05+00:00" 357 | }, 358 | { 359 | "id": 6, 360 | "license": 1, 361 | "file_name": "01304a45-10_34_04_580378_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win_WnYCyUT_png.rf.7b5dfc4585195e1d846574b715468897.png", 362 | "height": 2160, 363 | "width": 3840, 364 | "date_captured": "2024-07-22T07:36:05+00:00" 365 | }, 366 | { 367 | "id": 7, 368 | "license": 1, 369 | "file_name": "WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_12847792_1_False_original_png.rf.abeaa08198ff5a77fb70340952ba23f3.png", 370 | "height": 2160, 371 | "width": 3840, 372 | "date_captured": "2024-07-22T07:36:05+00:00" 373 | }, 374 | { 375 | "id": 8, 376 | "license": 1, 377 | "file_name": "WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_7476412_1_False_original_png.rf.a21fbbbefc2fa48f56917d19a6a443b2.png", 378 | "height": 2160, 379 | "width": 3840, 380 | "date_captured": "2024-07-22T07:36:05+00:00" 381 | }, 382 | { 383 | "id": 9, 384 | "license": 1, 385 | "file_name": "5841f84c-17_04_26_738022_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_TIDxIl1_png.rf.97a22c8e01f1fd88debc20c4c903b8b9.png", 386 | "height": 2160, 387 | "width": 3840, 388 | "date_captured": "2024-07-22T07:36:05+00:00" 389 | }, 390 | { 391 | "id": 10, 392 | "license": 1, 393 | "file_name": "9ed8b373-00_57_50_813872_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_3jLoRJh_png.rf.170fe2ae11c1d8058d8d3e4b2361357a.png", 394 | "height": 2160, 395 | "width": 3840, 396 | "date_captured": "2024-07-22T07:36:05+00:00" 397 | }, 398 | { 399 | "id": 11, 400 | "license": 1, 401 | "file_name": "e521850f-00_37_38_458841_Encore_liberation_0-274_original_png.rf.e7703de2285a6b2c70567e891804c760.png", 402 | "height": 2160, 403 | "width": 3840, 404 | "date_captured": "2024-07-22T07:36:05+00:00" 405 | }, 406 | { 407 | "id": 12, 408 | "license": 1, 409 | "file_name": "WindowsGraphicsCaptureMethod_1920x1080_title_None_Client-Win64-Shipping-exe_1920x1080_329010_1_False_original_png.rf.b6f619b8fe6f0e1e8a0993a5e61e2801.png", 410 | "height": 1080, 411 | "width": 1920, 412 | "date_captured": "2024-07-22T07:36:05+00:00" 413 | }, 414 | { 415 | "id": 13, 416 | "license": 1, 417 | "file_name": "WindowsGraphicsCaptureMethod_3840x2160_title_None_Client-Win64-Shipping-exe_3840x2160_4720836_1_False_original_png.rf.935bb51188f6bc516efc5a57b52fe8ad.png", 418 | "height": 2160, 419 | "width": 3840, 420 | "date_captured": "2024-07-22T07:36:05+00:00" 421 | }, 422 | { 423 | "id": 14, 424 | "license": 1, 425 | "file_name": "WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_1841174_1_False_original-1-_png.rf.e6c4a5734163badfbfa76780248cd2f8.png", 426 | "height": 2160, 427 | "width": 3840, 428 | "date_captured": "2024-07-22T07:36:05+00:00" 429 | }, 430 | { 431 | "id": 15, 432 | "license": 1, 433 | "file_name": "9dd77a5d-16_57_31_014010_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_RVvCfQe_png.rf.e8397fbd133d485459bb8c30fc37353c.png", 434 | "height": 2160, 435 | "width": 3840, 436 | "date_captured": "2024-07-22T07:36:05+00:00" 437 | }, 438 | { 439 | "id": 16, 440 | "license": 1, 441 | "file_name": "3b991d47-10_18_37_465122_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_3Hw8AdB_png.rf.7b75bb60b5b041f3c6f36e735ca3bc19.png", 442 | "height": 2160, 443 | "width": 3840, 444 | "date_captured": "2024-07-22T07:36:05+00:00" 445 | }, 446 | { 447 | "id": 17, 448 | "license": 1, 449 | "file_name": "934f0ca6-17_53_47_840123_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_aJQVINN_png.rf.a107256237035eec3be4125f726b8b73.png", 450 | "height": 2160, 451 | "width": 3840, 452 | "date_captured": "2024-07-22T07:36:05+00:00" 453 | }, 454 | { 455 | "id": 18, 456 | "license": 1, 457 | "file_name": "WindowsGraphicsCaptureMethod_2560x1440_title_None_Client-Win64-Shipping-exe_3840x2160_465786_1_False_original_png.rf.015495d17edfa6c8bf29047cfcc19d6d.png", 458 | "height": 2160, 459 | "width": 3840, 460 | "date_captured": "2024-07-22T07:36:05+00:00" 461 | }, 462 | { 463 | "id": 19, 464 | "license": 1, 465 | "file_name": "9f04db98-16_20_58_825243_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_VuLpBVc_png.rf.a16ce4854c42840f44a6c0a04d44c83c.png", 466 | "height": 2160, 467 | "width": 3840, 468 | "date_captured": "2024-07-22T07:36:05+00:00" 469 | }, 470 | { 471 | "id": 20, 472 | "license": 1, 473 | "file_name": "4_png.rf.180bb3f2bed38134b1d0bba90c3fcdb0.png", 474 | "height": 2160, 475 | "width": 3840, 476 | "date_captured": "2024-07-22T07:36:05+00:00" 477 | }, 478 | { 479 | "id": 21, 480 | "license": 1, 481 | "file_name": "221458a6-13_25_04_962031_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_XYXLS23_png.rf.281b46703c1e3f6bd436750f6aa82cf2.png", 482 | "height": 2160, 483 | "width": 3840, 484 | "date_captured": "2024-07-22T07:36:05+00:00" 485 | }, 486 | { 487 | "id": 22, 488 | "license": 1, 489 | "file_name": "cef1b3dd-00_55_22_640294_WindowsGraphicsCaptureMethod_3840x2160_title_None_Clie_NtclVno_png.rf.0eb178e9974bb9135567f91780337d55.png", 490 | "height": 2160, 491 | "width": 3840, 492 | "date_captured": "2024-07-22T07:36:05+00:00" 493 | }, 494 | { 495 | "id": 23, 496 | "license": 1, 497 | "file_name": "WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_133336_1_False_original_png.rf.a2b55fa3583fef2081d49205ae9c643b.png", 498 | "height": 2160, 499 | "width": 3840, 500 | "date_captured": "2024-07-22T07:36:05+00:00" 501 | }, 502 | { 503 | "id": 24, 504 | "license": 1, 505 | "file_name": "64425cad-10_44_19_536997_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win_BLAxtp1_png.rf.d37538db2b2d15d6aad459dd08bb348d.png", 506 | "height": 2160, 507 | "width": 3840, 508 | "date_captured": "2024-07-22T07:36:05+00:00" 509 | }, 510 | { 511 | "id": 25, 512 | "license": 1, 513 | "file_name": "17_11_07_938917_WindowsGraphicsCaptureMethod_0x0_title_None_Client-Win64-Shipping-exe_3840x2160_396094_1_False_original_png.rf.f921e6fb49de02d7ef17e7f5f6a976c4.png", 514 | "height": 2160, 515 | "width": 3840, 516 | "date_captured": "2024-07-22T07:36:05+00:00" 517 | } 518 | ], 519 | "annotations": [ 520 | { 521 | "id": 0, 522 | "image_id": 0, 523 | "category_id": 56, 524 | "bbox": [ 525 | 2887, 526 | 1405, 527 | 213.33, 528 | 281.67 529 | ], 530 | "area": 60088.661, 531 | "segmentation": [], 532 | "iscrowd": 0 533 | }, 534 | { 535 | "id": 1, 536 | "image_id": 1, 537 | "category_id": 9, 538 | "bbox": [ 539 | 3356, 540 | 1901, 541 | 111.66, 542 | 67.61 543 | ], 544 | "area": 7549.333, 545 | "segmentation": [], 546 | "iscrowd": 0 547 | }, 548 | { 549 | "id": 2, 550 | "image_id": 1, 551 | "category_id": 7, 552 | "bbox": [ 553 | 3496, 554 | 929, 555 | 176, 556 | 182 557 | ], 558 | "area": 32032, 559 | "segmentation": [], 560 | "iscrowd": 0 561 | }, 562 | { 563 | "id": 3, 564 | "image_id": 1, 565 | "category_id": 5, 566 | "bbox": [ 567 | 3508, 568 | 407, 569 | 172, 570 | 185 571 | ], 572 | "area": 31820, 573 | "segmentation": [], 574 | "iscrowd": 0 575 | }, 576 | { 577 | "id": 4, 578 | "image_id": 1, 579 | "category_id": 6, 580 | "bbox": [ 581 | 3515, 582 | 665, 583 | 169, 584 | 188 585 | ], 586 | "area": 31772, 587 | "segmentation": [], 588 | "iscrowd": 0 589 | }, 590 | { 591 | "id": 5, 592 | "image_id": 1, 593 | "category_id": 34, 594 | "bbox": [ 595 | 3536, 596 | 985, 597 | 83, 598 | 77 599 | ], 600 | "area": 6391, 601 | "segmentation": [], 602 | "iscrowd": 0 603 | }, 604 | { 605 | "id": 6, 606 | "image_id": 1, 607 | "category_id": 42, 608 | "bbox": [ 609 | 3401, 610 | 1939, 611 | 10, 612 | 10 613 | ], 614 | "area": 100, 615 | "segmentation": [], 616 | "iscrowd": 0 617 | }, 618 | { 619 | "id": 7, 620 | "image_id": 1, 621 | "category_id": 10, 622 | "bbox": [ 623 | 3566, 624 | 1896, 625 | 116.83, 626 | 71.99 627 | ], 628 | "area": 8410.592, 629 | "segmentation": [], 630 | "iscrowd": 0 631 | }, 632 | { 633 | "id": 8, 634 | "image_id": 1, 635 | "category_id": 3, 636 | "bbox": [ 637 | 3347, 638 | 740, 639 | 153, 640 | 72 641 | ], 642 | "area": 11016, 643 | "segmentation": [], 644 | "iscrowd": 0 645 | }, 646 | { 647 | "id": 9, 648 | "image_id": 1, 649 | "category_id": 39, 650 | "bbox": [ 651 | 3544, 652 | 709, 653 | 60, 654 | 35.45 655 | ], 656 | "area": 2127, 657 | "segmentation": [], 658 | "iscrowd": 0 659 | }, 660 | { 661 | "id": 10, 662 | "image_id": 1, 663 | "category_id": 4, 664 | "bbox": [ 665 | 3348, 666 | 994, 667 | 153, 668 | 72 669 | ], 670 | "area": 11016, 671 | "segmentation": [], 672 | "iscrowd": 0 673 | }, 674 | { 675 | "id": 11, 676 | "image_id": 1, 677 | "category_id": 2, 678 | "bbox": [ 679 | 3346, 680 | 480, 681 | 153, 682 | 72 683 | ], 684 | "area": 11016, 685 | "segmentation": [], 686 | "iscrowd": 0 687 | }, 688 | { 689 | "id": 12, 690 | "image_id": 2, 691 | "category_id": 53, 692 | "bbox": [ 693 | 2589, 694 | 1897, 695 | 242, 696 | 210 697 | ], 698 | "area": 50820, 699 | "segmentation": [], 700 | "iscrowd": 0 701 | }, 702 | { 703 | "id": 13, 704 | "image_id": 3, 705 | "category_id": 47, 706 | "bbox": [ 707 | 2413, 708 | 1859, 709 | 210, 710 | 197 711 | ], 712 | "area": 41370, 713 | "segmentation": [], 714 | "iscrowd": 0 715 | }, 716 | { 717 | "id": 14, 718 | "image_id": 4, 719 | "category_id": 40, 720 | "bbox": [ 721 | 1909, 722 | 1038, 723 | 34.81, 724 | 68.65 725 | ], 726 | "area": 2389.707, 727 | "segmentation": [], 728 | "iscrowd": 0 729 | }, 730 | { 731 | "id": 15, 732 | "image_id": 5, 733 | "category_id": 22, 734 | "bbox": [ 735 | 3473, 736 | 690, 737 | 28, 738 | 31 739 | ], 740 | "area": 868, 741 | "segmentation": [], 742 | "iscrowd": 0 743 | }, 744 | { 745 | "id": 16, 746 | "image_id": 5, 747 | "category_id": 24, 748 | "bbox": [ 749 | 3474, 750 | 954, 751 | 27, 752 | 32 753 | ], 754 | "area": 864, 755 | "segmentation": [], 756 | "iscrowd": 0 757 | }, 758 | { 759 | "id": 17, 760 | "image_id": 5, 761 | "category_id": 23, 762 | "bbox": [ 763 | 3649, 764 | 1005, 765 | 28, 766 | 21 767 | ], 768 | "area": 588, 769 | "segmentation": [], 770 | "iscrowd": 0 771 | }, 772 | { 773 | "id": 18, 774 | "image_id": 6, 775 | "category_id": 52, 776 | "bbox": [ 777 | 3579, 778 | 1875, 779 | 98, 780 | 184 781 | ], 782 | "area": 18032, 783 | "segmentation": [], 784 | "iscrowd": 0 785 | }, 786 | { 787 | "id": 19, 788 | "image_id": 7, 789 | "category_id": 54, 790 | "bbox": [ 791 | 1555, 792 | 1550, 793 | 306.67, 794 | 121.67 795 | ], 796 | "area": 37312.539, 797 | "segmentation": [], 798 | "iscrowd": 0 799 | }, 800 | { 801 | "id": 20, 802 | "image_id": 8, 803 | "category_id": 25, 804 | "bbox": [ 805 | 3557, 806 | 991, 807 | 63.33, 808 | 58.33 809 | ], 810 | "area": 3694.039, 811 | "segmentation": [], 812 | "iscrowd": 0 813 | }, 814 | { 815 | "id": 21, 816 | "image_id": 8, 817 | "category_id": 26, 818 | "bbox": [ 819 | 3550, 820 | 733, 821 | 78.33, 822 | 52.67 823 | ], 824 | "area": 4125.641, 825 | "segmentation": [], 826 | "iscrowd": 0 827 | }, 828 | { 829 | "id": 22, 830 | "image_id": 8, 831 | "category_id": 32, 832 | "bbox": [ 833 | 3538, 834 | 456, 835 | 54, 836 | 61.33 837 | ], 838 | "area": 3311.82, 839 | "segmentation": [], 840 | "iscrowd": 0 841 | }, 842 | { 843 | "id": 23, 844 | "image_id": 9, 845 | "category_id": 13, 846 | "bbox": [ 847 | 2523, 848 | 1221, 849 | 101, 850 | 82 851 | ], 852 | "area": 8282, 853 | "segmentation": [], 854 | "iscrowd": 0 855 | }, 856 | { 857 | "id": 24, 858 | "image_id": 9, 859 | "category_id": 14, 860 | "bbox": [ 861 | 2525, 862 | 1384, 863 | 104, 864 | 81 865 | ], 866 | "area": 8424, 867 | "segmentation": [], 868 | "iscrowd": 0 869 | }, 870 | { 871 | "id": 25, 872 | "image_id": 9, 873 | "category_id": 12, 874 | "bbox": [ 875 | 3381, 876 | 107, 877 | 58, 878 | 46 879 | ], 880 | "area": 2668, 881 | "segmentation": [], 882 | "iscrowd": 0 883 | }, 884 | { 885 | "id": 26, 886 | "image_id": 9, 887 | "category_id": 16, 888 | "bbox": [ 889 | 3565, 890 | 111, 891 | 63, 892 | 38 893 | ], 894 | "area": 2394, 895 | "segmentation": [], 896 | "iscrowd": 0 897 | }, 898 | { 899 | "id": 27, 900 | "image_id": 10, 901 | "category_id": 43, 902 | "bbox": [ 903 | 2937, 904 | 1888, 905 | 86, 906 | 84 907 | ], 908 | "area": 7224, 909 | "segmentation": [], 910 | "iscrowd": 0 911 | }, 912 | { 913 | "id": 28, 914 | "image_id": 11, 915 | "category_id": 48, 916 | "bbox": [ 917 | 1747, 918 | 274, 919 | 58, 920 | 54 921 | ], 922 | "area": 3132, 923 | "segmentation": [], 924 | "iscrowd": 0 925 | }, 926 | { 927 | "id": 29, 928 | "image_id": 12, 929 | "category_id": 1, 930 | "bbox": [ 931 | 1820, 932 | 227, 933 | 35, 934 | 90.71 935 | ], 936 | "area": 3174.85, 937 | "segmentation": [], 938 | "iscrowd": 0 939 | }, 940 | { 941 | "id": 30, 942 | "image_id": 12, 943 | "category_id": 46, 944 | "bbox": [ 945 | 36, 946 | 273, 947 | 78.57, 948 | 78.57 949 | ], 950 | "area": 6173.245, 951 | "segmentation": [], 952 | "iscrowd": 0 953 | }, 954 | { 955 | "id": 31, 956 | "image_id": 13, 957 | "category_id": 45, 958 | "bbox": [ 959 | 77, 960 | 533, 961 | 151.67, 962 | 153.33 963 | ], 964 | "area": 23255.561, 965 | "segmentation": [], 966 | "iscrowd": 0 967 | }, 968 | { 969 | "id": 32, 970 | "image_id": 14, 971 | "category_id": 57, 972 | "bbox": [ 973 | 1688, 974 | 983, 975 | 23.42, 976 | 23.25 977 | ], 978 | "area": 544.515, 979 | "segmentation": [], 980 | "iscrowd": 0 981 | }, 982 | { 983 | "id": 33, 984 | "image_id": 15, 985 | "category_id": 51, 986 | "bbox": [ 987 | 2513, 988 | 1239, 989 | 58, 990 | 100 991 | ], 992 | "area": 5800, 993 | "segmentation": [], 994 | "iscrowd": 0 995 | }, 996 | { 997 | "id": 34, 998 | "image_id": 16, 999 | "category_id": 21, 1000 | "bbox": [ 1001 | 3649, 1002 | 739, 1003 | 28, 1004 | 23 1005 | ], 1006 | "area": 644, 1007 | "segmentation": [], 1008 | "iscrowd": 0 1009 | }, 1010 | { 1011 | "id": 35, 1012 | "image_id": 16, 1013 | "category_id": 19, 1014 | "bbox": [ 1015 | 3650, 1016 | 474, 1017 | 27, 1018 | 25 1019 | ], 1020 | "area": 675, 1021 | "segmentation": [], 1022 | "iscrowd": 0 1023 | }, 1024 | { 1025 | "id": 36, 1026 | "image_id": 17, 1027 | "category_id": 15, 1028 | "bbox": [ 1029 | 3560, 1030 | 65, 1031 | 114, 1032 | 114 1033 | ], 1034 | "area": 12996, 1035 | "segmentation": [], 1036 | "iscrowd": 0 1037 | }, 1038 | { 1039 | "id": 37, 1040 | "image_id": 18, 1041 | "category_id": 18, 1042 | "bbox": [ 1043 | 3537, 1044 | 723, 1045 | 68.85, 1046 | 65 1047 | ], 1048 | "area": 4475.25, 1049 | "segmentation": [], 1050 | "iscrowd": 0 1051 | }, 1052 | { 1053 | "id": 38, 1054 | "image_id": 19, 1055 | "category_id": 20, 1056 | "bbox": [ 1057 | 3474, 1058 | 427, 1059 | 26, 1060 | 31 1061 | ], 1062 | "area": 806, 1063 | "segmentation": [], 1064 | "iscrowd": 0 1065 | }, 1066 | { 1067 | "id": 39, 1068 | "image_id": 19, 1069 | "category_id": 8, 1070 | "bbox": [ 1071 | 2169, 1072 | 1996, 1073 | 4, 1074 | 12 1075 | ], 1076 | "area": 48, 1077 | "segmentation": [], 1078 | "iscrowd": 0 1079 | }, 1080 | { 1081 | "id": 40, 1082 | "image_id": 19, 1083 | "category_id": 37, 1084 | "bbox": [ 1085 | 3534, 1086 | 745, 1087 | 70, 1088 | 57 1089 | ], 1090 | "area": 3990, 1091 | "segmentation": [], 1092 | "iscrowd": 0 1093 | }, 1094 | { 1095 | "id": 41, 1096 | "image_id": 19, 1097 | "category_id": 38, 1098 | "bbox": [ 1099 | 3541, 1100 | 485, 1101 | 68, 1102 | 50 1103 | ], 1104 | "area": 3400, 1105 | "segmentation": [], 1106 | "iscrowd": 0 1107 | }, 1108 | { 1109 | "id": 42, 1110 | "image_id": 19, 1111 | "category_id": 36, 1112 | "bbox": [ 1113 | 3541, 1114 | 977, 1115 | 74, 1116 | 71 1117 | ], 1118 | "area": 5254, 1119 | "segmentation": [], 1120 | "iscrowd": 0 1121 | }, 1122 | { 1123 | "id": 43, 1124 | "image_id": 19, 1125 | "category_id": 11, 1126 | "bbox": [ 1127 | 3138, 1128 | 1895, 1129 | 118.14, 1130 | 78 1131 | ], 1132 | "area": 9214.92, 1133 | "segmentation": [], 1134 | "iscrowd": 0 1135 | }, 1136 | { 1137 | "id": 44, 1138 | "image_id": 20, 1139 | "category_id": 44, 1140 | "bbox": [ 1141 | 3220, 1142 | 1930, 1143 | 76.67, 1144 | 158.33 1145 | ], 1146 | "area": 12139.161, 1147 | "segmentation": [], 1148 | "iscrowd": 0 1149 | }, 1150 | { 1151 | "id": 45, 1152 | "image_id": 21, 1153 | "category_id": 30, 1154 | "bbox": [ 1155 | 3554, 1156 | 734, 1157 | 76, 1158 | 60 1159 | ], 1160 | "area": 4560, 1161 | "segmentation": [], 1162 | "iscrowd": 0 1163 | }, 1164 | { 1165 | "id": 46, 1166 | "image_id": 21, 1167 | "category_id": 35, 1168 | "bbox": [ 1169 | 3534, 1170 | 1004, 1171 | 75, 1172 | 42 1173 | ], 1174 | "area": 3150, 1175 | "segmentation": [], 1176 | "iscrowd": 0 1177 | }, 1178 | { 1179 | "id": 47, 1180 | "image_id": 21, 1181 | "category_id": 29, 1182 | "bbox": [ 1183 | 3541, 1184 | 477, 1185 | 72, 1186 | 48 1187 | ], 1188 | "area": 3456, 1189 | "segmentation": [], 1190 | "iscrowd": 0 1191 | }, 1192 | { 1193 | "id": 48, 1194 | "image_id": 22, 1195 | "category_id": 50, 1196 | "bbox": [ 1197 | 272, 1198 | 327, 1199 | 108, 1200 | 108 1201 | ], 1202 | "area": 11664, 1203 | "segmentation": [], 1204 | "iscrowd": 0 1205 | }, 1206 | { 1207 | "id": 49, 1208 | "image_id": 23, 1209 | "category_id": 55, 1210 | "bbox": [ 1211 | 2488, 1212 | 1105, 1213 | 36.42, 1214 | 38.87 1215 | ], 1216 | "area": 1415.645, 1217 | "segmentation": [], 1218 | "iscrowd": 0 1219 | }, 1220 | { 1221 | "id": 50, 1222 | "image_id": 23, 1223 | "category_id": 28, 1224 | "bbox": [ 1225 | 3549, 1226 | 471, 1227 | 69.01, 1228 | 51.99 1229 | ], 1230 | "area": 3587.83, 1231 | "segmentation": [], 1232 | "iscrowd": 0 1233 | }, 1234 | { 1235 | "id": 51, 1236 | "image_id": 23, 1237 | "category_id": 33, 1238 | "bbox": [ 1239 | 3550, 1240 | 732, 1241 | 64.28, 1242 | 58.61 1243 | ], 1244 | "area": 3767.451, 1245 | "segmentation": [], 1246 | "iscrowd": 0 1247 | }, 1248 | { 1249 | "id": 52, 1250 | "image_id": 23, 1251 | "category_id": 27, 1252 | "bbox": [ 1253 | 3554, 1254 | 998, 1255 | 57.66, 1256 | 51.99 1257 | ], 1258 | "area": 2997.743, 1259 | "segmentation": [], 1260 | "iscrowd": 0 1261 | }, 1262 | { 1263 | "id": 53, 1264 | "image_id": 23, 1265 | "category_id": 41, 1266 | "bbox": [ 1267 | 2598, 1268 | 1108, 1269 | 32.06, 1270 | 25.29 1271 | ], 1272 | "area": 810.797, 1273 | "segmentation": [], 1274 | "iscrowd": 0 1275 | }, 1276 | { 1277 | "id": 54, 1278 | "image_id": 24, 1279 | "category_id": 49, 1280 | "bbox": [ 1281 | 2805, 1282 | 1225, 1283 | 165, 1284 | 267 1285 | ], 1286 | "area": 44055, 1287 | "segmentation": [], 1288 | "iscrowd": 0 1289 | }, 1290 | { 1291 | "id": 55, 1292 | "image_id": 24, 1293 | "category_id": 17, 1294 | "bbox": [ 1295 | 890, 1296 | 1243, 1297 | 75, 1298 | 211.67 1299 | ], 1300 | "area": 15875.25, 1301 | "segmentation": [], 1302 | "iscrowd": 0 1303 | }, 1304 | { 1305 | "id": 56, 1306 | "image_id": 25, 1307 | "category_id": 31, 1308 | "bbox": [ 1309 | 3543, 1310 | 733, 1311 | 81.36, 1312 | 64.55 1313 | ], 1314 | "area": 5251.788, 1315 | "segmentation": [], 1316 | "iscrowd": 0 1317 | } 1318 | ] 1319 | } --------------------------------------------------------------------------------