Merge pull request #484 from LmeSzinc/dev

增加角色养成规划 | Add Character Planner
This commit is contained in:
LmeSzinc 2024-05-28 22:55:24 +08:00 committed by GitHub
commit 3350a3d3af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 5738 additions and 609 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -435,6 +435,13 @@ pre.rich-traceback-code {
overflow-wrap: break-word;
}
*[style*="--dashboard-bold--"] {
font-size: 1rem;
font-weight: bold;
color: #7a77bb;
overflow-wrap: break-word;
}
[id^="pywebio-scope-dashboard-row-"] p {
margin-bottom: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -46,18 +46,64 @@
"Command": "Dungeon",
"ServerUpdate": "04:00"
},
"Planner": {
"Item_Credit": {},
"Item_Trailblaze_EXP": {},
"Item_Traveler_Guide": {},
"Item_Refined_Aether": {},
"Item_Lost_Crystal": {},
"Item_Broken_Teeth_of_Iron_Wolf": {},
"Item_Endotherm_Chitin": {},
"Item_Horn_of_Snow": {},
"Item_Lightning_Crown_of_the_Past_Shadow": {},
"Item_Storm_Eye": {},
"Item_Void_Cast_Iron": {},
"Item_Golden_Crown_of_the_Past_Shadow": {},
"Item_Netherworld_Token": {},
"Item_Searing_Steel_Blade": {},
"Item_Gelid_Chitin": {},
"Item_Shape_Shifter_Lightning_Staff": {},
"Item_Ascendant_Debris": {},
"Item_Nail_of_the_Ape": {},
"Item_Suppressing_Edict": {},
"Item_IPC_Work_Permit": {},
"Item_Raging_Heart": {},
"Item_Dream_Fridge": {},
"Item_Dream_Flamer": {},
"Item_Worldbreaker_Blade": {},
"Item_Arrow_of_the_Starchaser": {},
"Item_Key_of_Wisdom": {},
"Item_Safeguard_of_Amber": {},
"Item_Obsidian_of_Obsession": {},
"Item_Stellaris_Symphony": {},
"Item_Flower_of_Eternity": {},
"Item_Moon_Madness_Fang": {},
"Item_Countertemporal_Shot": {},
"Item_Divine_Amber": {},
"Item_Heaven_Incinerator": {},
"Item_Heavenly_Melody": {},
"Item_Myriad_Fruit": {},
"Item_Tracks_of_Destiny": {},
"Item_Destroyer_Final_Road": {},
"Item_Guardian_Lament": {},
"Item_Regret_of_Infinite_Ochema": {},
"Item_Past_Evils_of_the_Borehole_Planet_Disaster": {},
"Item_Lost_Echo_of_the_Shared_Wish": {},
"Item_Squirming_Core": {},
"Item_Conqueror_Will": {},
"Item_Silvermane_Medal": {},
"Item_Ancient_Engine": {},
"Item_Immortal_Lumintwig": {},
"Item_Artifex_Gyreheart": {},
"Item_Dream_Making_Engine": {},
"Item_Shards_of_Desires": {}
},
"Dungeon": {
"Name": "Calyx_Golden_Treasures_Jarilo_VI",
"NameAtDoubleCalyx": "Calyx_Golden_Treasures_Jarilo_VI",
"NameAtDoubleRelic": "Cavern_of_Corrosion_Path_of_Providence",
"Team": 1
},
"DungeonDaily": {
"CalyxGolden": "Calyx_Golden_Treasures_Jarilo_VI",
"CalyxCrimson": "Calyx_Crimson_Destruction_Herta_StorageZone",
"StagnantShadow": "Stagnant_Shadow_Quanta",
"CavernOfCorrosion": "Cavern_of_Corrosion_Path_of_Providence"
},
"DungeonSupport": {
"Use": "when_daily",
"Character": "FirstCharacter"
@ -79,9 +125,9 @@
},
"AchievableQuest": {
"Complete_1_Daily_Mission": "not_supported",
"Clear_Calyx_Golden_1_times": "achievable",
"Clear_Stagnant_Shadow_1_times": "achievable",
"Clear_Cavern_of_Corrosion_1_times": "achievable",
"Clear_Calyx_Golden_1_times": "not_set",
"Clear_Stagnant_Shadow_1_times": "not_set",
"Clear_Cavern_of_Corrosion_1_times": "not_set",
"In_a_single_battle_inflict_3_Weakness_Break_of_different_Types": "achievable",
"Inflict_Weakness_Break_5_times": "achievable",
"Defeat_a_total_of_20_enemies": "achievable",
@ -93,7 +139,7 @@
"Log_in_to_the_game": "achievable",
"Dispatch_1_assignments": "achievable",
"Complete_Simulated_Universe_1_times": "not_set",
"Clear_Calyx_Crimson_1_times": "achievable",
"Clear_Calyx_Crimson_1_times": "not_set",
"Enter_combat_by_attacking_enemie_Weakness_and_win_3_times": "achievable",
"Use_Technique_2_times": "achievable",
"Destroy_3_destructible_objects": "achievable",
@ -221,5 +267,10 @@
"Enable": true,
"AimClicker": "do_not_click"
}
},
"PlannerScan": {
"PlannerScan": {
"ResultAdd": false
}
}
}

View File

@ -3,11 +3,13 @@ import re
import typing as t
from dataclasses import dataclass
import cv2
import numpy as np
from tqdm import tqdm
from module.base.code_generator import CodeGenerator
from module.base.utils import SelectedGrids, area_limit, area_pad, get_bbox, get_color, image_size, load_image
from module.base.utils import (
SelectedGrids, area_center, area_limit, area_pad, corner2area, get_bbox, get_color, image_size, load_image)
from module.config.config_manual import ManualConfig as AzurLaneConfig
from module.config.server import VALID_LANG
from module.config.utils import deep_get, deep_iter, deep_set, iter_folder
@ -17,6 +19,44 @@ SHARE_SERVER = 'share'
ASSET_SERVER = [SHARE_SERVER] + VALID_LANG
def parse_grid(image):
"""
Args:
image:
Returns:
dict: Key: Grid position (x, y)
Value: Area on image
"""
image = cv2.inRange(image, (127, 127, 127), (255, 255, 255))
contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
dic_rect = {}
for corners in contours:
area = corner2area(corners.reshape(4, 2)) + (0, 0, 1, 1)
center = area_center(area)
dic_rect[center] = area
# for k, v in dic_rect.items():
# print(k, v)
dic_grid = {}
prev_y = -100
grid_y = -1
stack_center = []
for center in sorted(dic_rect.keys(), key=lambda x: x[1]):
if center[1] > prev_y + 3:
for x, c in enumerate(sorted(stack_center, key=lambda x: x[0])):
dic_grid[(x, grid_y)] = tuple(dic_rect[c].astype(int))
grid_y += 1
stack_center = []
stack_center.append(center)
prev_y = center[1]
for x, c in enumerate(sorted(stack_center, key=lambda x: x[0])):
dic_grid[(x, grid_y)] = tuple(dic_rect[c].astype(int))
# for k, v in dic_grid.items():
# print(k, v)
return dic_grid
class AssetsImage:
REGEX_ASSETS = re.compile(
f'^{AzurLaneConfig.ASSETS_FOLDER}/'
@ -24,7 +64,7 @@ class AssetsImage:
f'(?P<module>[a-zA-Z0-9_/]+?)/'
f'(?P<assets>\w+)'
f'(?P<frame>\.\d+)?'
f'(?P<attr>\.AREA|\.SEARCH|\.COLOR|\.BUTTON)?'
f'(?P<attr>\.AREA|\.SEARCH|\.COLOR|\.BUTTON|\.GRID)?'
f'\.png$'
)
@ -46,6 +86,7 @@ class AssetsImage:
self.assets = ''
self.frame = 1
self.attr = ''
self.posi = None
if res:
self.valid = True
@ -66,6 +107,7 @@ class AssetsImage:
self.bbox: t.Tuple = ()
self.mean: t.Tuple = ()
self.grids = {}
def parse(self):
image = load_image(self.file)
@ -79,6 +121,10 @@ class AssetsImage:
mean = tuple(np.rint(mean).astype(int))
self.bbox = bbox
self.mean = mean
if self.attr == 'GRID':
self.grids = parse_grid(image)
return bbox, mean
def __str__(self):
@ -87,6 +133,26 @@ class AssetsImage:
else:
return f'AssetsImage(file={self.file}, valid={self.valid})'
@property
def is_GRID(self):
return self.attr == 'GRID'
@property
def is_base(self):
return self.attr == ''
def iter_grids(self):
frame = 0
for posi, rect in self.grids.items():
frame += 1
image = AssetsImage(self.file)
image.attr = ''
image.bbox = rect
image.mean = self.mean
image.frame = frame
image.posi = posi
yield image
def iter_images():
for server in ASSET_SERVER:
@ -97,6 +163,12 @@ def iter_images():
yield AssetsImage(file)
def iter_grids(images):
for image in images:
for grid in image.iter_grids():
yield grid
@dataclass
class DataAssets:
module: str
@ -104,6 +176,7 @@ class DataAssets:
server: str
frame: int
file: str = ''
posi = None
area: t.Tuple[int, int, int, int] = ()
search: t.Tuple[int, int, int, int] = ()
color: t.Tuple[int, int, int] = ()
@ -126,7 +199,6 @@ class DataAssets:
Product DataAssets from AssetsImage with attr=""
"""
data = cls(module=image.module, assets=image.assets, server=image.server, frame=image.frame, file=image.file)
data.load_image(image)
return data
def load_image(self, image: AssetsImage):
@ -135,6 +207,7 @@ class DataAssets:
self.area = image.bbox
self.color = image.mean
self.button = image.bbox
self.posi = image.posi
elif image.attr == 'AREA':
self.area = image.bbox
self.has_raw_area = True
@ -147,6 +220,8 @@ class DataAssets:
elif image.attr == 'BUTTON':
self.button = image.bbox
self.has_raw_button = True
elif image.attr == 'GRID':
pass
else:
logger.warning(f'Trying to load an image with unknown attribute: {image}')
@ -161,17 +236,22 @@ def iter_assets():
for image in tqdm(images):
image.parse()
images += list(iter_grids(images))
# Validate images
images = SelectedGrids(images).select(valid=True)
images.create_index('module', 'assets', 'server', 'frame', 'attr')
for image in images.filter(lambda x: bool(x.attr)):
image: AssetsImage = image
if not images.indexed_select(image.module, image.assets, image.server, image.frame, ''):
logger.warning(f'Attribute assets has no parent assets: {image.file}')
image.valid = False
if not images.indexed_select(image.module, image.assets, image.server, 1, ''):
logger.warning(f'Attribute assets has no first frame: {image.file}')
image.valid = False
if image.is_GRID:
pass
else:
if not images.indexed_select(image.module, image.assets, image.server, image.frame, ''):
logger.warning(f'Attribute assets has no parent assets: {image.file}')
image.valid = False
if not images.indexed_select(image.module, image.assets, image.server, 1, ''):
logger.warning(f'Attribute assets has no first frame: {image.file}')
image.valid = False
if image.attr == 'SEARCH' and image.frame > 1:
logger.warning(f'Attribute SEARCH with frame > 1 is not allowed: {image.file}')
image.valid = False
@ -180,18 +260,18 @@ def iter_assets():
# Convert to DataAssets
data = {}
for image in images:
if image.attr == '':
if image.is_base:
row = DataAssets.product(image)
row.load_image(image)
deep_set(data, keys=[image.module, image.assets, image.server, image.frame], value=row)
# Load attribute images
for image in images:
if image.attr != '':
if not image.is_base:
row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame])
row.load_image(image)
# Set `search`
for path, frames in deep_iter(data, depth=3):
print(path, frames)
# print(path, frames)
for frame in frames.values():
# Generate `search` from `area`
if not frame.has_raw_search:
@ -253,6 +333,8 @@ def generate_code():
gen.ObjectAttr(key='search', value=frame.search)
gen.ObjectAttr(key='color', value=frame.color)
gen.ObjectAttr(key='button', value=frame.button)
if frame.posi is not None:
gen.ObjectAttr(key='posi', value=frame.posi)
elif len(frames) == 1:
frame = frames[0]
with gen.ObjectAttr(key=server, value=gen.Object(object_class='Button')):
@ -261,6 +343,8 @@ def generate_code():
gen.ObjectAttr(key='search', value=frame.search)
gen.ObjectAttr(key='color', value=frame.color)
gen.ObjectAttr(key='button', value=frame.button)
if frame.posi is not None:
gen.ObjectAttr(key='posi', value=frame.posi)
else:
gen.ObjectAttr(key=server, value=None)
gen.write(os.path.join(output, f'assets_{module.replace("/", "_")}.py'))

View File

@ -576,6 +576,8 @@ class KeywordExtract:
self.load_keywords(['养成材料', '光锥', '遗器', '其他材料', '消耗品', '任务', '贵重物'])
self.write_keywords(keyword_class='ItemTab', text_convert=lambda name: name.replace(' ', ''),
output_file='./tasks/item/keywords/tab.py')
from dev_tools.keywords.item import generate_items
generate_items()
self.generate_rogue_buff()
self.load_keywords(['已强化'])
self.write_keywords(keyword_class='RogueEnhancement', output_file='./tasks/rogue/keywords/enhancement.py')

View File

@ -159,6 +159,7 @@ class GenerateKeyword:
base = self.keyword_format.copy()
text_id = keyword.pop('text_id')
if text_id is None:
logger.warning(f'Empty text_id in {keyword}')
return
# id
self.keyword_index += 1
@ -191,3 +192,20 @@ class GenerateKeyword:
def __call__(self, *args, **kwargs):
self.generate()
class ShareData(GenerateKeyword):
@cached_property
def GameplayGuideData(self):
return self.read_file('./ExcelOutput/GameplayGuideData.json')
@cached_property
def MappingInfo(self):
return self.read_file('./ExcelOutput/MappingInfo.json')
@cached_property
def ItemConfig(self):
return self.read_file('./ExcelOutput/ItemConfig.json')
SHARE_DATA = ShareData()

View File

@ -1,7 +1,7 @@
import re
import typing as t
from dev_tools.keywords.base import GenerateKeyword, text_to_variable
from dev_tools.keywords.base import GenerateKeyword, SHARE_DATA, text_to_variable
from module.base.decorator import cached_property
from module.config.utils import deep_get
@ -26,13 +26,14 @@ class GenerateDungeonList(GenerateKeyword):
@cached_property
def data(self):
return self.read_file('./ExcelOutput/GameplayGuideData.json')
return SHARE_DATA.GameplayGuideData
def iter_keywords(self) -> t.Iterable[dict]:
for keyword in self.iter_dungeon():
if isinstance(keyword, str):
yield dict(
text_id=self.find_keyword(keyword, lang='cn')[0],
dungeon_id=-1,
plane_id=-1,
)
else:
@ -41,6 +42,7 @@ class GenerateDungeonList(GenerateKeyword):
def iter_dungeon(self):
temp_save = ""
for data in self.data.values():
dungeon_id = data.get('ID', 0)
text_id = deep_get(data, keys='Name.Hash')
plane_id = deep_get(data, 'MapEntranceID', 0)
_, name = self.find_keyword(text_id, lang='cn')
@ -51,6 +53,7 @@ class GenerateDungeonList(GenerateKeyword):
continue
yield dict(
text_id=text_id,
dungeon_id=dungeon_id,
plane_id=plane_id,
)
if temp_save:

137
dev_tools/keywords/item.py Normal file
View File

@ -0,0 +1,137 @@
import typing as t
from dev_tools.keywords.base import GenerateKeyword, SHARE_DATA
from module.base.decorator import cached_property
from module.config.utils import deep_get
class GenerateItemBase(GenerateKeyword):
purpose_type = []
blacklist = []
def iter_items(self) -> t.Iterable[dict]:
for data in SHARE_DATA.ItemConfig.values():
item_id = data.get('ID', 0)
text_id = deep_get(data, keys='ItemName.Hash')
subtype = data.get('ItemSubType', 0)
rarity = data.get('Rarity', 0)
purpose = data.get('PurposeType', 0)
item_group = data.get('ItemGroup', 0)
yield dict(
text_id=text_id,
rarity=rarity,
item_id=item_id,
item_group=item_group,
dungeon_id=-1,
subtype=subtype,
purpose=purpose,
)
def iter_keywords(self) -> t.Iterable[dict]:
for data in self.iter_items():
if data['subtype'] == 'Material' and data['purpose'] in self.purpose_type:
if data['item_id'] in self.blacklist:
continue
data['dungeon_id'] = self.dict_itemid_to_dungeonid.get(data['item_id'], -1)
yield data
def iter_rows(self) -> t.Iterable[dict]:
for data in super().iter_rows():
data.pop('subtype')
data.pop('purpose')
yield data
@cached_property
def dict_itemid_to_dungeonid(self):
"""
MappingInfo is like
dungeon_id:
dungeon_level:
data
"""
dic = {}
for level_data in SHARE_DATA.MappingInfo.values():
# Use the highest level
# And must contain:
# "Type": "FARM_ENTRANCE",
# "FarmType": "COCOON",
for dungeon_data in level_data.values():
if dungeon_data.get('Type') != 'FARM_ENTRANCE':
continue
# parse
dungeon_id = dungeon_data.get('ID', 0)
for item_data in dungeon_data.get('DisplayItemList', []):
item_id = item_data.get('ItemID', 0)
if item_id < 100:
continue
dic.setdefault(item_id, dungeon_id)
return dic
class GenerateItemCurrency(GenerateItemBase):
output_file = './tasks/planner/keywords/item_currency.py'
# Leave 'Credit' and `Trailblaze_EXP`
whitelist = [2, 22]
def iter_keywords(self) -> t.Iterable[dict]:
for data in self.iter_items():
if data['subtype'] == 'Virtual' and data['item_id'] < 100:
if data['item_id'] not in self.whitelist:
continue
yield data
class GenerateItemExp(GenerateItemBase):
output_file = './tasks/planner/keywords/item_exp.py'
purpose_type = [1, 5, 6]
# 'Lost_Essence' is not available in game currently
blacklist = [234]
class GenerateItemAscension(GenerateItemBase):
output_file = './tasks/planner/keywords/item_ascension.py'
purpose_type = [2]
# 'Enigmatic_Ectostella' is not available in game currently
blacklist = [110400]
class GenerateItemTrace(GenerateItemBase):
output_file = './tasks/planner/keywords/item_trace.py'
purpose_type = [3]
# Can't farm Tears_of_Dreams
blacklist = [110101]
class GenerateItemWeekly(GenerateItemBase):
output_file = './tasks/planner/keywords/item_weekly.py'
purpose_type = [4]
class GenerateItemCalyx(GenerateItemBase):
output_file = './tasks/planner/keywords/item_calyx.py'
purpose_type = [7]
def iter_keywords(self) -> t.Iterable[dict]:
items = list(super().iter_keywords())
# Copy dungeon_id from green item to all items in group
dic_group_to_dungeonid = {}
for item in items:
dungeon = item['dungeon_id']
if dungeon > 0:
dic_group_to_dungeonid[item['item_group']] = dungeon
for item in items:
dungeon = dic_group_to_dungeonid[item['item_group']]
item['dungeon_id'] = dungeon
yield from items
def generate_items():
GenerateItemCurrency()()
GenerateItemExp()()
GenerateItemAscension()()
GenerateItemTrace()()
GenerateItemWeekly()()
GenerateItemCalyx()()

View File

@ -6,7 +6,7 @@ from module.exception import ScriptError
class Button(Resource):
def __init__(self, file, area, search, color, button):
def __init__(self, file, area, search, color, button, posi=None):
"""
Args:
file: Filepath to an assets
@ -20,6 +20,7 @@ class Button(Resource):
self.search: t.Tuple[int, int, int, int] = search
self.color: t.Tuple[int, int, int] = color
self._button: t.Tuple[int, int, int, int] = button
self.posi: t.Optional[t.Tuple[int, int]] = posi
self.resource_add(self.file)
self._button_offset: t.Tuple[int, int] = (0, 0)

View File

@ -219,6 +219,308 @@
"display": "hide"
}
},
"Planner": {
"Item_Credit": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Trailblaze_EXP": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Traveler_Guide": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Refined_Aether": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Lost_Crystal": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Broken_Teeth_of_Iron_Wolf": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Endotherm_Chitin": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Horn_of_Snow": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Lightning_Crown_of_the_Past_Shadow": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Storm_Eye": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Void_Cast_Iron": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Golden_Crown_of_the_Past_Shadow": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Netherworld_Token": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Searing_Steel_Blade": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Gelid_Chitin": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Shape_Shifter_Lightning_Staff": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Ascendant_Debris": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Nail_of_the_Ape": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Suppressing_Edict": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_IPC_Work_Permit": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Raging_Heart": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Dream_Fridge": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Dream_Flamer": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Worldbreaker_Blade": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Arrow_of_the_Starchaser": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Key_of_Wisdom": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Safeguard_of_Amber": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Obsidian_of_Obsession": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Stellaris_Symphony": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Flower_of_Eternity": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Moon_Madness_Fang": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Countertemporal_Shot": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Divine_Amber": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Heaven_Incinerator": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Heavenly_Melody": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Myriad_Fruit": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Tracks_of_Destiny": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Destroyer_Final_Road": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Guardian_Lament": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Regret_of_Infinite_Ochema": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Past_Evils_of_the_Borehole_Planet_Disaster": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Lost_Echo_of_the_Shared_Wish": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Squirming_Core": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Conqueror_Will": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Silvermane_Medal": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Ancient_Engine": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Immortal_Lumintwig": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Artifex_Gyreheart": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Dream_Making_Engine": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
},
"Item_Shards_of_Desires": {
"type": "planner",
"value": {},
"display": "display",
"stored": "StoredPlanner"
}
},
"Dungeon": {
"Name": {
"type": "select",
@ -334,93 +636,13 @@
]
}
},
"DungeonDaily": {
"CalyxGolden": {
"type": "select",
"value": "Calyx_Golden_Treasures_Jarilo_VI",
"option": [
"do_not_achieve",
"Calyx_Golden_Memories_Jarilo_VI",
"Calyx_Golden_Memories_The_Xianzhou_Luofu",
"Calyx_Golden_Memories_Penacony",
"Calyx_Golden_Aether_Jarilo_VI",
"Calyx_Golden_Aether_The_Xianzhou_Luofu",
"Calyx_Golden_Aether_Penacony",
"Calyx_Golden_Treasures_Jarilo_VI",
"Calyx_Golden_Treasures_The_Xianzhou_Luofu",
"Calyx_Golden_Treasures_Penacony"
]
},
"CalyxCrimson": {
"type": "select",
"value": "Calyx_Crimson_Destruction_Herta_StorageZone",
"option": [
"do_not_achieve",
"Calyx_Crimson_Destruction_Herta_StorageZone",
"Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape",
"Calyx_Crimson_Preservation_Herta_SupplyZone",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden",
"Calyx_Crimson_Erudition_Jarilo_RivetTown",
"Calyx_Crimson_Harmony_Jarilo_RobotSettlement",
"Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape",
"Calyx_Crimson_Nihility_Jarilo_GreatMine",
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission"
]
},
"StagnantShadow": {
"type": "select",
"value": "Stagnant_Shadow_Quanta",
"option": [
"do_not_achieve",
"Stagnant_Shadow_Spike",
"Stagnant_Shadow_Perdition",
"Stagnant_Shadow_Duty",
"Stagnant_Shadow_Blaze",
"Stagnant_Shadow_Scorch",
"Stagnant_Shadow_Ire",
"Stagnant_Shadow_Rime",
"Stagnant_Shadow_Icicle",
"Stagnant_Shadow_Nectar",
"Stagnant_Shadow_Fulmination",
"Stagnant_Shadow_Doom",
"Stagnant_Shadow_Gust",
"Stagnant_Shadow_Celestial",
"Stagnant_Shadow_Quanta",
"Stagnant_Shadow_Abomination",
"Stagnant_Shadow_Roast",
"Stagnant_Shadow_Mirage",
"Stagnant_Shadow_Puppetry"
]
},
"CavernOfCorrosion": {
"type": "select",
"value": "Cavern_of_Corrosion_Path_of_Providence",
"option": [
"do_not_achieve",
"Cavern_of_Corrosion_Path_of_Gelid_Wind",
"Cavern_of_Corrosion_Path_of_Jabbing_Punch",
"Cavern_of_Corrosion_Path_of_Drifting",
"Cavern_of_Corrosion_Path_of_Providence",
"Cavern_of_Corrosion_Path_of_Holy_Hymn",
"Cavern_of_Corrosion_Path_of_Conflagration",
"Cavern_of_Corrosion_Path_of_Elixir_Seekers",
"Cavern_of_Corrosion_Path_of_Darkness",
"Cavern_of_Corrosion_Path_of_Dreamdive"
]
}
},
"DungeonSupport": {
"Use": {
"type": "select",
"value": "when_daily",
"option": [
"always_use",
"when_daily",
"do_not_use"
"always_use"
]
},
"Character": {
@ -1569,5 +1791,13 @@
]
}
}
},
"PlannerScan": {
"PlannerScan": {
"ResultAdd": {
"type": "checkbox",
"value": false
}
}
}
}

View File

@ -110,20 +110,6 @@ Dungeon:
Team:
value: 1
option: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
DungeonDaily:
# Dungeon names will be injected in config updater
CalyxGolden:
value: Calyx_Golden_Treasures_Jarilo_VI
option: [ do_not_achieve, ]
CalyxCrimson:
value: Calyx_Crimson_Destruction_Herta_StorageZone
option: [ do_not_achieve, ]
StagnantShadow:
value: Stagnant_Shadow_Quanta
option: [ do_not_achieve, ]
CavernOfCorrosion:
value: Cavern_of_Corrosion_Path_of_Providence
option: [ do_not_achieve, ]
DungeonSupport:
Use:
value: when_daily
@ -151,6 +137,8 @@ DungeonStorage:
color: "#8fb5fe"
SupportReward:
Collect: true
Planner: {}
# Items will be injected in config updater
Weekly:
Name:
@ -305,3 +293,5 @@ Daemon:
AimClicker:
value: do_not_click
option: [ item_enemy, item, enemy, do_not_click ]
PlannerScan:
ResultAdd: false

View File

@ -31,7 +31,8 @@
"menu": "list",
"page": "tool",
"tasks": [
"Daemon"
"Daemon",
"PlannerScan"
]
}
}

View File

@ -30,6 +30,9 @@ Dungeon:
value: true
option: [ true, ]
option_bold: [ true, ]
DungeonSupport:
Use:
option: [ when_daily, always_use ]
DailyQuest:
Scheduler:
Enable:

View File

@ -137,6 +137,556 @@
"order": 0,
"color": "#777777"
},
"Item_Credit": {
"name": "Item_Credit",
"path": "Dungeon.Planner.Item_Credit",
"i18n": "Planner.Item_Credit.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Trailblaze_EXP": {
"name": "Item_Trailblaze_EXP",
"path": "Dungeon.Planner.Item_Trailblaze_EXP",
"i18n": "Planner.Item_Trailblaze_EXP.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Traveler_Guide": {
"name": "Item_Traveler_Guide",
"path": "Dungeon.Planner.Item_Traveler_Guide",
"i18n": "Planner.Item_Traveler_Guide.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Refined_Aether": {
"name": "Item_Refined_Aether",
"path": "Dungeon.Planner.Item_Refined_Aether",
"i18n": "Planner.Item_Refined_Aether.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Lost_Crystal": {
"name": "Item_Lost_Crystal",
"path": "Dungeon.Planner.Item_Lost_Crystal",
"i18n": "Planner.Item_Lost_Crystal.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Broken_Teeth_of_Iron_Wolf": {
"name": "Item_Broken_Teeth_of_Iron_Wolf",
"path": "Dungeon.Planner.Item_Broken_Teeth_of_Iron_Wolf",
"i18n": "Planner.Item_Broken_Teeth_of_Iron_Wolf.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Endotherm_Chitin": {
"name": "Item_Endotherm_Chitin",
"path": "Dungeon.Planner.Item_Endotherm_Chitin",
"i18n": "Planner.Item_Endotherm_Chitin.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Horn_of_Snow": {
"name": "Item_Horn_of_Snow",
"path": "Dungeon.Planner.Item_Horn_of_Snow",
"i18n": "Planner.Item_Horn_of_Snow.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Lightning_Crown_of_the_Past_Shadow": {
"name": "Item_Lightning_Crown_of_the_Past_Shadow",
"path": "Dungeon.Planner.Item_Lightning_Crown_of_the_Past_Shadow",
"i18n": "Planner.Item_Lightning_Crown_of_the_Past_Shadow.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Storm_Eye": {
"name": "Item_Storm_Eye",
"path": "Dungeon.Planner.Item_Storm_Eye",
"i18n": "Planner.Item_Storm_Eye.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Void_Cast_Iron": {
"name": "Item_Void_Cast_Iron",
"path": "Dungeon.Planner.Item_Void_Cast_Iron",
"i18n": "Planner.Item_Void_Cast_Iron.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Golden_Crown_of_the_Past_Shadow": {
"name": "Item_Golden_Crown_of_the_Past_Shadow",
"path": "Dungeon.Planner.Item_Golden_Crown_of_the_Past_Shadow",
"i18n": "Planner.Item_Golden_Crown_of_the_Past_Shadow.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Netherworld_Token": {
"name": "Item_Netherworld_Token",
"path": "Dungeon.Planner.Item_Netherworld_Token",
"i18n": "Planner.Item_Netherworld_Token.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Searing_Steel_Blade": {
"name": "Item_Searing_Steel_Blade",
"path": "Dungeon.Planner.Item_Searing_Steel_Blade",
"i18n": "Planner.Item_Searing_Steel_Blade.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Gelid_Chitin": {
"name": "Item_Gelid_Chitin",
"path": "Dungeon.Planner.Item_Gelid_Chitin",
"i18n": "Planner.Item_Gelid_Chitin.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Shape_Shifter_Lightning_Staff": {
"name": "Item_Shape_Shifter_Lightning_Staff",
"path": "Dungeon.Planner.Item_Shape_Shifter_Lightning_Staff",
"i18n": "Planner.Item_Shape_Shifter_Lightning_Staff.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Ascendant_Debris": {
"name": "Item_Ascendant_Debris",
"path": "Dungeon.Planner.Item_Ascendant_Debris",
"i18n": "Planner.Item_Ascendant_Debris.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Nail_of_the_Ape": {
"name": "Item_Nail_of_the_Ape",
"path": "Dungeon.Planner.Item_Nail_of_the_Ape",
"i18n": "Planner.Item_Nail_of_the_Ape.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Suppressing_Edict": {
"name": "Item_Suppressing_Edict",
"path": "Dungeon.Planner.Item_Suppressing_Edict",
"i18n": "Planner.Item_Suppressing_Edict.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_IPC_Work_Permit": {
"name": "Item_IPC_Work_Permit",
"path": "Dungeon.Planner.Item_IPC_Work_Permit",
"i18n": "Planner.Item_IPC_Work_Permit.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Raging_Heart": {
"name": "Item_Raging_Heart",
"path": "Dungeon.Planner.Item_Raging_Heart",
"i18n": "Planner.Item_Raging_Heart.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Dream_Fridge": {
"name": "Item_Dream_Fridge",
"path": "Dungeon.Planner.Item_Dream_Fridge",
"i18n": "Planner.Item_Dream_Fridge.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Dream_Flamer": {
"name": "Item_Dream_Flamer",
"path": "Dungeon.Planner.Item_Dream_Flamer",
"i18n": "Planner.Item_Dream_Flamer.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Worldbreaker_Blade": {
"name": "Item_Worldbreaker_Blade",
"path": "Dungeon.Planner.Item_Worldbreaker_Blade",
"i18n": "Planner.Item_Worldbreaker_Blade.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Arrow_of_the_Starchaser": {
"name": "Item_Arrow_of_the_Starchaser",
"path": "Dungeon.Planner.Item_Arrow_of_the_Starchaser",
"i18n": "Planner.Item_Arrow_of_the_Starchaser.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Key_of_Wisdom": {
"name": "Item_Key_of_Wisdom",
"path": "Dungeon.Planner.Item_Key_of_Wisdom",
"i18n": "Planner.Item_Key_of_Wisdom.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Safeguard_of_Amber": {
"name": "Item_Safeguard_of_Amber",
"path": "Dungeon.Planner.Item_Safeguard_of_Amber",
"i18n": "Planner.Item_Safeguard_of_Amber.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Obsidian_of_Obsession": {
"name": "Item_Obsidian_of_Obsession",
"path": "Dungeon.Planner.Item_Obsidian_of_Obsession",
"i18n": "Planner.Item_Obsidian_of_Obsession.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Stellaris_Symphony": {
"name": "Item_Stellaris_Symphony",
"path": "Dungeon.Planner.Item_Stellaris_Symphony",
"i18n": "Planner.Item_Stellaris_Symphony.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Flower_of_Eternity": {
"name": "Item_Flower_of_Eternity",
"path": "Dungeon.Planner.Item_Flower_of_Eternity",
"i18n": "Planner.Item_Flower_of_Eternity.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Moon_Madness_Fang": {
"name": "Item_Moon_Madness_Fang",
"path": "Dungeon.Planner.Item_Moon_Madness_Fang",
"i18n": "Planner.Item_Moon_Madness_Fang.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Countertemporal_Shot": {
"name": "Item_Countertemporal_Shot",
"path": "Dungeon.Planner.Item_Countertemporal_Shot",
"i18n": "Planner.Item_Countertemporal_Shot.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Divine_Amber": {
"name": "Item_Divine_Amber",
"path": "Dungeon.Planner.Item_Divine_Amber",
"i18n": "Planner.Item_Divine_Amber.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Heaven_Incinerator": {
"name": "Item_Heaven_Incinerator",
"path": "Dungeon.Planner.Item_Heaven_Incinerator",
"i18n": "Planner.Item_Heaven_Incinerator.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Heavenly_Melody": {
"name": "Item_Heavenly_Melody",
"path": "Dungeon.Planner.Item_Heavenly_Melody",
"i18n": "Planner.Item_Heavenly_Melody.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Myriad_Fruit": {
"name": "Item_Myriad_Fruit",
"path": "Dungeon.Planner.Item_Myriad_Fruit",
"i18n": "Planner.Item_Myriad_Fruit.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Tracks_of_Destiny": {
"name": "Item_Tracks_of_Destiny",
"path": "Dungeon.Planner.Item_Tracks_of_Destiny",
"i18n": "Planner.Item_Tracks_of_Destiny.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Destroyer_Final_Road": {
"name": "Item_Destroyer_Final_Road",
"path": "Dungeon.Planner.Item_Destroyer_Final_Road",
"i18n": "Planner.Item_Destroyer_Final_Road.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Guardian_Lament": {
"name": "Item_Guardian_Lament",
"path": "Dungeon.Planner.Item_Guardian_Lament",
"i18n": "Planner.Item_Guardian_Lament.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Regret_of_Infinite_Ochema": {
"name": "Item_Regret_of_Infinite_Ochema",
"path": "Dungeon.Planner.Item_Regret_of_Infinite_Ochema",
"i18n": "Planner.Item_Regret_of_Infinite_Ochema.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Past_Evils_of_the_Borehole_Planet_Disaster": {
"name": "Item_Past_Evils_of_the_Borehole_Planet_Disaster",
"path": "Dungeon.Planner.Item_Past_Evils_of_the_Borehole_Planet_Disaster",
"i18n": "Planner.Item_Past_Evils_of_the_Borehole_Planet_Disaster.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Lost_Echo_of_the_Shared_Wish": {
"name": "Item_Lost_Echo_of_the_Shared_Wish",
"path": "Dungeon.Planner.Item_Lost_Echo_of_the_Shared_Wish",
"i18n": "Planner.Item_Lost_Echo_of_the_Shared_Wish.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Squirming_Core": {
"name": "Item_Squirming_Core",
"path": "Dungeon.Planner.Item_Squirming_Core",
"i18n": "Planner.Item_Squirming_Core.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Conqueror_Will": {
"name": "Item_Conqueror_Will",
"path": "Dungeon.Planner.Item_Conqueror_Will",
"i18n": "Planner.Item_Conqueror_Will.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Silvermane_Medal": {
"name": "Item_Silvermane_Medal",
"path": "Dungeon.Planner.Item_Silvermane_Medal",
"i18n": "Planner.Item_Silvermane_Medal.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Ancient_Engine": {
"name": "Item_Ancient_Engine",
"path": "Dungeon.Planner.Item_Ancient_Engine",
"i18n": "Planner.Item_Ancient_Engine.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Immortal_Lumintwig": {
"name": "Item_Immortal_Lumintwig",
"path": "Dungeon.Planner.Item_Immortal_Lumintwig",
"i18n": "Planner.Item_Immortal_Lumintwig.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Artifex_Gyreheart": {
"name": "Item_Artifex_Gyreheart",
"path": "Dungeon.Planner.Item_Artifex_Gyreheart",
"i18n": "Planner.Item_Artifex_Gyreheart.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Dream_Making_Engine": {
"name": "Item_Dream_Making_Engine",
"path": "Dungeon.Planner.Item_Dream_Making_Engine",
"i18n": "Planner.Item_Dream_Making_Engine.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Item_Shards_of_Desires": {
"name": "Item_Shards_of_Desires",
"path": "Dungeon.Planner.Item_Shards_of_Desires",
"i18n": "Planner.Item_Shards_of_Desires.name",
"stored": "StoredPlanner",
"attrs": {
"time": "2020-01-01 00:00:00"
},
"order": 0,
"color": "#777777"
},
"Immersifier": {
"name": "Immersifier",
"path": "Dungeon.DungeonStorage.Immersifier",

View File

@ -25,8 +25,8 @@ Daily:
tasks:
Dungeon:
- Scheduler
- Planner
- Dungeon
- DungeonDaily
- DungeonSupport
- DungeonStorage
DailyQuest:
@ -70,3 +70,5 @@ Tool:
tasks:
Daemon:
- Daemon
PlannerScan:
- PlannerScan

View File

@ -51,12 +51,6 @@ class GeneratedConfig:
Dungeon_NameAtDoubleRelic = 'Cavern_of_Corrosion_Path_of_Providence' # Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers, Cavern_of_Corrosion_Path_of_Darkness, Cavern_of_Corrosion_Path_of_Dreamdive
Dungeon_Team = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9
# Group `DungeonDaily`
DungeonDaily_CalyxGolden = 'Calyx_Golden_Treasures_Jarilo_VI' # do_not_achieve, Calyx_Golden_Memories_Jarilo_VI, Calyx_Golden_Memories_The_Xianzhou_Luofu, Calyx_Golden_Memories_Penacony, Calyx_Golden_Aether_Jarilo_VI, Calyx_Golden_Aether_The_Xianzhou_Luofu, Calyx_Golden_Aether_Penacony, Calyx_Golden_Treasures_Jarilo_VI, Calyx_Golden_Treasures_The_Xianzhou_Luofu, Calyx_Golden_Treasures_Penacony
DungeonDaily_CalyxCrimson = 'Calyx_Crimson_Destruction_Herta_StorageZone' # do_not_achieve, Calyx_Crimson_Destruction_Herta_StorageZone, Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape, Calyx_Crimson_Preservation_Herta_SupplyZone, Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark, Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains, Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue, Calyx_Crimson_Abundance_Jarilo_BackwaterPass, Calyx_Crimson_Abundance_Luofu_FyxestrollGarden, Calyx_Crimson_Erudition_Jarilo_RivetTown, Calyx_Crimson_Harmony_Jarilo_RobotSettlement, Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape, Calyx_Crimson_Nihility_Jarilo_GreatMine, Calyx_Crimson_Nihility_Luofu_AlchemyCommission
DungeonDaily_StagnantShadow = 'Stagnant_Shadow_Quanta' # do_not_achieve, Stagnant_Shadow_Spike, Stagnant_Shadow_Perdition, Stagnant_Shadow_Duty, Stagnant_Shadow_Blaze, Stagnant_Shadow_Scorch, Stagnant_Shadow_Ire, Stagnant_Shadow_Rime, Stagnant_Shadow_Icicle, Stagnant_Shadow_Nectar, Stagnant_Shadow_Fulmination, Stagnant_Shadow_Doom, Stagnant_Shadow_Gust, Stagnant_Shadow_Celestial, Stagnant_Shadow_Quanta, Stagnant_Shadow_Abomination, Stagnant_Shadow_Roast, Stagnant_Shadow_Mirage, Stagnant_Shadow_Puppetry
DungeonDaily_CavernOfCorrosion = 'Cavern_of_Corrosion_Path_of_Providence' # do_not_achieve, Cavern_of_Corrosion_Path_of_Gelid_Wind, Cavern_of_Corrosion_Path_of_Jabbing_Punch, Cavern_of_Corrosion_Path_of_Drifting, Cavern_of_Corrosion_Path_of_Providence, Cavern_of_Corrosion_Path_of_Holy_Hymn, Cavern_of_Corrosion_Path_of_Conflagration, Cavern_of_Corrosion_Path_of_Elixir_Seekers, Cavern_of_Corrosion_Path_of_Darkness, Cavern_of_Corrosion_Path_of_Dreamdive
# Group `DungeonSupport`
DungeonSupport_Use = 'when_daily' # always_use, when_daily, do_not_use
DungeonSupport_Character = 'FirstCharacter' # FirstCharacter, Acheron, Argenti, Arlan, Asta, Aventurine, Bailu, BlackSwan, Blade, Bronya, Clara, DanHeng, DanHengImbibitorLunae, DrRatio, FuXuan, Gallagher, Gepard, Guinaifen, Hanya, Herta, Himeko, Hook, Huohuo, JingYuan, Jingliu, Kafka, Luka, Luocha, Lynx, March7th, Misha, Natasha, Pela, Qingque, Robin, RuanMei, Sampo, Seele, Serval, SilverWolf, Sparkle, Sushang, Tingyun, TopazNumby, TrailblazerDestruction, TrailblazerPreservation, Welt, Xueyi, Yanqing, Yukong
@ -71,6 +65,58 @@ class GeneratedConfig:
# Group `SupportReward`
SupportReward_Collect = True
# Group `Planner`
Planner_Item_Credit = {}
Planner_Item_Trailblaze_EXP = {}
Planner_Item_Traveler_Guide = {}
Planner_Item_Refined_Aether = {}
Planner_Item_Lost_Crystal = {}
Planner_Item_Broken_Teeth_of_Iron_Wolf = {}
Planner_Item_Endotherm_Chitin = {}
Planner_Item_Horn_of_Snow = {}
Planner_Item_Lightning_Crown_of_the_Past_Shadow = {}
Planner_Item_Storm_Eye = {}
Planner_Item_Void_Cast_Iron = {}
Planner_Item_Golden_Crown_of_the_Past_Shadow = {}
Planner_Item_Netherworld_Token = {}
Planner_Item_Searing_Steel_Blade = {}
Planner_Item_Gelid_Chitin = {}
Planner_Item_Shape_Shifter_Lightning_Staff = {}
Planner_Item_Ascendant_Debris = {}
Planner_Item_Nail_of_the_Ape = {}
Planner_Item_Suppressing_Edict = {}
Planner_Item_IPC_Work_Permit = {}
Planner_Item_Raging_Heart = {}
Planner_Item_Dream_Fridge = {}
Planner_Item_Dream_Flamer = {}
Planner_Item_Worldbreaker_Blade = {}
Planner_Item_Arrow_of_the_Starchaser = {}
Planner_Item_Key_of_Wisdom = {}
Planner_Item_Safeguard_of_Amber = {}
Planner_Item_Obsidian_of_Obsession = {}
Planner_Item_Stellaris_Symphony = {}
Planner_Item_Flower_of_Eternity = {}
Planner_Item_Moon_Madness_Fang = {}
Planner_Item_Countertemporal_Shot = {}
Planner_Item_Divine_Amber = {}
Planner_Item_Heaven_Incinerator = {}
Planner_Item_Heavenly_Melody = {}
Planner_Item_Myriad_Fruit = {}
Planner_Item_Tracks_of_Destiny = {}
Planner_Item_Destroyer_Final_Road = {}
Planner_Item_Guardian_Lament = {}
Planner_Item_Regret_of_Infinite_Ochema = {}
Planner_Item_Past_Evils_of_the_Borehole_Planet_Disaster = {}
Planner_Item_Lost_Echo_of_the_Shared_Wish = {}
Planner_Item_Squirming_Core = {}
Planner_Item_Conqueror_Will = {}
Planner_Item_Silvermane_Medal = {}
Planner_Item_Ancient_Engine = {}
Planner_Item_Immortal_Lumintwig = {}
Planner_Item_Artifex_Gyreheart = {}
Planner_Item_Dream_Making_Engine = {}
Planner_Item_Shards_of_Desires = {}
# Group `Weekly`
Weekly_Name = 'Echo_of_War_Divine_Seed' # Echo_of_War_Destruction_Beginning, Echo_of_War_End_of_the_Eternal_Freeze, Echo_of_War_Divine_Seed, Echo_of_War_Borehole_Planet_Old_Crater, Echo_of_War_Salutations_of_Ashen_Dreams
Weekly_Team = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9
@ -157,3 +203,6 @@ class GeneratedConfig:
# Group `Daemon`
Daemon_Enable = True # True
Daemon_AimClicker = 'do_not_click' # item_enemy, item, enemy, do_not_click
# Group `PlannerScan`
PlannerScan_ResultAdd = False

View File

@ -66,8 +66,9 @@ class ConfigGenerator:
# Insert dungeons
from tasks.dungeon.keywords import DungeonList
calyx_golden = [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden_Memories] \
+ [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden_Aether] \
+ [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden_Treasures]
+ [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Calyx_Golden_Aether] \
+ [dungeon.name for dungeon in DungeonList.instances.values() if
dungeon.is_Calyx_Golden_Treasures]
# calyx_crimson
from tasks.rogue.keywords import KEYWORDS_ROGUE_PATH as Path
order = [Path.Destruction, Path.Preservation, Path.The_Hunt, Path.Abundance,
@ -82,7 +83,8 @@ class ConfigGenerator:
for type_ in CombatType.instances.values():
stagnant_shadow += [dungeon.name for dungeon in DungeonList.instances.values()
if dungeon.Stagnant_Shadow_Combat_Type == type_]
cavern_of_corrosion = [dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Cavern_of_Corrosion]
cavern_of_corrosion = [dungeon.name for dungeon in DungeonList.instances.values() if
dungeon.is_Cavern_of_Corrosion]
option_add(
keys='Dungeon.Name.option',
options=calyx_golden + calyx_crimson + stagnant_shadow + cavern_of_corrosion
@ -90,11 +92,6 @@ class ConfigGenerator:
# Double events
option_add(keys='Dungeon.NameAtDoubleCalyx.option', options=calyx_golden + calyx_crimson)
option_add(keys='Dungeon.NameAtDoubleRelic.option', options=cavern_of_corrosion)
# Dungeon daily
option_add(keys='DungeonDaily.CalyxGolden.option', options=calyx_golden)
option_add(keys='DungeonDaily.CalyxCrimson.option', options=calyx_crimson)
option_add(keys='DungeonDaily.StagnantShadow.option', options=stagnant_shadow)
option_add(keys='DungeonDaily.CavernOfCorrosion.option', options=cavern_of_corrosion)
option_add(
keys='Weekly.Name.option',
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Echo_of_War])
@ -120,6 +117,15 @@ class ConfigGenerator:
assignments = [entry.name for entry in AssignmentEntry.instances.values()]
for i in range(4):
option_add(keys=f'Assignment.Name_{i + 1}.option', options=assignments)
# Insert planner items
from tasks.planner.keywords.classes import ItemBase
for item in ItemBase.instances.values():
base = item.group_base
deep_set(raw, keys=['Planner', f'Item_{base.name}'], value={
'stored': 'StoredPlanner',
'display': 'display',
'type': 'planner',
})
# Load
for path, value in deep_iter(raw, depth=2):
@ -131,7 +137,7 @@ class ConfigGenerator:
if not isinstance(value, dict):
value = {'value': value}
arg['type'] = data_to_type(value, arg=path[1])
if arg['type'] == 'stored':
if arg['type'] in ['stored', 'planner']:
value['value'] = {}
arg['display'] = 'hide' # Hide `stored` by default
if isinstance(value['value'], datetime):
@ -381,7 +387,7 @@ class ConfigGenerator:
i18n_aether = {
'cn': '材料:武器经验({dungeon}',
'cht': '材料:武器經驗({dungeon}',
'jp': '素材:武器経験({dungeon}',
'jp': '素材:武器経験({dungeon}',
'en': 'Material: Light Cone EXP ({dungeon})',
'es': 'Material: EXP de conos de luz ({dungeon})',
}
@ -449,10 +455,6 @@ class ConfigGenerator:
update_dungeon_names('Dungeon.NameAtDoubleCalyx')
update_dungeon_names('Dungeon.NameAtDoubleRelic')
update_dungeon_names('DungeonDaily.CalyxGolden')
update_dungeon_names('DungeonDaily.CalyxCrimson')
update_dungeon_names('DungeonDaily.StagnantShadow')
update_dungeon_names('DungeonDaily.CavernOfCorrosion')
# Character names
from tasks.character.keywords import CharacterList
@ -497,6 +499,37 @@ class ConfigGenerator:
name = deep_get(new, keys=['RogueWorld', 'World', dungeon.name], default=None)
if name:
deep_set(new, keys=['RogueWorld', 'World', dungeon.name], value=dungeon.__getattribute__(ingame_lang))
# Planner items
from tasks.planner.keywords.classes import ItemBase
for item in ItemBase.instances.values():
item: ItemBase = item
name = f'Item_{item.name}'
if item.is_ItemCurrency or item.name == 'Tracks_of_Destiny':
i18n = item.__getattribute__(ingame_lang)
elif item.is_ItemExp and item.is_group_base:
dungeon = item.dungeon
if dungeon is None:
i18n = item.__getattribute__(ingame_lang)
elif dungeon.is_Calyx_Golden_Memories:
i18n = i18n_memories[ingame_lang]
elif dungeon.is_Calyx_Golden_Aether:
i18n = i18n_aether[ingame_lang]
else:
continue
if res := re.search(r'[:](.*)[(]', i18n):
i18n = res.group(1).strip()
elif item.is_ItemAscension or (item.is_ItemTrace and item.is_group_base):
dungeon = item.group_base.dungeon.name
i18n = deep_get(new, keys=['Dungeon', 'Name', dungeon], default='Unknown_Dungeon_Come_From')
elif item.is_ItemWeekly:
dungeon = item.dungeon.name
i18n = deep_get(new, keys=['Weekly', 'Name', dungeon], default='Unknown_Dungeon_Come_From')
elif item.is_ItemCalyx and item.is_group_base:
i18n = item.__getattribute__(ingame_lang)
else:
continue
deep_set(new, keys=['Planner', name, 'name'], value=i18n)
deep_set(new, keys=['Planner', name, 'help'], value='')
# GUI i18n
for path, _ in deep_iter(self.gui, depth=2):
@ -555,7 +588,7 @@ class ConfigGenerator:
import module.config.stored.classes as classes
data = {}
for path, value in deep_iter(self.args, depth=3):
if value.get('type') != 'stored':
if value.get('type') not in ['stored', 'planner']:
continue
name = path[-1]
stored = value.get('stored')
@ -752,14 +785,10 @@ class ConfigUpdater:
set_daily('Complete_1_Daily_Mission', 'not_supported')
# Dungeon
dungeon = deep_get(data, keys='Dungeon.Scheduler.Enable')
set_daily('Clear_Calyx_Golden_1_times',
dungeon and deep_get(data, 'Dungeon.DungeonDaily.CalyxGolden') != 'do_not_achieve')
set_daily('Clear_Calyx_Crimson_1_times',
dungeon and deep_get(data, 'Dungeon.DungeonDaily.CalyxCrimson') != 'do_not_achieve')
set_daily('Clear_Stagnant_Shadow_1_times',
dungeon and deep_get(data, 'Dungeon.DungeonDaily.StagnantShadow') != 'do_not_achieve')
set_daily('Clear_Cavern_of_Corrosion_1_times',
dungeon and deep_get(data, 'Dungeon.DungeonDaily.CavernOfCorrosion') != 'do_not_achieve')
set_daily('Clear_Calyx_Golden_1_times', 'not_set')
set_daily('Clear_Calyx_Crimson_1_times', 'not_set')
set_daily('Clear_Stagnant_Shadow_1_times', 'not_set')
set_daily('Clear_Cavern_of_Corrosion_1_times', 'not_set')
# Combat requirements
set_daily('In_a_single_battle_inflict_3_Weakness_Break_of_different_Types', 'achievable')
set_daily('Inflict_Weakness_Break_5_times', 'achievable')
@ -822,22 +851,10 @@ class ConfigUpdater:
if key.endswith('Name'):
if dungeon.is_Calyx_Golden:
yield 'Dungeon.Dungeon.NameAtDoubleCalyx', value
yield 'Dungeon.DungeonDaily.CalyxGolden', value
elif dungeon.is_Calyx_Crimson:
yield 'Dungeon.Dungeon.NameAtDoubleCalyx', value
yield 'Dungeon.DungeonDaily.CalyxCrimson', value
elif dungeon.is_Stagnant_Shadow:
yield 'Dungeon.DungeonDaily.StagnantShadow', value
elif dungeon.is_Cavern_of_Corrosion:
yield 'Dungeon.Dungeon.NameAtDoubleRelic', value
yield 'Dungeon.DungeonDaily.CavernOfCorrosion', value
elif key.endswith('NameAtDoubleCalyx'):
if dungeon.is_Calyx_Golden:
yield 'Dungeon.DungeonDaily.CalyxGolden', value
elif dungeon.is_Calyx_Crimson:
yield 'Dungeon.DungeonDaily.CalyxCrimson', value
elif key.endswith('NameAtDoubleRelic'):
yield 'Dungeon.DungeonDaily.CavernOfCorrosion', value
elif key.endswith('CavernOfCorrosion'):
yield 'Dungeon.Dungeon.NameAtDoubleRelic', value
elif key == 'Rogue.RogueWorld.UseImmersifier' and value is False:

View File

@ -61,6 +61,10 @@
"Daemon": {
"name": "Dialogue Clicker",
"help": ""
},
"PlannerScan": {
"name": "Character Planner",
"help": ""
}
},
"Scheduler": {
@ -351,81 +355,6 @@
"9": "9"
}
},
"DungeonDaily": {
"_info": {
"name": "Daily Quest Settings",
"help": "Clear required dungeon once to achieve daily quests"
},
"CalyxGolden": {
"name": "Clear Calyx Golden 1 times",
"help": "",
"do_not_achieve": "Don't Do This Quest",
"Calyx_Golden_Memories_Jarilo_VI": "Material: Character EXP (Bud of Memories (Jarilo-Ⅵ))",
"Calyx_Golden_Memories_The_Xianzhou_Luofu": "Material: Character EXP (Bud of Memories (The Xianzhou Luofu))",
"Calyx_Golden_Memories_Penacony": "Material: Character EXP (Bud of Memories (Penacony))",
"Calyx_Golden_Aether_Jarilo_VI": "Material: Light Cone EXP (Bud of Aether (Jarilo-Ⅵ))",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "Material: Light Cone EXP (Bud of Aether (The Xianzhou Luofu))",
"Calyx_Golden_Aether_Penacony": "Material: Light Cone EXP (Bud of Aether (Penacony))",
"Calyx_Golden_Treasures_Jarilo_VI": "Material: Credit (Bud of Treasures (Jarilo-Ⅵ))",
"Calyx_Golden_Treasures_The_Xianzhou_Luofu": "Material: Credit (Bud of Treasures (The Xianzhou Luofu))",
"Calyx_Golden_Treasures_Penacony": "Material: Credit (Bud of Treasures (Penacony))"
},
"CalyxCrimson": {
"name": "Clear Calyx Crimson 1 times",
"help": "",
"do_not_achieve": "Don't Do This Quest",
"Calyx_Crimson_Destruction_Herta_StorageZone": "Trace: Destruction (Storage Zone)",
"Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "Trace: Destruction (Scalegorge Waterscape)",
"Calyx_Crimson_Preservation_Herta_SupplyZone": "Trace: Preservation (Supply Zone)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "Trace: Preservation (Clock Studios Theme Park)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "Trace: The Hunt (Outlying Snow Plains)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "Trace: The Hunt (SoulGlad Scorchsand Audition Venue)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "Trace: Abundance (Backwater Pass)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Trace: Abundance (Fyxestroll Garden)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "Trace: Erudition (Rivet Town)",
"Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "Trace: The Harmony (Robot Settlement)",
"Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "Trace: The Harmony (The Reverie (Dreamscape))",
"Calyx_Crimson_Nihility_Jarilo_GreatMine": "Trace: Nihility (Great Mine)",
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "Trace: Nihility (Alchemy Commission)"
},
"StagnantShadow": {
"name": "Clear Stagnant Shadow 1 times",
"help": "",
"do_not_achieve": "Don't Do This Quest",
"Stagnant_Shadow_Spike": "Ascension: Physical (Natasha / Clara / Luka / Sushang)",
"Stagnant_Shadow_Perdition": "Ascension: Physical (Hanya / Argenti)",
"Stagnant_Shadow_Duty": "Ascension: Physical (Boothill / Robin)",
"Stagnant_Shadow_Blaze": "Ascension: Fire (Himeko / Asta / Hook)",
"Stagnant_Shadow_Scorch": "Ascension: Fire (Guinaifen / Topaz & Numby)",
"Stagnant_Shadow_Ire": "Ascension: Fire (Gallagher)",
"Stagnant_Shadow_Rime": "Ascension: Ice (March 7th / Herta / Gepard / Pela)",
"Stagnant_Shadow_Icicle": "Ascension: Ice (Yanqing / Jingliu / Ruan Mei)",
"Stagnant_Shadow_Nectar": "Ascension: Ice (Misha)",
"Stagnant_Shadow_Fulmination": "Ascension: Lightning (Arlan / Serval / Tingyun / Bailu)",
"Stagnant_Shadow_Doom": "Ascension: Lightning (Kafka / Jing Yuan / Acheron)",
"Stagnant_Shadow_Gust": "Ascension: Wind (Dan Heng / Bronya / Sampo)",
"Stagnant_Shadow_Celestial": "Ascension: Wind (Blade / Huohuo / Black Swan)",
"Stagnant_Shadow_Quanta": "Ascension: Quantum (Silver Wolf / Seele / Qingque)",
"Stagnant_Shadow_Abomination": "Ascension: Quantum (Lynx / Fu Xuan / Xueyi)",
"Stagnant_Shadow_Roast": "Ascension: Quantum (Sparkle)",
"Stagnant_Shadow_Mirage": "Ascension: Imaginary (Welt / Luocha / Yukong)",
"Stagnant_Shadow_Puppetry": "Ascension: Imaginary (Dan Heng • Imbibitor Lunae / Aventurine / Dr. Ratio)"
},
"CavernOfCorrosion": {
"name": "Clear Cavern of Corrosion 1 times",
"help": "",
"do_not_achieve": "Don't Do This Quest",
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "Relics: Ice Set & Wind Set (Path of Gelid Wind)",
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "Relics: Physical Set & Break Effect Set (Path of Jabbing Punch)",
"Cavern_of_Corrosion_Path_of_Drifting": "Relics: Healing Set & Musketeer Set (Path of Drifting)",
"Cavern_of_Corrosion_Path_of_Providence": "Relics: Guard Set & Quantum Set (Path of Providence)",
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "Relics: DEF Set & Lighting Set (Path of Holy Hymn)",
"Cavern_of_Corrosion_Path_of_Conflagration": "Relics: Fire Set & Imaginary Set (Path of Conflagration)",
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "Relics: HP Set & SPD Set (Path of Elixir Seekers)",
"Cavern_of_Corrosion_Path_of_Darkness": "Relics: Pursuit Set & Dot Set (Path of Darkness)",
"Cavern_of_Corrosion_Path_of_Dreamdive": "Relics: Debuff Set & Break Effect Set (Path of Dreamdive)"
}
},
"DungeonSupport": {
"_info": {
"name": "Support Settings",
@ -529,6 +458,212 @@
"help": ""
}
},
"Planner": {
"_info": {
"name": "Character Planner Progress",
"help": "Character planner is prioritized. After completed, \"Dungeon Settings\" will be executed."
},
"Item_Credit": {
"name": "Credit",
"help": ""
},
"Item_Trailblaze_EXP": {
"name": "Trailblaze EXP",
"help": ""
},
"Item_Traveler_Guide": {
"name": "Character EXP",
"help": ""
},
"Item_Refined_Aether": {
"name": "Light Cone EXP",
"help": ""
},
"Item_Lost_Crystal": {
"name": "Lost Crystal",
"help": ""
},
"Item_Broken_Teeth_of_Iron_Wolf": {
"name": "Ascension: Physical (Natasha / Clara / Luka / Sushang)",
"help": ""
},
"Item_Endotherm_Chitin": {
"name": "Ascension: Fire (Himeko / Asta / Hook)",
"help": ""
},
"Item_Horn_of_Snow": {
"name": "Ascension: Ice (March 7th / Herta / Gepard / Pela)",
"help": ""
},
"Item_Lightning_Crown_of_the_Past_Shadow": {
"name": "Ascension: Lightning (Arlan / Serval / Tingyun / Bailu)",
"help": ""
},
"Item_Storm_Eye": {
"name": "Ascension: Wind (Dan Heng / Bronya / Sampo)",
"help": ""
},
"Item_Void_Cast_Iron": {
"name": "Ascension: Quantum (Silver Wolf / Seele / Qingque)",
"help": ""
},
"Item_Golden_Crown_of_the_Past_Shadow": {
"name": "Ascension: Imaginary (Welt / Luocha / Yukong)",
"help": ""
},
"Item_Netherworld_Token": {
"name": "Ascension: Physical (Hanya / Argenti)",
"help": ""
},
"Item_Searing_Steel_Blade": {
"name": "Ascension: Fire (Guinaifen / Topaz & Numby)",
"help": ""
},
"Item_Gelid_Chitin": {
"name": "Ascension: Ice (Yanqing / Jingliu / Ruan Mei)",
"help": ""
},
"Item_Shape_Shifter_Lightning_Staff": {
"name": "Ascension: Lightning (Kafka / Jing Yuan / Acheron)",
"help": ""
},
"Item_Ascendant_Debris": {
"name": "Ascension: Wind (Blade / Huohuo / Black Swan)",
"help": ""
},
"Item_Nail_of_the_Ape": {
"name": "Ascension: Quantum (Lynx / Fu Xuan / Xueyi)",
"help": ""
},
"Item_Suppressing_Edict": {
"name": "Ascension: Imaginary (Dan Heng • Imbibitor Lunae / Aventurine / Dr. Ratio)",
"help": ""
},
"Item_IPC_Work_Permit": {
"name": "Ascension: Physical (Boothill / Robin)",
"help": ""
},
"Item_Raging_Heart": {
"name": "Ascension: Fire (Gallagher)",
"help": ""
},
"Item_Dream_Fridge": {
"name": "Ascension: Ice (Misha)",
"help": ""
},
"Item_Dream_Flamer": {
"name": "Ascension: Quantum (Sparkle)",
"help": ""
},
"Item_Worldbreaker_Blade": {
"name": "Trace: Destruction (Storage Zone)",
"help": ""
},
"Item_Arrow_of_the_Starchaser": {
"name": "Trace: The Hunt (Outlying Snow Plains)",
"help": ""
},
"Item_Key_of_Wisdom": {
"name": "Trace: Erudition (Rivet Town)",
"help": ""
},
"Item_Safeguard_of_Amber": {
"name": "Trace: Preservation (Supply Zone)",
"help": ""
},
"Item_Obsidian_of_Obsession": {
"name": "Trace: Nihility (Great Mine)",
"help": ""
},
"Item_Stellaris_Symphony": {
"name": "Trace: The Harmony (Robot Settlement)",
"help": ""
},
"Item_Flower_of_Eternity": {
"name": "Trace: Abundance (Backwater Pass)",
"help": ""
},
"Item_Moon_Madness_Fang": {
"name": "Trace: Destruction (Scalegorge Waterscape)",
"help": ""
},
"Item_Countertemporal_Shot": {
"name": "Trace: The Hunt (SoulGlad Scorchsand Audition Venue)",
"help": ""
},
"Item_Divine_Amber": {
"name": "Trace: Preservation (Clock Studios Theme Park)",
"help": ""
},
"Item_Heaven_Incinerator": {
"name": "Trace: Nihility (Alchemy Commission)",
"help": ""
},
"Item_Heavenly_Melody": {
"name": "Trace: The Harmony (The Reverie (Dreamscape))",
"help": ""
},
"Item_Myriad_Fruit": {
"name": "Trace: Abundance (Fyxestroll Garden)",
"help": ""
},
"Item_Tracks_of_Destiny": {
"name": "Tracks of Destiny",
"help": ""
},
"Item_Destroyer_Final_Road": {
"name": "Destruction's Beginning (Herta Space Station)",
"help": ""
},
"Item_Guardian_Lament": {
"name": "End of the Eternal Freeze (Jarilo-VI)",
"help": ""
},
"Item_Regret_of_Infinite_Ochema": {
"name": "Divine Seed (The Xianzhou Luofu)",
"help": ""
},
"Item_Past_Evils_of_the_Borehole_Planet_Disaster": {
"name": "Borehole Planet's Old Crater (Herta Space Station)",
"help": ""
},
"Item_Lost_Echo_of_the_Shared_Wish": {
"name": "Salutations of Ashen Dreams (Penacony)",
"help": ""
},
"Item_Squirming_Core": {
"name": "Squirming Core",
"help": ""
},
"Item_Conqueror_Will": {
"name": "Conqueror's Will",
"help": ""
},
"Item_Silvermane_Medal": {
"name": "Silvermane Medal",
"help": ""
},
"Item_Ancient_Engine": {
"name": "Ancient Engine",
"help": ""
},
"Item_Immortal_Lumintwig": {
"name": "Immortal Lumintwig",
"help": ""
},
"Item_Artifex_Gyreheart": {
"name": "Artifex's Gyreheart",
"help": ""
},
"Item_Dream_Making_Engine": {
"name": "Dream Making Engine",
"help": ""
},
"Item_Shards_of_Desires": {
"name": "Shards of Desires",
"help": ""
}
},
"Weekly": {
"_info": {
"name": "Echo of War Settings",
@ -1063,6 +1198,16 @@
"do_not_click": "Don't click"
}
},
"PlannerScan": {
"_info": {
"name": "Scan Character Planner Results",
"help": "Tools need to stop the scheduler and then run independently\nBefore use, set planner goal in the in-game planner, calculate the results, and start scanning from the result page. Detailed usage see: https://github.com/LmeSzinc/StarRailCopilot/wiki/Planner_cn"
},
"ResultAdd": {
"name": "Accumulate multiple scan results",
"help": "Turn on when planning multiple characters, will raise multiple characters all together\nTurn off when planning one character, planning goal will be refreshed at every scan"
}
},
"Gui": {
"Aside": {
"Install": "Install",

View File

@ -61,6 +61,10 @@
"Daemon": {
"name": "Clic de diálogo",
"help": ""
},
"PlannerScan": {
"name": "Planificador de personajes",
"help": ""
}
},
"Scheduler": {
@ -351,81 +355,6 @@
"9": "9"
}
},
"DungeonDaily": {
"_info": {
"name": "Ajustes de Entrenamiento diario",
"help": "Se limpiará la mazmorra requerida una vez para completar la misión diaria."
},
"CalyxGolden": {
"name": "Completar Cáliz (oro) 1 vez",
"help": "",
"do_not_achieve": "No hacer esta misión",
"Calyx_Golden_Memories_Jarilo_VI": "Material: EXP de personaje (Flor de los recuerdos (Jarilo-Ⅵ))",
"Calyx_Golden_Memories_The_Xianzhou_Luofu": "Material: EXP de personaje (Flor de los recuerdos (El Luofu de Xianzhou))",
"Calyx_Golden_Memories_Penacony": "Material: EXP de personaje (Flor de los recuerdos (Colonipenal))",
"Calyx_Golden_Aether_Jarilo_VI": "Material: EXP de conos de luz (Flor de éter (Jarilo-Ⅵ))",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "Material: EXP de conos de luz (Flor de éter (El Luofu de Xianzhou))",
"Calyx_Golden_Aether_Penacony": "Material: EXP de conos de luz (Flor de éter (Colonipenal))",
"Calyx_Golden_Treasures_Jarilo_VI": "Material: Créditos (Flor de tesoros (Jarilo-Ⅵ))",
"Calyx_Golden_Treasures_The_Xianzhou_Luofu": "Material: Créditos (Flor de tesoros (El Luofu de Xianzhou))",
"Calyx_Golden_Treasures_Penacony": "Material: Créditos (Flor de tesoros (Colonipenal))"
},
"CalyxCrimson": {
"name": "Completar Cáliz (carmesí) 1 vez",
"help": "",
"do_not_achieve": "No hacer esta misión",
"Calyx_Crimson_Destruction_Herta_StorageZone": "Rastros: Destrucción (Zona de almacenamiento)",
"Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "Rastros: Destrucción (Desfiladero de Escamas)",
"Calyx_Crimson_Preservation_Herta_SupplyZone": "Rastros: Conservación (Zona de suministros)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "Rastros: Conservación (Parque temático de los Estudios Reloj)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "Rastros: Cacería (Llanuras nevadas de las afueras)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "Rastros: Cacería (Recinto de las Audiciones FelizAlma en la Arena Ardiente)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "Rastros: Abundancia (Paso del Remanso)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Rastros: Abundancia (Jardín del Sosiego)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "Rastros: Erudición (Villarremache)",
"Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "Rastros: Armonía (Asentamiento robot)",
"Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "Rastros: Armonía (Hotel Fantasía (paisaje onírico))",
"Calyx_Crimson_Nihility_Jarilo_GreatMine": "Rastros: Nihilidad (Mina principal)",
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "Rastros: Nihilidad (Comisión de Alquimia)"
},
"StagnantShadow": {
"name": "Completar Sombra paralizada 1 vez",
"help": "",
"do_not_achieve": "No hacer esta misión",
"Stagnant_Shadow_Spike": "Ascension: Físico (Natasha / Clara / Luka / Sushang)",
"Stagnant_Shadow_Perdition": "Ascension: Físico (Hanya / Argenti)",
"Stagnant_Shadow_Duty": "Ascension: Físico (Boothill / Robin)",
"Stagnant_Shadow_Blaze": "Ascension: Fuego (Himeko / Asta / Hook)",
"Stagnant_Shadow_Scorch": "Ascension: Fuego (Guinaifen / Topaz y Conti)",
"Stagnant_Shadow_Ire": "Ascension: Fuego (Gallagher)",
"Stagnant_Shadow_Rime": "Ascension: Hielo (Siete de Marzo / Herta / Gepard / Pela)",
"Stagnant_Shadow_Icicle": "Ascension: Hielo (Yanqing / Jingliu / Ruan Mei)",
"Stagnant_Shadow_Nectar": "Ascension: Hielo (Misha)",
"Stagnant_Shadow_Fulmination": "Ascension: Rayo (Arlan / Serval / Tingyun / Bailu)",
"Stagnant_Shadow_Doom": "Ascension: Rayo (Kafka / Jing Yuan / Acheron)",
"Stagnant_Shadow_Gust": "Ascension: Viento (Dan Heng / Bronya / Sampo)",
"Stagnant_Shadow_Celestial": "Ascension: Viento (Blade / Huohuo / Cisne Negro)",
"Stagnant_Shadow_Quanta": "Ascension: Cuántico (Silver Wolf / Seele / Qingque)",
"Stagnant_Shadow_Abomination": "Ascension: Cuántico (Lynx / Fu Xuan / Xueyi)",
"Stagnant_Shadow_Roast": "Ascension: Cuántico (Sparkle)",
"Stagnant_Shadow_Mirage": "Ascension: Imaginario (Welt / Luocha / Yukong)",
"Stagnant_Shadow_Puppetry": "Ascension: Imaginario (Dan Heng - Imbibitor Lunae / Aventurino / Dr. Ratio)"
},
"CavernOfCorrosion": {
"name": "Completar Caverna de la corrosión 1 vez",
"help": "",
"do_not_achieve": "No hacer esta misión",
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "Artefactos: Hielo y Viento (Senda del viento gélido)",
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "Artefactos: Físico y Efecto de Ruptura (Senda de los puños rápidos)",
"Cavern_of_Corrosion_Path_of_Drifting": "Artefactos: Curación y Pistolera de la espiga silvestre (Senda de la deriva)",
"Cavern_of_Corrosion_Path_of_Providence": "Artefactos: Guardia de la nieve y Cuántico (Senda de al providencia) (Senda de la providencia)",
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "Artefactos: Defensa y Trueno (Senda del himno sagrado)",
"Cavern_of_Corrosion_Path_of_Conflagration": "Artefactos: Fuego e Imaginario (Senda de la conflagración)",
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "Artefactos: HP y SPD (Senda de los elixires)",
"Cavern_of_Corrosion_Path_of_Darkness": "Artefactos: Persecución y Dot (Senda de la oscuridad)",
"Cavern_of_Corrosion_Path_of_Dreamdive": "Artefactos: Debuff y Efecto de Ruptura (Senda de los sueños)"
}
},
"DungeonSupport": {
"_info": {
"name": "Ajustes de Apoyo",
@ -529,6 +458,212 @@
"help": ""
}
},
"Planner": {
"_info": {
"name": "Progreso del planificador de personajes",
"help": "Se prioriza el planificador de personajes. Una vez completado, se ejecutará la \"Ajustes de Mazmorra\"."
},
"Item_Credit": {
"name": "Crédito",
"help": ""
},
"Item_Trailblaze_EXP": {
"name": "EXP trazacaminos",
"help": ""
},
"Item_Traveler_Guide": {
"name": "EXP de personaje",
"help": ""
},
"Item_Refined_Aether": {
"name": "EXP de conos de luz",
"help": ""
},
"Item_Lost_Crystal": {
"name": "Cristal perdido",
"help": ""
},
"Item_Broken_Teeth_of_Iron_Wolf": {
"name": "Ascension: Físico (Natasha / Clara / Luka / Sushang)",
"help": ""
},
"Item_Endotherm_Chitin": {
"name": "Ascension: Fuego (Himeko / Asta / Hook)",
"help": ""
},
"Item_Horn_of_Snow": {
"name": "Ascension: Hielo (Siete de Marzo / Herta / Gepard / Pela)",
"help": ""
},
"Item_Lightning_Crown_of_the_Past_Shadow": {
"name": "Ascension: Rayo (Arlan / Serval / Tingyun / Bailu)",
"help": ""
},
"Item_Storm_Eye": {
"name": "Ascension: Viento (Dan Heng / Bronya / Sampo)",
"help": ""
},
"Item_Void_Cast_Iron": {
"name": "Ascension: Cuántico (Silver Wolf / Seele / Qingque)",
"help": ""
},
"Item_Golden_Crown_of_the_Past_Shadow": {
"name": "Ascension: Imaginario (Welt / Luocha / Yukong)",
"help": ""
},
"Item_Netherworld_Token": {
"name": "Ascension: Físico (Hanya / Argenti)",
"help": ""
},
"Item_Searing_Steel_Blade": {
"name": "Ascension: Fuego (Guinaifen / Topaz y Conti)",
"help": ""
},
"Item_Gelid_Chitin": {
"name": "Ascension: Hielo (Yanqing / Jingliu / Ruan Mei)",
"help": ""
},
"Item_Shape_Shifter_Lightning_Staff": {
"name": "Ascension: Rayo (Kafka / Jing Yuan / Acheron)",
"help": ""
},
"Item_Ascendant_Debris": {
"name": "Ascension: Viento (Blade / Huohuo / Cisne Negro)",
"help": ""
},
"Item_Nail_of_the_Ape": {
"name": "Ascension: Cuántico (Lynx / Fu Xuan / Xueyi)",
"help": ""
},
"Item_Suppressing_Edict": {
"name": "Ascension: Imaginario (Dan Heng - Imbibitor Lunae / Aventurino / Dr. Ratio)",
"help": ""
},
"Item_IPC_Work_Permit": {
"name": "Ascension: Físico (Boothill / Robin)",
"help": ""
},
"Item_Raging_Heart": {
"name": "Ascension: Fuego (Gallagher)",
"help": ""
},
"Item_Dream_Fridge": {
"name": "Ascension: Hielo (Misha)",
"help": ""
},
"Item_Dream_Flamer": {
"name": "Ascension: Cuántico (Sparkle)",
"help": ""
},
"Item_Worldbreaker_Blade": {
"name": "Rastros: Destrucción (Zona de almacenamiento)",
"help": ""
},
"Item_Arrow_of_the_Starchaser": {
"name": "Rastros: Cacería (Llanuras nevadas de las afueras)",
"help": ""
},
"Item_Key_of_Wisdom": {
"name": "Rastros: Erudición (Villarremache)",
"help": ""
},
"Item_Safeguard_of_Amber": {
"name": "Rastros: Conservación (Zona de suministros)",
"help": ""
},
"Item_Obsidian_of_Obsession": {
"name": "Rastros: Nihilidad (Mina principal)",
"help": ""
},
"Item_Stellaris_Symphony": {
"name": "Rastros: Armonía (Asentamiento robot)",
"help": ""
},
"Item_Flower_of_Eternity": {
"name": "Rastros: Abundancia (Paso del Remanso)",
"help": ""
},
"Item_Moon_Madness_Fang": {
"name": "Rastros: Destrucción (Desfiladero de Escamas)",
"help": ""
},
"Item_Countertemporal_Shot": {
"name": "Rastros: Cacería (Recinto de las Audiciones FelizAlma en la Arena Ardiente)",
"help": ""
},
"Item_Divine_Amber": {
"name": "Rastros: Conservación (Parque temático de los Estudios Reloj)",
"help": ""
},
"Item_Heaven_Incinerator": {
"name": "Rastros: Nihilidad (Comisión de Alquimia)",
"help": ""
},
"Item_Heavenly_Melody": {
"name": "Rastros: Armonía (Hotel Fantasía (paisaje onírico))",
"help": ""
},
"Item_Myriad_Fruit": {
"name": "Rastros: Abundancia (Jardín del Sosiego)",
"help": ""
},
"Item_Tracks_of_Destiny": {
"name": "Huellas del destino",
"help": ""
},
"Item_Destroyer_Final_Road": {
"name": "El principio de la Destrucción (Estación Espacial Herta)",
"help": ""
},
"Item_Guardian_Lament": {
"name": "El fin del Hielo Eterno (Jarilo-VI)",
"help": ""
},
"Item_Regret_of_Infinite_Ochema": {
"name": "Semilla divina (El Luofu de Xianzhou)",
"help": ""
},
"Item_Past_Evils_of_the_Borehole_Planet_Disaster": {
"name": "Cráter del planeta devorado (Estación Espacial Herta)",
"help": ""
},
"Item_Lost_Echo_of_the_Shared_Wish": {
"name": "Ecos de la guerra: Tributo del sueño ceniciento (Colonipenal)",
"help": ""
},
"Item_Squirming_Core": {
"name": "Núcleo serpenteante",
"help": ""
},
"Item_Conqueror_Will": {
"name": "Voluntad de conquista",
"help": ""
},
"Item_Silvermane_Medal": {
"name": "Medalla del guardia",
"help": ""
},
"Item_Ancient_Engine": {
"name": "Motor antiguo",
"help": ""
},
"Item_Immortal_Lumintwig": {
"name": "Rama gloriosa inmortal",
"help": ""
},
"Item_Artifex_Gyreheart": {
"name": "Corazón armonioso mecánico",
"help": ""
},
"Item_Dream_Making_Engine": {
"name": "Motor creasueños",
"help": ""
},
"Item_Shards_of_Desires": {
"name": "Fragmento de deseos",
"help": ""
}
},
"Weekly": {
"_info": {
"name": "Ajustes de Ecos de la guerra",
@ -1063,6 +1198,16 @@
"do_not_click": "No hacer clic"
}
},
"PlannerScan": {
"_info": {
"name": "Escanear resultados del planificador de caracteres",
"help": "Las herramientas deben detener el programador y luego ejecutarse de forma independiente\nAntes de usarlo, establezca el objetivo del planificador en el planificador del juego, calcule los resultados y comience a escanear desde la página de resultados. Para uso detallado, consulte: https://github .com/LmeSzinc/StarRailCopilot/wiki/Planner_cn"
},
"ResultAdd": {
"name": "Acumular múltiples resultados de escaneo",
"help": "Activar cuando se planifican varios personajes, generará varios personajes todos juntos\nDesactivar cuando se planifica un personaje, el objetivo de planificación se actualizará en cada escaneo"
}
},
"Gui": {
"Aside": {
"Install": "Instalar",

View File

@ -61,6 +61,10 @@
"Daemon": {
"name": "Task.Daemon.name",
"help": "Task.Daemon.help"
},
"PlannerScan": {
"name": "Task.PlannerScan.name",
"help": "Task.PlannerScan.help"
}
},
"Scheduler": {
@ -251,9 +255,9 @@
"Calyx_Golden_Memories_Jarilo_VI": "素材:役割経験(回憶の蕾・ヤリーロ-Ⅵ):",
"Calyx_Golden_Memories_The_Xianzhou_Luofu": "素材:役割経験(回憶の蕾・仙舟羅浮):",
"Calyx_Golden_Memories_Penacony": "素材:役割経験(回憶の蕾・ピノコニー):",
"Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ)",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮)",
"Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー)",
"Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ)",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮)",
"Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー)",
"Calyx_Golden_Treasures_Jarilo_VI": "素材:クレジット(秘蔵の蕾・ヤリーロ-Ⅵ)",
"Calyx_Golden_Treasures_The_Xianzhou_Luofu": "素材:クレジット(秘蔵の蕾・仙舟羅浮)",
"Calyx_Golden_Treasures_Penacony": "素材:クレジット(秘蔵の蕾・ピノコニー)",
@ -304,9 +308,9 @@
"Calyx_Golden_Memories_Jarilo_VI": "素材:役割経験(回憶の蕾・ヤリーロ-Ⅵ):",
"Calyx_Golden_Memories_The_Xianzhou_Luofu": "素材:役割経験(回憶の蕾・仙舟羅浮):",
"Calyx_Golden_Memories_Penacony": "素材:役割経験(回憶の蕾・ピノコニー):",
"Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ)",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮)",
"Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー)",
"Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ)",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮)",
"Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー)",
"Calyx_Golden_Treasures_Jarilo_VI": "素材:クレジット(秘蔵の蕾・ヤリーロ-Ⅵ)",
"Calyx_Golden_Treasures_The_Xianzhou_Luofu": "素材:クレジット(秘蔵の蕾・仙舟羅浮)",
"Calyx_Golden_Treasures_Penacony": "素材:クレジット(秘蔵の蕾・ピノコニー)",
@ -351,81 +355,6 @@
"9": "9"
}
},
"DungeonDaily": {
"_info": {
"name": "DungeonDaily._info.name",
"help": "DungeonDaily._info.help"
},
"CalyxGolden": {
"name": "DungeonDaily.CalyxGolden.name",
"help": "DungeonDaily.CalyxGolden.help",
"do_not_achieve": "do_not_achieve",
"Calyx_Golden_Memories_Jarilo_VI": "素材:役割経験(回憶の蕾・ヤリーロ-Ⅵ):",
"Calyx_Golden_Memories_The_Xianzhou_Luofu": "素材:役割経験(回憶の蕾・仙舟羅浮):",
"Calyx_Golden_Memories_Penacony": "素材:役割経験(回憶の蕾・ピノコニー):",
"Calyx_Golden_Aether_Jarilo_VI": "素材:武器経験(エーテルの蕾・ヤリーロ-Ⅵ):",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "素材:武器経験(エーテルの蕾・仙舟羅浮):",
"Calyx_Golden_Aether_Penacony": "素材:武器経験(エーテルの蕾・ピノコニー):",
"Calyx_Golden_Treasures_Jarilo_VI": "素材:クレジット(秘蔵の蕾・ヤリーロ-Ⅵ)",
"Calyx_Golden_Treasures_The_Xianzhou_Luofu": "素材:クレジット(秘蔵の蕾・仙舟羅浮)",
"Calyx_Golden_Treasures_Penacony": "素材:クレジット(秘蔵の蕾・ピノコニー)"
},
"CalyxCrimson": {
"name": "DungeonDaily.CalyxCrimson.name",
"help": "DungeonDaily.CalyxCrimson.help",
"do_not_achieve": "do_not_achieve",
"Calyx_Crimson_Destruction_Herta_StorageZone": "軌跡素材:壊滅(収容部分)",
"Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "軌跡素材:壊滅(鱗淵境)",
"Calyx_Crimson_Preservation_Herta_SupplyZone": "軌跡素材:存護(サポート部分)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "軌跡素材:存護(クラークフィルムランド)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "軌跡素材:巡狩(郊外雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "軌跡素材:巡狩(スラーダ熱砂オーディション会場)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "軌跡素材:豊穣(外縁通路)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "軌跡素材:豊穣(綏園)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "軌跡素材:知恵(リベットタウン)",
"Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "軌跡素材:調和(機械集落)",
"Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "軌跡素材:調和(ホテル・レバリー-夢境)",
"Calyx_Crimson_Nihility_Jarilo_GreatMine": "軌跡素材:虚無(大鉱区)",
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "軌跡素材:虚無(丹鼎司)"
},
"StagnantShadow": {
"name": "DungeonDaily.StagnantShadow.name",
"help": "DungeonDaily.StagnantShadow.help",
"do_not_achieve": "do_not_achieve",
"Stagnant_Shadow_Spike": "キャラクター昇格素材:物理(ナターシャ / クラーラ / ルカ / 素裳)",
"Stagnant_Shadow_Perdition": "キャラクター昇格素材:物理(寒鴉 / アルジェンティ)",
"Stagnant_Shadow_Duty": "キャラクター昇格素材:物理(ブートヒル / ロビン)",
"Stagnant_Shadow_Blaze": "キャラクター昇格素材:炎(姫子 / アスター / フック)",
"Stagnant_Shadow_Scorch": "キャラクター昇格素材:炎(桂乃芬 / トパーズ&カブ)",
"Stagnant_Shadow_Ire": "キャラクター昇格素材:炎(ギャラガー)",
"Stagnant_Shadow_Rime": "キャラクター昇格素材:氷(三月なのか / ヘルタ / ジェパード / ペラ)",
"Stagnant_Shadow_Icicle": "キャラクター昇格素材:氷(彦卿 / 鏡流 / ルアン・メェイ)",
"Stagnant_Shadow_Nectar": "キャラクター昇格素材:氷(ミーシャ)",
"Stagnant_Shadow_Fulmination": "キャラクター昇格素材:雷(アーラン / セーバル / 停雲 / 白露)",
"Stagnant_Shadow_Doom": "キャラクター昇格素材:雷(カフカ / 景元 / 黄泉)",
"Stagnant_Shadow_Gust": "キャラクター昇格素材:風(丹恒 / ブローニャ / サンポ)",
"Stagnant_Shadow_Celestial": "キャラクター昇格素材:風(刃 / フォフォ / ブラックスワン)",
"Stagnant_Shadow_Quanta": "キャラクター昇格素材:量子(銀狼 / ゼーレ / 青雀)",
"Stagnant_Shadow_Abomination": "キャラクター昇格素材:量子(リンクス / 符玄 / 雪衣)",
"Stagnant_Shadow_Roast": "キャラクター昇格素材:量子(花火)",
"Stagnant_Shadow_Mirage": "キャラクター昇格素材:虚数(ヴェルト / 羅刹 / 御空)",
"Stagnant_Shadow_Puppetry": "キャラクター昇格素材:虚数(丹恒・飲月 / アベンチュリン / Dr.レイシオ)"
},
"CavernOfCorrosion": {
"name": "DungeonDaily.CavernOfCorrosion.name",
"help": "DungeonDaily.CavernOfCorrosion.help",
"do_not_achieve": "do_not_achieve",
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "侵蝕トンネル・霜風の路(侵蝕トンネル・霜風の路)",
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "侵蝕トンネル・迅拳の路(侵蝕トンネル・迅拳の路)",
"Cavern_of_Corrosion_Path_of_Drifting": "侵蝕トンネル・漂泊の路(侵蝕トンネル・漂泊の路)",
"Cavern_of_Corrosion_Path_of_Providence": "侵蝕トンネル・睿治の路(侵蝕トンネル・睿治の路)",
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "侵蝕トンネル・聖頌の路(侵蝕トンネル・聖頌の路)",
"Cavern_of_Corrosion_Path_of_Conflagration": "侵蝕トンネル・野焔の路(侵蝕トンネル・野焔の路)",
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "侵蝕トンネル・薬使の路(侵蝕トンネル・薬使の路)",
"Cavern_of_Corrosion_Path_of_Darkness": "侵蝕トンネル・幽冥の路(侵蝕トンネル・幽冥の路)",
"Cavern_of_Corrosion_Path_of_Dreamdive": "侵蝕トンネル・夢潜の路(侵蝕トンネル・夢潜の路)"
}
},
"DungeonSupport": {
"_info": {
"name": "DungeonSupport._info.name",
@ -529,6 +458,212 @@
"help": ""
}
},
"Planner": {
"_info": {
"name": "Planner._info.name",
"help": "Planner._info.help"
},
"Item_Credit": {
"name": "信用ポイント",
"help": ""
},
"Item_Trailblaze_EXP": {
"name": "マイレージ",
"help": ""
},
"Item_Traveler_Guide": {
"name": "役割経験",
"help": ""
},
"Item_Refined_Aether": {
"name": "武器経験",
"help": ""
},
"Item_Lost_Crystal": {
"name": "遺失晶塊",
"help": ""
},
"Item_Broken_Teeth_of_Iron_Wolf": {
"name": "キャラクター昇格素材:物理(ナターシャ / クラーラ / ルカ / 素裳)",
"help": ""
},
"Item_Endotherm_Chitin": {
"name": "キャラクター昇格素材:炎(姫子 / アスター / フック)",
"help": ""
},
"Item_Horn_of_Snow": {
"name": "キャラクター昇格素材:氷(三月なのか / ヘルタ / ジェパード / ペラ)",
"help": ""
},
"Item_Lightning_Crown_of_the_Past_Shadow": {
"name": "キャラクター昇格素材:雷(アーラン / セーバル / 停雲 / 白露)",
"help": ""
},
"Item_Storm_Eye": {
"name": "キャラクター昇格素材:風(丹恒 / ブローニャ / サンポ)",
"help": ""
},
"Item_Void_Cast_Iron": {
"name": "キャラクター昇格素材:量子(銀狼 / ゼーレ / 青雀)",
"help": ""
},
"Item_Golden_Crown_of_the_Past_Shadow": {
"name": "キャラクター昇格素材:虚数(ヴェルト / 羅刹 / 御空)",
"help": ""
},
"Item_Netherworld_Token": {
"name": "キャラクター昇格素材:物理(寒鴉 / アルジェンティ)",
"help": ""
},
"Item_Searing_Steel_Blade": {
"name": "キャラクター昇格素材:炎(桂乃芬 / トパーズ&カブ)",
"help": ""
},
"Item_Gelid_Chitin": {
"name": "キャラクター昇格素材:氷(彦卿 / 鏡流 / ルアン・メェイ)",
"help": ""
},
"Item_Shape_Shifter_Lightning_Staff": {
"name": "キャラクター昇格素材:雷(カフカ / 景元 / 黄泉)",
"help": ""
},
"Item_Ascendant_Debris": {
"name": "キャラクター昇格素材:風(刃 / フォフォ / ブラックスワン)",
"help": ""
},
"Item_Nail_of_the_Ape": {
"name": "キャラクター昇格素材:量子(リンクス / 符玄 / 雪衣)",
"help": ""
},
"Item_Suppressing_Edict": {
"name": "キャラクター昇格素材:虚数(丹恒・飲月 / アベンチュリン / Dr.レイシオ)",
"help": ""
},
"Item_IPC_Work_Permit": {
"name": "キャラクター昇格素材:物理(ブートヒル / ロビン)",
"help": ""
},
"Item_Raging_Heart": {
"name": "キャラクター昇格素材:炎(ギャラガー)",
"help": ""
},
"Item_Dream_Fridge": {
"name": "キャラクター昇格素材:氷(ミーシャ)",
"help": ""
},
"Item_Dream_Flamer": {
"name": "キャラクター昇格素材:量子(花火)",
"help": ""
},
"Item_Worldbreaker_Blade": {
"name": "軌跡素材:壊滅(収容部分)",
"help": ""
},
"Item_Arrow_of_the_Starchaser": {
"name": "軌跡素材:巡狩(郊外雪原)",
"help": ""
},
"Item_Key_of_Wisdom": {
"name": "軌跡素材:知恵(リベットタウン)",
"help": ""
},
"Item_Safeguard_of_Amber": {
"name": "軌跡素材:存護(サポート部分)",
"help": ""
},
"Item_Obsidian_of_Obsession": {
"name": "軌跡素材:虚無(大鉱区)",
"help": ""
},
"Item_Stellaris_Symphony": {
"name": "軌跡素材:調和(機械集落)",
"help": ""
},
"Item_Flower_of_Eternity": {
"name": "軌跡素材:豊穣(外縁通路)",
"help": ""
},
"Item_Moon_Madness_Fang": {
"name": "軌跡素材:壊滅(鱗淵境)",
"help": ""
},
"Item_Countertemporal_Shot": {
"name": "軌跡素材:巡狩(スラーダ熱砂オーディション会場)",
"help": ""
},
"Item_Divine_Amber": {
"name": "軌跡素材:存護(クラークフィルムランド)",
"help": ""
},
"Item_Heaven_Incinerator": {
"name": "軌跡素材:虚無(丹鼎司)",
"help": ""
},
"Item_Heavenly_Melody": {
"name": "軌跡素材:調和(ホテル・レバリー-夢境)",
"help": ""
},
"Item_Myriad_Fruit": {
"name": "軌跡素材:豊穣(綏園)",
"help": ""
},
"Item_Tracks_of_Destiny": {
"name": "運命の足跡",
"help": ""
},
"Item_Destroyer_Final_Road": {
"name": "歴戦余韻・壊滅の始まり (宇宙ステーション「ヘルタ」)",
"help": ""
},
"Item_Guardian_Lament": {
"name": "歴戦余韻・寒波の幕切れ (ヤリーロ-VI)",
"help": ""
},
"Item_Regret_of_Infinite_Ochema": {
"name": "歴戦余韻・不死の神実 (仙舟「羅浮」)",
"help": ""
},
"Item_Past_Evils_of_the_Borehole_Planet_Disaster": {
"name": "歴戦余韻・星を蝕む往日の面影 (宇宙ステーション「ヘルタ」)",
"help": ""
},
"Item_Lost_Echo_of_the_Shared_Wish": {
"name": "歴戦余韻・現世の夢の礼賛 (ピノコニー)",
"help": ""
},
"Item_Squirming_Core": {
"name": "脈動する原核",
"help": ""
},
"Item_Conqueror_Will": {
"name": "踏みにじる意志",
"help": ""
},
"Item_Silvermane_Medal": {
"name": "シルバーメインの勲章",
"help": ""
},
"Item_Ancient_Engine": {
"name": "古代エンジン",
"help": ""
},
"Item_Immortal_Lumintwig": {
"name": "永寿の栄枝",
"help": ""
},
"Item_Artifex_Gyreheart": {
"name": "工造渾心",
"help": ""
},
"Item_Dream_Making_Engine": {
"name": "ドリームメイキングモーター",
"help": ""
},
"Item_Shards_of_Desires": {
"name": "砕けた欲望の鏡",
"help": ""
}
},
"Weekly": {
"_info": {
"name": "Weekly._info.name",
@ -1063,6 +1198,16 @@
"do_not_click": "do_not_click"
}
},
"PlannerScan": {
"_info": {
"name": "PlannerScan._info.name",
"help": "PlannerScan._info.help"
},
"ResultAdd": {
"name": "PlannerScan.ResultAdd.name",
"help": "PlannerScan.ResultAdd.help"
}
},
"Gui": {
"Aside": {
"Install": "インストール",

View File

@ -61,6 +61,10 @@
"Daemon": {
"name": "剧情连点器",
"help": ""
},
"PlannerScan": {
"name": "角色养成规划",
"help": ""
}
},
"Scheduler": {
@ -351,81 +355,6 @@
"9": "9"
}
},
"DungeonDaily": {
"_info": {
"name": "每日任务设置",
"help": "打一次特定的本,以满足每日任务的要求"
},
"CalyxGolden": {
"name": "完成1次拟造花萼",
"help": "",
"do_not_achieve": "不完成这个任务",
"Calyx_Golden_Memories_Jarilo_VI": "材料:角色经验(回忆之蕾•雅利洛-Ⅵ)",
"Calyx_Golden_Memories_The_Xianzhou_Luofu": "材料:角色经验(回忆之蕾•仙舟罗浮)",
"Calyx_Golden_Memories_Penacony": "材料:角色经验(回忆之蕾•匹诺康尼)",
"Calyx_Golden_Aether_Jarilo_VI": "材料:武器经验(以太之蕾•雅利洛-Ⅵ)",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "材料:武器经验(以太之蕾•仙舟罗浮)",
"Calyx_Golden_Aether_Penacony": "材料:武器经验(以太之蕾•匹诺康尼)",
"Calyx_Golden_Treasures_Jarilo_VI": "材料:信用点(藏珍之蕾•雅利洛-Ⅵ)",
"Calyx_Golden_Treasures_The_Xianzhou_Luofu": "材料:信用点(藏珍之蕾•仙舟罗浮)",
"Calyx_Golden_Treasures_Penacony": "材料:信用点(藏珍之蕾•匹诺康尼)"
},
"CalyxCrimson": {
"name": "完成1次拟造花萼",
"help": "",
"do_not_achieve": "不完成这个任务",
"Calyx_Crimson_Destruction_Herta_StorageZone": "行迹材料:毁灭(收容舱段)",
"Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "行迹材料:毁灭(鳞渊境)",
"Calyx_Crimson_Preservation_Herta_SupplyZone": "行迹材料:存护(支援舱段)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行迹材料:存护(克劳克影视乐园)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行迹材料:巡猎(城郊雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行迹材料:巡猎(苏乐达热砂海选会场)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行迹材料:丰饶(边缘通路)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行迹材料:丰饶(绥园)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "行迹材料:智识(铆钉镇)",
"Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "行迹材料:同谐(机械聚落)",
"Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "行迹材料:同谐(白日梦酒店-梦境)",
"Calyx_Crimson_Nihility_Jarilo_GreatMine": "行迹材料:虚无(大矿区)",
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "行迹材料:虚无(丹鼎司)"
},
"StagnantShadow": {
"name": "完成1次凝滞虚影",
"help": "",
"do_not_achieve": "不完成这个任务",
"Stagnant_Shadow_Spike": "角色晋阶材料:物理(娜塔莎 / 克拉拉 / 卢卡 / 素裳)",
"Stagnant_Shadow_Perdition": "角色晋阶材料:物理(寒鸦 / 银枝)",
"Stagnant_Shadow_Duty": "角色晋阶材料:物理(波提欧 / 知更鸟)",
"Stagnant_Shadow_Blaze": "角色晋阶材料:火(姬子 / 艾丝妲 / 虎克)",
"Stagnant_Shadow_Scorch": "角色晋阶材料:火(桂乃芬 / 托帕&账账)",
"Stagnant_Shadow_Ire": "角色晋阶材料:火(加拉赫)",
"Stagnant_Shadow_Rime": "角色晋阶材料:冰(三月七 / 黑塔 / 杰帕德 / 佩拉)",
"Stagnant_Shadow_Icicle": "角色晋阶材料:冰(彦卿 / 镜流 / 阮•梅)",
"Stagnant_Shadow_Nectar": "角色晋阶材料:冰(米沙)",
"Stagnant_Shadow_Fulmination": "角色晋阶材料:雷(阿兰 / 希露瓦 / 停云 / 白露)",
"Stagnant_Shadow_Doom": "角色晋阶材料:雷(卡芙卡 / 景元 / 黄泉)",
"Stagnant_Shadow_Gust": "角色晋阶材料:风(丹恒 / 布洛妮娅 / 桑博)",
"Stagnant_Shadow_Celestial": "角色晋阶材料:风(刃 / 藿藿 / 黑天鹅)",
"Stagnant_Shadow_Quanta": "角色晋阶材料:量子(银狼 / 希儿 / 青雀)",
"Stagnant_Shadow_Abomination": "角色晋阶材料:量子(玲可 / 符玄 / 雪衣)",
"Stagnant_Shadow_Roast": "角色晋阶材料:量子(花火)",
"Stagnant_Shadow_Mirage": "角色晋阶材料:虚数(瓦尔特 / 罗刹 / 驭空)",
"Stagnant_Shadow_Puppetry": "角色晋阶材料:虚数(丹恒•饮月 / 砂金 / 真理医生)"
},
"CavernOfCorrosion": {
"name": "完成1次侵蚀隧洞",
"help": "",
"do_not_achieve": "不完成这个任务",
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "遗器:冰套+风套(霜风之径•侵蚀隧洞)",
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "遗器:物理套+击破套(迅拳之径•侵蚀隧洞)",
"Cavern_of_Corrosion_Path_of_Drifting": "遗器:治疗套+快枪手(漂泊之径•侵蚀隧洞)",
"Cavern_of_Corrosion_Path_of_Providence": "遗器:铁卫套+量子套(睿治之径•侵蚀隧洞)",
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "遗器:防御套+雷套(圣颂之径•侵蚀隧洞)",
"Cavern_of_Corrosion_Path_of_Conflagration": "遗器:火套+虚数套(野焰之径•侵蚀隧洞)",
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遗器:生命套+速度套(药使之径•侵蚀隧洞)",
"Cavern_of_Corrosion_Path_of_Darkness": "遗器:追击套+dot套幽冥之径•侵蚀隧洞",
"Cavern_of_Corrosion_Path_of_Dreamdive": "遗器:负面套+击破套(梦潜之径•侵蚀隧洞)"
}
},
"DungeonSupport": {
"_info": {
"name": "支援设置",
@ -529,6 +458,212 @@
"help": ""
}
},
"Planner": {
"_info": {
"name": "养成规划进度",
"help": "优先执行养成规划,养成规划完成后,执行 \"每日副本设置\""
},
"Item_Credit": {
"name": "信用点",
"help": ""
},
"Item_Trailblaze_EXP": {
"name": "里程",
"help": ""
},
"Item_Traveler_Guide": {
"name": "角色经验",
"help": ""
},
"Item_Refined_Aether": {
"name": "武器经验",
"help": ""
},
"Item_Lost_Crystal": {
"name": "遗失晶块",
"help": ""
},
"Item_Broken_Teeth_of_Iron_Wolf": {
"name": "角色晋阶材料:物理(娜塔莎 / 克拉拉 / 卢卡 / 素裳)",
"help": ""
},
"Item_Endotherm_Chitin": {
"name": "角色晋阶材料:火(姬子 / 艾丝妲 / 虎克)",
"help": ""
},
"Item_Horn_of_Snow": {
"name": "角色晋阶材料:冰(三月七 / 黑塔 / 杰帕德 / 佩拉)",
"help": ""
},
"Item_Lightning_Crown_of_the_Past_Shadow": {
"name": "角色晋阶材料:雷(阿兰 / 希露瓦 / 停云 / 白露)",
"help": ""
},
"Item_Storm_Eye": {
"name": "角色晋阶材料:风(丹恒 / 布洛妮娅 / 桑博)",
"help": ""
},
"Item_Void_Cast_Iron": {
"name": "角色晋阶材料:量子(银狼 / 希儿 / 青雀)",
"help": ""
},
"Item_Golden_Crown_of_the_Past_Shadow": {
"name": "角色晋阶材料:虚数(瓦尔特 / 罗刹 / 驭空)",
"help": ""
},
"Item_Netherworld_Token": {
"name": "角色晋阶材料:物理(寒鸦 / 银枝)",
"help": ""
},
"Item_Searing_Steel_Blade": {
"name": "角色晋阶材料:火(桂乃芬 / 托帕&账账)",
"help": ""
},
"Item_Gelid_Chitin": {
"name": "角色晋阶材料:冰(彦卿 / 镜流 / 阮•梅)",
"help": ""
},
"Item_Shape_Shifter_Lightning_Staff": {
"name": "角色晋阶材料:雷(卡芙卡 / 景元 / 黄泉)",
"help": ""
},
"Item_Ascendant_Debris": {
"name": "角色晋阶材料:风(刃 / 藿藿 / 黑天鹅)",
"help": ""
},
"Item_Nail_of_the_Ape": {
"name": "角色晋阶材料:量子(玲可 / 符玄 / 雪衣)",
"help": ""
},
"Item_Suppressing_Edict": {
"name": "角色晋阶材料:虚数(丹恒•饮月 / 砂金 / 真理医生)",
"help": ""
},
"Item_IPC_Work_Permit": {
"name": "角色晋阶材料:物理(波提欧 / 知更鸟)",
"help": ""
},
"Item_Raging_Heart": {
"name": "角色晋阶材料:火(加拉赫)",
"help": ""
},
"Item_Dream_Fridge": {
"name": "角色晋阶材料:冰(米沙)",
"help": ""
},
"Item_Dream_Flamer": {
"name": "角色晋阶材料:量子(花火)",
"help": ""
},
"Item_Worldbreaker_Blade": {
"name": "行迹材料:毁灭(收容舱段)",
"help": ""
},
"Item_Arrow_of_the_Starchaser": {
"name": "行迹材料:巡猎(城郊雪原)",
"help": ""
},
"Item_Key_of_Wisdom": {
"name": "行迹材料:智识(铆钉镇)",
"help": ""
},
"Item_Safeguard_of_Amber": {
"name": "行迹材料:存护(支援舱段)",
"help": ""
},
"Item_Obsidian_of_Obsession": {
"name": "行迹材料:虚无(大矿区)",
"help": ""
},
"Item_Stellaris_Symphony": {
"name": "行迹材料:同谐(机械聚落)",
"help": ""
},
"Item_Flower_of_Eternity": {
"name": "行迹材料:丰饶(边缘通路)",
"help": ""
},
"Item_Moon_Madness_Fang": {
"name": "行迹材料:毁灭(鳞渊境)",
"help": ""
},
"Item_Countertemporal_Shot": {
"name": "行迹材料:巡猎(苏乐达热砂海选会场)",
"help": ""
},
"Item_Divine_Amber": {
"name": "行迹材料:存护(克劳克影视乐园)",
"help": ""
},
"Item_Heaven_Incinerator": {
"name": "行迹材料:虚无(丹鼎司)",
"help": ""
},
"Item_Heavenly_Melody": {
"name": "行迹材料:同谐(白日梦酒店-梦境)",
"help": ""
},
"Item_Myriad_Fruit": {
"name": "行迹材料:丰饶(绥园)",
"help": ""
},
"Item_Tracks_of_Destiny": {
"name": "命运的足迹",
"help": ""
},
"Item_Destroyer_Final_Road": {
"name": "毁灭的开端•历战余响 (空间站「黑塔」)",
"help": ""
},
"Item_Guardian_Lament": {
"name": "寒潮的落幕•历战余响 (雅利洛-Ⅵ)",
"help": ""
},
"Item_Regret_of_Infinite_Ochema": {
"name": "不死的神实•历战余响 (仙舟「罗浮」)",
"help": ""
},
"Item_Past_Evils_of_the_Borehole_Planet_Disaster": {
"name": "蛀星的旧靥•历战余响 (空间站「黑塔」)",
"help": ""
},
"Item_Lost_Echo_of_the_Shared_Wish": {
"name": "尘梦的赞礼•历战余响 (匹诺康尼)",
"help": ""
},
"Item_Squirming_Core": {
"name": "蠢动原核",
"help": ""
},
"Item_Conqueror_Will": {
"name": "践踏的意志",
"help": ""
},
"Item_Silvermane_Medal": {
"name": "铁卫勋章",
"help": ""
},
"Item_Ancient_Engine": {
"name": "古代引擎",
"help": ""
},
"Item_Immortal_Lumintwig": {
"name": "永寿荣枝",
"help": ""
},
"Item_Artifex_Gyreheart": {
"name": "工造浑心",
"help": ""
},
"Item_Dream_Making_Engine": {
"name": "造梦马达",
"help": ""
},
"Item_Shards_of_Desires": {
"name": "欲念碎镜",
"help": ""
}
},
"Weekly": {
"_info": {
"name": "历战余响设置",
@ -1063,6 +1198,16 @@
"do_not_click": "不点击"
}
},
"PlannerScan": {
"_info": {
"name": "识别角色养成规划计算结果",
"help": "工具需要停止调度器再单独运行\n使用前需要在游戏内养成计算器中设定养成目标并计算出结果在计算结果页面启动养成规划识别。详细使用教程见\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/Planner_cn"
},
"ResultAdd": {
"name": "累加多次扫描结果",
"help": "需要养成多个角色时勾选,同时养成多个角色\n仅养成一个角色不勾选每次扫描时刷新养成目标"
}
},
"Gui": {
"Aside": {
"Install": "安装",

View File

@ -61,6 +61,10 @@
"Daemon": {
"name": "劇情連點器",
"help": ""
},
"PlannerScan": {
"name": "角色養成規劃",
"help": ""
}
},
"Scheduler": {
@ -351,81 +355,6 @@
"9": "9"
}
},
"DungeonDaily": {
"_info": {
"name": "每日任務設定",
"help": "打一次特定的本,以滿足每日任務的要求"
},
"CalyxGolden": {
"name": "完成1次擬造花萼",
"help": "",
"do_not_achieve": "不完成這個任務",
"Calyx_Golden_Memories_Jarilo_VI": "材料:角色經驗(回憶之蕾•雅利洛-Ⅵ)",
"Calyx_Golden_Memories_The_Xianzhou_Luofu": "材料:角色經驗(回憶之蕾•仙舟羅浮)",
"Calyx_Golden_Memories_Penacony": "材料:角色經驗(回憶之蕾•匹諾康尼)",
"Calyx_Golden_Aether_Jarilo_VI": "材料:武器經驗(乙太之蕾•雅利洛-Ⅵ)",
"Calyx_Golden_Aether_The_Xianzhou_Luofu": "材料:武器經驗(乙太之蕾•仙舟羅浮)",
"Calyx_Golden_Aether_Penacony": "材料:武器經驗(乙太之蕾•匹諾康尼)",
"Calyx_Golden_Treasures_Jarilo_VI": "材料:信用點(藏珍之蕾•雅利洛-Ⅵ)",
"Calyx_Golden_Treasures_The_Xianzhou_Luofu": "材料:信用點(藏珍之蕾•仙舟羅浮)",
"Calyx_Golden_Treasures_Penacony": "材料:信用點(藏珍之蕾•匹諾康尼)"
},
"CalyxCrimson": {
"name": "完成1次擬造花萼",
"help": "",
"do_not_achieve": "不完成這個任務",
"Calyx_Crimson_Destruction_Herta_StorageZone": "行跡材料:毀滅(收容艙段)",
"Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape": "行跡材料:毀滅(鱗淵境)",
"Calyx_Crimson_Preservation_Herta_SupplyZone": "行跡材料:存護(支援艙段)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行跡材料:存護(克勞克影視樂園)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行跡材料:巡獵(城郊雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行跡材料:巡獵(蘇樂達熱砂海選會場)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行跡材料:豐饒(邊緣通道)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行跡材料:豐饒(綏園)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "行跡材料:智識(鉚釘鎮)",
"Calyx_Crimson_Harmony_Jarilo_RobotSettlement": "行跡材料:同諧(機械聚落)",
"Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape": "行跡材料:同諧(白日夢飯店-夢境)",
"Calyx_Crimson_Nihility_Jarilo_GreatMine": "行跡材料:虛無(大礦區)",
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "行跡材料:虛無(丹鼎司)"
},
"StagnantShadow": {
"name": "完成1次凝滯虛影",
"help": "",
"do_not_achieve": "不完成這個任務",
"Stagnant_Shadow_Spike": "角色晉階材料:物理(娜塔莎 / 克拉拉 / 盧卡 / 素裳)",
"Stagnant_Shadow_Perdition": "角色晉階材料:物理(寒鴉 / 銀枝)",
"Stagnant_Shadow_Duty": "角色晉階材料:物理(波提歐 / 知更鳥)",
"Stagnant_Shadow_Blaze": "角色晉階材料:火(姬子 / 艾絲妲 / 虎克)",
"Stagnant_Shadow_Scorch": "角色晉階材料:火(桂乃芬 / 托帕&帳帳)",
"Stagnant_Shadow_Ire": "角色晉階材料:火(加拉赫)",
"Stagnant_Shadow_Rime": "角色晉階材料:冰(三月七 / 黑塔 / 傑帕德 / 佩拉)",
"Stagnant_Shadow_Icicle": "角色晉階材料:冰(彥卿 / 鏡流 / 阮•梅)",
"Stagnant_Shadow_Nectar": "角色晉階材料:冰(米沙)",
"Stagnant_Shadow_Fulmination": "角色晉階材料:雷(阿蘭 / 希露瓦 / 停雲 / 白露)",
"Stagnant_Shadow_Doom": "角色晉階材料:雷(卡芙卡 / 景元 / 黃泉)",
"Stagnant_Shadow_Gust": "角色晉階材料:風(丹恆 / 布洛妮婭 / 桑博)",
"Stagnant_Shadow_Celestial": "角色晉階材料:風(刃 / 藿藿 / 黑天鵝)",
"Stagnant_Shadow_Quanta": "角色晉階材料:量子(銀狼 / 希兒 / 青雀)",
"Stagnant_Shadow_Abomination": "角色晉階材料:量子(玲可 / 符玄 / 雪衣)",
"Stagnant_Shadow_Roast": "角色晉階材料:量子(花火)",
"Stagnant_Shadow_Mirage": "角色晉階材料:虛數(瓦爾特 / 羅剎 / 馭空)",
"Stagnant_Shadow_Puppetry": "角色晉階材料:虛數(丹恆•飲月 / 砂金 / 真理醫生)"
},
"CavernOfCorrosion": {
"name": "完成1次侵蝕隧洞",
"help": "",
"do_not_achieve": "不完成這個任務",
"Cavern_of_Corrosion_Path_of_Gelid_Wind": "遺器:冰套+風套(霜風之徑•侵蝕隧洞)",
"Cavern_of_Corrosion_Path_of_Jabbing_Punch": "遺器:物理套+擊破套(迅拳之徑•侵蝕隧洞)",
"Cavern_of_Corrosion_Path_of_Drifting": "遺器:治療套+快槍手(漂泊之徑•侵蝕隧洞)",
"Cavern_of_Corrosion_Path_of_Providence": "遺器:鐵衛套+量子套(睿治之徑•侵蝕隧洞)",
"Cavern_of_Corrosion_Path_of_Holy_Hymn": "遺器:防禦套+雷套(聖頌之徑•侵蝕隧洞)",
"Cavern_of_Corrosion_Path_of_Conflagration": "遺器:火套+虛數套(野焰之徑•侵蝕隧洞)",
"Cavern_of_Corrosion_Path_of_Elixir_Seekers": "遺器:生命套+速度套(藥使之徑•侵蝕隧洞)",
"Cavern_of_Corrosion_Path_of_Darkness": "遺器:追擊套+dot套幽冥之徑•侵蝕隧洞",
"Cavern_of_Corrosion_Path_of_Dreamdive": "遺器:負面套+擊破套(夢潛之徑•侵蝕隧洞)"
}
},
"DungeonSupport": {
"_info": {
"name": "支援設定",
@ -529,6 +458,212 @@
"help": ""
}
},
"Planner": {
"_info": {
"name": "養成規劃進度",
"help": "優先執行養成規劃,養成規劃完成後,執行 \"每日副本設定\""
},
"Item_Credit": {
"name": "信用點",
"help": ""
},
"Item_Trailblaze_EXP": {
"name": "里程",
"help": ""
},
"Item_Traveler_Guide": {
"name": "角色經驗",
"help": ""
},
"Item_Refined_Aether": {
"name": "武器經驗",
"help": ""
},
"Item_Lost_Crystal": {
"name": "遺失晶塊",
"help": ""
},
"Item_Broken_Teeth_of_Iron_Wolf": {
"name": "角色晉階材料:物理(娜塔莎 / 克拉拉 / 盧卡 / 素裳)",
"help": ""
},
"Item_Endotherm_Chitin": {
"name": "角色晉階材料:火(姬子 / 艾絲妲 / 虎克)",
"help": ""
},
"Item_Horn_of_Snow": {
"name": "角色晉階材料:冰(三月七 / 黑塔 / 傑帕德 / 佩拉)",
"help": ""
},
"Item_Lightning_Crown_of_the_Past_Shadow": {
"name": "角色晉階材料:雷(阿蘭 / 希露瓦 / 停雲 / 白露)",
"help": ""
},
"Item_Storm_Eye": {
"name": "角色晉階材料:風(丹恆 / 布洛妮婭 / 桑博)",
"help": ""
},
"Item_Void_Cast_Iron": {
"name": "角色晉階材料:量子(銀狼 / 希兒 / 青雀)",
"help": ""
},
"Item_Golden_Crown_of_the_Past_Shadow": {
"name": "角色晉階材料:虛數(瓦爾特 / 羅剎 / 馭空)",
"help": ""
},
"Item_Netherworld_Token": {
"name": "角色晉階材料:物理(寒鴉 / 銀枝)",
"help": ""
},
"Item_Searing_Steel_Blade": {
"name": "角色晉階材料:火(桂乃芬 / 托帕&帳帳)",
"help": ""
},
"Item_Gelid_Chitin": {
"name": "角色晉階材料:冰(彥卿 / 鏡流 / 阮•梅)",
"help": ""
},
"Item_Shape_Shifter_Lightning_Staff": {
"name": "角色晉階材料:雷(卡芙卡 / 景元 / 黃泉)",
"help": ""
},
"Item_Ascendant_Debris": {
"name": "角色晉階材料:風(刃 / 藿藿 / 黑天鵝)",
"help": ""
},
"Item_Nail_of_the_Ape": {
"name": "角色晉階材料:量子(玲可 / 符玄 / 雪衣)",
"help": ""
},
"Item_Suppressing_Edict": {
"name": "角色晉階材料:虛數(丹恆•飲月 / 砂金 / 真理醫生)",
"help": ""
},
"Item_IPC_Work_Permit": {
"name": "角色晉階材料:物理(波提歐 / 知更鳥)",
"help": ""
},
"Item_Raging_Heart": {
"name": "角色晉階材料:火(加拉赫)",
"help": ""
},
"Item_Dream_Fridge": {
"name": "角色晉階材料:冰(米沙)",
"help": ""
},
"Item_Dream_Flamer": {
"name": "角色晉階材料:量子(花火)",
"help": ""
},
"Item_Worldbreaker_Blade": {
"name": "行跡材料:毀滅(收容艙段)",
"help": ""
},
"Item_Arrow_of_the_Starchaser": {
"name": "行跡材料:巡獵(城郊雪原)",
"help": ""
},
"Item_Key_of_Wisdom": {
"name": "行跡材料:智識(鉚釘鎮)",
"help": ""
},
"Item_Safeguard_of_Amber": {
"name": "行跡材料:存護(支援艙段)",
"help": ""
},
"Item_Obsidian_of_Obsession": {
"name": "行跡材料:虛無(大礦區)",
"help": ""
},
"Item_Stellaris_Symphony": {
"name": "行跡材料:同諧(機械聚落)",
"help": ""
},
"Item_Flower_of_Eternity": {
"name": "行跡材料:豐饒(邊緣通道)",
"help": ""
},
"Item_Moon_Madness_Fang": {
"name": "行跡材料:毀滅(鱗淵境)",
"help": ""
},
"Item_Countertemporal_Shot": {
"name": "行跡材料:巡獵(蘇樂達熱砂海選會場)",
"help": ""
},
"Item_Divine_Amber": {
"name": "行跡材料:存護(克勞克影視樂園)",
"help": ""
},
"Item_Heaven_Incinerator": {
"name": "行跡材料:虛無(丹鼎司)",
"help": ""
},
"Item_Heavenly_Melody": {
"name": "行跡材料:同諧(白日夢飯店-夢境)",
"help": ""
},
"Item_Myriad_Fruit": {
"name": "行跡材料:豐饒(綏園)",
"help": ""
},
"Item_Tracks_of_Destiny": {
"name": "命運的足跡",
"help": ""
},
"Item_Destroyer_Final_Road": {
"name": "毀滅的開端•歷戰餘響 (太空站「黑塔」)",
"help": ""
},
"Item_Guardian_Lament": {
"name": "寒潮的落幕•歷戰餘響 (雅利洛-Ⅵ)",
"help": ""
},
"Item_Regret_of_Infinite_Ochema": {
"name": "不死的神實•歷戰餘響 (仙舟「羅浮」)",
"help": ""
},
"Item_Past_Evils_of_the_Borehole_Planet_Disaster": {
"name": "蛀星的舊靨•歷戰餘響 (太空站「黑塔」)",
"help": ""
},
"Item_Lost_Echo_of_the_Shared_Wish": {
"name": "塵夢的讚禮•歷戰餘響 (匹諾康尼)",
"help": ""
},
"Item_Squirming_Core": {
"name": "蠢動原核",
"help": ""
},
"Item_Conqueror_Will": {
"name": "踐踏的意志",
"help": ""
},
"Item_Silvermane_Medal": {
"name": "鐵衛勳章",
"help": ""
},
"Item_Ancient_Engine": {
"name": "古代引擎",
"help": ""
},
"Item_Immortal_Lumintwig": {
"name": "永壽榮枝",
"help": ""
},
"Item_Artifex_Gyreheart": {
"name": "工造渾心",
"help": ""
},
"Item_Dream_Making_Engine": {
"name": "造夢馬達",
"help": ""
},
"Item_Shards_of_Desires": {
"name": "欲念碎鏡",
"help": ""
}
},
"Weekly": {
"_info": {
"name": "歷戰餘響設定",
@ -1063,6 +1198,16 @@
"do_not_click": "不點擊"
}
},
"PlannerScan": {
"_info": {
"name": "辨識角色養成規劃計算結果",
"help": "工具需要停止調度器再單獨運行\n使用前需要在遊戲內養成計算器中設定養成目標併計算出結果在計算結果頁面啟動養成規劃識別。詳細使用教程 請參閱:\nhttps://github.com/LmeSzinc/StarRailCopilot/wiki/Planner_cn"
},
"ResultAdd": {
"name": "累加多次掃描結果",
"help": "需要養成多個角色時勾選,同時養成多個角色\n只養成一個角色不勾選每次掃描時刷新養成目標"
}
},
"Gui": {
"Aside": {
"Install": "安裝",

View File

@ -398,3 +398,9 @@ class StoredBattlePassQuestCavernOfCorrosion(StoredCounter):
class StoredBattlePassQuestTrailblazePower(StoredCounter):
# Dynamic total from 100 to 1400
LIST_TOTAL = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400]
class StoredPlanner(StoredBase):
value: int
total: int
synthesize: int

View File

@ -19,6 +19,7 @@ from module.config.stored.classes import (
StoredExpiredAtMonday0400,
StoredImmersifier,
StoredInt,
StoredPlanner,
StoredSimulatedUniverse,
StoredSimulatedUniverseElite,
StoredTrailblazePower,
@ -32,6 +33,56 @@ class StoredGenerated:
CloudRemainSeasonPass = StoredInt("Alas.CloudStorage.CloudRemainSeasonPass")
CloudRemainPaid = StoredInt("Alas.CloudStorage.CloudRemainPaid")
CloudRemainFree = StoredInt("Alas.CloudStorage.CloudRemainFree")
Item_Credit = StoredPlanner("Dungeon.Planner.Item_Credit")
Item_Trailblaze_EXP = StoredPlanner("Dungeon.Planner.Item_Trailblaze_EXP")
Item_Traveler_Guide = StoredPlanner("Dungeon.Planner.Item_Traveler_Guide")
Item_Refined_Aether = StoredPlanner("Dungeon.Planner.Item_Refined_Aether")
Item_Lost_Crystal = StoredPlanner("Dungeon.Planner.Item_Lost_Crystal")
Item_Broken_Teeth_of_Iron_Wolf = StoredPlanner("Dungeon.Planner.Item_Broken_Teeth_of_Iron_Wolf")
Item_Endotherm_Chitin = StoredPlanner("Dungeon.Planner.Item_Endotherm_Chitin")
Item_Horn_of_Snow = StoredPlanner("Dungeon.Planner.Item_Horn_of_Snow")
Item_Lightning_Crown_of_the_Past_Shadow = StoredPlanner("Dungeon.Planner.Item_Lightning_Crown_of_the_Past_Shadow")
Item_Storm_Eye = StoredPlanner("Dungeon.Planner.Item_Storm_Eye")
Item_Void_Cast_Iron = StoredPlanner("Dungeon.Planner.Item_Void_Cast_Iron")
Item_Golden_Crown_of_the_Past_Shadow = StoredPlanner("Dungeon.Planner.Item_Golden_Crown_of_the_Past_Shadow")
Item_Netherworld_Token = StoredPlanner("Dungeon.Planner.Item_Netherworld_Token")
Item_Searing_Steel_Blade = StoredPlanner("Dungeon.Planner.Item_Searing_Steel_Blade")
Item_Gelid_Chitin = StoredPlanner("Dungeon.Planner.Item_Gelid_Chitin")
Item_Shape_Shifter_Lightning_Staff = StoredPlanner("Dungeon.Planner.Item_Shape_Shifter_Lightning_Staff")
Item_Ascendant_Debris = StoredPlanner("Dungeon.Planner.Item_Ascendant_Debris")
Item_Nail_of_the_Ape = StoredPlanner("Dungeon.Planner.Item_Nail_of_the_Ape")
Item_Suppressing_Edict = StoredPlanner("Dungeon.Planner.Item_Suppressing_Edict")
Item_IPC_Work_Permit = StoredPlanner("Dungeon.Planner.Item_IPC_Work_Permit")
Item_Raging_Heart = StoredPlanner("Dungeon.Planner.Item_Raging_Heart")
Item_Dream_Fridge = StoredPlanner("Dungeon.Planner.Item_Dream_Fridge")
Item_Dream_Flamer = StoredPlanner("Dungeon.Planner.Item_Dream_Flamer")
Item_Worldbreaker_Blade = StoredPlanner("Dungeon.Planner.Item_Worldbreaker_Blade")
Item_Arrow_of_the_Starchaser = StoredPlanner("Dungeon.Planner.Item_Arrow_of_the_Starchaser")
Item_Key_of_Wisdom = StoredPlanner("Dungeon.Planner.Item_Key_of_Wisdom")
Item_Safeguard_of_Amber = StoredPlanner("Dungeon.Planner.Item_Safeguard_of_Amber")
Item_Obsidian_of_Obsession = StoredPlanner("Dungeon.Planner.Item_Obsidian_of_Obsession")
Item_Stellaris_Symphony = StoredPlanner("Dungeon.Planner.Item_Stellaris_Symphony")
Item_Flower_of_Eternity = StoredPlanner("Dungeon.Planner.Item_Flower_of_Eternity")
Item_Moon_Madness_Fang = StoredPlanner("Dungeon.Planner.Item_Moon_Madness_Fang")
Item_Countertemporal_Shot = StoredPlanner("Dungeon.Planner.Item_Countertemporal_Shot")
Item_Divine_Amber = StoredPlanner("Dungeon.Planner.Item_Divine_Amber")
Item_Heaven_Incinerator = StoredPlanner("Dungeon.Planner.Item_Heaven_Incinerator")
Item_Heavenly_Melody = StoredPlanner("Dungeon.Planner.Item_Heavenly_Melody")
Item_Myriad_Fruit = StoredPlanner("Dungeon.Planner.Item_Myriad_Fruit")
Item_Tracks_of_Destiny = StoredPlanner("Dungeon.Planner.Item_Tracks_of_Destiny")
Item_Destroyer_Final_Road = StoredPlanner("Dungeon.Planner.Item_Destroyer_Final_Road")
Item_Guardian_Lament = StoredPlanner("Dungeon.Planner.Item_Guardian_Lament")
Item_Regret_of_Infinite_Ochema = StoredPlanner("Dungeon.Planner.Item_Regret_of_Infinite_Ochema")
Item_Past_Evils_of_the_Borehole_Planet_Disaster = StoredPlanner("Dungeon.Planner.Item_Past_Evils_of_the_Borehole_Planet_Disaster")
Item_Lost_Echo_of_the_Shared_Wish = StoredPlanner("Dungeon.Planner.Item_Lost_Echo_of_the_Shared_Wish")
Item_Squirming_Core = StoredPlanner("Dungeon.Planner.Item_Squirming_Core")
Item_Conqueror_Will = StoredPlanner("Dungeon.Planner.Item_Conqueror_Will")
Item_Silvermane_Medal = StoredPlanner("Dungeon.Planner.Item_Silvermane_Medal")
Item_Ancient_Engine = StoredPlanner("Dungeon.Planner.Item_Ancient_Engine")
Item_Immortal_Lumintwig = StoredPlanner("Dungeon.Planner.Item_Immortal_Lumintwig")
Item_Artifex_Gyreheart = StoredPlanner("Dungeon.Planner.Item_Artifex_Gyreheart")
Item_Dream_Making_Engine = StoredPlanner("Dungeon.Planner.Item_Dream_Making_Engine")
Item_Shards_of_Desires = StoredPlanner("Dungeon.Planner.Item_Shards_of_Desires")
TrailblazePower = StoredTrailblazePower("Dungeon.DungeonStorage.TrailblazePower")
Immersifier = StoredImmersifier("Dungeon.DungeonStorage.Immersifier")
DungeonDouble = StoredDungeonDouble("Dungeon.DungeonStorage.DungeonDouble")

View File

@ -1,15 +1,13 @@
import re
import time
from datetime import timedelta
import cv2
import numpy as np
from pponnxcr.predict_system import BoxedResult
import module.config.server as server
from module.base.button import ButtonWrapper
from module.base.decorator import cached_property
from module.base.utils import area_pad, corner2area, crop, extract_white_letters, float2str
from module.base.utils import *
from module.exception import ScriptError
from module.logger import logger
from module.ocr.models import OCR_MODEL, TextSystem
@ -325,7 +323,7 @@ class Ocr:
class Digit(Ocr):
def __init__(self, button: ButtonWrapper, lang='en', name=None):
def __init__(self, button: ButtonWrapper, lang=None, name=None):
super().__init__(button, lang=lang, name=name)
def format_result(self, result) -> int:
@ -345,7 +343,7 @@ class Digit(Ocr):
class DigitCounter(Ocr):
def __init__(self, button: ButtonWrapper, lang='en', name=None):
def __init__(self, button: ButtonWrapper, lang=None, name=None):
super().__init__(button, lang=lang, name=name)
@classmethod
@ -423,6 +421,12 @@ class Duration(Ocr):
class OcrWhiteLetterOnComplexBackground(Ocr):
white_preprocess = True
# 0.6 by default, 0.2 for lower
box_thresh = 0.2
# (x, y) Enlarge detected boxes to `min_boxes`
# So standalone digits can be better detected
# Note that min_box should be 4px larger than the actual letter
min_box = None
def pre_process(self, image):
if self.white_preprocess:
@ -430,12 +434,46 @@ class OcrWhiteLetterOnComplexBackground(Ocr):
image = cv2.merge([image, image, image])
return image
@staticmethod
def enlarge_box(box, min_box):
area = corner2area(box)
center = (int(x) for x in area_center(area))
size_x, size_y = area_size(area)
min_x, min_y = min_box
if size_x < min_x or size_y < min_y:
size_x = max(size_x, min_x) // 2
size_y = max(size_y, min_y) // 2
area = area_offset((-size_x, -size_y, size_x, size_y), center)
box = area2corner(area)
box = np.array([box[0], box[1], box[3], box[2]]).astype(np.float32)
return box
else:
return box
def enlarge_boxes(self, boxes):
if self.min_box is None:
return boxes
boxes = [self.enlarge_box(box, self.min_box) for box in boxes]
boxes = np.array(boxes)
return boxes
def detect_and_ocr(self, *args, **kwargs):
# Try hard to lower TextSystem.box_thresh
backup = self.model.text_detector.box_thresh
self.model.text_detector.box_thresh = 0.2
# Patch TextDetector
text_detector = self.model.text_detector
result = super().detect_and_ocr(*args, **kwargs)
def text_detector_with_min_box(*args, **kwargs):
dt_boxes, elapse = text_detector(*args, **kwargs)
dt_boxes = self.enlarge_boxes(dt_boxes)
return dt_boxes, elapse
self.model.text_detector.box_thresh = backup
self.model.text_detector = text_detector_with_min_box
try:
result = super().detect_and_ocr(*args, **kwargs)
finally:
self.model.text_detector.box_thresh = backup
self.model.text_detector = text_detector
return result

View File

@ -218,7 +218,7 @@ class AdaptiveScroll(Scroll):
"""
Args:
area (Button, tuple): A button or area of the whole scroll.
prominence (dict): Parameters passing to scipy.find_peaks
parameters (dict): Parameters passing to scipy.find_peaks
background (int):
is_vertical (bool): True if vertical, false if horizontal.
name (str):

View File

@ -151,6 +151,10 @@ class ProcessManager:
from tasks.base.daemon import Daemon
Daemon(config=config_name, task="Daemon").run()
elif func == "PlannerScan":
from tasks.planner.scan import PlannerScan
PlannerScan(config=config_name, task="PlannerScan").run()
else:
logger.critical(f"No function matched: {func}")
logger.info(f"[{config_name}] exited. Reason: Finish\n")

View File

@ -340,7 +340,7 @@ def product_stored_row(key, value):
def put_arg_stored(kwargs: T_Output_Kwargs) -> Output:
name: str = kwargs["name"]
kwargs["disabled"] = True
# kwargs["disabled"] = True
values = kwargs.pop("value", {})
value = values.pop("value", "")
@ -348,22 +348,26 @@ def put_arg_stored(kwargs: T_Output_Kwargs) -> Output:
time_ = values.pop("time", "")
if value != "" and total != "":
# 0 / 100
rows = [put_scope(f"dashboard-value-{name}", [
put_text(value).style("--dashboard-value--"),
put_text(f" / {total}").style("--dashboard-time--"),
])]
elif value != "":
# 100
rows = [put_scope(f"dashboard-value-{name}", [
put_text(value).style("--dashboard-value--")
])]
else:
# Empty
rows = []
# Add other key-value in stored
if values:
rows += [
put_scope(f"dashboard-value-{name}-{key}", product_stored_row(key, value))
for key, value in values.items() if value != ""
]
# Add time
if time_:
rows.append(
put_text(time_).style("--dashboard-time--")
@ -380,6 +384,36 @@ def put_arg_stored(kwargs: T_Output_Kwargs) -> Output:
]
)
def put_arg_planner(kwargs: T_Output_Kwargs) -> Output | None:
name: str = kwargs["name"]
values = kwargs.pop("value", {})
try:
progress = values["progress"]
except KeyError:
# Hide items not needed by the planner
return None
value = values.pop('value', 0)
if isinstance(value, dict):
value = tuple(value.values())
total = values.pop('total', 0)
if isinstance(total, dict):
total = tuple(total.values())
row = put_scope(f"arg_stored-stored-value-{name}", [
put_text(f"{progress:.2f}%").style("--dashboard-bold--"),
put_text(f"{value} / {total}").style("--dashboard-time--"),
])
return put_scope(
f"arg_container-planner-{name}",
[
get_title_help(kwargs),
row,
]
)
def put_arg_select(kwargs: T_Output_Kwargs) -> Output:
name: str = kwargs["name"]
@ -528,6 +562,7 @@ _widget_type_to_func: Dict[str, Callable] = {
"storage": put_arg_storage,
"state": put_arg_state,
"stored": put_arg_stored,
"planner": put_arg_planner,
}

View File

@ -1,5 +1,7 @@
from datetime import datetime
from datetime import datetime, timedelta
from module.config.stored.classes import now
from module.config.utils import get_server_next_update
from module.logger import logger
from tasks.assignment.claim import AssignmentClaim
from tasks.assignment.keywords import (KEYWORDS_ASSIGNMENT_GROUP,
@ -76,6 +78,12 @@ class Assignment(AssignmentClaim, SynthesizeUI):
# ValueError: min() arg is an empty sequence
logger.error('Empty dispatched list, delay 2 hours instead')
self.config.task_delay(minute=120)
# Check future daily
if now() > get_server_next_update(self.config.Scheduler_ServerUpdate) - timedelta(minutes=110) \
and KEYWORDS_DAILY_QUEST.Dispatch_1_assignments in quests:
logger.error(
"Assigment is scheduled tomorrow but today's assignment daily haven't been finished yet")
self.config.task_call('DailyQuest')
def _check_inlist(self, assignments: list[AssignmentEntry], duration: int):
"""

View File

@ -0,0 +1,102 @@
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 ```
ITEM_AMOUNT = ButtonWrapper(
name='ITEM_AMOUNT',
share=Button(
file='./assets/share/combat/obtain/ITEM_AMOUNT.png',
area=(190, 521, 490, 539),
search=(170, 501, 510, 559),
color=(195, 190, 188),
button=(190, 521, 490, 539),
),
)
ITEM_CLOSE = ButtonWrapper(
name='ITEM_CLOSE',
share=Button(
file='./assets/share/combat/obtain/ITEM_CLOSE.png',
area=(1043, 185, 1073, 215),
search=(1023, 165, 1093, 235),
color=(170, 170, 170),
button=(1043, 185, 1073, 215),
),
)
ITEM_NAME = ButtonWrapper(
name='ITEM_NAME',
share=Button(
file='./assets/share/combat/obtain/ITEM_NAME.png',
area=(495, 187, 855, 211),
search=(475, 167, 875, 231),
color=(176, 177, 179),
button=(495, 187, 855, 211),
),
)
MAY_OBTAIN = ButtonWrapper(
name='MAY_OBTAIN',
cn=Button(
file='./assets/cn/combat/obtain/MAY_OBTAIN.png',
area=(813, 379, 893, 397),
search=(812, 373, 895, 468),
color=(63, 71, 87),
button=(813, 379, 893, 397),
),
en=Button(
file='./assets/en/combat/obtain/MAY_OBTAIN.png',
area=(813, 379, 922, 397),
search=(813, 373, 923, 468),
color=(53, 61, 78),
button=(813, 379, 922, 397),
),
)
OBTAIN_1 = ButtonWrapper(
name='OBTAIN_1',
share=Button(
file='./assets/share/combat/obtain/OBTAIN_1.png',
area=(813, 414, 877, 478),
search=(793, 394, 897, 498),
color=(118, 96, 131),
button=(813, 414, 877, 478),
),
)
OBTAIN_2 = ButtonWrapper(
name='OBTAIN_2',
share=Button(
file='./assets/share/combat/obtain/OBTAIN_2.png',
area=(889, 414, 953, 478),
search=(869, 394, 973, 498),
color=(71, 96, 145),
button=(889, 414, 953, 478),
),
)
OBTAIN_3 = ButtonWrapper(
name='OBTAIN_3',
share=Button(
file='./assets/share/combat/obtain/OBTAIN_3.png',
area=(965, 414, 1029, 478),
search=(945, 394, 1049, 498),
color=(76, 101, 109),
button=(965, 414, 1029, 478),
),
)
OBTAIN_4 = ButtonWrapper(
name='OBTAIN_4',
share=Button(
file='./assets/share/combat/obtain/OBTAIN_4.png',
area=(1041, 414, 1105, 478),
search=(1021, 394, 1125, 498),
color=(76, 101, 109),
button=(1041, 414, 1105, 478),
),
)
OBTAIN_TRAILBLAZE_EXP = ButtonWrapper(
name='OBTAIN_TRAILBLAZE_EXP',
share=Button(
file='./assets/share/combat/obtain/OBTAIN_TRAILBLAZE_EXP.png',
area=(827, 425, 860, 451),
search=(813, 349, 877, 589),
color=(167, 173, 194),
button=(827, 425, 860, 451),
),
)

View File

@ -5,15 +5,20 @@ from tasks.combat.assets.assets_combat_finish import COMBAT_AGAIN, COMBAT_EXIT
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
from tasks.combat.assets.assets_combat_team import COMBAT_TEAM_PREPARE, COMBAT_TEAM_SUPPORT
from tasks.combat.interact import CombatInteract
from tasks.combat.obtain import CombatObtain
from tasks.combat.prepare import CombatPrepare
from tasks.combat.skill import CombatSkill
from tasks.combat.state import CombatState
from tasks.combat.support import CombatSupport
from tasks.combat.team import CombatTeam
from tasks.dungeon.keywords import DungeonList
from tasks.map.control.joystick import MapControlJoystick
class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSupport, CombatSkill, MapControlJoystick):
class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSupport, CombatSkill, CombatObtain,
MapControlJoystick):
dungeon: DungeonList | None = None
def handle_combat_prepare(self):
"""
Returns:
@ -120,6 +125,10 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
self.interval_reset(COMBAT_PREPARE)
self.map_A_timer.reset()
if self.appear(COMBAT_PREPARE, interval=2):
if self.obtained_is_full(self.dungeon, wave_done=self.combat_wave_done):
# Update stamina so task can be delayed if both obtained_is_full and stamina exhausted
self.combat_get_trailblaze_power()
return False
if not self.handle_combat_prepare():
return False
self.device.click(COMBAT_PREPARE)
@ -185,13 +194,17 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
in: COMBAT_AGAIN
"""
current = self.combat_get_trailblaze_power(expect_reduce=self.combat_wave_cost > 0)
# Planner
logger.attr('obtain_frequent_check', self.obtain_frequent_check)
if self.obtain_frequent_check:
logger.info('Exit combat to check obtained items')
return False
# Wave limit
if self.combat_wave_limit:
if self.combat_wave_done + self.combat_waves > self.combat_wave_limit:
logger.info(f'Combat wave limit: {self.combat_wave_done}/{self.combat_wave_limit}, '
f'can not run again')
return False
# Cost limit
if self.combat_wave_cost == 10:
if current >= self.combat_wave_cost * self.combat_waves:
@ -216,6 +229,19 @@ class Combat(CombatInteract, CombatPrepare, CombatState, CombatTeam, CombatSuppo
Returns:
bool: True to re-enter combat and run with another wave settings
"""
# Planner
logger.attr('obtain_frequent_check', self.obtain_frequent_check)
if self.obtain_frequent_check:
if self.config.stored.TrailblazePower.value >= self.combat_wave_cost \
and (self.combat_wave_limit and self.combat_wave_done < self.combat_wave_limit):
logger.info(f'Stall having some trailblaze power '
f'but wave limit reached {self.combat_wave_done}/{self.combat_wave_limit}, '
f'ignore obtain_frequent_check cause will reenter later')
return False
else:
logger.info('Re-enter combat to check obtained items')
return True
# Stamina
if self.config.stored.TrailblazePower.value < self.combat_wave_cost:
logger.info('Current trailblaze power is not enough for next run')
return False

304
tasks/combat/obtain.py Normal file
View File

@ -0,0 +1,304 @@
import re
from module.base.timer import Timer
from module.exception import ScriptError
from module.logger import logger
from module.ocr.ocr import Digit
from tasks.combat.assets.assets_combat_obtain import *
from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE
from tasks.dungeon.keywords import DungeonList
from tasks.planner.keywords import ITEM_CLASSES, KEYWORDS_ITEM_CURRENCY
from tasks.planner.model import ObtainedAmmount, PlannerMixin
from tasks.planner.scan import OcrItemName
class OcrItemAmount(Digit):
def format_result(self, result):
res = re.split(r'[:;]', result)
result = res[-1]
return super().format_result(result)
class CombatObtain(PlannerMixin):
"""
Parse items that can be obtained from dungeon
Pages:
in: COMBAT_PREPARE
"""
# False to click again when combat ends
# True to exit and reenter to get obtained items
obtain_frequent_check = False
def _obtain_enter(self, entry, appear_button, skip_first_screenshot=True):
"""
Args:
entry: Item entry
appear_button:
skip_first_screenshot:
Pages:
in: COMBAT_PREPARE
out: ITEM_CLOSE
"""
logger.info(f'Obtain enter {entry}')
self.interval_clear(appear_button)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if self.appear(ITEM_CLOSE):
break
if self.appear(appear_button, interval=2):
self.device.click(entry)
self.interval_reset(appear_button)
continue
# Wait animation
timeout = Timer(1.4, count=7).start()
skip_first_screenshot = True
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if self.image_color_count(ITEM_NAME, color=(0, 0, 0), threshold=221, count=50):
break
if timeout.reached():
logger.warning('Wait obtain item timeout')
break
def _obtain_close(self, check_button, skip_first_screenshot=True):
"""
Args:
check_button:
skip_first_screenshot:
Pages:
in: ITEM_CLOSE
out: COMBAT_PREPARE
"""
logger.info(f'Obtain close')
self.interval_clear(ITEM_CLOSE)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if not self.appear(ITEM_CLOSE):
if callable(check_button):
if check_button():
break
else:
if self.appear(check_button):
break
if self.appear_then_click(ITEM_CLOSE, interval=2):
continue
@staticmethod
def _obtain_get_entry(dungeon: DungeonList, index: int = 1, prev: ObtainedAmmount = None, start: int = 0):
"""
Args:
dungeon: Current dungeon
index: 1 to 3, index to check
prev: Previous item checked
Returns:
int: Item entry index, or None if no more check needed
"""
if (index > 1 and prev is None) or (index <= 1 and prev is not None):
raise ScriptError(f'_obtain_get_entry: index and prev must be set together, index={index}, prev={prev}')
if index > 3:
return None
def may_obtain_one():
if prev is None:
if start:
return 1 + start
else:
return 1
else:
return None
def may_obtain_multi():
if prev is None:
if start:
return 1 + start
else:
return 1
# End at the item with the lowest rarity
if prev.item.is_rarity_green:
return None
# End at credict
if prev.item == KEYWORDS_ITEM_CURRENCY.Credit:
return None
if start:
return index + start
else:
return index
if dungeon is None:
return may_obtain_multi()
if dungeon.is_Echo_of_War:
return may_obtain_one()
if dungeon.is_Cavern_of_Corrosion:
return None
if dungeon.is_Stagnant_Shadow:
return may_obtain_one()
if dungeon.is_Calyx_Golden:
if dungeon.is_Calyx_Golden_Treasures:
return may_obtain_one()
else:
return may_obtain_multi()
if dungeon.is_Calyx_Crimson:
return may_obtain_multi()
raise ScriptError(f'_obtain_get_entry: Cannot get entry from {dungeon}')
def _obtain_parse(self) -> ObtainedAmmount | None:
"""
Pages:
in: ITEM_CLOSE
"""
ocr = OcrItemName(ITEM_NAME)
item = ocr.matched_single_line(self.device.image, keyword_classes=ITEM_CLASSES)
ocr = OcrItemAmount(ITEM_AMOUNT)
amount = ocr.ocr_single_line(self.device.image)
if item is None:
logger.warning('_obtain_parse: Unknown item name')
return None
# logger.info(f'ObtainedAmmount: item={item}, value={amount}')
return ObtainedAmmount(
item=item,
value=amount,
)
def obtain_get(self, dungeon=None, skip_first_screenshot=True) -> list[ObtainedAmmount]:
"""
Args:
dungeon: Current dungeon,
or None for no early stop optimization
skip_first_screenshot:
Returns:
list[ObtainedAmmount]:
Pages:
in: COMBAT_PREPARE
out: COMBAT_PREPARE
"""
logger.hr('Obtain get', level=2)
if not skip_first_screenshot:
self.device.screenshot()
index = 1
prev = None
items = []
dic_entry = {
1: OBTAIN_1,
2: OBTAIN_2,
3: OBTAIN_3,
4: OBTAIN_4,
}
self._find_may_obtain()
trailblaze_exp = False
for _ in range(5):
if not trailblaze_exp and self.appear(OBTAIN_TRAILBLAZE_EXP):
trailblaze_exp = True
logger.attr('trailblaze_exp', trailblaze_exp)
entry_index = self._obtain_get_entry(dungeon, index=index, prev=prev, start=int(trailblaze_exp))
if entry_index is None:
logger.info('Obtain get end')
break
try:
entry = dic_entry[entry_index]
except KeyError:
logger.error(f'No obtain entry for {entry_index}')
break
self._obtain_enter(entry, appear_button=COMBAT_PREPARE)
item = self._obtain_parse()
if item.item == KEYWORDS_ITEM_CURRENCY.Trailblaze_EXP:
logger.warning('Trailblaze_EXP is in obtain list, OBTAIN_TRAILBLAZE_EXP may need to verify')
index += 1
prev = item
elif item is not None:
items.append(item)
index += 1
prev = item
self._obtain_close(check_button=MAY_OBTAIN)
logger.hr('Obtained Result')
for item in items:
# Pretend everything is full
# item.value += 1000
logger.info(f'Obtained item: {item.item.name}, {item.value}')
"""
<<< OBTAIN GET RESULT >>>
ItemAmount: Arrow_of_the_Starchaser, 15
ItemAmount: Arrow_of_the_Demon_Slayer, 68
ItemAmount: Arrow_of_the_Beast_Hunter, 85
"""
self.planner.load_obtained_amount(items)
self.planner_write()
return items
def obtained_is_full(self, dungeon: DungeonList | None, wave_done=0) -> bool:
if dungeon is None:
self.obtain_frequent_check = False
return False
row = self.planner.row_come_from_dungeon(dungeon)
if row is None:
self.obtain_frequent_check = False
return False
# Update
self.obtain_get(dungeon)
# Check progress
row = self.planner.row_come_from_dungeon(dungeon)
if row is None:
logger.error(f'obtained_is_full: Row disappeared after obtain_get')
self.obtain_frequent_check = False
return False
if not row.need_farm():
logger.info('Planner row full')
self.obtain_frequent_check = False
return True
# obtain_frequent_check
approaching = row.is_approaching_total(wave_done)
logger.attr('is_approaching_total', approaching)
self.obtain_frequent_check = approaching
return False
def _find_may_obtain(self, skip_first_screenshot=True):
logger.info('Find may obtain')
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if MAY_OBTAIN.match_template(self.device.image):
OBTAIN_1.load_offset(MAY_OBTAIN)
OBTAIN_2.load_offset(MAY_OBTAIN)
OBTAIN_3.load_offset(MAY_OBTAIN)
OBTAIN_4.load_offset(MAY_OBTAIN)
return True
if __name__ == '__main__':
self = CombatObtain('src')
self.device.screenshot()
self.obtain_get()

View File

@ -1,8 +1,11 @@
from datetime import timedelta
import cv2
import numpy as np
from module.base.timer import Timer
from module.base.utils import crop
from module.config.utils import DEFAULT_TIME, get_server_next_update
from module.logger import logger
from module.ocr.ocr import Ocr, OcrResultButton
from module.ocr.utils import split_and_pair_buttons
@ -165,6 +168,7 @@ class DailyQuestUI(DungeonUI, RouteLoader):
"""
self.claimed_point_reward will be set if claimed any point reward
"""
def get_active():
for b in [
ACTIVE_POINTS_1_UNLOCK,
@ -244,7 +248,7 @@ class DailyQuestUI(DungeonUI, RouteLoader):
int: Number of quests done
"""
logger.hr('Recognize quests', level=1)
quests = self.daily_quests_recognition()
quests = self.config.stored.DailyQuest.load_quests()
done = 0
logger.hr('Do quests', level=1)
@ -313,6 +317,62 @@ class DailyQuestUI(DungeonUI, RouteLoader):
return done
def check_future_achieve(self):
"""
Returns:
bool: True if daily activity will full, skip doing normal quests
False if daily activity will not full, do normal quests
"""
point = self.config.stored.DailyActivity.value
if point >= self.config.stored.DailyActivity.FIXED_TOTAL:
logger.warning('DailyActivity full, no need to check future')
return True
quests = self.config.stored.DailyQuest.load_quests()
if not len(quests):
logger.warning('DailyQuest empty, cannot check future')
return True
# Get task schedule
assignment = self.config.cross_get('Assignment.Scheduler.NextRun', default=DEFAULT_TIME)
dungeon = self.config.cross_get('Dungeon.Scheduler.NextRun', default=DEFAULT_TIME)
reset = get_server_next_update(self.config.Scheduler_ServerUpdate)
logger.info(f'Assignment next run: {assignment}')
logger.info(f'Dungeon next run: {dungeon}')
logger.info(f'Daily reset: {reset}')
# Calculate quests to be done in the future
future = 0
if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_Support_Characters_1_times in quests:
# 10min in advance to do quests
if dungeon < reset - timedelta(minutes=10):
logger.info('Daily support can be achieved in the future')
future += 200
else:
logger.info('Daily support cannot achieved, dungeon task is scheduled tomorrow')
if KEYWORDS_DAILY_QUEST.Consume_120_Trailblaze_Power in quests:
# 12h10min in advance, waiting for stamina
if dungeon < reset - timedelta(hours=12, minutes=10):
logger.info('Stamina consume can be achieved in the future')
future += 200
else:
logger.info('Stamina consume cannot achieved, dungeon task is scheduled tomorrow')
if KEYWORDS_DAILY_QUEST.Dispatch_1_assignments in quests:
# 10min in advance to do quests
if assignment < reset - timedelta(minutes=10):
logger.info('Assignment can be achieved in the future')
future += 100
else:
logger.info('Assignment cannot achieved, assignment task is scheduled tomorrow')
# Check
logger.attr('Future daily activity', future)
if point + future >= self.config.stored.DailyActivity.FIXED_TOTAL:
logger.info('Daily activity will full, skip doing normal quests')
return True
else:
logger.info('Daily activity will not full, do normal quests')
return False
def run(self):
self.config.update_battle_pass_quests()
self.claimed_point_reward = False
@ -322,6 +382,10 @@ class DailyQuestUI(DungeonUI, RouteLoader):
got = self.get_daily_rewards()
if got:
break
self.daily_quests_recognition()
future = self.check_future_achieve()
if future:
break
done = self.do_daily_quests()
if not done:
logger.info('No more quests able to do')

View File

@ -75,7 +75,9 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
if relic == 0:
return 0
# Combat
self.dungeon = dungeon
count = self.combat(team=team, wave_limit=wave_limit, support_character=support_character)
self.dungeon = None
# Update quest states
with self.config.multi_set():
@ -131,7 +133,7 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
# Support quest
if support_character is not None:
self.called_daily_support = True
if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_Support_Characters_1_times:
if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_Support_Characters_1_times in self.daily_quests:
logger.info('Achieve daily quest Obtain_victory_in_combat_with_Support_Characters_1_times')
self.achieved_daily_quest = True
# Stamina quest
@ -222,26 +224,21 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
self.config.stored.DungeonDouble.rogue = rogue
# Run double events
ran_calyx_golden = False
ran_calyx_crimson = False
ran_cavern_of_corrosion = False
planner = self.planner.get_dungeon(double_calyx=True)
# Double calyx
if self.config.stored.DungeonDouble.calyx > 0:
logger.info('Run double calyx')
dungeon = DungeonList.find(self.config.Dungeon_NameAtDoubleCalyx)
if planner is not None:
dungeon = planner
self.running_double = True
if self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.calyx):
if dungeon.is_Calyx_Golden:
ran_calyx_golden = True
if dungeon.is_Calyx_Crimson:
ran_calyx_crimson = True
self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.calyx)
# Double relic
if self.config.stored.DungeonDouble.relic > 0:
logger.info('Run double relic')
dungeon = DungeonList.find(self.config.Dungeon_NameAtDoubleRelic)
self.running_double = True
if self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.relic):
ran_cavern_of_corrosion = True
self.dungeon_run(dungeon=dungeon, wave_limit=self.config.stored.DungeonDouble.relic)
self.running_double = False
# Dungeon to clear all trailblaze power
@ -254,43 +251,11 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
and self.config.stored.DungeonDouble.rogue > 0:
logger.info('Going to use stamina in double rogue event')
do_rogue = True
if do_rogue:
final = KEYWORDS_DUNGEON_LIST.Simulated_Universe_World_1
else:
final = DungeonList.find(self.config.Dungeon_Name)
# Run dungeon that required by daily quests
# Calyx_Golden
if KEYWORDS_DAILY_QUEST.Clear_Calyx_Golden_1_times in self.daily_quests \
and self.config.DungeonDaily_CalyxGolden != 'do_not_achieve' \
and not final.is_Calyx_Golden \
and not ran_calyx_golden:
logger.info('Run Calyx_Golden once')
dungeon = DungeonList.find(self.config.DungeonDaily_CalyxGolden)
self.dungeon_run(dungeon=dungeon, wave_limit=1)
# Calyx_Crimson
if KEYWORDS_DAILY_QUEST.Clear_Calyx_Crimson_1_times in self.daily_quests \
and self.config.DungeonDaily_CalyxCrimson != 'do_not_achieve' \
and not final.is_Calyx_Crimson \
and not ran_calyx_crimson:
logger.info('Run Calyx_Crimson once')
dungeon = DungeonList.find(self.config.DungeonDaily_CalyxCrimson)
self.dungeon_run(dungeon=dungeon, wave_limit=1)
# Stagnant_Shadow
if KEYWORDS_DAILY_QUEST.Clear_Stagnant_Shadow_1_times in self.daily_quests \
and self.config.DungeonDaily_StagnantShadow != 'do_not_achieve' \
and not final.is_Stagnant_Shadow:
logger.info('Run Stagnant_Shadow once')
dungeon = DungeonList.find(self.config.DungeonDaily_StagnantShadow)
self.dungeon_run(dungeon=dungeon, wave_limit=1)
# Cavern_of_Corrosion
if KEYWORDS_DAILY_QUEST.Clear_Cavern_of_Corrosion_1_times in self.daily_quests \
and self.config.DungeonDaily_CavernOfCorrosion != 'do_not_achieve' \
and not final.is_Cavern_of_Corrosion \
and not ran_cavern_of_corrosion:
logger.info('Run Cavern_of_Corrosion once')
dungeon = DungeonList.find(self.config.DungeonDaily_CavernOfCorrosion)
self.dungeon_run(dungeon=dungeon, wave_limit=1)
final = DungeonList.find(self.config.Dungeon_Name)
# Planner
planner = self.planner.get_dungeon()
if planner is not None:
final = planner
# Check daily
if self.achieved_daily_quest:
@ -301,11 +266,11 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
self.config.task_stop()
# Use all stamina
if final.is_Simulated_Universe:
if do_rogue:
# Use support if prioritize rogue
if self.require_compulsory_support():
logger.info('Run dungeon with support once as stamina is rogue prioritized')
self.dungeon_run(dungeon=DungeonList.find(self.config.Dungeon_Name), wave_limit=1)
self.dungeon_run(dungeon=final, wave_limit=1)
# Store immersifiers
logger.info('Prioritize stamina for simulated universe, skip dungeon')
amount = 0
@ -337,6 +302,12 @@ class Dungeon(DungeonStamina, DungeonEvent, Combat):
# Check daily
if self.achieved_daily_quest:
self.config.task_call('DailyQuest')
else:
# Check future daily
if KEYWORDS_DAILY_QUEST.Obtain_victory_in_combat_with_Support_Characters_1_times in self.daily_quests:
logger.error("Dungeon ran but support daily haven't been finished yet")
self.config.task_call('DailyQuest')
# Delay tasks
self.dungeon_stamina_delay(dungeon)

View File

@ -20,6 +20,7 @@ class DungeonTab(Keyword):
class DungeonList(Keyword):
instances: ClassVar = {}
dungeon_id: int
plane_id: int
@cached_property
@ -183,6 +184,20 @@ class DungeonList(Keyword):
else:
return ''
@classmethod
def find_dungeon_id(cls, dungeon_id):
"""
Args:
dungeon_id:
Returns:
DungeonList: DungeonList object or None
"""
for instance in cls.instances.values():
if instance.dungeon_id == dungeon_id:
return instance
return None
@classmethod
def find_dungeon_by_string(cls, cn='', en='', **kwargs):
"""
@ -225,7 +240,6 @@ class DungeonList(Keyword):
return None
@dataclass(repr=False)
class DungeonEntrance(Keyword):
instances: ClassVar = {}

View File

@ -11,6 +11,7 @@ Calyx_Golden_Memories_Jarilo_VI = DungeonList(
en='Bud of Memories (Jarilo-Ⅵ)',
jp='回憶の蕾・ヤリーロ-Ⅵ',
es='Flor de los recuerdos (Jarilo-Ⅵ)',
dungeon_id=1001,
plane_id=2010101,
)
Calyx_Golden_Aether_Jarilo_VI = DungeonList(
@ -21,6 +22,7 @@ Calyx_Golden_Aether_Jarilo_VI = DungeonList(
en='Bud of Aether (Jarilo-Ⅵ)',
jp='エーテルの蕾・ヤリーロ-Ⅵ',
es='Flor de éter (Jarilo-Ⅵ)',
dungeon_id=1002,
plane_id=2011101,
)
Calyx_Golden_Treasures_Jarilo_VI = DungeonList(
@ -31,6 +33,7 @@ Calyx_Golden_Treasures_Jarilo_VI = DungeonList(
en='Bud of Treasures (Jarilo-Ⅵ)',
jp='秘蔵の蕾・ヤリーロ-Ⅵ',
es='Flor de tesoros (Jarilo-Ⅵ)',
dungeon_id=1003,
plane_id=2012101,
)
Calyx_Golden_Memories_The_Xianzhou_Luofu = DungeonList(
@ -41,6 +44,7 @@ Calyx_Golden_Memories_The_Xianzhou_Luofu = DungeonList(
en='Bud of Memories (The Xianzhou Luofu)',
jp='回憶の蕾・仙舟「羅浮」',
es='Flor de los recuerdos (El Luofu de Xianzhou)',
dungeon_id=1011,
plane_id=2021101,
)
Calyx_Golden_Aether_The_Xianzhou_Luofu = DungeonList(
@ -51,6 +55,7 @@ Calyx_Golden_Aether_The_Xianzhou_Luofu = DungeonList(
en='Bud of Aether (The Xianzhou Luofu)',
jp='エーテルの蕾・仙舟「羅浮」',
es='Flor de éter (El Luofu de Xianzhou)',
dungeon_id=1012,
plane_id=2022101,
)
Calyx_Golden_Treasures_The_Xianzhou_Luofu = DungeonList(
@ -61,6 +66,7 @@ Calyx_Golden_Treasures_The_Xianzhou_Luofu = DungeonList(
en='Bud of Treasures (The Xianzhou Luofu)',
jp='秘蔵の蕾・仙舟「羅浮」',
es='Flor de tesoros (El Luofu de Xianzhou)',
dungeon_id=1013,
plane_id=2022201,
)
Calyx_Golden_Memories_Penacony = DungeonList(
@ -71,6 +77,7 @@ Calyx_Golden_Memories_Penacony = DungeonList(
en='Bud of Memories (Penacony)',
jp='回憶の蕾・ピノコニー',
es='Flor de los recuerdos (Colonipenal)',
dungeon_id=1014,
plane_id=2031301,
)
Calyx_Golden_Aether_Penacony = DungeonList(
@ -81,6 +88,7 @@ Calyx_Golden_Aether_Penacony = DungeonList(
en='Bud of Aether (Penacony)',
jp='エーテルの蕾・ピノコニー',
es='Flor de éter (Colonipenal)',
dungeon_id=1015,
plane_id=2031201,
)
Calyx_Golden_Treasures_Penacony = DungeonList(
@ -91,6 +99,7 @@ Calyx_Golden_Treasures_Penacony = DungeonList(
en='Bud of Treasures (Penacony)',
jp='秘蔵の蕾・ピノコニー',
es='Flor de tesoros (Colonipenal)',
dungeon_id=1016,
plane_id=2031101,
)
Calyx_Crimson_Destruction_Herta_StorageZone = DungeonList(
@ -101,6 +110,7 @@ Calyx_Crimson_Destruction_Herta_StorageZone = DungeonList(
en='Calyx (Crimson): Bud of Destruction',
jp='疑似花萼(赤)・壊滅の蕾',
es='Flor de la Destrucción',
dungeon_id=1004,
plane_id=2000201,
)
Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape = DungeonList(
@ -111,6 +121,7 @@ Calyx_Crimson_Destruction_Luofu_ScalegorgeWaterscape = DungeonList(
en='Calyx (Crimson): Bud of Destruction',
jp='疑似花萼(赤)・壊滅の蕾',
es='Flor de la Destrucción',
dungeon_id=1018,
plane_id=2023201,
)
Calyx_Crimson_Preservation_Herta_SupplyZone = DungeonList(
@ -121,6 +132,7 @@ Calyx_Crimson_Preservation_Herta_SupplyZone = DungeonList(
en='Calyx (Crimson): Bud of Preservation',
jp='疑似花萼(赤)・存護の蕾',
es='Flor de la Conservación',
dungeon_id=1005,
plane_id=2000301,
)
Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark = DungeonList(
@ -131,6 +143,7 @@ Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark = DungeonList(
en='Calyx (Crimson): Bud of Preservation',
jp='疑似花萼(赤)・存護の蕾',
es='Flor de la Conservación',
dungeon_id=1020,
plane_id=2032101,
)
Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains = DungeonList(
@ -141,6 +154,7 @@ Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains = DungeonList(
en='Calyx (Crimson): Bud of The Hunt',
jp='疑似花萼(赤)・巡狩の蕾',
es='Flor de la Cacería',
dungeon_id=1006,
plane_id=2010101,
)
Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue = DungeonList(
@ -151,6 +165,7 @@ Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue = DungeonList(
en='Calyx (Crimson): Bud of The Hunt',
jp='疑似花萼(赤)・巡狩の蕾',
es='Cáliz (carmesí): Flor de la Cacería',
dungeon_id=1022,
plane_id=2033101,
)
Calyx_Crimson_Abundance_Jarilo_BackwaterPass = DungeonList(
@ -161,6 +176,7 @@ Calyx_Crimson_Abundance_Jarilo_BackwaterPass = DungeonList(
en='Calyx (Crimson): Bud of Abundance',
jp='疑似花萼(赤)・豊穣の蕾',
es='Flor de la Abundancia',
dungeon_id=1007,
plane_id=2011101,
)
Calyx_Crimson_Abundance_Luofu_FyxestrollGarden = DungeonList(
@ -171,6 +187,7 @@ Calyx_Crimson_Abundance_Luofu_FyxestrollGarden = DungeonList(
en='Calyx (Crimson): Bud of Abundance',
jp='疑似花萼(赤)・豊穣の蕾',
es='Flor de la Abundancia',
dungeon_id=1021,
plane_id=2022301,
)
Calyx_Crimson_Erudition_Jarilo_RivetTown = DungeonList(
@ -181,6 +198,7 @@ Calyx_Crimson_Erudition_Jarilo_RivetTown = DungeonList(
en='Calyx (Crimson): Bud of Erudition',
jp='疑似花萼(赤)・知恵の蕾',
es='Flor de la Erudición',
dungeon_id=1008,
plane_id=2012201,
)
Calyx_Crimson_Harmony_Jarilo_RobotSettlement = DungeonList(
@ -191,6 +209,7 @@ Calyx_Crimson_Harmony_Jarilo_RobotSettlement = DungeonList(
en='Calyx (Crimson): Bud of Harmony',
jp='疑似花萼(赤)・調和の蕾',
es='Flor de la Armonía',
dungeon_id=1009,
plane_id=2012301,
)
Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape = DungeonList(
@ -201,6 +220,7 @@ Calyx_Crimson_Harmony_Penacony_TheReverieDreamscape = DungeonList(
en='Calyx (Crimson): Bud of Harmony',
jp='疑似花萼(赤)・調和の蕾',
es='Flor de la Armonía',
dungeon_id=1019,
plane_id=2031101,
)
Calyx_Crimson_Nihility_Jarilo_GreatMine = DungeonList(
@ -211,6 +231,7 @@ Calyx_Crimson_Nihility_Jarilo_GreatMine = DungeonList(
en='Calyx (Crimson): Bud of Nihility',
jp='疑似花萼(赤)・虚無の蕾',
es='Flor de la Nihilidad',
dungeon_id=1010,
plane_id=2012101,
)
Calyx_Crimson_Nihility_Luofu_AlchemyCommission = DungeonList(
@ -221,6 +242,7 @@ Calyx_Crimson_Nihility_Luofu_AlchemyCommission = DungeonList(
en='Calyx (Crimson): Bud of Nihility',
jp='疑似花萼(赤)・虚無の蕾',
es='Flor de la Nihilidad',
dungeon_id=1017,
plane_id=2023101,
)
Stagnant_Shadow_Quanta = DungeonList(
@ -231,6 +253,7 @@ Stagnant_Shadow_Quanta = DungeonList(
en='Stagnant Shadow: Shape of Quanta',
jp='凝結虚影・虚海の形',
es='Forma del cuanto',
dungeon_id=1101,
plane_id=2000101,
)
Stagnant_Shadow_Gust = DungeonList(
@ -241,6 +264,7 @@ Stagnant_Shadow_Gust = DungeonList(
en='Stagnant Shadow: Shape of Gust',
jp='凝結虚影・薫風の形',
es='Forma del aire',
dungeon_id=1102,
plane_id=2012201,
)
Stagnant_Shadow_Fulmination = DungeonList(
@ -251,6 +275,7 @@ Stagnant_Shadow_Fulmination = DungeonList(
en='Stagnant Shadow: Shape of Fulmination',
jp='凝結虚影・鳴雷の形',
es='Forma del trueno',
dungeon_id=1103,
plane_id=2013201,
)
Stagnant_Shadow_Blaze = DungeonList(
@ -261,6 +286,7 @@ Stagnant_Shadow_Blaze = DungeonList(
en='Stagnant Shadow: Shape of Blaze',
jp='凝結虚影・炎華の形',
es='Forma de las llamas',
dungeon_id=1104,
plane_id=2013101,
)
Stagnant_Shadow_Spike = DungeonList(
@ -271,6 +297,7 @@ Stagnant_Shadow_Spike = DungeonList(
en='Stagnant Shadow: Shape of Spike',
jp='凝結虚影・切先の形',
es='Forma afilada',
dungeon_id=1105,
plane_id=2012101,
)
Stagnant_Shadow_Rime = DungeonList(
@ -281,6 +308,7 @@ Stagnant_Shadow_Rime = DungeonList(
en='Stagnant Shadow: Shape of Rime',
jp='凝結虚影・霜晶の形',
es='Forma de la escarcha',
dungeon_id=1106,
plane_id=2013201,
)
Stagnant_Shadow_Mirage = DungeonList(
@ -291,6 +319,7 @@ Stagnant_Shadow_Mirage = DungeonList(
en='Stagnant Shadow: Shape of Mirage',
jp='凝結虚影・幻光の形',
es='Forma del espejismo',
dungeon_id=1107,
plane_id=2011101,
)
Stagnant_Shadow_Icicle = DungeonList(
@ -301,6 +330,7 @@ Stagnant_Shadow_Icicle = DungeonList(
en='Stagnant Shadow: Shape of Icicle',
jp='凝結虚影・氷柱の形',
es='Forma del témpano',
dungeon_id=1108,
plane_id=2021101,
)
Stagnant_Shadow_Doom = DungeonList(
@ -311,6 +341,7 @@ Stagnant_Shadow_Doom = DungeonList(
en='Stagnant Shadow: Shape of Doom',
jp='凝結虚影・震厄の形',
es='Forma de la perdición',
dungeon_id=1109,
plane_id=2021201,
)
Stagnant_Shadow_Puppetry = DungeonList(
@ -321,6 +352,7 @@ Stagnant_Shadow_Puppetry = DungeonList(
en='Stagnant Shadow: Shape of Puppetry',
jp='凝結虚影・傀儡の形',
es='Forma de las marionetas',
dungeon_id=1110,
plane_id=2022201,
)
Stagnant_Shadow_Abomination = DungeonList(
@ -331,6 +363,7 @@ Stagnant_Shadow_Abomination = DungeonList(
en='Stagnant Shadow: Shape of Abomination',
jp='凝結虚影・厄獣の形',
es='Forma de la abominación',
dungeon_id=1111,
plane_id=2023201,
)
Stagnant_Shadow_Scorch = DungeonList(
@ -341,6 +374,7 @@ Stagnant_Shadow_Scorch = DungeonList(
en='Stagnant Shadow: Shape of Scorch',
jp='凝結虚影・燔灼の形',
es='Forma abrasada',
dungeon_id=1112,
plane_id=2012101,
)
Stagnant_Shadow_Celestial = DungeonList(
@ -351,6 +385,7 @@ Stagnant_Shadow_Celestial = DungeonList(
en='Stagnant Shadow: Shape of Celestial',
jp='凝結虚影・天人の形',
es='Forma de lo celestial',
dungeon_id=1113,
plane_id=2023101,
)
Stagnant_Shadow_Perdition = DungeonList(
@ -361,6 +396,7 @@ Stagnant_Shadow_Perdition = DungeonList(
en='Stagnant Shadow: Shape of Perdition',
jp='凝結虚影・幽府の形',
es='Forma del aislamiento',
dungeon_id=1114,
plane_id=2022301,
)
Stagnant_Shadow_Nectar = DungeonList(
@ -371,6 +407,7 @@ Stagnant_Shadow_Nectar = DungeonList(
en='Stagnant Shadow: Shape of Nectar',
jp='凝結虚影・氷醸の形',
es='Forma del néctar',
dungeon_id=1115,
plane_id=2031101,
)
Stagnant_Shadow_Roast = DungeonList(
@ -381,6 +418,7 @@ Stagnant_Shadow_Roast = DungeonList(
en='Stagnant Shadow: Shape of Roast',
jp='凝結虚影・焦灼の形',
es='Forma del agostamiento',
dungeon_id=1116,
plane_id=2031301,
)
Stagnant_Shadow_Ire = DungeonList(
@ -391,6 +429,7 @@ Stagnant_Shadow_Ire = DungeonList(
en='Stagnant Shadow: Shape of Ire',
jp='凝結虚影・憤怒の形',
es='Forma de la ira',
dungeon_id=1117,
plane_id=2032201,
)
Stagnant_Shadow_Duty = DungeonList(
@ -401,6 +440,7 @@ Stagnant_Shadow_Duty = DungeonList(
en='Stagnant Shadow: Shape of Duty',
jp='凝結虚影・職掌の形',
es='Sombra paralizada: Forma del deber',
dungeon_id=1118,
plane_id=2032101,
)
Cavern_of_Corrosion_Path_of_Gelid_Wind = DungeonList(
@ -411,6 +451,7 @@ Cavern_of_Corrosion_Path_of_Gelid_Wind = DungeonList(
en='Cavern of Corrosion: Path of Gelid Wind',
jp='侵蝕トンネル・霜風の路',
es='Senda del viento gélido',
dungeon_id=1201,
plane_id=2000201,
)
Cavern_of_Corrosion_Path_of_Jabbing_Punch = DungeonList(
@ -421,6 +462,7 @@ Cavern_of_Corrosion_Path_of_Jabbing_Punch = DungeonList(
en='Cavern of Corrosion: Path of Jabbing Punch',
jp='侵蝕トンネル・迅拳の路',
es='Senda de los puños rápidos',
dungeon_id=1202,
plane_id=2013101,
)
Cavern_of_Corrosion_Path_of_Drifting = DungeonList(
@ -431,6 +473,7 @@ Cavern_of_Corrosion_Path_of_Drifting = DungeonList(
en='Cavern of Corrosion: Path of Drifting',
jp='侵蝕トンネル・漂泊の路',
es='Senda de la deriva',
dungeon_id=1203,
plane_id=2013201,
)
Cavern_of_Corrosion_Path_of_Providence = DungeonList(
@ -441,6 +484,7 @@ Cavern_of_Corrosion_Path_of_Providence = DungeonList(
en='Cavern of Corrosion: Path of Providence',
jp='侵蝕トンネル・睿治の路',
es='Senda de la providencia',
dungeon_id=1204,
plane_id=2013401,
)
Cavern_of_Corrosion_Path_of_Holy_Hymn = DungeonList(
@ -451,6 +495,7 @@ Cavern_of_Corrosion_Path_of_Holy_Hymn = DungeonList(
en='Cavern of Corrosion: Path of Holy Hymn',
jp='侵蝕トンネル・聖頌の路',
es='Senda del himno sagrado',
dungeon_id=1205,
plane_id=2021101,
)
Cavern_of_Corrosion_Path_of_Conflagration = DungeonList(
@ -461,6 +506,7 @@ Cavern_of_Corrosion_Path_of_Conflagration = DungeonList(
en='Cavern of Corrosion: Path of Conflagration',
jp='侵蝕トンネル・野焔の路',
es='Senda de la conflagración',
dungeon_id=1206,
plane_id=2021201,
)
Cavern_of_Corrosion_Path_of_Elixir_Seekers = DungeonList(
@ -471,6 +517,7 @@ Cavern_of_Corrosion_Path_of_Elixir_Seekers = DungeonList(
en='Cavern of Corrosion: Path of Elixir Seekers',
jp='侵蝕トンネル・薬使の路',
es='Senda de los elixires',
dungeon_id=1207,
plane_id=2023101,
)
Cavern_of_Corrosion_Path_of_Darkness = DungeonList(
@ -481,6 +528,7 @@ Cavern_of_Corrosion_Path_of_Darkness = DungeonList(
en='Cavern of Corrosion: Path of Darkness',
jp='侵蝕トンネル・幽冥の路',
es='Senda de la oscuridad',
dungeon_id=1208,
plane_id=2022301,
)
Cavern_of_Corrosion_Path_of_Dreamdive = DungeonList(
@ -491,6 +539,7 @@ Cavern_of_Corrosion_Path_of_Dreamdive = DungeonList(
en='Cavern of Corrosion: Path of Dreamdive',
jp='侵蝕トンネル・夢潜の路',
es='Senda de los sueños',
dungeon_id=1209,
plane_id=2031101,
)
Echo_of_War_Destruction_Beginning = DungeonList(
@ -501,6 +550,7 @@ Echo_of_War_Destruction_Beginning = DungeonList(
en="Echo of War: Destruction's Beginning",
jp='歴戦余韻・壊滅の始まり',
es='El principio de la Destrucción',
dungeon_id=1301,
plane_id=2000301,
)
Echo_of_War_End_of_the_Eternal_Freeze = DungeonList(
@ -511,6 +561,7 @@ Echo_of_War_End_of_the_Eternal_Freeze = DungeonList(
en='Echo of War: End of the Eternal Freeze',
jp='歴戦余韻・寒波の幕切れ',
es='El fin del Hielo Eterno',
dungeon_id=1302,
plane_id=2013401,
)
Echo_of_War_Divine_Seed = DungeonList(
@ -521,6 +572,7 @@ Echo_of_War_Divine_Seed = DungeonList(
en='Echo of War: Divine Seed',
jp='歴戦余韻・不死の神実',
es='Semilla divina',
dungeon_id=1303,
plane_id=2023201,
)
Echo_of_War_Borehole_Planet_Old_Crater = DungeonList(
@ -531,6 +583,7 @@ Echo_of_War_Borehole_Planet_Old_Crater = DungeonList(
en="Echo of War: Borehole Planet's Old Crater",
jp='歴戦余韻・星を蝕む往日の面影',
es='Cráter del planeta devorado',
dungeon_id=1304,
plane_id=2000401,
)
Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList(
@ -541,6 +594,7 @@ Echo_of_War_Salutations_of_Ashen_Dreams = DungeonList(
en='Echo of War: Salutations of Ashen Dreams',
jp='歴戦余韻・現世の夢の礼賛',
es='Ecos de la guerra: Tributo del sueño ceniciento',
dungeon_id=1305,
plane_id=2033201,
)
Simulated_Universe_World_1 = DungeonList(
@ -551,6 +605,7 @@ Simulated_Universe_World_1 = DungeonList(
en='Simulated Universe: World 1',
jp='第一世界・模擬宇宙',
es='Mundo 1',
dungeon_id=110,
plane_id=100000104,
)
Simulated_Universe_World_3 = DungeonList(
@ -561,6 +616,7 @@ Simulated_Universe_World_3 = DungeonList(
en='Simulated Universe: World 3',
jp='第三世界・模擬宇宙',
es='Mundo 3',
dungeon_id=130,
plane_id=100000104,
)
Simulated_Universe_World_4 = DungeonList(
@ -571,6 +627,7 @@ Simulated_Universe_World_4 = DungeonList(
en='Simulated Universe: World 4',
jp='第四世界・模擬宇宙',
es='Mundo 4',
dungeon_id=140,
plane_id=100000104,
)
Simulated_Universe_World_5 = DungeonList(
@ -581,6 +638,7 @@ Simulated_Universe_World_5 = DungeonList(
en='Simulated Universe: World 5',
jp='第五世界・模擬宇宙',
es='Mundo 5',
dungeon_id=150,
plane_id=100000104,
)
Simulated_Universe_World_6 = DungeonList(
@ -591,6 +649,7 @@ Simulated_Universe_World_6 = DungeonList(
en='Simulated Universe: World 6',
jp='第六世界・模擬宇宙',
es='Mundo 6',
dungeon_id=160,
plane_id=100000104,
)
Simulated_Universe_World_7 = DungeonList(
@ -601,6 +660,7 @@ Simulated_Universe_World_7 = DungeonList(
en='Simulated Universe: World 7',
jp='第七世界・模擬宇宙',
es='Mundo 7',
dungeon_id=170,
plane_id=100000104,
)
Simulated_Universe_World_8 = DungeonList(
@ -611,6 +671,7 @@ Simulated_Universe_World_8 = DungeonList(
en='Simulated Universe: World 8',
jp='第八世界・模擬宇宙',
es='Mundo 8',
dungeon_id=180,
plane_id=100000104,
)
Simulated_Universe_World_9 = DungeonList(
@ -621,6 +682,7 @@ Simulated_Universe_World_9 = DungeonList(
en='Simulated Universe: World 9',
jp='第九世界・模擬宇宙',
es='Mundo 9',
dungeon_id=190,
plane_id=100000104,
)
Simulated_Universe_The_Swarm_Disaster = DungeonList(
@ -631,6 +693,7 @@ Simulated_Universe_The_Swarm_Disaster = DungeonList(
en='The Swarm Disaster',
jp='宇宙の蝗害',
es='La Plaga',
dungeon_id=-1,
plane_id=-1,
)
Simulated_Universe_Gold_and_Gears = DungeonList(
@ -641,6 +704,7 @@ Simulated_Universe_Gold_and_Gears = DungeonList(
en='Gold and Gears',
jp='黄金と機械',
es='Oro y maquinaria',
dungeon_id=-1,
plane_id=-1,
)
Memory_of_Chaos = DungeonList(
@ -651,6 +715,7 @@ Memory_of_Chaos = DungeonList(
en='Memory of Chaos',
jp='混沌の記憶',
es='Evocación caótica',
dungeon_id=-1,
plane_id=-1,
)
The_Voyage_of_Navis_Astriger = DungeonList(
@ -661,6 +726,7 @@ The_Voyage_of_Navis_Astriger = DungeonList(
en='The Voyage of Navis Astriger',
jp='天艟求仙放浪記',
es='El viaje de las naves astriger',
dungeon_id=-1,
plane_id=-1,
)
The_Last_Vestiges_of_Towering_Citadel = DungeonList(
@ -671,5 +737,6 @@ The_Last_Vestiges_of_Towering_Citadel = DungeonList(
en='The Last Vestiges of Towering Citadel',
jp='永屹の城の秘密',
es='Herencia de la Ciudadela Imponente',
dungeon_id=-1,
plane_id=-1,
)

View File

@ -51,6 +51,10 @@ class WeeklyDungeon(Dungeon):
self.weekly_quests = self.config.stored.BattlePassWeeklyQuest.load_quests()
dungeon = DungeonList.find(self.config.Weekly_Name)
planner = self.planner.get_weekly()
if planner is not None:
dungeon = planner
logger.attr('DungeonWeekly', dungeon)
# UI switches
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Survival_Index)

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 ```
ENTRY_ITEM_FROM = ButtonWrapper(
name='ENTRY_ITEM_FROM',
share=Button(
file='./assets/share/item/synthesize/ENTRY_ITEM_FROM.png',
area=(825, 408, 905, 488),
search=(805, 388, 925, 508),
color=(70, 105, 114),
button=(825, 408, 905, 488),
),
)
ENTRY_ITEM_TO = ButtonWrapper(
name='ENTRY_ITEM_TO',
share=Button(
file='./assets/share/item/synthesize/ENTRY_ITEM_TO.png',
area=(815, 193, 915, 293),
search=(795, 173, 935, 313),
color=(157, 143, 120),
button=(815, 193, 915, 293),
),
)
ITEM_NAME = ButtonWrapper(
name='ITEM_NAME',
share=Button(
file='./assets/share/item/synthesize/ITEM_NAME.png',
area=(657, 95, 1057, 118),
search=(637, 75, 1077, 138),
color=(92, 100, 111),
button=(657, 95, 1057, 118),
),
)
SWITCH_RARITY = ButtonWrapper(
name='SWITCH_RARITY',
share=Button(
file='./assets/share/item/synthesize/SWITCH_RARITY.png',
area=(1180, 262, 1216, 298),
search=(1160, 242, 1236, 318),
color=(89, 96, 124),
button=(1180, 262, 1216, 298),
),
)
SYNTHESIZE_AMOUNT = ButtonWrapper(
name='SYNTHESIZE_AMOUNT',
share=Button(
file='./assets/share/item/synthesize/SYNTHESIZE_AMOUNT.png',
area=(683, 548, 1034, 568),
search=(663, 528, 1054, 588),
color=(122, 132, 147),
button=(683, 548, 1034, 568),
),
)
SYNTHESIZE_MINUS = ButtonWrapper(
name='SYNTHESIZE_MINUS',
share=Button(
file='./assets/share/item/synthesize/SYNTHESIZE_MINUS.png',
area=(575, 569, 605, 589),
search=(555, 549, 625, 609),
color=(235, 235, 235),
button=(575, 569, 605, 589),
),
)
SYNTHESIZE_PLUS = ButtonWrapper(
name='SYNTHESIZE_PLUS',
share=Button(
file='./assets/share/item/synthesize/SYNTHESIZE_PLUS.png',
area=(1125, 567, 1155, 589),
search=(1105, 547, 1175, 609),
color=(228, 228, 228),
button=(1125, 567, 1155, 589),
),
)

211
tasks/item/synthesize.py Normal file
View File

@ -0,0 +1,211 @@
import cv2
import numpy as np
from module.base.timer import Timer
from module.base.utils import color_similarity_2d, crop, image_size
from module.logger import logger
from module.ocr.ocr import Ocr
from tasks.base.page import page_synthesize
from tasks.combat.obtain import CombatObtain
from tasks.item.assets.assets_item_synthesize import *
from tasks.planner.keywords import ITEM_CLASSES
from tasks.planner.model import ObtainedAmmount
from tasks.planner.scan import OcrItemName
RARITY_COLOR = {
'green': (68, 127, 124),
'blue': (76, 124, 191),
'purple': (141, 97, 203),
}
def image_color_count(image, color, threshold=221):
mask = color_similarity_2d(image, color=color)
cv2.inRange(mask, threshold, 255, dst=mask)
sum_ = cv2.countNonZero(mask)
return sum_
class WhiteStrip(Ocr):
def pre_process(self, image):
mask = color_similarity_2d(image, color=(255, 255, 255))
mask = cv2.inRange(mask, 180, 255, dst=mask)
mask = np.mean(mask, axis=0)
point = np.array(cv2.findNonZero(mask))[:, 0, 1]
x1, x2 = point.min(), point.max()
_, y = image_size(image)
image = crop(image, (x1 - 5, 0, x2 + 5, y), copy=False)
return image
class SynthesizeItemName(OcrItemName, WhiteStrip):
pass
class Synthesize(CombatObtain):
def item_get_rarity(self, button) -> str | None:
"""
Args:
button:
Returns:
str: Rarity color or None if no match
Pages:
in: page_synthesize
"""
image = self.image_crop(button)
image = cv2.GaussianBlur(image, (3, 3), 0)
x2, y2 = image_size(image)
y1 = y2 - int(y2 // 4)
image = crop(image, (0, y1, x2, y2))
# self.device.image_show(image)
# print(image.shape)
# Must contain 30% target color at icon bottom
minimum = x2 * (y2 - y1) * 0.3
for rarity, color in RARITY_COLOR.items():
count = image_color_count(image, color=color, threshold=221)
# print(rarity, count, minimum)
if count > minimum:
return rarity
return None
def item_get_rarity_retry(self, button, skip_first_screenshot=True) -> str | None:
timeout = Timer(1, count=3).start()
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# End
current = self.item_get_rarity(button)
logger.attr('SynthesizeRarity', current)
if current is not None:
return current
if timeout.reached():
logger.warning(f'item_get_rarity_retry timeout')
return None
def synthesize_rarity_set(self, rarity: str, skip_first_screenshot=True) -> bool:
"""
Args:
rarity: "green" or "blue"
note that rarity is the one you consume to synthesize
skip_first_screenshot:
Returns:
bool: If switched
Pages:
in: page_synthesize
"""
logger.info(f'item_synthesize_rarity_set: {rarity}')
switched = False
self.interval_clear(page_synthesize.check_button)
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# End
current = self.item_get_rarity(ENTRY_ITEM_FROM)
logger.attr('SynthesizeRarity', current)
if current is not None and current == rarity:
break
# Click
if self.ui_page_appear(page_synthesize, interval=2):
self.device.click(SWITCH_RARITY)
switched = True
continue
return switched
def synthesize_rarity_reset(self, skip_first_screenshot=True):
"""
Reset rarity switch, so current item will pin on the first row
Returns:
bool: If success
"""
current = self.item_get_rarity_retry(ENTRY_ITEM_FROM)
if current == 'blue':
r1, r2 = 'green', 'blue'
elif current == 'green':
r1, r2 = 'blue', 'green'
else:
logger.error(f'item_synthesize_rarity_reset: Unknown current rarity {current}')
return False
self.synthesize_rarity_set(r1, skip_first_screenshot=skip_first_screenshot)
self.synthesize_rarity_set(r2, skip_first_screenshot=True)
return True
def synthesize_obtain_get(self) -> list[ObtainedAmmount]:
"""
Update item amount from synthesize page
"""
logger.hr('Synthesize obtain get', level=2)
items = []
def obtain_end():
return self.item_get_rarity(ENTRY_ITEM_FROM) is not None
# Purple
self.synthesize_rarity_set('blue')
self._obtain_enter(ENTRY_ITEM_TO, appear_button=page_synthesize.check_button)
item = self._obtain_parse()
if item is not None:
items.append(item)
self._obtain_close(check_button=obtain_end)
# Blue
self._obtain_enter(ENTRY_ITEM_FROM, appear_button=page_synthesize.check_button)
item = self._obtain_parse()
if item is not None:
items.append(item)
self._obtain_close(check_button=obtain_end)
# Green
self.synthesize_rarity_set('green')
self._obtain_enter(ENTRY_ITEM_FROM, appear_button=page_synthesize.check_button)
item = self._obtain_parse()
if item is not None:
items.append(item)
self._obtain_close(check_button=obtain_end)
logger.hr('Obtained Result')
for item in items:
# Pretend everything is full
# item.value += 1000
logger.info(f'Obtained item: {item.item.name}, {item.value}')
"""
<<< OBTAIN GET RESULT >>>
ItemAmount: Arrow_of_the_Starchaser, 15
ItemAmount: Arrow_of_the_Demon_Slayer, 68
ItemAmount: Arrow_of_the_Beast_Hunter, 85
"""
self.planner.load_obtained_amount(items)
self.planner_write()
return items
def synthesize_get_item(self):
ocr = SynthesizeItemName(ITEM_NAME)
item = ocr.matched_single_line(self.device.image, keyword_classes=ITEM_CLASSES)
if item is None:
logger.warning('synthesize_get_item: Unknown item name')
return None
return item
if __name__ == '__main__':
self = Synthesize('src')
self.device.screenshot()
self.synthesize_obtain_get()

View File

@ -0,0 +1,65 @@
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 ```
CALCULATE_TITLE = ButtonWrapper(
name='CALCULATE_TITLE',
share=Button(
file='./assets/share/planner/result/CALCULATE_TITLE.png',
area=(264, 187, 346, 207),
search=(244, 167, 366, 227),
color=(141, 143, 161),
button=(264, 187, 346, 207),
),
)
DETAIL_TITLE = ButtonWrapper(
name='DETAIL_TITLE',
share=Button(
file='./assets/share/planner/result/DETAIL_TITLE.png',
area=(263, 402, 346, 422),
search=(243, 382, 366, 442),
color=(144, 146, 164),
button=(263, 402, 346, 422),
),
)
MATERIAL_TITLE = ButtonWrapper(
name='MATERIAL_TITLE',
share=Button(
file='./assets/share/planner/result/MATERIAL_TITLE.png',
area=(263, 194, 346, 214),
search=(243, 174, 366, 234),
color=(135, 136, 156),
button=(263, 194, 346, 214),
),
)
OCR_RESULT = ButtonWrapper(
name='OCR_RESULT',
share=Button(
file='./assets/share/planner/result/OCR_RESULT.png',
area=(331, 104, 1201, 592),
search=(311, 84, 1221, 612),
color=(254, 254, 254),
button=(331, 104, 1201, 592),
),
)
RESULT_CHECK = ButtonWrapper(
name='RESULT_CHECK',
share=Button(
file='./assets/share/planner/result/RESULT_CHECK.png',
area=(263, 128, 324, 148),
search=(255, 103, 355, 593),
color=(132, 119, 92),
button=(263, 128, 324, 148),
),
)
RESULT_SCROLL = ButtonWrapper(
name='RESULT_SCROLL',
share=Button(
file='./assets/share/planner/result/RESULT_SCROLL.png',
area=(1238, 104, 1245, 592),
search=(1218, 84, 1265, 612),
color=(33, 44, 62),
button=(1238, 104, 1245, 592),
),
)

View File

@ -0,0 +1,20 @@
from typing import Union
# Import order matters, DO NOT optimize imports
# 1
import tasks.planner.keywords.item_currency as KEYWORDS_ITEM_CURRENCY
# 2
import tasks.planner.keywords.item_exp as KEYWORDS_ITEM_EXP
# 3
import tasks.planner.keywords.item_ascension as KEYWORDS_ITEM_ASCENSION
# 4
import tasks.planner.keywords.item_trace as KEYWORDS_ITEM_TRACE
# 5
import tasks.planner.keywords.item_weekly as KEYWORDS_ITEM_WEEKLY
# 6
import tasks.planner.keywords.item_calyx as KEYWORDS_ITEM_CALYX
from tasks.planner.keywords.classes import ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly
ITEM_CLASSES = [ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly]
ITEM_TYPES = Union[ItemAscension, ItemCalyx, ItemCurrency, ItemExp, ItemTrace, ItemWeekly]

View File

@ -0,0 +1,152 @@
from dataclasses import dataclass
from functools import cached_property
from typing import ClassVar
from module.exception import ScriptError
from module.ocr.keyword import Keyword
@dataclass(repr=False)
class ItemBase(Keyword):
instances: ClassVar = {}
rarity: str
item_id: int
item_group: int
dungeon_id: int
def __post_init__(self):
self.__class__.instances[self.name] = self
@classmethod
def find_name(cls, name):
"""
Args:
name: Attribute name of keyword.
Returns:
Keyword instance.
Raises:
ScriptError: If nothing found.
"""
if isinstance(name, Keyword):
return name
try:
return cls.instances[name]
except KeyError:
# Not found
raise ScriptError(f'Cannot find a {cls.__name__} instance that matches "{name}"')
@cached_property
def dungeon(self):
"""
Dungeon that drops this item
Returns:
DungeonList: DungeonList object or None
"""
if self.dungeon_id > 0:
from tasks.dungeon.keywords.classes import DungeonList
return DungeonList.find_dungeon_id(self.dungeon_id)
else:
return None
@cached_property
def is_rarity_gold(self):
return self.rarity == 'SuperRare'
@cached_property
def is_rarity_purple(self):
return self.rarity == 'VeryRare'
@cached_property
def is_rarity_blue(self):
return self.rarity == 'Rare'
@cached_property
def is_rarity_green(self):
return self.rarity == 'NotNormal'
@cached_property
def is_ItemAscension(self):
return self.__class__.__name__ == 'ItemAscension'
@cached_property
def is_ItemCalyx(self):
return self.__class__.__name__ == 'ItemCalyx'
@cached_property
def is_ItemCurrency(self):
return self.__class__.__name__ == 'ItemCurrency'
@cached_property
def is_ItemExp(self):
return self.__class__.__name__ == 'ItemExp'
@cached_property
def is_ItemTrace(self):
return self.__class__.__name__ == 'ItemTrace'
@cached_property
def is_ItemWeekly(self):
return self.__class__.__name__ == 'ItemWeekly'
@cached_property
def group_base(self):
if not self.has_group_base:
return self
if self.item_group <= 0:
raise ScriptError(f'Item {self} has no item_group defined, cannot find group_base')
for instance in self.__class__.instances.values():
if instance.item_group == self.item_group and instance.is_rarity_purple:
return instance
raise ScriptError(f'Item {self} has no group_base')
@cached_property
def has_group_base(self):
return self.is_ItemCalyx or self.is_ItemExp or self.is_ItemTrace
@cached_property
def is_group_base(self):
if self.has_group_base:
return self.is_rarity_purple
else:
return True
"""
Sub item genres don't have `instances` defined, so all objects are in ItemBase.instances
"""
@dataclass(repr=False)
class ItemAscension(ItemBase):
pass
@dataclass(repr=False)
class ItemCalyx(ItemBase):
pass
@dataclass(repr=False)
class ItemCurrency(ItemBase):
pass
@dataclass(repr=False)
class ItemExp(ItemBase):
pass
@dataclass(repr=False)
class ItemTrace(ItemBase):
pass
@dataclass(repr=False)
class ItemWeekly(ItemBase):
pass

View File

@ -0,0 +1,239 @@
from .classes import ItemAscension
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Broken_Teeth_of_Iron_Wolf = ItemAscension(
id=1,
name='Broken_Teeth_of_Iron_Wolf',
cn='铁狼碎齿',
cht='鐵狼碎齒',
en='Broken Teeth of Iron Wolf',
jp='鉄狼の砕けた刃',
es='Dientes rotos del huargo férreo',
rarity='VeryRare',
item_id=110401,
item_group=1100,
dungeon_id=1105,
)
Endotherm_Chitin = ItemAscension(
id=2,
name='Endotherm_Chitin',
cn='恒温晶壳',
cht='恆溫晶殼',
en='Endotherm Chitin',
jp='恒温晶殻',
es='Quitina endoterma',
rarity='VeryRare',
item_id=110402,
item_group=1100,
dungeon_id=1104,
)
Horn_of_Snow = ItemAscension(
id=3,
name='Horn_of_Snow',
cn='风雪之角',
cht='風雪之角',
en='Horn of Snow',
jp='吹雪の角',
es='Cuerno de nieve',
rarity='VeryRare',
item_id=110403,
item_group=1100,
dungeon_id=1106,
)
Lightning_Crown_of_the_Past_Shadow = ItemAscension(
id=4,
name='Lightning_Crown_of_the_Past_Shadow',
cn='往日之影的雷冠',
cht='往日之影的雷冠',
en='Lightning Crown of the Past Shadow',
jp='過去の影の雷冠',
es='Corona de rayos de la sombra del pasado',
rarity='VeryRare',
item_id=110404,
item_group=1100,
dungeon_id=1103,
)
Storm_Eye = ItemAscension(
id=5,
name='Storm_Eye',
cn='暴风之眼',
cht='暴風之眼',
en='Storm Eye',
jp='暴風の眼',
es='Ojo del vendaval',
rarity='VeryRare',
item_id=110405,
item_group=1100,
dungeon_id=1102,
)
Void_Cast_Iron = ItemAscension(
id=6,
name='Void_Cast_Iron',
cn='虚幻铸铁',
cht='虛幻鑄鐵',
en='Void Cast Iron',
jp='虚幻鋳鉄',
es='Hierro forjado del Vacío',
rarity='VeryRare',
item_id=110406,
item_group=1100,
dungeon_id=1101,
)
Golden_Crown_of_the_Past_Shadow = ItemAscension(
id=7,
name='Golden_Crown_of_the_Past_Shadow',
cn='往日之影的金饰',
cht='往日之影的金飾',
en='Golden Crown of the Past Shadow',
jp='過去の影の金装飾',
es='Corona dorada de la sombra del pasado',
rarity='VeryRare',
item_id=110407,
item_group=1100,
dungeon_id=1107,
)
Netherworld_Token = ItemAscension(
id=8,
name='Netherworld_Token',
cn='幽府通令',
cht='幽府通令',
en='Netherworld Token',
jp='幽府通令',
es='Pase del inframundo',
rarity='VeryRare',
item_id=110411,
item_group=1100,
dungeon_id=1114,
)
Searing_Steel_Blade = ItemAscension(
id=9,
name='Searing_Steel_Blade',
cn='过热钢刃',
cht='過熱鋼刃',
en='Searing Steel Blade',
jp='灼熱の鋼刃',
es='Hoja de acero sobrecalentado',
rarity='VeryRare',
item_id=110412,
item_group=1100,
dungeon_id=1112,
)
Gelid_Chitin = ItemAscension(
id=10,
name='Gelid_Chitin',
cn='苦寒晶壳',
cht='苦寒晶殼',
en='Gelid Chitin',
jp='苦寒晶殻',
es='Quitina gélida',
rarity='VeryRare',
item_id=110413,
item_group=1100,
dungeon_id=1108,
)
Shape_Shifter_Lightning_Staff = ItemAscension(
id=11,
name='Shape_Shifter_Lightning_Staff',
cn='炼形者雷枝',
cht='煉形者雷枝',
en="Shape Shifter's Lightning Staff",
jp='鍛錬者の雷枝',
es='Báculo del Cambiaformas',
rarity='VeryRare',
item_id=110414,
item_group=1100,
dungeon_id=1109,
)
Ascendant_Debris = ItemAscension(
id=12,
name='Ascendant_Debris',
cn='天人遗垢',
cht='天人遺垢',
en='Ascendant Debris',
jp='天人の残穢',
es='Vestigios elevados',
rarity='VeryRare',
item_id=110415,
item_group=1100,
dungeon_id=1113,
)
Nail_of_the_Ape = ItemAscension(
id=13,
name='Nail_of_the_Ape',
cn='苍猿之钉',
cht='蒼猿之釘',
en='Nail of the Ape',
jp='蒼猿の釘',
es='Clavo del simio',
rarity='VeryRare',
item_id=110416,
item_group=1100,
dungeon_id=1111,
)
Suppressing_Edict = ItemAscension(
id=14,
name='Suppressing_Edict',
cn='镇灵敕符',
cht='鎮靈敕符',
en='Suppressing Edict',
jp='霊鎮めの勅符',
es='Edicto de la contención',
rarity='VeryRare',
item_id=110417,
item_group=1100,
dungeon_id=1110,
)
IPC_Work_Permit = ItemAscension(
id=15,
name='IPC_Work_Permit',
cn='星际和平工作证',
cht='星際和平工作證',
en='IPC Work Permit',
jp='スターピース社員証',
es='Permiso de trabajo de la Corporación',
rarity='VeryRare',
item_id=110421,
item_group=1100,
dungeon_id=1118,
)
Raging_Heart = ItemAscension(
id=16,
name='Raging_Heart',
cn='忿火之心',
cht='忿火之心',
en='Raging Heart',
jp='憤怒の心',
es='Corazón ardiente',
rarity='VeryRare',
item_id=110422,
item_group=1100,
dungeon_id=1117,
)
Dream_Fridge = ItemAscension(
id=17,
name='Dream_Fridge',
cn='冷藏梦箱',
cht='冷藏夢箱',
en='Dream Fridge',
jp='夢の冷蔵庫',
es='Nevera de sueños',
rarity='VeryRare',
item_id=110423,
item_group=1100,
dungeon_id=1115,
)
Dream_Flamer = ItemAscension(
id=18,
name='Dream_Flamer',
cn='炙梦喷枪',
cht='炙夢噴槍',
en='Dream Flamer',
jp='夢を炙るトーチバーナー',
es='Soplete de ensueño',
rarity='VeryRare',
item_id=110426,
item_group=1100,
dungeon_id=1116,
)

View File

@ -0,0 +1,317 @@
from .classes import ItemCalyx
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Extinguished_Core = ItemCalyx(
id=1,
name='Extinguished_Core',
cn='熄灭原核',
cht='熄滅原核',
en='Extinguished Core',
jp='消滅した原核',
es='Núcleo apagado',
rarity='NotNormal',
item_id=111001,
item_group=1401,
dungeon_id=1011,
)
Glimmering_Core = ItemCalyx(
id=2,
name='Glimmering_Core',
cn='微光原核',
cht='微光原核',
en='Glimmering Core',
jp='微かに光る原核',
es='Núcleo reluciente',
rarity='Rare',
item_id=111002,
item_group=1401,
dungeon_id=1011,
)
Squirming_Core = ItemCalyx(
id=3,
name='Squirming_Core',
cn='蠢动原核',
cht='蠢動原核',
en='Squirming Core',
jp='脈動する原核',
es='Núcleo serpenteante',
rarity='VeryRare',
item_id=111003,
item_group=1401,
dungeon_id=1011,
)
Thief_Instinct = ItemCalyx(
id=4,
name='Thief_Instinct',
cn='掠夺的本能',
cht='掠奪的本能',
en="Thief's Instinct",
jp='略奪の本能',
es='Instinto del ladrón',
rarity='NotNormal',
item_id=111011,
item_group=1402,
dungeon_id=1001,
)
Usurper_Scheme = ItemCalyx(
id=5,
name='Usurper_Scheme',
cn='篡改的野心',
cht='篡改的野心',
en="Usurper's Scheme",
jp='改ざんの野心',
es='Ambición distorsionada',
rarity='Rare',
item_id=111012,
item_group=1402,
dungeon_id=1001,
)
Conqueror_Will = ItemCalyx(
id=6,
name='Conqueror_Will',
cn='践踏的意志',
cht='踐踏的意志',
en="Conqueror's Will",
jp='踏みにじる意志',
es='Voluntad de conquista',
rarity='VeryRare',
item_id=111013,
item_group=1402,
dungeon_id=1001,
)
Silvermane_Badge = ItemCalyx(
id=7,
name='Silvermane_Badge',
cn='铁卫扣饰',
cht='鐵衛扣飾',
en='Silvermane Badge',
jp='シルバーメインの釦',
es='Pin del guardia',
rarity='NotNormal',
item_id=112001,
item_group=1403,
dungeon_id=1001,
)
Silvermane_Insignia = ItemCalyx(
id=8,
name='Silvermane_Insignia',
cn='铁卫军徽',
cht='鐵衛軍徽',
en='Silvermane Insignia',
jp='シルバーメインの記章',
es='Insignia del guardia',
rarity='Rare',
item_id=112002,
item_group=1403,
dungeon_id=1001,
)
Silvermane_Medal = ItemCalyx(
id=9,
name='Silvermane_Medal',
cn='铁卫勋章',
cht='鐵衛勳章',
en='Silvermane Medal',
jp='シルバーメインの勲章',
es='Medalla del guardia',
rarity='VeryRare',
item_id=112003,
item_group=1403,
dungeon_id=1001,
)
Ancient_Part = ItemCalyx(
id=10,
name='Ancient_Part',
cn='古代零件',
cht='古代零件',
en='Ancient Part',
jp='古代パーツ',
es='Componente antiguo',
rarity='NotNormal',
item_id=112011,
item_group=1404,
dungeon_id=1001,
)
Ancient_Spindle = ItemCalyx(
id=11,
name='Ancient_Spindle',
cn='古代转轴',
cht='古代轉軸',
en='Ancient Spindle',
jp='古代シャフト',
es='Eje antiguo',
rarity='Rare',
item_id=112012,
item_group=1404,
dungeon_id=1001,
)
Ancient_Engine = ItemCalyx(
id=12,
name='Ancient_Engine',
cn='古代引擎',
cht='古代引擎',
en='Ancient Engine',
jp='古代エンジン',
es='Motor antiguo',
rarity='VeryRare',
item_id=112013,
item_group=1404,
dungeon_id=1001,
)
Immortal_Scionette = ItemCalyx(
id=13,
name='Immortal_Scionette',
cn='永寿幼芽',
cht='永壽幼芽',
en='Immortal Scionette',
jp='永寿の萌芽',
es='Brote verde inmortal',
rarity='NotNormal',
item_id=113001,
item_group=1405,
dungeon_id=1011,
)
Immortal_Aeroblossom = ItemCalyx(
id=14,
name='Immortal_Aeroblossom',
cn='永寿天华',
cht='永壽天華',
en='Immortal Aeroblossom',
jp='永寿の天華',
es='Flor etérea inmortal',
rarity='Rare',
item_id=113002,
item_group=1405,
dungeon_id=1011,
)
Immortal_Lumintwig = ItemCalyx(
id=15,
name='Immortal_Lumintwig',
cn='永寿荣枝',
cht='永壽榮枝',
en='Immortal Lumintwig',
jp='永寿の栄枝',
es='Rama gloriosa inmortal',
rarity='VeryRare',
item_id=113003,
item_group=1405,
dungeon_id=1011,
)
Artifex_Module = ItemCalyx(
id=16,
name='Artifex_Module',
cn='工造机杼',
cht='工造機杼',
en="Artifex's Module",
jp='工造機関',
es='Componente artificial mecánico',
rarity='NotNormal',
item_id=113011,
item_group=1406,
dungeon_id=1011,
)
Artifex_Cogwheel = ItemCalyx(
id=17,
name='Artifex_Cogwheel',
cn='工造迴轮',
cht='工造迴輪',
en="Artifex's Cogwheel",
jp='工造迴輪',
es='Engranaje cilíndrico mecánico',
rarity='Rare',
item_id=113012,
item_group=1406,
dungeon_id=1011,
)
Artifex_Gyreheart = ItemCalyx(
id=18,
name='Artifex_Gyreheart',
cn='工造浑心',
cht='工造渾心',
en="Artifex's Gyreheart",
jp='工造渾心',
es='Corazón armonioso mecánico',
rarity='VeryRare',
item_id=113013,
item_group=1406,
dungeon_id=1011,
)
Dream_Collection_Component = ItemCalyx(
id=19,
name='Dream_Collection_Component',
cn='蓄梦元件',
cht='蓄夢元件',
en='Dream Collection Component',
jp='ドリームコレクションパーツ',
es='Componente del acumulador de sueños',
rarity='NotNormal',
item_id=114001,
item_group=1407,
dungeon_id=1014,
)
Dream_Flow_Valve = ItemCalyx(
id=20,
name='Dream_Flow_Valve',
cn='流梦阀门',
cht='流夢閥門',
en='Dream Flow Valve',
jp='ドリームフローバルブ',
es='Válvula del flujo de sueños',
rarity='Rare',
item_id=114002,
item_group=1407,
dungeon_id=1014,
)
Dream_Making_Engine = ItemCalyx(
id=21,
name='Dream_Making_Engine',
cn='造梦马达',
cht='造夢馬達',
en='Dream Making Engine',
jp='ドリームメイキングモーター',
es='Motor creasueños',
rarity='VeryRare',
item_id=114003,
item_group=1407,
dungeon_id=1014,
)
Tatters_of_Thought = ItemCalyx(
id=22,
name='Tatters_of_Thought',
cn='思绪末屑',
cht='思緒末屑',
en='Tatters of Thought',
jp='思考の粉末',
es='Jirones de pensamientos',
rarity='NotNormal',
item_id=114011,
item_group=1408,
dungeon_id=1014,
)
Fragments_of_Impression = ItemCalyx(
id=23,
name='Fragments_of_Impression',
cn='印象残晶',
cht='印象殘晶',
en='Fragments of Impression',
jp='印象の残晶',
es='Fragmento de impresiones',
rarity='Rare',
item_id=114012,
item_group=1408,
dungeon_id=1014,
)
Shards_of_Desires = ItemCalyx(
id=24,
name='Shards_of_Desires',
cn='欲念碎镜',
cht='欲念碎鏡',
en='Shards of Desires',
jp='砕けた欲望の鏡',
es='Fragmento de deseos',
rarity='VeryRare',
item_id=114013,
item_group=1408,
dungeon_id=1014,
)

View File

@ -0,0 +1,31 @@
from .classes import ItemCurrency
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Credit = ItemCurrency(
id=1,
name='Credit',
cn='信用点',
cht='信用點',
en='Credit',
jp='信用ポイント',
es='Crédito',
rarity='Rare',
item_id=2,
item_group=0,
dungeon_id=-1,
)
Trailblaze_EXP = ItemCurrency(
id=2,
name='Trailblaze_EXP',
cn='里程',
cht='里程',
en='Trailblaze EXP',
jp='マイレージ',
es='EXP trazacaminos',
rarity='Rare',
item_id=22,
item_group=0,
dungeon_id=-1,
)

View File

@ -0,0 +1,122 @@
from .classes import ItemExp
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Travel_Encounters = ItemExp(
id=1,
name='Travel_Encounters',
cn='旅情见闻',
cht='旅情見聞',
en='Travel Encounters',
jp='旅の見聞',
es='Noticias del viaje',
rarity='NotNormal',
item_id=211,
item_group=1010,
dungeon_id=1001,
)
Adventure_Log = ItemExp(
id=2,
name='Adventure_Log',
cn='冒险记录',
cht='冒險紀錄',
en='Adventure Log',
jp='冒険記録',
es='Registro de aventuras',
rarity='Rare',
item_id=212,
item_group=1010,
dungeon_id=1001,
)
Traveler_Guide = ItemExp(
id=3,
name='Traveler_Guide',
cn='漫游指南',
cht='漫遊指南',
en="Traveler's Guide",
jp='漫遊指南',
es='Guía del espíritu viajero',
rarity='VeryRare',
item_id=213,
item_group=1010,
dungeon_id=1001,
)
Sparse_Aether = ItemExp(
id=4,
name='Sparse_Aether',
cn='稀薄以太',
cht='稀薄乙太',
en='Sparse Aether',
jp='希薄なエーテル',
es='Éter disperso',
rarity='NotNormal',
item_id=221,
item_group=1020,
dungeon_id=1002,
)
Condensed_Aether = ItemExp(
id=5,
name='Condensed_Aether',
cn='凝缩以太',
cht='凝縮乙太',
en='Condensed Aether',
jp='濃縮エーテル',
es='Éter condensado',
rarity='Rare',
item_id=222,
item_group=1020,
dungeon_id=1002,
)
Refined_Aether = ItemExp(
id=6,
name='Refined_Aether',
cn='提纯以太',
cht='精煉乙太',
en='Refined Aether',
jp='精製エーテル',
es='Éter refinado',
rarity='VeryRare',
item_id=223,
item_group=1020,
dungeon_id=1002,
)
Lost_Lightdust = ItemExp(
id=7,
name='Lost_Lightdust',
cn='遗失光尘',
cht='遺失光塵',
en='Lost Lightdust',
jp='遺失光塵',
es='Polvo luminoso perdido',
rarity='NotNormal',
item_id=231,
item_group=1030,
dungeon_id=-1,
)
Lost_Gold_Fragment = ItemExp(
id=8,
name='Lost_Gold_Fragment',
cn='遗失碎金',
cht='遺失碎金',
en='Lost Gold Fragment',
jp='遺失砕金',
es='Fragmento dorado perdido',
rarity='Rare',
item_id=232,
item_group=1030,
dungeon_id=-1,
)
Lost_Crystal = ItemExp(
id=9,
name='Lost_Crystal',
cn='遗失晶块',
cht='遺失晶塊',
en='Lost Crystal',
jp='遺失晶塊',
es='Cristal perdido',
rarity='VeryRare',
item_id=233,
item_group=1030,
dungeon_id=-1,
)

View File

@ -0,0 +1,512 @@
from .classes import ItemTrace
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Shattered_Blade = ItemTrace(
id=1,
name='Shattered_Blade',
cn='破碎残刃',
cht='破碎殘刃',
en='Shattered Blade',
jp='破砕の刃切',
es='Espada rota',
rarity='NotNormal',
item_id=110111,
item_group=1201,
dungeon_id=1004,
)
Lifeless_Blade = ItemTrace(
id=2,
name='Lifeless_Blade',
cn='无生残刃',
cht='無生殘刃',
en='Lifeless Blade',
jp='無生の刃切',
es='Espada inerte',
rarity='Rare',
item_id=110112,
item_group=1201,
dungeon_id=1004,
)
Worldbreaker_Blade = ItemTrace(
id=3,
name='Worldbreaker_Blade',
cn='净世残刃',
cht='淨世殘刃',
en='Worldbreaker Blade',
jp='浄世の刃切',
es='Espada rompemundos',
rarity='VeryRare',
item_id=110113,
item_group=1201,
dungeon_id=1004,
)
Arrow_of_the_Beast_Hunter = ItemTrace(
id=4,
name='Arrow_of_the_Beast_Hunter',
cn='猎兽之矢',
cht='獵獸之矢',
en='Arrow of the Beast Hunter',
jp='狩獣の矢',
es='Flecha del cazabestias',
rarity='NotNormal',
item_id=110121,
item_group=1202,
dungeon_id=1006,
)
Arrow_of_the_Demon_Slayer = ItemTrace(
id=5,
name='Arrow_of_the_Demon_Slayer',
cn='屠魔之矢',
cht='屠魔之矢',
en='Arrow of the Demon Slayer',
jp='屠魔の矢',
es='Flecha del matademonios',
rarity='Rare',
item_id=110122,
item_group=1202,
dungeon_id=1006,
)
Arrow_of_the_Starchaser = ItemTrace(
id=6,
name='Arrow_of_the_Starchaser',
cn='逐星之矢',
cht='逐星之矢',
en='Arrow of the Starchaser',
jp='逐星の矢',
es='Flecha del persigueestrellas',
rarity='VeryRare',
item_id=110123,
item_group=1202,
dungeon_id=1006,
)
Key_of_Inspiration = ItemTrace(
id=7,
name='Key_of_Inspiration',
cn='灵感之钥',
cht='靈感之鑰',
en='Key of Inspiration',
jp='着想のカギ',
es='Llave de la inspiración',
rarity='NotNormal',
item_id=110131,
item_group=1203,
dungeon_id=1008,
)
Key_of_Knowledge = ItemTrace(
id=8,
name='Key_of_Knowledge',
cn='启迪之钥',
cht='啟迪之鑰',
en='Key of Knowledge',
jp='啓発のカギ',
es='Llave del conocimiento',
rarity='Rare',
item_id=110132,
item_group=1203,
dungeon_id=1008,
)
Key_of_Wisdom = ItemTrace(
id=9,
name='Key_of_Wisdom',
cn='智识之钥',
cht='智識之鑰',
en='Key of Wisdom',
jp='叡智のカギ',
es='Llave de la sabiduría',
rarity='VeryRare',
item_id=110133,
item_group=1203,
dungeon_id=1008,
)
Endurance_of_Bronze = ItemTrace(
id=10,
name='Endurance_of_Bronze',
cn='青铜的执着',
cht='青銅的執著',
en='Endurance of Bronze',
jp='青銅の執着',
es='Persistencia del bronce',
rarity='NotNormal',
item_id=110141,
item_group=1204,
dungeon_id=1005,
)
Oath_of_Steel = ItemTrace(
id=11,
name='Oath_of_Steel',
cn='寒铁的誓言',
cht='寒鐵的誓言',
en='Oath of Steel',
jp='寒鉄の誓い',
es='Juramento de acero',
rarity='Rare',
item_id=110142,
item_group=1204,
dungeon_id=1005,
)
Safeguard_of_Amber = ItemTrace(
id=12,
name='Safeguard_of_Amber',
cn='琥珀的坚守',
cht='琥珀的堅守',
en='Safeguard of Amber',
jp='琥珀の堅守',
es='Custodia de ámbar',
rarity='VeryRare',
item_id=110143,
item_group=1204,
dungeon_id=1005,
)
Obsidian_of_Dread = ItemTrace(
id=13,
name='Obsidian_of_Dread',
cn='黯淡黑曜',
cht='黯淡黑曜',
en='Obsidian of Dread',
jp='黯淡な黒曜',
es='Obsidiana lúgubre',
rarity='NotNormal',
item_id=110151,
item_group=1205,
dungeon_id=1010,
)
Obsidian_of_Desolation = ItemTrace(
id=14,
name='Obsidian_of_Desolation',
cn='虚空黑曜',
cht='虛空黑曜',
en='Obsidian of Desolation',
jp='虚空の黒曜',
es='Obsidiana del vacío',
rarity='Rare',
item_id=110152,
item_group=1205,
dungeon_id=1010,
)
Obsidian_of_Obsession = ItemTrace(
id=15,
name='Obsidian_of_Obsession',
cn='沉沦黑曜',
cht='沉淪黑曜',
en='Obsidian of Obsession',
jp='沈淪せし黒曜',
es='Obsidiana de la obsesión',
rarity='VeryRare',
item_id=110153,
item_group=1205,
dungeon_id=1010,
)
Harmonic_Tune = ItemTrace(
id=16,
name='Harmonic_Tune',
cn='谐乐小调',
cht='諧樂小調',
en='Harmonic Tune',
jp='調和のハーモニー',
es='Melodía armónica',
rarity='NotNormal',
item_id=110161,
item_group=1206,
dungeon_id=1009,
)
Ancestral_Hymn = ItemTrace(
id=17,
name='Ancestral_Hymn',
cn='家族颂歌',
cht='家族頌歌',
en='Ancestral Hymn',
jp='ファミリー賛歌',
es='Himno del acervo',
rarity='Rare',
item_id=110162,
item_group=1206,
dungeon_id=1009,
)
Stellaris_Symphony = ItemTrace(
id=18,
name='Stellaris_Symphony',
cn='群星乐章',
cht='群星樂章',
en='Stellaris Symphony',
jp='群星の楽章',
es='Opus Stellaris',
rarity='VeryRare',
item_id=110163,
item_group=1206,
dungeon_id=1009,
)
Seed_of_Abundance = ItemTrace(
id=19,
name='Seed_of_Abundance',
cn='丰饶之种',
cht='豐饒之種',
en='Seed of Abundance',
jp='豊穣の種',
es='Semilla de la abundancia',
rarity='NotNormal',
item_id=110171,
item_group=1207,
dungeon_id=1007,
)
Sprout_of_Life = ItemTrace(
id=20,
name='Sprout_of_Life',
cn='生命之芽',
cht='生命之芽',
en='Sprout of Life',
jp='生命の芽',
es='Brote de la vida',
rarity='Rare',
item_id=110172,
item_group=1207,
dungeon_id=1007,
)
Flower_of_Eternity = ItemTrace(
id=21,
name='Flower_of_Eternity',
cn='永恒之花',
cht='永恆之花',
en='Flower of Eternity',
jp='永久の花',
es='Flor de la eternidad',
rarity='VeryRare',
item_id=110173,
item_group=1207,
dungeon_id=1007,
)
Borisin_Teeth = ItemTrace(
id=22,
name='Borisin_Teeth',
cn='步离犬牙',
cht='步離犬牙',
en='Borisin Teeth',
jp='歩離の犬牙',
es='Canino de borisin',
rarity='NotNormal',
item_id=110181,
item_group=1211,
dungeon_id=1018,
)
Lupitoxin_Sawteeth = ItemTrace(
id=23,
name='Lupitoxin_Sawteeth',
cn='狼毒锯牙',
cht='狼毒鋸牙',
en='Lupitoxin Sawteeth',
jp='狼毒の牙',
es='Diente serrado con lupitoxina',
rarity='Rare',
item_id=110182,
item_group=1211,
dungeon_id=1018,
)
Moon_Madness_Fang = ItemTrace(
id=24,
name='Moon_Madness_Fang',
cn='月狂獠牙',
cht='月狂獠牙',
en='Moon Madness Fang',
jp='月狂いの凶牙',
es='Colmillo de la locura lunar',
rarity='VeryRare',
item_id=110183,
item_group=1211,
dungeon_id=1018,
)
Meteoric_Bullet = ItemTrace(
id=25,
name='Meteoric_Bullet',
cn='陨铁弹丸',
cht='隕鐵彈丸',
en='Meteoric Bullet',
jp='隕鉄の弾丸',
es='Perdigón meteórico',
rarity='NotNormal',
item_id=110191,
item_group=1212,
dungeon_id=1022,
)
Destined_Expiration = ItemTrace(
id=26,
name='Destined_Expiration',
cn='命定死因',
cht='命定死因',
en='Destined Expiration',
jp='定められた死因',
es='Deceso predestinado',
rarity='Rare',
item_id=110192,
item_group=1212,
dungeon_id=1022,
)
Countertemporal_Shot = ItemTrace(
id=27,
name='Countertemporal_Shot',
cn='逆时一击',
cht='逆時一擊',
en='Countertemporal Shot',
jp='時に抗う一撃',
es='Disparo antitemporal',
rarity='VeryRare',
item_id=110193,
item_group=1212,
dungeon_id=1022,
)
Scattered_Stardust = ItemTrace(
id=28,
name='Scattered_Stardust',
cn='散逸星砂',
cht='散逸星砂',
en='Scattered Stardust',
jp='散らばった星の砂',
es='Polvo estelar disperso',
rarity='NotNormal',
item_id=110211,
item_group=1214,
dungeon_id=1020,
)
Crystal_Meteorites = ItemTrace(
id=29,
name='Crystal_Meteorites',
cn='流星棱晶',
cht='流星稜晶',
en='Crystal Meteorites',
jp='流星プリズム',
es='Meteoritos de cristal',
rarity='Rare',
item_id=110212,
item_group=1214,
dungeon_id=1020,
)
Divine_Amber = ItemTrace(
id=30,
name='Divine_Amber',
cn='神体琥珀',
cht='神體琥珀',
en='Divine Amber',
jp='聖なる琥珀',
es='Ámbar divino',
rarity='VeryRare',
item_id=110213,
item_group=1214,
dungeon_id=1020,
)
Fiery_Spirit = ItemTrace(
id=31,
name='Fiery_Spirit',
cn='炽情之灵',
cht='熾情之靈',
en='Fiery Spirit',
jp='熾烈の霊',
es='Espíritu ardiente',
rarity='NotNormal',
item_id=110221,
item_group=1215,
dungeon_id=1017,
)
Starfire_Essence = ItemTrace(
id=32,
name='Starfire_Essence',
cn='星火之精',
cht='星火之精',
en='Starfire Essence',
jp='星火の精',
es='Esencia de fuego estelar',
rarity='Rare',
item_id=110222,
item_group=1215,
dungeon_id=1017,
)
Heaven_Incinerator = ItemTrace(
id=33,
name='Heaven_Incinerator',
cn='焚天之魔',
cht='焚天之魔',
en='Heaven Incinerator',
jp='焼天の魔',
es='Incinerador divino',
rarity='VeryRare',
item_id=110223,
item_group=1215,
dungeon_id=1017,
)
Firmament_Note = ItemTrace(
id=34,
name='Firmament_Note',
cn='云际音符',
cht='雲際音符',
en='Firmament Note',
jp='雲端の音符',
es='Nota del firmamento',
rarity='NotNormal',
item_id=110231,
item_group=1216,
dungeon_id=1019,
)
Celestial_Section = ItemTrace(
id=35,
name='Celestial_Section',
cn='空际小节',
cht='空際小節',
en='Celestial Section',
jp='空際の小節',
es='Compás celestial',
rarity='Rare',
item_id=110232,
item_group=1216,
dungeon_id=1019,
)
Heavenly_Melody = ItemTrace(
id=36,
name='Heavenly_Melody',
cn='天外乐章',
cht='天外樂章',
en='Heavenly Melody',
jp='天外の楽章',
es='Movimiento celestial',
rarity='VeryRare',
item_id=110233,
item_group=1216,
dungeon_id=1019,
)
Alien_Tree_Seed = ItemTrace(
id=37,
name='Alien_Tree_Seed',
cn='异木种籽',
cht='異木種籽',
en='Alien Tree Seed',
jp='珍木の種',
es='Semilla del árbol extraño',
rarity='NotNormal',
item_id=110241,
item_group=1217,
dungeon_id=1021,
)
Nourishing_Honey = ItemTrace(
id=38,
name='Nourishing_Honey',
cn='滋长花蜜',
cht='滋長花蜜',
en='Nourishing Honey',
jp='育みの蜜',
es='Miel nutricia',
rarity='Rare',
item_id=110242,
item_group=1217,
dungeon_id=1021,
)
Myriad_Fruit = ItemTrace(
id=39,
name='Myriad_Fruit',
cn='万相果实',
cht='萬相果實',
en='Myriad Fruit',
jp='森羅の果実',
es='Fruta del sinfín',
rarity='VeryRare',
item_id=110243,
item_group=1217,
dungeon_id=1021,
)

View File

@ -0,0 +1,83 @@
from .classes import ItemWeekly
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Tracks_of_Destiny = ItemWeekly(
id=1,
name='Tracks_of_Destiny',
cn='命运的足迹',
cht='命運的足跡',
en='Tracks of Destiny',
jp='運命の足跡',
es='Huellas del destino',
rarity='SuperRare',
item_id=241,
item_group=1300,
dungeon_id=-1,
)
Destroyer_Final_Road = ItemWeekly(
id=2,
name='Destroyer_Final_Road',
cn='毁灭者的末路',
cht='毀滅者的末路',
en="Destroyer's Final Road",
jp='壊滅者の末路',
es='Senda final del destructor',
rarity='VeryRare',
item_id=110501,
item_group=1310,
dungeon_id=1301,
)
Guardian_Lament = ItemWeekly(
id=3,
name='Guardian_Lament',
cn='守护者的悲愿',
cht='守護者的悲願',
en="Guardian's Lament",
jp='守護者の悲願',
es='Lamento de la Guardiana',
rarity='VeryRare',
item_id=110502,
item_group=1310,
dungeon_id=1302,
)
Regret_of_Infinite_Ochema = ItemWeekly(
id=4,
name='Regret_of_Infinite_Ochema',
cn='无穷假身的遗恨',
cht='無窮假身的遺恨',
en='Regret of Infinite Ochema',
jp='無窮なる仮身の遺恨',
es='Arrepentimiento del eterno vehículo del alma',
rarity='VeryRare',
item_id=110503,
item_group=1310,
dungeon_id=1303,
)
Past_Evils_of_the_Borehole_Planet_Disaster = ItemWeekly(
id=5,
name='Past_Evils_of_the_Borehole_Planet_Disaster',
cn='蛀星孕灾的旧恶',
cht='蛀星孕災的舊惡',
en='Past Evils of the Borehole Planet Disaster',
jp='星を蝕む古の悪',
es='Agravios pasados de la catástrofe devoraplanetas',
rarity='VeryRare',
item_id=110504,
item_group=1310,
dungeon_id=1304,
)
Lost_Echo_of_the_Shared_Wish = ItemWeekly(
id=6,
name='Lost_Echo_of_the_Shared_Wish',
cn='同愿的遗音',
cht='同願的遺音',
en='Lost Echo of the Shared Wish',
jp='同願の遺音',
es='Eco perdido del deseo compartido',
rarity='VeryRare',
item_id=110505,
item_group=1310,
dungeon_id=1305,
)

510
tasks/planner/model.py Normal file
View File

@ -0,0 +1,510 @@
import typing as t
from datetime import datetime
from functools import partial
from pydantic import BaseModel, ValidationError, WrapValidator, field_validator, model_validator
from module.base.decorator import cached_property, del_cached_property
from module.config.stored.classes import now
from module.config.utils import DEFAULT_TIME
from module.exception import ScriptError
from module.logger import logger
from tasks.base.ui import UI
from tasks.dungeon.keywords import DungeonList
from tasks.planner.keywords import ITEM_TYPES
from tasks.planner.keywords.classes import ItemBase
class PlannerResultRow(BaseModel):
"""
A row of data from planner result page
"""
item: ITEM_TYPES
total: int
synthesize: int
demand: int
def __eq__(self, other):
return self.item == other.item
class ObtainedAmmount(BaseModel):
"""
A row of data from DungeonObtain detection
"""
item: ITEM_TYPES
value: int
def _fallback_to_default_validator(
get_default: t.Callable[[], t.Any],
v: t.Any,
next_: t.Callable[[t.Any], t.Any],
) -> t.Any:
try:
return next_(v)
except ValueError as e:
logger.error(e)
return get_default()
class BaseModelWithFallback(BaseModel):
"""
Pydantic model that fallbacks to default on error
https://github.com/pydantic/pydantic/discussions/8579
"""
@classmethod
def __pydantic_init_subclass__(cls, **kwargs: t.Any) -> None:
for field in cls.model_fields.values():
if not field.is_required():
validator = WrapValidator(partial(_fallback_to_default_validator, field.get_default))
field.metadata.append(validator)
cls.model_rebuild(force=True)
class MultiValue(BaseModelWithFallback):
green: int = 0
blue: int = 0
purple: int = 0
def add(self, other: "MultiValue"):
self.green += other.green
self.blue += other.blue
self.purple += other.purple
def equivalent_green(self):
return self.green + self.blue * 3 + self.purple * 9
class StoredPlannerProxy(BaseModelWithFallback):
item: ITEM_TYPES
value: int | MultiValue = 0
total: int | MultiValue = 0
synthesize: int | MultiValue = 0
progress: float = 0.
time: datetime = DEFAULT_TIME
@field_validator('item', mode='before')
def val_item(cls, v, info):
if isinstance(v, str):
v = ItemBase.find_name(v)
return v
@model_validator(mode='after')
def val_value(self):
if self.item.has_group_base:
if not isinstance(self.value, MultiValue):
self.value = MultiValue()
if not isinstance(self.total, MultiValue):
self.total = MultiValue()
if not isinstance(self.synthesize, MultiValue):
self.synthesize = MultiValue()
else:
if not isinstance(self.value, int):
self.value = 0
if not isinstance(self.total, int):
self.total = 0
if not isinstance(self.synthesize, int):
self.synthesize = 0
return self
def update_synthesize(self):
if self.item.has_group_base:
green = self.value.green - self.total.green
blue = self.value.blue - self.total.blue
purple = self.value.purple - self.total.purple
syn_blue = 0
syn_purple = 0
if green >= 3 and blue < 0:
syn = min(green // 3, -blue)
syn_blue += syn
green -= syn * 3
# blue += syn
if blue >= 3 and purple < 0:
syn = min(blue // 3, -purple)
syn_purple += syn
blue -= syn * 3
purple += syn
if green >= 9 and purple < 0:
syn = min(green // 9, -purple)
syn_purple += syn
syn_blue += syn * 3
green -= syn * 9
# purple += syn
self.synthesize.green = 0
self.synthesize.blue = syn_blue
self.synthesize.purple = syn_purple
else:
self.synthesize = 0
def revert_synthesize(self):
if self.item.has_group_base:
self.value.green += self.synthesize.blue * 3
if self.synthesize.blue > 0:
self.value.green += self.synthesize.purple * 9
else:
self.value.blue += self.synthesize.purple * 3
def is_approaching_total(self, wave_done: int = 0):
"""
Args:
wave_done:
Returns:
bool: True if the future value may >= total after next combat
"""
wave_done = max(wave_done, 0)
# Items with a static drop rate will have `AVG * (wave_done + 1)
if self.item.dungeon.is_Calyx_Golden_Treasures:
return self.value + 24000 * (wave_done + 12) >= self.total
if self.item.dungeon.is_Calyx_Golden_Memories:
# purple, blue, green = 5, 1, 0
value = self.value.equivalent_green()
total = self.total.equivalent_green()
return value + 48 * (wave_done + 12) >= total
if self.item.dungeon.is_Calyx_Golden_Aether:
# purple, blue, green = 1, 2, 2.5
value = self.value.equivalent_green()
total = self.total.equivalent_green()
return value + 17.5 * (wave_done + 12) >= total
if self.item.is_ItemAscension:
return self.value + 3 * (wave_done + 1) >= self.total
if self.item.is_ItemTrace:
# purple, blue, green = 0.155, 1, 1.25
value = self.value.equivalent_green()
total = self.total.equivalent_green()
return value + 5.645 * (wave_done + 12) >= total
if self.item.is_ItemWeekly:
return self.value + 3 * (wave_done + 1) >= self.total
return False
def update_progress(self):
if self.item.has_group_base:
total = self.total.equivalent_green()
green = min(self.value.green, self.total.green)
blue = min(self.value.blue + self.synthesize.blue, self.total.blue)
purple = min(self.value.purple + self.synthesize.purple, self.total.purple)
value = green + blue * 3 + purple * 9
progress = value / total * 100
self.progress = round(min(max(progress, 0), 100), 2)
else:
progress = self.value / self.total * 100
self.progress = round(min(max(progress, 0), 100), 2)
def update(self):
self.update_synthesize()
self.update_progress()
self.time = now()
def load_value_total(self, item: ItemBase, value=None, total=None, synthesize=None):
"""
Update data from PlannerResultRow to self
"""
if self.item.has_group_base:
if item.group_base != self.item:
raise ScriptError(
f'load_value_total: Trying to load {item} into {self} but they are different items')
else:
if item != self.item:
raise ScriptError(
f'load_value_total: Trying to load {item} into {self} but they are different items')
if self.item.has_group_base:
if not self.item.is_rarity_purple:
raise ScriptError(
f'load_value_total: Trying to load {item} into {self} but self is not in rarity purple')
if item.is_rarity_green:
if value is not None:
self.value.green = value
if total is not None:
self.total.green = total
# Cannot synthesize green
# if synthesize is not None:
# self.synthesize.green = synthesize
self.synthesize.green = 0
elif item.is_rarity_blue:
if value is not None:
self.value.blue = value
if total is not None:
self.total.blue = total
if synthesize is not None:
self.synthesize.blue = synthesize
elif item.is_rarity_purple:
if value is not None:
self.value.purple = value
if total is not None:
self.total.purple = total
if synthesize is not None:
self.synthesize.purple = synthesize
else:
raise ScriptError(
f'load_value_total: Trying to load {item} in to {self} but item is in invalid rarity')
else:
# Cannot synthesize if item doesn't have multiple rarity
self.synthesize = 0
if value is not None:
self.value = value
if total is not None:
self.total = total
def add_planner_result(self, row: "StoredPlannerProxy"):
"""
Add data from another StoredPlannerProxy to self
"""
item = row.item
if self.item.has_group_base:
if item.group_base != self.item:
raise ScriptError(
f'load_value_total: Trying to load {item} into {self} but they are different items')
else:
if item != self.item:
raise ScriptError(
f'load_value_total: Trying to load {item} into {self} but they are different items')
if self.item.has_group_base:
if not self.item.is_rarity_purple:
raise ScriptError(
f'load_value_total: Trying to load {item} into {self} but self is not in rarity purple')
# Add `total` only
# `synthesize` will be updated later
# `value` remains unchanged since you still having that many items
self.total.add(row.total)
else:
self.value += row.value
self.total += row.total
self.synthesize += row.synthesize
def need_farm(self):
return self.progress < 100
def need_synthesize(self):
if self.item.has_group_base:
return self.synthesize.green > 0 or self.synthesize.blue > 0 or self.synthesize.purple > 0
else:
return self.synthesize > 0
def load_planner_result(self, row: PlannerResultRow):
"""
Update data from PlannerResultRow to self
"""
# Approximate value, accurate value can be update in DungeonObtain
value = row.total - row.synthesize - row.demand
self.load_value_total(item=row.item, value=value, total=row.total, synthesize=row.synthesize)
def load_item_amount(self, row: ObtainedAmmount):
"""
Update data from ObtainedAmmount to self
"""
value = row.value
self.load_value_total(item=row.item, value=value)
class PlannerProgressParser:
def __init__(self):
self.rows: dict[str, StoredPlannerProxy] = {}
def from_planner_results(self, results: list[PlannerResultRow]):
self.rows = {}
# Create objects of base items first
# them load value and total
for row in results:
base = row.item.group_base
if base.name not in self.rows:
try:
obj = StoredPlannerProxy(item=base)
except ScriptError as e:
logger.error(e)
continue
self.rows[base.name] = obj
else:
obj = self.rows[base.name]
obj.load_planner_result(row)
rows = {}
for name, row in self.rows.items():
row.revert_synthesize()
row.update()
if row.need_farm() or row.need_synthesize():
rows[name] = row
self.rows = rows
return self
def load_obtained_amount(self, results: list[ObtainedAmmount]):
for row in results:
base = row.item.group_base
try:
obj = self.rows[base.name]
except KeyError:
logger.warning(
f'load_obtained_amount() drops {row} because no need to farm')
continue
obj.load_item_amount(row)
obj.update()
return self
def from_config(self, data):
self.rows = {}
for row in data.values():
if not row:
continue
try:
row = StoredPlannerProxy(**row)
except (ScriptError, ValidationError) as e:
logger.error(e)
continue
if not row.item.is_group_base:
logger.error(f'from_config: item is not group base {row}')
continue
row.update_synthesize()
row.update_progress()
self.rows[row.item.name] = row
return self
def add_planner_result(self, planner: "PlannerProgressParser"):
"""
Add another planner result to self
"""
for name, row in planner.rows.items():
if name in self.rows:
self_row = self.rows[name]
self_row.add_planner_result(row)
else:
self.rows[name] = row
for row in self.rows.values():
row.update()
def to_config(self) -> dict:
data = {}
for row in self.rows.values():
name = f'Item_{row.item.name}'
dic = row.model_dump()
dic['item'] = row.item.name
data[name] = dic
return data
def iter_row_to_farm(self, need_farm=True) -> t.Iterable[StoredPlannerProxy]:
"""
Args:
need_farm: True if filter rows that need farm
Yields:
"""
if need_farm:
rows = [row for row in self.rows.values() if row.need_farm()]
else:
rows = self.rows.values()
for row in rows:
if row.item.is_ItemWeekly:
yield row
for row in rows:
if row.item.is_ItemAscension:
yield row
for row in rows:
if row.item.is_ItemTrace:
yield row
for row in rows:
if row.item.is_ItemExp:
yield row
for row in rows:
if row.item.is_ItemCurrency:
yield row
def get_dungeon(self, double_calyx=False) -> DungeonList | None:
"""
Get dungeon to farm, or None if planner finished or the remaining items cannot be farmed
"""
for row in self.iter_row_to_farm():
item = row.item
if item.is_ItemWeekly:
continue
dungeon = item.dungeon
if dungeon is None:
logger.error(f'Item {item} has nowhere to be farmed')
continue
if double_calyx:
if dungeon.is_Calyx:
logger.info(f'Planner farm (double_calyx): {dungeon}')
return dungeon
else:
logger.info(f'Planner farm: {dungeon}')
return dungeon
logger.info('Planner farm empty')
return None
def get_weekly(self) -> DungeonList | None:
for row in self.iter_row_to_farm():
item = row.item
if not item.is_ItemWeekly:
continue
dungeon = item.dungeon
if dungeon is None:
logger.error(f'Item {item} has nowhere to be farmed')
continue
logger.info(f'Planner weekly farm: {dungeon}')
return dungeon
logger.info('Planner weekly farm empty')
return None
def row_come_from_dungeon(self, dungeon: DungeonList | None) -> StoredPlannerProxy | None:
"""
If any items in planner is able to be farmed from given dungeon
"""
if dungeon is None:
return None
for row in self.iter_row_to_farm(need_farm=False):
if row.item.dungeon == dungeon:
logger.info(f'Planner {row} come from {dungeon}')
return row
return None
class PlannerMixin(UI):
def planner_write_results(self, results: list[PlannerResultRow]):
"""
Write planner detection results info user config
"""
add = self.config.PlannerScan_ResultAdd
logger.attr('ResultAdd', add)
planner = PlannerProgressParser().from_planner_results(results)
if add:
planner.add_planner_result(self.planner)
self.planner_write(planner)
@cached_property
def planner(self) -> PlannerProgressParser:
data = self.config.cross_get('Dungeon.Planner', default={})
model = PlannerProgressParser().from_config(data)
logger.hr('Planner')
for row in model.rows.values():
logger.info(row)
return model
def planner_write(self, planner=None):
"""
Write planner into user config, delete planner object
"""
if planner is None:
planner = self.planner
data = planner.to_config()
with self.config.multi_set():
# Set value
for key, value in data.items():
self.config.cross_set(f'Dungeon.Planner.{key}', value)
# Remove other value
remove = []
for key, value in self.config.cross_get('Dungeon.Planner', default={}).items():
if value != {} and key not in data:
remove.append(key)
for key in remove:
self.config.cross_set(f'Dungeon.Planner.{key}', {})
del_cached_property(self, 'planner')

228
tasks/planner/scan.py Normal file
View File

@ -0,0 +1,228 @@
import re
from pponnxcr.predict_system import BoxedResult
from module.base.utils import area_center, area_in_area
from module.logger import logger
from module.ocr.ocr import Ocr, OcrWhiteLetterOnComplexBackground
from module.ui.scroll import AdaptiveScroll
from tasks.daily.synthesize import SynthesizeUI
from tasks.planner.assets.assets_planner_result import *
from tasks.planner.keywords import ITEM_CLASSES
from tasks.planner.keywords.classes import ItemCurrency
from tasks.planner.model import PlannerMixin, PlannerResultRow
CALCULATE_TITLE.load_search(RESULT_CHECK.search)
MATERIAL_TITLE.load_search(RESULT_CHECK.search)
DETAIL_TITLE.load_search(RESULT_CHECK.search)
class OcrItemName(Ocr):
def after_process(self, result):
result = result.replace('念火之心', '忿火之心')
result = re.sub('工造机$', '工造机杼', result)
result = re.sub('工造迥?轮', '工造迴轮', result)
result = re.sub('月狂[療撩]?牙', '月狂獠牙', result)
# Error words on blank background
result = re.sub('^[國東]', '', result)
result = re.sub('時$', '', result)
return result
class OcrPlannerResult(OcrWhiteLetterOnComplexBackground, OcrItemName):
min_box = (16, 20)
def __init__(self):
# Planner currently CN only
super().__init__(OCR_RESULT, lang='cn')
self.limited_area = OCR_RESULT.area
self.limit_y = 720
def _match_result(
self,
result: str,
keyword_classes,
lang: str = 'cn',
ignore_punctuation=True,
ignore_digit=True):
return super()._match_result(
result,
keyword_classes,
lang,
ignore_punctuation,
ignore_digit,
)
def filter_detected(self, result: BoxedResult) -> bool:
if not area_in_area(result.box, self.limited_area, threshold=0):
return False
if area_center(result.box)[1] > self.limit_y:
return False
return True
def detect_and_ocr(self, image, *args, **kwargs):
# Remove rows below DETAIL_TITLE
if DETAIL_TITLE.match_template(image):
self.limit_y = DETAIL_TITLE.button[3]
else:
self.limit_y = 720
return super().detect_and_ocr(image, *args, **kwargs)
def pre_process(self, image):
# gray = rgb2gray(image)
# from PIL import Image
# Image.fromarray(gray).show()
# image = cv2.merge([gray, gray, gray])
return image
class PlannerScan(SynthesizeUI, PlannerMixin):
def is_in_planner_result(self):
if self.appear(RESULT_CHECK):
return True
if self.appear(CALCULATE_TITLE):
return True
if self.appear(MATERIAL_TITLE):
return True
if self.appear(DETAIL_TITLE):
return True
return False
def parse_planner_result_page(self) -> list[PlannerResultRow]:
"""
Pages:
in: planner result
"""
ocr = OcrPlannerResult()
results = ocr.detect_and_ocr(self.device.image)
x_total = 842
x_synthesize = 965
x_demand = 1129
def x_match(result: BoxedResult, x):
rx = area_center(result.box)[0]
return x - 50 <= rx <= x + 50
def y_match(result: BoxedResult, y):
rx = area_center(result.box)[1]
return y - 15 <= rx <= y + 15
# Split columns
list_item = [r for r in results
if not r.ocr_text.isdigit() and ocr._match_result(r.ocr_text, keyword_classes=ITEM_CLASSES)]
list_number = [r for r in results if r.ocr_text.isdigit()]
list_total = [r for r in list_number if x_match(r, x_total)]
list_synthesize = [r for r in list_number if x_match(r, x_synthesize)]
list_demand = [r for r in list_number if x_match(r, x_demand)]
# Structure
out: list[PlannerResultRow] = []
for item in list_item:
y_item = area_center(item.box)[1]
total = -1
for number in list_total:
if y_match(number, y_item):
total = int(number.ocr_text)
break
synthesize = 0
for number in list_synthesize:
if y_match(number, y_item):
synthesize = int(number.ocr_text)
break
demand = 0
for number in list_demand:
if y_match(number, y_item):
demand = int(number.ocr_text)
break
item = ocr._match_result(item.ocr_text, keyword_classes=ITEM_CLASSES)
row = PlannerResultRow(
item=item,
total=total,
synthesize=synthesize,
demand=demand
)
# Validate item
# print(row)
if row.total <= 0:
logger.warning(f'Planner row with total <= 0, {row}')
continue
if row.synthesize < 0:
# Credits always have `synthesize`=="-"
if row.item.__class__ != ItemCurrency:
logger.warning(f'Planner row with synthesize < 0, {row}')
continue
if row.demand < 0:
logger.warning(f'Planner row with demand < 0, {row}')
continue
# Add
out.append(row)
logger.info(f'parse_planner_result_page: {out}')
return out
def parse_planner_result(self, skip_first_screenshot=True) -> list[PlannerResultRow]:
"""
Pages:
in: planner result
"""
logger.hr('Parse planner result', level=2)
scroll = AdaptiveScroll(RESULT_SCROLL.button, name=RESULT_SCROLL.name)
scroll.drag_threshold = 0.1
scroll.edge_threshold = 0.1
scroll.parameters = {
'height': 50,
'prominence': 15,
'width': 5,
}
if not skip_first_screenshot:
self.device.screenshot()
skip_first_screenshot = False
if not scroll.at_top(main=self):
scroll.set_top(main=self)
out = []
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
# Skip first page
if self.appear(CALCULATE_TITLE):
scroll.next_page(main=self, page=0.75)
continue
# Parse
rows = self.parse_planner_result_page()
for row in rows:
if row not in out:
out.append(row)
logger.attr('PlannerResult', len(rows))
# Scroll
if scroll.at_bottom(main=self):
logger.info('Reached scroll end, stop')
break
elif self.appear(DETAIL_TITLE):
logger.info('Reached DETAIL_TITLE, stop')
break
else:
scroll.next_page(main=self, page=0.8)
logger.hr('Planner Result')
for row in out:
logger.info(f'Planner item: {row.item.name}, {row.total}, {row.synthesize}, {row.demand}')
self.planner_write_results(out)
return out
def run(self):
self.device.screenshot()
self.parse_planner_result()
if __name__ == '__main__':
self = PlannerScan('src', task='PlannerScan')
self.device.screenshot()
self.parse_planner_result()