From 836043560749190915ec4aae2a40ef14a6de8168 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Tue, 18 Jul 2023 22:51:01 -0400 Subject: [PATCH] Noita: Implement Extra Orbs, Shop Price Reduction, and some slight region tweaks (#1972) Co-authored-by: Adam Heinermann --- worlds/noita/Items.py | 16 ++++++++-------- worlds/noita/Locations.py | 13 +++++++++++-- worlds/noita/Options.py | 35 ++++++++++++++++++++++++++++++----- worlds/noita/Regions.py | 7 ++++--- worlds/noita/Rules.py | 20 +++++++++++++++++--- worlds/noita/__init__.py | 3 ++- 6 files changed, 72 insertions(+), 22 deletions(-) diff --git a/worlds/noita/Items.py b/worlds/noita/Items.py index 6499e945..ca53c962 100644 --- a/worlds/noita/Items.py +++ b/worlds/noita/Items.py @@ -1,13 +1,13 @@ import itertools from collections import Counter -from typing import Dict, List, NamedTuple, Optional, Set +from typing import Dict, List, NamedTuple, Set from BaseClasses import Item, ItemClassification, MultiWorld -from .Options import BossesAsChecks, VictoryCondition +from .Options import BossesAsChecks, VictoryCondition, ExtraOrbs class ItemData(NamedTuple): - code: Optional[int] + code: int group: str classification: ItemClassification = ItemClassification.progression required_num: int = 0 @@ -27,12 +27,12 @@ def create_fixed_item_pool() -> List[str]: return list(Counter(required_items).elements()) -def create_orb_items(victory_condition: VictoryCondition) -> List[str]: - orb_count = 0 +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 = 11 + orb_count = orb_count + 11 elif victory_condition == VictoryCondition.option_peaceful_ending: - orb_count = 33 + orb_count = orb_count + 33 return ["Orb" for _ in range(orb_count)] @@ -61,7 +61,7 @@ def create_all_items(multiworld: MultiWorld, player: int) -> None: itempool = ( create_fixed_item_pool() - + create_orb_items(multiworld.victory_condition[player]) + + 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]) ) diff --git a/worlds/noita/Locations.py b/worlds/noita/Locations.py index 6ad7b142..7c27d699 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 +from typing import Dict, NamedTuple, Optional, Set from BaseClasses import Location @@ -12,7 +12,7 @@ class NoitaLocation(Location): class LocationData(NamedTuple): id: int flag: int = 0 - ltype: Optional[str] = "" + ltype: Optional[str] = "shop" class LocationFlag(IntEnum): @@ -208,7 +208,16 @@ def generate_location_entries(locname: str, locinfo: LocationData) -> Dict[str, return {locname: locinfo.id} +location_name_groups: Dict[str, Set[str]] = {"shop": set(), "orb": set(), "boss": set(), "chest": set(), + "pedestal": set()} location_name_to_id: Dict[str, int] = {} + + for location_group in location_region_mapping.values(): for locname, locinfo in location_group.items(): location_name_to_id.update(generate_location_entries(locname, locinfo)) + if locinfo.ltype in ["chest", "pedestal"]: + for i in range(20): + location_name_groups[locinfo.ltype].add(f"{locname} {i + 1}") + else: + location_name_groups[locinfo.ltype].add(locname) diff --git a/worlds/noita/Options.py b/worlds/noita/Options.py index 270814f4..0b54597f 100644 --- a/worlds/noita/Options.py +++ b/worlds/noita/Options.py @@ -1,5 +1,5 @@ from typing import Dict -from Options import Choice, DeathLink, DefaultOnToggle, Option, Range +from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Range, StartInventoryPool class PathOption(Choice): @@ -66,9 +66,9 @@ class BossesAsChecks(Choice): # The sampo is required for every ending (having orbs and bringing the sampo to a different spot changes the ending). class VictoryCondition(Choice): """Greed is to get to the bottom, beat the boss, and win the game. - Pure is to get the 11 orbs in the main world, grab the sampo, and bring it to the mountain altar. - Peaceful is to get all 33 orbs in main + parallel, grab the sampo, and bring it to the mountain altar. - Orbs will be added to the randomizer pool according to what victory condition you chose. + Pure is to get 11 orbs, grab the sampo, and bring it to the mountain altar. + Peaceful is to get all 33 orbs, grab the sampo, and bring it to the mountain altar. + Orbs will be added to the randomizer pool based on which victory condition you chose. The base game orbs will not count towards these victory conditions.""" display_name = "Victory Condition" option_greed_ending = 0 @@ -77,7 +77,30 @@ class VictoryCondition(Choice): default = 0 -noita_options: Dict[str, type(Option)] = { +class ExtraOrbs(Range): + """Add extra orbs to your item pool, to prevent you from needing to wait as long + for the last orb you need for your victory condition. + Extra orbs received past your victory condition's amount will be received as hearts instead. + Can be turned on for the Greed Ending goal, but will only really make it harder.""" + display_name = "Extra Orbs" + range_start = 0 + range_end = 10 + default = 0 + + +class ShopPrice(Choice): + """Reduce the costs of Archipelago items in shops. + By default, the price of Archipelago items matches the price of wands at that shop.""" + display_name = "Shop Price Reduction" + option_full_price = 100 + option_25_percent_off = 75 + option_50_percent_off = 50 + option_75_percent_off = 25 + default = 100 + + +noita_options: Dict[str, AssembleOptions] = { + "start_inventory_from_pool": StartInventoryPool, "death_link": DeathLink, "bad_effects": Traps, "victory_condition": VictoryCondition, @@ -86,4 +109,6 @@ noita_options: Dict[str, type(Option)] = { "pedestal_checks": PedestalChecks, "orbs_as_checks": OrbsAsChecks, "bosses_as_checks": BossesAsChecks, + "extra_orbs": ExtraOrbs, + "shop_price": ShopPrice, } diff --git a/worlds/noita/Regions.py b/worlds/noita/Regions.py index 93d5a19a..a239b437 100644 --- a/worlds/noita/Regions.py +++ b/worlds/noita/Regions.py @@ -78,6 +78,7 @@ def create_all_regions_and_connections(multiworld: MultiWorld, player: int) -> N # - Frozen Vault is connected to the Vault instead of the Snowy Wasteland due to similar difficulty # - Lake is connected to The Laboratory, since the boss is 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, Set[str]] = { "Menu": {"Forest"}, "Forest": {"Mines", "Floating Island", "Desert", "Snowy Wasteland"}, @@ -96,12 +97,12 @@ noita_connections: Dict[str, Set[str]] = { "Lava Lake": {"Mines", "Abyss Orb Room"}, "Abyss Orb Room": {"Lava Lake"}, "Below Lava Lake": {"Snowy Depths"}, - "Dark Cave": {"Ancient Laboratory", "Collapsed Mines"}, - "Ancient Laboratory": {"Dark Cave"}, + "Dark Cave": {"Collapsed Mines"}, + "Ancient Laboratory": {"Coal Pits"}, ### "Coal Pits Holy Mountain": {"Coal Pits"}, - "Coal Pits": {"Coal Pits Holy Mountain", "Fungal Caverns", "Snowy Depths Holy Mountain"}, + "Coal Pits": {"Coal Pits Holy Mountain", "Fungal Caverns", "Snowy Depths Holy Mountain", "Ancient Laboratory"}, "Fungal Caverns": {"Coal Pits"}, ### diff --git a/worlds/noita/Rules.py b/worlds/noita/Rules.py index e0e4b16b..3eb6be5a 100644 --- a/worlds/noita/Rules.py +++ b/worlds/noita/Rules.py @@ -45,9 +45,9 @@ wand_tiers: List[str] = [ ] -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"} +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())) @@ -126,6 +126,19 @@ def holy_mountain_unlock_conditions(multiworld: MultiWorld, player: int) -> None ) +def biome_unlock_conditions(multiworld: MultiWorld, player: int): + lukki_entrances = multiworld.get_region("Lukki Lair", player).entrances + magical_entrances = multiworld.get_region("Magical Temple", player).entrances + wizard_entrances = multiworld.get_region("Wizards' Den", player).entrances + for entrance in lukki_entrances: + entrance.access_rule = lambda state: state.has("Melee Immunity Perk", player) and\ + state.has("All-Seeing Eye Perk", player) + for entrance in magical_entrances: + entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", player) + for entrance in wizard_entrances: + entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", player) + + def victory_unlock_conditions(multiworld: MultiWorld, player: int) -> None: victory_condition = multiworld.victory_condition[player].value victory_location = multiworld.get_location("Victory", player) @@ -146,6 +159,7 @@ def create_all_rules(multiworld: MultiWorld, player: int) -> None: 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) diff --git a/worlds/noita/__init__.py b/worlds/noita/__init__.py index 253c8e9d..499d202a 100644 --- a/worlds/noita/__init__.py +++ b/worlds/noita/__init__.py @@ -30,7 +30,8 @@ class NoitaWorld(World): location_name_to_id = Locations.location_name_to_id item_name_groups = Items.item_name_groups - data_version = 1 + location_name_groups = Locations.location_name_groups + data_version = 2 web = NoitaWeb()