mirror of
https://github.com/PaiGramTeam/python-genshin-artifact.git
synced 2024-11-21 14:38:24 +00:00
✨ Refactor genshin artifact core
Co-authored-by: kotoriのねこ <minamiktr@outlook.com>
This commit is contained in:
parent
5f7033d331
commit
6847ba685a
39
.github/workflows/build.yml
vendored
39
.github/workflows/build.yml
vendored
@ -67,21 +67,19 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
source activate_env.sh
|
source activate_env.sh
|
||||||
cd python_genshin_artifact_core
|
|
||||||
maturin develop
|
maturin develop
|
||||||
|
|
||||||
- name: Export install file
|
- name: Export install file
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
source activate_env.sh
|
source activate_env.sh
|
||||||
cd python_genshin_artifact_core
|
|
||||||
maturin build --out ./dist
|
maturin build --out ./dist
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: genshin-artifact-core
|
name: genshin-artifact-core
|
||||||
path: python_genshin_artifact_core/dist
|
path: ./dist
|
||||||
|
|
||||||
upload-core:
|
upload-core:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -110,38 +108,3 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TWINE_USERNAME: __token__
|
TWINE_USERNAME: __token__
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
|
||||||
build-artifact:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v3
|
|
||||||
with:
|
|
||||||
python-version: 3.11
|
|
||||||
|
|
||||||
- name: Install Poetry and Twine
|
|
||||||
run: |
|
|
||||||
pip install poetry twine
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: poetry install
|
|
||||||
|
|
||||||
- name: Build wheel
|
|
||||||
run: poetry build
|
|
||||||
|
|
||||||
- name: upload to pypi
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
run: twine upload dist/*
|
|
||||||
env:
|
|
||||||
TWINE_USERNAME: __token__
|
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
||||||
|
|
||||||
- name: upload to artifact
|
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: Python-Genshin-Artifact
|
|
||||||
path: dist/*.whl
|
|
||||||
|
38
Cargo.toml
Normal file
38
Cargo.toml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "python_genshin_artifact"
|
||||||
|
version = "0.1.4"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
include = [
|
||||||
|
"/pyproject.toml",
|
||||||
|
"/README.md",
|
||||||
|
"/LICENSE",
|
||||||
|
"/Makefile",
|
||||||
|
"/src",
|
||||||
|
"/watchfiles",
|
||||||
|
"/tests",
|
||||||
|
"/requirements",
|
||||||
|
"/.cargo",
|
||||||
|
"!__pycache__",
|
||||||
|
"!tests/.mypy_cache",
|
||||||
|
"!tests/.pytest_cache",
|
||||||
|
"!*.so",
|
||||||
|
]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "_python_genshin_artifact"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3 = { version = "0.19.2", features = ["anyhow"] }
|
||||||
|
mona_wasm = { path = "genshin_artifact/mona_wasm" }
|
||||||
|
mona = { path = "genshin_artifact/mona_core" }
|
||||||
|
mona_generate = { path = "genshin_artifact/mona_generate" }
|
||||||
|
num = "0.4"
|
||||||
|
serde="1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
anyhow = "1.0.75"
|
||||||
|
pythonize = "0.19.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["pyo3/extension-module"]
|
2
dev-requirements.txt
Normal file
2
dev-requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
black~=23.10.1
|
||||||
|
pytest~=4.1.0
|
@ -1,32 +1,55 @@
|
|||||||
[tool.poetry]
|
[project]
|
||||||
name = "Python-Genshin-Artifact"
|
name = "python_genshin_artifact"
|
||||||
|
requires-python = ">=3.8"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
description = "A Python library that binds to Genshin Artifact damage calculation and analysis engine."
|
authors = [
|
||||||
authors = ["luoshuijs"]
|
{name = "luoshuijs", email = "luoshuijs@outlook.com"},
|
||||||
license = "MIT license"
|
{name = "kotori", email = "minamiktr@outlook.com"}
|
||||||
readme = "README.md"
|
]
|
||||||
packages = [
|
classifiers = [
|
||||||
{ include = "python_genshin_artifact" },
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Intended Audience :: Information Technology",
|
||||||
|
"Intended Audience :: System Administrators",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Operating System :: Microsoft :: Windows",
|
||||||
|
"Operating System :: MacOS",
|
||||||
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
|
"Topic :: System :: Filesystems",
|
||||||
|
"Framework :: AnyIO",
|
||||||
|
]
|
||||||
|
dynamic = [
|
||||||
|
"license",
|
||||||
|
"readme",
|
||||||
|
"version"
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.maturin]
|
||||||
python = "^3.8"
|
module-name = "python_genshin_artifact._python_genshin_artifact"
|
||||||
pydantic = "^1.10.7"
|
bindings = "pyo3"
|
||||||
|
#python-source = "python"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["maturin>=1.0,<2.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
asyncio_mode = "auto"
|
|
||||||
log_cli = true
|
log_cli = true
|
||||||
log_cli_level = "INFO"
|
log_cli_level = "INFO"
|
||||||
log_cli_format = "%(message)s"
|
log_cli_format = "%(message)s"
|
||||||
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
include = '\.pyi?$'
|
include = '\.pyi?$'
|
||||||
line-length = 120
|
line-length = 120
|
||||||
target-version = ['py38']
|
target-version = ["py38"]
|
@ -0,0 +1,37 @@
|
|||||||
|
from ._python_genshin_artifact import (
|
||||||
|
get_damage_analysis,
|
||||||
|
get_transformative_damage,
|
||||||
|
gen_character_meta_as_json,
|
||||||
|
gen_weapon_meta_as_json,
|
||||||
|
gen_artifact_meta_as_json,
|
||||||
|
gen_generate_locale_as_json,
|
||||||
|
TransformativeDamage,
|
||||||
|
CharacterInterface,
|
||||||
|
WeaponInterface,
|
||||||
|
BuffInterface,
|
||||||
|
Artifact,
|
||||||
|
SkillInterface,
|
||||||
|
EnemyInterface,
|
||||||
|
CalculatorConfig,
|
||||||
|
DamageResult,
|
||||||
|
DamageAnalysis,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"get_damage_analysis",
|
||||||
|
"get_transformative_damage",
|
||||||
|
"gen_character_meta_as_json",
|
||||||
|
"gen_weapon_meta_as_json",
|
||||||
|
"gen_artifact_meta_as_json",
|
||||||
|
"gen_generate_locale_as_json",
|
||||||
|
"TransformativeDamage",
|
||||||
|
"CharacterInterface",
|
||||||
|
"WeaponInterface",
|
||||||
|
"BuffInterface",
|
||||||
|
"Artifact",
|
||||||
|
"SkillInterface",
|
||||||
|
"EnemyInterface",
|
||||||
|
"CalculatorConfig",
|
||||||
|
"DamageResult",
|
||||||
|
"DamageAnalysis",
|
||||||
|
)
|
234
python_genshin_artifact/_python_genshin_artifact.pyi
Normal file
234
python_genshin_artifact/_python_genshin_artifact.pyi
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
import sys
|
||||||
|
from typing import List, Optional, Tuple, TYPE_CHECKING, Dict, final
|
||||||
|
|
||||||
|
if sys.version_info < (3, 11):
|
||||||
|
from typing_extensions import Literal
|
||||||
|
else:
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
StatName = Literal[
|
||||||
|
"ATKFixed",
|
||||||
|
"ATKPercentage",
|
||||||
|
"HealingBonus",
|
||||||
|
"HPFixed",
|
||||||
|
"HPPercentage",
|
||||||
|
"DEFFixed",
|
||||||
|
"DEFPercentage",
|
||||||
|
"CriticalRate",
|
||||||
|
"CriticalDamage",
|
||||||
|
"ElementalMastery",
|
||||||
|
"Recharge",
|
||||||
|
"ElectroBonus",
|
||||||
|
"PyroBonus",
|
||||||
|
"HydroBonus",
|
||||||
|
"CryoBonus",
|
||||||
|
"AnemoBonus",
|
||||||
|
"GeoBonus",
|
||||||
|
"DendroBonus",
|
||||||
|
"PhysicalBonus",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
StatName = str
|
||||||
|
|
||||||
|
def get_damage_analysis(calculator_config: "CalculatorConfig") -> "DamageAnalysis": ...
|
||||||
|
def get_transformative_damage(calculator_config: "CalculatorConfig") -> "TransformativeDamage": ...
|
||||||
|
def gen_character_meta_as_json() -> str: ...
|
||||||
|
def gen_weapon_meta_as_json() -> str: ...
|
||||||
|
def gen_artifact_meta_as_json() -> str: ...
|
||||||
|
def gen_generate_locale_as_json(loc: str) -> str: ...
|
||||||
|
@final
|
||||||
|
class TransformativeDamage:
|
||||||
|
swirl_cryo: float
|
||||||
|
swirl_hydro: float
|
||||||
|
swirl_pyro: float
|
||||||
|
swirl_electro: float
|
||||||
|
overload: float
|
||||||
|
electro_charged: float
|
||||||
|
shatter: float
|
||||||
|
super_conduct: float
|
||||||
|
bloom: float
|
||||||
|
hyper_bloom: float
|
||||||
|
burgeon: float
|
||||||
|
burning: float
|
||||||
|
crystallize: float
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls,
|
||||||
|
swirl_cryo: float,
|
||||||
|
swirl_hydro: float,
|
||||||
|
swirl_pyro: float,
|
||||||
|
swirl_electro: float,
|
||||||
|
overload: float,
|
||||||
|
electro_charged: float,
|
||||||
|
shatter: float,
|
||||||
|
super_conduct: float,
|
||||||
|
bloom: float,
|
||||||
|
hyper_bloom: float,
|
||||||
|
burgeon: float,
|
||||||
|
burning: float,
|
||||||
|
crystallize: float,
|
||||||
|
) -> "TransformativeDamage": ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class DamageResult:
|
||||||
|
critical: float
|
||||||
|
non_critical: float
|
||||||
|
expectation: float
|
||||||
|
is_heal: bool
|
||||||
|
is_shield: bool
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls, critical: float, non_critical: float, expectation: float, is_heal: bool, is_shield: bool
|
||||||
|
) -> "DamageResult": ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class DamageAnalysis:
|
||||||
|
atk: Dict[str, float]
|
||||||
|
atk_ratio: Dict[str, float]
|
||||||
|
hp: Dict[str, float]
|
||||||
|
hp_ratio: Dict[str, float]
|
||||||
|
defense: Dict[str, float]
|
||||||
|
def_ratio: Dict[str, float]
|
||||||
|
em: Dict[str, float]
|
||||||
|
em_ratio: Dict[str, float]
|
||||||
|
extra_damage: Dict[str, float]
|
||||||
|
bonus: Dict[str, float]
|
||||||
|
critical: Dict[str, float]
|
||||||
|
critical_damage: Dict[str, float]
|
||||||
|
melt_enhance: Dict[str, float]
|
||||||
|
vaporize_enhance: Dict[str, float]
|
||||||
|
healing_bonus: Dict[str, float]
|
||||||
|
shield_strength: Dict[str, float]
|
||||||
|
spread_compose: Dict[str, float]
|
||||||
|
aggravate_compose: Dict[str, float]
|
||||||
|
|
||||||
|
def_minus: Dict[str, float]
|
||||||
|
def_penetration: Dict[str, float]
|
||||||
|
res_minus: Dict[str, float]
|
||||||
|
|
||||||
|
element: str
|
||||||
|
is_heal: bool
|
||||||
|
is_shield: bool
|
||||||
|
|
||||||
|
normal: DamageResult
|
||||||
|
melt: Optional[DamageResult]
|
||||||
|
vaporize: Optional[DamageResult]
|
||||||
|
spread: Optional[DamageResult]
|
||||||
|
aggravate: Optional[DamageResult]
|
||||||
|
|
||||||
|
@final
|
||||||
|
class CharacterInterface:
|
||||||
|
name: str
|
||||||
|
level: int
|
||||||
|
ascend: bool
|
||||||
|
constellation: int
|
||||||
|
skill1: int
|
||||||
|
skill2: int
|
||||||
|
skill3: int
|
||||||
|
params: Optional[dict] = None
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls,
|
||||||
|
name: str,
|
||||||
|
level: int,
|
||||||
|
ascend: bool,
|
||||||
|
constellation: int,
|
||||||
|
skill1: int,
|
||||||
|
skill2: int,
|
||||||
|
skill3: int,
|
||||||
|
params: Optional[dict] = None,
|
||||||
|
) -> "CharacterInterface": ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class WeaponInterface:
|
||||||
|
name: str
|
||||||
|
level: int
|
||||||
|
ascend: bool
|
||||||
|
refine: int
|
||||||
|
params: Optional[dict] = None
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls, name: str, level: int, ascend: bool, refine: int, params: Optional[dict] = None
|
||||||
|
) -> "WeaponInterface": ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class BuffInterface:
|
||||||
|
name: str
|
||||||
|
config: Optional[dict] = None
|
||||||
|
|
||||||
|
def __new__(cls, name: str, config: Optional[dict] = None) -> "BuffInterface": ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Artifact:
|
||||||
|
set_name: str
|
||||||
|
slot: str
|
||||||
|
level: int
|
||||||
|
star: int
|
||||||
|
sub_stats: List[Tuple[StatName, float]]
|
||||||
|
main_stat: Tuple[StatName, float]
|
||||||
|
id: int
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls,
|
||||||
|
set_name: str,
|
||||||
|
slot: str,
|
||||||
|
level: int,
|
||||||
|
star: int,
|
||||||
|
sub_stats: List[Tuple[StatName, float]],
|
||||||
|
main_stat: Tuple[StatName, float],
|
||||||
|
id: int,
|
||||||
|
) -> "Artifact": ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class SkillInterface:
|
||||||
|
index: int
|
||||||
|
config: Optional[dict] = None
|
||||||
|
|
||||||
|
def __new__(cls, index: int, config: Optional[dict] = None) -> "SkillInterface": ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class EnemyInterface:
|
||||||
|
level: int
|
||||||
|
electro_res: float
|
||||||
|
pyro_res: float
|
||||||
|
hydro_res: float
|
||||||
|
cryo_res: float
|
||||||
|
geo_res: float
|
||||||
|
anemo_res: float
|
||||||
|
dendro_res: float
|
||||||
|
physical_res: float
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls,
|
||||||
|
level: int,
|
||||||
|
electro_res: float,
|
||||||
|
pyro_res: float,
|
||||||
|
hydro_res: float,
|
||||||
|
cryo_res: float,
|
||||||
|
geo_res: float,
|
||||||
|
anemo_res: float,
|
||||||
|
dendro_res: float,
|
||||||
|
physical_res: float,
|
||||||
|
) -> "EnemyInterface": ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class CalculatorConfig:
|
||||||
|
character: CharacterInterface
|
||||||
|
weapon: WeaponInterface
|
||||||
|
buffs: List[BuffInterface] = []
|
||||||
|
artifacts: List[Artifact] = []
|
||||||
|
artifact_config: Optional[dict] = None
|
||||||
|
skill: SkillInterface
|
||||||
|
enemy: Optional[EnemyInterface] = None
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls,
|
||||||
|
character: CharacterInterface,
|
||||||
|
weapon: WeaponInterface,
|
||||||
|
skill: SkillInterface,
|
||||||
|
buffs: Optional[List[BuffInterface]] = None,
|
||||||
|
artifacts: Optional[List[Artifact]] = None,
|
||||||
|
artifact_config: Optional[dict] = None,
|
||||||
|
enemy: Optional[EnemyInterface] = None,
|
||||||
|
) -> "CalculatorConfig": ...
|
@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Dict, Tuple, List
|
from typing import Dict, Tuple, List
|
||||||
|
|
||||||
from genshin_artifact_core import (
|
from python_genshin_artifact import (
|
||||||
gen_character_meta_as_json,
|
gen_character_meta_as_json,
|
||||||
gen_weapon_meta_as_json,
|
gen_weapon_meta_as_json,
|
||||||
gen_artifact_meta_as_json,
|
gen_artifact_meta_as_json,
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
from json import JSONDecodeError
|
|
||||||
|
|
||||||
from genshin_artifact_core import (
|
|
||||||
get_damage_analysis as _get_damage_analysis,
|
|
||||||
get_transformative_damage as _get_transformative_damage,
|
|
||||||
)
|
|
||||||
|
|
||||||
from python_genshin_artifact.error import JsonParseException
|
|
||||||
from python_genshin_artifact.models.calculator import CalculatorConfig
|
|
||||||
from python_genshin_artifact.models.damage.analysis import DamageAnalysis, TransformativeDamage
|
|
||||||
|
|
||||||
|
|
||||||
def get_damage_analysis(value: CalculatorConfig) -> DamageAnalysis:
|
|
||||||
try:
|
|
||||||
ret = _get_damage_analysis(value.json())
|
|
||||||
except JSONDecodeError as exc:
|
|
||||||
raise JsonParseException from exc
|
|
||||||
return DamageAnalysis.parse_raw(ret)
|
|
||||||
|
|
||||||
|
|
||||||
def get_transformative_damage(value: CalculatorConfig) -> TransformativeDamage:
|
|
||||||
try:
|
|
||||||
ret = _get_transformative_damage(value.json())
|
|
||||||
except JSONDecodeError as exc:
|
|
||||||
raise JsonParseException from exc
|
|
||||||
return TransformativeDamage.parse_raw(ret)
|
|
@ -4,12 +4,10 @@ from typing import Tuple, List, Optional
|
|||||||
from python_genshin_artifact.enka.artifacts import artifacts_name_map, equip_type_map
|
from python_genshin_artifact.enka.artifacts import artifacts_name_map, equip_type_map
|
||||||
from python_genshin_artifact.enka.assets import Assets
|
from python_genshin_artifact.enka.assets import Assets
|
||||||
from python_genshin_artifact.enka.characters import characters_map
|
from python_genshin_artifact.enka.characters import characters_map
|
||||||
from python_genshin_artifact.enka.fight import fight_map, toFloat
|
from python_genshin_artifact.enka.fight import fight_map, to_float
|
||||||
from python_genshin_artifact.enka.weapon import weapon_name_map
|
from python_genshin_artifact.enka.weapon import weapon_name_map
|
||||||
from python_genshin_artifact.error import EnkaParseException
|
from python_genshin_artifact.error import EnkaParseException
|
||||||
from python_genshin_artifact.models.artifact import ArtifactInfo
|
from python_genshin_artifact import Artifact, CharacterInterface, WeaponInterface
|
||||||
from python_genshin_artifact.models.characterInfo import CharacterInfo
|
|
||||||
from python_genshin_artifact.models.weapon import WeaponInfo
|
|
||||||
|
|
||||||
assets = Assets()
|
assets = Assets()
|
||||||
|
|
||||||
@ -23,7 +21,7 @@ def is_ascend(level: int, promote_level: int) -> bool:
|
|||||||
return promote_level >= expected_promote_level
|
return promote_level >= expected_promote_level
|
||||||
|
|
||||||
|
|
||||||
def enka_parser(data: dict, avatar_id: int) -> Tuple[CharacterInfo, WeaponInfo, List[ArtifactInfo]]:
|
def enka_parser(data: dict, avatar_id: int) -> Tuple[CharacterInterface, WeaponInterface, List[Artifact]]:
|
||||||
character_info = assets.get_character(avatar_id)
|
character_info = assets.get_character(avatar_id)
|
||||||
if character_info is None:
|
if character_info is None:
|
||||||
raise EnkaParseException(f"avatarId={avatar_id} is not found in assets")
|
raise EnkaParseException(f"avatarId={avatar_id} is not found in assets")
|
||||||
@ -51,7 +49,7 @@ def enka_parser(data: dict, avatar_id: int) -> Tuple[CharacterInfo, WeaponInfo,
|
|||||||
if _value.endswith("02"):
|
if _value.endswith("02"):
|
||||||
skill_info["skill3"] += 3
|
skill_info["skill3"] += 3
|
||||||
character_name = characters_map.get(avatar_id)
|
character_name = characters_map.get(avatar_id)
|
||||||
character = CharacterInfo(
|
character = CharacterInterface(
|
||||||
name=character_name,
|
name=character_name,
|
||||||
level=level,
|
level=level,
|
||||||
constellation=len(talent_id_list),
|
constellation=len(talent_id_list),
|
||||||
@ -66,9 +64,9 @@ def enka_parser(data: dict, avatar_id: int) -> Tuple[CharacterInfo, WeaponInfo,
|
|||||||
return character, weapon, artifacts
|
return character, weapon, artifacts
|
||||||
|
|
||||||
|
|
||||||
def de_equip_list(equip_list: list[dict]) -> Tuple[WeaponInfo, List[ArtifactInfo]]:
|
def de_equip_list(equip_list: list[dict]) -> Tuple[WeaponInterface, List[Artifact]]:
|
||||||
weapon: Optional[WeaponInfo] = None
|
weapon: Optional[WeaponInterface] = None
|
||||||
artifacts: List[ArtifactInfo] = []
|
artifacts: List[Artifact] = []
|
||||||
for _equip in equip_list:
|
for _equip in equip_list:
|
||||||
_weapon = _equip.get("weapon")
|
_weapon = _equip.get("weapon")
|
||||||
_reliquary = _equip.get("reliquary")
|
_reliquary = _equip.get("reliquary")
|
||||||
@ -86,18 +84,18 @@ def de_equip_list(equip_list: list[dict]) -> Tuple[WeaponInfo, List[ArtifactInfo
|
|||||||
_reliquary_main_stat = _flat["reliquaryMainstat"]
|
_reliquary_main_stat = _flat["reliquaryMainstat"]
|
||||||
_main_prop_id = _reliquary_main_stat["mainPropId"]
|
_main_prop_id = _reliquary_main_stat["mainPropId"]
|
||||||
stat_name = fight_map[_main_prop_id]
|
stat_name = fight_map[_main_prop_id]
|
||||||
stat_value = toFloat(_main_prop_id, _reliquary_main_stat["statValue"])
|
stat_value = to_float(_main_prop_id, _reliquary_main_stat["statValue"])
|
||||||
_main_stat = (stat_name, stat_value)
|
_main_stat = (stat_name, stat_value)
|
||||||
for _reliquary_sub_stats in _flat["reliquarySubstats"]:
|
for _reliquary_sub_stats in _flat["reliquarySubstats"]:
|
||||||
_append_prop_id = _reliquary_sub_stats["appendPropId"]
|
_append_prop_id = _reliquary_sub_stats["appendPropId"]
|
||||||
stat_name = fight_map[_append_prop_id]
|
stat_name = fight_map[_append_prop_id]
|
||||||
stat_value = toFloat(_append_prop_id, _reliquary_sub_stats["statValue"])
|
stat_value = to_float(_append_prop_id, _reliquary_sub_stats["statValue"])
|
||||||
_sub_stats = (stat_name, stat_value)
|
_sub_stats = (stat_name, stat_value)
|
||||||
sub_stats.append(_sub_stats)
|
sub_stats.append(_sub_stats)
|
||||||
slot = equip_type_map[_flat["equipType"]]
|
slot = equip_type_map[_flat["equipType"]]
|
||||||
star = _flat["rankLevel"]
|
star = _flat["rankLevel"]
|
||||||
artifacts.append(
|
artifacts.append(
|
||||||
ArtifactInfo(
|
Artifact(
|
||||||
set_name=set_name,
|
set_name=set_name,
|
||||||
id=artifact_id,
|
id=artifact_id,
|
||||||
level=_level,
|
level=_level,
|
||||||
@ -118,5 +116,5 @@ def de_equip_list(equip_list: list[dict]) -> Tuple[WeaponInfo, List[ArtifactInfo
|
|||||||
ascend = False
|
ascend = False
|
||||||
if _promote_level is not None:
|
if _promote_level is not None:
|
||||||
ascend = is_ascend(_level, _promote_level)
|
ascend = is_ascend(_level, _promote_level)
|
||||||
weapon = WeaponInfo(level=_level, refine=refinement_level, ascend=ascend, name=weapon_name)
|
weapon = WeaponInterface(level=_level, refine=refinement_level, ascend=ascend, name=weapon_name)
|
||||||
return weapon, artifacts
|
return weapon, artifacts
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, List
|
from typing import Dict, Set
|
||||||
|
|
||||||
fight_map: Dict[str, str] = {
|
fight_map: Dict[str, str] = {
|
||||||
"FIGHT_PROP_ATTACK": "ATKFixed",
|
"FIGHT_PROP_ATTACK": "ATKFixed",
|
||||||
@ -22,7 +22,7 @@ fight_map: Dict[str, str] = {
|
|||||||
"FIGHT_PROP_GRASS_ADD_HURT": "DendroBonus",
|
"FIGHT_PROP_GRASS_ADD_HURT": "DendroBonus",
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed: List[str] = {
|
fixed: Set[str] = {
|
||||||
"FIGHT_PROP_ATTACK",
|
"FIGHT_PROP_ATTACK",
|
||||||
"FIGHT_PROP_DEFENSE",
|
"FIGHT_PROP_DEFENSE",
|
||||||
"FIGHT_PROP_HP",
|
"FIGHT_PROP_HP",
|
||||||
@ -30,5 +30,5 @@ fixed: List[str] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def toFloat(prop_id: str, pc: float) -> float:
|
def to_float(prop_id: str, pc: float) -> float:
|
||||||
return pc if prop_id in fixed else (pc / 100)
|
return pc if prop_id in fixed else (pc / 100)
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class ArtifactInfo(BaseModel):
|
|
||||||
set_name: str
|
|
||||||
slot: str
|
|
||||||
level: int
|
|
||||||
star: int
|
|
||||||
sub_stats: List[Tuple[str, float]]
|
|
||||||
main_stat: Tuple[str, float]
|
|
||||||
id: int
|
|
@ -1,7 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class BuffInfo(BaseModel):
|
|
||||||
name: str
|
|
||||||
config: str
|
|
||||||
star: int
|
|
@ -1,20 +0,0 @@
|
|||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from python_genshin_artifact.models.artifact import ArtifactInfo
|
|
||||||
from python_genshin_artifact.models.buff import BuffInfo
|
|
||||||
from python_genshin_artifact.models.characterInfo import CharacterInfo
|
|
||||||
from python_genshin_artifact.models.enemy import EnemyInfo
|
|
||||||
from python_genshin_artifact.models.skill import SkillInfo
|
|
||||||
from python_genshin_artifact.models.weapon import WeaponInfo
|
|
||||||
|
|
||||||
|
|
||||||
class CalculatorConfig(BaseModel):
|
|
||||||
character: CharacterInfo
|
|
||||||
weapon: WeaponInfo
|
|
||||||
buffs: List[BuffInfo] = []
|
|
||||||
artifacts: List[ArtifactInfo] = []
|
|
||||||
artifact_config: Optional[dict] = None
|
|
||||||
skill: SkillInfo
|
|
||||||
enemy: Optional[EnemyInfo] = None
|
|
@ -1,14 +0,0 @@
|
|||||||
from typing import Union
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class CharacterInfo(BaseModel):
|
|
||||||
name: str
|
|
||||||
level: int
|
|
||||||
ascend: bool
|
|
||||||
constellation: int
|
|
||||||
skill1: int
|
|
||||||
skill2: int
|
|
||||||
skill3: int
|
|
||||||
params: Union[str, dict] = "NoConfig"
|
|
@ -1,56 +0,0 @@
|
|||||||
from typing import Dict, Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
from python_genshin_artifact.models.damage.result import DamageResult
|
|
||||||
|
|
||||||
|
|
||||||
class DamageAnalysis(BaseModel):
|
|
||||||
atk: Dict[str, float]
|
|
||||||
atk_ratio: Dict[str, float]
|
|
||||||
hp: Dict[str, float]
|
|
||||||
hp_ratio: Dict[str, float]
|
|
||||||
def_: Dict[str, float] = Field(alias="def")
|
|
||||||
def_ratio: Dict[str, float]
|
|
||||||
em: Dict[str, float]
|
|
||||||
em_ratio: Dict[str, float]
|
|
||||||
extra_damage: Dict[str, float]
|
|
||||||
bonus: Dict[str, float]
|
|
||||||
critical: Dict[str, float]
|
|
||||||
critical_damage: Dict[str, float]
|
|
||||||
melt_enhance: Dict[str, float]
|
|
||||||
vaporize_enhance: Dict[str, float]
|
|
||||||
healing_bonus: Dict[str, float]
|
|
||||||
shield_strength: Dict[str, float]
|
|
||||||
spread_compose: Dict[str, float]
|
|
||||||
aggravate_compose: Dict[str, float]
|
|
||||||
|
|
||||||
def_minus: Dict[str, float]
|
|
||||||
def_penetration: Dict[str, float]
|
|
||||||
res_minus: Dict[str, float]
|
|
||||||
|
|
||||||
element: str
|
|
||||||
is_heal: bool
|
|
||||||
is_shield: bool
|
|
||||||
|
|
||||||
normal: DamageResult
|
|
||||||
melt: Optional[DamageResult]
|
|
||||||
vaporize: Optional[DamageResult]
|
|
||||||
spread: Optional[DamageResult]
|
|
||||||
aggravate: Optional[DamageResult]
|
|
||||||
|
|
||||||
|
|
||||||
class TransformativeDamage(BaseModel):
|
|
||||||
swirl_cryo: float
|
|
||||||
swirl_hydro: float
|
|
||||||
swirl_pyro: float
|
|
||||||
swirl_electro: float
|
|
||||||
overload: float
|
|
||||||
electro_charged: float
|
|
||||||
shatter: float
|
|
||||||
super_conduct: float = Field(alias="superconduct")
|
|
||||||
bloom: float
|
|
||||||
hyper_bloom: float = Field(alias="hyperbloom")
|
|
||||||
burgeon: float
|
|
||||||
burning: float
|
|
||||||
crystallize: float
|
|
@ -1,10 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class DamageResult(BaseModel):
|
|
||||||
critical: float
|
|
||||||
non_critical: float
|
|
||||||
expectation: float
|
|
||||||
|
|
||||||
is_heal: bool
|
|
||||||
is_shield: bool
|
|
@ -1,13 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class EnemyInfo(BaseModel):
|
|
||||||
level: int
|
|
||||||
electro_res: float
|
|
||||||
pyro_res: float
|
|
||||||
hydro_res: float
|
|
||||||
cryo_res: float
|
|
||||||
geo_res: float
|
|
||||||
anemo_res: float
|
|
||||||
dendro_res: float
|
|
||||||
physical_res: float
|
|
@ -1,7 +0,0 @@
|
|||||||
from typing import Union
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class SkillInfo(BaseModel):
|
|
||||||
index: int
|
|
||||||
config: Union[str, dict] = "NoConfig"
|
|
@ -1,11 +0,0 @@
|
|||||||
from typing import Union
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class WeaponInfo(BaseModel):
|
|
||||||
name: str
|
|
||||||
level: int
|
|
||||||
ascend: bool
|
|
||||||
refine: int
|
|
||||||
params: Union[str, dict] = "NoConfig"
|
|
@ -1,18 +0,0 @@
|
|||||||
import json
|
|
||||||
from json import JSONDecodeError
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from python_genshin_artifact.calculator import get_damage_analysis
|
|
||||||
from python_genshin_artifact.models.calculator import CalculatorConfig
|
|
||||||
|
|
||||||
|
|
||||||
TEST_DATA_DIR = Path(__file__).resolve().parent / "input"
|
|
||||||
|
|
||||||
|
|
||||||
def test_damage_analysis_exception():
|
|
||||||
"""Test damage analysis raises expected exception on invalid input"""
|
|
||||||
with open(TEST_DATA_DIR / "invalid_enka_name.json") as f:
|
|
||||||
config = CalculatorConfig(**json.load(f))
|
|
||||||
with pytest.raises(JSONDecodeError, match="unknown variant"):
|
|
||||||
get_damage_analysis(config)
|
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "genshin_artifact_core"
|
|
||||||
version = "0.1.4"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "genshin_artifact_core"
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
pyo3 = { version = "0.19.2", features = ["extension-module", "anyhow"] }
|
|
||||||
mona_wasm = { path = "../genshin_artifact/mona_wasm" }
|
|
||||||
mona = { path = "../genshin_artifact/mona_core" }
|
|
||||||
mona_generate = { path = "../genshin_artifact/mona_generate" }
|
|
||||||
num = "0.4"
|
|
||||||
serde="1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
anyhow = "1.0.75"
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod generate;
|
|
||||||
pub mod wasm;
|
|
@ -1,149 +0,0 @@
|
|||||||
use crate::JSONDecodeError;
|
|
||||||
use anyhow::Context;
|
|
||||||
use mona::artifacts::effect_config::{ArtifactConfigInterface, ArtifactEffectConfig};
|
|
||||||
use mona::artifacts::{Artifact, ArtifactList};
|
|
||||||
use mona::attribute::{AttributeUtils, ComplicatedAttributeGraph, SimpleAttributeGraph2};
|
|
||||||
use mona::buffs::Buff;
|
|
||||||
use mona::character::Character;
|
|
||||||
use mona::damage::transformative_damage::TransformativeDamage;
|
|
||||||
use mona::damage::DamageContext;
|
|
||||||
use mona::enemies::Enemy;
|
|
||||||
use mona::utils;
|
|
||||||
use mona_wasm::applications::common::{
|
|
||||||
BuffInterface, CharacterInterface, EnemyInterface, SkillInterface, WeaponInterface,
|
|
||||||
};
|
|
||||||
use mona_wasm::CalculatorInterface;
|
|
||||||
use pyo3::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct CalculatorConfigInterface {
|
|
||||||
pub character: CharacterInterface,
|
|
||||||
pub weapon: WeaponInterface,
|
|
||||||
pub buffs: Vec<BuffInterface>,
|
|
||||||
pub artifacts: Vec<Artifact>,
|
|
||||||
pub artifact_config: Option<ArtifactConfigInterface>,
|
|
||||||
pub skill: SkillInterface,
|
|
||||||
pub enemy: Option<EnemyInterface>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct TransformativeDamageBridge {
|
|
||||||
swirl_cryo: f64,
|
|
||||||
swirl_hydro: f64,
|
|
||||||
swirl_pyro: f64,
|
|
||||||
swirl_electro: f64,
|
|
||||||
overload: f64,
|
|
||||||
electro_charged: f64,
|
|
||||||
shatter: f64,
|
|
||||||
superconduct: f64,
|
|
||||||
bloom: f64,
|
|
||||||
hyperbloom: f64,
|
|
||||||
burgeon: f64,
|
|
||||||
burning: f64,
|
|
||||||
crystallize: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TransformativeDamage> for TransformativeDamageBridge {
|
|
||||||
fn from(damage: TransformativeDamage) -> Self {
|
|
||||||
Self {
|
|
||||||
swirl_cryo: damage.swirl_cryo,
|
|
||||||
swirl_hydro: damage.swirl_hydro,
|
|
||||||
swirl_pyro: damage.swirl_pyro,
|
|
||||||
swirl_electro: damage.swirl_electro,
|
|
||||||
overload: damage.overload,
|
|
||||||
electro_charged: damage.electro_charged,
|
|
||||||
shatter: damage.shatter,
|
|
||||||
superconduct: damage.superconduct,
|
|
||||||
bloom: damage.bloom,
|
|
||||||
hyperbloom: damage.hyperbloom,
|
|
||||||
burgeon: damage.burgeon,
|
|
||||||
burning: damage.burning,
|
|
||||||
crystallize: damage.crystallize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pyfunction]
|
|
||||||
pub fn get_damage_analysis(value_str: String) -> PyResult<String> {
|
|
||||||
let input: CalculatorConfigInterface = serde_json::from_str(&value_str)
|
|
||||||
.map_err(|e| JSONDecodeError::new_err((e.to_string(), value_str.to_owned(), 0)))?;
|
|
||||||
|
|
||||||
let character: Character<ComplicatedAttributeGraph> = input.character.to_character();
|
|
||||||
let weapon = input.weapon.to_weapon(&character);
|
|
||||||
|
|
||||||
let buffs: Vec<Box<dyn Buff<ComplicatedAttributeGraph>>> =
|
|
||||||
input.buffs.iter().map(|x| x.to_buff()).collect();
|
|
||||||
let artifacts: Vec<&Artifact> = input.artifacts.iter().collect();
|
|
||||||
|
|
||||||
let artifact_config = match input.artifact_config {
|
|
||||||
Some(x) => x.to_config(),
|
|
||||||
None => ArtifactEffectConfig::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let enemy = if let Some(x) = input.enemy {
|
|
||||||
x.to_enemy()
|
|
||||||
} else {
|
|
||||||
Enemy::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = CalculatorInterface::get_damage_analysis_internal(
|
|
||||||
&character,
|
|
||||||
&weapon,
|
|
||||||
&buffs,
|
|
||||||
artifacts,
|
|
||||||
&artifact_config,
|
|
||||||
input.skill.index,
|
|
||||||
&input.skill.config,
|
|
||||||
&enemy,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let result_str = serde_json::to_string(&result).context("Failed to serialize json")?;
|
|
||||||
Ok(result_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pyfunction]
|
|
||||||
pub fn get_transformative_damage(value_str: String) -> PyResult<String> {
|
|
||||||
utils::set_panic_hook();
|
|
||||||
let input: CalculatorConfigInterface = serde_json::from_str(&value_str)
|
|
||||||
.map_err(|e| JSONDecodeError::new_err((e.to_string(), value_str.to_owned(), 0)))?;
|
|
||||||
|
|
||||||
let character: Character<SimpleAttributeGraph2> = input.character.to_character();
|
|
||||||
let weapon = input.weapon.to_weapon(&character);
|
|
||||||
|
|
||||||
let buffs: Vec<_> = input.buffs.iter().map(|x| x.to_buff()).collect();
|
|
||||||
let artifacts: Vec<&Artifact> = input.artifacts.iter().collect();
|
|
||||||
|
|
||||||
let artifact_config = match input.artifact_config {
|
|
||||||
Some(x) => x.to_config(),
|
|
||||||
None => ArtifactEffectConfig::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let enemy = if let Some(x) = input.enemy {
|
|
||||||
x.to_enemy()
|
|
||||||
} else {
|
|
||||||
Enemy::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let attribute = AttributeUtils::create_attribute_from_big_config(
|
|
||||||
&ArtifactList {
|
|
||||||
artifacts: &artifacts,
|
|
||||||
},
|
|
||||||
&artifact_config,
|
|
||||||
&character,
|
|
||||||
&weapon,
|
|
||||||
&buffs,
|
|
||||||
);
|
|
||||||
|
|
||||||
let context: DamageContext<'_, SimpleAttributeGraph2> = DamageContext {
|
|
||||||
character_common_data: &character.common_data,
|
|
||||||
enemy: &enemy,
|
|
||||||
attribute: &attribute,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = context.transformative();
|
|
||||||
let bridge = TransformativeDamageBridge::from(result);
|
|
||||||
let result_str = serde_json::to_string(&bridge).context("Failed to serialize json")?;
|
|
||||||
Ok(result_str)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
extern crate core;
|
|
||||||
mod applications;
|
|
||||||
|
|
||||||
use applications::generate::artifact::gen_artifact_meta_as_json;
|
|
||||||
use applications::generate::character::gen_character_meta_as_json;
|
|
||||||
use applications::generate::locale::gen_generate_locale_as_json;
|
|
||||||
use applications::generate::weapon::gen_weapon_meta_as_json;
|
|
||||||
use applications::wasm::{get_damage_analysis, get_transformative_damage};
|
|
||||||
use pyo3::import_exception;
|
|
||||||
use pyo3::prelude::*;
|
|
||||||
|
|
||||||
import_exception!(json, JSONDecodeError);
|
|
||||||
|
|
||||||
#[pymodule]
|
|
||||||
fn genshin_artifact_core(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
|
||||||
m.add("JSONDecodeError", py.get_type::<JSONDecodeError>())?;
|
|
||||||
m.add_function(wrap_pyfunction!(get_damage_analysis, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(get_transformative_damage, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(gen_character_meta_as_json, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(gen_weapon_meta_as_json, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(gen_artifact_meta_as_json, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(gen_generate_locale_as_json, m)?)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
169
src/applications/analysis.rs
Normal file
169
src/applications/analysis.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use pyo3::exceptions::PyValueError;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
use mona::artifacts::effect_config::{ArtifactConfigInterface, ArtifactEffectConfig};
|
||||||
|
use mona::artifacts::{Artifact, ArtifactList};
|
||||||
|
use mona::attribute::{AttributeUtils, ComplicatedAttributeGraph, SimpleAttributeGraph2};
|
||||||
|
use mona::buffs::Buff;
|
||||||
|
use mona::character::Character;
|
||||||
|
use mona::damage::DamageContext;
|
||||||
|
use mona::enemies::Enemy;
|
||||||
|
use mona_wasm::applications::common::{
|
||||||
|
BuffInterface, CharacterInterface, SkillInterface, WeaponInterface,
|
||||||
|
};
|
||||||
|
use mona_wasm::CalculatorInterface;
|
||||||
|
use pythonize::depythonize;
|
||||||
|
|
||||||
|
use crate::applications::input::calculator::PyCalculatorConfig;
|
||||||
|
use crate::applications::output::damage_analysis::PyDamageAnalysis;
|
||||||
|
use crate::applications::output::transformative_damage::PyTransformativeDamage;
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
pub fn get_damage_analysis(calculator_config: PyCalculatorConfig) -> PyResult<PyDamageAnalysis> {
|
||||||
|
let character: CharacterInterface = calculator_config
|
||||||
|
.character
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e: anyhow::Error| PyValueError::new_err(e.to_string()))?;
|
||||||
|
let character: Character<ComplicatedAttributeGraph> = character.to_character();
|
||||||
|
|
||||||
|
let weapon: WeaponInterface = calculator_config
|
||||||
|
.weapon
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e: anyhow::Error| PyValueError::new_err(e.to_string()))?;
|
||||||
|
let weapon = weapon.to_weapon(&character);
|
||||||
|
|
||||||
|
let artifacts = calculator_config
|
||||||
|
.artifacts
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| -> Result<Artifact, anyhow::Error> { a.try_into() })
|
||||||
|
.collect::<Result<Vec<Artifact>, anyhow::Error>>()?;
|
||||||
|
let artifacts = artifacts.iter().collect::<Vec<&Artifact>>();
|
||||||
|
|
||||||
|
let artifact_config_interface: Option<ArtifactConfigInterface> =
|
||||||
|
if let Some(artifact_config) = calculator_config.artifact_config {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
depythonize(artifact_config.as_ref(py))
|
||||||
|
.map_err(|err| anyhow!("Failed to deserialize artifact config: {}", err))
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let artifact_config =
|
||||||
|
if let Some(artifact_config) = artifact_config_interface {
|
||||||
|
artifact_config.to_config()
|
||||||
|
} else {
|
||||||
|
ArtifactEffectConfig::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let buffs = calculator_config
|
||||||
|
.buffs
|
||||||
|
.into_iter()
|
||||||
|
.map(|buff| -> Result<BuffInterface, anyhow::Error> { buff.try_into() })
|
||||||
|
.map(|buff| match buff {
|
||||||
|
Ok(b) => Ok(b.to_buff()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Box<dyn Buff<ComplicatedAttributeGraph>>>, anyhow::Error>>()?;
|
||||||
|
|
||||||
|
let skill_config: SkillInterface = calculator_config
|
||||||
|
.skill
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e: anyhow::Error| PyValueError::new_err(e.to_string()))?;
|
||||||
|
|
||||||
|
let enemy: Enemy = if let Some(enemy) = calculator_config.enemy {
|
||||||
|
enemy.try_into()?
|
||||||
|
} else {
|
||||||
|
Enemy::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = CalculatorInterface::get_damage_analysis_internal(
|
||||||
|
&character,
|
||||||
|
&weapon,
|
||||||
|
&buffs,
|
||||||
|
artifacts,
|
||||||
|
&artifact_config,
|
||||||
|
skill_config.index,
|
||||||
|
&skill_config.config,
|
||||||
|
&enemy,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(PyDamageAnalysis::from(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
pub fn get_transformative_damage(
|
||||||
|
calculator_config: PyCalculatorConfig,
|
||||||
|
) -> PyResult<PyTransformativeDamage> {
|
||||||
|
let character: CharacterInterface = calculator_config
|
||||||
|
.character
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e: anyhow::Error| PyValueError::new_err(e.to_string()))?;
|
||||||
|
let character: Character<SimpleAttributeGraph2> = character.to_character();
|
||||||
|
|
||||||
|
let weapon: WeaponInterface = calculator_config
|
||||||
|
.weapon
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e: anyhow::Error| PyValueError::new_err(e.to_string()))?;
|
||||||
|
let weapon = weapon.to_weapon(&character);
|
||||||
|
|
||||||
|
let artifacts = calculator_config
|
||||||
|
.artifacts
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| -> Result<Artifact, anyhow::Error> { a.try_into() })
|
||||||
|
.collect::<Result<Vec<Artifact>, anyhow::Error>>()?;
|
||||||
|
let artifacts = artifacts.iter().collect::<Vec<&Artifact>>();
|
||||||
|
|
||||||
|
let artifact_config_interface: Option<ArtifactConfigInterface> =
|
||||||
|
if let Some(artifact_config) = calculator_config.artifact_config {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
depythonize(artifact_config.as_ref(py))
|
||||||
|
.map_err(|err| anyhow!("Failed to deserialize artifact config: {}", err))
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let artifact_config =
|
||||||
|
if let Some(artifact_config) = artifact_config_interface {
|
||||||
|
artifact_config.to_config()
|
||||||
|
} else {
|
||||||
|
ArtifactEffectConfig::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let buffs = calculator_config
|
||||||
|
.buffs
|
||||||
|
.into_iter()
|
||||||
|
.map(|buff| -> Result<BuffInterface, anyhow::Error> { buff.try_into() })
|
||||||
|
.map(|buff| match buff {
|
||||||
|
Ok(b) => Ok(b.to_buff()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Box<dyn Buff<SimpleAttributeGraph2>>>, anyhow::Error>>()?;
|
||||||
|
|
||||||
|
let enemy: Enemy = if let Some(enemy) = calculator_config.enemy {
|
||||||
|
enemy.try_into()?
|
||||||
|
} else {
|
||||||
|
Enemy::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let attribute = AttributeUtils::create_attribute_from_big_config(
|
||||||
|
&ArtifactList {
|
||||||
|
artifacts: &artifacts,
|
||||||
|
},
|
||||||
|
&artifact_config,
|
||||||
|
&character,
|
||||||
|
&weapon,
|
||||||
|
&buffs,
|
||||||
|
);
|
||||||
|
|
||||||
|
let context: DamageContext<'_, SimpleAttributeGraph2> = DamageContext {
|
||||||
|
character_common_data: &character.common_data,
|
||||||
|
enemy: &enemy,
|
||||||
|
attribute: &attribute,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = context.transformative();
|
||||||
|
|
||||||
|
Ok(PyTransformativeDamage::from(result))
|
||||||
|
}
|
25
src/applications/errors.rs
Normal file
25
src/applications/errors.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use pyo3::exceptions::PyValueError;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::pyclass;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[pyclass(extends = PyValueError)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ValidationError {
|
||||||
|
#[pyo3(get)]
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl ValidationError {
|
||||||
|
#[new]
|
||||||
|
pub fn new_err(message: String) -> Self {
|
||||||
|
Self { message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ValidationError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "ValidationError: {}", self.message)
|
||||||
|
}
|
||||||
|
}
|
126
src/applications/input/artifact.rs
Normal file
126
src/applications/input/artifact.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use mona::artifacts::{Artifact as MonaArtifact, ArtifactSetName, ArtifactSlotName};
|
||||||
|
use mona::common::StatName;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::{PyDict, PyList, PyString};
|
||||||
|
use pythonize::depythonize;
|
||||||
|
|
||||||
|
#[pyclass(name = "Artifact")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyArtifact {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub set_name: Py<PyString>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub slot: Py<PyString>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub level: i32,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub star: i32,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub sub_stats: Vec<(Py<PyString>, f64)>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub main_stat: (Py<PyString>, f64),
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyArtifact {
|
||||||
|
#[new]
|
||||||
|
pub fn py_new(
|
||||||
|
set_name: Py<PyString>,
|
||||||
|
slot: Py<PyString>,
|
||||||
|
level: i32,
|
||||||
|
star: i32,
|
||||||
|
sub_stats: Vec<(Py<PyString>, f64)>,
|
||||||
|
main_stat: (Py<PyString>, f64),
|
||||||
|
id: u64,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
set_name,
|
||||||
|
slot,
|
||||||
|
level,
|
||||||
|
star,
|
||||||
|
sub_stats,
|
||||||
|
main_stat,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self, py: Python) -> PyResult<String> {
|
||||||
|
let set_name = self.set_name.as_ref(py).to_str()?;
|
||||||
|
let slot = self.slot.as_ref(py).to_str()?;
|
||||||
|
let main_stat = self.main_stat.0.as_ref(py).to_str()?;
|
||||||
|
let main_stat_value = self.main_stat.1;
|
||||||
|
Ok(format!(
|
||||||
|
"PyArtifact(set_name='{}', slot='{}', level={}, star={}, main_stat=({}, {}), id={})",
|
||||||
|
set_name, slot, self.level, self.star, main_stat, main_stat_value, self.id
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
pub fn __dict__(&self, py: Python) -> PyResult<PyObject> {
|
||||||
|
let dict = PyDict::new(py);
|
||||||
|
dict.set_item("set_name", self.set_name.as_ref(py))?;
|
||||||
|
dict.set_item("slot", self.slot.as_ref(py))?;
|
||||||
|
dict.set_item("level", self.level)?;
|
||||||
|
dict.set_item("star", self.star)?;
|
||||||
|
let sub_stats_pylist = PyList::new(py, self.sub_stats.iter().map(|(s, v)| {
|
||||||
|
let stat_str = s.as_ref(py).to_str().unwrap();
|
||||||
|
(stat_str, *v)
|
||||||
|
}));
|
||||||
|
dict.set_item("sub_stats", sub_stats_pylist)?;
|
||||||
|
let main_stat_tuple = (self.main_stat.0.as_ref(py), self.main_stat.1);
|
||||||
|
dict.set_item("main_stat", main_stat_tuple)?;
|
||||||
|
dict.set_item("id", self.id)?;
|
||||||
|
|
||||||
|
Ok(dict.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MonaArtifact> for PyArtifact {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MonaArtifact, Self::Error> {
|
||||||
|
let name: ArtifactSetName = Python::with_gil(|py| {
|
||||||
|
let _string: &PyString = self.set_name.as_ref(py);
|
||||||
|
depythonize(_string)
|
||||||
|
.map_err(|err| anyhow!("Failed to deserialize artifact set name: {}", err))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let slot: ArtifactSlotName = Python::with_gil(|py| {
|
||||||
|
let _string: &PyString = self.slot.as_ref(py);
|
||||||
|
depythonize(_string)
|
||||||
|
.map_err(|err| anyhow!("Failed to deserialize artifact slot name: {}", err))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let main_stat_name: StatName = Python::with_gil(|py| {
|
||||||
|
depythonize(self.main_stat.0.as_ref(py))
|
||||||
|
.map_err(|err| anyhow!("Failed to deserialize main stat name: {}", err))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let sub_stats = Python::with_gil(|py| {
|
||||||
|
self.sub_stats
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
let name: Result<StatName, anyhow::Error> = depythonize(s.0.as_ref(py))
|
||||||
|
.map_err(|err| anyhow!("Failed to deserialize sub stat name: {}", err));
|
||||||
|
match name {
|
||||||
|
Ok(n) => Ok((n, s.1)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<(StatName, f64)>, anyhow::Error>>()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(MonaArtifact {
|
||||||
|
set_name: name,
|
||||||
|
slot,
|
||||||
|
level: self.level,
|
||||||
|
star: self.star,
|
||||||
|
sub_stats,
|
||||||
|
main_stat: (main_stat_name, self.main_stat.1),
|
||||||
|
id: self.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
70
src/applications/input/buff.rs
Normal file
70
src/applications/input/buff.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use mona::buffs::buff_name::BuffName;
|
||||||
|
use mona::buffs::BuffConfig;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pythonize::depythonize;
|
||||||
|
|
||||||
|
use pyo3::types::{PyDict, PyString};
|
||||||
|
|
||||||
|
use mona_wasm::applications::common::BuffInterface as MonaBuffInterface;
|
||||||
|
|
||||||
|
#[pyclass(name = "BuffInterface")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyBuffInterface {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub name: Py<PyString>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub config: Option<Py<PyDict>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyBuffInterface {
|
||||||
|
#[new]
|
||||||
|
pub fn py_new(name: Py<PyString>, config: Option<Py<PyDict>>) -> PyResult<Self> {
|
||||||
|
Ok(Self { name, config })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self, py: Python) -> PyResult<String> {
|
||||||
|
let name = self.name.as_ref(py).to_str()?;
|
||||||
|
let config_repr = match &self.config {
|
||||||
|
Some(config) => config.as_ref(py).repr()?.to_str()?.to_string(),
|
||||||
|
None => "None".to_string(),
|
||||||
|
};
|
||||||
|
Ok(format!("BuffInterface(name={}, config={})", name, config_repr))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
pub fn __dict__(&self, py: Python) -> PyResult<PyObject> {
|
||||||
|
let dict = PyDict::new(py);
|
||||||
|
let name_str = self.name.as_ref(py).to_str()?;
|
||||||
|
dict.set_item("name", name_str)?;
|
||||||
|
if let Some(config) = &self.config {
|
||||||
|
dict.set_item("config", config.as_ref(py))?;
|
||||||
|
} else {
|
||||||
|
dict.set_item("config", py.None())?;
|
||||||
|
}
|
||||||
|
Ok(dict.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MonaBuffInterface> for PyBuffInterface {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MonaBuffInterface, Self::Error> {
|
||||||
|
let name: BuffName = Python::with_gil(|py| {
|
||||||
|
let _string: &PyString = self.name.as_ref(py);
|
||||||
|
depythonize(_string).map_err(|err| anyhow!("Failed to deserialize name: {}", err))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let config: BuffConfig = if let Some(value) = self.config {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let _dict: &PyDict = value.as_ref(py);
|
||||||
|
depythonize(_dict).map_err(|err| anyhow!("Failed to deserialize config: {}", err))
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
BuffConfig::NoConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(MonaBuffInterface { name, config })
|
||||||
|
}
|
||||||
|
}
|
53
src/applications/input/calculator.rs
Normal file
53
src/applications/input/calculator.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use crate::applications::input::artifact::PyArtifact;
|
||||||
|
use crate::applications::input::buff::PyBuffInterface;
|
||||||
|
use crate::applications::input::character::PyCharacterInterface;
|
||||||
|
use crate::applications::input::enemy::PyEnemyInterface;
|
||||||
|
use crate::applications::input::skill::PySkillInterface;
|
||||||
|
use crate::applications::input::weapon::PyWeaponInterface;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
|
||||||
|
#[pyclass(name = "CalculatorConfig")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyCalculatorConfig {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub character: PyCharacterInterface,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub weapon: PyWeaponInterface,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub buffs: Vec<PyBuffInterface>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub artifacts: Vec<PyArtifact>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub artifact_config: Option<Py<PyDict>>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub skill: PySkillInterface,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub enemy: Option<PyEnemyInterface>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyCalculatorConfig {
|
||||||
|
#[new]
|
||||||
|
#[pyo3(signature=(character, weapon, skill, buffs = None, artifacts = None, artifact_config = None, enemy = None))]
|
||||||
|
pub fn py_new(
|
||||||
|
character: PyCharacterInterface,
|
||||||
|
weapon: PyWeaponInterface,
|
||||||
|
skill: PySkillInterface,
|
||||||
|
buffs: Option<Vec<PyBuffInterface>>,
|
||||||
|
artifacts: Option<Vec<PyArtifact>>,
|
||||||
|
artifact_config: Option<Py<PyDict>>,
|
||||||
|
enemy: Option<PyEnemyInterface>,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
character,
|
||||||
|
weapon,
|
||||||
|
buffs: buffs.unwrap_or_default(),
|
||||||
|
artifacts: artifacts.unwrap_or_default(),
|
||||||
|
artifact_config,
|
||||||
|
skill,
|
||||||
|
enemy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
198
src/applications/input/character.rs
Normal file
198
src/applications/input/character.rs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use pythonize::depythonize;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use mona::character::{CharacterConfig, CharacterName};
|
||||||
|
use mona_wasm::applications::common::CharacterInterface as MonaCharacterInterface;
|
||||||
|
|
||||||
|
#[pyclass(name = "CharacterInterface")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyCharacterInterface {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub name: String,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub level: usize,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub ascend: bool,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub constellation: i32,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub skill1: usize,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub skill2: usize,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub skill3: usize,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub params: Option<Py<PyDict>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyCharacterInterface {
|
||||||
|
#[new]
|
||||||
|
pub fn py_new(
|
||||||
|
name: String,
|
||||||
|
level: usize,
|
||||||
|
ascend: bool,
|
||||||
|
constellation: i32,
|
||||||
|
skill1: usize,
|
||||||
|
skill2: usize,
|
||||||
|
skill3: usize,
|
||||||
|
params: Option<Py<PyDict>>,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
level,
|
||||||
|
ascend,
|
||||||
|
constellation,
|
||||||
|
skill1,
|
||||||
|
skill2,
|
||||||
|
skill3,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self) -> PyResult<String> {
|
||||||
|
let params_repr = match &self.params {
|
||||||
|
Some(params) => format!("{:?}", params),
|
||||||
|
None => "None".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"CharacterInterface(name='{}', level={}, ascend={}, constellation={}, skill1={}, skill2={}, skill3={}, params={})",
|
||||||
|
self.name, self.level, self.ascend, self.constellation, self.skill1, self.skill2, self.skill3, params_repr
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __dict__(&self, py: Python) -> PyResult<PyObject> {
|
||||||
|
let dict = PyDict::new(py);
|
||||||
|
|
||||||
|
dict.set_item("name", &self.name)?;
|
||||||
|
dict.set_item("level", self.level)?;
|
||||||
|
dict.set_item("ascend", self.ascend)?;
|
||||||
|
dict.set_item("constellation", self.constellation)?;
|
||||||
|
dict.set_item("skill1", self.skill1)?;
|
||||||
|
dict.set_item("skill2", self.skill2)?;
|
||||||
|
dict.set_item("skill3", self.skill3)?;
|
||||||
|
|
||||||
|
if let Some(params) = &self.params {
|
||||||
|
dict.set_item("params", params)?;
|
||||||
|
} else {
|
||||||
|
dict.set_item("params", py.None())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(dict.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MonaCharacterInterface> for PyCharacterInterface {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MonaCharacterInterface, Self::Error> {
|
||||||
|
let name = CharacterName::from_str(&self.name).context("Failed to deserialize json")?;
|
||||||
|
let params: CharacterConfig = if let Some(value) = self.params {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let _dict: &PyDict = value.as_ref(py);
|
||||||
|
depythonize(_dict).context("Failed to convert PyDict to CharacterConfig")
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
CharacterConfig::NoConfig
|
||||||
|
};
|
||||||
|
Ok(MonaCharacterInterface {
|
||||||
|
name,
|
||||||
|
level: self.level,
|
||||||
|
ascend: self.ascend,
|
||||||
|
constellation: self.constellation,
|
||||||
|
skill1: self.skill1,
|
||||||
|
skill2: self.skill2,
|
||||||
|
skill3: self.skill3,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use mona::attribute::{Attribute, AttributeName, ComplicatedAttributeGraph};
|
||||||
|
use mona::character::Character;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_character_interface() {
|
||||||
|
pyo3::prepare_freethreaded_python();
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let inner_dict = PyDict::new(py);
|
||||||
|
inner_dict.set_item("le_50", "true").unwrap();
|
||||||
|
|
||||||
|
let outer_dict = PyDict::new(py);
|
||||||
|
outer_dict.set_item("HuTao", inner_dict).unwrap();
|
||||||
|
|
||||||
|
let py_character_interface = PyCharacterInterface {
|
||||||
|
name: "HuTao".to_string(),
|
||||||
|
level: 90,
|
||||||
|
ascend: true,
|
||||||
|
constellation: 6,
|
||||||
|
skill1: 12,
|
||||||
|
skill2: 12,
|
||||||
|
skill3: 12,
|
||||||
|
params: Some(Py::from(outer_dict)),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(py_character_interface.name, "HuTao");
|
||||||
|
assert_eq!(py_character_interface.level, 90);
|
||||||
|
assert!(py_character_interface.ascend);
|
||||||
|
assert_eq!(py_character_interface.constellation, 6);
|
||||||
|
assert_eq!(py_character_interface.skill1, 12);
|
||||||
|
assert_eq!(py_character_interface.skill2, 12);
|
||||||
|
assert_eq!(py_character_interface.skill3, 12);
|
||||||
|
|
||||||
|
match &py_character_interface.params {
|
||||||
|
Some(value) => {
|
||||||
|
let py_dict = value.as_ref(py);
|
||||||
|
let hutao_dict = py_dict
|
||||||
|
.get_item("HuTao")
|
||||||
|
.unwrap()
|
||||||
|
.downcast::<PyDict>()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
hutao_dict
|
||||||
|
.get_item("le_50")
|
||||||
|
.unwrap()
|
||||||
|
.extract::<&str>()
|
||||||
|
.unwrap(),
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => panic!("Expected PyDict, got None"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mona_character_interface: MonaCharacterInterface =
|
||||||
|
py_character_interface.try_into().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(mona_character_interface.name, CharacterName::HuTao);
|
||||||
|
assert_eq!(mona_character_interface.level, 90);
|
||||||
|
assert!(mona_character_interface.ascend);
|
||||||
|
assert_eq!(mona_character_interface.constellation, 6);
|
||||||
|
assert_eq!(mona_character_interface.skill1, 12);
|
||||||
|
assert_eq!(mona_character_interface.skill2, 12);
|
||||||
|
assert_eq!(mona_character_interface.skill3, 12);
|
||||||
|
|
||||||
|
let character: Character<ComplicatedAttributeGraph> =
|
||||||
|
mona_character_interface.to_character();
|
||||||
|
assert_eq!(character.common_data.name, CharacterName::HuTao);
|
||||||
|
|
||||||
|
match character.character_effect {
|
||||||
|
Some(effect) => {
|
||||||
|
let mut attribute = ComplicatedAttributeGraph::default();
|
||||||
|
effect.change_attribute(&mut attribute);
|
||||||
|
let value = attribute.get_value(AttributeName::BonusPyro);
|
||||||
|
assert_eq!(value, 0.33);
|
||||||
|
}
|
||||||
|
None => panic!("Expected character.character_effect, got None"),
|
||||||
|
}
|
||||||
|
println!("PyCharacterInterface 测试成功 遥遥领先!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
103
src/applications/input/enemy.rs
Normal file
103
src/applications/input/enemy.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
use mona::enemies::Enemy as MomaEnemy;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
|
||||||
|
#[pyclass(name = "EnemyInterface")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyEnemyInterface {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub level: usize,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub electro_res: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub pyro_res: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub hydro_res: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub cryo_res: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub geo_res: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub anemo_res: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub dendro_res: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub physical_res: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyEnemyInterface {
|
||||||
|
#[new]
|
||||||
|
fn py_new(
|
||||||
|
level: usize,
|
||||||
|
electro_res: f64,
|
||||||
|
pyro_res: f64,
|
||||||
|
hydro_res: f64,
|
||||||
|
cryo_res: f64,
|
||||||
|
geo_res: f64,
|
||||||
|
anemo_res: f64,
|
||||||
|
dendro_res: f64,
|
||||||
|
physical_res: f64,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
level,
|
||||||
|
electro_res,
|
||||||
|
pyro_res,
|
||||||
|
hydro_res,
|
||||||
|
cryo_res,
|
||||||
|
geo_res,
|
||||||
|
anemo_res,
|
||||||
|
dendro_res,
|
||||||
|
physical_res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __repr__(&self) -> PyResult<String> {
|
||||||
|
Ok(format!(
|
||||||
|
"EnemyInterface(level={}, electro_res={}, pyro_res={}, hydro_res={}, cryo_res={}, geo_res={}, anemo_res={}, dendro_res={}, physical_res={})",
|
||||||
|
self.level,
|
||||||
|
self.electro_res,
|
||||||
|
self.pyro_res,
|
||||||
|
self.hydro_res,
|
||||||
|
self.cryo_res,
|
||||||
|
self.geo_res,
|
||||||
|
self.anemo_res,
|
||||||
|
self.dendro_res,
|
||||||
|
self.physical_res,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
pub fn __dict__(&self, py: Python) -> PyResult<PyObject> {
|
||||||
|
let dict = PyDict::new(py);
|
||||||
|
dict.set_item("level", self.level)?;
|
||||||
|
dict.set_item("electro_res", self.electro_res)?;
|
||||||
|
dict.set_item("pyro_res", self.pyro_res)?;
|
||||||
|
dict.set_item("hydro_res", self.hydro_res)?;
|
||||||
|
dict.set_item("cryo_res", self.cryo_res)?;
|
||||||
|
dict.set_item("geo_res", self.geo_res)?;
|
||||||
|
dict.set_item("anemo_res", self.anemo_res)?;
|
||||||
|
dict.set_item("dendro_res", self.dendro_res)?;
|
||||||
|
dict.set_item("physical_res", self.physical_res)?;
|
||||||
|
Ok(dict.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MomaEnemy> for PyEnemyInterface {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MomaEnemy, Self::Error> {
|
||||||
|
Ok(MomaEnemy {
|
||||||
|
level: self.level as i32,
|
||||||
|
electro_res: self.electro_res,
|
||||||
|
pyro_res: self.pyro_res,
|
||||||
|
hydro_res: self.hydro_res,
|
||||||
|
cryo_res: self.cryo_res,
|
||||||
|
geo_res: self.geo_res,
|
||||||
|
anemo_res: self.anemo_res,
|
||||||
|
dendro_res: self.dendro_res,
|
||||||
|
physical_res: self.physical_res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
7
src/applications/input/mod.rs
Normal file
7
src/applications/input/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pub mod artifact;
|
||||||
|
pub mod buff;
|
||||||
|
pub mod calculator;
|
||||||
|
pub mod character;
|
||||||
|
pub mod enemy;
|
||||||
|
pub mod skill;
|
||||||
|
pub mod weapon;
|
59
src/applications/input/skill.rs
Normal file
59
src/applications/input/skill.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use pythonize::depythonize;
|
||||||
|
|
||||||
|
use mona::character::skill_config::CharacterSkillConfig;
|
||||||
|
use mona_wasm::applications::common::SkillInterface as MonaSkillInterface;
|
||||||
|
|
||||||
|
#[pyclass(name = "SkillInterface")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PySkillInterface {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub index: usize,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub config: Option<Py<PyDict>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PySkillInterface {
|
||||||
|
#[new]
|
||||||
|
fn new(index: usize, config: Option<Py<PyDict>>) -> PyResult<Self> {
|
||||||
|
Ok(Self { index, config })
|
||||||
|
}
|
||||||
|
pub fn __repr__(&self) -> PyResult<String> {
|
||||||
|
Ok(format!("SkillInterface(index: {}, config: {:?})", self.index, self.config))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
pub fn __dict__(&self, py: Python) -> PyResult<PyObject> {
|
||||||
|
let dict = PyDict::new(py);
|
||||||
|
dict.set_item("index", self.index)?;
|
||||||
|
if let Some(config) = &self.config {
|
||||||
|
dict.set_item("config", config.as_ref(py))?;
|
||||||
|
} else {
|
||||||
|
dict.set_item("config", py.None())?;
|
||||||
|
}
|
||||||
|
Ok(dict.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MonaSkillInterface> for PySkillInterface {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MonaSkillInterface, Self::Error> {
|
||||||
|
let config: CharacterSkillConfig = if let Some(value) = self.config {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let _dict: &PyDict = value.as_ref(py);
|
||||||
|
depythonize(_dict).context("Failed to convert PyDict to CharacterConfig")
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
CharacterSkillConfig::NoConfig
|
||||||
|
};
|
||||||
|
Ok(MonaSkillInterface {
|
||||||
|
index: self.index,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
201
src/applications/input/weapon.rs
Normal file
201
src/applications/input/weapon.rs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use mona::weapon::{WeaponConfig, WeaponName};
|
||||||
|
use mona_wasm::applications::common::WeaponInterface as MonaWeaponInterface;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::{PyDict, PyString};
|
||||||
|
use pythonize::depythonize;
|
||||||
|
|
||||||
|
#[pyclass(name = "WeaponInterface")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyWeaponInterface {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub name: Py<PyString>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub level: i32,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub ascend: bool,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub refine: i32,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub params: Option<Py<PyDict>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyWeaponInterface {
|
||||||
|
#[new]
|
||||||
|
pub fn py_new(
|
||||||
|
name: Py<PyString>,
|
||||||
|
level: i32,
|
||||||
|
ascend: bool,
|
||||||
|
refine: i32,
|
||||||
|
params: Option<Py<PyDict>>,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
level,
|
||||||
|
ascend,
|
||||||
|
refine,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn __repr__(&self, py: Python) -> PyResult<String> {
|
||||||
|
let name = self.name.as_ref(py).to_str()?;
|
||||||
|
let params_repr = match &self.params {
|
||||||
|
Some(params) => params.as_ref(py).repr()?.to_str()?.to_string(),
|
||||||
|
None => "None".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"WeaponInterface(name='{}', level={}, ascend={}, refine={}, params={})",
|
||||||
|
name, self.level, self.ascend, self.refine, params_repr
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
pub fn __dict__(&self, py: Python) -> PyResult<PyObject> {
|
||||||
|
let dict = PyDict::new(py);
|
||||||
|
dict.set_item("name", self.name.as_ref(py))?;
|
||||||
|
dict.set_item("level", self.level)?;
|
||||||
|
dict.set_item("ascend", self.ascend)?;
|
||||||
|
dict.set_item("refine", self.refine)?;
|
||||||
|
if let Some(params) = &self.params {
|
||||||
|
dict.set_item("params", params.as_ref(py))?;
|
||||||
|
} else {
|
||||||
|
dict.set_item("params", py.None())?;
|
||||||
|
}
|
||||||
|
Ok(dict.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<MonaWeaponInterface> for PyWeaponInterface {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<MonaWeaponInterface, Self::Error> {
|
||||||
|
let name: WeaponName = Python::with_gil(|py| {
|
||||||
|
let _string: &PyString = self.name.as_ref(py);
|
||||||
|
depythonize(_string).map_err(|err| anyhow!("Failed to deserialize name: {}", err))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let params: WeaponConfig = if let Some(value) = self.params {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let _dict: &PyDict = value.as_ref(py);
|
||||||
|
depythonize(_dict).map_err(|err| anyhow!("Failed to deserialize params: {}", err))
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
WeaponConfig::NoConfig
|
||||||
|
};
|
||||||
|
Ok(MonaWeaponInterface {
|
||||||
|
name,
|
||||||
|
level: self.level,
|
||||||
|
ascend: self.ascend,
|
||||||
|
refine: self.refine,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use mona::attribute::ComplicatedAttributeGraph;
|
||||||
|
use mona::character::{Character, CharacterConfig, CharacterName};
|
||||||
|
use mona::weapon::Weapon;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_weapon_interface() {
|
||||||
|
pyo3::prepare_freethreaded_python();
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let inner_dict = PyDict::new(py);
|
||||||
|
inner_dict.set_item("be50_rate", 1.0).unwrap();
|
||||||
|
|
||||||
|
let params_dict = PyDict::new(py);
|
||||||
|
params_dict.set_item("StaffOfHoma", inner_dict).unwrap();
|
||||||
|
|
||||||
|
let name = PyString::new(py, "StaffOfHoma");
|
||||||
|
|
||||||
|
let py_weapon_interface = PyWeaponInterface {
|
||||||
|
name: Py::from(name),
|
||||||
|
level: 90,
|
||||||
|
ascend: true,
|
||||||
|
refine: 5,
|
||||||
|
params: Some(Py::from(params_dict)),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
py_weapon_interface.name.as_ref(py).to_string(),
|
||||||
|
"StaffOfHoma"
|
||||||
|
);
|
||||||
|
assert_eq!(py_weapon_interface.level, 90);
|
||||||
|
assert!(py_weapon_interface.ascend);
|
||||||
|
assert_eq!(py_weapon_interface.refine, 5);
|
||||||
|
|
||||||
|
match &py_weapon_interface.params {
|
||||||
|
Some(value) => {
|
||||||
|
let py_dict = value.as_ref(py);
|
||||||
|
let params_dict = py_dict
|
||||||
|
.get_item("StaffOfHoma")
|
||||||
|
.unwrap()
|
||||||
|
.downcast::<PyDict>()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
params_dict
|
||||||
|
.get_item("be50_rate")
|
||||||
|
.unwrap()
|
||||||
|
.extract::<f64>()
|
||||||
|
.unwrap(),
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => panic!("Expected PyDict, got None"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mona_weapon_interface: MonaWeaponInterface =
|
||||||
|
py_weapon_interface.try_into().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(mona_weapon_interface.name, WeaponName::StaffOfHoma);
|
||||||
|
assert_eq!(mona_weapon_interface.level, 90);
|
||||||
|
assert!(mona_weapon_interface.ascend);
|
||||||
|
assert_eq!(mona_weapon_interface.refine, 5);
|
||||||
|
|
||||||
|
let character: Character<ComplicatedAttributeGraph> = Character::new(
|
||||||
|
CharacterName::HuTao,
|
||||||
|
90,
|
||||||
|
true,
|
||||||
|
6,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
&CharacterConfig::HuTao { le_50: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
let weapon: Weapon<ComplicatedAttributeGraph> =
|
||||||
|
mona_weapon_interface.to_weapon(&character);
|
||||||
|
|
||||||
|
assert_eq!(weapon.common_data.name, WeaponName::StaffOfHoma);
|
||||||
|
|
||||||
|
match weapon.effect {
|
||||||
|
Some(effect) => {
|
||||||
|
let mut attribute = ComplicatedAttributeGraph::default();
|
||||||
|
effect.apply(&weapon.common_data, &mut attribute);
|
||||||
|
assert!(
|
||||||
|
attribute
|
||||||
|
.edges
|
||||||
|
.iter()
|
||||||
|
.any(|item| item.key == "护摩之杖被动"),
|
||||||
|
"Expected to find key '护摩之杖被动'"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
attribute
|
||||||
|
.edges
|
||||||
|
.iter()
|
||||||
|
.any(|item| item.key == "护摩之杖被动等效"),
|
||||||
|
"Expected to find key '护摩之杖被动等效'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => panic!("Expected weapon.effect, got None"),
|
||||||
|
}
|
||||||
|
println!("PyWeaponInterface 测试成功 遥遥领先!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
5
src/applications/mod.rs
Normal file
5
src/applications/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod analysis;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod generate;
|
||||||
|
pub mod input;
|
||||||
|
pub mod output;
|
112
src/applications/output/damage_analysis.rs
Normal file
112
src/applications/output/damage_analysis.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use crate::applications::output::damage_result::PyDamageResult;
|
||||||
|
use mona::damage::DamageAnalysis as MonaDamageAnalysis;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[pyclass(name = "DamageAnalysis")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyDamageAnalysis {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub atk: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub atk_ratio: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub hp: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub hp_ratio: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set, name = "defense")]
|
||||||
|
pub def: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub def_ratio: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub em: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub em_ratio: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub extra_damage: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub bonus: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub critical: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub critical_damage: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub melt_enhance: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub vaporize_enhance: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub healing_bonus: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub shield_strength: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub spread_compose: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub aggravate_compose: HashMap<String, f64>,
|
||||||
|
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub def_minus: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub def_penetration: HashMap<String, f64>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub res_minus: HashMap<String, f64>,
|
||||||
|
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub element: String,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub is_heal: bool,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub is_shield: bool,
|
||||||
|
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub normal: PyDamageResult,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub melt: Option<PyDamageResult>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub vaporize: Option<PyDamageResult>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub spread: Option<PyDamageResult>,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub aggravate: Option<PyDamageResult>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MonaDamageAnalysis> for PyDamageAnalysis {
|
||||||
|
fn from(damage_analysis: MonaDamageAnalysis) -> Self {
|
||||||
|
let element = damage_analysis.element.to_string();
|
||||||
|
let normal = PyDamageResult::from(damage_analysis.normal);
|
||||||
|
let melt = damage_analysis.melt.map(PyDamageResult::from);
|
||||||
|
let vaporize = damage_analysis.vaporize.map(PyDamageResult::from);
|
||||||
|
let spread = damage_analysis.spread.map(PyDamageResult::from);
|
||||||
|
let aggravate = damage_analysis.aggravate.map(PyDamageResult::from);
|
||||||
|
Self {
|
||||||
|
atk: damage_analysis.atk,
|
||||||
|
atk_ratio: damage_analysis.atk_ratio,
|
||||||
|
hp: damage_analysis.hp,
|
||||||
|
hp_ratio: damage_analysis.hp_ratio,
|
||||||
|
def: damage_analysis.def,
|
||||||
|
def_ratio: damage_analysis.def_ratio,
|
||||||
|
em: damage_analysis.em,
|
||||||
|
em_ratio: damage_analysis.em_ratio,
|
||||||
|
extra_damage: damage_analysis.extra_damage,
|
||||||
|
bonus: damage_analysis.bonus,
|
||||||
|
critical: damage_analysis.critical,
|
||||||
|
critical_damage: damage_analysis.critical_damage,
|
||||||
|
melt_enhance: damage_analysis.melt_enhance,
|
||||||
|
vaporize_enhance: damage_analysis.vaporize_enhance,
|
||||||
|
healing_bonus: damage_analysis.healing_bonus,
|
||||||
|
shield_strength: damage_analysis.shield_strength,
|
||||||
|
spread_compose: damage_analysis.spread_compose,
|
||||||
|
aggravate_compose: damage_analysis.aggravate_compose,
|
||||||
|
def_minus: damage_analysis.def_minus,
|
||||||
|
def_penetration: damage_analysis.def_penetration,
|
||||||
|
res_minus: damage_analysis.res_minus,
|
||||||
|
element,
|
||||||
|
is_heal: damage_analysis.is_heal,
|
||||||
|
is_shield: damage_analysis.is_shield,
|
||||||
|
normal,
|
||||||
|
melt,
|
||||||
|
vaporize,
|
||||||
|
spread,
|
||||||
|
aggravate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
src/applications/output/damage_result.rs
Normal file
49
src/applications/output/damage_result.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use mona::damage::damage_result::DamageResult as MonaDamageResult;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass(name = "DamageResult")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyDamageResult {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub critical: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub non_critical: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub expectation: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub is_heal: bool,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
pub is_shield: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyDamageResult {
|
||||||
|
#[new]
|
||||||
|
fn py_new(
|
||||||
|
critical: f64,
|
||||||
|
non_critical: f64,
|
||||||
|
expectation: f64,
|
||||||
|
is_heal: bool,
|
||||||
|
is_shield: bool,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
critical,
|
||||||
|
non_critical,
|
||||||
|
expectation,
|
||||||
|
is_heal,
|
||||||
|
is_shield,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MonaDamageResult> for PyDamageResult {
|
||||||
|
fn from(damage_result: MonaDamageResult) -> Self {
|
||||||
|
Self {
|
||||||
|
critical: damage_result.critical,
|
||||||
|
non_critical: damage_result.non_critical,
|
||||||
|
expectation: damage_result.expectation,
|
||||||
|
is_heal: damage_result.is_heal,
|
||||||
|
is_shield: damage_result.is_shield,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
src/applications/output/mod.rs
Normal file
3
src/applications/output/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod damage_analysis;
|
||||||
|
pub mod damage_result;
|
||||||
|
pub mod transformative_damage;
|
89
src/applications/output/transformative_damage.rs
Normal file
89
src/applications/output/transformative_damage.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
use mona::damage::transformative_damage::TransformativeDamage as MonaTransformativeDamage;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass(name = "TransformativeDamage")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PyTransformativeDamage {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
swirl_cryo: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
swirl_hydro: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
swirl_pyro: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
swirl_electro: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
overload: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
electro_charged: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
shatter: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
super_conduct: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
bloom: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
hyper_bloom: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
burgeon: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
burning: f64,
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
crystallize: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyTransformativeDamage {
|
||||||
|
#[new]
|
||||||
|
fn py_new(
|
||||||
|
swirl_cryo: f64,
|
||||||
|
swirl_hydro: f64,
|
||||||
|
swirl_pyro: f64,
|
||||||
|
swirl_electro: f64,
|
||||||
|
overload: f64,
|
||||||
|
electro_charged: f64,
|
||||||
|
shatter: f64,
|
||||||
|
super_conduct: f64,
|
||||||
|
bloom: f64,
|
||||||
|
hyper_bloom: f64,
|
||||||
|
burgeon: f64,
|
||||||
|
burning: f64,
|
||||||
|
crystallize: f64,
|
||||||
|
) -> PyResult<Self> {
|
||||||
|
Ok(PyTransformativeDamage {
|
||||||
|
swirl_cryo,
|
||||||
|
swirl_hydro,
|
||||||
|
swirl_pyro,
|
||||||
|
swirl_electro,
|
||||||
|
overload,
|
||||||
|
electro_charged,
|
||||||
|
shatter,
|
||||||
|
super_conduct,
|
||||||
|
bloom,
|
||||||
|
hyper_bloom,
|
||||||
|
burgeon,
|
||||||
|
burning,
|
||||||
|
crystallize,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MonaTransformativeDamage> for PyTransformativeDamage {
|
||||||
|
fn from(damage: MonaTransformativeDamage) -> Self {
|
||||||
|
Self {
|
||||||
|
swirl_cryo: damage.swirl_cryo,
|
||||||
|
swirl_hydro: damage.swirl_hydro,
|
||||||
|
swirl_pyro: damage.swirl_pyro,
|
||||||
|
swirl_electro: damage.swirl_electro,
|
||||||
|
overload: damage.overload,
|
||||||
|
electro_charged: damage.electro_charged,
|
||||||
|
shatter: damage.shatter,
|
||||||
|
super_conduct: damage.superconduct,
|
||||||
|
bloom: damage.bloom,
|
||||||
|
hyper_bloom: damage.hyperbloom,
|
||||||
|
burgeon: damage.burgeon,
|
||||||
|
burning: damage.burning,
|
||||||
|
crystallize: damage.crystallize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/lib.rs
Normal file
48
src/lib.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
extern crate core;
|
||||||
|
mod applications;
|
||||||
|
|
||||||
|
use pyo3::import_exception;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
use crate::applications::errors::ValidationError;
|
||||||
|
use applications::analysis::{get_damage_analysis, get_transformative_damage};
|
||||||
|
use applications::generate::artifact::gen_artifact_meta_as_json;
|
||||||
|
use applications::generate::character::gen_character_meta_as_json;
|
||||||
|
use applications::generate::locale::gen_generate_locale_as_json;
|
||||||
|
use applications::generate::weapon::gen_weapon_meta_as_json;
|
||||||
|
|
||||||
|
use crate::applications::input::artifact::PyArtifact;
|
||||||
|
use crate::applications::input::buff::PyBuffInterface;
|
||||||
|
use crate::applications::input::calculator::PyCalculatorConfig;
|
||||||
|
use crate::applications::input::character::PyCharacterInterface;
|
||||||
|
use crate::applications::input::enemy::PyEnemyInterface;
|
||||||
|
use crate::applications::input::skill::PySkillInterface;
|
||||||
|
use crate::applications::input::weapon::PyWeaponInterface;
|
||||||
|
use crate::applications::output::damage_analysis::PyDamageAnalysis;
|
||||||
|
use crate::applications::output::damage_result::PyDamageResult;
|
||||||
|
use crate::applications::output::transformative_damage::PyTransformativeDamage;
|
||||||
|
|
||||||
|
import_exception!(json, JSONDecodeError);
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
fn _python_genshin_artifact(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||||
|
m.add("JSONDecodeError", py.get_type::<JSONDecodeError>())?;
|
||||||
|
m.add_function(wrap_pyfunction!(get_damage_analysis, m)?)?;
|
||||||
|
m.add_function(wrap_pyfunction!(get_transformative_damage, m)?)?;
|
||||||
|
m.add_function(wrap_pyfunction!(gen_character_meta_as_json, m)?)?;
|
||||||
|
m.add_function(wrap_pyfunction!(gen_weapon_meta_as_json, m)?)?;
|
||||||
|
m.add_function(wrap_pyfunction!(gen_artifact_meta_as_json, m)?)?;
|
||||||
|
m.add_function(wrap_pyfunction!(gen_generate_locale_as_json, m)?)?;
|
||||||
|
m.add_class::<PyCalculatorConfig>()?;
|
||||||
|
m.add_class::<PyCharacterInterface>()?;
|
||||||
|
m.add_class::<PyBuffInterface>()?;
|
||||||
|
m.add_class::<PyWeaponInterface>()?;
|
||||||
|
m.add_class::<PyTransformativeDamage>()?;
|
||||||
|
m.add_class::<PySkillInterface>()?;
|
||||||
|
m.add_class::<PyEnemyInterface>()?;
|
||||||
|
m.add_class::<PyArtifact>()?;
|
||||||
|
m.add_class::<PyDamageResult>()?;
|
||||||
|
m.add_class::<PyDamageAnalysis>()?;
|
||||||
|
m.add_class::<ValidationError>()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
16
tests/test_character_interface.py
Normal file
16
tests/test_character_interface.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from python_genshin_artifact import CharacterInterface
|
||||||
|
|
||||||
|
|
||||||
|
def test_character_interface():
|
||||||
|
params = {"HuTao": {"le_50": True}}
|
||||||
|
character = CharacterInterface(
|
||||||
|
name="HuTao", level=90, ascend=False, constellation=6, skill1=12, skill2=12, skill3=12, params=params
|
||||||
|
)
|
||||||
|
assert character.name == "HuTao"
|
||||||
|
assert character.level == 90
|
||||||
|
assert character.ascend is False
|
||||||
|
assert character.constellation == 6
|
||||||
|
assert character.skill1 == 12
|
||||||
|
assert character.skill2 == 12
|
||||||
|
assert character.skill3 == 12
|
||||||
|
assert character.params == params
|
18
tests/test_damage_calculator.py
Normal file
18
tests/test_damage_calculator.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from python_genshin_artifact import (
|
||||||
|
CalculatorConfig,
|
||||||
|
get_damage_analysis,
|
||||||
|
CharacterInterface,
|
||||||
|
SkillInterface,
|
||||||
|
WeaponInterface,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_damage_analysis():
|
||||||
|
character = CharacterInterface(
|
||||||
|
name="HuTao", level=90, ascend=False, constellation=6, skill1=12, skill2=12, skill3=12
|
||||||
|
)
|
||||||
|
skill = SkillInterface(index=1)
|
||||||
|
weapon = WeaponInterface(name="StaffOfHoma", level=90, ascend=False, refine=4)
|
||||||
|
calculator_config = CalculatorConfig(character=character, weapon=weapon, skill=skill)
|
||||||
|
damage_analysis = get_damage_analysis(calculator_config)
|
||||||
|
assert damage_analysis.is_heal is False
|
Loading…
Reference in New Issue
Block a user