-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathDoorRando.py
173 lines (134 loc) · 5.63 KB
/
DoorRando.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import copy
from enum import Enum
from .WorldMapping import AreaMapping, WorldMapping
from .data.RoomNames import RoomName
from .Items import SuitUpgrade
from .data.AreaNames import MetroidPrimeArea
from typing import TYPE_CHECKING, Callable, Dict, List
if TYPE_CHECKING:
from . import MetroidPrimeWorld
class DoorLockType(Enum):
Blue = "Blue"
Wave = "Wave Beam"
Ice = "Ice Beam"
Plasma = "Plasma Beam"
Missile = "Missile"
Power_Beam = "Power Beam Only"
Bomb = "Bomb"
None_ = "None"
COLOR_LOCK_TYPES = [
# DoorLockType.Blue, # this requires some extra logic
DoorLockType.Wave,
DoorLockType.Ice,
DoorLockType.Plasma,
]
BEAM_TO_LOCK_MAPPING = {
SuitUpgrade.Power_Beam: DoorLockType.Power_Beam,
SuitUpgrade.Wave_Beam: DoorLockType.Wave,
SuitUpgrade.Ice_Beam: DoorLockType.Ice,
SuitUpgrade.Plasma_Beam: DoorLockType.Plasma,
}
class DoorColorMapping(Dict[str, str]):
pass
class AreaDoorColorMapping(AreaMapping[DoorColorMapping]):
pass
class WorldDoorColorMapping(WorldMapping[DoorColorMapping]):
@classmethod
def from_option_value(
cls, data: Dict[str, Dict[str, str]]
) -> "WorldDoorColorMapping":
return WorldDoorColorMapping(
super().from_option_value_generic(data, AreaDoorColorMapping)
)
def generate_random_door_color_mapping(
world: "MetroidPrimeWorld", area: MetroidPrimeArea
) -> DoorColorMapping:
shuffled_lock_types = get_available_lock_types(world, area)
def is_valid_mapping(mapping: Dict[str, str]) -> bool:
return all(original != new for original, new in mapping.items())
# Can't start w/ Ice beam when fighting Thardus
def is_valid_mapping_for_quarantine_monitor(mapping: Dict[str, str]) -> bool:
return (
is_valid_mapping(mapping)
and mapping[DoorLockType.Wave.value] != DoorLockType.Ice.value
)
validate_func: Callable[[Dict[str, str]], bool] = lambda mapping: is_valid_mapping(
mapping
)
if (
world.starting_room_data
and world.starting_room_data.name == RoomName.Quarantine_Monitor.value
):
validate_func = is_valid_mapping_for_quarantine_monitor
while True:
world.random.shuffle(shuffled_lock_types)
type_mapping = DoorColorMapping(
{
original.value: new.value
for original, new in zip(COLOR_LOCK_TYPES, shuffled_lock_types)
}
)
# Verify that no color matches its original color
if validate_func(type_mapping):
break
return type_mapping
def get_world_door_mapping(world: "MetroidPrimeWorld") -> WorldDoorColorMapping:
door_type_mapping: Dict[str, AreaDoorColorMapping] = {}
assert world.starting_room_data is not None
if world.options.door_color_randomization == "global":
global_mapping = generate_random_door_color_mapping(
world, world.starting_room_data.area
)
for area in MetroidPrimeArea:
door_type_mapping[area.value] = AreaDoorColorMapping(
area.value, copy.deepcopy(global_mapping)
)
else:
for area in MetroidPrimeArea:
mapping = generate_random_door_color_mapping(world, area)
door_type_mapping[area.value] = AreaDoorColorMapping(area.value, mapping)
# Add Bomb doors to a random area if they are enabled
if world.options.include_morph_ball_bomb_doors:
bomb_door_area = world.random.choice(
[area for area in MetroidPrimeArea if area != world.starting_room_data.area]
)
replacement_color = world.random.choice(COLOR_LOCK_TYPES)
door_type_mapping[bomb_door_area.value].type_mapping[
replacement_color.value
] = DoorLockType.Bomb.value
return WorldDoorColorMapping(door_type_mapping)
def get_available_lock_types(
world: "MetroidPrimeWorld", area: MetroidPrimeArea
) -> List[DoorLockType]:
locks = COLOR_LOCK_TYPES[:]
# If start beam is randomized, we replace whatever the mapping to starting beam is with Power Beam Only
if (
world.options.include_power_beam_doors
and not world.options.randomize_starting_beam
):
locks.append(DoorLockType.Power_Beam)
return locks
# This needs to take place after the starting beam is initialized
def remap_doors_to_power_beam_if_necessary(world: "MetroidPrimeWorld"):
if world.options.include_power_beam_doors and world.door_color_mapping:
assert (
world.starting_room_data is not None
and world.starting_room_data.selected_loadout is not None
)
starting_beam = world.starting_room_data.selected_loadout.starting_beam
if starting_beam is not SuitUpgrade.Power_Beam:
assert world.starting_room_data
for area, mapping in world.door_color_mapping.items():
if (
area == world.starting_room_data.area.value
and world.starting_room_data.no_power_beam_door_on_starting_level
):
continue
for original, new in mapping.type_mapping.items():
if new == BEAM_TO_LOCK_MAPPING[starting_beam].value:
world.door_color_mapping[area].type_mapping[
original
] = DoorLockType.Power_Beam.value
world.options.door_color_mapping.value = (
world.door_color_mapping.to_option_value()
)