The Messenger: Add more difficult logic options (#1550)
This commit is contained in:
parent
91502505a1
commit
1c69fb3c3c
|
@ -199,11 +199,15 @@ class WorldTestBase(unittest.TestCase):
|
|||
|
||||
self.collect_all_but(all_items)
|
||||
for location in self.multiworld.get_locations():
|
||||
self.assertEqual(self.multiworld.state.can_reach(location), location.name not in locations)
|
||||
loc_reachable = self.multiworld.state.can_reach(location)
|
||||
self.assertEqual(loc_reachable, location.name not in locations,
|
||||
f"{location.name} is reachable without {all_items}" if loc_reachable
|
||||
else f"{location.name} is not reachable without {all_items}")
|
||||
for item_names in possible_items:
|
||||
items = self.collect_by_name(item_names)
|
||||
for location in locations:
|
||||
self.assertTrue(self.can_reach_location(location))
|
||||
self.assertTrue(self.can_reach_location(location),
|
||||
f"{location} not reachable with {item_names}")
|
||||
self.remove(items)
|
||||
|
||||
def assertBeatable(self, beatable: bool):
|
||||
|
|
|
@ -7,9 +7,19 @@ class MessengerAccessibility(Accessibility):
|
|||
__doc__ = Accessibility.__doc__.replace(f"default {Accessibility.default}", f"default {default}")
|
||||
|
||||
|
||||
class Logic(DefaultOnToggle):
|
||||
"""Whether the seed should be guaranteed completable."""
|
||||
display_name = "Use Logic"
|
||||
class Logic(Choice):
|
||||
"""
|
||||
The level of logic to use when determining what locations in your world are accessible.
|
||||
Normal can require damage boosts, but otherwise approachable for someone who has beaten the game.
|
||||
Hard has some easier speedrunning tricks in logic. May need to leash.
|
||||
Challenging contains more medium and hard difficulty speedrunning tricks.
|
||||
OoB places everything with the minimum amount of rules possible. Expect to do OoB. Not guaranteed completable.
|
||||
"""
|
||||
display_name = "Logic Level"
|
||||
option_normal = 0
|
||||
option_hard = 1
|
||||
option_challenging = 2
|
||||
option_oob = 3
|
||||
|
||||
|
||||
class PowerSeals(DefaultOnToggle):
|
||||
|
@ -55,7 +65,7 @@ class RequiredSeals(Range):
|
|||
|
||||
messenger_options = {
|
||||
"accessibility": MessengerAccessibility,
|
||||
"enable_logic": Logic,
|
||||
"logic_level": Logic,
|
||||
"shuffle_seals": PowerSeals,
|
||||
"goal": Goal,
|
||||
"music_box": MusicBox,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Dict, Callable, TYPE_CHECKING
|
||||
|
||||
from BaseClasses import CollectionState, MultiWorld
|
||||
from worlds.generic.Rules import set_rule, allow_self_locking_items
|
||||
from worlds.generic.Rules import set_rule, allow_self_locking_items, add_rule
|
||||
from .Options import MessengerAccessibility, Goal
|
||||
from .Constants import NOTES, PHOBEKINS
|
||||
|
||||
|
@ -14,12 +14,14 @@ else:
|
|||
class MessengerRules:
|
||||
player: int
|
||||
world: MessengerWorld
|
||||
region_rules: Dict[str, Callable[[CollectionState], bool]]
|
||||
location_rules: Dict[str, Callable[[CollectionState], bool]]
|
||||
|
||||
def __init__(self, world: MessengerWorld):
|
||||
def __init__(self, world: MessengerWorld) -> None:
|
||||
self.player = world.player
|
||||
self.world = world
|
||||
|
||||
self.region_rules: Dict[str, Callable[[CollectionState], bool]] = {
|
||||
self.region_rules = {
|
||||
"Ninja Village": self.has_wingsuit,
|
||||
"Autumn Hills": self.has_wingsuit,
|
||||
"Catacombs": self.has_wingsuit,
|
||||
|
@ -27,13 +29,13 @@ class MessengerRules:
|
|||
"Searing Crags Upper": self.has_vertical,
|
||||
"Cloud Ruins": lambda state: self.has_wingsuit(state) and state.has("Ruxxtin's Amulet", self.player),
|
||||
"Underworld": self.has_tabi,
|
||||
"Forlorn Temple": lambda state: state.has_all(PHOBEKINS, self.player) and self.has_wingsuit(state),
|
||||
"Forlorn Temple": lambda state: state.has_all({"Wingsuit", *PHOBEKINS}, self.player),
|
||||
"Glacial Peak": self.has_vertical,
|
||||
"Elemental Skylands": lambda state: state.has("Fairy Bottle", self.player),
|
||||
"Music Box": lambda state: state.has_all(NOTES, self.player)
|
||||
"Music Box": lambda state: state.has_all(set(NOTES), self.player) and self.has_vertical(state)
|
||||
}
|
||||
|
||||
self.location_rules: Dict[str, Callable[[CollectionState], bool]] = {
|
||||
self.location_rules = {
|
||||
# ninja village
|
||||
"Ninja Village Seal - Tree House": self.has_dart,
|
||||
# autumn hills
|
||||
|
@ -88,8 +90,11 @@ class MessengerRules:
|
|||
return self.has_wingsuit(state) or self.has_dart(state)
|
||||
|
||||
def has_enough_seals(self, state: CollectionState) -> bool:
|
||||
required_seals = state.multiworld.worlds[self.player].required_seals
|
||||
return state.has("Power Seal", self.player, required_seals)
|
||||
return not self.world.required_seals or state.has("Power Seal", self.player, self.world.required_seals)
|
||||
|
||||
def true(self, state: CollectionState) -> bool:
|
||||
"""I know this is stupid, but it's easier to read in the dicts."""
|
||||
return True
|
||||
|
||||
def set_messenger_rules(self) -> None:
|
||||
multiworld = self.world.multiworld
|
||||
|
@ -105,14 +110,111 @@ class MessengerRules:
|
|||
set_rule(multiworld.get_entrance("Tower HQ -> Music Box", self.player),
|
||||
lambda state: state.has("Shop Chest", self.player))
|
||||
|
||||
if multiworld.enable_logic[self.player]:
|
||||
multiworld.completion_condition[self.player] = lambda state: state.has("Rescue Phantom", self.player)
|
||||
else:
|
||||
multiworld.accessibility[self.player].value = MessengerAccessibility.option_minimal
|
||||
multiworld.completion_condition[self.player] = lambda state: state.has("Rescue Phantom", self.player)
|
||||
if multiworld.accessibility[self.player] > MessengerAccessibility.option_locations:
|
||||
set_self_locking_items(multiworld, self.player)
|
||||
|
||||
|
||||
class MessengerHardRules(MessengerRules):
|
||||
extra_rules: Dict[str, Callable[[CollectionState], bool]]
|
||||
|
||||
def __init__(self, world: MessengerWorld) -> None:
|
||||
super().__init__(world)
|
||||
|
||||
self.region_rules.update({
|
||||
"Ninja Village": self.has_vertical,
|
||||
"Autumn Hills": self.has_vertical,
|
||||
"Catacombs": self.has_vertical,
|
||||
"Bamboo Creek": self.has_vertical,
|
||||
"Forlorn Temple": lambda state: self.has_vertical(state) and state.has_all(set(PHOBEKINS), self.player),
|
||||
"Searing Crags Upper": self.true,
|
||||
"Glacial Peak": self.true,
|
||||
})
|
||||
|
||||
self.location_rules.update({
|
||||
"Howling Grotto Seal - Windy Saws and Balls": self.true,
|
||||
"Glacial Peak Seal - Projectile Spike Pit": self.true,
|
||||
})
|
||||
|
||||
self.extra_rules = {
|
||||
"Climbing Claws": self.has_dart,
|
||||
"Astral Seed": self.has_dart,
|
||||
"Candle": self.has_dart,
|
||||
"Key of Strength": lambda state: state.has("Power Thistle", self.player) or
|
||||
self.has_dart(state) or
|
||||
self.has_windmill(state),
|
||||
"Key of Symbiosis": self.has_windmill,
|
||||
"Autumn Hills Seal - Spike Ball Darts": lambda state: (self.has_dart(state) and self.has_windmill(state))
|
||||
or self.has_wingsuit(state),
|
||||
"Glacial Peak Seal - Glacial Air Swag": self.has_windmill,
|
||||
"Underworld Seal - Fireball Wave": lambda state: self.has_wingsuit(state)
|
||||
or state.has_all({"Ninja Tabi", "Windmill Shuriken"},
|
||||
self.player),
|
||||
}
|
||||
|
||||
def has_windmill(self, state: CollectionState) -> bool:
|
||||
return state.has("Windmill Shuriken", self.player)
|
||||
|
||||
def set_messenger_rules(self) -> None:
|
||||
super().set_messenger_rules()
|
||||
for loc, rule in self.extra_rules.items():
|
||||
add_rule(self.world.multiworld.get_location(loc, self.player), rule, "or")
|
||||
|
||||
|
||||
class MessengerChallengeRules(MessengerHardRules):
|
||||
def __init__(self, world: MessengerWorld) -> None:
|
||||
super().__init__(world)
|
||||
|
||||
self.region_rules.update({
|
||||
"Forlorn Temple": lambda state: (self.has_vertical(state) and state.has_all(set(PHOBEKINS), self.player))
|
||||
or state.has_all({"Wingsuit", "Windmill Shuriken"}, self.player),
|
||||
"Elemental Skylands": lambda state: self.has_wingsuit(state) or state.has("Fairy Bottle", self.player)
|
||||
})
|
||||
|
||||
self.location_rules.update({
|
||||
"Fairy Bottle": self.true,
|
||||
"Howling Grotto Seal - Crushing Pits": self.true,
|
||||
"Underworld Seal - Sharp and Windy Climb": self.true,
|
||||
"Riviere Turquoise Seal - Flower Power": self.true,
|
||||
})
|
||||
|
||||
self.extra_rules.update({
|
||||
"Key of Hope": self.has_vertical,
|
||||
"Key of Symbiosis": lambda state: self.has_vertical(state) or self.has_windmill(state),
|
||||
})
|
||||
|
||||
|
||||
class MessengerOOBRules(MessengerRules):
|
||||
def __init__(self, world: MessengerWorld) -> None:
|
||||
self.world = world
|
||||
self.player = world.player
|
||||
|
||||
self.region_rules = {
|
||||
"Elemental Skylands": lambda state: state.has_any({"Wingsuit", "Rope Dart", "Fairy Bottle"}, self.player),
|
||||
"Music Box": lambda state: state.has_all(set(NOTES), self.player)
|
||||
}
|
||||
|
||||
self.location_rules = {
|
||||
"Claustro": self.has_wingsuit,
|
||||
"Key of Strength": self.has_wingsuit,
|
||||
"Key of Love": lambda state: state.has_all({"Sun Crest", "Moon Crest"}, self.player),
|
||||
"Pyro": self.has_tabi,
|
||||
"Key of Chaos": self.has_tabi,
|
||||
"Key of Courage": lambda state: state.has_all({"Demon King Crown", "Fairy Bottle"}, self.player),
|
||||
"Autumn Hills Seal - Spike Ball Darts": self.has_dart,
|
||||
"Ninja Village Seal - Tree House": self.has_dart,
|
||||
"Underworld Seal - Fireball Wave": lambda state: state.has_any({"Wingsuit", "Windmill Shuriken"},
|
||||
self.player),
|
||||
"Tower of Time Seal - Time Waster Seal": self.has_dart,
|
||||
"Shop Chest": self.has_enough_seals
|
||||
}
|
||||
|
||||
def set_messenger_rules(self) -> None:
|
||||
super().set_messenger_rules()
|
||||
self.world.multiworld.completion_condition[self.player] = lambda state: True
|
||||
self.world.multiworld.accessibility[self.player].value = MessengerAccessibility.option_minimal
|
||||
|
||||
|
||||
def set_self_locking_items(multiworld: MultiWorld, player: int) -> None:
|
||||
# do the ones for seal shuffle on and off first
|
||||
allow_self_locking_items(multiworld.get_location("Key of Strength", player), "Power Thistle")
|
||||
|
|
|
@ -12,7 +12,7 @@ else:
|
|||
|
||||
|
||||
class MessengerRegion(Region):
|
||||
def __init__(self, name: str, world: MessengerWorld):
|
||||
def __init__(self, name: str, world: MessengerWorld) -> None:
|
||||
super().__init__(name, world.player, world.multiworld)
|
||||
self.add_locations(self.multiworld.worlds[self.player].location_name_to_id)
|
||||
world.multiworld.regions.append(self)
|
||||
|
@ -38,7 +38,7 @@ class MessengerRegion(Region):
|
|||
class MessengerLocation(Location):
|
||||
game = "The Messenger"
|
||||
|
||||
def __init__(self, name: str, parent: MessengerRegion, loc_id: Optional[int]):
|
||||
def __init__(self, name: str, parent: MessengerRegion, loc_id: Optional[int]) -> None:
|
||||
super().__init__(parent.player, name, loc_id, parent)
|
||||
if loc_id is None:
|
||||
self.place_locked_item(MessengerItem(name, parent.player, None))
|
||||
|
@ -47,8 +47,8 @@ class MessengerLocation(Location):
|
|||
class MessengerItem(Item):
|
||||
game = "The Messenger"
|
||||
|
||||
def __init__(self, name: str, player: int, item_id: Optional[int] = None):
|
||||
if name in {*NOTES, *PROG_ITEMS, *PHOBEKINS} or item_id is None:
|
||||
def __init__(self, name: str, player: int, item_id: Optional[int] = None, override_progression: bool = False) -> None:
|
||||
if name in {*NOTES, *PROG_ITEMS, *PHOBEKINS} or item_id is None or override_progression:
|
||||
item_class = ItemClassification.progression
|
||||
elif name in USEFUL_ITEMS:
|
||||
item_class = ItemClassification.useful
|
||||
|
|
|
@ -3,10 +3,10 @@ from typing import Dict, Any, List, Optional
|
|||
from BaseClasses import Tutorial, ItemClassification
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Constants import NOTES, PROG_ITEMS, PHOBEKINS, USEFUL_ITEMS, ALWAYS_LOCATIONS, SEALS, ALL_ITEMS
|
||||
from .Options import messenger_options, NotesNeeded, Goal, PowerSeals
|
||||
from .Options import messenger_options, NotesNeeded, Goal, PowerSeals, Logic
|
||||
from .Regions import REGIONS, REGION_CONNECTIONS
|
||||
from .Rules import MessengerRules
|
||||
from .SubClasses import MessengerRegion, MessengerItem
|
||||
from . import Rules
|
||||
|
||||
|
||||
class MessengerWeb(WebWorld):
|
||||
|
@ -100,7 +100,15 @@ class MessengerWorld(World):
|
|||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self) -> None:
|
||||
MessengerRules(self).set_messenger_rules()
|
||||
logic = self.multiworld.logic_level[self.player]
|
||||
if logic == Logic.option_normal:
|
||||
Rules.MessengerRules(self).set_messenger_rules()
|
||||
elif logic == Logic.option_hard:
|
||||
Rules.MessengerHardRules(self).set_messenger_rules()
|
||||
elif logic == Logic.option_challenging:
|
||||
Rules.MessengerChallengeRules(self).set_messenger_rules()
|
||||
else:
|
||||
Rules.MessengerOOBRules(self).set_messenger_rules()
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
locations: Dict[int, List[str]] = {}
|
||||
|
@ -114,7 +122,8 @@ class MessengerWorld(World):
|
|||
"music_box": self.multiworld.music_box[self.player].value,
|
||||
"required_seals": self.required_seals,
|
||||
"locations": locations,
|
||||
"settings": {"Difficulty": "Basic" if not self.multiworld.shuffle_seals[self.player] else "Advanced"}
|
||||
"settings": {"Difficulty": "Basic" if not self.multiworld.shuffle_seals[self.player] else "Advanced"},
|
||||
"logic": self.multiworld.logic_level[self.player].current_key,
|
||||
}
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
|
@ -122,4 +131,6 @@ class MessengerWorld(World):
|
|||
|
||||
def create_item(self, name: str) -> MessengerItem:
|
||||
item_id: Optional[int] = self.item_name_to_id.get(name, None)
|
||||
return MessengerItem(name, self.player, item_id)
|
||||
override_prog = name in {"Windmill Shuriken"} and getattr(self, "multiworld") is not None \
|
||||
and self.multiworld.logic_level[self.player] > Logic.option_normal
|
||||
return MessengerItem(name, self.player, item_id, override_prog)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from . import MessengerTestBase
|
||||
from ..Constants import NOTES, PHOBEKINS
|
||||
from ..Options import MessengerAccessibility
|
||||
|
||||
|
||||
class AccessTest(MessengerTestBase):
|
||||
|
@ -46,22 +45,22 @@ class AccessTest(MessengerTestBase):
|
|||
"Glacial Peak Seal - Ice Climbers", "Tower of Time Seal - Time Waster Seal",
|
||||
"Underworld Seal - Rising Fanta", "Key of Symbiosis",
|
||||
"Elemental Skylands Seal - Water", "Elemental Skylands Seal - Fire", "Candle",
|
||||
"Ninja Village Seal - Tree House", "Climbing Claws", "Key of Hope",
|
||||
"Autumn Hills Seal - Trip Saws", "Autumn Hills Seal - Double Swing Saws",
|
||||
"Autumn Hills Seal - Spike Ball Swing", "Autumn Hills Seal - Spike Ball Darts", "Necro",
|
||||
"Ruxxtin's Amulet", "Catacombs Seal - Triple Spike Crushers", "Catacombs Seal - Crusher Gauntlet",
|
||||
"Climbing Claws", "Key of Hope", "Autumn Hills Seal - Trip Saws",
|
||||
"Autumn Hills Seal - Double Swing Saws", "Autumn Hills Seal - Spike Ball Swing",
|
||||
"Autumn Hills Seal - Spike Ball Darts", "Necro", "Ruxxtin's Amulet",
|
||||
"Catacombs Seal - Triple Spike Crushers", "Catacombs Seal - Crusher Gauntlet",
|
||||
"Catacombs Seal - Dirty Pond", "Claustro", "Acro", "Bamboo Creek Seal - Spike Crushers and Doors",
|
||||
"Bamboo Creek Seal - Spike Ball Pits", "Bamboo Creek Seal - Spike Crushers and Doors v2",
|
||||
"Howling Grotto Seal - Crushing Pits", "Howling Grotto Seal - Windy Saws and Balls",
|
||||
"Tower of Time Seal - Lantern Climb", "Demon King Crown", "Cloud Ruins Seal - Ghost Pit",
|
||||
"Cloud Ruins Seal - Toothbrush Alley", "Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room",
|
||||
"Demon King Crown", "Cloud Ruins Seal - Ghost Pit", "Cloud Ruins Seal - Toothbrush Alley",
|
||||
"Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room",
|
||||
"Tower of Time Seal - Lantern Climb", "Tower of Time Seal - Arcane Orbs",
|
||||
"Underworld Seal - Sharp and Windy Climb", "Underworld Seal - Fireball Wave",
|
||||
"Elemental Skylands Seal - Air", "Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset",
|
||||
"Power Thistle", "Key of Strength", "Glacial Peak Seal - Projectile Spike Pit",
|
||||
"Glacial Peak Seal - Glacial Air Swag", "Fairy Bottle", "Riviere Turquoise Seal - Flower Power",
|
||||
"Searing Crags Seal - Triple Ball Spinner", "Searing Crags Seal - Raining Rocks",
|
||||
"Searing Crags Seal - Rhythm Rocks", "Astral Seed", "Astral Tea Leaves"]
|
||||
"Searing Crags Seal - Rhythm Rocks", "Astral Seed", "Astral Tea Leaves", "Rescue Phantom"]
|
||||
items = [["Wingsuit", "Rope Dart"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
|
@ -116,8 +115,8 @@ class AccessTest(MessengerTestBase):
|
|||
|
||||
class ItemsAccessTest(MessengerTestBase):
|
||||
options = {
|
||||
"shuffle_seals": False,
|
||||
"accessibility": MessengerAccessibility.option_items
|
||||
"shuffle_seals": "false",
|
||||
"accessibility": "items"
|
||||
}
|
||||
|
||||
def testSelfLockingItems(self) -> None:
|
||||
|
@ -136,14 +135,3 @@ class ItemsAccessTest(MessengerTestBase):
|
|||
with self.subTest("Fulfills Accessibility", location=loc, item=item_name):
|
||||
self.assertTrue(self.multiworld.get_location(loc, self.player).can_fill(self.multiworld.state, item, True))
|
||||
|
||||
|
||||
class NoLogicTest(MessengerTestBase):
|
||||
options = {
|
||||
"enable_logic": "false"
|
||||
}
|
||||
|
||||
def testNoLogic(self) -> None:
|
||||
"""Test some funny locations to make sure they aren't reachable but we can still win"""
|
||||
self.assertEqual(self.can_reach_location("Pyro"), False)
|
||||
self.assertEqual(self.can_reach_location("Rescue Phantom"), False)
|
||||
self.assertBeatable(True)
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
from BaseClasses import ItemClassification
|
||||
from . import MessengerTestBase
|
||||
|
||||
|
||||
class HardLogicTest(MessengerTestBase):
|
||||
options = {
|
||||
"logic_level": "hard"
|
||||
}
|
||||
|
||||
def testVertical(self) -> None:
|
||||
"""Test the locations that still require wingsuit or rope dart."""
|
||||
locations = [
|
||||
# tower of time
|
||||
"Tower of Time Seal - Time Waster Seal", "Tower of Time Seal - Lantern Climb",
|
||||
"Tower of Time Seal - Arcane Orbs",
|
||||
# ninja village
|
||||
"Candle", "Astral Seed", "Ninja Village Seal - Tree House",
|
||||
# autumn hills
|
||||
"Climbing Claws", "Key of Hope",
|
||||
"Autumn Hills Seal - Trip Saws", "Autumn Hills Seal - Double Swing Saws",
|
||||
"Autumn Hills Seal - Spike Ball Swing", "Autumn Hills Seal - Spike Ball Darts",
|
||||
# forlorn temple
|
||||
"Demon King Crown",
|
||||
"Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset",
|
||||
# catacombs
|
||||
"Necro", "Ruxxtin's Amulet",
|
||||
"Catacombs Seal - Triple Spike Crushers", "Catacombs Seal - Crusher Gauntlet", "Catacombs Seal - Dirty Pond",
|
||||
# bamboo creek
|
||||
"Claustro",
|
||||
"Bamboo Creek Seal - Spike Crushers and Doors", "Bamboo Creek Seal - Spike Ball Pits",
|
||||
"Bamboo Creek Seal - Spike Crushers and Doors v2",
|
||||
# howling grotto
|
||||
"Howling Grotto Seal - Crushing Pits", "Howling Grotto Seal - Crushing Pits",
|
||||
# glacial peak
|
||||
"Glacial Peak Seal - Ice Climbers",
|
||||
# cloud ruins
|
||||
"Acro", "Cloud Ruins Seal - Ghost Pit",
|
||||
"Cloud Ruins Seal - Toothbrush Alley", "Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room",
|
||||
# underworld
|
||||
"Underworld Seal - Rising Fanta", "Underworld Seal - Sharp and Windy Climb",
|
||||
# riviere turquoise
|
||||
"Fairy Bottle", "Riviere Turquoise Seal - Flower Power",
|
||||
# elemental skylands
|
||||
"Elemental Skylands Seal - Air", "Elemental Skylands Seal - Water", "Elemental Skylands Seal - Fire",
|
||||
# phantom
|
||||
"Rescue Phantom",
|
||||
]
|
||||
items = [["Wingsuit", "Rope Dart"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
def testWindmill(self) -> None:
|
||||
"""Windmill Shuriken isn't progression on normal difficulty, so test it's marked correctly and required."""
|
||||
self.assertEqual(ItemClassification.progression, self.get_item_by_name("Windmill Shuriken").classification)
|
||||
windmill_locs = [
|
||||
"Key of Strength",
|
||||
"Key of Symbiosis",
|
||||
"Underworld Seal - Fireball Wave"
|
||||
]
|
||||
for loc in windmill_locs:
|
||||
with self.subTest("can't reach location with nothing", location=loc):
|
||||
self.assertFalse(self.can_reach_location(loc))
|
||||
|
||||
items = self.get_items_by_name(["Windmill Shuriken", "Ninja Tabi", "Fairy Bottle"])
|
||||
self.collect(items)
|
||||
for loc in windmill_locs:
|
||||
with self.subTest("can reach with Windmill", location=loc):
|
||||
self.assertTrue(self.can_reach_location(loc))
|
||||
|
||||
special_loc = "Autumn Hills Seal - Spike Ball Darts"
|
||||
item = self.get_item_by_name("Wingsuit")
|
||||
self.collect(item)
|
||||
self.assertTrue(self.can_reach_location(special_loc))
|
||||
self.remove(item)
|
||||
|
||||
item = self.get_item_by_name("Rope Dart")
|
||||
self.collect(item)
|
||||
self.assertTrue(self.can_reach_location(special_loc))
|
||||
|
||||
|
||||
class ChallengingLogicTest(MessengerTestBase):
|
||||
options = {
|
||||
"logic_level": "challenging"
|
||||
}
|
||||
|
||||
|
||||
class NoLogicTest(MessengerTestBase):
|
||||
options = {
|
||||
"logic_level": "oob"
|
||||
}
|
||||
|
||||
def testAccess(self) -> None:
|
||||
"""Test the locations with rules still require things."""
|
||||
all_locations = [
|
||||
"Claustro", "Key of Strength", "Key of Symbiosis", "Key of Love", "Pyro", "Key of Chaos", "Key of Courage",
|
||||
"Autumn Hills Seal - Spike Ball Darts", "Ninja Village Seal - Tree House", "Underworld Seal - Fireball Wave",
|
||||
"Tower of Time Seal - Time Waster Seal", "Rescue Phantom", "Elemental Skylands Seal - Air",
|
||||
"Elemental Skylands Seal - Water", "Elemental Skylands Seal - Fire",
|
||||
]
|
||||
for loc in all_locations:
|
||||
with self.subTest("Default unreachables", location=loc):
|
||||
self.assertFalse(self.can_reach_location(loc))
|
||||
|
||||
def testNoLogic(self) -> None:
|
||||
"""Test some funny locations to make sure they aren't reachable, but we can still win"""
|
||||
self.assertEqual(self.can_reach_location("Pyro"), False)
|
||||
self.assertEqual(self.can_reach_location("Rescue Phantom"), False)
|
||||
self.assertBeatable(True)
|
|
@ -27,4 +27,9 @@ class DefaultGoalTest(MessengerTestBase):
|
|||
def testGoal(self) -> None:
|
||||
self.assertBeatable(False)
|
||||
self.collect_by_name(NOTES)
|
||||
rope_dart = self.get_item_by_name("Rope Dart")
|
||||
self.collect(rope_dart)
|
||||
self.assertBeatable(True)
|
||||
self.remove(rope_dart)
|
||||
self.collect_by_name("Wingsuit")
|
||||
self.assertBeatable(True)
|
||||
|
|
|
@ -4,11 +4,11 @@ from . import MessengerTestBase
|
|||
|
||||
class NoLogicTest(MessengerTestBase):
|
||||
options = {
|
||||
"enable_logic": "false",
|
||||
"logic_level": "oob",
|
||||
"goal": "power_seal_hunt",
|
||||
}
|
||||
|
||||
def testChestAccess(self):
|
||||
def testChestAccess(self) -> None:
|
||||
"""Test to make sure we can win even though we can't reach the chest."""
|
||||
self.assertEqual(self.can_reach_location("Shop Chest"), False)
|
||||
self.assertBeatable(True)
|
||||
|
|
Loading…
Reference in New Issue