The Messenger: Add Mega Time Shards and Quest 1 boss locations (#1661)

* implement mega shards

* create the option and locations, add to slot data and tests

* add boss refights as locations

* remove barma'thazel. it's apparently impossible to get to him

* remove barma'thazel again

* up max shard count to 85

* increment version

* dynamically alter the power seal pool

* revert host.yaml change

* two mega shards were missing from the maps

* add new checks to the info page

* add some more rules to skylands

* forgot to update my tests

* explicit imports, remove unnecessary typing, lower required client ver

* use generators for shard and seal creation
This commit is contained in:
alwaysintreble 2023-04-06 03:48:30 -05:00 committed by GitHub
parent c626618221
commit eef8f7af1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 169 additions and 49 deletions

View File

@ -1,5 +1,6 @@
# items
# listing individual groups first for easy lookup
NOTES = [
"Key of Hope",
"Key of Chaos",
@ -151,3 +152,10 @@ SEALS = [
"Elemental Skylands Seal - Water",
"Elemental Skylands Seal - Fire",
]
BOSS_LOCATIONS = [
"Leaf Golem",
"Ruxxtin",
"Emerald Golem",
"Queen of Quills",
]

View File

@ -1,4 +1,4 @@
from Options import DefaultOnToggle, DeathLink, Range, Accessibility, Choice
from Options import DefaultOnToggle, DeathLink, Range, Accessibility, Choice, Toggle
class MessengerAccessibility(Accessibility):
@ -27,6 +27,11 @@ class PowerSeals(DefaultOnToggle):
display_name = "Shuffle Seals"
class MegaShards(Toggle):
"""Whether mega shards should be item locations."""
display_name = "Shuffle Mega Time Shards"
class Goal(Choice):
"""Requirement to finish the game. Power Seal Hunt will force power seal locations to be shuffled."""
display_name = "Goal"
@ -51,8 +56,8 @@ class AmountSeals(Range):
"""Number of power seals that exist in the item pool when power seal hunt is the goal."""
display_name = "Total Power Seals"
range_start = 1
range_end = 45
default = range_end
range_end = 85
default = 45
class RequiredSeals(Range):
@ -67,6 +72,7 @@ messenger_options = {
"accessibility": MessengerAccessibility,
"logic_level": Logic,
"shuffle_seals": PowerSeals,
"shuffle_shards": MegaShards,
"goal": Goal,
"music_box": MusicBox,
"notes_needed": NotesNeeded,

View File

@ -6,16 +6,17 @@ REGIONS: Dict[str, List[str]] = {
"The Shop": [],
"Tower of Time": [],
"Ninja Village": ["Candle", "Astral Seed"],
"Autumn Hills": ["Climbing Claws", "Key of Hope"],
"Autumn Hills": ["Climbing Claws", "Key of Hope", "Leaf Golem"],
"Forlorn Temple": ["Demon King Crown"],
"Catacombs": ["Necro", "Ruxxtin's Amulet"],
"Catacombs": ["Necro", "Ruxxtin's Amulet", "Ruxxtin"],
"Bamboo Creek": ["Claustro"],
"Howling Grotto": ["Wingsuit"],
"Quillshroom Marsh": ["Seashell"],
"Howling Grotto": ["Wingsuit", "Emerald Golem"],
"Quillshroom Marsh": ["Seashell", "Queen of Quills"],
"Searing Crags": ["Rope Dart"],
"Searing Crags Upper": ["Power Thistle", "Key of Strength", "Astral Tea Leaves"],
"Glacial Peak": [],
"Cloud Ruins": ["Acro"],
"Cloud Ruins": [],
"Cloud Ruins Right": ["Acro"],
"Underworld": ["Pyro", "Key of Chaos"],
"Dark Cave": [],
"Riviere Turquoise": ["Fairy Bottle"],
@ -26,6 +27,24 @@ REGIONS: Dict[str, List[str]] = {
}
"""seal locations have the region in their name and may not need to be created so skip them here"""
MEGA_SHARDS: Dict[str, List[str]] = {
"Autumn Hills": ["Autumn Hills Mega Shard", "Hidden Entrance Mega Shard"],
"Catacombs": ["Catacombs Mega Shard"],
"Bamboo Creek": ["Above Entrance Mega Shard", "Abandoned Mega Shard", "Time Loop Mega Shard"],
"Howling Grotto": ["Bottom Left Mega Shard", "Near Portal Mega Shard", "Pie in the Sky Mega Shard"],
"Quillshroom Marsh": ["Quillshroom Marsh Mega Shard"],
"Searing Crags Upper": ["Searing Crags Mega Shard"],
"Glacial Peak": ["Glacial Peak Mega Shard"],
"Tower of Time": [],
"Cloud Ruins": ["Cloud Entrance Mega Shard", "Time Warp Mega Shard"],
"Cloud Ruins Right": ["Money Farm Room Mega Shard 1", "Money Farm Room Mega Shard 2"],
"Underworld": ["Under Entrance Mega Shard", "Hot Tub Mega Shard", "Projectile Pit Mega Shard"],
"Forlorn Temple": ["Sunny Day Mega Shard", "Down Under Mega Shard"],
"Sunken Shrine": ["Mega Shard of the Moon", "Beginner's Mega Shard", "Mega Shard of the Stars", "Mega Shard of the Sun"],
"Riviere Turquoise": ["Waterfall Mega Shard", "Quick Restock Mega Shard 1", "Quick Restock Mega Shard 2"],
"Elemental Skylands": ["Earth Mega Shard", "Water Mega Shard"],
}
REGION_CONNECTIONS: Dict[str, Set[str]] = {
"Menu": {"Tower HQ"},
@ -42,7 +61,8 @@ REGION_CONNECTIONS: Dict[str, Set[str]] = {
"Searing Crags": {"Searing Crags Upper", "Quillshroom Marsh", "Underworld"},
"Searing Crags Upper": {"Searing Crags", "Glacial Peak"},
"Glacial Peak": {"Searing Crags Upper", "Tower HQ", "Cloud Ruins", "Elemental Skylands"},
"Cloud Ruins": {"Underworld"},
"Cloud Ruins": {"Cloud Ruins Right"},
"Cloud Ruins Right": {"Underworld"},
"Underworld": set(),
"Dark Cave": {"Catacombs", "Riviere Turquoise"},
"Riviere Turquoise": set(),

View File

@ -27,7 +27,8 @@ class MessengerRules:
"Catacombs": self.has_wingsuit,
"Bamboo Creek": self.has_wingsuit,
"Searing Crags Upper": self.has_vertical,
"Cloud Ruins": lambda state: self.has_wingsuit(state) and state.has("Ruxxtin's Amulet", self.player),
"Cloud Ruins": lambda state: self.has_vertical(state) and state.has("Ruxxtin's Amulet", self.player),
"Cloud Ruins Right": self.has_wingsuit,
"Underworld": self.has_tabi,
"Forlorn Temple": lambda state: state.has_all({"Wingsuit", *PHOBEKINS}, self.player),
"Glacial Peak": self.has_vertical,
@ -43,6 +44,7 @@ class MessengerRules:
# howling grotto
"Howling Grotto Seal - Windy Saws and Balls": self.has_wingsuit,
"Howling Grotto Seal - Crushing Pits": lambda state: self.has_wingsuit(state) and self.has_dart(state),
"Emerald Golem": self.has_wingsuit,
# searing crags
"Astral Tea Leaves": lambda state: state.can_reach("Astral Seed", "Location", self.player),
"Key of Strength": lambda state: state.has("Power Thistle", self.player),
@ -64,14 +66,20 @@ class MessengerRules:
"Key of Love": lambda state: state.has_all({"Sun Crest", "Moon Crest"}, self.player),
"Sunken Shrine Seal - Waterfall Paradise": self.has_tabi,
"Sunken Shrine Seal - Tabi Gauntlet": self.has_tabi,
"Mega Shard of the Moon": self.has_tabi,
"Mega Shard of the Sun": self.has_tabi,
# riviere turquoise
"Fairy Bottle": self.has_vertical,
"Riviere Turquoise Seal - Flower Power": self.has_vertical,
"Quick Restock Mega Shard 1": self.has_vertical,
"Quick Restock Mega Shard 2": self.has_vertical,
# elemental skylands
"Key of Symbiosis": self.has_dart,
"Elemental Skylands Seal - Air": self.has_wingsuit,
"Elemental Skylands Seal - Water": self.has_dart,
"Elemental Skylands Seal - Fire": self.has_dart,
"Earth Mega Shard": self.has_dart,
"Water Mega Shard": self.has_dart,
# corrupted future
"Key of Courage": lambda state: state.has_all({"Demon King Crown", "Fairy Bottle"}, self.player),
# the shop
@ -130,12 +138,17 @@ class MessengerHardRules(MessengerRules):
"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,
"Elemental Skylands": lambda state: state.has("Fairy Bottle", self.player) or self.has_windmill(state),
})
self.location_rules.update({
"Howling Grotto Seal - Windy Saws and Balls": self.true,
"Glacial Peak Seal - Projectile Spike Pit": self.true,
"Claustro": self.has_wingsuit,
"Elemental Skylands Seal - Water": self.true,
"Elemental Skylands Seal - Fire": self.true,
"Earth Mega Shard": self.true,
"Water Mega Shard": self.true,
})
self.extra_rules = {
@ -156,6 +169,8 @@ class MessengerHardRules(MessengerRules):
for loc, rule in self.extra_rules.items():
if not self.world.multiworld.shuffle_seals[self.player] and "Seal" in loc:
continue
if not self.world.multiworld.shuffle_shards[self.player] and "Shard" in loc:
continue
add_rule(self.world.multiworld.get_location(loc, self.player), rule, "or")
@ -166,7 +181,8 @@ class MessengerChallengeRules(MessengerHardRules):
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),
"Elemental Skylands": lambda state: self.has_wingsuit(state) or state.has("Fairy Bottle", self.player)
or self.has_windmill(state),
})
self.location_rules.update({
@ -220,6 +236,6 @@ def set_self_locking_items(multiworld: MultiWorld, player: int) -> None:
allow_self_locking_items(multiworld.get_location("Key of Courage", player), "Demon King Crown")
# add these locations when seals aren't shuffled
if not multiworld.shuffle_seals[player]:
allow_self_locking_items(multiworld.get_region("Cloud Ruins", player), "Ruxxtin's Amulet")
if not multiworld.shuffle_seals[player] and not multiworld.shuffle_shards[player]:
allow_self_locking_items(multiworld.get_region("Cloud Ruins Right", player), "Ruxxtin's Amulet")
allow_self_locking_items(multiworld.get_region("Forlorn Temple", player), *PHOBEKINS)

View File

@ -3,7 +3,7 @@ from typing import Set, TYPE_CHECKING, Optional, Dict
from BaseClasses import Region, Location, Item, ItemClassification, Entrance
from .Constants import SEALS, NOTES, PROG_ITEMS, PHOBEKINS, USEFUL_ITEMS
from .Options import Goal
from .Regions import REGIONS
from .Regions import REGIONS, MEGA_SHARDS
if TYPE_CHECKING:
from . import MessengerWorld
@ -23,10 +23,12 @@ class MessengerRegion(Region):
if self.name == "The Shop" and self.multiworld.goal[self.player] > Goal.option_open_music_box:
self.locations.append(MessengerLocation("Shop Chest", self, name_to_id.get("Shop Chest", None)))
# putting some dumb special case for searing crags and ToT so i can split them into 2 regions
if self.multiworld.shuffle_seals[self.player] and self.name not in {"Searing Crags", "Tower HQ"}:
for seal_loc in SEALS:
if seal_loc.startswith(self.name.split(" ")[0]):
self.locations.append(MessengerLocation(seal_loc, self, name_to_id.get(seal_loc, None)))
if self.multiworld.shuffle_seals[self.player] and self.name not in {"Searing Crags", "Tower HQ", "Cloud Ruins"}:
self.locations += [MessengerLocation(seal_loc, self, name_to_id.get(seal_loc, None))
for seal_loc in SEALS if seal_loc.startswith(self.name.split(" ")[0])]
if self.multiworld.shuffle_shards[self.player] and self.name in MEGA_SHARDS:
self.locations += [MessengerLocation(shard, self, name_to_id.get(shard, None))
for shard in MEGA_SHARDS[self.name]]
def add_exits(self, exits: Set[str]) -> None:
for exit in exits:

View File

@ -1,10 +1,11 @@
from typing import Dict, Any, List, Optional
import logging
from typing import Dict, Any, Optional, List
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 .Constants import NOTES, PHOBEKINS, ALL_ITEMS, ALWAYS_LOCATIONS, SEALS, BOSS_LOCATIONS
from .Options import messenger_options, NotesNeeded, Goal, PowerSeals, Logic
from .Regions import REGIONS, REGION_CONNECTIONS
from .Regions import REGIONS, REGION_CONNECTIONS, MEGA_SHARDS
from .SubClasses import MessengerRegion, MessengerItem
from . import Rules
@ -48,21 +49,28 @@ class MessengerWorld(World):
base_offset = 0xADD_000
item_name_to_id = {item: item_id
for item_id, item in enumerate(ALL_ITEMS, base_offset)}
mega_shard_locs = [shard for region in MEGA_SHARDS for shard in MEGA_SHARDS[region]]
location_name_to_id = {location: location_id
for location_id, location in enumerate([*ALWAYS_LOCATIONS, *SEALS], base_offset)}
for location_id, location in
enumerate([
*ALWAYS_LOCATIONS,
*SEALS,
*mega_shard_locs,
*BOSS_LOCATIONS,
], base_offset)}
data_version = 1
data_version = 2
required_client_version = (0, 3, 9)
web = MessengerWeb()
total_seals: Optional[int] = None
required_seals: Optional[int] = None
total_seals: int = 0
required_seals: int = 0
def generate_early(self) -> None:
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
self.multiworld.shuffle_seals[self.player].value = PowerSeals.option_true
self.total_seals = self.multiworld.total_seals[self.player].value
self.required_seals = int(self.multiworld.percent_seals_required[self.player].value / 100 * self.total_seals)
def create_regions(self) -> None:
for region in [MessengerRegion(reg_name, self) for reg_name in REGIONS]:
@ -71,12 +79,7 @@ class MessengerWorld(World):
def create_items(self) -> None:
itempool: List[MessengerItem] = []
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
seals = [self.create_item("Power Seal") for _ in range(self.total_seals)]
for i in range(self.required_seals):
seals[i].classification = ItemClassification.progression_skip_balancing
itempool += seals
else:
if self.multiworld.goal[self.player] == Goal.option_open_music_box:
notes = self.multiworld.random.sample(NOTES, k=len(NOTES))
precollected_notes_amount = NotesNeeded.range_end - self.multiworld.notes_needed[self.player]
if precollected_notes_amount:
@ -94,6 +97,20 @@ class MessengerWorld(World):
# if we get in a position where this can have duplicates of items that aren't Power Seals
# or Time shards, this will need to be redone.
}]
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
total_seals = min(len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool),
self.multiworld.total_seals[self.player].value)
if total_seals < self.total_seals:
logging.warning(f"Not enough locations for total seals setting. Adjusting to {total_seals}")
self.total_seals = total_seals
self.required_seals = int(self.multiworld.percent_seals_required[self.player].value / 100 * self.total_seals)
seals = [self.create_item("Power Seal") for _ in range(self.total_seals)]
for i in range(self.required_seals):
seals[i].classification = ItemClassification.progression_skip_balancing
itempool += seals
itempool += [self.create_filler()
for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool))]
@ -122,7 +139,10 @@ 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",
"Mega Shards": self.multiworld.shuffle_shards[self.player].value
},
"logic": self.multiworld.logic_level[self.player].current_key,
}

View File

@ -32,7 +32,9 @@ You can find items wherever items can be picked up in the original game. This in
* Quest Item pickups
* Music Box notes
* Phobekins
* Bosses
* Power seals
* Mega Time Shards
## What are the item name groups?
@ -64,8 +66,6 @@ for it. The groups you can use for The Messenger are:
* Ruxxtin Coffin cutscene will sometimes not play correctly, but will still reward the item
* If you receive the Fairy Bottle while in Quillshroom Marsh, The De-curse Queen cutscene will not play. You can exit
to Searing Crags and re-enter to get it to play correctly.
* If you defeat Barma'thazël, the cutscene afterward will not play correctly since that is what normally transitions
you to 2nd quest. The game will not kill you if you fall here, so you can teleport to HQ at any point after defeating him.
* Sometimes upon teleporting back to HQ, Ninja will run left and enter a different portal than the one entered by the
player. This may also cause a softlock.
* Text entry menus don't accept controller input

View File

@ -3,12 +3,17 @@ from ..Constants import NOTES, PHOBEKINS
class AccessTest(MessengerTestBase):
options = {
"shuffle_shards": "true",
}
def testTabi(self) -> None:
"""locations that hard require the Ninja Tabi"""
locations = ["Pyro", "Key of Chaos", "Underworld Seal - Sharp and Windy Climb", "Underworld Seal - Spike Wall",
"Underworld Seal - Fireball Wave", "Underworld Seal - Rising Fanta", "Sun Crest", "Moon Crest",
"Sunken Shrine Seal - Waterfall Paradise", "Sunken Shrine Seal - Tabi Gauntlet"]
"Sunken Shrine Seal - Waterfall Paradise", "Sunken Shrine Seal - Tabi Gauntlet",
"Mega Shard of the Moon", "Mega Shard of the Sun", "Under Entrance Mega Shard",
"Hot Tub Mega Shard", "Projectile Pit Mega Shard"]
items = [["Ninja Tabi"]]
self.assertAccessDependency(locations, items)
@ -17,7 +22,8 @@ class AccessTest(MessengerTestBase):
locations = ["Ninja Village Seal - Tree House", "Key of Hope", "Howling Grotto Seal - Crushing Pits",
"Glacial Peak Seal - Ice Climbers", "Tower of Time Seal - Time Waster Seal",
"Tower of Time Seal - Arcane Orbs", "Underworld Seal - Rising Fanta", "Key of Symbiosis",
"Elemental Skylands Seal - Water", "Elemental Skylands Seal - Fire"]
"Elemental Skylands Seal - Water", "Elemental Skylands Seal - Fire", "Earth Mega Shard",
"Water Mega Shard"]
items = [["Rope Dart"]]
self.assertAccessDependency(locations, items)
@ -35,7 +41,11 @@ class AccessTest(MessengerTestBase):
"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", "Astral Seed", "Astral Tea Leaves"]
"Forlorn Temple Seal - Rocket Sunset", "Astral Seed", "Astral Tea Leaves",
"Autumn Hills Mega Shard", "Hidden Entrance Mega Shard", "Sunny Day Mega Shard",
"Down Under Mega Shard", "Catacombs Mega Shard", "Above Entrance Mega Shard",
"Abandoned Mega Shard", "Time Loop Mega Shard", "Money Farm Room Mega Shard 1",
"Money Farm Room Mega Shard 2", "Leaf Golem", "Ruxxtin", "Emerald Golem"]
items = [["Wingsuit"]]
self.assertAccessDependency(locations, items)
@ -56,18 +66,26 @@ class AccessTest(MessengerTestBase):
"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", "Rescue Phantom"]
"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", "Rescue Phantom", "Autumn Hills Mega Shard", "Hidden Entrance Mega Shard",
"Sunny Day Mega Shard", "Down Under Mega Shard", "Catacombs Mega Shard",
"Above Entrance Mega Shard", "Abandoned Mega Shard", "Time Loop Mega Shard",
"Searing Crags Mega Shard", "Glacial Peak Mega Shard", "Cloud Entrance Mega Shard",
"Time Warp Mega Shard", "Money Farm Room Mega Shard 1", "Money Farm Room Mega Shard 2",
"Quick Restock Mega Shard 1", "Quick Restock Mega Shard 2", "Earth Mega Shard", "Water Mega Shard",
"Leaf Golem", "Ruxxtin", "Emerald Golem"]
items = [["Wingsuit", "Rope Dart"]]
self.assertAccessDependency(locations, items)
def testAmulet(self) -> None:
"""Locations that require Ruxxtin's Amulet"""
locations = ["Acro", "Cloud Ruins Seal - Ghost Pit", "Cloud Ruins Seal - Toothbrush Alley",
"Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room"]
"Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room", "Cloud Entrance Mega Shard",
"Time Warp Mega Shard", "Money Farm Room Mega Shard 1", "Money Farm Room Mega Shard 2"]
# Cloud Ruins requires Ruxxtin's Amulet
items = [["Ruxxtin's Amulet"]]
self.assertAccessDependency(locations, items)
@ -75,7 +93,7 @@ class AccessTest(MessengerTestBase):
def testBottle(self) -> None:
"""Elemental Skylands and Corrupted Future require the Fairy Bottle"""
locations = ["Key of Symbiosis", "Elemental Skylands Seal - Air", "Elemental Skylands Seal - Fire",
"Elemental Skylands Seal - Water", "Key of Courage"]
"Elemental Skylands Seal - Water", "Key of Courage", "Earth Mega Shard", "Water Mega Shard"]
items = [["Fairy Bottle"]]
self.assertAccessDependency(locations, items)

View File

@ -16,21 +16,21 @@ class HardLogicTest(MessengerTestBase):
# ninja village
"Candle", "Astral Seed", "Ninja Village Seal - Tree House", "Astral Tea Leaves",
# autumn hills
"Climbing Claws", "Key of Hope",
"Climbing Claws", "Key of Hope", "Leaf Golem",
"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",
"Necro", "Ruxxtin's Amulet", "Ruxxtin",
"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",
"Emerald Golem", "Howling Grotto Seal - Crushing Pits", "Howling Grotto Seal - Crushing Pits",
# glacial peak
"Glacial Peak Seal - Ice Climbers",
# cloud ruins
@ -41,7 +41,7 @@ class HardLogicTest(MessengerTestBase):
# riviere turquoise
"Fairy Bottle", "Riviere Turquoise Seal - Flower Power",
# elemental skylands
"Elemental Skylands Seal - Air", "Elemental Skylands Seal - Water", "Elemental Skylands Seal - Fire",
"Elemental Skylands Seal - Air",
# phantom
"Rescue Phantom",
]

View File

@ -77,3 +77,33 @@ class ThirtyThirtySeals(MessengerTestBase):
required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing]
self.assertEqual(len(total_seals), 30)
self.assertEqual(len(required_seals), 10)
class MaxSealsNoShards(MessengerTestBase):
options = {
"goal": "power_seal_hunt",
"total_seals": 85,
}
def testSealsAmount(self) -> None:
"""Should set total seals to 57 since shards aren't shuffled."""
self.assertEqual(self.multiworld.total_seals[self.player], 85)
self.assertEqual(self.multiworld.worlds[self.player].total_seals, 57)
class MaxSealsWithShards(MessengerTestBase):
options = {
"goal": "power_seal_hunt",
"total_seals": 85,
"shuffle_shards": "true",
}
def testSealsAmount(self) -> None:
"""Should have 85 seals in the pool with all required and be a valid seed."""
self.assertEqual(self.multiworld.total_seals[self.player], 85)
self.assertEqual(self.multiworld.worlds[self.player].total_seals, 85)
self.assertEqual(self.multiworld.worlds[self.player].required_seals, 85)
total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"]
required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing]
self.assertEqual(len(total_seals), 85)
self.assertEqual(len(required_seals), 85)