mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
The Messenger: Transition Shuffle (#4402)
* The Messenger: transition rando * remove unused import * always link both directions for plando when using coupled transitions * er_type was renamed to randomization_type * use frozenset for things that shouldn't change * review suggestions * do portal and transition shuffle in `connect_entrances` * remove some unnecessary connections that were causing entrance caching collisions * add test for strictest possible ER settings * use unittest.skip on the skipped test, so we don't waste time doing setUp and tearDown * use the world helpers * make the plando connection description more verbose * always add searing crags portal if portal shuffle is disabled * guarantee an arbitrary number of locations with first connection * make the constraints more lenient for a bit more variety
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
from typing import Any, ClassVar, TextIO
|
||||
|
||||
from BaseClasses import CollectionState, Entrance, Item, ItemClassification, MultiWorld, Tutorial
|
||||
from BaseClasses import CollectionState, Entrance, EntranceType, Item, ItemClassification, MultiWorld, Tutorial
|
||||
from Options import Accessibility
|
||||
from Utils import output_path
|
||||
from settings import FilePath, Group
|
||||
@@ -17,6 +17,7 @@ from .regions import LEVELS, MEGA_SHARDS, LOCATIONS, REGION_CONNECTIONS
|
||||
from .rules import MessengerHardRules, MessengerOOBRules, MessengerRules
|
||||
from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS, shuffle_shop_prices
|
||||
from .subclasses import MessengerEntrance, MessengerItem, MessengerRegion, MessengerShopLocation
|
||||
from .transitions import shuffle_transitions
|
||||
|
||||
components.append(
|
||||
Component("The Messenger", component_type=Type.CLIENT, func=launch_game, game_name="The Messenger", supports_uri=True)
|
||||
@@ -128,7 +129,7 @@ class MessengerWorld(World):
|
||||
spoiler_portal_mapping: dict[str, str]
|
||||
portal_mapping: list[int]
|
||||
transitions: list[Entrance]
|
||||
reachable_locs: int = 0
|
||||
reachable_locs: bool = False
|
||||
filler: dict[str, int]
|
||||
|
||||
def generate_early(self) -> None:
|
||||
@@ -145,13 +146,13 @@ class MessengerWorld(World):
|
||||
|
||||
self.shop_prices, self.figurine_prices = shuffle_shop_prices(self)
|
||||
|
||||
starting_portals = ["Autumn Hills", "Howling Grotto", "Glacial Peak", "Riviere Turquoise", "Sunken Shrine", "Searing Crags"]
|
||||
starting_portals = ["Autumn Hills", "Howling Grotto", "Glacial Peak", "Riviere Turquoise", "Sunken Shrine",
|
||||
"Searing Crags"]
|
||||
self.starting_portals = [f"{portal} Portal"
|
||||
for portal in starting_portals[:3] +
|
||||
self.random.sample(starting_portals[3:], k=self.options.available_portals - 3)]
|
||||
|
||||
# super complicated method for adding searing crags to starting portals if it wasn't chosen
|
||||
# TODO add a check for transition shuffle when that gets added back in
|
||||
if not self.options.shuffle_portals and "Searing Crags Portal" not in self.starting_portals:
|
||||
self.starting_portals.append("Searing Crags Portal")
|
||||
portals_to_strip = [portal for portal in ["Riviere Turquoise Portal", "Sunken Shrine Portal"]
|
||||
@@ -181,7 +182,7 @@ class MessengerWorld(World):
|
||||
region_name = region.name.removeprefix(f"{region.parent} - ")
|
||||
connection_data = CONNECTIONS[region.parent][region_name]
|
||||
for exit_region in connection_data:
|
||||
region.connect(self.multiworld.get_region(exit_region, self.player))
|
||||
region.connect(self.get_region(exit_region))
|
||||
|
||||
# all regions need to be created before i can do these connections so we create and connect the complex first
|
||||
for region in [level for level in simple_regions if level.name in REGION_CONNECTIONS]:
|
||||
@@ -256,6 +257,7 @@ class MessengerWorld(World):
|
||||
f" {logic} for {self.multiworld.get_player_name(self.player)}")
|
||||
# MessengerOOBRules(self).set_messenger_rules()
|
||||
|
||||
def connect_entrances(self) -> None:
|
||||
add_closed_portal_reqs(self)
|
||||
# i need portal shuffle to happen after rules exist so i can validate it
|
||||
attempts = 5
|
||||
@@ -271,6 +273,9 @@ class MessengerWorld(World):
|
||||
else:
|
||||
raise RuntimeError("Unable to generate valid portal output.")
|
||||
|
||||
if self.options.shuffle_transitions:
|
||||
shuffle_transitions(self)
|
||||
|
||||
def write_spoiler_header(self, spoiler_handle: TextIO) -> None:
|
||||
if self.options.available_portals < 6:
|
||||
spoiler_handle.write(f"\nStarting Portals:\n\n")
|
||||
@@ -286,9 +291,54 @@ class MessengerWorld(World):
|
||||
key=lambda portal:
|
||||
["Autumn Hills", "Riviere Turquoise",
|
||||
"Howling Grotto", "Sunken Shrine",
|
||||
"Searing Crags", "Glacial Peak"].index(portal[0]))
|
||||
"Searing Crags", "Glacial Peak"].index(portal[0])
|
||||
)
|
||||
for portal, output in portal_info:
|
||||
spoiler.set_entrance(f"{portal} Portal", output, "I can write anything I want here lmao", self.player)
|
||||
spoiler.set_entrance(f"{portal} Portal", output, "", self.player)
|
||||
|
||||
if self.options.shuffle_transitions:
|
||||
for transition in self.transitions:
|
||||
if (transition.randomization_type == EntranceType.TWO_WAY
|
||||
and (transition.connected_region.name, "both", self.player) in spoiler.entrances):
|
||||
continue
|
||||
spoiler.set_entrance(
|
||||
transition.name if "->" not in transition.name else transition.parent_region.name,
|
||||
transition.connected_region.name,
|
||||
"both" if transition.randomization_type == EntranceType.TWO_WAY
|
||||
and self.options.shuffle_transitions == ShuffleTransitions.option_coupled else "",
|
||||
self.player
|
||||
)
|
||||
|
||||
def extend_hint_information(self, hint_data: dict[int, dict[int, str]]) -> None:
|
||||
if not self.options.shuffle_transitions:
|
||||
return
|
||||
|
||||
hint_data.update({self.player: {}})
|
||||
|
||||
all_state = self.multiworld.get_all_state(True)
|
||||
# sometimes some of my regions aren't in path for some reason?
|
||||
all_state.update_reachable_regions(self.player)
|
||||
paths = all_state.path
|
||||
start = self.get_region("Tower HQ")
|
||||
start_connections = [entrance.name for entrance in start.exits if entrance not in {"Home", "Shrink Down"}]
|
||||
transition_names = [transition.name for transition in self.transitions] + start_connections
|
||||
for loc in self.get_locations():
|
||||
if (loc.parent_region.name in {"Tower HQ", "The Shop", "Music Box", "The Craftsman's Corner"}
|
||||
or loc.address is None):
|
||||
continue
|
||||
path_to_loc: list[str] = []
|
||||
name, connection = paths.get(loc.parent_region, (None, None))
|
||||
while connection != ("Menu", None) and name is not None:
|
||||
name, connection = connection
|
||||
if name in transition_names:
|
||||
if name in start_connections:
|
||||
name = f"{name} -> {self.get_entrance(name).connected_region.name}"
|
||||
path_to_loc.append(name)
|
||||
|
||||
text = " => ".join(reversed(path_to_loc))
|
||||
if not text:
|
||||
continue
|
||||
hint_data[self.player][loc.address] = text
|
||||
|
||||
def fill_slot_data(self) -> dict[str, Any]:
|
||||
slot_data = {
|
||||
@@ -308,11 +358,13 @@ class MessengerWorld(World):
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
if not getattr(self, "_filler_items", None):
|
||||
self._filler_items = [name for name in self.random.choices(
|
||||
list(self.filler),
|
||||
weights=list(self.filler.values()),
|
||||
k=20
|
||||
)]
|
||||
self._filler_items = [
|
||||
name for name in self.random.choices(
|
||||
list(self.filler),
|
||||
weights=list(self.filler.values()),
|
||||
k=20
|
||||
)
|
||||
]
|
||||
return self._filler_items.pop(0)
|
||||
|
||||
def create_item(self, name: str) -> MessengerItem:
|
||||
@@ -331,7 +383,7 @@ class MessengerWorld(World):
|
||||
self.total_shards += count
|
||||
return ItemClassification.progression_skip_balancing if count else ItemClassification.filler
|
||||
|
||||
if name == "Windmill Shuriken" and getattr(self, "multiworld", None) is not None:
|
||||
if name == "Windmill Shuriken":
|
||||
return ItemClassification.progression if self.options.logic_level else ItemClassification.filler
|
||||
|
||||
if name == "Power Seal":
|
||||
@@ -344,7 +396,7 @@ class MessengerWorld(World):
|
||||
|
||||
if name in {*USEFUL_ITEMS, *USEFUL_SHOP_ITEMS}:
|
||||
return ItemClassification.useful
|
||||
|
||||
|
||||
if name in TRAPS:
|
||||
return ItemClassification.trap
|
||||
|
||||
@@ -354,7 +406,7 @@ class MessengerWorld(World):
|
||||
def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: set[int]) -> World:
|
||||
group = super().create_group(multiworld, new_player_id, players)
|
||||
assert isinstance(group, MessengerWorld)
|
||||
|
||||
|
||||
group.filler = FILLER.copy()
|
||||
group.options.traps.value = all(multiworld.worlds[player].options.traps for player in players)
|
||||
if group.options.traps:
|
||||
|
||||
Reference in New Issue
Block a user