🎉 v4分支启动,需结合core使用

This commit is contained in:
Wuyi无疑 2023-03-01 23:16:36 +08:00
commit 7b648b8ee8
387 changed files with 62951 additions and 0 deletions

682
.gitignore vendored Normal file
View File

@ -0,0 +1,682 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudio,visualstudiocode,jetbrains+all,python
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,visualstudiocode,jetbrains+all,python
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env/
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### VisualStudioCode ###
!.vscode/*
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# Support for Project snippet scope
.vscode/*.code-snippets
# Ignore code-workspaces
*.code-workspace
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
# Local History for Visual Studio Code
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
### VisualStudio Patch ###
# Additional files built by Visual Studio
# End of https://www.toptal.com/developers/gitignore/api/visualstudio,visualstudiocode,jetbrains+all,python,data
### CI ###
result.txt
### GenshinUID ###
GenshinUID/genshinuid_help/help.png
GenshinUID/genshinuid_map/map_data
GenshinUID/genshinuid_xkdata/abyss_total.png
成就汇总.xlsx
ReliquaryExcelConfigData.json
AvatarSkillExcelConfigData.json
WeaponExcelConfigData.json
AvatarTalentExcelConfigData.json
AvatarExcelConfigData.json
曲线素材.xlsx
参考面板.xlsx
### Debug ###
testnb2/

37
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,37 @@
ci:
autofix_commit_msg: "🚨 `pre-commit-ci`修复格式错误"
skip:
- "poetry-lock"
autofix_prs: true
autoupdate_branch: v4
autoupdate_schedule: monthly
autoupdate_commit_msg: "⬆️ `pre-commit-ci`自动升级"
repos:
- repo: https://github.com/pycqa/isort
rev: 5.11.5
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 22.6.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 5.0.4
hooks:
- id: flake8
- repo: https://github.com/hadialqattan/pycln
rev: v2.1.2
hooks:
- id: pycln
- repo: https://github.com/python-poetry/poetry
rev: 1.3.1
hooks:
- id: poetry-check
- id: poetry-lock
- id: poetry-export
args: ["-f", "requirements.txt", "--without-hashes", "-o", "requirements.txt"]
verbose: true

8
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"recommendations": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.isort",
"ms-python.black-formatter"
]
}

26
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"python.languageServer": "Pylance",
"python.analysis.typeCheckingMode": "basic",
"cSpell.words": [
"enka",
"genshin",
"genshinuid"
],
"editor.formatOnSave": true,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"isort.args": [
"--profile",
"black"
],
"python.formatting.provider": "black",
"python.linting.flake8Enabled": true,
"python.linting.flake8CategorySeverity.W": "Warning",
"python.linting.flake8CategorySeverity.F": "Warning",
"python.linting.flake8CategorySeverity.E": "Warning",
}

0
GenshinUID/__full__.py Normal file
View File

View File

@ -0,0 +1,39 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from ..utils.convert import get_uid
from ..utils.error_reply import UID_HINT
from .draw_abyss_card import draw_abyss_img
@SV('查询深渊').on_prefix(('查询深渊', 'sy', '查询上期深渊', 'sqsy'))
async def send_abyss_info(bot: Bot, ev: Event):
await bot.logger.info('开始执行[查询深渊信息]')
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
await bot.logger.info('[查询深渊信息]uid: {}'.format(uid))
if 'sq' in ev.command or '上期' in ev.command:
schedule_type = '2'
else:
schedule_type = '1'
await bot.logger.info('[查询深渊信息]深渊期数: {}'.format(schedule_type))
if ev.text in ['', '', '十一', '十二']:
floor = (
ev.text.replace('', '9')
.replace('十一', '11')
.replace('十二', '12')
.replace('', '10')
)
else:
floor = ev.text
if floor is not None:
floor = int(floor)
await bot.logger.info('[查询深渊信息]深渊层数: {}'.format(floor))
im = await draw_abyss_img(uid, floor, schedule_type)
await bot.send(im)

View File

@ -0,0 +1,315 @@
import time
import asyncio
from pathlib import Path
from typing import Union, Optional
from nonebot.log import logger
from PIL import Image, ImageDraw
from ..utils.convert import GsCookie
from ..utils.image.convert import convert_img
from ..utils.image.image_tools import get_simple_bg
from ..utils.resource.download_url import download_file
from ..gsuid_utils.api.mys.models import AbyssBattleAvatar
from ..utils.fonts.genshin_fonts import genshin_font_origin
from ..utils.resource.RESOURCE_PATH import (
CHAR_PATH,
TEXT2D_PATH,
CHAR_SIDE_PATH,
CHAR_STAND_PATH,
)
TEXT_PATH = Path(__file__).parent / 'texture2D'
TALENT_PATH = TEXT2D_PATH / 'talent'
abyss_title_pic = Image.open(TEXT_PATH / 'abyss_title.png')
char_mask = Image.open(TEXT_PATH / 'char_mask.png')
char_frame = Image.open(TEXT_PATH / 'char_frame.png')
text_title_color = (29, 29, 29)
text_floor_color = (30, 31, 25)
genshin_font_70 = genshin_font_origin(70)
genshin_font_32 = genshin_font_origin(32)
genshin_font_27 = genshin_font_origin(27)
async def get_abyss_star_pic(star: int) -> Image.Image:
star_pic = Image.open(TEXT_PATH / f'star{star}.png')
return star_pic
async def get_rarity_pic(rarity: int) -> Image.Image:
rarity_pic = Image.open(TEXT_PATH / f'rarity{rarity}.png')
return rarity_pic
async def get_talent_pic(talent: int) -> Image.Image:
return Image.open(TALENT_PATH / f'talent_{talent}.png')
async def get_rank_data(data, path):
char_id = data[0]['avatar_id']
# 只下载侧视图
if path == CHAR_SIDE_PATH:
# 确认角色头像路径
char_side_path = CHAR_PATH / f'{char_id}.png'
# 不存在自动下载
if not char_side_path.exists():
await download_file(data[0]['avatar_icon'], 3, f'{char_id}.png')
char_pic = Image.open(path / f'{char_id}.png').convert('RGBA')
if path == CHAR_STAND_PATH:
char_pic = char_pic.resize((862, 528), Image.Resampling.BICUBIC)
elif path == CHAR_SIDE_PATH:
char_pic = char_pic.resize((60, 60), Image.Resampling.BICUBIC)
rank_value = str(data[0]['value'])
return char_pic, rank_value
async def _draw_abyss_card(
char: AbyssBattleAvatar,
talent_num: str,
floor_pic: Image.Image,
index_char: int,
index_part: int,
):
char_card = Image.new('RGBA', (150, 190), (0, 0, 0, 0))
# 根据稀有度获取背景
char_bg = await get_rarity_pic(char['rarity'])
# 确认角色头像路径
char_pic_path = CHAR_PATH / f'{char["id"]}.png'
# 不存在自动下载
if not char_pic_path.exists():
await download_file(char['icon'], 1, f'{char["id"]}.png')
char_pic = (
Image.open(char_pic_path)
.convert('RGBA')
.resize((150, 150), Image.Resampling.LANCZOS) # type: ignore
)
char_img = Image.new('RGBA', (150, 190), (0, 0, 0, 0))
char_img.paste(char_pic, (0, 3), char_pic)
char_bg = Image.alpha_composite(char_bg, char_img)
char_card.paste(char_bg, (0, 0), char_mask)
char_card = Image.alpha_composite(char_card, char_frame)
talent_pic = await get_talent_pic(int(talent_num))
char_card.paste(talent_pic, (83, 156), talent_pic)
char_card_draw = ImageDraw.Draw(char_card)
char_card_draw.text(
(9, 172),
f'Lv.{char["level"]}',
font=genshin_font_27,
fill=text_floor_color,
anchor='lm',
)
floor_pic.paste(
char_card,
(0 + 155 * index_char, 50 + index_part * 195),
char_card,
)
async def _draw_floor_card(
level_star: int,
floor_pic: Image.Image,
bg_img: Image.Image,
time_str: str,
index_floor: int,
):
star_pic = await get_abyss_star_pic(level_star)
floor_pic.paste(star_pic, (420, -5), star_pic)
floor_pic_draw = ImageDraw.Draw(floor_pic)
floor_pic_draw.text(
(31, 25),
time_str,
font=genshin_font_27,
fill=text_floor_color,
anchor='lm',
)
bg_img.paste(floor_pic, (5, 415 + index_floor * 440), floor_pic)
async def draw_abyss_img(
uid: str,
floor: Optional[int] = None,
schedule_type: str = '1',
) -> Union[bytes, str]:
# 获取Cookies
data = GsCookie()
retcode = await data.get_cookie(uid)
if retcode:
return retcode
raw_data = data.raw_data
raw_abyss_data = await data.get_spiral_abyss_data(schedule_type)
# 获取数据
if not raw_abyss_data:
return '没有获取到深渊数据'
if raw_data:
char_data = raw_data['avatars']
else:
return '没有获取到角色数据'
char_temp = {}
# 获取查询者数据
is_unfull = False
if floor:
floor = floor - 9
if floor < 0:
return '楼层不能小于9层!'
if len(raw_abyss_data['floors']) >= floor + 1:
floors_data = raw_abyss_data['floors'][floor]
else:
return '你还没有挑战该层!'
else:
if len(raw_abyss_data['floors']) == 0:
return '你还没有挑战本期深渊!\n可以使用[上期深渊]命令查询上期~'
floors_data = raw_abyss_data['floors'][-1]
levels_num = len(floors_data['levels'])
if floors_data['levels'][0]['battles']:
floors_title = str(floors_data['index']) + ''
else:
floors_title = '统计'
is_unfull = True
# 获取背景图片各项参数
based_w = 625
based_h = 415 if is_unfull else 415 + levels_num * 440
white_overlay = Image.new('RGBA', (based_w, based_h), (255, 255, 255, 188))
bg_img = await get_simple_bg(based_w, based_h)
bg_img.paste(white_overlay, (0, 0), white_overlay)
abyss_title = Image.new('RGBA', (625, 415), (0, 0, 0, 0))
damage_rank = raw_abyss_data['damage_rank']
defeat_rank = raw_abyss_data['defeat_rank']
take_damage_rank = raw_abyss_data['take_damage_rank']
normal_skill_rank = raw_abyss_data['normal_skill_rank']
energy_skill_rank = raw_abyss_data['energy_skill_rank']
dmg_pic, dmg_val = await get_rank_data(damage_rank, CHAR_STAND_PATH)
defeat_pic, defeat_val = await get_rank_data(defeat_rank, CHAR_SIDE_PATH)
(
take_damage_pic,
take_damage_val,
) = await get_rank_data(take_damage_rank, CHAR_SIDE_PATH)
(
normal_skill_pic,
normal_skill_val,
) = await get_rank_data(normal_skill_rank, CHAR_SIDE_PATH)
(
energy_skill_pic,
energy_skill_val,
) = await get_rank_data(energy_skill_rank, CHAR_SIDE_PATH)
abyss_title.paste(dmg_pic, (13, -42), dmg_pic)
abyss_title = Image.alpha_composite(abyss_title, abyss_title_pic)
abyss_title.paste(defeat_pic, (5, 171), defeat_pic)
abyss_title.paste(take_damage_pic, (5, 171 + 54), take_damage_pic)
abyss_title.paste(normal_skill_pic, (5, 171 + 54 * 2), normal_skill_pic)
abyss_title.paste(energy_skill_pic, (5, 171 + 54 * 3), energy_skill_pic)
abyss_title_draw = ImageDraw.Draw(abyss_title)
abyss_title_draw.text(
(41, 95),
f'深渊{floors_title}',
font=genshin_font_70,
fill=text_title_color,
anchor='lm',
)
abyss_title_draw.text(
(41, 139),
f'UID{uid}',
font=genshin_font_27,
fill=text_title_color,
anchor='lm',
)
abyss_title_draw.text(
(610, 282),
dmg_val,
font=genshin_font_32,
fill=text_title_color,
anchor='rm',
)
abyss_title_draw.text(
(610, 357),
str(raw_abyss_data['total_battle_times']),
font=genshin_font_32,
fill=text_title_color,
anchor='rm',
)
abyss_title_draw.text(
(64, 217),
defeat_val,
font=genshin_font_27,
fill=text_title_color,
anchor='lm',
)
abyss_title_draw.text(
(64, 217 + 54),
take_damage_val,
font=genshin_font_27,
fill=text_title_color,
anchor='lm',
)
abyss_title_draw.text(
(64, 217 + 54 * 2),
normal_skill_val,
font=genshin_font_27,
fill=text_title_color,
anchor='lm',
)
abyss_title_draw.text(
(64, 217 + 54 * 3),
energy_skill_val,
font=genshin_font_27,
fill=text_title_color,
anchor='lm',
)
bg_img.paste(abyss_title, (0, 0), abyss_title)
if is_unfull:
pass
else:
task = []
for index_floor, level in enumerate(floors_data['levels']):
floor_pic = Image.new('RGBA', (615, 440), (0, 0, 0, 0))
level_star = level['star']
timestamp = int(level['battles'][0]['timestamp'])
time_array = time.localtime(timestamp)
time_str = time.strftime('%Y-%m-%d %H:%M:%S', time_array)
for index_part, battle in enumerate(level['battles']):
for index_char, char in enumerate(battle['avatars']):
# 获取命座
if char["id"] in char_temp:
talent_num = char_temp[char["id"]]
else:
for i in char_data:
if i["id"] == char["id"]:
talent_num = str(
i["actived_constellation_num"]
)
char_temp[char["id"]] = talent_num
break
task.append(
_draw_abyss_card(
char,
talent_num, # type: ignore
floor_pic,
index_char,
index_part,
)
)
await asyncio.gather(*task)
task.clear()
task.append(
_draw_floor_card(
level_star, floor_pic, bg_img, time_str, index_floor
)
)
await asyncio.gather(*task)
res = await convert_img(bg_img)
logger.info('[查询深渊信息]绘图已完成,等待发送!')
return res

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -0,0 +1,19 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from .get_achi_desc import get_achi, get_daily_achi
@SV('成就委托查询').on_prefix('查委托')
async def send_task_info(bot: Bot, ev: Event):
await bot.logger.info(f'[查委托] 参数:{ev.text}')
im = await get_daily_achi(ev.text)
await bot.send(im)
@SV('成就委托查询').on_prefix('查成就')
async def send_achi_info(bot: Bot, ev: Event):
await bot.logger.info(f'[查成就] 参数:{ev.text}')
im = await get_achi(ev.text)
await bot.send(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

@ -0,0 +1,15 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from .get_adv import char_adv, weapon_adv
@SV('文字推荐').on_suffix(('用什么', '能用啥', '怎么养'))
async def send_char_adv(bot: Bot, ev: Event):
await bot.send(await char_adv(ev.text))
@SV('文字推荐').on_suffix(('能给谁', '谁能用'))
async def send_weapon_adv(bot: Bot, ev: Event):
await bot.send(await weapon_adv(ev.text))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
import json
from pathlib import Path
with open(
Path(__file__).parent / 'char_adv_list.json', "r", encoding='UTF-8'
) as f:
adv_lst = json.load(f)
async def weapon_adv(name):
weapons = {}
artifacts = {}
for char, info in adv_lst.items():
char_weapons = []
char_artifacts = []
for i in info['weapon'].values(): # 3 stars, 4 stars, 5 stars
char_weapons.extend(i)
for i in info['artifact']:
char_artifacts.extend(i)
# char_artifacts = list(set(char_artifacts))
for weapon_name in char_weapons:
if name in weapon_name: # fuzzy search
char_weapon = weapons.get(weapon_name, [])
char_weapon.append(char)
weapons[weapon_name] = char_weapon
for artifact_name in char_artifacts:
if name in artifact_name: # fuzzy search
char_artifact = artifacts.get(artifact_name, [])
char_artifact.append(char)
char_artifact = list(set(char_artifact))
artifacts[artifact_name] = char_artifact
im = []
if weapons:
im.append('✨武器:')
for k, v in weapons.items():
im.append(f'{"".join(v)} 可能会用到【{k}')
if artifacts:
im.append('✨圣遗物:')
for k, v in artifacts.items():
im.append(f'{"".join(v)} 可能会用到【{k}')
if im == []:
im = '没有角色能使用【{}'.format(name)
else:
im = '\n'.join(im)
return im
async def char_adv(name):
for char, info in adv_lst.items():
if name in char:
im = [f'{char}', '-=-=-=-=-=-=-=-=-=-']
if weapon_5 := info['weapon']['5']:
im.append(f'推荐5★武器{"".join(weapon_5)}')
if weapon_4 := info['weapon']['4']:
im.append(f'推荐4★武器{"".join(weapon_4)}')
if weapon_3 := info['weapon']['3']:
im.append(f'推荐3★武器{"".join(weapon_3)}')
if artifacts := info['artifact']:
im.append('推荐圣遗物搭配:')
for arti in artifacts:
if len(arti) > 1:
im.append(f'[{arti[0]}]两件套 + [{arti[1]}]两件套')
else:
im.append(f'[{arti[0]}]四件套')
if remark := info['remark']:
im.append('-=-=-=-=-=-=-=-=-=-')
im.append('备注:')
mark = "\n".join(remark)
im.append(f'{mark}')
return '\n'.join(im)
return '没有找到角色信息'

View File

@ -0,0 +1,109 @@
import random
import asyncio
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.gss import gss
from gsuid_core.models import Event
from gsuid_core.aps import scheduler
from gsuid_core.logger import logger
from .util import black_ids
from ..utils.convert import get_uid
from .main import ann, consume_remind
from ..utils.error_reply import UID_HINT
from ..utils.image.convert import convert_img
from ..genshinuid_config.gs_config import gsconfig
from .ann_card import sub_ann, unsub_ann, ann_list_card, ann_detail_card
@SV('原神公告').on_command(('原神公告'))
async def ann_(bot: Bot, ev: Event):
ann_id = ev.text
if not ann_id:
img = await ann_list_card()
img = await convert_img(img)
return await bot.send(img)
if not ann_id.isdigit():
raise Exception('公告ID不正确')
img = await ann_detail_card(int(ann_id))
img = await convert_img(img)
await bot.send(img)
@SV('原神公告订阅', pm=2).on_fullmatch('订阅原神公告')
async def sub_ann_(bot: Bot, ev: Event):
if ev.group_id is None:
return await bot.send('请在群聊中订阅')
await bot.send(sub_ann(bot.bot_id, ev.group_id))
@SV('原神公告订阅', pm=2).on_fullmatch(('取消订阅原神公告', '取消原神公告', '退订原神公告'))
async def unsub_ann_(bot: Bot, ev: Event):
if ev.group_id is None:
return await bot.send('请在群聊中取消订阅')
await bot.send(unsub_ann(bot.bot_id, ev.group_id))
@SV('原神公告红点').on_fullmatch('取消原神公告红点')
async def consume_remind_(bot: Bot, ev: Event):
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
else:
await bot.send(await consume_remind(uid))
@scheduler.scheduled_job('cron', minute=10)
async def check_ann():
await check_ann_state()
async def check_ann_state():
logger.info('[原神公告] 定时任务: 原神公告查询..')
ids = gsconfig.get_config('Ann_Ids').data
sub_list = gsconfig.get_config('Ann_Groups').data
if not sub_list:
logger.info('没有群订阅, 取消获取数据')
return
if not ids:
ids = await ann().get_ann_ids()
if not ids:
raise Exception('获取原神公告ID列表错误,请检查接口')
gsconfig.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)
for bot_id in sub_list:
try:
if bot_id in gss.active_bot:
bot = gss.active_bot[bot_id]
else:
continue
for group_id in sub_list[bot_id]:
await bot.target_send(img, 'group', group_id)
await asyncio.sleep(random.uniform(1, 3))
except Exception as e:
logger.exception(e)
except Exception as e:
logger.exception(str(e))
logger.info('[原神公告] 推送完毕, 更新数据库')
gsconfig.set_config('Ann_Ids', new_ids)

View File

@ -0,0 +1,220 @@
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 ..utils.image.convert import convert_img
from ..genshinuid_config.gs_config import gsconfig
from ..utils.fonts.genshin_fonts import gs_font_18, gs_font_26
from ..utils.image.image_tools 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(bot_id: str, group: str):
groups = gsconfig.get_config('Ann_Groups').data
if bot_id not in groups:
groups[bot_id] = []
if group in groups[bot_id]:
return '已经订阅了'
else:
groups[bot_id].append(group)
gsconfig.set_config('Ann_Groups', groups)
return '成功订阅原神公告'
def unsub_ann(bot_id: str, group: str):
groups = gsconfig.get_config('Ann_Groups').data
if bot_id not in groups:
groups[bot_id] = []
if group in groups[bot_id]:
groups[bot_id].remove(group)
gsconfig.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

@ -0,0 +1,119 @@
import random
import asyncio
from typing import List
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.aps import scheduler
from ..utils.mys_api import mys_api
from .backup_data import data_backup
from ..utils.database import active_sqla
from ..gsuid_utils.database.models import GsUser
@scheduler.scheduled_job('cron', hour=0)
async def daily_refresh_charData():
await data_backup()
@SV('数据管理', pm=2).on_fullmatch(('gs清除缓存'))
async def send_backup_msg(bot: Bot):
await data_backup()
await bot.send('操作成功完成!')
@SV('数据管理', pm=2).on_fullmatch(('校验全部Cookies'))
async def send_check_cookie(bot: Bot, ev: Event):
user_list = await active_sqla[bot.bot_id].get_all_user()
invalid_user: List[GsUser] = []
for user in user_list:
if user.cookie and user.mys_id:
mys_data = await mys_api.get_mihoyo_bbs_info(
user.mys_id,
user.cookie,
True if int(user.uid[0]) > 5 else False,
)
if isinstance(mys_data, int):
await active_sqla[bot.bot_id].delete_user_data(user.uid)
invalid_user.append(user)
continue
for i in mys_data:
if i['game_id'] != 2:
mys_data.remove(i)
if len(user_list) > 4:
im = f'正常Cookies数量: {len(user_list) - len(invalid_user)}'
invalid = '\n'.join(
[
f'uid{user.uid}的Cookies是异常的!已删除该条Cookies!\n'
for user in invalid_user
]
)
return_str = f'{im}\n{invalid if invalid else "无失效Cookie!"}'
else:
return_str = '\n'.join(
[
f'uid{user.uid}/mys{user.mys_id}的Cookies是正常的!'
if user not in invalid_user
else f'uid{user.uid}的Cookies是异常的!已删除该条Cookies!'
for user in user_list
]
)
await bot.send(return_str)
for i in invalid_user:
await bot.target_send(
f'您绑定的Cookiesuid{i.uid})已失效,以下功能将会受到影响:\n'
'查看完整信息列表\n查看深渊配队\n自动签到/当前状态/每月统计\n'
'请及时重新绑定Cookies并重新开关相应功能。',
'direct',
target_id=i.user_id,
)
await asyncio.sleep(3 + random.randint(1, 3))
@SV('数据管理', pm=2).on_fullmatch(('校验全部Stoken'))
async def send_check_stoken(bot: Bot, ev: Event):
user_list = await active_sqla[bot.bot_id].get_all_user()
invalid_user: List[GsUser] = []
for user in user_list:
if user.stoken and user.mys_id:
mys_data = await mys_api.get_cookie_token_by_stoken(
user.stoken,
user.mys_id,
)
if isinstance(mys_data, int):
await active_sqla[bot.bot_id].update_user_stoken(
user.uid, None
)
invalid_user.append(user)
continue
if len(user_list) > 4:
im = f'正常Stoken数量: {len(user_list) - len(invalid_user)}'
invalid = '\n'.join(
[f'uid{user.uid}的Stoken是异常的!已清除Stoken!\n' for user in invalid_user]
)
return_str = f'{im}\n{invalid if invalid else "无失效Stoken!"}'
else:
return_str = '\n'.join(
[
f'uid{user.uid}/mys{user.mys_id}的Stoken是正常的!'
if user not in invalid_user
else f'uid{user.uid}的Stoken是异常的!已清除Stoken!'
for user in user_list
]
)
await bot.send(return_str)
for i in invalid_user:
await bot.target_send(
f'您绑定的Stokenuid{i.uid})已失效,以下功能将会受到影响:\n'
'gs开启自动米游币开始获取米游币。\n'
'重新添加后需要重新开启自动米游币。',
'direct',
target_id=i.user_id,
)
await asyncio.sleep(3 + random.randint(1, 3))

View File

@ -0,0 +1,31 @@
import os
import datetime
from shutil import copyfile
from nonebot.log import logger
from ..utils.database import active_sqla
from ..utils.resource.RESOURCE_PATH import TEMP_PATH
async def data_backup():
try:
today = datetime.date.today()
endday = today - datetime.timedelta(days=5)
date_format = today.strftime("%Y_%d_%b")
endday_format = endday.strftime("%Y_%d_%b")
copyfile('ID_DATA.db', f'ID_DATA_BAK_{date_format}.db')
if os.path.exists(f'ID_DATA_BAK_{endday_format}.db'):
os.remove(f'ID_DATA_BAK_{endday_format}.db')
logger.info(f'————已删除数据库备份{endday_format}————')
logger.info('————数据库成功备份————')
for f in TEMP_PATH.glob('*.jpg'):
try:
f.unlink()
except OSError as e:
print("Error: %s : %s" % (f, e.strerror))
for bot_id in active_sqla:
await active_sqla[bot_id].delete_cache()
logger.info('————缓存成功清除————')
except Exception:
logger.info('————数据库备份失败————')

View File

@ -0,0 +1,37 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from ..utils.convert import get_uid
from ..utils.error_reply import UID_HINT
from .draw_collection_card import draw_explora_img, draw_collection_img
@SV('查询数据').on_command(('查询收集', 'sj'))
async def send_collection_info(bot: Bot, ev: Event):
await bot.logger.info('开始执行[查询收集信息]')
user_id = ev.at if ev.at else ev.user_id
# 获取uid
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
await bot.logger.info('[查询角色面板]uid: {}'.format(uid))
im = await draw_collection_img(user_id, uid)
await bot.send(im)
@SV('查询数据').on_command(('查询探索', 'ts'))
async def send_explora_info(bot: Bot, ev: Event):
await bot.logger.info('开始执行[查询探索信息]')
user_id = ev.at if ev.at else ev.user_id
# 获取uid
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
await bot.logger.info('[查询角色面板]uid: {}'.format(uid))
im = await draw_explora_img(user_id, uid)
await bot.send(im)

View File

@ -0,0 +1,210 @@
from pathlib import Path
from typing import Dict, Tuple, Union, Literal
from PIL import Image, ImageDraw
from ..utils.mys_api import mys_api
from ..utils.image.convert import convert_img
from ..utils.map.GS_MAP_PATH import avatarId2Name
from ..gsuid_utils.api.mys.models import IndexData
from ..utils.fonts.genshin_fonts import gs_font_30, gs_font_40
from ..utils.image.image_tools import (
draw_bar,
get_color_bg,
get_qq_avatar,
draw_pic_with_ring,
)
TEXT_PATH = Path(__file__).parent / 'texture2D'
first_color = (29, 29, 29)
brown_color = (41, 25, 0)
red_color = (255, 66, 66)
green_color = (74, 189, 119)
max_data = {
'成就': 892,
'华丽的宝箱': 185,
'珍贵的宝箱': 487,
'精致的宝箱': 1589,
'普通的宝箱': 2527,
'奇馈宝箱': 146,
'解锁传送点': 286,
'解锁秘境': 48,
}
award_data = {
'成就': 5,
'华丽的宝箱': 10,
'珍贵的宝箱': 8,
'精致的宝箱': 3,
'普通的宝箱': 1,
'奇馈宝箱': 2,
'解锁传送点': 0,
'解锁秘境': 0,
}
expmax_data = {
'获得角色数': len(avatarId2Name) - 2,
'风神瞳': 66,
'岩神瞳': 131,
'雷神瞳': 181,
'草神瞳': 271,
}
async def draw_collection_img(
qid: Union[str, int], uid: str
) -> Union[str, bytes]:
return await draw_base_img(qid, uid, '收集')
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, IndexData]:
# 获取Cookies
raw_data = await mys_api.get_info(uid)
if isinstance(raw_data, int):
return '数据异常!'
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
# 处理数据
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']
percent_data = {}
value_data = {}
day: str = str(raw_data['stats']['active_day_number'])
me_percent = 0
world_percent = 0
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)}'
percent_data[name] = percent
value_data[name] = value
me_percent = _f(me_percent * 100 / (len(expmax_data) - 1))
world_percent = _f(world_percent * 100 / (len(data) - len(expmax_data)))
return percent_data, value_data, day, me_percent, world_percent
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']
# 处理数据
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.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,76 @@
import re
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from ..utils.database import active_sqla
from ..utils.error_reply import UID_HINT
from .draw_config_card import draw_config_img
from .set_config import set_push_value, set_config_func
@SV('原神配置').on_fullmatch(('gs配置', '原神配置'))
async def send_config_card(bot: Bot, ev: Event):
await bot.logger.info('开始执行[gs配置]')
im = await draw_config_img(ev.bot_id)
await bot.send(im)
@SV('原神配置').on_prefix(('gs设置'))
async def send_config_ev(bot: Bot, ev: Event):
await bot.logger.info('开始执行[设置阈值信息]')
sqla = active_sqla[ev.bot_id]
uid = await sqla.get_bind_uid(ev.user_id)
if uid is None:
return await bot.send(UID_HINT)
func = ''.join(re.findall('[\u4e00-\u9fa5]', ev.text.replace('阈值', '')))
value = re.findall(r'\d+', ev.text)
value = value[0] if value else None
if value is None:
return await bot.send('请输入正确的阈值数字...')
await bot.logger.info('[设置阈值信息]func: {}, value: {}'.format(func, value))
im = await set_push_value(ev.bot_id, func, uid, int(value))
await bot.send(im)
# 开启 自动签到 和 推送树脂提醒 功能
@SV('原神配置').on_prefix(('gs开启', 'gs关闭'))
async def open_switch_func(bot: Bot, ev: Event):
sqla = active_sqla[ev.bot_id]
user_id = ev.user_id
config_name = ev.text
await bot.logger.info(f'[{user_id}]尝试[{ev.command[2:]}]了[{ev.text}]功能')
if ev.command == 'gs开启':
query = True
gid = ev.group_id if ev.group_id else 'on'
else:
query = False
gid = 'off'
is_admin = ev.user_pm <= 2
if ev.at and is_admin:
user_id = ev.at
elif ev.at:
return await bot.send('你没有权限...')
uid = await sqla.get_bind_uid(ev.user_id)
if uid is None:
return await bot.send(UID_HINT)
im = await set_config_func(
ev.bot_id,
config_name=config_name,
uid=uid,
user_id=user_id,
option=gid,
query=query,
is_admin=is_admin,
)
await bot.send(im)

View File

@ -0,0 +1,85 @@
from typing import Dict, Union
from .models import GsStrConfig, GsBoolConfig, GsDictConfig, GsListConfig
CONIFG_DEFAULT: Dict[
str, Union[GsDictConfig, GsBoolConfig, GsListConfig, GsStrConfig]
] = {
'proxy': GsStrConfig('设置代理', '设置国际服的代理地址', ''),
'_pass_API': GsStrConfig('神奇API', '设置某种神奇的API', ''),
'random_pic_API': GsStrConfig(
'随机图API',
'用于面板查询的随机图API',
'https://genshin-res.cherishmoon.fun/img?name=',
),
'Ann_Groups': GsDictConfig(
'推送公告群组',
'原神公告推送群组',
{},
),
'Ann_Ids': GsListConfig(
'推送公告ID',
'原神公告推送ID列表',
[],
),
'MhyBBSCoinReport': GsBoolConfig(
'米游币推送',
'开启后会私聊每个用户当前米游币任务完成情况',
False,
),
'SignReportSimple': GsBoolConfig(
'简洁签到报告',
'开启后可以大大减少每日签到报告字数',
True,
),
'PrivateReport': GsBoolConfig(
'私聊报告',
'关闭后将不再给主人推送当天米游币任务完成情况',
False,
),
'RandomPic': GsBoolConfig(
'随机图',
'开启后[查询心海]等命令展示图将替换为随机图片',
False,
),
'SchedSignin': GsBoolConfig(
'定时签到',
'开启后每晚00:30将开始自动签到任务',
True,
),
'SchedMhyBBSCoin': GsBoolConfig(
'定时米游币',
'开启后每晚01:16将开始自动米游币任务',
True,
),
'CrazyNotice': GsBoolConfig(
'催命模式',
'开启后当达到推送阈值将会一直推送',
False,
),
'OldPanle': GsBoolConfig(
'旧面板',
'会稍微增加面板访问速度,但会损失很多功能',
False,
),
'OpenWeb': GsBoolConfig(
'网页控制台',
'开启后重启生效,地址位于Bot所处端口下/genshinuid',
False,
),
'ColorBG': GsBoolConfig(
'多彩面板',
'面板颜色不按照属性来渲染,而按照自定义颜色',
False,
),
'CaptchaPass': GsBoolConfig(
'失效项',
'该选项已经无效且可能有一定危险性...',
False,
),
'MysPass': GsBoolConfig(
'无效项',
'该选项已经无效且可能有一定危险性...',
False,
),
}

View File

@ -0,0 +1,78 @@
import asyncio
from pathlib import Path
from typing import Union
from nonebot.log import logger
from PIL import Image, ImageDraw
from ..utils.database import active_sqla
from .config_default import CONIFG_DEFAULT
from ..utils.image.convert import convert_img
from ..utils.image.image_tools import CustomizeImage
from ..utils.resource.RESOURCE_PATH import TEXT2D_PATH
from ..utils.fonts.genshin_fonts import gs_font_24, gs_font_36, gs_font_40
TEXT_PATH = Path(__file__).parent / 'texture2d'
config_title = Image.open(TEXT_PATH / 'config_title.png')
config_on = Image.open(TEXT_PATH / 'config_on.png')
config_off = Image.open(TEXT_PATH / 'config_off.png')
first_color = (20, 20, 20)
second_color = (57, 57, 57)
async def draw_config_img(bot_id: str) -> Union[bytes, str]:
sqla = active_sqla[bot_id]
# 获取背景图片各项参数
based_w = 850
based_h = 850 + 155 * (len(CONIFG_DEFAULT) - 5)
CI_img = CustomizeImage('', based_w, based_h)
img = CI_img.bg_img
color = CI_img.bg_color
color_mask = Image.new('RGBA', (based_w, based_h), color)
config_mask = Image.open(TEXT2D_PATH / 'mask.png').resize(
(based_w, based_h)
)
img.paste(color_mask, (0, 0), config_mask)
img.paste(config_title, (0, 0), config_title)
img_draw = ImageDraw.Draw(img)
# 获取数据
uid_list = await sqla.get_all_uid_list()
cookie_list = await sqla.get_all_cookie()
stoken_list = await sqla.get_all_stoken()
uid_num = len(uid_list)
cookie_num = len(cookie_list)
stoken_num = len(stoken_list)
img_draw.text((210, 600), str(uid_num), first_color, gs_font_40, 'mm')
img_draw.text((431, 600), str(cookie_num), first_color, gs_font_40, 'mm')
img_draw.text((651, 600), str(stoken_num), first_color, gs_font_40, 'mm')
tasks = []
index = 0
for name in CONIFG_DEFAULT:
if isinstance(CONIFG_DEFAULT[name].data, bool):
index += 1
tasks.append(_draw_config_line(img, name, index))
await asyncio.gather(*tasks)
res = await convert_img(img)
logger.info('[查询配置信息]绘图已完成,等待发送!')
return res
async def _draw_config_line(img: Image.Image, name: str, index: int):
detail = CONIFG_DEFAULT[name].desc
config_line = Image.open(TEXT_PATH / 'config_line.png')
config_line_draw = ImageDraw.Draw(config_line)
if name.startswith('定时'):
name += '(全部)'
config_line_draw.text((52, 46), name, first_color, gs_font_36, 'lm')
config_line_draw.text((52, 80), detail, second_color, gs_font_24, 'lm')
if CONIFG_DEFAULT[name].data:
config_line.paste(config_on, (613, 21), config_on)
else:
config_line.paste(config_off, (613, 21), config_off)
img.paste(config_line, (26, 850 + index * 155), config_line)

View File

@ -0,0 +1,106 @@
from typing import Dict, List, Union, Literal, overload
from msgspec import json as msgjson
from .config_default import CONIFG_DEFAULT
from ..utils.resource.RESOURCE_PATH import CONFIG_PATH
from .models import GSC, GsStrConfig, GsBoolConfig, GsDictConfig, GsListConfig
STR_CONFIG = Literal['proxy', '_pass_API', 'random_pic_API']
LIST_CONFIG = Literal['Ann_Ids']
DICT_CONFIG = Literal['Ann_Groups']
class StringConfig:
def __init__(self) -> None:
if not CONFIG_PATH.exists():
with open(CONFIG_PATH, 'wb') as file:
file.write(msgjson.encode(CONIFG_DEFAULT))
self.config: Dict[str, GSC] = {}
self.update_config()
def write_config(self):
with open(CONFIG_PATH, 'wb') as file:
file.write(msgjson.format(msgjson.encode(self.config), indent=4))
def update_config(self):
# 打开config.json
with open(CONFIG_PATH, 'r', encoding='UTF-8') as f:
self.config: Dict[str, GSC] = msgjson.decode(
f.read(),
type=Dict[str, GSC],
)
# 对没有的值,添加默认值
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) -> GsStrConfig:
...
@overload
def get_config(self, key: LIST_CONFIG) -> GsListConfig:
...
@overload
def get_config(self, key: DICT_CONFIG) -> GsDictConfig:
...
@overload
def get_config(self, key: str) -> GsBoolConfig:
...
def get_config(self, key: str) -> GSC:
if key in self.config:
return self.config[key]
elif key in CONIFG_DEFAULT:
self.update_config()
return self.config[key]
else:
return GsBoolConfig('缺省值', '获取错误的配置项', False)
@overload
def set_config(self, key: STR_CONFIG, value: str) -> bool:
...
@overload
def set_config(self, key: LIST_CONFIG, value: List) -> bool:
...
@overload
def set_config(self, key: DICT_CONFIG, value: Dict) -> bool:
...
@overload
def set_config(self, key: str, value: bool) -> bool:
...
def set_config(
self, key: str, value: Union[str, List, bool, Dict]
) -> bool:
if key in CONIFG_DEFAULT:
temp = self.config[key]
temp.data = value # type:ignore
# 设置值
self.config[key] = temp
# 重新写回
self.write_config()
return True
else:
return False
gsconfig = StringConfig()

View File

@ -0,0 +1,27 @@
from typing import Dict, List, Union
import msgspec
class GsConfig(msgspec.Struct, tag=True):
title: str
desc: str
class GsStrConfig(GsConfig, tag=True):
data: str
class GsBoolConfig(GsConfig, tag=True):
data: bool
class GsDictConfig(GsConfig, tag=True):
data: Dict[str, List]
class GsListConfig(GsConfig, tag=True):
data: List[str]
GSC = Union[GsDictConfig, GsBoolConfig, GsListConfig, GsStrConfig]

View File

@ -0,0 +1,92 @@
from typing import Optional
from nonebot.log import logger
from .gs_config import gsconfig
from ..utils.database import active_sqla
from .config_default import CONIFG_DEFAULT
PUSH_MAP = {
'宝钱': 'coin',
'体力': 'resin',
'派遣': 'go',
'质变仪': 'transform',
}
PRIV_MAP = {
'自动签到': 'sign',
'自动米游币': 'bbs',
'推送': 'push',
}
async def set_push_value(bot_id: str, func: str, uid: str, value: int):
sqla = active_sqla[bot_id]
if func in PUSH_MAP:
status = PUSH_MAP[func]
else:
return '该配置项不存在!'
logger.info('[设置推送阈值]func: {}, value: {}'.format(status, value))
if await sqla.update_push_data(uid, {f'{status}_value': value}):
return f'设置成功!\n当前{func}推送阈值:{value}'
else:
return '设置失败!\n请检查参数是否正确!'
async def set_config_func(
bot_id: str,
config_name: str = '',
uid: str = '0',
user_id: Optional[str] = None,
option: str = '0',
query: Optional[bool] = None,
is_admin: bool = False,
):
sqla = active_sqla[bot_id]
# 这里将传入的中文config_name转换为英文status
for _name in CONIFG_DEFAULT:
config = CONIFG_DEFAULT[_name]
if config.title == config_name and isinstance(config.data, bool):
name = _name
break
else:
logger.info(
f'uid: {uid}, option: {option}, config_name: {config_name}'
)
if config_name in PRIV_MAP:
# 执行设置
await sqla.update_user_data(
uid,
{
'user_id': user_id,
f'{PRIV_MAP[config_name]}_switch': option,
},
)
elif config_name.replace('推送', '') in PUSH_MAP:
await sqla.update_push_data(
uid,
{
f'{PUSH_MAP[config_name.replace("推送", "")]}_push': option,
},
)
else:
return '该配置项不存在!'
if option == 'on':
succeed_msg = '开启至私聊消息!'
elif option == 'off':
succeed_msg = '关闭!'
else:
succeed_msg = f'开启至群{option}'
return f'{config_name}{succeed_msg}'
if is_admin:
logger.info(f'config_name:{config_name},query:{query}')
# 执行设置
if query is not None:
gsconfig.set_config(name, query)
im = '成功设置{}{}'.format(config_name, '' if query else '')
else:
im = '未传入参数query!'
else:
im = '只有管理员才能设置群服务。'
return im

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View File

@ -0,0 +1,60 @@
import re
from typing import Tuple
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from .to_data import switch_api
from .to_card import enka_to_card
from ..utils.convert import get_uid
from .get_enka_img import draw_enka_img
from ..utils.error_reply import UID_HINT
from .draw_char_rank import draw_cahrcard_list
@SV('面板设置', pm=2).on_fullmatch('切换api')
async def send_change_api_info(bot: Bot):
await bot.send(await switch_api())
@SV('面板查询', priority=10).on_prefix('查询')
async def send_char_info(bot: Bot, ev: Event):
# 获取角色名
msg = ''.join(re.findall('[\u4e00-\u9fa5]', ev.text))
await bot.logger.info('开始执行[查询角色面板]')
# 获取uid
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
await bot.logger.info('[查询角色面板]uid: {}'.format(uid))
im = await draw_enka_img(msg, uid, ev.image)
if isinstance(im, str):
await bot.send(im)
elif isinstance(im, Tuple):
await bot.send(im[0])
else:
await bot.send('发生未知错误')
@SV('面板查询', priority=10).on_command('强制刷新')
async def send_card_info(bot: Bot, ev: Event):
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
await bot.logger.info('[强制刷新]uid: {}'.format(uid))
im = await enka_to_card(uid)
await bot.logger.info(f'UID{uid}获取角色数据成功!')
await bot.send(im)
@SV('面板查询', priority=10).on_command('毕业度统计')
async def send_charcard_list(bot: Bot, ev: Event):
uid = await get_uid(bot, ev)
user_id = ev.at if ev.at else ev.user_id
if uid is None:
return await bot.send(UID_HINT)
im = await draw_cahrcard_list(str(uid), user_id)
await bot.logger.info(f'UID{uid}获取角色数据成功!')
await bot.send(im)

View File

@ -0,0 +1,58 @@
{
"神里绫华": {"Y": "冰伤收益", "B": "攻击收益", "J": "暴击收益"},
"琴": {"W": "治疗加成", "X": "攻击对治疗收益"},
"丽莎": {"B": "攻击/雷伤收益", "D": "暴击收益"},
"芭芭拉": {"Q": "治疗加成", "R": "生命收益"},
"凯亚": {"B": "攻击/冰伤收益", "D": "暴击收益"},
"迪卢克": {"O": "精通收益", "B": "攻击/火伤收益", "K": "暴击收益"},
"雷泽": {"AE": "物伤收益", "B": "攻击收益", "D": "暴击收益"},
"安柏": {"P": "精通收益", "B": "火伤收益", "D": "暴击收益", "L": "攻击收益"},
"温迪": {},
"香菱": {"N": "精通收益", "B": "攻击/火伤收益", "D": "暴击收益"},
"北斗": {"L": "雷伤收益", "B": "攻击收益", "D": "暴击收益"},
"行秋": {"E": "精通收益", "B": "水伤收益", "L": "攻击收益", "D": "暴击收益"},
"魈": {"AK": "风伤收益", "B": "攻击收益", "K": "暴击收益"},
"凝光": {"L": "岩伤收益", "B": "攻击收益", "D": "暴击收益"},
"可莉": {"E": "精通收益", "B": "攻击收益", "M": "火伤收益", "D": "暴击收益"},
"钟离": {"M": "岩伤收益", "AB": "生命收益", "D": "暴击收益"},
"菲谢尔": {"C": "物伤收益", "L": "攻击收益", "B": "雷伤收益", "D": "暴击收益"},
"班尼特": {"E": "精通收益", "B": "攻击收益", "D": "暴击收益"},
"达达利亚": {"E": "精通收益", "M": "水伤收益", "B": "攻击收益", "D": "暴击收益"},
"诺艾尔": {"B": "岩伤收益", "AC": "防御收益", "D": "暴击收益"},
"七七": {"W": "治疗加成", "Z": "攻击对治疗收益"},
"重云": {"E": "精通收益", "B": "冰伤收益", "L": "攻击收益", "D": "暴击收益"},
"甘雨": {"E": "精通收益", "B": "攻击/冰伤收益", "J": "暴击收益"},
"阿贝多": {"M": "岩伤收益", "C": "防御收益", "D": "暴击收益"},
"迪奥娜": {"Q": "治疗加成", "AA": "生命收益"},
"莫娜": {"E": "精通收益", "S": "水伤收益", "B": "攻击收益", "D": "暴击收益"},
"刻晴": {"C": "物伤收益", "B": "攻击/雷伤收益", "J": "暴击收益"},
"砂糖": {},
"辛焱": {"C": "物伤收益", "L": "攻击收益", "D": "暴击收益", "B": "火伤收益"},
"罗莎莉亚": {"L": "攻击收益", "D": "暴击收益", "B": "冰伤收益"},
"胡桃": {"I": "精通收益", "B": "火伤收益", "H": "生命收益", "J": "暴击收益"},
"枫原万叶": {},
"烟绯": {"E": "精通收益", "B": "攻击收益", "L": "火伤收益", "D": "暴击收益"},
"宵宫": {"O": "精通收益", "B": "攻击/火伤收益", "K": "暴击收益"},
"托马": {},
"优菈": {"C": "物伤收益", "B": "攻击收益", "J": "暴击收益"},
"雷电将军": {"AG": "攻击收益", "AF": "雷伤收益", "D": "暴击收益"},
"早柚": {"Q": "治疗加成", "AL": "攻击对治疗收益", "AM": "精通对治疗收益"},
"珊瑚宫心海": {"M": "水伤收益", "T": "生命收益"},
"五郎": {"L": "岩伤收益", "C": "防御收益", "D": "暴击收益"},
"九条裟罗": {"L": "攻击收益", "B": "雷伤收益", "D": "暴击收益"},
"荒泷一斗": {"B": "岩伤收益", "AD": "防御收益", "K": "暴击收益"},
"八重神子": {"B": "攻击/雷伤收益", "K": "暴击收益"},
"鹿野院平藏": {"L": "风伤收益", "B": "攻击收益", "D": "暴击收益"},
"夜兰": {"B": "水伤收益", "U": "生命收益", "K": "暴击收益"},
"埃洛伊": {"B": "攻击收益", "M": "冰伤收益", "D": "暴击收益"},
"申鹤": {"B": "冰伤收益", "M": "攻击收益", "D": "暴击收益"},
"云堇": {"B": "岩伤收益", "C": "防御收益", "D": "暴击收益"},
"久岐忍": {"Q": "治疗加成", "AH": "生命对治疗收益", "AI": "精通对治疗收益"},
"神里绫人": {"B": "水伤收益", "V": "4浪闪攻击收益", "J": "暴击收益"},
"柯莱": {},
"多莉": {"Q": "治疗加成", "AJ": "生命对治疗收益"},
"提纳里": {},
"妮露": {},
"赛诺": {"B": "雷伤收益", "L": "攻击收益", "J": "暴击收益"},
"坎蒂丝": {"B": "水伤收益", "L": "生命收益", "D": "暴击收益"}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,219 @@
import json
from pathlib import Path
from typing import List, Tuple
from PIL import Image, ImageDraw
from ...utils.fonts.genshin_fonts import gs_font_22
DATA_PATH = Path(__file__).parent
TEXT_PATH = DATA_PATH / 'texture2D'
WEIGHT_MAP = {
'暴击率': 1,
'治疗加成': 1.15,
'百分比攻击力': 1.5,
'百分比血量': 1.5,
'元素伤害加成': 1.5,
'元素充能效率': 1.67,
'百分比防御力': 1.875,
'物理伤害加成': 1.875,
'暴击伤害': 2,
'元素精通': 6,
}
COLOR_MAP = {
'攻击': '#f19b60',
'精通': '#4dbe6b',
'暴击': '#5dbbee',
'物伤': '#ffffff',
'': '#a1252a',
'生命': '#67c750',
'防御': '#9999de',
'治疗': '#f9deb3',
'充能': '#ff5858',
}
# 引入曲线Map
with open(DATA_PATH / 'char_curve.json', 'r', encoding='UTF-8') as f:
CHAR_CURVE = json.load(f)
# 引入曲线Map
with open(DATA_PATH / 'curve.json', 'r', encoding='UTF-8') as f:
CURVE = json.load(f)
async def get_weight_temp(prop: dict, attr: str) -> List[float]:
weight = []
if '攻击' in attr:
weight.append(
(prop['atk_green'] / prop['baseAtk']) * 100 / WEIGHT_MAP['百分比攻击力']
)
elif '生命' in attr:
weight.append(
(prop['hp_green'] / prop['baseHp']) * 100 / WEIGHT_MAP['百分比血量']
)
elif '防御' in attr:
weight.append(
(prop['def_green'] / prop['baseDef']) * 100 / WEIGHT_MAP['百分比防御力']
)
elif '精通' in attr:
weight.append(prop['elementalMastery'] / WEIGHT_MAP['元素精通'])
elif '充能' in attr:
weight.append(prop['energyRecharge'] * 100 / WEIGHT_MAP['元素充能效率'])
elif '物伤' in attr:
weight.append(prop['physicalDmgBonus'] * 100 / WEIGHT_MAP['物理伤害加成'])
elif '' in attr:
weight.append(prop['dmgBonus'] * 100 / WEIGHT_MAP['元素伤害加成'])
elif '治疗' in attr:
weight.append(prop['healBonus'] * 100 / WEIGHT_MAP['治疗加成'])
elif '暴击' in attr:
weight.append(prop['critRate'] * 100 / WEIGHT_MAP['暴击率'])
weight.append(prop['critDmg'] * 100 / WEIGHT_MAP['暴击伤害'])
return weight
async def get_weight(prop: dict, attr: str) -> List[float]:
weight = []
if '/' in attr:
attr_list = attr.split('/')
else:
attr_list = [attr]
for i in attr_list:
weight.extend(await get_weight_temp(prop, i))
return weight
BLUE = "#0000ff"
lu_point = (23, 45)
rd_point = (927, 495)
X_D = rd_point[0] - lu_point[0]
Y_D = rd_point[1] - lu_point[1]
frame_img = Image.open(TEXT_PATH / 'frame.png')
point_img = Image.open(TEXT_PATH / 'point.png')
async def draw_char_curve_data(
char_name: str, raw_data: dict
) -> Tuple[Image.Image, int]:
# 如果曲线列表里不存在该角色,则返回空白图片
if char_name not in CHAR_CURVE or CHAR_CURVE[char_name] == {}:
return Image.new('RGBA', (950, 1)), 0
# 获得面板属性
if 'avatarFightProp' in raw_data:
fight_prop = raw_data['avatarFightProp']
else:
fight_prop = raw_data
fight_prop['atk_green'] = fight_prop['atk'] - fight_prop['baseAtk']
fight_prop['def_green'] = fight_prop['def'] - fight_prop['baseDef']
fight_prop['hp_green'] = fight_prop['hp'] - fight_prop['baseHp']
img = Image.open(TEXT_PATH / 'curve_bg.png')
img_draw = ImageDraw.Draw(img)
# 初始化X_MAX和Y_MAX值
X_MAX = 0
Y_MAX = 0
wight_point_dict: dict = {}
line_points_dict: dict = {}
wight_temp_dict: dict = {}
# 遍历曲线列表,根据函数获得权重
for col in CHAR_CURVE[char_name]:
wight_temp = await get_weight(fight_prop, CHAR_CURVE[char_name][col])
wight_temp_dict[CHAR_CURVE[char_name][col]] = wight_temp
# 对单个属性的权重列表进行遍历
for i in wight_temp:
# 确定X_MAX值
if i >= X_MAX:
X_MAX = i
# 确定Y_MAX值
for j in CURVE[col]:
if j >= Y_MAX:
Y_MAX = j
# 增加Y_MAX和X_MAX的值
X_MAX = X_MAX + 15
Y_MAX = Y_MAX + 0.002
# 遍历曲线列表,COL为列名,这一步拿到所有曲线的点,和所有权重的点
for col_index, col in enumerate(CHAR_CURVE[char_name]):
line_points = []
# 确定颜色
for m in COLOR_MAP:
if m in CHAR_CURVE[char_name][col]:
color = COLOR_MAP[m]
break
else:
color = '#ffffff'
for index, i in enumerate(CURVE[col]):
if index >= X_MAX:
break
x, y = (X_D / X_MAX) * index + lu_point[0], (
Y_D - (Y_D / Y_MAX) * i
) + lu_point[1]
line_points.append((x, y))
line_points_dict[color] = line_points
for wight in wight_temp_dict[CHAR_CURVE[char_name][col]]:
w_x = (wight / X_MAX) * X_D + lu_point[0]
w_y = line_points[int(wight)][1]
if CHAR_CURVE[char_name][col] not in wight_point_dict:
wight_point_dict[CHAR_CURVE[char_name][col]] = {
'color': color,
'point': [(w_x, w_y)],
}
else:
wight_point_dict[CHAR_CURVE[char_name][col]]['point'].append(
(w_x, w_y)
)
# 绘制右上角方块和文字
img_draw.rectangle(
((710, 65 + col_index * 30), (750, 85 + col_index * 30)),
fill=color,
)
img_draw.text(
(762, 75 + col_index * 30),
f'{CHAR_CURVE[char_name][col]}',
color,
gs_font_22,
'lm',
)
# 根据素材画曲线
for c in line_points_dict:
img_draw.line(line_points_dict[c], width=6, fill=c, joint='curve')
for attr in wight_point_dict:
attr_str = attr.replace('收益', '')
if attr_str == '暴击':
attr_list = ['暴击', '爆伤']
else:
attr_list = attr_str.split('/')
for index, point in enumerate(wight_point_dict[attr]['point']):
img_draw.text(
(point[0], 512),
f'{int((point[0] - lu_point[0])/X_D * X_MAX)}',
wight_point_dict[attr]['color'],
gs_font_22,
'mm',
)
img_draw.text(
(point[0], 535),
attr_list[index],
wight_point_dict[attr]['color'],
gs_font_22,
'mm',
)
img.paste(
point_img, (int(point[0] - 15), int(point[1] - 15)), point_img
)
img_draw.line(
[point, (point[0], rd_point[1])], width=1, fill=(255, 255, 255)
)
img.paste(frame_img, (0, 0), frame_img)
return img, 550

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

View File

@ -0,0 +1,92 @@
base_value_list = [
8.6,
9.3,
10.0,
10.6,
11.3,
12.3,
13.3,
14.4,
15.7,
17.1,
18.6,
20.3,
22.2,
24.3,
26.9,
29.5,
32.2,
34.9,
37.6,
40.3,
43.1,
45.9,
48.6,
51.4,
54.2,
56.6,
59.1,
61.5,
64.9,
68.2,
71.3,
74.5,
77.7,
80.9,
84.6,
88.3,
92,
95.9,
99.8,
103.7,
107.7,
112.1,
116.8,
121.1,
128.0,
134.3,
140.8,
147.5,
154.5,
161.8,
168.4,
175.3,
182.2,
189.3,
199.3,
208.2,
217.2,
226.5,
236.3,
246.4,
256.8,
269.6,
282.8,
296.3,
312.2,
325.7,
339.8,
353.9,
368.3,
382.8,
397.4,
412.3,
425.6,
438.9,
457.1,
473.4,
489.7,
505.6,
522.4,
538.7,
555.0,
571.5,
588.2,
605.1,
626.9,
644.5,
622.7,
681.7,
702.6,
723.4,
]

View File

@ -0,0 +1,112 @@
from typing import Dict, Tuple
from PIL import Image, ImageDraw
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.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_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]
)
char.percent = '{:.2f}'.format(percent)
char.dmg_data = dmg_data
return dmg_data
async def draw_dmg_img(char: Character) -> Tuple[Image.Image, int]:
# 获取值
dmg_data = await get_char_dmg_percent(char)
if dmg_data == {}:
return Image.new('RGBA', (950, 1)), 0
# 计算伤害计算部分图片长宽值
w = 950
h = 40 * (len(dmg_data) + 1)
result_img = Image.new('RGBA', (w, h), (0, 0, 0, 0))
# 反复贴上不同颜色的长条
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_size = gs_font_28
result_draw.text((45, 22), '角色动作', title_color, text_size, anchor='lm')
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),
name,
text_color,
text_size,
anchor='lm',
)
result_draw.text(
(450, 22 + (index + 1) * 40),
str(round(dmg_data[name]['crit'])),
text_color,
text_size,
anchor='lm',
)
result_draw.text(
(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(dmg_data) + 2

View File

@ -0,0 +1,77 @@
from typing import Tuple, Union, Optional
from PIL import Image, ImageDraw
from .mono.Character import Character
from .dmg_calc.dmg_calc import draw_dmg_img
from ..utils.image.convert import convert_img
from .draw_char_curve import draw_char_curve_card
from .etc.etc import TEXT_PATH, get_all_artifacts_value
from ..utils.fonts.genshin_fonts import gs_font_18, gs_font_50
from .draw_normal import (
get_bg_card,
get_char_img,
get_artifacts_card,
get_char_card_base,
)
async def draw_char_img(
char: Character,
charUrl: Optional[str] = None,
is_curve: bool = False,
) -> Union[str, Tuple[bytes, Optional[bytes]]]:
if is_curve:
res = await draw_char_curve_card(char, charUrl)
else:
res = await draw_char_card(char, charUrl)
return res, char.char_bytes
async def draw_char_card(char: Character, char_url: Optional[str]) -> bytes:
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)
img.paste(char_img, (0, 0), char_img)
char_info_1 = await get_char_card_base(char)
char_info_2 = Image.open(TEXT_PATH / 'char_info_2.png')
img.paste(char_info_1, (0, 0), char_info_1)
img.paste(char_info_2, (0, 1085), char_info_2)
img.paste(dmg_img, (0, 1850), dmg_img)
await get_artifacts_card(char, img)
img_text = ImageDraw.Draw(img)
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),
gs_font_50,
anchor='mm',
)
img_text.text(
(768, 1726),
percent_str,
(255, 255, 255),
gs_font_50,
anchor='mm',
)
img_text.text(
(768, 1673),
f'{char.seq_str}',
(255, 255, 255),
gs_font_18,
anchor='mm',
)
res = await convert_img(img)
if isinstance(res, str):
res = b''
return res

View File

@ -0,0 +1,80 @@
from io import BytesIO
from typing import Optional
from PIL import Image, ImageDraw
from .etc.etc import TEXT_PATH
from .mono.Character import Character
from .curve_calc.curve_calc import draw_char_curve_data
from ..utils.fonts.genshin_fonts import genshin_font_origin
from .draw_normal import (
get_bg_card,
get_char_img,
get_artifacts_card,
get_char_card_base,
)
async def get_adv_card() -> Image.Image:
adv_img = Image.open(TEXT_PATH / 'adv.png')
return adv_img
async def draw_char_curve_card(
char: Character, char_url: Optional[str]
) -> bytes:
await get_artifacts_card(char, Image.new('RGB', (1, 1)))
curve_img, curve_len = await draw_char_curve_data(
char.char_name, char.card_prop
)
curve2_img, curve2_len = await draw_char_curve_data(
char.char_name, char.fight_prop
)
char_img = await get_char_img(char, char_url)
adv_img = await get_adv_card()
img = await get_bg_card(
char.char_element, curve_len + curve2_len + 460, char_img
)
img.paste(char_img, (0, 0), char_img)
char_info_1 = await get_char_card_base(char)
img.paste(char_info_1, (0, 0), char_info_1)
img.paste(curve_img, (0, 1085), curve_img)
img.paste(curve2_img, (0, 1085 + curve_len), curve2_img)
img.paste(adv_img, (0, 1085 + curve_len + curve2_len), adv_img)
img_text = ImageDraw.Draw(img)
# 顶栏
img_text.text(
(475, 2240),
'曲线(上)为正常面板,曲线(下)为触发各种战斗buff后面板',
(255, 255, 255),
genshin_font_origin(32),
anchor='mm',
)
# 角色评分
img_text.text(
(785, 2380),
f'{round(char.artifacts_all_score, 1)}',
(255, 255, 255),
genshin_font_origin(50),
anchor='mm',
)
img_text.text(
(785, 2542),
f'{str(char.percent)+"%"}',
(255, 255, 255),
genshin_font_origin(50),
anchor='mm',
)
img_text.text(
(785, 2490),
f'{char.seq_str}',
(255, 255, 255),
genshin_font_origin(18),
anchor='mm',
)
img = img.convert('RGB')
result_buffer = BytesIO()
img.save(result_buffer, format='JPEG', subsampling=0, quality=90)
res = result_buffer.getvalue()
return res

View File

@ -0,0 +1,238 @@
import json
import asyncio
from typing import Tuple, Union, Literal
from PIL import Image, ImageDraw
from .mono.Character import Character
from ..utils.image.convert import convert_img
from .dmg_calc.dmg_calc import get_char_dmg_percent
from .etc.etc import TEXT_PATH, get_all_artifacts_value
from ..utils.map.name_covert import avatar_id_to_char_star
from ..utils.fonts.genshin_fonts import genshin_font_origin
from ..utils.resource.RESOURCE_PATH import CHAR_PATH, PLAYER_PATH, WEAPON_PATH
from ..utils.image.image_tools import (
get_color_bg,
get_qq_avatar,
get_fetter_pic,
get_talent_pic,
draw_pic_with_ring,
get_weapon_affix_pic,
)
black_color = (24, 24, 24)
white_color = (245, 245, 245)
level_color = {
5: (230, 0, 0),
4: (203, 131, 21),
3: (97, 17, 156),
2: (17, 105, 156),
1: (94, 96, 95),
}
level_map = {
'skill': {
10: 5,
7: 4,
5: 3,
3: 2,
0: 1,
},
'equip': {33: 5, 27: 4, 21: 3, 15: 2, 0: 1},
'percent': {99: 5, 90: 4, 85: 3, 70: 2, 0: 1},
}
star_color_map = {
'1': (94, 96, 95),
'2': (17, 105, 156),
'3': (91, 141, 192),
'4': (143, 123, 174),
'5': (205, 135, 76),
}
gs_font_24 = genshin_font_origin(24)
gs_font_26 = genshin_font_origin(26)
gs_font_28 = genshin_font_origin(28)
gs_font_30 = genshin_font_origin(30)
gs_font_36 = genshin_font_origin(36)
char_rank_title = Image.open(TEXT_PATH / 'char_rank_title.png')
skill_mask = Image.open(TEXT_PATH / 'skill_mask.png')
percent_mask = Image.open(TEXT_PATH / 'percent_mask.png')
value_mask = Image.open(TEXT_PATH / 'value_mask.png')
async def draw_cahrcard_list(
uid: str, qid: Union[str, int]
) -> Union[str, bytes]:
uid_fold = PLAYER_PATH / str(uid)
char_file_list = uid_fold.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 not char_list:
return '你还没有已缓存的角色!\n请先使用【强制刷新】进行刷新!'
char_done_list = []
for char_name in char_list:
temp = {}
with open(uid_fold / f'{char_name}.json', 'r', encoding='UTF-8') as f:
raw_data = json.load(f)
skill_list = raw_data['avatarSkill']
temp['char_name'] = char_name
temp['fetter'] = raw_data['avatarFetter']
temp['id'] = raw_data['avatarId']
char = Character(raw_data)
await char.new()
await char.get_fight_prop()
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,
char.baseAtk,
char.baseDef,
char_name,
)
temp['value'] = float('{:.2f}'.format(temp['value']))
temp['avatarElement'] = raw_data['avatarElement']
temp['a_skill_level'] = skill_list[0]['skillLevel']
temp['e_skill_level'] = skill_list[1]['skillLevel']
temp['q_skill_level'] = skill_list[-1]['skillLevel']
temp['talent_num'] = len(raw_data['talentList'])
# 武器
temp['weapon_name'] = raw_data['weaponInfo']['weaponName']
temp['weapon_level'] = raw_data['weaponInfo']['weaponLevel']
temp['weapon_affix'] = raw_data['weaponInfo']['weaponAffix']
temp['weapon_star'] = raw_data['weaponInfo']['weaponStar']
char_done_list.append(temp)
# 排序
char_done_list.sort(key=lambda x: (-x['percent']))
qid = str(qid)
if qid.startswith('http'):
char_pic = await get_qq_avatar(avatar_url=qid)
else:
char_pic = await get_qq_avatar(qid=qid)
char_pic = await draw_pic_with_ring(char_pic, 320)
img = await get_color_bg(950, 540 + 100 * len(char_done_list))
img.paste(char_rank_title, (0, 0), char_rank_title)
img.paste(char_pic, (318, 83), char_pic)
img_draw = ImageDraw.Draw(img)
img_draw.text((475, 464), f'UID {uid}', black_color, gs_font_36, 'mm')
tasks = []
for index, char in enumerate(char_done_list):
tasks.append(draw_single_rank(img, char, index))
await asyncio.wait(tasks)
res = await convert_img(img)
return res
async def get_color(
type: Literal['skill', 'equip', 'percent'], value: int
) -> Tuple[int, int, int]:
for v in level_map[type]:
if value >= v:
level = level_map[type][v]
break
else:
level = 1
return level_color[level]
async def draw_single_rank(img: Image.Image, char: dict, index: int):
char_id = char['id']
char_rank = Image.open(TEXT_PATH / 'char_rank.png')
char_pic = Image.open(CHAR_PATH / f'{char_id}.png')
char_star = await avatar_id_to_char_star(char_id)
weapon_star = str(char['weapon_star'])
char_pic = await draw_pic_with_ring(
char_pic, 82, star_color_map[char_star]
)
weapon_pic = Image.open(WEAPON_PATH / f'{char["weapon_name"]}.png')
weapon_pic = await draw_pic_with_ring(
weapon_pic, 82, star_color_map[weapon_star]
)
char_rank.paste(char_pic, (0, 0), char_pic)
char_rank.paste(weapon_pic, (626, 0), weapon_pic)
char_rank_draw = ImageDraw.Draw(char_rank)
# 角色名称
char_rank_draw.text(
(85, 24), char['char_name'], black_color, gs_font_28, 'lm'
)
# AEQ等级
for s_index, s in enumerate(['a', 'e', 'q']):
s_offset = s_index * 38
skill_color_img = Image.new(
'RGBA',
(35, 28),
await get_color('skill', char[f'{s}_skill_level']),
)
char_rank.paste(skill_color_img, (86 + s_offset, 44), skill_mask)
char_rank_draw.text(
(103 + s_offset, 58),
str(char[f'{s}_skill_level']),
white_color,
gs_font_26,
'mm',
)
# 圣遗物词条数
value_color_img = Image.new(
'RGBA',
(77, 33),
await get_color('equip', char['value']),
)
char_rank.paste(value_color_img, (233, 23), value_mask)
char_rank_draw.text(
(271, 40),
f'{str(char["value"])[:4]}',
white_color,
gs_font_24,
'mm',
)
# 毕业度
percent_color_img = Image.new(
'RGBA',
(99, 33),
await get_color('percent', char['percent']),
)
char_rank.paste(percent_color_img, (329, 23), percent_mask)
char_rank_draw.text(
(379, 40),
f'{char["percent"]}%',
white_color,
gs_font_24,
'mm',
)
# 好感和天赋
fetter_pic = await get_fetter_pic(char['fetter'])
fetter_pic = fetter_pic.resize((77, 33))
talent_pic = await get_talent_pic(char['talent_num'])
talent_pic = talent_pic.resize((66, 33))
char_rank.paste(fetter_pic, (444, 23), fetter_pic)
char_rank.paste(talent_pic, (536, 23), talent_pic)
# 武器
weapon_affix_pic = await get_weapon_affix_pic(char['weapon_affix'])
char_rank.paste(weapon_affix_pic, (714, 42), weapon_affix_pic)
char_rank_draw.text(
(788, 56), f'Lv.{char["weapon_level"]}', black_color, gs_font_26, 'lm'
)
char_rank_draw.text(
(712, 22), str(char['weapon_name']), black_color, gs_font_26, 'lm'
)
img.paste(char_rank, (30, 540 + 100 * index), char_rank)

View File

@ -0,0 +1,167 @@
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.image.convert import convert_img
from ..utils.resource.RESOURCE_PATH import CHAR_PATH
from ..utils.map.name_covert import name_to_avatar_id
from ..utils.image.image_tools import get_color_bg, draw_pic_with_ring
from ..utils.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

@ -0,0 +1,595 @@
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 ..genshinuid_config.gs_config import gsconfig
from .etc.MAP_PATH import COLOR_MAP, avatarName2SkillAdd
from ..utils.fonts.genshin_fonts import genshin_font_origin
from ..utils.image.image_tools import CustomizeImage, get_weapon_affix_pic
from .etc.etc import TEXT_PATH, strLenth, get_star_png, get_artifacts_value
from ..utils.resource.RESOURCE_PATH import (
REL_PATH,
ICON_PATH,
CU_CHBG_PATH,
GACHA_IMG_PATH,
CHAR_STAND_PATH,
)
ARTIFACTS_POS = {
'生之花': (18, 1075),
'死之羽': (318, 1075),
'时之沙': (618, 1075),
'空之杯': (18, 1447),
'理之冠': (318, 1447),
}
PIC_API = gsconfig.get_config('random_pic_API').data
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')
# 命座处理
lock_img = Image.open(TEXT_PATH / 'icon_lock.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]
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")
for _ in range(2):
char_info_1.paste(
talent_img_new,
(850, 375 + talent_num * 81),
talent_img_new,
)
else:
char_info_1.paste(lock_img, (850, 375 + talent_num * 81), lock_img)
# 天赋处理
skillList = card_prop['avatarSkill']
a_skill_level = skillList[0]['skillLevel']
e_skill_level = skillList[1]['skillLevel']
q_skill_level = skillList[-1]['skillLevel']
if char.char_name in avatarName2SkillAdd:
skill_add = avatarName2SkillAdd[char.char_name]
else:
skill_add = ['E', 'Q']
for skillAdd_index in range(0, 2):
if len(card_prop['talentList']) >= 3 + skillAdd_index * 2:
if skill_add[skillAdd_index] == 'E':
e_skill_level += 3
elif skill_add[skillAdd_index] == 'Q':
q_skill_level += 3
for skill_num, skill in enumerate(skillList[0:2] + [skillList[-1]]):
skill_img = Image.open(ICON_PATH / '{}.png'.format(skill['skillIcon']))
skill_img_new = skill_img.resize(
(50, 50), Image.Resampling.LANCZOS
).convert("RGBA")
char_info_1.paste(
skill_img_new, (78, 756 + 101 * skill_num), skill_img_new
)
# 武器部分
char_info_text = ImageDraw.Draw(char_info_1)
weapon_star_img = get_star_png(card_prop['weaponInfo']['weaponStar'])
weaponName = card_prop['weaponInfo']['weaponName']
weaponAtk = card_prop['weaponInfo']['weaponStats'][0]['statValue']
weaponLevel = card_prop['weaponInfo']['weaponLevel']
weaponAffix = card_prop['weaponInfo']['weaponAffix']
weaponEffect = card_prop['weaponInfo']['weaponEffect']
weapon_type = card_prop['weaponInfo']['weaponType']
char_info_1.paste(weapon_star_img, (402, 825), weapon_star_img)
char_info_text.text(
(412, 670),
weaponName,
(255, 255, 255),
genshin_font_origin(50),
anchor='lm',
)
char_info_text.text(
(412, 710),
weapon_type,
(255, 255, 255),
genshin_font_origin(20),
anchor='lm',
)
char_info_text.text(
(412, 750),
'基础攻击力',
(255, 255, 255),
genshin_font_origin(32),
anchor='lm',
)
char_info_text.text(
(755, 750),
str(weaponAtk),
(255, 255, 255),
genshin_font_origin(32),
anchor='rm',
)
if len(card_prop['weaponInfo']['weaponStats']) == 2:
weapon_sub_info = card_prop['weaponInfo']['weaponStats'][1]['statName']
weapon_sub_value = card_prop['weaponInfo']['weaponStats'][1][
'statValue'
]
char_info_text.text(
(412, 801),
weapon_sub_info,
(255, 255, 255),
genshin_font_origin(32),
anchor='lm',
)
char_info_text.text(
(755, 801),
str(weapon_sub_value),
(255, 255, 255),
genshin_font_origin(32),
anchor='rm',
)
else:
char_info_text.text(
(412, 801),
'该武器无副词条',
(255, 255, 255),
genshin_font_origin(32),
anchor='lm',
)
char_info_text.text(
(460, 893),
f'Lv.{weaponLevel}',
(255, 255, 255),
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)}',
(255, 239, 173),
genshin_font_origin(28),
anchor='lm',
)
'''
weaponEffect = strLenth(weaponEffect, 25, 455)
weaponEffect = '\n'.join(weaponEffect.split('\n')[:5])
char_info_text.text(
(412, 925), weaponEffect, (255, 255, 255), genshin_font_origin(25)
)
fight_prop = card_prop['avatarFightProp']
hp = fight_prop['hp']
attack = fight_prop['atk']
defense = fight_prop['def']
em = fight_prop['elementalMastery']
critrate = fight_prop['critRate']
critdmg = fight_prop['critDmg']
ce = fight_prop['energyRecharge']
dmgBonus = (
fight_prop['dmgBonus']
if fight_prop['physicalDmgBonus'] <= fight_prop['dmgBonus']
else fight_prop['physicalDmgBonus']
)
hp_green = fight_prop['hp'] - fight_prop['baseHp']
attack_green = fight_prop['atk'] - fight_prop['baseAtk']
defense_green = fight_prop['def'] - fight_prop['baseDef']
# 角色基本信息
char_info_text.text(
(411, 72),
char.char_name,
(255, 255, 255),
genshin_font_origin(55),
anchor='lm',
)
char_info_text.text(
(411, 122),
'等级{}'.format(char.char_level),
(255, 255, 255),
genshin_font_origin(40),
anchor='lm',
)
char_info_text.text(
(747, 126),
str(char.char_fetter),
(255, 255, 255),
genshin_font_origin(28),
anchor='lm',
)
char_info_text.text(
(103, 812),
f'{str(a_skill_level)}',
(255, 255, 255),
genshin_font_origin(30),
anchor='mm',
)
char_info_text.text(
(103, 915),
f'{str(e_skill_level)}',
(255, 255, 255),
genshin_font_origin(30),
anchor='mm',
)
char_info_text.text(
(103, 1016),
f'{str(q_skill_level)}',
(255, 255, 255),
genshin_font_origin(30),
anchor='mm',
)
# 属性
char_info_text.text(
(785, 174),
str(round(hp)),
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
char_info_text.text(
(785, 227),
str(round(attack)),
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
char_info_text.text(
(785, 280),
str(round(defense)),
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
char_info_text.text(
(785, 333),
str(round(em)),
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
char_info_text.text(
(785, 386),
f'{str(round(critrate * 100, 2))}%',
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
char_info_text.text(
(785, 439),
f'{str(round(critdmg * 100, 2))}%',
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
char_info_text.text(
(785, 492),
f'{str(round(ce * 100, 1))}%',
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
char_info_text.text(
(785, 545),
f'{str(round(dmgBonus * 100, 1))}%',
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
char_info_text.text(
(805, 174),
f'(+{str(round(hp_green))})',
(95, 251, 80),
genshin_font_origin(28),
anchor='lm',
)
char_info_text.text(
(805, 227),
f'(+{str(round(attack_green))})',
(95, 251, 80),
genshin_font_origin(28),
anchor='lm',
)
char_info_text.text(
(805, 280),
f'(+{str(round(defense_green))})',
(95, 251, 80),
genshin_font_origin(28),
anchor='lm',
)
uid = card_prop['playerUid']
data_time = card_prop['dataTime']
# uid
char_info_text.text(
(350, 1035),
f'UID{uid}',
(255, 255, 255),
genshin_font_origin(24),
anchor='rm',
)
# 数据最后更新时间
char_info_text.text(
(780, 600),
f'数据最后更新于{data_time}',
(255, 255, 255),
genshin_font_origin(22),
anchor='rm',
)
return char_info_1
async def get_bg_card(
char_element: str, ex_len: int, char_img: Image.Image
) -> Image.Image:
img_w, img_h = 950, 1085 + ex_len
overlay = Image.open(TEXT_PATH / 'overlay.png')
overlay_w, overlay_h = overlay.size
if overlay_h < img_h:
new_overlay_h = img_h
new_overlay_w = math.ceil(new_overlay_h * overlay_w / overlay_h)
overlay = overlay.resize(
(new_overlay_w, new_overlay_h), Image.Resampling.LANCZOS
)
overlay = overlay.crop((0, 0, img_w, img_h))
elif overlay_h > img_h:
new_overlay_w = img_w
new_overlay_h = math.ceil(overlay_w / new_overlay_w * overlay_h)
overlay = overlay.resize(
(new_overlay_w, new_overlay_h), Image.Resampling.LANCZOS
)
overlay = overlay.crop((0, 0, img_w, img_h))
if (
gsconfig.get_config('ColorBG').data
and gsconfig.get_config('RandomPic').data
):
bg_color = CustomizeImage.get_bg_color(char_img)
else:
bg_color = COLOR_MAP[char_element]
color_img = Image.new('RGBA', overlay.size, bg_color)
return ImageChops.overlay(color_img, overlay)
async def get_char_img(
char: Character, char_url: Optional[str] = None
) -> Image.Image:
char_name = char.char_name
if gsconfig.get_config('RandomPic').data and char_url is None:
if char_name == '旅行者':
char_name_url = ''
else:
char_name_url = char_name
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_data = get(char_url, follow_redirects=True)
if char_data.headers['Content-Type'] == 'application/json':
char_url = None
else:
char.char_bytes = char_data.content
based_w, based_h = 600, 1200
if char_url:
offset_x, offset_y = 200, 0
if char.char_bytes is None:
char.char_bytes = get(char_url).content
char_img = Image.open(BytesIO(char.char_bytes)).convert('RGBA')
else:
offset_x, offset_y = 200, 0
if char_name == '旅行者':
char_img = (
Image.open(CHAR_STAND_PATH / '10000007.png')
.convert('RGBA')
.resize((1421, 800))
)
else:
char_img = Image.open(GACHA_IMG_PATH / f'{char_name}.png') # 角色图像
# 确定图片的长宽
w, h = char_img.size
if (w, h) != (based_w, based_h):
based_new_w, based_new_h = based_w + offset_x, based_h + offset_y
based_scale = '%.3f' % (based_new_w / based_new_h)
scale_f = '%.3f' % (w / h)
new_w = math.ceil(based_new_h * float(scale_f))
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
)
x1 = new_w / 2 - based_new_w / 2 + offset_x
y1 = 0 + offset_y / 2
x2 = new_w / 2 + based_new_w / 2
y2 = based_new_h - offset_y / 2
else:
bg_img2 = char_img.resize(
(based_new_w, new_h), Image.Resampling.LANCZOS
)
x1 = 0 + offset_x
y1 = new_h / 2 - based_new_h / 2 + offset_y / 2
x2 = based_new_w
y2 = new_h / 2 + based_new_h / 2 - offset_y / 2
char_img = bg_img2.crop((x1, y1, x2, y2)) # type: ignore
char_info_mask = Image.open(TEXT_PATH / 'char_info_mask.png')
char_result = Image.new('RGBA', (based_w, based_h), (0, 0, 0, 0))
char_result.paste(char_img, (0, 0), char_info_mask)
return char_result
async def get_artifacts_card(char: Character, img: Image.Image):
card_prop = char.card_prop
# 圣遗物部分
for aritifact in card_prop['equipList']:
artifacts_img = Image.open(TEXT_PATH / 'char_info_artifacts.png')
artifacts_piece_img = Image.open(
REL_PATH / '{}.png'.format(aritifact['aritifactName'])
)
artifacts_piece_new_img = artifacts_piece_img.resize(
(120, 120), Image.Resampling.LANCZOS
).convert("RGBA")
artifacts_img.paste(
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, (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(
(22, 100),
main_name,
(255, 255, 255),
genshin_font_origin(28),
anchor='lm',
)
'''
artifacts_text.text(
(30, 102),
artifactsPos,
(255, 255, 255),
genshin_font_origin(20),
anchor='lm',
)
'''
mainValue: float = aritifact['reliquaryMainstat']['statValue']
mainName: str = aritifact['reliquaryMainstat']['statName']
mainLevel: int = aritifact['aritifactLevel']
if mainName in ['攻击力', '血量', '防御力', '元素精通']:
mainValueStr = str(mainValue)
else:
mainValueStr = str(mainValue) + '%'
mainNameNew = (
mainName.replace('百分比', '')
.replace('伤害加成', '伤加成')
.replace('元素', '')
.replace('', '')
)
artifacts_text.text(
(34, 174),
mainNameNew,
(255, 255, 255),
genshin_font_origin(28),
anchor='lm',
)
artifacts_text.text(
(266, 174),
mainValueStr,
(255, 255, 255),
genshin_font_origin(28),
anchor='rm',
)
artifacts_text.text(
(246, 132),
'+{}'.format(str(mainLevel)),
(255, 255, 255),
genshin_font_origin(23),
anchor='mm',
)
artifactsScore = 0
for index, i in enumerate(aritifact['reliquarySubstats']):
subName: str = i['statName']
subValue: float = i['statValue']
if subName in ['攻击力', '血量', '防御力', '元素精通']:
subValueStr = str(subValue)
else:
subValueStr = str(subValue) + '%'
value_temp = await get_artifacts_value(
subName,
subValue,
char.baseAtk,
char.baseHp,
char.baseDef,
char.char_name,
)
artifactsScore += value_temp
subNameStr = subName.replace('百分比', '').replace('元素', '')
# 副词条文字颜色
if value_temp == 0:
artifacts_color = (160, 160, 160)
else:
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(
(22, 225 + index * 35),
'·{}'.format(subNameStr),
artifacts_color,
genshin_font_origin(25),
anchor='lm',
)
artifacts_text.text(
(266, 225 + index * 35),
'{}'.format(subValueStr),
artifacts_color,
genshin_font_origin(25),
anchor='rm',
)
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 = (94, 96, 95)
char.artifacts_all_score += artifactsScore
artifacts_text.rounded_rectangle(
(21, 45, 104, 75), fill=artifactsScore_color, radius=8
)
artifacts_text.text(
(26, 60),
'{:.2f}'.format(artifactsScore) + '',
(255, 255, 255),
genshin_font_origin(23),
anchor='lm',
)
img.paste(artifacts_img, ARTIFACTS_POS[artifactsPos], artifacts_img)

View File

@ -0,0 +1,618 @@
{
"乐园遗落之花": {
"normal_effect": {
"2": "elementalMastery+80",
"4": ""
},
"fight_effect": {
"2": "",
"4": "a+40"
},
"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",
"4": ""
},
"fight_effect": {
"2": "",
"4": "DendroResist+-30"
},
"group_effect": {
"2": "",
"4": ""
}
},
"饰金之梦": {
"normal_effect": {
"2": "elementalMastery+80",
"4": ""
},
"fight_effect": {
"2": "",
"4": "elementalMastery+150"
},
"group_effect": {
"2": "",
"4": ""
}
},
"勇士": {
"normal_effect": {
"2": "addAtk+18",
"4": ""
},
"fight_effect": {
"2": "",
"4": "dmgBonus+30"
},
"group_effect": {
"2": "",
"4": ""
}
},
"辰砂往生录": {
"normal_effect": {
"2": "addAtk+18",
"4": ""
},
"fight_effect": {
"2": "",
"4": "addAtk+48"
},
"group_effect": {
"2": "",
"4": ""
}
},
"来歆余响": {
"normal_effect": {
"2": "addAtk+18",
"4": ""
},
"fight_effect": {
"2": "",
"4": "A:addDmg+70%atk"
},
"group_effect": {
"2": "",
"4": ""
}
},
"华馆梦醒形骸记": {
"normal_effect": {
"2": "addDef+30",
"4": ""
},
"fight_effect": {
"2": "",
"4": "dmgBonus+24;addDef+24"
},
"group_effect": {
"2": "",
"4": ""
}
},
"海染砗磲": {
"normal_effect": {
"2": "healBonus+15",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"绝缘之旗印": {
"normal_effect": {
"2": "energyRecharge+20",
"4": ""
},
"fight_effect": {
"2": "",
"4": "Q:dmgBonus+75%25%energyrecharge"
},
"group_effect": {
"2": "",
"4": ""
}
},
"追忆之注连": {
"normal_effect": {
"2": "addAtk+18",
"4": ""
},
"fight_effect": {
"2": "",
"4": "ABC:dmgBonus+50"
},
"group_effect": {
"2": "",
"4": ""
}
},
"千岩牢固": {
"normal_effect": {
"2": "addHp+20",
"4": ""
},
"fight_effect": {
"2": "",
"4": "addAtk+20;shieldBonus+30"
},
"group_effect": {
"2": "",
"4": "addAtk+20;shieldBonus+30"
}
},
"苍白之火": {
"normal_effect": {
"2": "physicalDmgBonus+25",
"4": ""
},
"fight_effect": {
"2": "",
"4": "addAtk+18;physicalDmgBonus+25"
},
"group_effect": {
"2": "",
"4": ""
}
},
"平息鸣雷的尊者": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": "dmgBonus+35"
},
"group_effect": {
"2": "",
"4": ""
}
},
"炽烈的炎之魔女": {
"normal_effect": {
"2": "PyroDmgBonus+15",
"4": ""
},
"fight_effect": {
"2": "",
"4": "a+15;dmgBonus+22.5"
},
"group_effect": {
"2": "",
"4": ""
}
},
"流浪大地的乐团": {
"normal_effect": {
"2": "elementalMastery+80",
"4": ""
},
"fight_effect": {
"2": "",
"4": "B:dmgBonus+35"
},
"group_effect": {
"2": "",
"4": ""
}
},
"染血的骑士道": {
"normal_effect": {
"2": "physicalDmgBonus+25",
"4": ""
},
"fight_effect": {
"2": "",
"4": "B:dmgBonus+50"
},
"group_effect": {
"2": "",
"4": ""
}
},
"被怜爱的少女": {
"normal_effect": {
"2": "healBonus+15",
"4": ""
},
"fight_effect": {
"2": "",
"4": "healBonus+20"
},
"group_effect": {
"2": "",
"4": ""
}
},
"角斗士的终幕礼": {
"normal_effect": {
"2": "addAtk+18",
"4": ""
},
"fight_effect": {
"2": "",
"4": "A:dmgBonus+35"
},
"group_effect": {
"2": "",
"4": ""
}
},
"渡过烈火的贤人": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": "dmgBonus+35"
},
"group_effect": {
"2": "",
"4": ""
}
},
"悠古的磐岩": {
"normal_effect": {
"2": "GeoDmgBonus+15",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": "dmgBonus+35"
}
},
"如雷的盛怒": {
"normal_effect": {
"2": "ElectroDmgBonus+15",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"沉沦之心": {
"normal_effect": {
"2": "HydroDmgBonus+15",
"4": ""
},
"fight_effect": {
"2": "",
"4": "AB:dmgBonus+30"
},
"group_effect": {
"2": "",
"4": ""
}
},
"逆飞的流星": {
"normal_effect": {
"2": "shieldBonus+35",
"4": ""
},
"fight_effect": {
"2": "",
"4": "AB:dmgBonus+40"
},
"group_effect": {
"2": "",
"4": ""
}
},
"昔日宗室之仪": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "Q:dmgBonus+20",
"4": "addAtk+20"
},
"group_effect": {
"2": "",
"4": "addAtk+20"
}
},
"翠绿之影": {
"normal_effect": {
"2": "AnemoDmgBonus+15",
"4": ""
},
"fight_effect": {
"2": "",
"4": "g+60;Resist+-40"
},
"group_effect": {
"2": "",
"4": ""
}
},
"冰风迷途的勇士": {
"normal_effect": {
"2": "CryoDmgBonus+15",
"4": ""
},
"fight_effect": {
"2": "",
"4": "critRate+40"
},
"group_effect": {
"2": "",
"4": ""
}
},
"勇士之心": {
"normal_effect": {
"2": "addAtk+18",
"4": ""
},
"fight_effect": {
"2": "",
"4": "dmgBonus+30"
},
"group_effect": {
"2": "",
"4": ""
}
},
"教官": {
"normal_effect": {
"2": "elementalMastery+80",
"4": ""
},
"fight_effect": {
"2": "",
"4": "elementalMastery+120"
},
"group_effect": {
"2": "",
"4": "elementalMastery+120"
}
},
"流放者": {
"normal_effect": {
"2": "energyRecharge+20",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"战狂": {
"normal_effect": {
"2": "critRate+12",
"4": ""
},
"fight_effect": {
"2": "",
"4": "critRate+24"
},
"group_effect": {
"2": "",
"4": ""
}
},
"武人": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "AB:dmgBonus+15",
"4": "AB:dmgBonus+25"
},
"group_effect": {
"2": "",
"4": ""
}
},
"学士": {
"normal_effect": {
"2": "energyRecharge+20",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"赌徒": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "E:dmgBonus+20",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"奇迹": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"行者之心": {
"normal_effect": {
"2": "addAtk+18",
"4": ""
},
"fight_effect": {
"2": "",
"4": "B:critRate+30"
},
"group_effect": {
"2": "",
"4": ""
}
},
"守护之心": {
"normal_effect": {
"2": "addDef+30",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"幸运儿": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"冒险家": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"游医": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"祭冰之人": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"祭雷之人": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"祭火之人": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
},
"祭水之人": {
"normal_effect": {
"2": "",
"4": ""
},
"fight_effect": {
"2": "",
"4": ""
},
"group_effect": {
"2": "",
"4": ""
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,258 @@
{
"旅行者": [
"E",
"Q"
],
"胡桃": [
"E",
"Q"
],
"托马": [
"E",
"Q"
],
"宵宫": [
"E",
"Q"
],
"烟绯": [
"E",
"Q"
],
"可莉": [
"E",
"Q"
],
"迪卢克": [
"E",
"Q"
],
"辛焱": [
"E",
"Q"
],
"安柏": [
"Q",
"E"
],
"香菱": [
"Q",
"E"
],
"班尼特": [
"E",
"Q"
],
"珊瑚宫心海": [
"Q",
"E"
],
"达达利亚": [
"E",
"Q"
],
"行秋": [
"Q",
"E"
],
"莫娜": [
"Q",
"E"
],
"芭芭拉": [
"Q",
"E"
],
"申鹤": [
"E",
"Q"
],
"神里绫华": [
"Q",
"E"
],
"优菈": [
"Q",
"E"
],
"甘雨": [
"Q",
"E"
],
"凯亚": [
"E",
"Q"
],
"重云": [
"Q",
"E"
],
"七七": [
"Q",
"E"
],
"迪奥娜": [
"Q",
"E"
],
"罗莎莉亚": [
"E",
"Q"
],
"埃洛伊": [
null,
null
],
"八重神子": [
"E",
"Q"
],
"雷电将军": [
"Q",
"E"
],
"九条裟罗": [
"Q",
"E"
],
"刻晴": [
"Q",
"E"
],
"雷泽": [
"Q",
"E"
],
"菲谢尔": [
"E",
"Q"
],
"丽莎": [
"Q",
"E"
],
"北斗": [
"E",
"Q"
],
"雷主": [
"E",
"Q"
],
"早柚": [
"Q",
"E"
],
"枫原万叶": [
"E",
"Q"
],
"魈": [
"E",
"Q"
],
"温迪": [
"Q",
"E"
],
"琴": [
"Q",
"E"
],
"砂糖": [
"E",
"Q"
],
"风主": [
"E",
"Q"
],
"荒泷一斗": [
"E",
"Q"
],
"五郎": [
"E",
"Q"
],
"阿贝多": [
"E",
"Q"
],
"钟离": [
"E",
"Q"
],
"诺艾尔": [
"E",
"Q"
],
"凝光": [
"Q",
"E"
],
"岩主": [
"E",
"Q"
],
"云堇": [
"Q",
"E"
],
"神里绫人": [
"E",
"Q"
],
"夜兰": [
"Q",
"E"
],
"久岐忍": [
"E",
"Q"
],
"鹿野院平藏": [
"E",
"Q"
],
"柯莱": [
"E",
"Q"
],
"提纳里": [
"Q",
"E"
],
"多莉": [
"E",
"Q"
],
"妮露": [
"Q",
"E"
],
"赛诺": [
"Q",
"E"
],
"坎蒂丝": [
"Q",
"E"
],
"莱依拉": [
"E",
"Q"
],
"纳西妲": [
"E",
"Q"
],
"流浪者": [
"E",
"Q"
],
"珐露珊": [
"E",
"Q"
]
}

View File

@ -0,0 +1,350 @@
{
"神里绫人": [
"血量",
"攻击力",
"暴击率",
"暴击伤害",
"元素精通"
],
"八重神子": [
"攻击力",
"暴击率",
"暴击伤害",
"元素精通",
""
],
"申鹤": [
"攻击力",
"元素充能效率"
],
"云堇": [
"防御力",
"元素充能效率"
],
"荒泷一斗": [
"防御力",
"暴击率",
"暴击伤害"
],
"五郎": [
"防御力",
"元素充能效率"
],
"班尼特": [
"血量",
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"枫原万叶": [
"元素精通",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"雷电将军": [
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"行秋": [
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"钟离": [
"血量",
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"神里绫华": [
"攻击力",
"暴击率",
"暴击伤害"
],
"香菱": [
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率",
"元素精通"
],
"胡桃": [
"血量",
"暴击率",
"暴击伤害",
"元素精通"
],
"甘雨": [
"攻击力",
"暴击率",
"暴击伤害",
"元素精通"
],
"温迪": [
"元素精通",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"珊瑚宫心海": [
"血量",
"元素充能效率"
],
"莫娜": [
"元素精通",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"阿贝多": [
"防御力",
"暴击率",
"暴击伤害"
],
"迪奥娜": [
"血量",
"元素充能效率"
],
"优菈": [
"攻击力",
"暴击率",
"暴击伤害"
],
"达达利亚": [
"攻击力",
"暴击率",
"暴击伤害",
"元素精通"
],
"魈": [
"攻击力",
"暴击率",
"暴击伤害"
],
"宵宫": [
"攻击力",
"暴击率",
"暴击伤害",
"元素精通"
],
"九条裟罗": [
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"琴": [
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"菲谢尔": [
"攻击力",
"暴击率",
"暴击伤害"
],
"罗莎莉亚": [
"攻击力",
"暴击率",
"暴击伤害"
],
"可莉": [
"攻击力",
"暴击率",
"暴击伤害"
],
"凝光": [
"攻击力",
"暴击率",
"暴击伤害"
],
"北斗": [
"攻击力",
"暴击率",
"暴击伤害"
],
"刻晴": [
"攻击力",
"暴击率",
"暴击伤害"
],
"托马": [
"血量",
"元素充能效率"
],
"迪卢克": [
"攻击力",
"暴击率",
"暴击伤害",
"元素精通"
],
"芭芭拉": [
"血量",
"元素充能效率"
],
"诺艾尔": [
"防御力",
"暴击率",
"暴击伤害"
],
"旅行者": [
"攻击力",
"暴击率",
"暴击伤害"
],
"重云": [
"攻击力",
"暴击率",
"暴击伤害"
],
"七七": [
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"凯亚": [
"攻击力",
"暴击率",
"暴击伤害"
],
"烟绯": [
"攻击力",
"暴击率",
"暴击伤害",
"元素精通"
],
"早柚": [
"元素精通",
"元素充能效率"
],
"安柏": [
"攻击力",
"暴击率",
"暴击伤害",
"元素精通"
],
"丽莎": [
"攻击力",
"暴击率",
"暴击伤害"
],
"埃洛伊": [
"攻击力",
"暴击率",
"暴击伤害"
],
"辛焱": [
"攻击力",
"暴击率",
"暴击伤害"
],
"砂糖": [
"元素精通",
"元素充能效率"
],
"雷泽": [
"攻击力",
"暴击率",
"暴击伤害"
],
"夜兰": [
"血量",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"久岐忍": [
"血量",
"元素充能效率",
"元素精通"
],
"鹿野院平藏": [
"攻击力",
"暴击率",
"暴击伤害"
],
"柯莱": [
"元素充能效率",
"元素精通",
"暴击率",
"暴击伤害"
],
"提纳里": [
"攻击力",
"元素精通",
"暴击率",
"暴击伤害"
],
"多莉": [
"元素充能效率",
"血量"
],
"妮露": [
"血量",
"暴击率",
"暴击伤害",
"元素精通"
],
"坎蒂丝": [
"血量",
"元素充能效率",
"暴击率",
"暴击伤害"
],
"赛诺": [
"攻击力",
"暴击率",
"暴击伤害",
"元素精通"
],
"莱依拉": [
"血量",
"元素充能效率"
],
"纳西妲": [
"元素精通",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"流浪者": [
"暴击率",
"暴击伤害",
"攻击力"
],
"珐露珊": [
"攻击力",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"艾尔海森": [
"元素精通",
"暴击率",
"暴击伤害",
"元素充能效率"
],
"瑶瑶": [
"元素充能效率",
"血量",
"攻击力"
],
"迪希雅": [
"血量",
"攻击力",
"暴击率",
"暴击伤害"
],
"米卡": [
"元素充能效率",
"血量"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
import json
from pathlib import Path
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]]]
] = json.load(f)
with open(EFFECT_PATH / 'char_effect.json', "r", encoding='UTF-8') as f:
char_effect_map: Dict[
str, Dict[str, Dict[str, Dict[str, str]]]
] = json.load(f)
with open(EFFECT_PATH / 'artifact_effect.json', "r", encoding='UTF-8') as f:
artifact_effect_map: Dict[str, Dict[str, Dict[str, str]]] = json.load(f)
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: 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)
with open(EFFECT_PATH / 'skill_add.json', 'r', encoding='UTF-8') as f:
avatarName2SkillAdd: Dict[str, List[str]] = json.load(f)
COLOR_MAP = {
'Anemo': (0, 145, 137),
'Cryo': (4, 126, 152),
'Dendro': (28, 145, 0),
'Electro': (133, 12, 159),
'Geo': (147, 112, 3),
'Hydro': (51, 73, 162),
'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

@ -0,0 +1,201 @@
from pathlib import Path
from PIL import Image
from .MAP_PATH import ATTR_MAP, dmgMap
R_PATH = Path(__file__).parents[1]
TEXT_PATH = R_PATH / 'texture2D'
SCORE_MAP = {
'暴击率': 2,
'暴击伤害': 1,
'元素精通': 0.25,
'元素充能效率': 0.65,
'百分比血量': 0.86,
'百分比攻击力': 1,
'百分比防御力': 0.7,
'血量': 0.014,
'攻击力': 0.12,
'防御力': 0.18,
}
VALUE_MAP = {
'攻击力': 4.975,
'血量': 4.975,
'防御力': 6.2,
'元素精通': 19.75,
'元素充能效率': 5.5,
'暴击率': 3.3,
'暴击伤害': 6.6,
}
def get_star_png(star: int) -> Image.Image:
png = Image.open(TEXT_PATH / 's-{}.png'.format(str(star)))
return png
def strLenth(r: str, size: int, limit: int = 540) -> str:
result = ''
temp = 0
for i in r:
if temp >= limit:
result += '\n' + i
temp = 0
else:
result += i
if i.isdigit():
temp += round(size / 10 * 6)
elif i == '/':
temp += round(size / 10 * 2.2)
elif i == '.':
temp += round(size / 10 * 3)
elif i == '%':
temp += round(size / 10 * 9.4)
else:
temp += size
return result
async def get_artifacts_score(subName: str, subValue: int) -> int:
score = subValue * SCORE_MAP[subName]
return score
async def get_artifacts_value(
subName: str,
subValue: float,
baseAtk: int,
baseHp: int,
baseDef: int,
charName: str,
) -> float:
if charName not in ATTR_MAP:
ATTR_MAP[charName] = ['攻击力', '暴击率', '暴击伤害']
if subName in ATTR_MAP[charName] and subName in ['血量', '防御力', '攻击力']:
if subName == '血量':
base = (subValue / baseHp) * 100
elif subName == '防御力':
base = (subValue / baseDef) * 100
elif subName == '攻击力':
base = (subValue / baseAtk) * 100
else:
base = 1.0
value = float('{:.2f}'.format(base / VALUE_MAP[subName]))
elif subName in ['百分比血量', '百分比防御力', '百分比攻击力']:
subName = subName.replace('百分比', '')
if subName in ATTR_MAP[charName]:
value = float('{:.2f}'.format(subValue / VALUE_MAP[subName]))
else:
return 0
else:
if subName in ATTR_MAP[charName]:
value = float('{:.2f}'.format(subValue / VALUE_MAP[subName]))
else:
value = 0
if charName == '胡桃' and subName == '攻击力':
value = value * 0.4
return value
async def get_all_artifacts_value(
raw_data: dict, baseHp: int, baseAtk: int, baseDef: int, char_name: str
) -> float:
artifactsValue = 0
raw_data = raw_data['equipList']
for aritifact in raw_data:
for i in aritifact['reliquarySubstats']:
subName = i['statName']
subValue = i['statValue']
value_temp = await get_artifacts_value(
subName, subValue, baseAtk, baseHp, baseDef, char_name
)
artifactsValue += value_temp
return artifactsValue
async def get_first_main(mainName: str) -> str:
if '伤害加成' in mainName:
equipMain = mainName[0]
elif '元素' in mainName:
equipMain = mainName[2]
elif '百分比' in mainName:
if '血量' in mainName:
equipMain = ''
else:
equipMain = mainName[3]
else:
equipMain = mainName[0]
return equipMain
async def get_char_std(raw_data: dict, char_name: str) -> dmgMap:
weaponName = raw_data['weaponInfo']['weaponName']
equipMain = ''
for aritifact in raw_data['equipList']:
mainName = aritifact['reliquaryMainstat']['statName']
artifactsPos = aritifact['aritifactPieceName']
if artifactsPos == '时之沙':
equipMain += await get_first_main(mainName)
elif artifactsPos == '空之杯':
equipMain += await get_first_main(mainName)
elif artifactsPos == '理之冠':
equipMain += await get_first_main(mainName)
if 'equipSets' in raw_data:
equipSets = raw_data['equipSets']
else:
artifact_set_list = []
for i in raw_data['equipList']:
artifact_set_list.append(i['aritifactSetsName'])
equipSetList = set(artifact_set_list)
equipSets = {'type': '', 'set': ''}
for equip in equipSetList:
if artifact_set_list.count(equip) >= 4:
equipSets['type'] = '4'
equipSets['set'] = equip
break
elif artifact_set_list.count(equip) == 1:
pass
elif artifact_set_list.count(equip) >= 2:
equipSets['type'] += '2'
equipSets['set'] += equip
if equipSets['type'] in ['2', '']:
seq = ''
else:
seq = '{}|{}|{}'.format(
weaponName.replace('', '').replace('', ''),
equipSets['set'],
equipMain,
)
std_prop = dmgMap[char_name]
seq_temp_a = ''
seq_temp_w = ''
for std_seq in std_prop:
# 如果序列完全相同, 则直接使用这个序列
if std_seq['seq'] == seq:
std = std_seq
break
# 如果不完全相同, 但是杯子的主词条相同, 也可以使用这个
if len(seq) >= 2 and len(std_seq['seq']) >= 2:
if std_seq['seq'][:2] == seq[:2] and seq_temp_w == '':
seq_temp_w = std_seq
if std_seq['seq'][-2] == seq[-2] and seq_temp_a == '':
seq_temp_a = std_seq
else:
# 如果存在备选那就用备选
if seq_temp_w:
std = seq_temp_w
elif seq_temp_a:
std = seq_temp_a
# 不存在则使用第一个
else:
std = dmgMap[char_name][0]
return std

View File

@ -0,0 +1,109 @@
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'],
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']
main = type
if type == 'group':
main = 'fight'
# 计算武器效果
WEM = weapon_effect_map[weaponName]
weapon_effet = WEM[main][f'{type}_effect'][str(weaponAffix)]
all_effect.append(weapon_effet)
# 计算圣遗物套装
if 'equipSets' in raw_data:
equipSets = raw_data['equipSets']
else:
artifact_set_list = []
for i in raw_data['equipList']:
artifact_set_list.append(i['aritifactSetsName'])
equipSetList = set(artifact_set_list)
equipSets = {'type': '', 'set': ''}
for equip in equipSetList:
if artifact_set_list.count(equip) >= 4:
equipSets['type'] = '4'
equipSets['set'] = equip
break
elif artifact_set_list.count(equip) == 1:
pass
elif artifact_set_list.count(equip) >= 2:
equipSets['type'] += '2'
equipSets['set'] += '|' + equip
if equipSets['set'].startswith('|'):
equipSets['set'] = equipSets['set'][1:]
# 计算圣遗物buff
if equipSets['type'] == '4':
all_effect.append(
artifact_effect_map[equipSets['set']][f'{type}_effect']['2']
)
all_effect.append(
artifact_effect_map[equipSets['set']][f'{type}_effect']['4']
)
elif equipSets['type'] == '2':
all_effect.append(
artifact_effect_map[equipSets['set']][f'{type}_effect']['2']
)
elif equipSets['type'] == '22':
if equipSets['set'][-2] == '':
e = equipSets['set'][-3:]
else:
e = equipSets['set'][-2:]
if equipSets['set'][2] == '':
t = equipSets['set'][:3]
else:
t = equipSets['set'][:2]
for i in artifact_effect_map:
if i.startswith(t):
all_effect.extend(
artifact_effect_map[i][f'{type}_effect']['2'].split(';')
)
elif i.endswith(e):
all_effect.extend(
artifact_effect_map[i][f'{type}_effect']['2'].split(';')
)
# 计算技能buff
if char_name in char_effect_map:
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):
all_effect.append(
char_effect_map[char_name][main][f'{type}_skill'][skill]
)
return all_effect

View File

@ -0,0 +1,433 @@
STATUS_CHAR_LIST = {
'': [
{
'name': 'Q普通攻击/重击/下落攻击伤害提升',
'type': '攻击力',
'plus': 1,
'value': [
'58.5%',
'61.9%',
'65.5%',
'70.0%',
'73.5%',
'77.0%',
'81.6%',
'86.1%',
'90.6%',
'95.2%',
'99.8%',
'104.3%',
'108.9%',
'113.4%',
'117.9%',
],
'effect': 'ABC:dmgBonus+{}',
}
],
'诺艾尔': [
{
'name': 'Q攻击力提高',
'type': '防御',
'plus': 1,
'value': [
'40%',
'43%',
'46%',
'50%',
'53%',
'56%',
'60%',
'64%',
'68%',
'72%',
'76%',
'80%',
'85%',
'90%',
'95%',
],
'effect': 'exAtk+{}def',
}
],
'胡桃': [
{
'name': 'E攻击力提高',
'type': '生命值',
'plus': 1,
'value': [
'3.84%',
'4.07%',
'4.30%',
'4.60%',
'4.83%',
'5.06%',
'5.36%',
'5.66%',
'5.96%',
'6.26%',
'6.55%',
'6.85%',
'7.15%',
'7.45%',
'7.75%',
],
'effect': 'exAtk+{}hp',
}
],
'烟绯': [
{
'name': 'Q重击伤害提升',
'type': '攻击力',
'plus': 1,
'value': [
'33%',
'35%',
'37%',
'40%',
'42%',
'44%',
'47%',
'49%',
'52%',
'54%',
'57%',
'60%',
'62%',
'65%',
'67%',
],
'effect': 'B:dmgBonus+{}',
}
],
'珊瑚宫心海': [
{
'name': 'Q普通攻击伤害提高',
'type': '生命值',
'plus': 1,
'value': [
'4.8%',
'5.2%',
'5.6%',
'6.0%',
'6.4%',
'6.8%',
'7.3%',
'7.7%',
'8.2%',
'8.7%',
'9.2%',
'9.7%',
'10.3%',
'10.9%',
'11.5%',
],
'effect': 'A:addDmg+{}hp',
},
{
'name': 'Q重击伤害提高',
'type': '生命值',
'plus': 1,
'value': [
'6.8%',
'7.3%',
'7.8%',
'8.5%',
'9.0%',
'9.5%',
'10.2%',
'10.8%',
'11.5%',
'12.2%',
'12.9%',
'13.6%',
'14.4%',
'15.2%',
'16.1%',
],
'effect': 'B:addDmg+{}hp',
},
{
'name': 'Q化海月伤害提高',
'type': '生命值',
'plus': 1,
'value': [
'7.1%',
'7.6%',
'8.2%',
'8.9%',
'9.4%',
'9.9%',
'10.6%',
'11.4%',
'12.1%',
'12.8%',
'13.5%',
'14.2%',
'15.1%',
'16.0%',
'16.9%',
],
'effect': 'E:addDmg+{}hp',
},
],
'荒泷一斗': [
{
'name': 'Q攻击力提高',
'type': '防御',
'plus': 1,
'value': [
'57.6%',
'61.9%',
'66.2%',
'72.0%',
'76.3%',
'80.6%',
'86.4%',
'92.2%',
'97.9%',
'103.7%',
'109.4%',
'115.2%',
'122.4%',
'129.6%',
'136.8%',
],
'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普通攻击伤害提升',
'type': '攻击力',
'plus': 1,
'value': [
'11.0%',
'12.0%',
'13.0%',
'14.0%',
'15.0%',
'16.0%',
'17.0%',
'18.0%',
'19.0%',
'20.0%',
'20.0%',
'20.0%',
'20.0%',
'20.0%',
'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 = {
'雷电将军': {
"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

@ -0,0 +1,272 @@
import re
import json
from typing import Dict, List, Tuple, Union, Optional
from gsuid_core.logger import logger
from .to_card import draw_enka_card
from ..utils.error_reply import CHAR_HINT
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.map.GS_MAP_PATH import avatarName2Element
from ..utils.resource.RESOURCE_PATH import PLAYER_PATH
from ..utils.map.name_covert import (
name_to_avatar_id,
alias_to_char_name,
avatarId_to_enName,
)
CHAR_TO_INT = {
'': 0,
'': 1,
'': 2,
'': 3,
'': 4,
'': 5,
'': 6,
'': 6,
}
WEAPON_TO_INT = {
'': 1,
'': 2,
'': 3,
'': 4,
'': 5,
'': 5,
}
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 = raw_mes.strip()
# 判断是否开启成长曲线或最佳, 并且去除
is_curve = False
is_group = False
if '成长曲线' in msg or '曲线' in msg:
is_curve = True
msg = msg.replace('成长曲线', '').replace('曲线', '')
if '队伍' in msg or '队伍伤害' in msg:
is_group = True
msg = msg.replace('队伍', '').replace('伤害', '').strip()
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:
im = await draw_group_dmg_img(uid, char_list)
if isinstance(im, str):
return im
return im, None
char = await get_char(*_args)
if isinstance(char, str):
logger.info('[查询角色] 绘图失败, 替换的武器不正确!')
return char
im = await draw_char_img(char, url, is_curve)
logger.info('[查询角色] 绘图完成,等待发送...')
return im
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()
percent = float(char.percent.replace('%', ''))
logger.info(f'[查找最佳圣遗物] UID:{uid}{num}次迭代...毕业度为{percent}!')
best.append({'percent': percent, 'char_data': char.card_prop})
async def get_artifacts_repo(uid: str) -> Dict[str, List[Dict]]:
artifacts_repo = {
'flower': [],
'plume': [],
'sands': [],
'goblet': [],
'circlet': [],
}
logger.info(f'[建立圣遗物仓库] UID:{uid}开始...')
# 开始查找全部角色
uid_fold = PLAYER_PATH / str(uid)
char_file_list = uid_fold.glob('*')
for i in char_file_list:
if '\u4e00' <= i.name[0] <= '\u9fff':
with open(uid_fold / f'{i.name}', 'r', encoding='UTF-8') as f:
raw_data = json.load(f)
for equip in raw_data['equipList']:
if equip not in artifacts_repo[equip['aritifactSetPiece']]:
artifacts_repo[equip['aritifactSetPiece']].append(equip)
logger.info(
f'[建立圣遗物仓库] UID:{uid}完成!共计\
{len(artifacts_repo["flower"])},\
{len(artifacts_repo["plume"])},\
{len(artifacts_repo["sands"])},\
{len(artifacts_repo["goblet"])},\
{len(artifacts_repo["circlet"])}个圣遗物!'
)
return artifacts_repo
async def get_fake_char_data(
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:
return '要查询的角色不存在...'
char_data['avatarLevel'] = '90'
char_data['avatarSkill'] = [
{'skillLevel': 10, 'skillIcon': 'Skill_A_02'},
{'skillLevel': 10, 'skillIcon': f'Skill_S_{en_name}_01'},
{'skillLevel': 10, 'skillIcon': f'Skill_E_{en_name}_01'},
]
return 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]]
char_name = char_name[2:]
return char_name, talent_num
async def get_fake_weapon_str(msg: str) -> Tuple[str, Optional[int]]:
weapon_affix = None
if '' in msg and msg[1] in WEAPON_TO_INT:
weapon_affix = WEAPON_TO_INT[msg[1]]
weapon = msg[2:]
else:
weapon = msg
return weapon, weapon_affix

View File

@ -0,0 +1,903 @@
from copy import deepcopy
from typing import Dict, List, Tuple, Optional
from nonebot.log import logger
from .Power import sp_prop
from ..etc.get_buff_list import get_buff_list
from ...genshinuid_config.gs_config import gsconfig
from ...gsuid_utils.api.minigg.models import MiniGGError
from ..etc.status_change import EXTRA_CHAR_LIST, STATUS_CHAR_LIST
from ..etc.MAP_PATH import ActionMAP, char_action, avatarName2SkillAdd
from ...utils.map.GS_MAP_PATH import avatarName2Weapon, avatarName2Element
from ...utils.map.name_covert import name_to_avatar_id, avatar_id_to_char_star
from ...utils.ambr_to_minigg import (
convert_ambr_to_minigg,
convert_ambr_to_weapon,
)
from ..etc.base_info import (
ATTR_MAP,
ELEMENT_MAP,
ICON_ELEMENT,
PERCENT_ATTR,
baseFightProp,
baseWeaponInfo,
)
from ...gsuid_utils.api.minigg.request import (
get_weapon_info,
get_weapon_stats,
get_character_info,
get_character_stats,
)
class Character:
def __init__(self, card_prop: Dict):
# 面板数据
self.card_prop: Dict = card_prop
# 无命座效果
self.without_talent_card = card_prop
# 战斗数据
self.fight_prop: Dict[str, float] = {}
# 战斗数据
self.without_talent_fight: Dict[str, float] = {}
# 实时数据
self.real_prop: Dict[str, float] = {}
# 角色等级,名称,元素,武器类型
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 = ''
# 角色的圣遗物总分
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.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,
weapon: Optional[str] = None,
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 gsconfig.get_config('OldPanle').data:
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 isinstance(weapon_raw_data, MiniGGError) or isinstance(
weapon_raw_data, List
):
weapon_raw_data = await convert_ambr_to_weapon(weapon)
if not weapon_raw_data:
return {}
else:
weapon_level_data = weapon_raw_data
weapon_info['weaponLevel'] = 90
weapon_info['promoteLevel'] = 6
else:
weapon_info['weaponStar'] = int(weapon_raw_data['rarity'])
if weapon_info['weaponStar'] >= 3:
weapon_level_data = await get_weapon_stats(weapon, 90)
weapon_info['weaponLevel'] = 90
weapon_info['promoteLevel'] = 6
else:
weapon_level_data = await get_weapon_stats(weapon, 70)
weapon_info['weaponLevel'] = 70
weapon_info['promoteLevel'] = 4
if isinstance(weapon_level_data, MiniGGError) or isinstance(
weapon_level_data, List
):
return {}
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_character_info(name=char_name_covert)
self.char_id = await name_to_avatar_id(char_name_covert)
if not self.char_id and char_name != '旅行者':
return {}
if isinstance(char_raw, MiniGGError) or isinstance(char_raw, List):
char_raw = char_data = await convert_ambr_to_minigg(self.char_id)
else:
char_data = await get_character_stats(char_name_covert, char_level)
if (
isinstance(char_data, List)
or isinstance(char_data, MiniGGError)
or char_data is None
):
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()
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
# 给每个技能 分别添加上属性
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.s_char_name not in char_action:
self.power_list = {}
else:
self.power_list = char_action[self.s_char_name]
# 额外增加钟离倍率
if self.char_name == '钟离':
self.power_list['E总护盾量'] = {
'name': 'E总护盾量',
'type': '生命值',
'plus': 1.5,
'value': [
f'{self.power_list["E护盾附加吸收量"]["value"][index]}+{i}'
for index, i in enumerate(
self.power_list['E护盾基础吸收量']['value']
)
],
}
elif self.char_name == '赛诺':
for power_name in ['E渡荒之雷', 'E渡荒之雷(超激化)']:
self.power_list[power_name] = {
'name': power_name,
'type': '攻击力',
'plus': 1,
'value': ['100%'] * 15,
}
elif self.char_name == '纳西妲':
for power_name in [
'E业障除(前台)',
'E业障除(蔓激化·前台)',
]:
self.power_list[power_name] = {
'name': power_name,
'type': '攻击力',
'plus': 1,
'value': ['200%+400%'] * 15,
}
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

@ -0,0 +1,179 @@
from typing import Dict
from enum import IntEnum
class Element(IntEnum):
Physical = 0
Anemo = 1
Cryo = 2
Dendro = 3
Electro = 4
Geo = 5
Hydro = 6
Pyro = 7
# 消耗后面元素量,抵消前面1单位元素量
reactable_elements_dict: Dict[Element, Dict[Element, Dict[str, str]]] = {
# 风反应,被所有元素克制
Element.Anemo: {
Element.Cryo: {
'value': '2',
'reaction': '扩散',
'dmg': '0',
},
Element.Dendro: {
'value': '2',
'reaction': '扩散',
'dmg': '0',
},
Element.Electro: {
'value': '2',
'reaction': '扩散',
'dmg': '0',
},
Element.Hydro: {
'value': '2',
'reaction': '扩散',
'dmg': '0',
},
Element.Pyro: {
'value': '2',
'reaction': '扩散',
'dmg': '0',
},
},
# 冰反应,被火克制,与雷水反应
Element.Cryo: {
Element.Pyro: {
'value': '2',
'reaction': '融化',
'dmg': '2',
},
Element.Electro: {
'value': '2',
'reaction': '超导',
'dmg': '0',
},
Element.Hydro: {
'value': '1',
'reaction': '冻结',
'dmg': '0',
},
},
# 草反应, 火,水,雷
Element.Dendro: {
Element.Hydro: {
'value': '1',
'reaction': '燃烧',
'dmg': '0',
},
Element.Pyro: {
'value': '2',
'reaction': '绽放',
'dmg': '0',
},
Element.Electro: {
'value': '2',
'reaction': '激化',
'dmg': '0',
},
},
# 雷反应, 水,火,冰,草
Element.Electro: {
Element.Hydro: {
'value': '1',
'reaction': '感电',
'dmg': '0',
},
Element.Pyro: {
'value': '1',
'reaction': '超载',
'dmg': '0',
},
Element.Cryo: {
'value': '1',
'reaction': '超导',
'dmg': '0',
},
Element.Dendro: {
'value': '1',
'reaction': '激化',
'dmg': '0',
},
},
# 岩反应,被所有元素克制
Element.Geo: {
Element.Cryo: {
'value': '2',
'reaction': '结晶',
'dmg': '0',
},
Element.Dendro: {
'value': '2',
'reaction': '结晶',
'dmg': '0',
},
Element.Electro: {
'value': '2',
'reaction': '结晶',
'dmg': '0',
},
Element.Hydro: {
'value': '2',
'reaction': '结晶',
'dmg': '0',
},
Element.Pyro: {
'value': '2',
'reaction': '结晶',
'dmg': '0',
},
},
# 水反应,草,火,冰,雷
Element.Hydro: {
Element.Dendro: {
'value': '1',
'reaction': '绽放',
'dmg': '0',
},
Element.Pyro: {
'value': '0.5',
'reaction': '蒸发',
'dmg': '1.5',
},
Element.Cryo: {
'value': '1',
'reaction': '冻结',
'dmg': '0',
},
Element.Electro: {
'value': '1',
'reaction': '感电',
'dmg': '0',
},
},
# 火反应, 雷,水,冰,草
Element.Pyro: {
Element.Dendro: {
'value': '1',
'reaction': '燃烧',
'dmg': '0',
},
Element.Hydro: {
'value': '2',
'reaction': '蒸发',
'dmg': '2',
},
Element.Cryo: {
'value': '0.5',
'reaction': '融化',
'dmg': '1.5',
},
Element.Electro: {
'value': '1',
'reaction': '超载',
'dmg': '0',
},
},
}

View File

@ -0,0 +1,160 @@
from typing import Dict, List, Optional
from .Character import Character
from .Element import Element, reactable_elements_dict
class Enemy:
def __init__(self, char_level: int, enemy_level: int):
self.char_level = char_level
self.level: int = enemy_level
self.hp: int = 10000000
self.time: float = 0
self.defense_resist: float = 0
self.ignore_defense: float = 0
self.element: Dict[Element, float] = {}
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 = []
async def update(self, time):
self.time += time
# TODO 遍历debuff列表, 超过时间的移除
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:
for element in self.element:
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']
)
break
else:
reaction_add_dmg = 1
return reaction_add_dmg
else:
if dmg_type:
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
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)
# 结算怪物的元素
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
return reaction
return 1
async def get_resist(self, dmg_type: Element):
# 计算抗性
r = getattr(self, f'{dmg_type.name}Resist')
if r > 0.75:
r = 1 / (1 + 4 * r)
elif r > 0:
r = 1 - r
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_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
# 返回减伤百分比
return proof

View File

@ -0,0 +1,514 @@
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.time = 0
self.total_crit_dmg: float = 0
self.total_normal_dmg: float = 0
self.total_avg_dmg: float = 0
self.SEQ: List = SEQ
self.seq_history: Dict = {}
self.char_list: Dict[str, Character] = Character_list
self.enemy = Enemy
self.dmg_data: Dict[str, Dict[str, float]] = {}
# 进行队伍伤害计算
async def update_dmg(self) -> Dict:
result = {}
for seq in self.SEQ:
# 获取本次攻击的信息
char_name = seq['char']
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)
await self.enemy.update(self.time)
# 获取本次攻击的类型
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, seq)
# 更新角色的属性
await self.get_new_fight_prop(char)
# 更新self.seq_history
self.seq_history = seq
# 聚变反应
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: Character, seq: Optional[Dict] = None
) -> Element:
# TODO 获取本次攻击的元素
dmg_type: Element = Element.Physical
char_element_dmg_type = getattr(Element, char.char_element)
# 对重复的计数
if seq:
if seq['action'] == self.seq_history:
return dmg_type
# 计算角色伤害加成应该使用什么
if char.weapon_type == '法器' or char.char_name in [
'荒泷一斗',
'刻晴',
'诺艾尔',
'胡桃',
'宵宫',
'',
'神里绫华',
]:
dmg_type = char_element_dmg_type
elif char.weapon_type == '':
if char.attack_type in ['B', 'E', 'Q']:
dmg_type = char_element_dmg_type
else:
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_power(self, char: Character) -> Power:
# 按照ABCEQ等级查找倍率
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 = 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 p2v(power, power_plus)
# 额外加成,目前有雷神和优菈
if char.extra_effect and power_name in char.extra_effect:
power_percent += char.extra_effect[power_name]
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:
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_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]:
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
# 获得这次攻击的减伤乘区(抗性区+防御区)
logger.debug(self.enemy.__dict__)
proof = await self.enemy.get_resist(dmg_type)
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 = (
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
self.total_crit_dmg += crit_dmg
return normal_dmg, avg_dmg, crit_dmg
async def p2v(power: str, power_plus: float) -> Tuple[float, float]:
"""
将power转换为value
"""
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

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

@ -0,0 +1,28 @@
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': '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.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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