Noita: Implement Extra Orbs, Shop Price Reduction, and some slight region tweaks (#1972)
Co-authored-by: Adam Heinermann <aheinerm@gmail.com>
This commit is contained in:
		
							parent
							
								
									83387da6a4
								
							
						
					
					
						commit
						8360435607
					
				| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
import itertools
 | 
					import itertools
 | 
				
			||||||
from collections import Counter
 | 
					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 BaseClasses import Item, ItemClassification, MultiWorld
 | 
				
			||||||
from .Options import BossesAsChecks, VictoryCondition
 | 
					from .Options import BossesAsChecks, VictoryCondition, ExtraOrbs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ItemData(NamedTuple):
 | 
					class ItemData(NamedTuple):
 | 
				
			||||||
    code: Optional[int]
 | 
					    code: int
 | 
				
			||||||
    group: str
 | 
					    group: str
 | 
				
			||||||
    classification: ItemClassification = ItemClassification.progression
 | 
					    classification: ItemClassification = ItemClassification.progression
 | 
				
			||||||
    required_num: int = 0
 | 
					    required_num: int = 0
 | 
				
			||||||
| 
						 | 
					@ -27,12 +27,12 @@ def create_fixed_item_pool() -> List[str]:
 | 
				
			||||||
    return list(Counter(required_items).elements())
 | 
					    return list(Counter(required_items).elements())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_orb_items(victory_condition: VictoryCondition) -> List[str]:
 | 
					def create_orb_items(victory_condition: VictoryCondition, extra_orbs: ExtraOrbs) -> List[str]:
 | 
				
			||||||
    orb_count = 0
 | 
					    orb_count = extra_orbs.value
 | 
				
			||||||
    if victory_condition == VictoryCondition.option_pure_ending:
 | 
					    if victory_condition == VictoryCondition.option_pure_ending:
 | 
				
			||||||
        orb_count = 11
 | 
					        orb_count = orb_count + 11
 | 
				
			||||||
    elif victory_condition == VictoryCondition.option_peaceful_ending:
 | 
					    elif victory_condition == VictoryCondition.option_peaceful_ending:
 | 
				
			||||||
        orb_count = 33
 | 
					        orb_count = orb_count + 33
 | 
				
			||||||
    return ["Orb" for _ in range(orb_count)]
 | 
					    return ["Orb" for _ in range(orb_count)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,7 +61,7 @@ def create_all_items(multiworld: MultiWorld, player: int) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    itempool = (
 | 
					    itempool = (
 | 
				
			||||||
        create_fixed_item_pool()
 | 
					        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_spatial_awareness_item(multiworld.bosses_as_checks[player])
 | 
				
			||||||
        + create_kantele(multiworld.victory_condition[player])
 | 
					        + create_kantele(multiworld.victory_condition[player])
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
# Locations are specific points that you would obtain an item at.
 | 
					# Locations are specific points that you would obtain an item at.
 | 
				
			||||||
from enum import IntEnum
 | 
					from enum import IntEnum
 | 
				
			||||||
from typing import Dict, NamedTuple, Optional
 | 
					from typing import Dict, NamedTuple, Optional, Set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from BaseClasses import Location
 | 
					from BaseClasses import Location
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ class NoitaLocation(Location):
 | 
				
			||||||
class LocationData(NamedTuple):
 | 
					class LocationData(NamedTuple):
 | 
				
			||||||
    id: int
 | 
					    id: int
 | 
				
			||||||
    flag: int = 0
 | 
					    flag: int = 0
 | 
				
			||||||
    ltype: Optional[str] = ""
 | 
					    ltype: Optional[str] = "shop"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LocationFlag(IntEnum):
 | 
					class LocationFlag(IntEnum):
 | 
				
			||||||
| 
						 | 
					@ -208,7 +208,16 @@ def generate_location_entries(locname: str, locinfo: LocationData) -> Dict[str,
 | 
				
			||||||
    return {locname: locinfo.id}
 | 
					    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] = {}
 | 
					location_name_to_id: Dict[str, int] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for location_group in location_region_mapping.values():
 | 
					for location_group in location_region_mapping.values():
 | 
				
			||||||
    for locname, locinfo in location_group.items():
 | 
					    for locname, locinfo in location_group.items():
 | 
				
			||||||
        location_name_to_id.update(generate_location_entries(locname, locinfo))
 | 
					        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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
from typing import Dict
 | 
					from typing import Dict
 | 
				
			||||||
from Options import Choice, DeathLink, DefaultOnToggle, Option, Range
 | 
					from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Range, StartInventoryPool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PathOption(Choice):
 | 
					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).
 | 
					# The sampo is required for every ending (having orbs and bringing the sampo to a different spot changes the ending).
 | 
				
			||||||
class VictoryCondition(Choice):
 | 
					class VictoryCondition(Choice):
 | 
				
			||||||
    """Greed is to get to the bottom, beat the boss, and win the game.
 | 
					    """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.
 | 
					    Pure is to get 11 orbs, 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.
 | 
					    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 according to what victory condition you chose.
 | 
					    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."""
 | 
					    The base game orbs will not count towards these victory conditions."""
 | 
				
			||||||
    display_name = "Victory Condition"
 | 
					    display_name = "Victory Condition"
 | 
				
			||||||
    option_greed_ending = 0
 | 
					    option_greed_ending = 0
 | 
				
			||||||
| 
						 | 
					@ -77,7 +77,30 @@ class VictoryCondition(Choice):
 | 
				
			||||||
    default = 0
 | 
					    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,
 | 
					    "death_link": DeathLink,
 | 
				
			||||||
    "bad_effects": Traps,
 | 
					    "bad_effects": Traps,
 | 
				
			||||||
    "victory_condition": VictoryCondition,
 | 
					    "victory_condition": VictoryCondition,
 | 
				
			||||||
| 
						 | 
					@ -86,4 +109,6 @@ noita_options: Dict[str, type(Option)] = {
 | 
				
			||||||
    "pedestal_checks": PedestalChecks,
 | 
					    "pedestal_checks": PedestalChecks,
 | 
				
			||||||
    "orbs_as_checks": OrbsAsChecks,
 | 
					    "orbs_as_checks": OrbsAsChecks,
 | 
				
			||||||
    "bosses_as_checks": BossesAsChecks,
 | 
					    "bosses_as_checks": BossesAsChecks,
 | 
				
			||||||
 | 
					    "extra_orbs": ExtraOrbs,
 | 
				
			||||||
 | 
					    "shop_price": ShopPrice,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
					# - 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)
 | 
					# - 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
 | 
					# - 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]] = {
 | 
					noita_connections: Dict[str, Set[str]] = {
 | 
				
			||||||
    "Menu": {"Forest"},
 | 
					    "Menu": {"Forest"},
 | 
				
			||||||
    "Forest": {"Mines", "Floating Island", "Desert", "Snowy Wasteland"},
 | 
					    "Forest": {"Mines", "Floating Island", "Desert", "Snowy Wasteland"},
 | 
				
			||||||
| 
						 | 
					@ -96,12 +97,12 @@ noita_connections: Dict[str, Set[str]] = {
 | 
				
			||||||
    "Lava Lake": {"Mines", "Abyss Orb Room"},
 | 
					    "Lava Lake": {"Mines", "Abyss Orb Room"},
 | 
				
			||||||
    "Abyss Orb Room": {"Lava Lake"},
 | 
					    "Abyss Orb Room": {"Lava Lake"},
 | 
				
			||||||
    "Below Lava Lake": {"Snowy Depths"},
 | 
					    "Below Lava Lake": {"Snowy Depths"},
 | 
				
			||||||
    "Dark Cave": {"Ancient Laboratory", "Collapsed Mines"},
 | 
					    "Dark Cave": {"Collapsed Mines"},
 | 
				
			||||||
    "Ancient Laboratory": {"Dark Cave"},
 | 
					    "Ancient Laboratory": {"Coal Pits"},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ###
 | 
					    ###
 | 
				
			||||||
    "Coal Pits Holy Mountain": {"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"},
 | 
					    "Fungal Caverns": {"Coal Pits"},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ###
 | 
					    ###
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,9 +45,9 @@ wand_tiers: List[str] = [
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
items_hidden_from_shops: Set[str] = {"Gold (200)", "Gold (1000)", "Potion", "Random Potion", "Secret Potion",
 | 
					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",
 | 
					                                      "Chaos Die", "Greed Die", "Kammi", "Refreshing Gourd", "Sädekivi", "Broken Wand",
 | 
				
			||||||
                                     "Powder Pouch"}
 | 
					                                      "Powder Pouch"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
perk_list: List[str] = list(filter(Items.item_is_perk, Items.item_table.keys()))
 | 
					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:
 | 
					def victory_unlock_conditions(multiworld: MultiWorld, player: int) -> None:
 | 
				
			||||||
    victory_condition = multiworld.victory_condition[player].value
 | 
					    victory_condition = multiworld.victory_condition[player].value
 | 
				
			||||||
    victory_location = multiworld.get_location("Victory", player)
 | 
					    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)
 | 
					    ban_early_high_tier_wands(multiworld, player)
 | 
				
			||||||
    lock_holy_mountains_into_spheres(multiworld, player)
 | 
					    lock_holy_mountains_into_spheres(multiworld, player)
 | 
				
			||||||
    holy_mountain_unlock_conditions(multiworld, player)
 | 
					    holy_mountain_unlock_conditions(multiworld, player)
 | 
				
			||||||
 | 
					    biome_unlock_conditions(multiworld, player)
 | 
				
			||||||
    victory_unlock_conditions(multiworld, player)
 | 
					    victory_unlock_conditions(multiworld, player)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Prevent the Map perk (used to find Toveri) from being on Toveri (boss)
 | 
					    # Prevent the Map perk (used to find Toveri) from being on Toveri (boss)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,8 @@ class NoitaWorld(World):
 | 
				
			||||||
    location_name_to_id = Locations.location_name_to_id
 | 
					    location_name_to_id = Locations.location_name_to_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    item_name_groups = Items.item_name_groups
 | 
					    item_name_groups = Items.item_name_groups
 | 
				
			||||||
    data_version = 1
 | 
					    location_name_groups = Locations.location_name_groups
 | 
				
			||||||
 | 
					    data_version = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    web = NoitaWeb()
 | 
					    web = NoitaWeb()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue