add code
This commit is contained in:
0
src/star_rail_relic_trimmer/__init__.py
Normal file
0
src/star_rail_relic_trimmer/__init__.py
Normal file
191
src/star_rail_relic_trimmer/load.py
Normal file
191
src/star_rail_relic_trimmer/load.py
Normal 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)
|
||||||
135
src/star_rail_relic_trimmer/models.py
Normal file
135
src/star_rail_relic_trimmer/models.py
Normal 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)
|
||||||
63
src/star_rail_relic_trimmer/optimise.py
Normal file
63
src/star_rail_relic_trimmer/optimise.py
Normal 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
|
||||||
Reference in New Issue
Block a user