diff --git a/worlds/RAC1/ItemPool.py b/worlds/RAC1/ItemPool.py index 6dc89a84232c..a3c9082eadba 100644 --- a/worlds/RAC1/ItemPool.py +++ b/worlds/RAC1/ItemPool.py @@ -4,41 +4,37 @@ def get_classification(item: ItemData) -> ItemClassification: - if item in Items.PLANETS: + if (item in Items.PLANETS + or item in Items.ALL_PACKS + or item in Items.GADGETS + or item in Items.ALL_BOOTS + or item in Items.GOLD_BOLTS): return ItemClassification.progression if item in [ - Items.HELI_PACK, - Items.THRUSTER_PACK, - Items.HYDRO_PACK, - Items.SWINGSHOT, - Items.MAGNEBOOTS, - Items.GRINDBOOTS, - Items.HYDRODISPLACER, Items.TAUNTER, Items.O2_MASK, Items.PILOTS_HELMET, - Items.TRESPASSER, - Items.HOLOGUISE, + Items.PROGRESSIVE_HELMET, Items.CODEBOT, Items.RARITANIUM, Items.HOVERBOARD, Items.ZOOMERATOR, + Items.PROGRESSIVE_HOVERBOARD, Items.BOMB_GLOVE, + Items.PROGRESSIVE_BOMB, + Items.BLASTER, + Items.MINE_GLOVE, + Items.PROGRESSIVE_MINE, Items.DEVASTATOR, + Items.PROGRESSIVE_DEVASTATOR, Items.VISIBOMB, - Items.METAL_DETECTOR, + Items.RYNO, + Items.PROGRESSIVE_TRADE, ]: return ItemClassification.progression - if item in [ - Items.BOLT_GRABBER, - Items.PERSUADER, - Items.PREMIUM_NANOTECH, - Items.ULTRA_NANOTECH, - ]: - return ItemClassification.useful - if item in Items.WEAPONS: - return ItemClassification.useful - if item in Items.GOLDEN_WEAPONS: + if (item == Items.SONIC_SUMMONER + or item in Items.ALL_WEAPONS + or item in Items.ALL_EXTRA_ITEMS): return ItemClassification.useful return ItemClassification.filler diff --git a/worlds/RAC1/Logic.py b/worlds/RAC1/Logic.py index 7f44300542f9..8976712369d1 100644 --- a/worlds/RAC1/Logic.py +++ b/worlds/RAC1/Logic.py @@ -1,41 +1,43 @@ +import logging + from BaseClasses import CollectionState from .data import Items +rac_logger = logging.getLogger("Ratchet & Clank") +rac_logger.setLevel(logging.DEBUG) + def can_swingshot(state: CollectionState, player: int) -> bool: return state.has(Items.SWINGSHOT.name, player) def can_improved_jump(state: CollectionState, player: int) -> bool: - return state.has_any([Items.HELI_PACK.name, Items.THRUSTER_PACK.name], player) + return (state.has_any_count(Items.PROG[Items.HELI_PACK.name], player) or + state.has_any_count(Items.PROG[Items.THRUSTER_PACK.name], player)) def can_heli_high_jump(state: CollectionState, player: int) -> bool: # relevant for eudora gold bolt - return state.has(Items.HELI_PACK.name, player) + return state.has_any_count(Items.PROG[Items.HELI_PACK.name], player) def can_glide(state: CollectionState, player: int) -> bool: # gliding is not possible without the heli pack - return state.has(Items.HELI_PACK.name, player) + return state.has_any_count(Items.PROG[Items.HELI_PACK.name], player) def can_ground_pound(state: CollectionState, player: int) -> bool: - return state.has(Items.THRUSTER_PACK.name, player) + return state.has_any_count(Items.PROG[Items.THRUSTER_PACK.name], player) def has_hydro_pack(state: CollectionState, player: int) -> bool: - return state.has(Items.HYDRO_PACK.name, player) + return state.has_any_count(Items.PROG[Items.HYDRO_PACK.name], player) def can_grind(state: CollectionState, player: int) -> bool: - return state.has(Items.GRINDBOOTS.name, player) + return state.has_any_count(Items.PROG[Items.GRINDBOOTS.name], player) def has_magneboots(state: CollectionState, player: int) -> bool: - return state.has(Items.MAGNEBOOTS.name, player) - - -def can_taunt(state: CollectionState, player: int) -> bool: - return state.has(Items.TAUNTER.name, player) + return state.has_any_count(Items.PROG[Items.MAGNEBOOTS.name], player) def has_hydrodisplacer(state: CollectionState, player: int) -> bool: @@ -43,19 +45,19 @@ def has_hydrodisplacer(state: CollectionState, player: int) -> bool: def has_raritanium(state: CollectionState, player: int) -> bool: - return state.has(Items.RARITANIUM.name, player) + return state.has_any_count(Items.PROG[Items.RARITANIUM.name], player) def has_zoomerator(state: CollectionState, player: int) -> bool: - return state.has(Items.ZOOMERATOR.name, player) + return state.has_any_count(Items.PROG[Items.ZOOMERATOR.name], player) def has_hoverboard(state: CollectionState, player: int) -> bool: - return state.has(Items.HOVERBOARD.name, player) + return state.has_any_count(Items.PROG[Items.HOVERBOARD.name], player) def has_o2_mask(state: CollectionState, player: int) -> bool: - return state.has(Items.O2_MASK.name, player) + return state.has_any_count(Items.PROG[Items.O2_MASK.name], player) def has_trespasser(state: CollectionState, player: int) -> bool: @@ -71,7 +73,7 @@ def has_hologuise(state: CollectionState, player: int) -> bool: def has_pilots_helmet(state: CollectionState, player: int) -> bool: - return state.has(Items.PILOTS_HELMET.name, player) + return state.has_any_count(Items.PROG[Items.PILOTS_HELMET.name], player) def has_codebot(state: CollectionState, player: int) -> bool: @@ -81,26 +83,73 @@ def has_codebot(state: CollectionState, player: int) -> bool: def has_taunter(state: CollectionState, player: int) -> bool: return state.has(Items.TAUNTER.name, player) - +# TODO Logic for accessing dig spots on each planet def has_metal_detector(state: CollectionState, player: int) -> bool: return state.has(Items.METAL_DETECTOR.name, player) def has_explosive_weapon(state: CollectionState, player: int) -> bool: - return (state.has_any( - [Items.BOMB_GLOVE.name, Items.MINE_GLOVE.name, Items.DEVASTATOR.name, Items.VISIBOMB.name, Items.RYNO.name], - player)) + return (state.has_any_count(Items.PROG[Items.BOMB_GLOVE.name], player) or + state.has_any_count(Items.PROG[Items.MINE_GLOVE.name], player) or + state.has_any_count(Items.PROG[Items.DEVASTATOR.name], player) or + state.has_any([Items.VISIBOMB.name, Items.RYNO.name], player)) def has_long_range_weapon(state: CollectionState, player: int) -> bool: - return (state.has_any([Items.BLASTER.name, - Items.DEVASTATOR.name, - Items.VISIBOMB.name, - Items.RYNO.name], player)) + return (state.has_any_count(Items.PROG[Items.BLASTER.name], player) or + state.has_any_count(Items.PROG[Items.DEVASTATOR.name], player) or + state.has_any([Items.VISIBOMB.name, Items.RYNO.name], player)) def has_40_gold_bolts(state: CollectionState, player: int) -> bool: - return state.has(Items.GOLD_BOLT.name, player, 40) + lookup: dict[int, tuple[str, int]] = { + 1: (Items.GOLD_BOLT_1.name, 40), + 2: (Items.GOLD_BOLT_2.name, 20), + 3: (Items.GOLD_BOLT_3.name, 14), + 4: (Items.GOLD_BOLT_4.name, 10), + 5: (Items.GOLD_BOLT_5.name, 8), + 6: (Items.GOLD_BOLT_6.name, 7), + 7: (Items.GOLD_BOLT_7.name, 6), + 8: (Items.GOLD_BOLT_8.name, 5), + 9: (Items.GOLD_BOLT_9.name, 5), + 10: (Items.GOLD_BOLT_10.name, 4), + 11: (Items.GOLD_BOLT_11.name, 4), + 12: (Items.GOLD_BOLT_12.name, 4), + 13: (Items.GOLD_BOLT_13.name, 4), + 14: (Items.GOLD_BOLT_14.name, 3), + 15: (Items.GOLD_BOLT_15.name, 3), + 16: (Items.GOLD_BOLT_16.name, 3), + 17: (Items.GOLD_BOLT_17.name, 3), + 18: (Items.GOLD_BOLT_18.name, 3), + 19: (Items.GOLD_BOLT_19.name, 3), + 20: (Items.GOLD_BOLT_20.name, 2), + 21: (Items.GOLD_BOLT_21.name, 2), + 22: (Items.GOLD_BOLT_22.name, 2), + 23: (Items.GOLD_BOLT_23.name, 2), + 24: (Items.GOLD_BOLT_24.name, 2), + 25: (Items.GOLD_BOLT_25.name, 2), + 26: (Items.GOLD_BOLT_26.name, 2), + 27: (Items.GOLD_BOLT_27.name, 2), + 28: (Items.GOLD_BOLT_28.name, 2), + 29: (Items.GOLD_BOLT_29.name, 2), + 30: (Items.GOLD_BOLT_30.name, 2), + 31: (Items.GOLD_BOLT_31.name, 2), + 32: (Items.GOLD_BOLT_32.name, 2), + 33: (Items.GOLD_BOLT_33.name, 2), + 34: (Items.GOLD_BOLT_34.name, 2), + 35: (Items.GOLD_BOLT_35.name, 2), + 36: (Items.GOLD_BOLT_36.name, 2), + 37: (Items.GOLD_BOLT_37.name, 2), + 38: (Items.GOLD_BOLT_38.name, 2), + 39: (Items.GOLD_BOLT_39.name, 2), + 40: (Items.GOLD_BOLT_40.name, 1), + } + item, count = lookup[state.multiworld.worlds[player].options.pack_size_gold_bolts.value] + if state.count(item, player) < count: + rac_logger.debug(f"Missing gold bolt packs from world, expected {count} but only had" + f" {state.count(item, player)}. Can reach " + f"{state.prog_items}") + return state.has(item, player, count) # Novalis diff --git a/worlds/RAC1/Options.py b/worlds/RAC1/Options.py index a0b7b66c5c61..975933108bad 100644 --- a/worlds/RAC1/Options.py +++ b/worlds/RAC1/Options.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Any -from Options import (Choice, PerGameCommonOptions, TextChoice, Toggle) +from Options import (Choice, PerGameCommonOptions, Range, TextChoice, Toggle) class ItemOptions(Choice): @@ -21,18 +21,30 @@ class ItemOptions(Choice): alias_false = 0 -class StartingItem(ItemOptions): +class StartingItem(Choice): """Randomize what weapon you start the game with. vanilla: Start with the Bomb Glove. random_same: Start with a random weapon. - random_item: Start with any random weapon, gadget, pack, helmet, boots, item, or infobot. - unrestricted: Start with anything (including Gold Bolts and Skillpoints). + random_item: Start with any random equipable item, weapons or gadgets. """ - display_name = "Starting Weapon" + display_name = "Starting Item" + rich_text_doc = True + value: int + option_vanilla = 0 + option_random_same = 1 + option_random_item = 2 default = 0 + alias_true = 2 + alias_false = 0 pool = "StartItem" +class StartingLocation(Toggle): + """Randomize what Planet you start on""" + display_name = "Shuffle Starting Planet" + default = 1 + + class ShuffleWeapons(ItemOptions): """Randomize Weapon locations vanilla: Weapons are unshuffled. @@ -41,17 +53,18 @@ class ShuffleWeapons(ItemOptions): unrestricted: Weapons are shuffled anywhere, anything can be found at Weapon locations. """ display_name = "Shuffle Weapons" + rich_text_doc = True default = 3 pool = "Weapons" -# TODO: Early Weapon option, off, shuffled amount (user states amount), list (user lists items) class EarlyWeapon(TextChoice): """ Force a weapon to be in your sphere 1. Set to off if 'Randomize Weapon locations' option is set to 'vanilla or random_same'. """ display_name = "Early Weapon" + rich_text_doc = True class ShuffleGadgets(ItemOptions): @@ -62,6 +75,7 @@ class ShuffleGadgets(ItemOptions): unrestricted: Gadgets are shuffled anywhere, anything can be found at Gadget locations. """ display_name = "Shuffle Gadgets" + rich_text_doc = True default = 3 pool = "Gadgets" @@ -74,6 +88,7 @@ class ShufflePacks(ItemOptions): unrestricted: Packs are shuffled anywhere, anything can be found at Pack locations. """ display_name = "Shuffle Packs" + rich_text_doc = True default = 3 pool = "Packs" @@ -86,6 +101,7 @@ class ShuffleHelmets(ItemOptions): unrestricted: Helmets are shuffled anywhere, anything can be found at Helmet locations. """ display_name = "Shuffle Helmets" + rich_text_doc = True default = 3 pool = "Helmets" @@ -98,6 +114,7 @@ class ShuffleBoots(ItemOptions): unrestricted: Boots are shuffled anywhere, anything can be found at Boot locations. """ display_name = "Shuffle Boots" + rich_text_doc = True default = 3 pool = "Boots" @@ -110,6 +127,7 @@ class ShuffleExtraItems(ItemOptions): unrestricted: Extra Items are shuffled anywhere, anything can be found at Extra Item locations. """ display_name = "Shuffle Extra Items" + rich_text_doc = True default = 3 pool = "ExtraItems" @@ -120,14 +138,59 @@ class ShuffleGoldBolts(Toggle): default = 1 +class GoldBoltPackSize(Range): + """ + Number of Gold Bolts received each time you collect a pack of Gold Bolts (Gold Bolts Shuffle Off forces this to 1) + """ + display_name = "Gold Bolt Pack Size" + default = 8 + range_start = 1 + range_end = 40 + + +class BoltPackSize(Choice): + """Number of Bolts received each time you collect a pack of Bolts.""" + display_name = "Bolt Pack Size" + option_0 = 0 + option_1 = 1 + option_10 = 10 + option_100 = 100 + option_250 = 250 + option_500 = 500 + option_750 = 750 + option_1000 = 1000 + option_2000 = 2000 + option_3000 = 3000 + option_4000 = 4000 + option_5000 = 5000 + option_6000 = 6000 + option_7000 = 7000 + option_8000 = 8000 + option_9000 = 9000 + option_10000 = 10000 + option_12500 = 12500 + option_15000 = 15000 + option_17500 = 17500 + option_20000 = 20000 + option_25000 = 25000 + option_30000 = 30000 + option_40000 = 40000 + option_50000 = 50000 + option_75000 = 75000 + option_100000 = 100000 + default = option_15000 + + class ShuffleInfobots(ItemOptions): """Randomize Infobot locations vanilla: Infobots are unshuffled. random_same: Infobots are shuffled to other Infobot locations. random_item: Infobots are shuffled anywhere, useful items are found at Infobot locations. + WARNING! Using random_same, or random_item with no other pool selected, is likely to fail on solo worlds. unrestricted: Infobots are shuffled anywhere, anything can be found at Infobot locations. """ - display_name = "Shuffle Infobots" + display_name = "Shuffle Infobots" # + rich_text_doc = True default = 3 pool = "Infobots" @@ -140,25 +203,186 @@ class ShuffleGoldWeapons(ItemOptions): unrestricted: Gold Weapons are shuffled anywhere, anything can be found at Gold Weapon locations. """ display_name = "Shuffle Gold Weapons" + rich_text_doc = True default = 3 pool = "GoldenWeapons" -class EnableBoltMultiplier(Toggle): + +class ShuffleSkillPoints(Toggle): + """Randomize Skillpoint locations""" + display_name = "Shuffle Skillpoints" + default = 1 + + +class EnableBoltMultiplier(Range): """Enables the bolt multiplier feature without being in New Game+.""" - display_name = "Enable Bolt Multiplier" + display_name = "Bolt Multiplier" + default = 5 + range_start = 1 + range_end = 20 + + +class MDBoltMultiplier(Range): + """Bolt Multiplier when using the metal detector""" + display_name = "Metal Detector Bolt Multiplier" + default = 35 + range_start = 1 + range_end = 100 + + +# TODO Option: Vendor in logic without metal detector + +class ProgressiveOptions(Choice): + """Template + vanilla: These items are not progressive, each item is independent of other items. + progressive: These items are progressive, collecting multiple of an item will upgrade it. + progressive_reversed: These items are progressive, the order of upgrading is reversed. + progressive_random: These items are progressive, the order of upgrading is random. + """ + value: int + option_vanilla = 0 + option_progressive = 1 + option_progressive_reversed = 2 + option_progressive_random = 3 + alias_true = 1 + alias_false = 0 + + +class GoldenWeaponProgression(ProgressiveOptions): + """ + If enabled, make golden weapons and their standard variants progressive items. + vanilla: Golden Weapons and Weapons are not progressive, Golden Weapons do nothing until their base item is + found. + normal: Golden Weapons and Weapons are not progressive, each item is independent of other items. + progressive: Golden Weapons and Weapons are progressive, collecting multiple of an item will upgrade it. + progressive_reversed: Golden Weapons and Weapons are progressive, the order of upgrading is reversed. + progressive_random: Golden Weapons and Weapons are progressive, the order of upgrading is random.""" + display_name = "Progressive Weapons" + rich_text_doc = True + value: int + option_vanilla = 0 + option_normal = 1 + option_progressive = 2 + option_progressive_reversed = 3 + option_progressive_random = 4 + alias_true = 0 + alias_false = 1 + default = 1 + + +class PackProgression(ProgressiveOptions): + """ + vanilla: Packs are not progressive, each item is independent of other items. + progressive: Packs are progressive, collecting multiple of an item will upgrade it. + progressive_reversed: Packs are progressive, the order of upgrading is reversed. + progressive_random: Packs are progressive, the order of upgrading is random. + """ + display_name = "Progressive Packs" + rich_text_doc = True + value: int + option_vanilla = 0 + option_progressive = 1 + option_progressive_reversed = 2 + option_progressive_random = 3 + alias_true = 1 + alias_false = 0 + + +class HelmetProgression(ProgressiveOptions): + """ + vanilla: Helmets are not progressive, each item is independent of other items. + progressive: Helmets are progressive, collecting multiple of an item will upgrade it. + progressive_reversed: Helmets are progressive, the order of upgrading is reversed. + progressive_random: Helmets are progressive, the order of upgrading is random. + """ + display_name = "Progressive Helmets" + rich_text_doc = True + value: int + option_vanilla = 0 + option_progressive = 1 + option_progressive_reversed = 2 + option_progressive_random = 3 + alias_true = 1 + alias_false = 0 + + +class BootsProgression(ProgressiveOptions): + """ + vanilla: Grind and Magneboots are not progressive, each item is independent of other items. + progressive: Grind and Magneboots are progressive, collecting multiple of an item will upgrade it. + progressive_reversed: Grind and Magneboots are progressive, the order of upgrading is reversed. + progressive_random: Grind and Magneboots are progressive, the order of upgrading is random. + """ + display_name = "Progressive Boots" + rich_text_doc = True + value: int + option_vanilla = 0 + option_progressive = 1 + option_progressive_reversed = 2 + option_progressive_random = 3 + alias_true = 1 + alias_false = 0 + +class HoverboardProgression(ProgressiveOptions): + """ + vanilla: Hoverboard and Zoomerator are not progressive, each item is independent of other items. + progressive: Hoverboard and Zoomerator are progressive, collecting multiple of an item will upgrade it. + progressive_reversed: Hoverboard and Zoomerator are progressive, the order of upgrading is reversed. + progressive_random: Hoverboard and Zoomerator are progressive, the order of upgrading is random. + """ + display_name = "Progressive Hoverboard" + rich_text_doc = True + value: int + option_vanilla = 0 + option_progressive = 1 + option_progressive_reversed = 2 + option_progressive_random = 3 + alias_true = 1 + alias_false = 0 + + +class RaritaniumProgression(ProgressiveOptions): + """ + vanilla: Raritanium and Persuader are not progressive, each item is independent of other items. + progressive: Raritanium and Persuader are progressive, collecting multiple of an item will upgrade it. + progressive_reversed: Raritanium and Persuader are progressive, the order of upgrading is reversed. + progressive_random: Raritanium and Persuader are progressive, the order of upgrading is random. + """ + display_name = "Progressive Raritanium" + rich_text_doc = True + value: int + option_vanilla = 0 + option_progressive = 1 + option_progressive_reversed = 2 + option_progressive_random = 3 + alias_true = 1 + alias_false = 0 -class GoldenWeaponProgression(Toggle): - """If enabled, make golden weapons and their standard variants progressive items. - This means that there are two copies of the regular item in the pool, when you collect one for the first time - then it is the standard version, but collecting the second one will give you the golden version.""" - display_name = "Golden Weapon Progression" + +class NanotechProgression(ProgressiveOptions): + """ + vanilla: Nanotech is not progressive, each item is independent of other items. + progressive: Nanotech are progressive, collecting multiple of an item will upgrade it. + progressive_reversed: Nanotech are progressive, the order of upgrading is reversed. + progressive_random: Nanotech are progressive, the order of upgrading is random. + """ + display_name = "Progressive Nanotech" + rich_text_doc = True + value: int + option_vanilla = 0 + option_progressive = 1 + option_progressive_reversed = 2 + option_progressive_random = 3 + alias_true = 1 + alias_false = 0 @dataclass class RacOptions(PerGameCommonOptions): # death_link: DeathLink starting_item: StartingItem + starting_location: StartingLocation shuffle_weapons: ShuffleWeapons shuffle_gadgets: ShuffleGadgets shuffle_packs: ShufflePacks @@ -168,15 +392,25 @@ class RacOptions(PerGameCommonOptions): shuffle_gold_bolts: ShuffleGoldBolts shuffle_infobots: ShuffleInfobots shuffle_gold_weapons: ShuffleGoldWeapons - # enable_bolt_multiplier: EnableBoltMultiplier - # extend_weapon_progression: GoldenWeaponProgression + # shuffle_skill_points: ShuffleSkillPoints + pack_size_gold_bolts: GoldBoltPackSize + pack_size_bolts: BoltPackSize + metal_bolt_multiplier: MDBoltMultiplier + enable_bolt_multiplier: EnableBoltMultiplier + progressive_weapons: GoldenWeaponProgression + progressive_packs: PackProgression + progressive_helmets: HelmetProgression + progressive_boots: BootsProgression + progressive_hoverboard: HoverboardProgression + progressive_raritanium: RaritaniumProgression + progressive_nanotech: NanotechProgression def get_options_as_dict(options: RacOptions) -> dict[str, Any]: return { # "death_link", - "start_inventory_from_pool": dict(), - "starting_item": options.starting_item.option_vanilla, + "starting_item": options.starting_item.value, + "starting_location": options.starting_location.value, "shuffle_weapons": options.shuffle_weapons.value, "shuffle_gadgets": options.shuffle_gadgets.value, "shuffle_packs": options.shuffle_packs.value, @@ -186,4 +420,16 @@ def get_options_as_dict(options: RacOptions) -> dict[str, Any]: "shuffle_gold_bolts": options.shuffle_gold_bolts.value, "shuffle_infobots": options.shuffle_infobots.value, "shuffle_gold_weapons": options.shuffle_gold_weapons.value, + # "shuffle_skill_points": options.shuffle_skill_points.value, + "pack_size_gold_bolts": options.pack_size_gold_bolts.value, + "pack_size_bolts": options.pack_size_bolts.value, + "metal_bolt_multiplier": options.metal_bolt_multiplier.value, + "enable_bolt_multiplier": options.enable_bolt_multiplier.value, + "progressive_weapons": options.progressive_weapons.value, + "progressive_packs": options.progressive_packs.value, + "progressive_helmets": options.progressive_helmets.value, + "progressive_boots": options.progressive_boots.value, + "progressive_hoverboard": options.progressive_hoverboard.value, + "progressive_raritanium": options.progressive_raritanium.value, + "progressive_nanotech": options.progressive_nanotech.value, } diff --git a/worlds/RAC1/Regions.py b/worlds/RAC1/Regions.py index ef100265dd77..9169dda2ac11 100644 --- a/worlds/RAC1/Regions.py +++ b/worlds/RAC1/Regions.py @@ -2,8 +2,10 @@ from BaseClasses import CollectionState, Location, Region from .data import Planets -from .data.Locations import LocationData +from .data.Items import check_progressive_item, get_gold_bolts +from .data.Locations import LocationData, POOL_GOLD_BOLT, POOL_GOLDEN_WEAPON from .data.Planets import PlanetData +from ..generic.Rules import forbid_item if typing.TYPE_CHECKING: from . import RacWorld @@ -47,10 +49,6 @@ def planet_access_rule(state: CollectionState): menu.connect(region, None, generate_planet_access_rule(planet_data)) for location_data in planet_data.locations: - # Don't create the location if there is a "pool" it is in that is not enabled - # if location_data.name in world.disabled_pools: - # continue - def generate_access_rule(loc: LocationData) -> typing.Callable[[CollectionState], bool]: def access_rule(state: CollectionState): if loc.access_rule: @@ -60,8 +58,15 @@ def access_rule(state: CollectionState): return access_rule region.add_locations({location_data.name: location_data.location_id}, RacLocation) + if POOL_GOLD_BOLT in location_data.pools: + location_data.vanilla_item = get_gold_bolts(world.options) + elif location_data.vanilla_item is not None: + location_data.vanilla_item = check_progressive_item(world.options, location_data.vanilla_item) + location = world.multiworld.get_location(location_data.name, world.player) location.access_rule = generate_access_rule(location_data) + if POOL_GOLDEN_WEAPON in location_data.pools: + forbid_item(location, get_gold_bolts(world.options), world.player) # from Utils import visualize_regions # visualize_regions(world.multiworld.get_region("Menu", world.player), "my_world.puml") diff --git a/worlds/RAC1/__init__.py b/worlds/RAC1/__init__.py index 496e67df3213..2c6d25e20aa8 100644 --- a/worlds/RAC1/__init__.py +++ b/worlds/RAC1/__init__.py @@ -1,17 +1,18 @@ import logging -from typing import Any, Mapping, Optional, Sequence +from typing import Any, Mapping, Optional -from BaseClasses import CollectionState, Item, ItemClassification, Location, Tutorial +from BaseClasses import CollectionState, Item, ItemClassification, Tutorial from Fill import fill_restrictive, FillError, sweep_from_pool from worlds.AutoWorld import WebWorld, World from worlds.LauncherComponents import Component, components, SuffixIdentifier, Type from . import ItemPool from .data import Items, Locations, Planets -from .data.Items import CollectableData, ItemData +from .data.Items import ALL_WEAPONS, check_progressive_item, CollectableData, get_bolt_pack, progression_rules from .data.Locations import (ALL_POOLS, DEFAULT_LIST, LocationData, POOL_BOOT, POOL_EXTRA_ITEM, POOL_GADGET, - POOL_GOLD_BOLT, POOL_HELMET, POOL_INFOBOT, POOL_PACK, POOL_WEAPON) + POOL_GOLD_BOLT, POOL_GOLDEN_WEAPON, POOL_HELMET, POOL_INFOBOT, POOL_PACK, POOL_SKILLPOINT, + POOL_WEAPON) from .data.Planets import ALL_LOCATIONS, location_groups, PlanetData -from .Options import RacOptions, ShuffleWeapons, StartingItem, ShuffleInfobots +from .Options import RacOptions, ShuffleGadgets, ShuffleInfobots, ShuffleWeapons, StartingItem, StartingLocation from .Regions import create_regions rac_logger = logging.getLogger("Ratchet & Clank") @@ -57,12 +58,13 @@ class RacWorld(World): location.location_id} item_name_groups = Items.get_item_groups() location_name_groups = location_groups + item_pool: dict[str, list[Item]] = {} starting_planet = Items.NOVALIS_INFOBOT.name - starting_items: list[Item] = [] - preplaced_locations: list[Location] = [] + preplaced_items: list[Item] = [] + orders: dict[str, list[int]] = {} - # def get_filler_item_name(self) -> str: - # return Items.BOLT_PACK.name + def get_filler_item_name(self) -> str: + return get_bolt_pack(self.options) def generate_early(self) -> None: rac_logger.debug(f"_________START EARLY GENERATION____________") @@ -71,11 +73,11 @@ def generate_early(self) -> None: self.player_name) rac_logger.warning("INCOMPLETE WORLD! Slot '%s' may require send_location/send_item for completion!", self.player_name) - self.starting_items = [] - self.preplaced_locations = [] - rac_logger.debug(f"Pre-placed Item List: {[loc.item for loc in self.preplaced_locations]}") - - # TODO: Item Pools + self.item_pool: dict[str, list[Item]] = {} + self.starting_planet = Items.NOVALIS_INFOBOT.name + self.preplaced_items = [] + rac_logger.debug(f"Pre-placed Item List: {self.preplaced_items}") + rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}") shuffle_pools: list = [ self.options.shuffle_infobots, @@ -96,10 +98,14 @@ def generate_early(self) -> None: enabled_pools += [POOL_GOLD_BOLT] else: disabled_pools += [POOL_GOLD_BOLT] + # if self.options.shuffle_skill_points.value: + # enabled_pools += [POOL_SKILLPOINT] + # else: + # disabled_pools += [POOL_SKILLPOINT] rac_logger.debug(f"Iterating through Options:") for pool_option in shuffle_pools: rac_logger.debug(f"Option: {pool_option}") - match pool_option.value: # TODO: starting item and planet + match pool_option.value: case Options.ItemOptions.option_vanilla: disabled_pools += [pool_option.pool] case Options.ItemOptions.option_random_same: @@ -121,92 +127,162 @@ def generate_early(self) -> None: rac_logger.debug(f"Useful Pools: {useful_pools}") rac_logger.debug(f"Enabled Pools: {enabled_pools}") + if not self.options.shuffle_gold_bolts.value: + self.options.pack_size_gold_bolts.value = 1 + + rac_logger.debug( + f"Gold Bolt Pack Size: {self.options.pack_size_gold_bolts.value}, Bolt pack size: " + f"{self.options.pack_size_bolts.value}") + + rac_logger.debug(f"Choose Progression Order") + self.orders = { + "progressive_suck_cannon_order": [Items.SUCK_CANNON.item_id, Items.GOLDEN_SUCK_CANNON.item_id], + "progressive_bomb_glove_order": [Items.BOMB_GLOVE.item_id, Items.GOLDEN_BOMB_GLOVE.item_id], + "progressive_devastator_order": [Items.DEVASTATOR.item_id, Items.GOLDEN_DEVASTATOR.item_id], + "progressive_blaster_order": [Items.BLASTER.item_id, Items.GOLDEN_BLASTER.item_id], + "progressive_pyrocitor_order": [Items.PYROCITOR.item_id, Items.GOLDEN_PYROCITOR.item_id], + "progressive_mine_glove_order": [Items.MINE_GLOVE.item_id, Items.GOLDEN_MINE_GLOVE.item_id], + "progressive_tesla_claw_order": [Items.TESLA_CLAW.item_id, Items.GOLDEN_TESLA_CLAW.item_id], + "progressive_glove_of_doom_order": [Items.GLOVE_OF_DOOM.item_id, Items.GOLDEN_GLOVE_OF_DOOM.item_id], + "progressive_morph_o_ray_order": [Items.MORPH_O_RAY.item_id, Items.GOLDEN_MORPH_O_RAY.item_id], + "progressive_decoy_glove_order": [Items.DECOY_GLOVE.item_id, Items.GOLDEN_DECOY_GLOVE.item_id], + "progressive_packs_order": [Items.HELI_PACK.item_id, Items.THRUSTER_PACK.item_id, Items.HYDRO_PACK.item_id], + "progressive_helmets_order": [Items.O2_MASK.item_id, Items.SONIC_SUMMONER.item_id, + Items.PILOTS_HELMET.item_id], + "progressive_boots_order": [Items.GRINDBOOTS.item_id, Items.MAGNEBOOTS.item_id], + "progressive_hoverboard_order": [Items.HOVERBOARD.item_id, Items.ZOOMERATOR.item_id], + "progressive_raritanium_order": [Items.RARITANIUM.item_id, Items.PERSUADER.item_id], + "progressive_nanotech_order": [Items.PREMIUM_NANOTECH.item_id, Items.ULTRA_NANOTECH.item_id], + } + progression_rules(self) + rac_logger.debug(f"Progression Order: {self.orders}") rac_logger.debug(f"Creating Regions") create_regions(self) - # TODO: Item Pools: get_pre_fill_items() + rac_logger.debug(f"___Generate Item Pool___") + option_list = Items.get_pool(self.options) + rac_logger.debug(f"length of option_list: {len(option_list)}") + for item in option_list: + rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}") + item_list = self.item_pool.get(item.name) or [] + item_list.append(self.create_item(item.name)) + self.item_pool[item.name] = item_list + + rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}") + if (self.options.shuffle_infobots == ShuffleInfobots.option_vanilla or + self.options.starting_location == StartingLocation.option_false): + starting_planet = self.item_pool[Items.NOVALIS_INFOBOT.name].pop(0) + else: + starting_planet = [planet for planet in Items.get_starting_planets(self.options)] + self.random.shuffle(starting_planet) + self.starting_planet = starting_planet[0].name + starting_planet = self.item_pool[starting_planet[0].name].pop(0) + rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}") + if (self.options.shuffle_weapons == ShuffleWeapons.option_vanilla or self.options.starting_item == StartingItem.option_vanilla): - starting_item = self.create_item(Items.BOMB_GLOVE.name) + starting_item = self.item_pool[check_progressive_item(self.options, Items.BOMB_GLOVE.name)].pop(0) else: starting_item = [] - match self.options.starting_item: - case StartingItem.option_random_same: - starting_item = list(Items.WEAPONS) - case StartingItem.option_random_item: - starting_item = [item for item in Items.ALL if item.name != "Gold Bolt"] - case StartingItem.option_unrestricted: - starting_item = list(Items.ALL) + item_list = [item.name for item in Items.STARTING_WEAPONS] + if (self.options.starting_item == StartingItem.option_random_item and + self.options.shuffle_gadgets > ShuffleGadgets.option_random_same): + item_list += [item.name for item in Items.GADGETS] + if self.options.progressive_weapons.value is Options.GoldenWeaponProgression.option_normal: + item_list += [item.name for item in Items.GOLDEN_WEAPONS] + for name, item in self.item_pool.items(): + if name in item_list: + starting_item.extend(item) self.random.shuffle(starting_item) - self.multiworld.push_precollected(self.create_item(starting_item[0].name)) - starting_item = self.create_item(starting_item[0].name) - - if (self.options.shuffle_infobots == ShuffleInfobots.option_vanilla or - self.options.starting_item == StartingItem.option_vanilla): - starting_planet = Items.KERWAN_INFOBOT.name - else: - starting_planet = list(Planets.LOGIC_PLANETS) - self.random.shuffle(starting_planet) - starting_planet = starting_planet[0].name + starting_item = self.item_pool[starting_item[0].name].pop(0) - self.starting_planet = starting_planet - starting_planet = self.create_item(self.starting_planet) - self.starting_items = [starting_item, starting_planet] + self.preplaced_items = [starting_item, starting_planet] self.multiworld.push_precollected(starting_item) self.multiworld.push_precollected(starting_planet) - rac_logger.debug(f"Starting items: {self.starting_items}") - - rac_logger.debug(f"Disabled Items:") - self.preplaced_locations += self.fill_pool(disabled_pools, 0) - rac_logger.debug(f"Restricted Items:") - self.preplaced_locations += self.fill_pool(restricted_pools, 1) - rac_logger.debug(f"Useful Items:") - self.preplaced_locations += self.fill_pool(useful_pools, 2) - rac_logger.debug(f"Pre-placed Items placed: {[loc.item for loc in self.preplaced_locations]}") - rac_logger.debug(f"Pre-filled Locations removed: {self.preplaced_locations}") + for name, count in self.options.start_inventory: + if count > len(self.item_pool[name]): + rac_logger.warning(f"Too many copies of {name} in yaml start inventory! Giving only " + f"{len(self.item_pool[name])} of {count} copies") + for _ in range(count): + if self.item_pool[name]: + self.preplaced_items += [self.item_pool[name].pop(0)] + else: + break + + rac_logger.debug(f"Starting items: {self.preplaced_items}") + + rac_logger.debug(f"___Vanilla Locations___") + self.preplaced_items += self.fill_pool(disabled_pools, 0) + rac_logger.debug(f"___Internal Shuffled Pools___") + self.preplaced_items += self.fill_pool(restricted_pools, 1) + rac_logger.debug(f"___Group Shuffled Pools___") + self.preplaced_items += self.fill_pool(useful_pools, 2) + rac_logger.debug(f"Pre-placed Items placed: {self.preplaced_items}") + rac_logger.debug(f"Pre-filled Locations removed: {[loc.name for loc in self.get_locations() if loc.item]}") rac_logger.debug(f"_________END EARLY GENERATION____________") - # self.disabled_location_data = set(loc for loc in ALL_LOCATIONS if not loc.pools.issubset(enabled_pools)) - - # for location in ALL_LOCATIONS: - # if not location.pools.issubset(enabled_pools): - # logging.debug(f"disable: {location.name}") - # self.disabled_location_data.append(location) - # output: list[str] = list(loc.name for loc in self.disabled_location_data) - # logging.debug(f"disabled location data: {output}") - def fill_pool(self, pools, scope) -> (list, list): multiworld = self.multiworld - placed_items = [loc.item.name for loc in self.preplaced_locations] - starting_item_list = [item.name for item in self.starting_items] - placed_items += starting_item_list - placed_items += [*[Items.GOLD_BOLT.name] * 40] - unplaced_items: list[ItemData] = [item for item in Items.ALL if item.name not in placed_items] - placed_locations: list[Location] = [] + placed_items = self.preplaced_items + # for name in self.item_pool: + # if Items.from_name(name).pool == POOL_SKILLPOINT: + # placed_items += self.item_pool[name] + rac_logger.debug(f"placed_items: {placed_items}") + unplaced_items: list[Item] = [] + for name, items in self.item_pool.items(): + rac_logger.debug(f"Checking if {name} is unplaced") + if items: + if items[0].name.endswith("Gold Bolts"): + placed_items += self.item_pool[name] + continue + elif items[0].name.endswith("Skill Point"): + continue + elif items[0].name.endswith("Gold Bolt"): + placed_items += self.item_pool[name] + continue + else: + rac_logger.debug(f"Add to unplaced: {name}") + unplaced_items += items + add_items: list[Item] = [] match scope: case 0: for pool in pools: - rac_logger.debug(f"Disabled Pool: {pool}") + rac_logger.debug(f"Disable Pool: {pool}") for loc in ALL_LOCATIONS: if pool in loc.pools and loc.vanilla_item is not None: if self.get_location(loc.name).item is not None: raise FillError(f"Slot {self.player_name} selected vanilla {pool}, but Location:" f" {loc.name} was already filled") - item = self.create_item(loc.vanilla_item) - placed_locations += [self.get_location(loc.name)] + elif pool == Items.GOLD_BOLT.pool: + item = self.item_pool[Items.GOLD_BOLT_1.name].pop(0) + elif self.item_pool[loc.vanilla_item]: + item = self.item_pool[loc.vanilla_item].pop(0) + else: + rac_logger.warning(f"vanilla item {loc.vanilla_item} can't be placed at {loc.name}, " + f"filler bolt pack placed instead") + item = self.create_item(get_bolt_pack(self.options)) + self.get_location(loc.name).place_locked_item(item) + add_items += [item] rac_logger.debug(f"vanilla: {loc.name}, item: {item}") - placed_locations[len(placed_locations) - 1].place_locked_item(item) case 1: for pool in pools: + if pool == POOL_GOLDEN_WEAPON and POOL_WEAPON in pools: + continue base_state = CollectionState(multiworld) - item_sweep: Sequence[Item] = [] - unplaced_items = [item for item in Items.ALL if item.name not in placed_items] - for item in starting_item_list: - item_sweep += [self.create_item(item)] + item_sweep = placed_items + rac_logger.debug(f"unplaced items: {unplaced_items}") for item in unplaced_items: - if item.pool != pool: - item_sweep += [self.create_item(item.name)] - rac_logger.debug(f"Item Sweep: {item_sweep}") + rac_logger.debug(f"check {pool} pool: {item}") + item_pool = Items.from_name(item.name).pool + if item_pool != pool: + if pool == POOL_WEAPON and POOL_GOLDEN_WEAPON in pools and item_pool == POOL_GOLDEN_WEAPON: + rac_logger.debug(f"Gold Weapon skipped: {item}") + else: + rac_logger.debug(f"add to assumed: {item}") + item_sweep += [item] + else: + rac_logger.debug(f"{item} is in pool {pool}") + rac_logger.debug(f"Assumed collected: {item_sweep}") base_state = sweep_from_pool(base_state, item_sweep) rac_logger.debug(f"Restricted Pool: {pool}") loc_temp = [] @@ -214,10 +290,26 @@ def fill_pool(self, pools, scope) -> (list, list): for loc in ALL_LOCATIONS: if pool in loc.pools and loc.vanilla_item is not None: loc_temp += [self.get_location(loc.name)] - item_temp += [self.create_item(loc.vanilla_item)] - placed_items += [loc.vanilla_item] + if self.item_pool[loc.vanilla_item]: + item_temp += [self.item_pool[loc.vanilla_item].pop(0)] + elif (self.starting_planet != Items.NOVALIS_INFOBOT.name and pool in + Items.NOVALIS_INFOBOT.pool): + item_temp += [self.item_pool[Items.NOVALIS_INFOBOT.name].pop(0)] + else: + rac_logger.warning(f"vanilla item {loc.vanilla_item} can't be shuffled into pool {pool}" + f", filler bolt pack added instead") + item_temp += [self.create_item(get_bolt_pack(self.options))] + if pool == POOL_WEAPON and POOL_GOLDEN_WEAPON in pools: + if POOL_GOLDEN_WEAPON in loc.pools and loc.vanilla_item is not None: + loc_temp += [self.get_location(loc.name)] + if self.item_pool[loc.vanilla_item]: + item_temp += [self.item_pool[loc.vanilla_item].pop(0)] + else: + rac_logger.warning(f"vanilla item {loc.vanilla_item} can't be shuffled into pool" + f" {pool}, filler bolt pack added instead") + item_temp += [self.create_item(get_bolt_pack(self.options))] rac_logger.debug(f"Randomize Locations: {loc_temp}") - placed_locations += loc_temp + add_items += item_temp self.random.shuffle(item_temp) rac_logger.debug(f"Shuffled items: {item_temp}") rac_logger.debug(f"Reachability before Shuffle: {base_state.reachable_regions}") @@ -226,9 +318,10 @@ def fill_pool(self, pools, scope) -> (list, list): if loc in loc_temp] rac_logger.debug(f"Reachable Locations: {reachable}") fill_restrictive(multiworld, base_state, loc_temp, item_temp, single_player_placement=True, - swap=True, name=f"RAC1 Restricted Item Fill: {pool}") - for loc in loc_temp: - placed_locations.remove(loc) + lock=False, swap=True, allow_partial=False, + name=f"RAC1 Restricted Item Fill: {pool}") + # for item in item_temp: + # add_items.remove(item) # if item_temp: # for loc in placed_locations: # rac_logger.debug(f"same group: {loc.name}, item: {loc.item}") @@ -237,34 +330,33 @@ def fill_pool(self, pools, scope) -> (list, list): case 2: loc_temp = [] item_temp = [] - item_sweep = [] + item_sweep = placed_items base_state = CollectionState(multiworld) for pool in pools: - rac_logger.debug(f"Useful Pool: {pool}") + rac_logger.debug(f"add Pool: {pool}") for loc in ALL_LOCATIONS: if pool in loc.pools and loc.vanilla_item is not None: loc_temp += [self.get_location(loc.name)] if loc_temp: - for item in starting_item_list: - item_sweep += [self.create_item(item)] - for item in unplaced_items: - if item.name != Items.GOLD_BOLT.name: - item_temp += [self.create_item(item.name)] base_state = sweep_from_pool(base_state, item_sweep) rac_logger.debug(f"Randomizing Useful Locations: {loc_temp}") - placed_locations += loc_temp - self.random.shuffle(item_temp) + self.random.shuffle(unplaced_items) + for i in range(len(loc_temp)): + item_temp += [self.item_pool[unplaced_items[i].name].pop(0)] rac_logger.debug(f"Shuffled items: {item_temp}") + add_items += item_temp rac_logger.debug(f"Reachability before Shuffle: {base_state.reachable_regions}") reachable = [loc for loc in multiworld.get_reachable_locations(base_state, self.player) if loc in loc_temp] rac_logger.debug(f"Reachable Locations: {reachable}") - # TODO: Try using remaining_fill() to prevent deadend seeds + fill_restrictive(multiworld, base_state, loc_temp, item_temp, single_player_placement=True, - swap=True, allow_partial=True, allow_excluded=True, - name="RAC1 Useful Item Fill") - for loc in loc_temp: - placed_locations.remove(loc) + lock=False, swap=True, allow_partial=True, name="RAC1 Useful Item Fill") + for item in item_temp: + add_items.remove(item) + item_list = self.item_pool.get(item.name) or [] + item_list.append(self.create_item(item.name)) + self.item_pool[item.name] = item_list # if loc_temp: # for loc in placed_locations: # rac_logger.debug(f"any item: {loc.name}, item: {loc.item}") @@ -274,50 +366,52 @@ def fill_pool(self, pools, scope) -> (list, list): # else: # raise FillError(f"Slot {self.player_name} has locations requiring useful items that are " # f"unfilled, No items left to get placed at Locations: {loc_temp}") - return placed_locations + return add_items def create_item(self, name: str, override: Optional[ItemClassification] = None) -> "Item": + new_name = Items.check_progressive_item(self.options, name) + if new_name is not name: + rac_logger.warning(f"Item {name} was not initially set to its progressive item: {new_name}") + if name == Items.GOLD_BOLT or name == Items.BOLT_PACK: + rac_logger.warning(f"{name} should not be in the item pool!!! Please report") if override: - return RacItem(name, override, self.item_name_to_id[name], self.player) - item_data = Items.from_name(name) - return RacItem(name, ItemPool.get_classification(item_data), self.item_name_to_id[name], self.player) + return RacItem(new_name, override, self.item_name_to_id[new_name], self.player) + item_data = Items.from_name(new_name) + return RacItem(new_name, ItemPool.get_classification(item_data), self.item_name_to_id[new_name], self.player) def create_event(self, name: str) -> "Item": return RacItem(name, ItemClassification.progression, None, self.player) def get_pre_fill_items(self) -> list["Item"]: rac_logger.debug(f"fetching preplaced_items") - return [loc.item for loc in self.preplaced_locations] + items = self.preplaced_items + return items def create_items(self) -> None: rac_logger.debug(f"_________START ITEM CREATION__________") - items_to_add: list[Item] = [self.create_item(item.name) for item in Items.ALL] - - # add gold bolts in whatever slots we have left - # unfilled = [i for i in self.multiworld.get_unfilled_locations(self.player) if not i.is_event] - # rac_logger.debug(self.multiworld.get_filled_locations(self.player)) - # rac_logger.debug(f"{len(items_to_add)} {len(unfilled)}") - # remain = len(unfilled) - len(items_to_add) - # assert remain >= 0, "There are more items than locations. This is not supported." - # rac_logger.debug(f"Not enough items to fill all locations. Adding {remain} filler items to the item pool") - # for _ in range(remain): - # items_to_add.append(self.create_item(Items.GOLD_BOLT.name, ItemClassification.filler)) - - items_to_remove: list[Item] = [loc.item for loc in self.preplaced_locations] - items_to_remove += self.starting_items - rac_logger.debug(f"Preplaced item list before removal: {items_to_remove}") - for item in items_to_remove: - items_to_add.remove(item) - rac_logger.debug(f"{item} removed") - if len(items_to_add) == len(self.multiworld.get_unfilled_locations(self.player)) - 1: - rac_logger.debug(f"Add item pool to multiworld: {items_to_add}") - self.multiworld.itempool.extend(items_to_add) - rac_logger.debug(f"_________END ITEM CREATION__________") - else: + rac_logger.debug(f"item_pool size: {len(self.item_pool.values())}") + items_to_add: list[Item] = [] + for items in self.item_pool.values(): + items_to_add.extend(items) + + # add bolt packs in whatever slots we have left + unfilled = [i for i in self.multiworld.get_unfilled_locations(self.player) if not i.is_event] + rac_logger.debug(f"Items:{len(items_to_add)}, Locations:{len(unfilled)}") + remain = len(unfilled) - len(items_to_add) + if remain < 0: rac_logger.debug(f"Items unplaced: {items_to_add}") rac_logger.debug(f"Locations unfilled: {self.multiworld.get_unfilled_locations(self.player)}") - raise FillError(f"Item Count: {len(items_to_add)} does not match Location count: " + raise FillError(f"Item Count: {len(items_to_add)} exceeds Location count: " f"{len(self.multiworld.get_unfilled_locations(self.player))}") + elif remain == 0: + pass + else: + rac_logger.debug(f"Not enough items to fill all locations. Adding {remain} filler items to the item pool") + for _ in range(remain): + items_to_add.append(self.create_item(get_bolt_pack(self.options))) + rac_logger.debug(f"Add item pool to multiworld: {items_to_add}") + self.multiworld.itempool.extend(items_to_add) + rac_logger.debug(f"_________END ITEM CREATION__________") def set_rules(self) -> None: boss_location = self.multiworld.get_location(Locations.VELDIN_DREK.name, self.player) @@ -335,6 +429,8 @@ def fill_slot_data(self) -> Mapping[str, Any]: slot_data: dict[str, Any] = {} slot_data |= Options.get_options_as_dict(self.options) slot_data["starting_planet"] = self.item_name_to_id[self.starting_planet] + for item, value in self.orders.items(): + slot_data[item] = value return slot_data # def post_fill(self) -> None: diff --git a/worlds/RAC1/data/Items.py b/worlds/RAC1/data/Items.py index 82fa176157b0..8f6cfa6c2002 100644 --- a/worlds/RAC1/data/Items.py +++ b/worlds/RAC1/data/Items.py @@ -1,7 +1,8 @@ from dataclasses import dataclass -from typing import Sequence +from typing import Mapping, Sequence from BaseClasses import Item +from worlds.RAC1 import Options @dataclass @@ -9,6 +10,7 @@ class ItemData(Item): item_id: int name: str pool: str + quantity: int = 1 HELI_PACK = ItemData(2, "Heli Pack", "Packs") @@ -63,6 +65,23 @@ class ItemData(Item): GOLDEN_MORPH_O_RAY = ItemData(321, "Golden Morph-o-ray", "GoldenWeapons") GOLDEN_DECOY_GLOVE = ItemData(325, "Golden Decoy glove", "GoldenWeapons") +PROGRESSIVE_PACK = ItemData(80, "Progressive Pack", "Packs") +PROGRESSIVE_HELMET = ItemData(81, "Progressive Helmet", "Helmets") +PROGRESSIVE_SUCK = ItemData(82, "Progressive Suck Cannon", "Weapons") +PROGRESSIVE_BOMB = ItemData(83, "Progressive Bomb glove", "Weapons") +PROGRESSIVE_DEVASTATOR = ItemData(84, "Progressive Devastator", "Weapons") +PROGRESSIVE_BLASTER = ItemData(85, "Progressive Blaster", "Weapons") +PROGRESSIVE_PYROCITOR = ItemData(86, "Progressive Pyrocitor", "Weapons") +PROGRESSIVE_MINE = ItemData(87, "Progressive Mine glove", "Weapons") +PROGRESSIVE_TESLA = ItemData(88, "Progressive Tesla claw", "Weapons") +PROGRESSIVE_DOOM = ItemData(89, "Progressive Glove of doom", "Weapons") +PROGRESSIVE_MORPH = ItemData(90, "Progressive Morph-o-ray", "Weapons") +PROGRESSIVE_DECOY = ItemData(91, "Progressive Decoy glove", "Weapons") +PROGRESSIVE_BOOT = ItemData(92, "Progressive Boots", "Boots") +PROGRESSIVE_HOVERBOARD = ItemData(93, "Progressive Hoverboard", "ExtraItems") +PROGRESSIVE_TRADE = ItemData(94, "Progressive Raritanium", "ExtraItems") +PROGRESSIVE_NANOTECH = ItemData(95, "Progressive Nanotech", "ExtraItems") + NOVALIS_INFOBOT = ItemData(101, "Novalis", "Infobots") ARIDIA_INFOBOT = ItemData(102, "Aridia", "Infobots") KERWAN_INFOBOT = ItemData(103, "Kerwan", "Infobots") @@ -120,27 +139,111 @@ class CollectableData(ItemData): # Collectables -GOLD_BOLT = CollectableData(301, "Gold Bolt", "GoldBolts", 40) +GOLD_BOLT = ItemData(261, "Generic Gold Bolt", "GoldBolts") +GOLD_BOLT_1 = ItemData(262, "1 Gold Bolt", "GoldBolts", 1) +GOLD_BOLT_2 = ItemData(263, "2 Gold Bolts", "GoldBolts", 2) +GOLD_BOLT_3 = ItemData(264, "3 Gold Bolts", "GoldBolts", 3) +GOLD_BOLT_4 = ItemData(265, "4 Gold Bolts", "GoldBolts", 4) +GOLD_BOLT_5 = ItemData(266, "5 Gold Bolts", "GoldBolts", 5) +GOLD_BOLT_6 = ItemData(267, "6 Gold Bolts", "GoldBolts", 6) +GOLD_BOLT_7 = ItemData(268, "7 Gold Bolts", "GoldBolts", 7) +GOLD_BOLT_8 = ItemData(269, "8 Gold Bolts", "GoldBolts", 8) +GOLD_BOLT_9 = ItemData(270, "9 Gold Bolts", "GoldBolts", 9) +GOLD_BOLT_10 = ItemData(271, "10 Gold Bolts", "GoldBolts", 10) +GOLD_BOLT_11 = ItemData(272, "11 Gold Bolts", "GoldBolts", 11) +GOLD_BOLT_12 = ItemData(273, "12 Gold Bolts", "GoldBolts", 12) +GOLD_BOLT_13 = ItemData(274, "13 Gold Bolts", "GoldBolts", 13) +GOLD_BOLT_14 = ItemData(275, "14 Gold Bolts", "GoldBolts", 14) +GOLD_BOLT_15 = ItemData(276, "15 Gold Bolts", "GoldBolts", 15) +GOLD_BOLT_16 = ItemData(277, "16 Gold Bolts", "GoldBolts", 16) +GOLD_BOLT_17 = ItemData(278, "17 Gold Bolts", "GoldBolts", 17) +GOLD_BOLT_18 = ItemData(279, "18 Gold Bolts", "GoldBolts", 18) +GOLD_BOLT_19 = ItemData(280, "19 Gold Bolts", "GoldBolts", 19) +GOLD_BOLT_20 = ItemData(281, "20 Gold Bolts", "GoldBolts", 20) +GOLD_BOLT_21 = ItemData(282, "21 Gold Bolts", "GoldBolts", 21) +GOLD_BOLT_22 = ItemData(283, "22 Gold Bolts", "GoldBolts", 22) +GOLD_BOLT_23 = ItemData(284, "23 Gold Bolts", "GoldBolts", 23) +GOLD_BOLT_24 = ItemData(285, "24 Gold Bolts", "GoldBolts", 24) +GOLD_BOLT_25 = ItemData(286, "25 Gold Bolts", "GoldBolts", 25) +GOLD_BOLT_26 = ItemData(287, "26 Gold Bolts", "GoldBolts", 26) +GOLD_BOLT_27 = ItemData(288, "27 Gold Bolts", "GoldBolts", 27) +GOLD_BOLT_28 = ItemData(289, "28 Gold Bolts", "GoldBolts", 28) +GOLD_BOLT_29 = ItemData(290, "29 Gold Bolts", "GoldBolts", 29) +GOLD_BOLT_30 = ItemData(291, "30 Gold Bolts", "GoldBolts", 30) +GOLD_BOLT_31 = ItemData(292, "31 Gold Bolts", "GoldBolts", 31) +GOLD_BOLT_32 = ItemData(293, "32 Gold Bolts", "GoldBolts", 32) +GOLD_BOLT_33 = ItemData(294, "33 Gold Bolts", "GoldBolts", 33) +GOLD_BOLT_34 = ItemData(295, "34 Gold Bolts", "GoldBolts", 34) +GOLD_BOLT_35 = ItemData(296, "35 Gold Bolts", "GoldBolts", 35) +GOLD_BOLT_36 = ItemData(297, "36 Gold Bolts", "GoldBolts", 36) +GOLD_BOLT_37 = ItemData(298, "37 Gold Bolts", "GoldBolts", 37) +GOLD_BOLT_38 = ItemData(299, "38 Gold Bolts", "GoldBolts", 38) +GOLD_BOLT_39 = ItemData(300, "39 Gold Bolts", "GoldBolts", 39) +GOLD_BOLT_40 = ItemData(301, "40 Gold Bolts", "GoldBolts", 40) +BOLT_PACK = ItemData(302, "Generic Bolt Pack", "Filler") +BOLT_PACK_0 = ItemData(400, "Nothing", "Filler", 0) +BOLT_PACK_1 = ItemData(401, "A single bolt", "Filler", 1) +BOLT_PACK_2 = ItemData(402, "10 bolts", "Filler", 10) +BOLT_PACK_3 = ItemData(403, "100 bolts", "Filler", 100) +BOLT_PACK_4 = ItemData(404, "250 bolts", "Filler", 250) +BOLT_PACK_5 = ItemData(405, "500 bolts", "Filler", 500) +BOLT_PACK_6 = ItemData(406, "750 bolts", "Filler", 750) +BOLT_PACK_7 = ItemData(407, "1,000 bolts", "Filler", 1000) +BOLT_PACK_8 = ItemData(408, "2,000 bolts", "Filler", 2000) +BOLT_PACK_9 = ItemData(409, "3,000 bolts", "Filler", 3000) +BOLT_PACK_10 = ItemData(410, "4,000 bolts", "Filler", 4000) +BOLT_PACK_11 = ItemData(411, "5,000 bolts", "Filler", 5000) +BOLT_PACK_12 = ItemData(412, "6,000 bolts", "Filler", 6000) +BOLT_PACK_13 = ItemData(413, "7,000 bolts", "Filler", 7000) +BOLT_PACK_14 = ItemData(414, "8,000 bolts", "Filler", 8000) +BOLT_PACK_15 = ItemData(415, "9,000 bolts", "Filler", 9000) +BOLT_PACK_16 = ItemData(416, "10,000 bolts", "Filler", 10000) +BOLT_PACK_17 = ItemData(417, "12,500 bolts", "Filler", 12500) +BOLT_PACK_18 = ItemData(418, "15,000 bolts", "Filler", 15000) +BOLT_PACK_19 = ItemData(419, "17,500 bolts", "Filler", 17500) +BOLT_PACK_20 = ItemData(420, "20,000 bolts", "Filler", 20000) +BOLT_PACK_21 = ItemData(421, "25,000 bolts", "Filler", 25000) +BOLT_PACK_22 = ItemData(422, "30,000 bolts", "Filler", 30000) +BOLT_PACK_23 = ItemData(423, "40,000 bolts", "Filler", 40000) +BOLT_PACK_24 = ItemData(424, "50,000 bolts", "Filler", 50000) +BOLT_PACK_25 = ItemData(425, "75,000 bolts", "Filler", 75000) +BOLT_PACK_26 = ItemData(426, "100,000 bolts", "Filler", 100000) WEAPONS: Sequence[ItemData] = [ + TAUNTER, + VISIBOMB, + WALLOPER, + RYNO, + DRONE_DEVICE, +] + +NON_PROGRESSIVE_WEAPONS: Sequence[ItemData] = [ SUCK_CANNON, BOMB_GLOVE, DEVASTATOR, - VISIBOMB, - TAUNTER, BLASTER, PYROCITOR, MINE_GLOVE, - WALLOPER, TESLA_CLAW, GLOVE_OF_DOOM, MORPH_O_RAY, - RYNO, - DRONE_DEVICE, DECOY_GLOVE, ] +PROGRESSIVE_WEAPONS: Sequence[ItemData] = [ + PROGRESSIVE_SUCK, + PROGRESSIVE_BOMB, + PROGRESSIVE_DEVASTATOR, + PROGRESSIVE_BLASTER, + PROGRESSIVE_PYROCITOR, + PROGRESSIVE_MINE, + PROGRESSIVE_TESLA, + PROGRESSIVE_DOOM, + PROGRESSIVE_MORPH, + PROGRESSIVE_DECOY, +] + GOLDEN_WEAPONS: Sequence[ItemData] = [ GOLDEN_SUCK_CANNON, GOLDEN_BOMB_GLOVE, @@ -154,6 +257,19 @@ class CollectableData(ItemData): GOLDEN_DECOY_GLOVE, ] +PROGRESSIVE_GOLDEN_WEAPONS: Sequence[ItemData] = [ + GOLDEN_SUCK_CANNON, + GOLDEN_BOMB_GLOVE, + GOLDEN_DEVASTATOR, + GOLDEN_BLASTER, + GOLDEN_PYROCITOR, + GOLDEN_MINE_GLOVE, + GOLDEN_TESLA_CLAW, + GOLDEN_GLOVE_OF_DOOM, + GOLDEN_MORPH_O_RAY, + GOLDEN_DECOY_GLOVE, +] + GADGETS: Sequence[ItemData] = [ HYDRODISPLACER, TRESPASSER, @@ -169,31 +285,135 @@ class CollectableData(ItemData): HYDRO_PACK, ] +PROGRESSIVE_PACKS: Sequence[ItemData] = [ + *[PROGRESSIVE_PACK] * 3, +] + HELMETS: Sequence[ItemData] = [ SONIC_SUMMONER, O2_MASK, PILOTS_HELMET, ] +PROGRESSIVE_HELMETS: Sequence[ItemData] = [ + *[PROGRESSIVE_HELMET] * 3, +] + BOOTS: Sequence[ItemData] = [ MAGNEBOOTS, GRINDBOOTS, ] +PROGRESSIVE_BOOTS: Sequence[ItemData] = [ + *[PROGRESSIVE_BOOT] * 2, +] + EXTRA_ITEMS: Sequence[ItemData] = [ - HOVERBOARD, MAP_O_MATIC, BOLT_GRABBER, - PERSUADER, + CODEBOT, +] + +NON_PROGRESSIVE_HOVERBOARDS: Sequence[ItemData] = [ + HOVERBOARD, ZOOMERATOR, +] + +PROGRESSIVE_HOVERBOARDS: Sequence[ItemData] = [ + *[PROGRESSIVE_HOVERBOARD] * 2, +] + +NON_PROGRESSIVE_TRADES: Sequence[ItemData] = [ + PERSUADER, RARITANIUM, - CODEBOT, +] + +PROGRESSIVE_TRADES: Sequence[ItemData] = [ + *[PROGRESSIVE_TRADE] * 2, +] + +NON_PROGRESSIVE_NANOTECHS: Sequence[ItemData] = [ PREMIUM_NANOTECH, ULTRA_NANOTECH, ] -COLLECTABLES: Sequence[CollectableData] = [ +PROGRESSIVE_NANOTECHS: Sequence[ItemData] = [ + *[PROGRESSIVE_NANOTECH] * 2, +] + +GOLD_BOLTS: Sequence[ItemData] = [ GOLD_BOLT, + GOLD_BOLT_1, + GOLD_BOLT_2, + GOLD_BOLT_3, + GOLD_BOLT_4, + GOLD_BOLT_5, + GOLD_BOLT_6, + GOLD_BOLT_7, + GOLD_BOLT_8, + GOLD_BOLT_9, + GOLD_BOLT_10, + GOLD_BOLT_11, + GOLD_BOLT_12, + GOLD_BOLT_13, + GOLD_BOLT_14, + GOLD_BOLT_15, + GOLD_BOLT_16, + GOLD_BOLT_17, + GOLD_BOLT_18, + GOLD_BOLT_19, + GOLD_BOLT_20, + GOLD_BOLT_21, + GOLD_BOLT_22, + GOLD_BOLT_23, + GOLD_BOLT_24, + GOLD_BOLT_25, + GOLD_BOLT_26, + GOLD_BOLT_27, + GOLD_BOLT_28, + GOLD_BOLT_29, + GOLD_BOLT_30, + GOLD_BOLT_31, + GOLD_BOLT_32, + GOLD_BOLT_33, + GOLD_BOLT_34, + GOLD_BOLT_35, + GOLD_BOLT_36, + GOLD_BOLT_37, + GOLD_BOLT_38, + GOLD_BOLT_39, + GOLD_BOLT_40, +] + +BOLT_PACKS: Sequence[ItemData] = [ + BOLT_PACK, + BOLT_PACK_0, + BOLT_PACK_1, + BOLT_PACK_2, + BOLT_PACK_3, + BOLT_PACK_4, + BOLT_PACK_5, + BOLT_PACK_6, + BOLT_PACK_7, + BOLT_PACK_8, + BOLT_PACK_9, + BOLT_PACK_10, + BOLT_PACK_11, + BOLT_PACK_12, + BOLT_PACK_13, + BOLT_PACK_14, + BOLT_PACK_15, + BOLT_PACK_16, + BOLT_PACK_17, + BOLT_PACK_18, + BOLT_PACK_19, + BOLT_PACK_20, + BOLT_PACK_21, + BOLT_PACK_22, + BOLT_PACK_23, + BOLT_PACK_24, + BOLT_PACK_25, + BOLT_PACK_26, ] PLANETS: Sequence[ItemData] = [ @@ -217,6 +437,14 @@ class CollectableData(ItemData): VELDIN_INFOBOT, ] +STARTING_PLANETS: Sequence[ItemData] = [ + NOVALIS_INFOBOT, + KERWAN_INFOBOT, + BLARG_INFOBOT, + BATALIA_INFOBOT, + ORXON_INFOBOT, +] + SKILLPOINTS: Sequence[ItemData] = [ TAKE_AIM, SWING_IT, @@ -250,14 +478,488 @@ class CollectableData(ItemData): GOING_COMMANDO, ] -ALL: Sequence[ItemData] = [*WEAPONS, *GADGETS, *PACKS, *HELMETS, *BOOTS, *EXTRA_ITEMS, *[GOLD_BOLT] * 40, *PLANETS, *GOLDEN_WEAPONS,] +ALL: Sequence[ItemData] = [*WEAPONS, *NON_PROGRESSIVE_WEAPONS, *PROGRESSIVE_WEAPONS, *GOLDEN_WEAPONS, + *GADGETS, *PACKS, *PROGRESSIVE_PACKS, *HELMETS, *PROGRESSIVE_HELMETS, *BOOTS, + *PROGRESSIVE_BOOTS, *EXTRA_ITEMS, *NON_PROGRESSIVE_HOVERBOARDS, *PROGRESSIVE_HOVERBOARDS, + *NON_PROGRESSIVE_TRADES, *PROGRESSIVE_TRADES, *NON_PROGRESSIVE_NANOTECHS, + *PROGRESSIVE_NANOTECHS, *GOLD_BOLTS, *PLANETS, *SKILLPOINTS, *BOLT_PACKS] + +ITEM_POOL: Sequence[ItemData] = [*PLANETS, *WEAPONS, *GADGETS, *EXTRA_ITEMS] # *SKILLPOINTS + +STARTING_WEAPONS: Sequence[ItemData] = [*WEAPONS, *NON_PROGRESSIVE_WEAPONS, *PROGRESSIVE_WEAPONS, + *PROGRESSIVE_GOLDEN_WEAPONS] +ALL_WEAPONS: Sequence[ItemData] = [*STARTING_WEAPONS, *GOLDEN_WEAPONS] +ALL_PACKS: Sequence[ItemData] = [*PACKS, *PROGRESSIVE_PACKS] +ALL_HELMETS: Sequence[ItemData] = [*HELMETS, *PROGRESSIVE_HELMETS] +ALL_BOOTS: Sequence[ItemData] = [*BOOTS, *PROGRESSIVE_BOOTS] +ALL_EXTRA_ITEMS: Sequence[ItemData] = [*EXTRA_ITEMS, *NON_PROGRESSIVE_HOVERBOARDS, *PROGRESSIVE_HOVERBOARDS, + *NON_PROGRESSIVE_TRADES, *PROGRESSIVE_TRADES, *NON_PROGRESSIVE_NANOTECHS, + *PROGRESSIVE_NANOTECHS] +ALL_HOVERBOARD: Sequence[ItemData] = [*NON_PROGRESSIVE_HOVERBOARDS, *PROGRESSIVE_HOVERBOARDS] +ALL_TRADE: Sequence[ItemData] = [*NON_PROGRESSIVE_TRADES, *PROGRESSIVE_TRADES] +ALL_NANOTECH: Sequence[ItemData] = [*NON_PROGRESSIVE_NANOTECHS, *PROGRESSIVE_NANOTECHS] +ALL_STARTING: Sequence[ItemData] = [*STARTING_WEAPONS, *GADGETS] + +SUCK_GROUP: Sequence[ItemData] = [SUCK_CANNON, GOLDEN_SUCK_CANNON, PROGRESSIVE_SUCK] +BOMB_GROUP: Sequence[ItemData] = [BOMB_GLOVE, GOLDEN_BOMB_GLOVE, PROGRESSIVE_BOMB] +DEVASTATOR_GROUP: Sequence[ItemData] = [DEVASTATOR, GOLDEN_DEVASTATOR, PROGRESSIVE_DEVASTATOR] +BLASTER_GROUP: Sequence[ItemData] = [BLASTER, GOLDEN_BLASTER, PROGRESSIVE_BLASTER] +PYROCITOR_GROUP: Sequence[ItemData] = [PYROCITOR, GOLDEN_PYROCITOR, PROGRESSIVE_PYROCITOR] +MINE_GROUP: Sequence[ItemData] = [MINE_GLOVE, GOLDEN_MINE_GLOVE, PROGRESSIVE_MINE] +TESLA_GROUP: Sequence[ItemData] = [TESLA_CLAW, GOLDEN_TESLA_CLAW, PROGRESSIVE_TESLA] +DOOM_GROUP: Sequence[ItemData] = [GLOVE_OF_DOOM, GOLDEN_GLOVE_OF_DOOM, PROGRESSIVE_DOOM] +MORPH_GROUP: Sequence[ItemData] = [MORPH_O_RAY, GOLDEN_MORPH_O_RAY, PROGRESSIVE_MORPH] +DECOY_GROUP: Sequence[ItemData] = [DECOY_GLOVE, GOLDEN_DECOY_GLOVE, PROGRESSIVE_DECOY] + +PROG: dict[str, Mapping[str, int]] = { + HELI_PACK.name: {HELI_PACK.name: 1}, + THRUSTER_PACK.name: {THRUSTER_PACK.name: 1}, + HYDRO_PACK.name: {HYDRO_PACK.name: 1}, + SONIC_SUMMONER.name: {SONIC_SUMMONER.name: 1, PROGRESSIVE_HELMET.name: 2}, + O2_MASK.name: {O2_MASK.name: 1, PROGRESSIVE_HELMET.name: 1}, + PILOTS_HELMET.name: {PILOTS_HELMET.name: 1, PROGRESSIVE_HELMET.name: 3}, + SUCK_CANNON.name: {SUCK_CANNON.name: 1}, + GOLDEN_SUCK_CANNON.name: {SUCK_CANNON.name: 1, GOLDEN_SUCK_CANNON.name: 1}, + BOMB_GLOVE.name: {BOMB_GLOVE.name: 1}, + GOLDEN_BOMB_GLOVE.name: {BOMB_GLOVE.name: 1, GOLDEN_BOMB_GLOVE.name: 1}, + DEVASTATOR.name: {DEVASTATOR.name: 1}, + GOLDEN_DEVASTATOR.name: {DEVASTATOR.name: 1, GOLDEN_DEVASTATOR.name: 1}, + BLASTER.name: {BLASTER.name: 1}, + GOLDEN_BLASTER.name: {BLASTER.name: 1, GOLDEN_BLASTER.name: 1}, + PYROCITOR.name: {PYROCITOR.name: 1}, + GOLDEN_PYROCITOR.name: {PYROCITOR.name: 1, GOLDEN_PYROCITOR.name: 1}, + MINE_GLOVE.name: {MINE_GLOVE.name: 1}, + GOLDEN_MINE_GLOVE.name: {MINE_GLOVE.name: 1, GOLDEN_MINE_GLOVE.name: 1}, + TESLA_CLAW.name: {TESLA_CLAW.name: 1}, + GOLDEN_TESLA_CLAW.name: {TESLA_CLAW.name: 1, GOLDEN_TESLA_CLAW.name: 1}, + GLOVE_OF_DOOM.name: {GLOVE_OF_DOOM.name: 1}, + GOLDEN_GLOVE_OF_DOOM.name: {GLOVE_OF_DOOM.name: 1, GOLDEN_GLOVE_OF_DOOM.name: 1}, + MORPH_O_RAY.name: {MORPH_O_RAY.name: 1}, + GOLDEN_MORPH_O_RAY.name: {MORPH_O_RAY.name: 1, GOLDEN_MORPH_O_RAY.name: 1}, + DECOY_GLOVE.name: {DECOY_GLOVE.name: 1}, + GOLDEN_DECOY_GLOVE.name: {DECOY_GLOVE.name: 1, GOLDEN_DECOY_GLOVE.name: 1}, + MAGNEBOOTS.name: {MAGNEBOOTS.name: 1, PROGRESSIVE_BOOT.name: 2}, + GRINDBOOTS.name: {GRINDBOOTS.name: 1, PROGRESSIVE_BOOT.name: 1}, + HOVERBOARD.name: {HOVERBOARD.name: 1, PROGRESSIVE_HOVERBOARD.name: 1}, + ZOOMERATOR.name: {ZOOMERATOR.name: 1, PROGRESSIVE_HOVERBOARD.name: 2}, + PERSUADER.name: {PERSUADER.name: 1, PROGRESSIVE_TRADE.name: 1}, + RARITANIUM.name: {RARITANIUM.name: 1, PROGRESSIVE_TRADE.name: 2}, + PREMIUM_NANOTECH.name: {PREMIUM_NANOTECH.name: 1, PROGRESSIVE_NANOTECH.name: 1}, + ULTRA_NANOTECH.name: {ULTRA_NANOTECH.name: 1, PROGRESSIVE_NANOTECH.name: 2}, +} + + +def get_bolt_pack(options: Options) -> str: + lookup: dict[int, str] = { + BOLT_PACK_0.quantity: BOLT_PACK_0.name, + BOLT_PACK_1.quantity: BOLT_PACK_1.name, + BOLT_PACK_2.quantity: BOLT_PACK_2.name, + BOLT_PACK_3.quantity: BOLT_PACK_3.name, + BOLT_PACK_4.quantity: BOLT_PACK_4.name, + BOLT_PACK_5.quantity: BOLT_PACK_5.name, + BOLT_PACK_6.quantity: BOLT_PACK_6.name, + BOLT_PACK_7.quantity: BOLT_PACK_7.name, + BOLT_PACK_8.quantity: BOLT_PACK_8.name, + BOLT_PACK_9.quantity: BOLT_PACK_9.name, + BOLT_PACK_10.quantity: BOLT_PACK_10.name, + BOLT_PACK_11.quantity: BOLT_PACK_11.name, + BOLT_PACK_12.quantity: BOLT_PACK_12.name, + BOLT_PACK_13.quantity: BOLT_PACK_13.name, + BOLT_PACK_14.quantity: BOLT_PACK_14.name, + BOLT_PACK_15.quantity: BOLT_PACK_15.name, + BOLT_PACK_16.quantity: BOLT_PACK_16.name, + BOLT_PACK_17.quantity: BOLT_PACK_17.name, + BOLT_PACK_18.quantity: BOLT_PACK_18.name, + BOLT_PACK_19.quantity: BOLT_PACK_19.name, + BOLT_PACK_20.quantity: BOLT_PACK_20.name, + BOLT_PACK_21.quantity: BOLT_PACK_21.name, + BOLT_PACK_22.quantity: BOLT_PACK_22.name, + BOLT_PACK_23.quantity: BOLT_PACK_23.name, + BOLT_PACK_24.quantity: BOLT_PACK_24.name, + BOLT_PACK_25.quantity: BOLT_PACK_25.name, + BOLT_PACK_26.quantity: BOLT_PACK_26.name, + } + return lookup[options.pack_size_bolts.value] + + +def get_gold_bolts(options: Options) -> str: + lookup: dict[int, str] = {} + for gold_bolt in GOLD_BOLTS: + if gold_bolt.name.startswith("Generic"): + continue + lookup.update({gold_bolt.quantity: gold_bolt.name}) + return lookup[options.pack_size_gold_bolts.value] + + +def progression_rules(world): + match world.options.progressive_weapons.value: + case Options.GoldenWeaponProgression.option_normal: + PROG[SUCK_CANNON.name] = {SUCK_CANNON.name: 1, GOLDEN_SUCK_CANNON.name: 1} + PROG[GOLDEN_SUCK_CANNON.name] = {GOLDEN_SUCK_CANNON.name: 1} + PROG[BOMB_GLOVE.name] = {BOMB_GLOVE.name: 1, GOLDEN_BOMB_GLOVE.name: 1} + PROG[GOLDEN_BOMB_GLOVE.name] = {GOLDEN_BOMB_GLOVE.name: 1} + PROG[DEVASTATOR.name] = {DEVASTATOR.name: 1, GOLDEN_DEVASTATOR.name: 1} + PROG[GOLDEN_DEVASTATOR.name] = {GOLDEN_DEVASTATOR.name: 1} + PROG[BLASTER.name] = {BLASTER.name: 1, GOLDEN_BLASTER.name: 1} + PROG[GOLDEN_BLASTER.name] = {GOLDEN_BLASTER.name: 1} + PROG[PYROCITOR.name] = {PYROCITOR.name: 1, GOLDEN_PYROCITOR.name: 1} + PROG[GOLDEN_PYROCITOR.name] = {GOLDEN_PYROCITOR.name: 1} + PROG[MINE_GLOVE.name] = {MINE_GLOVE.name: 1, GOLDEN_MINE_GLOVE.name: 1} + PROG[GOLDEN_MINE_GLOVE.name] = {GOLDEN_MINE_GLOVE.name: 1} + PROG[TESLA_CLAW.name] = {TESLA_CLAW.name: 1, GOLDEN_TESLA_CLAW.name: 1} + PROG[GOLDEN_TESLA_CLAW.name] = {GOLDEN_TESLA_CLAW.name: 1} + PROG[GLOVE_OF_DOOM.name] = {GLOVE_OF_DOOM.name: 1, GOLDEN_GLOVE_OF_DOOM.name: 1} + PROG[GOLDEN_GLOVE_OF_DOOM.name] = {GOLDEN_GLOVE_OF_DOOM.name: 1} + PROG[MORPH_O_RAY.name] = {MORPH_O_RAY.name: 1, GOLDEN_MORPH_O_RAY.name: 1} + PROG[GOLDEN_MORPH_O_RAY.name] = {GOLDEN_MORPH_O_RAY.name: 1} + PROG[DECOY_GLOVE.name] = {DECOY_GLOVE.name: 1, GOLDEN_DECOY_GLOVE.name: 1} + PROG[GOLDEN_DECOY_GLOVE.name] = {GOLDEN_DECOY_GLOVE.name: 1} + case Options.GoldenWeaponProgression.option_progressive: + PROG[SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1} + PROG[GOLDEN_SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 2} + PROG[BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1} + PROG[GOLDEN_BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 2} + PROG[DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1} + PROG[GOLDEN_DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 2} + PROG[BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1} + PROG[GOLDEN_BLASTER.name] = {PROGRESSIVE_BLASTER.name: 2} + PROG[PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1} + PROG[GOLDEN_PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 2} + PROG[MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1} + PROG[GOLDEN_MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 2} + PROG[TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1} + PROG[GOLDEN_TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 2} + PROG[GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1} + PROG[GOLDEN_GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 2} + PROG[MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1} + PROG[GOLDEN_MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 2} + PROG[DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1} + PROG[GOLDEN_DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 2} + case Options.GoldenWeaponProgression.option_progressive_reversed: + world.orders["progressive_suck_cannon_order"].reverse() + PROG[SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1} + PROG[GOLDEN_SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1} + world.orders["progressive_bomb_glove_order"].reverse() + PROG[BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1} + PROG[GOLDEN_BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1} + world.orders["progressive_devastator_order"].reverse() + PROG[DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1} + PROG[GOLDEN_DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1} + world.orders["progressive_blaster_order"].reverse() + PROG[BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1} + PROG[GOLDEN_BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1} + world.orders["progressive_pyrocitor_order"].reverse() + PROG[PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1} + PROG[GOLDEN_PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1} + world.orders["progressive_mine_glove_order"].reverse() + PROG[MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1} + PROG[GOLDEN_MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1} + world.orders["progressive_tesla_claw_order"].reverse() + PROG[TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1} + PROG[GOLDEN_TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1} + world.orders["progressive_glove_of_doom_order"].reverse() + PROG[GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1} + PROG[GOLDEN_GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1} + world.orders["progressive_morph_o_ray_order"].reverse() + PROG[MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1} + PROG[GOLDEN_MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1} + world.orders["progressive_decoy_glove_order"].reverse() + PROG[DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1} + PROG[GOLDEN_DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1} + case Options.GoldenWeaponProgression.option_progressive_random: + world.random.shuffle(world.orders["progressive_suck_cannon_order"]) + PROG[SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1} + PROG[GOLDEN_SUCK_CANNON.name] = {PROGRESSIVE_SUCK.name: 1 + world.orders[ + "progressive_suck_cannon_order"].index(GOLDEN_SUCK_CANNON.item_id)} + world.random.shuffle(world.orders["progressive_bomb_glove_order"]) + PROG[BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1} + PROG[GOLDEN_BOMB_GLOVE.name] = {PROGRESSIVE_BOMB.name: 1 + world.orders[ + "progressive_bomb_glove_order"].index(GOLDEN_BOMB_GLOVE.item_id)} + world.random.shuffle(world.orders["progressive_devastator_order"]) + PROG[DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1} + PROG[GOLDEN_DEVASTATOR.name] = {PROGRESSIVE_DEVASTATOR.name: 1 + world.orders[ + "progressive_devastator_order"].index(GOLDEN_DEVASTATOR.item_id)} + world.random.shuffle(world.orders["progressive_blaster_order"]) + PROG[BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1} + PROG[GOLDEN_BLASTER.name] = {PROGRESSIVE_BLASTER.name: 1 + world.orders[ + "progressive_blaster_order"].index(GOLDEN_BLASTER.item_id)} + world.random.shuffle(world.orders["progressive_pyrocitor_order"]) + PROG[PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1} + PROG[GOLDEN_PYROCITOR.name] = {PROGRESSIVE_PYROCITOR.name: 1 + world.orders[ + "progressive_pyrocitor_order"].index(GOLDEN_PYROCITOR.item_id)} + world.random.shuffle(world.orders["progressive_mine_glove_order"]) + PROG[MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1} + PROG[GOLDEN_MINE_GLOVE.name] = {PROGRESSIVE_MINE.name: 1 + world.orders[ + "progressive_mine_glove_order"].index(GOLDEN_MINE_GLOVE.item_id)} + world.random.shuffle(world.orders["progressive_tesla_claw_order"]) + PROG[TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1} + PROG[GOLDEN_TESLA_CLAW.name] = {PROGRESSIVE_TESLA.name: 1 + world.orders[ + "progressive_tesla_claw_order"].index(TESLA_CLAW.item_id)} + world.random.shuffle(world.orders["progressive_glove_of_doom_order"]) + PROG[GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1} + PROG[GOLDEN_GLOVE_OF_DOOM.name] = {PROGRESSIVE_DOOM.name: 1 + world.orders[ + "progressive_glove_of_doom_order"].index(GOLDEN_GLOVE_OF_DOOM.item_id)} + world.random.shuffle(world.orders["progressive_morph_o_ray_order"]) + PROG[MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1} + PROG[GOLDEN_MORPH_O_RAY.name] = {PROGRESSIVE_MORPH.name: 1 + world.orders[ + "progressive_morph_o_ray_order"].index(GOLDEN_MORPH_O_RAY.item_id)} + world.random.shuffle(world.orders["progressive_decoy_glove_order"]) + PROG[DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1} + PROG[GOLDEN_DECOY_GLOVE.name] = {PROGRESSIVE_DECOY.name: 1 + world.orders[ + "progressive_decoy_glove_order"].index(GOLDEN_DECOY_GLOVE.item_id)} + case _: + pass + + match world.options.progressive_packs.value: + case Options.ProgressiveOptions.option_progressive: + PROG[HELI_PACK.name] = {PROGRESSIVE_PACK.name: 1} + PROG[THRUSTER_PACK.name] = {PROGRESSIVE_PACK.name: 2} + PROG[HYDRO_PACK.name] = {PROGRESSIVE_PACK.name: 3} + case Options.ProgressiveOptions.option_progressive_reversed: + world.orders["progressive_packs_order"].reverse() + PROG[HELI_PACK.name] = {PROGRESSIVE_PACK.name: 3} + PROG[THRUSTER_PACK.name] = {PROGRESSIVE_PACK.name: 2} + PROG[HYDRO_PACK.name] = {PROGRESSIVE_PACK.name: 1} + case Options.ProgressiveOptions.option_progressive_random: + world.random.shuffle(world.orders["progressive_packs_order"]) + PROG[HELI_PACK.name] = { + PROGRESSIVE_PACK.name: 1 + world.orders["progressive_packs_order"].index(HELI_PACK.item_id)} + PROG[THRUSTER_PACK.name] = { + PROGRESSIVE_PACK.name: 1 + world.orders["progressive_packs_order"].index(THRUSTER_PACK.item_id)} + PROG[HYDRO_PACK.name] = { + PROGRESSIVE_PACK.name: 1 + world.orders["progressive_packs_order"].index(HYDRO_PACK.item_id)} + case _: + pass + + match world.options.progressive_helmets.value: + case Options.ProgressiveOptions.option_progressive: + PROG[O2_MASK.name] = {PROGRESSIVE_HELMET.name: 1} + if world.options.shuffle_helmets.value <= Options.ItemOptions.option_random_same: + PROG[SONIC_SUMMONER.name] = {PROGRESSIVE_HELMET.name: 3} + PROG[PILOTS_HELMET.name] = {PROGRESSIVE_HELMET.name: 2} + world.orders["progressive_helmets_order"] = [O2_MASK.item_id, PILOTS_HELMET.item_id, + SONIC_SUMMONER.item_id] + else: + PROG[SONIC_SUMMONER.name] = {PROGRESSIVE_HELMET.name: 2} + PROG[PILOTS_HELMET.name] = {PROGRESSIVE_HELMET.name: 3} + case Options.ProgressiveOptions.option_progressive_reversed: + world.orders["progressive_helmets_order"].reverse() + PROG[O2_MASK.name] = {PROGRESSIVE_HELMET.name: 3} + PROG[SONIC_SUMMONER.name] = {PROGRESSIVE_HELMET.name: 2} + PROG[PILOTS_HELMET.name] = {PROGRESSIVE_HELMET.name: 1} + case Options.ProgressiveOptions.option_progressive_random: + world.random.shuffle(world.orders["progressive_helmets_order"]) + PROG[O2_MASK.name] = { + PROGRESSIVE_HELMET.name: 1 + world.orders["progressive_helmets_order"].index(O2_MASK.item_id)} + PROG[SONIC_SUMMONER.name] = { + PROGRESSIVE_HELMET.name: 1 + world.orders["progressive_helmets_order"].index(SONIC_SUMMONER.item_id)} + PROG[PILOTS_HELMET.name] = { + PROGRESSIVE_HELMET.name: 1 + world.orders["progressive_helmets_order"].index(PILOTS_HELMET.item_id)} + if (world.options.shuffle_helmets.value <= Options.ItemOptions.option_random_same and + PROG[PILOTS_HELMET.name].values() == 3): + temp = PROG[PILOTS_HELMET.name] + PROG[PILOTS_HELMET.name] = PROG[SONIC_SUMMONER.name] + PROG[SONIC_SUMMONER.name] = temp + if world.orders["progressive_helmets_order"].index(O2_MASK.item_id) == 0: + world.orders["progressive_helmets_order"] = [O2_MASK.item_id, PILOTS_HELMET.item_id, + SONIC_SUMMONER.item_id] + else: + world.orders["progressive_helmets_order"] = [PILOTS_HELMET.item_id, O2_MASK.item_id, + SONIC_SUMMONER.item_id] + + case _: + pass + + match world.options.progressive_boots.value: + case Options.ProgressiveOptions.option_progressive: + PROG[GRINDBOOTS.name] = {PROGRESSIVE_BOOT.name: 1} + PROG[MAGNEBOOTS.name] = {PROGRESSIVE_BOOT.name: 2} + case Options.ProgressiveOptions.option_progressive_reversed: + world.orders["progressive_boots_order"].reverse() + PROG[GRINDBOOTS.name] = {PROGRESSIVE_BOOT.name: 2} + PROG[MAGNEBOOTS.name] = {PROGRESSIVE_BOOT.name: 1} + case Options.ProgressiveOptions.option_progressive_random: + world.random.shuffle(world.orders["progressive_boots_order"]) + PROG[GRINDBOOTS.name] = { + PROGRESSIVE_BOOT.name: 1 + world.orders["progressive_boots_order"].index(GRINDBOOTS.item_id)} + PROG[MAGNEBOOTS.name] = { + PROGRESSIVE_BOOT.name: 1 + world.orders["progressive_boots_order"].index(MAGNEBOOTS.item_id)} + case _: + pass + + if world.options.shuffle_extra_items.value == Options.ItemOptions.option_vanilla: + PROG[HOVERBOARD.name] = {HOVERBOARD.name: 1, PROGRESSIVE_HOVERBOARD.name: 1} + PROG[ZOOMERATOR.name] = {ZOOMERATOR.name: 1, PROGRESSIVE_HOVERBOARD.name: 2} + else: + match world.options.progressive_hoverboard.value: + case Options.ProgressiveOptions.option_progressive: + PROG[HOVERBOARD.name] = {PROGRESSIVE_HOVERBOARD.name: 1} + PROG[ZOOMERATOR.name] = {PROGRESSIVE_HOVERBOARD.name: 2} + case Options.ProgressiveOptions.option_progressive_reversed: + world.orders["progressive_hoverboard_order"].reverse() + PROG[HOVERBOARD.name] = {PROGRESSIVE_HOVERBOARD.name: 2} + PROG[ZOOMERATOR.name] = {PROGRESSIVE_HOVERBOARD.name: 1} + case Options.ProgressiveOptions.option_progressive_random: + world.random.shuffle(world.orders["progressive_hoverboard_order"]) + PROG[HOVERBOARD.name] = { + PROGRESSIVE_HOVERBOARD.name: 1 + world.orders["progressive_hoverboard_order"].index(HOVERBOARD.item_id)} + PROG[ZOOMERATOR.name] = { + PROGRESSIVE_HOVERBOARD.name: 1 + world.orders["progressive_hoverboard_order"].index(ZOOMERATOR.item_id)} + case _: + pass + if world.options.shuffle_extra_items.value == Options.ItemOptions.option_vanilla: + PROG[RARITANIUM.name] = {RARITANIUM.name: 1, PROGRESSIVE_TRADE.name: 1} + PROG[PERSUADER.name] = {PERSUADER.name: 1, PROGRESSIVE_TRADE.name: 2} + else: + match world.options.progressive_raritanium.value: + case Options.ProgressiveOptions.option_progressive: + PROG[RARITANIUM.name] = {PROGRESSIVE_TRADE.name: 1} + PROG[PERSUADER.name] = {PROGRESSIVE_TRADE.name: 2} + case Options.ProgressiveOptions.option_progressive_reversed: + world.orders["progressive_raritanium_order"].reverse() + PROG[RARITANIUM.name] = {PROGRESSIVE_TRADE.name: 2} + PROG[PERSUADER.name] = {PROGRESSIVE_TRADE.name: 1} + case Options.ProgressiveOptions.option_progressive_random: + world.random.shuffle(world.orders["progressive_raritanium_order"]) + PROG[RARITANIUM.name] = { + PROGRESSIVE_TRADE.name: 1 + world.orders["progressive_raritanium_order"].index(RARITANIUM.item_id)} + PROG[PERSUADER.name] = { + PROGRESSIVE_TRADE.name: 1 + world.orders["progressive_raritanium_order"].index(PERSUADER.item_id)} + case _: + pass + + match world.options.progressive_nanotech.value: + case Options.ProgressiveOptions.option_progressive: + PROG[PREMIUM_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 1} + PROG[ULTRA_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 2} + case Options.ProgressiveOptions.option_progressive_reversed: + world.orders["progressive_nanotech_order"].reverse() + PROG[PREMIUM_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 2} + PROG[ULTRA_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 1} + case Options.ProgressiveOptions.option_progressive_random: + world.random.shuffle(world.orders["progressive_nanotech_order"]) + PROG[PREMIUM_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 1 + world.orders[ + "progressive_nanotech_order"].index(PREMIUM_NANOTECH.item_id)} + PROG[ULTRA_NANOTECH.name] = {PROGRESSIVE_NANOTECH.name: 1 + world.orders[ + "progressive_nanotech_order"].index(ULTRA_NANOTECH.item_id)} + case _: + pass + return + + +def get_pool(options) -> Sequence[ItemData]: + pool = [] + for item in ITEM_POOL: + pool += [item] + if options.progressive_weapons.value > Options.GoldenWeaponProgression.option_normal: + for item in PROGRESSIVE_WEAPONS: + pool += [item, item] + else: + for item in NON_PROGRESSIVE_WEAPONS: + pool += [item] + for item in GOLDEN_WEAPONS: + pool += [item] + if options.progressive_packs.value > Options.ProgressiveOptions.option_vanilla: + for item in PROGRESSIVE_PACKS: + pool += [item] + else: + for item in PACKS: + pool += [item] + if options.progressive_helmets.value > Options.ProgressiveOptions.option_vanilla: + for item in PROGRESSIVE_HELMETS: + pool += [item] + else: + for item in HELMETS: + pool += [item] + if options.progressive_boots.value > Options.ProgressiveOptions.option_vanilla: + for item in PROGRESSIVE_BOOTS: + pool += [item] + else: + for item in BOOTS: + pool += [item] + if options.progressive_hoverboard.value > Options.ProgressiveOptions.option_vanilla: + for item in PROGRESSIVE_HOVERBOARDS: + pool += [item] + else: + for item in NON_PROGRESSIVE_HOVERBOARDS: + pool += [item] + if options.progressive_raritanium.value > Options.ProgressiveOptions.option_vanilla: + for item in PROGRESSIVE_TRADES: + pool += [item] + else: + for item in NON_PROGRESSIVE_TRADES: + pool += [item] + if options.progressive_nanotech.value > Options.ProgressiveOptions.option_vanilla: + for item in PROGRESSIVE_NANOTECHS: + pool += [item] + else: + for item in NON_PROGRESSIVE_NANOTECHS: + pool += [item] + lookup: dict[int, tuple[ItemData, int]] = { + 1: (GOLD_BOLT_1, 40), + 2: (GOLD_BOLT_2, 30), + 3: (GOLD_BOLT_3, 20), + 4: (GOLD_BOLT_4, 15), + 5: (GOLD_BOLT_5, 12), + 6: (GOLD_BOLT_6, 10), + 7: (GOLD_BOLT_7, 9), + 8: (GOLD_BOLT_8, 8), + 9: (GOLD_BOLT_9, 7), + 10: (GOLD_BOLT_10, 6), + 11: (GOLD_BOLT_11, 5), + 12: (GOLD_BOLT_12, 5), + 13: (GOLD_BOLT_13, 5), + 14: (GOLD_BOLT_14, 4), + 15: (GOLD_BOLT_15, 4), + 16: (GOLD_BOLT_16, 4), + 17: (GOLD_BOLT_17, 4), + 18: (GOLD_BOLT_18, 4), + 19: (GOLD_BOLT_19, 4), + 20: (GOLD_BOLT_20, 3), + 21: (GOLD_BOLT_21, 3), + 22: (GOLD_BOLT_22, 3), + 23: (GOLD_BOLT_23, 3), + 24: (GOLD_BOLT_24, 3), + 25: (GOLD_BOLT_25, 3), + 26: (GOLD_BOLT_26, 3), + 27: (GOLD_BOLT_27, 3), + 28: (GOLD_BOLT_28, 3), + 29: (GOLD_BOLT_29, 3), + 30: (GOLD_BOLT_30, 2), + 31: (GOLD_BOLT_31, 2), + 32: (GOLD_BOLT_32, 2), + 33: (GOLD_BOLT_33, 2), + 34: (GOLD_BOLT_34, 2), + 35: (GOLD_BOLT_35, 2), + 36: (GOLD_BOLT_36, 2), + 37: (GOLD_BOLT_37, 2), + 38: (GOLD_BOLT_38, 2), + 39: (GOLD_BOLT_39, 2), + 40: (GOLD_BOLT_40, 1), + } + for _ in range(lookup[options.pack_size_gold_bolts.value][1]): + pool += [lookup[options.pack_size_gold_bolts.value][0]] + return pool + + +def get_starting_planets(options) -> Sequence[ItemData]: + planets: Sequence[ItemData] = [] + for item in STARTING_PLANETS: + planets += [item] + if options.shuffle_infobots.value >= Options.ShuffleInfobots.option_unrestricted: + planets += [ARIDIA_INFOBOT] + if options.shuffle_helmets.value >= Options.ShuffleHelmets.option_unrestricted: + planets += [GASPAR_INFOBOT] + if options.shuffle_gold_bolts.value: + planets += [HOVEN_INFOBOT] + return planets def from_id(item_id: int) -> ItemData: matching = [item for item in ALL if item.item_id == item_id] if len(matching) == 0: raise ValueError(f"No item data for item id '{item_id}'") - assert len(matching) < 2, f"Multiple item data with id '{item_id}'. Please report." + assert len(matching) < 2, f"{len(matching)} item data found with id '{item_id}'. Items are: {matching}" return matching[0] @@ -265,22 +967,90 @@ def from_name(item_name: str) -> ItemData: matching = [item for item in ALL if item.name == item_name] if len(matching) == 0: raise ValueError(f"No item data for '{item_name}'") - if item_name != GOLD_BOLT.name: - assert len(matching) < 2, f"Multiple item data with name '{item_name}'. Please report." + # if item_name != GOLD_BOLT.name: + # assert len(matching) < 2, f"Multiple item data with name '{item_name}'. Please report." return matching[0] def get_item_groups() -> dict[str, set[str]]: groups: dict[str, set[str]] = { - "Weapons": {w.name for w in WEAPONS}, - "GoldenWeapons": {gw.name for gw in GOLDEN_WEAPONS}, + "Weapons": {w.name for w in ALL_WEAPONS}, "Gadgets": {g.name for g in GADGETS}, - "Packs": {p.name for p in PACKS}, - "Helmets": {h.name for h in HELMETS}, - "Boots": {b.name for b in BOOTS}, - "ExtraItems": {e.name for e in EXTRA_ITEMS}, - "GoldBolts": {c.name for c in COLLECTABLES}, + "Packs": {p.name for p in ALL_PACKS}, + "Helmets": {h.name for h in ALL_HELMETS}, + "Boots": {b.name for b in ALL_BOOTS}, + "ExtraItems": {e.name for e in ALL_EXTRA_ITEMS}, + "GoldBolts": {c.name for c in GOLD_BOLTS}, "Infobots": {i.name for i in PLANETS}, "Skillpoints": {s.name for s in SKILLPOINTS}, } return groups + + +def check_progressive_item(options, item) -> str: + new_item = item + match from_name(item).pool: + case SUCK_CANNON.pool | GOLDEN_SUCK_CANNON.pool: + if options.progressive_weapons.value > Options.GoldenWeaponProgression.option_normal: + match item: + case SUCK_CANNON.name: + new_item = PROGRESSIVE_SUCK.name + case GOLDEN_SUCK_CANNON.name: + new_item = PROGRESSIVE_SUCK.name + case BOMB_GLOVE.name: + new_item = PROGRESSIVE_BOMB.name + case GOLDEN_BOMB_GLOVE.name: + new_item = PROGRESSIVE_BOMB.name + case DEVASTATOR.name: + new_item = PROGRESSIVE_DEVASTATOR.name + case GOLDEN_DEVASTATOR.name: + new_item = PROGRESSIVE_DEVASTATOR.name + case BLASTER.name: + new_item = PROGRESSIVE_BLASTER.name + case GOLDEN_BLASTER.name: + new_item = PROGRESSIVE_BLASTER.name + case PYROCITOR.name: + new_item = PROGRESSIVE_PYROCITOR.name + case GOLDEN_PYROCITOR.name: + new_item = PROGRESSIVE_PYROCITOR.name + case MINE_GLOVE.name: + new_item = PROGRESSIVE_MINE.name + case GOLDEN_MINE_GLOVE.name: + new_item = PROGRESSIVE_MINE.name + case TESLA_CLAW.name: + new_item = PROGRESSIVE_TESLA.name + case GOLDEN_TESLA_CLAW.name: + new_item = PROGRESSIVE_TESLA.name + case GLOVE_OF_DOOM.name: + new_item = PROGRESSIVE_DOOM.name + case GOLDEN_GLOVE_OF_DOOM.name: + new_item = PROGRESSIVE_DOOM.name + case MORPH_O_RAY.name: + new_item = PROGRESSIVE_MORPH.name + case GOLDEN_MORPH_O_RAY.name: + new_item = PROGRESSIVE_MORPH.name + case DECOY_GLOVE.name: + new_item = PROGRESSIVE_DECOY.name + case GOLDEN_DECOY_GLOVE.name: + new_item = PROGRESSIVE_DECOY.name + case HELI_PACK.pool: + if options.progressive_packs.value: + new_item = PROGRESSIVE_PACK.name + case O2_MASK.pool: + if options.progressive_helmets.value: + new_item = PROGRESSIVE_HELMET.name + case GRINDBOOTS.pool: + if options.progressive_boots.value: + new_item = PROGRESSIVE_BOOT.name + case HOVERBOARD.pool: + match item: + case HOVERBOARD.name | ZOOMERATOR.name: + if options.progressive_hoverboard.value: + new_item = PROGRESSIVE_HOVERBOARD.name + case RARITANIUM.name | PERSUADER.name: + if options.progressive_raritanium.value: + new_item = PROGRESSIVE_TRADE.name + case PREMIUM_NANOTECH.name | ULTRA_NANOTECH.name: + if options.progressive_nanotech.value: + new_item = PROGRESSIVE_NANOTECH.name + return new_item diff --git a/worlds/RAC1/data/Locations.py b/worlds/RAC1/data/Locations.py index 33e0c58023d5..623a1d75d96f 100644 --- a/worlds/RAC1/data/Locations.py +++ b/worlds/RAC1/data/Locations.py @@ -12,11 +12,12 @@ POOL_HELMET: str = "Helmets" POOL_BOOT: str = "Boots" POOL_EXTRA_ITEM: str = "ExtraItems" -POOL_GOLD_BOLT: str = "GoldBolt" +POOL_GOLD_BOLT: str = "GoldBolts" POOL_INFOBOT: str = "Infobots" POOL_SKILLPOINT: str = "Skillpoint" -DEFAULT_LIST = list([POOL_WEAPON, POOL_GADGET, POOL_PACK, POOL_HELMET, POOL_BOOT, POOL_EXTRA_ITEM, POOL_INFOBOT]) +DEFAULT_LIST = list([POOL_WEAPON, POOL_GADGET, POOL_PACK, POOL_HELMET, POOL_BOOT, POOL_EXTRA_ITEM, POOL_GOLD_BOLT, + POOL_INFOBOT]) ALL_POOLS = list([POOL_START_PLANET, POOL_START_ITEM, POOL_WEAPON, POOL_GOLDEN_WEAPON, POOL_GADGET, POOL_PACK, POOL_HELMET, POOL_BOOT, POOL_EXTRA_ITEM, POOL_GOLD_BOLT, POOL_INFOBOT, POOL_SKILLPOINT]) @@ -44,17 +45,27 @@ class LocationData: NOVALIS_UNDERWATER_CAVES_GOLD_BOLT = LocationData(6, "Novalis", "Novalis: Gold Bolt: Amoeboid Caves", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, novalis_underwater_caves_rule) # Golden Weapon Locations -NOVALIS_GOLD_WEAPON_1 = LocationData(95, "Novalis", "Novalis: Golden Weapon 1 - 20,000", +NOVALIS_GOLD_WEAPON_1 = LocationData(100, "Novalis", "Novalis: Golden Weapon 1 - 60,000", + Items.GOLDEN_TESLA_CLAW.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) +NOVALIS_GOLD_WEAPON_2 = LocationData(95, "Novalis", "Novalis: Golden Weapon 2 - 20,000", Items.GOLDEN_BOMB_GLOVE.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) -NOVALIS_GOLD_WEAPON_2 = LocationData(96, "Novalis", "Novalis: Golden Weapon 2 - 30,000", +NOVALIS_GOLD_WEAPON_3 = LocationData(101, "Novalis", "Novalis: Golden Weapon 3 - 60,000", + Items.GOLDEN_DEVASTATOR.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) +NOVALIS_GOLD_WEAPON_4 = LocationData(96, "Novalis", "Novalis: Golden Weapon 4 - 30,000", Items.GOLDEN_PYROCITOR.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) -NOVALIS_GOLD_WEAPON_3 = LocationData(97, "Novalis", "Novalis: Golden Weapon 3 - 20,000", +NOVALIS_GOLD_WEAPON_5 = LocationData(102, "Novalis", "Novalis: Golden Weapon 5 - 10,000", + Items.GOLDEN_MINE_GLOVE.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) +NOVALIS_GOLD_WEAPON_6 = LocationData(97, "Novalis", "Novalis: Golden Weapon 6 - 20,000", Items.GOLDEN_BLASTER.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) -NOVALIS_GOLD_WEAPON_4 = LocationData(98, "Novalis", "Novalis: Golden Weapon 4 - 10,000", +NOVALIS_GOLD_WEAPON_7 = LocationData(103, "Novalis", "Novalis: Golden Weapon 7 - 20,000", + Items.GOLDEN_MORPH_O_RAY.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) +NOVALIS_GOLD_WEAPON_8 = LocationData(98, "Novalis", "Novalis: Golden Weapon 8 - 10,000", Items.GOLDEN_GLOVE_OF_DOOM.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) -NOVALIS_GOLD_WEAPON_5 = LocationData(99, "Novalis", "Novalis: Golden Weapon 5 - 10,000", - Items.GOLDEN_SUCK_CANNON.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) - +NOVALIS_GOLD_WEAPON_9 = LocationData(104, "Novalis", "Novalis: Golden Weapon 9 - 10,000", + Items.GOLDEN_DECOY_GLOVE.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) +NOVALIS_GOLD_WEAPON_10 = LocationData(99, "Novalis", "Novalis: Golden Weapon 10 - 10,000", + Items.GOLDEN_SUCK_CANNON.name, {POOL_GOLDEN_WEAPON}, novalis_gold_weapon_rule) +# TODO: Skillpoint Locations # Skill Point Locations NOVALIS_SKILLPOINT = LocationData( 105, "Novalis", "Novalis: Skillpoint: Take Aim", Items.TAKE_AIM.name, {POOL_SKILLPOINT}, novalis_skillpoint_rule) @@ -205,17 +216,6 @@ class LocationData: 69, "Gemlik", "Gemlik: Defeat Captain Quark", Items.OLTANIS_INFOBOT.name, {POOL_INFOBOT}, gemlik_quark_rule) GEMLIK_GOLD_BOLT = LocationData( 70, "Gemlik", "Gemlik: Gold Bolt: Visibomb Hidden Tower", Items.GOLD_BOLT.name, {POOL_GOLD_BOLT}, gemlik_bolt_rule) -# Golden Weapon Locations -GEMLIK_GOLD_WEAPON_1 = LocationData(100, "Gemlik", "Gemlik: Golden Weapon 1 - 60,000", - Items.GOLDEN_TESLA_CLAW.name, {POOL_GOLDEN_WEAPON}, gemlik_gold_weapon_rule) -GEMLIK_GOLD_WEAPON_2 = LocationData(101, "Gemlik", "Gemlik: Golden Weapon 2 - 60,000", - Items.GOLDEN_DEVASTATOR.name, {POOL_GOLDEN_WEAPON}, gemlik_gold_weapon_rule) -GEMLIK_GOLD_WEAPON_3 = LocationData(102, "Gemlik", "Gemlik: Golden Weapon 3 - 10,000", - Items.GOLDEN_MINE_GLOVE.name, {POOL_GOLDEN_WEAPON}, gemlik_gold_weapon_rule) -GEMLIK_GOLD_WEAPON_4 = LocationData(103, "Gemlik", "Gemlik: Golden Weapon 4 - 20,000", - Items.GOLDEN_MORPH_O_RAY.name, {POOL_GOLDEN_WEAPON}, gemlik_gold_weapon_rule) -GEMLIK_GOLD_WEAPON_5 = LocationData(104, "Gemlik", "Gemlik: Golden Weapon 5 - 10,000", - Items.GOLDEN_DECOY_GLOVE.name, {POOL_GOLDEN_WEAPON}, gemlik_gold_weapon_rule) # Oltanis OLTANIS_VENDOR_TESLA_CLAW = LocationData( diff --git a/worlds/RAC1/data/Planets.py b/worlds/RAC1/data/Planets.py index 9bdebaf8afcb..273c0df554ae 100644 --- a/worlds/RAC1/data/Planets.py +++ b/worlds/RAC1/data/Planets.py @@ -21,6 +21,11 @@ class PlanetData(NamedTuple): NOVALIS_GOLD_WEAPON_3, NOVALIS_GOLD_WEAPON_4, NOVALIS_GOLD_WEAPON_5, + NOVALIS_GOLD_WEAPON_6, + NOVALIS_GOLD_WEAPON_7, + NOVALIS_GOLD_WEAPON_8, + NOVALIS_GOLD_WEAPON_9, + NOVALIS_GOLD_WEAPON_10, ]) ARIDIA = PlanetData("Aridia", 2, [ @@ -121,11 +126,6 @@ class PlanetData(NamedTuple): GEMLIK = PlanetData("Gemlik", 13, [ GEMLIK_QUARK_FIGHT, GEMLIK_GOLD_BOLT, - GEMLIK_GOLD_WEAPON_1, - GEMLIK_GOLD_WEAPON_2, - GEMLIK_GOLD_WEAPON_3, - GEMLIK_GOLD_WEAPON_4, - GEMLIK_GOLD_WEAPON_5, ]) OLTANIS = PlanetData("Oltanis", 14, [ diff --git a/worlds/RAC1/test/TestOptions.py b/worlds/RAC1/test/TestOptions.py index bb640ddcaec1..6665681a6b7f 100644 --- a/worlds/RAC1/test/TestOptions.py +++ b/worlds/RAC1/test/TestOptions.py @@ -67,6 +67,11 @@ class TestVanillaGoldBolts(RACTestBase): options = {"shuffle_gold_bolts": ShuffleGoldBolts.option_false} +class TestRandomGoldBolts(RACTestBase): + """Test Gold Bolts with a random pack size""" + options = {"pack_size_gold_bolts": GoldBoltPackSize.weighted_range("random-low")} + + class TestVanillaInfobots(RACTestBase): """Test Infobots unshuffled to verify beatable""" options = {"shuffle_infobots": ShuffleInfobots.option_vanilla} @@ -81,6 +86,7 @@ class TestUsefuls(RACTestBase): """Test Useful items local shuffle to verify beatable""" options = { "shuffle_weapons": ShuffleWeapons.option_random_item, + "shuffle_gold_weapons": ShuffleGoldWeapons.option_random_item, "shuffle_gadgets": ShuffleGadgets.option_random_item, "shuffle_packs": ShufflePacks.option_random_item, "shuffle_helmets": ShuffleHelmets.option_random_item,