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
37 changes: 27 additions & 10 deletions worlds/noita/Items.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,31 @@ 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]:
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"]


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

shop_filler_pool = {}
for item_name, weight in filler_pool.items():
if item_name not in items_hidden_from_shops:
shop_filler_pool[item_name] = weight

return multiworld.random.choices(
population=list(filler_pool.keys()),
weights=list(filler_pool.values()),
k=random_count
)
shop_filler = multiworld.random.choices(population=list(shop_filler_pool.keys()),
weights=list(shop_filler_pool.values()), k=shop_count)
random_filler = multiworld.random.choices(population=list(filler_pool.keys()),
weights=list(filler_pool.values()), k=random_count)

return shop_filler + random_filler


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 +77,16 @@ 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 checks in the pool
shop_random_count = 0
if len(itempool) < 39:
shop_random_count = 39 - len(itempool)
random_count = locations_to_fill - 39
else:
random_count = locations_to_fill - len(itempool)

itempool += create_random_items(multiworld, player, shop_random_count, random_count)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a little bit more convoluted than it needs to be. We can pass the dict to create_random_items instead of separate count values. Something like

# 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))
itempool += create_random_items(multiworld, player, item_filler_weights, locations_to_fill - len(itempool))

Copy link
Collaborator

@heinermann heinermann Oct 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filler_weights below can then be defined as I think something like (assuming the weights will be the same)

filler_weights: Dict[str, int] = {
    **shop_only_filler_weights,
    "Gold (200)": 15,
    ....

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing it this way I'm getting constant failures with a minimal items solo yaml. The main issue: I don't think AP is placing the items correctly. I'm loading in exactly 7 perks, 32 hearts, and 7 Gold (200)s. The only way for it to work is if it places the perks + hearts in the 39 shop slots, and the 7 Gold (200)s in the 7 spell refresher spots. It is not doing this most of the time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of note, the number of Golds it fails to place is not consistent. Most of the time, it fails to place 2 of them. Sometimes, 3. Sometimes, 1. Rarely, it succeeds.

multiworld.itempool += [create_item(player, name) for name in itempool]


Expand Down Expand Up @@ -106,7 +124,6 @@ def create_all_items(multiworld: MultiWorld, player: int) -> None:
"Refreshing Gourd": ItemData(110029, "Items", ItemClassification.filler),
"Sädekivi": ItemData(110030, "Items", ItemClassification.filler),
"Broken Wand": ItemData(110031, "Items", ItemClassification.filler),

}

filler_weights: Dict[str, int] = {
Expand Down
7 changes: 1 addition & 6 deletions worlds/noita/Rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,6 @@ class EntranceLock(NamedTuple):
]


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 @@ -80,7 +75,7 @@ def forbid_items_at_location(multiworld: MultiWorld, location_name: str, items:
def ban_items_from_shops(multiworld: MultiWorld, player: int) -> None:
for location_name in Locations.location_name_to_id.keys():
if "Shop Item" in location_name:
forbid_items_at_location(multiworld, location_name, items_hidden_from_shops, player)
forbid_items_at_location(multiworld, location_name, Items.items_hidden_from_shops, player)


# Prevent high tier wands from appearing in early Holy Mountain shops
Expand Down