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