Merge pull request #58 from 5upernova-heng/add/rouge_buff_selector

Add: rogue buff selector
This commit is contained in:
LmeSzinc 2023-09-10 22:43:22 +08:00 committed by GitHub
commit e8a38bc1e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 3854 additions and 8 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -127,5 +127,26 @@
"Duration": 20,
"Assignment": {}
}
},
"Rogue": {
"Scheduler": {
"Enable": false,
"NextRun": "2020-01-01 00:00:00",
"Command": "Rogue",
"ServerUpdate": "04:00"
},
"Rogue": {
"Path": "The Hunt",
"Bonus": "Blessing Cosmos",
"PresetResonanceFilter": "preset-1",
"CustomResonanceFilter": "回响构音:均晶转变 > 回响构音:零维强化\n> 回响构音:第二次初恋 > 回响构音:体验的富翁\n> 回响构音:局外人 > 回响构音:怀疑的四重根\n> 回响构音:诸法无我 > 回响构音:诸行无常\n> 回响构音:射不主皮 > 回响构音:柘弓危矢\n> 回响构音:激变变星 > 回响构音:极端氦闪\n> 回响构音:末日狂欢 > 回响构音:树苗长高舞",
"ResonanceSelectionStrategy": "follow-presets",
"PresetBlessingFilter": "preset-1",
"CustomBlessingFilter": "巡猎-3 > 《冠军晚餐·猫的摇篮》 > 丰饶众生,一法界心 > 毁灭-3 \n> 火堆外的夜 > 巡猎-2 > 毁灭-2 > 巡猎 > reset > random",
"BlessingSelectionStrategy": "follow-presets",
"PresetCurioFilter": "preset-1",
"CustomCurioFilter": "博士之袍 > 福灵胶 > 分裂金币 > 信仰债券 > 换境桂冠 > 俱乐部券 > 碎星芳饵 > random",
"CurioSelectionStrategy": "follow-presets"
}
}
}

View File

@ -13,8 +13,8 @@ UI_LANGUAGES = ['cn', 'cht', 'en', 'jp']
def text_to_variable(text):
text = re.sub("'s |s' ", '_', text)
text = re.sub('[ \-—:\'/•]+', '_', text)
text = re.sub(r'[(),#"?]|</?\w+>', '', text)
text = re.sub('[ \-—:\'/•.]+', '_', text)
text = re.sub(r'[(),#"?!&]|</?\w+>', '', text)
# text = re.sub(r'[#_]?\d+(_times?)?', '', text)
return text
@ -30,6 +30,12 @@ def dungeon_name(name: str) -> str:
return name
def blessing_name(name: str) -> str:
name = text_to_variable(name)
name = re.sub(r'^\d', lambda match: f"_{match.group(0)}", name)
return name
nickname_count = 0
@ -147,7 +153,8 @@ class KeywordExtract:
keyword_class,
output_file: str = '',
text_convert=text_to_variable,
generator: CodeGenerator = None
generator: CodeGenerator = None,
extra_attrs: dict[str, dict] = None
):
"""
Args:
@ -155,6 +162,7 @@ class KeywordExtract:
output_file:
text_convert:
generator: Reuse an existing code generator
extra_attrs: Extra attributes write in keywords
"""
if generator is None:
gen = CodeGenerator()
@ -166,6 +174,12 @@ class KeywordExtract:
gen = generator
last_id = getattr(gen, 'last_id', 0)
if extra_attrs:
keyword_num = len(self.keywords_id)
for attr_key, attr_value in extra_attrs.items():
if len(attr_value) != keyword_num:
print(f"Extra attribute {attr_key} does not match the size of keywords")
return
for index, keyword in enumerate(self.keywords_id):
_, name = self.find_keyword(keyword, lang='en')
name = text_convert(replace_templates(name))
@ -174,6 +188,9 @@ class KeywordExtract:
gen.ObjectAttr(key='name', value=name)
for lang in UI_LANGUAGES:
gen.ObjectAttr(key=lang, value=replace_templates(self.find_keyword(keyword, lang=lang)[1]))
if extra_attrs:
for attr_key, attr_value in extra_attrs.items():
gen.ObjectAttr(key=attr_key, value=attr_value[keyword])
gen.last_id = index + last_id + 1
if output_file:
@ -279,6 +296,67 @@ class KeywordExtract:
self.load_quests(quests)
self.write_keywords(keyword_class='BattlePassQuest', output_file='./tasks/battle_pass/keywords/quest.py')
def generate_rogue_buff(self):
# paths
aeons = read_file(os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', 'RogueAeon.json'))
aeons_hash = [deep_get(aeon, '1.RogueAeonPathName2.Hash') for aeon in aeons.values()]
self.keywords_id = aeons_hash
self.write_keywords(keyword_class='RoguePath', output_file='./tasks/rogue/keywords/path.py')
# blessings
blessings_info = read_file(os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', 'RogueBuff.json'))
blessings_name_map = read_file(os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', 'RogueMazeBuff.json'))
blessings_id = [deep_get(blessing, '1.MazeBuffID') for blessing in blessings_info.values()
if not deep_get(blessing, '1.AeonID')][1:]
resonances_id = [deep_get(blessing, '1.MazeBuffID') for blessing in blessings_info.values()
if deep_get(blessing, '1.AeonID')]
def get_blessing_infos(id_list, with_enhancement: bool):
blessings_hash = [deep_get(blessings_name_map, f"{blessing_id}.1.BuffName.Hash")
for blessing_id in id_list]
blessings_path_id = {blessing_hash: int(deep_get(blessings_info, f'{blessing_id}.1.RogueBuffType')) - 119
# 119 is the magic number make type match with path in keyword above
for blessing_hash, blessing_id in zip(blessings_hash, id_list)}
blessings_rarity = {blessing_hash: deep_get(blessings_info, f'{blessing_id}.1.RogueBuffRarity')
for blessing_hash, blessing_id in zip(blessings_hash, id_list)}
enhancement = {blessing_hash: "" for blessing_hash in blessings_hash}
if with_enhancement:
return blessings_hash, {'path_id': blessings_path_id, 'rarity': blessings_rarity,
'enhancement': enhancement}
else:
return blessings_hash, {'path_id': blessings_path_id, 'rarity': blessings_rarity}
hash_list, extra_attrs = get_blessing_infos(blessings_id, with_enhancement=True)
self.keywords_id = hash_list
self.write_keywords(keyword_class='RogueBlessing', output_file='./tasks/rogue/keywords/blessing.py',
text_convert=blessing_name, extra_attrs=extra_attrs)
hash_list, extra_attrs = get_blessing_infos(resonances_id, with_enhancement=False)
self.keywords_id = hash_list
self.write_keywords(keyword_class='RogueResonance', output_file='./tasks/rogue/keywords/resonance.py',
text_convert=blessing_name, extra_attrs=extra_attrs)
def iter_without_duplication(self, file: dict, keys):
visited = set()
for data in file.values():
hash_ = deep_get(data, keys=keys)
_, name = self.find_keyword(hash_, lang='cn')
if name in visited:
continue
visited.add(name)
yield hash_
def iter_rogue_miracles(self):
miracles = read_file(os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', 'RogueMiracle.json'))
visited = set()
for data in miracles.values():
hash_ = deep_get(data, keys='MiracleName.Hash')
_, name = self.find_keyword(hash_, lang='cn')
if name in visited:
continue
visited.add(name)
yield hash_
def generate(self):
self.load_keywords(['模拟宇宙', '拟造花萼(金)', '拟造花萼(赤)', '凝滞虚影', '侵蚀隧洞', '历战余响', '忘却之庭'])
self.write_keywords(keyword_class='DungeonNav', output_file='./tasks/dungeon/keywords/nav.py')
@ -308,6 +386,15 @@ class KeywordExtract:
self.load_keywords(['养成材料', '光锥', '遗器', '其他材料', '消耗品', '任务', '贵重物'])
self.write_keywords(keyword_class='ItemTab', text_convert=lambda name: name.replace(' ', ''),
output_file='./tasks/item/keywords/tab.py')
self.generate_rogue_buff()
self.load_keywords(['已强化'])
self.write_keywords(keyword_class='RogueEnhancement', output_file='./tasks/rogue/keywords/enhancement.py')
self.load_keywords(list(self.iter_without_duplication(
read_file(os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', 'RogueMiracle.json')), 'MiracleName.Hash')))
self.write_keywords(keyword_class='RogueCurio', output_file='./tasks/rogue/keywords/curio.py')
self.load_keywords(list(self.iter_without_duplication(
read_file(os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', 'RogueBonus.json')), 'BonusTitle.Hash')))
self.write_keywords(keyword_class='RogueBonus', output_file='./tasks/rogue/keywords/bonus.py')
if __name__ == '__main__':

View File

@ -101,3 +101,35 @@ class Filter:
# Invalid filter will be ignored.
# Return strange things and make it impossible to match
return ['1nVa1d'] + [None] * (len(self.attr) - 1)
class MultiLangFilter(Filter):
"""
To support multi-language, there might be different correct matches of same object.
"""
def apply_filter_to_obj(self, obj, filter):
"""
Args:
obj (object): In this case, attributes of object are array (instead of plain string).
Any match of element in it will return True
filter (list[str]):
Returns:
bool: If an object satisfy a filter.
"""
for attr, value in zip(self.attr, filter):
if not value:
continue
if not hasattr(obj, attr):
continue
obj_value = obj.__getattribute__(attr)
if isinstance(obj_value, (str, int)):
if str(obj_value).lower() != str(value):
return False
if isinstance(obj_value, list):
if value not in obj_value:
return False
return True

View File

@ -982,5 +982,115 @@
"color": "#79dbc4"
}
}
},
"Rogue": {
"Scheduler": {
"Enable": {
"type": "checkbox",
"value": false
},
"NextRun": {
"type": "datetime",
"value": "2020-01-01 00:00:00",
"validate": "datetime"
},
"Command": {
"type": "input",
"value": "Rogue",
"display": "hide"
},
"ServerUpdate": {
"type": "input",
"value": "04:00",
"display": "hide"
}
},
"Rogue": {
"Path": {
"type": "select",
"value": "The Hunt",
"option": [
"Preservation",
"Remembrance",
"Nihility",
"Abundance",
"Hunt",
"Destruction",
"Elation"
]
},
"Bonus": {
"type": "select",
"value": "Blessing Cosmos",
"option": [
"Blessing Cosmos",
"Miracle Cosmos",
"Fragmented Cosmos"
]
},
"PresetResonanceFilter": {
"type": "select",
"value": "preset-1",
"option": [
"preset-1",
"custom"
]
},
"CustomResonanceFilter": {
"type": "textarea",
"value": "回响构音:均晶转变 > 回响构音:零维强化\n> 回响构音:第二次初恋 > 回响构音:体验的富翁\n> 回响构音:局外人 > 回响构音:怀疑的四重根\n> 回响构音:诸法无我 > 回响构音:诸行无常\n> 回响构音:射不主皮 > 回响构音:柘弓危矢\n> 回响构音:激变变星 > 回响构音:极端氦闪\n> 回响构音:末日狂欢 > 回响构音:树苗长高舞"
},
"ResonanceSelectionStrategy": {
"type": "select",
"value": "follow-presets",
"option": [
"follow-presets",
"unrecorded-first",
"before-random"
]
},
"PresetBlessingFilter": {
"type": "select",
"value": "preset-1",
"option": [
"preset-1",
"custom"
]
},
"CustomBlessingFilter": {
"type": "textarea",
"value": "巡猎-3 > 《冠军晚餐·猫的摇篮》 > 丰饶众生,一法界心 > 毁灭-3 \n> 火堆外的夜 > 巡猎-2 > 毁灭-2 > 巡猎 > reset > random"
},
"BlessingSelectionStrategy": {
"type": "select",
"value": "follow-presets",
"option": [
"follow-presets",
"unrecorded-first",
"before-random"
]
},
"PresetCurioFilter": {
"type": "select",
"value": "preset-1",
"option": [
"preset-1",
"custom"
]
},
"CustomCurioFilter": {
"type": "textarea",
"value": "博士之袍 > 福灵胶 > 分裂金币 > 信仰债券 > 换境桂冠 > 俱乐部券 > 碎星芳饵 > random"
},
"CurioSelectionStrategy": {
"type": "select",
"value": "follow-presets",
"option": [
"follow-presets",
"unrecorded-first",
"before-random"
]
}
}
}
}

View File

@ -164,3 +164,45 @@ Assignment:
stored: StoredAssignment
order: 3
color: "#79dbc4"
# ==================== Rogue ====================
Rogue:
Path:
value: The Hunt
option: [ Preservation, Remembrance, Nihility, Abundance, Hunt, Destruction, Elation ]
Bonus:
value: Blessing Cosmos
option: [ Blessing Cosmos, Miracle Cosmos, Fragmented Cosmos ]
PresetResonanceFilter:
value: preset-1
option: [ preset-1, custom ]
CustomResonanceFilter: |-
回响构音:均晶转变 > 回响构音:零维强化
> 回响构音:第二次初恋 > 回响构音:体验的富翁
> 回响构音:局外人 > 回响构音:怀疑的四重根
> 回响构音:诸法无我 > 回响构音:诸行无常
> 回响构音:射不主皮 > 回响构音:柘弓危矢
> 回响构音:激变变星 > 回响构音:极端氦闪
> 回响构音:末日狂欢 > 回响构音:树苗长高舞
ResonanceSelectionStrategy:
value: follow-presets
option: [ follow-presets, unrecorded-first, before-random ]
PresetBlessingFilter:
value: preset-1
option: [ preset-1, custom ]
CustomBlessingFilter: |-
巡猎-3 > 《冠军晚餐·猫的摇篮》 > 丰饶众生,一法界心 > 毁灭-3
> 火堆外的夜 > 巡猎-2 > 毁灭-2 > 巡猎 > reset > random
BlessingSelectionStrategy:
value: follow-presets
option: [ follow-presets, unrecorded-first, before-random ]
PresetCurioFilter:
value: preset-1
option: [ preset-1, custom ]
CustomCurioFilter: |-
博士之袍 > 福灵胶 > 分裂金币 > 信仰债券 > 换境桂冠 > 俱乐部券 > 碎星芳饵 > random
CurioSelectionStrategy:
value: follow-presets
option: [ follow-presets, unrecorded-first, before-random ]

View File

@ -16,5 +16,12 @@
"BattlePass",
"Assignment"
]
},
"Rogue": {
"menu": "list",
"page": "setting",
"tasks": [
"Rogue"
]
}
}

View File

@ -38,3 +38,13 @@ Daily:
Assignment:
- Scheduler
- Assignment
# ==================== Rogue ====================
Rogue:
menu: 'list'
page: 'setting'
tasks:
Rogue:
- Scheduler
- Rogue

View File

@ -101,3 +101,16 @@ class GeneratedConfig:
Assignment_Name_4 = 'Nine_Billion_Names' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity
Assignment_Duration = 20 # 4, 8, 12, 20
Assignment_Assignment = {}
# Group `Rogue`
Rogue_Path = 'The Hunt' # Preservation, Remembrance, Nihility, Abundance, Hunt, Destruction, Elation
Rogue_Bonus = 'Blessing Cosmos' # Blessing Cosmos, Miracle Cosmos, Fragmented Cosmos
Rogue_PresetResonanceFilter = 'preset-1' # preset-1, custom
Rogue_CustomResonanceFilter = '回响构音:均晶转变 > 回响构音:零维强化\n> 回响构音:第二次初恋 > 回响构音:体验的富翁\n> 回响构音:局外人 > 回响构音:怀疑的四重根\n> 回响构音:诸法无我 > 回响构音:诸行无常\n> 回响构音:射不主皮 > 回响构音:柘弓危矢\n> 回响构音:激变变星 > 回响构音:极端氦闪\n> 回响构音:末日狂欢 > 回响构音:树苗长高舞'
Rogue_ResonanceSelectionStrategy = 'follow-presets' # follow-presets, unrecorded-first, before-random
Rogue_PresetBlessingFilter = 'preset-1' # preset-1, custom
Rogue_CustomBlessingFilter = '巡猎-3 > 《冠军晚餐·猫的摇篮》 > 丰饶众生,一法界心 > 毁灭-3 \n> 火堆外的夜 > 巡猎-2 > 毁灭-2 > 巡猎 > reset > random'
Rogue_BlessingSelectionStrategy = 'follow-presets' # follow-presets, unrecorded-first, before-random
Rogue_PresetCurioFilter = 'preset-1' # preset-1, custom
Rogue_CustomCurioFilter = '博士之袍 > 福灵胶 > 分裂金币 > 信仰债券 > 换境桂冠 > 俱乐部券 > 碎星芳饵 > random'
Rogue_CurioSelectionStrategy = 'follow-presets' # follow-presets, unrecorded-first, before-random

View File

@ -7,6 +7,10 @@
"Daily": {
"name": "Daily",
"help": ""
},
"Rogue": {
"name": "Simulated Universe",
"help": ""
}
},
"Task": {
@ -33,6 +37,10 @@
"Assignment": {
"name": "Assignment",
"help": ""
},
"Rogue": {
"name": "Simulated Universe",
"help": ""
}
},
"Scheduler": {
@ -678,6 +686,81 @@
"help": ""
}
},
"Rogue": {
"_info": {
"name": "Simulated Universe",
"help": ""
},
"Path": {
"name": "Fate",
"help": "",
"Preservation": "Preservation",
"Remembrance": "Remembrance",
"Nihility": "Nihility",
"Abundance": "Abundance",
"Hunt": "The Hunt",
"Destruction": "Destruction",
"Elation": "Elation"
},
"Bonus": {
"name": "Blessings of Trailblaze",
"help": "",
"Blessing Cosmos": "Blessing Cosmos (Choose a blessing)",
"Miracle Cosmos": "Miracle Cosmos (Choose a curio)",
"Fragmented Cosmos": "Fragmented Cosmos (Claim some Cosmic Fragments)"
},
"PresetResonanceFilter": {
"name": "Preset Resonance Filter",
"help": "",
"preset-1": "preset-1",
"custom": "custom"
},
"CustomResonanceFilter": {
"name": "Custom Resonance Filter",
"help": ""
},
"ResonanceSelectionStrategy": {
"name": "Resonance Selection Strategy",
"help": "",
"follow-presets": "Follow presets",
"unrecorded-first": "Choose index locked resonance first",
"before-random": "Follow presets, but choose index locked one first when filter goes to randomly choose"
},
"PresetBlessingFilter": {
"name": "Preset Blessing Filter",
"help": "",
"preset-1": "preset-1",
"custom": "custom"
},
"CustomBlessingFilter": {
"name": "Custom Blessing Filter",
"help": ""
},
"BlessingSelectionStrategy": {
"name": "Blessing jSelection Strategy",
"help": "",
"follow-presets": "Follow presets",
"unrecorded-first": "Choose index locked blessing first",
"before-random": "Follow presets, but choose index locked one first when filter goes to randomly choose"
},
"PresetCurioFilter": {
"name": "Preset Curio Filter",
"help": "",
"preset-1": "preset-1",
"custom": "custom"
},
"CustomCurioFilter": {
"name": "Custom Curio Filter",
"help": ""
},
"CurioSelectionStrategy": {
"name": "Curio Selection Strategy",
"help": "",
"follow-presets": "Follow presets",
"unrecorded-first": "Choose index locked curio first",
"before-random": "Follow presets, but choose index locked one first when filter goes to randomly choose"
}
},
"Gui": {
"Aside": {
"Install": "Install",
@ -780,4 +863,4 @@
"Clear": "Clear"
}
}
}
}

View File

@ -7,6 +7,10 @@
"Daily": {
"name": "Menu.Daily.name",
"help": "Menu.Daily.help"
},
"Rogue": {
"name": "Menu.Rogue.name",
"help": "Menu.Rogue.help"
}
},
"Task": {
@ -33,6 +37,10 @@
"Assignment": {
"name": "依頼設定",
"help": ""
},
"Rogue": {
"name": "Task.Rogue.name",
"help": "Task.Rogue.help"
}
},
"Scheduler": {
@ -678,6 +686,81 @@
"help": "Assignment.Assignment.help"
}
},
"Rogue": {
"_info": {
"name": "Rogue._info.name",
"help": "Rogue._info.help"
},
"Path": {
"name": "Rogue.Path.name",
"help": "Rogue.Path.help",
"Preservation": "Preservation",
"Remembrance": "Remembrance",
"Nihility": "Nihility",
"Abundance": "Abundance",
"Hunt": "Hunt",
"Destruction": "Destruction",
"Elation": "Elation"
},
"Bonus": {
"name": "Rogue.Bonus.name",
"help": "Rogue.Bonus.help",
"Blessing Cosmos": "Blessing Cosmos",
"Miracle Cosmos": "Miracle Cosmos",
"Fragmented Cosmos": "Fragmented Cosmos"
},
"PresetResonanceFilter": {
"name": "Rogue.PresetResonanceFilter.name",
"help": "Rogue.PresetResonanceFilter.help",
"preset-1": "preset-1",
"custom": "custom"
},
"CustomResonanceFilter": {
"name": "Rogue.CustomResonanceFilter.name",
"help": "Rogue.CustomResonanceFilter.help"
},
"ResonanceSelectionStrategy": {
"name": "Rogue.ResonanceSelectionStrategy.name",
"help": "Rogue.ResonanceSelectionStrategy.help",
"follow-presets": "follow-presets",
"unrecorded-first": "unrecorded-first",
"before-random": "before-random"
},
"PresetBlessingFilter": {
"name": "Rogue.PresetBlessingFilter.name",
"help": "Rogue.PresetBlessingFilter.help",
"preset-1": "preset-1",
"custom": "custom"
},
"CustomBlessingFilter": {
"name": "Rogue.CustomBlessingFilter.name",
"help": "Rogue.CustomBlessingFilter.help"
},
"BlessingSelectionStrategy": {
"name": "Rogue.BlessingSelectionStrategy.name",
"help": "Rogue.BlessingSelectionStrategy.help",
"follow-presets": "follow-presets",
"unrecorded-first": "unrecorded-first",
"before-random": "before-random"
},
"PresetCurioFilter": {
"name": "Rogue.PresetCurioFilter.name",
"help": "Rogue.PresetCurioFilter.help",
"preset-1": "preset-1",
"custom": "custom"
},
"CustomCurioFilter": {
"name": "Rogue.CustomCurioFilter.name",
"help": "Rogue.CustomCurioFilter.help"
},
"CurioSelectionStrategy": {
"name": "Rogue.CurioSelectionStrategy.name",
"help": "Rogue.CurioSelectionStrategy.help",
"follow-presets": "follow-presets",
"unrecorded-first": "unrecorded-first",
"before-random": "before-random"
}
},
"Gui": {
"Aside": {
"Install": "インストール",

View File

@ -7,6 +7,10 @@
"Daily": {
"name": "每日",
"help": ""
},
"Rogue": {
"name": "模拟宇宙",
"help": ""
}
},
"Task": {
@ -33,6 +37,10 @@
"Assignment": {
"name": "委托",
"help": ""
},
"Rogue": {
"name": "模拟宇宙",
"help": ""
}
},
"Scheduler": {
@ -678,6 +686,81 @@
"help": ""
}
},
"Rogue": {
"_info": {
"name": "模拟宇宙",
"help": ""
},
"Path": {
"name": "命途",
"help": "",
"Preservation": "存护",
"Remembrance": "记忆",
"Nihility": "虚无",
"Abundance": "丰饶",
"Hunt": "巡猎",
"Destruction": "毁灭",
"Elation": "欢愉"
},
"Bonus": {
"name": "开拓祝福",
"help": "开局三选一",
"Blessing Cosmos": "祝福宇宙(获得一个祝福)",
"Miracle Cosmos": "神奇宇宙(获得一个奇物)",
"Fragmented Cosmos": "破碎宇宙(获得宇宙碎片)"
},
"PresetResonanceFilter": {
"name": "回响构音过滤器",
"help": "",
"preset-1": "预设 1",
"custom": "自定义"
},
"CustomResonanceFilter": {
"name": "自定义回响构音过滤器",
"help": ""
},
"ResonanceSelectionStrategy": {
"name": "回响构音选择策略",
"help": "",
"follow-presets": "遵循预设",
"unrecorded-first": "优先选择图鉴未解锁的回响构音",
"before-random": "遵循预设,但选择\"图鉴未解锁\"的优先级高于\"随机选择\""
},
"PresetBlessingFilter": {
"name": "祝福过滤器",
"help": "",
"preset-1": "预设 1",
"custom": "自定义"
},
"CustomBlessingFilter": {
"name": "自定义祝福过滤器",
"help": ""
},
"BlessingSelectionStrategy": {
"name": "祝福选择策略",
"help": "",
"follow-presets": "遵循预设",
"unrecorded-first": "优先选择图鉴未解锁的祝福",
"before-random": "遵循预设,但选择\"图鉴未解锁\"的优先级高于\"随机选择\""
},
"PresetCurioFilter": {
"name": "奇物过滤器",
"help": "",
"preset-1": "预设 1",
"custom": "自定义"
},
"CustomCurioFilter": {
"name": "自定义祝福过滤器",
"help": ""
},
"CurioSelectionStrategy": {
"name": "奇物选择策略",
"help": "",
"follow-presets": "遵循预设",
"unrecorded-first": "优先选择图鉴未解锁的奇物",
"before-random": "遵循预设,但选择\"图鉴未解锁\"的优先级高于\"随机选择\""
}
},
"Gui": {
"Aside": {
"Install": "安装",
@ -780,4 +863,4 @@
"Clear": "清除"
}
}
}
}

View File

@ -7,6 +7,10 @@
"Daily": {
"name": "每日",
"help": ""
},
"Rogue": {
"name": "模擬宇宙",
"help": ""
}
},
"Task": {
@ -33,6 +37,10 @@
"Assignment": {
"name": "委託",
"help": ""
},
"Rogue": {
"name": "模擬宇宙",
"help": ""
}
},
"Scheduler": {
@ -678,6 +686,81 @@
"help": ""
}
},
"Rogue": {
"_info": {
"name": "模擬宇宙",
"help": ""
},
"Path": {
"name": "命途",
"help": "",
"Preservation": "存護",
"Remembrance": "記憶",
"Nihility": "虛無",
"Abundance": "丰饶",
"Hunt": "巡獵",
"Destruction": "毀滅",
"Elation": "歡愉"
},
"Bonus": {
"name": "開拓祝福",
"help": "开局三选一",
"Blessing Cosmos": "祝福宇宙(獲得一個祝福)",
"Miracle Cosmos": "神奇宇宙(獲得一個奇物)",
"Fragmented Cosmos": "破碎宇宙(獲得宇宙碎片)"
},
"PresetResonanceFilter": {
"name": "迴響構音过滤器",
"help": "",
"preset-1": "預設 1",
"custom": "自訂"
},
"CustomResonanceFilter": {
"name": "自訂迴響構音过滤器",
"help": ""
},
"ResonanceSelectionStrategy": {
"name": "迴響構音選擇策略",
"help": "",
"follow-presets": "遵循預設",
"unrecorded-first": "優先選擇圖鑑未解鎖的迴響構音",
"before-random": "遵循預設,但選擇\"圖鑑未解鎖\"的優先級高於\"隨機選擇\""
},
"PresetBlessingFilter": {
"name": "祝福过滤器",
"help": "",
"preset-1": "預設 1",
"custom": "自訂"
},
"CustomBlessingFilter": {
"name": "自訂祝福过滤器",
"help": ""
},
"BlessingSelectionStrategy": {
"name": "祝福選擇策略",
"help": "",
"follow-presets": "遵循預設",
"unrecorded-first": "優先選擇圖鑑未解鎖的祝福",
"before-random": "遵循預設,但選擇\"圖鑑未解鎖\"的優先級高於\"隨機選擇\""
},
"PresetCurioFilter": {
"name": "奇物过滤器",
"help": "",
"preset-1": "preset-1",
"custom": "custom"
},
"CustomCurioFilter": {
"name": "自訂奇物过滤器",
"help": ""
},
"CurioSelectionStrategy": {
"name": "奇物選擇策略",
"help": "",
"follow-presets": "遵循預設",
"unrecorded-first": "優先選擇圖鑑未解鎖的奇物",
"before-random": "遵循預設,但選擇\"圖鑑未解鎖\"的優先級高於\"隨機選擇\""
}
},
"Gui": {
"Aside": {
"Install": "安裝",
@ -780,4 +863,4 @@
"Clear": "清除"
}
}
}
}

View File

@ -3,10 +3,10 @@ from dataclasses import dataclass
from functools import cached_property
from typing import ClassVar
from module.exception import ScriptError
import module.config.server as server
from module.exception import ScriptError
REGEX_PUNCTUATION = re.compile(r'[ ,.\'"“”,。!??·•\-—/\\\n\t()\[\]()「」『』【】]')
REGEX_PUNCTUATION = re.compile(r'[ ,.\'"“”,。:!??·•\-—/\\\n\t()\[\]()「」『』【】《》]')
def parse_name(n):
@ -26,6 +26,7 @@ class Keyword:
"""
Instance attributes and methods
"""
@cached_property
def ch(self) -> str:
return self.cn

View File

@ -0,0 +1,75 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
BLESSING_ENFORCE = ButtonWrapper(
name='BLESSING_ENFORCE',
share=Button(
file='./assets/share/rogue/blessing/BLESSING_ENFORCE.png',
area=(500, 141, 608, 556),
search=(480, 121, 628, 576),
color=(123, 100, 78),
button=(500, 141, 608, 556),
),
)
BLESSING_RESET = ButtonWrapper(
name='BLESSING_RESET',
share=Button(
file='./assets/share/rogue/blessing/BLESSING_RESET.png',
area=(652, 629, 889, 677),
search=(632, 609, 909, 697),
color=(204, 203, 204),
button=(652, 629, 889, 677),
),
)
BLESSING_STABLE_FLAG = ButtonWrapper(
name='BLESSING_STABLE_FLAG',
share=Button(
file='./assets/share/rogue/blessing/BLESSING_STABLE_FLAG.png',
area=(846, 520, 896, 542),
search=(826, 500, 916, 562),
color=(250, 250, 250),
button=(846, 520, 896, 542),
),
)
BOTTOM_WHITE_BAR = ButtonWrapper(
name='BOTTOM_WHITE_BAR',
share=Button(
file='./assets/share/rogue/blessing/BOTTOM_WHITE_BAR.png',
area=(166, 520, 1114, 542),
search=(146, 500, 1134, 562),
color=(210, 211, 211),
button=(166, 520, 1114, 542),
),
)
OCR_RESET_COST = ButtonWrapper(
name='OCR_RESET_COST',
share=Button(
file='./assets/share/rogue/blessing/OCR_RESET_COST.png',
area=(689, 642, 715, 665),
search=(669, 622, 735, 685),
color=(39, 23, 25),
button=(689, 642, 715, 665),
),
)
OCR_RESET_COUNT = ButtonWrapper(
name='OCR_RESET_COUNT',
share=Button(
file='./assets/share/rogue/blessing/OCR_RESET_COUNT.png',
area=(655, 593, 873, 623),
search=(635, 573, 893, 643),
color=(23, 24, 26),
button=(655, 593, 873, 623),
),
)
OCR_ROGUE_BUFF = ButtonWrapper(
name='OCR_ROGUE_BUFF',
share=Button(
file='./assets/share/rogue/blessing/OCR_ROGUE_BUFF.png',
area=(155, 140, 1123, 348),
search=(135, 120, 1143, 368),
color=(80, 87, 106),
button=(155, 140, 1123, 348),
),
)

View File

@ -0,0 +1,25 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
BONUS_BOTTOM_WHITE_BAR = ButtonWrapper(
name='BONUS_BOTTOM_WHITE_BAR',
share=Button(
file='./assets/share/rogue/bonus/BONUS_BOTTOM_WHITE_BAR.png',
area=(731, 568, 774, 590),
search=(711, 548, 794, 610),
color=(250, 250, 250),
button=(731, 568, 774, 590),
),
)
BONUS_CONFIRM = ButtonWrapper(
name='BONUS_CONFIRM',
share=Button(
file='./assets/share/rogue/bonus/BONUS_CONFIRM.png',
area=(504, 629, 620, 677),
search=(484, 609, 640, 697),
color=(222, 224, 224),
button=(504, 629, 620, 677),
),
)

View File

@ -0,0 +1,25 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
CURIO_ENFORCE = ButtonWrapper(
name='CURIO_ENFORCE',
share=Button(
file='./assets/share/rogue/curio/CURIO_ENFORCE.png',
area=(465, 114, 815, 545),
search=(445, 94, 835, 565),
color=(80, 66, 61),
button=(465, 114, 815, 545),
),
)
OCR_ROGUE_CURIO = ButtonWrapper(
name='OCR_ROGUE_CURIO',
share=Button(
file='./assets/share/rogue/curio/OCR_ROGUE_CURIO.png',
area=(87, 128, 1203, 187),
search=(67, 108, 1223, 207),
color=(18, 19, 20),
button=(87, 128, 1203, 187),
),
)

View File

@ -0,0 +1,105 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
BLESSING_CONFIRM = ButtonWrapper(
name='BLESSING_CONFIRM',
share=Button(
file='./assets/share/rogue/ui/BLESSING_CONFIRM.png',
area=(960, 629, 1233, 677),
search=(940, 609, 1253, 697),
color=(217, 218, 218),
button=(960, 629, 1233, 677),
),
)
CHECK_BLESSING = ButtonWrapper(
name='CHECK_BLESSING',
share=Button(
file='./assets/share/rogue/ui/CHECK_BLESSING.png',
area=(946, 17, 986, 57),
search=(926, 0, 1006, 77),
color=(61, 58, 52),
button=(946, 17, 986, 57),
),
)
COSMIC_FRAGMENT = ButtonWrapper(
name='COSMIC_FRAGMENT',
share=Button(
file='./assets/share/rogue/ui/COSMIC_FRAGMENT.png',
area=(1146, 19, 1181, 55),
search=(1126, 0, 1201, 75),
color=(86, 68, 62),
button=(1146, 19, 1181, 55),
),
)
FLAG_UNRECORD = ButtonWrapper(
name='FLAG_UNRECORD',
share=Button(
file='./assets/share/rogue/ui/FLAG_UNRECORD.png',
area=(746, 149, 778, 181),
search=(726, 129, 798, 201),
color=(133, 130, 161),
button=(746, 149, 778, 181),
),
)
OBTAIN_ITEM_POPUP = ButtonWrapper(
name='OBTAIN_ITEM_POPUP',
share=Button(
file='./assets/share/rogue/ui/OBTAIN_ITEM_POPUP.png',
area=(180, 573, 432, 720),
search=(160, 553, 452, 720),
color=(131, 109, 73),
button=(180, 573, 432, 720),
),
)
OCR_COSMIC_FRAGMENT = ButtonWrapper(
name='OCR_COSMIC_FRAGMENT',
share=Button(
file='./assets/share/rogue/ui/OCR_COSMIC_FRAGMENT.png',
area=(1183, 27, 1256, 46),
search=(1163, 7, 1276, 66),
color=(30, 32, 32),
button=(1183, 27, 1256, 46),
),
)
PAGE_CHOOSE_BONUS = ButtonWrapper(
name='PAGE_CHOOSE_BONUS',
share=Button(
file='./assets/share/rogue/ui/PAGE_CHOOSE_BONUS.png',
area=(503, 180, 555, 230),
search=(483, 160, 575, 250),
color=(33, 29, 54),
button=(503, 180, 555, 230),
),
)
PAGE_CHOOSE_BUFF = ButtonWrapper(
name='PAGE_CHOOSE_BUFF',
share=Button(
file='./assets/share/rogue/ui/PAGE_CHOOSE_BUFF.png',
area=(1020, 78, 1160, 96),
search=(1000, 58, 1180, 116),
color=(38, 39, 41),
button=(1020, 78, 1160, 96),
),
)
PAGE_CHOOSE_CURIO = ButtonWrapper(
name='PAGE_CHOOSE_CURIO',
share=Button(
file='./assets/share/rogue/ui/PAGE_CHOOSE_CURIO.png',
area=(988, 17, 1028, 57),
search=(968, 0, 1048, 77),
color=(40, 39, 34),
button=(988, 17, 1028, 57),
),
)
PAGE_EVENT = ButtonWrapper(
name='PAGE_EVENT',
share=Button(
file='./assets/share/rogue/ui/PAGE_EVENT.png',
area=(983, 485, 1007, 509),
search=(963, 465, 1027, 529),
color=(51, 48, 42),
button=(983, 485, 1007, 509),
),
)

322
tasks/rogue/blessing.py Normal file
View File

@ -0,0 +1,322 @@
import re
import numpy as np
from module.base.filter import MultiLangFilter
from module.base.timer import Timer
from module.base.utils import get_color
from module.logger import logger
from module.ocr.ocr import Ocr, OcrResultButton, DigitCounter, Digit
from module.ocr.utils import split_and_pair_buttons
from tasks.rogue.assets.assets_rogue_blessing import *
from tasks.rogue.assets.assets_rogue_ui import BLESSING_CONFIRM
from tasks.rogue.keywords import *
from tasks.rogue.preset import *
from tasks.rogue.selector import RogueSelector
from tasks.rogue.utils import get_regex_from_keyword_name, parse_name, is_card_selected
# normal blessing filter
# path name
pattern = ""
BLESSING_FILTER_ATTR = tuple()
PATH_ATTR_NAME = 'path_name'
path_regex = get_regex_from_keyword_name(RoguePath, PATH_ATTR_NAME)
pattern += path_regex
# remove 'the' in path 'the hunt'
pattern = pattern.lower().replace('the', '')
BLESSING_FILTER_ATTR += (PATH_ATTR_NAME,)
# rarity
pattern += "([123])?-?"
BLESSING_FILTER_ATTR += ("rarity",)
# blessing name
BLESSING_ATTR_NAME = 'blessing_name'
blessing_regex = get_regex_from_keyword_name(RogueBlessing, BLESSING_ATTR_NAME)
pattern += blessing_regex
BLESSING_FILTER_ATTR += (BLESSING_ATTR_NAME,)
# enhanced
ENHANCEMENT_ATTR_NAME = "enhancement"
enhancement_regex = get_regex_from_keyword_name(RogueEnhancement, "enhancement_keyword")
pattern += enhancement_regex
BLESSING_FILTER_ATTR += (ENHANCEMENT_ATTR_NAME,)
FILETER_REGEX = re.compile(pattern)
BLESSING_FILTER_PRESET = ("reset", "random", "unrecorded")
BLESSING_FILTER = MultiLangFilter(FILETER_REGEX, BLESSING_FILTER_ATTR, BLESSING_FILTER_PRESET)
# resonance filter
RESONANCE_ATTR_NAME = 'resonance_name'
pattern = get_regex_from_keyword_name(RogueResonance, RESONANCE_ATTR_NAME)
FILETER_REGEX = re.compile(pattern)
RESONANCE_FILTER_PRESET = ("random", "unrecorded")
RESONANCE_FILTER = MultiLangFilter(FILETER_REGEX, (RESONANCE_ATTR_NAME,), RESONANCE_FILTER_PRESET)
class RogueBuffOcr(Ocr):
merge_thres_x = 40
merge_thres_y = 20
def after_process(self, result):
result = super().after_process(result)
if self.lang == 'ch':
replace_pattern_dict = {
"蓬失": "蓬矢",
"柘弓危失": "柘弓危矢",
"飞虹珠?凿?齿": "飞虹诛凿齿",
"天培步危": "天棓步危",
"云[摘销锅]?逐步离": "云镝逐步离",
"制桑": "制穹桑",
"乌号基": "乌号綦",
"追摩物": "追孽物",
"特月": "狩月",
"彤弓素增?": "彤弓素矰",
"白决射御": "白矢决射御",
"苦表": "苦衷",
"[沦沧]肌髓": "沦浃肌髓",
"进发": "迸发",
"永缩体": "永坍缩体",
"完美体验:绒默": "完美体验:缄默",
"灭回归不等式": "湮灭回归不等式",
r".*灾$": "禳灾",
"虚安供品": "虚妄供品",
"原初的苦$": "原初的苦衷",
"厌离邪苦": "厌离邪秽苦",
r".*繁.*": "葳蕤繁祉,延彼遐龄",
}
for pat, replace in replace_pattern_dict.items():
result = re.sub(pat, replace, result)
return result
class RogueBlessingSelector(RogueSelector):
"""
Usage:
self = RogueBlessingSelector('alas')
self.device.screenshot()
self.recognize_and_select()
"""
def get_blessing_count(self) -> int:
"""
Returns: The number of blessing
"""
if not self.main.image_color_count(BOTTOM_WHITE_BAR.area, color=(255, 255, 255), count=5000):
return 0
color = get_color(self.main.device.image, BOTTOM_WHITE_BAR.area)
mean = np.mean(color)
return int(mean // 60) # the magic number that maps blessing num with mean_color
def recognition(self):
def not_enhancement_keyword(keyword):
return keyword != KEYWORDS_ROGUE_ENHANCEMENT.Already_Enhanced
self.ocr_results = []
self._wait_until_blessing_loaded()
ocr = RogueBuffOcr(OCR_ROGUE_BUFF)
results = ocr.matched_ocr(self.main.device.image,
[RogueBlessing, RogueResonance, RogueEnhancement])
enhanced_blessing = [result for result, _ in
split_and_pair_buttons(results, split_func=not_enhancement_keyword,
relative_area=(-300, -720, 0, 0))]
results = [result for result in results if not_enhancement_keyword(result)]
blessing_count = self.get_blessing_count()
if blessing_count != len(results):
logger.warning(f"The OCR result does not match the blessing count. "
f"Expect {blessing_count}, but recognized {len(results)} only.")
for result in results:
if result in enhanced_blessing:
result.matched_keyword.enhancement = KEYWORDS_ROGUE_ENHANCEMENT.Already_Enhanced.enhancement_keyword
self.ocr_results = results
return results
def ui_select(self, target: OcrResultButton | None, skip_first_screenshot=True):
"""
Select buff once. Multiple calls needed if there's more than one time to choose
It might occur that all listed blessings are not recognized
So this method provides a hard code way to choose one, which fit in case when blessing num is 1-3
"""
def is_select_blessing_complete():
"""
Case 1: back to main page
Case 2: choose curio
Case 3: another choose blessings, but no blessing is selected when the new selection page loaded
Case 4: event ui
"""
if self.main.is_in_main():
logger.info("Main page checked")
return True
if self.main.is_page_choose_curio():
logger.info("Choose curio page checked")
return True
if self.main.is_page_choose_blessing() and not is_card_selected(self.main, target, BLESSING_CONFIRM):
logger.info("A new choose blessing page checked")
return True
if self.main.is_page_event():
logger.info("Event page checked")
return True
return False
interval = Timer(1)
enforce = False
if not target:
enforce = True
# start -> selected
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.main.device.screenshot()
if is_card_selected(self.main, target, confirm_button=BLESSING_CONFIRM):
if enforce:
logger.info("Buff selected (enforce)")
else:
logger.info(f"Buff {target} selected")
break
if interval.reached():
if enforce:
self.main.device.click(BLESSING_ENFORCE)
else:
self.main.device.click(target)
interval.reset()
skip_first_screenshot = True
# selected -> confirm
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.main.device.screenshot()
if is_select_blessing_complete():
logger.info("Select blessing complete")
break
if interval.reached():
self.main.device.click(BLESSING_CONFIRM)
interval.reset()
def _get_reset_count(self):
current, _, _ = DigitCounter(OCR_RESET_COUNT).ocr_single_line(self.main.device.image)
return current
def _wait_until_blessing_loaded(self, timer=Timer(0.3, count=1), timeout=Timer(5, count=10)):
timer.reset()
timeout.reset()
previous_count = self.get_blessing_count()
while 1:
self.main.device.screenshot()
blessing_count = self.get_blessing_count()
if timeout.reached():
logger.warning('Wait blessing page loaded timeout')
break
if previous_count and previous_count == blessing_count:
if timer.reached():
logger.info('Blessing page stabled')
break
else:
previous_count = blessing_count
timer.reset()
def reset_blessing_list(self, skip_first_screenshot=True):
if not self.main.is_page_choose_blessing():
return False
reset_count = self._get_reset_count()
if not reset_count:
logger.info("Does not have enough reset count")
return False
reset_cost = Digit(OCR_RESET_COST).ocr_single_line(self.main.device.image)
if reset_cost > self.main.cosmic_fragment:
logger.info("Does not have enough cosmic fragment")
return False
interval = Timer(1)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.main.device.screenshot()
new_count = self._get_reset_count()
if reset_count - new_count == 1:
logger.info("Reset once")
break
if interval.reached():
self.main.device.click(BLESSING_RESET)
interval.reset()
return True
def load_filter(self):
keyword = self.ocr_results[0].matched_keyword
if not isinstance(keyword, (RogueBlessing, RogueResonance)):
return
filter_configs = {
RogueBlessing: {
"filter_": BLESSING_FILTER,
"preset_config": self.main.config.Rogue_PresetBlessingFilter,
"strategy_config": self.main.config.Rogue_BlessingSelectionStrategy,
"preset_values": {
'preset-1': BLESSING_PRESET_1,
'custom': self.main.config.Rogue_CustomBlessingFilter
},
},
RogueResonance: {
"filter_": RESONANCE_FILTER,
"preset_config": self.main.config.Rogue_PresetResonanceFilter,
"strategy_config": self.main.config.Rogue_ResonanceSelectionStrategy,
"preset_values": {
'preset-1': RESONANCE_PRESET_1,
'custom': self.main.config.Rogue_PresetResonanceFilter,
},
}
}
# preset
config = filter_configs[type(keyword)]
filter_ = config['filter_']
preset_config = config['preset_config']
preset_values = config['preset_values']
string = preset_values[preset_config]
string = parse_name(string)
# strategy
strategy_config = config['strategy_config']
if strategy_config == 'unrecorded-first':
string = "unrecorded > " + string
if strategy_config == 'before-random':
string = string.replace('random', 'unrecorded > random')
filter_.load(string)
self.filter_ = filter_
def try_select(self, option: OcrResultButton | str):
if isinstance(option, str):
if option.lower() == 'reset':
if self.reset_blessing_list():
self.recognize_and_select()
return True
if option.lower() == 'random':
choose = np.random.choice(self.ocr_results)
self.ui_select(choose)
return True
if option.lower() == 'unrecorded':
for result in self.ocr_results:
if self.main.is_unrecorded(result, (0, -720, 300, 0)):
self.ui_select(result)
return True
return False
if isinstance(option, OcrResultButton):
self.ui_select(option)
return True
return False

90
tasks/rogue/bonus.py Normal file
View File

@ -0,0 +1,90 @@
import numpy as np
from module.base.timer import Timer
from module.logger import logger
from module.ocr.ocr import OcrResultButton
from tasks.rogue.assets.assets_rogue_blessing import OCR_ROGUE_BUFF
from tasks.rogue.assets.assets_rogue_bonus import BONUS_BOTTOM_WHITE_BAR, BONUS_CONFIRM
from tasks.rogue.keywords import RogueBonus
from tasks.rogue.selector import RogueSelector
from tasks.rogue.ui import RogueBonusOcr
from tasks.rogue.utils import is_card_selected
class RogueBonusSelector(RogueSelector):
def _wait_bonus_page_loaded(self, timer=Timer(0.3, count=1), timeout=Timer(5, count=10)):
timer.reset()
timeout.reset()
while 1:
self.main.device.screenshot()
if timeout.reached():
logger.warning('Wait bonus page loaded timeout')
break
if self.main.appear(BONUS_BOTTOM_WHITE_BAR):
if timer.reached():
logger.info('Bonus page stabled')
break
else:
timer.reset()
def recognition(self):
self._wait_bonus_page_loaded()
ocr = RogueBonusOcr(OCR_ROGUE_BUFF)
results = ocr.matched_ocr(self.main.device.image, [RogueBonus])
expected_count = 3
if expected_count != len(results):
logger.warning(f"The OCR result does not match the bonus count. "
f"Expect {expected_count}, but recognized {len(results)} only.")
self.ocr_results = results
return results
def ui_select(self, target: OcrResultButton | None, skip_first_screenshot=True):
interval = Timer(1)
# start -> select
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.main.device.screenshot()
if is_card_selected(self.main, target, confirm_button=BONUS_CONFIRM):
break
if interval.reached():
self.main.device.click(target)
interval.reset()
skip_first_screenshot = True
# select -> confirm
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.main.device.screenshot()
if self.main.is_in_main():
logger.info("Main Page Checked")
break
if self.main.is_page_choose_curio():
logger.info("Choose curio page checked")
break
if self.main.is_page_choose_blessing():
logger.info("Choose blessing page checked")
break
if interval.reached():
self.main.device.click(BONUS_CONFIRM)
interval.reset()
def recognize_and_select(self):
self.recognition()
if not self.ocr_results:
self.ui_select(None)
options = {result.matched_keyword.en: result for result in self.ocr_results}
if self.main.config.Rogue_Bonus not in options.keys():
logger.warning(f"Can not find option: {self.main.config.Rogue_Bonus}, randomly choose one")
target = np.random.choice(options)
else:
target = options[self.main.config.Rogue_Bonus]
logger.info(f"Choose bonus: {target}")
self.ui_select(target)

142
tasks/rogue/curio.py Normal file
View File

@ -0,0 +1,142 @@
import re
import numpy as np
from module.base.filter import MultiLangFilter
from module.base.timer import Timer
from module.base.utils import get_color
from module.logger import logger
from module.ocr.ocr import Ocr, OcrResultButton
from tasks.rogue.assets.assets_rogue_curio import *
from tasks.rogue.assets.assets_rogue_ui import BLESSING_CONFIRM
from tasks.rogue.keywords import RogueCurio
from tasks.rogue.preset import CURIO_PRESET_1
from tasks.rogue.selector import RogueSelector
from tasks.rogue.utils import get_regex_from_keyword_name, parse_name
CURIO_FILTER_ATTR = tuple()
CURIO_ATTR_NAME = 'curio_name'
pattern = get_regex_from_keyword_name(RogueCurio, CURIO_ATTR_NAME)
CURIO_FILTER_ATTR += (CURIO_ATTR_NAME,)
CURIO_FILTER_PRESET = ('random', 'unrecorded')
FILTER_REGEX = re.compile(pattern)
CURIO_FILTER = MultiLangFilter(FILTER_REGEX, CURIO_FILTER_ATTR, CURIO_FILTER_PRESET)
class RogueCurioOcr(Ocr):
merge_thres_y = 40
def after_process(self, result):
result = super().after_process(result)
if self.lang == 'ch':
replace_pattern_dict = {
"": "",
"漂灭": "湮灭",
}
for pattern, replace in replace_pattern_dict.items():
result = re.sub(pattern, replace, result)
return result
class RogueCurioSelector(RogueSelector):
def recognition(self):
self.ocr_results = []
ocr = RogueCurioOcr(OCR_ROGUE_CURIO)
results = ocr.matched_ocr(self.main.device.image, RogueCurio)
expect_num = 3
if len(results) != expect_num:
logger.warning(f"The OCR result does not match the curio count. "
f"Expect {expect_num}, but recognized {len(results)} only.")
self.ocr_results = results
return results
def ui_select(self, target: OcrResultButton | None, skip_first_screenshot=True):
def is_curio_selected():
return np.mean(get_color(self.main.device.image, tuple(target.area))) > 60 # shiny background
def is_select_curio_complete():
"""
Case 1: back to main page
Case 2: event page
"""
if self.main.is_in_main():
logger.info("Main page checked")
return True
if self.main.is_page_event():
logger.info("Event page checked")
return True
return False
enforce = False
if not target:
enforce = True
interval = Timer(1)
# start -> selected
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.main.device.screenshot()
if is_curio_selected():
if enforce:
logger.info("Curio selected (enforce)")
else:
logger.info(f"Curio {target} selected")
break
if interval.reached():
if enforce:
self.main.device.click(CURIO_ENFORCE)
else:
self.main.device.click(target)
interval.reset()
skip_first_screenshot = True
# selected -> confirm
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.main.device.screenshot()
if is_select_curio_complete():
break
if interval.reached():
self.main.device.click(BLESSING_CONFIRM)
interval.reset()
def try_select(self, option: OcrResultButton | str):
if option == 'random':
target = np.random.choice(self.ocr_results)
self.ui_select(target)
return True
if option == 'unrecorded':
for result in self.ocr_results:
if self.main.is_unrecorded(result, (0, -720, 300, 0)):
self.ui_select(result)
return True
return False
if isinstance(option, OcrResultButton):
self.ui_select(option)
return True
return False
def load_filter(self):
filter_ = CURIO_FILTER
string = ""
match self.main.config.Rogue_PresetCurioFilter:
case 'preset-1':
string = CURIO_PRESET_1
case 'custom':
string = self.main.config.Rogue_CustomCurioFilter
string = parse_name(string)
match self.main.config.Rogue_CurioSelectionStrategy:
case 'unrecorded-first':
string = 'unrecorded > ' + string
case 'before-random':
string = string.replace('random', 'unrecorded > random')
filter_.load(string)
self.filter_ = filter_

View File

@ -0,0 +1,9 @@
import tasks.rogue.keywords.blessing as KEYWORDS_ROGUE_BLESSING
import tasks.rogue.keywords.bonus as KEYWORDS_ROGUE_BONUS
import tasks.rogue.keywords.curio as KEYWORDS_ROGUE_CURIO
import tasks.rogue.keywords.enhancement as KEYWORDS_ROGUE_ENHANCEMENT
import tasks.rogue.keywords.path as KEYWORDS_ROGUE_PATH
import tasks.rogue.keywords.resonance as KEYWORDS_ROGUE_RESONANCE
from tasks.rogue.keywords.classes import (RogueBlessing, RogueBonus, RogueEnhancement,
RoguePath, RogueResonance, RogueCurio)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
from .classes import RogueBonus
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Fragmented_Cosmos = RogueBonus(
id=1,
name='Fragmented_Cosmos',
cn='破碎宇宙',
cht='破碎宇宙',
en='Fragmented Cosmos',
jp='破裂した宇宙',
)
Blessing_Cosmos = RogueBonus(
id=2,
name='Blessing_Cosmos',
cn='祝福宇宙',
cht='祝福宇宙',
en='Blessing Cosmos',
jp='祝福された宇宙',
)
Miracle_Cosmos = RogueBonus(
id=3,
name='Miracle_Cosmos',
cn='神奇宇宙',
cht='神奇宇宙',
en='Miracle Cosmos',
jp='奇跡的な宇宙',
)

View File

@ -0,0 +1,70 @@
from dataclasses import dataclass
from typing import ClassVar
from dev_tools.keyword_extract import UI_LANGUAGES
from module.ocr.keyword import Keyword
@dataclass(repr=False)
class RogueBlessing(Keyword):
instances: ClassVar = {}
path_id: int
rarity: int
enhancement: str
@property
def path_name(self):
path = RoguePath.instances[self.path_id]
return path.path_name
@property
def blessing_name(self):
return [self.__getattribute__(f"{server}_parsed")
for server in UI_LANGUAGES if hasattr(self, f"{server}_parsed")]
@dataclass(repr=False)
class RoguePath(Keyword):
instances: ClassVar = {}
@property
def path_name(self):
return [self.__getattribute__(f"{server}_parsed").replace("the", '')
for server in UI_LANGUAGES if hasattr(self, f"{server}_parsed")]
@dataclass(repr=False)
class RogueResonance(Keyword):
instances: ClassVar = {}
path_id: int
rarity: int
@property
def resonance_name(self):
return [self.__getattribute__(f"{server}_parsed")
for server in UI_LANGUAGES if hasattr(self, f"{server}_parsed")]
@dataclass(repr=False)
class RogueCurio(Keyword):
instances: ClassVar = {}
@property
def curio_name(self):
return [self.__getattribute__(f"{server}_parsed")
for server in UI_LANGUAGES if hasattr(self, f"{server}_parsed")]
@dataclass(repr=False)
class RogueBonus(Keyword):
instances: ClassVar = {}
@dataclass(repr=False)
class RogueEnhancement(Keyword):
instances: ClassVar = {}
@property
def enhancement_keyword(self):
return [self.__getattribute__(f"{server}_parsed")
for server in UI_LANGUAGES if hasattr(self, f"{server}_parsed")]

View File

@ -0,0 +1,381 @@
from .classes import RogueCurio
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Dimension_Reduction_Dice = RogueCurio(
id=1,
name='Dimension_Reduction_Dice',
cn='降维骰子',
cht='降維骰子',
en='Dimension Reduction Dice',
jp='次元削減ダイス',
)
Chaos_Trametes = RogueCurio(
id=2,
name='Chaos_Trametes',
cn='混沌云芝',
cht='混沌雲芝',
en='Chaos Trametes',
jp='混沌の雲芝',
)
Warping_Compound_Eye = RogueCurio(
id=3,
name='Warping_Compound_Eye',
cn='跃迁复眼',
cht='躍遷複眼',
en='Warping Compound Eye',
jp='跳躍複眼',
)
Fruit_of_the_Alien_Tree = RogueCurio(
id=4,
name='Fruit_of_the_Alien_Tree',
cn='异木果实',
cht='異木果實',
en='Fruit of the Alien Tree',
jp='異木の果実',
)
Casket_of_Inaccuracy = RogueCurio(
id=5,
name='Casket_of_Inaccuracy',
cn='测不准匣',
cht='測不準匣',
en='Casket of Inaccuracy',
jp='不確定の匣',
)
Ambergris_Cheese = RogueCurio(
id=6,
name='Ambergris_Cheese',
cn='香涎干酪',
cht='香涎乾酪',
en='Ambergris Cheese',
jp='香涎チーズ',
)
Fortune_Glue = RogueCurio(
id=7,
name='Fortune_Glue',
cn='福灵胶',
cht='福靈膠',
en='Fortune Glue',
jp='幸福クリーム',
)
The_Parchment_That_Always_Eats = RogueCurio(
id=8,
name='The_Parchment_That_Always_Eats',
cn='永不停嘴的羊皮卷',
cht='永不停嘴的羊皮卷',
en='The Parchment That Always Eats',
jp='おしゃべり羊皮紙',
)
Broken_Cuckoo_Clock = RogueCurio(
id=9,
name='Broken_Cuckoo_Clock',
cn='破碎咕咕钟',
cht='破碎咕咕鐘',
en='Broken Cuckoo Clock',
jp='壊れた鳩時計',
)
Mechanical_Cuckoo_Clock = RogueCurio(
id=10,
name='Mechanical_Cuckoo_Clock',
cn='机械咕咕钟',
cht='機械咕咕鐘',
en='Mechanical Cuckoo Clock',
jp='機械式鳩時計',
)
The_Doctor_Robe = RogueCurio(
id=11,
name='The_Doctor_Robe',
cn='博士之袍',
cht='博士之袍',
en="The Doctor's Robe",
jp='博士のローブ',
)
Society_Ticket = RogueCurio(
id=12,
name='Society_Ticket',
cn='俱乐部券',
cht='俱樂部券',
en='Society Ticket',
jp='クラブチケット',
)
Faith_Bond = RogueCurio(
id=13,
name='Faith_Bond',
cn='信仰债券',
cht='信仰債券',
en='Faith Bond',
jp='信仰債券',
)
Robe_of_The_Beauty = RogueCurio(
id=14,
name='Robe_of_The_Beauty',
cn='纯美之袍',
cht='純美之袍',
en='Robe of The Beauty',
jp='純美のローブ',
)
Gold_Coin_of_Discord = RogueCurio(
id=15,
name='Gold_Coin_of_Discord',
cn='分裂金币',
cht='分裂金幣',
en='Gold Coin of Discord',
jp='分裂金貨',
)
Useless_Typewriter = RogueCurio(
id=16,
name='Useless_Typewriter',
cn='无效文字打印机',
cht='無效文字印表機',
en='Useless Typewriter',
jp='無効文字タイプライター',
)
Void_Wick_Trimmer = RogueCurio(
id=17,
name='Void_Wick_Trimmer',
cn='空无烛剪',
cht='空無燭剪',
en='Void Wick Trimmer',
jp='空無の芯切り',
)
Omniscient_Capsule = RogueCurio(
id=18,
name='Omniscient_Capsule',
cn='万识囊',
cht='萬識囊',
en='Omniscient Capsule',
jp='全知袋',
)
Record_from_Beyond_the_Sky = RogueCurio(
id=19,
name='Record_from_Beyond_the_Sky',
cn='天外重声大碟',
cht='天外合唱專輯',
en='Record from Beyond the Sky',
jp='天外聖歌隊のレコード',
)
Entropic_Die = RogueCurio(
id=20,
name='Entropic_Die',
cn='万象无常骰',
cht='萬象無常骰',
en='Entropic Die',
jp='万象無常のサイコロ',
)
Shining_Trapezohedron_Die = RogueCurio(
id=21,
name='Shining_Trapezohedron_Die',
cn='闪耀的偏方三八面骰',
cht='閃耀的偏方三八面骰',
en='Shining Trapezohedron Die',
jp='輝くトラペゾヘドロンサイコロ',
)
Sealing_Wax_of_Preservation = RogueCurio(
id=22,
name='Sealing_Wax_of_Preservation',
cn='存护火漆',
cht='存護火漆',
en='Sealing Wax of Preservation',
jp='存護の封蝋',
)
Sealing_Wax_of_Elation = RogueCurio(
id=23,
name='Sealing_Wax_of_Elation',
cn='欢愉火漆',
cht='歡愉火漆',
en='Sealing Wax of Elation',
jp='愉悦の封蝋',
)
Sealing_Wax_of_The_Hunt = RogueCurio(
id=24,
name='Sealing_Wax_of_The_Hunt',
cn='巡猎火漆',
cht='巡獵火漆',
en='Sealing Wax of The Hunt',
jp='巡狩の封蝋',
)
Sealing_Wax_of_Destruction = RogueCurio(
id=25,
name='Sealing_Wax_of_Destruction',
cn='毁灭火漆',
cht='毀滅火漆',
en='Sealing Wax of Destruction',
jp='壊滅の封蝋',
)
Sealing_Wax_of_Remembrance = RogueCurio(
id=26,
name='Sealing_Wax_of_Remembrance',
cn='记忆火漆',
cht='記憶火漆',
en='Sealing Wax of Remembrance',
jp='記憶の封蝋',
)
Sealing_Wax_of_Nihility = RogueCurio(
id=27,
name='Sealing_Wax_of_Nihility',
cn='虚无火漆',
cht='虛無火漆',
en='Sealing Wax of Nihility',
jp='虚無の封蝋',
)
Sealing_Wax_of_Abundance = RogueCurio(
id=28,
name='Sealing_Wax_of_Abundance',
cn='丰饶火漆',
cht='豐饒火漆',
en='Sealing Wax of Abundance',
jp='豊穣の封蝋',
)
Corrupted_Code = RogueCurio(
id=29,
name='Corrupted_Code',
cn='乱七八糟的代码',
cht='亂七八糟的程式碼',
en='Corrupted Code',
jp='ぐちゃぐちゃなコード',
)
Odd_Code = RogueCurio(
id=30,
name='Odd_Code',
cn='有点蹊跷的代码',
cht='有點蹊蹺的程式碼',
en='Odd Code',
jp='少し怪しげなコード',
)
Normal_Code = RogueCurio(
id=31,
name='Normal_Code',
cn='中规中矩的代码',
cht='中規中矩的程式碼',
en='Normal Code',
jp='杓子定規なコード',
)
Elegant_Code = RogueCurio(
id=32,
name='Elegant_Code',
cn='精确优雅的代码',
cht='精確優雅的程式碼',
en='Elegant Code',
jp='正確で完璧なコード',
)
Mysterious_Code = RogueCurio(
id=33,
name='Mysterious_Code',
cn='没有注释的代码',
cht='沒有註解的程式碼',
en='Mysterious Code',
jp='注釈がないコード',
)
Infinitely_Recursive_Code = RogueCurio(
id=34,
name='Infinitely_Recursive_Code',
cn='无限递归的代码',
cht='無限遞迴的程式碼',
en='Infinitely Recursive Code',
jp='無限再帰するコード',
)
Shattered_Star_Bait = RogueCurio(
id=35,
name='Shattered_Star_Bait',
cn='碎星芳饵',
cht='碎星芳餌',
en='Shattered Star Bait',
jp='砕けた星の釣り餌',
)
Obliteration_Wick_Trimmer = RogueCurio(
id=36,
name='Obliteration_Wick_Trimmer',
cn='湮灭烛剪',
cht='湮滅燭剪',
en='Obliteration Wick Trimmer',
jp='湮滅の芯切り',
)
Insect_Web = RogueCurio(
id=37,
name='Insect_Web',
cn='虫网',
cht='蟲網',
en='Insect Web',
jp='虫網',
)
Angel_type_I_O_U_Dispenser = RogueCurio(
id=38,
name='Angel_type_I_O_U_Dispenser',
cn='天使型谢债发行机',
cht='天使型謝債發行機',
en='Angel-type I.O.U. Dispenser',
jp='天使型謝債発行機',
)
Laurel_Crown_of_Planar_Shifts = RogueCurio(
id=39,
name='Laurel_Crown_of_Planar_Shifts',
cn='换境桂冠',
cht='換境桂冠',
en='Laurel Crown of Planar Shifts',
jp='換境桂冠',
)
Space_Time_Prism = RogueCurio(
id=40,
name='Space_Time_Prism',
cn='时空棱镜',
cht='時空稜鏡',
en='Space-Time Prism',
jp='時空のプリズム',
)
Galactic_Big_Lotto = RogueCurio(
id=41,
name='Galactic_Big_Lotto',
cn='银河大乐透',
cht='銀河大樂透',
en='Galactic Big Lotto',
jp='銀河ビッグロッタリー',
)
Divination_Cuckoo_Clock = RogueCurio(
id=42,
name='Divination_Cuckoo_Clock',
cn='卜筮咕咕钟',
cht='卜筮咕咕鐘',
en='Divination Cuckoo Clock',
jp='占い鳩時計',
)
Black_Forest_Cuckoo_Clock = RogueCurio(
id=43,
name='Black_Forest_Cuckoo_Clock',
cn='黑森林咕咕钟',
cht='黑森林咕咕鐘',
en='Black Forest Cuckoo Clock',
jp='黒森鳩時計',
)
Perpetual_Motion_Cuckoo_Clock = RogueCurio(
id=44,
name='Perpetual_Motion_Cuckoo_Clock',
cn='永动咕咕钟',
cht='永動咕咕鐘',
en='Perpetual Motion Cuckoo Clock',
jp='永久鳩時計',
)
Punklorde_Mentality = RogueCurio(
id=45,
name='Punklorde_Mentality',
cn='朋克洛德精神',
cht='龐克洛德精神',
en='Punklorde Mentality',
jp='パンクロードの精神',
)
Beacon_Coloring_Paste = RogueCurio(
id=46,
name='Beacon_Coloring_Paste',
cn='信标着色剂',
cht='信標著色劑',
en='Beacon Coloring Paste',
jp='ビーコン着色剤',
)
IPC_Cuckoo_Clock = RogueCurio(
id=47,
name='IPC_Cuckoo_Clock',
cn='公司咕咕钟',
cht='公司咕咕鐘',
en='IPC Cuckoo Clock',
jp='カンパニー鳩時計',
)

View File

@ -0,0 +1,13 @@
from .classes import RogueEnhancement
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Already_Enhanced = RogueEnhancement(
id=1,
name='Already_Enhanced',
cn='已强化',
cht='已強化',
en='Already Enhanced',
jp='強化済み',
)

View File

@ -0,0 +1,61 @@
from .classes import RoguePath
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Preservation = RoguePath(
id=1,
name='Preservation',
cn='存护',
cht='存護',
en='Preservation',
jp='存護',
)
Remembrance = RoguePath(
id=2,
name='Remembrance',
cn='记忆',
cht='記憶',
en='Remembrance',
jp='記憶',
)
Nihility = RoguePath(
id=3,
name='Nihility',
cn='虚无',
cht='虛無',
en='Nihility',
jp='虚無',
)
Abundance = RoguePath(
id=4,
name='Abundance',
cn='丰饶',
cht='豐饒',
en='Abundance',
jp='豊穣',
)
The_Hunt = RoguePath(
id=5,
name='The_Hunt',
cn='巡猎',
cht='巡獵',
en='The Hunt',
jp='巡狩',
)
Destruction = RoguePath(
id=6,
name='Destruction',
cn='毁灭',
cht='毀滅',
en='Destruction',
jp='壊滅',
)
Elation = RoguePath(
id=7,
name='Elation',
cn='欢愉',
cht='歡愉',
en='Elation',
jp='愉悦',
)

View File

@ -0,0 +1,285 @@
from .classes import RogueResonance
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Path_Resonance_Preservation = RogueResonance(
id=1,
name='Path_Resonance_Preservation',
cn='命途回响:「存护」',
cht='命途迴響:「存護」',
en='Path Resonance: Preservation',
jp='運命の反響:「存護」',
path_id=1,
rarity=3,
)
Resonance_Formation_Zero_Dimensional_Reinforcement = RogueResonance(
id=2,
name='Resonance_Formation_Zero_Dimensional_Reinforcement',
cn='回响构音:零维强化',
cht='迴響構音:零維強化',
en='Resonance Formation: Zero-Dimensional Reinforcement',
jp='反響構音:析出硬化',
path_id=1,
rarity=3,
)
Resonance_Formation_Eutectic_Reaction = RogueResonance(
id=3,
name='Resonance_Formation_Eutectic_Reaction',
cn='回响构音:共晶反应',
cht='迴響構音:共晶反應',
en='Resonance Formation: Eutectic Reaction',
jp='反響構音:共晶反応',
path_id=1,
rarity=3,
)
Resonance_Formation_Isomorphous_Reaction = RogueResonance(
id=4,
name='Resonance_Formation_Isomorphous_Reaction',
cn='回响构音:均晶转变',
cht='迴響構音:均晶轉變',
en='Resonance Formation: Isomorphous Reaction',
jp='反響構音:全率固溶体',
path_id=1,
rarity=3,
)
Path_Resonance_Remembrance = RogueResonance(
id=5,
name='Path_Resonance_Remembrance',
cn='命途回响:「记忆」',
cht='命途迴響:「記憶」',
en='Path Resonance: Remembrance',
jp='運命の反響:「記憶」',
path_id=2,
rarity=3,
)
Resonance_Formation_Total_Recall = RogueResonance(
id=6,
name='Resonance_Formation_Total_Recall',
cn='回响构音:全面回忆',
cht='迴響構音:全面回憶',
en='Resonance Formation: Total Recall',
jp='反響構音:全面追憶',
path_id=2,
rarity=3,
)
Resonance_Formation_Rich_Experience = RogueResonance(
id=7,
name='Resonance_Formation_Rich_Experience',
cn='回响构音:体验的富翁',
cht='迴響構音:體驗的富翁',
en='Resonance Formation: Rich Experience',
jp='反響構音:体験の富豪',
path_id=2,
rarity=3,
)
Resonance_Formation_First_Love_Once_More = RogueResonance(
id=8,
name='Resonance_Formation_First_Love_Once_More',
cn='回响构音:第二次初恋',
cht='迴響構音:第二次初戀',
en='Resonance Formation: First Love Once More',
jp='反響構音:二度目の初恋',
path_id=2,
rarity=3,
)
Path_Resonance_Nihility = RogueResonance(
id=9,
name='Path_Resonance_Nihility',
cn='命途回响:「虚无」',
cht='命途迴響:「虛無」',
en='Path Resonance: Nihility',
jp='運命の反響:「虚無」',
path_id=3,
rarity=3,
)
Resonance_Formation_The_Doubtful_Fourfold_Root = RogueResonance(
id=10,
name='Resonance_Formation_The_Doubtful_Fourfold_Root',
cn='回响构音:怀疑的四重根',
cht='迴響構音:懷疑的四重根',
en='Resonance Formation: The Doubtful Fourfold Root',
jp='反響構音:疑いの四つの根',
path_id=3,
rarity=3,
)
Resonance_Formation_Suffering_and_Sunshine = RogueResonance(
id=11,
name='Resonance_Formation_Suffering_and_Sunshine',
cn='回响构音:苦难与阳光',
cht='迴響構音:苦難與陽光',
en='Resonance Formation: Suffering and Sunshine',
jp='反響構音:苦難と陽光',
path_id=3,
rarity=3,
)
Resonance_Formation_Outsider = RogueResonance(
id=12,
name='Resonance_Formation_Outsider',
cn='回响构音:局外人',
cht='迴響構音:局外人',
en='Resonance Formation: Outsider',
jp='反響構音:異邦人',
path_id=3,
rarity=3,
)
Path_Resonance_Abundance = RogueResonance(
id=13,
name='Path_Resonance_Abundance',
cn='命途回响:「丰饶」',
cht='命途迴響:「豐饒」',
en='Path Resonance: Abundance',
jp='運命の反響:「豊穣」',
path_id=4,
rarity=3,
)
Resonance_Formation_Terminal_Nirvana = RogueResonance(
id=14,
name='Resonance_Formation_Terminal_Nirvana',
cn='回响构音:无余涅槃',
cht='迴響構音:無餘涅槃',
en='Resonance Formation: Terminal Nirvana',
jp='反響構音:無余涅槃',
path_id=4,
rarity=3,
)
Resonance_Formation_Anicca = RogueResonance(
id=15,
name='Resonance_Formation_Anicca',
cn='回响构音:诸行无常',
cht='迴響構音:諸行無常',
en='Resonance Formation: Anicca',
jp='反響構音:諸行無常',
path_id=4,
rarity=3,
)
Resonance_Formation_Anatta = RogueResonance(
id=16,
name='Resonance_Formation_Anatta',
cn='回响构音:诸法无我',
cht='迴響構音:諸法無我',
en='Resonance Formation: Anatta',
jp='反響構音:諸法無我',
path_id=4,
rarity=3,
)
Path_Resonance_The_Hunt = RogueResonance(
id=17,
name='Path_Resonance_The_Hunt',
cn='命途回响:「巡猎」',
cht='命途迴響:「巡獵」',
en='Path Resonance: The Hunt',
jp='運命の反響:「巡狩」',
path_id=5,
rarity=3,
)
Resonance_Formation_Star_Hunter = RogueResonance(
id=18,
name='Resonance_Formation_Star_Hunter',
cn='回响构音:狩星巡日',
cht='迴響構音:狩星巡日',
en='Resonance Formation: Star Hunter',
jp='反響構音:星を狩りて日を巡る',
path_id=5,
rarity=3,
)
Resonance_Formation_Bow_and_Arrow = RogueResonance(
id=19,
name='Resonance_Formation_Bow_and_Arrow',
cn='回响构音:柘弓危矢',
cht='迴響構音:柘弓危矢',
en='Resonance Formation: Bow and Arrow',
jp='反響構音:柘弓に疾矢',
path_id=5,
rarity=3,
)
Resonance_Formation_Perfect_Aim = RogueResonance(
id=20,
name='Resonance_Formation_Perfect_Aim',
cn='回响构音:射不主皮',
cht='迴響構音:射不主皮',
en='Resonance Formation: Perfect Aim',
jp='反響構音:射は皮を主とせず',
path_id=5,
rarity=3,
)
Path_Resonance_Destruction = RogueResonance(
id=21,
name='Path_Resonance_Destruction',
cn='命途回响:「毁灭」',
cht='命途迴響:「毀滅」',
en='Path Resonance: Destruction',
jp='運命の反響:「壊滅」',
path_id=6,
rarity=3,
)
Resonance_Formation_Cataclysmic_Variable = RogueResonance(
id=22,
name='Resonance_Formation_Cataclysmic_Variable',
cn='回响构音:激变变星',
cht='迴響構音:激變變星',
en='Resonance Formation: Cataclysmic Variable',
jp='反響構音:激変星',
path_id=6,
rarity=3,
)
Resonance_Formation_Extreme_Helium_Flash = RogueResonance(
id=23,
name='Resonance_Formation_Extreme_Helium_Flash',
cn='回响构音:极端氦闪',
cht='迴響構音:極端氦閃',
en='Resonance Formation: Extreme Helium Flash',
jp='反響構音:ヘリウムフラッシュ',
path_id=6,
rarity=3,
)
Resonance_Formation_Event_Horizon = RogueResonance(
id=24,
name='Resonance_Formation_Event_Horizon',
cn='回响构音:事件视界',
cht='迴響構音:事件視界',
en='Resonance Formation: Event Horizon',
jp='反響構音:事象の地平線',
path_id=6,
rarity=3,
)
Path_Resonance_Elation = RogueResonance(
id=25,
name='Path_Resonance_Elation',
cn='命途回响:「欢愉」',
cht='命途迴響:「歡愉」',
en='Path Resonance: Elation',
jp='運命の反響:「愉悦」',
path_id=7,
rarity=3,
)
Resonance_Formation_Doomsday_Carnival = RogueResonance(
id=26,
name='Resonance_Formation_Doomsday_Carnival',
cn='回响构音:末日狂欢',
cht='迴響構音:末日狂歡',
en='Resonance Formation: Doomsday Carnival',
jp='反響構音:終末の狂宴',
path_id=7,
rarity=3,
)
Resonance_Formation_Dance_of_Growth = RogueResonance(
id=27,
name='Resonance_Formation_Dance_of_Growth',
cn='回响构音:树苗长高舞',
cht='迴響構音:樹苗長高舞',
en='Resonance Formation: Dance of Growth',
jp='反響構音:苗木が育つ踊り',
path_id=7,
rarity=3,
)
Resonance_Formation_Instant_Win = RogueResonance(
id=28,
name='Resonance_Formation_Instant_Win',
cn='回响构音:开盖有奖',
cht='迴響構音:開蓋有獎',
en='Resonance Formation: Instant Win',
jp='反響構音もう1本',
path_id=7,
rarity=3,
)

10
tasks/rogue/preset.py Normal file
View File

@ -0,0 +1,10 @@
BLESSING_PRESET_1 = ("巡猎-3 > 《冠军晚餐•猫的摇篮》 > 丰饶众生,一法界心 > 毁灭-3 "
"> 火堆外的夜 > 巡猎-2 > 戒律性闪变 > 巡猎 > 存护-2 > reset > random")
RESONANCE_PRESET_1 = ("回响构音:均晶转变 > 回响构音:零维强化"
"> 回响构音:第二次初恋 > 回响构音:体验的富翁"
"> 回响构音:局外人 > 回响构音:怀疑的四重根"
"> 回响构音:诸法无我 > 回响构音:诸行无常"
"> 回响构音:射不主皮 > 回响构音:柘弓危矢"
"> 回响构音:激变变星 > 回响构音:极端氦闪"
"> 回响构音:末日狂欢 > 回响构音:树苗长高舞")
CURIO_PRESET_1 = "博士之袍 > 福灵胶 > 分裂金币 > 信仰债券 > 换境桂冠 > 俱乐部券 > 碎星芳饵 > random"

View File

@ -0,0 +1,67 @@
import numpy as np
from module.logger import logger
from module.ocr.keyword import Keyword
from module.ocr.ocr import OcrResultButton
from tasks.rogue.ui import RogueUI
class RogueSelector:
"""
An Interface used in blessing, curio, and other ui selection in rogue
"""
def __init__(self, main: RogueUI):
self.main = main
self.filter_ = None
self.ocr_results = []
def recognition(self):
...
def ui_select(self, target: OcrResultButton | None, skip_first_screenshot=True):
...
def try_select(self, option: OcrResultButton | str):
...
def load_filter(self):
...
def perform_selection(self, priority):
if not self.ocr_results:
logger.warning('No blessing recognized, randomly choose one')
self.ui_select(None)
return False
if not len(priority):
logger.info('No blessing project satisfies current filter, randomly choose one')
choose = np.random.choice(self.ocr_results)
self.ui_select(choose)
return False
for option in priority:
logger.info(f"Try to choose option: {option}")
if self.try_select(option):
return True
else:
logger.info(f"Can not choose option: {option}")
def recognize_and_select(self):
def match_ocr_result(matched_keyword: Keyword):
for result in self.ocr_results:
if result.matched_keyword == matched_keyword:
return result
return None
self.recognition()
self.load_filter()
if self.filter_:
keywords = [result.matched_keyword for result in self.ocr_results]
priority = self.filter_.apply(keywords)
priority = [option if isinstance(option, str) else match_ocr_result(option) for option in priority]
else:
logger.warning("No filter loaded, use random instead")
priority = ['random']
logger.info(f"Priority: {priority}")
self.perform_selection(priority)

58
tasks/rogue/ui.py Normal file
View File

@ -0,0 +1,58 @@
import re
from module.base.utils import area_offset
from module.ocr.ocr import Digit, Ocr, OcrResultButton
from tasks.base.ui import UI
from tasks.rogue.assets.assets_rogue_ui import *
from tasks.rogue.keywords import *
class RogueBonusOcr(Ocr):
def after_process(self, result):
result = super().after_process(result)
if self.lang == 'ch':
replace_pattern_dict = {
"[宇宝][宙审]": "宇宙",
}
for pat, replace in replace_pattern_dict.items():
result = re.sub(pat, replace, result)
return result
class RogueUI(UI):
path: RoguePath
@property
def cosmic_fragment(self):
"""
Return valid result only when template appear
"""
if self.appear(COSMIC_FRAGMENT):
return Digit(OCR_COSMIC_FRAGMENT).ocr_single_line(self.device.image)
return 0
def is_page_choose_blessing(self):
return (self.image_color_count(PAGE_CHOOSE_BUFF, (245, 245, 245), count=200)
and self.appear(CHECK_BLESSING))
def is_page_choose_curio(self):
return self.appear(PAGE_CHOOSE_CURIO)
def is_page_choose_bonus(self):
return self.appear(PAGE_CHOOSE_BONUS)
def is_page_event(self):
return self.appear(PAGE_EVENT)
def handle_obtain_item_popup(self, interval=5) -> bool:
"""After selecting some curio (e.g. Sealing_Wax_of_*), there will be a popup after back to main page"""
if self.appear_then_click(OBTAIN_ITEM_POPUP, interval=interval):
return True
return False
def is_unrecorded(self, target: OcrResultButton, relative_area):
"""
To check a rogue keyword is not record in game index by finding template
"""
FLAG_UNRECORD.matched_button.search = area_offset(relative_area, target.area[:2])
return self.appear(FLAG_UNRECORD)

33
tasks/rogue/utils.py Normal file
View File

@ -0,0 +1,33 @@
import re
from module.base.base import ModuleBase
from module.base.utils import area_offset
from module.ocr.ocr import OcrResultButton
REGEX_PUNCTUATION = re.compile(r'[ ,.\'"“”,。:!??·•—/()()「」『』【】《》]')
def parse_name(n):
n = REGEX_PUNCTUATION.sub('', str(n)).lower()
return n
def get_regex_from_keyword_name(keyword, attr_name):
string = ""
for instance in keyword.instances.values():
if hasattr(instance, attr_name):
for name in instance.__getattribute__(attr_name):
string += f"{name}|"
# some pattern contain each other, make sure each pattern end with "-" or the end of string
return f"(?:({string[:-1]})(?:-|$))?"
def is_card_selected(main: ModuleBase, target: OcrResultButton, confirm_button):
"""
There is a white border if a blessing is selected.
For the enforce case, just check the confirm button turning to white
"""
if not target:
return main.image_color_count(confirm_button, (230, 230, 230))
top_border = area_offset(target.area, (0, -180))
return main.image_color_count(top_border, (255, 255, 255))