Skip to content

Commit c560edb

Browse files
committed
Merge remote-tracking branch 'upstream/main' into zgi-v2
2 parents 9a70818 + cd761db commit c560edb

File tree

5 files changed

+61
-32
lines changed

5 files changed

+61
-32
lines changed

entrance_rando.py

+31-25
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,16 @@ def __init__(self, world: World, coupled: bool):
157157
def placed_regions(self) -> set[Region]:
158158
return self.collection_state.reachable_regions[self.world.player]
159159

160-
def find_placeable_exits(self, check_validity: bool) -> list[Entrance]:
160+
def find_placeable_exits(self, check_validity: bool, usable_exits: list[Entrance]) -> list[Entrance]:
161161
if check_validity:
162162
blocked_connections = self.collection_state.blocked_connections[self.world.player]
163-
blocked_connections = sorted(blocked_connections, key=lambda x: x.name)
164-
placeable_randomized_exits = [connection for connection in blocked_connections
165-
if not connection.connected_region
166-
and connection.is_valid_source_transition(self)]
163+
placeable_randomized_exits = [ex for ex in usable_exits
164+
if not ex.connected_region
165+
and ex in blocked_connections
166+
and ex.is_valid_source_transition(self)]
167167
else:
168168
# this is on a beaten minimal attempt, so any exit anywhere is fair game
169-
placeable_randomized_exits = [ex for region in self.world.multiworld.get_regions(self.world.player)
170-
for ex in region.exits if not ex.connected_region]
169+
placeable_randomized_exits = [ex for ex in usable_exits if not ex.connected_region]
171170
self.world.random.shuffle(placeable_randomized_exits)
172171
return placeable_randomized_exits
173172

@@ -181,7 +180,8 @@ def _connect_one_way(self, source_exit: Entrance, target_entrance: Entrance) ->
181180
self.placements.append(source_exit)
182181
self.pairings.append((source_exit.name, target_entrance.name))
183182

184-
def test_speculative_connection(self, source_exit: Entrance, target_entrance: Entrance) -> bool:
183+
def test_speculative_connection(self, source_exit: Entrance, target_entrance: Entrance,
184+
usable_exits: set[Entrance]) -> bool:
185185
copied_state = self.collection_state.copy()
186186
# simulated connection. A real connection is unsafe because the region graph is shallow-copied and would
187187
# propagate back to the real multiworld.
@@ -198,6 +198,9 @@ def test_speculative_connection(self, source_exit: Entrance, target_entrance: En
198198
# ignore the source exit, and, if coupled, the reverse exit. They're not actually new
199199
if _exit.name == source_exit.name or (self.coupled and _exit.name == target_entrance.name):
200200
continue
201+
# make sure we are only paying attention to usable exits
202+
if _exit not in usable_exits:
203+
continue
201204
# technically this should be is_valid_source_transition, but that may rely on side effects from
202205
# on_connect, which have not happened here (because we didn't do a real connection, and if we did, we would
203206
# not want them to persist). can_reach is a close enough approximation most of the time.
@@ -326,6 +329,24 @@ def randomize_entrances(
326329
# similar to fill, skip validity checks on entrances if the game is beatable on minimal accessibility
327330
perform_validity_check = True
328331

332+
if not er_targets:
333+
er_targets = sorted([entrance for region in world.multiworld.get_regions(world.player)
334+
for entrance in region.entrances if not entrance.parent_region], key=lambda x: x.name)
335+
if not exits:
336+
exits = sorted([ex for region in world.multiworld.get_regions(world.player)
337+
for ex in region.exits if not ex.connected_region], key=lambda x: x.name)
338+
if len(er_targets) != len(exits):
339+
raise EntranceRandomizationError(f"Unable to randomize entrances due to a mismatched count of "
340+
f"entrances ({len(er_targets)}) and exits ({len(exits)}.")
341+
342+
# used when membership checks are needed on the exit list, e.g. speculative sweep
343+
exits_set = set(exits)
344+
for entrance in er_targets:
345+
entrance_lookup.add(entrance)
346+
347+
# place the menu region and connected start region(s)
348+
er_state.collection_state.update_reachable_regions(world.player)
349+
329350
def do_placement(source_exit: Entrance, target_entrance: Entrance) -> None:
330351
placed_exits, removed_entrances = er_state.connect(source_exit, target_entrance)
331352
# remove the placed targets from consideration
@@ -339,7 +360,7 @@ def do_placement(source_exit: Entrance, target_entrance: Entrance) -> None:
339360

340361
def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
341362
nonlocal perform_validity_check
342-
placeable_exits = er_state.find_placeable_exits(perform_validity_check)
363+
placeable_exits = er_state.find_placeable_exits(perform_validity_check, exits)
343364
for source_exit in placeable_exits:
344365
target_groups = target_group_lookup[source_exit.randomization_group]
345366
for target_entrance in entrance_lookup.get_targets(target_groups, dead_end, preserve_group_order):
@@ -355,7 +376,7 @@ def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
355376
and len(placeable_exits) == 1)
356377
if exit_requirement_satisfied and source_exit.can_connect_to(target_entrance, dead_end, er_state):
357378
if (needs_speculative_sweep
358-
and not er_state.test_speculative_connection(source_exit, target_entrance)):
379+
and not er_state.test_speculative_connection(source_exit, target_entrance, exits_set)):
359380
continue
360381
do_placement(source_exit, target_entrance)
361382
return True
@@ -407,21 +428,6 @@ def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
407428
f"All unplaced entrances: {unplaced_entrances}\n"
408429
f"All unplaced exits: {unplaced_exits}")
409430

410-
if not er_targets:
411-
er_targets = sorted([entrance for region in world.multiworld.get_regions(world.player)
412-
for entrance in region.entrances if not entrance.parent_region], key=lambda x: x.name)
413-
if not exits:
414-
exits = sorted([ex for region in world.multiworld.get_regions(world.player)
415-
for ex in region.exits if not ex.connected_region], key=lambda x: x.name)
416-
if len(er_targets) != len(exits):
417-
raise EntranceRandomizationError(f"Unable to randomize entrances due to a mismatched count of "
418-
f"entrances ({len(er_targets)}) and exits ({len(exits)}.")
419-
for entrance in er_targets:
420-
entrance_lookup.add(entrance)
421-
422-
# place the menu region and connected start region(s)
423-
er_state.collection_state.update_reachable_regions(world.player)
424-
425431
# stage 1 - try to place all the non-dead-end entrances
426432
while entrance_lookup.others:
427433
if not find_pairing(dead_end=False, require_new_exits=True):

worlds/messenger/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def create_items(self) -> None:
228228
f"({self.options.total_seals}). Adjusting to {total_seals}"
229229
)
230230
self.total_seals = total_seals
231-
self.required_seals = int(self.options.percent_seals_required.value / 100 * self.total_seals)
231+
self.required_seals = max(1, int(self.options.percent_seals_required.value / 100 * self.total_seals))
232232

233233
seals = [self.create_item("Power Seal") for _ in range(self.total_seals)]
234234
itempool += seals

worlds/messenger/rules.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ def __init__(self, world: "MessengerWorld") -> None:
2626
maximum_price = (world.multiworld.get_location("The Shop - Demon's Bane", self.player).cost +
2727
world.multiworld.get_location("The Shop - Focused Power Sense", self.player).cost)
2828
self.maximum_price = min(maximum_price, world.total_shards)
29-
self.required_seals = max(1, world.required_seals)
29+
self.required_seals = world.required_seals
3030

3131
# dict of connection names and requirements to traverse the exit
3232
self.connection_rules = {
3333
# from ToTHQ
3434
"Artificer's Portal":
3535
lambda state: state.has_all({"Demon King Crown", "Magic Firefly"}, self.player),
3636
"Shrink Down":
37-
lambda state: state.has_all(NOTES, self.player) or self.has_enough_seals(state),
37+
lambda state: state.has_all(NOTES, self.player),
3838
# the shop
3939
"Money Sink":
4040
lambda state: state.has("Money Wrench", self.player) and self.can_shop(state),
@@ -314,6 +314,9 @@ def __init__(self, world: "MessengerWorld") -> None:
314314
self.has_dart,
315315
}
316316

317+
if self.required_seals:
318+
self.connection_rules["Shrink Down"] = self.has_enough_seals
319+
317320
def has_wingsuit(self, state: CollectionState) -> bool:
318321
return state.has("Wingsuit", self.player)
319322

worlds/messenger/test/test_shop_chest.py

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from BaseClasses import ItemClassification, CollectionState
1+
from BaseClasses import CollectionState, ItemClassification
22
from . import MessengerTestBase
33

44

@@ -10,8 +10,9 @@ class AllSealsRequired(MessengerTestBase):
1010
def test_chest_access(self) -> None:
1111
"""Defaults to a total of 45 power seals in the pool and required."""
1212
with self.subTest("Access Dependency"):
13-
self.assertEqual(len([seal for seal in self.multiworld.itempool if seal.name == "Power Seal"]),
14-
self.world.options.total_seals)
13+
self.assertEqual(
14+
len([seal for seal in self.multiworld.itempool if seal.name == "Power Seal"]),
15+
self.world.options.total_seals)
1516
locations = ["Rescue Phantom"]
1617
items = [["Power Seal"]]
1718
self.assertAccessDependency(locations, items)
@@ -93,3 +94,22 @@ def test_seals_amount(self) -> None:
9394
if seal.classification == ItemClassification.progression_skip_balancing]
9495
self.assertEqual(len(total_seals), 85)
9596
self.assertEqual(len(required_seals), 85)
97+
98+
99+
class NoSealsRequired(MessengerTestBase):
100+
options = {
101+
"goal": "power_seal_hunt",
102+
"total_seals": 1,
103+
"percent_seals_required": 10, # percentage
104+
}
105+
106+
def test_seals_amount(self) -> None:
107+
"""Should be 1 seal and it should be progression."""
108+
self.assertEqual(self.world.options.total_seals, 1)
109+
self.assertEqual(self.world.total_seals, 1)
110+
self.assertEqual(self.world.required_seals, 1)
111+
total_seals = [item for item in self.multiworld.itempool if item.name == "Power Seal"]
112+
required_seals = [item for item in self.multiworld.itempool if
113+
item.advancement and item.name == "Power Seal"]
114+
self.assertEqual(len(total_seals), 1)
115+
self.assertEqual(len(required_seals), 1)

worlds/mm2/rules.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def set_rules(world: "MM2World") -> None:
9292
world.wily_5_weapons = slot_data["wily_5_weapons"]
9393
else:
9494
if world.options.random_weakness == RandomWeaknesses.option_shuffled:
95-
weapon_tables = [table for weapon, table in weapon_damage.items() if weapon not in (0, 8)]
95+
weapon_tables = [table.copy() for weapon, table in weapon_damage.items() if weapon not in (0, 8)]
9696
world.random.shuffle(weapon_tables)
9797
for i in range(1, 8):
9898
world.weapon_damage[i] = weapon_tables.pop()

0 commit comments

Comments
 (0)