Merge branch 'LmeSzinc:master' into master

This commit is contained in:
zhaoqiman 2024-05-17 19:26:08 +08:00 committed by GitHub
commit 92c6433ae9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
150 changed files with 4163 additions and 1634 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/character/Robin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -441,10 +441,13 @@ pre.rich-traceback-code {
[id^="pywebio-scope-dashboard-value-"] { [id^="pywebio-scope-dashboard-value-"] {
display: flex; display: flex;
align-items: flex-end; align-items: baseline;
height: 1.5rem; height: 1.5rem;
} }
[id^="pywebio-scope-arg_stored-stored-value-"] p {
margin-bottom: 0;
}
#pywebio-scope-log { #pywebio-scope-log {
line-height: 1.2; line-height: 1.2;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

View File

@ -199,7 +199,9 @@
"DomainStrategy": "combat", "DomainStrategy": "combat",
"UseImmersifier": true, "UseImmersifier": true,
"DoubleEvent": true, "DoubleEvent": true,
"UseStamina": false "WeeklyFarming": false,
"UseStamina": false,
"SimulatedUniverseFarm": {}
}, },
"RogueBlessing": { "RogueBlessing": {
"PresetBlessingFilter": "preset", "PresetBlessingFilter": "preset",

View File

@ -1,6 +1,7 @@
import copy import copy
import os import os
import subprocess import subprocess
import sys
from typing import Optional, Union from typing import Optional, Union
from deploy.Windows.logger import logger from deploy.Windows.logger import logger
@ -80,12 +81,6 @@ class DeployConfig(ConfigModel):
self.config_template = {} self.config_template = {}
self.read() self.read()
# Bypass webui.config.DeployConfig.__setattr__()
# Don't write these into deploy.yaml
super().__setattr__('GitOverCdn', self.Repository in ['cn'])
if self.Repository in ['global', 'cn']:
super().__setattr__('Repository', 'https://github.com/LmeSzinc/StarRailCopilot')
self.write() self.write()
self.show_config() self.show_config()
@ -109,9 +104,21 @@ class DeployConfig(ConfigModel):
if hasattr(self, key): if hasattr(self, key):
super().__setattr__(key, value) super().__setattr__(key, value)
self.config_redirect()
def write(self): def write(self):
poor_yaml_write(self.config, self.file) poor_yaml_write(self.config, self.file)
def config_redirect(self):
"""
Redirect deploy config, must be called after each `read()`
"""
# Bypass webui.config.DeployConfig.__setattr__()
# Don't write these into deploy.yaml
super().__setattr__('GitOverCdn', self.Repository in ['cn'])
if self.Repository in ['global', 'cn']:
super().__setattr__('Repository', 'https://github.com/LmeSzinc/StarRailCopilot')
def filepath(self, path): def filepath(self, path):
""" """
Args: Args:
@ -143,7 +150,7 @@ class DeployConfig(ConfigModel):
if os.path.exists(exe): if os.path.exists(exe):
return exe return exe
logger.warning(f'AdbExecutable: {exe} does not exists, use `adb` instead') logger.warning(f'AdbExecutable: {exe} does not exist, use `adb` instead')
return 'adb' return 'adb'
@cached_property @cached_property
@ -152,12 +159,18 @@ class DeployConfig(ConfigModel):
if os.path.exists(exe): if os.path.exists(exe):
return exe return exe
logger.warning(f'GitExecutable: {exe} does not exists, use `git` instead') logger.warning(f'GitExecutable: {exe} does not exist, use `git` instead')
return 'git' return 'git'
@cached_property @cached_property
def python(self) -> str: def python(self) -> str:
return self.filepath(self.PythonExecutable) exe = self.filepath(self.PythonExecutable)
if os.path.exists(exe):
return exe
current = sys.executable.replace("\\", "/")
logger.warning(f'PythonExecutable: {exe} does not exist, use current python instead: {current}')
return current
@cached_property @cached_property
def requirements_file(self) -> str: def requirements_file(self) -> str:

View File

@ -5,6 +5,22 @@ from dev_tools.keywords.base import UI_LANGUAGES, GenerateKeyword
from module.config.utils import deep_get from module.config.utils import deep_get
def resort(dic: dict):
# Poor assigment sort for 2.2
order = [
1008, 1007, 1006, 1005, 1004, 1003, 1002, 1001,
3001, 2001, 4001,
5008, 5006, 5005, 5003, 5002, 5007, 5004, 5001,
]
out = {}
for index in order:
value = dic.pop(index)
out[index] = value
for k, v, in dic.items():
out[k] = v
return out
@cache @cache
def get_assignment_entry_data(): def get_assignment_entry_data():
""" """
@ -16,6 +32,9 @@ def get_assignment_entry_data():
deep_get(expedition, 'Name.Hash'): deep_get(expedition, 'ExpeditionID') deep_get(expedition, 'Name.Hash'): deep_get(expedition, 'ExpeditionID')
for expedition in GenerateKeyword.read_file('./ExcelOutput/ExpeditionData.json').values() for expedition in GenerateKeyword.read_file('./ExcelOutput/ExpeditionData.json').values()
} }
rev = {v: k for k, v in expedition_namehash_to_id.items()}
rev = resort(rev)
expedition_namehash_to_id = {v: k for k, v in rev.items()}
expedition_id_to_reward_id = { expedition_id_to_reward_id = {
deep_get(expedition, '4.2.ExpeditionID'): deep_get(expedition, '4.2.RewardID') deep_get(expedition, '4.2.ExpeditionID'): deep_get(expedition, '4.2.RewardID')
for expedition in GenerateKeyword.read_file('./ExcelOutput/ExpeditionReward.json').values() for expedition in GenerateKeyword.read_file('./ExcelOutput/ExpeditionReward.json').values()
@ -119,5 +138,6 @@ class GenerateAssignmentEventEntry(GenerateKeyword):
if __name__ == "__main__": if __name__ == "__main__":
from dev_tools.keywords.base import TextMap from dev_tools.keywords.base import TextMap
TextMap.DATA_FOLDER = '../StarRailData' TextMap.DATA_FOLDER = '../StarRailData'
GenerateAssignment()() GenerateAssignment()()

View File

@ -63,7 +63,8 @@ class TextMap:
def text_to_variable(text): def text_to_variable(text):
text = re.sub("'s |s' ", '_', text) text = re.sub("'s |s' ", '_', text)
text = re.sub(r'[ \-—:\'/•.]+', '_', text) text = re.sub(r'[ \-—–:\'/•.™]+', '_', text)
text = re.sub(r'[█]+', '', text)
text = re.sub(r'[(),#"?!&%*]|</?\w+>', '', text) text = re.sub(r'[(),#"?!&%*]|</?\w+>', '', text)
# text = re.sub(r'[#_]?\d+(_times?)?', '', text) # text = re.sub(r'[#_]?\d+(_times?)?', '', text)
text = re.sub(r'<color=#?\w+>', '', text) text = re.sub(r'<color=#?\w+>', '', text)

View File

@ -16,6 +16,8 @@ def dungeon_name(name: str) -> str:
name = f'Echo_of_War_{name}' name = f'Echo_of_War_{name}'
if name in ['The_Swarm_Disaster', 'Gold_and_Gears']: if name in ['The_Swarm_Disaster', 'Gold_and_Gears']:
name = f'Simulated_Universe_{name}' name = f'Simulated_Universe_{name}'
name = name.replace('Stagnant_Shadow_Stagnant_Shadow', 'Stagnant_Shadow')
name = name.replace('Cavern_of_Corrosion_Cavern_of_Corrosion', 'Cavern_of_Corrosion')
return name return name
@ -70,7 +72,10 @@ class GenerateDungeonList(GenerateKeyword):
if text.startswith('Calyx_Crimson'): if text.startswith('Calyx_Crimson'):
plane = MapPlane.find_plane_id(keyword['plane_id']) plane = MapPlane.find_plane_id(keyword['plane_id'])
if plane is not None:
text = f'{text}_{plane.name}' text = f'{text}_{plane.name}'
else:
text = f'{text}_unknown_plane'
return text return text
def convert_keyword(self, text: str, lang: str) -> str: def convert_keyword(self, text: str, lang: str) -> str:

View File

@ -68,3 +68,7 @@ class GenerateMapPlane(GenerateKeyword):
return f'Special_{text}' return f'Special_{text}'
else: else:
return f'{world.short_name}_{text}' return f'{world.short_name}_{text}'
def convert_keyword(self, text: str, lang: str) -> str:
text = text.replace('', '')
return super().convert_keyword(text, lang=lang)

View File

@ -1,5 +1,3 @@
from concurrent.futures import ThreadPoolExecutor
import module.config.server as server_ import module.config.server as server_
from module.base.button import Button, ButtonWrapper, ClickButton, match_template from module.base.button import Button, ButtonWrapper, ClickButton, match_template
from module.base.timer import Timer from module.base.timer import Timer
@ -50,11 +48,22 @@ class ModuleBase:
self.interval_timer = {} self.interval_timer = {}
@cached_class_property @cached_class_property
def worker(self) -> ThreadPoolExecutor: def worker(self):
""" """
A thread pool to run things at background A thread pool to run things at background
Examples:
```
def func(image):
logger.info('Update thread start')
with self.config.multi_set():
self.dungeon_get_simuni_point(image)
self.dungeon_update_stamina(image)
ModuleBase.worker.submit(func, self.device.image)
```
""" """
logger.hr('Creating worker') logger.hr('Creating worker')
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(1) pool = ThreadPoolExecutor(1)
return pool return pool

View File

@ -34,12 +34,34 @@ class Button(Resource):
def clear_offset(self): def clear_offset(self):
self._button_offset = (0, 0) self._button_offset = (0, 0)
def is_offset_in(self, x=0, y=0):
"""
Args:
x:
y:
Returns:
bool: If _button_offset is in (-x, -y, x, y)
"""
if x:
if self._button_offset[0] < -x or self._button_offset[0] > x:
return False
if y:
if self._button_offset[1] < -y or self._button_offset[1] > y:
return False
return True
@cached_property @cached_property
def image(self): def image(self):
return load_image(self.file, self.area) return load_image(self.file, self.area)
@cached_property
def image_binary(self):
return rgb2gray(self.image)
def resource_release(self): def resource_release(self):
del_cached_property(self, 'image') del_cached_property(self, 'image')
del_cached_property(self, 'image_binary')
self.clear_offset() self.clear_offset()
def __str__(self): def __str__(self):
@ -96,6 +118,29 @@ class Button(Resource):
self._button_offset = np.array(point) + self.search[:2] - self.area[:2] self._button_offset = np.array(point) + self.search[:2] - self.area[:2]
return sim > similarity return sim > similarity
def match_template_binary(self, image, similarity=0.85, direct_match=False) -> bool:
"""
Detects assets by template matching.
To Some buttons, its location may not be static, `_button_offset` will be set.
Args:
image: Screenshot.
similarity (float): 0-1.
direct_match: True to ignore `self.search`
Returns:
bool.
"""
if not direct_match:
image = crop(image, self.search, copy=False)
image = rgb2gray(image)
res = cv2.matchTemplate(self.image_binary, image, cv2.TM_CCOEFF_NORMED)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = np.array(point) + self.search[:2] - self.area[:2]
return sim > similarity
def match_multi_template(self, image, similarity=0.85, direct_match=False): def match_multi_template(self, image, similarity=0.85, direct_match=False):
""" """
Detects assets by template matching, return multiple reults Detects assets by template matching, return multiple reults
@ -208,6 +253,13 @@ class ButtonWrapper(Resource):
return True return True
return False return False
def match_template_binary(self, image, similarity=0.85, direct_match=False) -> bool:
for assets in self.buttons:
if assets.match_template_binary(image, similarity=similarity, direct_match=direct_match):
self._matched_button = assets
return True
return False
def match_multi_template(self, image, similarity=0.85, threshold=5, direct_match=False): def match_multi_template(self, image, similarity=0.85, threshold=5, direct_match=False):
""" """
Detects assets by template matching, return multiple results Detects assets by template matching, return multiple results
@ -295,6 +347,17 @@ class ButtonWrapper(Resource):
for b in self.iter_buttons(): for b in self.iter_buttons():
b.clear_offset() b.clear_offset()
def is_offset_in(self, x=0, y=0):
"""
Args:
x:
y:
Returns:
bool: If _button_offset is in (-x, -y, x, y)
"""
return self.matched_button.is_offset_in(x=x, y=y)
def load_search(self, area): def load_search(self, area):
""" """
Set `search` attribute. Set `search` attribute.

View File

@ -625,17 +625,29 @@ def image_paste(image, background, origin):
def rgb2gray(image): def rgb2gray(image):
""" """
gray = ( MAX(r, g, b) + MIN(r, g, b)) / 2
Args: Args:
image (np.ndarray): Shape (height, width, channel) image (np.ndarray): Shape (height, width, channel)
Returns: Returns:
np.ndarray: Shape (height, width) np.ndarray: Shape (height, width)
""" """
# r, g, b = cv2.split(image)
# return cv2.add(
# cv2.multiply(cv2.max(cv2.max(r, g), b), 0.5),
# cv2.multiply(cv2.min(cv2.min(r, g), b), 0.5)
# )
r, g, b = cv2.split(image) r, g, b = cv2.split(image)
return cv2.add( maximum = cv2.max(r, g)
cv2.multiply(cv2.max(cv2.max(r, g), b), 0.5), cv2.max(maximum, b, dst=maximum)
cv2.multiply(cv2.min(cv2.min(r, g), b), 0.5) cv2.convertScaleAbs(maximum, alpha=0.5, dst=maximum)
) cv2.min(r, g, dst=r)
cv2.min(r, b, dst=r)
cv2.convertScaleAbs(r, alpha=0.5, dst=r)
# minimum = r
cv2.add(maximum, r, dst=maximum)
return maximum
def rgb2hsv(image): def rgb2hsv(image):
@ -791,11 +803,24 @@ def color_similarity_2d(image, color):
Returns: Returns:
np.ndarray: uint8 np.ndarray: uint8
""" """
r, g, b = cv2.split(cv2.subtract(image, (*color, 0))) # r, g, b = cv2.split(cv2.subtract(image, (*color, 0)))
positive = cv2.max(cv2.max(r, g), b) # positive = cv2.max(cv2.max(r, g), b)
r, g, b = cv2.split(cv2.subtract((*color, 0), image)) # r, g, b = cv2.split(cv2.subtract((*color, 0), image))
negative = cv2.max(cv2.max(r, g), b) # negative = cv2.max(cv2.max(r, g), b)
return cv2.subtract(255, cv2.add(positive, negative)) # return cv2.subtract(255, cv2.add(positive, negative))
diff = cv2.subtract(image, (*color, 0))
r, g, b = cv2.split(diff)
cv2.max(r, g, dst=r)
cv2.max(r, b, dst=r)
positive = r
cv2.subtract((*color, 0), image, dst=diff)
r, g, b = cv2.split(diff)
cv2.max(r, g, dst=r)
cv2.max(r, b, dst=r)
negative = r
cv2.add(positive, negative, dst=positive)
cv2.subtract(255, positive, dst=positive)
return positive
def extract_letters(image, letter=(255, 255, 255), threshold=128): def extract_letters(image, letter=(255, 255, 255), threshold=128):
@ -809,11 +834,24 @@ def extract_letters(image, letter=(255, 255, 255), threshold=128):
Returns: Returns:
np.ndarray: Shape (height, width) np.ndarray: Shape (height, width)
""" """
r, g, b = cv2.split(cv2.subtract(image, (*letter, 0))) # r, g, b = cv2.split(cv2.subtract(image, (*letter, 0)))
positive = cv2.max(cv2.max(r, g), b) # positive = cv2.max(cv2.max(r, g), b)
r, g, b = cv2.split(cv2.subtract((*letter, 0), image)) # r, g, b = cv2.split(cv2.subtract((*letter, 0), image))
negative = cv2.max(cv2.max(r, g), b) # negative = cv2.max(cv2.max(r, g), b)
return cv2.multiply(cv2.add(positive, negative), 255.0 / threshold) # return cv2.multiply(cv2.add(positive, negative), 255.0 / threshold)
diff = cv2.subtract(image, (*letter, 0))
r, g, b = cv2.split(diff)
cv2.max(r, g, dst=r)
cv2.max(r, b, dst=r)
positive = r
cv2.subtract((*letter, 0), image, dst=diff)
r, g, b = cv2.split(diff)
cv2.max(r, g, dst=r)
cv2.max(r, b, dst=r)
negative = r
cv2.add(positive, negative, dst=positive)
cv2.convertScaleAbs(positive, alpha=255.0 / threshold, dst=positive)
return positive
def extract_white_letters(image, threshold=128): def extract_white_letters(image, threshold=128):
@ -827,10 +865,21 @@ def extract_white_letters(image, threshold=128):
Returns: Returns:
np.ndarray: Shape (height, width) np.ndarray: Shape (height, width)
""" """
# minimum = cv2.min(cv2.min(r, g), b)
# maximum = cv2.max(cv2.max(r, g), b)
# return cv2.multiply(cv2.add(maximum, cv2.subtract(maximum, minimum)), 255.0 / threshold)
r, g, b = cv2.split(cv2.subtract((255, 255, 255, 0), image)) r, g, b = cv2.split(cv2.subtract((255, 255, 255, 0), image))
minimum = cv2.min(cv2.min(r, g), b) maximum = cv2.max(r, g)
maximum = cv2.max(cv2.max(r, g), b) cv2.max(maximum, b, dst=maximum)
return cv2.multiply(cv2.add(maximum, cv2.subtract(maximum, minimum)), 255.0 / threshold) cv2.convertScaleAbs(maximum, alpha=0.5, dst=maximum)
cv2.min(r, g, dst=r)
cv2.min(r, b, dst=r)
cv2.convertScaleAbs(r, alpha=0.5, dst=r)
minimum = r
cv2.subtract(maximum, minimum, dst=minimum)
cv2.add(maximum, minimum, dst=maximum)
cv2.convertScaleAbs(maximum, alpha=255.0 / threshold, dst=maximum)
return maximum
def color_mapping(image, max_multiply=2): def color_mapping(image, max_multiply=2):
@ -849,7 +898,9 @@ def color_mapping(image, max_multiply=2):
low, high = np.min(image), np.max(image) low, high = np.min(image), np.max(image)
multiply = min(255 / (high - low), max_multiply) multiply = min(255 / (high - low), max_multiply)
add = (255 - multiply * (low + high)) / 2 add = (255 - multiply * (low + high)) / 2
image = cv2.add(cv2.multiply(image, multiply), add) # image = cv2.add(cv2.multiply(image, multiply), add)
cv2.multiply(image, multiply, dst=image)
cv2.add(image, add, dst=image)
image[image > 255] = 255 image[image > 255] = 255
image[image < 0] = 0 image[image < 0] = 0
return image.astype(np.uint8) return image.astype(np.uint8)
@ -909,7 +960,7 @@ def color_bar_percentage(image, area, prev_color, reverse=False, starter=0, thre
Returns: Returns:
float: 0 to 1. float: 0 to 1.
""" """
image = crop(image, area) image = crop(image, area, copy=False)
image = image[:, ::-1, :] if reverse else image image = image[:, ::-1, :] if reverse else image
length = image.shape[1] length = image.shape[1]
prev_index = starter prev_index = starter

View File

@ -48,7 +48,8 @@
"aScreenCap_nc", "aScreenCap_nc",
"DroidCast", "DroidCast",
"DroidCast_raw", "DroidCast_raw",
"scrcpy" "scrcpy",
"nemu_ipc"
], ],
"display": "hide" "display": "hide"
}, },
@ -237,6 +238,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone", "Calyx_Crimson_Preservation_Herta_SupplyZone",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden",
"Calyx_Crimson_Erudition_Jarilo_RivetTown", "Calyx_Crimson_Erudition_Jarilo_RivetTown",
@ -246,6 +248,7 @@
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission", "Calyx_Crimson_Nihility_Luofu_AlchemyCommission",
"Stagnant_Shadow_Spike", "Stagnant_Shadow_Spike",
"Stagnant_Shadow_Perdition", "Stagnant_Shadow_Perdition",
"Stagnant_Shadow_Duty",
"Stagnant_Shadow_Blaze", "Stagnant_Shadow_Blaze",
"Stagnant_Shadow_Scorch", "Stagnant_Shadow_Scorch",
"Stagnant_Shadow_Ire", "Stagnant_Shadow_Ire",
@ -290,6 +293,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone", "Calyx_Crimson_Preservation_Herta_SupplyZone",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden",
"Calyx_Crimson_Erudition_Jarilo_RivetTown", "Calyx_Crimson_Erudition_Jarilo_RivetTown",
@ -357,6 +361,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone", "Calyx_Crimson_Preservation_Herta_SupplyZone",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden",
"Calyx_Crimson_Erudition_Jarilo_RivetTown", "Calyx_Crimson_Erudition_Jarilo_RivetTown",
@ -373,6 +378,7 @@
"do_not_achieve", "do_not_achieve",
"Stagnant_Shadow_Spike", "Stagnant_Shadow_Spike",
"Stagnant_Shadow_Perdition", "Stagnant_Shadow_Perdition",
"Stagnant_Shadow_Duty",
"Stagnant_Shadow_Blaze", "Stagnant_Shadow_Blaze",
"Stagnant_Shadow_Scorch", "Stagnant_Shadow_Scorch",
"Stagnant_Shadow_Ire", "Stagnant_Shadow_Ire",
@ -426,6 +432,7 @@
"Argenti", "Argenti",
"Arlan", "Arlan",
"Asta", "Asta",
"Aventurine",
"Bailu", "Bailu",
"BlackSwan", "BlackSwan",
"Blade", "Blade",
@ -454,6 +461,7 @@
"Natasha", "Natasha",
"Pela", "Pela",
"Qingque", "Qingque",
"Robin",
"RuanMei", "RuanMei",
"Sampo", "Sampo",
"Seele", "Seele",
@ -1068,100 +1076,100 @@
"type": "select", "type": "select",
"value": "Nameless_Land_Nameless_People", "value": "Nameless_Land_Nameless_People",
"option": [ "option": [
"Nine_Billion_Names",
"Destruction_of_the_Destroyer",
"Winter_Soldiers",
"Born_to_Obey",
"Root_Out_the_Turpitude",
"Fire_Lord_Inflames_Blades_of_War",
"A_Startling_Night_Terror",
"Tranquility_of_Vimala_bhumi", "Tranquility_of_Vimala_bhumi",
"Nameless_Land_Nameless_People", "A_Startling_Night_Terror",
"Fire_Lord_Inflames_Blades_of_War",
"Root_Out_the_Turpitude",
"Born_to_Obey",
"Winter_Soldiers",
"Destruction_of_the_Destroyer",
"Nine_Billion_Names",
"Akashic_Records", "Akashic_Records",
"Nameless_Land_Nameless_People",
"The_Invisible_Hand", "The_Invisible_Hand",
"Abandoned_and_Insulted", "Scalpel_and_Screwdriver",
"Spring_of_Life",
"The_Land_of_Gold",
"The_Blossom_in_the_Storm",
"Legend_of_the_Puppet_Master",
"The_Wages_of_Humanity", "The_Wages_of_Humanity",
"Legend_of_the_Puppet_Master",
"The_Land_of_Gold",
"Spring_of_Life",
"Fragments_of_Illusory_Dreams", "Fragments_of_Illusory_Dreams",
"Scalpel_and_Screwdriver" "The_Blossom_in_the_Storm",
"Abandoned_and_Insulted"
] ]
}, },
"Name_2": { "Name_2": {
"type": "select", "type": "select",
"value": "Akashic_Records", "value": "Akashic_Records",
"option": [ "option": [
"Nine_Billion_Names",
"Destruction_of_the_Destroyer",
"Winter_Soldiers",
"Born_to_Obey",
"Root_Out_the_Turpitude",
"Fire_Lord_Inflames_Blades_of_War",
"A_Startling_Night_Terror",
"Tranquility_of_Vimala_bhumi", "Tranquility_of_Vimala_bhumi",
"Nameless_Land_Nameless_People", "A_Startling_Night_Terror",
"Fire_Lord_Inflames_Blades_of_War",
"Root_Out_the_Turpitude",
"Born_to_Obey",
"Winter_Soldiers",
"Destruction_of_the_Destroyer",
"Nine_Billion_Names",
"Akashic_Records", "Akashic_Records",
"Nameless_Land_Nameless_People",
"The_Invisible_Hand", "The_Invisible_Hand",
"Abandoned_and_Insulted", "Scalpel_and_Screwdriver",
"Spring_of_Life",
"The_Land_of_Gold",
"The_Blossom_in_the_Storm",
"Legend_of_the_Puppet_Master",
"The_Wages_of_Humanity", "The_Wages_of_Humanity",
"Legend_of_the_Puppet_Master",
"The_Land_of_Gold",
"Spring_of_Life",
"Fragments_of_Illusory_Dreams", "Fragments_of_Illusory_Dreams",
"Scalpel_and_Screwdriver" "The_Blossom_in_the_Storm",
"Abandoned_and_Insulted"
] ]
}, },
"Name_3": { "Name_3": {
"type": "select", "type": "select",
"value": "The_Invisible_Hand", "value": "The_Invisible_Hand",
"option": [ "option": [
"Nine_Billion_Names",
"Destruction_of_the_Destroyer",
"Winter_Soldiers",
"Born_to_Obey",
"Root_Out_the_Turpitude",
"Fire_Lord_Inflames_Blades_of_War",
"A_Startling_Night_Terror",
"Tranquility_of_Vimala_bhumi", "Tranquility_of_Vimala_bhumi",
"Nameless_Land_Nameless_People", "A_Startling_Night_Terror",
"Fire_Lord_Inflames_Blades_of_War",
"Root_Out_the_Turpitude",
"Born_to_Obey",
"Winter_Soldiers",
"Destruction_of_the_Destroyer",
"Nine_Billion_Names",
"Akashic_Records", "Akashic_Records",
"Nameless_Land_Nameless_People",
"The_Invisible_Hand", "The_Invisible_Hand",
"Abandoned_and_Insulted", "Scalpel_and_Screwdriver",
"Spring_of_Life",
"The_Land_of_Gold",
"The_Blossom_in_the_Storm",
"Legend_of_the_Puppet_Master",
"The_Wages_of_Humanity", "The_Wages_of_Humanity",
"Legend_of_the_Puppet_Master",
"The_Land_of_Gold",
"Spring_of_Life",
"Fragments_of_Illusory_Dreams", "Fragments_of_Illusory_Dreams",
"Scalpel_and_Screwdriver" "The_Blossom_in_the_Storm",
"Abandoned_and_Insulted"
] ]
}, },
"Name_4": { "Name_4": {
"type": "select", "type": "select",
"value": "Nine_Billion_Names", "value": "Nine_Billion_Names",
"option": [ "option": [
"Nine_Billion_Names",
"Destruction_of_the_Destroyer",
"Winter_Soldiers",
"Born_to_Obey",
"Root_Out_the_Turpitude",
"Fire_Lord_Inflames_Blades_of_War",
"A_Startling_Night_Terror",
"Tranquility_of_Vimala_bhumi", "Tranquility_of_Vimala_bhumi",
"Nameless_Land_Nameless_People", "A_Startling_Night_Terror",
"Fire_Lord_Inflames_Blades_of_War",
"Root_Out_the_Turpitude",
"Born_to_Obey",
"Winter_Soldiers",
"Destruction_of_the_Destroyer",
"Nine_Billion_Names",
"Akashic_Records", "Akashic_Records",
"Nameless_Land_Nameless_People",
"The_Invisible_Hand", "The_Invisible_Hand",
"Abandoned_and_Insulted", "Scalpel_and_Screwdriver",
"Spring_of_Life",
"The_Land_of_Gold",
"The_Blossom_in_the_Storm",
"Legend_of_the_Puppet_Master",
"The_Wages_of_Humanity", "The_Wages_of_Humanity",
"Legend_of_the_Puppet_Master",
"The_Land_of_Gold",
"Spring_of_Life",
"Fragments_of_Illusory_Dreams", "Fragments_of_Illusory_Dreams",
"Scalpel_and_Screwdriver" "The_Blossom_in_the_Storm",
"Abandoned_and_Insulted"
] ]
}, },
"Duration": { "Duration": {
@ -1303,7 +1311,8 @@
"Echo_of_War_Destruction_Beginning", "Echo_of_War_Destruction_Beginning",
"Echo_of_War_End_of_the_Eternal_Freeze", "Echo_of_War_End_of_the_Eternal_Freeze",
"Echo_of_War_Divine_Seed", "Echo_of_War_Divine_Seed",
"Echo_of_War_Borehole_Planet_Old_Crater" "Echo_of_War_Borehole_Planet_Old_Crater",
"Echo_of_War_Salutations_of_Ashen_Dreams"
] ]
}, },
"Team": { "Team": {
@ -1340,6 +1349,7 @@
"Argenti", "Argenti",
"Arlan", "Arlan",
"Asta", "Asta",
"Aventurine",
"Bailu", "Bailu",
"BlackSwan", "BlackSwan",
"Blade", "Blade",
@ -1368,6 +1378,7 @@
"Natasha", "Natasha",
"Pela", "Pela",
"Qingque", "Qingque",
"Robin",
"RuanMei", "RuanMei",
"Sampo", "Sampo",
"Seele", "Seele",
@ -1466,9 +1477,19 @@
"type": "checkbox", "type": "checkbox",
"value": true "value": true
}, },
"WeeklyFarming": {
"type": "checkbox",
"value": false
},
"UseStamina": { "UseStamina": {
"type": "checkbox", "type": "checkbox",
"value": false "value": false
},
"SimulatedUniverseFarm": {
"type": "stored",
"value": {},
"display": "disabled",
"stored": "StoredSimulatedUniverseElite"
} }
}, },
"RogueBlessing": { "RogueBlessing": {

View File

@ -29,7 +29,18 @@ Emulator:
option: [ auto, cn, en ] option: [ auto, cn, en ]
ScreenshotMethod: ScreenshotMethod:
value: auto value: auto
option: [ auto, ADB, ADB_nc, uiautomator2, aScreenCap, aScreenCap_nc, DroidCast, DroidCast_raw, scrcpy ] option: [
auto,
ADB,
ADB_nc,
uiautomator2,
aScreenCap,
aScreenCap_nc,
DroidCast,
DroidCast_raw,
scrcpy,
nemu_ipc,
]
ControlMethod: ControlMethod:
value: MaaTouch value: MaaTouch
option: [ minitouch, MaaTouch ] option: [ minitouch, MaaTouch ]
@ -246,7 +257,11 @@ RogueWorld:
option: [ combat, occurrence ] option: [ combat, occurrence ]
UseImmersifier: true UseImmersifier: true
DoubleEvent: true DoubleEvent: true
WeeklyFarming: false
UseStamina: false UseStamina: false
SimulatedUniverseFarm:
stored: StoredSimulatedUniverseElite
display: disabled
RogueBlessing: RogueBlessing:
PresetBlessingFilter: PresetBlessingFilter:

View File

@ -293,5 +293,18 @@
}, },
"order": 0, "order": 0,
"color": "#777777" "color": "#777777"
},
"SimulatedUniverseFarm": {
"name": "SimulatedUniverseFarm",
"path": "Rogue.RogueWorld.SimulatedUniverseFarm",
"i18n": "RogueWorld.SimulatedUniverseFarm.name",
"stored": "StoredSimulatedUniverseElite",
"attrs": {
"time": "2020-01-01 00:00:00",
"total": 100,
"value": 0
},
"order": 0,
"color": "#777777"
} }
} }

View File

@ -176,6 +176,10 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig, ConfigWatcher
self.data, keys="Alas.Optimization.CloseGameDuringWait", default=False self.data, keys="Alas.Optimization.CloseGameDuringWait", default=False
) )
@property
def is_actual_task(self):
return self.task.command.lower() not in ['alas', 'template']
@property @property
def is_cloud_game(self): def is_cloud_game(self):
return deep_get( return deep_get(

View File

@ -20,7 +20,7 @@ class GeneratedConfig:
Emulator_GameClient = 'android' # android, cloud_android Emulator_GameClient = 'android' # android, cloud_android
Emulator_PackageName = 'auto' # auto, CN-Official, CN-Bilibili, OVERSEA-America, OVERSEA-Asia, OVERSEA-Europe, OVERSEA-TWHKMO Emulator_PackageName = 'auto' # auto, CN-Official, CN-Bilibili, OVERSEA-America, OVERSEA-Asia, OVERSEA-Europe, OVERSEA-TWHKMO
Emulator_GameLanguage = 'auto' # auto, cn, en Emulator_GameLanguage = 'auto' # auto, cn, en
Emulator_ScreenshotMethod = 'auto' # auto, ADB, ADB_nc, uiautomator2, aScreenCap, aScreenCap_nc, DroidCast, DroidCast_raw, scrcpy Emulator_ScreenshotMethod = 'auto' # auto, ADB, ADB_nc, uiautomator2, aScreenCap, aScreenCap_nc, DroidCast, DroidCast_raw, scrcpy, nemu_ipc
Emulator_ControlMethod = 'MaaTouch' # minitouch, MaaTouch Emulator_ControlMethod = 'MaaTouch' # minitouch, MaaTouch
Emulator_AdbRestart = False Emulator_AdbRestart = False
@ -46,20 +46,20 @@ class GeneratedConfig:
CloudStorage_CloudRemainFree = {} CloudStorage_CloudRemainFree = {}
# Group `Dungeon` # Group `Dungeon`
Dungeon_Name = 'Calyx_Golden_Treasures' # 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, 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_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, Stagnant_Shadow_Spike, Stagnant_Shadow_Perdition, 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, 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_Name = 'Calyx_Golden_Treasures' # 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, 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, 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, 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_NameAtDoubleCalyx = 'Calyx_Golden_Treasures' # 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, 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_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 Dungeon_NameAtDoubleCalyx = 'Calyx_Golden_Treasures' # 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, 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
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_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 Dungeon_Team = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9
# Group `DungeonDaily` # 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_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_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_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_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_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 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` # Group `DungeonSupport`
DungeonSupport_Use = 'when_daily' # always_use, when_daily, do_not_use DungeonSupport_Use = 'when_daily' # always_use, when_daily, do_not_use
DungeonSupport_Character = 'FirstCharacter' # FirstCharacter, Acheron, Argenti, Arlan, Asta, 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, RuanMei, Sampo, Seele, Serval, SilverWolf, Sparkle, Sushang, Tingyun, TopazNumby, TrailblazerDestruction, TrailblazerPreservation, Welt, Xueyi, Yanqing, Yukong 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
# Group `DungeonStorage` # Group `DungeonStorage`
DungeonStorage_TrailblazePower = {} DungeonStorage_TrailblazePower = {}
@ -72,7 +72,7 @@ class GeneratedConfig:
SupportReward_Collect = True SupportReward_Collect = True
# Group `Weekly` # 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 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 Weekly_Team = 1 # 1, 2, 3, 4, 5, 6, 7, 8, 9
# Group `AchievableQuest` # Group `AchievableQuest`
@ -119,10 +119,10 @@ class GeneratedConfig:
BattlePassStorage_BattlePassQuestTrailblazePower = {} BattlePassStorage_BattlePassQuestTrailblazePower = {}
# Group `Assignment` # Group `Assignment`
Assignment_Name_1 = 'Nameless_Land_Nameless_People' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, A_Startling_Night_Terror, Tranquility_of_Vimala_bhumi, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity, Fragments_of_Illusory_Dreams, Scalpel_and_Screwdriver Assignment_Name_1 = 'Nameless_Land_Nameless_People' # Tranquility_of_Vimala_bhumi, A_Startling_Night_Terror, Fire_Lord_Inflames_Blades_of_War, Root_Out_the_Turpitude, Born_to_Obey, Winter_Soldiers, Destruction_of_the_Destroyer, Nine_Billion_Names, Akashic_Records, Nameless_Land_Nameless_People, The_Invisible_Hand, Scalpel_and_Screwdriver, The_Wages_of_Humanity, Legend_of_the_Puppet_Master, The_Land_of_Gold, Spring_of_Life, Fragments_of_Illusory_Dreams, The_Blossom_in_the_Storm, Abandoned_and_Insulted
Assignment_Name_2 = 'Akashic_Records' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, A_Startling_Night_Terror, Tranquility_of_Vimala_bhumi, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity, Fragments_of_Illusory_Dreams, Scalpel_and_Screwdriver Assignment_Name_2 = 'Akashic_Records' # Tranquility_of_Vimala_bhumi, A_Startling_Night_Terror, Fire_Lord_Inflames_Blades_of_War, Root_Out_the_Turpitude, Born_to_Obey, Winter_Soldiers, Destruction_of_the_Destroyer, Nine_Billion_Names, Akashic_Records, Nameless_Land_Nameless_People, The_Invisible_Hand, Scalpel_and_Screwdriver, The_Wages_of_Humanity, Legend_of_the_Puppet_Master, The_Land_of_Gold, Spring_of_Life, Fragments_of_Illusory_Dreams, The_Blossom_in_the_Storm, Abandoned_and_Insulted
Assignment_Name_3 = 'The_Invisible_Hand' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, A_Startling_Night_Terror, Tranquility_of_Vimala_bhumi, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity, Fragments_of_Illusory_Dreams, Scalpel_and_Screwdriver Assignment_Name_3 = 'The_Invisible_Hand' # Tranquility_of_Vimala_bhumi, A_Startling_Night_Terror, Fire_Lord_Inflames_Blades_of_War, Root_Out_the_Turpitude, Born_to_Obey, Winter_Soldiers, Destruction_of_the_Destroyer, Nine_Billion_Names, Akashic_Records, Nameless_Land_Nameless_People, The_Invisible_Hand, Scalpel_and_Screwdriver, The_Wages_of_Humanity, Legend_of_the_Puppet_Master, The_Land_of_Gold, Spring_of_Life, Fragments_of_Illusory_Dreams, The_Blossom_in_the_Storm, Abandoned_and_Insulted
Assignment_Name_4 = 'Nine_Billion_Names' # Nine_Billion_Names, Destruction_of_the_Destroyer, Winter_Soldiers, Born_to_Obey, Root_Out_the_Turpitude, Fire_Lord_Inflames_Blades_of_War, A_Startling_Night_Terror, Tranquility_of_Vimala_bhumi, Nameless_Land_Nameless_People, Akashic_Records, The_Invisible_Hand, Abandoned_and_Insulted, Spring_of_Life, The_Land_of_Gold, The_Blossom_in_the_Storm, Legend_of_the_Puppet_Master, The_Wages_of_Humanity, Fragments_of_Illusory_Dreams, Scalpel_and_Screwdriver Assignment_Name_4 = 'Nine_Billion_Names' # Tranquility_of_Vimala_bhumi, A_Startling_Night_Terror, Fire_Lord_Inflames_Blades_of_War, Root_Out_the_Turpitude, Born_to_Obey, Winter_Soldiers, Destruction_of_the_Destroyer, Nine_Billion_Names, Akashic_Records, Nameless_Land_Nameless_People, The_Invisible_Hand, Scalpel_and_Screwdriver, The_Wages_of_Humanity, Legend_of_the_Puppet_Master, The_Land_of_Gold, Spring_of_Life, Fragments_of_Illusory_Dreams, The_Blossom_in_the_Storm, Abandoned_and_Insulted
Assignment_Duration = 20 # 4, 8, 12, 20 Assignment_Duration = 20 # 4, 8, 12, 20
Assignment_Event = True Assignment_Event = True
Assignment_Assignment = {} Assignment_Assignment = {}
@ -138,7 +138,9 @@ class GeneratedConfig:
RogueWorld_DomainStrategy = 'combat' # combat, occurrence RogueWorld_DomainStrategy = 'combat' # combat, occurrence
RogueWorld_UseImmersifier = True RogueWorld_UseImmersifier = True
RogueWorld_DoubleEvent = True RogueWorld_DoubleEvent = True
RogueWorld_WeeklyFarming = False
RogueWorld_UseStamina = False RogueWorld_UseStamina = False
RogueWorld_SimulatedUniverseFarm = {}
# Group `RogueBlessing` # Group `RogueBlessing`
RogueBlessing_PresetBlessingFilter = 'preset' # preset, custom RogueBlessing_PresetBlessingFilter = 'preset' # preset, custom

View File

@ -100,7 +100,7 @@ class ConfigGenerator:
options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Echo_of_War]) options=[dungeon.name for dungeon in DungeonList.instances.values() if dungeon.is_Echo_of_War])
# Insert characters # Insert characters
from tasks.character.keywords import CharacterList from tasks.character.keywords import CharacterList
unsupported_characters = ['Aventurine'] unsupported_characters = ["Boothill", "TrailblazerHarmony"]
characters = [character.name for character in CharacterList.instances.values() characters = [character.name for character in CharacterList.instances.values()
if character.name not in unsupported_characters] if character.name not in unsupported_characters]
option_add(keys='DungeonSupport.Character.option', options=characters) option_add(keys='DungeonSupport.Character.option', options=characters)
@ -430,7 +430,7 @@ class ConfigGenerator:
value=i18n_crimson[ingame_lang].format(path=path, plane=plane)) value=i18n_crimson[ingame_lang].format(path=path, plane=plane))
if dungeon.is_Cavern_of_Corrosion: if dungeon.is_Cavern_of_Corrosion:
value = deep_get(new, keys=['Dungeon', 'Name', dungeon.name], default='') value = deep_get(new, keys=['Dungeon', 'Name', dungeon.name], default='')
suffix = i18n_relic[ingame_lang].format(dungeon=dungeon_name) suffix = i18n_relic[ingame_lang].format(dungeon=dungeon_name).replace('Cavern of Corrosion: ', '')
if not value.endswith(suffix): if not value.endswith(suffix):
deep_set(new, keys=['Dungeon', 'Name', dungeon.name], value=f'{value}{suffix}') deep_set(new, keys=['Dungeon', 'Name', dungeon.name], value=f'{value}{suffix}')
@ -489,7 +489,7 @@ class ConfigGenerator:
for dungeon in dungeons: for dungeon in dungeons:
world = dungeon.plane.world world = dungeon.plane.world
world_name = world.__getattribute__(ingame_lang) world_name = world.__getattribute__(ingame_lang)
dungeon_name = dungeon.__getattribute__(ingame_lang) dungeon_name = dungeon.__getattribute__(ingame_lang).replace('Echo of War: ', '')
value = f'{dungeon_name} ({world_name})' value = f'{dungeon_name} ({world_name})'
deep_set(new, keys=['Weekly', 'Name', dungeon.name], value=value) deep_set(new, keys=['Weekly', 'Name', dungeon.name], value=value)
# Rogue worlds # Rogue worlds
@ -653,6 +653,7 @@ class ConfigUpdater:
('Dungeon.Dungeon.NameAtDoubleCalyx', 'Dungeon.Dungeon.NameAtDoubleCalyx', convert_20_dungeon), ('Dungeon.Dungeon.NameAtDoubleCalyx', 'Dungeon.Dungeon.NameAtDoubleCalyx', convert_20_dungeon),
('Dungeon.DungeonDaily.CalyxGolden', 'Dungeon.DungeonDaily.CalyxGolden', convert_20_dungeon), ('Dungeon.DungeonDaily.CalyxGolden', 'Dungeon.DungeonDaily.CalyxGolden', convert_20_dungeon),
('Dungeon.DungeonDaily.CalyxCrimson', 'Dungeon.DungeonDaily.CalyxCrimson', convert_20_dungeon), ('Dungeon.DungeonDaily.CalyxCrimson', 'Dungeon.DungeonDaily.CalyxCrimson', convert_20_dungeon),
('Rogue.RogueWorld.SimulatedUniverseElite', 'Rogue.RogueWorld.SimulatedUniverseFarm', convert_rogue_farm),
] ]
@cached_property @cached_property
@ -863,6 +864,8 @@ class ConfigUpdater:
yield 'Rogue.RogueBlessing.CustomResonanceFilter' yield 'Rogue.RogueBlessing.CustomResonanceFilter'
if deep_get(data, 'Rogue.RogueBlessing.PresetCurioFilter') != 'custom': if deep_get(data, 'Rogue.RogueBlessing.PresetCurioFilter') != 'custom':
yield 'Rogue.RogueBlessing.CustomCurioFilter' yield 'Rogue.RogueBlessing.CustomCurioFilter'
if deep_get(data, 'Rogue.RogueWorld.WeeklyFarming', default=False) is False:
yield 'Rogue.RogueWorld.SimulatedUniverseFarm'
def get_hidden_args(self, data) -> t.Set[str]: def get_hidden_args(self, data) -> t.Set[str]:
""" """

View File

@ -30,3 +30,10 @@ def convert_20_dungeon(value):
return 'Calyx_Crimson_Abundance_Jarilo_BackwaterPass' return 'Calyx_Crimson_Abundance_Jarilo_BackwaterPass'
return value return value
def convert_rogue_farm(value):
if isinstance(value, dict) and 'value' in value.keys():
value['value'] = 100 - value['value']
value['total'] = 100
return value

View File

@ -131,7 +131,8 @@
"aScreenCap_nc": "aScreenCap_nc", "aScreenCap_nc": "aScreenCap_nc",
"DroidCast": "DroidCast", "DroidCast": "DroidCast",
"DroidCast_raw": "DroidCast_raw", "DroidCast_raw": "DroidCast_raw",
"scrcpy": "scrcpy" "scrcpy": "scrcpy",
"nemu_ipc": "nemu_ipc"
}, },
"ControlMethod": { "ControlMethod": {
"name": "Control Method", "name": "Control Method",
@ -261,6 +262,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "Trace: Preservation (Supply Zone)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "Trace: Preservation (Supply Zone)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "Trace: Preservation (Clock Studios Theme Park)", "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_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_Jarilo_BackwaterPass": "Trace: Abundance (Backwater Pass)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Trace: Abundance (Fyxestroll Garden)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Trace: Abundance (Fyxestroll Garden)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "Trace: Erudition (Rivet Town)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "Trace: Erudition (Rivet Town)",
@ -270,6 +272,7 @@
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "Trace: Nihility (Alchemy Commission)", "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "Trace: Nihility (Alchemy Commission)",
"Stagnant_Shadow_Spike": "Ascension: Physical (Natasha / Clara / Luka / Sushang)", "Stagnant_Shadow_Spike": "Ascension: Physical (Natasha / Clara / Luka / Sushang)",
"Stagnant_Shadow_Perdition": "Ascension: Physical (Hanya / Argenti)", "Stagnant_Shadow_Perdition": "Ascension: Physical (Hanya / Argenti)",
"Stagnant_Shadow_Duty": "Ascension: Physical (Boothill / Robin)",
"Stagnant_Shadow_Blaze": "Ascension: Fire (Himeko / Asta / Hook)", "Stagnant_Shadow_Blaze": "Ascension: Fire (Himeko / Asta / Hook)",
"Stagnant_Shadow_Scorch": "Ascension: Fire (Guinaifen / Topaz & Numby)", "Stagnant_Shadow_Scorch": "Ascension: Fire (Guinaifen / Topaz & Numby)",
"Stagnant_Shadow_Ire": "Ascension: Fire (Gallagher)", "Stagnant_Shadow_Ire": "Ascension: Fire (Gallagher)",
@ -312,6 +315,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "Trace: Preservation (Supply Zone)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "Trace: Preservation (Supply Zone)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "Trace: Preservation (Clock Studios Theme Park)", "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_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_Jarilo_BackwaterPass": "Trace: Abundance (Backwater Pass)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Trace: Abundance (Fyxestroll Garden)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Trace: Abundance (Fyxestroll Garden)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "Trace: Erudition (Rivet Town)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "Trace: Erudition (Rivet Town)",
@ -375,6 +379,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "Trace: Preservation (Supply Zone)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "Trace: Preservation (Supply Zone)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "Trace: Preservation (Clock Studios Theme Park)", "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_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_Jarilo_BackwaterPass": "Trace: Abundance (Backwater Pass)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Trace: Abundance (Fyxestroll Garden)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Trace: Abundance (Fyxestroll Garden)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "Trace: Erudition (Rivet Town)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "Trace: Erudition (Rivet Town)",
@ -389,6 +394,7 @@
"do_not_achieve": "Don't Do This Quest", "do_not_achieve": "Don't Do This Quest",
"Stagnant_Shadow_Spike": "Ascension: Physical (Natasha / Clara / Luka / Sushang)", "Stagnant_Shadow_Spike": "Ascension: Physical (Natasha / Clara / Luka / Sushang)",
"Stagnant_Shadow_Perdition": "Ascension: Physical (Hanya / Argenti)", "Stagnant_Shadow_Perdition": "Ascension: Physical (Hanya / Argenti)",
"Stagnant_Shadow_Duty": "Ascension: Physical (Boothill / Robin)",
"Stagnant_Shadow_Blaze": "Ascension: Fire (Himeko / Asta / Hook)", "Stagnant_Shadow_Blaze": "Ascension: Fire (Himeko / Asta / Hook)",
"Stagnant_Shadow_Scorch": "Ascension: Fire (Guinaifen / Topaz & Numby)", "Stagnant_Shadow_Scorch": "Ascension: Fire (Guinaifen / Topaz & Numby)",
"Stagnant_Shadow_Ire": "Ascension: Fire (Gallagher)", "Stagnant_Shadow_Ire": "Ascension: Fire (Gallagher)",
@ -440,6 +446,7 @@
"Argenti": "Argenti", "Argenti": "Argenti",
"Arlan": "Arlan", "Arlan": "Arlan",
"Asta": "Asta", "Asta": "Asta",
"Aventurine": "Aventurine",
"Bailu": "Bailu", "Bailu": "Bailu",
"BlackSwan": "Black Swan", "BlackSwan": "Black Swan",
"Blade": "Blade", "Blade": "Blade",
@ -468,6 +475,7 @@
"Natasha": "Natasha", "Natasha": "Natasha",
"Pela": "Pela", "Pela": "Pela",
"Qingque": "Qingque", "Qingque": "Qingque",
"Robin": "Robin",
"RuanMei": "Ruan Mei", "RuanMei": "Ruan Mei",
"Sampo": "Sampo", "Sampo": "Sampo",
"Seele": "Seele", "Seele": "Seele",
@ -532,7 +540,8 @@
"Echo_of_War_Destruction_Beginning": "Destruction's Beginning (Herta Space Station)", "Echo_of_War_Destruction_Beginning": "Destruction's Beginning (Herta Space Station)",
"Echo_of_War_End_of_the_Eternal_Freeze": "End of the Eternal Freeze (Jarilo-VI)", "Echo_of_War_End_of_the_Eternal_Freeze": "End of the Eternal Freeze (Jarilo-VI)",
"Echo_of_War_Divine_Seed": "Divine Seed (The Xianzhou Luofu)", "Echo_of_War_Divine_Seed": "Divine Seed (The Xianzhou Luofu)",
"Echo_of_War_Borehole_Planet_Old_Crater": "Borehole Planet's Old Crater (Herta Space Station)" "Echo_of_War_Borehole_Planet_Old_Crater": "Borehole Planet's Old Crater (Herta Space Station)",
"Echo_of_War_Salutations_of_Ashen_Dreams": "Salutations of Ashen Dreams (Penacony)"
}, },
"Team": { "Team": {
"name": "Dungeon Team", "name": "Dungeon Team",
@ -800,94 +809,94 @@
"Name_1": { "Name_1": {
"name": "Assignment 1 Preference", "name": "Assignment 1 Preference",
"help": "", "help": "",
"Nine_Billion_Names": "Extinguished Core (Nine Billion Names)",
"Destruction_of_the_Destroyer": "Thief's Instinct (Destruction of the Destroyer)",
"Winter_Soldiers": "Silvermane Badge (Winter Soldiers)",
"Born_to_Obey": "Ancient Part (Born to Obey)",
"Root_Out_the_Turpitude": "Immortal Scionette (Root Out the Turpitude)",
"Fire_Lord_Inflames_Blades_of_War": "Artifex's Module (Fire Lord Inflames Blades of War)",
"A_Startling_Night_Terror": "Dream Collection Component (A Startling Night Terror)",
"Tranquility_of_Vimala_bhumi": "Tatters of Thought (Tranquility of Vimala-bhumi)", "Tranquility_of_Vimala_bhumi": "Tatters of Thought (Tranquility of Vimala-bhumi)",
"Nameless_Land_Nameless_People": "Character EXP Material (Nameless Land, Nameless People)", "A_Startling_Night_Terror": "Dream Collection Component (A Startling Night Terror)",
"Fire_Lord_Inflames_Blades_of_War": "Artifex's Module (Fire Lord Inflames Blades of War)",
"Root_Out_the_Turpitude": "Immortal Scionette (Root Out the Turpitude)",
"Born_to_Obey": "Ancient Part (Born to Obey)",
"Winter_Soldiers": "Silvermane Badge (Winter Soldiers)",
"Destruction_of_the_Destroyer": "Thief's Instinct (Destruction of the Destroyer)",
"Nine_Billion_Names": "Extinguished Core (Nine Billion Names)",
"Akashic_Records": "Light Cone EXP Material (Akashic Records)", "Akashic_Records": "Light Cone EXP Material (Akashic Records)",
"Nameless_Land_Nameless_People": "Character EXP Material (Nameless Land, Nameless People)",
"The_Invisible_Hand": "Credit (The Invisible Hand)", "The_Invisible_Hand": "Credit (The Invisible Hand)",
"Abandoned_and_Insulted": "Phlogiston & Metal (Abandoned and Insulted)", "Scalpel_and_Screwdriver": "Rusty Gear & Old Molar (Scalpel and Screwdriver)",
"Spring_of_Life": "Solid Water & Virtual Particle (Spring of Life)",
"The_Land_of_Gold": "Basic Ingredients & Protein Rice (The Land of Gold)",
"The_Blossom_in_the_Storm": "Gaseous Liquid & Seed (The Blossom in the Storm)",
"Legend_of_the_Puppet_Master": "Discarded Ingenium Parts & Jade Abacus Unit (Legend of the Puppet Master)",
"The_Wages_of_Humanity": "Human-Height Auspicious Crops & Extract of Medicinal Herbs (The Wages of Humanity)", "The_Wages_of_Humanity": "Human-Height Auspicious Crops & Extract of Medicinal Herbs (The Wages of Humanity)",
"Legend_of_the_Puppet_Master": "Discarded Ingenium Parts & Jade Abacus Unit (Legend of the Puppet Master)",
"The_Land_of_Gold": "Basic Ingredients & Protein Rice (The Land of Gold)",
"Spring_of_Life": "Solid Water & Virtual Particle (Spring of Life)",
"Fragments_of_Illusory_Dreams": "Tranquility & Broken Dreams (Fragments of Illusory Dreams)", "Fragments_of_Illusory_Dreams": "Tranquility & Broken Dreams (Fragments of Illusory Dreams)",
"Scalpel_and_Screwdriver": "Rusty Gear & Old Molar (Scalpel and Screwdriver)" "The_Blossom_in_the_Storm": "Gaseous Liquid & Seed (The Blossom in the Storm)",
"Abandoned_and_Insulted": "Phlogiston & Metal (Abandoned and Insulted)"
}, },
"Name_2": { "Name_2": {
"name": "Assignment 2 Preference", "name": "Assignment 2 Preference",
"help": "", "help": "",
"Nine_Billion_Names": "Extinguished Core (Nine Billion Names)",
"Destruction_of_the_Destroyer": "Thief's Instinct (Destruction of the Destroyer)",
"Winter_Soldiers": "Silvermane Badge (Winter Soldiers)",
"Born_to_Obey": "Ancient Part (Born to Obey)",
"Root_Out_the_Turpitude": "Immortal Scionette (Root Out the Turpitude)",
"Fire_Lord_Inflames_Blades_of_War": "Artifex's Module (Fire Lord Inflames Blades of War)",
"A_Startling_Night_Terror": "Dream Collection Component (A Startling Night Terror)",
"Tranquility_of_Vimala_bhumi": "Tatters of Thought (Tranquility of Vimala-bhumi)", "Tranquility_of_Vimala_bhumi": "Tatters of Thought (Tranquility of Vimala-bhumi)",
"Nameless_Land_Nameless_People": "Character EXP Material (Nameless Land, Nameless People)", "A_Startling_Night_Terror": "Dream Collection Component (A Startling Night Terror)",
"Fire_Lord_Inflames_Blades_of_War": "Artifex's Module (Fire Lord Inflames Blades of War)",
"Root_Out_the_Turpitude": "Immortal Scionette (Root Out the Turpitude)",
"Born_to_Obey": "Ancient Part (Born to Obey)",
"Winter_Soldiers": "Silvermane Badge (Winter Soldiers)",
"Destruction_of_the_Destroyer": "Thief's Instinct (Destruction of the Destroyer)",
"Nine_Billion_Names": "Extinguished Core (Nine Billion Names)",
"Akashic_Records": "Light Cone EXP Material (Akashic Records)", "Akashic_Records": "Light Cone EXP Material (Akashic Records)",
"Nameless_Land_Nameless_People": "Character EXP Material (Nameless Land, Nameless People)",
"The_Invisible_Hand": "Credit (The Invisible Hand)", "The_Invisible_Hand": "Credit (The Invisible Hand)",
"Abandoned_and_Insulted": "Phlogiston & Metal (Abandoned and Insulted)", "Scalpel_and_Screwdriver": "Rusty Gear & Old Molar (Scalpel and Screwdriver)",
"Spring_of_Life": "Solid Water & Virtual Particle (Spring of Life)",
"The_Land_of_Gold": "Basic Ingredients & Protein Rice (The Land of Gold)",
"The_Blossom_in_the_Storm": "Gaseous Liquid & Seed (The Blossom in the Storm)",
"Legend_of_the_Puppet_Master": "Discarded Ingenium Parts & Jade Abacus Unit (Legend of the Puppet Master)",
"The_Wages_of_Humanity": "Human-Height Auspicious Crops & Extract of Medicinal Herbs (The Wages of Humanity)", "The_Wages_of_Humanity": "Human-Height Auspicious Crops & Extract of Medicinal Herbs (The Wages of Humanity)",
"Legend_of_the_Puppet_Master": "Discarded Ingenium Parts & Jade Abacus Unit (Legend of the Puppet Master)",
"The_Land_of_Gold": "Basic Ingredients & Protein Rice (The Land of Gold)",
"Spring_of_Life": "Solid Water & Virtual Particle (Spring of Life)",
"Fragments_of_Illusory_Dreams": "Tranquility & Broken Dreams (Fragments of Illusory Dreams)", "Fragments_of_Illusory_Dreams": "Tranquility & Broken Dreams (Fragments of Illusory Dreams)",
"Scalpel_and_Screwdriver": "Rusty Gear & Old Molar (Scalpel and Screwdriver)" "The_Blossom_in_the_Storm": "Gaseous Liquid & Seed (The Blossom in the Storm)",
"Abandoned_and_Insulted": "Phlogiston & Metal (Abandoned and Insulted)"
}, },
"Name_3": { "Name_3": {
"name": "Assignment 3 Preference", "name": "Assignment 3 Preference",
"help": "", "help": "",
"Nine_Billion_Names": "Extinguished Core (Nine Billion Names)",
"Destruction_of_the_Destroyer": "Thief's Instinct (Destruction of the Destroyer)",
"Winter_Soldiers": "Silvermane Badge (Winter Soldiers)",
"Born_to_Obey": "Ancient Part (Born to Obey)",
"Root_Out_the_Turpitude": "Immortal Scionette (Root Out the Turpitude)",
"Fire_Lord_Inflames_Blades_of_War": "Artifex's Module (Fire Lord Inflames Blades of War)",
"A_Startling_Night_Terror": "Dream Collection Component (A Startling Night Terror)",
"Tranquility_of_Vimala_bhumi": "Tatters of Thought (Tranquility of Vimala-bhumi)", "Tranquility_of_Vimala_bhumi": "Tatters of Thought (Tranquility of Vimala-bhumi)",
"Nameless_Land_Nameless_People": "Character EXP Material (Nameless Land, Nameless People)", "A_Startling_Night_Terror": "Dream Collection Component (A Startling Night Terror)",
"Fire_Lord_Inflames_Blades_of_War": "Artifex's Module (Fire Lord Inflames Blades of War)",
"Root_Out_the_Turpitude": "Immortal Scionette (Root Out the Turpitude)",
"Born_to_Obey": "Ancient Part (Born to Obey)",
"Winter_Soldiers": "Silvermane Badge (Winter Soldiers)",
"Destruction_of_the_Destroyer": "Thief's Instinct (Destruction of the Destroyer)",
"Nine_Billion_Names": "Extinguished Core (Nine Billion Names)",
"Akashic_Records": "Light Cone EXP Material (Akashic Records)", "Akashic_Records": "Light Cone EXP Material (Akashic Records)",
"Nameless_Land_Nameless_People": "Character EXP Material (Nameless Land, Nameless People)",
"The_Invisible_Hand": "Credit (The Invisible Hand)", "The_Invisible_Hand": "Credit (The Invisible Hand)",
"Abandoned_and_Insulted": "Phlogiston & Metal (Abandoned and Insulted)", "Scalpel_and_Screwdriver": "Rusty Gear & Old Molar (Scalpel and Screwdriver)",
"Spring_of_Life": "Solid Water & Virtual Particle (Spring of Life)",
"The_Land_of_Gold": "Basic Ingredients & Protein Rice (The Land of Gold)",
"The_Blossom_in_the_Storm": "Gaseous Liquid & Seed (The Blossom in the Storm)",
"Legend_of_the_Puppet_Master": "Discarded Ingenium Parts & Jade Abacus Unit (Legend of the Puppet Master)",
"The_Wages_of_Humanity": "Human-Height Auspicious Crops & Extract of Medicinal Herbs (The Wages of Humanity)", "The_Wages_of_Humanity": "Human-Height Auspicious Crops & Extract of Medicinal Herbs (The Wages of Humanity)",
"Legend_of_the_Puppet_Master": "Discarded Ingenium Parts & Jade Abacus Unit (Legend of the Puppet Master)",
"The_Land_of_Gold": "Basic Ingredients & Protein Rice (The Land of Gold)",
"Spring_of_Life": "Solid Water & Virtual Particle (Spring of Life)",
"Fragments_of_Illusory_Dreams": "Tranquility & Broken Dreams (Fragments of Illusory Dreams)", "Fragments_of_Illusory_Dreams": "Tranquility & Broken Dreams (Fragments of Illusory Dreams)",
"Scalpel_and_Screwdriver": "Rusty Gear & Old Molar (Scalpel and Screwdriver)" "The_Blossom_in_the_Storm": "Gaseous Liquid & Seed (The Blossom in the Storm)",
"Abandoned_and_Insulted": "Phlogiston & Metal (Abandoned and Insulted)"
}, },
"Name_4": { "Name_4": {
"name": "Assignment 4 Preference", "name": "Assignment 4 Preference",
"help": "", "help": "",
"Nine_Billion_Names": "Extinguished Core (Nine Billion Names)",
"Destruction_of_the_Destroyer": "Thief's Instinct (Destruction of the Destroyer)",
"Winter_Soldiers": "Silvermane Badge (Winter Soldiers)",
"Born_to_Obey": "Ancient Part (Born to Obey)",
"Root_Out_the_Turpitude": "Immortal Scionette (Root Out the Turpitude)",
"Fire_Lord_Inflames_Blades_of_War": "Artifex's Module (Fire Lord Inflames Blades of War)",
"A_Startling_Night_Terror": "Dream Collection Component (A Startling Night Terror)",
"Tranquility_of_Vimala_bhumi": "Tatters of Thought (Tranquility of Vimala-bhumi)", "Tranquility_of_Vimala_bhumi": "Tatters of Thought (Tranquility of Vimala-bhumi)",
"Nameless_Land_Nameless_People": "Character EXP Material (Nameless Land, Nameless People)", "A_Startling_Night_Terror": "Dream Collection Component (A Startling Night Terror)",
"Fire_Lord_Inflames_Blades_of_War": "Artifex's Module (Fire Lord Inflames Blades of War)",
"Root_Out_the_Turpitude": "Immortal Scionette (Root Out the Turpitude)",
"Born_to_Obey": "Ancient Part (Born to Obey)",
"Winter_Soldiers": "Silvermane Badge (Winter Soldiers)",
"Destruction_of_the_Destroyer": "Thief's Instinct (Destruction of the Destroyer)",
"Nine_Billion_Names": "Extinguished Core (Nine Billion Names)",
"Akashic_Records": "Light Cone EXP Material (Akashic Records)", "Akashic_Records": "Light Cone EXP Material (Akashic Records)",
"Nameless_Land_Nameless_People": "Character EXP Material (Nameless Land, Nameless People)",
"The_Invisible_Hand": "Credit (The Invisible Hand)", "The_Invisible_Hand": "Credit (The Invisible Hand)",
"Abandoned_and_Insulted": "Phlogiston & Metal (Abandoned and Insulted)", "Scalpel_and_Screwdriver": "Rusty Gear & Old Molar (Scalpel and Screwdriver)",
"Spring_of_Life": "Solid Water & Virtual Particle (Spring of Life)",
"The_Land_of_Gold": "Basic Ingredients & Protein Rice (The Land of Gold)",
"The_Blossom_in_the_Storm": "Gaseous Liquid & Seed (The Blossom in the Storm)",
"Legend_of_the_Puppet_Master": "Discarded Ingenium Parts & Jade Abacus Unit (Legend of the Puppet Master)",
"The_Wages_of_Humanity": "Human-Height Auspicious Crops & Extract of Medicinal Herbs (The Wages of Humanity)", "The_Wages_of_Humanity": "Human-Height Auspicious Crops & Extract of Medicinal Herbs (The Wages of Humanity)",
"Legend_of_the_Puppet_Master": "Discarded Ingenium Parts & Jade Abacus Unit (Legend of the Puppet Master)",
"The_Land_of_Gold": "Basic Ingredients & Protein Rice (The Land of Gold)",
"Spring_of_Life": "Solid Water & Virtual Particle (Spring of Life)",
"Fragments_of_Illusory_Dreams": "Tranquility & Broken Dreams (Fragments of Illusory Dreams)", "Fragments_of_Illusory_Dreams": "Tranquility & Broken Dreams (Fragments of Illusory Dreams)",
"Scalpel_and_Screwdriver": "Rusty Gear & Old Molar (Scalpel and Screwdriver)" "The_Blossom_in_the_Storm": "Gaseous Liquid & Seed (The Blossom in the Storm)",
"Abandoned_and_Insulted": "Phlogiston & Metal (Abandoned and Insulted)"
}, },
"Duration": { "Duration": {
"name": "Dispatch Duration", "name": "Dispatch Duration",
@ -969,9 +978,17 @@
"name": "Participate in Double Planer Event", "name": "Participate in Double Planer Event",
"help": "" "help": ""
}, },
"WeeklyFarming": {
"name": "Farm 100 Elites Weekly",
"help": ""
},
"UseStamina": { "UseStamina": {
"name": "Farm Planers Using Trailblase Power", "name": "Farm Planers Using Trailblase Power",
"help": "Task \"Dungeon\" will no longer run, and all trailblaze power will be used first to claim immersion rewards, except for double events." "help": "Task \"Dungeon\" will no longer run, and all trailblaze power will be used first to claim immersion rewards, except for double events."
},
"SimulatedUniverseFarm": {
"name": "Progress of elite boss farmed",
"help": ""
} }
}, },
"RogueBlessing": { "RogueBlessing": {

View File

@ -131,7 +131,8 @@
"aScreenCap_nc": "aScreenCap_nc", "aScreenCap_nc": "aScreenCap_nc",
"DroidCast": "DroidCast", "DroidCast": "DroidCast",
"DroidCast_raw": "DroidCast_raw", "DroidCast_raw": "DroidCast_raw",
"scrcpy": "scrcpy" "scrcpy": "scrcpy",
"nemu_ipc": "nemu_ipc"
}, },
"ControlMethod": { "ControlMethod": {
"name": "Método de control", "name": "Método de control",
@ -261,6 +262,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "Rastros: Conservación (Zona de suministros)", "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_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_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_Jarilo_BackwaterPass": "Rastros: Abundancia (Paso del Remanso)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Rastros: Abundancia (Jardín del Sosiego)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Rastros: Abundancia (Jardín del Sosiego)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "Rastros: Erudición (Villarremache)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "Rastros: Erudición (Villarremache)",
@ -270,6 +272,7 @@
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "Rastros: Nihilidad (Comisión de Alquimia)", "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "Rastros: Nihilidad (Comisión de Alquimia)",
"Stagnant_Shadow_Spike": "Ascension: Físico (Natasha / Clara / Luka / Sushang)", "Stagnant_Shadow_Spike": "Ascension: Físico (Natasha / Clara / Luka / Sushang)",
"Stagnant_Shadow_Perdition": "Ascension: Físico (Hanya / Argenti)", "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_Blaze": "Ascension: Fuego (Himeko / Asta / Hook)",
"Stagnant_Shadow_Scorch": "Ascension: Fuego (Guinaifen / Topaz y Conti)", "Stagnant_Shadow_Scorch": "Ascension: Fuego (Guinaifen / Topaz y Conti)",
"Stagnant_Shadow_Ire": "Ascension: Fuego (Gallagher)", "Stagnant_Shadow_Ire": "Ascension: Fuego (Gallagher)",
@ -312,6 +315,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "Rastros: Conservación (Zona de suministros)", "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_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_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_Jarilo_BackwaterPass": "Rastros: Abundancia (Paso del Remanso)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Rastros: Abundancia (Jardín del Sosiego)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Rastros: Abundancia (Jardín del Sosiego)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "Rastros: Erudición (Villarremache)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "Rastros: Erudición (Villarremache)",
@ -375,6 +379,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "Rastros: Conservación (Zona de suministros)", "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_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_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_Jarilo_BackwaterPass": "Rastros: Abundancia (Paso del Remanso)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Rastros: Abundancia (Jardín del Sosiego)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "Rastros: Abundancia (Jardín del Sosiego)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "Rastros: Erudición (Villarremache)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "Rastros: Erudición (Villarremache)",
@ -389,6 +394,7 @@
"do_not_achieve": "No hacer esta misión", "do_not_achieve": "No hacer esta misión",
"Stagnant_Shadow_Spike": "Ascension: Físico (Natasha / Clara / Luka / Sushang)", "Stagnant_Shadow_Spike": "Ascension: Físico (Natasha / Clara / Luka / Sushang)",
"Stagnant_Shadow_Perdition": "Ascension: Físico (Hanya / Argenti)", "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_Blaze": "Ascension: Fuego (Himeko / Asta / Hook)",
"Stagnant_Shadow_Scorch": "Ascension: Fuego (Guinaifen / Topaz y Conti)", "Stagnant_Shadow_Scorch": "Ascension: Fuego (Guinaifen / Topaz y Conti)",
"Stagnant_Shadow_Ire": "Ascension: Fuego (Gallagher)", "Stagnant_Shadow_Ire": "Ascension: Fuego (Gallagher)",
@ -440,6 +446,7 @@
"Argenti": "Argenti", "Argenti": "Argenti",
"Arlan": "Arlan", "Arlan": "Arlan",
"Asta": "Asta", "Asta": "Asta",
"Aventurine": "Aventurino",
"Bailu": "Bailu", "Bailu": "Bailu",
"BlackSwan": "Cisne Negro", "BlackSwan": "Cisne Negro",
"Blade": "Blade", "Blade": "Blade",
@ -468,6 +475,7 @@
"Natasha": "Natasha", "Natasha": "Natasha",
"Pela": "Pela", "Pela": "Pela",
"Qingque": "Qingque", "Qingque": "Qingque",
"Robin": "Robin",
"RuanMei": "Ruan Mei", "RuanMei": "Ruan Mei",
"Sampo": "Sampo", "Sampo": "Sampo",
"Seele": "Seele", "Seele": "Seele",
@ -532,7 +540,8 @@
"Echo_of_War_Destruction_Beginning": "El principio de la Destrucción (Estación Espacial Herta)", "Echo_of_War_Destruction_Beginning": "El principio de la Destrucción (Estación Espacial Herta)",
"Echo_of_War_End_of_the_Eternal_Freeze": "El fin del Hielo Eterno (Jarilo-VI)", "Echo_of_War_End_of_the_Eternal_Freeze": "El fin del Hielo Eterno (Jarilo-VI)",
"Echo_of_War_Divine_Seed": "Semilla divina (El Luofu de Xianzhou)", "Echo_of_War_Divine_Seed": "Semilla divina (El Luofu de Xianzhou)",
"Echo_of_War_Borehole_Planet_Old_Crater": "Cráter del planeta devorado (Estación Espacial Herta)" "Echo_of_War_Borehole_Planet_Old_Crater": "Cráter del planeta devorado (Estación Espacial Herta)",
"Echo_of_War_Salutations_of_Ashen_Dreams": "Ecos de la guerra: Tributo del sueño ceniciento (Colonipenal)"
}, },
"Team": { "Team": {
"name": "Equipo de mazmorra", "name": "Equipo de mazmorra",
@ -800,94 +809,94 @@
"Name_1": { "Name_1": {
"name": "Preferencia de Encargo 1", "name": "Preferencia de Encargo 1",
"help": "", "help": "",
"Nine_Billion_Names": "Núcleo apagado (Nueve mil millones de nombres)",
"Destruction_of_the_Destroyer": "Instinto del ladrón (La destrucción del destructor)",
"Winter_Soldiers": "Pin del guardia (Los guerreros del invierno)",
"Born_to_Obey": "Componente antiguo (Creados para obedecer)",
"Root_Out_the_Turpitude": "Brote verde inmortal (La raíz del mal)",
"Fire_Lord_Inflames_Blades_of_War": "Componente artificial mecánico (Prendan los fuelles, fundan las armas)",
"A_Startling_Night_Terror": "Componente del acumulador de sueños (Pesadilla aterradora)",
"Tranquility_of_Vimala_bhumi": "Jirones de pensamientos (Limpieza y purificación)", "Tranquility_of_Vimala_bhumi": "Jirones de pensamientos (Limpieza y purificación)",
"Nameless_Land_Nameless_People": "Material de EXP de personaje (Lugar anónimo, personas anónimas)", "A_Startling_Night_Terror": "Componente del acumulador de sueños (Pesadilla aterradora)",
"Fire_Lord_Inflames_Blades_of_War": "Componente artificial mecánico (Prendan los fuelles, fundan las armas)",
"Root_Out_the_Turpitude": "Brote verde inmortal (La raíz del mal)",
"Born_to_Obey": "Componente antiguo (Creados para obedecer)",
"Winter_Soldiers": "Pin del guardia (Los guerreros del invierno)",
"Destruction_of_the_Destroyer": "Instinto del ladrón (La destrucción del destructor)",
"Nine_Billion_Names": "Núcleo apagado (Nueve mil millones de nombres)",
"Akashic_Records": "Material de EXP de conos de luz (Los Registros de Akasha)", "Akashic_Records": "Material de EXP de conos de luz (Los Registros de Akasha)",
"Nameless_Land_Nameless_People": "Material de EXP de personaje (Lugar anónimo, personas anónimas)",
"The_Invisible_Hand": "Crédito (La mano invisible)", "The_Invisible_Hand": "Crédito (La mano invisible)",
"Abandoned_and_Insulted": "Flogisto & Metal (Abandonado e insultado)", "Scalpel_and_Screwdriver": "Engranaje oxidado & Muela vieja (Bisturí y destornillador)",
"Spring_of_Life": "Agua sólida & Partícula virtual (La fuente de la vida)",
"The_Land_of_Gold": "Ingredientes básicos & Arroz proteico (Tierra de oportunidades)",
"The_Blossom_in_the_Storm": "Líquido gaseoso & Semilla (Flores en la tormenta)",
"Legend_of_the_Puppet_Master": "Componentes mecánicos abandonados & Unidad de ábaco de jade (La leyenda del titiritero)",
"The_Wages_of_Humanity": "Cosecha tan alta como una persona & Extracto de hierbas medicinales (La paga de la humanidad)", "The_Wages_of_Humanity": "Cosecha tan alta como una persona & Extracto de hierbas medicinales (La paga de la humanidad)",
"Legend_of_the_Puppet_Master": "Componentes mecánicos abandonados & Unidad de ábaco de jade (La leyenda del titiritero)",
"The_Land_of_Gold": "Ingredientes básicos & Arroz proteico (Tierra de oportunidades)",
"Spring_of_Life": "Agua sólida & Partícula virtual (La fuente de la vida)",
"Fragments_of_Illusory_Dreams": "Tranquilidad & Sueños rotos (Fragmentos de sueños ilusorios)", "Fragments_of_Illusory_Dreams": "Tranquilidad & Sueños rotos (Fragmentos de sueños ilusorios)",
"Scalpel_and_Screwdriver": "Engranaje oxidado & Muela vieja (Bisturí y destornillador)" "The_Blossom_in_the_Storm": "Líquido gaseoso & Semilla (Flores en la tormenta)",
"Abandoned_and_Insulted": "Flogisto & Metal (Abandonado e insultado)"
}, },
"Name_2": { "Name_2": {
"name": "Preferencia de Encargo 2", "name": "Preferencia de Encargo 2",
"help": "", "help": "",
"Nine_Billion_Names": "Núcleo apagado (Nueve mil millones de nombres)",
"Destruction_of_the_Destroyer": "Instinto del ladrón (La destrucción del destructor)",
"Winter_Soldiers": "Pin del guardia (Los guerreros del invierno)",
"Born_to_Obey": "Componente antiguo (Creados para obedecer)",
"Root_Out_the_Turpitude": "Brote verde inmortal (La raíz del mal)",
"Fire_Lord_Inflames_Blades_of_War": "Componente artificial mecánico (Prendan los fuelles, fundan las armas)",
"A_Startling_Night_Terror": "Componente del acumulador de sueños (Pesadilla aterradora)",
"Tranquility_of_Vimala_bhumi": "Jirones de pensamientos (Limpieza y purificación)", "Tranquility_of_Vimala_bhumi": "Jirones de pensamientos (Limpieza y purificación)",
"Nameless_Land_Nameless_People": "Material de EXP de personaje (Lugar anónimo, personas anónimas)", "A_Startling_Night_Terror": "Componente del acumulador de sueños (Pesadilla aterradora)",
"Fire_Lord_Inflames_Blades_of_War": "Componente artificial mecánico (Prendan los fuelles, fundan las armas)",
"Root_Out_the_Turpitude": "Brote verde inmortal (La raíz del mal)",
"Born_to_Obey": "Componente antiguo (Creados para obedecer)",
"Winter_Soldiers": "Pin del guardia (Los guerreros del invierno)",
"Destruction_of_the_Destroyer": "Instinto del ladrón (La destrucción del destructor)",
"Nine_Billion_Names": "Núcleo apagado (Nueve mil millones de nombres)",
"Akashic_Records": "Material de EXP de conos de luz (Los Registros de Akasha)", "Akashic_Records": "Material de EXP de conos de luz (Los Registros de Akasha)",
"Nameless_Land_Nameless_People": "Material de EXP de personaje (Lugar anónimo, personas anónimas)",
"The_Invisible_Hand": "Crédito (La mano invisible)", "The_Invisible_Hand": "Crédito (La mano invisible)",
"Abandoned_and_Insulted": "Flogisto & Metal (Abandonado e insultado)", "Scalpel_and_Screwdriver": "Engranaje oxidado & Muela vieja (Bisturí y destornillador)",
"Spring_of_Life": "Agua sólida & Partícula virtual (La fuente de la vida)",
"The_Land_of_Gold": "Ingredientes básicos & Arroz proteico (Tierra de oportunidades)",
"The_Blossom_in_the_Storm": "Líquido gaseoso & Semilla (Flores en la tormenta)",
"Legend_of_the_Puppet_Master": "Componentes mecánicos abandonados & Unidad de ábaco de jade (La leyenda del titiritero)",
"The_Wages_of_Humanity": "Cosecha tan alta como una persona & Extracto de hierbas medicinales (La paga de la humanidad)", "The_Wages_of_Humanity": "Cosecha tan alta como una persona & Extracto de hierbas medicinales (La paga de la humanidad)",
"Legend_of_the_Puppet_Master": "Componentes mecánicos abandonados & Unidad de ábaco de jade (La leyenda del titiritero)",
"The_Land_of_Gold": "Ingredientes básicos & Arroz proteico (Tierra de oportunidades)",
"Spring_of_Life": "Agua sólida & Partícula virtual (La fuente de la vida)",
"Fragments_of_Illusory_Dreams": "Tranquilidad & Sueños rotos (Fragmentos de sueños ilusorios)", "Fragments_of_Illusory_Dreams": "Tranquilidad & Sueños rotos (Fragmentos de sueños ilusorios)",
"Scalpel_and_Screwdriver": "Engranaje oxidado & Muela vieja (Bisturí y destornillador)" "The_Blossom_in_the_Storm": "Líquido gaseoso & Semilla (Flores en la tormenta)",
"Abandoned_and_Insulted": "Flogisto & Metal (Abandonado e insultado)"
}, },
"Name_3": { "Name_3": {
"name": "Preferencia de Encargo 3", "name": "Preferencia de Encargo 3",
"help": "", "help": "",
"Nine_Billion_Names": "Núcleo apagado (Nueve mil millones de nombres)",
"Destruction_of_the_Destroyer": "Instinto del ladrón (La destrucción del destructor)",
"Winter_Soldiers": "Pin del guardia (Los guerreros del invierno)",
"Born_to_Obey": "Componente antiguo (Creados para obedecer)",
"Root_Out_the_Turpitude": "Brote verde inmortal (La raíz del mal)",
"Fire_Lord_Inflames_Blades_of_War": "Componente artificial mecánico (Prendan los fuelles, fundan las armas)",
"A_Startling_Night_Terror": "Componente del acumulador de sueños (Pesadilla aterradora)",
"Tranquility_of_Vimala_bhumi": "Jirones de pensamientos (Limpieza y purificación)", "Tranquility_of_Vimala_bhumi": "Jirones de pensamientos (Limpieza y purificación)",
"Nameless_Land_Nameless_People": "Material de EXP de personaje (Lugar anónimo, personas anónimas)", "A_Startling_Night_Terror": "Componente del acumulador de sueños (Pesadilla aterradora)",
"Fire_Lord_Inflames_Blades_of_War": "Componente artificial mecánico (Prendan los fuelles, fundan las armas)",
"Root_Out_the_Turpitude": "Brote verde inmortal (La raíz del mal)",
"Born_to_Obey": "Componente antiguo (Creados para obedecer)",
"Winter_Soldiers": "Pin del guardia (Los guerreros del invierno)",
"Destruction_of_the_Destroyer": "Instinto del ladrón (La destrucción del destructor)",
"Nine_Billion_Names": "Núcleo apagado (Nueve mil millones de nombres)",
"Akashic_Records": "Material de EXP de conos de luz (Los Registros de Akasha)", "Akashic_Records": "Material de EXP de conos de luz (Los Registros de Akasha)",
"Nameless_Land_Nameless_People": "Material de EXP de personaje (Lugar anónimo, personas anónimas)",
"The_Invisible_Hand": "Crédito (La mano invisible)", "The_Invisible_Hand": "Crédito (La mano invisible)",
"Abandoned_and_Insulted": "Flogisto & Metal (Abandonado e insultado)", "Scalpel_and_Screwdriver": "Engranaje oxidado & Muela vieja (Bisturí y destornillador)",
"Spring_of_Life": "Agua sólida & Partícula virtual (La fuente de la vida)",
"The_Land_of_Gold": "Ingredientes básicos & Arroz proteico (Tierra de oportunidades)",
"The_Blossom_in_the_Storm": "Líquido gaseoso & Semilla (Flores en la tormenta)",
"Legend_of_the_Puppet_Master": "Componentes mecánicos abandonados & Unidad de ábaco de jade (La leyenda del titiritero)",
"The_Wages_of_Humanity": "Cosecha tan alta como una persona & Extracto de hierbas medicinales (La paga de la humanidad)", "The_Wages_of_Humanity": "Cosecha tan alta como una persona & Extracto de hierbas medicinales (La paga de la humanidad)",
"Legend_of_the_Puppet_Master": "Componentes mecánicos abandonados & Unidad de ábaco de jade (La leyenda del titiritero)",
"The_Land_of_Gold": "Ingredientes básicos & Arroz proteico (Tierra de oportunidades)",
"Spring_of_Life": "Agua sólida & Partícula virtual (La fuente de la vida)",
"Fragments_of_Illusory_Dreams": "Tranquilidad & Sueños rotos (Fragmentos de sueños ilusorios)", "Fragments_of_Illusory_Dreams": "Tranquilidad & Sueños rotos (Fragmentos de sueños ilusorios)",
"Scalpel_and_Screwdriver": "Engranaje oxidado & Muela vieja (Bisturí y destornillador)" "The_Blossom_in_the_Storm": "Líquido gaseoso & Semilla (Flores en la tormenta)",
"Abandoned_and_Insulted": "Flogisto & Metal (Abandonado e insultado)"
}, },
"Name_4": { "Name_4": {
"name": "Preferencia de Encargo 4", "name": "Preferencia de Encargo 4",
"help": "", "help": "",
"Nine_Billion_Names": "Núcleo apagado (Nueve mil millones de nombres)",
"Destruction_of_the_Destroyer": "Instinto del ladrón (La destrucción del destructor)",
"Winter_Soldiers": "Pin del guardia (Los guerreros del invierno)",
"Born_to_Obey": "Componente antiguo (Creados para obedecer)",
"Root_Out_the_Turpitude": "Brote verde inmortal (La raíz del mal)",
"Fire_Lord_Inflames_Blades_of_War": "Componente artificial mecánico (Prendan los fuelles, fundan las armas)",
"A_Startling_Night_Terror": "Componente del acumulador de sueños (Pesadilla aterradora)",
"Tranquility_of_Vimala_bhumi": "Jirones de pensamientos (Limpieza y purificación)", "Tranquility_of_Vimala_bhumi": "Jirones de pensamientos (Limpieza y purificación)",
"Nameless_Land_Nameless_People": "Material de EXP de personaje (Lugar anónimo, personas anónimas)", "A_Startling_Night_Terror": "Componente del acumulador de sueños (Pesadilla aterradora)",
"Fire_Lord_Inflames_Blades_of_War": "Componente artificial mecánico (Prendan los fuelles, fundan las armas)",
"Root_Out_the_Turpitude": "Brote verde inmortal (La raíz del mal)",
"Born_to_Obey": "Componente antiguo (Creados para obedecer)",
"Winter_Soldiers": "Pin del guardia (Los guerreros del invierno)",
"Destruction_of_the_Destroyer": "Instinto del ladrón (La destrucción del destructor)",
"Nine_Billion_Names": "Núcleo apagado (Nueve mil millones de nombres)",
"Akashic_Records": "Material de EXP de conos de luz (Los Registros de Akasha)", "Akashic_Records": "Material de EXP de conos de luz (Los Registros de Akasha)",
"Nameless_Land_Nameless_People": "Material de EXP de personaje (Lugar anónimo, personas anónimas)",
"The_Invisible_Hand": "Crédito (La mano invisible)", "The_Invisible_Hand": "Crédito (La mano invisible)",
"Abandoned_and_Insulted": "Flogisto & Metal (Abandonado e insultado)", "Scalpel_and_Screwdriver": "Engranaje oxidado & Muela vieja (Bisturí y destornillador)",
"Spring_of_Life": "Agua sólida & Partícula virtual (La fuente de la vida)",
"The_Land_of_Gold": "Ingredientes básicos & Arroz proteico (Tierra de oportunidades)",
"The_Blossom_in_the_Storm": "Líquido gaseoso & Semilla (Flores en la tormenta)",
"Legend_of_the_Puppet_Master": "Componentes mecánicos abandonados & Unidad de ábaco de jade (La leyenda del titiritero)",
"The_Wages_of_Humanity": "Cosecha tan alta como una persona & Extracto de hierbas medicinales (La paga de la humanidad)", "The_Wages_of_Humanity": "Cosecha tan alta como una persona & Extracto de hierbas medicinales (La paga de la humanidad)",
"Legend_of_the_Puppet_Master": "Componentes mecánicos abandonados & Unidad de ábaco de jade (La leyenda del titiritero)",
"The_Land_of_Gold": "Ingredientes básicos & Arroz proteico (Tierra de oportunidades)",
"Spring_of_Life": "Agua sólida & Partícula virtual (La fuente de la vida)",
"Fragments_of_Illusory_Dreams": "Tranquilidad & Sueños rotos (Fragmentos de sueños ilusorios)", "Fragments_of_Illusory_Dreams": "Tranquilidad & Sueños rotos (Fragmentos de sueños ilusorios)",
"Scalpel_and_Screwdriver": "Engranaje oxidado & Muela vieja (Bisturí y destornillador)" "The_Blossom_in_the_Storm": "Líquido gaseoso & Semilla (Flores en la tormenta)",
"Abandoned_and_Insulted": "Flogisto & Metal (Abandonado e insultado)"
}, },
"Duration": { "Duration": {
"name": "Duración del encargo", "name": "Duración del encargo",
@ -969,9 +978,17 @@
"name": "Participa en doble planer evento", "name": "Participa en doble planer evento",
"help": "" "help": ""
}, },
"WeeklyFarming": {
"name": "Granja 100 élites semanalmente",
"help": ""
},
"UseStamina": { "UseStamina": {
"name": "Reclamar de planers mediante poder trazacaminos", "name": "Reclamar de planers mediante poder trazacaminos",
"help": "La tarea de mazmorra ya no se ejecutará y todo el poder trazacaminos se usará primero para reclamar recompensas de inmersión, excepto para eventos dobles" "help": "La tarea de mazmorra ya no se ejecutará y todo el poder trazacaminos se usará primero para reclamar recompensas de inmersión, excepto para eventos dobles"
},
"SimulatedUniverseFarm": {
"name": "Progreso de élites derrotadas",
"help": ""
} }
}, },
"RogueBlessing": { "RogueBlessing": {

View File

@ -131,7 +131,8 @@
"aScreenCap_nc": "aScreenCap_nc", "aScreenCap_nc": "aScreenCap_nc",
"DroidCast": "DroidCast", "DroidCast": "DroidCast",
"DroidCast_raw": "DroidCast_raw", "DroidCast_raw": "DroidCast_raw",
"scrcpy": "scrcpy" "scrcpy": "scrcpy",
"nemu_ipc": "nemu_ipc"
}, },
"ControlMethod": { "ControlMethod": {
"name": "Emulator.ControlMethod.name", "name": "Emulator.ControlMethod.name",
@ -261,6 +262,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "軌跡素材:存護(サポート部分)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "軌跡素材:存護(サポート部分)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "軌跡素材:存護(クラークフィルムランド)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "軌跡素材:存護(クラークフィルムランド)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "軌跡素材:巡狩(郊外雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "軌跡素材:巡狩(郊外雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "軌跡素材:巡狩(スラーダ熱砂オーディション会場)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "軌跡素材:豊穣(外縁通路)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "軌跡素材:豊穣(外縁通路)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "軌跡素材:豊穣(綏園)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "軌跡素材:豊穣(綏園)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "軌跡素材:知恵(リベットタウン)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "軌跡素材:知恵(リベットタウン)",
@ -270,6 +272,7 @@
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "軌跡素材:虚無(丹鼎司)", "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "軌跡素材:虚無(丹鼎司)",
"Stagnant_Shadow_Spike": "キャラクター昇格素材:物理(ナターシャ / クラーラ / ルカ / 素裳)", "Stagnant_Shadow_Spike": "キャラクター昇格素材:物理(ナターシャ / クラーラ / ルカ / 素裳)",
"Stagnant_Shadow_Perdition": "キャラクター昇格素材:物理(寒鴉 / アルジェンティ)", "Stagnant_Shadow_Perdition": "キャラクター昇格素材:物理(寒鴉 / アルジェンティ)",
"Stagnant_Shadow_Duty": "キャラクター昇格素材:物理(ブートヒル / ロビン)",
"Stagnant_Shadow_Blaze": "キャラクター昇格素材:炎(姫子 / アスター / フック)", "Stagnant_Shadow_Blaze": "キャラクター昇格素材:炎(姫子 / アスター / フック)",
"Stagnant_Shadow_Scorch": "キャラクター昇格素材:炎(桂乃芬 / トパーズ&カブ)", "Stagnant_Shadow_Scorch": "キャラクター昇格素材:炎(桂乃芬 / トパーズ&カブ)",
"Stagnant_Shadow_Ire": "キャラクター昇格素材:炎(ギャラガー)", "Stagnant_Shadow_Ire": "キャラクター昇格素材:炎(ギャラガー)",
@ -312,6 +315,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "軌跡素材:存護(サポート部分)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "軌跡素材:存護(サポート部分)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "軌跡素材:存護(クラークフィルムランド)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "軌跡素材:存護(クラークフィルムランド)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "軌跡素材:巡狩(郊外雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "軌跡素材:巡狩(郊外雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "軌跡素材:巡狩(スラーダ熱砂オーディション会場)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "軌跡素材:豊穣(外縁通路)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "軌跡素材:豊穣(外縁通路)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "軌跡素材:豊穣(綏園)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "軌跡素材:豊穣(綏園)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "軌跡素材:知恵(リベットタウン)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "軌跡素材:知恵(リベットタウン)",
@ -375,6 +379,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "軌跡素材:存護(サポート部分)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "軌跡素材:存護(サポート部分)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "軌跡素材:存護(クラークフィルムランド)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "軌跡素材:存護(クラークフィルムランド)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "軌跡素材:巡狩(郊外雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "軌跡素材:巡狩(郊外雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "軌跡素材:巡狩(スラーダ熱砂オーディション会場)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "軌跡素材:豊穣(外縁通路)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "軌跡素材:豊穣(外縁通路)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "軌跡素材:豊穣(綏園)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "軌跡素材:豊穣(綏園)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "軌跡素材:知恵(リベットタウン)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "軌跡素材:知恵(リベットタウン)",
@ -389,6 +394,7 @@
"do_not_achieve": "do_not_achieve", "do_not_achieve": "do_not_achieve",
"Stagnant_Shadow_Spike": "キャラクター昇格素材:物理(ナターシャ / クラーラ / ルカ / 素裳)", "Stagnant_Shadow_Spike": "キャラクター昇格素材:物理(ナターシャ / クラーラ / ルカ / 素裳)",
"Stagnant_Shadow_Perdition": "キャラクター昇格素材:物理(寒鴉 / アルジェンティ)", "Stagnant_Shadow_Perdition": "キャラクター昇格素材:物理(寒鴉 / アルジェンティ)",
"Stagnant_Shadow_Duty": "キャラクター昇格素材:物理(ブートヒル / ロビン)",
"Stagnant_Shadow_Blaze": "キャラクター昇格素材:炎(姫子 / アスター / フック)", "Stagnant_Shadow_Blaze": "キャラクター昇格素材:炎(姫子 / アスター / フック)",
"Stagnant_Shadow_Scorch": "キャラクター昇格素材:炎(桂乃芬 / トパーズ&カブ)", "Stagnant_Shadow_Scorch": "キャラクター昇格素材:炎(桂乃芬 / トパーズ&カブ)",
"Stagnant_Shadow_Ire": "キャラクター昇格素材:炎(ギャラガー)", "Stagnant_Shadow_Ire": "キャラクター昇格素材:炎(ギャラガー)",
@ -440,6 +446,7 @@
"Argenti": "アルジェンティ", "Argenti": "アルジェンティ",
"Arlan": "アーラン", "Arlan": "アーラン",
"Asta": "アスター", "Asta": "アスター",
"Aventurine": "アベンチュリン",
"Bailu": "白露", "Bailu": "白露",
"BlackSwan": "ブラックスワン", "BlackSwan": "ブラックスワン",
"Blade": "刃", "Blade": "刃",
@ -468,6 +475,7 @@
"Natasha": "ナターシャ", "Natasha": "ナターシャ",
"Pela": "ペラ", "Pela": "ペラ",
"Qingque": "青雀", "Qingque": "青雀",
"Robin": "ロビン",
"RuanMei": "ルアン・メェイ", "RuanMei": "ルアン・メェイ",
"Sampo": "サンポ", "Sampo": "サンポ",
"Seele": "ゼーレ", "Seele": "ゼーレ",
@ -532,7 +540,8 @@
"Echo_of_War_Destruction_Beginning": "歴戦余韻・壊滅の始まり (宇宙ステーション「ヘルタ」)", "Echo_of_War_Destruction_Beginning": "歴戦余韻・壊滅の始まり (宇宙ステーション「ヘルタ」)",
"Echo_of_War_End_of_the_Eternal_Freeze": "歴戦余韻・寒波の幕切れ (ヤリーロ-VI)", "Echo_of_War_End_of_the_Eternal_Freeze": "歴戦余韻・寒波の幕切れ (ヤリーロ-VI)",
"Echo_of_War_Divine_Seed": "歴戦余韻・不死の神実 (仙舟「羅浮」)", "Echo_of_War_Divine_Seed": "歴戦余韻・不死の神実 (仙舟「羅浮」)",
"Echo_of_War_Borehole_Planet_Old_Crater": "歴戦余韻・星を蝕む往日の面影 (宇宙ステーション「ヘルタ」)" "Echo_of_War_Borehole_Planet_Old_Crater": "歴戦余韻・星を蝕む往日の面影 (宇宙ステーション「ヘルタ」)",
"Echo_of_War_Salutations_of_Ashen_Dreams": "歴戦余韻・現世の夢の礼賛 (ピノコニー)"
}, },
"Team": { "Team": {
"name": "Weekly.Team.name", "name": "Weekly.Team.name",
@ -800,94 +809,94 @@
"Name_1": { "Name_1": {
"name": "依頼 1", "name": "依頼 1",
"help": "", "help": "",
"Nine_Billion_Names": "消滅した原核(九十億の御名)",
"Destruction_of_the_Destroyer": "略奪の本能(壊滅者の覆没)",
"Winter_Soldiers": "シルバーメインの釦(寒冬の戦士たち)",
"Born_to_Obey": "古代パーツ(生まれながらに服従する)",
"Root_Out_the_Turpitude": "永寿の萌芽(悪孽を根絶やしに)",
"Fire_Lord_Inflames_Blades_of_War": "工造機関(剣戟を焼却する火帝炉)",
"A_Startling_Night_Terror": "ドリームコレクションパーツ(魂震える悪夢)",
"Tranquility_of_Vimala_bhumi": "思考の粉末(離垢清浄)", "Tranquility_of_Vimala_bhumi": "思考の粉末(離垢清浄)",
"Nameless_Land_Nameless_People": "キャラクター経験値素材(無名の地、無名の人)", "A_Startling_Night_Terror": "ドリームコレクションパーツ(魂震える悪夢)",
"Fire_Lord_Inflames_Blades_of_War": "工造機関(剣戟を焼却する火帝炉)",
"Root_Out_the_Turpitude": "永寿の萌芽(悪孽を根絶やしに)",
"Born_to_Obey": "古代パーツ(生まれながらに服従する)",
"Winter_Soldiers": "シルバーメインの釦(寒冬の戦士たち)",
"Destruction_of_the_Destroyer": "略奪の本能(壊滅者の覆没)",
"Nine_Billion_Names": "消滅した原核(九十億の御名)",
"Akashic_Records": "光円錐経験値素材(アーカーシャの記録)", "Akashic_Records": "光円錐経験値素材(アーカーシャの記録)",
"Nameless_Land_Nameless_People": "キャラクター経験値素材(無名の地、無名の人)",
"The_Invisible_Hand": "信用ポイント(見えざる手)", "The_Invisible_Hand": "信用ポイント(見えざる手)",
"Abandoned_and_Insulted": "燃素 & 金属(捨てられしものと傷つけられしもの)", "Scalpel_and_Screwdriver": "錆びた歯車 & 古びた大臼歯(メスとスクリュードライバー)",
"Spring_of_Life": "固形純水 & 仮想粒子(生命の泉)",
"The_Land_of_Gold": "基本食材 & タンパク米(黄金の大地)",
"The_Blossom_in_the_Storm": "気態流体 & 種子(嵐の中で咲き誇る花)",
"Legend_of_the_Puppet_Master": "廃棄された機巧部品 & 玉兆単元(傀儡師伝説)",
"The_Wages_of_Humanity": "一人稲 & 薬草抽出物(人類扶養)", "The_Wages_of_Humanity": "一人稲 & 薬草抽出物(人類扶養)",
"Legend_of_the_Puppet_Master": "廃棄された機巧部品 & 玉兆単元(傀儡師伝説)",
"The_Land_of_Gold": "基本食材 & タンパク米(黄金の大地)",
"Spring_of_Life": "固形純水 & 仮想粒子(生命の泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 砕けた夢(幻夢の残片)", "Fragments_of_Illusory_Dreams": "安逸 & 砕けた夢(幻夢の残片)",
"Scalpel_and_Screwdriver": "錆びた歯車 & 古びた大臼歯(メスとスクリュードライバー)" "The_Blossom_in_the_Storm": "気態流体 & 種子(嵐の中で咲き誇る花)",
"Abandoned_and_Insulted": "燃素 & 金属(捨てられしものと傷つけられしもの)"
}, },
"Name_2": { "Name_2": {
"name": "依頼 2", "name": "依頼 2",
"help": "", "help": "",
"Nine_Billion_Names": "消滅した原核(九十億の御名)",
"Destruction_of_the_Destroyer": "略奪の本能(壊滅者の覆没)",
"Winter_Soldiers": "シルバーメインの釦(寒冬の戦士たち)",
"Born_to_Obey": "古代パーツ(生まれながらに服従する)",
"Root_Out_the_Turpitude": "永寿の萌芽(悪孽を根絶やしに)",
"Fire_Lord_Inflames_Blades_of_War": "工造機関(剣戟を焼却する火帝炉)",
"A_Startling_Night_Terror": "ドリームコレクションパーツ(魂震える悪夢)",
"Tranquility_of_Vimala_bhumi": "思考の粉末(離垢清浄)", "Tranquility_of_Vimala_bhumi": "思考の粉末(離垢清浄)",
"Nameless_Land_Nameless_People": "キャラクター経験値素材(無名の地、無名の人)", "A_Startling_Night_Terror": "ドリームコレクションパーツ(魂震える悪夢)",
"Fire_Lord_Inflames_Blades_of_War": "工造機関(剣戟を焼却する火帝炉)",
"Root_Out_the_Turpitude": "永寿の萌芽(悪孽を根絶やしに)",
"Born_to_Obey": "古代パーツ(生まれながらに服従する)",
"Winter_Soldiers": "シルバーメインの釦(寒冬の戦士たち)",
"Destruction_of_the_Destroyer": "略奪の本能(壊滅者の覆没)",
"Nine_Billion_Names": "消滅した原核(九十億の御名)",
"Akashic_Records": "光円錐経験値素材(アーカーシャの記録)", "Akashic_Records": "光円錐経験値素材(アーカーシャの記録)",
"Nameless_Land_Nameless_People": "キャラクター経験値素材(無名の地、無名の人)",
"The_Invisible_Hand": "信用ポイント(見えざる手)", "The_Invisible_Hand": "信用ポイント(見えざる手)",
"Abandoned_and_Insulted": "燃素 & 金属(捨てられしものと傷つけられしもの)", "Scalpel_and_Screwdriver": "錆びた歯車 & 古びた大臼歯(メスとスクリュードライバー)",
"Spring_of_Life": "固形純水 & 仮想粒子(生命の泉)",
"The_Land_of_Gold": "基本食材 & タンパク米(黄金の大地)",
"The_Blossom_in_the_Storm": "気態流体 & 種子(嵐の中で咲き誇る花)",
"Legend_of_the_Puppet_Master": "廃棄された機巧部品 & 玉兆単元(傀儡師伝説)",
"The_Wages_of_Humanity": "一人稲 & 薬草抽出物(人類扶養)", "The_Wages_of_Humanity": "一人稲 & 薬草抽出物(人類扶養)",
"Legend_of_the_Puppet_Master": "廃棄された機巧部品 & 玉兆単元(傀儡師伝説)",
"The_Land_of_Gold": "基本食材 & タンパク米(黄金の大地)",
"Spring_of_Life": "固形純水 & 仮想粒子(生命の泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 砕けた夢(幻夢の残片)", "Fragments_of_Illusory_Dreams": "安逸 & 砕けた夢(幻夢の残片)",
"Scalpel_and_Screwdriver": "錆びた歯車 & 古びた大臼歯(メスとスクリュードライバー)" "The_Blossom_in_the_Storm": "気態流体 & 種子(嵐の中で咲き誇る花)",
"Abandoned_and_Insulted": "燃素 & 金属(捨てられしものと傷つけられしもの)"
}, },
"Name_3": { "Name_3": {
"name": "依頼 3", "name": "依頼 3",
"help": "", "help": "",
"Nine_Billion_Names": "消滅した原核(九十億の御名)",
"Destruction_of_the_Destroyer": "略奪の本能(壊滅者の覆没)",
"Winter_Soldiers": "シルバーメインの釦(寒冬の戦士たち)",
"Born_to_Obey": "古代パーツ(生まれながらに服従する)",
"Root_Out_the_Turpitude": "永寿の萌芽(悪孽を根絶やしに)",
"Fire_Lord_Inflames_Blades_of_War": "工造機関(剣戟を焼却する火帝炉)",
"A_Startling_Night_Terror": "ドリームコレクションパーツ(魂震える悪夢)",
"Tranquility_of_Vimala_bhumi": "思考の粉末(離垢清浄)", "Tranquility_of_Vimala_bhumi": "思考の粉末(離垢清浄)",
"Nameless_Land_Nameless_People": "キャラクター経験値素材(無名の地、無名の人)", "A_Startling_Night_Terror": "ドリームコレクションパーツ(魂震える悪夢)",
"Fire_Lord_Inflames_Blades_of_War": "工造機関(剣戟を焼却する火帝炉)",
"Root_Out_the_Turpitude": "永寿の萌芽(悪孽を根絶やしに)",
"Born_to_Obey": "古代パーツ(生まれながらに服従する)",
"Winter_Soldiers": "シルバーメインの釦(寒冬の戦士たち)",
"Destruction_of_the_Destroyer": "略奪の本能(壊滅者の覆没)",
"Nine_Billion_Names": "消滅した原核(九十億の御名)",
"Akashic_Records": "光円錐経験値素材(アーカーシャの記録)", "Akashic_Records": "光円錐経験値素材(アーカーシャの記録)",
"Nameless_Land_Nameless_People": "キャラクター経験値素材(無名の地、無名の人)",
"The_Invisible_Hand": "信用ポイント(見えざる手)", "The_Invisible_Hand": "信用ポイント(見えざる手)",
"Abandoned_and_Insulted": "燃素 & 金属(捨てられしものと傷つけられしもの)", "Scalpel_and_Screwdriver": "錆びた歯車 & 古びた大臼歯(メスとスクリュードライバー)",
"Spring_of_Life": "固形純水 & 仮想粒子(生命の泉)",
"The_Land_of_Gold": "基本食材 & タンパク米(黄金の大地)",
"The_Blossom_in_the_Storm": "気態流体 & 種子(嵐の中で咲き誇る花)",
"Legend_of_the_Puppet_Master": "廃棄された機巧部品 & 玉兆単元(傀儡師伝説)",
"The_Wages_of_Humanity": "一人稲 & 薬草抽出物(人類扶養)", "The_Wages_of_Humanity": "一人稲 & 薬草抽出物(人類扶養)",
"Legend_of_the_Puppet_Master": "廃棄された機巧部品 & 玉兆単元(傀儡師伝説)",
"The_Land_of_Gold": "基本食材 & タンパク米(黄金の大地)",
"Spring_of_Life": "固形純水 & 仮想粒子(生命の泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 砕けた夢(幻夢の残片)", "Fragments_of_Illusory_Dreams": "安逸 & 砕けた夢(幻夢の残片)",
"Scalpel_and_Screwdriver": "錆びた歯車 & 古びた大臼歯(メスとスクリュードライバー)" "The_Blossom_in_the_Storm": "気態流体 & 種子(嵐の中で咲き誇る花)",
"Abandoned_and_Insulted": "燃素 & 金属(捨てられしものと傷つけられしもの)"
}, },
"Name_4": { "Name_4": {
"name": "依頼 4", "name": "依頼 4",
"help": "", "help": "",
"Nine_Billion_Names": "消滅した原核(九十億の御名)",
"Destruction_of_the_Destroyer": "略奪の本能(壊滅者の覆没)",
"Winter_Soldiers": "シルバーメインの釦(寒冬の戦士たち)",
"Born_to_Obey": "古代パーツ(生まれながらに服従する)",
"Root_Out_the_Turpitude": "永寿の萌芽(悪孽を根絶やしに)",
"Fire_Lord_Inflames_Blades_of_War": "工造機関(剣戟を焼却する火帝炉)",
"A_Startling_Night_Terror": "ドリームコレクションパーツ(魂震える悪夢)",
"Tranquility_of_Vimala_bhumi": "思考の粉末(離垢清浄)", "Tranquility_of_Vimala_bhumi": "思考の粉末(離垢清浄)",
"Nameless_Land_Nameless_People": "キャラクター経験値素材(無名の地、無名の人)", "A_Startling_Night_Terror": "ドリームコレクションパーツ(魂震える悪夢)",
"Fire_Lord_Inflames_Blades_of_War": "工造機関(剣戟を焼却する火帝炉)",
"Root_Out_the_Turpitude": "永寿の萌芽(悪孽を根絶やしに)",
"Born_to_Obey": "古代パーツ(生まれながらに服従する)",
"Winter_Soldiers": "シルバーメインの釦(寒冬の戦士たち)",
"Destruction_of_the_Destroyer": "略奪の本能(壊滅者の覆没)",
"Nine_Billion_Names": "消滅した原核(九十億の御名)",
"Akashic_Records": "光円錐経験値素材(アーカーシャの記録)", "Akashic_Records": "光円錐経験値素材(アーカーシャの記録)",
"Nameless_Land_Nameless_People": "キャラクター経験値素材(無名の地、無名の人)",
"The_Invisible_Hand": "信用ポイント(見えざる手)", "The_Invisible_Hand": "信用ポイント(見えざる手)",
"Abandoned_and_Insulted": "燃素 & 金属(捨てられしものと傷つけられしもの)", "Scalpel_and_Screwdriver": "錆びた歯車 & 古びた大臼歯(メスとスクリュードライバー)",
"Spring_of_Life": "固形純水 & 仮想粒子(生命の泉)",
"The_Land_of_Gold": "基本食材 & タンパク米(黄金の大地)",
"The_Blossom_in_the_Storm": "気態流体 & 種子(嵐の中で咲き誇る花)",
"Legend_of_the_Puppet_Master": "廃棄された機巧部品 & 玉兆単元(傀儡師伝説)",
"The_Wages_of_Humanity": "一人稲 & 薬草抽出物(人類扶養)", "The_Wages_of_Humanity": "一人稲 & 薬草抽出物(人類扶養)",
"Legend_of_the_Puppet_Master": "廃棄された機巧部品 & 玉兆単元(傀儡師伝説)",
"The_Land_of_Gold": "基本食材 & タンパク米(黄金の大地)",
"Spring_of_Life": "固形純水 & 仮想粒子(生命の泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 砕けた夢(幻夢の残片)", "Fragments_of_Illusory_Dreams": "安逸 & 砕けた夢(幻夢の残片)",
"Scalpel_and_Screwdriver": "錆びた歯車 & 古びた大臼歯(メスとスクリュードライバー)" "The_Blossom_in_the_Storm": "気態流体 & 種子(嵐の中で咲き誇る花)",
"Abandoned_and_Insulted": "燃素 & 金属(捨てられしものと傷つけられしもの)"
}, },
"Duration": { "Duration": {
"name": "派遣時間", "name": "派遣時間",
@ -969,9 +978,17 @@
"name": "RogueWorld.DoubleEvent.name", "name": "RogueWorld.DoubleEvent.name",
"help": "RogueWorld.DoubleEvent.help" "help": "RogueWorld.DoubleEvent.help"
}, },
"WeeklyFarming": {
"name": "RogueWorld.WeeklyFarming.name",
"help": "RogueWorld.WeeklyFarming.help"
},
"UseStamina": { "UseStamina": {
"name": "RogueWorld.UseStamina.name", "name": "RogueWorld.UseStamina.name",
"help": "RogueWorld.UseStamina.help" "help": "RogueWorld.UseStamina.help"
},
"SimulatedUniverseFarm": {
"name": "RogueWorld.SimulatedUniverseFarm.name",
"help": "RogueWorld.SimulatedUniverseFarm.help"
} }
}, },
"RogueBlessing": { "RogueBlessing": {

View File

@ -131,7 +131,8 @@
"aScreenCap_nc": "aScreenCap_nc", "aScreenCap_nc": "aScreenCap_nc",
"DroidCast": "DroidCast", "DroidCast": "DroidCast",
"DroidCast_raw": "DroidCast_raw", "DroidCast_raw": "DroidCast_raw",
"scrcpy": "scrcpy" "scrcpy": "scrcpy",
"nemu_ipc": "nemu_ipc"
}, },
"ControlMethod": { "ControlMethod": {
"name": "模拟器控制方案", "name": "模拟器控制方案",
@ -261,6 +262,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "行迹材料:存护(支援舱段)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "行迹材料:存护(支援舱段)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行迹材料:存护(克劳克影视乐园)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行迹材料:存护(克劳克影视乐园)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行迹材料:巡猎(城郊雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行迹材料:巡猎(城郊雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行迹材料:巡猎(苏乐达热砂海选会场)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行迹材料:丰饶(边缘通路)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行迹材料:丰饶(边缘通路)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行迹材料:丰饶(绥园)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行迹材料:丰饶(绥园)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "行迹材料:智识(铆钉镇)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "行迹材料:智识(铆钉镇)",
@ -270,6 +272,7 @@
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "行迹材料:虚无(丹鼎司)", "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "行迹材料:虚无(丹鼎司)",
"Stagnant_Shadow_Spike": "角色晋阶材料:物理(娜塔莎 / 克拉拉 / 卢卡 / 素裳)", "Stagnant_Shadow_Spike": "角色晋阶材料:物理(娜塔莎 / 克拉拉 / 卢卡 / 素裳)",
"Stagnant_Shadow_Perdition": "角色晋阶材料:物理(寒鸦 / 银枝)", "Stagnant_Shadow_Perdition": "角色晋阶材料:物理(寒鸦 / 银枝)",
"Stagnant_Shadow_Duty": "角色晋阶材料:物理(波提欧 / 知更鸟)",
"Stagnant_Shadow_Blaze": "角色晋阶材料:火(姬子 / 艾丝妲 / 虎克)", "Stagnant_Shadow_Blaze": "角色晋阶材料:火(姬子 / 艾丝妲 / 虎克)",
"Stagnant_Shadow_Scorch": "角色晋阶材料:火(桂乃芬 / 托帕&账账)", "Stagnant_Shadow_Scorch": "角色晋阶材料:火(桂乃芬 / 托帕&账账)",
"Stagnant_Shadow_Ire": "角色晋阶材料:火(加拉赫)", "Stagnant_Shadow_Ire": "角色晋阶材料:火(加拉赫)",
@ -312,6 +315,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "行迹材料:存护(支援舱段)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "行迹材料:存护(支援舱段)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行迹材料:存护(克劳克影视乐园)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行迹材料:存护(克劳克影视乐园)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行迹材料:巡猎(城郊雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行迹材料:巡猎(城郊雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行迹材料:巡猎(苏乐达热砂海选会场)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行迹材料:丰饶(边缘通路)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行迹材料:丰饶(边缘通路)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行迹材料:丰饶(绥园)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行迹材料:丰饶(绥园)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "行迹材料:智识(铆钉镇)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "行迹材料:智识(铆钉镇)",
@ -375,6 +379,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "行迹材料:存护(支援舱段)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "行迹材料:存护(支援舱段)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行迹材料:存护(克劳克影视乐园)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行迹材料:存护(克劳克影视乐园)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行迹材料:巡猎(城郊雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行迹材料:巡猎(城郊雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行迹材料:巡猎(苏乐达热砂海选会场)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行迹材料:丰饶(边缘通路)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行迹材料:丰饶(边缘通路)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行迹材料:丰饶(绥园)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行迹材料:丰饶(绥园)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "行迹材料:智识(铆钉镇)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "行迹材料:智识(铆钉镇)",
@ -389,6 +394,7 @@
"do_not_achieve": "不完成这个任务", "do_not_achieve": "不完成这个任务",
"Stagnant_Shadow_Spike": "角色晋阶材料:物理(娜塔莎 / 克拉拉 / 卢卡 / 素裳)", "Stagnant_Shadow_Spike": "角色晋阶材料:物理(娜塔莎 / 克拉拉 / 卢卡 / 素裳)",
"Stagnant_Shadow_Perdition": "角色晋阶材料:物理(寒鸦 / 银枝)", "Stagnant_Shadow_Perdition": "角色晋阶材料:物理(寒鸦 / 银枝)",
"Stagnant_Shadow_Duty": "角色晋阶材料:物理(波提欧 / 知更鸟)",
"Stagnant_Shadow_Blaze": "角色晋阶材料:火(姬子 / 艾丝妲 / 虎克)", "Stagnant_Shadow_Blaze": "角色晋阶材料:火(姬子 / 艾丝妲 / 虎克)",
"Stagnant_Shadow_Scorch": "角色晋阶材料:火(桂乃芬 / 托帕&账账)", "Stagnant_Shadow_Scorch": "角色晋阶材料:火(桂乃芬 / 托帕&账账)",
"Stagnant_Shadow_Ire": "角色晋阶材料:火(加拉赫)", "Stagnant_Shadow_Ire": "角色晋阶材料:火(加拉赫)",
@ -440,6 +446,7 @@
"Argenti": "银枝", "Argenti": "银枝",
"Arlan": "阿兰", "Arlan": "阿兰",
"Asta": "艾丝妲", "Asta": "艾丝妲",
"Aventurine": "砂金",
"Bailu": "白露", "Bailu": "白露",
"BlackSwan": "黑天鹅", "BlackSwan": "黑天鹅",
"Blade": "刃", "Blade": "刃",
@ -468,6 +475,7 @@
"Natasha": "娜塔莎", "Natasha": "娜塔莎",
"Pela": "佩拉", "Pela": "佩拉",
"Qingque": "青雀", "Qingque": "青雀",
"Robin": "知更鸟",
"RuanMei": "阮•梅", "RuanMei": "阮•梅",
"Sampo": "桑博", "Sampo": "桑博",
"Seele": "希儿", "Seele": "希儿",
@ -532,7 +540,8 @@
"Echo_of_War_Destruction_Beginning": "毁灭的开端•历战余响 (空间站「黑塔」)", "Echo_of_War_Destruction_Beginning": "毁灭的开端•历战余响 (空间站「黑塔」)",
"Echo_of_War_End_of_the_Eternal_Freeze": "寒潮的落幕•历战余响 (雅利洛-Ⅵ)", "Echo_of_War_End_of_the_Eternal_Freeze": "寒潮的落幕•历战余响 (雅利洛-Ⅵ)",
"Echo_of_War_Divine_Seed": "不死的神实•历战余响 (仙舟「罗浮」)", "Echo_of_War_Divine_Seed": "不死的神实•历战余响 (仙舟「罗浮」)",
"Echo_of_War_Borehole_Planet_Old_Crater": "蛀星的旧靥•历战余响 (空间站「黑塔」)" "Echo_of_War_Borehole_Planet_Old_Crater": "蛀星的旧靥•历战余响 (空间站「黑塔」)",
"Echo_of_War_Salutations_of_Ashen_Dreams": "尘梦的赞礼•历战余响 (匹诺康尼)"
}, },
"Team": { "Team": {
"name": "打本队伍", "name": "打本队伍",
@ -800,94 +809,94 @@
"Name_1": { "Name_1": {
"name": "第1个委托选择", "name": "第1个委托选择",
"help": "", "help": "",
"Nine_Billion_Names": "熄灭原核(九十亿个名字)",
"Destruction_of_the_Destroyer": "掠夺的本能(毁灭者的覆灭)",
"Winter_Soldiers": "铁卫扣饰(寒冬的战士们)",
"Born_to_Obey": "古代零件(生而服从)",
"Root_Out_the_Turpitude": "永寿幼芽(根除恶孽)",
"Fire_Lord_Inflames_Blades_of_War": "工造机杼(火帝动炉销剑戟)",
"A_Startling_Night_Terror": "蓄梦元件(劫梦惊魂)",
"Tranquility_of_Vimala_bhumi": "思绪末屑(离垢清净)", "Tranquility_of_Vimala_bhumi": "思绪末屑(离垢清净)",
"Nameless_Land_Nameless_People": "角色经验材料(无名之地,无名之人)", "A_Startling_Night_Terror": "蓄梦元件(劫梦惊魂)",
"Fire_Lord_Inflames_Blades_of_War": "工造机杼(火帝动炉销剑戟)",
"Root_Out_the_Turpitude": "永寿幼芽(根除恶孽)",
"Born_to_Obey": "古代零件(生而服从)",
"Winter_Soldiers": "铁卫扣饰(寒冬的战士们)",
"Destruction_of_the_Destroyer": "掠夺的本能(毁灭者的覆灭)",
"Nine_Billion_Names": "熄灭原核(九十亿个名字)",
"Akashic_Records": "光锥经验材料(阿卡夏记录)", "Akashic_Records": "光锥经验材料(阿卡夏记录)",
"Nameless_Land_Nameless_People": "角色经验材料(无名之地,无名之人)",
"The_Invisible_Hand": "信用点(看不见的手)", "The_Invisible_Hand": "信用点(看不见的手)",
"Abandoned_and_Insulted": "燃素 & 金属(被废弃与损害的)", "Scalpel_and_Screwdriver": "锈迹齿轮 & 老旧臼齿(手术刀与螺丝刀)",
"Spring_of_Life": "固态净水 & 虚粒子(生命之泉)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黄金大地)",
"The_Blossom_in_the_Storm": "气态流体 & 种子(风暴中怒放的花)",
"Legend_of_the_Puppet_Master": "废弃机巧零件 & 玉兆单元(偃师传说)",
"The_Wages_of_Humanity": "一人嘉禾 & 药草提取物(赡养人类)", "The_Wages_of_Humanity": "一人嘉禾 & 药草提取物(赡养人类)",
"Legend_of_the_Puppet_Master": "废弃机巧零件 & 玉兆单元(偃师传说)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黄金大地)",
"Spring_of_Life": "固态净水 & 虚粒子(生命之泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 碎梦(幻梦的残片)", "Fragments_of_Illusory_Dreams": "安逸 & 碎梦(幻梦的残片)",
"Scalpel_and_Screwdriver": "锈迹齿轮 & 老旧臼齿(手术刀与螺丝刀)" "The_Blossom_in_the_Storm": "气态流体 & 种子(风暴中怒放的花)",
"Abandoned_and_Insulted": "燃素 & 金属(被废弃与损害的)"
}, },
"Name_2": { "Name_2": {
"name": "第2个委托选择", "name": "第2个委托选择",
"help": "", "help": "",
"Nine_Billion_Names": "熄灭原核(九十亿个名字)",
"Destruction_of_the_Destroyer": "掠夺的本能(毁灭者的覆灭)",
"Winter_Soldiers": "铁卫扣饰(寒冬的战士们)",
"Born_to_Obey": "古代零件(生而服从)",
"Root_Out_the_Turpitude": "永寿幼芽(根除恶孽)",
"Fire_Lord_Inflames_Blades_of_War": "工造机杼(火帝动炉销剑戟)",
"A_Startling_Night_Terror": "蓄梦元件(劫梦惊魂)",
"Tranquility_of_Vimala_bhumi": "思绪末屑(离垢清净)", "Tranquility_of_Vimala_bhumi": "思绪末屑(离垢清净)",
"Nameless_Land_Nameless_People": "角色经验材料(无名之地,无名之人)", "A_Startling_Night_Terror": "蓄梦元件(劫梦惊魂)",
"Fire_Lord_Inflames_Blades_of_War": "工造机杼(火帝动炉销剑戟)",
"Root_Out_the_Turpitude": "永寿幼芽(根除恶孽)",
"Born_to_Obey": "古代零件(生而服从)",
"Winter_Soldiers": "铁卫扣饰(寒冬的战士们)",
"Destruction_of_the_Destroyer": "掠夺的本能(毁灭者的覆灭)",
"Nine_Billion_Names": "熄灭原核(九十亿个名字)",
"Akashic_Records": "光锥经验材料(阿卡夏记录)", "Akashic_Records": "光锥经验材料(阿卡夏记录)",
"Nameless_Land_Nameless_People": "角色经验材料(无名之地,无名之人)",
"The_Invisible_Hand": "信用点(看不见的手)", "The_Invisible_Hand": "信用点(看不见的手)",
"Abandoned_and_Insulted": "燃素 & 金属(被废弃与损害的)", "Scalpel_and_Screwdriver": "锈迹齿轮 & 老旧臼齿(手术刀与螺丝刀)",
"Spring_of_Life": "固态净水 & 虚粒子(生命之泉)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黄金大地)",
"The_Blossom_in_the_Storm": "气态流体 & 种子(风暴中怒放的花)",
"Legend_of_the_Puppet_Master": "废弃机巧零件 & 玉兆单元(偃师传说)",
"The_Wages_of_Humanity": "一人嘉禾 & 药草提取物(赡养人类)", "The_Wages_of_Humanity": "一人嘉禾 & 药草提取物(赡养人类)",
"Legend_of_the_Puppet_Master": "废弃机巧零件 & 玉兆单元(偃师传说)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黄金大地)",
"Spring_of_Life": "固态净水 & 虚粒子(生命之泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 碎梦(幻梦的残片)", "Fragments_of_Illusory_Dreams": "安逸 & 碎梦(幻梦的残片)",
"Scalpel_and_Screwdriver": "锈迹齿轮 & 老旧臼齿(手术刀与螺丝刀)" "The_Blossom_in_the_Storm": "气态流体 & 种子(风暴中怒放的花)",
"Abandoned_and_Insulted": "燃素 & 金属(被废弃与损害的)"
}, },
"Name_3": { "Name_3": {
"name": "第3个委托选择", "name": "第3个委托选择",
"help": "", "help": "",
"Nine_Billion_Names": "熄灭原核(九十亿个名字)",
"Destruction_of_the_Destroyer": "掠夺的本能(毁灭者的覆灭)",
"Winter_Soldiers": "铁卫扣饰(寒冬的战士们)",
"Born_to_Obey": "古代零件(生而服从)",
"Root_Out_the_Turpitude": "永寿幼芽(根除恶孽)",
"Fire_Lord_Inflames_Blades_of_War": "工造机杼(火帝动炉销剑戟)",
"A_Startling_Night_Terror": "蓄梦元件(劫梦惊魂)",
"Tranquility_of_Vimala_bhumi": "思绪末屑(离垢清净)", "Tranquility_of_Vimala_bhumi": "思绪末屑(离垢清净)",
"Nameless_Land_Nameless_People": "角色经验材料(无名之地,无名之人)", "A_Startling_Night_Terror": "蓄梦元件(劫梦惊魂)",
"Fire_Lord_Inflames_Blades_of_War": "工造机杼(火帝动炉销剑戟)",
"Root_Out_the_Turpitude": "永寿幼芽(根除恶孽)",
"Born_to_Obey": "古代零件(生而服从)",
"Winter_Soldiers": "铁卫扣饰(寒冬的战士们)",
"Destruction_of_the_Destroyer": "掠夺的本能(毁灭者的覆灭)",
"Nine_Billion_Names": "熄灭原核(九十亿个名字)",
"Akashic_Records": "光锥经验材料(阿卡夏记录)", "Akashic_Records": "光锥经验材料(阿卡夏记录)",
"Nameless_Land_Nameless_People": "角色经验材料(无名之地,无名之人)",
"The_Invisible_Hand": "信用点(看不见的手)", "The_Invisible_Hand": "信用点(看不见的手)",
"Abandoned_and_Insulted": "燃素 & 金属(被废弃与损害的)", "Scalpel_and_Screwdriver": "锈迹齿轮 & 老旧臼齿(手术刀与螺丝刀)",
"Spring_of_Life": "固态净水 & 虚粒子(生命之泉)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黄金大地)",
"The_Blossom_in_the_Storm": "气态流体 & 种子(风暴中怒放的花)",
"Legend_of_the_Puppet_Master": "废弃机巧零件 & 玉兆单元(偃师传说)",
"The_Wages_of_Humanity": "一人嘉禾 & 药草提取物(赡养人类)", "The_Wages_of_Humanity": "一人嘉禾 & 药草提取物(赡养人类)",
"Legend_of_the_Puppet_Master": "废弃机巧零件 & 玉兆单元(偃师传说)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黄金大地)",
"Spring_of_Life": "固态净水 & 虚粒子(生命之泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 碎梦(幻梦的残片)", "Fragments_of_Illusory_Dreams": "安逸 & 碎梦(幻梦的残片)",
"Scalpel_and_Screwdriver": "锈迹齿轮 & 老旧臼齿(手术刀与螺丝刀)" "The_Blossom_in_the_Storm": "气态流体 & 种子(风暴中怒放的花)",
"Abandoned_and_Insulted": "燃素 & 金属(被废弃与损害的)"
}, },
"Name_4": { "Name_4": {
"name": "第4个委托选择", "name": "第4个委托选择",
"help": "", "help": "",
"Nine_Billion_Names": "熄灭原核(九十亿个名字)",
"Destruction_of_the_Destroyer": "掠夺的本能(毁灭者的覆灭)",
"Winter_Soldiers": "铁卫扣饰(寒冬的战士们)",
"Born_to_Obey": "古代零件(生而服从)",
"Root_Out_the_Turpitude": "永寿幼芽(根除恶孽)",
"Fire_Lord_Inflames_Blades_of_War": "工造机杼(火帝动炉销剑戟)",
"A_Startling_Night_Terror": "蓄梦元件(劫梦惊魂)",
"Tranquility_of_Vimala_bhumi": "思绪末屑(离垢清净)", "Tranquility_of_Vimala_bhumi": "思绪末屑(离垢清净)",
"Nameless_Land_Nameless_People": "角色经验材料(无名之地,无名之人)", "A_Startling_Night_Terror": "蓄梦元件(劫梦惊魂)",
"Fire_Lord_Inflames_Blades_of_War": "工造机杼(火帝动炉销剑戟)",
"Root_Out_the_Turpitude": "永寿幼芽(根除恶孽)",
"Born_to_Obey": "古代零件(生而服从)",
"Winter_Soldiers": "铁卫扣饰(寒冬的战士们)",
"Destruction_of_the_Destroyer": "掠夺的本能(毁灭者的覆灭)",
"Nine_Billion_Names": "熄灭原核(九十亿个名字)",
"Akashic_Records": "光锥经验材料(阿卡夏记录)", "Akashic_Records": "光锥经验材料(阿卡夏记录)",
"Nameless_Land_Nameless_People": "角色经验材料(无名之地,无名之人)",
"The_Invisible_Hand": "信用点(看不见的手)", "The_Invisible_Hand": "信用点(看不见的手)",
"Abandoned_and_Insulted": "燃素 & 金属(被废弃与损害的)", "Scalpel_and_Screwdriver": "锈迹齿轮 & 老旧臼齿(手术刀与螺丝刀)",
"Spring_of_Life": "固态净水 & 虚粒子(生命之泉)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黄金大地)",
"The_Blossom_in_the_Storm": "气态流体 & 种子(风暴中怒放的花)",
"Legend_of_the_Puppet_Master": "废弃机巧零件 & 玉兆单元(偃师传说)",
"The_Wages_of_Humanity": "一人嘉禾 & 药草提取物(赡养人类)", "The_Wages_of_Humanity": "一人嘉禾 & 药草提取物(赡养人类)",
"Legend_of_the_Puppet_Master": "废弃机巧零件 & 玉兆单元(偃师传说)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黄金大地)",
"Spring_of_Life": "固态净水 & 虚粒子(生命之泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 碎梦(幻梦的残片)", "Fragments_of_Illusory_Dreams": "安逸 & 碎梦(幻梦的残片)",
"Scalpel_and_Screwdriver": "锈迹齿轮 & 老旧臼齿(手术刀与螺丝刀)" "The_Blossom_in_the_Storm": "气态流体 & 种子(风暴中怒放的花)",
"Abandoned_and_Insulted": "燃素 & 金属(被废弃与损害的)"
}, },
"Duration": { "Duration": {
"name": "派遣时长", "name": "派遣时长",
@ -969,9 +978,17 @@
"name": "参与双倍内圈仪器活动", "name": "参与双倍内圈仪器活动",
"help": "" "help": ""
}, },
"WeeklyFarming": {
"name": "每周刷100精英怪",
"help": ""
},
"UseStamina": { "UseStamina": {
"name": "使用开拓力刷内圈遗器", "name": "使用开拓力刷内圈遗器",
"help": "每日副本任务将不再打本,所有开拓力将优先被用于领取浸器奖励,双倍活动时除外" "help": "每日副本任务将不再打本,所有开拓力将优先被用于领取浸器奖励,双倍活动时除外"
},
"SimulatedUniverseFarm": {
"name": "刷精英怪进度",
"help": ""
} }
}, },
"RogueBlessing": { "RogueBlessing": {

View File

@ -131,7 +131,8 @@
"aScreenCap_nc": "aScreenCap_nc", "aScreenCap_nc": "aScreenCap_nc",
"DroidCast": "DroidCast", "DroidCast": "DroidCast",
"DroidCast_raw": "DroidCast_raw", "DroidCast_raw": "DroidCast_raw",
"scrcpy": "scrcpy" "scrcpy": "scrcpy",
"nemu_ipc": "nemu_ipc"
}, },
"ControlMethod": { "ControlMethod": {
"name": "模擬器控制方案", "name": "模擬器控制方案",
@ -261,6 +262,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "行跡材料:存護(支援艙段)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "行跡材料:存護(支援艙段)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行跡材料:存護(克勞克影視樂園)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行跡材料:存護(克勞克影視樂園)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行跡材料:巡獵(城郊雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行跡材料:巡獵(城郊雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行跡材料:巡獵(蘇樂達熱砂海選會場)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行跡材料:豐饒(邊緣通道)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行跡材料:豐饒(邊緣通道)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行跡材料:豐饒(綏園)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行跡材料:豐饒(綏園)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "行跡材料:智識(鉚釘鎮)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "行跡材料:智識(鉚釘鎮)",
@ -270,6 +272,7 @@
"Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "行跡材料:虛無(丹鼎司)", "Calyx_Crimson_Nihility_Luofu_AlchemyCommission": "行跡材料:虛無(丹鼎司)",
"Stagnant_Shadow_Spike": "角色晉階材料:物理(娜塔莎 / 克拉拉 / 盧卡 / 素裳)", "Stagnant_Shadow_Spike": "角色晉階材料:物理(娜塔莎 / 克拉拉 / 盧卡 / 素裳)",
"Stagnant_Shadow_Perdition": "角色晉階材料:物理(寒鴉 / 銀枝)", "Stagnant_Shadow_Perdition": "角色晉階材料:物理(寒鴉 / 銀枝)",
"Stagnant_Shadow_Duty": "角色晉階材料:物理(波提歐 / 知更鳥)",
"Stagnant_Shadow_Blaze": "角色晉階材料:火(姬子 / 艾絲妲 / 虎克)", "Stagnant_Shadow_Blaze": "角色晉階材料:火(姬子 / 艾絲妲 / 虎克)",
"Stagnant_Shadow_Scorch": "角色晉階材料:火(桂乃芬 / 托帕&帳帳)", "Stagnant_Shadow_Scorch": "角色晉階材料:火(桂乃芬 / 托帕&帳帳)",
"Stagnant_Shadow_Ire": "角色晉階材料:火(加拉赫)", "Stagnant_Shadow_Ire": "角色晉階材料:火(加拉赫)",
@ -312,6 +315,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "行跡材料:存護(支援艙段)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "行跡材料:存護(支援艙段)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行跡材料:存護(克勞克影視樂園)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行跡材料:存護(克勞克影視樂園)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行跡材料:巡獵(城郊雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行跡材料:巡獵(城郊雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行跡材料:巡獵(蘇樂達熱砂海選會場)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行跡材料:豐饒(邊緣通道)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行跡材料:豐饒(邊緣通道)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行跡材料:豐饒(綏園)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行跡材料:豐饒(綏園)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "行跡材料:智識(鉚釘鎮)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "行跡材料:智識(鉚釘鎮)",
@ -375,6 +379,7 @@
"Calyx_Crimson_Preservation_Herta_SupplyZone": "行跡材料:存護(支援艙段)", "Calyx_Crimson_Preservation_Herta_SupplyZone": "行跡材料:存護(支援艙段)",
"Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行跡材料:存護(克勞克影視樂園)", "Calyx_Crimson_Preservation_Penacony_ClockStudiosThemePark": "行跡材料:存護(克勞克影視樂園)",
"Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行跡材料:巡獵(城郊雪原)", "Calyx_Crimson_The_Hunt_Jarilo_OutlyingSnowPlains": "行跡材料:巡獵(城郊雪原)",
"Calyx_Crimson_The_Hunt_Penacony_SoulGladScorchsandAuditionVenue": "行跡材料:巡獵(蘇樂達熱砂海選會場)",
"Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行跡材料:豐饒(邊緣通道)", "Calyx_Crimson_Abundance_Jarilo_BackwaterPass": "行跡材料:豐饒(邊緣通道)",
"Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行跡材料:豐饒(綏園)", "Calyx_Crimson_Abundance_Luofu_FyxestrollGarden": "行跡材料:豐饒(綏園)",
"Calyx_Crimson_Erudition_Jarilo_RivetTown": "行跡材料:智識(鉚釘鎮)", "Calyx_Crimson_Erudition_Jarilo_RivetTown": "行跡材料:智識(鉚釘鎮)",
@ -389,6 +394,7 @@
"do_not_achieve": "不完成這個任務", "do_not_achieve": "不完成這個任務",
"Stagnant_Shadow_Spike": "角色晉階材料:物理(娜塔莎 / 克拉拉 / 盧卡 / 素裳)", "Stagnant_Shadow_Spike": "角色晉階材料:物理(娜塔莎 / 克拉拉 / 盧卡 / 素裳)",
"Stagnant_Shadow_Perdition": "角色晉階材料:物理(寒鴉 / 銀枝)", "Stagnant_Shadow_Perdition": "角色晉階材料:物理(寒鴉 / 銀枝)",
"Stagnant_Shadow_Duty": "角色晉階材料:物理(波提歐 / 知更鳥)",
"Stagnant_Shadow_Blaze": "角色晉階材料:火(姬子 / 艾絲妲 / 虎克)", "Stagnant_Shadow_Blaze": "角色晉階材料:火(姬子 / 艾絲妲 / 虎克)",
"Stagnant_Shadow_Scorch": "角色晉階材料:火(桂乃芬 / 托帕&帳帳)", "Stagnant_Shadow_Scorch": "角色晉階材料:火(桂乃芬 / 托帕&帳帳)",
"Stagnant_Shadow_Ire": "角色晉階材料:火(加拉赫)", "Stagnant_Shadow_Ire": "角色晉階材料:火(加拉赫)",
@ -440,6 +446,7 @@
"Argenti": "銀枝", "Argenti": "銀枝",
"Arlan": "阿蘭", "Arlan": "阿蘭",
"Asta": "艾絲妲", "Asta": "艾絲妲",
"Aventurine": "砂金",
"Bailu": "白露", "Bailu": "白露",
"BlackSwan": "黑天鵝", "BlackSwan": "黑天鵝",
"Blade": "刃", "Blade": "刃",
@ -468,6 +475,7 @@
"Natasha": "娜塔莎", "Natasha": "娜塔莎",
"Pela": "佩拉", "Pela": "佩拉",
"Qingque": "青雀", "Qingque": "青雀",
"Robin": "知更鳥",
"RuanMei": "阮•梅", "RuanMei": "阮•梅",
"Sampo": "桑博", "Sampo": "桑博",
"Seele": "希兒", "Seele": "希兒",
@ -532,7 +540,8 @@
"Echo_of_War_Destruction_Beginning": "毀滅的開端•歷戰餘響 (太空站「黑塔」)", "Echo_of_War_Destruction_Beginning": "毀滅的開端•歷戰餘響 (太空站「黑塔」)",
"Echo_of_War_End_of_the_Eternal_Freeze": "寒潮的落幕•歷戰餘響 (雅利洛-Ⅵ)", "Echo_of_War_End_of_the_Eternal_Freeze": "寒潮的落幕•歷戰餘響 (雅利洛-Ⅵ)",
"Echo_of_War_Divine_Seed": "不死的神實•歷戰餘響 (仙舟「羅浮」)", "Echo_of_War_Divine_Seed": "不死的神實•歷戰餘響 (仙舟「羅浮」)",
"Echo_of_War_Borehole_Planet_Old_Crater": "蛀星的舊靨•歷戰餘響 (太空站「黑塔」)" "Echo_of_War_Borehole_Planet_Old_Crater": "蛀星的舊靨•歷戰餘響 (太空站「黑塔」)",
"Echo_of_War_Salutations_of_Ashen_Dreams": "塵夢的讚禮•歷戰餘響 (匹諾康尼)"
}, },
"Team": { "Team": {
"name": "打本隊伍", "name": "打本隊伍",
@ -800,94 +809,94 @@
"Name_1": { "Name_1": {
"name": "第1個委託選擇", "name": "第1個委託選擇",
"help": "", "help": "",
"Nine_Billion_Names": "熄滅原核(九十億個名字)",
"Destruction_of_the_Destroyer": "掠奪的本能(毀滅者的覆滅)",
"Winter_Soldiers": "鐵衛扣飾(寒冬的戰士們)",
"Born_to_Obey": "古代零件(生而服從)",
"Root_Out_the_Turpitude": "永壽幼芽(根除惡孽)",
"Fire_Lord_Inflames_Blades_of_War": "工造機杼(火帝動爐銷劍戟)",
"A_Startling_Night_Terror": "蓄夢元件(劫夢驚魂)",
"Tranquility_of_Vimala_bhumi": "思緒末屑(離垢清淨)", "Tranquility_of_Vimala_bhumi": "思緒末屑(離垢清淨)",
"Nameless_Land_Nameless_People": "角色經驗素材(無名之地,無名之人)", "A_Startling_Night_Terror": "蓄夢元件(劫夢驚魂)",
"Fire_Lord_Inflames_Blades_of_War": "工造機杼(火帝動爐銷劍戟)",
"Root_Out_the_Turpitude": "永壽幼芽(根除惡孽)",
"Born_to_Obey": "古代零件(生而服從)",
"Winter_Soldiers": "鐵衛扣飾(寒冬的戰士們)",
"Destruction_of_the_Destroyer": "掠奪的本能(毀滅者的覆滅)",
"Nine_Billion_Names": "熄滅原核(九十億個名字)",
"Akashic_Records": "光錐經驗素材(阿卡夏紀錄)", "Akashic_Records": "光錐經驗素材(阿卡夏紀錄)",
"Nameless_Land_Nameless_People": "角色經驗素材(無名之地,無名之人)",
"The_Invisible_Hand": "信用點(看不見的手)", "The_Invisible_Hand": "信用點(看不見的手)",
"Abandoned_and_Insulted": "燃素 & 金屬(被廢棄與損害的)", "Scalpel_and_Screwdriver": "鏽跡齒輪 & 老舊臼齒(手術刀與螺絲起子)",
"Spring_of_Life": "固態淨水 & 虛粒子(生命之泉)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黃金大地)",
"The_Blossom_in_the_Storm": "氣態流體 & 種子(風暴中怒放的花)",
"Legend_of_the_Puppet_Master": "廢棄機巧零件 & 玉兆單元(偃師傳說)",
"The_Wages_of_Humanity": "一人嘉禾 & 藥草萃取物(贍養人類)", "The_Wages_of_Humanity": "一人嘉禾 & 藥草萃取物(贍養人類)",
"Legend_of_the_Puppet_Master": "廢棄機巧零件 & 玉兆單元(偃師傳說)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黃金大地)",
"Spring_of_Life": "固態淨水 & 虛粒子(生命之泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 碎夢(幻夢的殘片)", "Fragments_of_Illusory_Dreams": "安逸 & 碎夢(幻夢的殘片)",
"Scalpel_and_Screwdriver": "鏽跡齒輪 & 老舊臼齒(手術刀與螺絲起子)" "The_Blossom_in_the_Storm": "氣態流體 & 種子(風暴中怒放的花)",
"Abandoned_and_Insulted": "燃素 & 金屬(被廢棄與損害的)"
}, },
"Name_2": { "Name_2": {
"name": "第2個委託選擇", "name": "第2個委託選擇",
"help": "", "help": "",
"Nine_Billion_Names": "熄滅原核(九十億個名字)",
"Destruction_of_the_Destroyer": "掠奪的本能(毀滅者的覆滅)",
"Winter_Soldiers": "鐵衛扣飾(寒冬的戰士們)",
"Born_to_Obey": "古代零件(生而服從)",
"Root_Out_the_Turpitude": "永壽幼芽(根除惡孽)",
"Fire_Lord_Inflames_Blades_of_War": "工造機杼(火帝動爐銷劍戟)",
"A_Startling_Night_Terror": "蓄夢元件(劫夢驚魂)",
"Tranquility_of_Vimala_bhumi": "思緒末屑(離垢清淨)", "Tranquility_of_Vimala_bhumi": "思緒末屑(離垢清淨)",
"Nameless_Land_Nameless_People": "角色經驗素材(無名之地,無名之人)", "A_Startling_Night_Terror": "蓄夢元件(劫夢驚魂)",
"Fire_Lord_Inflames_Blades_of_War": "工造機杼(火帝動爐銷劍戟)",
"Root_Out_the_Turpitude": "永壽幼芽(根除惡孽)",
"Born_to_Obey": "古代零件(生而服從)",
"Winter_Soldiers": "鐵衛扣飾(寒冬的戰士們)",
"Destruction_of_the_Destroyer": "掠奪的本能(毀滅者的覆滅)",
"Nine_Billion_Names": "熄滅原核(九十億個名字)",
"Akashic_Records": "光錐經驗素材(阿卡夏紀錄)", "Akashic_Records": "光錐經驗素材(阿卡夏紀錄)",
"Nameless_Land_Nameless_People": "角色經驗素材(無名之地,無名之人)",
"The_Invisible_Hand": "信用點(看不見的手)", "The_Invisible_Hand": "信用點(看不見的手)",
"Abandoned_and_Insulted": "燃素 & 金屬(被廢棄與損害的)", "Scalpel_and_Screwdriver": "鏽跡齒輪 & 老舊臼齒(手術刀與螺絲起子)",
"Spring_of_Life": "固態淨水 & 虛粒子(生命之泉)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黃金大地)",
"The_Blossom_in_the_Storm": "氣態流體 & 種子(風暴中怒放的花)",
"Legend_of_the_Puppet_Master": "廢棄機巧零件 & 玉兆單元(偃師傳說)",
"The_Wages_of_Humanity": "一人嘉禾 & 藥草萃取物(贍養人類)", "The_Wages_of_Humanity": "一人嘉禾 & 藥草萃取物(贍養人類)",
"Legend_of_the_Puppet_Master": "廢棄機巧零件 & 玉兆單元(偃師傳說)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黃金大地)",
"Spring_of_Life": "固態淨水 & 虛粒子(生命之泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 碎夢(幻夢的殘片)", "Fragments_of_Illusory_Dreams": "安逸 & 碎夢(幻夢的殘片)",
"Scalpel_and_Screwdriver": "鏽跡齒輪 & 老舊臼齒(手術刀與螺絲起子)" "The_Blossom_in_the_Storm": "氣態流體 & 種子(風暴中怒放的花)",
"Abandoned_and_Insulted": "燃素 & 金屬(被廢棄與損害的)"
}, },
"Name_3": { "Name_3": {
"name": "第3個委託選擇", "name": "第3個委託選擇",
"help": "", "help": "",
"Nine_Billion_Names": "熄滅原核(九十億個名字)",
"Destruction_of_the_Destroyer": "掠奪的本能(毀滅者的覆滅)",
"Winter_Soldiers": "鐵衛扣飾(寒冬的戰士們)",
"Born_to_Obey": "古代零件(生而服從)",
"Root_Out_the_Turpitude": "永壽幼芽(根除惡孽)",
"Fire_Lord_Inflames_Blades_of_War": "工造機杼(火帝動爐銷劍戟)",
"A_Startling_Night_Terror": "蓄夢元件(劫夢驚魂)",
"Tranquility_of_Vimala_bhumi": "思緒末屑(離垢清淨)", "Tranquility_of_Vimala_bhumi": "思緒末屑(離垢清淨)",
"Nameless_Land_Nameless_People": "角色經驗素材(無名之地,無名之人)", "A_Startling_Night_Terror": "蓄夢元件(劫夢驚魂)",
"Fire_Lord_Inflames_Blades_of_War": "工造機杼(火帝動爐銷劍戟)",
"Root_Out_the_Turpitude": "永壽幼芽(根除惡孽)",
"Born_to_Obey": "古代零件(生而服從)",
"Winter_Soldiers": "鐵衛扣飾(寒冬的戰士們)",
"Destruction_of_the_Destroyer": "掠奪的本能(毀滅者的覆滅)",
"Nine_Billion_Names": "熄滅原核(九十億個名字)",
"Akashic_Records": "光錐經驗素材(阿卡夏紀錄)", "Akashic_Records": "光錐經驗素材(阿卡夏紀錄)",
"Nameless_Land_Nameless_People": "角色經驗素材(無名之地,無名之人)",
"The_Invisible_Hand": "信用點(看不見的手)", "The_Invisible_Hand": "信用點(看不見的手)",
"Abandoned_and_Insulted": "燃素 & 金屬(被廢棄與損害的)", "Scalpel_and_Screwdriver": "鏽跡齒輪 & 老舊臼齒(手術刀與螺絲起子)",
"Spring_of_Life": "固態淨水 & 虛粒子(生命之泉)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黃金大地)",
"The_Blossom_in_the_Storm": "氣態流體 & 種子(風暴中怒放的花)",
"Legend_of_the_Puppet_Master": "廢棄機巧零件 & 玉兆單元(偃師傳說)",
"The_Wages_of_Humanity": "一人嘉禾 & 藥草萃取物(贍養人類)", "The_Wages_of_Humanity": "一人嘉禾 & 藥草萃取物(贍養人類)",
"Legend_of_the_Puppet_Master": "廢棄機巧零件 & 玉兆單元(偃師傳說)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黃金大地)",
"Spring_of_Life": "固態淨水 & 虛粒子(生命之泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 碎夢(幻夢的殘片)", "Fragments_of_Illusory_Dreams": "安逸 & 碎夢(幻夢的殘片)",
"Scalpel_and_Screwdriver": "鏽跡齒輪 & 老舊臼齒(手術刀與螺絲起子)" "The_Blossom_in_the_Storm": "氣態流體 & 種子(風暴中怒放的花)",
"Abandoned_and_Insulted": "燃素 & 金屬(被廢棄與損害的)"
}, },
"Name_4": { "Name_4": {
"name": "第4個委託選擇", "name": "第4個委託選擇",
"help": "", "help": "",
"Nine_Billion_Names": "熄滅原核(九十億個名字)",
"Destruction_of_the_Destroyer": "掠奪的本能(毀滅者的覆滅)",
"Winter_Soldiers": "鐵衛扣飾(寒冬的戰士們)",
"Born_to_Obey": "古代零件(生而服從)",
"Root_Out_the_Turpitude": "永壽幼芽(根除惡孽)",
"Fire_Lord_Inflames_Blades_of_War": "工造機杼(火帝動爐銷劍戟)",
"A_Startling_Night_Terror": "蓄夢元件(劫夢驚魂)",
"Tranquility_of_Vimala_bhumi": "思緒末屑(離垢清淨)", "Tranquility_of_Vimala_bhumi": "思緒末屑(離垢清淨)",
"Nameless_Land_Nameless_People": "角色經驗素材(無名之地,無名之人)", "A_Startling_Night_Terror": "蓄夢元件(劫夢驚魂)",
"Fire_Lord_Inflames_Blades_of_War": "工造機杼(火帝動爐銷劍戟)",
"Root_Out_the_Turpitude": "永壽幼芽(根除惡孽)",
"Born_to_Obey": "古代零件(生而服從)",
"Winter_Soldiers": "鐵衛扣飾(寒冬的戰士們)",
"Destruction_of_the_Destroyer": "掠奪的本能(毀滅者的覆滅)",
"Nine_Billion_Names": "熄滅原核(九十億個名字)",
"Akashic_Records": "光錐經驗素材(阿卡夏紀錄)", "Akashic_Records": "光錐經驗素材(阿卡夏紀錄)",
"Nameless_Land_Nameless_People": "角色經驗素材(無名之地,無名之人)",
"The_Invisible_Hand": "信用點(看不見的手)", "The_Invisible_Hand": "信用點(看不見的手)",
"Abandoned_and_Insulted": "燃素 & 金屬(被廢棄與損害的)", "Scalpel_and_Screwdriver": "鏽跡齒輪 & 老舊臼齒(手術刀與螺絲起子)",
"Spring_of_Life": "固態淨水 & 虛粒子(生命之泉)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黃金大地)",
"The_Blossom_in_the_Storm": "氣態流體 & 種子(風暴中怒放的花)",
"Legend_of_the_Puppet_Master": "廢棄機巧零件 & 玉兆單元(偃師傳說)",
"The_Wages_of_Humanity": "一人嘉禾 & 藥草萃取物(贍養人類)", "The_Wages_of_Humanity": "一人嘉禾 & 藥草萃取物(贍養人類)",
"Legend_of_the_Puppet_Master": "廢棄機巧零件 & 玉兆單元(偃師傳說)",
"The_Land_of_Gold": "基本食材 & 蛋白米(黃金大地)",
"Spring_of_Life": "固態淨水 & 虛粒子(生命之泉)",
"Fragments_of_Illusory_Dreams": "安逸 & 碎夢(幻夢的殘片)", "Fragments_of_Illusory_Dreams": "安逸 & 碎夢(幻夢的殘片)",
"Scalpel_and_Screwdriver": "鏽跡齒輪 & 老舊臼齒(手術刀與螺絲起子)" "The_Blossom_in_the_Storm": "氣態流體 & 種子(風暴中怒放的花)",
"Abandoned_and_Insulted": "燃素 & 金屬(被廢棄與損害的)"
}, },
"Duration": { "Duration": {
"name": "派遣時間", "name": "派遣時間",
@ -969,9 +978,17 @@
"name": "參與雙倍內圈儀器活動", "name": "參與雙倍內圈儀器活動",
"help": "" "help": ""
}, },
"WeeklyFarming": {
"name": "每週農100精英怪",
"help": ""
},
"UseStamina": { "UseStamina": {
"name": "用開拓力農遺器", "name": "用開拓力農遺器",
"help": "每日副本任務將不再打本,所有開拓力將優先被用於領取浸器獎勵,雙倍活動時除外" "help": "每日副本任務將不再打本,所有開拓力將優先被用於領取浸器獎勵,雙倍活動時除外"
},
"SimulatedUniverseFarm": {
"name": "農精英怪進度",
"help": ""
} }
}, },
"RogueBlessing": { "RogueBlessing": {

View File

@ -18,7 +18,7 @@ VALID_PACKAGE = set(list(VALID_SERVER.values()))
VALID_CLOUD_SERVER = { VALID_CLOUD_SERVER = {
'CN-Official': 'com.miHoYo.cloudgames.hkrpg', 'CN-Official': 'com.miHoYo.cloudgames.hkrpg',
} }
VALID_CLOUD_PACKAGE = set(list(VALID_SERVER.values())) VALID_CLOUD_PACKAGE = set(list(VALID_CLOUD_SERVER.values()))
def set_lang(lang_: str): def set_lang(lang_: str):

View File

@ -208,6 +208,15 @@ class StoredSimulatedUniverse(StoredCounter, StoredExpiredAtMonday0400):
pass pass
class StoredSimulatedUniverseElite(StoredCounter, StoredExpiredAtMonday0400):
# These variables are used in Rogue Farming feature.
# FIXED_TOTAL --- Times of boss drop chance per week. In current version of StarRail, this value is 100.
FIXED_TOTAL = 100
# value --- Times left to farm. Resets to 100 every Monday 04:00, and decreases each time the elite boss is cleared.
class StoredAssignment(StoredCounter): class StoredAssignment(StoredCounter):
pass pass

View File

@ -20,6 +20,7 @@ from module.config.stored.classes import (
StoredImmersifier, StoredImmersifier,
StoredInt, StoredInt,
StoredSimulatedUniverse, StoredSimulatedUniverse,
StoredSimulatedUniverseElite,
StoredTrailblazePower, StoredTrailblazePower,
) )
@ -50,3 +51,4 @@ class StoredGenerated:
Assignment = StoredAssignment("Assignment.Assignment.Assignment") Assignment = StoredAssignment("Assignment.Assignment.Assignment")
Credit = StoredInt("DataUpdate.ItemStorage.Credit") Credit = StoredInt("DataUpdate.ItemStorage.Credit")
StallerJade = StoredInt("DataUpdate.ItemStorage.StallerJade") StallerJade = StoredInt("DataUpdate.ItemStorage.StallerJade")
SimulatedUniverseFarm = StoredSimulatedUniverseElite("Rogue.RogueWorld.SimulatedUniverseFarm")

View File

@ -1,9 +1,9 @@
import ipaddress import ipaddress
import logging import logging
import platform
import re import re
import socket import socket
import subprocess import subprocess
import sys
import time import time
from functools import wraps from functools import wraps
@ -12,7 +12,8 @@ from adbutils import AdbClient, AdbDevice, AdbTimeout, ForwardItem, ReverseItem
from adbutils.errors import AdbError from adbutils.errors import AdbError
import module.config.server as server_ import module.config.server as server_
from module.base.decorator import Config, cached_property, del_cached_property import platform
from module.base.decorator import Config, cached_property, del_cached_property, run_once
from module.base.utils import SelectedGrids, ensure_time from module.base.utils import SelectedGrids, ensure_time
from module.device.connection_attr import ConnectionAttr from module.device.connection_attr import ConnectionAttr
from module.device.method.utils import ( from module.device.method.utils import (
@ -84,10 +85,17 @@ class AdbDeviceWithStatus(AdbDevice):
def __bool__(self): def __bool__(self):
return True return True
@cached_property
def port(self) -> int:
try:
return int(self.serial.split(':')[1])
except (IndexError, ValueError):
return 0
@cached_property @cached_property
def may_mumu12_family(self): def may_mumu12_family(self):
# 127.0.0.1:16XXX # 127.0.0.1:16XXX
return len(self.serial) == 15 and self.serial.startswith('127.0.0.1:16') return 16384 <= self.port <= 17408
class Connection(ConnectionAttr): class Connection(ConnectionAttr):
@ -276,6 +284,7 @@ class Connection(ConnectionAttr):
@cached_property @cached_property
def nemud_app_keep_alive(self) -> str: def nemud_app_keep_alive(self) -> str:
res = self.adb_getprop('nemud.app_keep_alive') res = self.adb_getprop('nemud.app_keep_alive')
logger.attr('nemud.app_keep_alive', res)
return res return res
@retry @retry
@ -284,7 +293,6 @@ class Connection(ConnectionAttr):
return False return False
res = self.nemud_app_keep_alive res = self.nemud_app_keep_alive
logger.attr('nemud.app_keep_alive', res)
if res == '': if res == '':
# Empty property, probably MuMu6 or MuMu12 version < 3.5.6 # Empty property, probably MuMu6 or MuMu12 version < 3.5.6
return True return True
@ -299,6 +307,15 @@ class Connection(ConnectionAttr):
logger.warning(f'Invalid nemud.app_keep_alive value: {res}') logger.warning(f'Invalid nemud.app_keep_alive value: {res}')
return False return False
@cached_property
def is_mumu_over_version_356(self) -> bool:
"""
Returns:
bool: If MuMu12 version >= 3.5.6,
which has nemud.app_keep_alive and always be a vertical device
"""
return self.nemud_app_keep_alive != ''
@cached_property @cached_property
def _nc_server_host_port(self): def _nc_server_host_port(self):
""" """
@ -496,30 +513,51 @@ class Connection(ConnectionAttr):
def adb_forward_remove(self, local): def adb_forward_remove(self, local):
""" """
Equivalent to `adb -s <serial> forward --remove <local>` Equivalent to `adb -s <serial> forward --remove <local>`
No error raised when removing a non-existent forward
More about the commands send to ADB server, see: More about the commands send to ADB server, see:
https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/SERVICES.TXT https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/SERVICES.TXT
Args: Args:
local (str): Such as 'tcp:2437' local (str): Such as 'tcp:2437'
""" """
try:
with self.adb_client._connect() as c: with self.adb_client._connect() as c:
list_cmd = f"host-serial:{self.serial}:killforward:{local}" list_cmd = f"host-serial:{self.serial}:killforward:{local}"
c.send_command(list_cmd) c.send_command(list_cmd)
c.check_okay() c.check_okay()
except AdbError as e:
# No error raised when removing a non-existed forward
# adbutils.errors.AdbError: listener 'tcp:8888' not found
msg = str(e)
if re.search(r'listener .*? not found', msg):
logger.warning(f'{type(e).__name__}: {msg}')
else:
raise
def adb_reverse_remove(self, local): def adb_reverse_remove(self, local):
""" """
Equivalent to `adb -s <serial> reverse --remove <local>` Equivalent to `adb -s <serial> reverse --remove <local>`
No error raised when removing a non-existent reverse
Args: Args:
local (str): Such as 'tcp:2437' local (str): Such as 'tcp:2437'
""" """
try:
with self.adb_client._connect() as c: with self.adb_client._connect() as c:
c.send_command(f"host:transport:{self.serial}") c.send_command(f"host:transport:{self.serial}")
c.check_okay() c.check_okay()
list_cmd = f"reverse:killforward:{local}" list_cmd = f"reverse:killforward:{local}"
c.send_command(list_cmd) c.send_command(list_cmd)
c.check_okay() c.check_okay()
except AdbError as e:
# No error raised when removing a non-existed forward
# adbutils.errors.AdbError: listener 'tcp:8888' not found
msg = str(e)
if re.search(r'listener .*? not found', msg):
logger.warning(f'{type(e).__name__}: {msg}')
else:
raise
def adb_push(self, local, remote): def adb_push(self, local, remote):
""" """
@ -549,14 +587,14 @@ class Connection(ConnectionAttr):
# Disconnect offline device before connecting # Disconnect offline device before connecting
for device in self.list_device(): for device in self.list_device():
if device.status == 'offline': if device.status == 'offline':
logger.warning(f'Device {serial} is offline, disconnect it before connecting') logger.warning(f'Device {device.serial} is offline, disconnect it before connecting')
self.adb_disconnect(serial) self.adb_disconnect(device.serial)
elif device.status == 'unauthorized': elif device.status == 'unauthorized':
logger.error(f'Device {serial} is unauthorized, please accept ADB debugging on your device') logger.error(f'Device {device.serial} is unauthorized, please accept ADB debugging on your device')
elif device.status == 'device': elif device.status == 'device':
pass pass
else: else:
logger.warning(f'Device {serial} is is having a unknown status: {device.status}') logger.warning(f'Device {device.serial} is is having a unknown status: {device.status}')
# Skip for emulator-5554 # Skip for emulator-5554
if 'emulator-' in serial: if 'emulator-' in serial:
@ -764,6 +802,17 @@ class Connection(ConnectionAttr):
If serial=='auto' and only 1 device detected, use it If serial=='auto' and only 1 device detected, use it
""" """
logger.hr('Detect device') logger.hr('Detect device')
available = SelectedGrids([])
devices = SelectedGrids([])
@run_once
def brute_force_connect():
logger.info('Brute force connect')
from deploy.Windows.emulator import EmulatorManager
manager = EmulatorManager()
manager.brute_force_connect()
for _ in range(2):
logger.info('Here are the available devices, ' logger.info('Here are the available devices, '
'copy to Alas.Emulator.Serial to use it or set Alas.Emulator.Serial="auto"') 'copy to Alas.Emulator.Serial to use it or set Alas.Emulator.Serial="auto"')
devices = self.list_device() devices = self.list_device()
@ -782,6 +831,17 @@ class Connection(ConnectionAttr):
for device in unavailable: for device in unavailable:
logger.info(f'{device.serial} ({device.status})') logger.info(f'{device.serial} ({device.status})')
# brute_force_connect
if self.config.Emulator_Serial == 'auto' and available.count == 0:
logger.warning(f'No available device found')
if sys.platform == 'win32':
brute_force_connect()
continue
else:
break
else:
break
# Auto device detection # Auto device detection
if self.config.Emulator_Serial == 'auto': if self.config.Emulator_Serial == 'auto':
if available.count == 0: if available.count == 0:
@ -790,7 +850,7 @@ class Connection(ConnectionAttr):
raise RequestHumanTakeover raise RequestHumanTakeover
elif available.count == 1: elif available.count == 1:
logger.info(f'Auto device detection found only one device, using it') logger.info(f'Auto device detection found only one device, using it')
self.serial = available[0].serial self.config.Emulator_Serial = self.serial = available[0].serial
del_cached_property(self, 'adb') del_cached_property(self, 'adb')
elif available.count == 2 \ elif available.count == 2 \
and available.select(serial='127.0.0.1:7555') \ and available.select(serial='127.0.0.1:7555') \
@ -799,7 +859,7 @@ class Connection(ConnectionAttr):
# For MuMu12 serials like 127.0.0.1:7555 and 127.0.0.1:16384 # For MuMu12 serials like 127.0.0.1:7555 and 127.0.0.1:16384
# ignore 7555 use 16384 # ignore 7555 use 16384
remain = available.select(may_mumu12_family=True).first_or_none() remain = available.select(may_mumu12_family=True).first_or_none()
self.serial = remain.serial self.config.Emulator_Serial = self.serial = remain.serial
del_cached_property(self, 'adb') del_cached_property(self, 'adb')
else: else:
logger.critical('Multiple devices found, auto device detection cannot decide which to choose, ' logger.critical('Multiple devices found, auto device detection cannot decide which to choose, '
@ -808,6 +868,7 @@ class Connection(ConnectionAttr):
# Handle LDPlayer # Handle LDPlayer
# LDPlayer serial jumps between `127.0.0.1:5555+{X}` and `emulator-5554+{X}` # LDPlayer serial jumps between `127.0.0.1:5555+{X}` and `emulator-5554+{X}`
# No config write since it's dynamic
port_serial, emu_serial = get_serial_pair(self.serial) port_serial, emu_serial = get_serial_pair(self.serial)
if port_serial and emu_serial: if port_serial and emu_serial:
# Might be LDPlayer, check connected devices # Might be LDPlayer, check connected devices
@ -834,6 +895,57 @@ class Connection(ConnectionAttr):
f'Using serial: {emu_serial}') f'Using serial: {emu_serial}')
self.serial = emu_serial self.serial = emu_serial
# Redirect MuMu12 from 127.0.0.1:7555 to 127.0.0.1:16xxx
if self.serial == '127.0.0.1:7555':
for _ in range(2):
mumu12 = available.select(may_mumu12_family=True)
if mumu12.count == 1:
emu_serial = mumu12.first_or_none().serial
logger.warning(f'Redirect MuMu12 {self.serial} to {emu_serial}')
self.config.Emulator_Serial = self.serial = emu_serial
break
elif mumu12.count >= 2:
logger.warning(f'Multiple MuMu12 serial found, cannot redirect')
break
else:
# Only 127.0.0.1:7555
if self.is_mumu_over_version_356:
# is_mumu_over_version_356 and nemud_app_keep_alive was cached
# Acceptable since it's the same device
logger.warning(f'Device {self.serial} is MuMu12 but corresponding port not found')
brute_force_connect()
devices = self.list_device()
# Show available devices
available = devices.select(status='device')
for device in available:
logger.info(device.serial)
if not len(available):
logger.info('No available devices')
continue
else:
# MuMu6
break
# MuMu12 uses 127.0.0.1:16385 if port 16384 is occupied, auto redirect
# No config write since it's dynamic
if self.is_mumu12_family:
matched = False
for device in available.select(may_mumu12_family=True):
if device.port == self.port:
# Exact match
matched = True
break
if not matched:
for device in available.select(may_mumu12_family=True):
if -2 <= device.port - self.port <= 2:
# Port switched
logger.info(f'MuMu12 port switches from {self.serial} to {device.serial}')
del_cached_property(self, 'port')
del_cached_property(self, 'is_mumu12_family')
del_cached_property(self, 'is_mumu_family')
self.serial = device.serial
break
@retry @retry
def list_package(self, show_log=True): def list_package(self, show_log=True):
""" """
@ -864,7 +976,7 @@ class Connection(ConnectionAttr):
list[str]: List of package names list[str]: List of package names
""" """
packages = self.list_package(show_log=show_log) packages = self.list_package(show_log=show_log)
packages = [p for p in packages if p in server_.VALID_PACKAGE] packages = [p for p in packages if p in server_.VALID_PACKAGE or p in server_.VALID_CLOUD_PACKAGE]
return packages return packages
def detect_package(self, set_config=True): def detect_package(self, set_config=True):

View File

@ -7,7 +7,6 @@ from adbutils import AdbClient, AdbDevice
from module.base.decorator import cached_property from module.base.decorator import cached_property
from module.config.config import AzurLaneConfig from module.config.config import AzurLaneConfig
from module.config.utils import deep_iter
from module.exception import RequestHumanTakeover from module.exception import RequestHumanTakeover
from module.logger import logger from module.logger import logger
@ -49,7 +48,6 @@ class ConnectionAttr:
self.serial_check() self.serial_check()
self.config.DEVICE_OVER_HTTP = self.is_over_http self.config.DEVICE_OVER_HTTP = self.is_over_http
@staticmethod @staticmethod
def revise_serial(serial): def revise_serial(serial):
serial = serial.replace(' ', '') serial = serial.replace(' ', '')
@ -123,6 +121,18 @@ class ConnectionAttr:
def is_wsa(self): def is_wsa(self):
return bool(re.match(r'^wsa', self.serial)) return bool(re.match(r'^wsa', self.serial))
@cached_property
def port(self) -> int:
try:
return int(self.serial.split(':')[1])
except (IndexError, ValueError):
return 0
@cached_property
def is_mumu12_family(self):
# 127.0.0.1:16XXX
return 16384 <= self.port <= 17408
@cached_property @cached_property
def is_mumu_family(self): def is_mumu_family(self):
# 127.0.0.1:7555 # 127.0.0.1:7555
@ -130,9 +140,8 @@ class ConnectionAttr:
return self.serial == '127.0.0.1:7555' or self.is_mumu12_family return self.serial == '127.0.0.1:7555' or self.is_mumu12_family
@cached_property @cached_property
def is_mumu12_family(self): def is_nox_family(self):
# 127.0.0.1:16384 + 32*n return 62001 <= self.port <= 63025
return len(self.serial) == 15 and self.serial.startswith('127.0.0.1:16')
@cached_property @cached_property
def is_emulator(self): def is_emulator(self):
@ -178,7 +187,8 @@ class ConnectionAttr:
rf"SOFTWARE\BlueStacks_bgp64_hyperv\Guests\{folder_name}\Config") as key: rf"SOFTWARE\BlueStacks_bgp64_hyperv\Guests\{folder_name}\Config") as key:
port = QueryValueEx(key, "BstAdbPort")[0] port = QueryValueEx(key, "BstAdbPort")[0]
except FileNotFoundError: except FileNotFoundError:
logger.error(rf'Unable to find registry HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_bgp64_hyperv\Guests\{folder_name}\Config') logger.error(
rf'Unable to find registry HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_bgp64_hyperv\Guests\{folder_name}\Config')
logger.error('Please confirm that your are using BlueStack 4 hyper-v and not regular BlueStacks 4') logger.error('Please confirm that your are using BlueStack 4 hyper-v and not regular BlueStacks 4')
logger.error(r'Please check if there is any other emulator instances under ' logger.error(r'Please check if there is any other emulator instances under '
r'registry HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_bgp64_hyperv\Guests') r'registry HKEY_LOCAL_MACHINE\SOFTWARE\BlueStacks_bgp64_hyperv\Guests')

View File

@ -5,11 +5,12 @@ from module.base.utils import *
from module.device.method.hermit import Hermit from module.device.method.hermit import Hermit
from module.device.method.maatouch import MaaTouch from module.device.method.maatouch import MaaTouch
from module.device.method.minitouch import Minitouch from module.device.method.minitouch import Minitouch
from module.device.method.nemu_ipc import NemuIpc
from module.device.method.scrcpy import Scrcpy from module.device.method.scrcpy import Scrcpy
from module.logger import logger from module.logger import logger
class Control(Hermit, Minitouch, Scrcpy, MaaTouch): class Control(Hermit, Minitouch, Scrcpy, MaaTouch, NemuIpc):
def handle_control_check(self, button): def handle_control_check(self, button):
# Will be overridden in Device # Will be overridden in Device
pass pass
@ -22,6 +23,7 @@ class Control(Hermit, Minitouch, Scrcpy, MaaTouch):
'minitouch': self.click_minitouch, 'minitouch': self.click_minitouch,
'Hermit': self.click_hermit, 'Hermit': self.click_hermit,
'MaaTouch': self.click_maatouch, 'MaaTouch': self.click_maatouch,
'nemu_ipc': self.click_nemu_ipc,
} }
def click(self, button, control_check=True): def click(self, button, control_check=True):
@ -78,6 +80,8 @@ class Control(Hermit, Minitouch, Scrcpy, MaaTouch):
self.long_click_scrcpy(x, y, duration) self.long_click_scrcpy(x, y, duration)
elif method == 'MaaTouch': elif method == 'MaaTouch':
self.long_click_maatouch(x, y, duration) self.long_click_maatouch(x, y, duration)
elif method == 'nemu_ipc':
self.long_click_nemu_ipc(x, y, duration)
else: else:
self.swipe_adb((x, y), (x, y), duration) self.swipe_adb((x, y), (x, y), duration)
@ -86,13 +90,9 @@ class Control(Hermit, Minitouch, Scrcpy, MaaTouch):
p1, p2 = ensure_int(p1, p2) p1, p2 = ensure_int(p1, p2)
duration = ensure_time(duration) duration = ensure_time(duration)
method = self.config.Emulator_ControlMethod method = self.config.Emulator_ControlMethod
if method == 'minitouch': if method == 'uiautomator2':
logger.info('Swipe %s -> %s' % (point2str(*p1), point2str(*p2)))
elif method == 'uiautomator2':
logger.info('Swipe %s -> %s, %s' % (point2str(*p1), point2str(*p2), duration)) logger.info('Swipe %s -> %s, %s' % (point2str(*p1), point2str(*p2), duration))
elif method == 'scrcpy': elif method in ['minitouch', 'MaaTouch', 'scrcpy', 'nemu_ipc']:
logger.info('Swipe %s -> %s' % (point2str(*p1), point2str(*p2)))
elif method == 'MaaTouch':
logger.info('Swipe %s -> %s' % (point2str(*p1), point2str(*p2))) logger.info('Swipe %s -> %s' % (point2str(*p1), point2str(*p2)))
else: else:
# ADB needs to be slow, or swipe doesn't work # ADB needs to be slow, or swipe doesn't work
@ -114,6 +114,8 @@ class Control(Hermit, Minitouch, Scrcpy, MaaTouch):
self.swipe_scrcpy(p1, p2) self.swipe_scrcpy(p1, p2)
elif method == 'MaaTouch': elif method == 'MaaTouch':
self.swipe_maatouch(p1, p2) self.swipe_maatouch(p1, p2)
elif method == 'nemu_ipc':
self.swipe_nemu_ipc(p1, p2)
else: else:
self.swipe_adb(p1, p2, duration=duration) self.swipe_adb(p1, p2, duration=duration)
@ -163,6 +165,8 @@ class Control(Hermit, Minitouch, Scrcpy, MaaTouch):
self.drag_scrcpy(p1, p2, point_random=point_random) self.drag_scrcpy(p1, p2, point_random=point_random)
elif method == 'MaaTouch': elif method == 'MaaTouch':
self.drag_maatouch(p1, p2, point_random=point_random) self.drag_maatouch(p1, p2, point_random=point_random)
elif method == 'nemu_ipc':
self.drag_nemu_ipc(p1, p2, point_random=point_random)
else: else:
logger.warning(f'Control method {method} does not support drag well, ' logger.warning(f'Control method {method} does not support drag well, '
f'falling back to ADB swipe may cause unexpected behaviour') f'falling back to ADB swipe may cause unexpected behaviour')

View File

@ -1,10 +1,15 @@
import collections import collections
import itertools import itertools
# Patch pkg_resources before importing adbutils and uiautomator2
from module.device.pkg_resources import get_distribution
# Just avoid being removed by import optimization
_ = get_distribution
from module.base.timer import Timer from module.base.timer import Timer
from module.device.app_control import AppControl from module.device.app_control import AppControl
from module.device.control import Control from module.device.control import Control
from module.device.platform import Platform
from module.device.screenshot import Screenshot from module.device.screenshot import Screenshot
from module.exception import ( from module.exception import (
EmulatorNotRunningError, EmulatorNotRunningError,
@ -56,7 +61,7 @@ def show_function_call():
logger.info('Function calls:' + ''.join(func_list)) logger.info('Function calls:' + ''.join(func_list))
class Device(Screenshot, Control, AppControl, Platform): class Device(Screenshot, Control, AppControl):
_screen_size_checked = False _screen_size_checked = False
detect_record = set() detect_record = set()
click_record = collections.deque(maxlen=30) click_record = collections.deque(maxlen=30)
@ -82,12 +87,26 @@ class Device(Screenshot, Control, AppControl, Platform):
if self.config.EmulatorInfo_Emulator == 'auto': if self.config.EmulatorInfo_Emulator == 'auto':
_ = self.emulator_instance _ = self.emulator_instance
# SRC only, use nemu_ipc if available
available = self.nemu_ipc_available()
logger.attr('nemu_ipc_available', available)
if available:
self.config.override(Emulator_ScreenshotMethod='nemu_ipc')
self.screenshot_interval_set() self.screenshot_interval_set()
self.method_check()
# Auto-select the fastest screenshot method # Auto-select the fastest screenshot method
if not self.config.is_template_config and self.config.Emulator_ScreenshotMethod == 'auto': if not self.config.is_template_config and self.config.Emulator_ScreenshotMethod == 'auto':
self.run_simple_screenshot_benchmark() self.run_simple_screenshot_benchmark()
# Early init
if self.config.is_actual_task:
if self.config.Emulator_ControlMethod == 'MaaTouch':
self.early_maatouch_init()
if self.config.Emulator_ControlMethod == 'minitouch':
self.early_minitouch_init()
def run_simple_screenshot_benchmark(self): def run_simple_screenshot_benchmark(self):
""" """
Perform a screenshot method benchmark, test 3 times on each method. Perform a screenshot method benchmark, test 3 times on each method.
@ -101,7 +120,23 @@ class Device(Screenshot, Control, AppControl, Platform):
bench = Benchmark(config=self.config, device=self) bench = Benchmark(config=self.config, device=self)
method = bench.run_simple_screenshot_benchmark() method = bench.run_simple_screenshot_benchmark()
# Set # Set
with self.config.multi_set():
self.config.Emulator_ScreenshotMethod = method self.config.Emulator_ScreenshotMethod = method
# if method == 'nemu_ipc':
# self.config.Emulator_ControlMethod = 'nemu_ipc'
def method_check(self):
"""
Check combinations of screenshot method and control methods
"""
# nemu_ipc should be together
# if self.config.Emulator_ScreenshotMethod == 'nemu_ipc' and self.config.Emulator_ControlMethod != 'nemu_ipc':
# logger.warning('When using nemu_ipc, both screenshot and control should use nemu_ipc')
# self.config.Emulator_ControlMethod = 'nemu_ipc'
# if self.config.Emulator_ScreenshotMethod != 'nemu_ipc' and self.config.Emulator_ControlMethod == 'nemu_ipc':
# logger.warning('When not using nemu_ipc, both screenshot and control should not use nemu_ipc')
# self.config.Emulator_ControlMethod = 'minitouch'
pass
def screenshot(self): def screenshot(self):
""" """
@ -127,6 +162,18 @@ class Device(Screenshot, Control, AppControl, Platform):
# stop it during wait # stop it during wait
if self.config.Emulator_ScreenshotMethod == 'scrcpy': if self.config.Emulator_ScreenshotMethod == 'scrcpy':
self._scrcpy_server_stop() self._scrcpy_server_stop()
if self.config.Emulator_ScreenshotMethod == 'nemu_ipc':
self.nemu_ipc_release()
def get_orientation(self):
"""
Callbacks when orientation changed.
"""
o = super().get_orientation()
self.on_orientation_change_maatouch()
return o
def stuck_record_add(self, button): def stuck_record_add(self, button):
self.detect_record.add(str(button)) self.detect_record.add(str(button))

View File

@ -128,7 +128,7 @@ class Adb(Connection):
if image is None: if image is None:
raise ImageTruncated('Empty image after cv2.imdecode') raise ImageTruncated('Empty image after cv2.imdecode')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image) cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image)
if image is None: if image is None:
raise ImageTruncated('Empty image after cv2.cvtColor') raise ImageTruncated('Empty image after cv2.cvtColor')

View File

@ -95,6 +95,8 @@ class DroidCast(Uiautomator2):
""" """
_droidcast_port: int = 0 _droidcast_port: int = 0
droidcast_width: int = 0
droidcast_height: int = 0
@cached_property @cached_property
def droidcast_session(self): def droidcast_session(self):
@ -112,15 +114,37 @@ class DroidCast(Uiautomator2):
- /preview - /preview
To get PNG screenshots. To get PNG screenshots.
""" """
def droidcast_url(self, url='/preview'): def droidcast_url(self, url='/preview'):
if self.is_mumu_over_version_356:
w, h = self.droidcast_width, self.droidcast_height
if self.orientation == 0:
return f'http://127.0.0.1:{self._droidcast_port}{url}?width={w}&height={h}'
elif self.orientation == 1:
return f'http://127.0.0.1:{self._droidcast_port}{url}?width={h}&height={w}'
else:
# logger.warning('DroidCast receives invalid device orientation')
pass
return f'http://127.0.0.1:{self._droidcast_port}{url}' return f'http://127.0.0.1:{self._droidcast_port}{url}'
def droidcast_raw_url(self, url='/screenshot'): def droidcast_raw_url(self, url='/screenshot'):
if self.is_mumu_over_version_356:
w, h = self.droidcast_width, self.droidcast_height
if self.orientation == 0:
return f'http://127.0.0.1:{self._droidcast_port}{url}?width={w}&height={h}'
elif self.orientation == 1:
return f'http://127.0.0.1:{self._droidcast_port}{url}?width={h}&height={w}'
else:
# logger.warning('DroidCast receives invalid device orientation')
pass
return f'http://127.0.0.1:{self._droidcast_port}{url}' return f'http://127.0.0.1:{self._droidcast_port}{url}'
def droidcast_init(self): def droidcast_init(self):
logger.hr('DroidCast init') logger.hr('DroidCast init')
self.droidcast_stop() self.droidcast_stop()
self._droidcast_update_resolution()
logger.info('Pushing DroidCast apk') logger.info('Pushing DroidCast apk')
self.adb_push(self.config.DROIDCAST_FILEPATH_LOCAL, self.config.DROIDCAST_FILEPATH_REMOTE) self.adb_push(self.config.DROIDCAST_FILEPATH_LOCAL, self.config.DROIDCAST_FILEPATH_REMOTE)
@ -150,10 +174,25 @@ class DroidCast(Uiautomator2):
else: else:
logger.error(f'Unknown DROIDCAST_VERSION: {self.config.DROIDCAST_VERSION}') logger.error(f'Unknown DROIDCAST_VERSION: {self.config.DROIDCAST_VERSION}')
def _droidcast_update_resolution(self):
if self.is_mumu_over_version_356:
logger.info('Update droidcast resolution')
w, h = self.resolution_uiautomator2(cal_rotation=False)
self.get_orientation()
# 720, 1280
# mumu12 > 3.5.6 is always a vertical device
self.droidcast_width, self.droidcast_height = w, h
logger.info(f'Droicast resolution: {(w, h)}')
@retry @retry
def screenshot_droidcast(self): def screenshot_droidcast(self):
self.config.DROIDCAST_VERSION = 'DroidCast' self.config.DROIDCAST_VERSION = 'DroidCast'
if self.is_mumu_over_version_356:
if not self.droidcast_width or not self.droidcast_height:
self._droidcast_update_resolution()
resp = self.droidcast_session.get(self.droidcast_url(), timeout=3) resp = self.droidcast_session.get(self.droidcast_url(), timeout=3)
if resp.status_code == 404: if resp.status_code == 404:
raise DroidCastVersionIncompatible('DroidCast server does not have /preview') raise DroidCastVersionIncompatible('DroidCast server does not have /preview')
image = resp.content image = resp.content
@ -173,16 +212,37 @@ class DroidCast(Uiautomator2):
if image is None: if image is None:
raise ImageTruncated('Empty image after cv2.cvtColor') raise ImageTruncated('Empty image after cv2.cvtColor')
if self.is_mumu_over_version_356:
if self.orientation == 1:
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
return image return image
@retry @retry
def screenshot_droidcast_raw(self): def screenshot_droidcast_raw(self):
self.config.DROIDCAST_VERSION = 'DroidCast_raw' self.config.DROIDCAST_VERSION = 'DroidCast_raw'
shape = (720, 1280)
if self.is_mumu_over_version_356:
if not self.droidcast_width or not self.droidcast_height:
self._droidcast_update_resolution()
if self.droidcast_height and self.droidcast_width:
shape = (self.droidcast_height, self.droidcast_width)
rotate = self.is_mumu_over_version_356 and self.orientation == 1
image = self.droidcast_session.get(self.droidcast_raw_url(), timeout=3).content image = self.droidcast_session.get(self.droidcast_raw_url(), timeout=3).content
# DroidCast_raw returns a RGB565 bitmap # DroidCast_raw returns a RGB565 bitmap
try: try:
arr = np.frombuffer(image, dtype=np.uint16).reshape((720, 1280)) arr = np.frombuffer(image, dtype=np.uint16)
if rotate:
arr = arr.reshape(shape)
# arr = cv2.rotate(arr, cv2.ROTATE_90_CLOCKWISE)
# A little bit faster?
arr = cv2.transpose(arr)
cv2.flip(arr, 1, dst=arr)
else:
arr = arr.reshape(shape)
except ValueError as e: except ValueError as e:
if len(image) < 500: if len(image) < 500:
logger.warning(f'Unexpected screenshot: {image}') logger.warning(f'Unexpected screenshot: {image}')
@ -210,26 +270,26 @@ class DroidCast(Uiautomator2):
# b = b.astype(np.uint8) # b = b.astype(np.uint8)
# image = cv2.merge([r, g, b]) # image = cv2.merge([r, g, b])
# The same as the code above but costs about 5ms instead of 10ms. # The same as the code above but costs about 3~4ms instead of 10ms.
# Note that cv2.convertScaleAbs is 5x fast as cv2.multiply, cv2.add is 8x fast as cv2.convertScaleAbs
# Note that cv2.convertScaleAbs includes rounding
r = cv2.bitwise_and(arr, 0b1111100000000000) r = cv2.bitwise_and(arr, 0b1111100000000000)
cv2.multiply(r, 0.00390625, dst=r) r = cv2.convertScaleAbs(r, alpha=0.00390625)
r = np.uint8(r) m = cv2.convertScaleAbs(r, alpha=0.03125)
m = cv2.multiply(r, 0.03125)
cv2.add(r, m, dst=r) cv2.add(r, m, dst=r)
g = cv2.bitwise_and(arr, 0b0000011111100000) g = cv2.bitwise_and(arr, 0b0000011111100000)
cv2.multiply(g, 0.125, dst=g) g = cv2.convertScaleAbs(g, alpha=0.125)
g = np.uint8(g) m = cv2.convertScaleAbs(g, alpha=0.015625, dst=m)
m = cv2.multiply(g, 0.015625)
cv2.add(g, m, dst=g) cv2.add(g, m, dst=g)
b = cv2.bitwise_and(arr, 0b0000000000011111) b = cv2.bitwise_and(arr, 0b0000000000011111)
cv2.multiply(b, 8, dst=b) b = cv2.convertScaleAbs(b, alpha=8)
b = np.uint8(b) m = cv2.convertScaleAbs(b, alpha=0.03125, dst=m)
m = cv2.multiply(b, 0.03125)
cv2.add(b, m, dst=b) cv2.add(b, m, dst=b)
image = cv2.merge([r, g, b]) image = cv2.merge([r, g, b])
return image return image
def droidcast_wait_startup(self): def droidcast_wait_startup(self):

View File

@ -1,14 +1,15 @@
import socket import socket
import threading
from functools import wraps from functools import wraps
from adbutils.errors import AdbError from adbutils.errors import AdbError
from module.base.decorator import cached_property, del_cached_property from module.base.decorator import cached_property, del_cached_property, has_cached_property
from module.base.timer import Timer from module.base.timer import Timer
from module.base.utils import * from module.base.utils import *
from module.device.connection import Connection from module.device.connection import Connection
from module.device.method.minitouch import CommandBuilder, insert_swipe from module.device.method.minitouch import CommandBuilder, insert_swipe
from module.device.method.utils import RETRY_TRIES, retry_sleep, handle_adb_error from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep
from module.exception import RequestHumanTakeover from module.exception import RequestHumanTakeover
from module.logger import logger from module.logger import logger
@ -36,20 +37,20 @@ def retry(func):
def init(): def init():
self.adb_reconnect() self.adb_reconnect()
del_cached_property(self, 'maatouch_builder') del_cached_property(self, '_maatouch_builder')
# Emulator closed # Emulator closed
except ConnectionAbortedError as e: except ConnectionAbortedError as e:
logger.error(e) logger.error(e)
def init(): def init():
self.adb_reconnect() self.adb_reconnect()
del_cached_property(self, 'maatouch_builder') del_cached_property(self, '_maatouch_builder')
# AdbError # AdbError
except AdbError as e: except AdbError as e:
if handle_adb_error(e): if handle_adb_error(e):
def init(): def init():
self.adb_reconnect() self.adb_reconnect()
del_cached_property(self, 'maatouch_builder') del_cached_property(self, '_maatouch_builder')
else: else:
break break
# MaaTouchNotInstalledError: Received "Aborted" from MaaTouch # MaaTouchNotInstalledError: Received "Aborted" from MaaTouch
@ -58,12 +59,12 @@ def retry(func):
def init(): def init():
self.maatouch_install() self.maatouch_install()
del_cached_property(self, 'maatouch_builder') del_cached_property(self, '_maatouch_builder')
except BrokenPipeError as e: except BrokenPipeError as e:
logger.error(e) logger.error(e)
def init(): def init():
del_cached_property(self, 'maatouch_builder') del_cached_property(self, '_maatouch_builder')
# Unknown, probably a trucked image # Unknown, probably a trucked image
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
@ -101,20 +102,76 @@ class MaaTouch(Connection):
""" """
max_x: int max_x: int
max_y: int max_y: int
_maatouch_stream = socket.socket _maatouch_stream: socket.socket = None
_maatouch_stream_storage = None _maatouch_stream_storage = None
_maatouch_init_thread = None
_maatouch_orientation: int = None
@cached_property @cached_property
def maatouch_builder(self): @retry
def _maatouch_builder(self):
self.maatouch_init() self.maatouch_init()
return MaatouchBuilder(self) return MaatouchBuilder(self)
@property
def maatouch_builder(self):
# Wait init thread
if self._maatouch_init_thread is not None:
self._maatouch_init_thread.join()
del self._maatouch_init_thread
self._maatouch_init_thread = None
return self._maatouch_builder
def early_maatouch_init(self):
"""
Start a thread to init maatouch connection while the Alas instance just starting to take screenshots
This would speed up the first click 0.2 ~ 0.4s.
"""
if has_cached_property(self, '_maatouch_builder'):
return
def early_maatouch_init_func():
_ = self._maatouch_builder
thread = threading.Thread(target=early_maatouch_init_func, daemon=True)
self._maatouch_init_thread = thread
thread.start()
def on_orientation_change_maatouch(self):
"""
MaaTouch caches devices orientation at its startup
A restart is required when orientation changed
"""
if self._maatouch_orientation is None:
return
if self.orientation == self._maatouch_orientation:
return
logger.info(f'Orientation changed {self._maatouch_orientation} => {self.orientation}, re-init MaaTouch')
del_cached_property(self, '_maatouch_builder')
self.early_maatouch_init()
def maatouch_init(self): def maatouch_init(self):
logger.hr('MaaTouch init') logger.hr('MaaTouch init')
max_x, max_y = 1280, 720 max_x, max_y = 1280, 720
max_contacts = 2 max_contacts = 2
max_pressure = 50 max_pressure = 50
# Try to close existing stream
if self._maatouch_stream is not None:
try:
self._maatouch_stream.close()
except Exception as e:
logger.error(e)
del self._maatouch_stream
if self._maatouch_stream_storage is not None:
del self._maatouch_stream_storage
# MaaTouch caches devices orientation at its startup
super(MaaTouch, self).get_orientation()
self._maatouch_orientation = self.orientation
# CLASSPATH=/data/local/tmp/maatouch app_process / com.shxyke.MaaTouch.App # CLASSPATH=/data/local/tmp/maatouch app_process / com.shxyke.MaaTouch.App
stream = self.adb_shell( stream = self.adb_shell(
['CLASSPATH=/data/local/tmp/maatouch', 'app_process', '/', 'com.shxyke.MaaTouch.App'], ['CLASSPATH=/data/local/tmp/maatouch', 'app_process', '/', 'com.shxyke.MaaTouch.App'],
@ -245,3 +302,8 @@ class MaaTouch(Connection):
builder.up().commit() builder.up().commit()
builder.send() builder.send()
if __name__ == '__main__':
self = MaaTouch('src')
self.maatouch_uninstall()

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
import json import json
import re
import socket import socket
import threading
import time import time
from functools import wraps from functools import wraps
from typing import List from typing import List
@ -10,11 +10,11 @@ import websockets
from adbutils.errors import AdbError from adbutils.errors import AdbError
from uiautomator2 import _Service from uiautomator2 import _Service
from module.base.decorator import Config, cached_property, del_cached_property from module.base.decorator import Config, cached_property, del_cached_property, has_cached_property
from module.base.timer import Timer from module.base.timer import Timer
from module.base.utils import * from module.base.utils import *
from module.device.connection import Connection from module.device.connection import Connection
from module.device.method.utils import RETRY_TRIES, retry_sleep, handle_adb_error from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep
from module.exception import RequestHumanTakeover, ScriptError from module.exception import RequestHumanTakeover, ScriptError
from module.logger import logger from module.logger import logger
@ -86,6 +86,8 @@ def insert_swipe(p0, p3, speed=15, min_distance=10):
distance = np.linalg.norm(np.subtract(points[1:], points[0]), axis=1) distance = np.linalg.norm(np.subtract(points[1:], points[0]), axis=1)
mask = np.append(True, distance > min_distance) mask = np.append(True, distance > min_distance)
points = np.array(points)[mask].tolist() points = np.array(points)[mask].tolist()
if len(points) <= 1:
points = [p0, p3]
else: else:
points = [p0, p3] points = [p0, p3]
@ -314,12 +316,18 @@ def retry(func):
def init(): def init():
self.adb_reconnect() self.adb_reconnect()
if self._minitouch_port:
self.adb_forward_remove(f'tcp:{self._minitouch_port}')
del_cached_property(self, '_minitouch_builder')
# Emulator closed # Emulator closed
except ConnectionAbortedError as e: except ConnectionAbortedError as e:
logger.error(e) logger.error(e)
def init(): def init():
self.adb_reconnect() self.adb_reconnect()
if self._minitouch_port:
self.adb_forward_remove(f'tcp:{self._minitouch_port}')
del_cached_property(self, '_minitouch_builder')
# MinitouchNotInstalledError: Received empty data from minitouch # MinitouchNotInstalledError: Received empty data from minitouch
except MinitouchNotInstalledError as e: except MinitouchNotInstalledError as e:
logger.error(e) logger.error(e)
@ -328,7 +336,7 @@ def retry(func):
self.install_uiautomator2() self.install_uiautomator2()
if self._minitouch_port: if self._minitouch_port:
self.adb_forward_remove(f'tcp:{self._minitouch_port}') self.adb_forward_remove(f'tcp:{self._minitouch_port}')
del_cached_property(self, 'minitouch_builder') del_cached_property(self, '_minitouch_builder')
# MinitouchOccupiedError: Timeout when connecting to minitouch # MinitouchOccupiedError: Timeout when connecting to minitouch
except MinitouchOccupiedError as e: except MinitouchOccupiedError as e:
logger.error(e) logger.error(e)
@ -337,19 +345,22 @@ def retry(func):
self.restart_atx() self.restart_atx()
if self._minitouch_port: if self._minitouch_port:
self.adb_forward_remove(f'tcp:{self._minitouch_port}') self.adb_forward_remove(f'tcp:{self._minitouch_port}')
del_cached_property(self, 'minitouch_builder') del_cached_property(self, '_minitouch_builder')
# AdbError # AdbError
except AdbError as e: except AdbError as e:
if handle_adb_error(e): if handle_adb_error(e):
def init(): def init():
self.adb_reconnect() self.adb_reconnect()
if self._minitouch_port:
self.adb_forward_remove(f'tcp:{self._minitouch_port}')
del_cached_property(self, '_minitouch_builder')
else: else:
break break
except BrokenPipeError as e: except BrokenPipeError as e:
logger.error(e) logger.error(e)
def init(): def init():
del_cached_property(self, 'minitouch_builder') del_cached_property(self, '_minitouch_builder')
# Unknown, probably a trucked image # Unknown, probably a trucked image
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
@ -365,23 +376,59 @@ def retry(func):
class Minitouch(Connection): class Minitouch(Connection):
_minitouch_port: int = 0 _minitouch_port: int = 0
_minitouch_client: socket.socket _minitouch_client: socket.socket = None
_minitouch_pid: int _minitouch_pid: int
_minitouch_ws: websockets.WebSocketClientProtocol _minitouch_ws: websockets.WebSocketClientProtocol
max_x: int max_x: int
max_y: int max_y: int
_minitouch_init_thread = None
@cached_property @cached_property
def minitouch_builder(self): @retry
def _minitouch_builder(self):
self.minitouch_init() self.minitouch_init()
return CommandBuilder(self) return CommandBuilder(self)
@property
def minitouch_builder(self):
# Wait init thread
if self._minitouch_init_thread is not None:
self._minitouch_init_thread.join()
del self._minitouch_init_thread
self._minitouch_init_thread = None
return self._minitouch_builder
def early_minitouch_init(self):
"""
Start a thread to init minitouch connection while the Alas instance just starting to take screenshots
This would speed up the first click 0.05s.
"""
if has_cached_property(self, '_minitouch_builder'):
return
def early_minitouch_init_func():
_ = self._minitouch_builder
thread = threading.Thread(target=early_minitouch_init_func, daemon=True)
self._minitouch_init_thread = thread
thread.start()
@Config.when(DEVICE_OVER_HTTP=False) @Config.when(DEVICE_OVER_HTTP=False)
def minitouch_init(self): def minitouch_init(self):
logger.hr('MiniTouch init') logger.hr('MiniTouch init')
max_x, max_y = 1280, 720 max_x, max_y = 1280, 720
max_contacts = 2 max_contacts = 2
max_pressure = 50 max_pressure = 50
# Try to close existing stream
if self._minitouch_client is not None:
try:
self._minitouch_client.close()
except Exception as e:
logger.error(e)
del self._minitouch_client
self.get_orientation() self.get_orientation()
self._minitouch_port = self.adb_forward("localabstract:minitouch") self._minitouch_port = self.adb_forward("localabstract:minitouch")

View File

@ -0,0 +1,541 @@
import asyncio
import ctypes
import os
import sys
from functools import partial, wraps
import cv2
import numpy as np
from module.base.decorator import cached_property, del_cached_property, has_cached_property
from module.base.utils import ensure_time
from module.device.method.minitouch import insert_swipe, random_rectangle_point
from module.device.method.utils import RETRY_TRIES, retry_sleep
from module.device.platform import Platform
from module.exception import RequestHumanTakeover
from module.logger import logger
class NemuIpcIncompatible(Exception):
pass
class NemuIpcError(Exception):
pass
class CaptureStd:
"""
Capture stdout and stderr from both python and C library
https://stackoverflow.com/questions/5081657/how-do-i-prevent-a-c-shared-library-to-print-on-stdout-in-python/17954769
```
with CaptureStd() as capture:
# String wasn't printed
print('whatever')
# But captured in ``capture.stdout``
print(f'Got stdout: "{capture.stdout}"')
print(f'Got stderr: "{capture.stderr}"')
```
"""
def __init__(self):
self.stdout = b''
self.stderr = b''
def _redirect_stdout(self, to):
sys.stdout.close()
os.dup2(to, self.fdout)
sys.stdout = os.fdopen(self.fdout, 'w')
def _redirect_stderr(self, to):
sys.stderr.close()
os.dup2(to, self.fderr)
sys.stderr = os.fdopen(self.fderr, 'w')
def __enter__(self):
self.fdout = sys.stdout.fileno()
self.fderr = sys.stderr.fileno()
self.reader_out, self.writer_out = os.pipe()
self.reader_err, self.writer_err = os.pipe()
self.old_stdout = os.dup(self.fdout)
self.old_stderr = os.dup(self.fderr)
file_out = os.fdopen(self.writer_out, 'w')
file_err = os.fdopen(self.writer_err, 'w')
self._redirect_stdout(to=file_out.fileno())
self._redirect_stderr(to=file_err.fileno())
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._redirect_stdout(to=self.old_stdout)
self._redirect_stderr(to=self.old_stderr)
os.close(self.old_stdout)
os.close(self.old_stderr)
self.stdout = self.recvall(self.reader_out)
self.stderr = self.recvall(self.reader_err)
os.close(self.reader_out)
os.close(self.reader_err)
@staticmethod
def recvall(reader, length=1024) -> bytes:
fragments = []
while 1:
chunk = os.read(reader, length)
if chunk:
fragments.append(chunk)
else:
break
output = b''.join(fragments)
return output
class CaptureNemuIpc(CaptureStd):
instance = None
def is_capturing(self):
"""
Only capture at the topmost wrapper to avoid nested capturing
If a capture is ongoing, this instance does nothing
"""
cls = self.__class__
return isinstance(cls.instance, cls) and cls.instance != self
def __enter__(self):
if self.is_capturing():
return self
super().__enter__()
CaptureNemuIpc.instance = self
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.is_capturing():
return
CaptureNemuIpc.instance = None
super().__exit__(exc_type, exc_val, exc_tb)
self.check_stdout()
self.check_stderr()
def check_stdout(self):
if not self.stdout:
return
logger.info(f'NemuIpc stdout: {self.stdout}')
def check_stderr(self):
if not self.stderr:
return
logger.error(f'NemuIpc stderr: {self.stderr}')
# Calling an old MuMu12 player
# Tested on 3.4.0
# b'nemu_capture_display rpc error: 1783\r\n'
# Tested on 3.7.3
# b'nemu_capture_display rpc error: 1745\r\n'
if b'error: 1783' in self.stderr or b'error: 1745' in self.stderr:
raise NemuIpcIncompatible(
f'NemuIpc requires MuMu12 version >= 3.8.13, please check your version')
# contact_id incorrect
# b'nemu_capture_display cannot find rpc connection\r\n'
if b'cannot find rpc connection' in self.stderr:
raise NemuIpcError(self.stderr)
# Emulator died
# b'nemu_capture_display rpc error: 1722\r\n'
# MuMuVMMSVC.exe died
# b'nemu_capture_display rpc error: 1726\r\n'
# No idea how to handle yet
if b'error: 1722' in self.stderr or b'error: 1726' in self.stderr:
raise NemuIpcError('Emulator instance is probably dead')
def retry(func):
@wraps(func)
def retry_wrapper(self, *args, **kwargs):
"""
Args:
self (NemuIpcImpl):
"""
init = None
for _ in range(RETRY_TRIES):
try:
if callable(init):
retry_sleep(_)
init()
return func(self, *args, **kwargs)
# Can't handle
except RequestHumanTakeover:
break
# Can't handle
except NemuIpcIncompatible as e:
logger.error(e)
break
# Function call timeout
except asyncio.TimeoutError:
logger.warning(f'Func {func.__name__}() call timeout, retrying: {_}')
def init():
self.reconnect()
# NemuIpcError
except NemuIpcError as e:
logger.error(e)
def init():
self.reconnect()
# Unknown, probably a trucked image
except Exception as e:
logger.exception(e)
def init():
pass
logger.critical(f'Retry {func.__name__}() failed')
raise RequestHumanTakeover
return retry_wrapper
class NemuIpcImpl:
def __init__(self, nemu_folder: str, instance_id: int, display_id: int = 0):
"""
Args:
nemu_folder: Installation path of MuMu12, e.g. E:/ProgramFiles/MuMuPlayer-12.0
instance_id: Emulator instance ID, starting from 0
display_id: Always 0 if keep app alive was disabled
"""
self.nemu_folder: str = nemu_folder
self.instance_id: int = instance_id
self.display_id: int = display_id
ipc_dll = os.path.abspath(os.path.join(nemu_folder, './shell/sdk/external_renderer_ipc.dll'))
logger.info(
f'NemuIpcImpl init, '
f'nemu_folder={nemu_folder}, '
f'ipc_dll={ipc_dll}, '
f'instance_id={instance_id}, '
f'display_id={display_id}'
)
try:
self.lib = ctypes.CDLL(ipc_dll)
except OSError as e:
logger.error(e)
# OSError: [WinError 126] 找不到指定的模块。
if not os.path.exists(ipc_dll):
raise NemuIpcIncompatible(
f'ipc_dll={ipc_dll} does not exist, '
f'NemuIpc requires MuMu12 version >= 3.8.13, please check your version')
else:
raise NemuIpcIncompatible(
f'ipc_dll={ipc_dll} exists, but cannot be loaded')
self.connect_id: int = 0
self.width = 0
self.height = 0
def connect(self):
if self.connect_id > 0:
return
connect_id = self.ev_run_sync(
self.lib.nemu_connect,
self.nemu_folder, self.instance_id
)
if connect_id == 0:
raise NemuIpcError(
'Connection failed, please check if nemu_folder is correct and emulator is running'
)
self.connect_id = connect_id
# logger.info(f'NemuIpc connected: {self.connect_id}')
def disconnect(self):
if self.connect_id == 0:
return
self.ev_run_sync(
self.lib.nemu_disconnect,
self.connect_id
)
# logger.info(f'NemuIpc disconnected: {self.connect_id}')
self.connect_id = 0
def reconnect(self):
self.disconnect()
self.connect()
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.disconnect()
@cached_property
def _ev(self):
return asyncio.new_event_loop()
async def ev_run_async(self, func, *args, **kwargs):
"""
Args:
func: Sync function to call
*args:
**kwargs:
Raises:
asyncio.TimeoutError: If function call timeout
"""
func_wrapped = partial(func, *args, **kwargs)
# Increased timeout for slow PCs
# Default screenshot interval is 0.2s, so a 0.15s timeout would have a fast retry without extra time costs
result = await asyncio.wait_for(self._ev.run_in_executor(None, func_wrapped), timeout=0.15)
return result
def ev_run_sync(self, func, *args, **kwargs):
"""
Args:
func: Sync function to call
*args:
**kwargs:
Raises:
asyncio.TimeoutError: If function call timeout
NemuIpcIncompatible:
NemuIpcError
"""
result = self._ev.run_until_complete(self.ev_run_async(func, *args, **kwargs))
err = False
if func.__name__ == 'nemu_connect':
if result == 0:
err = True
else:
if result > 0:
err = True
# Get to actual error message printed in std
if err:
logger.warning(f'Failed to call {func.__name__}, result={result}')
with CaptureNemuIpc():
result = self._ev.run_until_complete(self.ev_run_async(func, *args, **kwargs))
return result
def get_resolution(self):
"""
Get emulator resolution, `self.width` and `self.height` will be set
"""
if self.connect_id == 0:
self.connect()
width_ptr = ctypes.pointer(ctypes.c_int(0))
height_ptr = ctypes.pointer(ctypes.c_int(0))
nullptr = ctypes.POINTER(ctypes.c_int)()
ret = self.ev_run_sync(
self.lib.nemu_capture_display,
self.connect_id, self.display_id, 0, width_ptr, height_ptr, nullptr
)
if ret > 0:
raise NemuIpcError('nemu_capture_display failed during get_resolution()')
self.width = width_ptr.contents.value
self.height = height_ptr.contents.value
@retry
def screenshot(self):
"""
Returns:
np.ndarray: Image array in RGBA color space
Note that image is upside down
"""
if self.connect_id == 0:
self.connect()
self.get_resolution()
width_ptr = ctypes.pointer(ctypes.c_int(self.width))
height_ptr = ctypes.pointer(ctypes.c_int(self.height))
length = self.width * self.height * 4
pixels_pointer = ctypes.pointer((ctypes.c_ubyte * length)())
ret = self.ev_run_sync(
self.lib.nemu_capture_display,
self.connect_id, self.display_id, length, width_ptr, height_ptr, pixels_pointer
)
if ret > 0:
raise NemuIpcError('nemu_capture_display failed during screenshot()')
# image = np.ctypeslib.as_array(pixels_pointer, shape=(self.height, self.width, 4))
image = np.ctypeslib.as_array(pixels_pointer.contents).reshape((self.height, self.width, 4))
return image
def convert_xy(self, x, y):
"""
Convert classic ADB coordinates to Nemu's
`self.height` must be updated before calling this method
Returns:
int, int
"""
x, y = int(x), int(y)
x, y = self.height - y, x
return x, y
@retry
def down(self, x, y):
"""
Contact down, continuous contact down will be considered as swipe
"""
if self.connect_id == 0:
self.connect()
if self.height == 0:
self.get_resolution()
x, y = self.convert_xy(x, y)
ret = self.ev_run_sync(
self.lib.nemu_input_event_touch_down,
self.connect_id, self.display_id, x, y
)
if ret > 0:
raise NemuIpcError('nemu_input_event_touch_down failed')
@retry
def up(self):
"""
Contact up
"""
if self.connect_id == 0:
self.connect()
ret = self.ev_run_sync(
self.lib.nemu_input_event_touch_up,
self.connect_id, self.display_id
)
if ret > 0:
raise NemuIpcError('nemu_input_event_touch_up failed')
def serial_to_id(serial: str):
"""
Predict instance ID from serial
E.g.
"127.0.0.1:16384" -> 0
"127.0.0.1:16416" -> 1
Returns:
int: instance_id, or None if failed to predict
"""
try:
port = int(serial.split(':')[1])
except (IndexError, ValueError):
return None
index, offset = divmod(port - 16384, 32)
if 0 <= index < 32 and offset in [0, 1, 2]:
return index
else:
return None
class NemuIpc(Platform):
@cached_property
def nemu_ipc(self) -> NemuIpcImpl:
"""
Initialize a nemu ipc implementation
"""
# Try existing settings first
if self.config.EmulatorInfo_path:
folder = os.path.abspath(os.path.join(self.config.EmulatorInfo_path, '../../'))
index = serial_to_id(self.serial)
if index is not None:
try:
return NemuIpcImpl(
nemu_folder=folder,
instance_id=index,
display_id=0
).__enter__()
except (NemuIpcIncompatible, NemuIpcError) as e:
logger.error(e)
logger.error('Emulator info incorrect')
# Search emulator instance
# with E:\ProgramFiles\MuMuPlayer-12.0\shell\MuMuPlayer.exe
# installation path is E:\ProgramFiles\MuMuPlayer-12.0
if self.emulator_instance is None:
logger.error('Unable to use NemuIpc because emulator instance not found')
raise RequestHumanTakeover
try:
return NemuIpcImpl(
nemu_folder=self.emulator_instance.emulator.abspath('../'),
instance_id=self.emulator_instance.MuMuPlayer12_id,
display_id=0
).__enter__()
except (NemuIpcIncompatible, NemuIpcError) as e:
logger.error(e)
logger.error('Unable to initialize NemuIpc')
raise RequestHumanTakeover
def nemu_ipc_available(self) -> bool:
if not self.is_mumu_family:
return False
if self.nemud_app_keep_alive == '':
return False
try:
_ = self.nemu_ipc
except RequestHumanTakeover:
return False
return True
def nemu_ipc_release(self):
if has_cached_property(self, 'nemu_ipc'):
self.nemu_ipc.disconnect()
del_cached_property(self, 'nemu_ipc')
logger.info('nemu_ipc released')
def screenshot_nemu_ipc(self):
image = self.nemu_ipc.screenshot()
image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
cv2.flip(image, 0, dst=image)
return image
def click_nemu_ipc(self, x, y):
down = ensure_time((0.010, 0.020))
self.nemu_ipc.down(x, y)
self.sleep(down)
self.nemu_ipc.up()
self.sleep(0.050 - down)
def long_click_nemu_ipc(self, x, y, duration=1.0):
self.nemu_ipc.down(x, y)
self.sleep(duration)
self.nemu_ipc.up()
self.sleep(0.050)
def swipe_nemu_ipc(self, p1, p2):
points = insert_swipe(p0=p1, p3=p2)
for point in points:
self.nemu_ipc.down(*point)
self.sleep(0.010)
self.nemu_ipc.up()
self.sleep(0.050)
def drag_nemu_ipc(self, p1, p2, point_random=(-10, -10, 10, 10)):
p1 = np.array(p1) - random_rectangle_point(point_random)
p2 = np.array(p2) - random_rectangle_point(point_random)
points = insert_swipe(p0=p1, p3=p2, speed=20)
for point in points:
self.nemu_ipc.down(*point)
self.sleep(0.010)
self.nemu_ipc.down(*p2)
self.sleep(0.140)
self.nemu_ipc.down(*p2)
self.sleep(0.140)
self.nemu_ipc.up()
self.sleep(0.050)

View File

@ -65,6 +65,8 @@ class ScrcpyCore(Connection):
Raises: Raises:
ScrcpyError: ScrcpyError:
adbutils.AdbTimeout:
socket.timeout:
""" """
logger.hr('Scrcpy server start') logger.hr('Scrcpy server start')
commands = ScrcpyOptions.command_v120(jar_path=self.config.SCRCPY_FILEPATH_REMOTE) commands = ScrcpyOptions.command_v120(jar_path=self.config.SCRCPY_FILEPATH_REMOTE)
@ -72,6 +74,7 @@ class ScrcpyCore(Connection):
commands, commands,
stream=True, stream=True,
) )
self._scrcpy_server_stream.conn.settimeout(3)
logger.info('Create server stream') logger.info('Create server stream')
ret = self._scrcpy_server_stream.read(10) ret = self._scrcpy_server_stream.read(10)
@ -104,6 +107,7 @@ class ScrcpyCore(Connection):
self._scrcpy_video_socket = self.adb.create_connection( self._scrcpy_video_socket = self.adb.create_connection(
Network.LOCAL_ABSTRACT, "scrcpy" Network.LOCAL_ABSTRACT, "scrcpy"
) )
self._scrcpy_video_socket.settimeout(3)
break break
except AdbError: except AdbError:
sleep(0.1) sleep(0.1)
@ -115,6 +119,7 @@ class ScrcpyCore(Connection):
self._scrcpy_control_socket = self.adb.create_connection( self._scrcpy_control_socket = self.adb.create_connection(
Network.LOCAL_ABSTRACT, "scrcpy" Network.LOCAL_ABSTRACT, "scrcpy"
) )
self._scrcpy_control_socket.settimeout(3)
logger.info('Fetch device info') logger.info('Fetch device info')
device_name = self._scrcpy_video_socket.recv(64).decode("utf-8").rstrip("\x00") device_name = self._scrcpy_video_socket.recv(64).decode("utf-8").rstrip("\x00")
@ -151,23 +156,35 @@ class ScrcpyCore(Connection):
# logger.error(err) # logger.error(err)
self._scrcpy_alive = False self._scrcpy_alive = False
if self._scrcpy_server_stream is not None:
try: if self._scrcpy_stream_loop_thread is not None:
self._scrcpy_server_stream.close() self._scrcpy_stream_loop_thread.join(1)
except Exception: del self._scrcpy_stream_loop_thread
pass self._scrcpy_stream_loop_thread = None
if self._scrcpy_control_socket is not None: if self._scrcpy_control_socket is not None:
try: try:
self._scrcpy_control_socket.close() self._scrcpy_control_socket.close()
except Exception: except Exception as e:
pass logger.error(e)
del self._scrcpy_control_socket
self._scrcpy_control_socket = None
if self._scrcpy_video_socket is not None: if self._scrcpy_video_socket is not None:
try: try:
self._scrcpy_video_socket.close() self._scrcpy_video_socket.close()
except Exception: except Exception as e:
pass logger.error(e)
del self._scrcpy_video_socket
self._scrcpy_video_socket = None
if self._scrcpy_server_stream is not None:
try:
self._scrcpy_server_stream.close()
except Exception as e:
logger.error(e)
del self._scrcpy_server_stream
self._scrcpy_server_stream = None
logger.info('Scrcpy server stopped') logger.info('Scrcpy server stopped')
@ -195,7 +212,8 @@ class ScrcpyCore(Connection):
try: try:
raw_h264 = self._scrcpy_video_socket.recv(0x10000) raw_h264 = self._scrcpy_video_socket.recv(0x10000)
if raw_h264 == b"": if raw_h264 == b"":
raise ScrcpyError("Video stream is disconnected") if self._scrcpy_alive:
raise ScrcpyError("_scrcpy_stream_loop_thread: Video stream disconnected")
packets = codec.parse(raw_h264) packets = codec.parse(raw_h264)
for packet in packets: for packet in packets:
frames = codec.decode(packet) frames = codec.decode(packet)
@ -212,5 +230,8 @@ class ScrcpyCore(Connection):
if self._scrcpy_alive: if self._scrcpy_alive:
logger.error(f'_scrcpy_stream_loop_thread: {repr(e)}') logger.error(f'_scrcpy_stream_loop_thread: {repr(e)}')
raise raise
except Exception as e:
logger.error(f'_scrcpy_stream_loop_thread exception: {repr(e)}')
raise
raise ScrcpyError('_scrcpy_stream_loop stopped') raise ScrcpyError('_scrcpy_stream_loop stopped')

View File

@ -1,15 +1,16 @@
import socket
import time import time
from functools import wraps from functools import wraps
import numpy as np import numpy as np
from adbutils.errors import AdbError from adbutils.errors import AdbError, AdbTimeout
import module.device.method.scrcpy.const as const import module.device.method.scrcpy.const as const
from module.base.utils import random_rectangle_point from module.base.utils import random_rectangle_point
from module.device.method.minitouch import insert_swipe from module.device.method.minitouch import insert_swipe
from module.device.method.scrcpy.core import ScrcpyCore, ScrcpyError from module.device.method.scrcpy.core import ScrcpyCore, ScrcpyError
from module.device.method.uiautomator_2 import Uiautomator2 from module.device.method.uiautomator_2 import Uiautomator2
from module.device.method.utils import RETRY_TRIES, retry_sleep, handle_adb_error from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep
from module.exception import RequestHumanTakeover from module.exception import RequestHumanTakeover
from module.logger import logger from module.logger import logger
@ -19,7 +20,7 @@ def retry(func):
def retry_wrapper(self, *args, **kwargs): def retry_wrapper(self, *args, **kwargs):
""" """
Args: Args:
self (Minitouch): self (ScrcpyCore):
""" """
init = None init = None
for _ in range(RETRY_TRIES): for _ in range(RETRY_TRIES):
@ -47,6 +48,13 @@ def retry(func):
except ScrcpyError as e: except ScrcpyError as e:
logger.error(e) logger.error(e)
def init():
self.scrcpy_init()
# AdbTimeout
# socket.timeout
except (AdbTimeout, socket.timeout) as e:
logger.error(e)
def init(): def init():
self.scrcpy_init() self.scrcpy_init()
# AdbError # AdbError
@ -85,7 +93,8 @@ class Scrcpy(ScrcpyCore, Uiautomator2):
now = time.time() now = time.time()
while 1: while 1:
time.sleep(0.001) time.sleep(0.001)
if self._scrcpy_stream_loop_thread is None or not self._scrcpy_stream_loop_thread.is_alive(): thread = self._scrcpy_stream_loop_thread
if thread is None or not thread.is_alive():
raise ScrcpyError('_scrcpy_stream_loop_thread died') raise ScrcpyError('_scrcpy_stream_loop_thread died')
if self._scrcpy_last_frame_time > now: if self._scrcpy_last_frame_time > now:
screenshot = self._scrcpy_last_frame.copy() screenshot = self._scrcpy_last_frame.copy()

View File

@ -242,8 +242,19 @@ class Uiautomator2(Connection):
hierarchy = etree.fromstring(content.encode('utf-8')) hierarchy = etree.fromstring(content.encode('utf-8'))
return hierarchy return hierarchy
def uninstall_uiautomator2(self):
logger.info('Removing uiautomator2')
for file in [
'app-uiautomator.apk',
'app-uiautomator-test.apk',
'minitouch',
'minitouch.so',
'atx-agent',
]:
self.adb_shell(["rm", f"/data/local/tmp/{file}"])
@retry @retry
def resolution_uiautomator2(self) -> t.Tuple[int, int]: def resolution_uiautomator2(self, cal_rotation=True) -> t.Tuple[int, int]:
""" """
Faster u2.window_size(), cause that calls `dumpsys display` twice. Faster u2.window_size(), cause that calls `dumpsys display` twice.
@ -252,6 +263,7 @@ class Uiautomator2(Connection):
""" """
info = self.u2.http.get('/info').json() info = self.u2.http.get('/info').json()
w, h = info['display']['width'], info['display']['height'] w, h = info['display']['width'], info['display']['height']
if cal_rotation:
rotation = self.get_orientation() rotation = self.get_orientation()
if (w > h) != (rotation % 2 == 1): if (w > h) != (rotation % 2 == 1):
w, h = h, w w, h = h, w

View File

@ -1,3 +1,4 @@
import os
import random import random
import re import re
import socket import socket
@ -5,6 +6,7 @@ import time
import typing as t import typing as t
import uiautomator2 as u2 import uiautomator2 as u2
import uiautomator2cache
from adbutils import AdbTimeout from adbutils import AdbTimeout
from lxml import etree from lxml import etree
@ -51,6 +53,25 @@ from module.logger import logger
RETRY_TRIES = 5 RETRY_TRIES = 5
RETRY_DELAY = 3 RETRY_DELAY = 3
# Patch uiautomator2 appdir
u2.init.appdir = os.path.dirname(uiautomator2cache.__file__)
# Patch uiautomator2 logger
u2_logger = u2.logger
u2_logger.debug = logger.info
u2_logger.info = logger.info
u2_logger.warning = logger.warning
u2_logger.error = logger.error
u2_logger.critical = logger.critical
def setup_logger(*args, **kwargs):
return u2_logger
u2.setup_logger = setup_logger
u2.init.setup_logger = setup_logger
def is_port_using(port_num): def is_port_using(port_num):
""" if port is using by others, return True. else return False """ """ if port is using by others, return True. else return False """
@ -253,7 +274,7 @@ def remove_suffix(s, suffix):
Returns: Returns:
str, bytes: str, bytes:
""" """
return s[:len(suffix)] if s.endswith(suffix) else s return s[:-len(suffix)] if s.endswith(suffix) else s
def remove_shell_warning(s): def remove_shell_warning(s):

View File

@ -0,0 +1,109 @@
import os
import re
import sys
from module.base.decorator import cached_property
from module.logger import logger
"""
Importing pkg_resources is so slow, like 0.4 ~ 1.0s, just google it you will find it indeed really slow.
Since it was some kind of standard library there is no way to modify it or speed it up.
So here's a poor but fast implementation of pkg_resources returning the things in need.
To patch:
```
# Patch pkg_resources before importing adbutils and uiautomator2
from module.device.pkg_resources import get_distribution
# Just avoid being removed by import optimization
_ = get_distribution
```
"""
# Inject sys.modules, pretend we have pkg_resources imported
try:
sys.modules['pkg_resources'] = sys.modules['module.device.pkg_resources']
except KeyError:
logger.error('Patch pkg_resources failed, patch module does not exists')
def remove_suffix(s, suffix):
"""
Remove suffix of a string or bytes like `string.removesuffix(suffix)`, which is on Python3.9+
Args:
s (str, bytes):
suffix (str, bytes):
Returns:
str, bytes:
"""
return s[:-len(suffix)] if s.endswith(suffix) else s
class FakeDistributionObject:
def __init__(self, dist, version):
self.dist = dist
self.version = version
def __str__(self):
return f'{self.__class__.__name__}({self.dist}={self.version})'
__repr__ = __str__
class PackageCache:
@cached_property
def site_packages(self):
# Just whatever library to locate the `site-packages` directory
import requests
path = os.path.abspath(os.path.join(requests.__file__, '../../'))
return path
@cached_property
def dict_installed_packages(self):
"""
Returns:
dict: Key: str, package name
Value: FakeDistributionObject
"""
dic = {}
for file in os.listdir(self.site_packages):
# mxnet_cu101-1.6.0.dist-info
# adbutils-0.11.0-py3.7.egg-info
res = re.match(r'^([a-zA-Z0-9._]+)-([a-zA-Z0-9._]+)-', file)
if res:
version = remove_suffix(res.group(2), '.dist')
# version = res.group(2)
obj = FakeDistributionObject(
dist=res.group(1),
version=version,
)
dic[obj.dist] = obj
return dic
PACKAGE_CACHE = PackageCache()
def resource_filename(*args):
if args == ("adbutils", "binaries"):
path = os.path.abspath(os.path.join(PACKAGE_CACHE.site_packages, *args))
return path
def get_distribution(dist):
"""Return a current distribution object for a Requirement or string"""
if dist == 'adbutils':
return PACKAGE_CACHE.dict_installed_packages.get(
'adbutils',
FakeDistributionObject('adbutils', '0.11.0'),
)
if dist == 'uiautomator2':
return PACKAGE_CACHE.dict_installed_packages.get(
'uiautomator2',
FakeDistributionObject('uiautomator2', '2.16.17'),
)
class DistributionNotFound(Exception):
pass

View File

@ -36,6 +36,21 @@ def get_serial_pair(serial):
return None, None return None, None
def remove_duplicated_path(paths):
"""
Args:
paths (list[str]):
Returns:
list[str]:
"""
paths = sorted(set(paths))
dic = {}
for path in paths:
dic.setdefault(path.lower(), path)
return list(dic.values())
@dataclass @dataclass
class EmulatorInstanceBase: class EmulatorInstanceBase:
# Serial for adb connection # Serial for adb connection
@ -90,7 +105,7 @@ class EmulatorInstanceBase:
Returns: Returns:
int: Instance ID, or None if this is not a MuMu 12 instance int: Instance ID, or None if this is not a MuMu 12 instance
""" """
res = re.search(r'MuMuPlayer-12.0-(\d+)', self.name) res = re.search(r'MuMuPlayer(?:Global)?-12.0-(\d+)', self.name)
if res: if res:
return int(res.group(1)) return int(res.group(1))
res = re.search(r'YXArkNights-12.0-(\d+)', self.name) res = re.search(r'YXArkNights-12.0-(\d+)', self.name)
@ -205,6 +220,14 @@ class EmulatorBase:
class EmulatorManagerBase: class EmulatorManagerBase:
@staticmethod
def iter_running_emulator():
"""
Yields:
str: Path to emulator executables, may contains duplicate values
"""
return
@cached_property @cached_property
def all_emulators(self) -> t.List[EmulatorBase]: def all_emulators(self) -> t.List[EmulatorBase]:
""" """

View File

@ -8,7 +8,8 @@ from dataclasses import dataclass
# module/device/platform/emulator_base.py # module/device/platform/emulator_base.py
# module/device/platform/emulator_windows.py # module/device/platform/emulator_windows.py
# Will be used in Alas Easy Install, they shouldn't import any Alas modules. # Will be used in Alas Easy Install, they shouldn't import any Alas modules.
from module.device.platform.emulator_base import EmulatorBase, EmulatorInstanceBase, EmulatorManagerBase from module.device.platform.emulator_base import EmulatorBase, EmulatorInstanceBase, EmulatorManagerBase, \
remove_duplicated_path
from module.device.platform.utils import cached_property, iter_folder from module.device.platform.utils import cached_property, iter_folder
@ -70,7 +71,7 @@ class Emulator(EmulatorBase):
def path_to_type(cls, path: str) -> str: def path_to_type(cls, path: str) -> str:
""" """
Args: Args:
path: Path to .exe file path: Path to .exe file, case insensitive
Returns: Returns:
str: Emulator type, such as Emulator.NoxPlayer str: Emulator type, such as Emulator.NoxPlayer
@ -78,46 +79,49 @@ class Emulator(EmulatorBase):
folder, exe = os.path.split(path) folder, exe = os.path.split(path)
folder, dir1 = os.path.split(folder) folder, dir1 = os.path.split(folder)
folder, dir2 = os.path.split(folder) folder, dir2 = os.path.split(folder)
if exe == 'Nox.exe': exe = exe.lower()
if dir2 == 'Nox': dir1 = dir1.lower()
dir2 = dir2.lower()
if exe == 'nox.exe':
if dir2 == 'nox':
return cls.NoxPlayer return cls.NoxPlayer
elif dir2 == 'Nox64': elif dir2 == 'nox64':
return cls.NoxPlayer64 return cls.NoxPlayer64
else: else:
return cls.NoxPlayer return cls.NoxPlayer
if exe == 'Bluestacks.exe': if exe == 'bluestacks.exe':
if dir1 in ['BlueStacks', 'BlueStacks_cn']: if dir1 in ['bluestacks', 'bluestacks_cn']:
return cls.BlueStacks4 return cls.BlueStacks4
elif dir1 in ['BlueStacks_nxt', 'BlueStacks_nxt_cn']: elif dir1 in ['bluestacks_nxt', 'bluestacks_nxt_cn']:
return cls.BlueStacks5 return cls.BlueStacks5
else: else:
return cls.BlueStacks4 return cls.BlueStacks4
if exe == 'HD-Player.exe': if exe == 'hd-player.exe':
if dir1 in ['BlueStacks', 'BlueStacks_cn']: if dir1 in ['bluestacks', 'bluestacks_cn']:
return cls.BlueStacks4 return cls.BlueStacks4
elif dir1 in ['BlueStacks_nxt', 'BlueStacks_nxt_cn']: elif dir1 in ['bluestacks_nxt', 'bluestacks_nxt_cn']:
return cls.BlueStacks5 return cls.BlueStacks5
else: else:
return cls.BlueStacks5 return cls.BlueStacks5
if exe == 'dnplayer.exe': if exe == 'dnplayer.exe':
if dir1 == 'LDPlayer': if dir1 == 'ldplayer':
return cls.LDPlayer3 return cls.LDPlayer3
elif dir1 == 'LDPlayer4': elif dir1 == 'ldplayer4':
return cls.LDPlayer4 return cls.LDPlayer4
elif dir1 == 'LDPlayer9': elif dir1 == 'ldplayer9':
return cls.LDPlayer9 return cls.LDPlayer9
else: else:
return cls.LDPlayer3 return cls.LDPlayer3
if exe == 'NemuPlayer.exe': if exe == 'nemuplayer.exe':
if dir2 == 'nemu': if dir2 == 'nemu':
return cls.MuMuPlayer return cls.MuMuPlayer
elif dir2 == 'nemu9': elif dir2 == 'nemu9':
return cls.MuMuPlayerX return cls.MuMuPlayerX
else: else:
return cls.MuMuPlayer return cls.MuMuPlayer
if exe == 'MuMuPlayer.exe': if exe == 'mumuplayer.exe':
return cls.MuMuPlayer12 return cls.MuMuPlayer12
if exe == 'MEmu.exe': if exe == 'memu.exe':
return cls.MEmuPlayer return cls.MEmuPlayer
return '' return ''
@ -143,7 +147,9 @@ class Emulator(EmulatorBase):
elif 'NemuMultiPlayer.exe' in exe: elif 'NemuMultiPlayer.exe' in exe:
yield exe.replace('NemuMultiPlayer.exe', 'NemuPlayer.exe') yield exe.replace('NemuMultiPlayer.exe', 'NemuPlayer.exe')
elif 'MuMuMultiPlayer.exe' in exe: elif 'MuMuMultiPlayer.exe' in exe:
yield exe.replace('MuMuMultiPlayer.exe', 'MuMuManager.exe') yield exe.replace('MuMuMultiPlayer.exe', 'MuMuPlayer.exe')
elif 'MuMuManager.exe' in exe:
yield exe.replace('MuMuManager.exe', 'MuMuPlayer.exe')
elif 'MEmuConsole.exe' in exe: elif 'MEmuConsole.exe' in exe:
yield exe.replace('MEmuConsole.exe', 'MEmu.exe') yield exe.replace('MEmuConsole.exe', 'MEmu.exe')
else: else:
@ -316,7 +322,7 @@ class EmulatorManager(EmulatorManagerBase):
Get recently executed programs in UserAssist Get recently executed programs in UserAssist
https://github.com/forensicmatt/MonitorUserAssist https://github.com/forensicmatt/MonitorUserAssist
Returns: Yields:
str: Path to emulator executables, may contains duplicate values str: Path to emulator executables, may contains duplicate values
""" """
path = r'Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist' path = r'Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist'
@ -447,6 +453,31 @@ class EmulatorManager(EmulatorManagerBase):
uninstall = res.group(1) if res else uninstall uninstall = res.group(1) if res else uninstall
yield uninstall yield uninstall
@staticmethod
def iter_running_emulator():
"""
Yields:
str: Path to emulator executables, may contains duplicate values
"""
try:
import psutil
except ModuleNotFoundError:
return
# Since this is a one-time-usage, we access psutil._psplatform.Process directly
# to bypass the call of psutil.Process.is_running().
# This only costs about 0.017s.
for pid in psutil.pids():
proc = psutil._psplatform.Process(pid)
try:
exe = proc.cmdline()
exe = exe[0].replace(r'\\', '/').replace('\\', '/')
except (psutil.AccessDenied, IndexError):
# psutil.AccessDenied
continue
if Emulator.is_emulator(exe):
yield exe
@cached_property @cached_property
def all_emulators(self) -> t.List[Emulator]: def all_emulators(self) -> t.List[Emulator]:
""" """
@ -474,7 +505,7 @@ class EmulatorManager(EmulatorManagerBase):
exe.add(ld) exe.add(ld)
# Uninstall registry # Uninstall registry
for uninstall in self.iter_uninstall_registry(): for uninstall in EmulatorManager.iter_uninstall_registry():
# Find emulator executable from uninstaller # Find emulator executable from uninstaller
for file in iter_folder(abspath(os.path.dirname(uninstall)), ext='.exe'): for file in iter_folder(abspath(os.path.dirname(uninstall)), ext='.exe'):
if Emulator.is_emulator(file) and os.path.exists(file): if Emulator.is_emulator(file) and os.path.exists(file):
@ -488,9 +519,14 @@ class EmulatorManager(EmulatorManagerBase):
if Emulator.is_emulator(file) and os.path.exists(file): if Emulator.is_emulator(file) and os.path.exists(file):
exe.add(file) exe.add(file)
# Running
for file in EmulatorManager.iter_running_emulator():
if os.path.exists(file):
exe.add(file)
# De-redundancy
exe = [Emulator(path).path for path in exe if Emulator.is_emulator(path)] exe = [Emulator(path).path for path in exe if Emulator.is_emulator(path)]
exe = sorted(set(exe)) exe = [Emulator(path) for path in remove_duplicated_path(exe)]
exe = [Emulator(path) for path in exe]
return exe return exe
@cached_property @cached_property

View File

@ -6,7 +6,8 @@ from pydantic import BaseModel
from module.base.decorator import cached_property, del_cached_property from module.base.decorator import cached_property, del_cached_property
from module.base.utils import SelectedGrids from module.base.utils import SelectedGrids
from module.device.connection import Connection from module.device.connection import Connection
from module.device.platform.emulator_base import EmulatorInstanceBase, EmulatorManagerBase from module.device.method.utils import get_serial_pair
from module.device.platform.emulator_base import EmulatorInstanceBase, EmulatorManagerBase, remove_duplicated_path
from module.logger import logger from module.logger import logger
@ -47,8 +48,20 @@ class PlatformBase(Connection, EmulatorManagerBase):
@cached_property @cached_property
def emulator_info(self) -> EmulatorInfo: def emulator_info(self) -> EmulatorInfo:
emulator = self.config.EmulatorInfo_Emulator emulator = self.config.EmulatorInfo_Emulator
name = str(self.config.EmulatorInfo_name).strip().replace('\n', '') if emulator == 'auto':
path = str(self.config.EmulatorInfo_path).strip().replace('\n', '') emulator = ''
def parse_info(value):
if isinstance(value, str):
value = value.strip().replace('\n', '')
if value in ['None', 'False', 'True']:
value = ''
return value
else:
return ''
name = parse_info(self.config.EmulatorInfo_name)
path = parse_info(self.config.EmulatorInfo_path)
return EmulatorInfo( return EmulatorInfo(
emulator=emulator, emulator=emulator,
@ -68,8 +81,14 @@ class PlatformBase(Connection, EmulatorManagerBase):
path=data.path, path=data.path,
name=data.name, name=data.name,
) )
# Redirect emulator-5554 to 127.0.0.1:5555
serial = self.serial
port_serial, _ = get_serial_pair(self.serial)
if port_serial is not None:
serial = port_serial
instance = self.find_emulator_instance( instance = self.find_emulator_instance(
serial=str(self.config.Emulator_Serial).strip(), serial=serial,
name=data.name, name=data.name,
path=data.path, path=data.path,
emulator=data.emulator, emulator=data.emulator,
@ -117,7 +136,7 @@ class PlatformBase(Connection, EmulatorManagerBase):
# Search by serial # Search by serial
select = instances.select(**search_args) select = instances.select(**search_args)
if select.count == 0: if select.count == 0:
logger.warning(f'No emulator instance with {search_args}') logger.warning(f'No emulator instance with {search_args}, serial invalid')
return None return None
if select.count == 1: if select.count == 1:
instance = select[0] instance = select[0]
@ -130,9 +149,9 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['name'] = name search_args['name'] = name
select = instances.select(**search_args) select = instances.select(**search_args)
if select.count == 0: if select.count == 0:
logger.warning(f'No emulator instances with {search_args}') logger.warning(f'No emulator instances with {search_args}, name invalid')
return None search_args.pop('name')
if select.count == 1: elif select.count == 1:
instance = select[0] instance = select[0]
logger.hr('Emulator instance', level=2) logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}') logger.info(f'Found emulator instance: {instance}')
@ -143,9 +162,9 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['path'] = path search_args['path'] = path
select = instances.select(**search_args) select = instances.select(**search_args)
if select.count == 0: if select.count == 0:
logger.warning(f'No emulator instances with {search_args}') logger.warning(f'No emulator instances with {search_args}, path invalid')
return None search_args.pop('path')
if select.count == 1: elif select.count == 1:
instance = select[0] instance = select[0]
logger.hr('Emulator instance', level=2) logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}') logger.info(f'Found emulator instance: {instance}')
@ -156,9 +175,28 @@ class PlatformBase(Connection, EmulatorManagerBase):
search_args['type'] = emulator search_args['type'] = emulator
select = instances.select(**search_args) select = instances.select(**search_args)
if select.count == 0: if select.count == 0:
logger.warning(f'No emulator instances with {search_args}') logger.warning(f'No emulator instances with {search_args}, type invalid')
return None search_args.pop('type')
if select.count == 1: elif select.count == 1:
instance = select[0]
logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}')
return instance
# Still too many instances, search from running emulators
running = remove_duplicated_path(list(self.iter_running_emulator()))
logger.info('Running emulators')
for exe in running:
logger.info(exe)
if len(running) == 1:
logger.info('Only one running emulator')
# Same as searching path
search_args['path'] = running[0]
select = instances.select(**search_args)
if select.count == 0:
logger.warning(f'No emulator instances with {search_args}, path invalid')
search_args.pop('path')
elif select.count == 1:
instance = select[0] instance = select[0]
logger.hr('Emulator instance', level=2) logger.hr('Emulator instance', level=2)
logger.info(f'Found emulator instance: {instance}') logger.info(f'Found emulator instance: {instance}')
@ -167,9 +205,3 @@ class PlatformBase(Connection, EmulatorManagerBase):
# Still too many instances # Still too many instances
logger.warning(f'Found multiple emulator instances with {search_args}') logger.warning(f'Found multiple emulator instances with {search_args}')
return None return None
if __name__ == '__main__':
self = PlatformBase('alas')
d = self.emulator_instance
print(d)

View File

@ -13,13 +13,14 @@ from module.base.utils import get_color, image_size, limit_in, save_image
from module.device.method.adb import Adb from module.device.method.adb import Adb
from module.device.method.ascreencap import AScreenCap from module.device.method.ascreencap import AScreenCap
from module.device.method.droidcast import DroidCast from module.device.method.droidcast import DroidCast
from module.device.method.nemu_ipc import NemuIpc
from module.device.method.scrcpy import Scrcpy from module.device.method.scrcpy import Scrcpy
from module.device.method.wsa import WSA from module.device.method.wsa import WSA
from module.exception import RequestHumanTakeover, ScriptError from module.exception import RequestHumanTakeover, ScriptError
from module.logger import logger from module.logger import logger
class Screenshot(Adb, WSA, DroidCast, AScreenCap, Scrcpy): class Screenshot(Adb, WSA, DroidCast, AScreenCap, Scrcpy, NemuIpc):
_screen_size_checked = False _screen_size_checked = False
_screen_black_checked = False _screen_black_checked = False
_minicap_uninstalled = False _minicap_uninstalled = False
@ -38,6 +39,7 @@ class Screenshot(Adb, WSA, DroidCast, AScreenCap, Scrcpy):
'DroidCast': self.screenshot_droidcast, 'DroidCast': self.screenshot_droidcast,
'DroidCast_raw': self.screenshot_droidcast_raw, 'DroidCast_raw': self.screenshot_droidcast_raw,
'scrcpy': self.screenshot_scrcpy, 'scrcpy': self.screenshot_scrcpy,
'nemu_ipc': self.screenshot_nemu_ipc,
} }
def screenshot(self): def screenshot(self):
@ -70,6 +72,10 @@ class Screenshot(Adb, WSA, DroidCast, AScreenCap, Scrcpy):
return self.image return self.image
@property
def has_cached_image(self):
return hasattr(self, 'image') and self.image is not None
def _handle_orientated_image(self, image): def _handle_orientated_image(self, image):
""" """
Args: Args:
@ -159,6 +165,9 @@ class Screenshot(Adb, WSA, DroidCast, AScreenCap, Scrcpy):
if interval != origin: if interval != origin:
logger.warning(f'Optimization.ScreenshotInterval {origin} is revised to {interval}') logger.warning(f'Optimization.ScreenshotInterval {origin} is revised to {interval}')
self.config.Optimization_ScreenshotInterval = interval self.config.Optimization_ScreenshotInterval = interval
# Allow nemu_ipc to have a lower default
if self.config.Emulator_ScreenshotMethod == 'nemu_ipc':
interval = limit_in(origin, 0.1, 0.2)
elif interval == 'combat': elif interval == 'combat':
origin = self.config.Optimization_CombatScreenshotInterval origin = self.config.Optimization_CombatScreenshotInterval
interval = limit_in(origin, 0.3, 1.0) interval = limit_in(origin, 0.3, 1.0)

View File

@ -1,4 +0,0 @@
from module.logger import logger
def handle_notify(*args, **kwargs):
logger.error('Error notify is not supported yet')

View File

@ -0,0 +1,4 @@
def handle_notify(*args, **kwargs):
# Lazy import onepush
from module.notify.notify import handle_notify
return handle_notify(*args, **kwargs)

75
module/notify/notify.py Normal file
View File

@ -0,0 +1,75 @@
import onepush.core
import yaml
from onepush import get_notifier
from onepush.core import Provider
from onepush.exceptions import OnePushException
from onepush.providers.custom import Custom
from requests import Response
from module.logger import logger
onepush.core.log = logger
def handle_notify(_config: str, **kwargs) -> bool:
try:
config = {}
for item in yaml.safe_load_all(_config):
config.update(item)
except Exception:
logger.error("Fail to load onepush config, skip sending")
return False
try:
provider_name: str = config.pop("provider", None)
if provider_name is None:
logger.info("No provider specified, skip sending")
return False
notifier: Provider = get_notifier(provider_name)
required: list[str] = notifier.params["required"]
config.update(kwargs)
# pre check
for key in required:
if key not in config:
logger.warning(
f"Notifier {notifier.name} require param '{key}' but not provided"
)
if isinstance(notifier, Custom):
if "method" not in config or config["method"] == "post":
config["datatype"] = "json"
if not ("data" in config or isinstance(config["data"], dict)):
config["data"] = {}
if "title" in kwargs:
config["data"]["title"] = kwargs["title"]
if "content" in kwargs:
config["data"]["content"] = kwargs["content"]
if provider_name.lower() == "gocqhttp":
access_token = config.get("access_token")
if access_token:
config["token"] = access_token
resp = notifier.notify(**config)
if isinstance(resp, Response):
if resp.status_code != 200:
logger.warning("Push notify failed!")
logger.warning(f"HTTP Code:{resp.status_code}")
return False
else:
if provider_name.lower() == "gocqhttp":
return_data: dict = resp.json()
if return_data["status"] == "failed":
logger.warning("Push notify failed!")
logger.warning(
f"Return message:{return_data['wording']}")
return False
except OnePushException:
logger.exception("Push notify failed")
return False
except Exception as e:
logger.exception(e)
return False
logger.info("Push notify success")
return True

View File

@ -7,7 +7,7 @@ import module.config.server as server
from module.exception import ScriptError from module.exception import ScriptError
# ord('') = 65294 # ord('') = 65294
REGEX_PUNCTUATION = re.compile(r'[ ,.\'"“”,。…:;!??·・•●〇°*※\-—/\\\n\t()\[\]()「」『』【】《》[]]') REGEX_PUNCTUATION = re.compile(r'[ ,.\'"“”,。…:;!??·・•●〇°*※\-—/\\|丨\n\t()\[\]()「」『』【】《》[]]')
def parse_name(n): def parse_name(n):

View File

@ -422,7 +422,10 @@ class Duration(Ocr):
class OcrWhiteLetterOnComplexBackground(Ocr): class OcrWhiteLetterOnComplexBackground(Ocr):
white_preprocess = True
def pre_process(self, image): def pre_process(self, image):
if self.white_preprocess:
image = extract_white_letters(image, threshold=255) image = extract_white_letters(image, threshold=255)
image = cv2.merge([image, image, image]) image = cv2.merge([image, image, image])
return image return image

View File

@ -160,6 +160,7 @@ class DraggableList:
logger.info(f'Insight row: {row}, index={row_index}') logger.info(f'Insight row: {row}, index={row_index}')
last_buttons: set[OcrResultButton] = None last_buttons: set[OcrResultButton] = None
bottom_check = Timer(3, count=5).start()
while 1: while 1:
if skip_first_screenshot: if skip_first_screenshot:
skip_first_screenshot = False skip_first_screenshot = False
@ -183,8 +184,11 @@ class DraggableList:
0, count=0), timeout=Timer(1.5, count=5)) 0, count=0), timeout=Timer(1.5, count=5))
skip_first_screenshot = True skip_first_screenshot = True
if self.cur_buttons and last_buttons == set(self.cur_buttons): if self.cur_buttons and last_buttons == set(self.cur_buttons):
if bottom_check.reached():
logger.warning(f'No more rows in {self}') logger.warning(f'No more rows in {self}')
return False return False
else:
bottom_check.reset()
last_buttons = set(self.cur_buttons) last_buttons = set(self.cur_buttons)
return True return True

View File

@ -37,6 +37,8 @@ class DeployConfig(_DeployConfig):
if hasattr(self, key): if hasattr(self, key):
super().__setattr__(key, value) super().__setattr__(key, value)
self.config_redirect()
def write(self): def write(self):
""" """
Write `self.config` into deploy config. Write `self.config` into deploy config.

View File

@ -325,11 +325,17 @@ def put_arg_input(kwargs: T_Output_Kwargs) -> Output:
) )
def product_stored_row(kwargs: T_Output_Kwargs, key, value): def product_stored_row(key, value):
kwargs = copy.copy(kwargs) if key[-1].isdigit():
kwargs["name"] += f'_{key}' # quest1, quest2, quest3
kwargs["value"] = value return [put_text(value).style("--dashboard-time--")]
return put_input(**kwargs).style("--input--") else:
# calyx, relic
# 3 (relic)
return [
put_text(value).style("--dashboard-value--"),
put_text(f" ({key})").style("--dashboard-time--"),
]
def put_arg_stored(kwargs: T_Output_Kwargs) -> Output: def put_arg_stored(kwargs: T_Output_Kwargs) -> Output:
@ -337,11 +343,32 @@ def put_arg_stored(kwargs: T_Output_Kwargs) -> Output:
kwargs["disabled"] = True kwargs["disabled"] = True
values = kwargs.pop("value", {}) values = kwargs.pop("value", {})
value = values.pop("value", "")
total = values.pop("total", "")
time_ = values.pop("time", "") time_ = values.pop("time", "")
rows = [product_stored_row(kwargs, key, value) for key, value in values.items() if value] if value != "" and total != "":
rows = [put_scope(f"dashboard-value-{name}", [
put_text(value).style("--dashboard-value--"),
put_text(f" / {total}").style("--dashboard-time--"),
])]
elif value != "":
rows = [put_scope(f"dashboard-value-{name}", [
put_text(value).style("--dashboard-value--")
])]
else:
rows = []
if values:
rows += [
put_scope(f"dashboard-value-{name}-{key}", product_stored_row(key, value))
for key, value in values.items() if value != ""
]
if time_: if time_:
rows += [product_stored_row(kwargs, "time", time_)] rows.append(
put_text(time_).style("--dashboard-time--")
)
return put_scope( return put_scope(
f"arg_container-stored-{name}", f"arg_container-stored-{name}",
[ [

View File

@ -21,6 +21,7 @@ pyyaml
inflection inflection
prettytable==2.2.1 prettytable==2.2.1
pydantic>=2.4 pydantic>=2.4
onepush==1.3.0
# OCR # OCR
pponnxcr==2.0 pponnxcr==2.0

View File

@ -37,6 +37,7 @@ markdown-it-py==2.2.0 # via rich
mdurl==0.1.2 # via markdown-it-py mdurl==0.1.2 # via markdown-it-py
mpmath==1.3.0 # via sympy mpmath==1.3.0 # via sympy
numpy==1.24.3 # via -r requirements-in.txt, onnxruntime, opencv-python, pponnxcr, scipy, shapely numpy==1.24.3 # via -r requirements-in.txt, onnxruntime, opencv-python, pponnxcr, scipy, shapely
onepush==1.3.0 # via -r requirements-in.txt
onnxruntime==1.14.1 # via pponnxcr onnxruntime==1.14.1 # via pponnxcr
opencv-python==4.7.0.72 # via -r requirements-in.txt, pponnxcr opencv-python==4.7.0.72 # via -r requirements-in.txt, pponnxcr
packaging==20.9 # via deprecation, onnxruntime, uiautomator2 packaging==20.9 # via deprecation, onnxruntime, uiautomator2
@ -48,6 +49,7 @@ protobuf==4.23.0 # via onnxruntime
psutil==5.9.3 # via -r requirements-in.txt psutil==5.9.3 # via -r requirements-in.txt
py==1.11.0 # via retry py==1.11.0 # via retry
pyclipper==1.3.0.post4 # via pponnxcr pyclipper==1.3.0.post4 # via pponnxcr
pycryptodome==3.20.0 # via onepush
pydantic==2.4.2 # via -r requirements-in.txt pydantic==2.4.2 # via -r requirements-in.txt
pydantic-core==2.10.1 # via pydantic pydantic-core==2.10.1 # via pydantic
pyelftools==0.29 # via apkutils2 pyelftools==0.29 # via apkutils2
@ -58,7 +60,7 @@ pyreadline3==3.4.1 # via humanfriendly
python-dotenv==1.0.0 # via uvicorn python-dotenv==1.0.0 # via uvicorn
pywebio==1.8.3 # via -r requirements-in.txt pywebio==1.8.3 # via -r requirements-in.txt
pyyaml==6.0 # via -r requirements-in.txt, uvicorn pyyaml==6.0 # via -r requirements-in.txt, uvicorn
requests==2.30.0 # via adbutils, uiautomator2 requests==2.30.0 # via adbutils, onepush, uiautomator2
retry==0.9.2 # via adbutils, uiautomator2 retry==0.9.2 # via adbutils, uiautomator2
rich==13.3.5 # via -r requirements-in.txt rich==13.3.5 # via -r requirements-in.txt
scipy==1.10.1 # via -r requirements-in.txt scipy==1.10.1 # via -r requirements-in.txt

View File

@ -16,7 +16,13 @@ class Route(RouteBase, Combat, CharacterTrial):
def wait_next_skill(self, expected_end=None, skip_first_screenshot=True): def wait_next_skill(self, expected_end=None, skip_first_screenshot=True):
# Ended at START_TRIAL # Ended at START_TRIAL
def combat_end(): def combat_end():
return self.match_template_color(START_TRIAL) if self.match_template_color(START_TRIAL):
logger.info('Trial ended at START_TRIAL')
return True
if self.is_in_main():
logger.warning('Trial ended at is_in_main()')
return True
return False
return super().wait_next_skill(expected_end=combat_end, skip_first_screenshot=skip_first_screenshot) return super().wait_next_skill(expected_end=combat_end, skip_first_screenshot=skip_first_screenshot)

View File

@ -15,6 +15,7 @@ class Route(RouteBase):
| enemy1 | Waypoint((46.2, 328.2)), | 12.6 | 8 | | enemy1 | Waypoint((46.2, 328.2)), | 12.6 | 8 |
| item2 | Waypoint((42.4, 299.0)), | 352.8 | 348 | | item2 | Waypoint((42.4, 299.0)), | 352.8 | 348 |
| door2 | Waypoint((46.4, 284.5)), | 4.2 | 1 | | door2 | Waypoint((46.4, 284.5)), | 4.2 | 1 |
| door2end | Waypoint((47.2, 274.8)), | 11.1 | 4 |
| enemy2left | Waypoint((31.2, 248.8)), | 183.8 | 84 | | enemy2left | Waypoint((31.2, 248.8)), | 183.8 | 84 |
| enemy2right | Waypoint((55.2, 247.2)), | 96.7 | 91 | | enemy2right | Waypoint((55.2, 247.2)), | 96.7 | 91 |
| item3 | Waypoint((68.5, 226.5)), | 30.2 | 29 | | item3 | Waypoint((68.5, 226.5)), | 30.2 | 29 |
@ -32,6 +33,7 @@ class Route(RouteBase):
enemy1 = Waypoint((46.2, 328.2)) enemy1 = Waypoint((46.2, 328.2))
item2 = Waypoint((42.4, 299.0)) item2 = Waypoint((42.4, 299.0))
door2 = Waypoint((46.4, 284.5)) door2 = Waypoint((46.4, 284.5))
door2end = Waypoint((47.2, 274.8))
enemy2left = Waypoint((31.2, 248.8)) enemy2left = Waypoint((31.2, 248.8))
enemy2right = Waypoint((55.2, 247.2)) enemy2right = Waypoint((55.2, 247.2))
item3 = Waypoint((68.5, 226.5)) item3 = Waypoint((68.5, 226.5))
@ -47,6 +49,7 @@ class Route(RouteBase):
# self.clear_item(item2) # self.clear_item(item2)
self.clear_enemy( self.clear_enemy(
door2.set_threshold(3), door2.set_threshold(3),
door2end.set_threshold(3),
# Go through door # Go through door
enemy2left, enemy2left,
enemy2right.straight_run(), enemy2right.straight_run(),
@ -58,6 +61,7 @@ class Route(RouteBase):
self.clear_enemy( self.clear_enemy(
enemy3.straight_run(), enemy3.straight_run(),
) )
# ('Combat_Herta_SupplyZone_F2_X45Y369', 0.243, (57.2, 351.6))
def Herta_SupplyZone_F2_X397Y233(self): def Herta_SupplyZone_F2_X397Y233(self):
""" """

View File

@ -63,7 +63,7 @@ class Route(RouteBase):
) )
# 2 # 2
self.clear_enemy( self.clear_enemy(
node2, node2.set_threshold(3),
enemy2.straight_run(), enemy2.straight_run(),
) )
# 3 # 3
@ -116,6 +116,43 @@ class Route(RouteBase):
self.clear_enemy(enemy2left.straight_run()) self.clear_enemy(enemy2left.straight_run())
self.clear_enemy(enemy3.straight_run()) self.clear_enemy(enemy3.straight_run())
def Jarilo_BackwaterPass_F1_X503Y736(self):
"""
| Waypoint | Position | Direction | Rotation |
| ---------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((507.2, 733.7)), | 6.7 | 4 |
| enemy1 | Waypoint((507.0, 644.0)), | 12.6 | 6 |
| enemy2left | Waypoint((536.0, 630.5)), | 48.1 | 43 |
| enemy3 | Waypoint((557.0, 585.2)), | 114.1 | 6 |
| exit_ | Waypoint((557.0, 585.2)), | 114.1 | 6 |
| exit1 | Waypoint((549.5, 575.4)), | 356.2 | 354 |
| exit2 | Waypoint((565.4, 575.6)), | 4.1 | 359 |
"""
self.map_init(plane=Jarilo_BackwaterPass, floor="F1", position=(503.2, 736.9))
self.register_domain_exit(
Waypoint((557.0, 585.2)), end_rotation=6,
left_door=Waypoint((549.5, 575.4)), right_door=Waypoint((565.4, 575.6)))
enemy1 = Waypoint((507.0, 644.0))
enemy2left = Waypoint((536.0, 630.5))
enemy3 = Waypoint((557.0, 585.2))
# ===== End of generated waypoints =====
self.clear_enemy(enemy1)
self.clear_enemy(enemy2left.straight_run())
self.clear_enemy(enemy3.straight_run())
"""
Notes
Herta_SupplyZone_F2_X397Y239 is the same as Herta_SupplyZone_F2_X397Y233
but for wrong spawn point detected
"""
# Best 3 predictions: [
# ('Combat_Jarilo_BackwaterPass_F1_X507Y733', 0.26, (503.2, 736.9)),
# ('Combat_Luofu_ArtisanshipCommission_F1_X41Y640', 0.18, (50.7, 644.4)),
# ('Combat_Luofu_DivinationCommission_F1_X737Y372', 0.174, (717.2, 355.4)),
# ('Combat_Herta_SupplyZone_F2_X45Y369', 0.168, (46.5, 370.0))
# ]
def Jarilo_BackwaterPass_F1_X555Y643(self): def Jarilo_BackwaterPass_F1_X555Y643(self):
""" """
| Waypoint | Position | Direction | Rotation | | Waypoint | Position | Direction | Rotation |

View File

@ -1,3 +1,4 @@
from module.logger import logger
from tasks.map.control.waypoint import Waypoint from tasks.map.control.waypoint import Waypoint
from tasks.map.keywords.plane import Jarilo_CorridorofFadingEchoes from tasks.map.keywords.plane import Jarilo_CorridorofFadingEchoes
from tasks.rogue.route.base import RouteBase from tasks.rogue.route.base import RouteBase
@ -30,6 +31,7 @@ class Route(RouteBase):
| ----------- | -------------------------- | --------- | -------- | | ----------- | -------------------------- | --------- | -------- |
| spawn | Waypoint((201.2, 1071.4)), | 6.7 | 4 | | spawn | Waypoint((201.2, 1071.4)), | 6.7 | 4 |
| enemy1right | Waypoint((200.3, 1032.4)), | 342.0 | 343 | | enemy1right | Waypoint((200.3, 1032.4)), | 342.0 | 343 |
| node1 | Waypoint((194.6, 1023.4)), | 109.3 | 294 |
| enemy1left | Waypoint((168.6, 1022.3)), | 279.8 | 89 | | enemy1left | Waypoint((168.6, 1022.3)), | 279.8 | 89 |
| node2 | Waypoint((118.4, 1019.0)), | 282.9 | 285 | | node2 | Waypoint((118.4, 1019.0)), | 282.9 | 285 |
| enemy2left | Waypoint((105.2, 1012.0)), | 317.9 | 315 | | enemy2left | Waypoint((105.2, 1012.0)), | 317.9 | 315 |
@ -45,6 +47,7 @@ class Route(RouteBase):
Waypoint((103.4, 919.2)), end_rotation=4, Waypoint((103.4, 919.2)), end_rotation=4,
left_door=Waypoint((98.8, 908.9)), right_door=Waypoint((111.4, 909.8))) left_door=Waypoint((98.8, 908.9)), right_door=Waypoint((111.4, 909.8)))
enemy1right = Waypoint((200.3, 1032.4)) enemy1right = Waypoint((200.3, 1032.4))
node1 = Waypoint((194.6, 1023.4))
enemy1left = Waypoint((168.6, 1022.3)) enemy1left = Waypoint((168.6, 1022.3))
node2 = Waypoint((118.4, 1019.0)) node2 = Waypoint((118.4, 1019.0))
enemy2left = Waypoint((105.2, 1012.0)) enemy2left = Waypoint((105.2, 1012.0))
@ -56,20 +59,22 @@ class Route(RouteBase):
# 1 # 1
self.rotation_set(315) self.rotation_set(315)
self.clear_enemy( self.clear_enemy(
enemy1right.set_threshold(5), enemy1right.set_threshold(3),
enemy1left.set_threshold(5), node1.set_threshold(3),
enemy1left.set_threshold(3),
) )
# 2 # 2
self.clear_enemy( self.clear_enemy(
enemy1left.set_threshold(5), enemy1left.set_threshold(3),
node2.set_threshold(5), node2.set_threshold(5),
enemy2left, enemy2left,
enemy2right, enemy2right,
) )
# 3 # 3
self.rotation_set(0)
self.clear_enemy( self.clear_enemy(
node3.set_threshold(5), node3.set_threshold(3),
enemy3.straight_run(), enemy3,
) )
def Jarilo_CorridorofFadingEchoes_F1_X266Y457(self): def Jarilo_CorridorofFadingEchoes_F1_X266Y457(self):
@ -167,12 +172,23 @@ class Route(RouteBase):
enemy2right.straight_run(), enemy2right.straight_run(),
enemy2left.straight_run().set_threshold(5), enemy2left.straight_run().set_threshold(5),
) )
if self.minimap.is_position_near(enemy2left.position, threshold=30):
logger.info('Near enemy2right')
self.clear_enemy( self.clear_enemy(
enemy2left.set_threshold(5), enemy2left.set_threshold(5),
node3.straight_run(), node3.straight_run(),
node4.set_threshold(3).straight_run(), node4.set_threshold(3).straight_run(),
enemy4.straight_run(), enemy4.straight_run(),
) )
else:
logger.info('Not near enemy2right')
self.clear_enemy(
enemy2right.set_threshold(5),
enemy2left.set_threshold(5),
node3.straight_run(),
node4.set_threshold(3).straight_run(),
enemy4.straight_run(),
)
def Jarilo_CorridorofFadingEchoes_F1_X437Y122(self): def Jarilo_CorridorofFadingEchoes_F1_X437Y122(self):
""" """

View File

@ -105,6 +105,51 @@ class Route(RouteBase):
if self.minimap.position_diff(enemy3.position) > 25: if self.minimap.position_diff(enemy3.position) > 25:
self.clear_enemy(enemy3) self.clear_enemy(enemy3)
def Luofu_ArtisanshipCommission_F1_X481Y920(self):
"""
| Waypoint | Position | Direction | Rotation |
| ----------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((473.5, 920.9)), | 4.5 | 4 |
| enemy1left | Waypoint((475.0, 848.4)), | 4.4 | 4 |
| enemy2right | Waypoint((493.5, 807.4)), | 157.1 | 48 |
| enemy3 | Waypoint((528.9, 782.9)), | 198.5 | 91 |
| exit_ | Waypoint((528.9, 782.9)), | 198.5 | 91 |
| exit1 | Waypoint((537.0, 773.2)), | 99.0 | 89 |
| exit2 | Waypoint((537.5, 790.6)), | 101.1 | 91 |
"""
self.map_init(plane=Luofu_ArtisanshipCommission, floor="F1", position=(481.5, 920.9))
self.register_domain_exit(
Waypoint((528.9, 782.9)), end_rotation=91,
left_door=Waypoint((537.0, 773.2)), right_door=Waypoint((537.5, 790.6)))
enemy1left = Waypoint((475.0, 848.4))
enemy2right = Waypoint((493.5, 807.4))
enemy3 = Waypoint((528.9, 782.9))
# ===== End of generated waypoints =====
self.rotation_set(30)
self.clear_enemy(
enemy1left,
enemy2right,
)
self.clear_enemy(enemy3)
if self.minimap.position_diff(enemy3.position) > 25:
self.clear_enemy(enemy3)
"""
Notes
Luofu_ArtisanshipCommission_F1_X481Y920 is the same as Luofu_ArtisanshipCommission_F1_X473Y920
but for wrong spawn point detected
"""
# Best 3 predictions: [
# ('Combat_Luofu_ArtisanshipCommission_F1_X473Y920', 0.168, (481.0, 920.9)),
# ('Combat_Luofu_ArtisanshipCommission_F1_X41Y640', 0.163, (40.8, 664.0)),
# ('Combat_Luofu_ArtisanshipCommission_F1_X667Y189', 0.14, (705.0, 193.3))
# ]
# Best 3 nearby predictions: [
# ('Combat_Herta_SupplyZone_F2_X45Y369', 0.128, (47.6, 369.4)),
# ('Combat_Herta_SupplyZone_F2Rogue_X219Y112', 0.093, (219.6, 108.7))
# ]
def Luofu_ArtisanshipCommission_F1_X543Y269(self): def Luofu_ArtisanshipCommission_F1_X543Y269(self):
""" """
| Waypoint | Position | Direction | Rotation | | Waypoint | Position | Direction | Rotation |

View File

@ -256,3 +256,58 @@ class Route(RouteBase):
) )
self.clear_item(item4) self.clear_item(item4)
self.clear_enemy(enemy4) self.clear_enemy(enemy4)
def Luofu_Cloudford_F1_X432Y685(self):
"""
| Waypoint | Position | Direction | Rotation |
| -------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((435.4, 669.2)), | 6.7 | 4 |
| item1 | Waypoint((432.2, 628.3)), | 2.7 | 357 |
| enemy1 | Waypoint((428.6, 598.8)), | 8.0 | 177 |
| node2 | Waypoint((421.2, 590.8)), | 44.2 | 285 |
| node3 | Waypoint((366.6, 588.2)), | 274.2 | 274 |
| enemy3 | Waypoint((344.9, 590.4)), | 191.8 | 357 |
| item4 | Waypoint((309.6, 580.2)), | 290.1 | 281 |
| enemy4 | Waypoint((271.3, 585.5)), | 285.0 | 274 |
| exit_ | Waypoint((271.3, 585.5)), | 285.0 | 274 |
| exit1 | Waypoint((267.9, 592.3)), | 275.9 | 274 |
| exit2 | Waypoint((267.8, 580.0)), | 275.8 | 274 |
"""
self.map_init(plane=Luofu_Cloudford, floor="F1", position=(432.8, 685.1))
self.register_domain_exit(
Waypoint((271.3, 585.5)), end_rotation=274,
left_door=Waypoint((267.9, 592.3)), right_door=Waypoint((267.8, 580.0)))
item1 = Waypoint((432.2, 628.3))
enemy1 = Waypoint((428.6, 598.8))
node2 = Waypoint((421.2, 590.8))
node3 = Waypoint((366.6, 588.2))
enemy3 = Waypoint((344.9, 590.4))
item4 = Waypoint((309.6, 580.2))
enemy4 = Waypoint((271.3, 585.5))
# ===== End of generated waypoints =====
self.clear_item(item1)
self.clear_enemy(enemy1)
# Go through bridges
self.rotation_set(270)
self.minimap.lock_rotation(270)
self.clear_enemy(
node2.set_threshold(3),
node3.set_threshold(3),
enemy3,
)
self.clear_item(item4)
self.clear_enemy(enemy4)
"""
Notes
Luofu_Cloudford_F1_X435Y685 is the same as Luofu_Cloudford_F1_X435Y669
but for wrong spawn point detected
"""
# Best 3 predictions: [
# ('Combat_Luofu_Cloudford_F1_X433Y617', 0.195, (432.8, 668.4)),
# ('Combat_Herta_SupplyZone_F2_X45Y369', 0.18, (24.2, 372.2)),
# ('Combat_Luofu_Cloudford_F1_X435Y669', 0.18, (432.8, 685.1))
# ]
# (432.9, 684.9)
# ('Combat_Luofu_Cloudford_F1_X435Y669', 0.172, (432.8, 685.0))

View File

@ -105,3 +105,62 @@ class Route(RouteBase):
Luofu_Cloudford_F1Rogue_X49Y405 is the same as Luofu_Cloudford_F1Rogue_X59Y405 Luofu_Cloudford_F1Rogue_X49Y405 is the same as Luofu_Cloudford_F1Rogue_X59Y405
but for wrong spawn point detected but for wrong spawn point detected
""" """
def Luofu_Cloudford_F1Rogue_X44Y405(self):
"""
| Waypoint | Position | Direction | Rotation |
| ------------ | ------------------------- | --------- | -------- |
| spawn | Waypoint((59.3, 405.6)), | 96.7 | 91 |
| item1 | Waypoint((96.9, 393.0)), | 87.7 | 84 |
| enemy1 | Waypoint((126.2, 402.5)), | 96.8 | 101 |
| node2 | Waypoint((142.9, 413.0)), | 96.8 | 101 |
| enemy2top | Waypoint((214.6, 432.8)), | 94.1 | 87 |
| enemy2bottom | Waypoint((211.4, 483.3)), | 191.8 | 174 |
| enemy3 | Waypoint((288.0, 452.2)), | 87.7 | 260 |
| exit_ | Waypoint((291.8, 454.4)), | 5.7 | 91 |
| exit1 | Waypoint((295.0, 451.4)), | 96.7 | 89 |
| exit2 | Waypoint((296.0, 460.2)), | 96.9 | 89 |
"""
self.map_init(plane=Luofu_Cloudford, floor="F1Rogue", position=(43.8, 405.0))
self.register_domain_exit(
Waypoint((291.8, 454.4)), end_rotation=91,
left_door=Waypoint((295.0, 451.4)), right_door=Waypoint((296.0, 460.2)))
item1 = Waypoint((96.9, 393.0))
enemy1 = Waypoint((126.2, 402.5))
node2 = Waypoint((142.9, 413.0))
enemy2top = Waypoint((214.6, 432.8))
enemy2bottom = Waypoint((211.4, 483.3))
enemy3 = Waypoint((288.0, 452.2))
# ===== End of generated waypoints =====
self.rotation_set(120)
self.minimap.lock_rotation(120)
# 1, ignore item1, which position may cause detection error
# self.clear_item(item1)
self.clear_enemy(enemy1)
# 2 moving enemy
# Ignore enemy2, it might be a pig, you can never catch it.
# self.clear_enemy(
# enemy2top,
# enemy2bottom.straight_run(),
# )
# 3
self.clear_enemy(
node2.set_threshold(3),
enemy3,
)
if self.minimap.position_diff(enemy3.position) > 60:
logger.info('Cleared an enemy but have not reached enemy3')
self.clear_enemy(enemy3)
"""
Notes
Luofu_Cloudford_F1Rogue_X44Y405 is the same as Luofu_Cloudford_F1Rogue_X59Y405
but for wrong spawn point detected
"""
# Best 3 predictions: [
# ('Combat_Luofu_Cloudford_F1Rogue_X59Y405', 0.349, (43.8, 405.0)),
# ('Combat_Luofu_Cloudford_F1Rogue_X49Y405', 0.349, (43.8, 405.0)),
# ('Combat_Herta_SupplyZone_F2_X594Y247', 0.262, (641.3, 250.5))
# ]
# Best 3 nearby predictions: [('Combat_Jarilo_RivetTown_F1_X181Y439', 0.109, (178.5, 435.4))]

View File

@ -70,6 +70,7 @@ class Route(RouteBase):
# 1, enemy first # 1, enemy first
self.clear_enemy(enemy1) self.clear_enemy(enemy1)
self.clear_item(item1) # item1 is cleared on the way to enemy2, or will get stuck at corner
# self.clear_item(item1)
# 2, ignore item2, bad way # 2, ignore item2, bad way
self.clear_enemy(enemy2) self.clear_enemy(enemy2)

View File

@ -30,6 +30,36 @@ class Route(RouteBase):
but for wrong spawn point detected but for wrong spawn point detected
""" """
def Jarilo_CorridorofFadingEchoes_F1_X415Y953(self):
"""
| Waypoint | Position | Direction | Rotation |
| -------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((415.5, 947.9)), | 96.7 | 91 |
| enemy | Waypoint((464.0, 953.0)), | 96.8 | 94 |
| reward | Waypoint((472.7, 958.5)), | 214.6 | 114 |
| exit_ | Waypoint((480.0, 944.0)), | 92.7 | 84 |
"""
self.map_init(plane=Jarilo_CorridorofFadingEchoes, floor="F1", position=(415.4, 953.3))
enemy = Waypoint((464.0, 953.0))
reward = Waypoint((472.7, 958.5))
exit_ = Waypoint((480.0, 944.0))
self.clear_elite(enemy)
self.domain_reward(reward)
self.domain_single_exit(exit_)
# ===== End of generated waypoints =====
"""
Notes
Jarilo_CorridorofFadingEchoes_F1_X415Y953 is the same as Jarilo_CorridorofFadingEchoes_F1_X415Y947
but for wrong spawn point detected
"""
# Best 3 predictions: [
# ('Elite_Jarilo_CorridorofFadingEchoes_F1_X415Y933', 0.169, (415.4, 953.3)),
# ('Elite_Jarilo_CorridorofFadingEchoes_F1_X415Y947', 0.169, (415.4, 953.3)),
# ('Elite_Herta_SupplyZone_F2_X680Y247', 0.162, (738.4, 252.2))
# ]
def Jarilo_CorridorofFadingEchoes_F1_X415Y947(self): def Jarilo_CorridorofFadingEchoes_F1_X415Y947(self):
""" """
| Waypoint | Position | Direction | Rotation | | Waypoint | Position | Direction | Rotation |

View File

@ -24,6 +24,36 @@ class Route(RouteBase):
self.domain_single_exit(exit_) self.domain_single_exit(exit_)
# ===== End of generated waypoints ===== # ===== End of generated waypoints =====
def Luofu_ArtisanshipCommission_F1_X391Y493(self):
"""
| Waypoint | Position | Direction | Rotation |
| -------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((385.2, 494.6)), | 94.2 | 91 |
| enemy | Waypoint((444.2, 490.5)), | 94.2 | 91 |
| reward | Waypoint((448.6, 497.2)), | 149.7 | 91 |
| exit_ | Waypoint((458.0, 483.7)), | 94.2 | 91 |
"""
self.map_init(plane=Luofu_ArtisanshipCommission, floor="F1", position=(391.1, 493.2))
enemy = Waypoint((444.2, 490.5))
reward = Waypoint((448.6, 497.2))
exit_ = Waypoint((458.0, 483.7))
self.clear_elite(enemy)
self.domain_reward(reward)
self.domain_single_exit(exit_)
# ===== End of generated waypoints =====
"""
Notes
Luofu_ArtisanshipCommission_F1_X391Y493 is the same as Luofu_ArtisanshipCommission_F1_X385Y494
but for wrong spawn point detected
"""
# Best 3 predictions: [
# ('Elite_Luofu_ArtisanshipCommission_F1_X385Y494', 0.182, (391.1, 493.2)),
# ('Elite_Jarilo_CorridorofFadingEchoes_F1_X415Y933', 0.157, (364.0, 951.0)),
# ('Elite_Jarilo_CorridorofFadingEchoes_F1_X415Y947', 0.157, (364.0, 951.0))
# ]
def Luofu_ArtisanshipCommission_F1_X504Y493(self): def Luofu_ArtisanshipCommission_F1_X504Y493(self):
""" """
| Waypoint | Position | Direction | Rotation | | Waypoint | Position | Direction | Rotation |

View File

@ -25,3 +25,34 @@ class Route(RouteBase):
self.domain_reward(reward) self.domain_reward(reward)
self.domain_single_exit(exit_) self.domain_single_exit(exit_)
# ===== End of generated waypoints ===== # ===== End of generated waypoints =====
@locked_rotation(0)
def Luofu_Cloudford_F1_X342Y1003(self):
"""
| Waypoint | Position | Direction | Rotation |
| -------- | -------------------------- | --------- | -------- |
| spawn | Waypoint((337.3, 1003.4)), | 6.7 | 4 |
| enemy | Waypoint((336.2, 962.2)), | 6.7 | 4 |
| reward | Waypoint((342.9, 950.8)), | 44.2 | 31 |
| exit_ | Waypoint((328.8, 942.8)), | 316.1 | 331 |
"""
self.map_init(plane=Luofu_Cloudford, floor="F1", position=(342.3, 1003.4))
enemy = Waypoint((336.2, 962.2))
reward = Waypoint((342.9, 950.8))
exit_ = Waypoint((328.8, 942.8))
self.clear_elite(enemy)
self.domain_reward(reward)
self.domain_single_exit(exit_)
# ===== End of generated waypoints =====
"""
Notes
Luofu_Cloudford_F1_X342Y1003 is the same as Luofu_Cloudford_F1_X337Y1003
but for wrong spawn point detected
"""
# Best 3 predictions: [
# ('Elite_Luofu_Cloudford_F1_X337Y1003', 0.169, (342.3, 1002.7)),
# ('Elite_Luofu_ArtisanshipCommission_F1_X504Y493', 0.106, (519.3, 452.7)),
# ('Elite_Jarilo_CorridorofFadingEchoes_F1_X415Y933', 0.104, (433.8, 982.0))
# ]

View File

@ -25,3 +25,34 @@ class Route(RouteBase):
self.domain_reward(reward) self.domain_reward(reward)
self.domain_single_exit(exit_) self.domain_single_exit(exit_)
# ===== End of generated waypoints ===== # ===== End of generated waypoints =====
@locked_rotation(90)
def Luofu_StargazerNavalia_F1_X617Y511(self):
"""
| Waypoint | Position | Direction | Rotation |
| -------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((617.5, 511.5)), | 96.7 | 91 |
| enemy | Waypoint((664.6, 512.6)), | 96.8 | 94 |
| reward | Waypoint((677.1, 521.2)), | 212.8 | 108 |
| exit_ | Waypoint((684.6, 505.0)), | 91.3 | 82 |
"""
self.map_init(plane=Luofu_StargazerNavalia, floor="F1", position=(617.5, 511.5))
enemy = Waypoint((664.6, 512.6))
reward = Waypoint((677.1, 521.2))
exit_ = Waypoint((684.6, 505.0))
self.clear_elite(enemy)
self.domain_reward(reward)
self.domain_single_exit(exit_)
# ===== End of generated waypoints =====
"""
Notes
Herta_SupplyZone_F2_X397Y239 is the same as Herta_SupplyZone_F2_X397Y233
but for wrong spawn point detected
"""
# Best 3 predictions: [
# ('Elite_Luofu_StargazerNavalia_F1_X617Y511', 0.338, (621.0, 507.0)),
# ('Elite_Luofu_ArtisanshipCommission_F1_X385Y494', 0.203, (329.2, 492.8)),
# ('Elite_Jarilo_SilvermaneGuardRestrictedZone_F1_X225Y425', 0.181, (224.8, 423.2))
# ]

View File

@ -67,3 +67,35 @@ class Route(RouteBase):
self.clear_item(item) self.clear_item(item)
self.clear_event(event) self.clear_event(event)
# ===== End of generated waypoints ===== # ===== End of generated waypoints =====
def Jarilo_BackwaterPass_F1_X611Y761(self):
"""
| Waypoint | Position | Direction | Rotation |
| -------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((613.3, 755.7)), | 319.8 | 318 |
| item | Waypoint((603.0, 734.6)), | 342.6 | 343 |
| event | Waypoint((586.8, 724.7)), | 318.0 | 315 |
| exit_ | Waypoint((576.9, 728.6)), | 126.2 | 304 |
| exit1 | Waypoint((567.0, 732.7)), | 311.8 | 306 |
| exit2 | Waypoint((576.2, 722.0)), | 308.1 | 306 |
"""
self.map_init(plane=Jarilo_BackwaterPass, floor="F1", position=(611.4, 761.2))
self.register_domain_exit(
Waypoint((576.9, 728.6)), end_rotation=304,
left_door=Waypoint((567.0, 732.7)), right_door=Waypoint((576.2, 722.0)))
item = Waypoint((603.0, 734.6))
event = Waypoint((586.8, 724.7))
self.clear_item(item)
self.clear_event(event)
# ===== End of generated waypoints =====
# Best 3 predictions: [
# ('Occurrence_Jarilo_BackwaterPass_F1_X613Y755', 0.203, (611.4, 761.2)),
# ('Occurrence_Herta_SupplyZone_F2Rogue_X397Y223', 0.148, (381.4, 207.5)),
# ('Occurrence_Herta_SupplyZone_F2Rogue_X397Y227', 0.148, (381.4, 207.5))
# ]
"""
Notes
Jarilo_BackwaterPass_F1_X611Y761 is the same as Jarilo_BackwaterPass_F1_X613Y755
but for wrong spawn point detected
"""

View File

@ -49,6 +49,38 @@ class Route(RouteBase):
self.clear_event(event) self.clear_event(event)
# ===== End of generated waypoints ===== # ===== End of generated waypoints =====
def Jarilo_SilvermaneGuardRestrictedZone_F1_X435Y233(self):
"""
| Waypoint | Position | Direction | Rotation |
| -------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((439.3, 237.1)), | 354.1 | 348 |
| item | Waypoint((440.8, 215.2)), | 15.6 | 11 |
| event | Waypoint((434.8, 192.4)), | 355.9 | 359 |
| exit_ | Waypoint((428.6, 190.4)), | 76.4 | 338 |
| exit1 | Waypoint((416.8, 184.4)), | 337.5 | 334 |
| exit2 | Waypoint((428.8, 180.4)), | 339.1 | 336 |
"""
self.map_init(plane=Jarilo_SilvermaneGuardRestrictedZone, floor="F1", position=(435.5, 233.5))
self.register_domain_exit(
Waypoint((428.6, 190.4)), end_rotation=338,
left_door=Waypoint((416.8, 184.4)), right_door=Waypoint((428.8, 180.4)))
item = Waypoint((440.8, 215.2))
event = Waypoint((434.8, 192.4))
self.clear_item(item)
self.clear_event(event)
# ===== End of generated waypoints =====
# Best 3 predictions: [
# ('Occurrence_Jarilo_SilvermaneGuardRestrictedZone_F1_X439Y237', 0.194, (435.5, 233.5)),
# ('Occurrence_Luofu_ScalegorgeWaterscape_F1_X619Y387', 0.118, (593.5, 412.0)),
# ('Occurrence_Luofu_Cloudford_F1_X244Y951', 0.098, (193.8, 931.7))
# ]
"""
Notes
Jarilo_SilvermaneGuardRestrictedZone_F1_X435Y233 is the same as Jarilo_SilvermaneGuardRestrictedZone_F1_X439Y237
but for wrong spawn point detected
"""
def Jarilo_SilvermaneGuardRestrictedZone_F1_X509Y541(self): def Jarilo_SilvermaneGuardRestrictedZone_F1_X509Y541(self):
""" """
| Waypoint | Position | Direction | Rotation | | Waypoint | Position | Direction | Rotation |

View File

@ -27,6 +27,32 @@ class Route(RouteBase):
self.clear_event(event) self.clear_event(event)
# ===== End of generated waypoints ===== # ===== End of generated waypoints =====
@locked_rotation(270)
def Luofu_Cloudford_F1_X244Y951(self):
"""
| Waypoint | Position | Direction | Rotation |
| -------- | ------------------------- | --------- | -------- |
| spawn | Waypoint((241.4, 947.5)), | 274.2 | 274 |
| event | Waypoint((199.0, 940.8)), | 300.1 | 294 |
| exit_ | Waypoint((193.1, 947.2)), | 12.8 | 274 |
| exit1 | Waypoint((179.0, 956.4)), | 279.8 | 278 |
| exit2 | Waypoint((184.1, 940.2)), | 282.9 | 278 |
"""
self.map_init(plane=Luofu_Cloudford, floor="F1", position=(244, 951))
self.register_domain_exit(
Waypoint((193.1, 947.2)), end_rotation=274,
left_door=Waypoint((179.0, 956.4)), right_door=Waypoint((184.1, 940.2)))
event = Waypoint((199.0, 940.8))
self.clear_event(event)
# ===== End of generated waypoints =====
"""
Notes
Luofu_Cloudford_F1_X244Y951 is the same as Luofu_Cloudford_F1_X241Y947
but for wrong spawn point detected
"""
@locked_position @locked_position
@locked_rotation(0) @locked_rotation(0)
def Luofu_Cloudford_F1_X281Y873(self): def Luofu_Cloudford_F1_X281Y873(self):

View File

@ -28,3 +28,8 @@ class Route(RouteBase):
self.clear_item(item_X504Y610) self.clear_item(item_X504Y610)
self.clear_event(event_X510Y626) self.clear_event(event_X510Y626)
# ===== End of generated waypoints ===== # ===== End of generated waypoints =====
def clear_event(self, *waypoints):
# Too many clicks on A_BUTTON, so no items enroute in Luofu_StargazerNavalia_F1_X521Y595
self.enroute_add_item = False
return super().clear_event(*waypoints)

Some files were not shown because too many files have changed in this diff Show More