167 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			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)
 | 
						|
}
 |