Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various small additions #59

Merged
merged 12 commits into from
Jul 11, 2024
5 changes: 3 additions & 2 deletions Dolphin scripts/Entrance Randomizer/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,6 +32,7 @@
draw_text,
dump_spoiler_logs,
follow_pointer_path,
highjack_transition,
prevent_item_softlock,
prevent_transition_softlocks,
reset_draw_text_index,
Expand All @@ -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():
Expand Down
4 changes: 2 additions & 2 deletions Dolphin scripts/Entrance Randomizer/lib/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
146 changes: 65 additions & 81 deletions Dolphin scripts/Entrance Randomizer/lib/entrance_rando.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ class Choice(IntEnum):
INBETWEEN = 2


temples = (
LevelCRC.MONKEY_TEMPLE,
LevelCRC.SCORPION_TEMPLE,
LevelCRC.PENGUIN_TEMPLE,
)

one_way_exits = (
wossnameGitHub marked this conversation as resolved.
Show resolved Hide resolved
# 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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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
Expand Down
48 changes: 27 additions & 21 deletions Dolphin scripts/Entrance Randomizer/lib/graph_creation.py
Original file line number Diff line number Diff line change
@@ -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__
Expand Down Expand Up @@ -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 = ""
Expand Down Expand Up @@ -63,25 +64,25 @@ def create_vertices(
output_text += (
f'<node positionX="{counter_x * 100 + counter_y * 20}" '
+ f'positionY="{counter_x * 50 + counter_y * 50}" '
+ f'id="{int(area_id)}" mainText="{area_name}" '
+ f'id="{int(area_id)}" mainText="{area_name}"'
)
if area_id == starting_area:
output_text += (
'ownStyles = "{&quot;0&quot;:{&quot;fillStyle&quot;:&quot;'
' ownStyles="{&quot;0&quot;:{&quot;fillStyle&quot;:&quot;'
+ STARTING_AREA_COLOR
+ '&quot;}}" '
+ '&quot;}}"'
)
elif area_id in UPGRADE_AREAS:
output_text += (
'ownStyles = "{&quot;0&quot;:{&quot;fillStyle&quot;:&quot;'
' ownStyles="{&quot;0&quot;:{&quot;fillStyle&quot;:&quot;'
+ UPGRADE_AREAS_COLOR
+ '&quot;}}" '
+ '&quot;}}"'
)
elif area_id in IMPORTANT_STORY_TRIGGER_AREAS:
output_text += (
'ownStyles = "{&quot;0&quot;:{&quot;fillStyle&quot;:&quot;'
' ownStyles="{&quot;0&quot;:{&quot;fillStyle&quot;:&quot;'
+ IMPORTANT_STORY_TRIGGER_AREAS_COLOR
+ '&quot;}}" '
+ '&quot;}}"'
)
output_text += "></node>\n"
row_length = 10
Expand All @@ -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]] = []
Expand All @@ -109,32 +110,37 @@ def create_edges(transitions_map: Mapping[tuple[int, int], tuple[int, int]]):
output_text += (
f'<edge source="{TRANSITION_INFOS_DICT[pairing[0]].area_id}" '
+ f'target="{TRANSITION_INFOS_DICT[pairing[1]].area_id}" isDirect="false" '
+ f'id="{counter}" ></edge>\n'
+ f'id="{counter}"></edge>\n'
)
counter += 1
for pairing in connections_one_way:
output_text += (
f'<edge source="{TRANSITION_INFOS_DICT[pairing[0]].area_id}" '
+ f'target="{TRANSITION_INFOS_DICT[pairing[1]].area_id}" isDirect="true" '
+ f'id="{counter}" ></edge>\n'
+ f'id="{counter}"></edge>\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"""\
<?xml version="1.0" encoding="UTF-8"?>
<graphml>
<graph id="Graph" uidGraph="1" uidEdge="1">
{create_vertices(transitions_map, starting_area)}
{create_edges(transitions_map)}
</graph>
</graphml>"""
all_transitions = transitions_map.copy()
for item in temp_disabled_exits:
all_transitions[item] = item

graphml_text = (
'<?xml version="1.0" encoding="UTF-8"?>'
+ '<graphml><graph id="Graph" uidGraph="1" uidEdge="1">\n'
+ create_vertices(all_transitions, starting_area)
+ create_edges(all_transitions)
+ "</graph></graphml>"
)

# TODO (Avasam): Get actual user folder based whether Dolphin Emulator is in AppData/Roaming
# and if the current installation is portable.
Expand Down
Loading