From d853235d112a1a27942c3566af2d66a09f645aba Mon Sep 17 00:00:00 2001 From: Avasam Date: Thu, 13 Jun 2024 00:20:32 -0400 Subject: [PATCH 1/8] Scan for previous area value across a range of addresses --- .../Entrance Randomizer/__main__.py | 22 ++++++---- .../Entrance Randomizer/lib/constants.py | 14 +++---- .../Entrance Randomizer/lib/entrance_rando.py | 5 ++- .../Entrance Randomizer/lib/utils.py | 42 +++++++++++++++++++ 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/Dolphin scripts/Entrance Randomizer/__main__.py b/Dolphin scripts/Entrance Randomizer/__main__.py index f9cf6ea..d35deec 100644 --- a/Dolphin scripts/Entrance Randomizer/__main__.py +++ b/Dolphin scripts/Entrance Randomizer/__main__.py @@ -29,9 +29,9 @@ from lib.graph_creation import create_graphml from lib.shaman_shop import patch_shaman_shop, randomize_shaman_shop from lib.utils import ( + PreviousArea, draw_text, dump_spoiler_logs, - follow_pointer_path, prevent_transition_softlocks, reset_draw_text_index, state, @@ -60,8 +60,7 @@ async def main_loop(): state.current_area_new = memory.read_u32(ADDRESSES.current_area) state.area_load_state_new = memory.read_u32(ADDRESSES.area_load_state) current_area = TRANSITION_INFOS_DICT.get(state.current_area_new) - previous_area_id = memory.read_u32(follow_pointer_path(ADDRESSES.prev_area)) - previous_area = TRANSITION_INFOS_DICT.get(previous_area_id) + previous_area_id = PreviousArea.get() draw_text(f"Rando version: {__version__}") draw_text(f"Seed: {seed_string}") draw_text(patch_shaman_shop()) @@ -76,10 +75,19 @@ async def main_loop(): f"Current area: {hex(state.current_area_new).upper()} " + (f"({current_area.name})" if current_area else ""), ) - draw_text( - f"From entrance: {hex(previous_area_id).upper()} " - + (f"({previous_area.name})" if previous_area else ""), - ) + if previous_area_id != -1 or state.current_area_new in {starting_area, 0}: + previous_area = TRANSITION_INFOS_DICT.get(previous_area_id) + draw_text( + f"From entrance: {hex(previous_area_id).upper()} " + + (f"({previous_area.name})" if previous_area else ""), + ) + else: + draw_text( + "From entrance: NOT FOUND!!!\n" + + "This is either an entrance using a special ID we're not aware of, or a bug!\n" + + "PLEASE REPORT THIS TO RANDO DEVS\n" + + f"ID might be: {hex(memory.read_u32(PreviousArea._previous_area_address)).upper()}", # noqa: SLF001 # pyright: ignore[reportPrivateUsage] + ) # Always re-enable Item Swap. if memory.read_u32(ADDRESSES.item_swap) == 1: diff --git a/Dolphin scripts/Entrance Randomizer/lib/constants.py b/Dolphin scripts/Entrance Randomizer/lib/constants.py index 0bc5af6..494a26f 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/constants.py +++ b/Dolphin scripts/Entrance Randomizer/lib/constants.py @@ -31,7 +31,7 @@ @dataclass(frozen=True) class Addresses: version_string: str - prev_area: tuple[int, ...] + previous_area_blocks_ptr: int current_area: int area_load_state: int player_x: tuple[int, ...] @@ -75,7 +75,7 @@ class Addresses: "GPH": { "D": Addresses( version_string="GC DE 0-00", - prev_area=(0x80747648,), + previous_area_blocks_ptr=TODO, current_area=0x80417F50, area_load_state=TODO, player_x=(), @@ -86,7 +86,7 @@ class Addresses: ), "E": Addresses( version_string="GC US 0-00", - prev_area=(0x8072B648,), + previous_area_blocks_ptr=0x80425788, current_area=0x8041BEB4, area_load_state=0x8041BEC8, player_x=(0x8041BE4C, 0x338), @@ -97,7 +97,7 @@ class Addresses: ), "F": Addresses( version_string="GC FR 0-00", - prev_area=(0x80747648,), + previous_area_blocks_ptr=TODO, current_area=0x80417F30, area_load_state=TODO, player_x=(), @@ -108,7 +108,7 @@ class Addresses: ), "P": Addresses( version_string="GC EU 0-00", - prev_area=(0x80747648,), + previous_area_blocks_ptr=TODO, current_area=0x80417F10, area_load_state=TODO, player_x=(), @@ -121,7 +121,7 @@ class Addresses: "RPF": { "E": Addresses( version_string="Wii US 0-00", - prev_area=(0x804542DC, 0x8), + previous_area_blocks_ptr=0x804542DC, current_area=0x80448D04, area_load_state=TODO, player_x=(), @@ -132,7 +132,7 @@ class Addresses: ), "P": Addresses( version_string="Wii EU 0-00", - prev_area=(0x804546DC, 0x18), + previous_area_blocks_ptr=0x804546DC, current_area=0x80449104, area_load_state=TODO, player_x=(), diff --git a/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py b/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py index 2f80c28..a6f42e8 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py +++ b/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py @@ -7,7 +7,7 @@ import CONFIGS from lib.constants import * # noqa: F403 -from lib.utils import follow_pointer_path, state +from lib.utils import PreviousArea, state class Transition(NamedTuple): @@ -42,6 +42,7 @@ class Transition(NamedTuple): def highjack_transition_rando(): + return None # Early return, faster check. Detect the start of a transition if state.current_area_old == state.current_area_new: return False @@ -64,7 +65,7 @@ def highjack_transition_rando(): f"Redirecting to: {hex(redirect.to)}", f"({hex(redirect.from_)} entrance)\n", ) - memory.write_u32(follow_pointer_path(ADDRESSES.prev_area), redirect.from_) + PreviousArea.set(redirect.from_) memory.write_u32(ADDRESSES.current_area, redirect.to) return redirect diff --git a/Dolphin scripts/Entrance Randomizer/lib/utils.py b/Dolphin scripts/Entrance Randomizer/lib/utils.py index c13055b..ae2aa70 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/utils.py +++ b/Dolphin scripts/Entrance Randomizer/lib/utils.py @@ -106,3 +106,45 @@ def prevent_transition_softlocks(): # 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) + + +class PreviousArea: + _previous_area_address = 0 + __ALL_LEVELS = ALL_TRANSITION_AREAS | set(LevelCRC) - {LevelCRC.MAIN_MENU} + + @classmethod + def get(cls): + return cls.__update_previous_area_address() + + @classmethod + def set(cls, value: int): + cls.__update_previous_area_address() + memory.write_u32(cls._previous_area_address, value) + + @classmethod + def __update_previous_area_address(cls): + # First check that the current value is a sensible known level + previous_area = memory.read_u32(cls._previous_area_address) + if previous_area in cls.__ALL_LEVELS: + return previous_area + + # If not, start iterating over 16-bit blocks, + # where the first half is consistent (a pointer?) + # and the second half is maybe the value we're looking for + block_address = memory.read_u32(ADDRESSES.previous_area_blocks_ptr) + prefix = memory.read_u32(block_address) + for _ in range(32): # Limited iteration as extra safety for infinite loops + # Check if the current block is a valid level id + block_address += 8 + previous_area = memory.read_u32(block_address) + if previous_area in cls.__ALL_LEVELS: + # Valid id. Assume this is our address + cls._previous_area_address = block_address + return previous_area + + # Check if the next block is still part of the dynamic data + block_address += 8 + next_prefix = memory.read_u32(block_address) + if next_prefix != prefix: + return -1 # We went the entire dynamic data structure w/o finding a valid ID ! + return -1 From a66e4f962b39f45f6efb1b30d4a0c74732991466 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:09:43 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Dolphin scripts/Entrance Randomizer/__main__.py | 1 - Dolphin scripts/Entrance Randomizer/lib/utils.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Dolphin scripts/Entrance Randomizer/__main__.py b/Dolphin scripts/Entrance Randomizer/__main__.py index bcd577d..7d9530a 100644 --- a/Dolphin scripts/Entrance Randomizer/__main__.py +++ b/Dolphin scripts/Entrance Randomizer/__main__.py @@ -32,7 +32,6 @@ PreviousArea, draw_text, dump_spoiler_logs, - follow_pointer_path, highjack_transition, prevent_item_softlock, prevent_transition_softlocks, diff --git a/Dolphin scripts/Entrance Randomizer/lib/utils.py b/Dolphin scripts/Entrance Randomizer/lib/utils.py index 0c2184d..853fc45 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/utils.py +++ b/Dolphin scripts/Entrance Randomizer/lib/utils.py @@ -221,6 +221,7 @@ def __update_previous_area_address(cls): return -1 # We went the entire dynamic data structure w/o finding a valid ID ! return -1 + def dump_spoiler_logs( starting_area_name: str, transitions_map: Mapping[tuple[int, int], tuple[int, int]], From 1991ae5178caae1bc171d3a2c21937fa07726566 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 15 Jul 2024 01:09:23 -0400 Subject: [PATCH 3/8] get double entrances ids --- .../Entrance Randomizer/__main__.py | 45 +++++-- .../Entrance Randomizer/lib/entrance_rando.py | 10 +- .../Entrance Randomizer/lib/utils.py | 121 +++++++++++++++--- .../newsfragments/51.bugfix.md | 12 ++ .../newsfragments/51.doc.md | 0 Dolphin scripts/README.md | 12 +- 6 files changed, 162 insertions(+), 38 deletions(-) create mode 100644 Dolphin scripts/Entrance Randomizer/newsfragments/51.bugfix.md create mode 100644 Dolphin scripts/Entrance Randomizer/newsfragments/51.doc.md diff --git a/Dolphin scripts/Entrance Randomizer/__main__.py b/Dolphin scripts/Entrance Randomizer/__main__.py index 7d9530a..d76b96c 100644 --- a/Dolphin scripts/Entrance Randomizer/__main__.py +++ b/Dolphin scripts/Entrance Randomizer/__main__.py @@ -62,26 +62,34 @@ async def main_loop(): state.current_area_new = memory.read_u32(ADDRESSES.current_area) state.area_load_state_new = memory.read_u32(ADDRESSES.area_load_state) current_area = TRANSITION_INFOS_DICT.get(state.current_area_new) + if current_area: + current_area_name = current_area.name + elif state.current_area_new == LevelCRC.MAIN_MENU: + current_area_name = "Main Menu" + else: + current_area_name = "" previous_area_id = PreviousArea.get() draw_text(f"Rando version: {__version__}") draw_text(f"Seed: {seed_string}") draw_text(patch_shaman_shop()) draw_text( f"Current area: {hex(state.current_area_new).upper()} " - + (f"({current_area.name})" if current_area else ""), + + (f"({current_area_name})" if current_area_name else ""), + ) + draw_text( + f"From entrance addr: {hex(PreviousArea._previous_area_address).upper()} ", # noqa: SLF001 # pyright: ignore[reportPrivateUsage] ) - if previous_area_id != -1 or state.current_area_new in {starting_area, 0}: - previous_area = TRANSITION_INFOS_DICT.get(previous_area_id) + if previous_area_id != -1 or state.current_area_new in {starting_area, LevelCRC.MAIN_MENU}: + previous_area_name = PreviousArea.get_name(previous_area_id) draw_text( f"From entrance: {hex(previous_area_id).upper()} " - + (f"({previous_area.name})" if previous_area else ""), + + (f"({previous_area_name})" if previous_area_name else ""), ) else: draw_text( "From entrance: NOT FOUND!!!\n" + "This is either an entrance using a special ID we're not aware of, or a bug!\n" - + "PLEASE REPORT THIS TO RANDO DEVS\n" - + f"ID might be: {hex(memory.read_u32(PreviousArea._previous_area_address)).upper()}", # noqa: SLF001 # pyright: ignore[reportPrivateUsage] + + "PLEASE REPORT THIS TO RANDO DEVS (unless you just loaded a state)\n", ) # Always re-enable Item Swap @@ -94,27 +102,44 @@ async def main_loop(): # Skip both Jaguar fights if configured if CONFIGS.SKIP_JAGUAR: - if highjack_transition(LevelCRC.MAIN_MENU, LevelCRC.JAGUAR, starting_area): + if highjack_transition( + LevelCRC.MAIN_MENU, + LevelCRC.JAGUAR, + 0, + starting_area, + ): return - if highjack_transition(LevelCRC.GATES_OF_EL_DORADO, LevelCRC.JAGUAR, LevelCRC.PUSCA): + if highjack_transition( + LevelCRC.GATES_OF_EL_DORADO, + LevelCRC.JAGUAR, + LevelCRC.JAGUAR, + LevelCRC.PUSCA, + ): return # Standardize the Altar of Ages exit to remove the Altar -> BBCamp transition highjack_transition( LevelCRC.ALTAR_OF_AGES, LevelCRC.BITTENBINDERS_CAMP, + LevelCRC.ALTAR_OF_AGES, LevelCRC.MYSTERIOUS_TEMPLE, ) - # Standardize the Viracocha Monoliths cutscene + # Standardize and autoskip the Viracocha Monoliths cutscene highjack_transition( None, LevelCRC.VIRACOCHA_MONOLITHS_CUTSCENE, + state.current_area_old, LevelCRC.VIRACOCHA_MONOLITHS, ) # Standardize St. Claire's Excavation Camp - highjack_transition(None, LevelCRC.ST_CLAIRE_NIGHT, LevelCRC.ST_CLAIRE_DAY) + highjack_transition( + None, + LevelCRC.ST_CLAIRE_NIGHT, + LevelCRC.FLOODED_COURTYARD, + LevelCRC.ST_CLAIRE_DAY, + ) # TODO: Skip swim levels (3) diff --git a/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py b/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py index d65fe35..c417db6 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py +++ b/Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py @@ -5,17 +5,11 @@ from copy import copy from enum import IntEnum from itertools import starmap -from typing import NamedTuple import CONFIGS from lib.constants import * # noqa: F403 from lib.transition_infos import Area -from lib.utils import PreviousArea, state - - -class Transition(NamedTuple): - from_: int - to: int +from lib.utils import PreviousArea, Transition, state class Choice(IntEnum): @@ -192,7 +186,7 @@ def highjack_transition_rando(): f"Redirecting to: {hex(redirect.to)}", f"({hex(redirect.from_)} entrance)\n", ) - PreviousArea.set(redirect.from_) + PreviousArea.set(redirect) memory.write_u32(ADDRESSES.current_area, redirect.to) state.current_area_new = redirect.to return redirect diff --git a/Dolphin scripts/Entrance Randomizer/lib/utils.py b/Dolphin scripts/Entrance Randomizer/lib/utils.py index 853fc45..cee6b86 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/utils.py +++ b/Dolphin scripts/Entrance Randomizer/lib/utils.py @@ -2,13 +2,15 @@ from collections.abc import Mapping, Sequence from pathlib import Path -from typing import ClassVar +from typing import ClassVar, NamedTuple from dolphin import gui # pyright: ignore[reportMissingModuleSource] from lib.constants import * # noqa: F403 from lib.constants import __version__ from lib.types_ import SeedType +from .constants import LevelCRC + DRAW_TEXT_STEP = 24 DRAW_TEXT_OFFSET_X = 272 _draw_text_index = 0 @@ -58,27 +60,36 @@ def follow_pointer_path(ppath: Sequence[int]): def highjack_transition( - from_: int | None, - to: int | None, - redirect: int, + original_from: int | None, + original_to: int | None, + redirect_from: int, + redirect_to: int, ): - if from_ is None: - from_ = state.current_area_old - if to is None: - to = state.current_area_new + """ + Highjack a transition to transport the player elsewhere. + + `original_from=None` means that any entrance will match. + `original_from=None` means that any exit will match. + """ + if original_from is None: + original_from = state.current_area_old + if original_to is None: + original_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: + if original_from == state.current_area_old and original_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)}", + f"Redirecting to: {hex(redirect_to)} ", + f"from {hex(redirect_from)} entrance", ) - memory.write_u32(ADDRESSES.current_area, redirect) + PreviousArea.set(Transition(redirect_from, redirect_to)) + memory.write_u32(ADDRESSES.current_area, redirect_to) return True return False @@ -180,18 +191,98 @@ def prevent_item_softlock(): return +class Transition(NamedTuple): + from_: int + to: int + + class PreviousArea: _previous_area_address = 0 - __ALL_LEVELS = ALL_TRANSITION_AREAS | set(LevelCRC) - {LevelCRC.MAIN_MENU} + # TODO: This information is going to be extremely important for the transition rando, + # we're gonna have to update out data structure to be able to make use of this. + CORRECTED_TRANSITION_FROM: ClassVar[dict[Transition, int]] = { + Transition(from_=LevelCRC.APU_ILLAPU_SHRINE, to=LevelCRC.WHITE_VALLEY): 0xF3ACDE92, + # Probably a leftover from when plane crash + cockpit was a single map + Transition(from_=LevelCRC.CRASH_SITE, to=LevelCRC.JUNGLE_CANYON): 0xD33711E2, + # Scorpion/Explorer entrance + # Transition(from_=LevelCRC.NATIVE_JUNGLE, to=LevelCRC.FLOODED_COURTYARD): 0x83A6748F, + # HACK for CORRECTED_PREV_ID + Transition(from_=LevelCRC.NATIVE_JUNGLE, to=0): 0x83A6748F, + # Dark cave entrance + Transition(from_=LevelCRC.NATIVE_JUNGLE, to=LevelCRC.FLOODED_COURTYARD): 0x1AAF2535, + # Dark cave entrance + Transition(from_=LevelCRC.FLOODED_COURTYARD, to=LevelCRC.NATIVE_JUNGLE): 0x402D3708, + # Jungle Outpost well + # Transition(from_=LevelCRC.TWIN_OUTPOSTS, to=LevelCRC.TWIN_OUTPOSTS_UNDERWATER): 0x9D1A6D4A, + # HACK for CORRECTED_PREV_ID + Transition(from_=LevelCRC.TWIN_OUTPOSTS, to=0): 0x9D1A6D4A, + # Burning Outpost well + Transition(from_=LevelCRC.TWIN_OUTPOSTS, to=LevelCRC.TWIN_OUTPOSTS_UNDERWATER): 0x7C65128A, + # Jungle Outpost side + # Transition(from_=LevelCRC.TWIN_OUTPOSTS_UNDERWATER, to=LevelCRC.TWIN_OUTPOSTS): 0x00D15464, + # HACK for CORRECTED_PREV_ID + Transition(from_=LevelCRC.TWIN_OUTPOSTS_UNDERWATER, to=0): 0x00D15464, + # Burning Outpost side + Transition(from_=LevelCRC.TWIN_OUTPOSTS_UNDERWATER, to=LevelCRC.TWIN_OUTPOSTS): 0xE1AE2BA4, + # Native Village uses its own ID to spawn at the Native Games gate + Transition(from_=LevelCRC.KABOOM, to=LevelCRC.NATIVE_VILLAGE): LevelCRC.NATIVE_VILLAGE, + Transition(from_=LevelCRC.PICKAXE_RACE, to=LevelCRC.NATIVE_VILLAGE): LevelCRC.NATIVE_VILLAGE, # noqa: E501 + Transition(from_=LevelCRC.RAFT_BOWLING, to=LevelCRC.NATIVE_VILLAGE): LevelCRC.NATIVE_VILLAGE, # noqa: E501 + Transition(from_=LevelCRC.TUCO_SHOOT, to=LevelCRC.NATIVE_VILLAGE): LevelCRC.NATIVE_VILLAGE, + Transition(from_=LevelCRC.WHACK_A_TUCO, to=LevelCRC.NATIVE_VILLAGE): LevelCRC.NATIVE_VILLAGE, # noqa: E501 + } + """ + Some entrances are mapped to a different ID than the level the player actually comes from. + This maps the transition to the fake ID. + """ + + CORRECTED_PREV_ID: ClassVar[dict[int, int]] = { + area_id: transition.from_ + for transition, area_id + in CORRECTED_TRANSITION_FROM.items() + } + """ + Some entrances are mapped to a different ID than the level the player actually comes from. + This maps the fake ID to the real ID. + """ + __ALL_LEVELS = ( + ALL_TRANSITION_AREAS + | set(CORRECTED_PREV_ID) + | set(LevelCRC) + - {LevelCRC.MAIN_MENU} + ) @classmethod - def get(cls): + def get(cls) -> int | LevelCRC: return cls.__update_previous_area_address() @classmethod - def set(cls, value: int): + def set(cls, value: Transition): + """ + Sets the "previous area id" in memory. + + `value` is a `Transition` because this method tries to the fake ID + to spawn the player on the proper entrance. + + If no mapping is found (ie: either value is incorrect), + then `value.from_` is used directly, + which at worst causes use the default entrance. + """ cls.__update_previous_area_address() - memory.write_u32(cls._previous_area_address, value) + memory.write_u32( + cls._previous_area_address, + cls.CORRECTED_TRANSITION_FROM.get(value, value.from_), + ) + + @classmethod + def get_name(cls, area_id: int): + """ + Gets the name of an area. + + If a "fake ID" is passed, it'll be mapped to the real "from level". + """ + area = TRANSITION_INFOS_DICT.get(cls.CORRECTED_PREV_ID.get(area_id, area_id)) + return area.name if area else "" @classmethod def __update_previous_area_address(cls): diff --git a/Dolphin scripts/Entrance Randomizer/newsfragments/51.bugfix.md b/Dolphin scripts/Entrance Randomizer/newsfragments/51.bugfix.md new file mode 100644 index 0000000..92bf7f2 --- /dev/null +++ b/Dolphin scripts/Entrance Randomizer/newsfragments/51.bugfix.md @@ -0,0 +1,12 @@ +- Death, Loading savestate, New Save file, or Load Save file, will no longer permanently mess up the "previous area" information to transport to the right level entrance. +- The Altar of Ages --> Mysterious Temple and Viracocha Monoliths cutscene transition standardizations should now spawn you on the correct entrance. +- The randomizer should now send to the proper entrances for those cases: + - White Valley from Apu Illapu Shrine + - Jungle Canyon from Crash Site + - Flooded Courtyard from Native Jungle (Dark cave side) + - Native Jungle from Flooded Courtyard (Dark cave side) + - Twin Outposts (Underwater) from Burning side + - Twin Outposts from Burning side well +- Updated documentation to list more problematic double entrances + +-- by @Avasam diff --git a/Dolphin scripts/Entrance Randomizer/newsfragments/51.doc.md b/Dolphin scripts/Entrance Randomizer/newsfragments/51.doc.md new file mode 100644 index 0000000..e69de29 diff --git a/Dolphin scripts/README.md b/Dolphin scripts/README.md index 755d8ae..6133d32 100644 --- a/Dolphin scripts/README.md +++ b/Dolphin scripts/README.md @@ -63,11 +63,13 @@ In order to display the generated map take these steps: - Some seeds will result in impossible to complete configurations, because you might need some items to progress that you don't have yet. - When using `LINKED_TRANSITIONS = False` the generated `.graphml` map will become very hard to read, given the extreme amount of connections that will be drawn. - In rare occasions, a transition might send you to the game-intended level instead of the level decided by the randomizer (this is an issue with the script patching the destination). -- In rare occasions, a transition might not make you enter a level from the correct entrance, but make you enter from the default entrance instead - - The odds of this happening increase dramatically if at any point in the run Harry died or a save file was loaded. -- Some linked transitions are not spawning at the right entrance and use the default entrance instead. Known cases: - - Jungle Canyon from Punchau Shrine - - Bittenbinder's Camp from Mysterious Temple +- Dying may cause you to respawn from a different entrance (the game tries to spawn you where you *would've* entered if the rando didn't highjack the transition). +- The following entrances are not randomized as the randomizer currently isn't aware that two levels can have two entrances between them: + - Altar of Huitaca from Mouth of Inti (both entrances) + - Mouth of Inti from from Altar of Huitaca (both entrances) + - Twin Outpost (Underwater) from the Jungle side + - Twin Outposts from Jungle side well + - Flooded Courtyard from Native Jungle (Scorpion/Explorer entrance) ### Developing From 2c0a2476f24ab063c6576609f72c2a00129a32c7 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 16 Jul 2024 13:37:56 -0400 Subject: [PATCH 4/8] Update Dolphin scripts/Entrance Randomizer/lib/utils.py --- Dolphin scripts/Entrance Randomizer/lib/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dolphin scripts/Entrance Randomizer/lib/utils.py b/Dolphin scripts/Entrance Randomizer/lib/utils.py index cee6b86..40ae001 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/utils.py +++ b/Dolphin scripts/Entrance Randomizer/lib/utils.py @@ -9,8 +9,6 @@ from lib.constants import __version__ from lib.types_ import SeedType -from .constants import LevelCRC - DRAW_TEXT_STEP = 24 DRAW_TEXT_OFFSET_X = 272 _draw_text_index = 0 From abc7768138b556a395281df18de6a61629b82c5d Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 16 Jul 2024 13:40:00 -0400 Subject: [PATCH 5/8] Update Dolphin scripts/Entrance Randomizer/lib/utils.py --- Dolphin scripts/Entrance Randomizer/lib/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dolphin scripts/Entrance Randomizer/lib/utils.py b/Dolphin scripts/Entrance Randomizer/lib/utils.py index 40ae001..2e25113 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/utils.py +++ b/Dolphin scripts/Entrance Randomizer/lib/utils.py @@ -211,13 +211,13 @@ class PreviousArea: # Dark cave entrance Transition(from_=LevelCRC.FLOODED_COURTYARD, to=LevelCRC.NATIVE_JUNGLE): 0x402D3708, # Jungle Outpost well - # Transition(from_=LevelCRC.TWIN_OUTPOSTS, to=LevelCRC.TWIN_OUTPOSTS_UNDERWATER): 0x9D1A6D4A, + # Transition(from_=LevelCRC.TWIN_OUTPOSTS, to=LevelCRC.TWIN_OUTPOSTS_UNDERWATER): 0x9D1A6D4A, # noqa: E501 # HACK for CORRECTED_PREV_ID Transition(from_=LevelCRC.TWIN_OUTPOSTS, to=0): 0x9D1A6D4A, # Burning Outpost well Transition(from_=LevelCRC.TWIN_OUTPOSTS, to=LevelCRC.TWIN_OUTPOSTS_UNDERWATER): 0x7C65128A, # Jungle Outpost side - # Transition(from_=LevelCRC.TWIN_OUTPOSTS_UNDERWATER, to=LevelCRC.TWIN_OUTPOSTS): 0x00D15464, + # Transition(from_=LevelCRC.TWIN_OUTPOSTS_UNDERWATER, to=LevelCRC.TWIN_OUTPOSTS): 0x00D15464, # noqa: E501 # HACK for CORRECTED_PREV_ID Transition(from_=LevelCRC.TWIN_OUTPOSTS_UNDERWATER, to=0): 0x00D15464, # Burning Outpost side From 1a635489e05e7e3e94d595a7b0bb96dac1b377d0 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 16 Jul 2024 13:40:40 -0400 Subject: [PATCH 6/8] Delete Dolphin scripts/Entrance Randomizer/newsfragments/51.doc.md --- Dolphin scripts/Entrance Randomizer/newsfragments/51.doc.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Dolphin scripts/Entrance Randomizer/newsfragments/51.doc.md diff --git a/Dolphin scripts/Entrance Randomizer/newsfragments/51.doc.md b/Dolphin scripts/Entrance Randomizer/newsfragments/51.doc.md deleted file mode 100644 index e69de29..0000000 From 0f6e53859b49299723c04104a09d683572329829 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 16 Jul 2024 14:34:34 -0400 Subject: [PATCH 7/8] Use 0x0 for default entrance --- Dolphin scripts/Entrance Randomizer/__main__.py | 7 ++----- Dolphin scripts/Entrance Randomizer/lib/constants.py | 2 +- Dolphin scripts/Entrance Randomizer/lib/utils.py | 12 ++++++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Dolphin scripts/Entrance Randomizer/__main__.py b/Dolphin scripts/Entrance Randomizer/__main__.py index d76b96c..73e5d20 100644 --- a/Dolphin scripts/Entrance Randomizer/__main__.py +++ b/Dolphin scripts/Entrance Randomizer/__main__.py @@ -79,12 +79,9 @@ async def main_loop(): draw_text( f"From entrance addr: {hex(PreviousArea._previous_area_address).upper()} ", # noqa: SLF001 # pyright: ignore[reportPrivateUsage] ) - if previous_area_id != -1 or state.current_area_new in {starting_area, LevelCRC.MAIN_MENU}: + if previous_area_id or state.current_area_new in {starting_area, LevelCRC.MAIN_MENU}: previous_area_name = PreviousArea.get_name(previous_area_id) - draw_text( - f"From entrance: {hex(previous_area_id).upper()} " - + (f"({previous_area_name})" if previous_area_name else ""), - ) + draw_text(f"From entrance: {hex(previous_area_id).upper()} ({previous_area_name})") else: draw_text( "From entrance: NOT FOUND!!!\n" diff --git a/Dolphin scripts/Entrance Randomizer/lib/constants.py b/Dolphin scripts/Entrance Randomizer/lib/constants.py index a4ba055..dee62f2 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/constants.py +++ b/Dolphin scripts/Entrance Randomizer/lib/constants.py @@ -250,7 +250,7 @@ class LevelCRC(IntEnum): SOFTLOCKABLE_ENTRANCES = { int(LevelCRC.FLOODED_COURTYARD): 8, # From st claire: 7 - int(LevelCRC.EYES_OF_DOOM): 9, + int(LevelCRC.EYES_OF_DOOM): 12, # From Scorpion Temple: 9 int(LevelCRC.VALLEY_OF_SPIRITS): 8, int(LevelCRC.COPACANTI_LAKE): 8, } diff --git a/Dolphin scripts/Entrance Randomizer/lib/utils.py b/Dolphin scripts/Entrance Randomizer/lib/utils.py index 2e25113..b94bff5 100644 --- a/Dolphin scripts/Entrance Randomizer/lib/utils.py +++ b/Dolphin scripts/Entrance Randomizer/lib/utils.py @@ -243,7 +243,7 @@ class PreviousArea: Some entrances are mapped to a different ID than the level the player actually comes from. This maps the fake ID to the real ID. """ - __ALL_LEVELS = ( + __ALL_ENTRANCE_IDS = ( ALL_TRANSITION_AREAS | set(CORRECTED_PREV_ID) | set(LevelCRC) @@ -280,13 +280,13 @@ def get_name(cls, area_id: int): If a "fake ID" is passed, it'll be mapped to the real "from level". """ area = TRANSITION_INFOS_DICT.get(cls.CORRECTED_PREV_ID.get(area_id, area_id)) - return area.name if area else "" + return area.name if area else "Default" @classmethod def __update_previous_area_address(cls): # First check that the current value is a sensible known level previous_area = memory.read_u32(cls._previous_area_address) - if previous_area in cls.__ALL_LEVELS: + if previous_area in cls.__ALL_ENTRANCE_IDS: return previous_area # If not, start iterating over 16-bit blocks, @@ -298,7 +298,7 @@ def __update_previous_area_address(cls): # Check if the current block is a valid level id block_address += 8 previous_area = memory.read_u32(block_address) - if previous_area in cls.__ALL_LEVELS: + if previous_area in cls.__ALL_ENTRANCE_IDS: # Valid id. Assume this is our address cls._previous_area_address = block_address return previous_area @@ -307,8 +307,8 @@ def __update_previous_area_address(cls): block_address += 8 next_prefix = memory.read_u32(block_address) if next_prefix != prefix: - return -1 # We went the entire dynamic data structure w/o finding a valid ID ! - return -1 + return 0 # We went the entire dynamic data structure w/o finding a valid ID ! + return 0 def dump_spoiler_logs( From 67aa701b99e0c08480ffab288088a3e9aac9fa65 Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 16 Jul 2024 15:48:43 -0400 Subject: [PATCH 8/8] attempt s&q back to start --- Dolphin scripts/Entrance Randomizer/__main__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Dolphin scripts/Entrance Randomizer/__main__.py b/Dolphin scripts/Entrance Randomizer/__main__.py index 73e5d20..95ada8c 100644 --- a/Dolphin scripts/Entrance Randomizer/__main__.py +++ b/Dolphin scripts/Entrance Randomizer/__main__.py @@ -30,6 +30,7 @@ from lib.shaman_shop import patch_shaman_shop, randomize_shaman_shop from lib.utils import ( PreviousArea, + Transition, draw_text, dump_spoiler_logs, highjack_transition, @@ -98,13 +99,17 @@ async def main_loop(): state.visited_levels.add(state.current_area_old) # Skip both Jaguar fights if configured - if CONFIGS.SKIP_JAGUAR: + # And always load save file to starting area + if CONFIGS.SKIP_JAGUAR or state.current_area_new != LevelCRC.JAGUAR: if highjack_transition( LevelCRC.MAIN_MENU, - LevelCRC.JAGUAR, - 0, + None, + # If the starting area is Crash Site, we need to avoid resetting progress + # Default entrance otherwise + LevelCRC.JUNGLE_CANYON, starting_area, ): + PreviousArea.set(Transition(LevelCRC.MAIN_MENU, starting_area)) return if highjack_transition( LevelCRC.GATES_OF_EL_DORADO,