132 Commits
ntchat ... main

Author SHA1 Message Date
574d908fd4 🍱 更新xx用什么的数据集 2023-04-15 01:42:08 +08:00
42c57c0fda 🍱 更新收集的宝箱数据 2023-04-14 11:58:07 +08:00
dc98f25514 🍱 更新新圣遗物数据 2023-04-13 14:21:16 +08:00
303aad8df6 添加查询琦良良换xx圣遗物的支持 2023-04-13 01:08:38 +08:00
87cbd2af64 🍱 更新原神3.6版本的metadata 2023-04-12 01:01:30 +08:00
c27704089d 🍱 更新血量表等杂图资源 2023-04-09 00:55:38 +08:00
8c6d5ed29e 🍱 更新原石预估素材 2023-04-02 00:27:31 +08:00
09d318bf2e 🍱 新增新角色的参考面板,优化体验 2023-04-01 00:48:10 +08:00
96570f7774 🐛 修改导出的bot_id 2023-03-25 18:32:06 +08:00
05c89cdcff 🍱 更新参考面板版本深渊内容 2023-03-25 13:23:28 +08:00
60e7e71f48 🐛 修复材料 (#471) 2023-03-21 17:14:03 +08:00
4baf75ef55 🍱 更新原石预估 2023-03-14 00:22:52 +08:00
f16fd2d383 新增导出v3数据 2023-03-09 23:38:51 +08:00
ed4b519d89 新增gsrc (#463)
* add oprc

* 🚨 auto fix by pre-commit-ci

* Update __init__.py

* Update topup.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-08 18:16:40 +08:00
86b454ab99 🐛 修复查询白术的数据异常 2023-03-02 22:24:04 +08:00
49f61d1fc8 🍱 补充部分原神3.6的数据资料 2023-03-02 21:49:24 +08:00
be009834ec ️ 支持查询白术查询卡维,修复几个BUG,优化体验 2023-03-02 21:17:01 +08:00
57766ef6e4 🎨 删除通用获取成功log (#461) 2023-03-02 02:03:18 +08:00
ad06c09305 🐛 修补MetaData, 调整BUG(#461) 2023-03-02 00:03:11 +08:00
08d9ab738d 🍱 更新原神3.5版本MetaData 2023-03-01 01:14:49 +08:00
2961685798 🎨 补充扫码登陆的别名触发 2023-02-26 20:02:09 +08:00
386ed31f85 🎨扫码登陆改为群聊触发 2023-02-23 00:55:55 +08:00
f4b48c05f1 🎨 更新切换api的API地址 2023-02-15 21:05:18 +08:00
32c5b081e3 🐛 修复下载全部资源的问题 2023-02-14 23:27:33 +08:00
c0f2968dfe 下载全部资源将会检查版本差异 2023-02-14 22:27:53 +08:00
5a38f1575e 🐛 修复部分情况下,扫码登陆无法获取正确UID 2023-02-13 00:01:53 +08:00
5e223820e5 🐛 修复刷新抽卡记录 (#447) 2023-02-12 02:35:20 +08:00
d203cdbfe9 🎨 Update get_mhy_data.py (#442) 2023-02-09 21:50:45 +08:00
0489649a59 新增强制刷新抽卡记录 2023-02-09 02:10:03 +08:00
845b55a9c8 📝 合并雷神和薙刀效果 2023-02-08 00:30:01 +08:00
bfb0c1d6a5 🚑️ 修复删除配置项错误的问题 2023-02-07 00:09:22 +08:00
d937db7ddf 🎨 移除失效配置、新增随机图配置 2023-02-06 23:47:02 +08:00
ae5ea9f806 🐛 修复查询雷神的数值错误 2023-02-06 23:39:45 +08:00
d2c73d0b35 🔧 修复pre-commit 2023-02-05 22:07:50 +08:00
b8511958c3 📝 更新查询收集的最大值 2023-02-05 22:06:25 +08:00
c7834872cb 👽️ 更换随机图接口地址 (#441) 2023-02-05 20:23:22 +08:00
9bbff850c3 📝 更新攻略、gs帮助、修复伤害参考bug 2023-02-04 19:14:34 +08:00
892160c347 🎨 微调强制刷新界面` 2023-02-03 00:30:49 +08:00
bb6738407e 🎨 修改强制刷新界面 2023-02-02 00:29:57 +08:00
ebcf210193 👽 更新ENKA_API 2023-01-29 22:53:05 +08:00
5805b502f7 🍱 更新3.4血量表抗性表 2023-01-27 23:44:47 +08:00
7d9fbbb2d7 🐛 修复注册时间 2023-01-26 20:28:55 +08:00
bf85710a0c 🚨 移除重复导入 2023-01-26 19:33:52 +08:00
204d265997 新增原神注册时间命令 (#431) 2023-01-26 19:22:48 +08:00
ab57306806 🐛 修复查询心海的伤害计算和毕业度错误 2023-01-25 20:58:12 +08:00
4dd4f44fdf 🚨 修改重复使用的函数名 2023-01-25 16:49:15 +08:00
b502a75f0e 🎨扫码登陆套聊天记录 (#430) 2023-01-25 16:46:58 +08:00
097c6cf0e7 新增chbg文件夹以供查询角色的自定义图 2023-01-24 00:03:34 +08:00
f5214ac84a 新增查成就查委托 2023-01-23 16:02:02 +08:00
f2b6f410a1 🍱 更新一部分成就资源 2023-01-23 15:21:52 +08:00
b6849a0b8f 🍱 更新艾尔海森瑶瑶的参考面板 2023-01-22 23:03:50 +08:00
a60cd5311a 🍱 增加查询米卡的治疗量计算,修复旅行者BUG 2023-01-20 19:11:16 +08:00
fe895c7360 🐛 修复新角色基础数值错误的问题 2023-01-20 00:10:00 +08:00
91b199cb8e 🍱 更新3.5版本角色的别名和有效词条 2023-01-19 23:37:01 +08:00
75f9784c42 🍱 支持部分3.5版本的角色和武器的替换 2023-01-19 23:22:25 +08:00
e361833691 🐛 修复签到 (#428) 2023-01-18 21:55:03 +08:00
983ee6ee2e 🍱 更新原神3.4版本MetaData 2023-01-18 02:00:16 +08:00
4ef3e580ed 🐛 修复原神每日签到奖励物品名称获取错误的问题 (#428) 2023-01-17 22:26:41 +08:00
e2dd96f9f4 📝 修改ck帮助的说明 (#421) 2023-01-17 00:21:40 +08:00
a26016842e 🎨 补充_pass内容 2023-01-17 00:16:00 +08:00
63983ca097 新增刷新全部CK(需SU用户) 2023-01-16 23:38:28 +08:00
85b53199d9 🐛 修复xx在哪的一部分使用体验 (#427) 2023-01-15 17:47:46 +08:00
1e6f3eb833 新增刷新CK(需要用户绑定SK) 2023-01-15 00:52:39 +08:00
f03d9eb79c 🎨 增加几个安柏计划API 2023-01-14 23:59:28 +08:00
8516093a54 🐛 修复定时公告推送的图片发送问题 (#424)
* 修复定时公告推送的图片发送问题

* no message
2023-01-11 13:15:20 +08:00
d3f7bb00a7 🐛 修正暴击率低于0%时期望伤害低于普通值的问题 (#422)
* 修正暴击率低于0%时期望伤害低于普通值的问题

* 🚨 auto fix by pre-commit-ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-10 22:52:01 +08:00
10d7030b89 🍱 更新原始预估3.4 2023-01-10 22:52:10 +08:00
7a643f3614 🎨 修改convert_img方法 2023-01-08 01:00:08 +08:00
30533608a3 🐛 修复错误的sp_base 2023-01-06 23:17:32 +08:00
a6df16a77e 🍱 同步查询艾尔海森的倍率,新增查询夜兰的蒸发伤害 2023-01-05 23:49:59 +08:00
1278e462a5 🐛 修复原神公告的图片发送问题 (#415) 2023-01-04 13:00:49 +08:00
544ff51f70 🐛 修复特殊情况下几种默认反应倍率 2023-01-03 23:59:49 +08:00
2b0a753f6b 🐛 修复原神公告的部分详情出现https错误 2023-01-02 23:50:23 +08:00
d93c8a91ef 🐛 修复清除原神公告红点 2023-01-01 01:32:46 +08:00
064bec0b44 🔥 remove all_import 2022-12-31 01:08:15 +08:00
0be8350990 🚨 fix lint warnings
@KimigaiiWuyi 单独sv和统一sv这个想一下,还有logger和bot

logger: nonebot.logger or base.logger(i.e. sv.logger)

bot: nonebot.get_bot(i.e. NoneBot) or hoshino.get_bot(i.e. HoshinoBot)
2022-12-31 00:43:19 +08:00
2cab5fc497 🐛 修复原神公告图片发送问题 2022-12-30 00:32:14 +08:00
55cd588fda 📝 更新gs帮助 2022-12-30 00:30:45 +08:00
2e3a796de4 🐛 修复查询绫人等几个角色的伤害异常(#413) 2022-12-29 23:37:32 +08:00
fc30ef62e2 🚨 fix lint warnings 2022-12-29 00:16:05 +08:00
6ff4c6dc4e 🚑 fix bbs coin error 2022-12-28 14:21:48 +08:00
a37333902e 🔥 移除typing.Unpack 2022-12-28 01:39:11 +08:00
f6f2ed13ec 🔥 remove pre-commit action 2022-12-27 22:39:32 +08:00
ab46d03bec 新增原神公告 (#409)
* 新增原神公告

* 🚨 auto fix by pre-commit-ci

* 🎨 功能分离

* 🚨 auto fix by pre-commit-ci

* 🎨 优化util的一部分函数

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: KimigaiiWuyi <444835641@qq.com>
2022-12-27 22:37:32 +08:00
2ed1b6120a 新增二维码登录 (#410)
*  缝合自动过验证码,提高成功率 (#384)

* 缝合自动过验证码,提高成功率

* 🚨 auto fix by pre-commit-ci

* 🎨 use httpx.AsyncClient instead

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: MingxuanGame <MingxuanGame@outlook.com>

* Update README.md

* add qrlogin

* 1

* 2

* 3

* 3

* 3

* 4

* 5

* 🚨 auto fix by pre-commit-ci

*  improve qrcode login

* 🐛 fix unexpected arguments error

* 🚨 auto fix by pre-commit-ci

* delete aiorequests

* delete aiorequests

* 🎨 update dependencies

Co-authored-by: SonderXiaoming <98363578+SonderXiaoming@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: MingxuanGame <MingxuanGame@outlook.com>
2022-12-27 16:04:27 +08:00
21065764f2 新增版本深渊,后可跟版本号 2022-12-22 00:21:45 +08:00
a7055bc926 增加hhw_api的相关方法 2022-12-21 23:01:32 +08:00
94ad87b725 🎨 调整添加CK时候的判定顺序,修复几个BUG 2022-12-20 00:43:16 +08:00
c0d36979ac 新增数据文件夹内bg文件夹,用于存放自定义图片 2022-12-19 00:04:48 +08:00
f361f0067a 新增查询探索, 重绘的查询收集 2022-12-18 19:54:50 +08:00
55dad23026 🎨 添加draw_bar绘图方法,重绘收集样式 2022-12-18 19:50:00 +08:00
799bfdce3e 🐛 修复已知BUG、修改参考数值和角色BUFF错误 2022-12-17 19:17:31 +08:00
3453905a82 🐛 修复已知BUG,优化报错提示(#405) 2022-12-17 00:08:47 +08:00
923240c4d2 🎨 为多条推送信息添加换行符 (#403) 2022-12-14 12:11:18 +08:00
a97a01a7d9 🐛 修正国际服七圣召唤api (#402) 2022-12-14 11:59:36 +08:00
2ec298be3a 🐛 下载全部资源时判断是否为空文件名 2022-12-14 09:45:48 +08:00
a1f086d24b 攻略换用本地攻略源 2022-12-13 23:32:21 +08:00
5279c23e7f 🎨 更新深渊概览API,添加自动下载攻略 2022-12-13 23:18:48 +08:00
40cd95f3ca 🍱 补充3.3新角色的参考面板毕业度 2022-12-12 22:53:29 +08:00
1e5aa0a899 📝 限制绑定uid位数, 补充gs帮助 2022-12-11 17:19:11 +08:00
6518166bd0 🎨 修改资源文件名 (#400)
* 修改资源文件名

统一命名,去除卡牌资源文件结尾的符号

* 🚨 auto fix by pre-commit-ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-11 14:07:08 +08:00
c68c2fab99 新增七圣召唤命令,后可跟UID 2022-12-11 14:06:52 +08:00
4e620b29e4 🎨 添加七圣召唤的API,修复一部分BUG 2022-12-11 13:51:21 +08:00
4323fd52d8 🍱 补充一些遗漏资源,修复几个BUG 2022-12-09 23:15:22 +08:00
4fc5ae5d03 🍱 补充一部分3.4的资源文件 2022-12-09 20:25:11 +08:00
a6c3ccc890 新增原牌功能, 例如原牌狼末 2022-12-08 23:36:03 +08:00
19a7fb6219 🍱 增加一些资源文件 2022-12-08 23:21:21 +08:00
ac4888469f 📝 提高原神版本至3.3 2022-12-07 01:08:47 +08:00
8ab1a26df7 🍱 新增原神3.3版本的元数据 2022-12-07 01:06:14 +08:00
742cc36ca4 🐛 修复低星圣遗物的错误 2022-12-05 23:39:52 +08:00
b6a301f796 🐛 修复抽卡记录平均UP错误和提纳里的毕业度 2022-12-04 17:03:54 +08:00
3dcf462138 🎨 优化一部分API访问 2022-12-04 14:00:01 +08:00
a1632a5fba 🐛 修复截获CK字段的错误 2022-12-03 00:05:22 +08:00
f531e9422f 🍱 补充版本规划3.3 2022-12-02 22:52:02 +08:00
b28f41f543 🐛 修复深渊概览刷新失败 2022-12-02 22:45:49 +08:00
89ad282ed4 🐛 修复未绑定UID时签到指令异常返回 2022-11-30 23:53:20 +08:00
7d2864a40c 🐛 修复查询妮露的不正确毕业度(#397) 2022-11-29 22:01:50 +08:00
96dad7e0ce 🎨 强制刷新支持使用@别人查询(#396) 2022-11-29 21:14:09 +08:00
b48789aa69 🎨 优化毕业度的计算值 2022-11-29 21:09:42 +08:00
4664b80137 🎨 重做的毕业度计算 2022-11-29 01:48:19 +08:00
a395fc9f7e 🚑️ 补充错误获取的CK 2022-11-26 23:32:01 +08:00
1f34c9d8a2 新增深渊统计(深渊概览) 2022-11-26 15:15:28 +08:00
ebfde531e6 🐛 修复缓存获取CK的BUG 2022-11-26 15:07:07 +08:00
cc132c3e36 🐛 修复仅修改命座的报错 2022-11-24 11:56:28 +08:00
f16edc8b63 🐛 修复查询六命散兵换xx无法生效的问题 2022-11-24 00:35:08 +08:00
b7a19253da 支持查询公子换香菱沙类似用法,并可以任意组合 2022-11-24 00:24:32 +08:00
8fc962857c 💥 修改PROXY配置至config.json 2022-11-23 22:05:30 +08:00
281d028878 🐛 兼容国际服的ID至UID的转换, 尝试从绑定UID读取 2022-11-12 17:00:43 +08:00
a23e12b62e 👽️ 判断v2版本的米游社CK(#383) 2022-11-12 16:18:48 +08:00
1df00f92ed 🎨 改进一部分错误提示 2022-11-12 00:29:20 +08:00
1ce7348f62 👽️ 增加mr出现验证码的错误提示(#379) 2022-11-11 23:43:02 +08:00
e381d43fff 🎨 修改部分角色BUFF,修改部分UI表现 2022-11-11 22:51:41 +08:00
410 changed files with 28091 additions and 8530 deletions

View File

@ -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

2
.gitignore vendored
View File

@ -668,3 +668,5 @@ result.txt
### GenshinUID ###
GenshinUID/genshinuid_help/help.png
GenshinUID/genshinuid_map/map_data
.vscode/settings.json
.vscode/settings.json

View File

@ -5,8 +5,13 @@ ci:
autoupdate_schedule: monthly
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit-ci"
repos:
- repo: https://github.com/hadialqattan/pycln
rev: v2.1.2
hooks:
- id: pycln
- repo: https://github.com/pycqa/isort
rev: 5.10.1
rev: 5.11.5
hooks:
- id: isort
@ -19,3 +24,18 @@ repos:
rev: 5.0.4
hooks:
- id: flake8
- repo: https://github.com/python-poetry/poetry
rev: 1.3.1
hooks:
- id: poetry-check
- id: poetry-export
args:
[
"-f",
"requirements.txt",
"--without-hashes",
"-o",
"requirements.txt",
]
verbose: true

View File

@ -1,6 +1,6 @@
{
"python.languageServer": "Pylance",
"python.analysis.typeCheckingMode": "basic",
"python.analysis.typeCheckingMode": "off",
"cSpell.words": [
"audioid",
"enka",

View File

@ -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

6
GenshinUID/base.py Normal file
View File

@ -0,0 +1,6 @@
import hoshino
from hoshino import Service
sv = Service('genshinuid', bundle='genshin', help_='发送"原神帮助"查看详情')
hoshino_bot = hoshino.get_bot()
logger = sv.logger

View File

@ -1,6 +1,13 @@
import re
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv, logger
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.draw_image_tools.send_image_tool import convert_img
@sv.on_rex(

View File

@ -0,0 +1,36 @@
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv, logger
from .get_achi_desc import get_achi, get_daily_achi
@sv.on_prefix('查委托')
async def send_task_info(bot: HoshinoBot, ev: CQEvent):
if ev.message:
message = ev.message.extract_plain_text().replace(' ', '')
else:
return
if message == '':
return
name = str(message)
logger.info(f'[查委托] 参数:{name}')
im = await get_daily_achi(name)
await bot.send(ev, im)
@sv.on_prefix('查成就')
async def send_achi_info(bot: HoshinoBot, ev: CQEvent):
if ev.message:
message = ev.message.extract_plain_text().replace(' ', '')
else:
return
if message == '':
return
name = str(message)
logger.info(f'[查成就] 参数:{name}')
im = await get_achi(name)
await bot.send(ev, im)

File diff suppressed because it is too large Load Diff

View 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": ""
}
}

View 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

View 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 = '''合辑:【{}
成就:【{}
描述:【{}
'''

View File

@ -1,5 +1,8 @@
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv
from .get_adv import char_adv, weapon_adv
from ..all_import import * # noqa: F401, F403
from ..utils.alias.alias_to_char_name import alias_to_char_name
@sv.on_rex(r'([\u4e00-\u9fa5]+)(用什么|能用啥|怎么养)')

File diff suppressed because it is too large Load Diff

View File

@ -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}', '-=-=-=-=-=-=-=-=-=-']

View File

@ -0,0 +1,133 @@
import random
import asyncio
from nonebot import get_bot
from hoshino import Service, priv
from ..base import logger
from .util import black_ids
from .main import ann, consume_remind
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.draw_image_tools.send_image_tool import convert_img
from .ann_card import sub_ann, unsub_ann, ann_list_card, ann_detail_card
sv_help = '''
原神公告
原神公告#ID
'''.strip()
sv = Service(
name='原神公告', # 功能名
use_priv=priv.NORMAL, # 使用权限
manage_priv=priv.ADMIN, # 管理权限
visible=True, # 可见性
enable_on_default=True, # 默认启用
# bundle = '娱乐', #分组归类
help_=sv_help, # 帮助说明
)
prefix = '原神'
@sv.on_prefix((f'{prefix}公告#', f'{prefix}公告'))
async def ann_(bot, ev):
ann_id = ev.message.extract_plain_text().strip()
if not ann_id:
img = await ann_list_card()
img = await convert_img(img)
await bot.send(ev, img, at_sender=True)
return
if not ann_id.isdigit():
raise Exception("公告ID不正确")
try:
img = await ann_detail_card(int(ann_id))
img = await convert_img(img)
await bot.send(ev, img, at_sender=True)
except Exception as e:
await bot.finish(ev, str(e))
@sv.on_fullmatch(f'订阅{prefix}公告')
async def sub_ann_(bot, ev):
if not priv.check_priv(ev, priv.ADMIN):
raise Exception("你没有权限开启原神公告推送")
try:
await bot.send(ev, sub_ann(ev.group_id))
except Exception as e:
await bot.finish(ev, str(e))
@sv.on_fullmatch((f'取消订阅{prefix}公告', f'取消{prefix}公告', f'退订{prefix}公告'))
async def unsub_ann_(bot, ev):
if not priv.check_priv(ev, priv.ADMIN):
raise Exception("你没有权限取消原神公告推送")
try:
await bot.send(ev, unsub_ann(ev.group_id))
except Exception as e:
await bot.finish(ev, str(e))
@sv.on_fullmatch(f'取消{prefix}公告红点')
async def consume_remind_(bot, ev):
try:
qid = int(ev.sender['user_id'])
uid = await select_db(qid, mode='uid')
uid = str(uid)
if '未找到绑定的UID' in uid:
await bot.send(ev, UID_HINT)
return
await bot.send(ev, await consume_remind(uid), at_sender=True)
except Exception as e:
await bot.finish(ev, str(e))
@sv.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)
img = await convert_img(img)
bot = get_bot()
for group in sub_list:
try:
await bot.send_group_msg(group_id=group, message=img)
await asyncio.sleep(random.uniform(1, 3))
except Exception as e:
logger.exception(e)
except Exception as e:
logger.exception(str(e))
logger.info('[原神公告] 推送完毕, 更新数据库')
string_config.set_config('Ann_Ids', new_ids)

View 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 '已经不在订阅中了'

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View 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&region=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}'
'&region={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

View 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]

View File

@ -1,10 +1,10 @@
import random
import asyncio
from nonebot.log import logger
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv, logger
from .backup_data import data_backup
from ..all_import import * # noqa: F403, F401
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 (
@ -31,7 +31,7 @@ async def send_backup_msg(
return
await data_backup()
await bot.send(ev, f'操作成功完成!')
await bot.send(ev, '操作成功完成!')
@sv.on_fullmatch('清除无效用户')

View File

@ -1,22 +1,20 @@
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
import re
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv, logger
from ..utils.data_convert.get_uid import get_uid
from ..utils.message.error_reply import UID_HINT
from ..utils.message.error_reply import * # noqa: F403,F401
from ..utils.mhy_api.convert_mysid_to_uid import convert_mysid
from ..utils.draw_image_tools.send_image_tool import convert_img
from .draw_collection_card import draw_explora_img, draw_collection_img
@sv.on_rex(
r'^(\[CQ:at,qq=[0-9]+\])?( )?'
r'(uid|查询|mys)?([0-9]+)?'
r'(收集|宝箱|sj|bx)'
r'(\[CQ:at,qq=[0-9]+\])?( )?$',
)
@sv.on_prefix(('查询收集', '收集', 'sj'))
async def send_collection_info(bot: HoshinoBot, ev: CQEvent):
args = ev['match'].groups()
raw_mes = ev.message.extract_plain_text().replace(' ', '')
logger.info('开始执行[查询收集信息]')
logger.info('[查询收集信息]参数: {}'.format(args))
at = re.search(r'\[CQ:at,qq=(\d*)]', str(ev.message))
at = re.search(r'\[CQ:at,qq=(\d*)]', raw_mes)
if at:
qid = int(at.group(1))
@ -26,22 +24,43 @@ async def send_collection_info(bot: HoshinoBot, ev: CQEvent):
else:
return
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)
im = await draw_collection_img(uid)
im = await draw_collection_img(qid, uid)
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_prefix(('查询探索', '探索', 'ts'))
async def send_explora_info(bot: HoshinoBot, ev: CQEvent):
raw_mes = ev.message.extract_plain_text().replace(' ', '')
logger.info('开始执行[查询探索信息]')
at = re.search(r'\[CQ:at,qq=(\d*)]', raw_mes)
if at:
qid = int(at.group(1))
else:
if ev.sender:
qid = int(ev.sender['user_id'])
else:
return
uid = await get_uid(qid, raw_mes)
logger.info('[查询探索信息]uid: {}'.format(uid))
if '未找到绑定的UID' in uid:
await bot.send(ev, UID_HINT)
im = await draw_explora_img(qid, uid)
if isinstance(im, str):
await bot.send(ev, im)
elif isinstance(im, bytes):

View File

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,8 +1,14 @@
import re
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv, logger
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.draw_image_tools.send_image_tool import convert_img
@sv.on_fullmatch('gs配置')

View 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()

View File

@ -41,7 +41,8 @@ DETAIL_MAP = {
'重启使用Poetry': '将会以Poetry方式重启',
'旧面板': '会稍微增加面板访问速度,但会损失很多功能',
'多彩面板': '面板颜色不按照属性来渲染,而按照自定义颜色',
'跳过无感验证': '尝试跳过米游社签到触发的极验,可能有一定危险性',
'失效项': '可能有一定危险性',
'无效项': '可能有一定危险性',
}

View File

@ -25,7 +25,8 @@ SWITCH_MAP = {
'重启使用Poetry': 'UsePoetry',
'旧面板': 'OldPanle',
'多彩面板': 'ColorBG',
'跳过无感验证': 'CaptchaPass',
'失效项': 'CaptchaPass',
'无效项': 'MhyPass',
}
PUSH_MAP = {

View File

@ -0,0 +1,17 @@
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv
from .export_data import export_v3
@sv.on_prefix(('导出v3数据'))
async def send_export_msg(bot: HoshinoBot, ev: CQEvent):
if ev.sender:
qid = int(ev.sender['user_id'])
else:
return
if qid not in bot.config.SUPERUSERS:
return
await bot.send(ev, '开始导出数据..可能需要一定时间!')
await export_v3()
await bot.send(ev, '导出数据成功!')

View 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 = 'onebot'
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)

View File

@ -1,18 +1,20 @@
import re
import random
import asyncio
from typing import Tuple
from .draw_char_card import *
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv, logger
from .get_enka_img import draw_enka_img
from .draw_char_card import draw_char_img
from ..all_import import * # noqa: F401,F403
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.draw_image_tools.send_image_tool import convert_img
from ..utils.db_operation.db_operation import select_db, get_all_uid
AUTO_REFRESH = False
@ -114,7 +116,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)}个角色!'
@ -137,14 +139,20 @@ async def send_card_info(bot: HoshinoBot, ev: CQEvent):
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
uid = re.findall(r'\d+', message) # str
m = ''.join(re.findall('[\u4e00-\u9fa5]', message))
if ev.sender:
qid = int(ev.sender['user_id'])
else:
return
if len(uid) >= 1:
uid = uid[0]
else:
@ -163,7 +171,7 @@ async def send_card_info(bot: HoshinoBot, ev: CQEvent):
await bot.send(ev, UID_HINT)
return
im = await enka_to_card(uid)
logger.info(f'UID{uid}获取角色数据成功!')
if isinstance(im, str):
await bot.send(ev, im)
elif isinstance(im, bytes):

View File

@ -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

View File

@ -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)

View File

@ -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',

View File

@ -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,

View 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

View File

@ -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)

View File

@ -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": "",

File diff suppressed because one or more lines are too long

View File

@ -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"
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -254,5 +254,13 @@
"珐露珊": [
"E",
"Q"
],
"卡维": [
"Q",
"E"
],
"白术": [
"Q",
"E"
]
}

View File

@ -314,5 +314,51 @@
"暴击率",
"暴击伤害",
"元素充能效率"
],
"流浪者": [
"暴击率",
"暴击伤害",
"攻击力"
],
"珐露珊": [
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"艾尔海森": [
"元素精通",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"瑶瑶": [
"元素充能效率",
"血量",
"攻击力"
],
"迪希雅": [
"血量",
"攻击力",
"暴击率",
"暴击伤害"
],
"米卡": [
"元素充能效率",
"血量"
],
"白术": [
"血量",
"元素充能效率",
"精通",
"暴击率",
"暴击伤害"
],
"卡维": [
"元素充能效率",
"精通",
"暴击率",
"暴击伤害",
"攻击力"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -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),
}

View 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',
}

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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,
],
},
},
}

View File

@ -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]]

View File

@ -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,290 @@ 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

View File

@ -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
# 返回减伤百分比

View File

@ -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
"""

View 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

View File

@ -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 = {'万达国际': ['香菱', '达达利亚', '班尼特', '枫原万叶']}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 899 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,11 +1,13 @@
from pathlib import Path
from ..all_import import *
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv, logger
from ..version import Genshin_version
from ..utils.draw_image_tools.send_image_tool import convert_img
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+)?$')
@ -14,8 +16,9 @@ async def send_primogems_data(bot: HoshinoBot, ev: CQEvent):
logger.info('开始执行[图片][版本规划]')
logger.info('[图片][版本规划]参数: {}'.format(args))
if args[1]:
if str(args[1]) in version:
img = f'{args[1]}.png'
path = PRIMOGEMS_DATA_PATH / f'{str(args[1])}.png'
if path.exists():
img = f'{str(args[1])}.png'
else:
return
else:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 910 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 KiB

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

View File

@ -1,5 +1,8 @@
from ..all_import import * # noqa: F401, F403
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv
from .draw_event_img import get_event_img, get_all_event_img
from ..utils.draw_image_tools.send_image_tool import convert_img
@sv.scheduled_job('cron', hour='2')

View File

@ -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):

View File

@ -1,9 +1,12 @@
from nonebot import get_bot
from hoshino.typing import CQEvent, HoshinoBot, NoticeSession
from ..base import sv, logger
from .get_gachalogs import save_gachalogs
from ..all_import import * # noqa: F403,F401
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.draw_image_tools.send_image_tool import convert_img
from .export_and_import import export_gachalogs, import_gachalogs
@ -71,7 +74,7 @@ async def send_gacha_log_card_info(bot: HoshinoBot, ev: CQEvent):
await bot.send(ev, UID_HINT)
@sv.on_fullmatch('刷新抽卡记录')
@sv.on_fullmatch(('刷新抽卡记录', '强制刷新抽卡记录'))
async def send_daily_info(bot: HoshinoBot, ev: CQEvent):
logger.info('开始执行[刷新抽卡记录]')
if ev.sender:
@ -80,7 +83,11 @@ async def send_daily_info(bot: HoshinoBot, ev: CQEvent):
return
uid = await select_db(qid, mode='uid')
if isinstance(uid, str):
im = await save_gachalogs(uid)
is_force = False
if ev.raw_message and str(ev.raw_message).startswith('强制'):
logger.info('[WARNING]本次为强制刷新')
is_force = True
im = await save_gachalogs(uid, None, is_force)
await bot.send(ev, im)
else:
await bot.send(ev, UID_HINT)

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,49 @@
import re
from hoshino.typing import CQEvent, HoshinoBot
from ..base import sv, logger
from .draw_gcginfo import draw_gcg_info
from ..utils.message.error_reply import UID_HINT
from ..utils.db_operation.db_operation import select_db
from ..utils.draw_image_tools.send_image_tool import convert_img
@sv.on_prefix(('七圣召唤', '七圣', '召唤'))
async def send_gcg_pic(bot: HoshinoBot, ev: CQEvent):
if ev.message:
raw_mes = ev.message.extract_plain_text().replace(' ', '')
else:
return
at = re.search(r'\[CQ:at,qq=(\d*)]', str(ev.message))
logger.info('开始执行[七圣召唤]')
if at:
qid = int(at.group(1))
else:
qid = int(ev.sender['user_id'])
logger.info('[七圣召唤]QQ: {}'.format(qid))
# 获取uid
uid = re.findall(r'\d+', raw_mes)
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 bot.send(ev, UID_HINT)
im = await draw_gcg_info(uid)
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, '发生了未知错误,请联系管理员检查后台输出!')

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

View File

@ -1,27 +1,39 @@
import asyncio
import threading
from typing import List
from pathlib import Path
import httpx
from hoshino.typing import CQEvent, HoshinoBot
from ..all_import import *
from ..base import sv, logger
from .get_card import get_gs_card
from .get_guide import get_gs_guide
from ..version import Genshin_version
from .get_abyss_data import get_review, generate_data
from ..utils.alias.alias_to_char_name import alias_to_char_name
from ..utils.draw_image_tools.send_image_tool import convert_img
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:
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))
if ev.message:
name = name = str(ev['match'].group(1))
else:
logger.warning('未获得{}推荐图片。'.format(name))
return
if name == '':
return
im = await get_gs_guide(name)
if im:
logger.info('获得{}攻略成功!'.format(name))
im = await convert_img(im)
await bot.send(ev, im)
else:
logger.warning('未找到{}攻略图片'.format(name))
@sv.on_prefix('参考面板')
@ -45,3 +57,63 @@ async def send_bluekun_pic(bot: HoshinoBot, ev: CQEvent):
await bot.send(ev, img)
else:
logger.warning('未找到{}参考面板图片'.format(name))
@sv.on_prefix('原牌')
async def send_gscard_pic(bot: HoshinoBot, ev: CQEvent):
if ev.message:
name = ev.message.extract_plain_text().replace(' ', '')
else:
return
if name == '':
return
im = await get_gs_card(name)
if im:
logger.info('获得{}原牌成功!'.format(name))
im = await convert_img(im)
await bot.send(ev, im)
else:
logger.warning('未找到{}原牌图片'.format(name))
@sv.on_prefix('版本深渊')
async def send_abyss_review(bot: HoshinoBot, ev: CQEvent):
if ev.message:
version = ev.message.extract_plain_text().replace(' ', '')
else:
return
if version == '':
version = Genshin_version[:-2]
im = await get_review(version)
if isinstance(im, List):
mes = []
for msg in im:
mes.append(
{
'type': 'node',
'data': {
'name': '小仙',
'uin': '3399214199',
'content': msg,
},
}
)
await bot.send_group_forward_msg(group_id=ev.group_id, messages=mes)
elif 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, '发生了未知错误,请联系管理员检查后台输出!')
threading.Thread(
target=lambda: asyncio.run(generate_data()), daemon=True
).start()

View 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'},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

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