Skip to content

Commit

Permalink
Merge pull request #767 from Starry-Wind/main-beta
Browse files Browse the repository at this point in the history
Main beta
  • Loading branch information
Night-stars-1 authored Nov 20, 2023
2 parents 08402d5 + 9cf5540 commit 76119b9
Show file tree
Hide file tree
Showing 9 changed files with 1,686 additions and 314 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ LICENSE
picture_list.json
version.json
*relics*.json
char_panel.json
char_weight.json
screencast.png
screencast1.png
utils/A_.py
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,20 @@ This software is open source, free of charge and for learning and exchange purpo
- [ ] 模拟宇宙正在开发
- [x] GUI开发
- [ ] 后续将会新增找宝箱、锄大地顺带捡垃圾等功能
- [x] 遗器模块 (不支持GUI)
- [x] 遗器的识别、匹配、搜索
- [x] 角色配装的保存、编辑、读取并装备
- [x] 队伍配装的保存、读取并装备
- [x] 支持创建、编辑、载入角色裸装面板与属性权重
- [x] 支持对相关文本的风格化打印
- [ ] 对遗器与角色配装的评估、推荐

[项目进度请点击查看](https://github.com/users/Night-stars-1/projects/2)

## 遗器模块展示
<img src="https://github.com/weiduhuo/StarRailAssistant/blob/picture/relic1.gif" />
<img src="https://github.com/weiduhuo/StarRailAssistant/blob/picture/relic2.gif" />

## 贡献

[问题反馈](https://github.com/Starry-Wind/StarRailAssistant/issues/new/choose) | [PR 提交](https://github.com/Starry-Wind/StarRailAssistant/compare)
Expand Down
3 changes: 3 additions & 0 deletions data/fixed_data/char_weight_default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"说明": "此文件为预留文件,待后续开发 (内容物为角色的默认属性权重)"
}
148 changes: 142 additions & 6 deletions utils/calculated.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import re
import sys
import time
import copy
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
Expand All @@ -22,6 +23,8 @@
from pynput.keyboard import Controller as KeyboardController
from pynput.keyboard import Key
from pynput.mouse import Controller as MouseController
from questionary import Validator, ValidationError, Style
from collections.abc import Iterable

from .config import CONFIG_FILE_NAME, _, get_file, sra_config_obj
from .cv_tools import CV_Tools, show_img
Expand Down Expand Up @@ -699,6 +702,7 @@ def part_ocr(self, points = (0,0,0,0), debug=False, left=False, number=False, im
if debug:
log.info(data)
# show_img(img_fp)
os.makedirs("logs/image", exist_ok=True)
timestamp_str = str(int(datetime.timestamp(datetime.now())))
cv.imwrite(f"logs/image/relic_{str(points)}_{timestamp_str}.png", img_fp)
else:
Expand Down Expand Up @@ -993,16 +997,19 @@ def change_team(self):

class Array2dict:

def __init__(self, arr: np.ndarray, key_index: int = -1, value_index: Optional[int]=None):
def __init__(self, arr: Union[np.ndarray, List[str]], key_index: int = -1, value_index: Optional[int]=None):
"""
说明:
将np数组转化为字典暂住内存,用于对数组短时间内的频繁查找
将np数组或序列转化为字典暂住内存,用于短时间内的频繁查找
参数:
:param arr: 二维数组
:param arr: 二维数组或一维序列
:param key_index: 待查找的关键字所在的数组列标
:param value_index: 待查找的数值所在的数组列标 (为空时表示查找关键字的行标)
"""
if arr.ndim != 2:
if isinstance(arr, list):
self.data_dict = {element: idx for idx, element in enumerate(arr)}
return
if isinstance(arr, np.ndarray) and arr.ndim != 2:
raise ValueError("输入的数组必须为二维数组")
# 将np数组转化为字典
if value_index is None: # 默认将key的行标作为value,以取代np.where
Expand All @@ -1012,8 +1019,137 @@ def __init__(self, arr: np.ndarray, key_index: int = -1, value_index: Optional[i
# log.debug(self.data_dict)

def __getitem__(self, key: Any) -> Any:
return self.data_dict[key]
return self.data_dict.get(key, None)


class FloatValidator(Validator):
"""
说明:
为questionary校验所输入的文本是否为规定范围内的小数
"""
def __init__(self, st: Optional[float]=None, ed: Optional[float]=None) -> None:
super().__init__()
self.st = st
self.ed = ed

def validate(self, document):
try:
number = float(document.text)
except ValueError:
raise ValidationError(message=_("请输入整数或小数"))
if self.st is not None and number < self.st:
raise ValidationError(message=_("数字小于下界{}").format(self.st))
if self.ed is not None and number > self.ed:
raise ValidationError(message=_("数字大于下界{}").format(self.ed))


class ConflictValidator(Validator):
"""
说明:
为questionary校验所输入的文本是否存在命名冲突
"""
def __init__(self, names: Iterable[str]) -> None:
super().__init__()
self.names = names

def validate(self, document):
if document.text in self.names:
raise ValidationError(message=_("存在命名冲突"), cursor_position=len(document.text))


class StyledText(List[Tuple[str, str]]):
"""
说明:
风格化文本序列,继承 List[Tuple[str, str]],(0-style_class, 1-text),
重载了`append()`和`extend()`方法,支持链式操作。
可直接作为`questionary.Choice.description`的初始化参数
"""
def __getitem__(self, key: slice) -> List["StyledText"]:
return StyledText(super().__getitem__(key))

def append(self, text: Union[str, Tuple[str, str]], style_class: str="") -> "StyledText":
if isinstance(text, str):
if style_class and "class:" not in style_class:
style_class = "class:" + style_class
super().append((style_class, text))
else:
super().append(text)
return self

def extend(self, *texts: Iterable[Tuple[str, str]], sep: Optional[Union[str, Tuple[str, str]]]=None, indent: int=0) -> "StyledText":
"""
说明:
继承`list.extend()`
参数:
:param texts: 任意个`StyledText`类型的文本序列
:param sep: 插入在文本序列间的内容,默认为空
:param indent: 起始位置的缩进长度,默认为零
"""
if indent:
self.append(" "*indent)
for i, __iterable in enumerate(texts):
if i and sep:
self.append(sep)
super().extend(__iterable)
return self

def splitlines(self, keepends=False) -> List["StyledText"]:
"""
说明:
按行分割为`StyledText`序列
"""
lines_list = [StyledText()]
def end_with_n(s :str) -> bool:
return s[-1] == "\n"
for style, text in self:
if text == "":
continue
lines = str(text).splitlines(keepends=True)
lines_list[-1].append(
(style, lines[0][:-1] if end_with_n(lines[0]) and not keepends else lines[0])
)
for line in lines[1:]:
lines_list.append(
StyledText([
(style, line[:-1] if end_with_n(line) and not keepends else line)
]))
if end_with_n(lines[-1]): # 开启空的一行
lines_list.append(StyledText())
if not lines_list[-1]: # 删除无效行
lines_list.pop()
return lines_list

def combine_styled_text(*texts: StyledText, prefix: Optional[Union[str, Tuple[str, str]]]=None, **kwargs) -> StyledText:
"""
说明:
将多个风格化文本序列,按行进行横向并联
参数:
:param sep: 插入在文本序列间的内容,默认为空
:param prefix: 文本前缀
:param indent: 起始位置的缩进长度,默认为零
"""
result = StyledText()
lines_list_list: List[List[StyledText]] = []
if prefix:
result.append(prefix)
for text in texts:
lines_list_list.append(text.splitlines())
for line in zip(*lines_list_list):
result.extend(*line, **kwargs)
result.append("\n")
return result

def print_styled_text(text: StyledText, style: Style, **kwargs: Any) -> None:
"""
说明:
打印风格化文本
"""
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import FormattedText

print_formatted_text(FormattedText(text), style=style, **kwargs)


def get_data_hash(data: Any, key_filter: Optional[List[str]]=None, speed_modified=False) -> str:
"""
说明:
Expand All @@ -1027,7 +1163,7 @@ def get_data_hash(data: Any, key_filter: Optional[List[str]]=None, speed_modifie
if not key_filter:
tmp_data = data
elif isinstance(data, dict):
tmp_data = {key: value for key, value in data.items() if key not in key_filter}
tmp_data = copy.deepcopy({key: value for key, value in data.items() if key not in key_filter}) # 深拷贝
if speed_modified and _("速度") in tmp_data["subs_stats"]:
tmp_data["subs_stats"][_("速度")] = float(int(tmp_data["subs_stats"][_("速度")])) # 去除小数部分
else:
Expand Down
37 changes: 28 additions & 9 deletions utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,43 @@
from .log import log

CONFIG_FILE_NAME = "config.json"
RELIC_FILE_NAME = "relics_set.json"
LOADOUT_FILE_NAME = "relics_loadout.json"
TEAM_FILE_NAME = "relics_team.json"

USER_DATA_PREFIX = "data/user_data/"
FIXED_DATA_PREFIX = "data/fixed_data/"
os.makedirs(USER_DATA_PREFIX, exist_ok=True)

RELIC_FILE_NAME = USER_DATA_PREFIX + "relics_set.json"
LOADOUT_FILE_NAME = USER_DATA_PREFIX + "relics_loadout.json"
TEAM_FILE_NAME = USER_DATA_PREFIX + "relics_team.json"
CHAR_PANEL_FILE_NAME = USER_DATA_PREFIX + "char_panel.json"
CHAR_WEIGHT_FILE_NAME = USER_DATA_PREFIX + "char_weight.json"


def normalize_file_path(filename):
# 尝试在当前目录下读取文件
current_dir = os.getcwd()
file_path = os.path.join(current_dir, filename)
if os.path.exists(file_path):
return file_path
pre_file_path = os.path.join(current_dir, filename)
if os.path.exists(pre_file_path):
return pre_file_path
else:
# 如果当前目录下没有该文件,则尝试在上一级目录中查找
parent_dir = os.path.dirname(current_dir)
file_path = os.path.join(parent_dir, filename)
if os.path.exists(file_path):
return file_path
else:
# 如果上一级目录中也没有该文件,则返回None
return None
# 如果仍然没有,则尝试在当前目录仅查找文件名
pre_filename = str(filename).rsplit('/', 1)[-1]
file_path = os.path.join(current_dir, pre_filename)
if os.path.exists(file_path):
if str(filename).rsplit('/', 1)[0] == USER_DATA_PREFIX[:-1]:
# 判断为旧版本 (<=1.8.7) 数据文件位置
import shutil
shutil.move(file_path, pre_file_path)
log.info(_("文件位置更改,由'{}'迁移至'{}'").format(pre_filename, filename))
return pre_file_path
return file_path
# 如果仍然没有,则返回None
return None


def read_json_file(filename: str, path=False, schema:dict=None) -> dict:
Expand Down Expand Up @@ -328,6 +345,8 @@ class SRAData(metaclass=SRADataMeta):
"""是否在打印遗器信息时显示拓展信息"""
ndigits_for_relic: int = 2
"""在打印遗器信息时的小数精度"""
stats_weight_for_relic: int = 0
"""遗器副词条档位权重:0-空,1-主流赋值,2-真实比例赋值,3-主流赋值比例矫正"""
auto_shutdown: bool = False
"""是否自动关机"""

Expand Down
10 changes: 7 additions & 3 deletions utils/questionary/questionary/prompts/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class Choice:
shortcut_key: Optional[str]
"""A shortcut key for the choice"""

description: Optional[str]
description: Optional[FormattedText]
"""Choice description"""

def __init__(
Expand All @@ -82,7 +82,7 @@ def __init__(
disabled: Optional[str] = None,
checked: Optional[bool] = False,
shortcut_key: Optional[Union[str, bool]] = True,
description: Optional[str] = None,
description: Optional[FormattedText] = None,
) -> None:
self.disabled = disabled
self.title = title
Expand Down Expand Up @@ -445,7 +445,11 @@ def append(index: int, choice: Choice):
description = current.description

if description is not None:
tokens.append(("class:text", " Description: {}".format(description)))
tokens.append(("class:text", " Description: "))
if isinstance(description, list):
tokens.extend(description)
else:
tokens.append(("class:text", description))
else:
tokens.pop() # Remove last newline.
return tokens
Expand Down
22 changes: 13 additions & 9 deletions utils/questionary/questionary/prompts/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def select(
use_shortcuts: bool = False,
use_arrow_keys: bool = True,
use_indicator: bool = False,
use_jk_keys: bool = True,
use_jk_keys: bool = False,
use_emacs_keys: bool = True,
show_selected: bool = False,
show_description: bool = True,
Expand Down Expand Up @@ -135,14 +135,18 @@ def select(
if choices is None or len(choices) == 0:
raise ValueError("A list of choices needs to be provided.")

if use_shortcuts and len(choices) > len(InquirerControl.SHORTCUT_KEYS):
raise ValueError(
"A list with shortcuts supports a maximum of {} "
"choices as this is the maximum number "
"of keyboard shortcuts that are available. You"
"provided {} choices!"
"".format(len(InquirerControl.SHORTCUT_KEYS), len(choices))
if use_shortcuts:
real_len_of_choices = sum(
1 for c in choices if not isinstance(c, Separator)
)
if real_len_of_choices > len(InquirerControl.SHORTCUT_KEYS):
raise ValueError(
"A list with shortcuts supports a maximum of {} "
"choices as this is the maximum number "
"of keyboard shortcuts that are available. You "
"provided {} choices!"
"".format(len(InquirerControl.SHORTCUT_KEYS), real_len_of_choices)
)

merged_style = merge_styles_default([style])

Expand Down Expand Up @@ -204,7 +208,7 @@ def _(event):
"for movement are disabled. "
"This choice is not reachable.".format(c.title)
)
if isinstance(c, Separator) or c.shortcut_key is None:
if isinstance(c, Separator) or c.shortcut_key is None or c.disabled:
continue

# noinspection PyShadowingNames
Expand Down
Loading

0 comments on commit 76119b9

Please sign in to comment.