Merge branch 'LmeSzinc:master' into master
BIN
assets/character/Aventurine.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
assets/character/Robin.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.1 KiB |
@ -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;
|
||||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.6 KiB |
BIN
assets/share/rogue/path/PATH_LOADED_CHECK.png
Normal file
After Width: | Height: | Size: 13 KiB |
@ -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",
|
||||||
|
@ -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:
|
||||||
|
@ -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()()
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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": {
|
||||||
|
@ -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:
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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]:
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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": {
|
||||||
|
@ -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": {
|
||||||
|
@ -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": {
|
||||||
|
@ -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": {
|
||||||
|
@ -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": {
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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):
|
||||||
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
@ -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))
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
@ -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")
|
||||||
|
541
module/device/method/nemu_ipc.py
Normal 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)
|
@ -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')
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
109
module/device/pkg_resources/__init__.py
Normal 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
|
@ -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]:
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
|
||||||
|
@ -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)
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
from module.logger import logger
|
|
||||||
|
|
||||||
def handle_notify(*args, **kwargs):
|
|
||||||
logger.error('Error notify is not supported yet')
|
|
4
module/notify/__init__.py
Normal 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
@ -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
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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}",
|
||||||
[
|
[
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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 |
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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 |
|
||||||
|
@ -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))
|
||||||
|
@ -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))]
|
||||||
|
@ -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)
|
||||||
|
@ -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 |
|
||||||
|
@ -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 |
|
||||||
|
@ -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))
|
||||||
|
# ]
|
||||||
|
@ -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))
|
||||||
|
# ]
|
||||||
|
@ -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
|
||||||
|
"""
|
||||||
|
@ -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 |
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|