Archipelago/worlds/noita/Items.py

167 lines
7.8 KiB
Python

import itertools
from collections import Counter
from typing import Dict, List, NamedTuple, Set
from BaseClasses import Item, ItemClassification, MultiWorld
from .Options import BossesAsChecks, VictoryCondition, ExtraOrbs
class ItemData(NamedTuple):
code: int
group: str
classification: ItemClassification = ItemClassification.progression
required_num: int = 0
class NoitaItem(Item):
game: str = "Noita"
def create_item(player: int, name: str) -> Item:
item_data = item_table[name]
return NoitaItem(name, item_data.classification, item_data.code, player)
def create_fixed_item_pool() -> List[str]:
required_items: Dict[str, int] = {name: data.required_num for name, data in item_table.items()}
return list(Counter(required_items).elements())
def create_orb_items(victory_condition: VictoryCondition, extra_orbs: ExtraOrbs) -> List[str]:
orb_count = extra_orbs.value
if victory_condition == VictoryCondition.option_pure_ending:
orb_count = orb_count + 11
elif victory_condition == VictoryCondition.option_peaceful_ending:
orb_count = orb_count + 33
return ["Orb" for _ in range(orb_count)]
def create_spatial_awareness_item(bosses_as_checks: BossesAsChecks) -> List[str]:
return ["Spatial Awareness Perk"] if bosses_as_checks.value >= BossesAsChecks.option_all_bosses else []
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, 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=count)
def create_all_items(multiworld: MultiWorld, player: int) -> None:
locations_to_fill = len(multiworld.get_unfilled_locations(player))
itempool = (
create_fixed_item_pool()
+ create_orb_items(multiworld.victory_condition[player], multiworld.extra_orbs[player])
+ create_spatial_awareness_item(multiworld.bosses_as_checks[player])
+ create_kantele(multiworld.victory_condition[player])
)
# 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]
# 110000 - 110032
item_table: Dict[str, ItemData] = {
"Trap": ItemData(110000, "Traps", ItemClassification.trap),
"Extra Max HP": ItemData(110001, "Pickups", ItemClassification.useful),
"Spell Refresher": ItemData(110002, "Pickups", ItemClassification.filler),
"Potion": ItemData(110003, "Items", ItemClassification.filler),
"Gold (200)": ItemData(110004, "Gold", ItemClassification.filler),
"Gold (1000)": ItemData(110005, "Gold", ItemClassification.filler),
"Wand (Tier 1)": ItemData(110006, "Wands", ItemClassification.useful),
"Wand (Tier 2)": ItemData(110007, "Wands", ItemClassification.useful),
"Wand (Tier 3)": ItemData(110008, "Wands", ItemClassification.useful),
"Wand (Tier 4)": ItemData(110009, "Wands", ItemClassification.useful),
"Wand (Tier 5)": ItemData(110010, "Wands", ItemClassification.useful, 1),
"Wand (Tier 6)": ItemData(110011, "Wands", ItemClassification.useful, 1),
"Kantele": ItemData(110012, "Wands", ItemClassification.useful),
"Fire Immunity Perk": ItemData(110013, "Perks", ItemClassification.progression, 1),
"Toxic Immunity Perk": ItemData(110014, "Perks", ItemClassification.progression, 1),
"Explosion Immunity Perk": ItemData(110015, "Perks", ItemClassification.progression, 1),
"Melee Immunity Perk": ItemData(110016, "Perks", ItemClassification.progression, 1),
"Electricity Immunity Perk": ItemData(110017, "Perks", ItemClassification.progression, 1),
"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, 1),
"Orb": ItemData(110022, "Orbs", ItemClassification.progression_skip_balancing),
"Random Potion": ItemData(110023, "Items", ItemClassification.filler),
"Secret Potion": ItemData(110024, "Items", ItemClassification.filler),
"Powder Pouch": ItemData(110025, "Items", ItemClassification.filler),
"Chaos Die": ItemData(110026, "Items", ItemClassification.filler),
"Greed Die": ItemData(110027, "Items", ItemClassification.filler),
"Kammi": ItemData(110028, "Items", ItemClassification.filler, 1),
"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] = {
**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,
}
# These helper functions make the comprehensions below more readable
def get_item_group(item_name: str) -> str:
return item_table[item_name].group
def item_is_filler(item_name: str) -> bool:
return item_table[item_name].classification == ItemClassification.filler
def item_is_perk(item_name: str) -> bool:
return item_table[item_name].group == "Perks"
filler_items: List[str] = list(filter(item_is_filler, item_table.keys()))
item_name_to_id: Dict[str, int] = {name: data.code for name, data in item_table.items()}
item_name_groups: Dict[str, Set[str]] = {
group: set(item_names) for group, item_names in itertools.groupby(item_table, get_item_group)
}