From 90417e002292b3982f8dff68fae4270bdbd9db5c Mon Sep 17 00:00:00 2001 From: qwint Date: Sun, 26 Jan 2025 07:06:27 -0500 Subject: [PATCH 1/8] CommonClient: Expand on make_gui docstring (#4449) * adds docstring to make_gui describing what things you might want to change without dealing with kivy/kvui directly (there are better places to document those) * Update CommonClient.py Co-authored-by: Doug Hoskisson * Update CommonClient.py Co-authored-by: Doug Hoskisson --------- Co-authored-by: Doug Hoskisson --- CommonClient.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CommonClient.py b/CommonClient.py index f6b2623f8c02..996ba3300575 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -709,8 +709,16 @@ def handle_connection_loss(self, msg: str) -> None: logger.exception(msg, exc_info=exc_info, extra={'compact_gui': True}) self._messagebox_connection_loss = self.gui_error(msg, exc_info[1]) - def make_gui(self) -> typing.Type["kvui.GameManager"]: - """To return the Kivy App class needed for run_gui so it can be overridden before being built""" + def make_gui(self) -> "type[kvui.GameManager]": + """ + To return the Kivy `App` class needed for `run_gui` so it can be overridden before being built + + Common changes are changing `base_title` to update the window title of the client and + updating `logging_pairs` to automatically make new tabs that can be filled with their respective logger. + + ex. `logging_pairs.append(("Foo", "Bar"))` + will add a "Bar" tab which follows the logger returned from `logging.getLogger("Foo")` + """ from kvui import GameManager class TextManager(GameManager): From 8622cb62040e1da2d1d3c66cb1563f76bddb57f9 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 26 Jan 2025 22:14:39 +0100 Subject: [PATCH 2/8] Factorio: Inventory Spill Traps (#4457) --- worlds/factorio/Options.py | 7 ++++ worlds/factorio/__init__.py | 22 +++++------ worlds/factorio/data/mod/lib.lua | 37 +++++++++++++++++++ worlds/factorio/data/mod_template/control.lua | 5 +++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/worlds/factorio/Options.py b/worlds/factorio/Options.py index 0fa75e1b8bfa..4848cd992664 100644 --- a/worlds/factorio/Options.py +++ b/worlds/factorio/Options.py @@ -304,6 +304,11 @@ class EvolutionTrapIncrease(Range): range_end = 100 +class InventorySpillTrapCount(TrapCount): + """Trap items that when received trigger dropping your main inventory and trash inventory onto the ground.""" + display_name = "Inventory Spill Traps" + + class FactorioWorldGen(OptionDict): """World Generation settings. Overview of options at https://wiki.factorio.com/Map_generator, with in-depth documentation at https://lua-api.factorio.com/latest/Concepts.html#MapGenSettings""" @@ -484,6 +489,7 @@ class FactorioOptions(PerGameCommonOptions): artillery_traps: ArtilleryTrapCount atomic_rocket_traps: AtomicRocketTrapCount atomic_cliff_remover_traps: AtomicCliffRemoverTrapCount + inventory_spill_traps: InventorySpillTrapCount attack_traps: AttackTrapCount evolution_traps: EvolutionTrapCount evolution_trap_increase: EvolutionTrapIncrease @@ -518,6 +524,7 @@ class FactorioOptions(PerGameCommonOptions): ArtilleryTrapCount, AtomicRocketTrapCount, AtomicCliffRemoverTrapCount, + InventorySpillTrapCount, ], start_collapsed=True ), diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index a2bc518ae3fd..ca9f12f1b21a 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -78,6 +78,7 @@ class FactorioItem(Item): all_items["Artillery Trap"] = factorio_base_id - 6 all_items["Atomic Rocket Trap"] = factorio_base_id - 7 all_items["Atomic Cliff Remover Trap"] = factorio_base_id - 8 +all_items["Inventory Spill Trap"] = factorio_base_id - 9 class Factorio(World): @@ -112,6 +113,8 @@ class Factorio(World): science_locations: typing.List[FactorioScienceLocation] removed_technologies: typing.Set[str] settings: typing.ClassVar[FactorioSettings] + trap_names: tuple[str] = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", + "Atomic Rocket", "Atomic Cliff Remover", "Inventory Spill") def __init__(self, world, player: int): super(Factorio, self).__init__(world, player) @@ -136,15 +139,11 @@ def create_regions(self): random = self.random nauvis = Region("Nauvis", player, self.multiworld) - location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \ - self.options.evolution_traps + \ - self.options.attack_traps + \ - self.options.teleport_traps + \ - self.options.grenade_traps + \ - self.options.cluster_grenade_traps + \ - self.options.atomic_rocket_traps + \ - self.options.atomic_cliff_remover_traps + \ - self.options.artillery_traps + location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + + for name in self.trap_names: + name = name.replace(" ", "_").lower()+"_traps" + location_count += getattr(self.options, name) location_pool = [] @@ -196,9 +195,8 @@ def sorter(loc: FactorioScienceLocation): def create_items(self) -> None: self.custom_technologies = self.set_custom_technologies() self.set_custom_recipes() - traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket", - "Atomic Cliff Remover") - for trap_name in traps: + + for trap_name in self.trap_names: self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in range(getattr(self.options, f"{trap_name.lower().replace(' ', '_')}_traps"))) diff --git a/worlds/factorio/data/mod/lib.lua b/worlds/factorio/data/mod/lib.lua index 517a54e3d642..edec5b7acdc0 100644 --- a/worlds/factorio/data/mod/lib.lua +++ b/worlds/factorio/data/mod/lib.lua @@ -48,3 +48,40 @@ function fire_entity_at_entities(entity_name, entities, speed) target=target, speed=speed} end end + +function spill_character_inventory(character) + if not (character and character.valid) then + return false + end + + -- grab attrs once pre-loop + local position = character.position + local surface = character.surface + + local inventories_to_spill = { + defines.inventory.character_main, -- Main inventory + defines.inventory.character_trash, -- Logistic trash slots + } + + for _, inventory_type in pairs(inventories_to_spill) do + local inventory = character.get_inventory(inventory_type) + if inventory and inventory.valid then + -- Spill each item stack onto the ground + for i = 1, #inventory do + local stack = inventory[i] + if stack and stack.valid_for_read then + local spilled_items = surface.spill_item_stack{ + position = position, + stack = stack, + enable_looted = false, -- do not mark for auto-pickup + force = nil, -- do not mark for auto-deconstruction + allow_belts = true, -- do mark for putting it onto belts + } + if #spilled_items > 0 then + stack.clear() -- only delete if spilled successfully + end + end + end + end + end +end diff --git a/worlds/factorio/data/mod_template/control.lua b/worlds/factorio/data/mod_template/control.lua index 87669beaf199..07fd4c04afae 100644 --- a/worlds/factorio/data/mod_template/control.lua +++ b/worlds/factorio/data/mod_template/control.lua @@ -750,6 +750,11 @@ end, fire_entity_at_entities("atomic-rocket", {cliffs[math.random(#cliffs)]}, 0.1) end end, +["Inventory Spill Trap"] = function () + for _, player in ipairs(game.forces["player"].players) do + spill_character_inventory(player.character) + end +end, } commands.add_command("ap-get-technology", "Grant a technology, used by the Archipelago Client.", function(call) From 57a571cc110a0df310f0debc2a6fbbb9ea9304ca Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sun, 26 Jan 2025 18:52:02 -0600 Subject: [PATCH 3/8] KDL3: Fix world access on non-strict open world (#4543) * Update rules.py * lambda capture --- worlds/kdl3/rules.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/worlds/kdl3/rules.py b/worlds/kdl3/rules.py index a08e99257e17..828740859e9b 100644 --- a/worlds/kdl3/rules.py +++ b/worlds/kdl3/rules.py @@ -206,19 +206,19 @@ def set_rules(world: "KDL3World") -> None: lambda state: can_reach_needle(state, world.player)) set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u2, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) - or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) - or can_reach_nago(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u3, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) - or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) - or can_reach_nago(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(location_name.sand_canyon_5_u4, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) - or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) - or can_reach_nago(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(location_name.cloudy_park_6_u1, world.player), lambda state: can_reach_cutter(state, world.player)) @@ -248,9 +248,9 @@ def set_rules(world: "KDL3World") -> None: for i in range(12, 18): set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) - or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) - or can_reach_nago(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) for i in range(21, 23): set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), lambda state: can_reach_chuchu(state, world.player)) @@ -307,7 +307,7 @@ def set_rules(world: "KDL3World") -> None: lambda state: can_reach_coo(state, world.player) and can_reach_burning(state, world.player)) set_rule(world.multiworld.get_location(animal_friend_spawns.iceberg_4_a3, world.player), lambda state: can_reach_chuchu(state, world.player) and can_reach_coo(state, world.player) - and can_reach_burning(state, world.player)) + and can_reach_burning(state, world.player)) for boss_flag, purification, i in zip(["Level 1 Boss - Purified", "Level 2 Boss - Purified", "Level 3 Boss - Purified", "Level 4 Boss - Purified", @@ -329,6 +329,14 @@ def set_rules(world: "KDL3World") -> None: world.options.ow_boss_requirement.value, world.player_levels))) + if world.options.open_world: + for boss_flag, level in zip(["Level 1 Boss - Defeated", "Level 2 Boss - Defeated", "Level 3 Boss - Defeated", + "Level 4 Boss - Defeated", "Level 5 Boss - Defeated"], + location_name.level_names.keys()): + set_rule(world.get_location(boss_flag), + lambda state, lvl=level: state.has(f"{lvl} - Stage Completion", world.player, + world.options.ow_boss_requirement.value)) + set_rule(world.multiworld.get_entrance("To Level 6", world.player), lambda state: state.has("Heart Star", world.player, world.required_heart_stars)) From c43233120a828b4c89ee6c8ce1352c396fbe7266 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Mon, 27 Jan 2025 07:24:26 -0800 Subject: [PATCH 4/8] Pokemon Emerald: Clarify death link and start inventory descriptions (#4517) --- worlds/pokemon_emerald/__init__.py | 3 ++- worlds/pokemon_emerald/options.py | 27 +++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index 7b62b9ef73b1..50d6279179d9 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -22,7 +22,7 @@ set_free_fly, set_legendary_cave_entrances) from .opponents import randomize_opponent_parties from .options import (Goal, DarkCavesRequireFlash, HmRequirements, ItemPoolType, PokemonEmeraldOptions, - RandomizeWildPokemon, RandomizeBadges, RandomizeHms, NormanRequirement) + RandomizeWildPokemon, RandomizeBadges, RandomizeHms, NormanRequirement, OPTION_GROUPS) from .pokemon import (get_random_move, get_species_id_by_label, randomize_abilities, randomize_learnsets, randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters, randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters) @@ -63,6 +63,7 @@ class PokemonEmeraldWebWorld(WebWorld): ) tutorials = [setup_en, setup_es, setup_sv] + option_groups = OPTION_GROUPS class PokemonEmeraldSettings(settings.Group): diff --git a/worlds/pokemon_emerald/options.py b/worlds/pokemon_emerald/options.py index cf0c692d06d8..32644d52e0b6 100644 --- a/worlds/pokemon_emerald/options.py +++ b/worlds/pokemon_emerald/options.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from Options import (Choice, DeathLink, DefaultOnToggle, OptionSet, NamedRange, Range, Toggle, FreeText, - PerGameCommonOptions) + PerGameCommonOptions, OptionGroup, StartInventory) from .data import data @@ -803,6 +803,10 @@ class RandomizeFanfares(Toggle): display_name = "Randomize Fanfares" +class PokemonEmeraldDeathLink(DeathLink): + __doc__ = DeathLink.__doc__ + "\n\n In Pokemon Emerald, whiting out sends a death and receiving a death causes you to white out." + + class WonderTrading(DefaultOnToggle): """ Allows participation in wonder trading with other players in your current multiworld. Speak with the center receptionist on the second floor of any pokecenter. @@ -828,6 +832,14 @@ class EasterEgg(FreeText): default = "EMERALD SECRET" +class PokemonEmeraldStartInventory(StartInventory): + """ + Start with these items. + + They will be in your PC, which you can access from your home or a pokemon center. + """ + + @dataclass class PokemonEmeraldOptions(PerGameCommonOptions): goal: Goal @@ -904,7 +916,18 @@ class PokemonEmeraldOptions(PerGameCommonOptions): music: RandomizeMusic fanfares: RandomizeFanfares - death_link: DeathLink + death_link: PokemonEmeraldDeathLink enable_wonder_trading: WonderTrading easter_egg: EasterEgg + + start_inventory: PokemonEmeraldStartInventory + + +OPTION_GROUPS = [ + OptionGroup( + "Item & Location Options", [ + PokemonEmeraldStartInventory, + ], True, + ), +] From b570aa2ec6c811db280a835827aa3983f145a1a6 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Mon, 27 Jan 2025 07:25:31 -0800 Subject: [PATCH 5/8] Pokemon Emerald: Clean up free fly blacklist (#4552) --- worlds/pokemon_emerald/locations.py | 10 +++++++++- worlds/pokemon_emerald/options.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/worlds/pokemon_emerald/locations.py b/worlds/pokemon_emerald/locations.py index 2bae8e00ed34..49ce147041ee 100644 --- a/worlds/pokemon_emerald/locations.py +++ b/worlds/pokemon_emerald/locations.py @@ -34,6 +34,11 @@ } BLACKLIST_OPTION_TO_VISITED_EVENT = { + "Littleroot Town": "EVENT_VISITED_LITTLEROOT_TOWN", + "Oldale Town": "EVENT_VISITED_OLDALE_TOWN", + "Petalburg City": "EVENT_VISITED_PETALBURG_CITY", + "Rustboro City": "EVENT_VISITED_RUSTBORO_CITY", + "Dewford Town": "EVENT_VISITED_DEWFORD_TOWN", "Slateport City": "EVENT_VISITED_SLATEPORT_CITY", "Mauville City": "EVENT_VISITED_MAUVILLE_CITY", "Verdanturf Town": "EVENT_VISITED_VERDANTURF_TOWN", @@ -46,6 +51,9 @@ "Ever Grande City": "EVENT_VISITED_EVER_GRANDE_CITY", } +VISITED_EVENTS = frozenset(BLACKLIST_OPTION_TO_VISITED_EVENT.values()) + + class PokemonEmeraldLocation(Location): game: str = "Pokemon Emerald" item_address: Optional[int] @@ -142,7 +150,7 @@ def set_free_fly(world: "PokemonEmeraldWorld") -> None: fly_location_name = "EVENT_VISITED_LITTLEROOT_TOWN" if world.options.free_fly_location: blacklisted_locations = set(BLACKLIST_OPTION_TO_VISITED_EVENT[city] for city in world.options.free_fly_blacklist.value) - free_fly_locations = sorted(set(BLACKLIST_OPTION_TO_VISITED_EVENT.values()) - blacklisted_locations) + free_fly_locations = sorted(VISITED_EVENTS - blacklisted_locations) if free_fly_locations: fly_location_name = world.random.choice(free_fly_locations) diff --git a/worlds/pokemon_emerald/options.py b/worlds/pokemon_emerald/options.py index 32644d52e0b6..29929bd67237 100644 --- a/worlds/pokemon_emerald/options.py +++ b/worlds/pokemon_emerald/options.py @@ -725,13 +725,20 @@ class FreeFlyLocation(Toggle): """ display_name = "Free Fly Location" + class FreeFlyBlacklist(OptionSet): """ Disables specific locations as valid free fly locations. + Has no effect if Free Fly Location is disabled. """ display_name = "Free Fly Blacklist" valid_keys = [ + "Littleroot Town", + "Oldale Town", + "Petalburg City", + "Rustboro City", + "Dewford Town", "Slateport City", "Mauville City", "Verdanturf Town", @@ -743,6 +750,14 @@ class FreeFlyBlacklist(OptionSet): "Sootopolis City", "Ever Grande City", ] + default = [ + "Littleroot Town", + "Oldale Town", + "Petalburg City", + "Rustboro City", + "Dewford Town", + ] + class HmRequirements(Choice): """ From 43874b1d28fa8d5a5bdc96d4408e303f57763ddd Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Mon, 27 Jan 2025 10:27:43 -0500 Subject: [PATCH 6/8] Noita: Add clarification to check option descriptions (#4553) --- worlds/noita/options.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/worlds/noita/options.py b/worlds/noita/options.py index 0fdd62365a5a..8a973a0d7229 100644 --- a/worlds/noita/options.py +++ b/worlds/noita/options.py @@ -20,6 +20,8 @@ class PathOption(Choice): class HiddenChests(Range): """ Number of hidden chest checks added to the applicable biomes. + Note: The number of hidden chests that spawn per run in each biome varies. + You are expected do multiple runs to get all of your checks. """ display_name = "Hidden Chests per Biome" range_start = 0 @@ -30,6 +32,8 @@ class HiddenChests(Range): class PedestalChecks(Range): """ Number of checks that will spawn on pedestals in the applicable biomes. + Note: The number of pedestals that spawn per run in each biome varies. + You are expected do multiple runs to get all of your checks. """ display_name = "Pedestal Checks per Biome" range_start = 0 From 41055cd963c183244e262344e03d6ae6369fc52a Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Mon, 27 Jan 2025 08:01:18 -0800 Subject: [PATCH 7/8] Pokemon Emerald: Update changelog (#4551) --- worlds/pokemon_emerald/CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/worlds/pokemon_emerald/CHANGELOG.md b/worlds/pokemon_emerald/CHANGELOG.md index 0dd874b25029..8d33d7090044 100644 --- a/worlds/pokemon_emerald/CHANGELOG.md +++ b/worlds/pokemon_emerald/CHANGELOG.md @@ -1,3 +1,16 @@ +# 2.4.0 + +### Features + +- New option `free_fly_blacklist` limits which cities can show up as a free fly location. +- Spoiler log and hint text for maps where a species can be found now use human-friendly labels. +- Added many item and location groups based on item type, location type, and location geography. + +### Fixes + +- Now excludes the location "Navel Rock Top - Hidden Item Sacred Ash" if your goal is Champion and you didn't randomize +event tickets. + # 2.3.0 ### Features From 8c5592e40684af4b9ac855e1a3b4b6e69622bffb Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:06:10 -0500 Subject: [PATCH 8/8] KH2: Fix determinism by using tuples instead of sets (#4548) --- worlds/kh2/Regions.py | 176 +++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/worlds/kh2/Regions.py b/worlds/kh2/Regions.py index e6e8a7b2f663..72b3c95b0947 100644 --- a/worlds/kh2/Regions.py +++ b/worlds/kh2/Regions.py @@ -1032,99 +1032,99 @@ def connect_regions(self): multiworld = self.multiworld player = self.player # connecting every first visit to the GoA - KH2RegionConnections: typing.Dict[str, typing.Set[str]] = { - "Menu": {RegionName.GoA}, - RegionName.GoA: {RegionName.Sp, RegionName.Pr, RegionName.Tt, RegionName.Oc, RegionName.Ht, + KH2RegionConnections: typing.Dict[str, typing.Tuple[str]] = { + "Menu": (RegionName.GoA,), + RegionName.GoA: (RegionName.Sp, RegionName.Pr, RegionName.Tt, RegionName.Oc, RegionName.Ht, RegionName.LoD, RegionName.Twtnw, RegionName.Bc, RegionName.Ag, RegionName.Pl, RegionName.Hb, RegionName.Dc, RegionName.Stt, RegionName.Ha1, RegionName.Keyblade, RegionName.LevelsVS1, RegionName.Valor, RegionName.Wisdom, RegionName.Limit, RegionName.Master, - RegionName.Final, RegionName.Summon, RegionName.AtlanticaSongOne}, - RegionName.LoD: {RegionName.ShanYu}, - RegionName.ShanYu: {RegionName.LoD2}, - RegionName.LoD2: {RegionName.AnsemRiku}, - RegionName.AnsemRiku: {RegionName.StormRider}, - RegionName.StormRider: {RegionName.DataXigbar}, - RegionName.Ag: {RegionName.TwinLords}, - RegionName.TwinLords: {RegionName.Ag2}, - RegionName.Ag2: {RegionName.GenieJafar}, - RegionName.GenieJafar: {RegionName.DataLexaeus}, - RegionName.Dc: {RegionName.Tr}, - RegionName.Tr: {RegionName.OldPete}, - RegionName.OldPete: {RegionName.FuturePete}, - RegionName.FuturePete: {RegionName.Terra, RegionName.DataMarluxia}, - RegionName.Ha1: {RegionName.Ha2}, - RegionName.Ha2: {RegionName.Ha3}, - RegionName.Ha3: {RegionName.Ha4}, - RegionName.Ha4: {RegionName.Ha5}, - RegionName.Ha5: {RegionName.Ha6}, - RegionName.Pr: {RegionName.Barbosa}, - RegionName.Barbosa: {RegionName.Pr2}, - RegionName.Pr2: {RegionName.GrimReaper1}, - RegionName.GrimReaper1: {RegionName.GrimReaper2}, - RegionName.GrimReaper2: {RegionName.DataLuxord}, - RegionName.Oc: {RegionName.Cerberus}, - RegionName.Cerberus: {RegionName.OlympusPete}, - RegionName.OlympusPete: {RegionName.Hydra}, - RegionName.Hydra: {RegionName.OcPainAndPanicCup, RegionName.OcCerberusCup, RegionName.Oc2}, - RegionName.Oc2: {RegionName.Hades}, - RegionName.Hades: {RegionName.Oc2TitanCup, RegionName.Oc2GofCup, RegionName.DataZexion}, - RegionName.Oc2GofCup: {RegionName.HadesCups}, - RegionName.Bc: {RegionName.Thresholder}, - RegionName.Thresholder: {RegionName.Beast}, - RegionName.Beast: {RegionName.DarkThorn}, - RegionName.DarkThorn: {RegionName.Bc2}, - RegionName.Bc2: {RegionName.Xaldin}, - RegionName.Xaldin: {RegionName.DataXaldin}, - RegionName.Sp: {RegionName.HostileProgram}, - RegionName.HostileProgram: {RegionName.Sp2}, - RegionName.Sp2: {RegionName.Mcp}, - RegionName.Mcp: {RegionName.DataLarxene}, - RegionName.Ht: {RegionName.PrisonKeeper}, - RegionName.PrisonKeeper: {RegionName.OogieBoogie}, - RegionName.OogieBoogie: {RegionName.Ht2}, - RegionName.Ht2: {RegionName.Experiment}, - RegionName.Experiment: {RegionName.DataVexen}, - RegionName.Hb: {RegionName.Hb2}, - RegionName.Hb2: {RegionName.CoR, RegionName.HBDemyx}, - RegionName.HBDemyx: {RegionName.ThousandHeartless}, - RegionName.ThousandHeartless: {RegionName.Mushroom13, RegionName.DataDemyx, RegionName.Sephi}, - RegionName.CoR: {RegionName.CorFirstFight}, - RegionName.CorFirstFight: {RegionName.CorSecondFight}, - RegionName.CorSecondFight: {RegionName.Transport}, - RegionName.Pl: {RegionName.Scar}, - RegionName.Scar: {RegionName.Pl2}, - RegionName.Pl2: {RegionName.GroundShaker}, - RegionName.GroundShaker: {RegionName.DataSaix}, - RegionName.Stt: {RegionName.TwilightThorn}, - RegionName.TwilightThorn: {RegionName.Axel1}, - RegionName.Axel1: {RegionName.Axel2}, - RegionName.Axel2: {RegionName.DataRoxas}, - RegionName.Tt: {RegionName.Tt2}, - RegionName.Tt2: {RegionName.Tt3}, - RegionName.Tt3: {RegionName.DataAxel}, - RegionName.Twtnw: {RegionName.Roxas}, - RegionName.Roxas: {RegionName.Xigbar}, - RegionName.Xigbar: {RegionName.Luxord}, - RegionName.Luxord: {RegionName.Saix}, - RegionName.Saix: {RegionName.Twtnw2}, - RegionName.Twtnw2: {RegionName.Xemnas}, - RegionName.Xemnas: {RegionName.ArmoredXemnas, RegionName.DataXemnas}, - RegionName.ArmoredXemnas: {RegionName.ArmoredXemnas2}, - RegionName.ArmoredXemnas2: {RegionName.FinalXemnas}, - RegionName.LevelsVS1: {RegionName.LevelsVS3}, - RegionName.LevelsVS3: {RegionName.LevelsVS6}, - RegionName.LevelsVS6: {RegionName.LevelsVS9}, - RegionName.LevelsVS9: {RegionName.LevelsVS12}, - RegionName.LevelsVS12: {RegionName.LevelsVS15}, - RegionName.LevelsVS15: {RegionName.LevelsVS18}, - RegionName.LevelsVS18: {RegionName.LevelsVS21}, - RegionName.LevelsVS21: {RegionName.LevelsVS24}, - RegionName.LevelsVS24: {RegionName.LevelsVS26}, - RegionName.AtlanticaSongOne: {RegionName.AtlanticaSongTwo}, - RegionName.AtlanticaSongTwo: {RegionName.AtlanticaSongThree}, - RegionName.AtlanticaSongThree: {RegionName.AtlanticaSongFour}, + RegionName.Final, RegionName.Summon, RegionName.AtlanticaSongOne), + RegionName.LoD: (RegionName.ShanYu,), + RegionName.ShanYu: (RegionName.LoD2,), + RegionName.LoD2: (RegionName.AnsemRiku,), + RegionName.AnsemRiku: (RegionName.StormRider,), + RegionName.StormRider: (RegionName.DataXigbar,), + RegionName.Ag: (RegionName.TwinLords,), + RegionName.TwinLords: (RegionName.Ag2,), + RegionName.Ag2: (RegionName.GenieJafar,), + RegionName.GenieJafar: (RegionName.DataLexaeus,), + RegionName.Dc: (RegionName.Tr,), + RegionName.Tr: (RegionName.OldPete,), + RegionName.OldPete: (RegionName.FuturePete,), + RegionName.FuturePete: (RegionName.Terra, RegionName.DataMarluxia), + RegionName.Ha1: (RegionName.Ha2,), + RegionName.Ha2: (RegionName.Ha3,), + RegionName.Ha3: (RegionName.Ha4,), + RegionName.Ha4: (RegionName.Ha5,), + RegionName.Ha5: (RegionName.Ha6,), + RegionName.Pr: (RegionName.Barbosa,), + RegionName.Barbosa: (RegionName.Pr2,), + RegionName.Pr2: (RegionName.GrimReaper1,), + RegionName.GrimReaper1: (RegionName.GrimReaper2,), + RegionName.GrimReaper2: (RegionName.DataLuxord,), + RegionName.Oc: (RegionName.Cerberus,), + RegionName.Cerberus: (RegionName.OlympusPete,), + RegionName.OlympusPete: (RegionName.Hydra,), + RegionName.Hydra: (RegionName.OcPainAndPanicCup, RegionName.OcCerberusCup, RegionName.Oc2), + RegionName.Oc2: (RegionName.Hades,), + RegionName.Hades: (RegionName.Oc2TitanCup, RegionName.Oc2GofCup, RegionName.DataZexion), + RegionName.Oc2GofCup: (RegionName.HadesCups,), + RegionName.Bc: (RegionName.Thresholder,), + RegionName.Thresholder: (RegionName.Beast,), + RegionName.Beast: (RegionName.DarkThorn,), + RegionName.DarkThorn: (RegionName.Bc2,), + RegionName.Bc2: (RegionName.Xaldin,), + RegionName.Xaldin: (RegionName.DataXaldin,), + RegionName.Sp: (RegionName.HostileProgram,), + RegionName.HostileProgram: (RegionName.Sp2,), + RegionName.Sp2: (RegionName.Mcp,), + RegionName.Mcp: (RegionName.DataLarxene,), + RegionName.Ht: (RegionName.PrisonKeeper,), + RegionName.PrisonKeeper: (RegionName.OogieBoogie,), + RegionName.OogieBoogie: (RegionName.Ht2,), + RegionName.Ht2: (RegionName.Experiment,), + RegionName.Experiment: (RegionName.DataVexen,), + RegionName.Hb: (RegionName.Hb2,), + RegionName.Hb2: (RegionName.CoR, RegionName.HBDemyx), + RegionName.HBDemyx: (RegionName.ThousandHeartless,), + RegionName.ThousandHeartless: (RegionName.Mushroom13, RegionName.DataDemyx, RegionName.Sephi), + RegionName.CoR: (RegionName.CorFirstFight,), + RegionName.CorFirstFight: (RegionName.CorSecondFight,), + RegionName.CorSecondFight: (RegionName.Transport,), + RegionName.Pl: (RegionName.Scar,), + RegionName.Scar: (RegionName.Pl2,), + RegionName.Pl2: (RegionName.GroundShaker,), + RegionName.GroundShaker: (RegionName.DataSaix,), + RegionName.Stt: (RegionName.TwilightThorn,), + RegionName.TwilightThorn: (RegionName.Axel1,), + RegionName.Axel1: (RegionName.Axel2,), + RegionName.Axel2: (RegionName.DataRoxas,), + RegionName.Tt: (RegionName.Tt2,), + RegionName.Tt2: (RegionName.Tt3,), + RegionName.Tt3: (RegionName.DataAxel,), + RegionName.Twtnw: (RegionName.Roxas,), + RegionName.Roxas: (RegionName.Xigbar,), + RegionName.Xigbar: (RegionName.Luxord,), + RegionName.Luxord: (RegionName.Saix,), + RegionName.Saix: (RegionName.Twtnw2,), + RegionName.Twtnw2: (RegionName.Xemnas,), + RegionName.Xemnas: (RegionName.ArmoredXemnas, RegionName.DataXemnas), + RegionName.ArmoredXemnas: (RegionName.ArmoredXemnas2,), + RegionName.ArmoredXemnas2: (RegionName.FinalXemnas,), + RegionName.LevelsVS1: (RegionName.LevelsVS3,), + RegionName.LevelsVS3: (RegionName.LevelsVS6,), + RegionName.LevelsVS6: (RegionName.LevelsVS9,), + RegionName.LevelsVS9: (RegionName.LevelsVS12,), + RegionName.LevelsVS12: (RegionName.LevelsVS15,), + RegionName.LevelsVS15: (RegionName.LevelsVS18,), + RegionName.LevelsVS18: (RegionName.LevelsVS21,), + RegionName.LevelsVS21: (RegionName.LevelsVS24,), + RegionName.LevelsVS24: (RegionName.LevelsVS26,), + RegionName.AtlanticaSongOne: (RegionName.AtlanticaSongTwo,), + RegionName.AtlanticaSongTwo: (RegionName.AtlanticaSongThree,), + RegionName.AtlanticaSongThree: (RegionName.AtlanticaSongFour,), } for source, target in KH2RegionConnections.items():