PaiGram/plugins/genshin/model/gcsim.py
2024-11-30 22:32:07 +08:00

241 lines
7.8 KiB
Python

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