🎉 v4
分支启动,需结合core
使用
682
.gitignore
vendored
Normal 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
@ -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
@ -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
@ -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
39
GenshinUID/genshinuid_abyss/__init__.py
Normal 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)
|
315
GenshinUID/genshinuid_abyss/draw_abyss_card.py
Normal 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
|
BIN
GenshinUID/genshinuid_abyss/texture2D/abyss_title.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
GenshinUID/genshinuid_abyss/texture2D/char_frame.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
GenshinUID/genshinuid_abyss/texture2D/char_mask.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
GenshinUID/genshinuid_abyss/texture2D/rarity4.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
GenshinUID/genshinuid_abyss/texture2D/rarity5.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
GenshinUID/genshinuid_abyss/texture2D/star0.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
GenshinUID/genshinuid_abyss/texture2D/star1.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
GenshinUID/genshinuid_abyss/texture2D/star2.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
GenshinUID/genshinuid_abyss/texture2D/star3.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
19
GenshinUID/genshinuid_achievement/__init__.py
Normal 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)
|
4388
GenshinUID/genshinuid_achievement/all_achi.json
Normal file
542
GenshinUID/genshinuid_achievement/daily_achi.json
Normal file
@ -0,0 +1,542 @@
|
||||
{
|
||||
"语言交流": {
|
||||
"achievement": "…Odomu?",
|
||||
"desc": "在「语言交流」中与丘丘人交流成功。",
|
||||
"guide": "《语言交流》:注意 1、不要攻击附近的丘丘人;2、注意雷雨天小心落雷击中丘丘人导致任务失败",
|
||||
"link": ""
|
||||
},
|
||||
"诗歌交流": {
|
||||
"achievement": "Yo dala?",
|
||||
"desc": "在「诗歌交流」中与丘丘人交流成功。",
|
||||
"guide": "《诗歌交流》:选择这 3 项即可【Celi dada,mimi nunu!】、【Ye dada!】、【Muhe ye!】其余同上",
|
||||
"link": ""
|
||||
},
|
||||
"来自冬天的故事": {
|
||||
"achievement": "有一说一",
|
||||
"desc": "在「来自冬天的故事」中探听到所有关于至冬国的情报。",
|
||||
"guide": "《来自冬天的故事》:愚人众、邪眼、女皇陛下,三个选项各选 1 次,也就是至少要做 3 次\n*2.4新增后续维克托回至冬(向冬日回归)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Xt4y1z7qw?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"说到做到": {
|
||||
"achievement": "追求极致",
|
||||
"desc": "在「说到做到!」中完美完成查耶维奇的所有委托。",
|
||||
"guide": "《说到做到!》至少 3 次“完美”且不重复地完成委托才能拿到成就。\n山顶(按照顺序击杀:火斧、木盾、丘丘人萨满);山腰(不能损坏货物,可以拉怪出来);\n山底(需要 1 分钟内完成,跑图时间也算,可以提前放个口袋锚点,打怪前保留角色大招)\n*详解地址",
|
||||
"link": "https://www.bilibili.com/video/BV1m64y1y7rk?"
|
||||
},
|
||||
"岩游记": {
|
||||
"achievement": "帝君故事",
|
||||
"desc": "搜集到「岩游记」中所有有关岩王帝君的故事。",
|
||||
"guide": "《岩游记》需要做 4 次,给 4 次不同的道具:财神(必须是琉璃百合);开拓之神(野外采集物:琉璃袋 清心 绝云辣椒 霓裳花等);炉灶之神(各类矿石:夜泊石 铁矿 石珀 等等);历史之神(璃月菜品:翡翠什锦袋 水煮黑背鲈 等等)。\n*当“财神”和“历史之神”共同存在任务道具会被回收,当“炉灶之神”和“开拓之神”共同存在任务道具会被回收,注意记录。\n*视频地址及道具回收演示",
|
||||
"link": "https://www.bilibili.com/video/BV1s64y1m718?p=1"
|
||||
},
|
||||
"且听下回分解": {
|
||||
"achievement": "且听我一言。",
|
||||
"desc": "在「且听下回分解」中听完《海山履云记》。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"璃月港,有海盗!": {
|
||||
"achievement": "哎呀!海盗!",
|
||||
"desc": "陪璐璐、阿飞与小蒙各玩一次海盗游戏。",
|
||||
"guide": "《璃月港,有海盗!》;1.5 版本更新后在这三人附近挂机很容易刷到;2.4后又新增后续\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1q44y1N7Dn?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"好兆头": {
|
||||
"achievement": "「…而尽人事。」",
|
||||
"desc": "破坏了四种爱情运来临的征兆。",
|
||||
"guide": "在《好兆头》中捕鱼、风吹或者火烧落叶、杀鸽子、看到狗要赶走。人为干涉所有的征兆。\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV11r4y127Rw?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"愿风带走思念": {
|
||||
"achievement": "过量的思念",
|
||||
"desc": "完成五次「愿风带走思念」。",
|
||||
"guide": "《愿风带走思念》做 5 次。1.0版本(2020年11月11日之前)有BUG会做1次=5次的情况,拿到成就后不会重置。如果进度不为5次会重置为0。从1.1后后重新计算。\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1xu41167hS?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"勿言勿笑": {
|
||||
"achievement": "厨子与渔夫",
|
||||
"desc": "完成「独钓江雪」与「勿言勿笑」。",
|
||||
"guide": "正常完成 2 个任务即可",
|
||||
"link": ""
|
||||
},
|
||||
"独钓江雪": {
|
||||
"achievement": "厨子与渔夫",
|
||||
"desc": "完成「独钓江雪」与「勿言勿笑」。",
|
||||
"guide": "正常完成 2 个任务即可",
|
||||
"link": ""
|
||||
},
|
||||
"望舒须筑阶": {
|
||||
"achievement": "更上一层楼",
|
||||
"desc": "帮助淮安修复望舒客栈的断桥。",
|
||||
"guide": "正常完成《望舒须筑阶》2次任务即可",
|
||||
"link": ""
|
||||
},
|
||||
"鸽子、鸭子、小孩子": {
|
||||
"achievement": "略表歉意",
|
||||
"desc": "向提米道歉。",
|
||||
"guide": "《鸽子、鸭子、小孩子》里投食鸭子后故意杀掉鸭子,第二天刷《提米,对不起》",
|
||||
"link": ""
|
||||
},
|
||||
"提米,对不起!": {
|
||||
"achievement": "略表歉意",
|
||||
"desc": "向提米道歉。",
|
||||
"guide": "《鸽子、鸭子、小孩子》里投食鸭子后故意杀掉鸭子,第二天刷《提米,对不起》",
|
||||
"link": ""
|
||||
},
|
||||
"鸽子习惯一去不回": {
|
||||
"achievement": "「您好,亲爱的爸爸…」",
|
||||
"desc": "了解提米的故事。",
|
||||
"guide": "水银的讲解视频点此;\n每日委托《鸽子习惯一去不回》有三个支线,第一种:正常赶走鸽子,做完没有后续;\n第二种:赶鸽子的时候玩家杀了鸽子,被杜拉夫要求让旅行者亲自去送信,做完后也没有后续;\n第三种:赶鸽子的时候发现任务提示点的地上一团金光,跑过去发现丘丘人把鸽子抓走烧了吃了,打死丘丘人完成任务,没有后续;\n第三种丘丘人支线又分成两种情况,一是地上有一封信,捡到信的话解锁后续每日委托《一个男孩的去信》,做完这个委托后可以拿到成就\n建议在另外一个每日委托《鸽子、鸭子、小孩子》里不要杀死提米要你喂的鸽子,这样更有可能进入掉信的支线\n至于提米那个略表歉意的成就可以在另外一次刷到鸽子鸭子小孩子以后再做",
|
||||
"link": "https://www.bilibili.com/video/BV1xR4y1E7PR"
|
||||
},
|
||||
"一个男孩的去信": {
|
||||
"achievement": "「您好,亲爱的爸爸…」",
|
||||
"desc": "了解提米的故事。",
|
||||
"guide": "水银的讲解视频点此;\n每日委托《鸽子习惯一去不回》有三个支线,第一种:正常赶走鸽子,做完没有后续;\n第二种:赶鸽子的时候玩家杀了鸽子,被杜拉夫要求让旅行者亲自去送信,做完后也没有后续;\n第三种:赶鸽子的时候发现任务提示点的地上一团金光,跑过去发现丘丘人把鸽子抓走烧了吃了,打死丘丘人完成任务,没有后续;\n第三种丘丘人支线又分成两种情况,一是地上有一封信,捡到信的话解锁后续每日委托《一个男孩的去信》,做完这个委托后可以拿到成就\n建议在另外一个每日委托《鸽子、鸭子、小孩子》里不要杀死提米要你喂的鸽子,这样更有可能进入掉信的支线\n至于提米那个略表歉意的成就可以在另外一次刷到鸽子鸭子小孩子以后再做",
|
||||
"link": "https://www.bilibili.com/video/BV1xR4y1E7PR"
|
||||
},
|
||||
"奇药庐中来": {
|
||||
"achievement": "妙手怪医",
|
||||
"desc": "治好安娜的病。",
|
||||
"guide": "《奇药庐中来》要做 3 次,之后解锁《大病初愈》后续任务;第一次做完《大病初愈》后给成就。\n成就拿完后《大病初愈》可能还会反复刷,安娜会出现在三个位置(风车顶上、教堂顶上、风神像手上)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Hw411Z7zp?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"大病初愈": {
|
||||
"achievement": "妙手怪医",
|
||||
"desc": "治好安娜的病。",
|
||||
"guide": "《奇药庐中来》要做 3 次,之后解锁《大病初愈》后续任务;第一次做完《大病初愈》后给成就。\n成就拿完后《大病初愈》可能还会反复刷,安娜会出现在三个位置(风车顶上、教堂顶上、风神像手上)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Hw411Z7zp?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"餐品订单": {
|
||||
"achievement": "这不是应急食品",
|
||||
"desc": "在「餐品订单」任务中吃掉了餐品…?",
|
||||
"guide": "成就是刷到任务以后吃掉任务菜品就可以拿,但是蟹黄火腿焗时蔬的食谱是不一定给的(送餐给活跃的欧琳的支线才会给,途中要打史莱姆)(触发哪条支线是随机的)",
|
||||
"link": ""
|
||||
},
|
||||
"惊喜大礼": {
|
||||
"achievement": "西风佑我",
|
||||
"desc": "见证吉丽安娜的故事。",
|
||||
"guide": "《惊喜大礼》要做 4 次不同路线(莎拉店、纪念品店、坤恩水果摊、芙萝拉花店)\n最后触发最终剧情(此任务还有后续,盗宝团来复仇,触发“那位先生的委托”)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Ew411f74K?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"「冒险家」的能力极限": {
|
||||
"achievement": "凑合…也能用",
|
||||
"desc": "只带给赫尔曼木桩的材料。",
|
||||
"guide": "《「冒险家」的能力极限》木桩一定要打坏带回",
|
||||
"link": ""
|
||||
},
|
||||
"冒险家测验·作战方式": {
|
||||
"achievement": "安娜冒险记",
|
||||
"desc": "帮助安娜成为一名冒险家。",
|
||||
"guide": "《冒险家,安娜!》\n拿到成就的过程和给安娜治病差不多,且需要先给安娜治好病,也就是完成每日委托成就《妙手怪医》,之后完成前置任务:《冒险家测验·作战方式》、《冒险家测验·冒险诀窍》、《冒险家测验·起飞方式》,最后接到后续《冒险家,安娜!》,第一次完成任务后拿到成就\n(做完成就后还有后续支线,其中一条支线有“彩蛋”级内容,但没成就)\n*前置很阴间主要是情商选项,后续这4个也很阴间不按套路出牌\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Bu411r7Kb?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"冒险家测验·冒险诀窍": {
|
||||
"achievement": "安娜冒险记",
|
||||
"desc": "帮助安娜成为一名冒险家。",
|
||||
"guide": "《冒险家,安娜!》\n拿到成就的过程和给安娜治病差不多,且需要先给安娜治好病,也就是完成每日委托成就《妙手怪医》,之后完成前置任务:《冒险家测验·作战方式》、《冒险家测验·冒险诀窍》、《冒险家测验·起飞方式》,最后接到后续《冒险家,安娜!》,第一次完成任务后拿到成就\n(做完成就后还有后续支线,其中一条支线有“彩蛋”级内容,但没成就)\n*前置很阴间主要是情商选项,后续这4个也很阴间不按套路出牌\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Bu411r7Kb?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"冒险家测验·起飞方式": {
|
||||
"achievement": "安娜冒险记",
|
||||
"desc": "帮助安娜成为一名冒险家。",
|
||||
"guide": "《冒险家,安娜!》\n拿到成就的过程和给安娜治病差不多,且需要先给安娜治好病,也就是完成每日委托成就《妙手怪医》,之后完成前置任务:《冒险家测验·作战方式》、《冒险家测验·冒险诀窍》、《冒险家测验·起飞方式》,最后接到后续《冒险家,安娜!》,第一次完成任务后拿到成就\n(做完成就后还有后续支线,其中一条支线有“彩蛋”级内容,但没成就)\n*前置很阴间主要是情商选项,后续这4个也很阴间不按套路出牌\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Bu411r7Kb?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"冒险家,安娜!": {
|
||||
"achievement": "安娜冒险记",
|
||||
"desc": "帮助安娜成为一名冒险家。",
|
||||
"guide": "《冒险家,安娜!》\n拿到成就的过程和给安娜治病差不多,且需要先给安娜治好病,也就是完成每日委托成就《妙手怪医》,之后完成前置任务:《冒险家测验·作战方式》、《冒险家测验·冒险诀窍》、《冒险家测验·起飞方式》,最后接到后续《冒险家,安娜!》,第一次完成任务后拿到成就\n(做完成就后还有后续支线,其中一条支线有“彩蛋”级内容,但没成就)\n*前置很阴间主要是情商选项,后续这4个也很阴间不按套路出牌\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1Bu411r7Kb?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"『遗落』的文物": {
|
||||
"achievement": "学者与「学者」",
|
||||
"desc": "完成「『遗落』的文物」与「『夺宝』小行动」。",
|
||||
"guide": "《遗落的文物》至少要做 3 次,剧情进展到解救学者索拉雅以后有才有几率刷出《夺宝小行动》",
|
||||
"link": ""
|
||||
},
|
||||
"『夺宝』小行动": {
|
||||
"achievement": "学者与「学者」",
|
||||
"desc": "完成「『遗落』的文物」与「『夺宝』小行动」。",
|
||||
"guide": "《遗落的文物》至少要做 3 次,剧情进展到解救学者索拉雅以后有才有几率刷出《夺宝小行动》",
|
||||
"link": ""
|
||||
},
|
||||
"港口驶过几艘船,二四六七八": {
|
||||
"achievement": "梦想与工作,诗与面包",
|
||||
"desc": "完成「所谓『工作』」,并获得霖铃的诗集。",
|
||||
"guide": "完成《港口驶过几艘船,二四六七八》时故意告诉霖铃错误的数量(注意船有驶入和驶出的区别),大概率第二天刷《所谓工作》;如果第二天没刷,可能过一阵子才会刷",
|
||||
"link": ""
|
||||
},
|
||||
"所谓「工作」": {
|
||||
"achievement": "梦想与工作,诗与面包",
|
||||
"desc": "完成「所谓『工作』」,并获得霖铃的诗集。",
|
||||
"guide": "完成《港口驶过几艘船,二四六七八》时故意告诉霖铃错误的数量(注意船有驶入和驶出的区别),大概率第二天刷《所谓工作》;如果第二天没刷,可能过一阵子才会刷",
|
||||
"link": ""
|
||||
},
|
||||
"点石成…什么": {
|
||||
"achievement": "时也运也",
|
||||
"desc": "一次就选中了最高价值的璞石。",
|
||||
"guide": "《点石成…什么》:正确方法是选最亮的石头。\n不放心的话可以卡视角来透视璞石内部,有完整的石珀就可以选。\n注意并不是 100% 有石珀,纯随机,同理“餐品订单”任务\n外观最亮的,通过透视可以看到里面发光、纹路有完整条纹的即可。\n",
|
||||
"link": ""
|
||||
},
|
||||
"这本小说真厉害": {
|
||||
"achievement": "这本小说真厉害!",
|
||||
"desc": "偷看常九爷的书稿。",
|
||||
"guide": "《这本小说真厉害!》,交书稿前派蒙会问你是否偷看,选择偷看即可",
|
||||
"link": ""
|
||||
},
|
||||
"久久望故人": {
|
||||
"achievement": "故人久未归",
|
||||
"desc": "完成「久久望故人」任务。",
|
||||
"guide": "《久久望故人》:一定要先做过世界任务的小九九,否则可能做完没成就。\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV11U4y137Tr?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"哎呀!海盗想长大!": {
|
||||
"achievement": "远大前程",
|
||||
"desc": "一位少年即将启程远行…",
|
||||
"guide": "《小海盗,要出海!》\n前置《哎呀!海盗想长大!》、《随水而来的烦恼》\n后续《小小的远行》系列 3 个(没成就)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1q44y1N7Dn?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"随水而来的烦恼": {
|
||||
"achievement": "远大前程",
|
||||
"desc": "一位少年即将启程远行…",
|
||||
"guide": "《小海盗,要出海!》\n前置《哎呀!海盗想长大!》、《随水而来的烦恼》\n后续《小小的远行》系列 3 个(没成就)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1q44y1N7Dn?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"小海盗,要出海!": {
|
||||
"achievement": "远大前程",
|
||||
"desc": "一位少年即将启程远行…",
|
||||
"guide": "《小海盗,要出海!》\n前置《哎呀!海盗想长大!》、《随水而来的烦恼》\n后续《小小的远行》系列 3 个(没成就)\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1q44y1N7Dn?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"试问,藏锋何处?": {
|
||||
"achievement": "四方求剑",
|
||||
"desc": "见证岚姐与「藏锋」的故事。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"剑去之日": {
|
||||
"achievement": "行万里路…?",
|
||||
"desc": "见证孙宇的故事。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"万端珊瑚事件簿": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·搜索工作": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·迷惑行动": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·合适的身份": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·结案时刻": {
|
||||
"achievement": "瞳孔中的伪装者",
|
||||
"desc": "帮助珊瑚和龙二破获案件。",
|
||||
"guide": "完成《万端珊瑚事件簿·结案时刻》后解锁成就,需要先完成前置任务,推测的顺序是:\n万端珊瑚事件簿 → 搜索工作x3次 → 合适的身份 → 迷惑行动x3次 → 结案时刻→收尾工作\n第二和四环节有 3 个分支地点(随机给其中 1 个):稻妻城附近、甘金岛附近、神里屋敷附近\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"万端珊瑚事件簿·收尾工作": {
|
||||
"achievement": "真相只有一个…?",
|
||||
"desc": "见证龙二的故事。",
|
||||
"guide": "做完《万端珊瑚事件簿·结案时刻》后解锁《万端珊瑚事件簿·收尾工作》,新登场大和田剧情。\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1i3411K7YD?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"家乡之味": {
|
||||
"achievement": "璃月一番",
|
||||
"desc": "用美味的料理治愈汤雯。",
|
||||
"guide": "前置任务每日委托《家乡之味》交付奇怪的料理(无论哪种料理的奇怪的版本都行)给汤雯,汤雯拿到以后会说味道很微妙。\n正确完成前置任务后,再接到后续每日委托《绝对独特的美味》 (非强制触发,最快第二天可以接到,最慢1个月后),三个选项 (绝云锅巴、腌笃鲜、烤吃虎鱼) 选哪种都可以,交付美味的料理即可解锁成就\n*.2.5版本后在再做一次该任务,汤雯会回璃月\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV12f4y157fC?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"绝对独特的美味": {
|
||||
"achievement": "璃月一番",
|
||||
"desc": "用美味的料理治愈汤雯。",
|
||||
"guide": "前置任务每日委托《家乡之味》交付奇怪的料理(无论哪种料理的奇怪的版本都行)给汤雯,汤雯拿到以后会说味道很微妙。\n正确完成前置任务后,再接到后续每日委托《绝对独特的美味》 (非强制触发,最快第二天可以接到,最慢1个月后),三个选项 (绝云锅巴、腌笃鲜、烤吃虎鱼) 选哪种都可以,交付美味的料理即可解锁成就\n*.2.5版本后在再做一次该任务,汤雯会回璃月\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV12f4y157fC?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"全能美食队·突破性思维": {
|
||||
"achievement": "噼咔,为什么又是噼咔",
|
||||
"desc": "向香菱请教到特别的烹饪手法。",
|
||||
"guide": "《全能美食队·突破性思维》\n剧情里旭东会让旅行者去璃月的万民堂找卯师傅,卯师傅会给你 2 个选项,选择“也许香菱知道怎么解决他的问题…”这个选项,卯师傅说香菱去轻策庄了,让你找点绝云椒椒和禽肉。这时候你可以不管卯师傅要的东西,自己传送到轻策庄去找香菱对话,最后回去找旭东\n香菱在轻策庄西南的传送点(刚传送过去就会被野猪撞的那个)附近\n此外只要自己去找香菱对话,最后交付道具时交付香菱给的就行了,对话选哪个选项其实无所谓\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1zv411g7VE?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"全能美食队·烹饪对决": {
|
||||
"achievement": "双人餐行",
|
||||
"desc": "帮助旭东和龟井宗久各完成一次烹饪。",
|
||||
"guide": "《全能美食队·烹饪对决》:双方各胜利 1 次即可。\n灭火BUG已经修护,如实正常做任务即可\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1zv411g7VE?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"全能美食队·美食小问答": {
|
||||
"achievement": "饮食问题",
|
||||
"desc": "帮助芭尔瓦涅校对全部食谱。",
|
||||
"guide": "《全能美食队·美食小问答》正确答案如下:\n北地苹果焖肉——胡椒 ;天枢肉———清心\n腌笃鲜—————竹笋 ;串串三味——鸟蛋 ;水煮黑背鲈——盐\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1zv411g7VE?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"全能美食队·厨道的极意": {
|
||||
"achievement": "武士饭",
|
||||
"desc": "帮助龟井宗久搜集过全部两侧营地的食材。",
|
||||
"guide": "《全能美食队·厨道的极意》:左右两边各要做 1 次\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1zv411g7VE?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"稻妻销售员": {
|
||||
"achievement": "「给您添蘑菇了!」",
|
||||
"desc": "在「售后服务」中收到顾客绀田传助的抱怨。",
|
||||
"guide": "前置任务《稻妻销售员》中,告诉绀田传助错误的化肥使用方法\n任务里有三个选项,选择错误的选项\n每次刷到这个任务时,瓦希德告诉你的方法的顺序可能是不一样的,没有固定答案,需要你自己判断哪个选项是错误的(类似璃月港数船)\n教错了的话,绀田传助会说感觉不对劲\n之后解锁每日委托《售后服务》(解锁代表有机会刷到,但不是第二天一定就刷),为绀田传助摘除田地里的蘑菇时,注意要把全部的蘑菇都摘除、摘了一部分时派蒙会说“这下应该差不多了”,同时系统提示可以找绀田传助交任务了;此时不要理会,继续摘蘑菇,全部摘完以后派蒙会说“这下就全部摘干净了”,这时再去找绀田传助交任务,任务完成后解锁成就",
|
||||
"link": ""
|
||||
},
|
||||
"售后服务": {
|
||||
"achievement": "「给您添蘑菇了!」",
|
||||
"desc": "在「售后服务」中收到顾客绀田传助的抱怨。",
|
||||
"guide": "前置任务《稻妻销售员》中,告诉绀田传助错误的化肥使用方法\n任务里有三个选项,选择错误的选项\n每次刷到这个任务时,瓦希德告诉你的方法的顺序可能是不一样的,没有固定答案,需要你自己判断哪个选项是错误的(类似璃月港数船)\n教错了的话,绀田传助会说感觉不对劲\n之后解锁每日委托《售后服务》(解锁代表有机会刷到,但不是第二天一定就刷),为绀田传助摘除田地里的蘑菇时,注意要把全部的蘑菇都摘除、摘了一部分时派蒙会说“这下应该差不多了”,同时系统提示可以找绀田传助交任务了;此时不要理会,继续摘蘑菇,全部摘完以后派蒙会说“这下就全部摘干净了”,这时再去找绀田传助交任务,任务完成后解锁成就",
|
||||
"link": ""
|
||||
},
|
||||
"这本小说…厉害吗?": {
|
||||
"achievement": "编辑部的一己之见",
|
||||
"desc": "帮助阿茂和顺吉回到正确的创作轨道。",
|
||||
"guide": "一阶段:《这本小说…厉害吗?》(支线:天目锻冶屋、九十九物、观察同心们的工作)\n二阶段支线 A:《这本小说…有问题?》\n二阶段支线 B:《这本小说…好像看过?》(按顺序123依次交书即可)\n从剧情逻辑上来看,在一阶段支持编辑阿茂解锁刷到《这本小说…好像看过》的可能性,支持作家顺吉解锁刷到《这本小说…有问题》的可能性\n需要这三个每日都做完,且在 B 支线交付\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"这本小说…有问题?": {
|
||||
"achievement": "编辑部的一己之见",
|
||||
"desc": "帮助阿茂和顺吉回到正确的创作轨道。",
|
||||
"guide": "一阶段:《这本小说…厉害吗?》(支线:天目锻冶屋、九十九物、观察同心们的工作)\n二阶段支线 A:《这本小说…有问题?》\n二阶段支线 B:《这本小说…好像看过?》(按顺序123依次交书即可)\n从剧情逻辑上来看,在一阶段支持编辑阿茂解锁刷到《这本小说…好像看过》的可能性,支持作家顺吉解锁刷到《这本小说…有问题》的可能性\n需要这三个每日都做完,且在 B 支线交付\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"这本小说…好像看过?": {
|
||||
"achievement": "编辑部的一己之见",
|
||||
"desc": "帮助阿茂和顺吉回到正确的创作轨道。",
|
||||
"guide": "一阶段:《这本小说…厉害吗?》(支线:天目锻冶屋、九十九物、观察同心们的工作)\n二阶段支线 A:《这本小说…有问题?》\n二阶段支线 B:《这本小说…好像看过?》(按顺序123依次交书即可)\n从剧情逻辑上来看,在一阶段支持编辑阿茂解锁刷到《这本小说…好像看过》的可能性,支持作家顺吉解锁刷到《这本小说…有问题》的可能性\n需要这三个每日都做完,且在 B 支线交付\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"必须精进的武艺": {
|
||||
"achievement": "同心训练家?",
|
||||
"desc": "协助朝仓进行5次训练。",
|
||||
"guide": "做 4 次每日委托《必须精进的武艺》后解锁世界任务《洗刷耻辱的一战》,完成世界任务后有机会刷到每日委托《永不停歇的修炼》,《必须精进的武艺》+《永不停歇的修炼》合计 5 次即可",
|
||||
"link": ""
|
||||
},
|
||||
"永不停歇的修炼": {
|
||||
"achievement": "同心训练家?",
|
||||
"desc": "协助朝仓进行5次训练。",
|
||||
"guide": "做 4 次每日委托《必须精进的武艺》后解锁世界任务《洗刷耻辱的一战》,完成世界任务后有机会刷到每日委托《永不停歇的修炼》,《必须精进的武艺》+《永不停歇的修炼》合计 5 次即可",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《这本小说…有问题?》": {
|
||||
"achievement": "至少有了个结局",
|
||||
"desc": "听顺吉讲述完他所构思的故事。",
|
||||
"guide": "需要在 2.1 版本后(2.0 版本做过的不算)重做《这本小说…有问题?》和《这本小说…好像看过?》才能解锁世界任务《故事构思法》 ,做完世界任务后得到成\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"每日委托《这本小说…好像看过?》": {
|
||||
"achievement": "至少有了个结局",
|
||||
"desc": "听顺吉讲述完他所构思的故事。",
|
||||
"guide": "需要在 2.1 版本后(2.0 版本做过的不算)重做《这本小说…有问题?》和《这本小说…好像看过?》才能解锁世界任务《故事构思法》 ,做完世界任务后得到成\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"世界任务《故事构思法》": {
|
||||
"achievement": "至少有了个结局",
|
||||
"desc": "听顺吉讲述完他所构思的故事。",
|
||||
"guide": "需要在 2.1 版本后(2.0 版本做过的不算)重做《这本小说…有问题?》和《这本小说…好像看过?》才能解锁世界任务《故事构思法》 ,做完世界任务后得到成\n*视频成就",
|
||||
"link": "https://www.bilibili.com/video/BV1tu411C7jN?spm_id_from=333.999.0.0"
|
||||
},
|
||||
"每日委托《神社大扫除》": {
|
||||
"achievement": "她和她的猫",
|
||||
"desc": "陪寝子前往影向山,寻找「阿响」的痕迹。",
|
||||
"guide": "做完寝子系列的世界任务后,累积做 4 个寝子相关每日委托(指《神社大扫除》、《鱼之味》、《猫之迹》,大岛纯平那三个不算)后解锁世界任务《鸣神寻踪》,完成世界任务后解锁成就\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1T3411m7kJ"
|
||||
},
|
||||
"每日委托《鱼之味》": {
|
||||
"achievement": "她和她的猫",
|
||||
"desc": "陪寝子前往影向山,寻找「阿响」的痕迹。",
|
||||
"guide": "做完寝子系列的世界任务后,累积做 4 个寝子相关每日委托(指《神社大扫除》、《鱼之味》、《猫之迹》,大岛纯平那三个不算)后解锁世界任务《鸣神寻踪》,完成世界任务后解锁成就\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1T3411m7kJ"
|
||||
},
|
||||
"每日委托《猫之迹》": {
|
||||
"achievement": "她和她的猫",
|
||||
"desc": "陪寝子前往影向山,寻找「阿响」的痕迹。",
|
||||
"guide": "做完寝子系列的世界任务后,累积做 4 个寝子相关每日委托(指《神社大扫除》、《鱼之味》、《猫之迹》,大岛纯平那三个不算)后解锁世界任务《鸣神寻踪》,完成世界任务后解锁成就\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1T3411m7kJ"
|
||||
},
|
||||
"世界任务《鸣神寻踪》": {
|
||||
"achievement": "她和她的猫",
|
||||
"desc": "陪寝子前往影向山,寻找「阿响」的痕迹。",
|
||||
"guide": "做完寝子系列的世界任务后,累积做 4 个寝子相关每日委托(指《神社大扫除》、《鱼之味》、《猫之迹》,大岛纯平那三个不算)后解锁世界任务《鸣神寻踪》,完成世界任务后解锁成就\n*视频地址",
|
||||
"link": "https://www.bilibili.com/video/BV1T3411m7kJ"
|
||||
},
|
||||
"每日委托《鱼钩上的绝景?》": {
|
||||
"achievement": "啊哈…什么上钩了?",
|
||||
"desc": "与凯万一起钓起奇怪的东西…",
|
||||
"guide": "累积做 3 次前置每日委托《鱼钩上的绝景?》,有墩墩桃、鸟蛋、蘑菇三种支线(注意是累积3次),第二天4点后解锁世界任务《鱼钩的物尽其用》,做完世界任务拿到成就。\n任务还有后续,但是无成就(可能未来版本会加后续成就)。\n做完世界任务以后有机会接到每日委托《鱼钩的奇异时光?》,和前边的也差不多,有帕蒂沙兰、香辛果、甜甜花三种支线",
|
||||
"link": ""
|
||||
},
|
||||
"世界任务《鱼钩的物尽其用》": {
|
||||
"achievement": "啊哈…什么上钩了?",
|
||||
"desc": "与凯万一起钓起奇怪的东西…",
|
||||
"guide": "累积做 3 次前置每日委托《鱼钩上的绝景?》,有墩墩桃、鸟蛋、蘑菇三种支线(注意是累积3次),第二天4点后解锁世界任务《鱼钩的物尽其用》,做完世界任务拿到成就。\n任务还有后续,但是无成就(可能未来版本会加后续成就)。\n做完世界任务以后有机会接到每日委托《鱼钩的奇异时光?》,和前边的也差不多,有帕蒂沙兰、香辛果、甜甜花三种支线",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《鱼钩的奇异时光?》": {
|
||||
"achievement": "啊哈…什么上钩了?",
|
||||
"desc": "与凯万一起钓起奇怪的东西…",
|
||||
"guide": "累积做 3 次前置每日委托《鱼钩上的绝景?》,有墩墩桃、鸟蛋、蘑菇三种支线(注意是累积3次),第二天4点后解锁世界任务《鱼钩的物尽其用》,做完世界任务拿到成就。\n任务还有后续,但是无成就(可能未来版本会加后续成就)。\n做完世界任务以后有机会接到每日委托《鱼钩的奇异时光?》,和前边的也差不多,有帕蒂沙兰、香辛果、甜甜花三种支线",
|
||||
"link": ""
|
||||
},
|
||||
"吞金和蓄财": {
|
||||
"achievement": "卡里米之蕈兽",
|
||||
"desc": "见证哈特姆在「期货交易」大赚一笔!",
|
||||
"guide": "全随机后续,正常需要做5次拿到2成就,最速欧皇可以3次拿2成就。\n2022/9/5更新:\n「吞金料理」中有3种料理:摩拉肉,黄油鸡和「堆高高」。\n建议选择美味的堆高高。\n2022/9/16更新:\n现在发现变成了随机后续,给任何料理都会触发任意成就,原本应该给2次料理2个成就的,也可以1次料理2成就。",
|
||||
"link": ""
|
||||
},
|
||||
"喵…喵喵?喵!喵。": {
|
||||
"achievement": "捉猫记",
|
||||
"desc": "帮莎莉寻找过所有小猫。",
|
||||
"guide": "至少要做 3 次,正确完成寻找 3 只不同小猫的支线\n「黑白色」的猫「拉勒」:【喵,喵喵喵,喵——】\n「深灰色」的猫「纳尔吉斯」:【喵!喵喵,喵】\n「灰黑条纹」的猫「萝赞」:【喵喵喵,喵喵喵】",
|
||||
"link": ""
|
||||
},
|
||||
"世界任务《加尔恰的赞歌》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·关键物品》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·替代物》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·轴承在上》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·举手之劳》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"每日委托《加尔恰的赞歌·某人的回响》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"世界任务《加尔恰的赞歌·适配性赠礼》": {
|
||||
"achievement": "推分算数原理",
|
||||
"desc": "帮助加尔恰完善他的机器。",
|
||||
"guide": "推测需要先完后璃月望舒客栈的世界任务《加尔恰的赞歌》后才能在须弥接到这系列每日委托。\n《举手之劳》里,正确选项:【二次入炉的时候,燃料记得加多点。】【从内向外敲打。】\n做完《某人的回响》后应该可以拿到成就,同时解锁后续世界任务",
|
||||
"link": ""
|
||||
},
|
||||
"御用在他乡": {
|
||||
"achievement": "「为了工作。」",
|
||||
"desc": "为范兵卫采到更多的蘑菇。",
|
||||
"guide": "每日委托《御用在他乡》\n√完结!\n可以一次性拿到成就 :要求采5个蘑菇,但可以采7个给成就\n推测需要先做完稻妻的世界任务《踏鞴物语》系列才能在须弥接到这个委托。\n做完第一次以后他以后还会让你摘蘑菇,对话内容会有些变化",
|
||||
"link": ""
|
||||
},
|
||||
"谨遵医嘱": {
|
||||
"achievement": "放松疗法",
|
||||
"desc": "满足三个病人的愿望。",
|
||||
"guide": "《洁净与健康》不是前置任务,可以直接刷到了《谨遵医嘱》\n《谨遵医嘱》这个任务是你和病人对话完成就可以回去交差了的,但这样拿不到成就。你需要:\n细节:① 主动为古尔根清理田里的杂草,清理完以后再次与古尔根对话;② 阿兹拉说药太苦,和她对话,送给她【糖】;③ 阿夫塔想吃肉,给他【美味的烤肉排】。\n满足病人的愿望以后再回去交差。其中阿兹拉和阿夫塔不会主动问你要东西,需要你听完他们的话以后再次与他们对话来交付道具",
|
||||
"link": ""
|
||||
},
|
||||
"生不出的花": {
|
||||
"achievement": "斩花除根",
|
||||
"desc": "找到并打倒逃走的骗骗花。",
|
||||
"guide": "前置累积3次《生不出的花》后,后续出《花开之时》,\n随机后续1,有骗骗花的支线才有成就,追击并干掉骗骗花后获得成就。\n随机后续2,无骗骗花,寄了,再来3次……\n细节:前置有 2 个支线,一个是提供肥料,另一个是浇水。",
|
||||
"link": ""
|
||||
},
|
||||
"花开之时": {
|
||||
"achievement": "斩花除根",
|
||||
"desc": "找到并打倒逃走的骗骗花。",
|
||||
"guide": "前置累积3次《生不出的花》后,后续出《花开之时》,\n随机后续1,有骗骗花的支线才有成就,追击并干掉骗骗花后获得成就。\n随机后续2,无骗骗花,寄了,再来3次……\n细节:前置有 2 个支线,一个是提供肥料,另一个是浇水。",
|
||||
"link": ""
|
||||
},
|
||||
"衡量世界之人!": {
|
||||
"achievement": "天有多高,地有多…",
|
||||
"desc": "协助法伽尼进行测量工作。",
|
||||
"guide": "1和2分支都是随机给的,其中分支1还有3种怪:丘丘人、蕈兽、遗迹蛇\n至少做 3 次,分别是:① 打怪;② 设置信标;③ 回收信标+打怪。",
|
||||
"link": ""
|
||||
},
|
||||
"宝贝计划": {
|
||||
"achievement": "非必要需求",
|
||||
"desc": "找到古拉布吉尔给小蛇制作的所有道具。",
|
||||
"guide": "帮古拉布吉尔找宠物蛇口粮,有 5 个支线。任务是你找到【古拉布吉尔的特制宠物蛇口粮】交给 NPC 就可以完成,但是做成就需要你额外找到 3 个东西:【奇怪的珠子】、【奇怪的小型帽子】、【破旧的架子】。\n每次随机给1个隐藏道具,但是也可能没有。",
|
||||
"link": ""
|
||||
},
|
||||
"问题的转化": {
|
||||
"achievement": "船说了算",
|
||||
"desc": "与拉菲克成功地测试了船体强度。",
|
||||
"guide": "目前第三段就可以拿到成就,但是还有后续,可能未来版本还有成就。\n(类似稻妻八重堂作家和编辑《小说有问题》在后续版本上线新成就。)\n细节:在《问题的转化·理论强度》中拿5块木头,这样在《问题的转化·负载问题》中可以成功拿到成就,失败的支线可能会退回到第二阶段。",
|
||||
"link": ""
|
||||
},
|
||||
"问题的转化·理论强度": {
|
||||
"achievement": "船说了算",
|
||||
"desc": "与拉菲克成功地测试了船体强度。",
|
||||
"guide": "目前第三段就可以拿到成就,但是还有后续,可能未来版本还有成就。\n(类似稻妻八重堂作家和编辑《小说有问题》在后续版本上线新成就。)\n细节:在《问题的转化·理论强度》中拿5块木头,这样在《问题的转化·负载问题》中可以成功拿到成就,失败的支线可能会退回到第二阶段。",
|
||||
"link": ""
|
||||
},
|
||||
"问题的转化·负载问题": {
|
||||
"achievement": "船说了算",
|
||||
"desc": "与拉菲克成功地测试了船体强度。",
|
||||
"guide": "目前第三段就可以拿到成就,但是还有后续,可能未来版本还有成就。\n(类似稻妻八重堂作家和编辑《小说有问题》在后续版本上线新成就。)\n细节:在《问题的转化·理论强度》中拿5块木头,这样在《问题的转化·负载问题》中可以成功拿到成就,失败的支线可能会退回到第二阶段。",
|
||||
"link": ""
|
||||
},
|
||||
"问题的转化·关键在何?": {
|
||||
"achievement": "船说了算",
|
||||
"desc": "与拉菲克成功地测试了船体强度。",
|
||||
"guide": "目前第三段就可以拿到成就,但是还有后续,可能未来版本还有成就。\n(类似稻妻八重堂作家和编辑《小说有问题》在后续版本上线新成就。)\n细节:在《问题的转化·理论强度》中拿5块木头,这样在《问题的转化·负载问题》中可以成功拿到成就,失败的支线可能会退回到第二阶段。",
|
||||
"link": ""
|
||||
},
|
||||
"食与学": {
|
||||
"achievement": "问题何在?",
|
||||
"desc": "享受三道贾法尔制作的料理。",
|
||||
"guide": "每次随机给一个。\n完成 3 个支线:薄荷豆汤、绿汁脆球、烤肉卷",
|
||||
"link": ""
|
||||
},
|
||||
"教令院,小问题": {
|
||||
"achievement": "须弥博学者",
|
||||
"desc": "答对六道不同的问题。",
|
||||
"guide": "总共 6 道题,每次抽 3 道,正确答案分别是:1、阿弥利多学院;2、悉般多摩学院;3、圣树;4、防沙壁;5、驮兽;6、蕈兽",
|
||||
"link": ""
|
||||
},
|
||||
"跑,希尔米,跑": {
|
||||
"achievement": "一步之遥",
|
||||
"desc": "在与希尔米的赛跑中大意落败…",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"良药难求": {
|
||||
"achievement": "医用笔迹",
|
||||
"desc": "帮助马鲁夫正确地解析药方。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
},
|
||||
"沙上花·余香": {
|
||||
"achievement": "手有余香",
|
||||
"desc": "见证内尔敏的故事。",
|
||||
"guide": null,
|
||||
"link": ""
|
||||
}
|
||||
}
|
62
GenshinUID/genshinuid_achievement/get_achi_desc.py
Normal file
@ -0,0 +1,62 @@
|
||||
import re
|
||||
|
||||
from .template import all_achi, daily_achi, achi_template, daily_template
|
||||
|
||||
|
||||
async def get_daily_achi(task: str) -> str:
|
||||
_similarity = 0
|
||||
detail = {}
|
||||
if task in daily_achi:
|
||||
detail = daily_achi[task]
|
||||
else:
|
||||
for _task in daily_achi:
|
||||
__task = ''.join(re.findall('[\u4e00-\u9fa5]', _task))
|
||||
__task = __task.replace('每日委托', '').replace('世界任务', '')
|
||||
similarity = len(set(__task) & set(task))
|
||||
if similarity >= len(__task) / 2:
|
||||
if similarity > _similarity:
|
||||
_similarity = similarity
|
||||
detail = daily_achi[_task]
|
||||
task = _task
|
||||
else:
|
||||
if detail == {}:
|
||||
return '该委托暂无成就...'
|
||||
|
||||
achi = detail['achievement']
|
||||
desc = detail['desc']
|
||||
guide = detail['guide']
|
||||
link = detail['link']
|
||||
|
||||
im = daily_template.format(task, achi, desc, guide)
|
||||
im = f'{im}\n{link}' if link else im
|
||||
return im
|
||||
|
||||
|
||||
async def get_achi(achi: str) -> str:
|
||||
_similarity = 0
|
||||
detail = {}
|
||||
if achi in all_achi:
|
||||
detail = all_achi[achi]
|
||||
else:
|
||||
for _achi in all_achi:
|
||||
__achi = ''.join(re.findall('[\u4e00-\u9fa5]', _achi))
|
||||
__achi = __achi.replace('每日委托', '').replace('世界任务', '')
|
||||
similarity = len(set(__achi) & set(achi))
|
||||
if similarity >= len(__achi) / 2:
|
||||
if similarity > _similarity:
|
||||
_similarity = similarity
|
||||
detail = all_achi[_achi]
|
||||
achi = _achi
|
||||
else:
|
||||
if detail == {}:
|
||||
return '暂无该成就...'
|
||||
|
||||
book = detail['book']
|
||||
desc = detail['desc']
|
||||
guide = detail['guide']
|
||||
link = detail['link']
|
||||
|
||||
im = achi_template.format(book, achi, desc)
|
||||
im = f'{im}\n{guide}' if guide else im
|
||||
im = f'{im}\n{link}' if link else im
|
||||
return im
|
20
GenshinUID/genshinuid_achievement/template.py
Normal file
@ -0,0 +1,20 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(__file__).parent
|
||||
with open(path / 'all_achi.json', "r", encoding='UTF-8') as f:
|
||||
all_achi = json.load(f)
|
||||
|
||||
with open(path / 'daily_achi.json', "r", encoding='UTF-8') as f:
|
||||
daily_achi = json.load(f)
|
||||
|
||||
daily_template = '''任务:【{}】
|
||||
成就:【{}】
|
||||
描述:【{}】
|
||||
攻略:【{}】
|
||||
'''
|
||||
|
||||
achi_template = '''合辑:【{}】
|
||||
成就:【{}】
|
||||
描述:【{}】
|
||||
'''
|
15
GenshinUID/genshinuid_adv/__init__.py
Normal 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))
|
1515
GenshinUID/genshinuid_adv/char_adv_list.json
Normal file
75
GenshinUID/genshinuid_adv/get_adv.py
Normal 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 '没有找到角色信息'
|
109
GenshinUID/genshinuid_ann/__init__.py
Normal 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)
|
220
GenshinUID/genshinuid_ann/ann_card.py
Normal 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 '已经不在订阅中了'
|
BIN
GenshinUID/genshinuid_ann/assets/item.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
GenshinUID/genshinuid_ann/assets/list.png
Normal file
After Width: | Height: | Size: 26 KiB |
120
GenshinUID/genshinuid_ann/main.py
Normal file
@ -0,0 +1,120 @@
|
||||
import httpx
|
||||
|
||||
from .util import _Dict, black_ids, filter_list, cache_request_json
|
||||
|
||||
# https://webstatic.mihoyo.com/hk4e/announcement/index.html?auth_appid=announcement&authkey_ver=1&bundle_id=hk4e_cn&channel_id=1&game=hk4e&game_biz=hk4e_cn&lang=zh-cn&level=57&platform=pc®ion=cn_gf01&sdk_presentation_style=fullscreen&sdk_screen_transparent=true&sign_type=2&uid=105293904#/
|
||||
api_url = 'https://hk4e-api-static.mihoyo.com/common/hk4e_cn/announcement/api/'
|
||||
api_params = (
|
||||
'?game=hk4e'
|
||||
'&game_biz=hk4e_cn'
|
||||
'&lang=zh-cn'
|
||||
'&bundle_id=hk4e_cn'
|
||||
'&level=57'
|
||||
'&platform={platform}'
|
||||
'®ion={region}'
|
||||
'&uid={uid}'
|
||||
)
|
||||
ann_content_url = f'{api_url}getAnnContent{api_params}'
|
||||
ann_list_url = f'{api_url}getAnnList{api_params}'
|
||||
|
||||
|
||||
class ann:
|
||||
ann_list_data = []
|
||||
ann_content_data = []
|
||||
today = 0
|
||||
|
||||
def __init__(self, platform='pc', uid='114514', region='cn_gf01'):
|
||||
# self.today = datetime.datetime.fromtimestamp(
|
||||
# time.mktime(datetime.date.today().timetuple()))
|
||||
self.platform = platform
|
||||
self.uid = uid
|
||||
self.region = region
|
||||
|
||||
async def get_ann_content(self):
|
||||
url = ann_content_url.format(
|
||||
platform=self.platform, uid=self.uid, region=self.region
|
||||
)
|
||||
res = await cache_request_json(url=url)
|
||||
if res.retcode == 0:
|
||||
self.ann_content_data = res.data.list
|
||||
return self.ann_content_data
|
||||
|
||||
async def get_ann_list(self):
|
||||
url = ann_list_url.format(
|
||||
platform=self.platform, uid=self.uid, region=self.region
|
||||
)
|
||||
res = await cache_request_json(url=url)
|
||||
if res.retcode == 0:
|
||||
result = []
|
||||
for data in res.data.list:
|
||||
data_list = [
|
||||
x for x in data['list'] if not x['ann_id'] in black_ids
|
||||
]
|
||||
data['list'] = data_list
|
||||
result.append(data)
|
||||
|
||||
self.ann_list_data = result
|
||||
return self.ann_list_data
|
||||
|
||||
async def get_ann_ids(self):
|
||||
await self.get_ann_list()
|
||||
if not self.ann_list_data:
|
||||
return []
|
||||
ids = []
|
||||
for label in self.ann_list_data:
|
||||
ids += [x['ann_id'] for x in label['list']]
|
||||
return ids
|
||||
|
||||
|
||||
async def get_consume_remind_ann_ids(region, platform, uid):
|
||||
ann_list = await ann(
|
||||
platform=platform, uid=uid, region=region
|
||||
).get_ann_list()
|
||||
ids = []
|
||||
for label in ann_list:
|
||||
ids += filter_list(label.list, lambda x: x.remind == 1)
|
||||
return [x.ann_id for x in ids]
|
||||
|
||||
|
||||
async def consume_remind(uid):
|
||||
region = 'cn_gf01'
|
||||
if uid[0] == "5":
|
||||
region = 'cn_qd01'
|
||||
platform = ['pc']
|
||||
ids = []
|
||||
for p in platform:
|
||||
ids += await get_consume_remind_ann_ids(region, p, uid)
|
||||
|
||||
ids = set(ids)
|
||||
msg = f'取消公告红点完毕! 一共取消了{len(ids)}个'
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
base_url="https://hk4e-api.mihoyo.com/common/hk4e_cn/announcement/api"
|
||||
) as client:
|
||||
for ann_id in ids:
|
||||
for p in platform:
|
||||
res = await client.get(
|
||||
"/consumeRemind",
|
||||
timeout=10,
|
||||
params={
|
||||
'ann_id': ann_id,
|
||||
'auth_appid': 'announcement',
|
||||
'authkey_ver': '1',
|
||||
'bundle_id': 'hk4e_cn',
|
||||
'channel_id': '1',
|
||||
'game': 'hk4e',
|
||||
'game_biz': 'hk4e_cn',
|
||||
'lang': 'zh-cn',
|
||||
'level': '57',
|
||||
'platform': p,
|
||||
'region': region,
|
||||
'sdk_presentation_style': 'fullscreen',
|
||||
'sdk_screen_transparent': 'true',
|
||||
'sign_type': '2',
|
||||
'uid': uid,
|
||||
},
|
||||
)
|
||||
res = res.json(object_hook=_Dict)
|
||||
if res.retcode != 0:
|
||||
msg += '\n %s 失败,原因:%s' % (ann_id, res.message)
|
||||
return msg
|
65
GenshinUID/genshinuid_ann/util.py
Normal file
@ -0,0 +1,65 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
import inspect
|
||||
import datetime
|
||||
import functools
|
||||
from typing import Dict, Optional, TypedDict
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class _Dict(dict):
|
||||
__setattr__ = dict.__setitem__ # type: ignore
|
||||
__getattr__ = dict.__getitem__
|
||||
|
||||
|
||||
class _CacheData(TypedDict):
|
||||
time: Optional[datetime.datetime]
|
||||
value: Optional[int]
|
||||
|
||||
|
||||
def filter_list(plist, func):
|
||||
return list(filter(func, plist))
|
||||
|
||||
|
||||
def cache(ttl=datetime.timedelta(hours=1), **kwargs):
|
||||
def wrap(func):
|
||||
cache_data: Dict[str, _CacheData] = {}
|
||||
|
||||
@functools.wraps(func)
|
||||
async def wrapped(*args, **kw):
|
||||
nonlocal cache_data
|
||||
bound = inspect.signature(func).bind(*args, **kw)
|
||||
bound.apply_defaults()
|
||||
ins_key = '|'.join(
|
||||
['%s_%s' % (k, v) for k, v in bound.arguments.items()]
|
||||
)
|
||||
default_data: _CacheData = {
|
||||
'time': None,
|
||||
'value': None,
|
||||
}
|
||||
data = cache_data.get(ins_key, default_data)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
if not data['time'] or now - data['time'] > ttl:
|
||||
try:
|
||||
data['value'] = await func(*args, **kw)
|
||||
data['time'] = now
|
||||
cache_data[ins_key] = data
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
return data['value']
|
||||
|
||||
return wrapped
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
@cache(ttl=datetime.timedelta(minutes=30), arg_key='url')
|
||||
async def cache_request_json(url):
|
||||
async with httpx.AsyncClient() as client:
|
||||
res = await client.get(url, timeout=10)
|
||||
return res.json(object_hook=_Dict)
|
||||
|
||||
|
||||
black_ids = [762, 422, 423, 1263, 495, 1957, 2522, 2388, 2516, 2476]
|
119
GenshinUID/genshinuid_check/__init__.py
Normal 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'您绑定的Cookies(uid{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'您绑定的Stoken(uid{i.uid})已失效,以下功能将会受到影响:\n'
|
||||
'gs开启自动米游币,开始获取米游币。\n'
|
||||
'重新添加后需要重新开启自动米游币。',
|
||||
'direct',
|
||||
target_id=i.user_id,
|
||||
)
|
||||
await asyncio.sleep(3 + random.randint(1, 3))
|
31
GenshinUID/genshinuid_check/backup_data.py
Normal 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('————数据库备份失败————')
|
37
GenshinUID/genshinuid_collection/__init__.py
Normal 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)
|
210
GenshinUID/genshinuid_collection/draw_collection_card.py
Normal 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)
|
BIN
GenshinUID/genshinuid_collection/texture2D/collection_title.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
GenshinUID/genshinuid_collection/texture2D/explora_title.png
Normal file
After Width: | Height: | Size: 19 KiB |
76
GenshinUID/genshinuid_config/__init__.py
Normal 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)
|
85
GenshinUID/genshinuid_config/config_default.py
Normal 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,
|
||||
),
|
||||
}
|
78
GenshinUID/genshinuid_config/draw_config_card.py
Normal 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)
|
106
GenshinUID/genshinuid_config/gs_config.py
Normal 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()
|
27
GenshinUID/genshinuid_config/models.py
Normal 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]
|
92
GenshinUID/genshinuid_config/set_config.py
Normal 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
|
BIN
GenshinUID/genshinuid_config/texture2d/config_line.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
GenshinUID/genshinuid_config/texture2d/config_off.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
GenshinUID/genshinuid_config/texture2d/config_on.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
GenshinUID/genshinuid_config/texture2d/config_title.png
Normal file
After Width: | Height: | Size: 155 KiB |
60
GenshinUID/genshinuid_enka/__init__.py
Normal 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)
|
58
GenshinUID/genshinuid_enka/curve_calc/char_curve.json
Normal 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": "暴击收益"}
|
||||
}
|
11780
GenshinUID/genshinuid_enka/curve_calc/curve.json
Normal file
219
GenshinUID/genshinuid_enka/curve_calc/curve_calc.py
Normal 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
|
BIN
GenshinUID/genshinuid_enka/curve_calc/texture2D/curve_bg.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
GenshinUID/genshinuid_enka/curve_calc/texture2D/frame.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
GenshinUID/genshinuid_enka/curve_calc/texture2D/point.png
Normal file
After Width: | Height: | Size: 553 B |
92
GenshinUID/genshinuid_enka/dmg_calc/base_value.py
Normal 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,
|
||||
]
|
112
GenshinUID/genshinuid_enka/dmg_calc/dmg_calc.py
Normal 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
|
77
GenshinUID/genshinuid_enka/draw_char_card.py
Normal 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
|
80
GenshinUID/genshinuid_enka/draw_char_curve.py
Normal 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
|
238
GenshinUID/genshinuid_enka/draw_char_rank.py
Normal 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)
|
167
GenshinUID/genshinuid_enka/draw_group_dmg.py
Normal 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
|
595
GenshinUID/genshinuid_enka/draw_normal.py
Normal 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)
|
618
GenshinUID/genshinuid_enka/effect/artifact_effect.json
Normal 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": ""
|
||||
}
|
||||
}
|
||||
}
|
1
GenshinUID/genshinuid_enka/effect/char_action.json
Normal file
2984
GenshinUID/genshinuid_enka/effect/char_effect.json
Normal file
1
GenshinUID/genshinuid_enka/effect/dmg_map.json
Normal file
258
GenshinUID/genshinuid_enka/effect/skill_add.json
Normal 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"
|
||||
]
|
||||
}
|
350
GenshinUID/genshinuid_enka/effect/value_attr.json
Normal file
@ -0,0 +1,350 @@
|
||||
{
|
||||
"神里绫人": [
|
||||
"血量",
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"八重神子": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通",
|
||||
""
|
||||
],
|
||||
"申鹤": [
|
||||
"攻击力",
|
||||
"元素充能效率"
|
||||
],
|
||||
"云堇": [
|
||||
"防御力",
|
||||
"元素充能效率"
|
||||
],
|
||||
"荒泷一斗": [
|
||||
"防御力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"五郎": [
|
||||
"防御力",
|
||||
"元素充能效率"
|
||||
],
|
||||
"班尼特": [
|
||||
"血量",
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"枫原万叶": [
|
||||
"元素精通",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"雷电将军": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"行秋": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"钟离": [
|
||||
"血量",
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"神里绫华": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"香菱": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率",
|
||||
"元素精通"
|
||||
],
|
||||
"胡桃": [
|
||||
"血量",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"甘雨": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"温迪": [
|
||||
"元素精通",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"珊瑚宫心海": [
|
||||
"血量",
|
||||
"元素充能效率"
|
||||
],
|
||||
"莫娜": [
|
||||
"元素精通",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"阿贝多": [
|
||||
"防御力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"迪奥娜": [
|
||||
"血量",
|
||||
"元素充能效率"
|
||||
],
|
||||
"优菈": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"达达利亚": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"魈": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"宵宫": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"九条裟罗": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"琴": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"菲谢尔": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"罗莎莉亚": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"可莉": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"凝光": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"北斗": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"刻晴": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"托马": [
|
||||
"血量",
|
||||
"元素充能效率"
|
||||
],
|
||||
"迪卢克": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"芭芭拉": [
|
||||
"血量",
|
||||
"元素充能效率"
|
||||
],
|
||||
"诺艾尔": [
|
||||
"防御力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"旅行者": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"重云": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"七七": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"凯亚": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"烟绯": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"早柚": [
|
||||
"元素精通",
|
||||
"元素充能效率"
|
||||
],
|
||||
"安柏": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"丽莎": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"埃洛伊": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"辛焱": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"砂糖": [
|
||||
"元素精通",
|
||||
"元素充能效率"
|
||||
],
|
||||
"雷泽": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"夜兰": [
|
||||
"血量",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"久岐忍": [
|
||||
"血量",
|
||||
"元素充能效率",
|
||||
"元素精通"
|
||||
],
|
||||
"鹿野院平藏": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"柯莱": [
|
||||
"元素充能效率",
|
||||
"元素精通",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"提纳里": [
|
||||
"攻击力",
|
||||
"元素精通",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"多莉": [
|
||||
"元素充能效率",
|
||||
"血量"
|
||||
],
|
||||
"妮露": [
|
||||
"血量",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"坎蒂丝": [
|
||||
"血量",
|
||||
"元素充能效率",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"赛诺": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素精通"
|
||||
],
|
||||
"莱依拉": [
|
||||
"血量",
|
||||
"元素充能效率"
|
||||
],
|
||||
"纳西妲": [
|
||||
"元素精通",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"流浪者": [
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"攻击力"
|
||||
],
|
||||
"珐露珊": [
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"艾尔海森": [
|
||||
"元素精通",
|
||||
"暴击率",
|
||||
"暴击伤害",
|
||||
"元素充能效率"
|
||||
],
|
||||
"瑶瑶": [
|
||||
"元素充能效率",
|
||||
"血量",
|
||||
"攻击力"
|
||||
],
|
||||
"迪希雅": [
|
||||
"血量",
|
||||
"攻击力",
|
||||
"暴击率",
|
||||
"暴击伤害"
|
||||
],
|
||||
"米卡": [
|
||||
"元素充能效率",
|
||||
"血量"
|
||||
]
|
||||
}
|
4745
GenshinUID/genshinuid_enka/effect/weapon_effect.json
Normal file
48
GenshinUID/genshinuid_enka/etc/MAP_PATH.py
Normal 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),
|
||||
}
|
83
GenshinUID/genshinuid_enka/etc/base_info.py
Normal file
@ -0,0 +1,83 @@
|
||||
PERCENT_ATTR = ['dmgBonus', 'addAtk', 'addDef', 'addHp']
|
||||
|
||||
baseWeaponInfo = {
|
||||
'itemId': 0,
|
||||
'nameTextMapHash': '0',
|
||||
'weaponIcon': 'UI_EquipIcon_Bow_Changed',
|
||||
'weaponType': '',
|
||||
'weaponName': '',
|
||||
'weaponStar': 0,
|
||||
'promoteLevel': 0,
|
||||
'weaponLevel': 0,
|
||||
'weaponAffix': 1,
|
||||
'weaponStats': [
|
||||
{
|
||||
'appendPropId': '',
|
||||
'statName': '基础攻击力',
|
||||
'statValue': 0,
|
||||
},
|
||||
{
|
||||
'appendPropId': '',
|
||||
'statName': '',
|
||||
'statValue': 0,
|
||||
},
|
||||
],
|
||||
'weaponEffect': '',
|
||||
}
|
||||
|
||||
baseFightProp = {
|
||||
'hp': 0.0,
|
||||
'baseHp': 0.0,
|
||||
'addHp': 0.0,
|
||||
'exHp': 0.0,
|
||||
'atk': 0.0,
|
||||
'baseAtk': 0.0,
|
||||
'addAtk': 0.0,
|
||||
'exAtk': 0.0,
|
||||
'def': 0.0,
|
||||
'baseDef': 0.0,
|
||||
'addDef': 0.0,
|
||||
'exDef': 0.0,
|
||||
'elementalMastery': 0.0,
|
||||
'critRate': 0.05,
|
||||
'critDmg': 0.5,
|
||||
'energyRecharge': 1.0,
|
||||
'healBonus': 0.0,
|
||||
'healedBonus': 0.0,
|
||||
'physicalDmgSub': 0.0,
|
||||
'physicalDmgBonus': 0.0,
|
||||
'dmgBonus': 0.0,
|
||||
}
|
||||
|
||||
ATTR_MAP = {
|
||||
'元素精通': 'elementalMastery',
|
||||
'物理伤害加成': 'physicalDmgBonus',
|
||||
'元素伤害加成': 'dmgBonus',
|
||||
'充能效率': 'energyRecharge',
|
||||
'暴击伤害': 'critDmg',
|
||||
'暴击率': 'critRate',
|
||||
'攻击力': 'addAtk',
|
||||
'防御力': 'addDef',
|
||||
'生命值': 'addHp',
|
||||
'百分比血量': 'addHp',
|
||||
}
|
||||
|
||||
ELEMENT_MAP = {
|
||||
'风': 'Anemo',
|
||||
'冰': 'Cryo',
|
||||
'草': 'Dendro',
|
||||
'雷': 'Electro',
|
||||
'岩': 'Geo',
|
||||
'水': 'Hydro',
|
||||
'火': 'Pyro',
|
||||
}
|
||||
|
||||
ICON_ELEMENT = {
|
||||
'风': 'Wind',
|
||||
'冰': 'Ice',
|
||||
'草': 'Grass',
|
||||
'水': 'Water',
|
||||
'雷': 'Electric',
|
||||
'岩': 'Rock',
|
||||
'火': 'Fire',
|
||||
}
|
201
GenshinUID/genshinuid_enka/etc/etc.py
Normal 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
|
109
GenshinUID/genshinuid_enka/etc/get_buff_list.py
Normal 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
|
433
GenshinUID/genshinuid_enka/etc/status_change.py
Normal 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,
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
272
GenshinUID/genshinuid_enka/get_enka_img.py
Normal 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
|
903
GenshinUID/genshinuid_enka/mono/Character.py
Normal 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
|
179
GenshinUID/genshinuid_enka/mono/Element.py
Normal 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',
|
||||
},
|
||||
},
|
||||
}
|
160
GenshinUID/genshinuid_enka/mono/Enemy.py
Normal 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
|
514
GenshinUID/genshinuid_enka/mono/Fight.py
Normal 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
|
16
GenshinUID/genshinuid_enka/mono/Power.py
Normal file
@ -0,0 +1,16 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Power(BaseModel):
|
||||
name: str
|
||||
level: int
|
||||
raw: str
|
||||
percent: float
|
||||
value: float
|
||||
plus: float
|
||||
|
||||
|
||||
class sp_prop(BaseModel):
|
||||
dmgBonus: float = 0
|
||||
addDmg: float = 0
|
||||
attack: float = 0
|
28
GenshinUID/genshinuid_enka/mono/SEQ.py
Normal 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 = {'万达国际': ['香菱', '达达利亚', '班尼特', '枫原万叶']}
|
BIN
GenshinUID/genshinuid_enka/texture2D/204.png
Normal file
After Width: | Height: | Size: 414 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/500.png
Normal file
After Width: | Height: | Size: 407 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/adv.png
Normal file
After Width: | Height: | Size: 147 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_card_4.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_card_5.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_info_1.png
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_info_2.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_info_artifacts.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_info_mask.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_mask.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_rank.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/char_rank_title.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/dmgBar_1.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/dmgBar_2.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/equip_mask.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/footbar.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/holo.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/icon_lock.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
GenshinUID/genshinuid_enka/texture2D/overlay.png
Normal file
After Width: | Height: | Size: 296 KiB |
BIN
GenshinUID/genshinuid_enka/texture2D/percent_mask.png
Normal file
After Width: | Height: | Size: 421 B |
BIN
GenshinUID/genshinuid_enka/texture2D/s-1.png
Normal file
After Width: | Height: | Size: 2.2 KiB |