Skip to content

Commit

Permalink
Various small additions (#59)
Browse files Browse the repository at this point in the history
- changed order of some code blocks in entrance_rando.py
- changed order of some code blocks in utils.py
- the 2 highjack_transitions functions are moved from entrance_rando.py to utils.py
- temp_disabled_exits is added (mainly used in the graph)
- VALLEY_OF_THE_SPIRITS renamed to VALLEY_OF_SPIRITS
- adjusted some of the spacing in the generated graphml file

---------

Co-authored-by: wossnameGitHub <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Avasam <[email protected]>
  • Loading branch information
4 people authored Jul 11, 2024
1 parent d7d4a71 commit f494c8a
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 163 deletions.
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 = (
# 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

0 comments on commit f494c8a

Please sign in to comment.