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