Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Noita: Fix rare item fill failure for single-player games #2387

Merged
merged 9 commits into from
Oct 29, 2023
78 changes: 44 additions & 34 deletions worlds/noita/Items.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,18 @@ def create_kantele(victory_condition: VictoryCondition) -> List[str]:
return ["Kantele"] if victory_condition.value >= VictoryCondition.option_pure_ending else []


def create_random_items(multiworld: MultiWorld, player: int, random_count: int) -> List[str]:
filler_pool = filler_weights.copy()
def create_random_items(multiworld: MultiWorld, player: int, weights: Dict[str, int], count: int) -> List[str]:
filler_pool = weights.copy()
if multiworld.bad_effects[player].value == 0:
del filler_pool["Trap"]

return multiworld.random.choices(
population=list(filler_pool.keys()),
weights=list(filler_pool.values()),
k=random_count
)
return multiworld.random.choices(population=list(filler_pool.keys()),
weights=list(filler_pool.values()),
k=count)


def create_all_items(multiworld: MultiWorld, player: int) -> None:
sum_locations = len(multiworld.get_unfilled_locations(player))
locations_to_fill = len(multiworld.get_unfilled_locations(player))

itempool = (
create_fixed_item_pool()
Expand All @@ -66,9 +64,18 @@ def create_all_items(multiworld: MultiWorld, player: int) -> None:
+ create_kantele(multiworld.victory_condition[player])
)

random_count = sum_locations - len(itempool)
itempool += create_random_items(multiworld, player, random_count)

# if there's not enough shop-allowed items in the pool, we can encounter gen issues
# 39 is the number of shop-valid items we need to guarantee
if len(itempool) < 39:
itempool += create_random_items(multiworld, player, shop_only_filler_weights, 39 - len(itempool))
# this is so that it passes tests and gens if you have minimal locations and only one player
if multiworld.players == 1:
for location in multiworld.get_unfilled_locations(player):
if "Shop Item" in location.name:
location.item = create_item(player, itempool.pop())
locations_to_fill = len(multiworld.get_unfilled_locations(player))

itempool += create_random_items(multiworld, player, filler_weights, locations_to_fill - len(itempool))
multiworld.itempool += [create_item(player, name) for name in itempool]


Expand All @@ -95,7 +102,7 @@ def create_all_items(multiworld: MultiWorld, player: int) -> None:
"Tinker with Wands Everywhere Perk": ItemData(110018, "Perks", ItemClassification.progression, 1),
"All-Seeing Eye Perk": ItemData(110019, "Perks", ItemClassification.progression, 1),
"Spatial Awareness Perk": ItemData(110020, "Perks", ItemClassification.progression),
"Extra Life Perk": ItemData(110021, "Repeatable Perks", ItemClassification.useful, 2),
"Extra Life Perk": ItemData(110021, "Repeatable Perks", ItemClassification.useful, 1),
"Orb": ItemData(110022, "Orbs", ItemClassification.progression_skip_balancing),
"Random Potion": ItemData(110023, "Items", ItemClassification.filler),
"Secret Potion": ItemData(110024, "Items", ItemClassification.filler),
Expand All @@ -106,32 +113,35 @@ def create_all_items(multiworld: MultiWorld, player: int) -> None:
"Refreshing Gourd": ItemData(110029, "Items", ItemClassification.filler, 1),
"Sädekivi": ItemData(110030, "Items", ItemClassification.filler),
"Broken Wand": ItemData(110031, "Items", ItemClassification.filler),
}

shop_only_filler_weights: Dict[str, int] = {
"Trap": 15,
"Extra Max HP": 25,
"Spell Refresher": 20,
"Wand (Tier 1)": 10,
"Wand (Tier 2)": 8,
"Wand (Tier 3)": 7,
"Wand (Tier 4)": 6,
"Wand (Tier 5)": 5,
"Wand (Tier 6)": 4,
"Extra Life Perk": 10,
}

filler_weights: Dict[str, int] = {
"Trap": 15,
"Extra Max HP": 25,
"Spell Refresher": 20,
"Potion": 40,
"Gold (200)": 15,
"Gold (1000)": 6,
"Wand (Tier 1)": 10,
"Wand (Tier 2)": 8,
"Wand (Tier 3)": 7,
"Wand (Tier 4)": 6,
"Wand (Tier 5)": 5,
"Wand (Tier 6)": 4,
"Extra Life Perk": 3,
"Random Potion": 10,
"Secret Potion": 10,
"Powder Pouch": 10,
"Chaos Die": 4,
"Greed Die": 4,
"Kammi": 4,
"Refreshing Gourd": 4,
"Sädekivi": 3,
"Broken Wand": 8,
**shop_only_filler_weights,
"Gold (200)": 15,
"Gold (1000)": 6,
"Potion": 40,
"Random Potion": 9,
"Secret Potion": 10,
"Powder Pouch": 10,
"Chaos Die": 4,
"Greed Die": 4,
"Kammi": 4,
"Refreshing Gourd": 4,
"Sädekivi": 3,
"Broken Wand": 10,
}


Expand Down
13 changes: 6 additions & 7 deletions worlds/noita/Rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,10 @@ class EntranceLock(NamedTuple):
"Wand (Tier 6)", # Temple of the Art
]


items_hidden_from_shops: List[str] = ["Gold (200)", "Gold (1000)", "Potion", "Random Potion", "Secret Potion",
"Chaos Die", "Greed Die", "Kammi", "Refreshing Gourd", "Sädekivi", "Broken Wand",
"Powder Pouch"]


perk_list: List[str] = list(filter(Items.item_is_perk, Items.item_table.keys()))


Expand Down Expand Up @@ -155,11 +153,12 @@ def victory_unlock_conditions(multiworld: MultiWorld, player: int) -> None:


def create_all_rules(multiworld: MultiWorld, player: int) -> None:
ban_items_from_shops(multiworld, player)
ban_early_high_tier_wands(multiworld, player)
lock_holy_mountains_into_spheres(multiworld, player)
holy_mountain_unlock_conditions(multiworld, player)
biome_unlock_conditions(multiworld, player)
if multiworld.players > 1:
ban_items_from_shops(multiworld, player)
ban_early_high_tier_wands(multiworld, player)
lock_holy_mountains_into_spheres(multiworld, player)
holy_mountain_unlock_conditions(multiworld, player)
biome_unlock_conditions(multiworld, player)
victory_unlock_conditions(multiworld, player)

# Prevent the Map perk (used to find Toveri) from being on Toveri (boss)
Expand Down