This commit is contained in:
2024-02-27 23:05:51 +00:00
parent 48baff0736
commit bfc86df2e8
4 changed files with 389 additions and 0 deletions

View File

View File

@@ -0,0 +1,191 @@
from __future__ import annotations
import itertools
from enum import Enum
from pathlib import Path
from typing import Any, Generator
import yaml
from pydantic import BaseModel
from rich.console import Console
from rich.table import Table
from star_rail_relic_trimmer.models import CharacterRelic, Position, RelicToKeep
from star_rail_relic_trimmer.optimise import main_stat_plus_stat, no_main_stat_two_stats, prune_any
def convert_model_to_dict(
obj: BaseModel | list[BaseModel] | dict[str, BaseModel],
) -> dict[str, Any] | list[dict[str, Any]]:
if isinstance(obj, BaseModel):
return {k: (convert_model_to_dict(v) if v is not None else None) for k, v in obj.model_dump().items()}
if isinstance(obj, Enum):
return obj.value
if isinstance(obj, list):
return [convert_model_to_dict(item) for item in obj]
if isinstance(obj, dict):
return {k: convert_model_to_dict(v) for k, v in obj.items()}
return obj
def deduplicate_relics(relics: list[RelicToKeep]) -> set[RelicToKeep]:
unique_relics = {}
for relic in relics:
key = (relic.primary_stat, relic.secondary_stat, relic.tertiary_stat)
if key not in unique_relics:
unique_relics[key] = relic
else:
unique_relics[key].characters.update(relic.characters)
return set(unique_relics.values())
def sort_relics(relics_to_keep: Generator[RelicToKeep, Any, None]):
return sorted(
convert_model_to_dict(list(deduplicate_relics(relics_to_keep))),
key=lambda x: (x["primary_stat"] == "ANY", x["primary_stat"]),
)
def show_relics_to_keep(data_file: Path):
with data_file.open() as f:
data = yaml.safe_load(f)
character_relics = [CharacterRelic(**relic) for relic in data["Relics"]]
head_relics = []
hand_relics = []
body_relics = []
feet_relics = []
sphere_relics = []
rope_relics = []
for character_relic in character_relics:
head_relics.extend(
itertools.chain(
main_stat_plus_stat(
relic=character_relic.Head,
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Head,
),
no_main_stat_two_stats(
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Head,
),
),
)
hand_relics.extend(
itertools.chain(
main_stat_plus_stat(
relic=character_relic.Hands,
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Hands,
),
no_main_stat_two_stats(
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Hands,
),
),
)
body_relics.extend(
itertools.chain(
main_stat_plus_stat(
relic=character_relic.Body,
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Body,
),
no_main_stat_two_stats(
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Body,
),
),
)
feet_relics.extend(
itertools.chain(
main_stat_plus_stat(
relic=character_relic.Feet,
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Feet,
),
no_main_stat_two_stats(
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Feet,
),
),
)
sphere_relics.extend(
itertools.chain(
main_stat_plus_stat(
relic=character_relic.Sphere,
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Sphere,
),
no_main_stat_two_stats(
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Sphere,
),
),
)
rope_relics.extend(
itertools.chain(
main_stat_plus_stat(
relic=character_relic.Rope,
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Rope,
),
no_main_stat_two_stats(
stats=character_relic.stats,
character_name=character_relic.CharacterName,
position=Position.Rope,
),
),
)
head_relics_sorted = sort_relics(prune_any(head_relics))
hand_relics_sorted = sort_relics(prune_any(hand_relics))
body_relics_sorted = sort_relics(prune_any(body_relics))
feet_relics_sorted = sort_relics(prune_any(feet_relics))
sphere_relics_sorted = sort_relics(prune_any(sphere_relics))
rope_relics_sorted = sort_relics(prune_any(rope_relics))
for _table in (
("Head Relics", head_relics_sorted),
("Hand Relics", hand_relics_sorted),
("Body Relics", body_relics_sorted),
("Feet Relics", feet_relics_sorted),
("Sphere Relics", sphere_relics_sorted),
("Rope Relics", rope_relics_sorted),
):
table = Table(title=_table[0])
table.add_column("Primary Stat", style="white")
table.add_column("Secondary Stat", style="white")
table.add_column("Tertiary Stat", style="white")
table.add_column("Characters", style="white")
for item in _table[1]:
table.add_row(
item.get("primary_stat"),
item.get("secondary_stat"),
item.get("tertiary_stat"),
",".join(item.get("characters")),
)
console = Console()
console.print(table)

View File

@@ -0,0 +1,135 @@
from __future__ import annotations
from enum import Enum
from pydantic import BaseModel
class Stat(Enum):
HP = "HP"
ATK = "ATK"
HP_PERC = "HP%"
ATK_PERC = "ATK%"
DEF_PERC = "DEF%"
EFFECTHITRATE = "EffectHitRate"
OUTGOINGHEALING = "OutgoingHealing"
CRITRATE = "CRITRate"
CRITDMG = "CRITDMG"
SPD = "SPD"
PHYSICALDMG = "PhysicalDMG"
FIREDMG = "FireDMG"
ICEDMG = "IceDMG"
WINDDMG = "WindDMG"
LIGHTNINGDMG = "LightningDMG"
QUANTUMDMG = "QuantumDMG"
IMAGINARYDMG = "ImaginaryDMG"
BREAKEFFECT = "BreakEffect"
ENERGYREGENERATIONRATE = "EnergyRegenerationRate"
EFFECTRES_PERC = "EffectRes%"
ANY = "ANY"
class Position(Enum):
Head = "Head"
Hands = "Hands"
Body = "Body"
Feet = "Feet"
Sphere = "Sphere"
Rope = "Rope"
class CharacterRelic(BaseModel):
CharacterName: str
Head: Head
Hands: Hands
Body: Body
Feet: Feet
Sphere: Sphere
Rope: Rope
stats: list[Stat]
class Relic(BaseModel):
stat: Stat
allowed_stats: list[Stat]
class Head(Relic):
stat: Stat
allowed_stats: list[Stat] = [Stat.HP]
class Hands(Relic):
stat: Stat
allowed_stats: list[Stat] = [Stat.ATK]
class Body(Relic):
stat: Stat
allowed_stats: list[Stat] = [
Stat.HP_PERC,
Stat.ATK_PERC,
Stat.DEF_PERC,
Stat.EFFECTHITRATE,
Stat.OUTGOINGHEALING,
Stat.CRITRATE,
Stat.CRITDMG,
]
class Feet(Relic):
stat: Stat
allowed_stats: list[Stat] = [
Stat.HP_PERC,
Stat.ATK_PERC,
Stat.DEF_PERC,
Stat.SPD,
]
class Sphere(Relic):
stat: Stat
allowed_stats: list[Stat] = [
Stat.HP_PERC,
Stat.ATK_PERC,
Stat.DEF_PERC,
Stat.PHYSICALDMG,
Stat.FIREDMG,
Stat.ICEDMG,
Stat.WINDDMG,
Stat.LIGHTNINGDMG,
Stat.QUANTUMDMG,
Stat.IMAGINARYDMG,
]
class Rope(Relic):
stat: Stat
allowed_stats: list[Stat] = [
Stat.HP_PERC,
Stat.ATK_PERC,
Stat.DEF_PERC,
Stat.BREAKEFFECT,
Stat.ENERGYREGENERATIONRATE,
]
class RelicToKeep(BaseModel):
position: Position
primary_stat: Stat
secondary_stat: Stat
tertiary_stat: Stat | None
characters: set[str] = set()
def __hash__(self) -> int:
return hash((self.primary_stat, self.secondary_stat, self.tertiary_stat))
def __eq__(self, other) -> bool:
return (self.primary_stat, self.secondary_stat, self.tertiary_stat) == (
other.primary_stat,
other.secondary_stat,
other.tertiary_stat,
)
def add_character(self, character: str) -> None:
self.characters.add(character)

View File

@@ -0,0 +1,63 @@
from __future__ import annotations
from typing import Any, Generator
from star_rail_relic_trimmer.models import Position, Relic, RelicToKeep, Stat
def main_stat_plus_stat(
relic: Relic,
stats: list[Stat],
character_name: str,
position: Position,
) -> Generator[RelicToKeep, Any, None]:
if position in (Position.Head, Position.Hands):
relics_to_keep = [
RelicToKeep(
primary_stat=relic.stat,
secondary_stat=stats[0],
tertiary_stat=stats[i],
characters={character_name},
position=position.value,
)
for i in range(1, len(stats))
]
else:
relics_to_keep = [
RelicToKeep(
primary_stat=relic.stat,
secondary_stat=stat,
tertiary_stat=None,
characters={character_name},
position=position.value,
)
for stat in stats
if relic.stat != stat
]
yield from relics_to_keep
def no_main_stat_two_stats(
stats: list[Stat],
character_name: str,
position: Position,
) -> Generator[RelicToKeep, Any, None]:
relics_to_keep = [
RelicToKeep(
primary_stat=Stat.ANY,
secondary_stat=stats[0],
tertiary_stat=stats[i],
characters={character_name},
position=position.value,
)
for i in range(1, 3)
]
yield from relics_to_keep
def prune_any(relics: list[RelicToKeep]):
_relics = relics[:]
for _, item in enumerate(relics):
if item.position in (Position.Head, Position.Hands) and item.primary_stat == Stat.ANY:
_relics.pop(_relics.index(item))
return _relics