Compare commits
140 Commits
Author | SHA1 | Date | |
---|---|---|---|
42e8693463 | |||
92e032077c | |||
00b1ebd71e | |||
de71e83be8 | |||
a398fcc0f3 | |||
11840e0f75 | |||
77a597cfca | |||
b128fd38b8 | |||
447b24dde9 | |||
72c36be0a9 | |||
2c07c490bb | |||
914ff0e83b | |||
a9eacfbe0c | |||
91f0ee82b9 | |||
8727fb0bc1 | |||
6cb7bd7ab8 | |||
efa924c1a8 | |||
01b9d48b38 | |||
b84764d907 | |||
236fbebd3b | |||
a9600d85b7 | |||
b087dd7f24 | |||
083f55d852 | |||
5aec15e9f4 | |||
d952e1bf78 | |||
2e4120bf89 | |||
a325bfe4f2 | |||
f9e84e5fdf | |||
c8588a2848 | |||
79d1e86200 | |||
40217dba09 | |||
2cb0ccd452 | |||
52b1c06125 | |||
1f54160e2b | |||
15fc075878 | |||
2a03b056da | |||
6bf9cef4f4 | |||
c3562ae49e | |||
de312d2314 | |||
db96d23876 | |||
ecb286b33f | |||
a93787bfea | |||
f93eb116f1 | |||
3265e2cf0d | |||
2c4c3589fc | |||
77ea5118a2 | |||
a7e0477f72 | |||
fe59404ee9 | |||
402184496f | |||
6a3504026b | |||
59184adea7 | |||
83fd57ec54 | |||
e6979b75be | |||
7878f0e278 | |||
e318984801 | |||
d62ab6e341 | |||
d5a379d0ef | |||
02375b1284 | |||
2aaf5aa1c6 | |||
54701a448b | |||
d74959b751 | |||
d087e75e4e | |||
79a4faf5c4 | |||
38f08262d4 | |||
13c5fe2eb0 | |||
92844b1897 | |||
2984f4e578 | |||
8407d556ee | |||
d68cbc24fa | |||
8032841408 | |||
9eaf184f19 | |||
a215bc16cb | |||
ed412e890c | |||
99ea01d9e4 | |||
ad385bd77c | |||
18fd5e2cd0 | |||
cd2303d37d | |||
6ba82d4d1b | |||
96227fe4be | |||
fa35be809f | |||
9f7db4e5b2 | |||
5c6008381b | |||
461911e186 | |||
ef1d7eb997 | |||
8f2d3ae8e5 | |||
21c40bcfe5 | |||
fbb1cec548 | |||
0371014fd6 | |||
d367197838 | |||
4cfd3537f0 | |||
a221356da4 | |||
5bc15948fe | |||
a8040c7321 | |||
5cb62671fc | |||
9b959b0f2c | |||
bb2b19ec22 | |||
c5e1d9e88a | |||
82842b25fc | |||
56700bf420 | |||
c3e8aaa6fc | |||
8c53148488 | |||
96b40242e8 | |||
7df0a67f8d | |||
bf17cc3caf | |||
a1002d91ec | |||
9ae5605af0 | |||
f79dbd03b4 | |||
8c08e1c2d6 | |||
6e76e909d3 | |||
a39e3c7365 | |||
46444af78c | |||
166d530f35 | |||
571b9bd97a | |||
18e5c968ad | |||
142995766e | |||
9db963ea18 | |||
996a4e8c29 | |||
4a7f822b94 | |||
f04e8ccc48 | |||
98907c5671 | |||
f6e4f8c129 | |||
f20c0b7e1f | |||
9f9106d788 | |||
cdab68347c | |||
87885db01e | |||
90ce5b77fe | |||
dc4ba1811f | |||
32f391e629 | |||
46a176283d | |||
7b15e44f9d | |||
1204b5a5e7 | |||
b0b09cdc70 | |||
eb17cebc28 | |||
d9eeca0ba8 | |||
7c30452e15 | |||
9c7a51e749 | |||
bc9af2b8d6 | |||
2a92081968 | |||
1f4322b769 | |||
bbb301d72a |
16
.dockerignore
Normal file
@ -0,0 +1,16 @@
|
||||
# .git
|
||||
.vscode
|
||||
.env
|
||||
.venv
|
||||
env
|
||||
venv
|
||||
.pytest_cache
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
tests
|
||||
GenshinUID.egg-info
|
||||
build
|
||||
__pycache__
|
||||
.pytest_cache
|
54
.github/workflows/pre-commit.yml
vendored
@ -1,54 +0,0 @@
|
||||
name: Pre Commit
|
||||
on:
|
||||
- pull_request
|
||||
jobs:
|
||||
Pre-Commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.10"
|
||||
architecture: "x64"
|
||||
|
||||
- name: Setup pre-commit and actions-toolkit
|
||||
run: "python3 -m pip install pre-commit actions-toolkit"
|
||||
|
||||
- name: Run pre-commit
|
||||
run: python3 -c "import os;os.system('python3 -m pre_commit run --color never -a > result.txt')"
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email github-actions[bot]@users.noreply.github.com
|
||||
git add .
|
||||
git diff-index --quiet HEAD || git commit -m ":rotating_light: auto fix by pre-commit"
|
||||
git push
|
||||
|
||||
- name: Read result
|
||||
id: rr
|
||||
run: python3 -c "from pathlib import Path;from actions_toolkit.core import set_output;comment=Path('./result.txt').read_text();set_output('comment', comment)"
|
||||
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: "pre-commit"
|
||||
|
||||
- name: Create or update comment
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
pre-commit output:
|
||||
```
|
||||
${{ steps.rr.outputs.COMMENT }}
|
||||
```
|
||||
edit-mode: replace
|
5
.gitignore
vendored
@ -210,7 +210,7 @@ celerybeat.pid
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.env/
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
@ -668,3 +668,6 @@ result.txt
|
||||
### GenshinUID ###
|
||||
GenshinUID/genshinuid_help/help.png
|
||||
GenshinUID/genshinuid_map/map_data
|
||||
|
||||
### Debug ###
|
||||
testnb2/
|
||||
|
@ -6,7 +6,7 @@ ci:
|
||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit-ci"
|
||||
repos:
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.11.5
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
@ -19,3 +19,17 @@ repos:
|
||||
rev: 5.0.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
|
||||
- repo: https://github.com/hadialqattan/pycln
|
||||
rev: v2.1.2
|
||||
hooks:
|
||||
- id: pycln
|
||||
|
||||
- repo: https://github.com/python-poetry/poetry
|
||||
rev: 1.3.1
|
||||
hooks:
|
||||
- id: poetry-check
|
||||
- id: poetry-lock
|
||||
- id: poetry-export
|
||||
args: ["-f", "requirements.txt", "--without-hashes", "-o", "requirements.txt"]
|
||||
verbose: true
|
||||
|
43
Dockerfile
Normal file
@ -0,0 +1,43 @@
|
||||
FROM python:3.9
|
||||
|
||||
# 1. 环境设置
|
||||
|
||||
# 使用镜像源
|
||||
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple
|
||||
|
||||
RUN apt-get update && apt-get install -y tzdata
|
||||
|
||||
ENV TZ Asia/Shanghai
|
||||
|
||||
# 2. 安装 GenshinUID
|
||||
WORKDIR /plugin
|
||||
|
||||
COPY ./pyproject.toml ./LICENSE ./README.md /plugin/
|
||||
|
||||
COPY ./GenshinUID /plugin/GenshinUID/
|
||||
|
||||
RUN pip install --editable "."
|
||||
|
||||
COPY ./.git /plugin/.git
|
||||
|
||||
# 3. 生成 nb2 项目
|
||||
WORKDIR /nb2
|
||||
|
||||
RUN pip install tomlkit "cookiecutter>=2.1.1"
|
||||
|
||||
COPY ./deploy/cookiecutter.yml ./deploy/update_pyproject.py /nb2/
|
||||
|
||||
# RUN cookiecutter --config-file ./cookiecutter.yml https://github.com/nonebot/nb-cli.git --directory="nb_cli/project" --no-input
|
||||
# 使用镜像源
|
||||
RUN cookiecutter --config-file ./cookiecutter.yml https://ghproxy.com/https://github.com/nonebot/nb-cli.git --directory="nb_cli/project" --no-input
|
||||
|
||||
RUN python update_pyproject.py
|
||||
|
||||
COPY ./deploy/.env.dev /nb2/nb2/
|
||||
|
||||
WORKDIR /nb2/nb2
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
|
||||
CMD python bot.py
|
@ -0,0 +1,39 @@
|
||||
from nonebot.log import logger
|
||||
from nonebot import require, load_all_plugins, get_plugin_by_module_name
|
||||
|
||||
require('nonebot_plugin_apscheduler')
|
||||
|
||||
if get_plugin_by_module_name("GenshinUID"):
|
||||
logger.info("推荐直接加载 GenshinUID 仓库文件夹")
|
||||
load_all_plugins(
|
||||
[
|
||||
'GenshinUID.genshinuid_abyss',
|
||||
'GenshinUID.genshinuid_adv',
|
||||
'GenshinUID.genshinuid_ann',
|
||||
'GenshinUID.genshinuid_gcg',
|
||||
'GenshinUID.genshinuid_xkdata',
|
||||
'GenshinUID.genshinuid_check',
|
||||
'GenshinUID.genshinuid_collection',
|
||||
'GenshinUID.genshinuid_config',
|
||||
'GenshinUID.genshinuid_enka',
|
||||
'GenshinUID.genshinuid_etcimg',
|
||||
'GenshinUID.genshinuid_eventlist',
|
||||
'GenshinUID.genshinuid_gachalog',
|
||||
'GenshinUID.genshinuid_guide',
|
||||
'GenshinUID.genshinuid_help',
|
||||
'GenshinUID.genshinuid_map',
|
||||
'GenshinUID.genshinuid_meta',
|
||||
'GenshinUID.genshinuid_mhybbscoin',
|
||||
'GenshinUID.genshinuid_mys',
|
||||
'GenshinUID.genshinuid_note',
|
||||
'GenshinUID.genshinuid_resin',
|
||||
'GenshinUID.genshinuid_resource',
|
||||
'GenshinUID.genshinuid_roleinfo',
|
||||
'GenshinUID.genshinuid_signin',
|
||||
'GenshinUID.genshinuid_user',
|
||||
'GenshinUID.genshinuid_wikitext',
|
||||
'GenshinUID.genshinuid_webconsole',
|
||||
'GenshinUID.genshinuid_update',
|
||||
],
|
||||
[],
|
||||
)
|
||||
|
@ -1,40 +0,0 @@
|
||||
import re
|
||||
import base64
|
||||
import asyncio
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
||||
|
||||
import httpx
|
||||
import hoshino
|
||||
from hoshino import Service
|
||||
|
||||
# from nonebot.log import logger
|
||||
from aiohttp import ClientConnectorError
|
||||
from aiocqhttp.exceptions import ActionFailed
|
||||
from nonebot import MessageSegment, get_bot # type: ignore
|
||||
from hoshino.util import (
|
||||
FreqLimiter,
|
||||
pic2b64,
|
||||
silence,
|
||||
concat_pic,
|
||||
filt_message,
|
||||
)
|
||||
from hoshino.typing import ( # type: ignore
|
||||
CQEvent,
|
||||
HoshinoBot,
|
||||
NoticeSession,
|
||||
CommandSession,
|
||||
)
|
||||
|
||||
from .utils.db_operation.db_operation import select_db
|
||||
from .utils.message.get_image_and_at import ImageAndAt
|
||||
from .utils.message.error_reply import * # noqa: F403,F401
|
||||
from .utils.alias.alias_to_char_name import alias_to_char_name
|
||||
from .utils.exception.handle_exception import handle_exception
|
||||
from .utils.draw_image_tools.send_image_tool import convert_img
|
||||
from .utils.genshin_fonts.genshin_fonts import genshin_font_origin
|
||||
|
||||
sv = Service('genshinuid', bundle='genshin', help_='发送"原神帮助"查看详情')
|
||||
hoshino_bot = hoshino.get_bot()
|
||||
logger = sv.logger
|
5
GenshinUID/config.py
Normal file
@ -0,0 +1,5 @@
|
||||
from nonebot import get_driver
|
||||
|
||||
config = get_driver().config
|
||||
SUPERUSERS = {x for x in config.superusers}
|
||||
priority = 2
|
@ -1,52 +1,85 @@
|
||||
from typing import Any, Tuple
|
||||
|
||||
from nonebot import on_regex
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import RegexGroup
|
||||
from nonebot.adapters.ntchat import MessageSegment, TextMessageEvent
|
||||
|
||||
from ..genshinuid_meta import register_menu
|
||||
from .draw_abyss_card import draw_abyss_img
|
||||
from ..all_import import * # noqa: F403,F401
|
||||
from ..utils.message.error_reply import UID_HINT
|
||||
from ..utils.db_operation.db_operation import select_db
|
||||
from ..utils.mhy_api.convert_mysid_to_uid import convert_mysid
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
from ..utils.draw_image_tools.send_image_tool import convert_img
|
||||
|
||||
|
||||
@sv.on_rex(
|
||||
get_abyss_info = on_regex(
|
||||
r'^(\[CQ:at,qq=[0-9]+\])?( )?'
|
||||
r'(uid|查询|mys)?([0-9]+)?(上期)?(深渊|sy)'
|
||||
r'(9|10|11|12|九|十|十一|十二)?(层)?'
|
||||
r'(\[CQ:at,qq=[0-9]+\])?( )?$',
|
||||
block=True,
|
||||
)
|
||||
async def send_abyss_info(bot: HoshinoBot, ev: CQEvent):
|
||||
args = ev['match'].groups()
|
||||
# 恢复了查询二字,和其他的查询打架下次解决
|
||||
|
||||
|
||||
@get_abyss_info.handle()
|
||||
@handle_exception('查询深渊信息')
|
||||
@register_menu(
|
||||
'查询深渊信息',
|
||||
'查询(@某人)(上期)深渊(xx层)',
|
||||
'查询你的或者指定人的深渊战绩',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'可以用来查看你的或者指定人的深渊战绩,可以指定层数,默认为最高层数\n'
|
||||
'可以在命令文本后带一张图以自定义背景图\n'
|
||||
' \n' # 如果想要空行,请在换行符前面打个空格,不然会忽略换行符
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>{查询</ft>'
|
||||
'<ft color=(125,125,125)>(@某人)</ft>'
|
||||
'<ft color=(238,120,0)>|uid</ft><ft color=(0,148,200)>xx</ft>'
|
||||
'<ft color=(238,120,0)>|mys</ft><ft color=(0,148,200)>xx</ft>'
|
||||
'<ft color=(238,120,0)>}</ft>'
|
||||
'<ft color=(125,125,125)>(上期)</ft>'
|
||||
'<ft color=(238,120,0)>深渊</ft>'
|
||||
'<ft color=(125,125,125)>(xx层)</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>查询深渊</ft>\n'
|
||||
'- <ft color=(238,120,0)>uid123456789上期深渊</ft>\n'
|
||||
'- <ft color=(238,120,0)>查询</ft><ft color=(0,123,67)>@无疑Wuyi</ft> '
|
||||
'<ft color=(238,120,0)>上期深渊12层</ft>'
|
||||
),
|
||||
)
|
||||
async def send_abyss_info(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
args: Tuple[Any, ...] = RegexGroup(),
|
||||
):
|
||||
logger.info('开始执行[查询深渊信息]')
|
||||
logger.info('[查询深渊信息]参数: {}'.format(args))
|
||||
at = re.search(r'\[CQ:at,qq=(\d*)]', str(ev.message))
|
||||
logger.info(f'[查询深渊信息]参数: {args}')
|
||||
qid = event.from_wxid
|
||||
if event.at_user_list:
|
||||
for user in event.at_user_list:
|
||||
user = user.strip()
|
||||
if user != "":
|
||||
qid = user
|
||||
|
||||
if at:
|
||||
qid = int(at.group(1))
|
||||
else:
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
|
||||
# 判断uid
|
||||
if args[2] != 'mys':
|
||||
if args[3] is None:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
uid = str(uid)
|
||||
elif len(args[3]) != 9:
|
||||
return
|
||||
else:
|
||||
uid = args[3]
|
||||
else:
|
||||
if args[2] == 'mys':
|
||||
uid = await convert_mysid(args[3])
|
||||
|
||||
logger.info('[查询深渊信息]uid: {}'.format(uid))
|
||||
|
||||
if '未找到绑定的UID' in uid:
|
||||
await bot.send(ev, UID_HINT)
|
||||
|
||||
# 判断深渊期数
|
||||
if args[4] is None:
|
||||
schedule_type = '1'
|
||||
elif args[3] is None:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
uid = str(uid)
|
||||
elif len(args[3]) != 9:
|
||||
return
|
||||
else:
|
||||
schedule_type = '2'
|
||||
logger.info('[查询深渊信息]深渊期数: {}'.format(schedule_type))
|
||||
|
||||
uid = args[3]
|
||||
logger.info(f'[查询深渊信息]uid: {uid}')
|
||||
if '未找到绑定的UID' in uid:
|
||||
await matcher.finish(UID_HINT)
|
||||
schedule_type = '1' if args[4] is None else '2'
|
||||
logger.info(f'[查询深渊信息]深渊期数: {schedule_type}')
|
||||
if args[6] in ['九', '十', '十一', '十二']:
|
||||
floor = (
|
||||
args[6]
|
||||
@ -55,17 +88,17 @@ async def send_abyss_info(bot: HoshinoBot, ev: CQEvent):
|
||||
.replace('十二', '12')
|
||||
.replace('十', '10')
|
||||
)
|
||||
|
||||
else:
|
||||
floor = args[6]
|
||||
if floor is not None:
|
||||
floor = int(floor)
|
||||
logger.info('[查询深渊信息]深渊层数: {}'.format(floor))
|
||||
|
||||
logger.info(f'[查询深渊信息]深渊层数: {floor}')
|
||||
im = await draw_abyss_img(uid, floor, schedule_type)
|
||||
if isinstance(im, str):
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, bytes):
|
||||
im = await convert_img(im)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await bot.send(ev, '发生了未知错误,请联系管理员检查后台输出!')
|
||||
await matcher.finish('发生了未知错误,请联系管理员检查后台输出!')
|
||||
|
34
GenshinUID/genshinuid_achievement/__init__.py
Normal file
@ -0,0 +1,34 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.adapters.ntchat import Message
|
||||
|
||||
from ..config import priority
|
||||
from .get_achi_desc import get_achi, get_daily_achi
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
|
||||
get_task_info = on_command('查委托', aliases={'委托'}, priority=priority)
|
||||
get_achi_info = on_command('查成就', aliases={'成就'}, priority=priority)
|
||||
|
||||
|
||||
@get_task_info.handle()
|
||||
@handle_exception('查委托')
|
||||
async def send_task_info(matcher: Matcher, args: Message = CommandArg()):
|
||||
if not args:
|
||||
return
|
||||
name = str(args[0])
|
||||
logger.info(f'[查委托] 参数:{name}')
|
||||
im = await get_daily_achi(name)
|
||||
await matcher.finish(im)
|
||||
|
||||
|
||||
@get_achi_info.handle()
|
||||
@handle_exception('查成就')
|
||||
async def send_achi_info(matcher: Matcher, args: Message = CommandArg()):
|
||||
if not args:
|
||||
return
|
||||
name = str(args[0])
|
||||
logger.info(f'[查成就] 参数:{name}')
|
||||
im = await get_achi(name)
|
||||
await matcher.finish(im)
|
4388
GenshinUID/genshinuid_achievement/all_achi.json
Normal file
542
GenshinUID/genshinuid_achievement/daily_achi.json
Normal file
@ -0,0 +1,542 @@
|
||||
{
|
||||
"语言交流": {
|
||||
"achievement": "…Odomu?",
|
||||
"desc": "在「语言交流」中与丘丘人交流成功。",
|
||||
"guide": "《语言交流》:注意 1、不要攻击附近的丘丘人;2、注意雷雨天小心落雷击中丘丘人导致任务失败",
|
||||
"link": ""
|
||||
},
|
||||
"诗歌交流": {
|
||||
"achievement": "Yo dala?",
|
||||
"desc": "在「诗歌交流」中与丘丘人交流成功。",
|
||||
"guide": "《诗歌交流》:选择这 3 项即可【Celi dada,mimi nunu!】、【Ye dada!】、【Muhe ye!】其余同上",
|
||||
"link": ""
|
||||
},
|
||||
"来自冬天的故事": {
|
||||
"achievement": "有一说一",
|
||||
"desc": "在「来自冬天的故事」中探听到所有关于至冬国的情报。",
|
||||
"guide": "《来自冬天的故事》:愚人众、邪眼、女皇陛下,三个选项各选 1 次,也就是至少要做 3 次\n*2.4新增后续维克托回至冬(向冬日回归)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Xt4y1z7qw?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"说到做到": {
|
||||
"achievement": "追求极致",
|
||||
"desc": "在「说到做到!」中完美完成查耶维奇的所有委托。",
|
||||
"guide": "《说到做到!》至少 3 次“完美”且不重复地完成委托才能拿到成就。\n山顶(按照顺序击杀:火斧、木盾、丘丘人萨满);山腰(不能损坏货物,可以拉怪出来);\n山底(需要 1 分钟内完成,跑图时间也算,可以提前放个口袋锚点,打怪前保留角色大招)\n*详解地址",
|
||||
"link": "https://www.bilibili.com/video/BV1m64y1y7rk?"
|
||||
},
|
||||
"岩游记": {
|
||||
"achievement": "帝君故事",
|
||||
"desc": "搜集到「岩游记」中所有有关岩王帝君的故事。",
|
||||
"guide": "《岩游记》需要做 4 次,给 4 次不同的道具:财神(必须是琉璃百合);开拓之神(野外采集物:琉璃袋 清心 绝云辣椒 霓裳花等);炉灶之神(各类矿石:夜泊石 铁矿 石珀 等等);历史之神(璃月菜品:翡翠什锦袋 水煮黑背鲈 等等)。\n*当“财神”和“历史之神”共同存在任务道具会被回收,当“炉灶之神”和“开拓之神”共同存在任务道具会被回收,注意记录。\n*视频地址及道具回收演示",
|
||||
"link": "https://www.bilibili.com/video/BV1s64y1m718?p=1"
|
||||
},
|
||||
"且听下回分解": {
|
||||
"achievement": "且听我一言。",
|
||||
"desc": "在「且听下回分解」中听完《海山履云记》。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"璃月港,有海盗!": {
|
||||
"achievement": "哎呀!海盗!",
|
||||
"desc": "陪璐璐、阿飞与小蒙各玩一次海盗游戏。",
|
||||
"guide": "《璃月港,有海盗!》;1.5 版本更新后在这三人附近挂机很容易刷到;2.4后又新增后续\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1q44y1N7Dn?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"好兆头": {
|
||||
"achievement": "「…而尽人事。」",
|
||||
"desc": "破坏了四种爱情运来临的征兆。",
|
||||
"guide": "在《好兆头》中捕鱼、风吹或者火烧落叶、杀鸽子、看到狗要赶走。人为干涉所有的征兆。\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV11r4y127Rw?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"愿风带走思念": {
|
||||
"achievement": "过量的思念",
|
||||
"desc": "完成五次「愿风带走思念」。",
|
||||
"guide": "《愿风带走思念》做 5 次。1.0版本(2020年11月11日之前)有BUG会做1次=5次的情况,拿到成就后不会重置。如果进度不为5次会重置为0。从1.1后后重新计算。\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1xu41167hS?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"勿言勿笑": {
|
||||
"achievement": "厨子与渔夫",
|
||||
"desc": "完成「独钓江雪」与「勿言勿笑」。",
|
||||
"guide": "正常完成 2 个任务即可",
|
||||
"link": ""
|
||||
},
|
||||
"独钓江雪": {
|
||||
"achievement": "厨子与渔夫",
|
||||
"desc": "完成「独钓江雪」与「勿言勿笑」。",
|
||||
"guide": "正常完成 2 个任务即可",
|
||||
"link": ""
|
||||
},
|
||||
"望舒须筑阶": {
|
||||
"achievement": "更上一层楼",
|
||||
"desc": "帮助淮安修复望舒客栈的断桥。",
|
||||
"guide": "正常完成《望舒须筑阶》2次任务即可",
|
||||
"link": ""
|
||||
},
|
||||
"鸽子、鸭子、小孩子": {
|
||||
"achievement": "略表歉意",
|
||||
"desc": "向提米道歉。",
|
||||
"guide": "《鸽子、鸭子、小孩子》里投食鸭子后故意杀掉鸭子,第二天刷《提米,对不起》",
|
||||
"link": ""
|
||||
},
|
||||
"提米,对不起!": {
|
||||
"achievement": "略表歉意",
|
||||
"desc": "向提米道歉。",
|
||||
"guide": "《鸽子、鸭子、小孩子》里投食鸭子后故意杀掉鸭子,第二天刷《提米,对不起》",
|
||||
"link": ""
|
||||
},
|
||||
"鸽子习惯一去不回": {
|
||||
"achievement": "「您好,亲爱的爸爸…」",
|
||||
"desc": "了解提米的故事。",
|
||||
"guide": "水银的讲解视频点此;\n每日委托《鸽子习惯一去不回》有三个支线,第一种:正常赶走鸽子,做完没有后续;\n第二种:赶鸽子的时候玩家杀了鸽子,被杜拉夫要求让旅行者亲自去送信,做完后也没有后续;\n第三种:赶鸽子的时候发现任务提示点的地上一团金光,跑过去发现丘丘人把鸽子抓走烧了吃了,打死丘丘人完成任务,没有后续;\n第三种丘丘人支线又分成两种情况,一是地上有一封信,捡到信的话解锁后续每日委托《一个男孩的去信》,做完这个委托后可以拿到成就\n建议在另外一个每日委托《鸽子、鸭子、小孩子》里不要杀死提米要你喂的鸽子,这样更有可能进入掉信的支线\n至于提米那个略表歉意的成就可以在另外一次刷到鸽子鸭子小孩子以后再做",
|
||||
"link": "https://www.bilibili.com/video/BV1xR4y1E7PR"
|
||||
},
|
||||
"一个男孩的去信": {
|
||||
"achievement": "「您好,亲爱的爸爸…」",
|
||||
"desc": "了解提米的故事。",
|
||||
"guide": "水银的讲解视频点此;\n每日委托《鸽子习惯一去不回》有三个支线,第一种:正常赶走鸽子,做完没有后续;\n第二种:赶鸽子的时候玩家杀了鸽子,被杜拉夫要求让旅行者亲自去送信,做完后也没有后续;\n第三种:赶鸽子的时候发现任务提示点的地上一团金光,跑过去发现丘丘人把鸽子抓走烧了吃了,打死丘丘人完成任务,没有后续;\n第三种丘丘人支线又分成两种情况,一是地上有一封信,捡到信的话解锁后续每日委托《一个男孩的去信》,做完这个委托后可以拿到成就\n建议在另外一个每日委托《鸽子、鸭子、小孩子》里不要杀死提米要你喂的鸽子,这样更有可能进入掉信的支线\n至于提米那个略表歉意的成就可以在另外一次刷到鸽子鸭子小孩子以后再做",
|
||||
"link": "https://www.bilibili.com/video/BV1xR4y1E7PR"
|
||||
},
|
||||
"奇药庐中来": {
|
||||
"achievement": "妙手怪医",
|
||||
"desc": "治好安娜的病。",
|
||||
"guide": "《奇药庐中来》要做 3 次,之后解锁《大病初愈》后续任务;第一次做完《大病初愈》后给成就。\n成就拿完后《大病初愈》可能还会反复刷,安娜会出现在三个位置(风车顶上、教堂顶上、风神像手上)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Hw411Z7zp?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"大病初愈": {
|
||||
"achievement": "妙手怪医",
|
||||
"desc": "治好安娜的病。",
|
||||
"guide": "《奇药庐中来》要做 3 次,之后解锁《大病初愈》后续任务;第一次做完《大病初愈》后给成就。\n成就拿完后《大病初愈》可能还会反复刷,安娜会出现在三个位置(风车顶上、教堂顶上、风神像手上)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Hw411Z7zp?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"餐品订单": {
|
||||
"achievement": "这不是应急食品",
|
||||
"desc": "在「餐品订单」任务中吃掉了餐品…?",
|
||||
"guide": "成就是刷到任务以后吃掉任务菜品就可以拿,但是蟹黄火腿焗时蔬的食谱是不一定给的(送餐给活跃的欧琳的支线才会给,途中要打史莱姆)(触发哪条支线是随机的)",
|
||||
"link": ""
|
||||
},
|
||||
"惊喜大礼": {
|
||||
"achievement": "西风佑我",
|
||||
"desc": "见证吉丽安娜的故事。",
|
||||
"guide": "《惊喜大礼》要做 4 次不同路线(莎拉店、纪念品店、坤恩水果摊、芙萝拉花店)\n最后触发最终剧情(此任务还有后续,盗宝团来复仇,触发“那位先生的委托”)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Ew411f74K?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"「冒险家」的能力极限": {
|
||||
"achievement": "凑合…也能用",
|
||||
"desc": "只带给赫尔曼木桩的材料。",
|
||||
"guide": "《「冒险家」的能力极限》木桩一定要打坏带回",
|
||||
"link": ""
|
||||
},
|
||||
"冒险家测验·作战方式": {
|
||||
"achievement": "安娜冒险记",
|
||||
"desc": "帮助安娜成为一名冒险家。",
|
||||
"guide": "《冒险家,安娜!》\n拿到成就的过程和给安娜治病差不多,且需要先给安娜治好病,也就是完成每日委托成就《妙手怪医》,之后完成前置任务:《冒险家测验·作战方式》、《冒险家测验·冒险诀窍》、《冒险家测验·起飞方式》,最后接到后续《冒险家,安娜!》,第一次完成任务后拿到成就\n(做完成就后还有后续支线,其中一条支线有“彩蛋”级内容,但没成就)\n*前置很阴间主要是情商选项,后续这4个也很阴间不按套路出牌\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Bu411r7Kb?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"冒险家测验·冒险诀窍": {
|
||||
"achievement": "安娜冒险记",
|
||||
"desc": "帮助安娜成为一名冒险家。",
|
||||
"guide": "《冒险家,安娜!》\n拿到成就的过程和给安娜治病差不多,且需要先给安娜治好病,也就是完成每日委托成就《妙手怪医》,之后完成前置任务:《冒险家测验·作战方式》、《冒险家测验·冒险诀窍》、《冒险家测验·起飞方式》,最后接到后续《冒险家,安娜!》,第一次完成任务后拿到成就\n(做完成就后还有后续支线,其中一条支线有“彩蛋”级内容,但没成就)\n*前置很阴间主要是情商选项,后续这4个也很阴间不按套路出牌\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Bu411r7Kb?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"冒险家测验·起飞方式": {
|
||||
"achievement": "安娜冒险记",
|
||||
"desc": "帮助安娜成为一名冒险家。",
|
||||
"guide": "《冒险家,安娜!》\n拿到成就的过程和给安娜治病差不多,且需要先给安娜治好病,也就是完成每日委托成就《妙手怪医》,之后完成前置任务:《冒险家测验·作战方式》、《冒险家测验·冒险诀窍》、《冒险家测验·起飞方式》,最后接到后续《冒险家,安娜!》,第一次完成任务后拿到成就\n(做完成就后还有后续支线,其中一条支线有“彩蛋”级内容,但没成就)\n*前置很阴间主要是情商选项,后续这4个也很阴间不按套路出牌\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Bu411r7Kb?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"冒险家,安娜!": {
|
||||
"achievement": "安娜冒险记",
|
||||
"desc": "帮助安娜成为一名冒险家。",
|
||||
"guide": "《冒险家,安娜!》\n拿到成就的过程和给安娜治病差不多,且需要先给安娜治好病,也就是完成每日委托成就《妙手怪医》,之后完成前置任务:《冒险家测验·作战方式》、《冒险家测验·冒险诀窍》、《冒险家测验·起飞方式》,最后接到后续《冒险家,安娜!》,第一次完成任务后拿到成就\n(做完成就后还有后续支线,其中一条支线有“彩蛋”级内容,但没成就)\n*前置很阴间主要是情商选项,后续这4个也很阴间不按套路出牌\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Bu411r7Kb?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"『遗落』的文物": {
|
||||
"achievement": "学者与「学者」",
|
||||
"desc": "完成「『遗落』的文物」与「『夺宝』小行动」。",
|
||||
"guide": "《遗落的文物》至少要做 3 次,剧情进展到解救学者索拉雅以后有才有几率刷出《夺宝小行动》",
|
||||
"link": ""
|
||||
},
|
||||
"『夺宝』小行动": {
|
||||
"achievement": "学者与「学者」",
|
||||
"desc": "完成「『遗落』的文物」与「『夺宝』小行动」。",
|
||||
"guide": "《遗落的文物》至少要做 3 次,剧情进展到解救学者索拉雅以后有才有几率刷出《夺宝小行动》",
|
||||
"link": ""
|
||||
},
|
||||
"港口驶过几艘船,二四六七八": {
|
||||
"achievement": "梦想与工作,诗与面包",
|
||||
"desc": "完成「所谓『工作』」,并获得霖铃的诗集。",
|
||||
"guide": "完成《港口驶过几艘船,二四六七八》时故意告诉霖铃错误的数量(注意船有驶入和驶出的区别),大概率第二天刷《所谓工作》;如果第二天没刷,可能过一阵子才会刷",
|
||||
"link": ""
|
||||
},
|
||||
"所谓「工作」": {
|
||||
"achievement": "梦想与工作,诗与面包",
|
||||
"desc": "完成「所谓『工作』」,并获得霖铃的诗集。",
|
||||
"guide": "完成《港口驶过几艘船,二四六七八》时故意告诉霖铃错误的数量(注意船有驶入和驶出的区别),大概率第二天刷《所谓工作》;如果第二天没刷,可能过一阵子才会刷",
|
||||
"link": ""
|
||||
},
|
||||
"点石成…什么": {
|
||||
"achievement": "时也运也",
|
||||
"desc": "一次就选中了最高价值的璞石。",
|
||||
"guide": "《点石成…什么》:正确方法是选最亮的石头。\n不放心的话可以卡视角来透视璞石内部,有完整的石珀就可以选。\n注意并不是 100% 有石珀,纯随机,同理“餐品订单”任务\n外观最亮的,通过透视可以看到里面发光、纹路有完整条纹的即可。\n",
|
||||
"link": ""
|
||||
},
|
||||
"这本小说真厉害": {
|
||||
"achievement": "这本小说真厉害!",
|
||||
"desc": "偷看常九爷的书稿。",
|
||||
"guide": "《这本小说真厉害!》,交书稿前派蒙会问你是否偷看,选择偷看即可",
|
||||
"link": ""
|
||||
},
|
||||
"久久望故人": {
|
||||
"achievement": "故人久未归",
|
||||
"desc": "完成「久久望故人」任务。",
|
||||
"guide": "《久久望故人》:一定要先做过世界任务的小九九,否则可能做完没成就。\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV11U4y137Tr?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"哎呀!海盗想长大!": {
|
||||
"achievement": "远大前程",
|
||||
"desc": "一位少年即将启程远行…",
|
||||
"guide": "《小海盗,要出海!》\n前置《哎呀!海盗想长大!》、《随水而来的烦恼》\n后续《小小的远行》系列 3 个(没成就)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1q44y1N7Dn?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"随水而来的烦恼": {
|
||||
"achievement": "远大前程",
|
||||
"desc": "一位少年即将启程远行…",
|
||||
"guide": "《小海盗,要出海!》\n前置《哎呀!海盗想长大!》、《随水而来的烦恼》\n后续《小小的远行》系列 3 个(没成就)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1q44y1N7Dn?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"小海盗,要出海!": {
|
||||
"achievement": "远大前程",
|
||||
"desc": "一位少年即将启程远行…",
|
||||
"guide": "《小海盗,要出海!》\n前置《哎呀!海盗想长大!》、《随水而来的烦恼》\n后续《小小的远行》系列 3 个(没成就)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1q44y1N7Dn?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"试问,藏锋何处?": {
|
||||
"achievement": "四方求剑",
|
||||
"desc": "见证岚姐与「藏锋」的故事。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"剑去之日": {
|
||||
"achievement": "行万里路…?",
|
||||
"desc": "见证孙宇的故事。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"万端珊瑚事件簿": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·搜索工作": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·迷惑行动": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·合适的身份": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·结案时刻": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·收尾工作": {
|
||||
"achievement": "真相只有一个…?",
|
||||
"desc": "见证龙二的故事。",
|
||||
"guide": "做完《万端珊瑚事件簿·结案时刻》后解锁《万端珊瑚事件簿·收尾工作》,新登场大和田剧情。\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"家乡之味": {
|
||||
"achievement": "璃月一番",
|
||||
"desc": "用美味的料理治愈汤雯。",
|
||||
"guide": "前置任务每日委托《家乡之味》交付奇怪的料理(无论哪种料理的奇怪的版本都行)给汤雯,汤雯拿到以后会说味道很微妙。\n正确完成前置任务后,再接到后续每日委托《绝对独特的美味》 (非强制触发,最快第二天可以接到,最慢1个月后),三个选项 (绝云锅巴、腌笃鲜、烤吃虎鱼) 选哪种都可以,交付美味的料理即可解锁成就\n*.2.5版本后在再做一次该任务,汤雯会回璃月\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV12f4y157fC?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"绝对独特的美味": {
|
||||
"achievement": "璃月一番",
|
||||
"desc": "用美味的料理治愈汤雯。",
|
||||
"guide": "前置任务每日委托《家乡之味》交付奇怪的料理(无论哪种料理的奇怪的版本都行)给汤雯,汤雯拿到以后会说味道很微妙。\n正确完成前置任务后,再接到后续每日委托《绝对独特的美味》 (非强制触发,最快第二天可以接到,最慢1个月后),三个选项 (绝云锅巴、腌笃鲜、烤吃虎鱼) 选哪种都可以,交付美味的料理即可解锁成就\n*.2.5版本后在再做一次该任务,汤雯会回璃月\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV12f4y157fC?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"全能美食队·突破性思维": {
|
||||
"achievement": "噼咔,为什么又是噼咔",
|
||||
"desc": "向香菱请教到特别的烹饪手法。",
|
||||
"guide": "《全能美食队·突破性思维》\n剧情里旭东会让旅行者去璃月的万民堂找卯师傅,卯师傅会给你 2 个选项,选择“也许香菱知道怎么解决他的问题…”这个选项,卯师傅说香菱去轻策庄了,让你找点绝云椒椒和禽肉。这时候你可以不管卯师傅要的东西,自己传送到轻策庄去找香菱对话,最后回去找旭东\n香菱在轻策庄西南的传送点(刚传送过去就会被野猪撞的那个)附近\n此外只要自己去找香菱对话,最后交付道具时交付香菱给的就行了,对话选哪个选项其实无所谓\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1zv411g7VE?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"全能美食队·烹饪对决": {
|
||||
"achievement": "双人餐行",
|
||||
"desc": "帮助旭东和龟井宗久各完成一次烹饪。",
|
||||
"guide": "《全能美食队·烹饪对决》:双方各胜利 1 次即可。\n灭火BUG已经修护,如实正常做任务即可\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1zv411g7VE?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"全能美食队·美食小问答": {
|
||||
"achievement": "饮食问题",
|
||||
"desc": "帮助芭尔瓦涅校对全部食谱。",
|
||||
"guide": "《全能美食队·美食小问答》正确答案如下:\n北地苹果焖肉——胡椒 ;天枢肉———清心\n腌笃鲜—————竹笋 ;串串三味——鸟蛋 ;水煮黑背鲈——盐\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1zv411g7VE?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"全能美食队·厨道的极意": {
|
||||
"achievement": "武士饭",
|
||||
"desc": "帮助龟井宗久搜集过全部两侧营地的食材。",
|
||||
"guide": "《全能美食队·厨道的极意》:左右两边各要做 1 次\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1zv411g7VE?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"稻妻销售员": {
|
||||
"achievement": "「给您添蘑菇了!」",
|
||||
"desc": "在「售后服务」中收到顾客绀田传助的抱怨。",
|
||||
"guide": "前置任务《稻妻销售员》中,告诉绀田传助错误的化肥使用方法\n任务里有三个选项,选择错误的选项\n每次刷到这个任务时,瓦希德告诉你的方法的顺序可能是不一样的,没有固定答案,需要你自己判断哪个选项是错误的(类似璃月港数船)\n教错了的话,绀田传助会说感觉不对劲\n之后解锁每日委托《售后服务》(解锁代表有机会刷到,但不是第二天一定就刷),为绀田传助摘除田地里的蘑菇时,注意要把全部的蘑菇都摘除、摘了一部分时派蒙会说“这下应该差不多了”,同时系统提示可以找绀田传助交任务了;此时不要理会,继续摘蘑菇,全部摘完以后派蒙会说“这下就全部摘干净了”,这时再去找绀田传助交任务,任务完成后解锁成就",
|
||||
"link": ""
|
||||
},
|
||||
"售后服务": {
|
||||
"achievement": "「给您添蘑菇了!」",
|
||||
"desc": "在「售后服务」中收到顾客绀田传助的抱怨。",
|
||||
"guide": "前置任务《稻妻销售员》中,告诉绀田传助错误的化肥使用方法\n任务里有三个选项,选择错误的选项\n每次刷到这个任务时,瓦希德告诉你的方法的顺序可能是不一样的,没有固定答案,需要你自己判断哪个选项是错误的(类似璃月港数船)\n教错了的话,绀田传助会说感觉不对劲\n之后解锁每日委托《售后服务》(解锁代表有机会刷到,但不是第二天一定就刷),为绀田传助摘除田地里的蘑菇时,注意要把全部的蘑菇都摘除、摘了一部分时派蒙会说“这下应该差不多了”,同时系统提示可以找绀田传助交任务了;此时不要理会,继续摘蘑菇,全部摘完以后派蒙会说“这下就全部摘干净了”,这时再去找绀田传助交任务,任务完成后解锁成就",
|
||||
"link": ""
|
||||
},
|
||||
"这本小说…厉害吗?": {
|
||||
"achievement": "编辑部的一己之见",
|
||||
"desc": "帮助阿茂和顺吉回到正确的创作轨道。",
|
||||
"guide": "一阶段:《这本小说…厉害吗?》(支线:天目锻冶屋、九十九物、观察同心们的工作)\n二阶段支线 A:《这本小说…有问题?》\n二阶段支线 B:《这本小说…好像看过?》(按顺序123依次交书即可)\n从剧情逻辑上来看,在一阶段支持编辑阿茂解锁刷到《这本小说…好像看过》的可能性,支持作家顺吉解锁刷到《这本小说…有问题》的可能性\n需要这三个每日都做完,且在 B 支线交付\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"这本小说…有问题?": {
|
||||
"achievement": "编辑部的一己之见",
|
||||
"desc": "帮助阿茂和顺吉回到正确的创作轨道。",
|
||||
"guide": "一阶段:《这本小说…厉害吗?》(支线:天目锻冶屋、九十九物、观察同心们的工作)\n二阶段支线 A:《这本小说…有问题?》\n二阶段支线 B:《这本小说…好像看过?》(按顺序123依次交书即可)\n从剧情逻辑上来看,在一阶段支持编辑阿茂解锁刷到《这本小说…好像看过》的可能性,支持作家顺吉解锁刷到《这本小说…有问题》的可能性\n需要这三个每日都做完,且在 B 支线交付\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"这本小说…好像看过?": {
|
||||
"achievement": "编辑部的一己之见",
|
||||
"desc": "帮助阿茂和顺吉回到正确的创作轨道。",
|
||||
"guide": "一阶段:《这本小说…厉害吗?》(支线:天目锻冶屋、九十九物、观察同心们的工作)\n二阶段支线 A:《这本小说…有问题?》\n二阶段支线 B:《这本小说…好像看过?》(按顺序123依次交书即可)\n从剧情逻辑上来看,在一阶段支持编辑阿茂解锁刷到《这本小说…好像看过》的可能性,支持作家顺吉解锁刷到《这本小说…有问题》的可能性\n需要这三个每日都做完,且在 B 支线交付\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"必须精进的武艺": {
|
||||
"achievement": "同心训练家?",
|
||||
"desc": "协助朝仓进行5次训练。",
|
||||
"guide": "做 4 次每日委托《必须精进的武艺》后解锁世界任务《洗刷耻辱的一战》,完成世界任务后有机会刷到每日委托《永不停歇的修炼》,《必须精进的武艺》+《永不停歇的修炼》合计 5 次即可",
|
||||
"link": ""
|
||||
},
|
||||
"永不停歇的修炼": {
|
||||
"achievement": "同心训练家?",
|
||||
"desc": "协助朝仓进行5次训练。",
|
||||
"guide": "做 4 次每日委托《必须精进的武艺》后解锁世界任务《洗刷耻辱的一战》,完成世界任务后有机会刷到每日委托《永不停歇的修炼》,《必须精进的武艺》+《永不停歇的修炼》合计 5 次即可",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《这本小说…有问题?》": {
|
||||
"achievement": "至少有了个结局",
|
||||
"desc": "听顺吉讲述完他所构思的故事。",
|
||||
"guide": "需要在 2.1 版本后(2.0 版本做过的不算)重做《这本小说…有问题?》和《这本小说…好像看过?》才能解锁世界任务《故事构思法》 ,做完世界任务后得到成\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"每日委托《这本小说…好像看过?》": {
|
||||
"achievement": "至少有了个结局",
|
||||
"desc": "听顺吉讲述完他所构思的故事。",
|
||||
"guide": "需要在 2.1 版本后(2.0 版本做过的不算)重做《这本小说…有问题?》和《这本小说…好像看过?》才能解锁世界任务《故事构思法》 ,做完世界任务后得到成\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"世界任务《故事构思法》": {
|
||||
"achievement": "至少有了个结局",
|
||||
"desc": "听顺吉讲述完他所构思的故事。",
|
||||
"guide": "需要在 2.1 版本后(2.0 版本做过的不算)重做《这本小说…有问题?》和《这本小说…好像看过?》才能解锁世界任务《故事构思法》 ,做完世界任务后得到成\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"每日委托《神社大扫除》": {
|
||||
"achievement": "她和她的猫",
|
||||
"desc": "陪寝子前往影向山,寻找「阿响」的痕迹。",
|
||||
"guide": "做完寝子系列的世界任务后,累积做 4 个寝子相关每日委托(指《神社大扫除》、《鱼之味》、《猫之迹》,大岛纯平那三个不算)后解锁世界任务《鸣神寻踪》,完成世界任务后解锁成就\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1T3411m7kJ"
|
||||
},
|
||||
"每日委托《鱼之味》": {
|
||||
"achievement": "她和她的猫",
|
||||
"desc": "陪寝子前往影向山,寻找「阿响」的痕迹。",
|
||||
"guide": "做完寝子系列的世界任务后,累积做 4 个寝子相关每日委托(指《神社大扫除》、《鱼之味》、《猫之迹》,大岛纯平那三个不算)后解锁世界任务《鸣神寻踪》,完成世界任务后解锁成就\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1T3411m7kJ"
|
||||
},
|
||||
"每日委托《猫之迹》": {
|
||||
"achievement": "她和她的猫",
|
||||
"desc": "陪寝子前往影向山,寻找「阿响」的痕迹。",
|
||||
"guide": "做完寝子系列的世界任务后,累积做 4 个寝子相关每日委托(指《神社大扫除》、《鱼之味》、《猫之迹》,大岛纯平那三个不算)后解锁世界任务《鸣神寻踪》,完成世界任务后解锁成就\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1T3411m7kJ"
|
||||
},
|
||||
"世界任务《鸣神寻踪》": {
|
||||
"achievement": "她和她的猫",
|
||||
"desc": "陪寝子前往影向山,寻找「阿响」的痕迹。",
|
||||
"guide": "做完寝子系列的世界任务后,累积做 4 个寝子相关每日委托(指《神社大扫除》、《鱼之味》、《猫之迹》,大岛纯平那三个不算)后解锁世界任务《鸣神寻踪》,完成世界任务后解锁成就\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1T3411m7kJ"
|
||||
},
|
||||
"每日委托《鱼钩上的绝景?》": {
|
||||
"achievement": "啊哈…什么上钩了?",
|
||||
"desc": "与凯万一起钓起奇怪的东西…",
|
||||
"guide": "累积做 3 次前置每日委托《鱼钩上的绝景?》,有墩墩桃、鸟蛋、蘑菇三种支线(注意是累积3次),第二天4点后解锁世界任务《鱼钩的物尽其用》,做完世界任务拿到成就。\n任务还有后续,但是无成就(可能未来版本会加后续成就)。\n做完世界任务以后有机会接到每日委托《鱼钩的奇异时光?》,和前边的也差不多,有帕蒂沙兰、香辛果、甜甜花三种支线",
|
||||
"link": ""
|
||||
},
|
||||
"世界任务《鱼钩的物尽其用》": {
|
||||
"achievement": "啊哈…什么上钩了?",
|
||||
"desc": "与凯万一起钓起奇怪的东西…",
|
||||
"guide": "累积做 3 次前置每日委托《鱼钩上的绝景?》,有墩墩桃、鸟蛋、蘑菇三种支线(注意是累积3次),第二天4点后解锁世界任务《鱼钩的物尽其用》,做完世界任务拿到成就。\n任务还有后续,但是无成就(可能未来版本会加后续成就)。\n做完世界任务以后有机会接到每日委托《鱼钩的奇异时光?》,和前边的也差不多,有帕蒂沙兰、香辛果、甜甜花三种支线",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《鱼钩的奇异时光?》": {
|
||||
"achievement": "啊哈…什么上钩了?",
|
||||
"desc": "与凯万一起钓起奇怪的东西…",
|
||||
"guide": "累积做 3 次前置每日委托《鱼钩上的绝景?》,有墩墩桃、鸟蛋、蘑菇三种支线(注意是累积3次),第二天4点后解锁世界任务《鱼钩的物尽其用》,做完世界任务拿到成就。\n任务还有后续,但是无成就(可能未来版本会加后续成就)。\n做完世界任务以后有机会接到每日委托《鱼钩的奇异时光?》,和前边的也差不多,有帕蒂沙兰、香辛果、甜甜花三种支线",
|
||||
"link": ""
|
||||
},
|
||||
"吞金和蓄财": {
|
||||
"achievement": "卡里米之蕈兽",
|
||||
"desc": "见证哈特姆在「期货交易」大赚一笔!",
|
||||
"guide": "全随机后续,正常需要做5次拿到2成就,最速欧皇可以3次拿2成就。\n2022/9/5更新:\n「吞金料理」中有3种料理:摩拉肉,黄油鸡和「堆高高」。\n建议选择美味的堆高高。\n2022/9/16更新:\n现在发现变成了随机后续,给任何料理都会触发任意成就,原本应该给2次料理2个成就的,也可以1次料理2成就。",
|
||||
"link": ""
|
||||
},
|
||||
"喵…喵喵?喵!喵。": {
|
||||
"achievement": "捉猫记",
|
||||
"desc": "帮莎莉寻找过所有小猫。",
|
||||
"guide": "至少要做 3 次,正确完成寻找 3 只不同小猫的支线\n「黑白色」的猫「拉勒」:【喵,喵喵喵,喵——】\n「深灰色」的猫「纳尔吉斯」:【喵!喵喵,喵】\n「灰黑条纹」的猫「萝赞」:【喵喵喵,喵喵喵】",
|
||||
"link": ""
|
||||
},
|
||||
"世界任务《加尔恰的赞歌》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·关键物品》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·替代物》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·轴承在上》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·举手之劳》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·某人的回响》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"世界任务《加尔恰的赞歌·适配性赠礼》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"御用在他乡": {
|
||||
"achievement": "「为了工作。」",
|
||||
"desc": "为范兵卫采到更多的蘑菇。",
|
||||
"guide": "每日委托《御用在他乡》\n√完结!\n可以一次性拿到成就 :要求采5个蘑菇,但可以采7个给成就\n推测需要先做完稻妻的世界任务《踏鞴物语》系列才能在须弥接到这个委托。\n做完第一次以后他以后还会让你摘蘑菇,对话内容会有些变化",
|
||||
"link": ""
|
||||
},
|
||||
"谨遵医嘱": {
|
||||
"achievement": "放松疗法",
|
||||
"desc": "满足三个病人的愿望。",
|
||||
"guide": "《洁净与健康》不是前置任务,可以直接刷到了《谨遵医嘱》\n《谨遵医嘱》这个任务是你和病人对话完成就可以回去交差了的,但这样拿不到成就。你需要:\n细节:① 主动为古尔根清理田里的杂草,清理完以后再次与古尔根对话;② 阿兹拉说药太苦,和她对话,送给她【糖】;③ 阿夫塔想吃肉,给他【美味的烤肉排】。\n满足病人的愿望以后再回去交差。其中阿兹拉和阿夫塔不会主动问你要东西,需要你听完他们的话以后再次与他们对话来交付道具",
|
||||
"link": ""
|
||||
},
|
||||
"生不出的花": {
|
||||
"achievement": "斩花除根",
|
||||
"desc": "找到并打倒逃走的骗骗花。",
|
||||
"guide": "前置累积3次《生不出的花》后,后续出《花开之时》,\n随机后续1,有骗骗花的支线才有成就,追击并干掉骗骗花后获得成就。\n随机后续2,无骗骗花,寄了,再来3次……\n细节:前置有 2 个支线,一个是提供肥料,另一个是浇水。",
|
||||
"link": ""
|
||||
},
|
||||
"花开之时": {
|
||||
"achievement": "斩花除根",
|
||||
"desc": "找到并打倒逃走的骗骗花。",
|
||||
"guide": "前置累积3次《生不出的花》后,后续出《花开之时》,\n随机后续1,有骗骗花的支线才有成就,追击并干掉骗骗花后获得成就。\n随机后续2,无骗骗花,寄了,再来3次……\n细节:前置有 2 个支线,一个是提供肥料,另一个是浇水。",
|
||||
"link": ""
|
||||
},
|
||||
"衡量世界之人!": {
|
||||
"achievement": "天有多高,地有多…",
|
||||
"desc": "协助法伽尼进行测量工作。",
|
||||
"guide": "1和2分支都是随机给的,其中分支1还有3种怪:丘丘人、蕈兽、遗迹蛇\n至少做 3 次,分别是:① 打怪;② 设置信标;③ 回收信标+打怪。",
|
||||
"link": ""
|
||||
},
|
||||
"宝贝计划": {
|
||||
"achievement": "非必要需求",
|
||||
"desc": "找到古拉布吉尔给小蛇制作的所有道具。",
|
||||
"guide": "帮古拉布吉尔找宠物蛇口粮,有 5 个支线。任务是你找到【古拉布吉尔的特制宠物蛇口粮】交给 NPC 就可以完成,但是做成就需要你额外找到 3 个东西:【奇怪的珠子】、【奇怪的小型帽子】、【破旧的架子】。\n每次随机给1个隐藏道具,但是也可能没有。",
|
||||
"link": ""
|
||||
},
|
||||
"问题的转化": {
|
||||
"achievement": "船说了算",
|
||||
"desc": "与拉菲克成功地测试了船体强度。",
|
||||
"guide": "目前第三段就可以拿到成就,但是还有后续,可能未来版本还有成就。\n(类似稻妻八重堂作家和编辑《小说有问题》在后续版本上线新成就。)\n细节:在《问题的转化·理论强度》中拿5块木头,这样在《问题的转化·负载问题》中可以成功拿到成就,失败的支线可能会退回到第二阶段。",
|
||||
"link": ""
|
||||
},
|
||||
"问题的转化·理论强度": {
|
||||
"achievement": "船说了算",
|
||||
"desc": "与拉菲克成功地测试了船体强度。",
|
||||
"guide": "目前第三段就可以拿到成就,但是还有后续,可能未来版本还有成就。\n(类似稻妻八重堂作家和编辑《小说有问题》在后续版本上线新成就。)\n细节:在《问题的转化·理论强度》中拿5块木头,这样在《问题的转化·负载问题》中可以成功拿到成就,失败的支线可能会退回到第二阶段。",
|
||||
"link": ""
|
||||
},
|
||||
"问题的转化·负载问题": {
|
||||
"achievement": "船说了算",
|
||||
"desc": "与拉菲克成功地测试了船体强度。",
|
||||
"guide": "目前第三段就可以拿到成就,但是还有后续,可能未来版本还有成就。\n(类似稻妻八重堂作家和编辑《小说有问题》在后续版本上线新成就。)\n细节:在《问题的转化·理论强度》中拿5块木头,这样在《问题的转化·负载问题》中可以成功拿到成就,失败的支线可能会退回到第二阶段。",
|
||||
"link": ""
|
||||
},
|
||||
"问题的转化·关键在何?": {
|
||||
"achievement": "船说了算",
|
||||
"desc": "与拉菲克成功地测试了船体强度。",
|
||||
"guide": "目前第三段就可以拿到成就,但是还有后续,可能未来版本还有成就。\n(类似稻妻八重堂作家和编辑《小说有问题》在后续版本上线新成就。)\n细节:在《问题的转化·理论强度》中拿5块木头,这样在《问题的转化·负载问题》中可以成功拿到成就,失败的支线可能会退回到第二阶段。",
|
||||
"link": ""
|
||||
},
|
||||
"食与学": {
|
||||
"achievement": "问题何在?",
|
||||
"desc": "享受三道贾法尔制作的料理。",
|
||||
"guide": "每次随机给一个。\n完成 3 个支线:薄荷豆汤、绿汁脆球、烤肉卷",
|
||||
"link": ""
|
||||
},
|
||||
"教令院,小问题": {
|
||||
"achievement": "须弥博学者",
|
||||
"desc": "答对六道不同的问题。",
|
||||
"guide": "总共 6 道题,每次抽 3 道,正确答案分别是:1、阿弥利多学院;2、悉般多摩学院;3、圣树;4、防沙壁;5、驮兽;6、蕈兽",
|
||||
"link": ""
|
||||
},
|
||||
"跑,希尔米,跑": {
|
||||
"achievement": "一步之遥",
|
||||
"desc": "在与希尔米的赛跑中大意落败…",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"良药难求": {
|
||||
"achievement": "医用笔迹",
|
||||
"desc": "帮助马鲁夫正确地解析药方。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"沙上花·余香": {
|
||||
"achievement": "手有余香",
|
||||
"desc": "见证内尔敏的故事。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
}
|
||||
}
|
62
GenshinUID/genshinuid_achievement/get_achi_desc.py
Normal file
@ -0,0 +1,62 @@
|
||||
import re
|
||||
|
||||
from .template import all_achi, daily_achi, achi_template, daily_template
|
||||
|
||||
|
||||
async def get_daily_achi(task: str) -> str:
|
||||
_similarity = 0
|
||||
detail = {}
|
||||
if task in daily_achi:
|
||||
detail = daily_achi[task]
|
||||
else:
|
||||
for _task in daily_achi:
|
||||
__task = ''.join(re.findall('[\u4e00-\u9fa5]', _task))
|
||||
__task = __task.replace('每日委托', '').replace('世界任务', '')
|
||||
similarity = len(set(__task) & set(task))
|
||||
if similarity >= len(__task) / 2:
|
||||
if similarity > _similarity:
|
||||
_similarity = similarity
|
||||
detail = daily_achi[_task]
|
||||
task = _task
|
||||
else:
|
||||
if detail == {}:
|
||||
return '该委托暂无成就...'
|
||||
|
||||
achi = detail['achievement']
|
||||
desc = detail['desc']
|
||||
guide = detail['guide']
|
||||
link = detail['link']
|
||||
|
||||
im = daily_template.format(task, achi, desc, guide)
|
||||
im = f'{im}\n{link}' if link else im
|
||||
return im
|
||||
|
||||
|
||||
async def get_achi(achi: str) -> str:
|
||||
_similarity = 0
|
||||
detail = {}
|
||||
if achi in all_achi:
|
||||
detail = all_achi[achi]
|
||||
else:
|
||||
for _achi in all_achi:
|
||||
__achi = ''.join(re.findall('[\u4e00-\u9fa5]', _achi))
|
||||
__achi = __achi.replace('每日委托', '').replace('世界任务', '')
|
||||
similarity = len(set(__achi) & set(achi))
|
||||
if similarity >= len(__achi) / 2:
|
||||
if similarity > _similarity:
|
||||
_similarity = similarity
|
||||
detail = all_achi[_achi]
|
||||
achi = _achi
|
||||
else:
|
||||
if detail == {}:
|
||||
return '暂无该成就...'
|
||||
|
||||
book = detail['book']
|
||||
desc = detail['desc']
|
||||
guide = detail['guide']
|
||||
link = detail['link']
|
||||
|
||||
im = achi_template.format(book, achi, desc)
|
||||
im = f'{im}\n{guide}' if guide else im
|
||||
im = f'{im}\n{link}' if link else im
|
||||
return im
|
20
GenshinUID/genshinuid_achievement/template.py
Normal file
@ -0,0 +1,20 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(__file__).parent
|
||||
with open(path / 'all_achi.json', "r", encoding='UTF-8') as f:
|
||||
all_achi = json.load(f)
|
||||
|
||||
with open(path / 'daily_achi.json', "r", encoding='UTF-8') as f:
|
||||
daily_achi = json.load(f)
|
||||
|
||||
daily_template = '''任务:【{}】
|
||||
成就:【{}】
|
||||
描述:【{}】
|
||||
攻略:【{}】
|
||||
'''
|
||||
|
||||
achi_template = '''合辑:【{}】
|
||||
成就:【{}】
|
||||
描述:【{}】
|
||||
'''
|
@ -1,16 +1,72 @@
|
||||
from typing import Any, Tuple
|
||||
|
||||
from nonebot import on_regex
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import RegexGroup
|
||||
|
||||
from ..config import priority
|
||||
from .get_adv import char_adv, weapon_adv
|
||||
from ..all_import import * # noqa: F401, F403
|
||||
from ..genshinuid_meta import register_menu
|
||||
from ..utils.alias.alias_to_char_name import alias_to_char_name
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
|
||||
get_char_adv = on_regex('([\u4e00-\u9fa5]+)(用什么|能用啥|怎么养)', priority=priority)
|
||||
get_weapon_adv = on_regex(
|
||||
'([\u4e00-\u9fa5]+)(能给谁|给谁用|要给谁|谁能用)', priority=priority
|
||||
)
|
||||
|
||||
|
||||
@sv.on_rex(r'([\u4e00-\u9fa5]+)(用什么|能用啥|怎么养)')
|
||||
async def send_char_adv(bot: HoshinoBot, ev: CQEvent):
|
||||
name = await alias_to_char_name(str(ev['match'].group(1)))
|
||||
@get_char_adv.handle()
|
||||
@handle_exception('建议')
|
||||
@register_menu(
|
||||
'角色配置推荐',
|
||||
'xx用什么',
|
||||
'查询角色武器/圣遗物推荐配置',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'可以查询某角色的武器/圣遗物推荐配置\n'
|
||||
'支持部分角色别名\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(0,148,200)>[角色名]</ft>'
|
||||
'<ft color=(238,120,0)>{用什么|能用啥|怎么养}</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>钟离用什么</ft>\n'
|
||||
'- <ft color=(238,120,0)>公子怎么养</ft>'
|
||||
),
|
||||
)
|
||||
async def send_char_adv(
|
||||
matcher: Matcher, args: Tuple[Any, ...] = RegexGroup()
|
||||
):
|
||||
name = await alias_to_char_name(str(args[0]))
|
||||
im = await char_adv(name)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(im)
|
||||
|
||||
|
||||
@sv.on_rex(r'([\u4e00-\u9fa5]+)(能给谁|给谁用|要给谁|谁能用)')
|
||||
async def send_weapon_adv(bot: HoshinoBot, ev: CQEvent):
|
||||
name = await alias_to_char_name(str(ev['match'].group(1)))
|
||||
@get_weapon_adv.handle()
|
||||
@handle_exception('建议')
|
||||
@register_menu(
|
||||
'装备适用角色',
|
||||
'xx能给谁',
|
||||
'查询某武器/圣遗物能给谁用',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'可以通过武器/圣遗物名反查适用的角色\n'
|
||||
'支持部分别名\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(0,148,200)>[武器名]</ft>'
|
||||
'<ft color=(238,120,0)>{能给谁|给谁用|要给谁|谁能用}</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>四风原典能给谁</ft>\n'
|
||||
'- <ft color=(238,120,0)>千岩给谁用</ft>'
|
||||
),
|
||||
)
|
||||
async def send_weapon_adv(
|
||||
matcher: Matcher, args: Tuple[Any, ...] = RegexGroup()
|
||||
):
|
||||
name = await alias_to_char_name(str(args[0]))
|
||||
im = await weapon_adv(name)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(im)
|
||||
|
@ -1,6 +1,8 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from ..utils.alias.alias_to_char_name import alias_to_char_name
|
||||
|
||||
with open(
|
||||
Path(__file__).parent / 'char_adv_list.json', "r", encoding='UTF-8'
|
||||
) as f:
|
||||
@ -48,7 +50,8 @@ async def weapon_adv(name):
|
||||
return im
|
||||
|
||||
|
||||
async def char_adv(name):
|
||||
async def char_adv(name: str):
|
||||
name = await alias_to_char_name(name)
|
||||
for char, info in adv_lst.items():
|
||||
if name in char:
|
||||
im = [f'「{char}」', '-=-=-=-=-=-=-=-=-=-']
|
||||
|
142
GenshinUID/genshinuid_ann/__init__.py
Normal file
@ -0,0 +1,142 @@
|
||||
import base64
|
||||
import random
|
||||
import asyncio
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot import get_bot, on_command
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot.adapters.ntchat.message import Message
|
||||
from nonebot.adapters.ntchat import (
|
||||
MessageEvent,
|
||||
MessageSegment,
|
||||
TextMessageEvent,
|
||||
)
|
||||
|
||||
from .util import black_ids
|
||||
from ..config import priority
|
||||
from .main import ann, consume_remind
|
||||
from ..utils.nonebot2.rule import FullCommand
|
||||
from ..utils.message.error_reply import UID_HINT
|
||||
from ..utils.db_operation.db_operation import select_db
|
||||
from ..genshinuid_config.default_config import string_config
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
from .ann_card import sub_ann, unsub_ann, ann_list_card, ann_detail_card
|
||||
|
||||
update_ann_scheduler = scheduler
|
||||
get_ann_info = on_command('原神公告', priority=priority)
|
||||
reg_ann = on_command(
|
||||
'订阅原神公告', priority=priority, rule=FullCommand(), permission=SUPERUSER
|
||||
)
|
||||
unreg_ann = on_command(
|
||||
'取消订阅原神公告',
|
||||
aliases={'取消原神公告', '退订原神公告'},
|
||||
priority=priority,
|
||||
rule=FullCommand(),
|
||||
permission=SUPERUSER,
|
||||
)
|
||||
consume_ann = on_command('清除原神公告红点', priority=priority, rule=FullCommand())
|
||||
|
||||
|
||||
@get_ann_info.handle()
|
||||
@handle_exception('原神公告', '获取/发送原神公告失败')
|
||||
async def send_ann_pic(
|
||||
matcher: Matcher,
|
||||
args: Message = CommandArg(),
|
||||
):
|
||||
ann_id = str(args).replace(' ', '').replace('#', '')
|
||||
|
||||
if not ann_id:
|
||||
img = await ann_list_card()
|
||||
await matcher.finish(MessageSegment.image(img))
|
||||
|
||||
if not ann_id.isdigit():
|
||||
raise Exception("公告ID不正确")
|
||||
|
||||
img = await ann_detail_card(int(ann_id))
|
||||
await matcher.finish(MessageSegment.image(img))
|
||||
|
||||
|
||||
@reg_ann.handle()
|
||||
@handle_exception('设置原神公告', '设置原神公告失败')
|
||||
async def send_reg_ann(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
await matcher.finish(sub_ann(event.room_wxid))
|
||||
|
||||
|
||||
@unreg_ann.handle()
|
||||
@handle_exception('取消原神公告', '取消设置原神公告失败')
|
||||
async def send_unreg_ann(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
await matcher.finish(unsub_ann(event.room_wxid))
|
||||
|
||||
|
||||
@consume_ann.handle()
|
||||
@handle_exception('取消原神公告红点', '取消红点失败')
|
||||
async def send_consume_ann(
|
||||
event: MessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
qid = event.from_wxid
|
||||
uid = await select_db(qid, mode='uid')
|
||||
uid = str(uid)
|
||||
if '未找到绑定的UID' in uid:
|
||||
await matcher.finish(UID_HINT)
|
||||
await matcher.finish(await consume_remind(uid))
|
||||
|
||||
|
||||
@update_ann_scheduler.scheduled_job('cron', minute=10)
|
||||
async def check_ann():
|
||||
await check_ann_state()
|
||||
|
||||
|
||||
async def check_ann_state():
|
||||
logger.info('[原神公告] 定时任务: 原神公告查询..')
|
||||
ids = string_config.get_config('Ann_Ids')
|
||||
sub_list = string_config.get_config('Ann_Groups')
|
||||
if not sub_list:
|
||||
logger.info('没有群订阅, 取消获取数据')
|
||||
return
|
||||
if not ids:
|
||||
ids = await ann().get_ann_ids()
|
||||
if not ids:
|
||||
raise Exception('获取原神公告ID列表错误,请检查接口')
|
||||
string_config.set_config('Ann_Ids', ids)
|
||||
logger.info('初始成功, 将在下个轮询中更新.')
|
||||
return
|
||||
new_ids = await ann().get_ann_ids()
|
||||
|
||||
new_ann = set(ids) ^ set(new_ids)
|
||||
if not new_ann:
|
||||
logger.info('[原神公告] 没有最新公告')
|
||||
return
|
||||
|
||||
for ann_id in new_ann:
|
||||
if ann_id in black_ids:
|
||||
continue
|
||||
try:
|
||||
img = await ann_detail_card(ann_id) # 防止抛出异常报错
|
||||
bot = get_bot()
|
||||
b64img = base64.b64encode(img)
|
||||
|
||||
for group in sub_list:
|
||||
try:
|
||||
await bot.call_api(
|
||||
api='send_image',
|
||||
to_wxid=str(group),
|
||||
file_path="base64://" + b64img.decode(),
|
||||
)
|
||||
await asyncio.sleep(random.uniform(1, 3))
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
logger.info('[原神公告] 推送完毕, 更新数据库')
|
||||
string_config.set_config('Ann_Ids', new_ids)
|
216
GenshinUID/genshinuid_ann/ann_card.py
Normal file
@ -0,0 +1,216 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from PIL import Image, ImageOps, ImageDraw
|
||||
|
||||
from .main import ann
|
||||
from .util import filter_list
|
||||
from ..genshinuid_config.default_config import string_config
|
||||
from ..utils.draw_image_tools.send_image_tool import convert_img
|
||||
from ..utils.genshin_fonts.genshin_fonts import gs_font_18, gs_font_26
|
||||
from ..utils.draw_image_tools.draw_image_tool import (
|
||||
get_pic,
|
||||
easy_paste,
|
||||
draw_text_by_line,
|
||||
easy_alpha_composite,
|
||||
)
|
||||
|
||||
assets_dir = Path(__file__).parent / 'assets'
|
||||
|
||||
list_head = Image.open(assets_dir / 'list.png')
|
||||
list_item = (
|
||||
Image.open(assets_dir / 'item.png').resize((384, 96)).convert('RGBA')
|
||||
)
|
||||
|
||||
|
||||
async def ann_list_card() -> bytes:
|
||||
ann_list = await ann().get_ann_list()
|
||||
if not ann_list:
|
||||
raise Exception('获取游戏公告失败,请检查接口是否正常')
|
||||
|
||||
height_len = max(len(ann_list[0]['list']), len(ann_list[1]['list']))
|
||||
|
||||
bg = Image.new(
|
||||
'RGBA',
|
||||
(
|
||||
list_head.width,
|
||||
list_head.height + list_item.height * height_len + 20 + 30,
|
||||
),
|
||||
'#f9f6f2',
|
||||
)
|
||||
easy_paste(bg, list_head, (0, 0))
|
||||
|
||||
for data in ann_list:
|
||||
x = 45
|
||||
if data['type_id'] == 1:
|
||||
x = 472
|
||||
|
||||
for index, ann_info in enumerate(data['list']):
|
||||
new_item = list_item.copy()
|
||||
subtitle = ann_info['subtitle']
|
||||
draw_text_by_line(
|
||||
new_item,
|
||||
(0, 30 - (len(subtitle) > 10 and 10 or 0)),
|
||||
subtitle,
|
||||
gs_font_26,
|
||||
'#3b4354',
|
||||
250,
|
||||
True,
|
||||
)
|
||||
|
||||
draw_text_by_line(
|
||||
new_item,
|
||||
(new_item.width - 80, 10),
|
||||
str(ann_info['ann_id']),
|
||||
gs_font_18,
|
||||
'#3b4354',
|
||||
100,
|
||||
)
|
||||
|
||||
bg = easy_alpha_composite(
|
||||
bg, new_item, (x, list_head.height + (index * new_item.height))
|
||||
)
|
||||
|
||||
tip = '*可以使用 原神公告#0000(右上角ID) 来查看详细内容, 例子: 原神公告#2434'
|
||||
draw_text_by_line(
|
||||
bg, (0, bg.height - 35), tip, gs_font_18, '#767779', 1000, True
|
||||
)
|
||||
|
||||
return await convert_img(bg)
|
||||
|
||||
|
||||
async def ann_detail_card(ann_id):
|
||||
ann_list = await ann().get_ann_content()
|
||||
if not ann_list:
|
||||
raise Exception('获取游戏公告失败,请检查接口是否正常')
|
||||
content = filter_list(ann_list, lambda x: x['ann_id'] == ann_id)
|
||||
if not content:
|
||||
raise Exception('没有找到对应的公告ID :%s' % ann_id)
|
||||
soup = BeautifulSoup(content[0]['content'], 'lxml')
|
||||
banner = content[0]['banner']
|
||||
ann_img = banner if banner else ''
|
||||
for a in soup.find_all('a'):
|
||||
a.string = ''
|
||||
|
||||
for img in soup.find_all('img'):
|
||||
img.string = img.get('src')
|
||||
|
||||
msg_list = [ann_img]
|
||||
msg_list += [
|
||||
BeautifulSoup(x.get_text('').replace('<<', ''), 'lxml').get_text()
|
||||
+ '\n'
|
||||
for x in soup.find_all('p')
|
||||
]
|
||||
|
||||
drow_height = 0
|
||||
for msg in msg_list:
|
||||
if msg.strip().endswith(('jpg', 'png')):
|
||||
_msg = re.search(r'(https://.*[png|jpg])', msg)
|
||||
if _msg:
|
||||
msg = _msg.group(0)
|
||||
img = await get_pic(msg.strip())
|
||||
img_height = img.size[1]
|
||||
if img.width > 1080:
|
||||
img_height = int(img.height * 0.6)
|
||||
drow_height += img_height + 40
|
||||
else:
|
||||
(
|
||||
x_drow_duanluo,
|
||||
x_drow_note_height,
|
||||
x_drow_line_height,
|
||||
x_drow_height,
|
||||
) = split_text(msg)
|
||||
drow_height += x_drow_height
|
||||
|
||||
im = Image.new('RGB', (1080, drow_height), '#f9f6f2')
|
||||
draw = ImageDraw.Draw(im)
|
||||
# 左上角开始
|
||||
x, y = 0, 0
|
||||
for msg in msg_list:
|
||||
if msg.strip().endswith(('jpg', 'png')):
|
||||
_msg = re.search(r'(https://.*[png|jpg])', msg)
|
||||
if _msg:
|
||||
msg = _msg.group(0)
|
||||
img = await get_pic(msg.strip())
|
||||
if img.width > im.width:
|
||||
img = img.resize((int(img.width * 0.6), int(img.height * 0.6)))
|
||||
easy_paste(im, img, (0, y))
|
||||
y += img.size[1] + 40
|
||||
else:
|
||||
(
|
||||
drow_duanluo,
|
||||
drow_note_height,
|
||||
drow_line_height,
|
||||
drow_height,
|
||||
) = split_text(msg)
|
||||
for duanluo, line_count in drow_duanluo:
|
||||
draw.text((x, y), duanluo, fill=(0, 0, 0), font=gs_font_26)
|
||||
y += drow_line_height * line_count
|
||||
|
||||
_x, _y = gs_font_26.getsize('囗')
|
||||
padding = (_x, _y, _x, _y)
|
||||
im = ImageOps.expand(im, padding, '#f9f6f2')
|
||||
|
||||
return await convert_img(im)
|
||||
|
||||
|
||||
def split_text(content: str):
|
||||
# 按规定宽度分组
|
||||
max_line_height, total_lines = 0, 0
|
||||
allText = []
|
||||
for text in content.split('\n'):
|
||||
duanluo, line_height, line_count = get_duanluo(text)
|
||||
max_line_height = max(line_height, max_line_height)
|
||||
total_lines += line_count
|
||||
allText.append((duanluo, line_count))
|
||||
line_height = max_line_height
|
||||
total_height = total_lines * line_height
|
||||
drow_height = total_lines * line_height
|
||||
return allText, total_height, line_height, drow_height
|
||||
|
||||
|
||||
def get_duanluo(text: str):
|
||||
txt = Image.new('RGBA', (600, 800), (255, 255, 255, 0))
|
||||
draw = ImageDraw.Draw(txt)
|
||||
# 所有文字的段落
|
||||
duanluo = ''
|
||||
max_width = 1080
|
||||
# 宽度总和
|
||||
sum_width = 0
|
||||
# 几行
|
||||
line_count = 1
|
||||
# 行高
|
||||
line_height = 0
|
||||
for char in text:
|
||||
width, height = draw.textsize(char, gs_font_26)
|
||||
sum_width += width
|
||||
if sum_width > max_width: # 超过预设宽度就修改段落 以及当前行数
|
||||
line_count += 1
|
||||
sum_width = 0
|
||||
duanluo += '\n'
|
||||
duanluo += char
|
||||
line_height = max(height, line_height)
|
||||
if not duanluo.endswith('\n'):
|
||||
duanluo += '\n'
|
||||
return duanluo, line_height, line_count
|
||||
|
||||
|
||||
def sub_ann(group):
|
||||
groups = string_config.get_config('Ann_Groups')
|
||||
if group in groups:
|
||||
return '已经订阅了'
|
||||
else:
|
||||
groups.append(group)
|
||||
string_config.set_config('Ann_Groups', groups)
|
||||
return '成功订阅原神公告'
|
||||
|
||||
|
||||
def unsub_ann(group):
|
||||
groups = string_config.get_config('Ann_Groups')
|
||||
if group in groups:
|
||||
groups.remove(group)
|
||||
string_config.set_config('Ann_Groups', groups)
|
||||
return '成功取消订阅原神公告'
|
||||
else:
|
||||
return '已经不在订阅中了'
|
BIN
GenshinUID/genshinuid_ann/assets/item.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
GenshinUID/genshinuid_ann/assets/list.png
Normal file
After Width: | Height: | Size: 26 KiB |
120
GenshinUID/genshinuid_ann/main.py
Normal file
@ -0,0 +1,120 @@
|
||||
import httpx
|
||||
|
||||
from .util import _Dict, black_ids, filter_list, cache_request_json
|
||||
|
||||
# https://webstatic.mihoyo.com/hk4e/announcement/index.html?auth_appid=announcement&authkey_ver=1&bundle_id=hk4e_cn&channel_id=1&game=hk4e&game_biz=hk4e_cn&lang=zh-cn&level=57&platform=pc®ion=cn_gf01&sdk_presentation_style=fullscreen&sdk_screen_transparent=true&sign_type=2&uid=105293904#/
|
||||
api_url = 'https://hk4e-api-static.mihoyo.com/common/hk4e_cn/announcement/api/'
|
||||
api_params = (
|
||||
'?game=hk4e'
|
||||
'&game_biz=hk4e_cn'
|
||||
'&lang=zh-cn'
|
||||
'&bundle_id=hk4e_cn'
|
||||
'&level=57'
|
||||
'&platform={platform}'
|
||||
'®ion={region}'
|
||||
'&uid={uid}'
|
||||
)
|
||||
ann_content_url = f'{api_url}getAnnContent{api_params}'
|
||||
ann_list_url = f'{api_url}getAnnList{api_params}'
|
||||
|
||||
|
||||
class ann:
|
||||
ann_list_data = []
|
||||
ann_content_data = []
|
||||
today = 0
|
||||
|
||||
def __init__(self, platform='pc', uid='114514', region='cn_gf01'):
|
||||
# self.today = datetime.datetime.fromtimestamp(
|
||||
# time.mktime(datetime.date.today().timetuple()))
|
||||
self.platform = platform
|
||||
self.uid = uid
|
||||
self.region = region
|
||||
|
||||
async def get_ann_content(self):
|
||||
url = ann_content_url.format(
|
||||
platform=self.platform, uid=self.uid, region=self.region
|
||||
)
|
||||
res = await cache_request_json(url=url)
|
||||
if res.retcode == 0:
|
||||
self.ann_content_data = res.data.list
|
||||
return self.ann_content_data
|
||||
|
||||
async def get_ann_list(self):
|
||||
url = ann_list_url.format(
|
||||
platform=self.platform, uid=self.uid, region=self.region
|
||||
)
|
||||
res = await cache_request_json(url=url)
|
||||
if res.retcode == 0:
|
||||
result = []
|
||||
for data in res.data.list:
|
||||
data_list = [
|
||||
x for x in data['list'] if not x['ann_id'] in black_ids
|
||||
]
|
||||
data['list'] = data_list
|
||||
result.append(data)
|
||||
|
||||
self.ann_list_data = result
|
||||
return self.ann_list_data
|
||||
|
||||
async def get_ann_ids(self):
|
||||
await self.get_ann_list()
|
||||
if not self.ann_list_data:
|
||||
return []
|
||||
ids = []
|
||||
for label in self.ann_list_data:
|
||||
ids += [x['ann_id'] for x in label['list']]
|
||||
return ids
|
||||
|
||||
|
||||
async def get_consume_remind_ann_ids(region, platform, uid):
|
||||
ann_list = await ann(
|
||||
platform=platform, uid=uid, region=region
|
||||
).get_ann_list()
|
||||
ids = []
|
||||
for label in ann_list:
|
||||
ids += filter_list(label.list, lambda x: x.remind == 1)
|
||||
return [x.ann_id for x in ids]
|
||||
|
||||
|
||||
async def consume_remind(uid):
|
||||
region = 'cn_gf01'
|
||||
if uid[0] == "5":
|
||||
region = 'cn_qd01'
|
||||
platform = ['pc']
|
||||
ids = []
|
||||
for p in platform:
|
||||
ids += await get_consume_remind_ann_ids(region, p, uid)
|
||||
|
||||
ids = set(ids)
|
||||
msg = f'取消公告红点完毕! 一共取消了{len(ids)}个'
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
base_url="https://hk4e-api.mihoyo.com/common/hk4e_cn/announcement/api"
|
||||
) as client:
|
||||
for ann_id in ids:
|
||||
for p in platform:
|
||||
res = await client.get(
|
||||
"/consumeRemind",
|
||||
timeout=10,
|
||||
params={
|
||||
'ann_id': ann_id,
|
||||
'auth_appid': 'announcement',
|
||||
'authkey_ver': '1',
|
||||
'bundle_id': 'hk4e_cn',
|
||||
'channel_id': '1',
|
||||
'game': 'hk4e',
|
||||
'game_biz': 'hk4e_cn',
|
||||
'lang': 'zh-cn',
|
||||
'level': '57',
|
||||
'platform': p,
|
||||
'region': region,
|
||||
'sdk_presentation_style': 'fullscreen',
|
||||
'sdk_screen_transparent': 'true',
|
||||
'sign_type': '2',
|
||||
'uid': uid,
|
||||
},
|
||||
)
|
||||
res = res.json(object_hook=_Dict)
|
||||
if res.retcode != 0:
|
||||
msg += '\n %s 失败,原因:%s' % (ann_id, res.message)
|
||||
return msg
|
65
GenshinUID/genshinuid_ann/util.py
Normal file
@ -0,0 +1,65 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
import inspect
|
||||
import datetime
|
||||
import functools
|
||||
from typing import Dict, Optional, TypedDict
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class _Dict(dict):
|
||||
__setattr__ = dict.__setitem__ # type: ignore
|
||||
__getattr__ = dict.__getitem__
|
||||
|
||||
|
||||
class _CacheData(TypedDict):
|
||||
time: Optional[datetime.datetime]
|
||||
value: Optional[int]
|
||||
|
||||
|
||||
def filter_list(plist, func):
|
||||
return list(filter(func, plist))
|
||||
|
||||
|
||||
def cache(ttl=datetime.timedelta(hours=1), **kwargs):
|
||||
def wrap(func):
|
||||
cache_data: Dict[str, _CacheData] = {}
|
||||
|
||||
@functools.wraps(func)
|
||||
async def wrapped(*args, **kw):
|
||||
nonlocal cache_data
|
||||
bound = inspect.signature(func).bind(*args, **kw)
|
||||
bound.apply_defaults()
|
||||
ins_key = '|'.join(
|
||||
['%s_%s' % (k, v) for k, v in bound.arguments.items()]
|
||||
)
|
||||
default_data: _CacheData = {
|
||||
'time': None,
|
||||
'value': None,
|
||||
}
|
||||
data = cache_data.get(ins_key, default_data)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
if not data['time'] or now - data['time'] > ttl:
|
||||
try:
|
||||
data['value'] = await func(*args, **kw)
|
||||
data['time'] = now
|
||||
cache_data[ins_key] = data
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
return data['value']
|
||||
|
||||
return wrapped
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
@cache(ttl=datetime.timedelta(minutes=30), arg_key='url')
|
||||
async def cache_request_json(url):
|
||||
async with httpx.AsyncClient() as client:
|
||||
res = await client.get(url, timeout=10)
|
||||
return res.json(object_hook=_Dict)
|
||||
|
||||
|
||||
black_ids = [762, 422, 423, 1263, 495, 1957, 2522, 2388, 2516, 2476]
|
@ -1,10 +1,18 @@
|
||||
import random
|
||||
import asyncio
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot.adapters.ntchat import Bot, TextMessageEvent
|
||||
|
||||
from ..config import priority
|
||||
from .backup_data import data_backup
|
||||
from ..all_import import * # noqa: F403, F401
|
||||
from ..genshinuid_meta import register_menu
|
||||
from ..utils.nonebot2.rule import FullCommand
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
from ..utils.db_operation.db_cache_and_check import check_db, check_stoken_db
|
||||
from ..utils.db_operation.db_operation import delete_cookies, get_all_push_list
|
||||
from ..utils.message.get_cqhttp_data import (
|
||||
@ -12,35 +20,75 @@ from ..utils.message.get_cqhttp_data import (
|
||||
get_group_member_list,
|
||||
)
|
||||
|
||||
backup = on_command(
|
||||
'gs清除缓存', rule=FullCommand(), priority=priority, permission=SUPERUSER
|
||||
)
|
||||
check = on_command(
|
||||
'校验全部Cookies', rule=FullCommand(), priority=priority, permission=SUPERUSER
|
||||
)
|
||||
check_stoken = on_command(
|
||||
'校验全部Stoken', rule=FullCommand(), priority=priority, permission=SUPERUSER
|
||||
)
|
||||
remove_invalid_user = on_command(
|
||||
'清除无效用户', rule=FullCommand(), priority=priority, permission=SUPERUSER
|
||||
)
|
||||
|
||||
@sv.scheduled_job('cron', hour=0)
|
||||
backup_scheduler = scheduler
|
||||
|
||||
|
||||
@backup_scheduler.scheduled_job('cron', hour=0)
|
||||
async def daily_refresh_charData():
|
||||
await data_backup()
|
||||
|
||||
|
||||
@sv.on_fullmatch('gs清除缓存')
|
||||
@backup.handle()
|
||||
@handle_exception('清除缓存', '清除缓存错误')
|
||||
@register_menu(
|
||||
'清除缓存',
|
||||
'清除缓存',
|
||||
'清除插件产生的缓存数据',
|
||||
trigger_method='超级用户指令',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'备份一份插件数据库后清除插件产生的文件与数据库缓存\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>清除缓存</ft>'
|
||||
),
|
||||
)
|
||||
async def send_backup_msg(
|
||||
bot: HoshinoBot,
|
||||
ev: CQEvent,
|
||||
bot: Bot,
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
if not await SUPERUSER(bot, event):
|
||||
return
|
||||
if qid not in bot.config.SUPERUSERS:
|
||||
return
|
||||
|
||||
await data_backup()
|
||||
await bot.send(ev, f'操作成功完成!')
|
||||
await matcher.finish('操作成功完成!')
|
||||
|
||||
|
||||
@sv.on_fullmatch('清除无效用户')
|
||||
async def send_remove_invalid_user_msg(bot: HoshinoBot, ev: CQEvent):
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
if qid not in bot.config.SUPERUSERS:
|
||||
@remove_invalid_user.handle()
|
||||
@handle_exception('清除无效用户', '清除无效用户错误')
|
||||
@register_menu(
|
||||
'清除无效用户',
|
||||
'清除无效用户',
|
||||
'清除非好友或非推送群成员的数据',
|
||||
trigger_method='超级用户指令',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'从数据库中删除掉 开启了私聊推送但不是Bot好友的用户 '
|
||||
'以及 开启了群聊推送但不在推送目标群的用户 的数据\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>清除无效用户</ft>'
|
||||
),
|
||||
)
|
||||
async def send_remove_invalid_user_msg(
|
||||
bot: Bot,
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
if not await SUPERUSER(bot, event):
|
||||
return
|
||||
im_list = []
|
||||
invalid_user = {}
|
||||
@ -62,40 +110,78 @@ async def send_remove_invalid_user_msg(bot: HoshinoBot, ev: CQEvent):
|
||||
for uid in invalid_uid_list:
|
||||
im_list.append(await delete_cookies(str(uid)))
|
||||
logger.warning(f'无效UID已被删除: {uid}')
|
||||
await bot.send(ev, f'已清理失效用户{len(im_list)}个!')
|
||||
await matcher.finish(f'已清理失效用户{len(im_list)}个!')
|
||||
|
||||
|
||||
# 群聊内 校验Cookies 是否正常的功能,不正常自动删掉
|
||||
@sv.on_fullmatch('校验全部Cookies')
|
||||
async def send_check_cookies(bot: HoshinoBot, ev: CQEvent):
|
||||
@check.handle()
|
||||
@handle_exception('Cookie校验', 'Cookie校验错误')
|
||||
@register_menu(
|
||||
'校验全部Cookies',
|
||||
'校验全部Cookies',
|
||||
'校验数据库内所有Cookies是否正常',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'校验数据库内所有Cookies是否正常,不正常的会自动删除\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>校验全部Cookies</ft>\n'
|
||||
'注意<ft color=(238,120,0)>Cookies</ft>的'
|
||||
'<ft color=(238,120,0)>C</ft>为大写'
|
||||
),
|
||||
)
|
||||
async def send_check_cookie(bot: Bot, matcher: Matcher):
|
||||
raw_mes = await check_db()
|
||||
im = raw_mes[0]
|
||||
await bot.send(ev, im)
|
||||
await matcher.send(im)
|
||||
for i in raw_mes[1]:
|
||||
await bot.send_private_msg(
|
||||
user_id=i[0],
|
||||
message=(
|
||||
'您绑定的Cookies(uid{})已失效,以下功能将会受到影响:\n'
|
||||
'查看完整信息列表\n查看深渊配队\n自动签到/当前状态/每月统计\n'
|
||||
'请及时重新绑定Cookies并重新开关相应功能。'
|
||||
).format(i[1]),
|
||||
await bot.call_api(
|
||||
api='send_private_msg',
|
||||
**{
|
||||
'user_id': i[0],
|
||||
'message': (
|
||||
'您绑定的Cookies(uid{})已失效,以下功能将会受到影响:\n'
|
||||
'查看完整信息列表\n查看深渊配队\n自动签到/当前状态/每月统计\n'
|
||||
'请及时重新绑定Cookies并重新开关相应功能。'
|
||||
).format(i[1]),
|
||||
},
|
||||
)
|
||||
await asyncio.sleep(3 + random.randint(1, 3))
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
# 群聊内 校验Stoken 是否正常的功能,不正常自动删掉
|
||||
@sv.on_fullmatch('校验全部Stoken')
|
||||
async def send_check_stoken(bot: HoshinoBot, ev: CQEvent):
|
||||
@check_stoken.handle()
|
||||
@handle_exception('Stoken校验', 'Stoken校验错误')
|
||||
@register_menu(
|
||||
'校验全部Stoken',
|
||||
'校验全部Stoken',
|
||||
'校验数据库内所有Stoken是否正常',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'校验数据库内所有Stoken是否正常,不正常的会自动删除\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>校验全部Stoken</ft>\n'
|
||||
'注意<ft color=(238,120,0)>Stoken</ft>的<ft color=(238,120,0)>S</ft>为大写'
|
||||
),
|
||||
)
|
||||
async def send_check_stoken(bot: Bot, matcher: Matcher):
|
||||
raw_mes = await check_stoken_db()
|
||||
im = raw_mes[0]
|
||||
await bot.send(ev, im)
|
||||
await matcher.send(im)
|
||||
for i in raw_mes[1]:
|
||||
await bot.send_private_msg(
|
||||
user_id=i[0],
|
||||
message=(
|
||||
'您绑定的Stoken(uid{})已失效,以下功能将会受到影响:\n'
|
||||
'gs开启自动米游币,开始获取米游币。\n'
|
||||
'重新添加后需要重新开启自动米游币。'
|
||||
).format(i[1]),
|
||||
await bot.call_api(
|
||||
api='send_private_msg',
|
||||
**{
|
||||
'user_id': i[0],
|
||||
'message': (
|
||||
'您绑定的Stoken(uid{})已失效,以下功能将会受到影响:\n'
|
||||
'gs开启自动米游币,开始获取米游币。\n'
|
||||
'重新添加后需要重新开启自动米游币。'
|
||||
).format(i[1]),
|
||||
},
|
||||
)
|
||||
await asyncio.sleep(3 + random.randint(1, 3))
|
||||
|
||||
await matcher.finish()
|
||||
|
@ -1,51 +1,99 @@
|
||||
from ..all_import import * # noqa: F403,F401
|
||||
from .draw_collection_card import draw_collection_img
|
||||
from ..utils.db_operation.db_operation import select_db
|
||||
from ..utils.message.get_image_and_at import ImageAndAt
|
||||
from ..utils.message.error_reply import * # noqa: F403,F401
|
||||
from ..utils.mhy_api.convert_mysid_to_uid import convert_mysid
|
||||
from nonebot import on_command
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.adapters.ntchat.message import Message
|
||||
from nonebot.adapters.ntchat import MessageSegment, TextMessageEvent
|
||||
|
||||
from ..genshinuid_meta import register_menu
|
||||
from ..utils.data_convert.get_uid import get_uid
|
||||
from ..utils.message.error_reply import UID_HINT
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
from .draw_collection_card import draw_explora_img, draw_collection_img
|
||||
|
||||
get_collection_info = on_command('查询收集', aliases={'收集', 'sj'}, block=True)
|
||||
get_explora_info = on_command('查询探索', aliases={'探索', 'ts'}, block=True)
|
||||
|
||||
|
||||
@sv.on_rex(
|
||||
r'^(\[CQ:at,qq=[0-9]+\])?( )?'
|
||||
r'(uid|查询|mys)?([0-9]+)?'
|
||||
r'(收集|宝箱|sj|bx)'
|
||||
r'(\[CQ:at,qq=[0-9]+\])?( )?$',
|
||||
@get_collection_info.handle()
|
||||
@handle_exception('查询收集信息')
|
||||
@register_menu(
|
||||
'查询收集信息',
|
||||
'查询(@某人)收集',
|
||||
'查询你的或者指定人的宝箱收集度',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'可以用来查看你的或者指定人的宝箱收集度\n'
|
||||
'可以在命令文本后带一张图以自定义背景图\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>{查询</ft>'
|
||||
'<ft color=(125,125,125)>(@某人)</ft>'
|
||||
'<ft color=(238,120,0)>|uid</ft><ft color=(0,148,200)>xx</ft>'
|
||||
'<ft color=(238,120,0)>|mys</ft><ft color=(0,148,200)>xx</ft>'
|
||||
'<ft color=(238,120,0)>}</ft>'
|
||||
'<ft color=(238,120,0)>{收集|宝箱|sj|bx}</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>查询收集</ft>\n'
|
||||
'- <ft color=(238,120,0)>uid123456789宝箱</ft>\n'
|
||||
'- <ft color=(238,120,0)>查询</ft><ft color=(0,123,67)>@无疑Wuyi'
|
||||
'</ft> <ft color=(238,120,0)>bx</ft>'
|
||||
),
|
||||
)
|
||||
async def send_collection_info(bot: HoshinoBot, ev: CQEvent):
|
||||
args = ev['match'].groups()
|
||||
async def send_collection_info(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
args: Message = CommandArg(),
|
||||
):
|
||||
logger.info('开始执行[查询收集信息]')
|
||||
logger.info('[查询收集信息]参数: {}'.format(args))
|
||||
at = re.search(r'\[CQ:at,qq=(\d*)]', str(ev.message))
|
||||
|
||||
if at:
|
||||
qid = int(at.group(1))
|
||||
raw_mes = args.extract_plain_text().strip()
|
||||
if event.at_user_list:
|
||||
qid = event.at_user_list[0]
|
||||
else:
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
qid = event.from_wxid
|
||||
|
||||
if args[2] != 'mys':
|
||||
if args[3] is None:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
uid = str(uid)
|
||||
elif len(args[3]) != 9:
|
||||
return
|
||||
else:
|
||||
uid = args[3]
|
||||
else:
|
||||
uid = await convert_mysid(args[3])
|
||||
uid = await get_uid(qid, raw_mes)
|
||||
logger.info('[查询收集信息]uid: {}'.format(uid))
|
||||
|
||||
if '未找到绑定的UID' in uid:
|
||||
await bot.send(ev, UID_HINT)
|
||||
await matcher.finish(UID_HINT)
|
||||
|
||||
im = await draw_collection_img(uid)
|
||||
im = await draw_collection_img(qid, uid)
|
||||
if isinstance(im, str):
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, bytes):
|
||||
im = await convert_img(im)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await bot.send(ev, '发生了未知错误,请联系管理员检查后台输出!')
|
||||
await matcher.finish('发生了未知错误,请联系管理员检查后台输出!')
|
||||
|
||||
|
||||
@get_explora_info.handle()
|
||||
@handle_exception('查询探索信息')
|
||||
async def send_explora_info(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
args: Message = CommandArg(),
|
||||
):
|
||||
logger.info('开始执行[查询探索信息]')
|
||||
logger.info('[查询探索信息]参数: {}'.format(args))
|
||||
raw_mes = args.extract_plain_text().strip()
|
||||
if event.at_user_list:
|
||||
qid = event.at_user_list[0]
|
||||
else:
|
||||
qid = event.from_wxid
|
||||
|
||||
uid = await get_uid(qid, raw_mes)
|
||||
logger.info('[查询探索信息]uid: {}'.format(uid))
|
||||
|
||||
if '未找到绑定的UID' in uid:
|
||||
await matcher.finish(UID_HINT)
|
||||
|
||||
im = await draw_explora_img(qid, uid)
|
||||
if isinstance(im, str):
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, bytes):
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await matcher.finish('发生了未知错误,请联系管理员检查后台输出!')
|
||||
|
@ -1,45 +1,70 @@
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
from typing import Dict, Tuple, Union, Literal
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from ..utils.get_cookies.get_cookies import GetCookies
|
||||
from ..utils.enka_api.map.GS_MAP_PATH import avatarId2Name
|
||||
from ..utils.draw_image_tools.send_image_tool import convert_img
|
||||
from ..utils.draw_image_tools.draw_image_tool import get_simple_bg
|
||||
from ..utils.genshin_fonts.genshin_fonts import genshin_font_origin
|
||||
from ..utils.genshin_fonts.genshin_fonts import gs_font_30, gs_font_40
|
||||
from ..utils.draw_image_tools.draw_image_tool import (
|
||||
draw_bar,
|
||||
get_color_bg,
|
||||
get_qq_avatar,
|
||||
draw_pic_with_ring,
|
||||
)
|
||||
|
||||
TEXT_PATH = Path(__file__).parent / 'texture2D'
|
||||
collection_fg_pic = Image.open(TEXT_PATH / 'collection_fg.png')
|
||||
|
||||
text_color = (31, 32, 26)
|
||||
gs_font_23 = genshin_font_origin(23)
|
||||
gs_font_26 = genshin_font_origin(26)
|
||||
|
||||
based_w = 500
|
||||
based_h = 750
|
||||
white_overlay = Image.new('RGBA', (based_w, based_h), (255, 255, 255, 222))
|
||||
first_color = (29, 29, 29)
|
||||
brown_color = (41, 25, 0)
|
||||
red_color = (255, 66, 66)
|
||||
green_color = (74, 189, 119)
|
||||
|
||||
max_data = {
|
||||
'成就': 815,
|
||||
'华丽的宝箱': 173,
|
||||
'珍贵的宝箱': 454,
|
||||
'精致的宝箱': 1516,
|
||||
'普通的宝箱': 2365,
|
||||
'成就': 945,
|
||||
'华丽的宝箱': 192,
|
||||
'珍贵的宝箱': 510,
|
||||
'精致的宝箱': 1639,
|
||||
'普通的宝箱': 2690,
|
||||
'奇馈宝箱': 161,
|
||||
'解锁传送点': 304,
|
||||
'解锁秘境': 51,
|
||||
}
|
||||
|
||||
award_data = {
|
||||
'成就': 5,
|
||||
'华丽的宝箱': 10,
|
||||
'珍贵的宝箱': 8,
|
||||
'精致的宝箱': 3,
|
||||
'普通的宝箱': 1,
|
||||
'奇馈宝箱': 2,
|
||||
'解锁传送点': 0,
|
||||
'解锁秘境': 0,
|
||||
}
|
||||
|
||||
expmax_data = {
|
||||
'获得角色数': len(avatarId2Name) - 2,
|
||||
'风神瞳': 66,
|
||||
'岩神瞳': 131,
|
||||
'雷神瞳': 181,
|
||||
'草神瞳': 271,
|
||||
}
|
||||
|
||||
|
||||
async def dataToDataStr(max: int, my: int) -> List:
|
||||
return [
|
||||
str('{:.2f}'.format(100 * (my / max)))
|
||||
+ '% | '
|
||||
+ str(my)
|
||||
+ '/'
|
||||
+ str(max),
|
||||
float('{:.2f}'.format(my / max)) * 450,
|
||||
]
|
||||
async def draw_collection_img(
|
||||
qid: Union[str, int], uid: str
|
||||
) -> Union[str, bytes]:
|
||||
return await draw_base_img(qid, uid, '收集')
|
||||
|
||||
|
||||
async def draw_collection_img(uid: str) -> Union[bytes, str]:
|
||||
async def draw_explora_img(
|
||||
qid: Union[str, int], uid: str
|
||||
) -> Union[str, bytes]:
|
||||
return await draw_base_img(qid, uid, '探索')
|
||||
|
||||
|
||||
async def get_base_data(uid: str) -> Union[str, Dict]:
|
||||
# 获取Cookies
|
||||
data_def = GetCookies()
|
||||
retcode = await data_def.get_useable_cookies(uid)
|
||||
@ -53,139 +78,143 @@ async def draw_collection_img(uid: str) -> Union[bytes, str]:
|
||||
if raw_data:
|
||||
raw_data = raw_data['data']
|
||||
else:
|
||||
return '获取数据为空!'
|
||||
return '数据为空~'
|
||||
|
||||
# 获取背景图片各项参数
|
||||
img = await get_simple_bg(based_w, based_h)
|
||||
img.paste(white_overlay, (0, 0), white_overlay)
|
||||
img.paste(collection_fg_pic, (0, 0), collection_fg_pic)
|
||||
text_draw = ImageDraw.Draw(img)
|
||||
return raw_data
|
||||
|
||||
|
||||
async def get_explore_data(
|
||||
uid: str,
|
||||
) -> Union[str, Tuple[Dict[str, float], Dict[str, str], str, str, str]]:
|
||||
raw_data = await get_base_data(uid)
|
||||
if isinstance(raw_data, str):
|
||||
return raw_data
|
||||
|
||||
# 处理数据
|
||||
achieve = raw_data['stats']['achievement_number']
|
||||
chest4 = raw_data['stats']['common_chest_number']
|
||||
chest3 = raw_data['stats']['exquisite_chest_number']
|
||||
chest2 = raw_data['stats']['precious_chest_number']
|
||||
chest1 = raw_data['stats']['luxurious_chest_number']
|
||||
data: Dict[str, int] = {
|
||||
'获得角色数': raw_data['stats']['avatar_number'],
|
||||
'风神瞳': raw_data['stats']['anemoculus_number'],
|
||||
'岩神瞳': raw_data['stats']['geoculus_number'],
|
||||
'雷神瞳': raw_data['stats']['electroculus_number'],
|
||||
'草神瞳': raw_data['stats']['dendroculus_number'],
|
||||
}
|
||||
for i in raw_data['world_explorations']:
|
||||
data[i['name']] = i['exploration_percentage']
|
||||
|
||||
achieveStr = await dataToDataStr(max_data['成就'], achieve)
|
||||
chest1Str = await dataToDataStr(max_data['华丽的宝箱'], chest1)
|
||||
chest2Str = await dataToDataStr(max_data['珍贵的宝箱'], chest2)
|
||||
chest3Str = await dataToDataStr(max_data['精致的宝箱'], chest3)
|
||||
chest4Str = await dataToDataStr(max_data['普通的宝箱'], chest4)
|
||||
percent_data = {}
|
||||
value_data = {}
|
||||
day: str = str(raw_data['stats']['active_day_number'])
|
||||
me_percent = 0
|
||||
world_percent = 0
|
||||
|
||||
# 计算
|
||||
val = (
|
||||
str(
|
||||
float(
|
||||
'{:.2f}'.format(
|
||||
(
|
||||
achieveStr[1]
|
||||
+ chest1Str[1]
|
||||
+ chest2Str[1]
|
||||
+ chest3Str[1]
|
||||
+ chest4Str[1]
|
||||
)
|
||||
/ 22.5
|
||||
)
|
||||
)
|
||||
)
|
||||
+ '%'
|
||||
)
|
||||
left = (
|
||||
(max_data['华丽的宝箱'] - chest1) * 10
|
||||
+ (max_data['珍贵的宝箱'] - chest2) * 5
|
||||
+ (max_data['精致的宝箱'] - chest3) * 2
|
||||
+ (max_data['普通的宝箱'] - chest4) * 0
|
||||
+ (max_data['成就'] - achieve) * 5
|
||||
)
|
||||
for name in data:
|
||||
# 百分比
|
||||
p_str = f'{data[name]}'
|
||||
if name in expmax_data:
|
||||
percent = data[name] / expmax_data[name]
|
||||
if name != '获得角色数':
|
||||
me_percent += percent
|
||||
value = f'{p_str} / {expmax_data[name]} | {_f(percent * 100)}'
|
||||
else:
|
||||
percent = data[name] / 1000
|
||||
world_percent += percent
|
||||
value = f'{_f(percent * 100)}'
|
||||
|
||||
# 用户信息
|
||||
text_draw.text(
|
||||
(50, 135),
|
||||
f'UID{uid}',
|
||||
text_color,
|
||||
gs_font_26,
|
||||
anchor='lm',
|
||||
)
|
||||
percent_data[name] = percent
|
||||
value_data[name] = value
|
||||
|
||||
text_draw.text((130, 200), str(val), text_color, gs_font_26, anchor='lm')
|
||||
text_draw.text(
|
||||
(360, 200),
|
||||
f'约{str(left)}',
|
||||
text_color,
|
||||
gs_font_26,
|
||||
anchor='lm',
|
||||
)
|
||||
me_percent = _f(me_percent * 100 / (len(expmax_data) - 1))
|
||||
world_percent = _f(world_percent * 100 / (len(data) - len(expmax_data)))
|
||||
|
||||
# 成就
|
||||
text_draw.text(
|
||||
(470, 275),
|
||||
achieveStr[0],
|
||||
text_color,
|
||||
gs_font_23,
|
||||
anchor='rm',
|
||||
)
|
||||
return percent_data, value_data, day, me_percent, world_percent
|
||||
|
||||
# 宝箱
|
||||
text_draw.text(
|
||||
(470, 275 + 100),
|
||||
chest1Str[0],
|
||||
text_color,
|
||||
gs_font_23,
|
||||
anchor='rm',
|
||||
)
|
||||
text_draw.text(
|
||||
(470, 275 + 100 * 2),
|
||||
chest2Str[0],
|
||||
text_color,
|
||||
gs_font_23,
|
||||
anchor='rm',
|
||||
)
|
||||
text_draw.text(
|
||||
(470, 275 + 100 * 3),
|
||||
chest3Str[0],
|
||||
text_color,
|
||||
gs_font_23,
|
||||
anchor='rm',
|
||||
)
|
||||
text_draw.text(
|
||||
(470, 275 + 100 * 4),
|
||||
chest4Str[0],
|
||||
text_color,
|
||||
gs_font_23,
|
||||
anchor='rm',
|
||||
)
|
||||
|
||||
base = 304
|
||||
offset = 99.5
|
||||
async def get_collection_data(
|
||||
uid: str,
|
||||
) -> Union[str, Tuple[Dict[str, float], Dict[str, str], str, str, str]]:
|
||||
raw_data = await get_base_data(uid)
|
||||
if isinstance(raw_data, str):
|
||||
return raw_data
|
||||
raw_data = raw_data['stats']
|
||||
|
||||
# 进度条
|
||||
text_draw.rounded_rectangle(
|
||||
(23, base, 22 + achieveStr[1], base + 13),
|
||||
fill=(234, 210, 124),
|
||||
radius=20,
|
||||
)
|
||||
text_draw.rounded_rectangle(
|
||||
(23, base + offset, 22 + chest1Str[1], base + offset + 13),
|
||||
fill=(235, 173, 43),
|
||||
radius=20,
|
||||
)
|
||||
text_draw.rounded_rectangle(
|
||||
(23, base + offset * 2, 22 + chest2Str[1], base + offset * 2 + 13),
|
||||
fill=(218, 128, 248),
|
||||
radius=20,
|
||||
)
|
||||
text_draw.rounded_rectangle(
|
||||
(23, base + offset * 3, 22 + chest3Str[1], base + offset * 3 + 13),
|
||||
fill=(60, 122, 227),
|
||||
radius=20,
|
||||
)
|
||||
text_draw.rounded_rectangle(
|
||||
(23, base + offset * 4, 22 + chest4Str[1], base + offset * 4 + 13),
|
||||
fill=(168, 248, 177),
|
||||
radius=20,
|
||||
)
|
||||
# 处理数据
|
||||
data: Dict[str, int] = {
|
||||
'成就': raw_data['achievement_number'],
|
||||
'普通的宝箱': raw_data['common_chest_number'],
|
||||
'精致的宝箱': raw_data['exquisite_chest_number'],
|
||||
'珍贵的宝箱': raw_data['precious_chest_number'],
|
||||
'华丽的宝箱': raw_data['luxurious_chest_number'],
|
||||
'奇馈宝箱': raw_data['magic_chest_number'],
|
||||
'解锁传送点': raw_data['way_point_number'],
|
||||
'解锁秘境': raw_data['domain_number'],
|
||||
}
|
||||
percent_data = {}
|
||||
value_data = {}
|
||||
left = 0
|
||||
day: str = str(raw_data['active_day_number'])
|
||||
all_percent = 0
|
||||
|
||||
for name in data:
|
||||
# 百分比
|
||||
percent = data[name] / max_data[name]
|
||||
all_percent += percent
|
||||
p_str = f'{data[name]} / {max_data[name]}'
|
||||
value = f'{p_str} | {_f(percent * 100)}'
|
||||
# 可获石头
|
||||
left += award_data[name] * (max_data[name] - data[name])
|
||||
percent_data[name] = percent
|
||||
value_data[name] = value
|
||||
|
||||
all_percent = _f(all_percent * 100 / len(data))
|
||||
|
||||
return percent_data, value_data, day, all_percent, f'约{left}'
|
||||
|
||||
|
||||
async def draw_base_img(
|
||||
qid: Union[str, int], uid: str, mode: Literal['探索', '收集'] = '收集'
|
||||
) -> Union[str, bytes]:
|
||||
# 获取数据
|
||||
if mode == '收集':
|
||||
data = await get_collection_data(uid)
|
||||
else:
|
||||
data = await get_explore_data(uid)
|
||||
if isinstance(data, str):
|
||||
return data
|
||||
percent_data, value_data = data[0], data[1]
|
||||
|
||||
# 获取背景图片各项参数
|
||||
_id = str(qid)
|
||||
if _id.startswith('http'):
|
||||
char_pic = await get_qq_avatar(avatar_url=_id)
|
||||
else:
|
||||
char_pic = await get_qq_avatar(qid=qid)
|
||||
char_pic = await draw_pic_with_ring(char_pic, 264)
|
||||
|
||||
if mode == '收集':
|
||||
title = Image.open(TEXT_PATH / 'collection_title.png')
|
||||
else:
|
||||
title = Image.open(TEXT_PATH / 'explora_title.png')
|
||||
|
||||
img = await get_color_bg(750, 600 + len(percent_data) * 115)
|
||||
img.paste(title, (0, 0), title)
|
||||
img.paste(char_pic, (241, 40), char_pic)
|
||||
|
||||
for index, name in enumerate(percent_data):
|
||||
percent = percent_data[name]
|
||||
value = value_data[name]
|
||||
bar = await draw_bar(f'·{name}', percent, value)
|
||||
img.paste(bar, (0, 600 + index * 115), bar)
|
||||
|
||||
# 头
|
||||
img_draw = ImageDraw.Draw(img)
|
||||
img_draw.text((378, 357), f'UID {uid}', first_color, gs_font_30, 'mm')
|
||||
img_draw.text((137, 498), data[2], first_color, gs_font_40, 'mm')
|
||||
img_draw.text((372, 498), data[3], first_color, gs_font_40, 'mm')
|
||||
img_draw.text((607, 498), data[4], first_color, gs_font_40, 'mm')
|
||||
|
||||
res = await convert_img(img)
|
||||
return res
|
||||
|
||||
|
||||
def _f(value: float) -> str:
|
||||
return '{:.2f}%'.format(value)
|
||||
|
Before Width: | Height: | Size: 49 KiB |
BIN
GenshinUID/genshinuid_collection/texture2D/collection_title.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
GenshinUID/genshinuid_collection/texture2D/explora_title.png
Normal file
After Width: | Height: | Size: 19 KiB |
@ -1,127 +1,211 @@
|
||||
from typing import Any, Tuple
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import RegexGroup
|
||||
from nonebot import on_regex, on_command
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.adapters.ntchat import Bot, MessageSegment, TextMessageEvent
|
||||
|
||||
from ..genshinuid_meta import register_menu
|
||||
from ..utils.nonebot2.rule import FullCommand
|
||||
from .draw_config_card import draw_config_img
|
||||
from ..all_import import * # noqa: F403, F401
|
||||
from ..utils.message.error_reply import UID_HINT
|
||||
from ..utils.db_operation.db_operation import select_db
|
||||
from .set_config import set_push_value, set_config_func
|
||||
from ..utils.message.error_reply import * # noqa: F403,F401
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
|
||||
open_and_close_switch = on_regex(
|
||||
r'^(\[CQ:at,qq=[0-9]+\])?( )?'
|
||||
r'(gs)(开启|关闭)(.*)'
|
||||
r'(\[CQ:at,qq=[0-9]+\])?( )?$'
|
||||
)
|
||||
|
||||
@sv.on_fullmatch('gs配置')
|
||||
async def send_config_card(bot: HoshinoBot, ev: CQEvent):
|
||||
logger.info('开始执行[gs配置]')
|
||||
im = await draw_config_img()
|
||||
if isinstance(im, str):
|
||||
await bot.send(ev, im)
|
||||
elif isinstance(im, bytes):
|
||||
im = await convert_img(im)
|
||||
await bot.send(ev, im)
|
||||
else:
|
||||
await bot.send(ev, '发生了未知错误,请联系管理员检查后台输出!')
|
||||
|
||||
|
||||
@sv.on_rex(
|
||||
push_config = on_regex(
|
||||
r'^(\[CQ:at,qq=[0-9]+\])?( )?'
|
||||
r'(gs)(设置)([\u4e00-\u9fffa-zA-Z]*)([0-9]*)'
|
||||
r'(\[CQ:at,qq=[0-9]+\])?( )?$'
|
||||
)
|
||||
async def send_config_msg(bot: HoshinoBot, ev: CQEvent):
|
||||
args = ev['match'].groups()
|
||||
|
||||
config_card = on_command('gs配置', rule=FullCommand())
|
||||
|
||||
|
||||
@config_card.handle()
|
||||
@handle_exception('发送配置表')
|
||||
@register_menu(
|
||||
'发送配置表',
|
||||
'gs配置',
|
||||
'查看插件当前配置项开关情况',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'查看插件当前配置项开关情况\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>gs配置</ft>'
|
||||
),
|
||||
)
|
||||
async def send_config_card(matcher: Matcher):
|
||||
logger.info('开始执行[gs配置]')
|
||||
im = await draw_config_img()
|
||||
if isinstance(im, str):
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, bytes):
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await matcher.finish('发生了未知错误,请联系管理员检查后台输出!')
|
||||
|
||||
|
||||
@push_config.handle()
|
||||
@handle_exception('设置推送服务')
|
||||
@register_menu(
|
||||
'设置推送阈值',
|
||||
'gs设置xx(@某人)',
|
||||
'设置自己或指定人的推送服务阈值',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'设置某人的推送服务阈值\n'
|
||||
'超级用户可以设置他人的推送服务阈值\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>gs设置</ft>'
|
||||
'<ft color=(0,148,200)>[服务名称][阈值]</ft>'
|
||||
'<ft color=(125,125,125)>(@某人)</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>gs设置推送140</ft>'
|
||||
),
|
||||
)
|
||||
async def send_config_msg(
|
||||
bot: Bot,
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
args: Tuple[Any, ...] = RegexGroup(),
|
||||
):
|
||||
logger.info('开始执行[设置阈值信息]')
|
||||
logger.info('[设置阈值信息]参数: {}'.format(args))
|
||||
wxid_list = []
|
||||
wxid_list.append(event.from_wxid)
|
||||
qid = event.from_wxid
|
||||
if event.at_user_list:
|
||||
for user in event.at_user_list:
|
||||
user = user.strip()
|
||||
if user != "" and await SUPERUSER(bot, event):
|
||||
qid = user
|
||||
else:
|
||||
await matcher.finish(
|
||||
MessageSegment.room_at_msg(
|
||||
content="{$@}你没有权限操作别人的状态噢~", at_list=wxid_list
|
||||
)
|
||||
)
|
||||
|
||||
at = re.search(r'\[CQ:at,qq=(\d*)]', str(ev.message))
|
||||
|
||||
if at:
|
||||
qid = int(at.group(1))
|
||||
else:
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
|
||||
if qid in bot.config.SUPERUSERS:
|
||||
is_admin = True
|
||||
else:
|
||||
is_admin = False
|
||||
|
||||
if at and is_admin:
|
||||
qid = at
|
||||
elif at and at != qid:
|
||||
await bot.send(ev, '你没有权限操作别人的状态噢~', at_sender=True)
|
||||
logger.info('[设置阈值信息]qid: {}'.format(qid))
|
||||
|
||||
try:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
except TypeError:
|
||||
await bot.send(ev, UID_HINT)
|
||||
return
|
||||
await matcher.finish(
|
||||
MessageSegment.room_at_msg(
|
||||
content='{$@}' + UID_HINT, at_list=wxid_list
|
||||
)
|
||||
)
|
||||
|
||||
func = args[4].replace('阈值', '')
|
||||
if args[5]:
|
||||
try:
|
||||
value = int(args[5])
|
||||
except ValueError:
|
||||
await bot.send(ev, '请输入数字哦~', at_sender=True)
|
||||
return
|
||||
await matcher.finish(
|
||||
MessageSegment.room_at_msg(
|
||||
content="{$@}请输入数字哦~", at_list=wxid_list
|
||||
)
|
||||
)
|
||||
else:
|
||||
await bot.send(ev, '请输入正确的阈值数字!', at_sender=True)
|
||||
return
|
||||
await matcher.finish(
|
||||
MessageSegment.room_at_msg(
|
||||
content="{$@}请输入正确的阈值数字!", at_list=wxid_list
|
||||
)
|
||||
)
|
||||
logger.info('[设置阈值信息]func: {}, value: {}'.format(func, value))
|
||||
im = await set_push_value(func, str(uid), value)
|
||||
await bot.send(ev, im, at_sender=True)
|
||||
await matcher.finish(
|
||||
MessageSegment.room_at_msg(content="{$@}" + f"{im}", at_list=wxid_list)
|
||||
)
|
||||
|
||||
|
||||
# 开启 自动签到 和 推送树脂提醒 功能
|
||||
@sv.on_rex(
|
||||
r'^(\[CQ:at,qq=[0-9]+\])?( )?'
|
||||
r'(gs)(开启|关闭)(.*)'
|
||||
r'(\[CQ:at,qq=[0-9]+\])?( )?$'
|
||||
@open_and_close_switch.handle()
|
||||
@register_menu(
|
||||
'开关推送服务',
|
||||
'gs{开启|关闭}xx(@某人)',
|
||||
'开关自己或指定人的推送服务状态',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'设置某人的推送服务开关状态\n'
|
||||
'超级用户可以设置他人的推送服务状态\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>gs{开启|关闭}</ft>'
|
||||
'<ft color=(0,148,200)>[服务名称]</ft>'
|
||||
'<ft color=(125,125,125)>(@某人)</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>gs开启推送</ft>'
|
||||
),
|
||||
)
|
||||
async def open_switch_func(bot: HoshinoBot, ev: CQEvent):
|
||||
args = ev['match'].groups()
|
||||
at = re.search(r'\[CQ:at,qq=(\d*)]', str(ev.message))
|
||||
|
||||
if at:
|
||||
qid = int(at.group(1))
|
||||
else:
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
async def open_switch_func(
|
||||
bot: Bot,
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
args: Tuple[Any, ...] = RegexGroup(),
|
||||
):
|
||||
wxid_list = []
|
||||
wxid_list.append(event.from_wxid)
|
||||
qid = event.from_wxid
|
||||
if event.at_user_list:
|
||||
for user in event.at_user_list:
|
||||
user = user.strip()
|
||||
if user != "" and await SUPERUSER(bot, event):
|
||||
qid = user
|
||||
else:
|
||||
await matcher.finish(
|
||||
MessageSegment.room_at_msg(
|
||||
content="{$@}你没有权限操作别人的状态噢~", at_list=wxid_list
|
||||
)
|
||||
)
|
||||
|
||||
config_name = args[4]
|
||||
|
||||
logger.info(f'[{qid}]尝试[{args[3]}]了[{config_name}]功能')
|
||||
|
||||
fake_seession = f'group_{event.room_wxid}_{event.from_wxid}'
|
||||
|
||||
if args[3] == '开启':
|
||||
query = 'OPEN'
|
||||
gid = str(ev.group_id) if ev.group_id else 'on'
|
||||
gid = (
|
||||
fake_seession.split('_')[1]
|
||||
if len(fake_seession.split('_')) == 3
|
||||
else 'on'
|
||||
)
|
||||
else:
|
||||
query = 'CLOSED'
|
||||
gid = 'off'
|
||||
|
||||
if qid in bot.config.SUPERUSERS:
|
||||
is_admin = True
|
||||
else:
|
||||
is_admin = False
|
||||
|
||||
if at and is_admin:
|
||||
qid = at
|
||||
elif at and at != qid:
|
||||
bot.send(ev, '你没有权限操作别人的状态噢~', at_sender=True)
|
||||
return
|
||||
|
||||
try:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
except TypeError:
|
||||
await bot.send(ev, UID_HINT)
|
||||
return
|
||||
uid = await select_db(qid, mode='uid')
|
||||
if uid is None or not isinstance(uid, str) or not uid.isdecimal():
|
||||
await matcher.finish(
|
||||
MessageSegment.room_at_msg(
|
||||
content='{$@}' + UID_HINT, at_list=wxid_list
|
||||
)
|
||||
)
|
||||
|
||||
im = await set_config_func(
|
||||
config_name=config_name,
|
||||
uid=uid, # type: ignore
|
||||
qid=qid, # type: ignore
|
||||
uid=uid,
|
||||
qid=str(qid),
|
||||
option=gid,
|
||||
query=query,
|
||||
is_admin=is_admin,
|
||||
is_admin=await SUPERUSER(bot, event),
|
||||
)
|
||||
await matcher.finish(
|
||||
MessageSegment.room_at_msg(content="{$@}" + f"{im}", at_list=wxid_list)
|
||||
)
|
||||
await bot.send(ev, im, at_sender=True)
|
||||
|
86
GenshinUID/genshinuid_config/default_config.py
Normal file
@ -0,0 +1,86 @@
|
||||
import json
|
||||
from typing import Dict, List, Union, Literal, overload
|
||||
|
||||
from ..utils.download_resource.RESOURCE_PATH import CONFIG_PATH
|
||||
|
||||
CONIFG_DEFAULT = {
|
||||
'proxy': '',
|
||||
'_pass_API': '',
|
||||
'random_pic_API': 'https://genshin-res.cherishmoon.fun/img?name=',
|
||||
'Ann_Groups': [],
|
||||
'Ann_Ids': [],
|
||||
}
|
||||
|
||||
STR_CONFIG = Literal['proxy', '_pass_API', 'random_pic_API']
|
||||
LIST_CONFIG = Literal['Ann_Groups', 'Ann_Ids']
|
||||
|
||||
|
||||
class StringConfig:
|
||||
def __init__(self) -> None:
|
||||
if not CONFIG_PATH.exists():
|
||||
with open(CONFIG_PATH, 'w', encoding='UTF-8') as file:
|
||||
json.dump(CONIFG_DEFAULT, file, ensure_ascii=False)
|
||||
|
||||
self.update_config()
|
||||
|
||||
def write_config(self):
|
||||
with open(CONFIG_PATH, 'w', encoding='UTF-8') as file:
|
||||
json.dump(self.config, file, ensure_ascii=False)
|
||||
|
||||
def update_config(self):
|
||||
# 打开config.json
|
||||
with open(CONFIG_PATH, 'r', encoding='UTF-8') as f:
|
||||
self.config: Dict = json.load(f)
|
||||
# 对没有的值,添加默认值
|
||||
for key in CONIFG_DEFAULT:
|
||||
if key not in self.config:
|
||||
self.config[key] = CONIFG_DEFAULT[key]
|
||||
|
||||
# 对默认值没有的值,直接删除
|
||||
delete_keys = []
|
||||
for key in self.config:
|
||||
if key not in CONIFG_DEFAULT:
|
||||
delete_keys.append(key)
|
||||
for key in delete_keys:
|
||||
self.config.pop(key)
|
||||
|
||||
# 重新写回
|
||||
self.write_config()
|
||||
|
||||
@overload
|
||||
def get_config(self, key: STR_CONFIG) -> str:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_config(self, key: LIST_CONFIG) -> List:
|
||||
...
|
||||
|
||||
def get_config(self, key: str) -> Union[str, List]:
|
||||
if key in self.config:
|
||||
return self.config[key]
|
||||
elif key in CONIFG_DEFAULT:
|
||||
self.update_config()
|
||||
return self.config[key]
|
||||
else:
|
||||
return []
|
||||
|
||||
@overload
|
||||
def set_config(self, key: STR_CONFIG, value: str) -> bool:
|
||||
...
|
||||
|
||||
@overload
|
||||
def set_config(self, key: LIST_CONFIG, value: List) -> bool:
|
||||
...
|
||||
|
||||
def set_config(self, key: str, value: Union[str, List]) -> bool:
|
||||
if key in CONIFG_DEFAULT:
|
||||
# 设置值
|
||||
self.config[key] = value
|
||||
# 重新写回
|
||||
self.write_config()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
string_config = StringConfig()
|
@ -41,7 +41,8 @@ DETAIL_MAP = {
|
||||
'重启使用Poetry': '将会以Poetry方式重启',
|
||||
'旧面板': '会稍微增加面板访问速度,但会损失很多功能',
|
||||
'多彩面板': '面板颜色不按照属性来渲染,而按照自定义颜色',
|
||||
'跳过无感验证': '尝试跳过米游社签到触发的极验,可能有一定危险性',
|
||||
'失效项': '可能有一定危险性',
|
||||
'无效项': '可能有一定危险性',
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,7 +25,8 @@ SWITCH_MAP = {
|
||||
'重启使用Poetry': 'UsePoetry',
|
||||
'旧面板': 'OldPanle',
|
||||
'多彩面板': 'ColorBG',
|
||||
'跳过无感验证': 'CaptchaPass',
|
||||
'失效项': 'CaptchaPass',
|
||||
'无效项': 'MhyPass',
|
||||
}
|
||||
|
||||
PUSH_MAP = {
|
||||
|
15
GenshinUID/genshinuid_data/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from .export_data import export_v3
|
||||
from ..utils.nonebot2.rule import FullCommand
|
||||
|
||||
export_v3_data = on_command('导出v3数据', permission=SUPERUSER, rule=FullCommand())
|
||||
|
||||
|
||||
@export_v3_data.handle()
|
||||
async def send_export_msg(matcher: Matcher):
|
||||
await matcher.send('开始导出数据..可能需要一定时间!')
|
||||
await export_v3()
|
||||
await matcher.send('导出数据成功!')
|
53
GenshinUID/genshinuid_data/export_data.py
Normal file
@ -0,0 +1,53 @@
|
||||
import re
|
||||
import json
|
||||
|
||||
from ..utils.download_resource.RESOURCE_PATH import MAIN_PATH
|
||||
from ..utils.db_operation.db_operation import get_all_bind, get_user_bind_data
|
||||
|
||||
DATA_PATH = MAIN_PATH / 'v3_data.json'
|
||||
BOT_ID = 'ntchat'
|
||||
RECOGNIZE_SERVER = {
|
||||
'1': 'cn_gf01',
|
||||
'2': 'cn_gf01',
|
||||
'5': 'cn_qd01',
|
||||
'6': 'os_usa',
|
||||
'7': 'os_euro',
|
||||
'8': 'os_asia',
|
||||
'9': 'os_cht',
|
||||
}
|
||||
|
||||
|
||||
async def export_v3():
|
||||
result = {'bot_id': BOT_ID, 'bind': [], 'user': []}
|
||||
bind_list = await get_all_bind()
|
||||
for bind in bind_list:
|
||||
bind_data = {}
|
||||
bind_data['bot_id'] = BOT_ID
|
||||
bind_data['user_id'] = str(bind['USERID'])
|
||||
bind_data['uid'] = str(bind['UID']) # 导出bind数据
|
||||
bind_data['mys_id'] = str(bind['MYSID'])
|
||||
if bind_data not in result['bind']:
|
||||
result['bind'].append(bind_data)
|
||||
uid_list = bind['UID'].split('_')
|
||||
for uid in uid_list:
|
||||
new_data = {}
|
||||
data = await get_user_bind_data(uid)
|
||||
if not data:
|
||||
continue
|
||||
new_data['bot_id'] = BOT_ID
|
||||
new_data['user_id'] = data['QID']
|
||||
new_data['region'] = RECOGNIZE_SERVER[uid[0]]
|
||||
new_data['stoken'] = data['Stoken']
|
||||
new_data['cookie'] = data['Cookies']
|
||||
account_id = re.search(r'account_id=(\d*)', new_data['cookie'])
|
||||
assert account_id is not None
|
||||
new_data['mys_id'] = str(account_id.group(1))
|
||||
new_data['uid'] = uid
|
||||
new_data['push_switch'] = data['StatusA']
|
||||
new_data['sign_switch'] = data['StatusB']
|
||||
new_data['bbs_switch'] = data['StatusC']
|
||||
new_data['status'] = data['Extra']
|
||||
if new_data not in result['user']:
|
||||
result['user'].append(new_data)
|
||||
with open(DATA_PATH, 'w', encoding='UTF-8') as file:
|
||||
json.dump(result, file, ensure_ascii=False, indent=2)
|
@ -1,104 +1,199 @@
|
||||
import re
|
||||
import random
|
||||
import asyncio
|
||||
from typing import Tuple
|
||||
|
||||
from .draw_char_card import *
|
||||
from nonebot import on_command
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot.adapters.ntchat.message import Message
|
||||
from nonebot.adapters.ntchat import (
|
||||
Bot,
|
||||
MessageEvent,
|
||||
MessageSegment,
|
||||
TextMessageEvent,
|
||||
QuoteMessageEvent,
|
||||
)
|
||||
|
||||
from ..config import priority
|
||||
from .get_enka_img import draw_enka_img
|
||||
from .draw_char_card import draw_char_img
|
||||
from ..all_import import * # noqa: F401,F403
|
||||
from ..genshinuid_meta import register_menu
|
||||
from ..utils.nonebot2.rule import FullCommand
|
||||
from .draw_char_rank import draw_cahrcard_list
|
||||
from ..utils.message.error_reply import UID_HINT
|
||||
from ..utils.enka_api.get_enka_data import switch_api
|
||||
from ..utils.enka_api.enka_to_card import enka_to_card
|
||||
from ..utils.enka_api.enka_to_data import enka_to_data
|
||||
from ..utils.db_operation.db_operation import get_all_uid
|
||||
from ..utils.message.error_reply import * # noqa: F401,F403
|
||||
from ..utils.download_resource.RESOURCE_PATH import TEMP_PATH
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
from ..utils.db_operation.db_operation import select_db, get_all_uid
|
||||
|
||||
refresh = on_command('强制刷新')
|
||||
original_pic = on_command('原图', rule=FullCommand())
|
||||
change_api = on_command('切换api', rule=FullCommand())
|
||||
get_charcard_list = on_command('毕业度统计')
|
||||
get_char_info = on_command(
|
||||
'查询',
|
||||
priority=priority,
|
||||
)
|
||||
|
||||
AUTO_REFRESH = False
|
||||
refresh_scheduler = scheduler
|
||||
|
||||
|
||||
@sv.on_fullmatch('切换api')
|
||||
async def send_change_api_info(bot: HoshinoBot, ev: CQEvent):
|
||||
print(ev)
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
|
||||
if qid not in bot.config.SUPERUSERS:
|
||||
@change_api.handle()
|
||||
@handle_exception('切换api')
|
||||
@register_menu(
|
||||
'切换API',
|
||||
'切换api',
|
||||
'切换获取角色面板时使用的API',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'切换获取角色面板时使用的Enka Network API\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>切换api</ft>'
|
||||
),
|
||||
)
|
||||
async def send_change_api_info(
|
||||
bot: Bot,
|
||||
event: MessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
if not await SUPERUSER(bot, event):
|
||||
return
|
||||
|
||||
im = await switch_api()
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(im)
|
||||
|
||||
|
||||
@sv.on_rex(r'^(\[CQ:reply,id=[0-9]+\])?( )?(\[CQ:at,qq=[0-9]+\])?( )?原图')
|
||||
async def send_original_pic(bot: HoshinoBot, ev: CQEvent):
|
||||
for msg in ev.message:
|
||||
if msg['type'] == 'reply':
|
||||
msg_id = msg['data']['id']
|
||||
path = TEMP_PATH / f'{msg_id}.jpg'
|
||||
if path.exists():
|
||||
logger.info('[原图]访问图片: {}'.format(path))
|
||||
with open(path, 'rb') as f:
|
||||
im = await convert_img(f.read())
|
||||
await bot.send(ev, im)
|
||||
|
||||
|
||||
@sv.on_rex(
|
||||
r'^(\[CQ:at,qq=[0-9]+\])?( )?'
|
||||
r'(uid|查询|mys)([0-9]+)?'
|
||||
r'([\u4e00-\u9fa5]+)'
|
||||
r'(\[CQ:at,qq=[0-9]+\])?( )?',
|
||||
@original_pic.handle()
|
||||
@handle_exception('原图')
|
||||
@register_menu(
|
||||
'查看面板原图',
|
||||
'原图',
|
||||
'查看角色面板中原随机图',
|
||||
trigger_method='回复+指令',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'查看开启随机图功能时角色面板中角色位置的原图,需要回复要查看原图的面板图片消息\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>原图</ft>'
|
||||
),
|
||||
)
|
||||
async def send_char_info(bot: HoshinoBot, ev: CQEvent):
|
||||
args = ev['match'].groups()
|
||||
if args[4] is None:
|
||||
return
|
||||
# 获取角色名
|
||||
msg = ''.join(re.findall('[\u4e00-\u9fa5]', args[4]))
|
||||
logger.info('开始执行[查询角色面板]')
|
||||
logger.info('[查询角色面板]参数: {}'.format(args))
|
||||
# 获取角色名
|
||||
at = re.search(r'\[CQ:at,qq=(\d*)]', str(ev.message))
|
||||
image = re.search(r'\[CQ:image,file=(.*),url=(.*)]', str(ev.message))
|
||||
async def send_original_pic(
|
||||
event: QuoteMessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
msg_id = event.quote_message_id
|
||||
path = TEMP_PATH / f'{msg_id}.jpg'
|
||||
if path.exists():
|
||||
logger.info('[原图]访问图片: {}'.format(path))
|
||||
with open(path, 'rb') as f:
|
||||
await matcher.finish(MessageSegment.image(f.read()))
|
||||
|
||||
img = None
|
||||
if image:
|
||||
img = image.group(2)
|
||||
if at:
|
||||
qid = int(at.group(1))
|
||||
else:
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
|
||||
@get_char_info.handle()
|
||||
@handle_exception('查询角色面板')
|
||||
@register_menu(
|
||||
'查询角色面板',
|
||||
'查询(@某人)角色名',
|
||||
'查询你的或者指定人的已缓存展柜角色的面板',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'可以用来查看你的或者指定人的已缓存展柜角色的面板\n'
|
||||
'支持部分角色别名\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>{查询</ft>'
|
||||
'<ft color=(125,125,125)>(@某人)</ft>'
|
||||
'<ft color=(238,120,0)>|uid</ft><ft color=(0,148,200)>xx</ft>'
|
||||
'<ft color=(238,120,0)>|mys</ft><ft color=(0,148,200)>xx</ft>'
|
||||
'<ft color=(238,120,0)>}</ft>'
|
||||
'<ft color=(0,148,200)>[角色名]</ft>\n'
|
||||
'后面可以跟 '
|
||||
'<ft color=(238,120,0)>换</ft>'
|
||||
'<ft color=(125,125,125)>(精{一|二|三|四|五})</ft>'
|
||||
'<ft color=(0,148,200)>[武器名]</ft> '
|
||||
'来更换展示的武器\n'
|
||||
'可以跟 '
|
||||
'<ft color=(125,125,125)>(成长)</ft><ft color=(238,120,0)>曲线</ft> '
|
||||
'来查询该角色成长曲线\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>查询宵宫</ft>\n'
|
||||
'- <ft color=(238,120,0)>查询绫华换精五雾切</ft>\n'
|
||||
'- <ft color=(238,120,0)>查询一斗成长曲线</ft>\n'
|
||||
'- <ft color=(238,120,0)>查询</ft><ft color=(0,123,67)>@无疑Wuyi</ft>'
|
||||
' <ft color=(238,120,0)>公子</ft>'
|
||||
),
|
||||
)
|
||||
@register_menu(
|
||||
'查询展柜角色',
|
||||
'查询展柜角色',
|
||||
'查询插件已缓存的展柜角色列表',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'查询插件当前已缓存的展柜角色列表\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>查询展柜角色</ft>'
|
||||
),
|
||||
)
|
||||
async def send_char_info(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
args: Message = CommandArg(),
|
||||
):
|
||||
raw_mes = args.extract_plain_text().strip()
|
||||
name = ''.join(re.findall('[\u4e00-\u9fa5]', raw_mes))
|
||||
# 如果输入中(查询之后的内容)没有其他字符串(如角色名等)则忽略。
|
||||
if not name:
|
||||
return
|
||||
# 若查询中有@则排除@后内容,排除后若@前没有 其他内容则不继续运行下方代码 防止与roleinfo查询打架
|
||||
# 修改后@人应该在命令末尾 如:查询万叶@XXXX
|
||||
if "@" in raw_mes:
|
||||
raw_mes = raw_mes.split("@")[0]
|
||||
if not raw_mes:
|
||||
return
|
||||
logger.info('[查询角色面板]QQ: {}'.format(qid))
|
||||
logger.info('开始执行[查询角色面板]')
|
||||
# 获取被@的Wxid,排除""
|
||||
qid = event.from_wxid
|
||||
if event.at_user_list:
|
||||
for user in event.at_user_list:
|
||||
user = user.strip()
|
||||
if user != "":
|
||||
qid = user
|
||||
logger.info('[查询角色面板]WXID: {}'.format(qid))
|
||||
|
||||
# 获取uid
|
||||
if args[3] is None:
|
||||
uid = re.findall(r'\d+', raw_mes.split("@")[0])
|
||||
if uid:
|
||||
uid = uid[0]
|
||||
else:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
uid = str(uid)
|
||||
else:
|
||||
uid = args[3]
|
||||
logger.info('[查询角色面板]uid: {}'.format(uid))
|
||||
|
||||
if '未找到绑定的UID' in uid:
|
||||
await bot.send(ev, UID_HINT)
|
||||
await matcher.finish(UID_HINT)
|
||||
|
||||
im = await draw_enka_img(msg, uid, img)
|
||||
im = await draw_enka_img(raw_mes, uid, None)
|
||||
|
||||
if isinstance(im, str):
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, Tuple):
|
||||
img = await convert_img(im[0])
|
||||
req = await bot.send(ev, img)
|
||||
msg_id = req['message_id']
|
||||
await matcher.send(MessageSegment.image(im[0]))
|
||||
# msg_id = req['message_id']
|
||||
if im[1]:
|
||||
with open(TEMP_PATH / f'{msg_id}.jpg', 'wb') as f:
|
||||
with open(TEMP_PATH / f'{uid}.jpg', 'wb') as f:
|
||||
f.write(im[1])
|
||||
else:
|
||||
await bot.send(ev, '发生了未知错误,请联系管理员检查后台输出!')
|
||||
await matcher.finish('发生了未知错误,请联系管理员检查后台输出!')
|
||||
|
||||
|
||||
async def refresh_char_data():
|
||||
@ -114,7 +209,7 @@ async def refresh_char_data():
|
||||
logger.info(im)
|
||||
t += 1
|
||||
await asyncio.sleep(35 + random.randint(1, 20))
|
||||
except:
|
||||
except Exception:
|
||||
logger.exception(f'{uid}刷新失败!')
|
||||
logger.error(f'{uid}刷新失败!本次自动刷新结束!')
|
||||
return f'执行失败从{uid}!共刷新{str(t)}个角色!'
|
||||
@ -123,86 +218,119 @@ async def refresh_char_data():
|
||||
return f'执行成功!共刷新{str(t)}个角色!'
|
||||
|
||||
|
||||
@sv.scheduled_job('cron', hour='4')
|
||||
@refresh_scheduler.scheduled_job('cron', hour='4')
|
||||
async def daily_refresh_charData():
|
||||
global AUTO_REFRESH
|
||||
if AUTO_REFRESH:
|
||||
await refresh_char_data()
|
||||
|
||||
|
||||
@sv.on_prefix('强制刷新')
|
||||
async def send_card_info(bot: HoshinoBot, ev: CQEvent):
|
||||
if ev.message:
|
||||
message = ev.message.extract_plain_text().replace(' ', '')
|
||||
else:
|
||||
return
|
||||
|
||||
uid = re.findall(r'\d+', message) # str
|
||||
@refresh.handle()
|
||||
@handle_exception('强制刷新')
|
||||
@register_menu(
|
||||
'刷新展柜缓存',
|
||||
'强制刷新',
|
||||
'强制刷新你的或者指定人缓存的角色展柜数据',
|
||||
detail_des=(
|
||||
'指令:'
|
||||
'<ft color=(238,120,0)>强制刷新</ft>'
|
||||
'<ft color=(125,125,125)>(uid/全部数据)</ft>\n'
|
||||
' \n'
|
||||
'用于强制刷新你的或者指定人缓存的角色展柜数据\n'
|
||||
'当刷新全部数据时需要机器人管理员权限\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'<ft color=(238,120,0)>强制刷新</ft>;\n'
|
||||
'<ft color=(238,120,0)>强制刷新123456789</ft>;\n'
|
||||
'<ft color=(238,120,0)>强制刷新全部数据</ft>'
|
||||
),
|
||||
)
|
||||
async def send_card_info(
|
||||
bot: Bot,
|
||||
matcher: Matcher,
|
||||
event: TextMessageEvent,
|
||||
args: Message = CommandArg(),
|
||||
):
|
||||
message = args.extract_plain_text().strip().replace(' ', '')
|
||||
uid = re.findall(r'\d+', message.split("@")[0]) # str
|
||||
m = ''.join(re.findall('[\u4e00-\u9fa5]', message))
|
||||
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
# 获取被@的Wxid,排除""
|
||||
qid = event.from_wxid
|
||||
if event.at_user_list:
|
||||
for user in event.at_user_list:
|
||||
user = user.strip()
|
||||
if user != "":
|
||||
qid = user
|
||||
|
||||
if len(uid) >= 1:
|
||||
uid = uid[0]
|
||||
else:
|
||||
if m == '全部数据':
|
||||
if qid in bot.config.SUPERUSERS:
|
||||
await bot.send(ev, '开始刷新全部数据,这可能需要相当长的一段时间!!')
|
||||
if await SUPERUSER(bot, event):
|
||||
await matcher.send('开始刷新全部数据,这可能需要相当长的一段时间!!')
|
||||
im = await refresh_char_data()
|
||||
await bot.send(ev, str(im))
|
||||
return
|
||||
await matcher.finish(str(im))
|
||||
else:
|
||||
return
|
||||
else:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
uid = str(uid)
|
||||
if '未找到绑定的UID' in uid:
|
||||
await bot.send(ev, UID_HINT)
|
||||
return
|
||||
if not uid:
|
||||
await matcher.finish(UID_HINT)
|
||||
im = await enka_to_card(uid)
|
||||
logger.info(f'UID{uid}获取角色数据成功!')
|
||||
|
||||
if isinstance(im, str):
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, bytes):
|
||||
im = await convert_img(im)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await bot.send(ev, '发生了未知错误,请联系管理员检查后台输出!')
|
||||
await matcher.finish('发生了未知错误,请联系管理员检查后台输出!')
|
||||
|
||||
|
||||
@sv.on_prefix('毕业度统计')
|
||||
async def send_charcard_list(bot: HoshinoBot, ev: CQEvent):
|
||||
if ev.message:
|
||||
message = ev.message.extract_plain_text().replace(' ', '')
|
||||
else:
|
||||
return
|
||||
|
||||
at = re.search(r'\[CQ:at,qq=(\d*)]', str(ev.message))
|
||||
|
||||
if at:
|
||||
qid = int(at.group(1))
|
||||
message = message.replace(str(at), '')
|
||||
else:
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
@get_charcard_list.handle()
|
||||
@handle_exception('毕业度统计')
|
||||
@register_menu(
|
||||
'毕业度统计',
|
||||
'毕业度统计',
|
||||
'查看你或指定人已缓存的展柜角色毕业度',
|
||||
detail_des=(
|
||||
'指令:'
|
||||
'<ft color=(238,120,0)>毕业度统计</ft>'
|
||||
'<ft color=(125,125,125)>(@某人)</ft>\n'
|
||||
' \n'
|
||||
'可以查看你或指定人已缓存的所有展柜角色毕业度\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'<ft color=(238,120,0)>毕业度统计</ft>;\n'
|
||||
'<ft color=(238,120,0)>毕业度统计</ft><ft color=(0,148,200)>@无疑Wuyi</ft>'
|
||||
),
|
||||
)
|
||||
async def send_charcard_list(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
args: Message = CommandArg(),
|
||||
):
|
||||
raw_mes = args.extract_plain_text().strip()
|
||||
# 获取被@的Wxid,排除""
|
||||
qid = event.from_wxid
|
||||
if event.at_user_list:
|
||||
for user in event.at_user_list:
|
||||
user = user.strip()
|
||||
if user != "":
|
||||
qid = user
|
||||
|
||||
# 获取uid
|
||||
uid = re.findall(r'\d+', message)
|
||||
uid = re.findall(r'\d+', raw_mes.split("@")[0])
|
||||
if uid:
|
||||
uid = uid[0]
|
||||
else:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
uid = str(uid)
|
||||
|
||||
im = await draw_cahrcard_list(str(uid), qid)
|
||||
|
||||
logger.info(f'UID{uid}获取角色数据成功!')
|
||||
if isinstance(im, bytes):
|
||||
im = await convert_img(im)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await bot.send(ev, str(im))
|
||||
await matcher.finish(str(im))
|
||||
|
@ -1,417 +1,112 @@
|
||||
from typing import Tuple
|
||||
from copy import deepcopy
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from ..etc.etc import TEXT_PATH
|
||||
from .base_value import base_value_list
|
||||
from ..etc.buff_calc import get_effect_prop
|
||||
from ..etc.get_buff_list import get_buff_list
|
||||
from ..etc.MAP_PATH import avatarName2SkillAdd
|
||||
from ..etc.status_change import EXTRA_CHAR_LIST, STATUS_CHAR_LIST
|
||||
from ...utils.genshin_fonts.genshin_fonts import genshin_font_origin
|
||||
from ..mono.Enemy import Enemy
|
||||
from ..mono.Fight import Fight
|
||||
from ..etc.MAP_PATH import dmgMap
|
||||
from ..mono.Character import Character
|
||||
from ..etc.etc import TEXT_PATH, get_char_std
|
||||
from ...utils.genshin_fonts.genshin_fonts import gs_font_28
|
||||
|
||||
dmgBar_1 = Image.open(TEXT_PATH / 'dmgBar_1.png')
|
||||
dmgBar_2 = Image.open(TEXT_PATH / 'dmgBar_2.png')
|
||||
|
||||
text_color = (255, 255, 255)
|
||||
title_color = (255, 255, 100)
|
||||
|
||||
async def get_fight_prop(raw_data: dict) -> dict:
|
||||
# 获取值
|
||||
char_name = raw_data['avatarName']
|
||||
|
||||
skillList = raw_data['avatarSkill']
|
||||
prop = deepcopy(raw_data['avatarFightProp'])
|
||||
prop['A_skill_level'] = skillList[0]['skillLevel']
|
||||
prop['E_skill_level'] = skillList[1]['skillLevel']
|
||||
prop['Q_skill_level'] = skillList[-1]['skillLevel']
|
||||
|
||||
if char_name in avatarName2SkillAdd:
|
||||
skill_add = avatarName2SkillAdd[char_name]
|
||||
else:
|
||||
skill_add = ['E', 'Q']
|
||||
for skillAdd_index in range(0, 2):
|
||||
if len(raw_data['talentList']) >= 3 + skillAdd_index * 2:
|
||||
if skill_add[skillAdd_index] == 'E':
|
||||
prop['E_skill_level'] += 3
|
||||
elif skill_add[skillAdd_index] == 'Q':
|
||||
prop['Q_skill_level'] += 3
|
||||
|
||||
prop = await get_effect_prop(prop, [], char_name)
|
||||
all_effect = await get_buff_list(raw_data, 'fight')
|
||||
|
||||
# 开启效果
|
||||
if char_name in STATUS_CHAR_LIST:
|
||||
for skill_effect in STATUS_CHAR_LIST[char_name]:
|
||||
skill_level = prop[f'{skill_effect["name"][0]}_skill_level'] - 1
|
||||
skill_value = skill_effect['value'][skill_level]
|
||||
skill: str = skill_effect['effect'].format(skill_value)
|
||||
if skill.endswith('%'):
|
||||
skill = skill[:-1]
|
||||
all_effect.append(skill)
|
||||
|
||||
# 特殊效果,目前有雷神满愿力
|
||||
prop['extra_effect'] = {}
|
||||
if char_name in EXTRA_CHAR_LIST:
|
||||
if char_name == '雷电将军':
|
||||
skill_effect = EXTRA_CHAR_LIST[char_name]
|
||||
skill_level = prop[f'{skill_effect["name"][0]}_skill_level'] - 1
|
||||
value_1 = float(skill_effect['value'][skill_level].split('+')[0])
|
||||
value_1 *= 0.6
|
||||
value_2 = float(skill_effect['value'][skill_level].split('+')[1])
|
||||
all_effect.append(
|
||||
(
|
||||
f'Q一段伤害:addAtk+{60*value_2};'
|
||||
f'Q重击伤害:addAtk+{60*value_2};'
|
||||
f'Q高空下落伤害:addAtk+{60*value_2}'
|
||||
async def get_char_dmg_percent(char: Character) -> Dict:
|
||||
enemy = Enemy(char.char_level, char.char_level)
|
||||
fight = Fight({char.char_name: char}, enemy)
|
||||
dmg_data = await fight.get_dmg_dict(char.char_name)
|
||||
without_talent = await fight.get_dmg_dict(char.char_name, True)
|
||||
percent = 0
|
||||
char.seq_str = '无匹配'
|
||||
if char.char_name in dmgMap:
|
||||
std = await get_char_std(char.card_prop, char.char_name)
|
||||
if std['skill']:
|
||||
value = 0
|
||||
std_value = 0
|
||||
if std['skill'] == 'atk':
|
||||
value = char.fight_prop['atk']
|
||||
std_value = std['atk']
|
||||
elif std['skill'] == 'def':
|
||||
value = char.fight_prop['def']
|
||||
std_value = std['other']['防御']
|
||||
elif std['skill'] in without_talent:
|
||||
if without_talent[std['skill']]['crit'] == 0:
|
||||
value = without_talent[std['skill']]['normal']
|
||||
elif char.char_name == '妮露':
|
||||
value = without_talent[std['skill']]['normal']
|
||||
else:
|
||||
value = without_talent[std['skill']]['avg']
|
||||
std_value = std['value']
|
||||
if char.char_name == '夜兰':
|
||||
std_value *= 3
|
||||
elif char.char_name == '刻晴':
|
||||
std_value *= 2
|
||||
if std_value != 0:
|
||||
percent = (value / std_value) * 100
|
||||
char.seq_str = (
|
||||
'|'.join([i[:2] for i in std['seq'].split('|')])
|
||||
+ std['seq'][-1]
|
||||
)
|
||||
)
|
||||
prop['extra_effect'] = {'Q梦想一刀基础伤害(满愿力)': value_1}
|
||||
|
||||
# 在计算buff前, 引入特殊效果
|
||||
if char_name == '雷电将军':
|
||||
all_effect.append('Q:dmgBonus+27')
|
||||
elif char_name == '钟离':
|
||||
all_effect.append('r+-20')
|
||||
elif char_name == '妮露':
|
||||
all_effect.append('addHp+25')
|
||||
all_effect.append('elementalMastery+80')
|
||||
|
||||
# 计算全部的buff,添加入属性
|
||||
prop = await get_effect_prop(prop, all_effect, char_name)
|
||||
return prop
|
||||
char.percent = '{:.2f}'.format(percent)
|
||||
char.dmg_data = dmg_data
|
||||
return dmg_data
|
||||
|
||||
|
||||
async def draw_dmg_img(
|
||||
raw_data: dict, power_list: dict, prop: dict
|
||||
) -> Tuple[Image.Image, int]:
|
||||
async def draw_dmg_img(char: Character) -> Tuple[Image.Image, int]:
|
||||
# 获取值
|
||||
char_name = raw_data['avatarName']
|
||||
char_level = int(raw_data['avatarLevel'])
|
||||
enemy_level = char_level
|
||||
|
||||
extra_effect = prop['extra_effect']
|
||||
sp = prop['sp']
|
||||
|
||||
dmg_data = await get_char_dmg_percent(char)
|
||||
if dmg_data == {}:
|
||||
return Image.new('RGBA', (950, 1)), 0
|
||||
# 计算伤害计算部分图片长宽值
|
||||
w = 950
|
||||
h = 40 * (len(power_list) + 1)
|
||||
h = 40 * (len(dmg_data) + 1)
|
||||
result_img = Image.new('RGBA', (w, h), (0, 0, 0, 0))
|
||||
# 反复贴上不同颜色的长条
|
||||
for i in range(0, len(power_list) + 1):
|
||||
if i % 2 == 0:
|
||||
result_img.paste(dmgBar_1, (0, i * 40))
|
||||
else:
|
||||
result_img.paste(dmgBar_2, (0, i * 40))
|
||||
for i in range(0, len(dmg_data) + 1):
|
||||
pic = dmgBar_1 if i % 2 == 0 else dmgBar_2
|
||||
result_img.paste(pic, (0, i * 40))
|
||||
|
||||
result_draw = ImageDraw.Draw(result_img)
|
||||
|
||||
text_color = (255, 255, 255)
|
||||
title_color = (255, 255, 100)
|
||||
text_size = genshin_font_origin(28)
|
||||
text_size = gs_font_28
|
||||
result_draw.text((45, 22), '角色动作', title_color, text_size, anchor='lm')
|
||||
result_draw.text((460, 22), '暴击伤害', title_color, text_size, anchor='lm')
|
||||
result_draw.text((695, 22), '期望伤害', title_color, text_size, anchor='lm')
|
||||
|
||||
for index, power_name in enumerate(power_list):
|
||||
# 攻击类型ABCEQ应为label首位
|
||||
attack_type = power_name[0]
|
||||
# 如果是雷电将军, 则就按首位,因为Q的几段伤害均视为元素爆发
|
||||
if char_name == '雷电将军':
|
||||
pass
|
||||
else:
|
||||
# 重击或瞄准射击在label内,则视为B重击伤害,例如公子E内的重击伤害,不视为E伤害,而是B伤害
|
||||
if '重击' in power_name or '瞄准射击' in power_name:
|
||||
attack_type = 'B'
|
||||
# 特殊重击类型,例如甘雨和夜兰
|
||||
elif (
|
||||
'破局矢' in power_name
|
||||
or '霜华矢' in power_name
|
||||
or '藏蕴花矢' in power_name
|
||||
or '花筥箭' in power_name
|
||||
):
|
||||
attack_type = 'B'
|
||||
# 下落伤害类型,例如魈
|
||||
elif '高空下落' in power_name:
|
||||
attack_type = 'C'
|
||||
# 一段伤害, 二段伤害等等 应视为A伤害
|
||||
elif '段' in power_name and '伤害' in power_name:
|
||||
attack_type = 'A'
|
||||
|
||||
# 额外的伤害增益,基于之前的特殊label
|
||||
sp_dmgBonus = 0
|
||||
sp_addDmg = 0
|
||||
sp_attack = 0
|
||||
|
||||
if sp:
|
||||
for sp_single in sp:
|
||||
if sp_single['effect_name'] in power_name:
|
||||
if sp_single['effect_attr'] == 'dmgBonus':
|
||||
sp_dmgBonus += sp_single['effect_value']
|
||||
elif sp_single['effect_attr'] == 'addDmg':
|
||||
sp_addDmg += sp_single['effect_value']
|
||||
elif sp_single['effect_attr'] == 'atk':
|
||||
sp_attack += sp_single['effect_value']
|
||||
else:
|
||||
sp_attack += sp_single['effect_value']
|
||||
|
||||
# 根据type计算有效属性
|
||||
if '攻击' in power_list[power_name]['type']:
|
||||
effect_prop = prop[f'{power_name[0]}_atk'] + sp_attack
|
||||
elif '生命值' in power_list[power_name]['type']:
|
||||
effect_prop = prop[f'{power_name[0]}_hp']
|
||||
elif '防御' in power_list[power_name]['type']:
|
||||
effect_prop = prop[f'{power_name[0]}_def']
|
||||
else:
|
||||
effect_prop = prop[f'{power_name[0]}_atk']
|
||||
|
||||
# 按照ABCEQ等级查找倍率
|
||||
power: str = power_list[power_name]['value'][
|
||||
prop['{}_skill_level'.format(power_name[0])] - 1
|
||||
]
|
||||
# 计算是否多次伤害
|
||||
power_plus = power_list[power_name]['plus']
|
||||
|
||||
# 拿到百分比和固定值,百分比为float,形如2.2 也就是202%
|
||||
power_percent, power_value = await power_to_value(power, power_plus)
|
||||
|
||||
# 额外加成,目前只有雷神
|
||||
if extra_effect and power_name in extra_effect:
|
||||
power_percent += extra_effect[power_name]
|
||||
|
||||
# 计算这个label的伤害加成为多少
|
||||
# 这个label是否为物理伤害
|
||||
if power_name in ['Q光降之剑基础伤害', 'Q光降之剑基础伤害(13层)', 'Q每层能量伤害']:
|
||||
dmgBonus_cal = (
|
||||
prop['{}_dmgBonus'.format(attack_type)]
|
||||
+ sp_dmgBonus
|
||||
+ prop['physicalDmgBonus']
|
||||
)
|
||||
# 常规元素伤害
|
||||
else:
|
||||
dmgBonus_cal = (
|
||||
prop['{}_dmgBonus'.format(attack_type)] + sp_dmgBonus
|
||||
)
|
||||
# 计算暴击伤害
|
||||
critdmg_cal = prop['{}_critDmg'.format(attack_type)]
|
||||
# 计算暴击率
|
||||
critrate_cal = prop['{}_critRate'.format(attack_type)]
|
||||
em_cal = prop['{}_elementalMastery'.format(attack_type)]
|
||||
# 计算防御区
|
||||
d_cal = (char_level + 100) / (
|
||||
(char_level + 100)
|
||||
+ (1 - prop['{}_d'.format(attack_type)])
|
||||
* (1 - prop['{}_ignoreDef'.format(attack_type)])
|
||||
* (enemy_level + 100)
|
||||
)
|
||||
# 计算抗性区
|
||||
if prop['{}_r'.format(attack_type)] > 0.75:
|
||||
r = 1 / (1 + 4 * prop['{}_r'.format(attack_type)])
|
||||
elif prop['{}_r'.format(attack_type)] > 0:
|
||||
r = 1 - prop['{}_r'.format(attack_type)]
|
||||
else:
|
||||
r = 1 - prop['{}_r'.format(attack_type)] / 2
|
||||
|
||||
# 特殊buff计算
|
||||
if '前台' in power_list[power_name]['name']:
|
||||
if char_name == '纳西妲':
|
||||
em_cal += 0.25 * em_cal if 0.25 * em_cal <= 250 else 250
|
||||
|
||||
# 计算元素反应 增幅
|
||||
for reaction in ['蒸发', '融化']:
|
||||
if reaction in power_list[power_name]['name']:
|
||||
k = 0
|
||||
if reaction == '蒸发':
|
||||
if raw_data['avatarElement'] == 'Pyro':
|
||||
k = 1.5
|
||||
else:
|
||||
k = 2
|
||||
elif reaction == '融化':
|
||||
if raw_data['avatarElement'] == 'Pyro':
|
||||
k = 2
|
||||
else:
|
||||
k = 1.5
|
||||
reaction_add_dmg = k * (
|
||||
1 + (2.78 * em_cal) / (em_cal + 1400) + prop['a']
|
||||
)
|
||||
break
|
||||
else:
|
||||
reaction_add_dmg = 1
|
||||
|
||||
# 计算草系相关反应
|
||||
reaction_power = 0
|
||||
for reaction in ['超激化', '蔓激化']:
|
||||
if reaction in power_list[power_name]['name']:
|
||||
if reaction == '超激化':
|
||||
k = 2.3
|
||||
else:
|
||||
k = 2.5
|
||||
power_times = 1
|
||||
if '*' in power_list[power_name]['name']:
|
||||
power_times = float(
|
||||
(
|
||||
power_list[power_name]['name']
|
||||
.split('*')[-1]
|
||||
.replace(')', '')
|
||||
)
|
||||
)
|
||||
reaction_power = (
|
||||
k
|
||||
* base_value_list[char_level - 1]
|
||||
* (1 + (5 * em_cal) / (em_cal + 1200))
|
||||
) * power_times
|
||||
break
|
||||
|
||||
# 草系反应增加到倍率区
|
||||
power_value += reaction_power
|
||||
|
||||
# 计算直接增加的伤害
|
||||
add_dmg = prop['{}_addDmg'.format(attack_type)] + sp_addDmg
|
||||
add_heal = prop['{}_addHeal'.format(attack_type)]
|
||||
|
||||
# 特殊伤害提高
|
||||
sp_power_percent = 0
|
||||
if '13层' in power_name:
|
||||
sp_power_percent = (
|
||||
float(
|
||||
power_list['Q每层能量伤害']['value'][
|
||||
prop['{}_skill_level'.format(power_name[0])] - 1
|
||||
].replace('%', '')
|
||||
)
|
||||
/ 100
|
||||
) * 13
|
||||
|
||||
if '灭净三业' in power_name or '业障除' in power_name:
|
||||
power_sp = power.replace('%', '').split('+')
|
||||
base_calc = (
|
||||
float(power_sp[0]) * prop['E_atk'] / 100
|
||||
+ float(power_sp[1]) * em_cal / 100
|
||||
+ reaction_power
|
||||
+ add_dmg
|
||||
)
|
||||
else:
|
||||
base_calc = (
|
||||
effect_prop * (power_percent + sp_power_percent)
|
||||
+ power_value
|
||||
+ add_dmg
|
||||
)
|
||||
|
||||
# 根据label_name 计算数值
|
||||
if '治疗' in power_name:
|
||||
crit_dmg = avg_dmg = (
|
||||
effect_prop * power_percent + power_value + add_heal
|
||||
) * (1 + prop['healBonus'])
|
||||
elif '扩散伤害' in power_name:
|
||||
crit_dmg = avg_dmg = (
|
||||
base_value_list[char_level - 1]
|
||||
* 1.2
|
||||
* (1 + (16.0 * em_cal) / (em_cal + 2000) + prop['a'])
|
||||
* (1 + prop['g'] / 100)
|
||||
* r
|
||||
)
|
||||
if power_list[power_name]['name'][0] == 'A':
|
||||
power_list[power_name]['name'] = power_list[power_name][
|
||||
'name'
|
||||
][1:]
|
||||
elif '绽放)' in power_name:
|
||||
if '丰穰之核' in power_name:
|
||||
ex_add = ((prop['hp'] - 30000) / 1000) * 0.09
|
||||
if ex_add >= 4:
|
||||
ex_add = 4
|
||||
if '(暴击)' not in power_name:
|
||||
prop['a'] += ex_add
|
||||
if '(暴击)' in power_name:
|
||||
critdmg_cal = 2
|
||||
else:
|
||||
critdmg_cal = 1
|
||||
|
||||
if '烈绽放' in power_name or '超绽放' in power_name:
|
||||
base_time = 6
|
||||
else:
|
||||
base_time = 4
|
||||
crit_dmg = avg_dmg = (
|
||||
base_value_list[char_level - 1]
|
||||
* base_time
|
||||
* (1 + (16.0 * em_cal) / (em_cal + 2000) + prop['a'])
|
||||
* r
|
||||
* critdmg_cal
|
||||
)
|
||||
if power_list[power_name]['name'][0] == 'A':
|
||||
power_list[power_name]['name'] = power_list[power_name][
|
||||
'name'
|
||||
][1:]
|
||||
elif '伤害值提升' in power_name:
|
||||
crit_dmg = avg_dmg = effect_prop * power_percent + power_value
|
||||
elif '护盾' in power_name:
|
||||
crit_dmg = avg_dmg = (
|
||||
effect_prop * power_percent + power_value
|
||||
) * (1 + prop['shieldBonus'])
|
||||
if char_name == '钟离' and '总护盾量' in power_name:
|
||||
crit_dmg = avg_dmg = avg_dmg * 1.5
|
||||
elif '提升' in power_name or '提高' in power_name:
|
||||
crit_dmg = avg_dmg = effect_prop * power_percent + power_value
|
||||
else:
|
||||
# 不暴击伤害
|
||||
normal_dmg = (
|
||||
base_calc * (1 + dmgBonus_cal) * d_cal * r * reaction_add_dmg
|
||||
)
|
||||
# 暴击伤害
|
||||
crit_dmg = normal_dmg * (1 + critdmg_cal)
|
||||
# 平均伤害
|
||||
avg_dmg = crit_dmg * critrate_cal + (1 - critrate_cal) * normal_dmg
|
||||
# 如果平均伤害超过了暴击伤害,则直接使用暴击伤害(暴击率>100)
|
||||
if avg_dmg >= crit_dmg:
|
||||
avg_dmg = crit_dmg
|
||||
# 如果暴击率为负数,则期望伤害直接使用普通伤害
|
||||
if critrate_cal <= 0:
|
||||
avg_dmg = normal_dmg
|
||||
result_draw.text((450, 22), '暴击值', title_color, text_size, anchor='lm')
|
||||
result_draw.text((615, 22), '期望值', title_color, text_size, anchor='lm')
|
||||
result_draw.text((780, 22), '普通值', title_color, text_size, anchor='lm')
|
||||
|
||||
for index, name in enumerate(dmg_data):
|
||||
result_draw.text(
|
||||
(45, 22 + (index + 1) * 40),
|
||||
power_list[power_name]['name'],
|
||||
name,
|
||||
text_color,
|
||||
text_size,
|
||||
anchor='lm',
|
||||
)
|
||||
result_draw.text(
|
||||
(460, 22 + (index + 1) * 40),
|
||||
str(round(crit_dmg)),
|
||||
(450, 22 + (index + 1) * 40),
|
||||
str(round(dmg_data[name]['crit'])),
|
||||
text_color,
|
||||
text_size,
|
||||
anchor='lm',
|
||||
)
|
||||
result_draw.text(
|
||||
(695, 22 + (index + 1) * 40),
|
||||
str(round(avg_dmg)),
|
||||
(615, 22 + (index + 1) * 40),
|
||||
str(round(dmg_data[name]['avg'])),
|
||||
text_color,
|
||||
text_size,
|
||||
anchor='lm',
|
||||
)
|
||||
result_draw.text(
|
||||
(780, 22 + (index + 1) * 40),
|
||||
str(round(dmg_data[name]['normal'])),
|
||||
text_color,
|
||||
text_size,
|
||||
anchor='lm',
|
||||
)
|
||||
|
||||
return result_img, len(power_list) + 2
|
||||
|
||||
|
||||
async def power_to_value(power: str, power_plus: int) -> Tuple[float, float]:
|
||||
"""
|
||||
将power转换为value
|
||||
"""
|
||||
# 如果存在123%+123%形式的
|
||||
if '+' in power:
|
||||
power_percent = (
|
||||
float(power.split('+')[0].replace('%', '')) / 100
|
||||
) * power_plus
|
||||
power_value = power.split('+')[1]
|
||||
if '%' in power_value:
|
||||
power_percent += (
|
||||
float(power_value.replace('%', '')) / 100 * power_plus
|
||||
)
|
||||
power_value = 0
|
||||
else:
|
||||
power_value = float(power_value)
|
||||
elif '%' in power:
|
||||
power_percent = float(power.replace('%', '')) / 100 * power_plus
|
||||
power_value = 0
|
||||
else:
|
||||
power_percent = 0
|
||||
power_value = float(power)
|
||||
|
||||
return power_percent, power_value
|
||||
return result_img, len(dmg_data) + 2
|
||||
|
@ -1,14 +1,13 @@
|
||||
from io import BytesIO
|
||||
from typing import Tuple, Union, Optional
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from .mono.Fight import Character
|
||||
from .mono.Character import Character
|
||||
from .dmg_calc.dmg_calc import draw_dmg_img
|
||||
from .draw_char_curve import draw_char_curve_card
|
||||
from .etc.etc import TEXT_PATH, get_all_artifacts_value
|
||||
from ..utils.draw_image_tools.send_image_tool import convert_img
|
||||
from ..utils.genshin_fonts.genshin_fonts import genshin_font_origin
|
||||
from ..utils.genshin_fonts.genshin_fonts import gs_font_18, gs_font_50
|
||||
from .draw_normal import (
|
||||
get_bg_card,
|
||||
get_char_img,
|
||||
@ -18,25 +17,11 @@ from .draw_normal import (
|
||||
|
||||
|
||||
async def draw_char_img(
|
||||
raw_data: dict,
|
||||
weapon: Optional[str] = None,
|
||||
weapon_affix: Optional[int] = None,
|
||||
talent_num: Optional[int] = None,
|
||||
char: Character,
|
||||
charUrl: Optional[str] = None,
|
||||
is_curve: bool = False,
|
||||
) -> Union[str, Tuple[bytes, Optional[bytes]]]:
|
||||
|
||||
char = Character(card_prop=raw_data)
|
||||
err = await char.new(
|
||||
weapon=weapon,
|
||||
weapon_affix=weapon_affix,
|
||||
talent_num=talent_num,
|
||||
)
|
||||
if isinstance(err, str):
|
||||
return err
|
||||
|
||||
await char.init_prop()
|
||||
|
||||
if is_curve:
|
||||
res = await draw_char_curve_card(char, charUrl)
|
||||
else:
|
||||
@ -44,23 +29,8 @@ async def draw_char_img(
|
||||
return res, char.char_bytes
|
||||
|
||||
|
||||
async def get_dmg_card(
|
||||
card_prop: dict, fight_prop: dict, power_list: dict
|
||||
) -> Tuple[Image.Image, int]:
|
||||
# 拿到倍率表
|
||||
if power_list == {}:
|
||||
dmg_img, dmg_len = Image.new('RGBA', (950, 1)), 0
|
||||
else:
|
||||
dmg_img, dmg_len = await draw_dmg_img(
|
||||
card_prop, power_list, fight_prop
|
||||
)
|
||||
return dmg_img, dmg_len
|
||||
|
||||
|
||||
async def draw_char_card(char: Character, char_url: Optional[str]) -> bytes:
|
||||
dmg_img, dmg_len = await get_dmg_card(
|
||||
char.card_prop, char.fight_prop, char.power_list
|
||||
)
|
||||
dmg_img, dmg_len = await draw_dmg_img(char)
|
||||
char_img = await get_char_img(char, char_url)
|
||||
ex_len = dmg_len * 40 + 765
|
||||
img = await get_bg_card(char.char_element, ex_len, char_img)
|
||||
@ -75,26 +45,30 @@ async def draw_char_card(char: Character, char_url: Optional[str]) -> bytes:
|
||||
artifacts_all_score = await get_all_artifacts_value(
|
||||
char.card_prop, char.baseHp, char.baseAtk, char.baseDef, char.char_name
|
||||
)
|
||||
if char.percent == '0.00':
|
||||
percent_str = '暂无匹配'
|
||||
else:
|
||||
percent_str = f'{char.percent}%'
|
||||
# 角色评分
|
||||
img_text.text(
|
||||
(768, 1564),
|
||||
f'{round(artifacts_all_score, 1)}',
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(50),
|
||||
gs_font_50,
|
||||
anchor='mm',
|
||||
)
|
||||
img_text.text(
|
||||
(768, 1726),
|
||||
f'{str(char.percent)+"%"}',
|
||||
percent_str,
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(50),
|
||||
gs_font_50,
|
||||
anchor='mm',
|
||||
)
|
||||
img_text.text(
|
||||
(768, 1673),
|
||||
f'{char.seq_str}',
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(18),
|
||||
gs_font_18,
|
||||
anchor='mm',
|
||||
)
|
||||
res = await convert_img(img)
|
||||
|
@ -45,7 +45,7 @@ async def draw_char_curve_card(
|
||||
# 顶栏
|
||||
img_text.text(
|
||||
(475, 2240),
|
||||
f'曲线(上)为正常面板,曲线(下)为触发各种战斗buff后面板',
|
||||
'曲线(上)为正常面板,曲线(下)为触发各种战斗buff后面板',
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(32),
|
||||
anchor='mm',
|
||||
|
@ -5,11 +5,11 @@ from typing import Tuple, Union, Literal
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from .mono.Character import Character
|
||||
from .dmg_calc.dmg_calc import get_fight_prop
|
||||
from .dmg_calc.dmg_calc import get_char_dmg_percent
|
||||
from .etc.etc import TEXT_PATH, get_all_artifacts_value
|
||||
from ..utils.draw_image_tools.send_image_tool import convert_img
|
||||
from ..utils.genshin_fonts.genshin_fonts import genshin_font_origin
|
||||
from ..utils.alias.avatarId_to_char_star import avatar_id_to_char_star
|
||||
from .etc.etc import TEXT_PATH, get_char_percent, get_all_artifacts_value
|
||||
from ..utils.download_resource.RESOURCE_PATH import (
|
||||
CHAR_PATH,
|
||||
PLAYER_PATH,
|
||||
@ -49,7 +49,7 @@ level_map = {
|
||||
star_color_map = {
|
||||
'1': (94, 96, 95),
|
||||
'2': (17, 105, 156),
|
||||
'3': (97, 17, 156),
|
||||
'3': (91, 141, 192),
|
||||
'4': (143, 123, 174),
|
||||
'5': (205, 135, 76),
|
||||
}
|
||||
@ -93,10 +93,9 @@ async def draw_cahrcard_list(
|
||||
char = Character(raw_data)
|
||||
await char.new()
|
||||
await char.get_fight_prop()
|
||||
temp['percent'] = await get_char_percent(
|
||||
raw_data, char.fight_prop, char_name
|
||||
)
|
||||
temp['percent'] = float(temp['percent'][0])
|
||||
await get_char_dmg_percent(char)
|
||||
temp['percent'] = char.percent
|
||||
temp['percent'] = float(temp['percent'])
|
||||
temp['value'] = await get_all_artifacts_value(
|
||||
raw_data,
|
||||
char.baseHp,
|
||||
|
170
GenshinUID/genshinuid_enka/draw_group_dmg.py
Normal file
@ -0,0 +1,170 @@
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from .mono.Enemy import Enemy
|
||||
from .mono.Fight import Fight
|
||||
from .etc.etc import TEXT_PATH
|
||||
from .mono.Character import Character
|
||||
from .mono.SEQ import ALL_SEQ, SEQ_ARG
|
||||
from ..utils.download_resource.RESOURCE_PATH import CHAR_PATH
|
||||
from ..utils.draw_image_tools.send_image_tool import convert_img
|
||||
from ..utils.alias.avatarId_and_name_covert import name_to_avatar_id
|
||||
from ..utils.draw_image_tools.draw_image_tool import (
|
||||
get_color_bg,
|
||||
draw_pic_with_ring,
|
||||
)
|
||||
from ..utils.genshin_fonts.genshin_fonts import (
|
||||
gs_font_26,
|
||||
gs_font_32,
|
||||
gs_font_44,
|
||||
gs_font_50,
|
||||
)
|
||||
|
||||
TD_PATH = TEXT_PATH / 'team_dmg'
|
||||
|
||||
team_title = Image.open(TD_PATH / 'team_title.png')
|
||||
action_title = Image.open(TD_PATH / 'action_title.png')
|
||||
|
||||
|
||||
async def get_group_dmg_data(
|
||||
char_list: List[Character],
|
||||
) -> Union[Dict[float, Dict], str]:
|
||||
# 获取值
|
||||
enemy = Enemy(90, 90)
|
||||
char_dict: Dict[str, Character] = {}
|
||||
char_arg = [char.char_name for char in char_list]
|
||||
for arg in SEQ_ARG:
|
||||
if sorted(char_arg) == sorted(SEQ_ARG[arg]):
|
||||
seq = ALL_SEQ[arg]
|
||||
break
|
||||
else:
|
||||
return '暂时不支持该配队...'
|
||||
|
||||
for char in char_list:
|
||||
char_dict[char.char_name] = char
|
||||
fight = Fight(char_dict, enemy)
|
||||
fight.SEQ = seq
|
||||
|
||||
dmg_data: Dict[float, Dict] = await fight.update_dmg()
|
||||
return dmg_data
|
||||
|
||||
|
||||
def _f(value: float, is_float: bool = True) -> str:
|
||||
if is_float:
|
||||
return '{:.1f}'.format(value)
|
||||
else:
|
||||
return str(int(value))
|
||||
|
||||
|
||||
def _p(value: float) -> str:
|
||||
return '{:.2f}%'.format(value * 100)
|
||||
|
||||
|
||||
async def draw_group_dmg_img(
|
||||
uid: str, char_list: List[Character]
|
||||
) -> Union[bytes, str]:
|
||||
# 获取数据
|
||||
dmg_data = await get_group_dmg_data(char_list)
|
||||
if isinstance(dmg_data, str):
|
||||
return dmg_data
|
||||
|
||||
# 计算高度
|
||||
bar_offset = 65
|
||||
h = 900 + 120 + len(dmg_data) * bar_offset + 50
|
||||
|
||||
# 开始绘图
|
||||
img = await get_color_bg(950, h, 'teamdmg_bg')
|
||||
img.paste(team_title, (0, 0), team_title)
|
||||
|
||||
# 角色基本情况
|
||||
for index, char in enumerate(char_list):
|
||||
char_bg = Image.open(TD_PATH / 'char_bg.png')
|
||||
char_pic = Image.open(CHAR_PATH / f'{char.char_id}.png')
|
||||
char_img = await draw_pic_with_ring(char_pic, 100)
|
||||
char_bg.paste(char_img, (31, 27), char_img)
|
||||
|
||||
hp = _f(char.fight_prop['hp'], False)
|
||||
atk = _f(char.fight_prop['atk'], False)
|
||||
critr = _p(char.fight_prop['critRate'])
|
||||
critd = _p(char.fight_prop['critDmg'])
|
||||
lv = f'Lv.{char.char_level}'
|
||||
char_draw = ImageDraw.Draw(char_bg)
|
||||
char_draw.text((210, 69), hp, 'white', gs_font_26, 'lm')
|
||||
char_draw.text((344, 69), atk, 'white', gs_font_26, 'lm')
|
||||
char_draw.text((210, 130), critr, 'white', gs_font_26, 'lm')
|
||||
char_draw.text((344, 130), critd, 'white', gs_font_26, 'lm')
|
||||
char_draw.text((85, 154), lv, 'white', gs_font_26, 'mm')
|
||||
|
||||
# 将绘制好的角色卡贴到队伍伤害卡上
|
||||
img.paste(
|
||||
char_bg,
|
||||
(16 + 443 * (index % 2), 540 + 170 * (index // 2)),
|
||||
char_bg,
|
||||
)
|
||||
|
||||
img.paste(action_title, (0, 895), action_title)
|
||||
|
||||
# 初始化一些数值
|
||||
all_avgdmg = 0
|
||||
all_critdmg = 0
|
||||
dmg_info = {}
|
||||
|
||||
# 粘贴动作序列
|
||||
for index, time in enumerate(dmg_data):
|
||||
_data = dmg_data[time]
|
||||
char_id = await name_to_avatar_id(_data['char'])
|
||||
char_pic = Image.open(CHAR_PATH / f'{char_id}.png')
|
||||
char_img = await draw_pic_with_ring(char_pic, 50)
|
||||
|
||||
bar = Image.open(TD_PATH / 'dmg_bar.png')
|
||||
|
||||
bar.paste(char_img, (100, 10), char_img)
|
||||
|
||||
bar_draw = ImageDraw.Draw(bar)
|
||||
|
||||
# Action
|
||||
bar_draw.text((190, 35), _data['action'], 'white', gs_font_32, 'lm')
|
||||
# 具体伤害
|
||||
_dmg = _data['avg_dmg'] if _data['avg_dmg'] else _data['normal_dmg']
|
||||
bar_draw.text((600, 35), _f(_dmg), 'white', gs_font_32, 'lm')
|
||||
|
||||
img.paste(bar, (0, 1030 + index * bar_offset), bar)
|
||||
|
||||
# 总平均伤害加值
|
||||
all_avgdmg += _data['avg_dmg']
|
||||
all_critdmg += _data['crit_dmg']
|
||||
|
||||
# 计算一些数据
|
||||
if _data['char'] not in dmg_info:
|
||||
dmg_info[_data['char']] = _data['avg_dmg']
|
||||
else:
|
||||
dmg_info[_data['char']] += _data['avg_dmg']
|
||||
|
||||
ac_len = len(dmg_data)
|
||||
all_time = list(dmg_data.keys())[-1]
|
||||
avg_dps = all_avgdmg / all_time
|
||||
|
||||
char_id = '10000029'
|
||||
char_pic = Image.open(CHAR_PATH / f'{char_id}.png')
|
||||
char_img = await draw_pic_with_ring(char_pic, 280)
|
||||
img.paste(char_img, (60, 78), char_img)
|
||||
|
||||
img_draw = ImageDraw.Draw(img)
|
||||
# UID
|
||||
img_draw.text((395, 98), f'UID{uid}', 'white', gs_font_50, 'lm')
|
||||
|
||||
# 标题
|
||||
img_draw.text((396, 200), '总期望伤害', 'white', gs_font_26, 'lm')
|
||||
img_draw.text((656, 200), '总暴击伤害', 'white', gs_font_26, 'lm')
|
||||
img_draw.text((396, 297), '平均DPS', 'white', gs_font_26, 'lm')
|
||||
img_draw.text((656, 297), f'{ac_len}个动作', 'white', gs_font_26, 'lm')
|
||||
|
||||
# 数值
|
||||
img_draw.text((390, 236), f'{_f(all_avgdmg)}', 'white', gs_font_44, 'lm')
|
||||
img_draw.text((650, 236), f'{_f(all_critdmg)}', 'white', gs_font_44, 'lm')
|
||||
img_draw.text((390, 333), f'{_f(avg_dps)}', 'white', gs_font_44, 'lm')
|
||||
img_draw.text((650, 333), f'{_f(all_time)}秒内', 'white', gs_font_44, 'lm')
|
||||
|
||||
img = await convert_img(img)
|
||||
return img
|
@ -1,19 +1,26 @@
|
||||
import math
|
||||
import random
|
||||
from io import BytesIO
|
||||
from typing import Optional
|
||||
|
||||
import aiofiles
|
||||
from httpx import get
|
||||
from PIL import Image, ImageDraw, ImageChops
|
||||
|
||||
from .mono.Character import Character
|
||||
from .etc.MAP_PATH import COLOR_MAP, avatarName2SkillAdd
|
||||
from ..utils.db_operation.db_operation import config_check
|
||||
from ..utils.draw_image_tools.draw_image_tool import CustomizeImage
|
||||
from ..genshinuid_config.default_config import string_config
|
||||
from ..utils.genshin_fonts.genshin_fonts import genshin_font_origin
|
||||
from .etc.etc import TEXT_PATH, strLenth, get_star_png, get_artifacts_value
|
||||
from ..utils.draw_image_tools.draw_image_tool import (
|
||||
CustomizeImage,
|
||||
get_weapon_affix_pic,
|
||||
)
|
||||
from ..utils.download_resource.RESOURCE_PATH import (
|
||||
REL_PATH,
|
||||
ICON_PATH,
|
||||
CU_CHBG_PATH,
|
||||
GACHA_IMG_PATH,
|
||||
CHAR_STAND_PATH,
|
||||
)
|
||||
@ -25,33 +32,37 @@ ARTIFACTS_POS = {
|
||||
'空之杯': (18, 1447),
|
||||
'理之冠': (318, 1447),
|
||||
}
|
||||
PIC_API = string_config.get_config('random_pic_API')
|
||||
|
||||
|
||||
async def get_char_card_base(char: Character) -> Image.Image:
|
||||
card_prop = char.card_prop
|
||||
char_info_1 = Image.open(TEXT_PATH / 'char_info_1.png')
|
||||
holo_temp = Image.new('RGBA', char_info_1.size, (0, 0, 0, 0))
|
||||
# 命座处理
|
||||
lock_img = Image.open(TEXT_PATH / 'icon_lock.png')
|
||||
holo_img = Image.open(TEXT_PATH / 'holo.png')
|
||||
# holo_img = Image.open(TEXT_PATH / 'holo.png')
|
||||
for talent_num in range(0, 6):
|
||||
if talent_num + 1 <= len(card_prop['talentList']):
|
||||
talent = card_prop['talentList'][talent_num]
|
||||
talent_img = Image.open(
|
||||
ICON_PATH / '{}.png'.format(talent['talentIcon'])
|
||||
)
|
||||
try:
|
||||
talent_img = Image.open(
|
||||
ICON_PATH / '{}.png'.format(talent['talentIcon'])
|
||||
)
|
||||
except Exception:
|
||||
talent_img = Image.open(
|
||||
ICON_PATH / 'UI_Talent_S_Kazuha_02.png'
|
||||
)
|
||||
talent_img_new = talent_img.resize(
|
||||
(50, 50), Image.Resampling.LANCZOS
|
||||
).convert("RGBA")
|
||||
holo_temp.paste(holo_img, (775, 300 + talent_num * 81), holo_img)
|
||||
holo_temp.paste(
|
||||
talent_img_new,
|
||||
(850, 375 + talent_num * 81),
|
||||
talent_img_new,
|
||||
)
|
||||
for _ in range(2):
|
||||
char_info_1.paste(
|
||||
talent_img_new,
|
||||
(850, 375 + talent_num * 81),
|
||||
talent_img_new,
|
||||
)
|
||||
else:
|
||||
holo_temp.paste(lock_img, (850, 375 + talent_num * 81), lock_img)
|
||||
char_info_1 = Image.alpha_composite(char_info_1, holo_temp)
|
||||
char_info_1.paste(lock_img, (850, 375 + talent_num * 81), lock_img)
|
||||
|
||||
# 天赋处理
|
||||
skillList = card_prop['avatarSkill']
|
||||
@ -153,6 +164,9 @@ async def get_char_card_base(char: Character) -> Image.Image:
|
||||
genshin_font_origin(28),
|
||||
anchor='mm',
|
||||
)
|
||||
affix_pic = await get_weapon_affix_pic(weaponAffix)
|
||||
char_info_1.paste(affix_pic, (420 + len(weaponName) * 50, 660), affix_pic)
|
||||
'''
|
||||
char_info_text.text(
|
||||
(517, 895),
|
||||
f'精炼{str(weaponAffix)}阶',
|
||||
@ -160,6 +174,7 @@ async def get_char_card_base(char: Character) -> Image.Image:
|
||||
genshin_font_origin(28),
|
||||
anchor='lm',
|
||||
)
|
||||
'''
|
||||
|
||||
weaponEffect = strLenth(weaponEffect, 25, 455)
|
||||
weaponEffect = '\n'.join(weaponEffect.split('\n')[:5])
|
||||
@ -367,12 +382,18 @@ async def get_char_img(
|
||||
char_name_url = '荧'
|
||||
else:
|
||||
char_name_url = char_name
|
||||
char_url = f'http://img.genshin.cherishmoon.fun/{char_name_url}'
|
||||
char_data = get(char_url, follow_redirects=True)
|
||||
if char_data.headers['Content-Type'] == 'application/json':
|
||||
char_url = None
|
||||
chbg_path = CU_CHBG_PATH / char_name_url
|
||||
char_url = f'{PIC_API}{char_name_url}'
|
||||
if chbg_path.exists():
|
||||
cuch_img = random.choice(list(chbg_path.iterdir()))
|
||||
async with aiofiles.open(cuch_img, 'rb') as f:
|
||||
char.char_bytes = await f.read()
|
||||
else:
|
||||
char.char_bytes = char_data.content
|
||||
char_data = get(char_url, follow_redirects=True)
|
||||
if 'application/json' in char_data.headers['Content-Type']:
|
||||
char_url = None
|
||||
else:
|
||||
char.char_bytes = char_data.content
|
||||
|
||||
based_w, based_h = 600, 1200
|
||||
if char_url:
|
||||
@ -400,7 +421,7 @@ async def get_char_img(
|
||||
new_h = math.ceil(based_new_w / float(scale_f))
|
||||
if scale_f > based_scale:
|
||||
bg_img2 = char_img.resize(
|
||||
(new_w, based_new_h), Image.Resampling.LANCZOS # type: ignore
|
||||
(new_w, based_new_h), Image.Resampling.LANCZOS
|
||||
)
|
||||
x1 = new_w / 2 - based_new_w / 2 + offset_x
|
||||
y1 = 0 + offset_y / 2
|
||||
@ -408,7 +429,7 @@ async def get_char_img(
|
||||
y2 = based_new_h - offset_y / 2
|
||||
else:
|
||||
bg_img2 = char_img.resize(
|
||||
(based_new_w, new_h), Image.Resampling.LANCZOS # type: ignore
|
||||
(based_new_w, new_h), Image.Resampling.LANCZOS
|
||||
)
|
||||
x1 = 0 + offset_x
|
||||
y1 = new_h / 2 - based_new_h / 2 + offset_y / 2
|
||||
@ -431,24 +452,32 @@ async def get_artifacts_card(char: Character, img: Image.Image):
|
||||
REL_PATH / '{}.png'.format(aritifact['aritifactName'])
|
||||
)
|
||||
artifacts_piece_new_img = artifacts_piece_img.resize(
|
||||
(75, 75), Image.Resampling.LANCZOS
|
||||
(120, 120), Image.Resampling.LANCZOS
|
||||
).convert("RGBA")
|
||||
|
||||
artifacts_img.paste(
|
||||
artifacts_piece_new_img, (195, 35), artifacts_piece_new_img
|
||||
artifacts_piece_new_img, (165, 22), artifacts_piece_new_img
|
||||
)
|
||||
aritifactStar_img = get_star_png(aritifact['aritifactStar'])
|
||||
artifactsPos = aritifact['aritifactPieceName']
|
||||
|
||||
artifacts_img.paste(aritifactStar_img, (20, 165), aritifactStar_img)
|
||||
# 圣遗物星星和名称&位置
|
||||
artifacts_img.paste(aritifactStar_img, (16, 115), aritifactStar_img)
|
||||
artifacts_text = ImageDraw.Draw(artifacts_img)
|
||||
if len(aritifact['aritifactName']) <= 5:
|
||||
main_name = aritifact['aritifactName']
|
||||
else:
|
||||
main_name = (
|
||||
aritifact['aritifactName'][:2] + aritifact['aritifactName'][4:]
|
||||
)
|
||||
artifacts_text.text(
|
||||
(30, 66),
|
||||
aritifact['aritifactName'][:4],
|
||||
(22, 100),
|
||||
main_name,
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(34),
|
||||
genshin_font_origin(28),
|
||||
anchor='lm',
|
||||
)
|
||||
'''
|
||||
artifacts_text.text(
|
||||
(30, 102),
|
||||
artifactsPos,
|
||||
@ -456,10 +485,11 @@ async def get_artifacts_card(char: Character, img: Image.Image):
|
||||
genshin_font_origin(20),
|
||||
anchor='lm',
|
||||
)
|
||||
'''
|
||||
|
||||
mainValue = aritifact['reliquaryMainstat']['statValue']
|
||||
mainName = aritifact['reliquaryMainstat']['statName']
|
||||
mainLevel = aritifact['aritifactLevel']
|
||||
mainValue: float = aritifact['reliquaryMainstat']['statValue']
|
||||
mainName: str = aritifact['reliquaryMainstat']['statName']
|
||||
mainLevel: int = aritifact['aritifactLevel']
|
||||
|
||||
if mainName in ['攻击力', '血量', '防御力', '元素精通']:
|
||||
mainValueStr = str(mainValue)
|
||||
@ -474,31 +504,31 @@ async def get_artifacts_card(char: Character, img: Image.Image):
|
||||
)
|
||||
|
||||
artifacts_text.text(
|
||||
(30, 141),
|
||||
(34, 174),
|
||||
mainNameNew,
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(28),
|
||||
anchor='lm',
|
||||
)
|
||||
artifacts_text.text(
|
||||
(263, 141),
|
||||
(266, 174),
|
||||
mainValueStr,
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(28),
|
||||
anchor='rm',
|
||||
)
|
||||
artifacts_text.text(
|
||||
(55, 219),
|
||||
(246, 132),
|
||||
'+{}'.format(str(mainLevel)),
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(24),
|
||||
genshin_font_origin(23),
|
||||
anchor='mm',
|
||||
)
|
||||
|
||||
artifactsScore = 0
|
||||
for index, i in enumerate(aritifact['reliquarySubstats']):
|
||||
subName = i['statName']
|
||||
subValue = i['statValue']
|
||||
subName: str = i['statName']
|
||||
subValue: float = i['statValue']
|
||||
if subName in ['攻击力', '血量', '防御力', '元素精通']:
|
||||
subValueStr = str(subValue)
|
||||
else:
|
||||
@ -513,38 +543,54 @@ async def get_artifacts_card(char: Character, img: Image.Image):
|
||||
)
|
||||
artifactsScore += value_temp
|
||||
subNameStr = subName.replace('百分比', '').replace('元素', '')
|
||||
# 副词条文字颜色
|
||||
if value_temp == 0:
|
||||
artifacts_color = (160, 160, 160)
|
||||
elif value_temp >= 5.1:
|
||||
artifacts_color = (247, 50, 50)
|
||||
elif value_temp >= 3.7:
|
||||
artifacts_color = (255, 255, 100)
|
||||
else:
|
||||
artifacts_color = (250, 250, 250)
|
||||
artifacts_color = (255, 255, 255)
|
||||
|
||||
# 副词条底色
|
||||
if value_temp >= 3.4:
|
||||
artifacts_bg = (205, 135, 76)
|
||||
if value_temp >= 4.5:
|
||||
artifacts_bg = (158, 39, 39)
|
||||
artifacts_text.rounded_rectangle(
|
||||
(22, 209 + index * 35, 274, 238 + index * 35),
|
||||
fill=artifacts_bg,
|
||||
radius=8,
|
||||
)
|
||||
|
||||
artifacts_text.text(
|
||||
(20, 256 + index * 33),
|
||||
(22, 225 + index * 35),
|
||||
'·{}'.format(subNameStr),
|
||||
artifacts_color,
|
||||
genshin_font_origin(25),
|
||||
anchor='lm',
|
||||
)
|
||||
artifacts_text.text(
|
||||
(268, 256 + index * 33),
|
||||
(266, 225 + index * 35),
|
||||
'{}'.format(subValueStr),
|
||||
artifacts_color,
|
||||
genshin_font_origin(25),
|
||||
anchor='rm',
|
||||
)
|
||||
if artifactsScore >= 6:
|
||||
artifactsScore_color = (247, 26, 26)
|
||||
if artifactsScore >= 8.4:
|
||||
artifactsScore_color = (158, 39, 39)
|
||||
elif artifactsScore >= 6.5:
|
||||
artifactsScore_color = (205, 135, 76)
|
||||
elif artifactsScore >= 5.2:
|
||||
artifactsScore_color = (143, 123, 174)
|
||||
else:
|
||||
artifactsScore_color = (255, 255, 255)
|
||||
artifactsScore_color = (94, 96, 95)
|
||||
char.artifacts_all_score += artifactsScore
|
||||
artifacts_text.rounded_rectangle(
|
||||
(21, 45, 104, 75), fill=artifactsScore_color, radius=8
|
||||
)
|
||||
artifacts_text.text(
|
||||
(268, 190),
|
||||
(26, 60),
|
||||
'{:.2f}'.format(artifactsScore) + '条',
|
||||
artifactsScore_color,
|
||||
(255, 255, 255),
|
||||
genshin_font_origin(23),
|
||||
anchor='rm',
|
||||
anchor='lm',
|
||||
)
|
||||
img.paste(artifacts_img, ARTIFACTS_POS[artifactsPos], artifacts_img)
|
||||
|
@ -1,4 +1,60 @@
|
||||
{
|
||||
"乐园遗落之花": {
|
||||
"normal_effect": {
|
||||
"2": "elementalMastery+80",
|
||||
"4": ""
|
||||
},
|
||||
"fight_effect": {
|
||||
"2": "",
|
||||
"4": "a+40"
|
||||
},
|
||||
"group_effect": {
|
||||
"2": "",
|
||||
"4": ""
|
||||
}
|
||||
},
|
||||
"水仙之梦": {
|
||||
"normal_effect": {
|
||||
"2": "HydroDmgBonus+15",
|
||||
"4": ""
|
||||
},
|
||||
"fight_effect": {
|
||||
"2": "",
|
||||
"4": "addAtk+25;HydroDmgBonus+15"
|
||||
},
|
||||
"group_effect": {
|
||||
"2": "",
|
||||
"4": ""
|
||||
}
|
||||
},
|
||||
"花海甘露之光": {
|
||||
"normal_effect": {
|
||||
"2": "addHp+20",
|
||||
"4": ""
|
||||
},
|
||||
"fight_effect": {
|
||||
"2": "",
|
||||
"4": "EQ:dmgBonus+50"
|
||||
},
|
||||
"group_effect": {
|
||||
"2": "",
|
||||
"4": ""
|
||||
}
|
||||
},
|
||||
"沙上楼阁史话": {
|
||||
"normal_effect": {
|
||||
"2": "AnemoDmgBonus+15",
|
||||
"4": ""
|
||||
},
|
||||
"fight_effect": {
|
||||
"2": "",
|
||||
"4": "ABC:dmgBonus+40"
|
||||
},
|
||||
"group_effect": {
|
||||
"2": "",
|
||||
"4": ""
|
||||
}
|
||||
},
|
||||
"深林的记忆": {
|
||||
"normal_effect": {
|
||||
"2": "DendroDmgBonus+15",
|
||||
@ -6,7 +62,7 @@
|
||||
},
|
||||
"fight_effect": {
|
||||
"2": "",
|
||||
"4": "r+-30"
|
||||
"4": "DendroResist+-30"
|
||||
},
|
||||
"group_effect": {
|
||||
"2": "",
|
||||
@ -104,7 +160,7 @@
|
||||
},
|
||||
"fight_effect": {
|
||||
"2": "",
|
||||
"4": "Q:dmgBonus+75%25%energyRecharge"
|
||||
"4": "Q:dmgBonus+75%25%energyrecharge"
|
||||
},
|
||||
"group_effect": {
|
||||
"2": "",
|
||||
@ -328,7 +384,7 @@
|
||||
},
|
||||
"fight_effect": {
|
||||
"2": "",
|
||||
"4": "g+60;r+-40"
|
||||
"4": "g+60;Resist+-40"
|
||||
},
|
||||
"group_effect": {
|
||||
"2": "",
|
||||
|
@ -357,7 +357,7 @@
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"2": "E:dmgBonus+200",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
@ -399,11 +399,11 @@
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "r+-12",
|
||||
"2": "AnemoResist+-12;PhysicalResist+-12",
|
||||
"3": "",
|
||||
"4": "dmgBonus+25",
|
||||
"5": "",
|
||||
"6": "r+-20"
|
||||
"6": "Resist+-20"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
@ -440,7 +440,7 @@
|
||||
"70": "addAtk+10"
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "r+-15",
|
||||
"1": "PyroResist+-15",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
@ -487,7 +487,7 @@
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "r+-15"
|
||||
"6": "ElectroResist+-15"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
@ -525,7 +525,7 @@
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "r+-15",
|
||||
"2": "HydroResist+-15",
|
||||
"3": "",
|
||||
"4": "E:dmgBonus+50",
|
||||
"5": "",
|
||||
@ -941,7 +941,7 @@
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": "r+-10"
|
||||
"70": "CryoResist+-10"
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
@ -986,7 +986,7 @@
|
||||
"70": "BE:dmgBonus+20"
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "r+-15",
|
||||
"1": "CryoResist+-15",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "dmgBonus+15",
|
||||
@ -1109,7 +1109,7 @@
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": "dmgBonus+20%energyRecharge"
|
||||
"70": "dmgBonus+20%energyrecharge"
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
@ -1241,9 +1241,9 @@
|
||||
"1": "",
|
||||
"2": "Q:critRate+100",
|
||||
"3": "",
|
||||
"4": "r+-15",
|
||||
"4": "PhysicalResist+-15",
|
||||
"5": "",
|
||||
"6": "B:addAtk+50%def"
|
||||
"6": "B:exAtk+50%def"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
@ -1285,7 +1285,7 @@
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "r+-20"
|
||||
"6": "PhysicalResist+-20"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
@ -1850,7 +1850,7 @@
|
||||
"夜兰": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "hp+43",
|
||||
"50": "hp+52",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
@ -2405,7 +2405,7 @@
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "critRate+30%0.06%hp;critDmg+60%0.12%hp"
|
||||
"6": "critRate+30%0.0006%hp;critDmg+60%0.0012%hp"
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
@ -2415,7 +2415,7 @@
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "水月:dmgBonus+65",
|
||||
"2": "r+-35",
|
||||
"2": "HydroResist+-35;DendroResist+-35",
|
||||
"3": "",
|
||||
"4": "Q:dmgBonus+50",
|
||||
"5": "",
|
||||
@ -2644,5 +2644,467 @@
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"旅行者(风)": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "energyRecharge+16",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "Resist+-20"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"旅行者(岩)": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "critRate+10",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"旅行者(雷)": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "ElectroResist+-15",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"旅行者(草)": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "elementalMastery+60",
|
||||
"70": "Q:dmgBonus+0.12%elementalMastery;光幕攻击伤害:dmgBonus+0.12%elementalMastery"
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "dmgBonus+12"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "elementalMastery+60",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "dmgBonus+12"
|
||||
}
|
||||
}
|
||||
},
|
||||
"艾尔海森": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": "E:dmgBonus+100%0.15%elementalMastery;Q:dmgBonus+100%0.1%elementalMastery"
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "critDmg+70;critRate+10"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "elementalMastery+90",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"瑶瑶": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "DendroDmgBonus+15",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "elementalMastery+120%0.3%hp",
|
||||
"5": "",
|
||||
"6": ""
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "DendroDmgBonus+15",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"迪希雅": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "addHp+15;E:addDmg+5.2%hp;Q:addDmg+8.4%hp",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "Q:critRate+10;Q:critDmg+60"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"米卡": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "A:critDmg+60"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"卡维": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": "elementalMastery+100"
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"白术": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": "DendroDmgBonus+25"
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "elementalMastery+80",
|
||||
"5": "",
|
||||
"6": "灵气脉:addDmg+6%hp"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "elementalMastery+80",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"绮良良": {
|
||||
"normal": {
|
||||
"normal_skill": {
|
||||
"50": "",
|
||||
"70": "E:dmgBonus+0.0004%hp;Q:dmgBonus+0.0003%hp"
|
||||
},
|
||||
"normal_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"fight": {
|
||||
"fight_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"fight_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "EQ:dmgBonus+12"
|
||||
},
|
||||
"group_skill": {
|
||||
"50": "",
|
||||
"70": ""
|
||||
},
|
||||
"group_talent": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "dmgBonus+12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -254,5 +254,13 @@
|
||||
"珐露珊": [
|
||||
"E",
|
||||
"Q"
|
||||
],
|
||||
"卡维": [
|
||||
"Q",
|
||||
"E"
|
||||
],
|
||||
"白术": [
|
||||
"Q",
|
||||
"E"
|
||||
]
|
||||
}
|
@ -314,5 +314,51 @@
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"流浪者": [
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"攻击力"
|
||||
],
|
||||
"珐露珊": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"艾尔海森": [
|
||||
"元素精通",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"瑶瑶": [
|
||||
"元素充能效率",
|
||||
"血量",
|
||||
"攻击力"
|
||||
],
|
||||
"迪希雅": [
|
||||
"血量",
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"米卡": [
|
||||
"元素充能效率",
|
||||
"血量"
|
||||
],
|
||||
"白术": [
|
||||
"血量",
|
||||
"元素充能效率",
|
||||
"精通",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"卡维": [
|
||||
"元素充能效率",
|
||||
"精通",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"攻击力"
|
||||
]
|
||||
}
|
@ -1,9 +1,17 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
from typing import Dict, List, TypedDict
|
||||
|
||||
EFFECT_PATH = Path(__file__).parents[1] / 'effect'
|
||||
|
||||
|
||||
class ActionMAP(TypedDict):
|
||||
name: str
|
||||
type: str
|
||||
plus: float
|
||||
value: List[str]
|
||||
|
||||
|
||||
with open(EFFECT_PATH / 'weapon_effect.json', "r", encoding='UTF-8') as f:
|
||||
weapon_effect_map: Dict[
|
||||
str, Dict[str, Dict[str, Dict[str, str]]]
|
||||
@ -21,7 +29,7 @@ with open(EFFECT_PATH / 'value_attr.json', 'r', encoding='UTF-8') as f:
|
||||
ATTR_MAP: Dict[str, List[str]] = json.load(f)
|
||||
|
||||
with open(EFFECT_PATH / 'char_action.json', 'r', encoding='UTF-8') as f:
|
||||
char_action = json.load(f)
|
||||
char_action: Dict[str, Dict[str, ActionMAP]] = json.load(f)
|
||||
|
||||
with open(EFFECT_PATH / 'dmg_map.json', 'r', encoding='UTF-8') as f:
|
||||
dmgMap = json.load(f)
|
||||
@ -36,5 +44,5 @@ COLOR_MAP = {
|
||||
'Electro': (133, 12, 159),
|
||||
'Geo': (147, 112, 3),
|
||||
'Hydro': (51, 73, 162),
|
||||
'Pyro': (119, 12, 17),
|
||||
'Pyro': (136, 28, 33),
|
||||
}
|
||||
|
83
GenshinUID/genshinuid_enka/etc/base_info.py
Normal file
@ -0,0 +1,83 @@
|
||||
PERCENT_ATTR = ['dmgBonus', 'addAtk', 'addDef', 'addHp']
|
||||
|
||||
baseWeaponInfo = {
|
||||
'itemId': 0,
|
||||
'nameTextMapHash': '0',
|
||||
'weaponIcon': 'UI_EquipIcon_Bow_Changed',
|
||||
'weaponType': '',
|
||||
'weaponName': '',
|
||||
'weaponStar': 0,
|
||||
'promoteLevel': 0,
|
||||
'weaponLevel': 0,
|
||||
'weaponAffix': 1,
|
||||
'weaponStats': [
|
||||
{
|
||||
'appendPropId': '',
|
||||
'statName': '基础攻击力',
|
||||
'statValue': 0,
|
||||
},
|
||||
{
|
||||
'appendPropId': '',
|
||||
'statName': '',
|
||||
'statValue': 0,
|
||||
},
|
||||
],
|
||||
'weaponEffect': '',
|
||||
}
|
||||
|
||||
baseFightProp = {
|
||||
'hp': 0.0,
|
||||
'baseHp': 0.0,
|
||||
'addHp': 0.0,
|
||||
'exHp': 0.0,
|
||||
'atk': 0.0,
|
||||
'baseAtk': 0.0,
|
||||
'addAtk': 0.0,
|
||||
'exAtk': 0.0,
|
||||
'def': 0.0,
|
||||
'baseDef': 0.0,
|
||||
'addDef': 0.0,
|
||||
'exDef': 0.0,
|
||||
'elementalMastery': 0.0,
|
||||
'critRate': 0.05,
|
||||
'critDmg': 0.5,
|
||||
'energyRecharge': 1.0,
|
||||
'healBonus': 0.0,
|
||||
'healedBonus': 0.0,
|
||||
'physicalDmgSub': 0.0,
|
||||
'physicalDmgBonus': 0.0,
|
||||
'dmgBonus': 0.0,
|
||||
}
|
||||
|
||||
ATTR_MAP = {
|
||||
'元素精通': 'elementalMastery',
|
||||
'物理伤害加成': 'physicalDmgBonus',
|
||||
'元素伤害加成': 'dmgBonus',
|
||||
'充能效率': 'energyRecharge',
|
||||
'暴击伤害': 'critDmg',
|
||||
'暴击率': 'critRate',
|
||||
'攻击力': 'addAtk',
|
||||
'防御力': 'addDef',
|
||||
'生命值': 'addHp',
|
||||
'百分比血量': 'addHp',
|
||||
}
|
||||
|
||||
ELEMENT_MAP = {
|
||||
'风': 'Anemo',
|
||||
'冰': 'Cryo',
|
||||
'草': 'Dendro',
|
||||
'雷': 'Electro',
|
||||
'岩': 'Geo',
|
||||
'水': 'Hydro',
|
||||
'火': 'Pyro',
|
||||
}
|
||||
|
||||
ICON_ELEMENT = {
|
||||
'风': 'Wind',
|
||||
'冰': 'Ice',
|
||||
'草': 'Grass',
|
||||
'水': 'Water',
|
||||
'雷': 'Electric',
|
||||
'岩': 'Rock',
|
||||
'火': 'Fire',
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
from typing import List
|
||||
from copy import deepcopy
|
||||
|
||||
from ...utils.enka_api.map.GS_MAP_PATH import (
|
||||
avatarName2Weapon,
|
||||
avatarName2Element,
|
||||
)
|
||||
|
||||
PERCENT_ATTR = ['dmgBonus', 'addAtk', 'addDef', 'addHp']
|
||||
|
||||
|
||||
async def get_effect_prop(
|
||||
prop: dict,
|
||||
effect_list: List[str],
|
||||
char_name: str,
|
||||
) -> dict:
|
||||
if 'A_d' not in prop:
|
||||
for attr in [
|
||||
'shieldBonus',
|
||||
'addDmg',
|
||||
'addHeal',
|
||||
'ignoreDef',
|
||||
'd',
|
||||
'g',
|
||||
'a',
|
||||
]:
|
||||
prop[attr] = 0
|
||||
prop['r'] = 0.1
|
||||
prop['k'] = 1
|
||||
prop['sp'] = []
|
||||
if prop['baseHp'] + prop['addHp'] == prop['hp']:
|
||||
prop['exHp'] = prop['addHp']
|
||||
prop['exAtk'] = prop['addAtk']
|
||||
prop['exDef'] = prop['addDef']
|
||||
prop['addHp'] = 0
|
||||
prop['addAtk'] = 0
|
||||
prop['addDef'] = 0
|
||||
|
||||
# 给每个技能 分别添加上属性
|
||||
for prop_attr in deepcopy(prop):
|
||||
for prop_limit in ['A', 'B', 'C', 'E', 'Q']:
|
||||
prop[f'{prop_limit}_{prop_attr}'] = prop[prop_attr]
|
||||
|
||||
weapon_type = avatarName2Weapon[char_name]
|
||||
|
||||
# 计算角色伤害加成应该使用什么
|
||||
for prop_limit in ['A', 'B', 'C', 'E', 'Q']:
|
||||
if weapon_type == '法器' or char_name in [
|
||||
'荒泷一斗',
|
||||
'刻晴',
|
||||
'诺艾尔',
|
||||
'胡桃',
|
||||
'宵宫',
|
||||
'魈',
|
||||
'神里绫华',
|
||||
]:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop['dmgBonus']
|
||||
elif weapon_type == '弓':
|
||||
if prop_limit in ['A', 'C']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop[
|
||||
'physicalDmgBonus'
|
||||
]
|
||||
elif prop_limit in ['B', 'E', 'Q']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop['dmgBonus']
|
||||
else:
|
||||
if prop_limit in ['A', 'B', 'C']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop[
|
||||
'physicalDmgBonus'
|
||||
]
|
||||
elif prop_limit in ['E', 'Q']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop['dmgBonus']
|
||||
|
||||
# 防止复数效果
|
||||
with_trans_effect: List[str] = []
|
||||
without_trans_effect: List[str] = []
|
||||
for effect in effect_list:
|
||||
if ';' in effect:
|
||||
effect = effect.split(';')
|
||||
else:
|
||||
effect = [effect]
|
||||
|
||||
for _effect in effect:
|
||||
if _effect == '':
|
||||
continue
|
||||
else:
|
||||
if '%' in _effect:
|
||||
with_trans_effect.append(_effect)
|
||||
else:
|
||||
without_trans_effect.append(_effect)
|
||||
|
||||
new_effect_list: List[str] = without_trans_effect + with_trans_effect
|
||||
|
||||
# 正式开始计算
|
||||
for effect in new_effect_list:
|
||||
# 分割效果
|
||||
# 例如:Q:dmgBonus+96%27%em
|
||||
# 分割后:
|
||||
# effect_limit = Q
|
||||
effect_limit = ''
|
||||
if ':' in effect:
|
||||
effect_limit = effect.split(':')[0]
|
||||
effect = effect.split(':')[1]
|
||||
|
||||
effect_attr, effect_value = effect.split('+')
|
||||
effect_max = 9999999
|
||||
effect_base: str = ''
|
||||
|
||||
# 判断effect_value中有几个百分号
|
||||
p_count = effect_value.count('%')
|
||||
# 如果有%,则认为是基于值的提升
|
||||
base_check = True
|
||||
if p_count >= 2:
|
||||
effect_max, effect_value, effect_base = effect_value.split('%')
|
||||
elif p_count == 1:
|
||||
effect_value, effect_base = effect_value.split('%')
|
||||
else:
|
||||
base_check = False
|
||||
|
||||
# effect_attr, effect_value, effect_base, effect_max
|
||||
# dmgBonus, 27, em, 96
|
||||
|
||||
# 暂时不处理extraDmg
|
||||
if effect_attr == 'extraDmg':
|
||||
continue
|
||||
|
||||
effect_max = float(effect_max) / 100
|
||||
# 如果要增加的属性不是em元素精通,那么都要除于100
|
||||
if effect_attr not in ['exHp', 'exAtk', 'exDef', 'elementalMastery']:
|
||||
# 正常除100
|
||||
effect_value = float(effect_value) / 100
|
||||
# 元素精通则为正常值
|
||||
else:
|
||||
if effect_base in ['hp', 'elementalMastery', 'def']:
|
||||
effect_value = float(effect_value) / 100
|
||||
else:
|
||||
effect_value = float(effect_value)
|
||||
|
||||
# 如果属性是血量,攻击,防御值,并且是按照%增加的,那么增加值应为百分比乘上基础值
|
||||
if base_check:
|
||||
if effect_base == 'energyRecharge':
|
||||
if effect_attr in PERCENT_ATTR:
|
||||
effect_base_value = prop[effect_base] - 1
|
||||
else:
|
||||
effect_base_value = (prop[effect_base] - 1) / 100
|
||||
|
||||
# 针对莫娜的
|
||||
if char_name == '莫娜':
|
||||
effect_base_value += 1
|
||||
elif effect_base == 'elementalMastery':
|
||||
# 针对草神的
|
||||
if char_name == '纳西妲' and effect_attr == 'dmgBonus':
|
||||
effect_base_value = (prop[effect_base] - 200) / 100
|
||||
else:
|
||||
effect_base_value = prop[effect_base]
|
||||
else:
|
||||
effect_base_value = prop[effect_base]
|
||||
effect_value = effect_value * effect_base_value
|
||||
|
||||
# 判断是否超过上限,超过则使用上限值
|
||||
if effect_value >= effect_max:
|
||||
effect_value = effect_max
|
||||
|
||||
if char_name == '旅行者':
|
||||
char_element = 'Hydro'
|
||||
else:
|
||||
char_element = avatarName2Element[char_name]
|
||||
|
||||
# 判断是否是自己属性的叠加
|
||||
if 'DmgBonus' in effect_attr:
|
||||
if effect_attr.replace('DmgBonus', '') == char_element:
|
||||
effect_attr = 'dmgBonus'
|
||||
else:
|
||||
continue
|
||||
|
||||
# 如果效果有限制条件
|
||||
if effect_limit:
|
||||
# 如果限制条件为中文,则为特殊label才生效
|
||||
if '\u4e00' <= effect_limit[-1] <= '\u9fff':
|
||||
prop['sp'].append(
|
||||
{
|
||||
'effect_name': effect_limit,
|
||||
'effect_attr': effect_attr,
|
||||
'effect_value': effect_value,
|
||||
}
|
||||
)
|
||||
# 如果限制条件为英文,例如Q,则为Q才生效
|
||||
else:
|
||||
# 形如ABC:dmgBonus+75,则遍历ABC,增加值
|
||||
for limit in effect_limit:
|
||||
prop['{}_{}'.format(limit, effect_attr)] += effect_value
|
||||
# 如果没有限制条件,直接增加
|
||||
else:
|
||||
if effect_attr in ['a', 'addDmg']:
|
||||
pass
|
||||
else:
|
||||
for attr in ['A', 'B', 'C', 'E', 'Q']:
|
||||
prop[f'{attr}_{effect_attr}'] += effect_value
|
||||
prop[f'{effect_attr}'] += effect_value
|
||||
|
||||
prop['hp'] = (prop['addHp'] + 1) * prop['baseHp'] + prop['exHp']
|
||||
prop['atk'] = (prop['addAtk'] + 1) * prop['baseAtk'] + prop['exAtk']
|
||||
prop['def'] = (prop['addDef'] + 1) * prop['baseDef'] + prop['exDef']
|
||||
for prop_limit in ['A', 'B', 'C', 'E', 'Q']:
|
||||
for attr in ['hp', 'atk', 'def']:
|
||||
attr_up = attr[0].upper() + attr[1:]
|
||||
prop[f'{prop_limit}_{attr}'] = (
|
||||
prop[f'{prop_limit}_add{attr_up}'] + 1
|
||||
) * prop[f'base{attr_up}'] + prop[f'ex{attr_up}']
|
||||
return prop
|
@ -1,5 +1,4 @@
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
from PIL import Image
|
||||
|
||||
@ -67,7 +66,7 @@ async def get_artifacts_score(subName: str, subValue: int) -> int:
|
||||
|
||||
async def get_artifacts_value(
|
||||
subName: str,
|
||||
subValue: int,
|
||||
subValue: float,
|
||||
baseAtk: int,
|
||||
baseHp: int,
|
||||
baseDef: int,
|
||||
@ -133,10 +132,7 @@ async def get_first_main(mainName: str) -> str:
|
||||
return equipMain
|
||||
|
||||
|
||||
async def get_char_percent(
|
||||
raw_data: dict, prop: dict, char_name: str
|
||||
) -> Tuple[str, str]:
|
||||
percent = '0.0'
|
||||
async def get_char_std(raw_data: dict, char_name: str) -> dmgMap:
|
||||
weaponName = raw_data['weaponInfo']['weaponName']
|
||||
|
||||
equipMain = ''
|
||||
@ -178,8 +174,6 @@ async def get_char_percent(
|
||||
equipMain,
|
||||
)
|
||||
|
||||
if char_name not in dmgMap:
|
||||
return percent, ''
|
||||
std_prop = dmgMap[char_name]
|
||||
seq_temp_a = ''
|
||||
seq_temp_w = ''
|
||||
@ -204,52 +198,4 @@ async def get_char_percent(
|
||||
else:
|
||||
std = dmgMap[char_name][0]
|
||||
|
||||
f = []
|
||||
c = 0.83
|
||||
if std['critRate'] != 'any':
|
||||
crate = (prop['critRate'] - std['critRate']) / 2
|
||||
c = c * (crate + 1)
|
||||
if char_name == '珊瑚宫心海':
|
||||
c = 0.83
|
||||
else:
|
||||
if std['critDmg'] != 'any':
|
||||
if char_name == '香菱':
|
||||
prop['atk'] += 0.25 * prop['baseAtk']
|
||||
f.append(float(prop['critDmg'] / std['critDmg']))
|
||||
|
||||
atk_val = 1
|
||||
if std['atk'] != 'any':
|
||||
atk_val = float(prop['atk'] / std['atk'])
|
||||
if '防御力' in std['other']:
|
||||
atk_val = (atk_val - 0.9) * 0.2 + 0.9
|
||||
f.append(atk_val)
|
||||
|
||||
for i in std['other']:
|
||||
if '生命' in i:
|
||||
f.append(float(prop['hp'] / std['other'][i]))
|
||||
elif '充能' in i:
|
||||
f.append(float(prop['energyRecharge'] / std['other'][i]))
|
||||
elif '精通' in i:
|
||||
em_val = float(prop['elementalMastery'] / std['other'][i])
|
||||
if atk_val <= 0.7:
|
||||
em_val = 0.35 * em_val
|
||||
elif atk_val <= 0.8:
|
||||
em_val = 0.52 * em_val
|
||||
elif atk_val <= 0.95:
|
||||
em_val = 0.68 * em_val
|
||||
elif atk_val <= 1.05:
|
||||
em_val = 0.95 * em_val
|
||||
elif atk_val <= 1.15:
|
||||
em_val = 1.0 * em_val
|
||||
elif atk_val <= 1.3:
|
||||
em_val = 1.08 * em_val
|
||||
else:
|
||||
em_val = 1.15 * em_val
|
||||
f.append(em_val)
|
||||
elif '防御' in i:
|
||||
f.append(float(prop['def'] / std['other'][i]))
|
||||
else:
|
||||
f.append(1)
|
||||
|
||||
percent = '{:.2f}'.format(c * (float(sum(f) / len(f)) * 100))
|
||||
return percent, std['seq']
|
||||
return std
|
||||
|
@ -1,16 +1,25 @@
|
||||
from typing import List, Literal
|
||||
|
||||
from ..etc.base_info import ELEMENT_MAP
|
||||
from .MAP_PATH import char_effect_map, weapon_effect_map, artifact_effect_map
|
||||
|
||||
|
||||
async def get_buff_list(
|
||||
raw_data: dict, type: Literal['group', 'normal', 'fight']
|
||||
raw_data: dict,
|
||||
type: Literal['group', 'normal', 'fight'],
|
||||
with_talent: bool = True,
|
||||
) -> List[str]:
|
||||
|
||||
all_effect: List[str] = []
|
||||
|
||||
# 获取初始数据
|
||||
char_name = raw_data['avatarName']
|
||||
# 处理旅行者
|
||||
if char_name == '旅行者':
|
||||
for element in ELEMENT_MAP:
|
||||
if raw_data['avatarElement'] == ELEMENT_MAP[element]:
|
||||
char_name += f'({element})'
|
||||
break
|
||||
char_level = int(raw_data['avatarLevel'])
|
||||
weaponName = raw_data['weaponInfo']['weaponName']
|
||||
weaponAffix = raw_data['weaponInfo']['weaponAffix']
|
||||
@ -82,11 +91,14 @@ async def get_buff_list(
|
||||
|
||||
# 计算技能buff
|
||||
if char_name in char_effect_map:
|
||||
for talent in char_effect_map[char_name][main][f'{type}_talent']:
|
||||
if len(raw_data['talentList']) >= int(talent):
|
||||
all_effect.append(
|
||||
char_effect_map[char_name][main][f'{type}_talent'][talent]
|
||||
)
|
||||
if with_talent:
|
||||
for talent in char_effect_map[char_name][main][f'{type}_talent']:
|
||||
if len(raw_data['talentList']) >= int(talent):
|
||||
all_effect.append(
|
||||
char_effect_map[char_name][main][f'{type}_talent'][
|
||||
talent
|
||||
]
|
||||
)
|
||||
# 计算角色buff
|
||||
for skill in char_effect_map[char_name][main][f'{type}_skill']:
|
||||
if char_level >= int(skill):
|
||||
|
@ -1,275 +0,0 @@
|
||||
from copy import deepcopy
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from .buff_calc import get_effect_prop
|
||||
from ..etc.get_buff_list import get_buff_list
|
||||
from ...utils.alias.avatarId_and_name_covert import name_to_avatar_id
|
||||
from ...utils.ambr_api.convert_ambr_data import convert_ambr_to_minigg
|
||||
from .MAP_PATH import char_effect_map, weapon_effect_map, artifact_effect_map
|
||||
from ...utils.minigg_api.get_minigg_data import get_char_info, get_weapon_info
|
||||
|
||||
baseWeaponInfo = {
|
||||
'itemId': 0,
|
||||
'nameTextMapHash': '0',
|
||||
'weaponIcon': 'UI_EquipIcon_Bow_Changed',
|
||||
'weaponType': '',
|
||||
'weaponName': '',
|
||||
'weaponStar': 0,
|
||||
'promoteLevel': 0,
|
||||
'weaponLevel': 0,
|
||||
'weaponAffix': 1,
|
||||
'weaponStats': [
|
||||
{
|
||||
'appendPropId': '',
|
||||
'statName': '基础攻击力',
|
||||
'statValue': 0,
|
||||
},
|
||||
{
|
||||
'appendPropId': '',
|
||||
'statName': '',
|
||||
'statValue': 0,
|
||||
},
|
||||
],
|
||||
'weaponEffect': '',
|
||||
}
|
||||
|
||||
baseFightProp = {
|
||||
'hp': 0.0,
|
||||
'baseHp': 0.0,
|
||||
'addHp': 0.0,
|
||||
'exHp': 0.0,
|
||||
'atk': 0.0,
|
||||
'baseAtk': 0.0,
|
||||
'addAtk': 0.0,
|
||||
'exAtk': 0.0,
|
||||
'def': 0.0,
|
||||
'baseDef': 0.0,
|
||||
'addDef': 0.0,
|
||||
'exDef': 0.0,
|
||||
'elementalMastery': 0.0,
|
||||
'critRate': 0.05,
|
||||
'critDmg': 0.5,
|
||||
'energyRecharge': 1.0,
|
||||
'healBonus': 0.0,
|
||||
'healedBonus': 0.0,
|
||||
'physicalDmgSub': 0.0,
|
||||
'physicalDmgBonus': 0.0,
|
||||
'dmgBonus': 0.0,
|
||||
}
|
||||
|
||||
ATTR_MAP = {
|
||||
'元素精通': 'elementalMastery',
|
||||
'物理伤害加成': 'physicalDmgBonus',
|
||||
'元素伤害加成': 'dmgBonus',
|
||||
'充能效率': 'energyRecharge',
|
||||
'暴击伤害': 'critDmg',
|
||||
'暴击率': 'critRate',
|
||||
'攻击力': 'addAtk',
|
||||
'防御力': 'addDef',
|
||||
'生命值': 'addHp',
|
||||
'百分比血量': 'addHp',
|
||||
}
|
||||
|
||||
|
||||
async def get_card_prop(
|
||||
raw_data: dict,
|
||||
weapon: Optional[str] = None,
|
||||
weapon_affix: Optional[int] = None,
|
||||
talent_num: Optional[int] = None,
|
||||
) -> dict:
|
||||
char_name = raw_data['avatarName']
|
||||
char_level = int(raw_data['avatarLevel'])
|
||||
|
||||
# 创造一个假武器
|
||||
if weapon:
|
||||
weapon_info = deepcopy(baseWeaponInfo)
|
||||
weapon_raw_data = await get_weapon_info(weapon)
|
||||
if 'errcode' in weapon_raw_data:
|
||||
return {}
|
||||
weapon_info['weaponStar'] = int(weapon_raw_data['rarity'])
|
||||
if weapon_info['weaponStar'] >= 3:
|
||||
weapon_level_data = await get_weapon_info(weapon, '90')
|
||||
weapon_info['weaponLevel'] = 90
|
||||
weapon_info['promoteLevel'] = 6
|
||||
else:
|
||||
weapon_level_data = await get_weapon_info(weapon, '70')
|
||||
weapon_info['weaponLevel'] = 70
|
||||
weapon_info['promoteLevel'] = 4
|
||||
weapon_info['weaponName'] = weapon_raw_data['name']
|
||||
if weapon_affix is None:
|
||||
if weapon_info['weaponStar'] >= 5:
|
||||
weapon_info['weaponAffix'] = 1
|
||||
else:
|
||||
weapon_info['weaponAffix'] = 5
|
||||
else:
|
||||
weapon_info['weaponAffix'] = weapon_affix
|
||||
weapon_info['weaponStats'][0]['statValue'] = round(
|
||||
weapon_level_data['attack']
|
||||
)
|
||||
if weapon_raw_data['substat'] != '':
|
||||
weapon_info['weaponStats'][1]['statName'] = weapon_raw_data[
|
||||
'substat'
|
||||
]
|
||||
if weapon_raw_data['substat'] == '元素精通':
|
||||
fake_value = round(weapon_level_data['specialized'])
|
||||
else:
|
||||
fake_value = float(
|
||||
'{:.2f}'.format(weapon_level_data['specialized'] * 100)
|
||||
)
|
||||
weapon_info['weaponStats'][1]['statValue'] = fake_value
|
||||
if 'effect' in weapon_raw_data:
|
||||
weapon_info['weaponEffect'] = weapon_raw_data['effect'].format(
|
||||
*weapon_raw_data['r{}'.format(str(weapon_info['weaponAffix']))]
|
||||
)
|
||||
else:
|
||||
weapon_info['weaponEffect'] = '无特效。'
|
||||
weapon_info['weaponType'] = weapon_raw_data['weapontype']
|
||||
raw_data['weaponInfo'] = weapon_info
|
||||
|
||||
# 修改假命座:
|
||||
if talent_num or talent_num == 0:
|
||||
talent_list = []
|
||||
for i in range(1, talent_num + 1):
|
||||
talent_list.append(
|
||||
{
|
||||
'talentId': 300 + i,
|
||||
'talentName': f'FakeTalent{i}',
|
||||
'talentIcon': f'UI_Talent_S_{raw_data["avatarEnName"]}_0{i}',
|
||||
}
|
||||
)
|
||||
raw_data['talentList'] = talent_list
|
||||
|
||||
fight_prop = await get_base_prop(raw_data, char_name, char_level)
|
||||
raw_data['avatarFightProp'] = fight_prop
|
||||
all_effects = await get_buff_list(raw_data, 'normal')
|
||||
|
||||
# 计算圣遗物效果
|
||||
all_effects.extend(await get_artifacts_value(raw_data))
|
||||
|
||||
fight_prop = await get_effect_prop(fight_prop, all_effects, char_name)
|
||||
raw_data['avatarFightProp'] = fight_prop
|
||||
return raw_data
|
||||
|
||||
|
||||
ELEMENT_MAP = {
|
||||
'风': 'Anemo',
|
||||
'冰': 'Cryo',
|
||||
'草': 'Dendro',
|
||||
'雷': 'Electro',
|
||||
'岩': 'Geo',
|
||||
'水': 'Hydro',
|
||||
'火': 'Pyro',
|
||||
}
|
||||
|
||||
|
||||
async def get_simple_card_prop(raw_data: Dict, base_prop: Dict):
|
||||
char_name = raw_data['avatarName']
|
||||
raw_data['avatarFightProp'] = base_prop
|
||||
all_effects = await get_buff_list(raw_data, 'normal')
|
||||
all_effects.extend(await get_artifacts_value(raw_data))
|
||||
fight_prop = await get_effect_prop(base_prop, all_effects, char_name)
|
||||
raw_data['avatarFightProp'] = fight_prop
|
||||
return raw_data
|
||||
|
||||
|
||||
async def get_base_prop(
|
||||
raw_data: Dict, char_name: str, char_level: int
|
||||
) -> Dict:
|
||||
# 武器基本属
|
||||
weapon_atk = raw_data['weaponInfo']['weaponStats'][0]['statValue']
|
||||
if len(raw_data['weaponInfo']['weaponStats']) > 1:
|
||||
weapon_sub = raw_data['weaponInfo']['weaponStats'][1]['statName']
|
||||
weapon_sub_val = raw_data['weaponInfo']['weaponStats'][1]['statValue']
|
||||
else:
|
||||
weapon_sub = ''
|
||||
weapon_sub_val = 0
|
||||
|
||||
fight_prop = deepcopy(baseFightProp)
|
||||
if '珊瑚宫心海' == char_name:
|
||||
fight_prop['critRate'] -= 1.0
|
||||
fight_prop['healBonus'] += 0.25
|
||||
|
||||
char_name_covert = char_name
|
||||
if char_name == '旅行者':
|
||||
char_name_covert = '荧'
|
||||
|
||||
char_raw = await get_char_info(name=char_name_covert, mode='char')
|
||||
if char_raw is not None and 'errcode' in char_raw:
|
||||
char_id = await name_to_avatar_id(char_name_covert)
|
||||
char_raw = char_data = await convert_ambr_to_minigg(char_id)
|
||||
else:
|
||||
char_data = await get_char_info(
|
||||
name=char_name_covert, mode='char', level=str(char_level)
|
||||
)
|
||||
|
||||
if char_data is None or isinstance(char_data, List):
|
||||
return {}
|
||||
|
||||
fight_prop['baseHp'] = char_data['hp']
|
||||
fight_prop['baseAtk'] = char_data['attack'] + weapon_atk
|
||||
fight_prop['baseDef'] = char_data['defense']
|
||||
fight_prop['exHp'] = 0
|
||||
fight_prop['exAtk'] = 0
|
||||
fight_prop['exDef'] = 0
|
||||
|
||||
# 计算突破加成
|
||||
if isinstance(char_raw, dict):
|
||||
for attr in ATTR_MAP:
|
||||
if attr in char_raw['substat']:
|
||||
sp = char_data['specialized']
|
||||
if attr == '暴击伤害':
|
||||
sp -= 0.5
|
||||
elif attr == '暴击率':
|
||||
sp -= 0.05
|
||||
fight_prop[ATTR_MAP[attr]] += sp
|
||||
if attr in weapon_sub:
|
||||
if attr == '元素精通':
|
||||
weapon_sub_val *= 100
|
||||
fight_prop[ATTR_MAP[attr]] += weapon_sub_val / 100
|
||||
else:
|
||||
return {}
|
||||
|
||||
return fight_prop
|
||||
|
||||
|
||||
async def get_artifacts_value(raw_data: Dict) -> List[str]:
|
||||
# 计算圣遗物效果
|
||||
all_effects = []
|
||||
for equip in raw_data['equipList']:
|
||||
statNmae = equip['reliquaryMainstat']['statName']
|
||||
statValue = equip['reliquaryMainstat']['statValue']
|
||||
all_effects.append(await text_to_effect(statNmae, statValue))
|
||||
for sub in equip['reliquarySubstats']:
|
||||
sub_name = sub['statName']
|
||||
sub_value = sub['statValue']
|
||||
all_effects.append(await text_to_effect(sub_name, sub_value))
|
||||
return all_effects
|
||||
|
||||
|
||||
async def text_to_effect(name: str, value: float) -> str:
|
||||
str = ''
|
||||
if name == '血量':
|
||||
str = f'exHp+{value}'
|
||||
elif name == '百分比血量':
|
||||
str = f'addHp+{value}'
|
||||
elif name == '攻击力':
|
||||
str = f'exAtk+{value}'
|
||||
elif name == '百分比攻击力':
|
||||
str = f'addAtk+{value}'
|
||||
elif name == '防御力':
|
||||
str = f'exDef+{value}'
|
||||
elif name == '百分比防御力':
|
||||
str = f'addDef+{value}'
|
||||
elif name == '暴击率':
|
||||
str = f'critRate+{value}'
|
||||
elif name == '暴击伤害':
|
||||
str = f'critDmg+{value}'
|
||||
elif name == '元素精通':
|
||||
str = f'elementalMastery+{value}'
|
||||
elif name == '元素充能效率':
|
||||
str = f'energyRecharge+{value}'
|
||||
elif name == '物理伤害加成':
|
||||
str = f'physicalDmgBonus+{value}'
|
||||
elif '元素伤害加成' in name:
|
||||
str = f'{ELEMENT_MAP[name[0]]}DmgBonus+{value}'
|
||||
return str
|
@ -195,6 +195,79 @@ STATUS_CHAR_LIST = {
|
||||
'effect': 'exAtk+{}def',
|
||||
}
|
||||
],
|
||||
'宵宫': [
|
||||
{
|
||||
'name': 'A普通攻击伤害提升',
|
||||
'type': '攻击力',
|
||||
'plus': 1,
|
||||
'value': [
|
||||
1.3790899515151978,
|
||||
1.4017900228500366,
|
||||
1.424489974975586,
|
||||
1.4539999961853027,
|
||||
1.476699948310852,
|
||||
1.499400019645691,
|
||||
1.5289100408554077,
|
||||
1.558419942855835,
|
||||
1.5879299640655518,
|
||||
1.6174399852752686,
|
||||
1.6469500064849854,
|
||||
1.6764600276947021,
|
||||
1.705970048904419,
|
||||
1.7354799509048462,
|
||||
1.764989972114563,
|
||||
],
|
||||
'effect': 'A:baseArea+{}',
|
||||
}
|
||||
],
|
||||
'流浪者': [
|
||||
{
|
||||
'name': 'A普通攻击伤害提升',
|
||||
'type': '攻击力',
|
||||
'plus': 1,
|
||||
'value': [
|
||||
1.3298250436782837,
|
||||
1.3495750427246094,
|
||||
1.369325041770935,
|
||||
1.3949999809265137,
|
||||
1.4147499799728394,
|
||||
1.434499979019165,
|
||||
1.4601750373840332,
|
||||
1.4858499765396118,
|
||||
1.51152503490448,
|
||||
1.5371999740600586,
|
||||
1.5628750324249268,
|
||||
1.5885499715805054,
|
||||
1.6142250299453735,
|
||||
1.6398999691009521,
|
||||
1.6655750274658203,
|
||||
],
|
||||
'effect': 'A:baseArea+{}',
|
||||
},
|
||||
{
|
||||
'name': 'A普通攻击伤害提升',
|
||||
'type': '攻击力',
|
||||
'plus': 1,
|
||||
'value': [
|
||||
1.2638599872589111,
|
||||
1.2796599864959717,
|
||||
1.2954599857330322,
|
||||
1.315999984741211,
|
||||
1.3317999839782715,
|
||||
1.347599983215332,
|
||||
1.3681399822235107,
|
||||
1.3886799812316895,
|
||||
1.4092199802398682,
|
||||
1.4297599792480469,
|
||||
1.4502999782562256,
|
||||
1.4708399772644043,
|
||||
1.491379976272583,
|
||||
1.5119199752807617,
|
||||
1.5324599742889404,
|
||||
],
|
||||
'effect': 'B:baseArea+{}',
|
||||
},
|
||||
],
|
||||
'神里绫人': [
|
||||
{
|
||||
'name': 'Q普通攻击伤害提升',
|
||||
@ -218,31 +291,143 @@ STATUS_CHAR_LIST = {
|
||||
'20.0%',
|
||||
],
|
||||
'effect': 'A:dmgBonus+{}',
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'E浪闪',
|
||||
'type': '生命值',
|
||||
'plus': 4,
|
||||
'value': [
|
||||
0.005611000116914511,
|
||||
0.006066999863833189,
|
||||
0.006523999851197004,
|
||||
0.0071760001592338085,
|
||||
0.007633000146597624,
|
||||
0.008155000396072865,
|
||||
0.00887299980968237,
|
||||
0.009589999914169312,
|
||||
0.01030800025910139,
|
||||
0.011091000400483608,
|
||||
0.011873999610543251,
|
||||
0.012656999751925468,
|
||||
0.013438999652862549,
|
||||
0.014221999794244766,
|
||||
0.015004999935626984,
|
||||
],
|
||||
'effect': '瞬水剑:addDmg+{}hp',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
EXTRA_CHAR_LIST = {
|
||||
'雷电将军': {
|
||||
"name": "Q愿力加成",
|
||||
"type": "攻击",
|
||||
"plus": 1,
|
||||
"value": [
|
||||
"3.89+0.73",
|
||||
"4.18+0.78",
|
||||
"4.47+0.84",
|
||||
"4.86+0.91",
|
||||
"5.15+0.96",
|
||||
"5.44+1.02",
|
||||
"5.83+1.09",
|
||||
"6.22+1.16",
|
||||
"6.61+1.23",
|
||||
"7.00+1.31",
|
||||
"7.39+1.38",
|
||||
"7.78+1.45",
|
||||
"8.26+1.54",
|
||||
"8.75+1.63",
|
||||
"9.23+1.72",
|
||||
],
|
||||
}
|
||||
"Q愿力加成": {
|
||||
"type": "攻击",
|
||||
"plus": 1,
|
||||
"value": [
|
||||
"3.89+0.73",
|
||||
"4.18+0.78",
|
||||
"4.47+0.84",
|
||||
"4.86+0.91",
|
||||
"5.15+0.96",
|
||||
"5.44+1.02",
|
||||
"5.83+1.09",
|
||||
"6.22+1.16",
|
||||
"6.61+1.23",
|
||||
"7.00+1.31",
|
||||
"7.39+1.38",
|
||||
"7.78+1.45",
|
||||
"8.26+1.54",
|
||||
"8.75+1.63",
|
||||
"9.23+1.72",
|
||||
],
|
||||
},
|
||||
'Q伤害提升': {
|
||||
"type": "攻击",
|
||||
"plus": 1,
|
||||
'value': [
|
||||
0.002199999988079071,
|
||||
0.002300000051036477,
|
||||
0.002400000113993883,
|
||||
0.0024999999441206455,
|
||||
0.0026000000070780516,
|
||||
0.0027000000700354576,
|
||||
0.00279999990016222,
|
||||
0.002899999963119626,
|
||||
0.003000000026077032,
|
||||
0.003000000026077032,
|
||||
0.003000000026077032,
|
||||
0.003000000026077032,
|
||||
0.003000000026077032,
|
||||
0.003000000026077032,
|
||||
0.003000000026077032,
|
||||
],
|
||||
},
|
||||
},
|
||||
'优菈': {
|
||||
"Q每层能量伤害": {
|
||||
"type": "攻击",
|
||||
"plus": 1,
|
||||
'value': [
|
||||
0.7499200105667114,
|
||||
0.8109599947929382,
|
||||
0.871999979019165,
|
||||
0.9592000246047974,
|
||||
1.0202399492263794,
|
||||
1.090000033378601,
|
||||
1.185920000076294,
|
||||
1.2818399667739868,
|
||||
1.3777600526809692,
|
||||
1.4823999404907227,
|
||||
1.6023000478744507,
|
||||
1.7433019876480103,
|
||||
1.8843050003051758,
|
||||
2.0253069400787354,
|
||||
2.1791279315948486,
|
||||
],
|
||||
}
|
||||
},
|
||||
'纳西妲': {
|
||||
"E灭净三业伤害提升0": {
|
||||
"type": "攻击",
|
||||
"plus": 1,
|
||||
'value': [
|
||||
0.14880000054836273,
|
||||
0.15996000170707703,
|
||||
0.17112000286579132,
|
||||
0.1860000044107437,
|
||||
0.197160005569458,
|
||||
0.2083200067281723,
|
||||
0.2231999933719635,
|
||||
0.2380799949169159,
|
||||
0.2529599964618683,
|
||||
0.2678399980068207,
|
||||
0.28271999955177307,
|
||||
0.29760000109672546,
|
||||
0.31619998812675476,
|
||||
0.33480000495910645,
|
||||
0.35339999198913574,
|
||||
],
|
||||
},
|
||||
"E灭净三业伤害提升1": {
|
||||
"type": "攻击",
|
||||
"plus": 1,
|
||||
'value': [
|
||||
0.2231999933719635,
|
||||
0.23994000256061554,
|
||||
0.2566800117492676,
|
||||
0.27900001406669617,
|
||||
0.295740008354187,
|
||||
0.31248000264167786,
|
||||
0.33480000495910645,
|
||||
0.35712000727653503,
|
||||
0.3794400095939636,
|
||||
0.4017600119113922,
|
||||
0.4240800142288208,
|
||||
0.446399986743927,
|
||||
0.47429999709129333,
|
||||
0.5022000074386597,
|
||||
0.5300999879837036,
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,16 +1,14 @@
|
||||
import re
|
||||
import json
|
||||
import asyncio
|
||||
from copy import deepcopy
|
||||
from typing import Dict, List, Tuple, Union, Optional
|
||||
|
||||
from nonebot.log import logger
|
||||
|
||||
from .mono.Character import Character
|
||||
from .draw_char_card import draw_char_img
|
||||
from .draw_group_dmg import draw_group_dmg_img
|
||||
from .mono.Character import Character, get_char
|
||||
from ..utils.message.error_reply import CHAR_HINT
|
||||
from ..utils.enka_api.enka_to_card import draw_enka_card
|
||||
from .etc.prop_calc import get_base_prop, get_simple_card_prop
|
||||
from ..utils.alias.alias_to_char_name import alias_to_char_name
|
||||
from ..utils.alias.enName_to_avatarId import avatarId_to_enName
|
||||
from ..utils.download_resource.RESOURCE_PATH import PLAYER_PATH
|
||||
@ -42,118 +40,152 @@ async def draw_enka_img(
|
||||
raw_mes: str, uid: str, url: Optional[str]
|
||||
) -> Union[str, Tuple[Union[bytes, str], Optional[bytes]]]:
|
||||
# 获取角色名
|
||||
msg = ''.join(re.findall('[\u4e00-\u9fa5]', raw_mes))
|
||||
msg = ' '.join(re.findall('[\u4e00-\u9fa5]+', raw_mes))
|
||||
# msg = raw_mes.strip()
|
||||
|
||||
# 判断是否开启成长曲线或最佳, 并且去除
|
||||
is_curve = False
|
||||
is_best = False
|
||||
is_group = False
|
||||
if '成长曲线' in msg or '曲线' in msg:
|
||||
is_curve = True
|
||||
msg = msg.replace('成长曲线', '').replace('曲线', '')
|
||||
elif '最佳' in msg:
|
||||
is_best = True
|
||||
msg = msg.replace('最佳', '')
|
||||
if '队伍' in msg or '队伍伤害' in msg:
|
||||
is_group = True
|
||||
msg = msg.replace('队伍', '').replace('伤害', '').strip()
|
||||
|
||||
# 以 带 作为分割
|
||||
fake_char_name = ''
|
||||
if '带' in msg and '换' in msg:
|
||||
# 公子带天空之卷换可莉圣遗物
|
||||
msg_list = msg.split('带')
|
||||
fake_char_name, talent_num = await get_fake_char_str(msg_list[0])
|
||||
msg_list = msg_list[1].split('换')
|
||||
weapon, weapon_affix = await get_fake_weapon_str(msg_list[0])
|
||||
char_name, _ = await get_fake_char_str(msg_list[1].replace('圣遗物', ''))
|
||||
if '展柜角色' in msg:
|
||||
sc = await get_showcase(uid)
|
||||
if isinstance(sc, str):
|
||||
return sc
|
||||
return sc, None
|
||||
|
||||
msg_list = msg.split(' ')
|
||||
char_list = []
|
||||
for msg in msg_list:
|
||||
_args = await get_char_args(msg, uid)
|
||||
if isinstance(_args, str):
|
||||
return _args
|
||||
else:
|
||||
if isinstance(_args[0], str):
|
||||
return _args[0]
|
||||
if is_group:
|
||||
char = await get_char(*_args)
|
||||
char_list.append(char)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
# 以 换 作为分割
|
||||
msg = msg.replace('带', '换')
|
||||
msg_list = msg.split('换')
|
||||
char_name, talent_num = await get_fake_char_str(msg_list[0])
|
||||
if len(msg_list) > 1:
|
||||
weapon, weapon_affix = await get_fake_weapon_str(msg_list[1])
|
||||
else:
|
||||
weapon, weapon_affix = None, None
|
||||
im = await draw_group_dmg_img(uid, char_list)
|
||||
if isinstance(im, str):
|
||||
return im
|
||||
return im, None
|
||||
|
||||
player_path = PLAYER_PATH / str(uid)
|
||||
if char_name == '展柜角色':
|
||||
char_file_list = player_path.glob('*')
|
||||
char_list = []
|
||||
for i in char_file_list:
|
||||
file_name = i.name
|
||||
if '\u4e00' <= file_name[0] <= '\u9fff':
|
||||
char_list.append(file_name.split('.')[0])
|
||||
img = await draw_enka_card(uid=uid, char_list=char_list)
|
||||
return img, None
|
||||
else:
|
||||
if '旅行者' in char_name:
|
||||
char_name = '旅行者'
|
||||
else:
|
||||
char_name = await alias_to_char_name(char_name)
|
||||
char_path = player_path / f'{char_name}.json'
|
||||
if char_path.exists():
|
||||
with open(char_path, 'r', encoding='utf8') as fp:
|
||||
char_data = json.load(fp)
|
||||
else:
|
||||
return CHAR_HINT.format(char_name)
|
||||
char = await get_char(*_args)
|
||||
|
||||
if fake_char_name:
|
||||
char_data = await get_fake_char_data(char_data, fake_char_name)
|
||||
if isinstance(char, str):
|
||||
logger.info('[查询角色] 绘图失败, 替换的武器不正确!')
|
||||
return char
|
||||
|
||||
'''
|
||||
if is_best:
|
||||
char_data = await get_best_char(char_data, uid)
|
||||
'''
|
||||
if isinstance(char_data, str):
|
||||
logger.info('[查询角色] 绘图失败,发送错误原因...')
|
||||
return char_data
|
||||
|
||||
im = await draw_char_img(
|
||||
char_data, weapon, weapon_affix, talent_num, url, is_curve
|
||||
)
|
||||
im = await draw_char_img(char, url, is_curve)
|
||||
logger.info('[查询角色] 绘图完成,等待发送...')
|
||||
return im
|
||||
|
||||
|
||||
async def get_best_char(char_data: Dict, uid: str) -> Dict:
|
||||
# 设定初始值
|
||||
char_level = int(char_data['avatarLevel'])
|
||||
char_name = char_data['avatarName']
|
||||
fight_prop = await get_base_prop(char_data, char_name, char_level)
|
||||
|
||||
# 开始
|
||||
logger.info(f'[查找最佳圣遗物] UID:{uid}开始进行迭代...')
|
||||
best = []
|
||||
artifacts_repo = await get_artifacts_repo(uid)
|
||||
num = 0
|
||||
TASKS = []
|
||||
for flower in artifacts_repo['flower']:
|
||||
for plume in artifacts_repo['plume']:
|
||||
for sands in artifacts_repo['sands']:
|
||||
for goblet in artifacts_repo['goblet']:
|
||||
for circlet in artifacts_repo['circlet']:
|
||||
char_data['equipList'] = [
|
||||
flower,
|
||||
plume,
|
||||
sands,
|
||||
goblet,
|
||||
circlet,
|
||||
]
|
||||
char_data = await get_simple_card_prop(
|
||||
char_data, fight_prop
|
||||
)
|
||||
num += 1
|
||||
TASKS.append(
|
||||
await get_single_percent(
|
||||
deepcopy(char_data), uid, num, best
|
||||
)
|
||||
)
|
||||
break
|
||||
# await get_single_percent(char, uid, num, best)
|
||||
asyncio.gather(*TASKS)
|
||||
best.sort(key=lambda x: (-x['percent']))
|
||||
logger.info(f'[查找最佳圣遗物] UID:{uid}完成!毕业度为{best[0]["percent"]}')
|
||||
async def get_char_data(uid: str, char_name: str) -> Union[Dict, str]:
|
||||
player_path = PLAYER_PATH / str(uid)
|
||||
if '旅行者' in char_name:
|
||||
char_name = '旅行者'
|
||||
else:
|
||||
char_name = await alias_to_char_name(char_name)
|
||||
char_path = player_path / f'{char_name}.json'
|
||||
if char_path.exists():
|
||||
with open(char_path, 'r', encoding='utf8') as fp:
|
||||
char_data = json.load(fp)
|
||||
else:
|
||||
return CHAR_HINT.format(char_name)
|
||||
return char_data
|
||||
|
||||
|
||||
async def get_showcase(uid: str) -> Union[bytes, str]:
|
||||
player_path = PLAYER_PATH / str(uid)
|
||||
char_file_list = player_path.glob('*')
|
||||
char_list = []
|
||||
for i in char_file_list:
|
||||
file_name = i.name
|
||||
if '\u4e00' <= file_name[0] <= '\u9fff':
|
||||
char_list.append(file_name.split('.')[0])
|
||||
if char_list == []:
|
||||
return '您还没有已缓存的角色噢~\n请先使用[强制刷新]命令缓存~'
|
||||
img = await draw_enka_card(uid=uid, char_list=char_list)
|
||||
return img
|
||||
|
||||
|
||||
async def change_equip(
|
||||
uid: str, char_data: Dict, part: str, s: str, i: int
|
||||
) -> Dict:
|
||||
char_name = part.replace(part[-1], '')
|
||||
fake_data = await get_char_data(uid, char_name)
|
||||
if isinstance(fake_data, str):
|
||||
return {}
|
||||
for equip in fake_data['equipList']:
|
||||
if equip['aritifactPieceName'] == s:
|
||||
char_data['equipList'][i] = equip
|
||||
break
|
||||
return char_data
|
||||
|
||||
|
||||
async def get_char_args(
|
||||
msg: str, uid: str
|
||||
) -> Union[Tuple[Dict, Optional[str], Optional[int], Optional[int]], str]:
|
||||
# 可能进来的值
|
||||
# 六命公子带天空之卷换可莉圣遗物换刻晴羽换可莉花
|
||||
# 六命公子带天空之卷换刻晴羽
|
||||
# 公子换刻晴羽
|
||||
fake_name = ''
|
||||
talent_num = None
|
||||
char_data = {}
|
||||
weapon, weapon_affix = None, None
|
||||
|
||||
msg = msg.replace('带', '换').replace('拿', '换')
|
||||
|
||||
# 公子带天空之卷换可莉圣遗物
|
||||
msg_list = msg.split('换')
|
||||
for index, part in enumerate(msg_list):
|
||||
# 判断主体
|
||||
if index == 0:
|
||||
fake_name, talent_num = await get_fake_char_str(part)
|
||||
# 判断是否开启fake_char
|
||||
if '圣遗物' in msg:
|
||||
char_data = await get_fake_char_data(char_data, fake_name, uid)
|
||||
else:
|
||||
char_data = await get_char_data(uid, fake_name)
|
||||
if isinstance(char_data, str):
|
||||
return char_data
|
||||
continue
|
||||
|
||||
if '圣遗物' in part:
|
||||
fake_data = await get_char_data(uid, part.replace('圣遗物', ''))
|
||||
if isinstance(fake_data, str):
|
||||
return fake_data
|
||||
char_data = await get_fake_char_data(fake_data, fake_name, uid)
|
||||
if isinstance(char_data, str):
|
||||
return char_data
|
||||
else:
|
||||
for i, s in enumerate(['生之花', '死之羽', '时之沙', '空之杯', '理之冠']):
|
||||
if '赤沙' in part:
|
||||
continue
|
||||
if part[-1] == s[-1]:
|
||||
if isinstance(char_data, str):
|
||||
return char_data
|
||||
char_data = await change_equip(uid, char_data, part, s, i)
|
||||
if not char_data:
|
||||
return '要替换的部件不存在噢~'
|
||||
break
|
||||
else:
|
||||
weapon, weapon_affix = await get_fake_weapon_str(part)
|
||||
|
||||
return char_data, weapon, weapon_affix, talent_num
|
||||
|
||||
|
||||
async def get_single_percent(char_data: Dict, uid: str, num: int, best: List):
|
||||
char = Character(char_data)
|
||||
await char.init_prop()
|
||||
@ -193,12 +225,16 @@ async def get_artifacts_repo(uid: str) -> Dict[str, List[Dict]]:
|
||||
|
||||
|
||||
async def get_fake_char_data(
|
||||
char_data: Dict, fake_name: str
|
||||
char_data: Dict, fake_name: str, uid: str
|
||||
) -> Union[Dict, str]:
|
||||
fake_name = await alias_to_char_name(fake_name)
|
||||
original_data = await get_char_data(uid, fake_name)
|
||||
if isinstance(original_data, Dict):
|
||||
char_data['weaponInfo'] = original_data['weaponInfo']
|
||||
char_data['avatarName'] = fake_name
|
||||
char_data['avatarId'] = await name_to_avatar_id(fake_name)
|
||||
en_name = await avatarId_to_enName(char_data['avatarId'])
|
||||
char_data['avatarEnName'] = en_name
|
||||
if fake_name in avatarName2Element:
|
||||
char_data['avatarElement'] = avatarName2Element[fake_name]
|
||||
else:
|
||||
@ -213,6 +249,10 @@ async def get_fake_char_data(
|
||||
|
||||
|
||||
async def get_fake_char_str(char_name: str) -> Tuple[str, Optional[int]]:
|
||||
'''
|
||||
获取一个角色信息
|
||||
|
||||
'''
|
||||
talent_num = None
|
||||
if '命' in char_name and char_name[0] in CHAR_TO_INT:
|
||||
talent_num = CHAR_TO_INT[char_name[0]]
|
||||
|
@ -1,26 +1,57 @@
|
||||
from typing import Dict, List, Tuple, Union, Optional
|
||||
from copy import deepcopy
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
from ..etc.etc import get_char_percent
|
||||
from ..etc.prop_calc import get_card_prop
|
||||
from ..dmg_calc.dmg_calc import get_fight_prop
|
||||
from nonebot.log import logger
|
||||
|
||||
from .Power import sp_prop
|
||||
from ..etc.get_buff_list import get_buff_list
|
||||
from ...utils.db_operation.db_operation import config_check
|
||||
from ..etc.MAP_PATH import char_action, avatarName2SkillAdd
|
||||
from ..etc.status_change import EXTRA_CHAR_LIST, STATUS_CHAR_LIST
|
||||
from ...utils.alias.avatarId_and_name_covert import name_to_avatar_id
|
||||
from ..etc.MAP_PATH import ActionMAP, char_action, avatarName2SkillAdd
|
||||
from ...utils.alias.avatarId_to_char_star import avatar_id_to_char_star
|
||||
from ...utils.minigg_api.get_minigg_data import get_char_info, get_weapon_info
|
||||
from ...utils.enka_api.map.GS_MAP_PATH import (
|
||||
avatarName2Weapon,
|
||||
avatarName2Element,
|
||||
)
|
||||
from ...utils.ambr_api.convert_ambr_data import (
|
||||
convert_ambr_to_minigg,
|
||||
convert_ambr_to_weapon,
|
||||
)
|
||||
from ..etc.base_info import (
|
||||
ATTR_MAP,
|
||||
ELEMENT_MAP,
|
||||
ICON_ELEMENT,
|
||||
PERCENT_ATTR,
|
||||
baseFightProp,
|
||||
baseWeaponInfo,
|
||||
)
|
||||
|
||||
|
||||
class Character:
|
||||
def __init__(self, card_prop: Dict):
|
||||
# 面板数据
|
||||
self.card_prop: Dict = card_prop
|
||||
# 无命座效果
|
||||
self.without_talent_card = card_prop
|
||||
# 战斗数据
|
||||
self.fight_prop: Dict = {}
|
||||
self.fight_prop: Dict[str, float] = {}
|
||||
# 战斗数据
|
||||
self.without_talent_fight: Dict[str, float] = {}
|
||||
# 实时数据
|
||||
self.real_prop: Dict[str, float] = {}
|
||||
|
||||
# 角色等级,名称,元素,武器类型
|
||||
self.char_level: int = card_prop['avatarLevel']
|
||||
self.char_level: int = int(card_prop['avatarLevel'])
|
||||
self.char_id: str = '10000029'
|
||||
self.char_name: str = card_prop['avatarName']
|
||||
self.char_element = self.card_prop['avatarElement']
|
||||
self.char_fetter = self.card_prop['avatarFetter']
|
||||
self.char_talent: int = len(self.card_prop['talentList'])
|
||||
self.weapon_type = self.card_prop['weaponInfo']['weaponType']
|
||||
self.char_bytes: Optional[bytes] = None
|
||||
self.rarity: str = '4'
|
||||
|
||||
self.power_name: str = ''
|
||||
self.attack_type: str = ''
|
||||
@ -28,11 +59,26 @@ class Character:
|
||||
# 角色的圣遗物总分
|
||||
self.artifacts_all_score: float = 0
|
||||
self.percent: str = '0.0'
|
||||
self.dmg_data: Dict = {}
|
||||
self.seq_str: str = '无匹配'
|
||||
|
||||
# 特殊
|
||||
self.sp_list: List = []
|
||||
self.sp: sp_prop = sp_prop()
|
||||
self.extra_effect: Dict = {}
|
||||
|
||||
self.time: float = 0
|
||||
self.buff: List = []
|
||||
self.power_list: Dict = {}
|
||||
self.enemy_debuff: List = []
|
||||
self.power_list: Dict[str, ActionMAP] = {}
|
||||
|
||||
# 处理旅行者
|
||||
self.s_char_name = self.char_name
|
||||
if self.char_name == '旅行者':
|
||||
for element in ELEMENT_MAP:
|
||||
if self.char_element == ELEMENT_MAP[element]:
|
||||
self.s_char_name += f'({element})'
|
||||
break
|
||||
|
||||
async def new(
|
||||
self,
|
||||
@ -40,40 +86,498 @@ class Character:
|
||||
weapon_affix: Optional[int] = None,
|
||||
talent_num: Optional[int] = None,
|
||||
):
|
||||
'''
|
||||
<初始化角色 - 1>
|
||||
|
||||
<新生成角色的基础属性>
|
||||
如果要替换武器也在这边进行处理
|
||||
|
||||
参数:
|
||||
weapon: `Optional[str]`
|
||||
武器名称(fake)
|
||||
weapon_affix: `Optional[int]`
|
||||
武器精炼次数(fake)
|
||||
talent_num: `Optional[int]`
|
||||
命座数量(fake)
|
||||
'''
|
||||
if not await config_check('OldPanle'):
|
||||
self.card_prop = await get_card_prop(
|
||||
self.card_prop, weapon, weapon_affix, talent_num
|
||||
self.card_prop = await self.get_card_prop(
|
||||
weapon, weapon_affix, talent_num
|
||||
)
|
||||
if self.card_prop == {}:
|
||||
return '要替换的武器不正确或发生了未知错误~'
|
||||
self.baseHp = self.card_prop['avatarFightProp']['baseHp']
|
||||
self.baseAtk = self.card_prop['avatarFightProp']['baseAtk']
|
||||
self.baseDef = self.card_prop['avatarFightProp']['baseDef']
|
||||
self.rarity = await avatar_id_to_char_star(
|
||||
str(self.card_prop['avatarId'])
|
||||
)
|
||||
self.char_id = await name_to_avatar_id(self.char_name)
|
||||
|
||||
async def get_card_prop(
|
||||
self,
|
||||
weapon: Optional[str] = None,
|
||||
weapon_affix: Optional[int] = None,
|
||||
talent_num: Optional[int] = None,
|
||||
) -> dict:
|
||||
# 创造一个假武器
|
||||
if weapon:
|
||||
weapon_info = deepcopy(baseWeaponInfo)
|
||||
weapon_raw_data = await get_weapon_info(weapon)
|
||||
if 'retcode' in weapon_raw_data:
|
||||
weapon_raw_data = await convert_ambr_to_weapon(weapon)
|
||||
if not weapon_raw_data:
|
||||
return {}
|
||||
weapon_info['weaponStar'] = int(weapon_raw_data['rarity'])
|
||||
if 'level' in weapon_raw_data:
|
||||
weapon_level_data = weapon_raw_data
|
||||
weapon_info['weaponLevel'] = 90
|
||||
weapon_info['promoteLevel'] = 6
|
||||
else:
|
||||
if weapon_info['weaponStar'] >= 3:
|
||||
weapon_level_data = await get_weapon_info(weapon, '90')
|
||||
weapon_info['weaponLevel'] = 90
|
||||
weapon_info['promoteLevel'] = 6
|
||||
else:
|
||||
weapon_level_data = await get_weapon_info(weapon, '70')
|
||||
weapon_info['weaponLevel'] = 70
|
||||
weapon_info['promoteLevel'] = 4
|
||||
weapon_info['weaponName'] = weapon_raw_data['name']
|
||||
if weapon_affix is None:
|
||||
if weapon_info['weaponStar'] >= 5:
|
||||
weapon_info['weaponAffix'] = 1
|
||||
else:
|
||||
weapon_info['weaponAffix'] = 5
|
||||
else:
|
||||
weapon_info['weaponAffix'] = weapon_affix
|
||||
weapon_info['weaponStats'][0]['statValue'] = round(
|
||||
weapon_level_data['attack']
|
||||
)
|
||||
if weapon_raw_data['substat'] != '':
|
||||
weapon_info['weaponStats'][1]['statName'] = weapon_raw_data[
|
||||
'substat'
|
||||
]
|
||||
if weapon_raw_data['substat'] == '元素精通':
|
||||
fake_value = round(weapon_level_data['specialized'])
|
||||
else:
|
||||
fake_value = float(
|
||||
'{:.2f}'.format(weapon_level_data['specialized'] * 100)
|
||||
)
|
||||
weapon_info['weaponStats'][1]['statValue'] = fake_value
|
||||
if 'effect' in weapon_raw_data:
|
||||
weapon_info['weaponEffect'] = weapon_raw_data['effect'].format(
|
||||
*weapon_raw_data[
|
||||
'r{}'.format(str(weapon_info['weaponAffix']))
|
||||
]
|
||||
)
|
||||
else:
|
||||
weapon_info['weaponEffect'] = '无特效。'
|
||||
weapon_info['weaponType'] = weapon_raw_data['weapontype']
|
||||
self.card_prop['weaponInfo'] = weapon_info
|
||||
|
||||
# 修改假命座:
|
||||
|
||||
if self.s_char_name.startswith('旅行者'):
|
||||
icon_name = f'Player{ICON_ELEMENT[self.s_char_name[-2]]}'
|
||||
else:
|
||||
icon_name = self.card_prop['avatarEnName']
|
||||
|
||||
if talent_num is None:
|
||||
talent_num = len(self.card_prop['talentList'])
|
||||
|
||||
if talent_num or talent_num == 0:
|
||||
talent_list = []
|
||||
for i in range(1, talent_num + 1):
|
||||
talent_list.append(
|
||||
{
|
||||
'talentId': 300 + i,
|
||||
'talentName': f'FakeTalent{i}',
|
||||
'talentIcon': f'UI_Talent_S_{icon_name}_0{i}',
|
||||
}
|
||||
)
|
||||
self.card_prop['talentList'] = talent_list
|
||||
|
||||
fight_prop = await self.get_base_prop(self.char_name, self.char_level)
|
||||
self.card_prop['avatarFightProp'] = fight_prop
|
||||
|
||||
self.without_talent_card = self.card_prop
|
||||
# 计算圣遗物效果
|
||||
all_effects = await get_artifacts_value(self.card_prop)
|
||||
part_effects = deepcopy(all_effects)
|
||||
|
||||
all_effects.extend(await get_buff_list(self.card_prop, 'normal'))
|
||||
part_effects.extend(
|
||||
await get_buff_list(self.card_prop, 'normal', False)
|
||||
)
|
||||
|
||||
fight_prop_part = await self.get_effect_prop(
|
||||
deepcopy(fight_prop), part_effects, self.char_name
|
||||
)
|
||||
fight_prop_all = await self.get_effect_prop(
|
||||
deepcopy(fight_prop), all_effects, self.char_name
|
||||
)
|
||||
|
||||
self.card_prop['avatarFightProp'] = fight_prop_all
|
||||
self.without_talent_card['avatarFightProp'] = fight_prop_part
|
||||
return self.card_prop
|
||||
|
||||
async def get_base_prop(self, char_name: str, char_level: int) -> Dict:
|
||||
# 武器基本属
|
||||
weapon_atk = self.card_prop['weaponInfo']['weaponStats'][0][
|
||||
'statValue'
|
||||
]
|
||||
if len(self.card_prop['weaponInfo']['weaponStats']) > 1:
|
||||
weapon_sub = self.card_prop['weaponInfo']['weaponStats'][1][
|
||||
'statName'
|
||||
]
|
||||
weapon_sub_val = self.card_prop['weaponInfo']['weaponStats'][1][
|
||||
'statValue'
|
||||
]
|
||||
else:
|
||||
weapon_sub = ''
|
||||
weapon_sub_val = 0
|
||||
|
||||
fight_prop = deepcopy(baseFightProp)
|
||||
if '珊瑚宫心海' == char_name:
|
||||
fight_prop['critRate'] -= 1.0
|
||||
fight_prop['healBonus'] += 0.25
|
||||
|
||||
char_name_covert = char_name
|
||||
if char_name == '旅行者':
|
||||
char_name_covert = '荧'
|
||||
|
||||
char_raw = await get_char_info(name=char_name_covert, mode='char')
|
||||
self.char_id = await name_to_avatar_id(char_name_covert)
|
||||
if not self.char_id and char_name != '旅行者':
|
||||
return {}
|
||||
if char_raw is not None and 'retcode' in char_raw:
|
||||
char_raw = char_data = await convert_ambr_to_minigg(self.char_id)
|
||||
else:
|
||||
char_data = await get_char_info(
|
||||
name=char_name_covert, mode='char', level=str(char_level)
|
||||
)
|
||||
|
||||
if char_data is None or isinstance(char_data, List):
|
||||
return {}
|
||||
|
||||
fight_prop['baseHp'] = char_data['hp']
|
||||
fight_prop['baseAtk'] = char_data['attack'] + weapon_atk
|
||||
fight_prop['baseDef'] = char_data['defense']
|
||||
fight_prop['exHp'] = 0
|
||||
fight_prop['exAtk'] = 0
|
||||
fight_prop['exDef'] = 0
|
||||
|
||||
# 计算突破加成
|
||||
if isinstance(char_raw, dict):
|
||||
for attr in ATTR_MAP:
|
||||
if attr in char_raw['substat']:
|
||||
sp = char_data['specialized']
|
||||
if attr == '暴击伤害':
|
||||
sp -= 0.5
|
||||
elif attr == '暴击率':
|
||||
sp -= 0.05
|
||||
fight_prop[ATTR_MAP[attr]] += sp
|
||||
if attr in weapon_sub:
|
||||
if attr == '元素精通':
|
||||
weapon_sub_val *= 100
|
||||
fight_prop[ATTR_MAP[attr]] += weapon_sub_val / 100
|
||||
else:
|
||||
return {}
|
||||
|
||||
return fight_prop
|
||||
|
||||
async def init_prop(self):
|
||||
'''
|
||||
<初始化角色 - 2>
|
||||
|
||||
生成角色的战斗属性和毕业度
|
||||
'''
|
||||
await self.get_fight_prop()
|
||||
await self.get_percent()
|
||||
|
||||
async def get_percent(self):
|
||||
self.percent, seq = await get_char_percent(
|
||||
self.card_prop, self.fight_prop, self.char_name
|
||||
)
|
||||
seq_str = '·'.join([s[:2] for s in seq.split('|')]) + seq[-1:]
|
||||
if seq_str:
|
||||
self.seq_str = seq_str
|
||||
async def get_effect_prop(
|
||||
self,
|
||||
prop: dict,
|
||||
effect_list: List[str],
|
||||
char_name: str,
|
||||
) -> dict:
|
||||
logger.debug(effect_list)
|
||||
if 'A_d' not in prop:
|
||||
for attr in [
|
||||
'shieldBonus',
|
||||
'addDmg',
|
||||
'addHeal',
|
||||
'ignoreDef',
|
||||
'd',
|
||||
'g',
|
||||
'a',
|
||||
]:
|
||||
prop[attr] = 0
|
||||
prop['k'] = 1
|
||||
prop['sp'] = []
|
||||
prop['baseArea'] = 1
|
||||
if prop['baseHp'] + prop['addHp'] == prop['hp']:
|
||||
prop['exHp'] = prop['addHp']
|
||||
prop['exAtk'] = prop['addAtk']
|
||||
prop['exDef'] = prop['addDef']
|
||||
prop['addHp'] = 0
|
||||
prop['addAtk'] = 0
|
||||
prop['addDef'] = 0
|
||||
|
||||
async def get_fight_prop(self):
|
||||
# 给每个技能 分别添加上属性
|
||||
for prop_attr in deepcopy(prop):
|
||||
for prop_limit in ['A', 'B', 'C', 'E', 'Q']:
|
||||
prop[f'{prop_limit}_{prop_attr}'] = prop[prop_attr]
|
||||
|
||||
weapon_type = avatarName2Weapon[char_name]
|
||||
|
||||
# 计算角色伤害加成应该使用什么
|
||||
for prop_limit in ['A', 'B', 'C', 'E', 'Q']:
|
||||
if weapon_type == '法器' or char_name in [
|
||||
'荒泷一斗',
|
||||
'刻晴',
|
||||
'诺艾尔',
|
||||
'胡桃',
|
||||
'宵宫',
|
||||
'魈',
|
||||
'神里绫华',
|
||||
]:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop['dmgBonus']
|
||||
elif weapon_type == '弓':
|
||||
if prop_limit in ['A', 'C']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop[
|
||||
'physicalDmgBonus'
|
||||
]
|
||||
elif prop_limit in ['B', 'E', 'Q']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop[
|
||||
'dmgBonus'
|
||||
]
|
||||
else:
|
||||
if prop_limit in ['A', 'B', 'C']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop[
|
||||
'physicalDmgBonus'
|
||||
]
|
||||
elif prop_limit in ['E', 'Q']:
|
||||
prop['{}_dmgBonus'.format(prop_limit)] = prop[
|
||||
'dmgBonus'
|
||||
]
|
||||
|
||||
# 防止复数效果
|
||||
with_trans_effect: List[str] = []
|
||||
without_trans_effect: List[str] = []
|
||||
for effect in effect_list:
|
||||
if ';' in effect:
|
||||
effect = effect.split(';')
|
||||
else:
|
||||
effect = [effect]
|
||||
|
||||
for _effect in effect:
|
||||
if _effect == '':
|
||||
continue
|
||||
else:
|
||||
if '%' in _effect:
|
||||
with_trans_effect.append(_effect)
|
||||
else:
|
||||
without_trans_effect.append(_effect)
|
||||
|
||||
new_effect_list: List[str] = without_trans_effect + with_trans_effect
|
||||
|
||||
# 建立一份基于基础属性的effect_list, 确保hp,atk,def有正确的值
|
||||
base_effect_list: List[List] = []
|
||||
# 正式开始计算
|
||||
for effect in new_effect_list:
|
||||
if 'Resist' in effect:
|
||||
self.enemy_debuff.append(effect)
|
||||
continue
|
||||
else:
|
||||
self.buff.append(effect)
|
||||
# 分割效果
|
||||
# 例如:Q:dmgBonus+96%27%em
|
||||
# 分割后:
|
||||
# effect_limit = Q
|
||||
effect_limit = ''
|
||||
if ':' in effect:
|
||||
effect_limit = effect.split(':')[0]
|
||||
effect = effect.split(':')[1]
|
||||
|
||||
effect_attr, effect_value = effect.split('+')
|
||||
effect_max = 9999999
|
||||
effect_base: str = ''
|
||||
|
||||
# 判断effect_value中有几个百分号
|
||||
p_count = effect_value.count('%')
|
||||
# 如果有%,则认为是基于值的提升
|
||||
base_check = True
|
||||
if p_count >= 2:
|
||||
effect_max, effect_value, effect_base = effect_value.split('%')
|
||||
elif p_count == 1:
|
||||
effect_value, effect_base = effect_value.split('%')
|
||||
else:
|
||||
base_check = False
|
||||
|
||||
# effect_attr, effect_value, effect_base, effect_max
|
||||
# dmgBonus, 27, em, 96
|
||||
|
||||
# 暂时不处理extraDmg
|
||||
if effect_attr == 'extraDmg':
|
||||
continue
|
||||
|
||||
effect_max = float(effect_max) / 100
|
||||
# 如果要增加的属性不是em元素精通,那么都要除于100
|
||||
if effect_attr not in [
|
||||
'exHp',
|
||||
'exAtk',
|
||||
'exDef',
|
||||
'elementalMastery',
|
||||
]:
|
||||
# 正常除100
|
||||
effect_value = float(effect_value) / 100
|
||||
# 元素精通则为正常值
|
||||
else:
|
||||
if effect_base in ['hp', 'elementalMastery', 'def']:
|
||||
effect_value = float(effect_value) / 100
|
||||
else:
|
||||
effect_value = float(effect_value)
|
||||
|
||||
# 如果属性是血量,攻击,防御值,并且是按照%增加的,那么增加值应为百分比乘上基础值
|
||||
if base_check:
|
||||
if effect_base in ['hp', 'atk', 'def']:
|
||||
base_effect_list.append(
|
||||
[effect_limit, effect_attr, effect_value, effect_base]
|
||||
)
|
||||
continue
|
||||
|
||||
if effect_base == 'energyRecharge':
|
||||
if effect_attr in PERCENT_ATTR:
|
||||
effect_base_value = prop[effect_base] - 1
|
||||
else:
|
||||
effect_base_value = (prop[effect_base] - 1) / 100
|
||||
|
||||
elif effect_base == 'energyrecharge':
|
||||
effect_base = 'energyRecharge'
|
||||
if effect_attr in PERCENT_ATTR:
|
||||
effect_base_value = prop[effect_base]
|
||||
else:
|
||||
effect_base_value = prop[effect_base] / 100
|
||||
|
||||
elif effect_base == 'elementalMastery':
|
||||
# 针对草神的
|
||||
if char_name == '纳西妲' and effect_attr == 'dmgBonus':
|
||||
effect_base_value = (prop[effect_base] - 200) / 100
|
||||
else:
|
||||
effect_base_value = prop[effect_base]
|
||||
else:
|
||||
effect_base_value = prop[effect_base]
|
||||
effect_value = effect_value * effect_base_value
|
||||
|
||||
# 判断是否超过上限,超过则使用上限值
|
||||
if effect_value >= effect_max:
|
||||
effect_value = effect_max
|
||||
|
||||
if char_name == '旅行者':
|
||||
char_element = 'Hydro'
|
||||
else:
|
||||
char_element = avatarName2Element[char_name]
|
||||
|
||||
# 判断是否是自己属性的叠加
|
||||
if 'DmgBonus' in effect_attr:
|
||||
if effect_attr.replace('DmgBonus', '') == char_element:
|
||||
effect_attr = 'dmgBonus'
|
||||
elif effect_attr == 'physicalDmgBonus':
|
||||
effect_attr = 'physicalDmgBonus'
|
||||
else:
|
||||
continue
|
||||
|
||||
# 如果效果有限制条件
|
||||
prop = await self.get_buff_value(
|
||||
prop,
|
||||
effect_limit,
|
||||
effect_attr,
|
||||
effect_value,
|
||||
effect_base,
|
||||
False,
|
||||
)
|
||||
|
||||
prop = await self.get_base_value(prop)
|
||||
|
||||
# 重新计算加成值
|
||||
# base_effect_list = [
|
||||
# [limit_list, effect_attr, effect_value, effect_base]
|
||||
# ]
|
||||
for effect in base_effect_list:
|
||||
prop = await self.get_buff_value(prop, *effect)
|
||||
|
||||
prop = await self.get_base_value(prop)
|
||||
logger.debug(prop)
|
||||
return prop
|
||||
|
||||
async def get_base_value(self, prop: Dict) -> Dict:
|
||||
prop['hp'] = (prop['addHp'] + 1) * prop['baseHp'] + prop['exHp']
|
||||
prop['atk'] = (prop['addAtk'] + 1) * prop['baseAtk'] + prop['exAtk']
|
||||
prop['def'] = (prop['addDef'] + 1) * prop['baseDef'] + prop['exDef']
|
||||
for prop_limit in ['A', 'B', 'C', 'E', 'Q']:
|
||||
for attr in ['hp', 'atk', 'def']:
|
||||
attr_up = attr[0].upper() + attr[1:]
|
||||
prop[f'{prop_limit}_{attr}'] = (
|
||||
prop[f'{prop_limit}_add{attr_up}'] + 1
|
||||
) * prop[f'base{attr_up}'] + prop[f'ex{attr_up}']
|
||||
return prop
|
||||
|
||||
async def get_buff_value(
|
||||
self,
|
||||
prop: Dict,
|
||||
effect_limit: Optional[str],
|
||||
effect_attr: str,
|
||||
effect_value: float,
|
||||
effect_base: Optional[str] = None,
|
||||
is_calc_base: Optional[bool] = True,
|
||||
) -> Dict:
|
||||
if effect_base and is_calc_base:
|
||||
effect_value = prop[effect_base] * effect_value
|
||||
if effect_limit:
|
||||
# 如果限制条件为中文,则为特殊label才生效
|
||||
if '\u4e00' <= effect_limit[-1] <= '\u9fff':
|
||||
prop['sp'].append(
|
||||
{
|
||||
'effect_name': effect_limit,
|
||||
'effect_attr': effect_attr,
|
||||
'effect_value': effect_value,
|
||||
}
|
||||
)
|
||||
# 如果限制条件为英文,例如Q,则为Q才生效
|
||||
else:
|
||||
# 形如ABC:dmgBonus+75,则遍历ABC,增加值
|
||||
for limit in effect_limit:
|
||||
prop['{}_{}'.format(limit, effect_attr)] += effect_value
|
||||
else:
|
||||
if effect_attr in ['a', 'addDmg']:
|
||||
pass
|
||||
else:
|
||||
for attr in ['A', 'B', 'C', 'E', 'Q']:
|
||||
prop[f'{attr}_{effect_attr}'] += effect_value
|
||||
prop[f'{effect_attr}'] += effect_value
|
||||
|
||||
logger.debug(f'{effect_attr} + {effect_value} 基于[{effect_base}]')
|
||||
|
||||
return prop
|
||||
|
||||
async def get_fight_prop(self) -> Dict:
|
||||
'''
|
||||
生成角色的倍率表
|
||||
|
||||
返回:
|
||||
self.fight_prop
|
||||
'''
|
||||
# 拿到倍率表
|
||||
if self.char_name not in char_action:
|
||||
if self.s_char_name not in char_action:
|
||||
self.power_list = {}
|
||||
else:
|
||||
self.power_list = char_action[self.char_name]
|
||||
self.power_list = char_action[self.s_char_name]
|
||||
# 额外增加钟离倍率
|
||||
if self.char_name == '钟离':
|
||||
self.power_list['E总护盾量'] = {
|
||||
'name': 'E总护盾量',
|
||||
'type': '生命值',
|
||||
'plus': 1,
|
||||
'plus': 1.5,
|
||||
'value': [
|
||||
f'{self.power_list["E护盾附加吸收量"]["value"][index]}+{i}'
|
||||
for index, i in enumerate(
|
||||
@ -100,8 +604,294 @@ class Character:
|
||||
'plus': 1,
|
||||
'value': ['200%+400%'] * 15,
|
||||
}
|
||||
self.fight_prop = await get_fight_prop(self.card_prop)
|
||||
elif self.char_name == '甘雨':
|
||||
for power_name in [
|
||||
'A霜华矢两段伤害',
|
||||
'A霜华矢两段伤害(融化)',
|
||||
]:
|
||||
self.power_list[power_name] = {
|
||||
'name': power_name,
|
||||
'type': '攻击力',
|
||||
'plus': 1,
|
||||
'value': [
|
||||
f'''{
|
||||
int(i[:-1]) + int(
|
||||
self.power_list[
|
||||
"A霜华矢·霜华绽发伤害"
|
||||
]["value"][index][:-1]
|
||||
)
|
||||
}%'''
|
||||
for index, i in enumerate(
|
||||
self.power_list['A霜华矢命中伤害']['value']
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
# 获取值
|
||||
skillList = self.card_prop['avatarSkill']
|
||||
prop = deepcopy(self.card_prop['avatarFightProp'])
|
||||
prop['A_skill_level'] = skillList[0]['skillLevel']
|
||||
prop['E_skill_level'] = skillList[1]['skillLevel']
|
||||
prop['Q_skill_level'] = skillList[-1]['skillLevel']
|
||||
|
||||
if self.char_name in avatarName2SkillAdd:
|
||||
skill_add = avatarName2SkillAdd[self.char_name]
|
||||
else:
|
||||
skill_add = ['E', 'Q']
|
||||
for skillAdd_index in range(0, 2):
|
||||
if len(self.card_prop['talentList']) >= 3 + skillAdd_index * 2:
|
||||
if skill_add[skillAdd_index] == 'E':
|
||||
prop['E_skill_level'] += 3
|
||||
elif skill_add[skillAdd_index] == 'Q':
|
||||
prop['Q_skill_level'] += 3
|
||||
|
||||
prop = await self.get_effect_prop(prop, [], self.char_name)
|
||||
all_effect = await get_buff_list(self.card_prop, 'fight')
|
||||
part_effect = await get_buff_list(self.card_prop, 'fight', False)
|
||||
|
||||
ex_effect = []
|
||||
# 开启效果
|
||||
if self.char_name in STATUS_CHAR_LIST:
|
||||
for skill_effect in STATUS_CHAR_LIST[self.char_name]:
|
||||
skill_level = (
|
||||
prop[f'{skill_effect["name"][0]}_skill_level'] - 1
|
||||
)
|
||||
skill_value = skill_effect['value'][skill_level]
|
||||
plus = skill_effect['plus']
|
||||
if isinstance(skill_value, float):
|
||||
skill_value = '{:.4f}%'.format(skill_value * 100 * plus)
|
||||
skill: str = skill_effect['effect'].format(skill_value)
|
||||
if skill.endswith('%'):
|
||||
skill = skill[:-1]
|
||||
ex_effect.append(skill)
|
||||
|
||||
# 特殊效果,目前有雷神满愿力
|
||||
if self.char_name in EXTRA_CHAR_LIST:
|
||||
if self.char_name == '雷电将军':
|
||||
skill1 = EXTRA_CHAR_LIST[self.char_name]['Q愿力加成']['value']
|
||||
skill2 = EXTRA_CHAR_LIST[self.char_name]['Q伤害提升']['value']
|
||||
attack_type = 'Q'
|
||||
skill_level = prop[f'{attack_type}_skill_level'] - 1
|
||||
|
||||
value_1 = float(skill1[skill_level].split('+')[0])
|
||||
value_1 *= 0.6
|
||||
value_2 = float(skill1[skill_level].split('+')[1])
|
||||
value_2 *= 0.6
|
||||
value_3 = skill2[skill_level] * 90
|
||||
ex_effect.append((f'Q梦想一刀基础伤害:dmgBonus+{value_3}'))
|
||||
self.extra_effect = {
|
||||
'Q梦想一刀基础伤害(满愿力)': value_1,
|
||||
'Q一段伤害(满愿力)': value_2,
|
||||
'Q重击伤害(满愿力)': value_2,
|
||||
'Q高空下落伤害(满愿力)': value_2,
|
||||
}
|
||||
if self.card_prop['weaponInfo']['weaponName'] == '薙草之稻光':
|
||||
weaponAffix = self.card_prop['weaponInfo']['weaponAffix']
|
||||
_ex = 10 + weaponAffix * 2
|
||||
ex_effect.append(f'Q:dmgBonus+{_ex}')
|
||||
elif self.char_name == '优菈':
|
||||
skill_effect = EXTRA_CHAR_LIST[self.char_name]['Q每层能量伤害'][
|
||||
'value'
|
||||
]
|
||||
attack_type = 'Q'
|
||||
skill_level = prop[f'{attack_type}_skill_level'] - 1
|
||||
value = float(skill_effect[skill_level])
|
||||
self.extra_effect = {
|
||||
'Q光降之剑基础伤害(13层)': value * 13,
|
||||
'Q光降之剑基础伤害(24层)': value * 24,
|
||||
}
|
||||
elif self.char_name == '纳西妲':
|
||||
self.char_talent = len(self.card_prop['talentList'])
|
||||
if self.char_talent >= 1:
|
||||
char_talent = 1
|
||||
else:
|
||||
char_talent = 0
|
||||
skill_effect = EXTRA_CHAR_LIST[self.char_name][
|
||||
f'E灭净三业伤害提升{char_talent}'
|
||||
]['value']
|
||||
attack_type = 'E'
|
||||
skill_level = prop[f'{attack_type}_skill_level'] - 1
|
||||
value = float(skill_effect[skill_level])
|
||||
ex_effect.append((f'前台:dmgBonus+{value*100}'))
|
||||
|
||||
# 在计算buff前, 引入特殊效果
|
||||
if self.char_name == '雷电将军':
|
||||
ex_effect.append('Q:dmgBonus+27')
|
||||
elif self.char_name == '钟离':
|
||||
ex_effect.append('AnemoResist+-20;PhysicalResist+-20')
|
||||
ex_effect.append('CryoResist+-20;DendroResist+-20')
|
||||
ex_effect.append('ElectroResist+-20;HydroResist+-20')
|
||||
ex_effect.append('PyroResist+-20;GeoResist+-20')
|
||||
elif self.char_name == '妮露':
|
||||
ex_effect.append('addHp+25')
|
||||
ex_effect.append('elementalMastery+80')
|
||||
|
||||
all_effect.extend(ex_effect)
|
||||
part_effect.extend(ex_effect)
|
||||
|
||||
# 计算全部的buff,添加入属性
|
||||
self.fight_prop = await self.get_effect_prop(
|
||||
deepcopy(prop), all_effect, self.char_name
|
||||
)
|
||||
|
||||
if self.rarity != '5' and self.char_name != '香菱':
|
||||
self.without_talent_fight = self.fight_prop
|
||||
else:
|
||||
if self.char_name == '香菱':
|
||||
part_effect.append('exAtk+1202')
|
||||
self.without_talent_fight = await self.get_effect_prop(
|
||||
deepcopy(prop), part_effect, self.char_name
|
||||
)
|
||||
return self.fight_prop
|
||||
|
||||
async def get_sp_fight_prop(self, power_name: str) -> sp_prop:
|
||||
'''
|
||||
获得角色的特殊状态战斗加成
|
||||
|
||||
返回:
|
||||
self.sp: `sp_prop`
|
||||
'''
|
||||
self.sp = sp_prop()
|
||||
for sp_single in self.fight_prop['sp']: # type:ignore
|
||||
if sp_single['effect_name'] in power_name:
|
||||
if sp_single['effect_attr'] == 'dmgBonus':
|
||||
self.sp.dmgBonus += sp_single['effect_value']
|
||||
elif sp_single['effect_attr'] == 'addDmg':
|
||||
self.sp.addDmg += sp_single['effect_value']
|
||||
elif sp_single['effect_attr'] == 'atk':
|
||||
self.sp.attack += sp_single['effect_value']
|
||||
else:
|
||||
self.sp.attack += sp_single['effect_value']
|
||||
return self.sp
|
||||
|
||||
async def get_attack_type(self, power_name: str) -> str:
|
||||
'''
|
||||
获得角色的当前攻击类型
|
||||
|
||||
参数:
|
||||
power_name: `str`
|
||||
返回:
|
||||
self.attack_type: `Literal['A','B','C','E','Q']`
|
||||
'''
|
||||
# 攻击类型ABCEQ应为label首位
|
||||
self.attack_type = power_name[0]
|
||||
# 如果是雷电将军, 则就按首位,因为Q的几段伤害均视为元素爆发
|
||||
if self.char_name == '雷电将军':
|
||||
pass
|
||||
else:
|
||||
# 重击或瞄准射击在label内,则视为B重击伤害,例如公子E内的重击伤害,不视为E伤害,而是B伤害
|
||||
if '重击' in power_name or '瞄准射击' in power_name:
|
||||
self.attack_type = 'B'
|
||||
# 特殊重击类型,例如甘雨和夜兰
|
||||
elif (
|
||||
'破局矢' in power_name
|
||||
or '霜华矢' in power_name
|
||||
or '藏蕴花矢' in power_name
|
||||
or '花筥箭' in power_name
|
||||
or '刀风界' in power_name
|
||||
):
|
||||
self.attack_type = 'B'
|
||||
# 下落伤害类型,例如魈
|
||||
elif '高空下落' in power_name:
|
||||
self.attack_type = 'C'
|
||||
# 一段伤害, 二段伤害等等 应视为A伤害
|
||||
elif '段' in power_name and '伤害' in power_name:
|
||||
self.attack_type = 'A'
|
||||
elif '不生断' in power_name:
|
||||
self.attack_type = 'A'
|
||||
return self.attack_type
|
||||
|
||||
async def update(self, time):
|
||||
self.time += time
|
||||
# TODO 遍历buff列表, 超过时间的移除
|
||||
|
||||
|
||||
async def p2v(power: str, power_plus: int) -> Tuple[float, float]:
|
||||
"""
|
||||
将power转换为value
|
||||
"""
|
||||
# 如果存在123%+123%形式的
|
||||
if '+' in power:
|
||||
power_percent = (
|
||||
float(power.split('+')[0].replace('%', '')) / 100
|
||||
) * power_plus
|
||||
power_value = power.split('+')[1]
|
||||
if '%' in power_value:
|
||||
power_percent += (
|
||||
float(power_value.replace('%', '')) / 100 * power_plus
|
||||
)
|
||||
power_value = 0
|
||||
else:
|
||||
power_value = float(power_value)
|
||||
elif '%' in power:
|
||||
power_percent = float(power.replace('%', '')) / 100 * power_plus
|
||||
power_value = 0
|
||||
else:
|
||||
power_percent = 0
|
||||
power_value = float(power)
|
||||
|
||||
return power_percent, power_value
|
||||
|
||||
|
||||
async def get_artifacts_value(raw_data: Dict) -> List[str]:
|
||||
# 计算圣遗物效果
|
||||
all_effects = []
|
||||
for equip in raw_data['equipList']:
|
||||
statNmae = equip['reliquaryMainstat']['statName']
|
||||
statValue = equip['reliquaryMainstat']['statValue']
|
||||
all_effects.append(await text_to_effect(statNmae, statValue))
|
||||
for sub in equip['reliquarySubstats']:
|
||||
sub_name = sub['statName']
|
||||
sub_value = sub['statValue']
|
||||
all_effects.append(await text_to_effect(sub_name, sub_value))
|
||||
return all_effects
|
||||
|
||||
|
||||
async def text_to_effect(name: str, value: float) -> str:
|
||||
str = ''
|
||||
if name == '血量':
|
||||
str = f'exHp+{value}'
|
||||
elif name == '百分比血量':
|
||||
str = f'addHp+{value}'
|
||||
elif name == '攻击力':
|
||||
str = f'exAtk+{value}'
|
||||
elif name == '百分比攻击力':
|
||||
str = f'addAtk+{value}'
|
||||
elif name == '防御力':
|
||||
str = f'exDef+{value}'
|
||||
elif name == '百分比防御力':
|
||||
str = f'addDef+{value}'
|
||||
elif name == '暴击率':
|
||||
str = f'critRate+{value}'
|
||||
elif name == '暴击伤害':
|
||||
str = f'critDmg+{value}'
|
||||
elif name == '元素精通':
|
||||
str = f'elementalMastery+{value}'
|
||||
elif name == '元素充能效率':
|
||||
str = f'energyRecharge+{value}'
|
||||
elif name == '物理伤害加成':
|
||||
str = f'physicalDmgBonus+{value}'
|
||||
elif '元素伤害加成' in name:
|
||||
str = f'{ELEMENT_MAP[name[0]]}DmgBonus+{value}'
|
||||
elif '治疗加成' in name:
|
||||
str = f'healBonus+{value}'
|
||||
return str
|
||||
|
||||
|
||||
async def get_char(
|
||||
raw_data: dict,
|
||||
weapon: Optional[str] = None,
|
||||
weapon_affix: Optional[int] = None,
|
||||
talent_num: Optional[int] = None,
|
||||
):
|
||||
char = Character(card_prop=raw_data)
|
||||
err = await char.new(
|
||||
weapon=weapon,
|
||||
weapon_affix=weapon_affix,
|
||||
talent_num=talent_num,
|
||||
)
|
||||
if isinstance(err, str):
|
||||
return err
|
||||
|
||||
await char.init_prop()
|
||||
return char
|
||||
|
@ -1,5 +1,6 @@
|
||||
from typing import List, Dict
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from .Character import Character
|
||||
from .Element import Element, reactable_elements_dict
|
||||
|
||||
|
||||
@ -15,14 +16,14 @@ class Enemy:
|
||||
self.ignore_defense: float = 0
|
||||
self.element: Dict[Element, float] = {}
|
||||
|
||||
self.physical_resist: float = 0.1
|
||||
self.anemo_resist: float = 0.1
|
||||
self.cryo_resist: float = 0.1
|
||||
self.dendro_resist: float = 0.1
|
||||
self.electro_resist: float = 0.1
|
||||
self.geo_resist: float = 0.1
|
||||
self.hydro_resist: float = 0.1
|
||||
self.pyro_resist: float = 0.1
|
||||
self.PhysicalResist: float = 0.1
|
||||
self.AnemoResist: float = 0.1
|
||||
self.CryoResist: float = 0.1
|
||||
self.DendroResist: float = 0.1
|
||||
self.ElectroResist: float = 0.1
|
||||
self.GeoResist: float = 0.1
|
||||
self.HydroResist: float = 0.1
|
||||
self.PyroResist: float = 0.1
|
||||
|
||||
self.total_dmg: float = 0
|
||||
self.debuff: List = []
|
||||
@ -31,56 +32,98 @@ class Enemy:
|
||||
self.time += time
|
||||
# TODO 遍历debuff列表, 超过时间的移除
|
||||
|
||||
async def get_dmg_reaction(self, dmg_type: Element) -> float:
|
||||
reaction: float = 1
|
||||
# 如果是物理伤害,则不反应
|
||||
if dmg_type == Element.Physical:
|
||||
return 1
|
||||
|
||||
# 如果怪物头上没元素,给定此次伤害类型元素量1
|
||||
if self.element == {}:
|
||||
self.element[dmg_type] = 1
|
||||
# 如果怪物头上元素相同,则刷新元素量
|
||||
elif dmg_type in self.element:
|
||||
self.element[dmg_type] = 1
|
||||
async def update_resist(self, effect: str):
|
||||
name, val = effect.split('+')
|
||||
val = float(val) / 100
|
||||
if name != 'Resist':
|
||||
r = getattr(self, name)
|
||||
setattr(self, name, r + val)
|
||||
else:
|
||||
# 遍历怪物头上的元素
|
||||
new_element_list = self.element
|
||||
for element in self.element:
|
||||
# 如果本次伤害类型,在这个元素的可反应列表里
|
||||
if dmg_type in reactable_elements_dict[element]:
|
||||
# 元素列表里的这个元素 就要减去反应量
|
||||
new_element_list[element] -= float(
|
||||
reactable_elements_dict[element][dmg_type]['value']
|
||||
r = getattr(self, f'{element.name}Resist')
|
||||
setattr(self, name, r + val)
|
||||
|
||||
async def get_dmg_reaction(
|
||||
self,
|
||||
dmg_type: Optional[Element] = None,
|
||||
char: Optional[Character] = None,
|
||||
) -> float:
|
||||
if char:
|
||||
for react in ['蒸发', '融化']:
|
||||
if react in char.power_name:
|
||||
em = char.real_prop[f'{char.attack_type}_elementalMastery']
|
||||
k = 0
|
||||
if react == '蒸发':
|
||||
if char.char_element == 'Pyro':
|
||||
k = 1.5
|
||||
else:
|
||||
k = 2
|
||||
elif react == '融化':
|
||||
if char.char_element == 'Pyro':
|
||||
k = 2
|
||||
else:
|
||||
k = 1.5
|
||||
reaction_add_dmg = k * (
|
||||
1 + (2.78 * em) / (em + 1400) + char.real_prop['a']
|
||||
)
|
||||
# 如果是增幅反应,给出相对应的倍率
|
||||
reaction_name = reactable_elements_dict[element][dmg_type][
|
||||
'reaction'
|
||||
]
|
||||
if reaction_name in [
|
||||
'蒸发',
|
||||
'融化',
|
||||
]:
|
||||
reaction *= float(
|
||||
reactable_elements_dict[element][dmg_type]['dmg']
|
||||
)
|
||||
else:
|
||||
self.debuff.append(reaction_name)
|
||||
break
|
||||
else:
|
||||
reaction_add_dmg = 1
|
||||
return reaction_add_dmg
|
||||
else:
|
||||
if dmg_type:
|
||||
reaction: float = 1
|
||||
# 如果是物理伤害,则不反应
|
||||
if dmg_type == Element.Physical:
|
||||
return 1
|
||||
|
||||
# 结算怪物的元素
|
||||
result_element: Dict[Element, float] = {}
|
||||
for element in new_element_list:
|
||||
if new_element_list[element] > 0:
|
||||
result_element[element] = new_element_list[element]
|
||||
self.element = result_element
|
||||
# 如果怪物头上没元素,给定此次伤害类型元素量1
|
||||
if self.element == {}:
|
||||
self.element[dmg_type] = 1
|
||||
# 如果怪物头上元素相同,则刷新元素量
|
||||
elif dmg_type in self.element:
|
||||
self.element[dmg_type] = 1
|
||||
else:
|
||||
# 遍历怪物头上的元素
|
||||
new_element_list = self.element
|
||||
for element in self.element:
|
||||
# 如果本次伤害类型,在这个元素的可反应列表里
|
||||
if dmg_type in reactable_elements_dict[element]:
|
||||
# 元素列表里的这个元素 就要减去反应量
|
||||
new_element_list[element] -= float(
|
||||
reactable_elements_dict[element][dmg_type][
|
||||
'value'
|
||||
]
|
||||
)
|
||||
# 如果是增幅反应,给出相对应的倍率
|
||||
reaction_name = reactable_elements_dict[element][
|
||||
dmg_type
|
||||
]['reaction']
|
||||
if reaction_name in [
|
||||
'蒸发',
|
||||
'融化',
|
||||
]:
|
||||
reaction *= float(
|
||||
reactable_elements_dict[element][dmg_type][
|
||||
'dmg'
|
||||
]
|
||||
)
|
||||
else:
|
||||
self.debuff.append(reaction_name)
|
||||
|
||||
return reaction
|
||||
# 结算怪物的元素
|
||||
result_element: Dict[Element, float] = {}
|
||||
for element in new_element_list:
|
||||
if new_element_list[element] > 0:
|
||||
result_element[element] = new_element_list[element]
|
||||
self.element = result_element
|
||||
|
||||
async def get_dmg_proof(self, dmg_type: Element) -> float:
|
||||
proof: float = 0
|
||||
return reaction
|
||||
return 1
|
||||
|
||||
async def get_resist(self, dmg_type: Element):
|
||||
# 计算抗性
|
||||
r = getattr(self, f'{dmg_type}_resist')
|
||||
r = getattr(self, f'{dmg_type.name}Resist')
|
||||
if r > 0.75:
|
||||
r = 1 / (1 + 4 * r)
|
||||
elif r > 0:
|
||||
@ -88,13 +131,29 @@ class Enemy:
|
||||
else:
|
||||
r = 1 - r / 2
|
||||
|
||||
return r
|
||||
|
||||
async def get_dmg_proof(
|
||||
self,
|
||||
dmg_type: Element,
|
||||
extra_d: float = 0,
|
||||
extra_ignoreD: float = 0,
|
||||
) -> float:
|
||||
proof: float = 0
|
||||
|
||||
# 计算抗性
|
||||
r = await self.get_resist(dmg_type)
|
||||
|
||||
# 计算防御
|
||||
d = (self.char_level + 100) / (
|
||||
(self.char_level + 100)
|
||||
+ (1 - self.defense_resist)
|
||||
* (1 - self.ignore_defense)
|
||||
d_up = self.char_level + 100
|
||||
d_down = (
|
||||
self.char_level
|
||||
+ 100
|
||||
+ (1 - self.defense_resist - extra_d)
|
||||
* (1 - self.ignore_defense - extra_ignoreD)
|
||||
* (self.level + 100)
|
||||
)
|
||||
d = d_up / d_down
|
||||
proof = r * d
|
||||
|
||||
# 返回减伤百分比
|
||||
|
@ -1,13 +1,21 @@
|
||||
from typing import List, Tuple, Dict
|
||||
from copy import deepcopy
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
from nonebot.log import logger
|
||||
|
||||
from .Enemy import Enemy
|
||||
from .Power import Power
|
||||
from .Element import Element
|
||||
from .Character import Character
|
||||
from ..dmg_calc.base_value import base_value_list
|
||||
|
||||
|
||||
class Fight:
|
||||
def __init__(
|
||||
self, Character_list: Dict[str, Character], Enemy: Enemy, SEQ: List
|
||||
self,
|
||||
Character_list: Dict[str, Character],
|
||||
Enemy: Enemy,
|
||||
SEQ: List = [],
|
||||
):
|
||||
self.time = 0
|
||||
self.total_crit_dmg: float = 0
|
||||
@ -19,46 +27,118 @@ class Fight:
|
||||
self.char_list: Dict[str, Character] = Character_list
|
||||
self.enemy = Enemy
|
||||
|
||||
async def update_dmg(self):
|
||||
self.dmg_data: Dict[str, Dict[str, float]] = {}
|
||||
|
||||
# 进行队伍伤害计算
|
||||
async def update_dmg(self) -> Dict:
|
||||
result = {}
|
||||
for seq in self.SEQ:
|
||||
# 获取本次攻击的信息
|
||||
char_name = seq['char']
|
||||
self.char_list[char_name].power_name = seq['action']
|
||||
char = self.char_list[char_name]
|
||||
char.power_name = seq['action']
|
||||
self.time += 0.4
|
||||
|
||||
# 更新角色和怪物
|
||||
for char in self.char_list:
|
||||
await self.char_list[char].update(self.time)
|
||||
for _char in self.char_list:
|
||||
await self.char_list[_char].update(self.time)
|
||||
await self.enemy.update(self.time)
|
||||
|
||||
# 获取本次攻击的类型
|
||||
attack_type = await self.get_attack_type(char_name)
|
||||
await char.get_sp_fight_prop(char.power_name)
|
||||
await char.get_attack_type(char.power_name)
|
||||
# 获取本次攻击的元素
|
||||
dmg_type = await self.get_dmg_type(char_name, attack_type, seq)
|
||||
dmg_type = await self.get_dmg_type(char, seq)
|
||||
# 更新角色的属性
|
||||
await self.get_new_fight_prop(char)
|
||||
|
||||
# 更新self.seq_history
|
||||
self.seq_history = seq
|
||||
|
||||
# 进行攻击
|
||||
normal_dmg, avg_dmg, crit_dmg = await self.get_dmg(
|
||||
char_name, dmg_type, attack_type
|
||||
)
|
||||
# 聚变反应
|
||||
for i in ['扩散', '绽放)', '感电', '超载']:
|
||||
if i in char.power_name:
|
||||
dmg = await self.get_transform_dmg(char)
|
||||
break
|
||||
else:
|
||||
# 进行攻击
|
||||
dmg = await self.get_dmg(char, dmg_type)
|
||||
normal_dmg, avg_dmg, crit_dmg = dmg[0], dmg[1], dmg[2]
|
||||
|
||||
result[self.time] = {
|
||||
'char': char_name,
|
||||
'action': seq['action'],
|
||||
'normal_dmg': normal_dmg,
|
||||
'avg_dmg': avg_dmg,
|
||||
'crit_dmg': crit_dmg,
|
||||
'enemy_element': self.enemy.element,
|
||||
}
|
||||
logger.debug(result)
|
||||
return result
|
||||
|
||||
# 进行单人伤害计算
|
||||
async def get_dmg_dict(
|
||||
self, char_name: str, without_talent: bool = False
|
||||
) -> Dict:
|
||||
result = {}
|
||||
char = self.char_list[char_name]
|
||||
# 获取本次攻击的类型
|
||||
if without_talent:
|
||||
if char.rarity == '4' and char_name != '香菱':
|
||||
return self.dmg_data
|
||||
char.fight_prop = char.without_talent_fight
|
||||
|
||||
for power_name in char.power_list:
|
||||
# 更新powername
|
||||
char.power_name = power_name
|
||||
await char.get_sp_fight_prop(char.power_name)
|
||||
await char.get_attack_type(char.power_name)
|
||||
# 更新角色的属性
|
||||
await self.get_new_fight_prop(char)
|
||||
# 聚变反应
|
||||
for i in ['扩散', '绽放)', '感电', '超载']:
|
||||
if i in power_name:
|
||||
dmg = await self.get_transform_dmg(char)
|
||||
break
|
||||
else:
|
||||
dmg = []
|
||||
|
||||
# 正常伤害
|
||||
if not dmg:
|
||||
if '治疗' in power_name or '回复' in power_name:
|
||||
dmg = await self.get_heal(char)
|
||||
elif '护盾' in power_name:
|
||||
dmg = await self.get_shield(char)
|
||||
else:
|
||||
# 获取本次攻击的元素
|
||||
dmg_type = await self.get_dmg_type(char)
|
||||
dmg = await self.get_dmg(char, dmg_type, True)
|
||||
|
||||
# 得到结果
|
||||
result[power_name] = {
|
||||
'normal': dmg[0],
|
||||
'avg': dmg[1],
|
||||
'crit': dmg[2],
|
||||
}
|
||||
self.dmg_data = result
|
||||
logger.debug(result)
|
||||
return result
|
||||
|
||||
# 伤害类型
|
||||
async def get_dmg_type(
|
||||
self, char_name: str, attack_type: str, seq: Dict
|
||||
self, char: Character, seq: Optional[Dict] = None
|
||||
) -> Element:
|
||||
# TODO 获取本次攻击的元素
|
||||
dmg_type: Element = Element.Physical
|
||||
char_element_dmg_type = getattr(
|
||||
Element, self.char_list[char_name].char_element
|
||||
)
|
||||
char_element_dmg_type = getattr(Element, char.char_element)
|
||||
|
||||
# 对重复的计数
|
||||
if seq['action'] == self.seq_history:
|
||||
return dmg_type
|
||||
if seq:
|
||||
if seq['action'] == self.seq_history:
|
||||
return dmg_type
|
||||
|
||||
# 计算角色伤害加成应该使用什么
|
||||
if self.char_list[char_name].weapon_type == '法器' or char_name in [
|
||||
if char.weapon_type == '法器' or char.char_name in [
|
||||
'荒泷一斗',
|
||||
'刻晴',
|
||||
'诺艾尔',
|
||||
@ -68,138 +148,338 @@ class Fight:
|
||||
'神里绫华',
|
||||
]:
|
||||
dmg_type = char_element_dmg_type
|
||||
elif self.char_list[char_name].weapon_type == '弓':
|
||||
if attack_type in ['B', 'E', 'Q']:
|
||||
elif char.weapon_type == '弓':
|
||||
if char.attack_type in ['B', 'E', 'Q']:
|
||||
dmg_type = char_element_dmg_type
|
||||
else:
|
||||
if attack_type in ['E', 'Q']:
|
||||
if char.attack_type in ['E', 'Q']:
|
||||
dmg_type = char_element_dmg_type
|
||||
|
||||
if char.power_name in [
|
||||
'Q光降之剑基础伤害',
|
||||
'Q光降之剑基础伤害(13层)',
|
||||
'Q每层能量伤害',
|
||||
'Q光降之剑基础伤害(24层)',
|
||||
]:
|
||||
dmg_type = Element.Physical
|
||||
|
||||
if '段' in char.power_name and 'A' not in char.power_name:
|
||||
dmg_type = char_element_dmg_type
|
||||
|
||||
if char.char_name == '辛焱' and char.power_name == 'Q伤害':
|
||||
dmg_type = Element.Physical
|
||||
|
||||
return dmg_type
|
||||
|
||||
async def get_attack_type(self, char_name: str) -> str:
|
||||
# 攻击类型ABCEQ应为label首位
|
||||
attack_type = self.char_list[char_name].power_name[0]
|
||||
# 如果是雷电将军, 则就按首位,因为Q的几段伤害均视为元素爆发
|
||||
if char_name == '雷电将军':
|
||||
pass
|
||||
else:
|
||||
# 重击或瞄准射击在label内,则视为B重击伤害,例如公子E内的重击伤害,不视为E伤害,而是B伤害
|
||||
if (
|
||||
'重击' in self.char_list[char_name].power_name
|
||||
or '瞄准射击' in self.char_list[char_name].power_name
|
||||
):
|
||||
attack_type = 'B'
|
||||
# 特殊重击类型,例如甘雨和夜兰
|
||||
elif (
|
||||
'破局矢' in self.char_list[char_name].power_name
|
||||
or '霜华矢' in self.char_list[char_name].power_name
|
||||
):
|
||||
attack_type = 'B'
|
||||
# 下落伤害类型,例如魈
|
||||
elif '高空下落' in self.char_list[char_name].power_name:
|
||||
attack_type = 'C'
|
||||
# 一段伤害, 二段伤害等等 应视为A伤害
|
||||
elif (
|
||||
'段' in self.char_list[char_name].power_name
|
||||
and '伤害' in self.char_list[char_name].power_name
|
||||
):
|
||||
attack_type = 'A'
|
||||
|
||||
self.char_list[char_name].attack_type = attack_type
|
||||
return attack_type
|
||||
|
||||
async def get_power(
|
||||
self, char_name: str, attack_type: str
|
||||
) -> Tuple[float, float]:
|
||||
power: str = ''
|
||||
power_plus: int = 0
|
||||
|
||||
# 计算倍率
|
||||
async def get_power(self, char: Character) -> Power:
|
||||
# 按照ABCEQ等级查找倍率
|
||||
power = self.char_list[char_name].power_list[
|
||||
self.char_list[char_name].power_name
|
||||
]['value'][
|
||||
self.char_list[char_name].fight_prop[
|
||||
'{}_skill_level'.format(attack_type)
|
||||
]
|
||||
- 1
|
||||
]
|
||||
power_name = char.power_name
|
||||
real_prop = char.real_prop
|
||||
power_list = char.power_list
|
||||
power_level = int(real_prop[f'{power_name[0]}_skill_level'])
|
||||
|
||||
# 拿到倍率
|
||||
power = power_list[power_name]['value'][power_level - 1]
|
||||
# 计算是否多次伤害
|
||||
power_plus = self.char_list[char_name].power_list[
|
||||
self.char_list[char_name].power_name
|
||||
]['plus']
|
||||
power_plus = power_list[power_name]['plus']
|
||||
|
||||
if char.char_name == '宵宫' and power_name == 'A一段伤害':
|
||||
power_plus = 1
|
||||
|
||||
# 拿到百分比和固定值,百分比为float,形如2.2 也就是202%
|
||||
power_percent, power_value = await power_to_value(power, power_plus)
|
||||
power_percent, power_value = await p2v(power, power_plus)
|
||||
|
||||
return power_percent, power_value
|
||||
# 额外加成,目前有雷神和优菈
|
||||
if char.extra_effect and power_name in char.extra_effect:
|
||||
power_percent += char.extra_effect[power_name]
|
||||
|
||||
async def get_effect_prop(self, char_name: str, attack_type: str):
|
||||
# 根据type计算有效属性
|
||||
if (
|
||||
'攻击'
|
||||
in self.char_list[char_name].power_list[
|
||||
self.char_list[char_name].power_name
|
||||
]['type']
|
||||
):
|
||||
effect_prop = self.char_list[char_name].fight_prop[
|
||||
f'{attack_type}_attack'
|
||||
]
|
||||
elif (
|
||||
'生命值'
|
||||
in self.char_list[char_name].power_list[
|
||||
self.char_list[char_name].power_name
|
||||
]['type']
|
||||
):
|
||||
effect_prop = self.char_list[char_name].fight_prop[
|
||||
f'{attack_type}_hp'
|
||||
]
|
||||
elif (
|
||||
'防御'
|
||||
in self.char_list[char_name].power_list[
|
||||
self.char_list[char_name].power_name
|
||||
]['type']
|
||||
):
|
||||
effect_prop = self.char_list[char_name].fight_prop[
|
||||
f'{attack_type}_defense'
|
||||
]
|
||||
return Power(
|
||||
name=power_name,
|
||||
level=power_level,
|
||||
percent=power_percent,
|
||||
value=power_value,
|
||||
plus=power_plus,
|
||||
raw=power,
|
||||
)
|
||||
|
||||
# 额外加成和抗性计算
|
||||
async def get_new_fight_prop(self, char: Character) -> Dict:
|
||||
# 抗性传达
|
||||
if char.enemy_debuff:
|
||||
for effect in char.enemy_debuff:
|
||||
await self.enemy.update_resist(effect)
|
||||
char.enemy_debuff = []
|
||||
|
||||
# 特殊buff计算
|
||||
effect_list = []
|
||||
if '前台' in char.power_list[char.power_name]['name']:
|
||||
if char.char_name == '纳西妲':
|
||||
em = char.fight_prop[f'{char.attack_type}_elementalMastery']
|
||||
effect = f'''
|
||||
elementalMastery+{0.25 * em if 0.25 * em <= 250 else 250}
|
||||
'''.strip()
|
||||
effect_list.append(effect)
|
||||
|
||||
if '丰穰之核' in char.power_name and char.fight_prop['hp'] >= 30000:
|
||||
ex_add = ((char.fight_prop['hp'] - 30000) / 1000) * 9
|
||||
if ex_add >= 400:
|
||||
ex_add = 400
|
||||
effect = f'a+{ex_add}'
|
||||
effect_list.append(effect)
|
||||
|
||||
if effect_list:
|
||||
char.real_prop = await char.get_effect_prop(
|
||||
deepcopy(char.fight_prop), effect_list, char.char_name
|
||||
)
|
||||
return char.real_prop
|
||||
else:
|
||||
effect_prop = self.char_list[char_name].fight_prop[
|
||||
f'{attack_type}_attack'
|
||||
]
|
||||
char.real_prop = char.fight_prop
|
||||
|
||||
return char.real_prop
|
||||
|
||||
# 治疗值加成
|
||||
async def get_add_heal(self, char: Character) -> float:
|
||||
add_heal: float = char.real_prop[f'{char.attack_type}_addHeal']
|
||||
return add_heal
|
||||
|
||||
# 增幅反应
|
||||
async def get_amplify_dmg(self, char: Character) -> float:
|
||||
# 计算元素反应 增幅
|
||||
em_cal = char.real_prop[f'{char.attack_type}_elementalMastery']
|
||||
for reaction in ['蒸发', '融化']:
|
||||
if reaction in char.power_list[char.power_name]['name']:
|
||||
if reaction == '蒸发':
|
||||
if char.char_element == 'Pyro':
|
||||
k = 1.5
|
||||
else:
|
||||
k = 2
|
||||
else:
|
||||
if char.char_element == 'Pyro':
|
||||
k = 2
|
||||
else:
|
||||
k = 1.5
|
||||
reaction_add_dmg = k * (
|
||||
1 + (2.78 * em_cal) / (em_cal + 1400) + char.real_prop['a']
|
||||
)
|
||||
break
|
||||
else:
|
||||
reaction_add_dmg = 1
|
||||
return reaction_add_dmg
|
||||
|
||||
# 激化反应
|
||||
async def get_quicken_dmg(self, char: Character) -> float:
|
||||
quicken_dmg = 0
|
||||
char_level = char.char_level
|
||||
power_name = char.power_list[char.power_name]['name']
|
||||
em_cal = char.real_prop[f'{char.attack_type}_elementalMastery']
|
||||
for reaction in ['超激化', '蔓激化']:
|
||||
if reaction in power_name:
|
||||
if reaction == '超激化':
|
||||
k = 2.3
|
||||
else:
|
||||
k = 2.5
|
||||
power_times = 1
|
||||
if '*' in power_name:
|
||||
power_times = float(
|
||||
(power_name.split('*')[-1].replace(')', ''))
|
||||
)
|
||||
quicken_dmg = (
|
||||
k
|
||||
* base_value_list[char_level - 1]
|
||||
* (1 + (5 * em_cal) / (em_cal + 1200))
|
||||
) * power_times
|
||||
break
|
||||
return quicken_dmg
|
||||
|
||||
# 有效数值
|
||||
async def get_effect_prop(self, char: Character):
|
||||
# 根据type计算有效属性
|
||||
_type = char.power_list[char.power_name]['type']
|
||||
if '攻击' in _type:
|
||||
effect_prop = char.real_prop[f'{char.attack_type}_atk']
|
||||
elif '生命值' in _type:
|
||||
effect_prop = char.real_prop[f'{char.attack_type}_hp']
|
||||
elif '防御' in _type:
|
||||
effect_prop = char.real_prop[f'{char.attack_type}_def']
|
||||
else:
|
||||
effect_prop = char.real_prop[f'{char.attack_type}_atk']
|
||||
|
||||
return effect_prop
|
||||
|
||||
async def get_dmg(
|
||||
self, char_name: str, dmg_type: Element, attack_type: str
|
||||
# 伤害值加成
|
||||
async def get_add_dmg(self, char: Character) -> float:
|
||||
# 计算直接增加的伤害
|
||||
add_dmg: float = char.real_prop[f'{char.attack_type}_addDmg']
|
||||
return add_dmg
|
||||
|
||||
# 防御值加成
|
||||
async def get_extra_d(self, char: Character) -> float:
|
||||
# 计算直接增加的伤害
|
||||
extra_d: float = char.real_prop[f'{char.attack_type}_d']
|
||||
return extra_d
|
||||
|
||||
# 防御值加成
|
||||
async def get_base_area_plus(self, char: Character) -> float:
|
||||
# 计算直接增加的伤害
|
||||
base_area_plus: float = char.real_prop[f'{char.attack_type}_baseArea']
|
||||
return base_area_plus
|
||||
|
||||
# 防御值加成
|
||||
async def get_extra_ignoreD(self, char: Character) -> float:
|
||||
# 计算直接增加的伤害
|
||||
extra_ignoreD: float = char.real_prop[f'{char.attack_type}_ignoreDef']
|
||||
return extra_ignoreD
|
||||
|
||||
async def get_sp_base(self, power: Power, char: Character) -> float:
|
||||
power_sp = power.raw.replace('%', '').split('+')
|
||||
power_sp = [float(x) / 100 for x in power_sp]
|
||||
real_prop = char.real_prop
|
||||
atk = real_prop['E_atk'] + char.sp.attack
|
||||
em = real_prop[f'{char.attack_type}_elementalMastery']
|
||||
base = (power_sp[0] * atk + power_sp[1] * em) * power.plus
|
||||
return base
|
||||
|
||||
# 基础乘区
|
||||
async def get_base_area(self, char: Character) -> float:
|
||||
# 获得该次伤害的倍率信息
|
||||
power = await self.get_power(char)
|
||||
# 获得激化乘区的信息
|
||||
reaction_power = await self.get_quicken_dmg(char)
|
||||
# 获得该次伤害的有效属性
|
||||
effect_prop = await self.get_effect_prop(char)
|
||||
# 获得伤害提高值的信息
|
||||
add_dmg = await self.get_add_dmg(char)
|
||||
|
||||
base_area_plus = await self.get_base_area_plus(char)
|
||||
|
||||
# 对草神进行特殊计算
|
||||
if '灭净三业' in power.name or '业障除' in power.name:
|
||||
base = await self.get_sp_base(power, char)
|
||||
elif char.char_name == '艾尔海森' and power.name.startswith('E'):
|
||||
base = await self.get_sp_base(power, char)
|
||||
else:
|
||||
base = effect_prop * power.percent + power.value
|
||||
|
||||
if char.char_name == '珊瑚宫心海':
|
||||
hp = char.real_prop['hp']
|
||||
hb = char.real_prop['healBonus']
|
||||
add_dmg += 0.15 * hp * hb
|
||||
|
||||
# 基本乘区 = 有效数值(例如攻击力) * 倍率 + 固定值 + 激化区 + 额外加成值 + 特殊加成值
|
||||
base_area = base + reaction_power + add_dmg + char.sp.addDmg
|
||||
if base_area_plus != 1:
|
||||
base_area_plus -= 1
|
||||
base_area = base_area_plus * base_area
|
||||
return base_area
|
||||
|
||||
# 聚变反应
|
||||
async def get_transform_dmg(
|
||||
self, char: Character
|
||||
) -> Tuple[float, float, float]:
|
||||
effect_prop = await self.get_effect_prop(char_name, attack_type)
|
||||
power_percent, power_value = await self.get_power(
|
||||
char_name, attack_type
|
||||
)
|
||||
proof = await self.enemy.get_dmg_proof(dmg_type)
|
||||
reactio = await self.enemy.get_dmg_reaction(dmg_type)
|
||||
em = char.real_prop[f'{char.attack_type}_elementalMastery']
|
||||
is_crit = False
|
||||
if '绽放)' in char.power_name:
|
||||
# 获取激变反应基数
|
||||
if '烈绽放' in char.power_name:
|
||||
dmg_type = Element.Pyro
|
||||
base_time = 6
|
||||
elif '超绽放' in char.power_name:
|
||||
dmg_type = Element.Dendro
|
||||
base_time = 6
|
||||
else:
|
||||
dmg_type = Element.Dendro
|
||||
base_time = 4
|
||||
base_area = (
|
||||
base_value_list[char.char_level - 1]
|
||||
* base_time
|
||||
* (1 + (16.0 * em) / (em + 2000) + char.real_prop['a'])
|
||||
)
|
||||
is_crit = True
|
||||
elif '扩散伤害' in char.power_name:
|
||||
dmg_type = Element.Anemo
|
||||
base_area = (
|
||||
base_value_list[char.char_level - 1]
|
||||
* 1.2
|
||||
* (1 + (16.0 * em) / (em + 2000) + char.real_prop['a'])
|
||||
* (1 + char.real_prop['g'] / 100)
|
||||
)
|
||||
else:
|
||||
dmg_type = Element.Physical
|
||||
base_area = 0
|
||||
|
||||
critrate_cal = self.char_list[char_name].fight_prop[
|
||||
f'{attack_type}_critrate'
|
||||
]
|
||||
critdmg_cal = self.char_list[char_name].fight_prop[
|
||||
f'{attack_type}_critdmg'
|
||||
]
|
||||
dmgBonus_cal = self.char_list[char_name].fight_prop[
|
||||
f'{attack_type}_dmgBonus'
|
||||
]
|
||||
# 获得这次攻击的减伤乘区(抗性区+防御区)
|
||||
logger.debug(self.enemy.__dict__)
|
||||
proof = await self.enemy.get_resist(dmg_type)
|
||||
|
||||
normal_dmg = (
|
||||
(effect_prop * power_percent + power_value)
|
||||
* (1 + dmgBonus_cal)
|
||||
* proof
|
||||
* reactio
|
||||
)
|
||||
# 暴击伤害
|
||||
crit_dmg = normal_dmg * (1 + critdmg_cal)
|
||||
normal_dmg = base_area * proof
|
||||
if is_crit:
|
||||
crit_dmg = normal_dmg * 2
|
||||
avg_dmg = normal_dmg * 1.2
|
||||
else:
|
||||
crit_dmg = avg_dmg = 0
|
||||
return normal_dmg, avg_dmg, crit_dmg
|
||||
|
||||
async def get_heal(self, char: Character) -> Tuple[float, float, float]:
|
||||
# 获得治疗增加值
|
||||
add_heal = await self.get_add_heal(char)
|
||||
# 获得治疗倍率
|
||||
power = await self.get_power(char)
|
||||
# 获得该次治疗的有效属性
|
||||
effect_prop = await self.get_effect_prop(char)
|
||||
heal_bonus = 1 + char.real_prop['healBonus']
|
||||
base_area = effect_prop * power.percent + power.value + add_heal
|
||||
normal_value = base_area * heal_bonus
|
||||
return normal_value, normal_value, 0
|
||||
|
||||
async def get_shield(self, char: Character) -> Tuple[float, float, float]:
|
||||
# 获得护盾倍率
|
||||
power = await self.get_power(char)
|
||||
# 获得该次护盾的有效属性
|
||||
effect_prop = await self.get_effect_prop(char)
|
||||
shield_bonus = 1 + char.real_prop['shieldBonus']
|
||||
base_area = effect_prop * power.percent + power.value
|
||||
normal_value = base_area * shield_bonus
|
||||
return normal_value, 0, 0
|
||||
|
||||
async def get_dmg(
|
||||
self,
|
||||
char: Character,
|
||||
dmg_type: Element,
|
||||
is_single: bool = False,
|
||||
) -> Tuple[float, float, float]:
|
||||
# 获得基础乘区(攻击区+倍率区+激化区)
|
||||
base_area = await self.get_base_area(char)
|
||||
# 获得这次攻击的减伤乘区(抗性区+防御区)
|
||||
d = await self.get_extra_d(char)
|
||||
i_d = await self.get_extra_ignoreD(char)
|
||||
# logger.debug(self.enemy.__dict__)
|
||||
proof = await self.enemy.get_dmg_proof(dmg_type, d, i_d)
|
||||
# 获得这次攻击的增幅乘区
|
||||
_char = char if is_single else None
|
||||
reactio = await self.enemy.get_dmg_reaction(dmg_type, _char)
|
||||
|
||||
if dmg_type == Element.Physical:
|
||||
_dmgBonus = char.real_prop[f'{char.attack_type}_physicalDmgBonus']
|
||||
else:
|
||||
_dmgBonus = char.real_prop[f'{char.attack_type}_dmgBonus']
|
||||
critrate = char.real_prop[f'{char.attack_type}_critRate']
|
||||
critdmg = char.real_prop[f'{char.attack_type}_critDmg']
|
||||
dmgBonus = _dmgBonus + char.sp.dmgBonus
|
||||
|
||||
# 基础乘区 = 攻击*倍率+激化
|
||||
# 普通伤害 = 基础 * 增伤区 * 增幅区 * 抗性区
|
||||
normal_dmg = base_area * (1 + dmgBonus) * reactio * proof
|
||||
# 暴击伤害 = 普通伤害 * 暴击区
|
||||
crit_dmg = normal_dmg * (1 + critdmg)
|
||||
# 平均伤害
|
||||
avg_dmg = crit_dmg * critrate_cal + (1 - critrate_cal) * normal_dmg
|
||||
avg_dmg = (
|
||||
normal_dmg
|
||||
if critrate < 0
|
||||
else crit_dmg
|
||||
if critrate > 1
|
||||
else crit_dmg * critrate + (1 - critrate) * normal_dmg
|
||||
)
|
||||
|
||||
self.total_normal_dmg += normal_dmg
|
||||
self.total_avg_dmg += avg_dmg
|
||||
@ -208,7 +488,7 @@ class Fight:
|
||||
return normal_dmg, avg_dmg, crit_dmg
|
||||
|
||||
|
||||
async def power_to_value(power: str, power_plus: int) -> Tuple[float, float]:
|
||||
async def p2v(power: str, power_plus: float) -> Tuple[float, float]:
|
||||
"""
|
||||
将power转换为value
|
||||
"""
|
||||
|
16
GenshinUID/genshinuid_enka/mono/Power.py
Normal file
@ -0,0 +1,16 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Power(BaseModel):
|
||||
name: str
|
||||
level: int
|
||||
raw: str
|
||||
percent: float
|
||||
value: float
|
||||
plus: float
|
||||
|
||||
|
||||
class sp_prop(BaseModel):
|
||||
dmgBonus: float = 0
|
||||
addDmg: float = 0
|
||||
attack: float = 0
|
@ -1,18 +1,28 @@
|
||||
TEST_SEQ = [
|
||||
{'char': '达达利亚', 'action': 'A满蓄力瞄准射击', 'type': '伤害'},
|
||||
{'char': '枫原万叶', 'action': 'E长按伤害', 'type': '伤害'},
|
||||
{'char': '枫原万叶', 'action': 'Q斩击伤害', 'type': '伤害'},
|
||||
{'char': '枫原万叶', 'action': 'Q持续伤害', 'type': '持续'},
|
||||
{'char': '班尼特', 'action': 'E点按伤害', 'type': '伤害'},
|
||||
{'char': '班尼特', 'action': 'Q伤害', 'type': '伤害'},
|
||||
{'char': '香菱', 'action': 'E喷火伤害', 'type': '持续'},
|
||||
{'char': '香菱', 'action': 'Q旋火轮伤害', 'type': '持续'},
|
||||
WAN_DA = [
|
||||
{'char': '达达利亚', 'action': 'E状态激发伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E一段伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E重击伤害', 'type': '伤害'},
|
||||
{'char': '班尼特', 'action': 'Q伤害', 'type': '伤害'},
|
||||
{'char': '枫原万叶', 'action': 'Q斩击伤害', 'type': '伤害'},
|
||||
{'char': '枫原万叶', 'action': 'A扩散伤害', 'type': '持续'},
|
||||
{'char': '枫原万叶', 'action': 'E长按伤害', 'type': '伤害'},
|
||||
{'char': '枫原万叶', 'action': 'Q持续伤害', 'type': '持续'},
|
||||
{'char': '枫原万叶', 'action': 'A高空下落伤害', 'type': '伤害'},
|
||||
{'char': '香菱', 'action': 'Q旋火轮伤害', 'type': '持续'},
|
||||
{'char': '香菱', 'action': 'E喷火伤害', 'type': '持续'},
|
||||
{'char': '达达利亚', 'action': 'E状态激发伤害', 'type': '伤害'},
|
||||
{'char': '枫原万叶', 'action': 'Q持续伤害', 'type': '持续'},
|
||||
{'char': '达达利亚', 'action': 'E一段伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E重击伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E一段伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E重击伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'Q伤害·近战', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E一段伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E重击伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E一段伤害', 'type': '伤害'},
|
||||
{'char': '枫原万叶', 'action': 'Q持续伤害', 'type': '持续'},
|
||||
{'char': '达达利亚', 'action': 'E重击伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E一段伤害', 'type': '伤害'},
|
||||
{'char': '达达利亚', 'action': 'E重击伤害', 'type': '伤害'},
|
||||
]
|
||||
|
||||
ALL_SEQ = {'万达国际': WAN_DA}
|
||||
|
||||
SEQ_ARG = {'万达国际': ['香菱', '达达利亚', '班尼特', '枫原万叶']}
|
||||
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 899 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 890 B After Width: | Height: | Size: 1.1 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/equip_mask.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 296 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/team_dmg/action_title.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/team_dmg/char_bg.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/team_dmg/dmg_bar.png
Normal file
After Width: | Height: | Size: 775 B |
BIN
GenshinUID/genshinuid_enka/texture2D/team_dmg/team_title.png
Normal file
After Width: | Height: | Size: 22 KiB |
@ -1,39 +1,119 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Tuple
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot import on_regex, on_command
|
||||
from nonebot.params import CommandArg, RegexGroup
|
||||
from nonebot.adapters.ntchat import MessageSegment
|
||||
from nonebot.adapters.ntchat.message import Message
|
||||
|
||||
from ..all_import import *
|
||||
from ..version import Genshin_version
|
||||
from ..genshinuid_meta import register_menu
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
|
||||
get_primogems_data = on_command('版本规划', aliases={'原石预估'})
|
||||
get_img_data = on_regex(r'^(查询)?(伤害乘区|血量表|抗性表|血量排行)$')
|
||||
|
||||
PRIMOGEMS_DATA_PATH = Path(__file__).parent / 'primogems_data'
|
||||
IMG_PATH = Path(__file__).parent / 'img_data'
|
||||
version = ['3.0', '3.1']
|
||||
|
||||
|
||||
@sv.on_rex(r'^(版本规划|原石预估)(\S+)?$')
|
||||
async def send_primogems_data(bot: HoshinoBot, ev: CQEvent):
|
||||
args = ev['match'].groups()
|
||||
@get_primogems_data.handle()
|
||||
@handle_exception('版本规划')
|
||||
@register_menu(
|
||||
'版本原石规划',
|
||||
'版本规划(版本号)',
|
||||
'发送一张指定版本的原石规划图',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'发送一张指定版本的原石规划图\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>版本规划</ft><ft color=(125,125,125)>(版本号)</ft>\n'
|
||||
'- <ft color=(238,120,0)>原石预估</ft><ft color=(125,125,125)>(版本号)</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>版本规划</ft>\n'
|
||||
'- <ft color=(238,120,0)>版本规划3.0</ft>'
|
||||
),
|
||||
)
|
||||
async def send_primogems_data(matcher: Matcher, args: Message = CommandArg()):
|
||||
logger.info('开始执行[图片][版本规划]')
|
||||
logger.info('[图片][版本规划]参数: {}'.format(args))
|
||||
if args[1]:
|
||||
if str(args[1]) in version:
|
||||
img = f'{args[1]}.png'
|
||||
if args:
|
||||
path = PRIMOGEMS_DATA_PATH / f'{str(args)}.png'
|
||||
if path.exists():
|
||||
img = f'{str(args)}.png'
|
||||
else:
|
||||
return
|
||||
await matcher.finish()
|
||||
else:
|
||||
img = f'{Genshin_version[:3]}.png'
|
||||
primogems_img = PRIMOGEMS_DATA_PATH / img
|
||||
logger.info('[图片][版本规划]访问图片: {}'.format(img))
|
||||
primogems_img = await convert_img(primogems_img)
|
||||
await bot.send(ev, primogems_img)
|
||||
if primogems_img.exists():
|
||||
logger.info('[图片][版本规划]访问图片: {}'.format(img))
|
||||
with open(primogems_img, 'rb') as f:
|
||||
await matcher.finish(MessageSegment.image(f.read()))
|
||||
else:
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
@sv.on_rex(r'^(查询)?(伤害乘区|血量表|抗性表|血量排行)$')
|
||||
async def send_img_data(bot: HoshinoBot, ev: CQEvent):
|
||||
args = ev['match'].groups()
|
||||
@get_img_data.handle()
|
||||
@handle_exception('杂图')
|
||||
@register_menu(
|
||||
'伤害乘区图',
|
||||
'(查询)伤害乘区',
|
||||
'发送一张理论伤害计算公式图',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'发送一张理论伤害计算公式图\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(125,125,125)>(查询)</ft><ft color=(238,120,0)>伤害乘区</ft>'
|
||||
),
|
||||
)
|
||||
@register_menu(
|
||||
'怪物血量表',
|
||||
'(查询)血量表',
|
||||
'发送一张原神怪物血量表图',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'发送一张原神怪物血量表图\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(125,125,125)>(查询)</ft><ft color=(238,120,0)>血量表</ft>'
|
||||
),
|
||||
)
|
||||
@register_menu(
|
||||
'怪物抗性表',
|
||||
'(查询)抗性表',
|
||||
'发送一张原神怪物抗性表图',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'发送一张原神怪物抗性表图\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(125,125,125)>(查询)</ft><ft color=(238,120,0)>抗性表</ft>'
|
||||
),
|
||||
)
|
||||
@register_menu(
|
||||
'怪物血量排行',
|
||||
'(查询)血量排行',
|
||||
'发送一张原神怪物血量排行图',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'发送一张原神怪物血量排行图\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(125,125,125)>(查询)</ft><ft color=(238,120,0)>血量排行</ft>'
|
||||
),
|
||||
)
|
||||
async def send_img_data(
|
||||
matcher: Matcher, args: Tuple[Any, ...] = RegexGroup()
|
||||
):
|
||||
logger.info('开始执行[图片][杂图]')
|
||||
logger.info('[图片][杂图]参数: {}'.format(args))
|
||||
img = IMG_PATH / f'{args[1]}.jpg'
|
||||
if img.exists():
|
||||
img = await convert_img(img)
|
||||
await bot.send(ev, img)
|
||||
else:
|
||||
return
|
||||
with open(img, 'rb') as f:
|
||||
await matcher.finish(MessageSegment.image(f.read()))
|
||||
|
Before Width: | Height: | Size: 910 KiB After Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 664 KiB After Width: | Height: | Size: 755 KiB |
Before Width: | Height: | Size: 628 KiB |
Before Width: | Height: | Size: 538 KiB |
BIN
GenshinUID/genshinuid_etcimg/primogems_data/3.5.png
Normal file
After Width: | Height: | Size: 617 KiB |
BIN
GenshinUID/genshinuid_etcimg/primogems_data/3.6.png
Normal file
After Width: | Height: | Size: 513 KiB |
@ -1,21 +1,56 @@
|
||||
from ..all_import import * # noqa: F401, F403
|
||||
from nonebot import on_command
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot.adapters.ntchat import MessageSegment
|
||||
|
||||
from ..config import priority
|
||||
from ..genshinuid_meta import register_menu
|
||||
from ..utils.nonebot2.rule import FullCommand
|
||||
from .draw_event_img import get_event_img, get_all_event_img
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
|
||||
get_event = on_command('活动列表', priority=priority, rule=FullCommand())
|
||||
get_gacha = on_command('卡池列表', priority=priority, rule=FullCommand())
|
||||
|
||||
|
||||
@sv.scheduled_job('cron', hour='2')
|
||||
@scheduler.scheduled_job('cron', hour='2')
|
||||
async def draw_event():
|
||||
await get_all_event_img()
|
||||
|
||||
|
||||
@sv.on_fullmatch('活动列表')
|
||||
async def send_events(bot: HoshinoBot, ev: CQEvent):
|
||||
@get_event.handle()
|
||||
@handle_exception('活动')
|
||||
@register_menu(
|
||||
'活动列表',
|
||||
'活动列表',
|
||||
'查询当前版本活动日程表',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'查询当前版本活动日程表\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>活动列表</ft>'
|
||||
),
|
||||
)
|
||||
async def send_events(matcher: Matcher):
|
||||
img = await get_event_img('EVENT')
|
||||
im = await convert_img(img)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(MessageSegment.image(img))
|
||||
|
||||
|
||||
@sv.on_fullmatch('卡池列表')
|
||||
async def send_gachas(bot: HoshinoBot, ev: CQEvent):
|
||||
@get_gacha.handle()
|
||||
@handle_exception('活动')
|
||||
@register_menu(
|
||||
'卡池列表',
|
||||
'卡池列表',
|
||||
'查询当前版本卡池列表',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'查询当前版本卡池列表\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>卡池列表</ft>'
|
||||
),
|
||||
)
|
||||
async def send_gachas(matcher: Matcher):
|
||||
img = await get_event_img('GACHA')
|
||||
im = await convert_img(img)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(MessageSegment.image(img))
|
||||
|
@ -1,7 +1,6 @@
|
||||
from io import BytesIO
|
||||
from re import findall
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import List, Literal
|
||||
|
||||
from httpx import get
|
||||
@ -100,10 +99,10 @@ class DrawEventList:
|
||||
base_img = await get_color_bg(950, base_h)
|
||||
|
||||
font_l = genshin_font_origin(62)
|
||||
font_m = genshin_font_origin(34)
|
||||
# font_m = genshin_font_origin(34)
|
||||
font_s = genshin_font_origin(26)
|
||||
|
||||
now_time = datetime.now().strftime('%Y/%m/%d')
|
||||
# now_time = datetime.now().strftime('%Y/%m/%d')
|
||||
event_cover = Image.open(TEXT_PATH / 'normal_event_cover.png')
|
||||
|
||||
for index, value in enumerate(self.normal_event):
|
||||
@ -177,10 +176,10 @@ class DrawEventList:
|
||||
base_img = await get_color_bg(950, base_h)
|
||||
|
||||
font_l = genshin_font_origin(62)
|
||||
font_m = genshin_font_origin(34)
|
||||
# font_m = genshin_font_origin(34)
|
||||
font_s = genshin_font_origin(26)
|
||||
|
||||
now_time = datetime.now().strftime('%Y/%m/%d')
|
||||
# now_time = datetime.now().strftime('%Y/%m/%d')
|
||||
gacha_cover = Image.open(TEXT_PATH / 'gacha_event_cover.png')
|
||||
|
||||
for index, value in enumerate(self.gacha_event):
|
||||
|
BIN
GenshinUID/genshinuid_eventlist/event.jpg
Normal file
After Width: | Height: | Size: 307 KiB |
BIN
GenshinUID/genshinuid_eventlist/gacha.jpg
Normal file
After Width: | Height: | Size: 411 KiB |
@ -1,86 +1,169 @@
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.rule import is_type
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot import get_bot, on_command, on_message
|
||||
from nonebot.adapters.ntchat.permission import GROUP
|
||||
from nonebot.adapters.ntchat import (
|
||||
MessageSegment,
|
||||
FileMessageEvent,
|
||||
TextMessageEvent,
|
||||
)
|
||||
|
||||
from .get_gachalogs import save_gachalogs
|
||||
from ..all_import import * # noqa: F403,F401
|
||||
from ..genshinuid_meta import register_menu
|
||||
from ..utils.nonebot2.rule import FullCommand
|
||||
from .draw_gachalogs import draw_gachalogs_img
|
||||
from ..utils.message.error_reply import UID_HINT
|
||||
from ..utils.db_operation.db_operation import select_db
|
||||
from ..utils.message.get_image_and_at import ImageAndAt
|
||||
from ..utils.message.error_reply import * # noqa: F403,F401
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
from .export_and_import import export_gachalogs, import_gachalogs
|
||||
|
||||
|
||||
@sv.on_notice()
|
||||
async def import_gacha_log_info(session: NoticeSession):
|
||||
ev = session.event
|
||||
if ev['notice_type'] != 'offline_file':
|
||||
return
|
||||
url = ev['file']['url']
|
||||
name: str = ev['file']['name']
|
||||
if not name.endswith('.json'):
|
||||
return
|
||||
qid = ev['user_id']
|
||||
uid = await select_db(qid, mode='uid')
|
||||
if not isinstance(uid, str) or '未找到绑定的UID' in uid:
|
||||
await session.send(UID_HINT)
|
||||
return
|
||||
logger.info('开始执行[导入抽卡记录]')
|
||||
im = await import_gachalogs(url, uid)
|
||||
await session.send(im, at_sender=True)
|
||||
get_gacha_log = on_command('刷新抽卡记录', aliases={'强制刷新抽卡记录'}, rule=FullCommand())
|
||||
get_gacha_log_card = on_command('抽卡记录', rule=FullCommand())
|
||||
import_gacha_log = on_message(block=False, rule=(is_type(FileMessageEvent)))
|
||||
export_gacha_log = on_command('导出抽卡记录', rule=FullCommand(), permission=GROUP)
|
||||
|
||||
|
||||
@sv.on_fullmatch('导出抽卡记录')
|
||||
async def export_gacha_log_info(bot: HoshinoBot, ev: CQEvent):
|
||||
@export_gacha_log.handle()
|
||||
@handle_exception('导出抽卡记录')
|
||||
@register_menu(
|
||||
'导出抽卡记录',
|
||||
'导出抽卡记录',
|
||||
'导出符合UIGF规范的抽卡记录',
|
||||
trigger_method='群聊指令',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'导出UIGF格式规范的json格式抽卡记录上传到群文件\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>导出抽卡记录</ft>'
|
||||
),
|
||||
)
|
||||
async def export_gacha_log_info(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
logger.info('开始执行[导出抽卡记录]')
|
||||
qid = int(ev.sender['user_id'])
|
||||
gid = int(ev.group_id)
|
||||
qid = event.from_wxid
|
||||
gid = event.room_wxid
|
||||
uid = await select_db(qid, mode='uid')
|
||||
bot = get_bot()
|
||||
if not isinstance(uid, str) or '未找到绑定的UID' in uid:
|
||||
await bot.send(ev, UID_HINT)
|
||||
return
|
||||
await matcher.finish(UID_HINT)
|
||||
raw_data = await export_gachalogs(uid)
|
||||
if raw_data['retcode'] == 'ok':
|
||||
await bot.call_action(
|
||||
action='upload_group_file',
|
||||
group_id=gid,
|
||||
name=raw_data['name'],
|
||||
file=raw_data['url'],
|
||||
await bot.call_api(
|
||||
'send_file',
|
||||
to_wxid=gid,
|
||||
file_path=raw_data['url'],
|
||||
)
|
||||
logger.info(f'[导出抽卡记录] UID{uid}成功!')
|
||||
await bot.send(ev, '上传成功!')
|
||||
await matcher.finish('上传成功!')
|
||||
else:
|
||||
logger.warning(f'[导出抽卡记录] UID{uid}失败!')
|
||||
await bot.send(ev, '导出抽卡记录失败!')
|
||||
await matcher.finish('导出抽卡记录失败!')
|
||||
|
||||
|
||||
@sv.on_fullmatch('抽卡记录')
|
||||
async def send_gacha_log_card_info(bot: HoshinoBot, ev: CQEvent):
|
||||
@import_gacha_log.handle()
|
||||
@handle_exception('导入抽卡记录')
|
||||
@register_menu(
|
||||
'导入抽卡记录',
|
||||
'json格式文件',
|
||||
'导入符合UIGF规范的抽卡记录',
|
||||
trigger_method='私聊离线文件',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'导入UIGF格式规范的json格式抽卡记录到插件本地缓存\n'
|
||||
' \n'
|
||||
'触发方式:\n'
|
||||
'- <ft color=(238,120,0)>私聊发送json格式的离线文件</ft>'
|
||||
),
|
||||
)
|
||||
async def import_gacha_log_info(event: FileMessageEvent, matcher: Matcher):
|
||||
await asyncio.sleep(2) # 等待下载文件,避免占用
|
||||
# 检测文件是否存在并小于8MB
|
||||
if (
|
||||
os.path.exists(event.file)
|
||||
and os.path.getsize(event.file) <= 8 * 1024 * 1024
|
||||
and event.file_name.endswith(".json")
|
||||
):
|
||||
uid = await select_db(event.from_wxid, mode='uid')
|
||||
if not isinstance(uid, str) or '未找到绑定的UID' in uid:
|
||||
await matcher.finish(UID_HINT)
|
||||
logger.info('开始执行[导入抽卡记录]')
|
||||
im = await import_gachalogs(event.file, uid)
|
||||
await matcher.finish(im)
|
||||
else:
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
@get_gacha_log_card.handle()
|
||||
@handle_exception('抽卡记录')
|
||||
@register_menu(
|
||||
'查询抽卡记录',
|
||||
'抽卡记录',
|
||||
'查询你的原神抽卡记录',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'查询你的原神抽卡记录\n'
|
||||
'需要<ft color=(238,120,0)>绑定Stoken</ft>\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'<ft color=(238,120,0)>抽卡记录</ft>'
|
||||
),
|
||||
)
|
||||
async def send_gacha_log_card_info(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
logger.info('开始执行[抽卡记录]')
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
uid = await select_db(qid, mode='uid')
|
||||
|
||||
uid = await select_db(event.from_wxid, mode='uid')
|
||||
if isinstance(uid, str):
|
||||
im = await draw_gachalogs_img(uid, qid)
|
||||
im = await draw_gachalogs_img(uid, event.from_wxid) # type: ignore
|
||||
if isinstance(im, bytes):
|
||||
im = await convert_img(im)
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await bot.send(ev, im)
|
||||
await matcher.finish(im)
|
||||
else:
|
||||
await bot.send(ev, UID_HINT)
|
||||
await matcher.finish(UID_HINT)
|
||||
|
||||
|
||||
@sv.on_fullmatch('刷新抽卡记录')
|
||||
async def send_daily_info(bot: HoshinoBot, ev: CQEvent):
|
||||
@get_gacha_log.handle()
|
||||
@handle_exception('刷新抽卡记录')
|
||||
@register_menu(
|
||||
'刷新抽卡记录',
|
||||
'刷新抽卡记录',
|
||||
'刷新你的原神抽卡记录本地缓存',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'刷新你的原神抽卡记录本地缓存\n'
|
||||
'需要<ft color=(238,120,0)>绑定Stoken</ft>\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>刷新抽卡记录</ft>'
|
||||
),
|
||||
)
|
||||
async def send_daily_info(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
):
|
||||
logger.info('开始执行[刷新抽卡记录]')
|
||||
if ev.sender:
|
||||
qid = int(ev.sender['user_id'])
|
||||
else:
|
||||
return
|
||||
uid = await select_db(qid, mode='uid')
|
||||
wxid_list = []
|
||||
wxid_list.append(event.from_wxid)
|
||||
uid = await select_db(event.from_wxid, mode='uid')
|
||||
if isinstance(uid, str):
|
||||
im = await save_gachalogs(uid)
|
||||
await bot.send(ev, im)
|
||||
is_force = False
|
||||
if event.msg.startswith('强制'):
|
||||
is_force = True
|
||||
tip = '正在刷新抽卡记录,请耐心等待,不要重复发送命令。'
|
||||
await matcher.send(
|
||||
MessageSegment.room_at_msg(content=tip, at_list=wxid_list)
|
||||
)
|
||||
im = await save_gachalogs(uid, None, is_force)
|
||||
await matcher.finish(im)
|
||||
else:
|
||||
await bot.send(ev, UID_HINT)
|
||||
await matcher.finish(UID_HINT)
|
||||
|
@ -11,9 +11,6 @@ from PIL import Image, ImageDraw
|
||||
from ..utils.draw_image_tools.send_image_tool import convert_img
|
||||
from ..utils.genshin_fonts.genshin_fonts import genshin_font_origin
|
||||
from ..utils.alias.avatarId_and_name_covert import name_to_avatar_id
|
||||
from ..utils.download_resource.RESOURCE_PATH import (
|
||||
CHAR_STAND_PATH as STAND_PATH,
|
||||
)
|
||||
from ..utils.download_resource.RESOURCE_PATH import (
|
||||
CHAR_PATH,
|
||||
PLAYER_PATH,
|
||||
@ -22,11 +19,7 @@ from ..utils.download_resource.RESOURCE_PATH import (
|
||||
from ..utils.draw_image_tools.draw_image_tool import (
|
||||
get_color_bg,
|
||||
get_qq_avatar,
|
||||
get_simple_bg,
|
||||
get_fetter_pic,
|
||||
get_talent_pic,
|
||||
draw_pic_with_ring,
|
||||
get_weapon_affix_pic,
|
||||
)
|
||||
|
||||
TEXT_PATH = Path(__file__).parent / 'texture2d'
|
||||
@ -138,6 +131,7 @@ async def draw_gachalogs_img(uid: str, qid: int) -> Union[bytes, str]:
|
||||
'avg_up': 0, # up平均数
|
||||
'remain': 0, # 已xx抽未出金
|
||||
'r_num': [], # 不包含首位的抽卡数量
|
||||
'e_num': [], # 包含首位的up抽卡数量
|
||||
'up_list': [], # 抽到的UP列表(不包含首位)
|
||||
'normal_list': [], # 抽到的五星列表(不包含首位)
|
||||
'list': [], # 抽到的五星列表
|
||||
@ -224,10 +218,9 @@ async def draw_gachalogs_img(uid: str, qid: int) -> Union[bytes, str]:
|
||||
# 往里加东西
|
||||
if is_not_first:
|
||||
total_data[i]['r_num'].append(num)
|
||||
total_data[i]['normal_list'].append(data)
|
||||
if data['is_up']:
|
||||
total_data[i]['up_list'].append(data)
|
||||
else:
|
||||
total_data[i]['normal_list'].append(data)
|
||||
|
||||
# 把这个数据扔到抽到的五星列表内
|
||||
total_data[i]['list'].append(data)
|
||||
|
@ -16,22 +16,30 @@ INT_TO_TYPE = {
|
||||
|
||||
|
||||
async def import_gachalogs(history_url: str, uid: str) -> str:
|
||||
history_data: dict = json.loads(get(history_url).text)
|
||||
data_uid = history_data['info']['uid']
|
||||
if data_uid != uid:
|
||||
return f'该抽卡记录UID{data_uid}与你绑定UID{uid}不符合!'
|
||||
raw_data = history_data['list']
|
||||
result = {'新手祈愿': [], '常驻祈愿': [], '角色祈愿': [], '武器祈愿': []}
|
||||
for item in raw_data:
|
||||
item['uid'] = uid
|
||||
item['item_id'] = ''
|
||||
item['count'] = '1'
|
||||
item['lang'] = 'zh-cn'
|
||||
item['id'] = str(item['id'])
|
||||
del item['uigf_gacha_type']
|
||||
result[INT_TO_TYPE[item['gacha_type']]].append(item)
|
||||
im = await save_gachalogs(uid, result)
|
||||
return im
|
||||
# 是否Json文件检测
|
||||
try:
|
||||
if not history_url.startswith(('http', 'https')):
|
||||
with open(history_url, 'r', encoding='utf-8') as history_url_files:
|
||||
history_data = json.load(history_url_files)
|
||||
else:
|
||||
history_data: dict = get(history_url).json()
|
||||
data_uid = history_data['info']['uid']
|
||||
if data_uid != uid:
|
||||
return f'该抽卡记录UID{data_uid}与你绑定UID{uid}不符合!'
|
||||
raw_data = history_data['list']
|
||||
result = {'新手祈愿': [], '常驻祈愿': [], '角色祈愿': [], '武器祈愿': []}
|
||||
for item in raw_data:
|
||||
item['uid'] = uid
|
||||
item['item_id'] = ''
|
||||
item['count'] = '1'
|
||||
item['lang'] = 'zh-cn'
|
||||
item['id'] = str(item['id'])
|
||||
del item['uigf_gacha_type']
|
||||
result[INT_TO_TYPE[item['gacha_type']]].append(item)
|
||||
im = await save_gachalogs(uid, result)
|
||||
return im
|
||||
except Exception:
|
||||
return '导入失败,请检查你的Json文件!'
|
||||
|
||||
|
||||
async def export_gachalogs(uid: str) -> dict:
|
||||
|
@ -2,11 +2,14 @@ import json
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
from ..utils.message.error_reply import SK_HINT
|
||||
from ..utils.download_resource.RESOURCE_PATH import PLAYER_PATH
|
||||
from ..utils.mhy_api.get_mhy_data import get_gacha_log_by_authkey
|
||||
|
||||
|
||||
async def save_gachalogs(uid: str, raw_data: Optional[dict] = None) -> str:
|
||||
async def save_gachalogs(
|
||||
uid: str, raw_data: Optional[dict] = None, is_force: bool = False
|
||||
) -> str:
|
||||
path = PLAYER_PATH / str(uid)
|
||||
if not path.exists():
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
@ -34,23 +37,33 @@ async def save_gachalogs(uid: str, raw_data: Optional[dict] = None) -> str:
|
||||
|
||||
# 获取新抽卡记录
|
||||
if raw_data is None:
|
||||
raw_data = await get_gacha_log_by_authkey(uid, gachalogs_history)
|
||||
raw_data = await get_gacha_log_by_authkey(
|
||||
uid, gachalogs_history, is_force
|
||||
)
|
||||
else:
|
||||
new_data = {'新手祈愿': [], '常驻祈愿': [], '角色祈愿': [], '武器祈愿': []}
|
||||
if gachalogs_history:
|
||||
for i in ['新手祈愿', '常驻祈愿', '角色祈愿', '武器祈愿']:
|
||||
for item in raw_data[i]:
|
||||
if item not in gachalogs_history[i]:
|
||||
if (
|
||||
item not in gachalogs_history[i]
|
||||
and item not in new_data[i]
|
||||
):
|
||||
new_data[i].append(item)
|
||||
raw_data = new_data
|
||||
for i in ['新手祈愿', '常驻祈愿', '角色祈愿', '武器祈愿']:
|
||||
raw_data[i].extend(gachalogs_history[i])
|
||||
|
||||
if raw_data == {}:
|
||||
return '你还没有绑定过Stoken噢~'
|
||||
if not raw_data:
|
||||
return '你还没有绑定过Stoken或者Stoken已失效~'
|
||||
if raw_data == {} or not raw_data:
|
||||
return SK_HINT
|
||||
|
||||
temp_data = {'新手祈愿': [], '常驻祈愿': [], '角色祈愿': [], '武器祈愿': []}
|
||||
for i in ['新手祈愿', '常驻祈愿', '角色祈愿', '武器祈愿']:
|
||||
for item in raw_data[i]:
|
||||
if item not in temp_data[i]:
|
||||
temp_data[i].append(item)
|
||||
raw_data = temp_data
|
||||
'''
|
||||
# 校验值 & 两个版本后删除这段
|
||||
temp_data = {'新手祈愿': [], '常驻祈愿': [], '角色祈愿': [], '武器祈愿': []}
|
||||
for i in ['新手祈愿', '常驻祈愿', '角色祈愿', '武器祈愿']:
|
||||
@ -58,6 +71,7 @@ async def save_gachalogs(uid: str, raw_data: Optional[dict] = None) -> str:
|
||||
if 'count' in item:
|
||||
temp_data[i].append(item)
|
||||
raw_data = temp_data
|
||||
'''
|
||||
|
||||
result['uid'] = uid
|
||||
result['data_time'] = current_time
|
||||
|
64
GenshinUID/genshinuid_gcg/__init__.py
Normal file
@ -0,0 +1,64 @@
|
||||
import re
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.adapters.ntchat.message import Message
|
||||
from nonebot.adapters.ntchat import MessageSegment, TextMessageEvent
|
||||
|
||||
from ..config import priority
|
||||
from .draw_gcginfo import draw_gcg_info
|
||||
from ..utils.db_operation.db_operation import select_db
|
||||
from ..utils.message.error_reply import CK_HINT, UID_HINT
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
|
||||
get_gcg_info = on_command('七圣召唤', aliases={'七圣', '召唤'}, priority=priority)
|
||||
|
||||
|
||||
# 群聊内 每月统计 功能
|
||||
@get_gcg_info.handle()
|
||||
@handle_exception('七圣召唤', '获取/发送七圣召唤失败', '@未找到绑定信息\n' + CK_HINT)
|
||||
async def send_gcg_pic(
|
||||
event: TextMessageEvent,
|
||||
matcher: Matcher,
|
||||
args: Message = CommandArg(),
|
||||
):
|
||||
raw_mes = args.extract_plain_text().strip().replace(' ', '')
|
||||
if "@" in raw_mes:
|
||||
name = ''.join(re.findall('^[\u4e00-\u9fa5]+', raw_mes.split("@")[0]))
|
||||
if name:
|
||||
return
|
||||
else:
|
||||
name = ''.join(re.findall('^[\u4e00-\u9fa5]+', raw_mes))
|
||||
if name:
|
||||
return
|
||||
logger.info('开始执行[七圣召唤]')
|
||||
qid = event.from_wxid
|
||||
if event.at_user_list:
|
||||
for user in event.at_user_list:
|
||||
user = user.strip()
|
||||
if user != "":
|
||||
qid = user
|
||||
logger.info('[七圣召唤]WXID: {}'.format(qid))
|
||||
|
||||
# 获取uid
|
||||
uid = re.findall(r'\d+', raw_mes.split("@")[0])
|
||||
if uid:
|
||||
uid = uid[0]
|
||||
else:
|
||||
uid = await select_db(qid, mode='uid')
|
||||
uid = str(uid)
|
||||
logger.info('[七圣召唤]uid: {}'.format(uid))
|
||||
|
||||
if '未找到绑定的UID' in uid:
|
||||
await matcher.finish(UID_HINT)
|
||||
|
||||
im = await draw_gcg_info(uid)
|
||||
|
||||
if isinstance(im, str):
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, bytes):
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await matcher.finish('发生了未知错误,请联系管理员检查后台输出!')
|
101
GenshinUID/genshinuid_gcg/draw_gcginfo.py
Normal file
@ -0,0 +1,101 @@
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
from nonebot.log import logger
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from ..utils.message.error_reply import CK_HINT
|
||||
from ..utils.mhy_api.get_mhy_data import get_gcg_info
|
||||
from ..utils.download_resource.download_url import download
|
||||
from ..utils.download_resource.RESOURCE_PATH import CARD_PATH
|
||||
from ..utils.draw_image_tools.send_image_tool import convert_img
|
||||
from ..utils.genshin_fonts.genshin_fonts import (
|
||||
gs_font_18,
|
||||
gs_font_26,
|
||||
gs_font_32,
|
||||
gs_font_50,
|
||||
)
|
||||
|
||||
TEXT_PATH = Path(__file__).parent / 'texture2d'
|
||||
|
||||
rank1 = Image.open(TEXT_PATH / '1.png').resize((400, 54))
|
||||
rank2 = Image.open(TEXT_PATH / '2.png').resize((400, 54))
|
||||
rank3 = Image.open(TEXT_PATH / '3.png').resize((400, 54))
|
||||
rank4 = Image.open(TEXT_PATH / '4.png').resize((400, 54))
|
||||
|
||||
frist_color = (45, 45, 45)
|
||||
second_color = (53, 53, 53)
|
||||
|
||||
|
||||
async def draw_gcg_info(uid: str) -> Union[bytes, str]:
|
||||
# 获得数据
|
||||
raw_data = await get_gcg_info(uid)
|
||||
if raw_data == {} or 'retcode' not in raw_data or 'data' not in raw_data:
|
||||
return CK_HINT
|
||||
if raw_data['retcode'] != 0:
|
||||
return CK_HINT
|
||||
raw_data = raw_data['data']
|
||||
if raw_data['covers'] == []:
|
||||
return f'UID{uid}还没有开启七圣召唤玩法 或 未去米游社激活数据!'
|
||||
|
||||
# 解析数据
|
||||
nickname: str = raw_data['nickname']
|
||||
level: int = raw_data['level']
|
||||
avatar_card_num_gained: int = raw_data['avatar_card_num_gained']
|
||||
avatar_card_num_total: int = raw_data['avatar_card_num_total']
|
||||
action_card_num_gained: int = raw_data['action_card_num_gained']
|
||||
action_card_num_total: int = raw_data['action_card_num_total']
|
||||
|
||||
avatar_rate = avatar_card_num_gained / avatar_card_num_total
|
||||
action_rate = action_card_num_gained / action_card_num_total
|
||||
|
||||
avatar = f'{avatar_card_num_gained} / {avatar_card_num_total}'
|
||||
action = f'{action_card_num_gained} / {action_card_num_total}'
|
||||
|
||||
# 制作图片
|
||||
img = Image.open(TEXT_PATH / 'BG.png')
|
||||
avatar_bar = await get_bar(avatar_rate)
|
||||
action_bar = await get_bar(action_rate)
|
||||
img.paste(avatar_bar, (440, 36), avatar_bar)
|
||||
img.paste(action_bar, (440, 101), action_bar)
|
||||
|
||||
img_draw = ImageDraw.Draw(img)
|
||||
# 右上区域
|
||||
img_draw.text((469, 63), '已解锁角色牌', frist_color, gs_font_26, 'lm')
|
||||
img_draw.text((469, 128), '已收集行动牌', frist_color, gs_font_26, 'lm')
|
||||
|
||||
img_draw.text((805, 63), avatar, frist_color, gs_font_26, 'rm')
|
||||
img_draw.text((805, 128), action, frist_color, gs_font_26, 'rm')
|
||||
|
||||
# 左上区域
|
||||
img_draw.text((165, 87), nickname, frist_color, gs_font_32, 'lm')
|
||||
img_draw.text((165, 120), f'UID{uid}', frist_color, gs_font_18, 'lm')
|
||||
img_draw.text((102, 97), str(level), 'white', gs_font_50, 'mm')
|
||||
|
||||
for i, card in enumerate(raw_data['covers']):
|
||||
file_name = f'{card["id"]}.png'
|
||||
path = CARD_PATH / file_name
|
||||
if path.exists():
|
||||
card_img = Image.open(path).resize((160, 275))
|
||||
else:
|
||||
await download(card['image'], 9, file_name)
|
||||
card_img = Image.open(path).resize((160, 275))
|
||||
|
||||
img.paste(card_img, (65 + i * 204, 198), card_img)
|
||||
|
||||
img = await convert_img(img)
|
||||
logger.info('[七圣召唤]绘图已结束,等待发送...')
|
||||
return img
|
||||
|
||||
|
||||
async def get_bar(rate: float) -> Image.Image:
|
||||
if rate <= 0.25:
|
||||
bar = rank1
|
||||
elif rate <= 0.58:
|
||||
bar = rank2
|
||||
elif rate <= 0.8:
|
||||
bar = rank3
|
||||
else:
|
||||
bar = rank4
|
||||
|
||||
return bar
|
BIN
GenshinUID/genshinuid_gcg/texture2d/1.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
GenshinUID/genshinuid_gcg/texture2d/2.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
GenshinUID/genshinuid_gcg/texture2d/3.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
GenshinUID/genshinuid_gcg/texture2d/4.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
GenshinUID/genshinuid_gcg/texture2d/BG.png
Normal file
After Width: | Height: | Size: 293 KiB |
@ -1,47 +1,136 @@
|
||||
import asyncio
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
import httpx
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot import on_regex, on_command
|
||||
from nonebot.params import CommandArg, RegexGroup
|
||||
from nonebot.adapters.ntchat import MessageSegment
|
||||
from nonebot.adapters.ntchat.message import Message
|
||||
|
||||
from ..all_import import *
|
||||
from .get_card import get_gs_card
|
||||
from .get_guide import get_gs_guide
|
||||
from ..version import Genshin_version
|
||||
from ..genshinuid_meta import register_menu
|
||||
from .get_abyss_data import get_review, generate_data
|
||||
from ..utils.alias.alias_to_char_name import alias_to_char_name
|
||||
from ..utils.exception.handle_exception import handle_exception
|
||||
|
||||
get_guide_pic = on_regex('([\u4e00-\u9fa5]+)(推荐|攻略)')
|
||||
get_bluekun_pic = on_command('参考面板')
|
||||
get_card = on_command('原牌')
|
||||
get_abyss = on_command('版本深渊')
|
||||
|
||||
IMG_PATH = Path(__file__).parent / 'img'
|
||||
|
||||
|
||||
@sv.on_rex('([\u4e00-\u9fa5]+)(推荐|攻略)')
|
||||
async def send_guide_pic(bot: HoshinoBot, ev: CQEvent):
|
||||
name = str(ev['match'].group(1))
|
||||
if not name:
|
||||
@get_guide_pic.handle()
|
||||
@handle_exception('建议')
|
||||
@register_menu(
|
||||
'角色攻略',
|
||||
'xx攻略',
|
||||
'发送一张对应角色的西风驿站攻略图',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'发送一张对应角色的米游社西风驿站攻略图\n'
|
||||
'支持部分角色别名\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(0,148,200)>[角色名]</ft>'
|
||||
'<ft color=(238,120,0)>{推荐|攻略}</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>钟离推荐</ft>\n'
|
||||
'- <ft color=(238,120,0)>公子攻略</ft>'
|
||||
),
|
||||
)
|
||||
async def send_guide_pic(
|
||||
matcher: Matcher, args: Tuple[Any, ...] = RegexGroup()
|
||||
):
|
||||
if not args:
|
||||
return
|
||||
name = await alias_to_char_name(name)
|
||||
if name.startswith('旅行者'):
|
||||
name = f'{name[:3]}-{name[-1]}'
|
||||
url = 'https://file.microgg.cn/MiniGG/guide/{}.jpg'.format(name)
|
||||
if httpx.head(url).status_code == 200:
|
||||
logger.info('获得{}推荐图片成功!'.format(name))
|
||||
await bot.send(ev, MessageSegment.image(url))
|
||||
name = str(args[0])
|
||||
im = await get_gs_guide(name)
|
||||
if im:
|
||||
logger.info('获得{}攻略成功!'.format(name))
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
logger.warning('未获得{}推荐图片。'.format(name))
|
||||
logger.warning('未找到{}攻略图片'.format(name))
|
||||
|
||||
|
||||
@sv.on_prefix('参考面板')
|
||||
async def send_bluekun_pic(bot: HoshinoBot, ev: CQEvent):
|
||||
if ev.message:
|
||||
message = ev.message.extract_plain_text().replace(' ', '')
|
||||
@get_bluekun_pic.handle()
|
||||
@handle_exception('参考面板')
|
||||
@register_menu(
|
||||
'参考面板',
|
||||
'参考面板[角色名/元素名]',
|
||||
'发送一张对应角色/元素的参考面板图',
|
||||
detail_des=(
|
||||
'介绍:\n'
|
||||
'发送一张对应角色/元素的参考面板图\n'
|
||||
'支持部分角色别名\n'
|
||||
' \n'
|
||||
'指令:\n'
|
||||
'- <ft color=(238,120,0)>参考面板</ft>'
|
||||
'<ft color=(0,148,200)>[角色名/元素名]</ft>\n'
|
||||
' \n'
|
||||
'示例:\n'
|
||||
'- <ft color=(238,120,0)>参考面板火</ft>\n'
|
||||
'- <ft color=(238,120,0)>参考面板公子</ft>'
|
||||
),
|
||||
)
|
||||
async def send_bluekun_pic(matcher: Matcher, args: Message = CommandArg()):
|
||||
if str(args[0]) in ['冰', '水', '火', '草', '雷', '风', '岩']:
|
||||
name = str(args[0])
|
||||
else:
|
||||
return
|
||||
|
||||
if message == '':
|
||||
return
|
||||
|
||||
if str(message) in ['冰', '水', '火', '草', '雷', '风', '岩']:
|
||||
name = str(message)
|
||||
else:
|
||||
name = await alias_to_char_name(str(message))
|
||||
name = await alias_to_char_name(str(args[0]))
|
||||
img = IMG_PATH / '{}.jpg'.format(name)
|
||||
if img.exists():
|
||||
img = await convert_img(img)
|
||||
with open(img, 'rb') as f:
|
||||
im = MessageSegment.image(f.read())
|
||||
logger.info('获得{}参考面板图片成功!'.format(name))
|
||||
await bot.send(ev, img)
|
||||
await matcher.finish(im)
|
||||
else:
|
||||
logger.warning('未找到{}参考面板图片'.format(name))
|
||||
|
||||
|
||||
@get_card.handle()
|
||||
@handle_exception('原牌')
|
||||
async def send_gscard_pic(matcher: Matcher, args: Message = CommandArg()):
|
||||
if not args:
|
||||
return
|
||||
name = str(args[0])
|
||||
im = await get_gs_card(name)
|
||||
if im:
|
||||
logger.info('获得{}原牌成功!'.format(name))
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
logger.warning('未找到{}原牌图片'.format(name))
|
||||
|
||||
|
||||
@get_abyss.handle()
|
||||
@handle_exception('版本深渊')
|
||||
async def send_abyss_review(
|
||||
matcher: Matcher,
|
||||
args: Message = CommandArg(),
|
||||
):
|
||||
if not args:
|
||||
version = Genshin_version[:-2]
|
||||
else:
|
||||
version = str(args[0])
|
||||
im = await get_review(version)
|
||||
if isinstance(im, List):
|
||||
im = '\n'.join(im)
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, str):
|
||||
await matcher.finish(im)
|
||||
elif isinstance(im, bytes):
|
||||
await matcher.finish(MessageSegment.image(im))
|
||||
else:
|
||||
await matcher.finish('发生了未知错误,请联系管理员检查后台输出!')
|
||||
|
||||
|
||||
threading.Thread(
|
||||
target=lambda: asyncio.run(generate_data()), daemon=True
|
||||
).start()
|
||||
|
25
GenshinUID/genshinuid_guide/abyss_history.py
Normal file
@ -0,0 +1,25 @@
|
||||
history_data = {
|
||||
'3.6': {'9': '1044', '10': '1045', '11': '1058', '12': '1059'},
|
||||
'3.5': {'9': '1044', '10': '1045', '11': '1056', '12': '1057'},
|
||||
'3.4': {'9': '1044', '10': '1045', '11': '1054', '12': '1055'},
|
||||
'3.3': {'9': '1044', '10': '1045', '11': '1052', '12': '1053'},
|
||||
'3.2': {'9': '1044', '10': '1045', '11': '1050', '12': '1051'},
|
||||
'3.1': {'9': '1044', '10': '1045', '11': '1048', '12': '1049'},
|
||||
'3.0': {'9': '1044', '10': '1045', '11': '1046', '12': '1047'},
|
||||
'2.8': {'9': '1024', '10': '1025', '11': '1042', '12': '1043'},
|
||||
'2.7': {'9': '1024', '10': '1025', '11': '1040', '12': '1041'},
|
||||
'2.6': {'9': '1024', '10': '1025', '11': '1038', '12': '1039'},
|
||||
'2.5': {'9': '1024', '10': '1025', '11': '1036', '12': '1037'},
|
||||
'2.4': {'9': '1024', '10': '1025', '11': '1034', '12': '1035'},
|
||||
'2.3': {'9': '1024', '10': '1025', '11': '1032', '12': '1033'},
|
||||
'2.2': {'9': '1024', '10': '1025', '11': '1030', '12': '1031'},
|
||||
'2.1': {'9': '1024', '10': '1025', '11': '1028', '12': '1029'},
|
||||
'2.0': {'9': '1024', '10': '1025', '11': '1026', '12': '1027'},
|
||||
'1.6': {'9': '1020', '10': '1021', '11': '1022', '12': '1023'},
|
||||
'1.5': {'9': '1013', '10': '1014', '11': '1018', '12': '1019'},
|
||||
'1.4': {'9': '1013', '10': '1014', '11': '1015', '12': '1017'},
|
||||
'1.3': {'9': '1013', '10': '1014', '11': '1015', '12': '1016'},
|
||||
'1.2': {'9': '1009', '10': '1010', '11': '1011', '12': '1012'},
|
||||
'1.1': {'9': '1009', '10': '1010', '11': '1011', '12': '1012'},
|
||||
'1.0': {'9': '1009', '10': '1010', '11': '1011', '12': '1012'},
|
||||
}
|
BIN
GenshinUID/genshinuid_guide/card/artifact/不动玄石之相.jpg
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
GenshinUID/genshinuid_guide/card/artifact/冒险家头带.jpg
Normal file
After Width: | Height: | Size: 53 KiB |