From be162f5b8deb926dfc4e3f750acf036033266633 Mon Sep 17 00:00:00 2001
From: Fabian Dill <fabian.dill@web.de>
Date: Sat, 2 Jan 2021 22:41:03 +0100
Subject: [PATCH] Connections Plando Support

---
 BaseClasses.py        | 10 ++++++++++
 EntranceRandomizer.py |  4 +++-
 EntranceShuffle.py    | 22 ++++++++++++++++++----
 Main.py               |  4 +++-
 Mystery.py            | 13 ++++++++++++-
 host.yaml             |  2 +-
 6 files changed, 47 insertions(+), 8 deletions(-)

diff --git a/BaseClasses.py b/BaseClasses.py
index db0ab667..9cc25bcb 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -22,6 +22,9 @@ class World(object):
     required_medallions: dict
     dark_room_logic: Dict[int, str]
     restrict_dungeon_item_on_boss: Dict[int, bool]
+    plando_texts: List[Dict[str, str]]
+    plando_items: List[PlandoItem]
+    plando_connections: List[PlandoConnection]
 
     def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer,
                  progressive,
@@ -137,6 +140,7 @@ class World(object):
             set_player_attr('restrict_dungeon_item_on_boss', False)
             set_player_attr('plando_items', [])
             set_player_attr('plando_texts', {})
+            set_player_attr('plando_connections', [])
 
     def secure(self):
         self.random = secrets.SystemRandom()
@@ -1467,3 +1471,9 @@ class PlandoItem(NamedTuple):
     location: str
     world: Union[bool, str] = False  # False -> own world, True -> not own world
     from_pool: bool = True  # if item should be removed from item pool
+
+
+class PlandoConnection(NamedTuple):
+    entrance: str
+    exit: str
+    direction: str  # entrance, exit or both
diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py
index f06bb7ad..bf07cca7 100755
--- a/EntranceRandomizer.py
+++ b/EntranceRandomizer.py
@@ -359,6 +359,7 @@ def parse_arguments(argv, no_defaults=False):
     # cannot be set through CLI currently
     ret.plando_items = []
     ret.plando_texts = {}
+    ret.plando_connections = []
 
     ret.glitch_boots = not ret.disable_glitch_boots
     if ret.timer == "none":
@@ -387,7 +388,8 @@ def parse_arguments(argv, no_defaults=False):
                          'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
                          'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
                          'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
-                         "triforce_pieces_required", "shop_shuffle", "plando_items", "plando_texts",
+                         "triforce_pieces_required", "shop_shuffle",
+                         "plando_items", "plando_texts", "plando_connections",
                          'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
                          'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
                          'restrict_dungeon_item_on_boss',
diff --git a/EntranceShuffle.py b/EntranceShuffle.py
index 66a9a657..96dfb583 100644
--- a/EntranceShuffle.py
+++ b/EntranceShuffle.py
@@ -2240,6 +2240,20 @@ def unbias_some_entrances(world, Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_
     tuplize_lists_in_list(Cave_Three_Exits)
 
 
+lookup = {
+    "both": connect_two_way,
+    "entrance": connect_entrance,
+    "exit": lambda x, y, z, w: connect_exit(x, z, y, w)
+}
+
+
+def plando_connect(world, player: int):
+    if world.plando_connections[player]:
+        for connection in world.plando_connections[player]:
+            func = lookup[connection.direction]
+            func(world, connection.entrance, connection.exit, player)
+
+
 LW_Dungeon_Entrances = ['Desert Palace Entrance (South)',
                         'Desert Palace Entrance (West)',
                         'Desert Palace Entrance (North)',
@@ -2305,10 +2319,10 @@ Cave_Exits_Base = [['Elder House Exit (East)', 'Elder House Exit (West)'],
 Cave_Exits_Base += [('Superbunny Cave Exit (Bottom)', 'Superbunny Cave Exit (Top)'),
               ('Spiral Cave Exit (Top)', 'Spiral Cave Exit')]
 
-
-Cave_Three_Exits_Base = [('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cave Exit (Top)',
- 'Spectacle Rock Cave Exit'),
-                    ['Paradox Cave Exit (Top)', 'Paradox Cave Exit (Middle)','Paradox Cave Exit (Bottom)']]
+Cave_Three_Exits_Base = [
+    ('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cave Exit (Top)', 'Spectacle Rock Cave Exit'),
+    ('Paradox Cave Exit (Top)', 'Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Bottom)')
+]
 
 
 LW_Entrances = ['Elder House (East)',
diff --git a/Main.py b/Main.py
index 1f80f754..50efc82e 100644
--- a/Main.py
+++ b/Main.py
@@ -13,7 +13,7 @@ from BaseClasses import World, CollectionState, Item, Region, Location, PlandoIt
 from Items import ItemFactory, item_table, item_name_groups
 from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance
 from InvertedRegions import create_inverted_regions, mark_dark_world_regions
-from EntranceShuffle import link_entrances, link_inverted_entrances
+from EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
 from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string
 from Rules import set_rules
 from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
@@ -89,6 +89,7 @@ def main(args, seed=None):
     world.dark_room_logic = args.dark_room_logic.copy()
     world.plando_items = args.plando_items.copy()
     world.plando_texts = args.plando_texts.copy()
+    world.plando_connections = args.plando_connections.copy()
     world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy()
 
     world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
@@ -159,6 +160,7 @@ def main(args, seed=None):
         else:
             link_inverted_entrances(world, player)
             mark_dark_world_regions(world, player)
+        plando_connect(world, player)
 
     logger.info('Generating Item Pool.')
 
diff --git a/Mystery.py b/Mystery.py
index 474067e2..cf2c7a16 100644
--- a/Mystery.py
+++ b/Mystery.py
@@ -7,7 +7,7 @@ import typing
 import os
 
 import ModuleUpdate
-from BaseClasses import PlandoItem
+from BaseClasses import PlandoItem, PlandoConnection
 
 ModuleUpdate.update()
 
@@ -566,6 +566,17 @@ def roll_settings(weights, plando_options: typing.Set[str] = frozenset(("bosses"
             if roll_percentage(get_choice("percentage", placement, 100)):
                 ret.plando_texts[str(get_choice("at", placement))] = str(get_choice("text", placement))
 
+    ret.plando_connections = []
+    if "connections" in plando_options:
+        options = weights.get("plando_connections", [])
+        for placement in options:
+            if roll_percentage(get_choice("percentage", placement, 100)):
+                ret.plando_connections.append(PlandoConnection(
+                    get_choice("entrance", placement),
+                    get_choice("exit", placement),
+                    get_choice("direction", placement, "both")
+                ))
+
     if 'rom' in weights:
         romweights = weights['rom']
 
diff --git a/host.yaml b/host.yaml
index dc59c4ef..bccfc9e5 100644
--- a/host.yaml
+++ b/host.yaml
@@ -100,5 +100,5 @@ multi_mystery_options:
   # Create encrypted race roms
   race: 0
   # List of options that can be plando'd. Can be combined, for example "bosses, items"
-  # Available options: bosses
+  # Available options: bosses, items, texts, connections
   plando_options: "bosses"