PaiGram/plugins/genshin/model/gcsim.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

241 lines
7.8 KiB
Python
Raw Normal View History

from decimal import Decimal
from typing import Any, NewType, List, Optional, Tuple, Dict
from gcsim_pypi.aliases import ARTIFACT_ALIASES, CHARACTER_ALIASES, WEAPON_ALIASES
from gcsim_pypi.availability import AVAILABLE_ARTIFACTS, AVAILABLE_CHARACTERS, AVAILABLE_WEAPONS
2024-11-30 14:32:07 +00:00
from pydantic import field_validator, BaseModel
GCSimCharacter = NewType("GCSimCharacter", str)
GCSimWeapon = NewType("GCSimWeapon", str)
GCSimSet = NewType("GCSimSet", str)
class GCSimWeaponInfo(BaseModel):
weapon: GCSimWeapon = "dullblade"
refinement: int = 1
level: int = 1
max_level: int = 20
params: List[str] = []
2024-11-30 14:32:07 +00:00
@field_validator("weapon")
@classmethod
def validate_weapon(cls, v):
if v not in AVAILABLE_WEAPONS or v not in WEAPON_ALIASES:
raise ValueError(f"Not supported weapon: {v}")
return WEAPON_ALIASES[v]
class GCSimSetInfo(BaseModel):
set: GCSimSet
count: int = 2
params: List[str] = []
2024-11-30 14:32:07 +00:00
@field_validator("set")
@classmethod
def validate_set(cls, v):
if v not in AVAILABLE_ARTIFACTS or v not in ARTIFACT_ALIASES:
raise ValueError(f"Not supported set: {v}")
return ARTIFACT_ALIASES[v]
class GCSimCharacterStats(BaseModel):
HP: Decimal = Decimal(0)
HP_PERCENT: Decimal = Decimal(0)
ATK: Decimal = Decimal(0)
ATK_PERCENT: Decimal = Decimal(0)
DEF: Decimal = Decimal(0)
DEF_PERCENT: Decimal = Decimal(0)
EM: Decimal = Decimal(0)
ER: Decimal = Decimal(0)
CR: Decimal = Decimal(0)
CD: Decimal = Decimal(0)
HEAL: Decimal = Decimal(0)
PYRO_PERCENT: Decimal = Decimal(0)
HYDRO_PERCENT: Decimal = Decimal(0)
DENDRO_PERCENT: Decimal = Decimal(0)
ELECTRO_PERCENT: Decimal = Decimal(0)
ANEMO_PERCENT: Decimal = Decimal(0)
CRYO_PERCENT: Decimal = Decimal(0)
GEO_PERCENT: Decimal = Decimal(0)
PHYS_PERCENT: Decimal = Decimal(0)
class GCSimCharacterInfo(BaseModel):
character: GCSimCharacter
level: int = 1
max_level: int = 20
constellation: int = 0
talent: List[int] = [1, 1, 1]
start_hp: Optional[int] = None
weapon_info: GCSimWeaponInfo = GCSimWeaponInfo()
set_info: List[GCSimSetInfo] = []
stats: GCSimCharacterStats = GCSimCharacterStats()
params: List[str] = []
2024-11-30 14:32:07 +00:00
@field_validator("character")
@classmethod
def validate_character(cls, v):
if v not in AVAILABLE_CHARACTERS or v not in CHARACTER_ALIASES:
raise ValueError(f"Not supported character: {v}")
return CHARACTER_ALIASES[v]
@property
def char(self) -> str:
return self.character
@property
def char_line(self) -> str:
return (
" ".join(
filter(
lambda w: w,
[
f"{self.char}",
"char",
f"lvl={self.level}/{self.max_level}",
f"cons={self.constellation}",
f"start_hp={self.start_hp}" if self.start_hp is not None else "",
f"talent={','.join(str(t) for t in self.talent)}",
f"+params=[{','.join(self.params)}] " if self.params else "",
],
)
)
+ ";"
)
@property
def weapon_line(self) -> str:
return (
" ".join(
filter(
lambda w: w,
[
f"{self.char}",
f'add weapon="{self.weapon_info.weapon}"',
f"refine={self.weapon_info.refinement}",
f"lvl={self.weapon_info.level}/{self.weapon_info.max_level}",
f"+params=[{','.join(self.weapon_info.params)}] " if self.weapon_info.params else "",
],
)
)
+ ";"
)
@property
def set_line(self) -> str:
return "\n".join(
" ".join(
filter(
lambda w: w,
[
f"{self.char}",
f'add set="{set_info.set}"',
f"count={4 if set_info.count >= 4 else 2}",
f"+params=[{','.join(set_info.params)}] " if set_info.params else "",
],
)
)
+ ";"
for set_info in self.set_info
# NOTE: 祭*系列似乎并不支持
if set_info.count > 1
)
@property
def stats_line(self) -> str:
if all(value == 0 for _, value in self.stats):
return ""
return (
f"{self.char} add stats "
+ " ".join(
[
(
f"{stat.replace('_PERCENT', '%').lower()}={value:.4f}"
if stat.endswith("_PERCENT") or stat in {"CR", "CD", "ER"}
else f"{stat.lower()}={value:.2f}"
)
for stat, value in iter(self.stats)
if value > 0
]
)
+ ";"
)
def __str__(self) -> str:
return "\n".join([self.char_line, self.weapon_line, self.set_line, self.stats_line])
class GCSimTarget(BaseModel):
level: int = 100
resist: float = 0.1
position: Tuple[str, str] = ("0", "0")
interval: List[int] = []
radius: Optional[float] = None
hp: Optional[int] = None
amount: Optional[int] = None
particle_threshold: Optional[float] = None
particle_drop_count: Optional[float] = None
others: Dict[str, Any] = {}
def __str__(self) -> str:
return (
" ".join(
filter(
lambda w: w,
[
f"target lvl={self.level} resist={self.resist} ",
f"pos={','.join(self.position)}",
f"radius={self.radius}" if self.radius is not None else "",
f"hp={self.hp}" if self.hp is not None else "",
f"amount={self.amount}" if self.amount is not None else "",
f"interval={','.join(str(i) for i in self.interval)}" if self.interval else "",
f"particle_threshold={self.particle_threshold}" if self.particle_threshold is not None else "",
(
f"particle_drop_count={self.particle_drop_count}"
if self.particle_drop_count is not None
else ""
),
" ".join([f"{k}={v}" for k, v in self.others.items()]),
],
)
)
+ ";"
)
class GCSimEnergySettings(BaseModel):
intervals: List[int] = [480, 720]
amount: int = 1
def __str__(self) -> str:
return f"energy every interval={','.join(str(i) for i in self.intervals)} amount={self.amount};"
class GCSim(BaseModel):
options: Optional[str] = None
characters: List[GCSimCharacterInfo] = []
targets: List[GCSimTarget] = [GCSimTarget()]
energy_settings: Optional[GCSimEnergySettings] = None
# TODO: Do we even want this?
hurt_settings: Optional[str] = None
active_character: Optional[GCSimCharacter] = None
script_lines: List[str] = []
def __str__(self) -> str:
line = ""
if self.options:
line += f"{self.options};\n"
line += "\n".join([str(c) for c in self.characters])
line += "\n"
line += "\n".join([str(t) for t in self.targets])
line += "\n"
if self.energy_settings:
line += f"{self.energy_settings}\n"
if self.active_character:
line += f"active {self.active_character};\n"
else:
line += f"active {self.characters[0].char};\n"
line += "\n".join(self.script_lines)
line += "\n"
return line