diff --git a/worlds/noita/__init__.py b/worlds/noita/__init__.py index af292176..a0b94458 100644 --- a/worlds/noita/__init__.py +++ b/worlds/noita/__init__.py @@ -38,7 +38,7 @@ class NoitaWorld(World): web = NoitaWeb() def generate_early(self) -> None: - if not self.multiworld.get_player_name(self.player).isascii(): + if not self.player_name.isascii(): raise Exception("Noita yaml's slot name has invalid character(s).") # Returned items will be sent over to the client diff --git a/worlds/noita/events.py b/worlds/noita/events.py index 4ec04e98..2ae524d9 100644 --- a/worlds/noita/events.py +++ b/worlds/noita/events.py @@ -1,4 +1,4 @@ -from typing import Dict, TYPE_CHECKING +from typing import TYPE_CHECKING from BaseClasses import Item, ItemClassification, Location, Region from . import items, locations @@ -6,7 +6,7 @@ if TYPE_CHECKING: from . import NoitaWorld -def create_event(player: int, name: str) -> Item: +def create_event_item(player: int, name: str) -> Item: return items.NoitaItem(name, ItemClassification.progression, None, player) @@ -16,13 +16,13 @@ def create_location(player: int, name: str, region: Region) -> Location: def create_locked_location_event(player: int, region: Region, item: str) -> Location: new_location = create_location(player, item, region) - new_location.place_locked_item(create_event(player, item)) + new_location.place_locked_item(create_event_item(player, item)) region.locations.append(new_location) return new_location -def create_all_events(world: "NoitaWorld", created_regions: Dict[str, Region]) -> None: +def create_all_events(world: "NoitaWorld", created_regions: dict[str, Region]) -> None: for region_name, event in event_locks.items(): region = created_regions[region_name] create_locked_location_event(world.player, region, event) @@ -31,7 +31,7 @@ def create_all_events(world: "NoitaWorld", created_regions: Dict[str, Region]) - # Maps region names to event names -event_locks: Dict[str, str] = { +event_locks: dict[str, str] = { "The Work": "Victory", "Mines": "Portal to Holy Mountain 1", "Coal Pits": "Portal to Holy Mountain 2", diff --git a/worlds/noita/items.py b/worlds/noita/items.py index 20d9ff19..4cd0b5ef 100644 --- a/worlds/noita/items.py +++ b/worlds/noita/items.py @@ -1,6 +1,6 @@ import itertools from collections import Counter -from typing import Dict, List, NamedTuple, Set, TYPE_CHECKING +from typing import NamedTuple, TYPE_CHECKING from BaseClasses import Item, ItemClassification from .options import BossesAsChecks, VictoryCondition, ExtraOrbs @@ -27,12 +27,12 @@ def create_item(player: int, name: str) -> Item: 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()} +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]: +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 @@ -41,15 +41,15 @@ def create_orb_items(victory_condition: VictoryCondition, extra_orbs: ExtraOrbs) return ["Orb" for _ in range(orb_count)] -def create_spatial_awareness_item(bosses_as_checks: BossesAsChecks) -> List[str]: +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]: +def create_kantele(victory_condition: VictoryCondition) -> list[str]: return ["Kantele"] if victory_condition.value >= VictoryCondition.option_pure_ending else [] -def create_random_items(world: NoitaWorld, weights: Dict[str, int], count: int) -> List[str]: +def create_random_items(world: NoitaWorld, weights: dict[str, int], count: int) -> list[str]: filler_pool = weights.copy() if not world.options.bad_effects: filler_pool["Trap"] = 0 @@ -87,7 +87,7 @@ def create_all_items(world: NoitaWorld) -> None: # 110000 - 110032 -item_table: Dict[str, ItemData] = { +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), @@ -122,7 +122,7 @@ item_table: Dict[str, ItemData] = { "Broken Wand": ItemData(110031, "Items", ItemClassification.filler), } -shop_only_filler_weights: Dict[str, int] = { +shop_only_filler_weights: dict[str, int] = { "Trap": 15, "Extra Max HP": 25, "Spell Refresher": 20, @@ -135,7 +135,7 @@ shop_only_filler_weights: Dict[str, int] = { "Extra Life Perk": 10, } -filler_weights: Dict[str, int] = { +filler_weights: dict[str, int] = { **shop_only_filler_weights, "Gold (200)": 15, "Gold (1000)": 6, @@ -152,22 +152,10 @@ filler_weights: Dict[str, int] = { } -# These helper functions make the comprehensions below more readable -def get_item_group(item_name: str) -> str: - return item_table[item_name].group +filler_items: list[str] = list(filter(lambda item: item_table[item].classification == ItemClassification.filler, + item_table.keys())) +item_name_to_id: dict[str, int] = {name: data.code for name, data in item_table.items()} - -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) +item_name_groups: dict[str, set[str]] = { + group: set(item_names) for group, item_names in itertools.groupby(item_table, lambda item: item_table[item].group) } diff --git a/worlds/noita/locations.py b/worlds/noita/locations.py index 5dd87b5b..31995576 100644 --- a/worlds/noita/locations.py +++ b/worlds/noita/locations.py @@ -1,6 +1,6 @@ # Locations are specific points that you would obtain an item at. from enum import IntEnum -from typing import Dict, NamedTuple, Optional, Set +from typing import NamedTuple from BaseClasses import Location @@ -27,7 +27,7 @@ class LocationFlag(IntEnum): # Only the first Hidden Chest and Pedestal are mapped here, the others are created in Regions. # ltype key: "Chest" = Hidden Chests, "Pedestal" = Pedestals, "Boss" = Boss, "Orb" = Orb. # 110000-110671 -location_region_mapping: Dict[str, Dict[str, LocationData]] = { +location_region_mapping: dict[str, dict[str, LocationData]] = { "Coal Pits Holy Mountain": { "Coal Pits Holy Mountain Shop Item 1": LocationData(110000), "Coal Pits Holy Mountain Shop Item 2": LocationData(110001), @@ -207,15 +207,15 @@ location_region_mapping: Dict[str, Dict[str, LocationData]] = { } -def make_location_range(location_name: str, base_id: int, amt: int) -> Dict[str, int]: +def make_location_range(location_name: str, base_id: int, amt: int) -> dict[str, int]: if amt == 1: return {location_name: base_id} return {f"{location_name} {i+1}": base_id + i for i in range(amt)} -location_name_groups: Dict[str, Set[str]] = {"Shop": set(), "Orb": set(), "Boss": set(), "Chest": set(), +location_name_groups: dict[str, set[str]] = {"Shop": set(), "Orb": set(), "Boss": set(), "Chest": set(), "Pedestal": set()} -location_name_to_id: Dict[str, int] = {} +location_name_to_id: dict[str, int] = {} for region_name, location_group in location_region_mapping.items(): diff --git a/worlds/noita/regions.py b/worlds/noita/regions.py index 184cd960..55a0ad1f 100644 --- a/worlds/noita/regions.py +++ b/worlds/noita/regions.py @@ -1,5 +1,5 @@ # Regions are areas in your game that you travel to. -from typing import Dict, List, TYPE_CHECKING +from typing import TYPE_CHECKING from BaseClasses import Entrance, Region from . import locations @@ -36,28 +36,21 @@ def create_region(world: "NoitaWorld", region_name: str) -> Region: return new_region -def create_regions(world: "NoitaWorld") -> Dict[str, Region]: +def create_regions(world: "NoitaWorld") -> dict[str, Region]: return {name: create_region(world, name) for name in noita_regions} -# An "Entrance" is really just a connection between two regions -def create_entrance(player: int, source: str, destination: str, regions: Dict[str, Region]) -> Entrance: - entrance = Entrance(player, f"From {source} To {destination}", regions[source]) - entrance.connect(regions[destination]) - return entrance - - # Creates connections based on our access mapping in `noita_connections`. -def create_connections(player: int, regions: Dict[str, Region]) -> None: +def create_connections(regions: dict[str, Region]) -> None: for source, destinations in noita_connections.items(): - new_entrances = [create_entrance(player, source, destination, regions) for destination in destinations] - regions[source].exits = new_entrances + for destination in destinations: + regions[source].connect(regions[destination]) # Creates all regions and connections. Called from NoitaWorld. def create_all_regions_and_connections(world: "NoitaWorld") -> None: created_regions = create_regions(world) - create_connections(world.player, created_regions) + create_connections(created_regions) create_all_events(world, created_regions) world.multiworld.regions += created_regions.values() @@ -75,7 +68,7 @@ def create_all_regions_and_connections(world: "NoitaWorld") -> None: # - Lake is connected to The Laboratory, since the bosses are hard without specific set-ups (which means late game) # - Snowy Depths connects to Lava Lake orb since you need digging for it, so fairly early is acceptable # - Ancient Laboratory is connected to the Coal Pits, so that Ylialkemisti isn't sphere 1 -noita_connections: Dict[str, List[str]] = { +noita_connections: dict[str, list[str]] = { "Menu": ["Forest"], "Forest": ["Mines", "Floating Island", "Desert", "Snowy Wasteland"], "Frozen Vault": ["The Vault"], @@ -117,4 +110,4 @@ noita_connections: Dict[str, List[str]] = { ### } -noita_regions: List[str] = sorted(set(noita_connections.keys()).union(*noita_connections.values())) +noita_regions: list[str] = sorted(set(noita_connections.keys()).union(*noita_connections.values())) diff --git a/worlds/noita/rules.py b/worlds/noita/rules.py index 65871a80..c2c48324 100644 --- a/worlds/noita/rules.py +++ b/worlds/noita/rules.py @@ -1,6 +1,5 @@ -from typing import List, NamedTuple, Set, TYPE_CHECKING +from typing import NamedTuple, TYPE_CHECKING -from BaseClasses import CollectionState from . import items, locations from .options import BossesAsChecks, VictoryCondition from worlds.generic import Rules as GenericRules @@ -16,7 +15,7 @@ class EntranceLock(NamedTuple): items_needed: int -entrance_locks: List[EntranceLock] = [ +entrance_locks: list[EntranceLock] = [ EntranceLock("Mines", "Coal Pits Holy Mountain", "Portal to Holy Mountain 1", 1), EntranceLock("Coal Pits", "Snowy Depths Holy Mountain", "Portal to Holy Mountain 2", 2), EntranceLock("Snowy Depths", "Hiisi Base Holy Mountain", "Portal to Holy Mountain 3", 3), @@ -27,7 +26,7 @@ entrance_locks: List[EntranceLock] = [ ] -holy_mountain_regions: List[str] = [ +holy_mountain_regions: list[str] = [ "Coal Pits Holy Mountain", "Snowy Depths Holy Mountain", "Hiisi Base Holy Mountain", @@ -38,7 +37,7 @@ holy_mountain_regions: List[str] = [ ] -wand_tiers: List[str] = [ +wand_tiers: list[str] = [ "Wand (Tier 1)", # Coal Pits "Wand (Tier 2)", # Snowy Depths "Wand (Tier 3)", # Hiisi Base @@ -48,29 +47,21 @@ wand_tiers: List[str] = [ ] -items_hidden_from_shops: Set[str] = {"Gold (200)", "Gold (1000)", "Potion", "Random Potion", "Secret Potion", +items_hidden_from_shops: set[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())) +perk_list: list[str] = list(filter(lambda item: items.item_table[item].group == "Perks", items.item_table.keys())) # ---------------- -# Helper Functions +# Helper Function # ---------------- -def has_perk_count(state: CollectionState, player: int, amount: int) -> bool: - return sum(state.count(perk, player) for perk in perk_list) >= amount - - -def has_orb_count(state: CollectionState, player: int, amount: int) -> bool: - return state.count("Orb", player) >= amount - - -def forbid_items_at_locations(world: "NoitaWorld", shop_locations: Set[str], forbidden_items: Set[str]) -> None: +def forbid_items_at_locations(world: "NoitaWorld", shop_locations: set[str], forbidden_items: set[str]) -> None: for shop_location in shop_locations: - location = world.multiworld.get_location(shop_location, world.player) + location = world.get_location(shop_location) GenericRules.forbid_items_for_player(location, forbidden_items, world.player) @@ -104,38 +95,38 @@ def ban_early_high_tier_wands(world: "NoitaWorld") -> None: def lock_holy_mountains_into_spheres(world: "NoitaWorld") -> None: for lock in entrance_locks: - location = world.multiworld.get_entrance(f"From {lock.source} To {lock.destination}", world.player) + location = world.get_entrance(f"{lock.source} -> {lock.destination}") GenericRules.set_rule(location, lambda state, evt=lock.event: state.has(evt, world.player)) def holy_mountain_unlock_conditions(world: "NoitaWorld") -> None: victory_condition = world.options.victory_condition.value for lock in entrance_locks: - location = world.multiworld.get_location(lock.event, world.player) + location = world.get_location(lock.event) if victory_condition == VictoryCondition.option_greed_ending: location.access_rule = lambda state, items_needed=lock.items_needed: ( - has_perk_count(state, world.player, items_needed//2) + state.has_group_unique("Perks", world.player, items_needed // 2) ) elif victory_condition == VictoryCondition.option_pure_ending: location.access_rule = lambda state, items_needed=lock.items_needed: ( - has_perk_count(state, world.player, items_needed//2) and - has_orb_count(state, world.player, items_needed) + state.has_group_unique("Perks", world.player, items_needed // 2) and + state.has("Orb", world.player, items_needed) ) elif victory_condition == VictoryCondition.option_peaceful_ending: location.access_rule = lambda state, items_needed=lock.items_needed: ( - has_perk_count(state, world.player, items_needed//2) and - has_orb_count(state, world.player, items_needed * 3) + state.has_group_unique("Perks", world.player, items_needed // 2) and + state.has("Orb", world.player, items_needed * 3) ) def biome_unlock_conditions(world: "NoitaWorld") -> None: - lukki_entrances = world.multiworld.get_region("Lukki Lair", world.player).entrances - magical_entrances = world.multiworld.get_region("Magical Temple", world.player).entrances - wizard_entrances = world.multiworld.get_region("Wizards' Den", world.player).entrances + lukki_entrances = world.get_region("Lukki Lair").entrances + magical_entrances = world.get_region("Magical Temple").entrances + wizard_entrances = world.get_region("Wizards' Den").entrances for entrance in lukki_entrances: - entrance.access_rule = lambda state: state.has("Melee Immunity Perk", world.player) and\ - state.has("All-Seeing Eye Perk", world.player) + entrance.access_rule = lambda state: ( + state.has_all(("Melee Immunity Perk", "All-Seeing Eye Perk"), world.player)) for entrance in magical_entrances: entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", world.player) for entrance in wizard_entrances: @@ -144,12 +135,12 @@ def biome_unlock_conditions(world: "NoitaWorld") -> None: def victory_unlock_conditions(world: "NoitaWorld") -> None: victory_condition = world.options.victory_condition.value - victory_location = world.multiworld.get_location("Victory", world.player) + victory_location = world.get_location("Victory") if victory_condition == VictoryCondition.option_pure_ending: - victory_location.access_rule = lambda state: has_orb_count(state, world.player, 11) + victory_location.access_rule = lambda state: state.has("Orb", world.player, 11) elif victory_condition == VictoryCondition.option_peaceful_ending: - victory_location.access_rule = lambda state: has_orb_count(state, world.player, 33) + victory_location.access_rule = lambda state: state.has("Orb", world.player, 33) # ---------------- @@ -168,5 +159,5 @@ def create_all_rules(world: "NoitaWorld") -> None: # Prevent the Map perk (used to find Toveri) from being on Toveri (boss) if world.options.bosses_as_checks.value >= BossesAsChecks.option_all_bosses: - toveri = world.multiworld.get_location("Toveri", world.player) + toveri = world.get_location("Toveri") GenericRules.forbid_items_for_player(toveri, {"Spatial Awareness Perk"}, world.player)