Created
December 4, 2024 02:30
-
-
Save RandomComputerUser/06f52f3f28d5175c38bae3442fbf19de to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""See https://terraria.wiki.gg/wiki/Modifiers.""" | |
from collections.abc import Iterable | |
from dataclasses import dataclass | |
import random | |
from typing import Final | |
REFORGE_COST_MULT: Final[float] = 5 / 3 # relative to sell price | |
@dataclass(frozen=True) | |
class Modifier: | |
name: str | |
value_increase_pct: float | |
affects_knockback: bool = False | |
@property | |
def value_mult(self) -> float: | |
return 1.0 + 0.01 * self.value_increase_pct | |
def parse_wiki_modifier_table( | |
table: str, knockback_col: int, value_col: int | |
) -> list[Modifier]: | |
lines = table.splitlines() | |
modifiers: list[Modifier] = [] | |
for line in lines: | |
words = line.split() | |
if not words: | |
continue | |
value = float(words[value_col][:-1].replace("\u2212", "-")) | |
name = words[0] | |
knockback = words[knockback_col] != "-" | |
modifiers.append(Modifier(name, value, knockback)) | |
return modifiers | |
ACCESSORY_MODIFIERS: Final[list[Modifier]] = [ | |
Modifier("Hard", 10.25), | |
Modifier("Guarding", 21), | |
Modifier("Armored", 32.25), | |
Modifier("Warding", 44), | |
Modifier("Precise", 21), | |
Modifier("Lucky", 44), | |
Modifier("Jagged", 10.25), | |
Modifier("Spiked", 21), | |
Modifier("Angry", 32.25), | |
Modifier("Menacing", 44), | |
Modifier("Brisk", 10.25), | |
Modifier("Fleeting", 21), | |
Modifier("Hasty", 32.25), | |
Modifier("Quick", 44), | |
Modifier("Wild", 10.25), | |
Modifier("Rash", 21), | |
Modifier("Intrepid", 32.25), | |
Modifier("Violent", 44), | |
Modifier("Arcane", 32.25), | |
] | |
UNIVERSAL_MODIFIERS: Final[list[Modifier]] = parse_wiki_modifier_table( | |
""" | |
Keen - +3% - +1 +12.36% | |
Superior +10% +3% +10% +2 +64.51% | |
Forceful - - +15% +1 +32.25% | |
Broken −30% - −20% −2 −68.64% | |
Damaged −15% - - −1 −27.75% | |
Shoddy −10% - −15% −2 −41.48% | |
Hurtful +10% - - +1 +21% | |
Strong - - +15% +1 +32.25% | |
Unpleasant +5% - +15% +2 +45.81% | |
Weak - - −20% −2 −36% | |
Ruthless +18% - −10% +1 +12.78% | |
Godly +15% +5% +15% +2 +111.63% | |
Demonic +15% +5% - +2 +60.02% | |
Zealous - +5% - +1 +21% | |
""", | |
3, | |
5, | |
) | |
# swords, pickaxes, hammers, axes, hamaxes, magic, summon | |
COMMON_MODIFIERS: Final[list[Modifier]] = parse_wiki_modifier_table( | |
""" | |
Quick - +10% - - +1 +21% | |
Deadly +10% +10% - - +2 +46.41% | |
Agile - +10% +3% - +1 +35.96% | |
Nimble - +5% - - +1 +10.25% | |
Murderous +7% +6% +3% - +2 +44.54% | |
Slow - −15% - - −1 −27.75% | |
Sluggish - −20% - - −2 −36% | |
Lazy - −8% - - −1 −15.36% | |
Annoying −20% −15% - - −2 −53.76% | |
Nasty +5% +10% +2% −10% +1 +16.87% | |
""", | |
4, | |
6, | |
) | |
# ranged | |
# removed duplicate Deadly modifier, which is different for ranged weapons with knockback | |
COMMON_RANGED_MODIFIERS: Final[list[Modifier]] = parse_wiki_modifier_table( | |
""" | |
Quick - +10% - - +1 +21% | |
Agile - +10% +3% - +1 +35.96% | |
Nimble - +5% - - +1 +10.25% | |
Murderous +7% +6% +3% - +2 +44.54% | |
Slow - −15% - - −1 −27.75% | |
Sluggish - −20% - - −2 −36% | |
Lazy - −8% - - −1 −15.36% | |
Annoying −20% −15% - - −2 −53.76% | |
Nasty +5% +10% +2% −10% +1 +16.87% | |
""", | |
4, | |
6, | |
) | |
# swords, pickaxes, hammers, axes, hamaxes, whips | |
MELEE_MODIFIERS: Final[list[Modifier]] = parse_wiki_modifier_table( | |
""" | |
Large - - - +12% - +1 +25.44% | |
Massive - - - +18% - +1 +39.24% | |
Dangerous +5% - +2% +5% - +1 +31.47% | |
Savage +10% - - +10% +10% +2 +77.16% | |
Sharp +15% - - - - +1 +32.25% | |
Pointy +10% - - - - +1 +21% | |
Tiny - - - −18% - −1 −32.76% | |
Terrible −15% - - −13% −15% −2 −60.49% | |
Small - - - −10% - −1 −19% | |
Dull −15% - - - - −1 −27.75% | |
Unhappy - −10% - −10% −10% −2 −46.86% | |
Bulky +5% −15% - +10% +10% +1 +16.62% | |
Shameful −10% - - +10% −20% −2 −37.27% | |
Heavy - −10% - - +15% - +7.12% | |
Light - +15% - - −10% - +7.12% | |
Legendary +15% +10% +5% +10% +15% +2 +209.85% | |
""", | |
5, | |
7, | |
) | |
RANGED_MODIFIERS: Final[list[Modifier]] = parse_wiki_modifier_table( | |
""" | |
Sighted +10% - +3% - - +1 +35.96% | |
Rapid - +15% - +10% - +2 +60.02% | |
Hasty - +10% - +15% - +2 +60.02% | |
Intimidating - - - +5% +15% +2 +45.81% | |
Deadly +10% +5% +2% +5% +5% +2 +75.38% | |
Staunch +10% - - - +15% +2 +60.02% | |
Awful −15% - - −10% −10% −2 −52.6% | |
Lethargic - −15% - −10% - −2 −41.48% | |
Awkward - −10% - - −20% −2 −48.16% | |
Powerful +15% −10% +1% - - +1 +11.45% | |
Frenzying −15% +15% - - - - −4.45% | |
Unreal +15% +10% +5% +10% +15% +2 +209.85% | |
""", | |
5, | |
7, | |
) | |
MAGIC_MODIFIERS: Final[list[Modifier]] = parse_wiki_modifier_table( | |
""" | |
Mystic +10% - - −15% - +2 +60.02% | |
Adept - - - −15% - +1 +32.25% | |
Masterful +15% - - −15% +5% +2 +92.83% | |
Inept - - - +10% - −1 −19% | |
Ignorant −10% - - +20% - −2 −48.16% | |
Deranged −10% - - - −10% −1 −34.39% | |
Intense +10% - - +15% - −1 −12.58% | |
Taboo - +10% - +10% +10% +1 +18.59% | |
Celestial +10% −10% - −10% +10% +1 +43.5% | |
Furious +15% - - +20% +15% +1 +11.94% | |
Manic −10% +10% - −10% - +1 +18.59% | |
Mythical +15% +10% +5% −10% +15% +2 +209.85% | |
""", | |
5, | |
7, | |
) | |
def main() -> None: | |
display_cost("Accessories", True, ["Warding"], ACCESSORY_MODIFIERS) | |
display_cost("Accessories", True, ["Lucky"], ACCESSORY_MODIFIERS) | |
display_cost("Accessories", True, ["Menacing"], ACCESSORY_MODIFIERS) | |
display_cost("Accessories", True, ["Lucky", "Menacing"], ACCESSORY_MODIFIERS) | |
display_cost("Accessories", True, ["Quick"], ACCESSORY_MODIFIERS) | |
display_cost("Accessories", True, ["Violent"], ACCESSORY_MODIFIERS) | |
display_cost("Accessories", True, ["Lucky", "Menacing", "Violent"], ACCESSORY_MODIFIERS) | |
display_cost("Accessories", True, ["Arcane"], ACCESSORY_MODIFIERS) | |
# All melee weapons have knockback | |
display_cost( | |
"Swords", | |
True, | |
["Legendary"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MELEE_MODIFIERS, | |
) | |
# All pickaxes, hammers, axes, and hamaxes have knockback | |
display_cost( | |
"Pickaxes, Hammers, Axes, and Hamaxes", | |
True, | |
["Light"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MELEE_MODIFIERS, | |
) | |
display_cost( | |
"Pickaxes, Hammers, Axes, and Hamaxes", | |
True, | |
["Light", "Legendary", "Deadly", "Agile", "Quick", "Nasty"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MELEE_MODIFIERS, | |
) | |
display_cost( | |
"Other Melee Weapons", | |
True, | |
["Godly"], | |
UNIVERSAL_MODIFIERS, | |
) | |
display_cost( | |
"Other Melee Weapons", | |
True, | |
["Godly", "Demonic"], | |
UNIVERSAL_MODIFIERS, | |
) | |
display_cost( | |
"Other Melee Weapons", | |
True, | |
["Ruthless"], | |
UNIVERSAL_MODIFIERS, | |
) | |
display_cost( | |
"Other Melee Weapons", | |
True, | |
["Godly", "Demonic", "Ruthless"], | |
UNIVERSAL_MODIFIERS, | |
) | |
display_cost( | |
"Ranged Weapons", | |
True, | |
["Unreal"], | |
UNIVERSAL_MODIFIERS + COMMON_RANGED_MODIFIERS + RANGED_MODIFIERS, | |
) | |
display_cost( | |
"Ranged Weapons", | |
False, | |
["Demonic"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + RANGED_MODIFIERS, | |
) | |
display_cost( | |
"Ranged Weapons", | |
False, | |
["Deadly"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + RANGED_MODIFIERS, | |
) | |
display_cost( | |
"Ranged Weapons", | |
False, | |
["Demonic", "Deadly"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + RANGED_MODIFIERS, | |
) | |
display_cost( | |
"Magic Weapons", | |
True, | |
["Mythical"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
display_cost( | |
"Magic Weapons", | |
False, | |
["Demonic"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
display_cost( | |
"Magic Weapons", | |
False, | |
["Deadly"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
display_cost( | |
"Magic Weapons", | |
False, | |
["Demonic", "Deadly"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
display_cost( | |
"Magic Weapons", | |
False, | |
["Mystic"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
display_cost( | |
"Magic Weapons", | |
False, | |
["Demonic", "Deadly", "Mystic"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
display_cost( | |
"Summon Weapons", | |
True, | |
["Ruthless"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
display_cost( | |
"Summon Weapons", | |
True, | |
["Ruthless", "Mythical"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
display_cost( | |
"Summon Weapons - Blade Staff", | |
False, | |
["Demonic", "Deadly", "Mystic", "Hurtful"], | |
UNIVERSAL_MODIFIERS + COMMON_MODIFIERS + MAGIC_MODIFIERS, | |
) | |
# All whips have knockback | |
display_cost( | |
"Whips", | |
True, | |
["Legendary"], | |
UNIVERSAL_MODIFIERS + MELEE_MODIFIERS, | |
) | |
def display_cost( | |
item_type: str, knockback: bool, goal: list[str], modifiers: Iterable[Modifier] | |
) -> None: | |
expected_cost = calculate_expected_reforge_cost(modifiers, goal, knockback) | |
item_label = item_type | |
if not knockback: | |
item_label += ", no knockback" | |
goal_label: str | |
if len(goal) == 1: | |
goal_label = goal[0] | |
elif len(goal) == 2: | |
goal_label = f"{goal[0]} or {goal[1]}" | |
else: | |
goal_label = f"{', '.join(goal[:-1])}, or {goal[-1]}" | |
label = f"{item_label} ({goal_label}):" | |
print( | |
f"{label} {expected_cost:.2f}x base sell price + initial reforge cost" | |
) | |
def calculate_expected_reforge_cost( | |
modifiers: Iterable[Modifier], | |
desired_modifiers: Iterable[str], | |
has_knockback: bool, | |
) -> float: | |
if not has_knockback: | |
modifiers = ( | |
modifier for modifier in modifiers if not modifier.affects_knockback | |
) | |
goal = set(desired_modifiers) | |
single_reforge_cost = 0.0 | |
modifier_count = 0 | |
goal_count = 0 | |
for modifier in modifiers: | |
modifier_count += 1 | |
if modifier.name in goal: | |
goal_count += 1 | |
continue | |
single_reforge_cost += modifier.value_mult | |
if goal_count != len(goal): | |
raise ValueError("invalid desired modifier") | |
single_reforge_cost *= REFORGE_COST_MULT / modifier_count | |
reroll_probability = 1 - goal_count / modifier_count | |
return single_reforge_cost / (1 - reroll_probability) | |
def simulate_reforge_cost( | |
modifiers: Iterable[Modifier], | |
desired_modifiers: Iterable[str], | |
has_knockback: bool, | |
) -> float: | |
possible: list[Modifier] | |
if has_knockback: | |
possible = list(modifiers) | |
else: | |
possible = [ | |
modifier for modifier in modifiers if not modifier.affects_knockback | |
] | |
goal = set(desired_modifiers) | |
cost = 0.0 | |
modifier = "" | |
while True: | |
new_modifier = random.choice(possible) | |
modifier = new_modifier.name | |
if modifier in goal: | |
break | |
cost += new_modifier.value_mult | |
return REFORGE_COST_MULT * cost | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment