diff --git a/Dolphin scripts/Entrance Randomizer/__main__.py b/Dolphin scripts/Entrance Randomizer/__main__.py
index 992d779..2d97bd9 100644
--- a/Dolphin scripts/Entrance Randomizer/__main__.py
+++ b/Dolphin scripts/Entrance Randomizer/__main__.py
@@ -20,10 +20,10 @@
from lib.constants import * # noqa: F403
from lib.constants import __version__
from lib.entrance_rando import (
- highjack_transition,
highjack_transition_rando,
set_transitions_map,
starting_area,
+ temp_disabled_exits,
transitions_map,
)
from lib.graph_creation import create_graphml
@@ -32,6 +32,7 @@
draw_text,
dump_spoiler_logs,
follow_pointer_path,
+ highjack_transition,
prevent_item_softlock,
prevent_transition_softlocks,
reset_draw_text_index,
@@ -49,7 +50,7 @@
# Dump spoiler logs and graph
dump_spoiler_logs(starting_area_name, transitions_map, seed_string)
-create_graphml(transitions_map, seed_string, starting_area)
+create_graphml(transitions_map, temp_disabled_exits, seed_string, starting_area)
async def main_loop():
diff --git a/Dolphin scripts/Entrance Randomizer/lib/constants.py b/Dolphin scripts/Entrance Randomizer/lib/constants.py
index c35500a..b0613af 100644
--- a/Dolphin scripts/Entrance Randomizer/lib/constants.py
+++ b/Dolphin scripts/Entrance Randomizer/lib/constants.py
@@ -239,7 +239,7 @@ class LevelCRC(IntEnum):
TWIN_OUTPOSTS = 0xE6B9138A
TWIN_OUTPOSTS_UNDERWATER = 0xDE524DA6
UNDERGROUND_DAM = 0x9D6149E1
- VALLEY_OF_THE_SPIRITS = 0x08E3C641
+ VALLEY_OF_SPIRITS = 0x08E3C641
VIRACOCHA_MONOLITHS = 0x6F498BBD
VIRACOCHA_MONOLITHS_CUTSCENE = 0xE8362F5F
WHACK_A_TUCO = 0x0A1F2526
@@ -255,7 +255,7 @@ class LevelCRC(IntEnum):
SOFTLOCKABLE_ENTRANCES = {
int(LevelCRC.FLOODED_COURTYARD): 8, # From st claire: 7
int(LevelCRC.EYES_OF_DOOM): 9,
- int(LevelCRC.VALLEY_OF_THE_SPIRITS): 8,
+ int(LevelCRC.VALLEY_OF_SPIRITS): 8,
int(LevelCRC.COPACANTI_LAKE): 8,
}
"""Entrances that can softlock by infinitely running into a door.
diff --git a/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py b/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py
index 535e395..0d69492 100644
--- a/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py
+++ b/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py
@@ -22,6 +22,23 @@ class Choice(IntEnum):
INBETWEEN = 2
+temples = (
+ LevelCRC.MONKEY_TEMPLE,
+ LevelCRC.SCORPION_TEMPLE,
+ LevelCRC.PENGUIN_TEMPLE,
+)
+
+one_way_exits = (
+ # the White Valley geyser
+ Transition(LevelCRC.WHITE_VALLEY, LevelCRC.MOUNTAIN_SLED_RUN),
+ # the Apu Illapu Shrine geyser
+ Transition(LevelCRC.APU_ILLAPU_SHRINE, LevelCRC.WHITE_VALLEY),
+ # the Apu Illapu Shrine one-way door
+ Transition(LevelCRC.MOUNTAIN_SLED_RUN, LevelCRC.APU_ILLAPU_SHRINE),
+ # the Jungle Canyon waterfall
+ Transition(LevelCRC.CAVERN_LAKE, LevelCRC.JUNGLE_CANYON),
+)
+
_possible_starting_areas = [
area for area in ALL_TRANSITION_AREAS
# Remove unwanted starting areas from the list of possibilities
@@ -57,33 +74,7 @@ class Choice(IntEnum):
}
]
-# Call RNG even if this is unused to not impact randomization of other things for the same seed
-starting_area = random.choice(_possible_starting_areas)
-if CONFIGS.STARTING_AREA is not None:
- starting_area = CONFIGS.STARTING_AREA
-
-transitions_map: dict[tuple[int, int], Transition] = {}
-"""```python
-{
- (og_from_id, og_to_id): (og_from_id, og_to_id)
-}
-```"""
-
-__connections_left: dict[int, int] = {}
-"""Used in randomization process to track per Area how many exits aren't connected yet."""
-
-one_way_exits = (
- # the White Valley geyser
- Transition(LevelCRC.WHITE_VALLEY, LevelCRC.MOUNTAIN_SLED_RUN),
- # the Apu Illapu Shrine geyser
- Transition(LevelCRC.APU_ILLAPU_SHRINE, LevelCRC.WHITE_VALLEY),
- # the Apu Illapu Shrine one-way door
- Transition(LevelCRC.MOUNTAIN_SLED_RUN, LevelCRC.APU_ILLAPU_SHRINE),
- # the Jungle Canyon waterfall
- Transition(LevelCRC.CAVERN_LAKE, LevelCRC.JUNGLE_CANYON),
-)
-
-disabled_exits = (
+temp_disabled_exits = [
# Mouth of Inti has 2 connections with Altar of Huitaca, which causes problems,
# basically it's very easy to get softlocked by the spider web when entering Altar of Huitaca
# So for now just don't randomize it. That way runs don't just end out of nowhere
@@ -95,6 +86,10 @@ class Choice(IntEnum):
# So for now just don't randomize it. That way we won't have to worry about that yet
(LevelCRC.TWIN_OUTPOSTS, LevelCRC.TWIN_OUTPOSTS_UNDERWATER),
(LevelCRC.TWIN_OUTPOSTS_UNDERWATER, LevelCRC.TWIN_OUTPOSTS),
+]
+
+disabled_exits = (
+ *temp_disabled_exits,
# The 3 Spirit Fights are not randomized,
# because that will cause issues with the transformation cutscene trigger.
# Plus it wouldn't really improve anything, given that the Temples are randomized anyway.
@@ -141,64 +136,23 @@ class Choice(IntEnum):
(LevelCRC.BETA_VOLCANO, LevelCRC.PLANE_COCKPIT),
)
+# Call RNG even if this is unused to not impact randomization of other things for the same seed
+starting_area = random.choice(_possible_starting_areas)
+if CONFIGS.STARTING_AREA is not None:
+ starting_area = CONFIGS.STARTING_AREA
+
TRANSITION_INFOS_DICT_RANDO = TRANSITION_INFOS_DICT.copy()
ALL_POSSIBLE_TRANSITIONS_RANDO = ALL_POSSIBLE_TRANSITIONS
+transitions_map: dict[tuple[int, int], Transition] = {}
+"""```python
+{
+ (og_from_id, og_to_id): (og_from_id, og_to_id)
+}
+```"""
-def initialize_connections_left():
- for area in TRANSITION_INFOS_DICT.values():
- __connections_left[area.area_id] = len(area.exits)
-
-
-def remove_disabled_exits():
- # remove exits from TRANSITION_INFOS_DICT_RANDO
- for area in TRANSITION_INFOS_DICT.values():
- for ex in area.exits:
- current = (area.area_id, ex.area_id)
- if current in one_way_exits or current in disabled_exits:
- TRANSITION_INFOS_DICT_RANDO[area.area_id] = Area(
- area.area_id,
- area.name,
- area.default_entrance,
- tuple([
- x for x in TRANSITION_INFOS_DICT_RANDO[area.area_id].exits if x != ex
- ]),
- )
- __connections_left[area.area_id] -= 1
-
- # remove exits from ALL_POSSIBLE_TRANSITIONS_RANDO
- global ALL_POSSIBLE_TRANSITIONS_RANDO
- for trans in ALL_POSSIBLE_TRANSITIONS:
- if trans in one_way_exits or trans in disabled_exits:
- ALL_POSSIBLE_TRANSITIONS_RANDO = [ # pyright: ignore[reportConstantRedefinition]
- x for x in ALL_POSSIBLE_TRANSITIONS_RANDO if x != trans
- ]
-
-
-def highjack_transition(
- from_: int | None,
- to: int | None,
- redirect: int,
-):
- if from_ is None:
- from_ = state.current_area_old
- if to is None:
- to = state.current_area_new
-
- # Early return. Detect the start of a transition
- if state.current_area_old == state.current_area_new:
- return False
-
- if from_ == state.current_area_old and to == state.current_area_new:
- print(
- "highjack_transition |",
- f"From: {hex(state.current_area_old)},",
- f"To: {hex(state.current_area_new)}.",
- f"Redirecting to: {hex(redirect)}",
- )
- memory.write_u32(ADDRESSES.current_area, redirect)
- return True
- return False
+__connections_left: dict[int, int] = {}
+"""Used in randomization process to track per Area how many exits aren't connected yet."""
def highjack_transition_rando():
@@ -242,6 +196,36 @@ def highjack_transition_rando():
return redirect
+def initialize_connections_left():
+ for area in TRANSITION_INFOS_DICT.values():
+ __connections_left[area.area_id] = len(area.exits)
+
+
+def remove_disabled_exits():
+ # remove exits from TRANSITION_INFOS_DICT_RANDO
+ for area in TRANSITION_INFOS_DICT.values():
+ for ex in area.exits:
+ current = (area.area_id, ex.area_id)
+ if current in one_way_exits or current in disabled_exits:
+ TRANSITION_INFOS_DICT_RANDO[area.area_id] = Area(
+ area.area_id,
+ area.name,
+ area.default_entrance,
+ tuple([
+ x for x in TRANSITION_INFOS_DICT_RANDO[area.area_id].exits if x != ex
+ ]),
+ )
+ __connections_left[area.area_id] -= 1
+
+ # remove exits from ALL_POSSIBLE_TRANSITIONS_RANDO
+ global ALL_POSSIBLE_TRANSITIONS_RANDO
+ for trans in ALL_POSSIBLE_TRANSITIONS:
+ if trans in one_way_exits or trans in disabled_exits:
+ ALL_POSSIBLE_TRANSITIONS_RANDO = [ # pyright: ignore[reportConstantRedefinition]
+ x for x in ALL_POSSIBLE_TRANSITIONS_RANDO if x != trans
+ ]
+
+
def link_two_levels(first: Area, second: Area):
__connections_left[first.area_id] -= 1
__connections_left[second.area_id] -= 1
diff --git a/Dolphin scripts/Entrance Randomizer/lib/graph_creation.py b/Dolphin scripts/Entrance Randomizer/lib/graph_creation.py
index 3482ffa..2b7136a 100644
--- a/Dolphin scripts/Entrance Randomizer/lib/graph_creation.py
+++ b/Dolphin scripts/Entrance Randomizer/lib/graph_creation.py
@@ -1,7 +1,8 @@
from __future__ import annotations
-from collections.abc import Mapping
+from collections.abc import Sequence
from pathlib import Path
+from typing import Any
from lib.constants import * # noqa: F403
from lib.constants import __version__
@@ -33,7 +34,7 @@
def create_vertices(
- transitions_map: Mapping[tuple[int, int], tuple[int, int]],
+ transitions_map: dict[tuple[int, int], tuple[int, int]],
starting_area: int,
):
output_text = ""
@@ -63,25 +64,25 @@ def create_vertices(
output_text += (
f'\n"
row_length = 10
@@ -92,7 +93,7 @@ def create_vertices(
return output_text
-def create_edges(transitions_map: Mapping[tuple[int, int], tuple[int, int]]):
+def create_edges(transitions_map: dict[tuple[int, int], tuple[int, int]]):
connections = [(original[0], redirect[1]) for original, redirect in transitions_map.items()]
connections_two_way: list[tuple[int, int]] = []
connections_one_way: list[tuple[int, int]] = []
@@ -109,32 +110,37 @@ def create_edges(transitions_map: Mapping[tuple[int, int], tuple[int, int]]):
output_text += (
f'\n'
+ + f'id="{counter}">\n'
)
counter += 1
for pairing in connections_one_way:
output_text += (
f'\n'
+ + f'id="{counter}">\n'
)
counter += 1
return output_text
def create_graphml(
- transitions_map: Mapping[tuple[int, int], tuple[int, int]],
+ # NOTE: dict is invariant, but Mapping doesn't implement copy
+ transitions_map: dict[tuple[int, int], tuple[int, int]] | dict[tuple[int, int], Any],
+ temp_disabled_exits: Sequence[tuple[int, int]],
seed_string: SeedType,
starting_area: int,
):
- graphml_text = f"""\
-
-
-
- {create_vertices(transitions_map, starting_area)}
- {create_edges(transitions_map)}
-
-"""
+ all_transitions = transitions_map.copy()
+ for item in temp_disabled_exits:
+ all_transitions[item] = item
+
+ graphml_text = (
+ ''
+ + '\n'
+ + create_vertices(all_transitions, starting_area)
+ + create_edges(all_transitions)
+ + ""
+ )
# TODO (Avasam): Get actual user folder based whether Dolphin Emulator is in AppData/Roaming
# and if the current installation is portable.
diff --git a/Dolphin scripts/Entrance Randomizer/lib/utils.py b/Dolphin scripts/Entrance Randomizer/lib/utils.py
index d7ddd23..5a3c7a3 100644
--- a/Dolphin scripts/Entrance Randomizer/lib/utils.py
+++ b/Dolphin scripts/Entrance Randomizer/lib/utils.py
@@ -45,49 +45,6 @@ def draw_text(text: str):
_draw_text_index += 1
-def dump_spoiler_logs(
- starting_area_name: str,
- transitions_map: Mapping[tuple[int, int], tuple[int, int]],
- seed_string: SeedType,
-):
- spoiler_logs = f"Starting area: {starting_area_name}\n"
- red_string_list = [
- f"{TRANSITION_INFOS_DICT[original[0]].name} "
- + f"({TRANSITION_INFOS_DICT[original[1]].name} exit) "
- + f"will redirect to: {TRANSITION_INFOS_DICT[redirect[1]].name} "
- + f"({TRANSITION_INFOS_DICT[redirect[0]].name} entrance)\n"
- for original, redirect in transitions_map.items()
- ]
- red_string_list.sort()
- for string in red_string_list:
- spoiler_logs += string
-
- unrandomized_transitions = ALL_POSSIBLE_TRANSITIONS - transitions_map.keys()
- if len(unrandomized_transitions) > 0:
- spoiler_logs += "\nUnrandomized transitions:\n"
- non_random_string_list = [
- f"From: {TRANSITION_INFOS_DICT[transition[0]].name}, "
- + f"To: {TRANSITION_INFOS_DICT[transition[1]].name}.\n"
- for transition in unrandomized_transitions
- ]
- non_random_string_list.sort()
- for string in non_random_string_list:
- spoiler_logs += string
-
- # TODO (Avasam): Get actual user folder based whether Dolphin Emulator is in AppData/Roaming
- # and if the current installation is portable.
- dolphin_path = Path().absolute()
- spoiler_logs_file = (
- dolphin_path
- / "User"
- / "Logs"
- / f"SPOILER_LOGS_v{__version__}_{seed_string}.txt"
- )
- Path.mkdir(spoiler_logs_file.parent, parents=True, exist_ok=True)
- Path.write_text(spoiler_logs_file, spoiler_logs)
- print("Spoiler logs written to", spoiler_logs_file)
-
-
def follow_pointer_path(ppath: Sequence[int]):
addr = ppath[0]
for i in range(len(ppath) - 1):
@@ -100,6 +57,48 @@ def follow_pointer_path(ppath: Sequence[int]):
return addr
+def highjack_transition(
+ from_: int | None,
+ to: int | None,
+ redirect: int,
+):
+ if from_ is None:
+ from_ = state.current_area_old
+ if to is None:
+ to = state.current_area_new
+
+ # Early return. Detect the start of a transition
+ if state.current_area_old == state.current_area_new:
+ return False
+
+ if from_ == state.current_area_old and to == state.current_area_new:
+ print(
+ "highjack_transition |",
+ f"From: {hex(state.current_area_old)},",
+ f"To: {hex(state.current_area_new)}.",
+ f"Redirecting to: {hex(redirect)}",
+ )
+ memory.write_u32(ADDRESSES.current_area, redirect)
+ return True
+ return False
+
+
+def prevent_transition_softlocks():
+ """Prevents softlocking on closed doors by making Harry land."""
+ height_offset = SOFTLOCKABLE_ENTRANCES.get(state.current_area_new)
+ if (
+ # As far as we're concerned, these are indeed magic numbers.
+ # We haven't identified a name for these states yet.
+ state.area_load_state_old == 5 and state.area_load_state_new == 6 # noqa: PLR2004
+ # TODO: Include "from" transition to only bump player up when needed
+ and height_offset
+ ):
+ player_z_addr = follow_pointer_path((ADDRESSES.player_ptr, PlayerPtrOffset.PositionZ))
+ # memory.write_f32(player_x_addr, memory.read_f32(player_x_addr) + 30)
+ # memory.write_f32(player_y_addr, memory.read_f32(player_y_addr) + 30)
+ memory.write_f32(player_z_addr, memory.read_f32(player_z_addr) + height_offset)
+
+
def prevent_item_softlock():
"""
Prevent softlocking by missing the right items.
@@ -183,17 +182,44 @@ def prevent_item_softlock():
return
-def prevent_transition_softlocks():
- """Prevents softlocking on closed doors by making Harry land."""
- height_offset = SOFTLOCKABLE_ENTRANCES.get(state.current_area_new)
- if (
- # As far as we're concerned, these are indeed magic numbers.
- # We haven't identified a name for these states yet.
- state.area_load_state_old == 5 and state.area_load_state_new == 6 # noqa: PLR2004
- # TODO: Include "from" transition to only bump player up when needed
- and height_offset
- ):
- player_z_addr = follow_pointer_path((ADDRESSES.player_ptr, PlayerPtrOffset.PositionZ))
- # memory.write_f32(player_x_addr, memory.read_f32(player_x_addr) + 30)
- # memory.write_f32(player_y_addr, memory.read_f32(player_y_addr) + 30)
- memory.write_f32(player_z_addr, memory.read_f32(player_z_addr) + height_offset)
+def dump_spoiler_logs(
+ starting_area_name: str,
+ transitions_map: Mapping[tuple[int, int], tuple[int, int]],
+ seed_string: SeedType,
+):
+ spoiler_logs = f"Starting area: {starting_area_name}\n"
+ red_string_list = [
+ f"{TRANSITION_INFOS_DICT[original[0]].name} "
+ + f"({TRANSITION_INFOS_DICT[original[1]].name} exit) "
+ + f"will redirect to: {TRANSITION_INFOS_DICT[redirect[1]].name} "
+ + f"({TRANSITION_INFOS_DICT[redirect[0]].name} entrance)\n"
+ for original, redirect in transitions_map.items()
+ ]
+ red_string_list.sort()
+ for string in red_string_list:
+ spoiler_logs += string
+
+ unrandomized_transitions = ALL_POSSIBLE_TRANSITIONS - transitions_map.keys()
+ if len(unrandomized_transitions) > 0:
+ spoiler_logs += "\nUnrandomized transitions:\n"
+ non_random_string_list = [
+ f"From: {TRANSITION_INFOS_DICT[transition[0]].name}, "
+ + f"To: {TRANSITION_INFOS_DICT[transition[1]].name}.\n"
+ for transition in unrandomized_transitions
+ ]
+ non_random_string_list.sort()
+ for string in non_random_string_list:
+ spoiler_logs += string
+
+ # TODO (Avasam): Get actual user folder based whether Dolphin Emulator is in AppData/Roaming
+ # and if the current installation is portable.
+ dolphin_path = Path().absolute()
+ spoiler_logs_file = (
+ dolphin_path
+ / "User"
+ / "Logs"
+ / f"SPOILER_LOGS_v{__version__}_{seed_string}.txt"
+ )
+ Path.mkdir(spoiler_logs_file.parent, parents=True, exist_ok=True)
+ Path.write_text(spoiler_logs_file, spoiler_logs)
+ print("Spoiler logs written to", spoiler_logs_file)