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:
parent
c626618221
commit
eef8f7af1a
|
@ -1,5 +1,6 @@
|
||||||
# items
|
# items
|
||||||
# listing individual groups first for easy lookup
|
# listing individual groups first for easy lookup
|
||||||
|
|
||||||
NOTES = [
|
NOTES = [
|
||||||
"Key of Hope",
|
"Key of Hope",
|
||||||
"Key of Chaos",
|
"Key of Chaos",
|
||||||
|
@ -151,3 +152,10 @@ SEALS = [
|
||||||
"Elemental Skylands Seal - Water",
|
"Elemental Skylands Seal - Water",
|
||||||
"Elemental Skylands Seal - Fire",
|
"Elemental Skylands Seal - Fire",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
BOSS_LOCATIONS = [
|
||||||
|
"Leaf Golem",
|
||||||
|
"Ruxxtin",
|
||||||
|
"Emerald Golem",
|
||||||
|
"Queen of Quills",
|
||||||
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from Options import DefaultOnToggle, DeathLink, Range, Accessibility, Choice
|
from Options import DefaultOnToggle, DeathLink, Range, Accessibility, Choice, Toggle
|
||||||
|
|
||||||
|
|
||||||
class MessengerAccessibility(Accessibility):
|
class MessengerAccessibility(Accessibility):
|
||||||
|
@ -27,6 +27,11 @@ class PowerSeals(DefaultOnToggle):
|
||||||
display_name = "Shuffle Seals"
|
display_name = "Shuffle Seals"
|
||||||
|
|
||||||
|
|
||||||
|
class MegaShards(Toggle):
|
||||||
|
"""Whether mega shards should be item locations."""
|
||||||
|
display_name = "Shuffle Mega Time Shards"
|
||||||
|
|
||||||
|
|
||||||
class Goal(Choice):
|
class Goal(Choice):
|
||||||
"""Requirement to finish the game. Power Seal Hunt will force power seal locations to be shuffled."""
|
"""Requirement to finish the game. Power Seal Hunt will force power seal locations to be shuffled."""
|
||||||
display_name = "Goal"
|
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."""
|
"""Number of power seals that exist in the item pool when power seal hunt is the goal."""
|
||||||
display_name = "Total Power Seals"
|
display_name = "Total Power Seals"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 45
|
range_end = 85
|
||||||
default = range_end
|
default = 45
|
||||||
|
|
||||||
|
|
||||||
class RequiredSeals(Range):
|
class RequiredSeals(Range):
|
||||||
|
@ -67,6 +72,7 @@ messenger_options = {
|
||||||
"accessibility": MessengerAccessibility,
|
"accessibility": MessengerAccessibility,
|
||||||
"logic_level": Logic,
|
"logic_level": Logic,
|
||||||
"shuffle_seals": PowerSeals,
|
"shuffle_seals": PowerSeals,
|
||||||
|
"shuffle_shards": MegaShards,
|
||||||
"goal": Goal,
|
"goal": Goal,
|
||||||
"music_box": MusicBox,
|
"music_box": MusicBox,
|
||||||
"notes_needed": NotesNeeded,
|
"notes_needed": NotesNeeded,
|
||||||
|
|
|
@ -6,16 +6,17 @@ REGIONS: Dict[str, List[str]] = {
|
||||||
"The Shop": [],
|
"The Shop": [],
|
||||||
"Tower of Time": [],
|
"Tower of Time": [],
|
||||||
"Ninja Village": ["Candle", "Astral Seed"],
|
"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"],
|
"Forlorn Temple": ["Demon King Crown"],
|
||||||
"Catacombs": ["Necro", "Ruxxtin's Amulet"],
|
"Catacombs": ["Necro", "Ruxxtin's Amulet", "Ruxxtin"],
|
||||||
"Bamboo Creek": ["Claustro"],
|
"Bamboo Creek": ["Claustro"],
|
||||||
"Howling Grotto": ["Wingsuit"],
|
"Howling Grotto": ["Wingsuit", "Emerald Golem"],
|
||||||
"Quillshroom Marsh": ["Seashell"],
|
"Quillshroom Marsh": ["Seashell", "Queen of Quills"],
|
||||||
"Searing Crags": ["Rope Dart"],
|
"Searing Crags": ["Rope Dart"],
|
||||||
"Searing Crags Upper": ["Power Thistle", "Key of Strength", "Astral Tea Leaves"],
|
"Searing Crags Upper": ["Power Thistle", "Key of Strength", "Astral Tea Leaves"],
|
||||||
"Glacial Peak": [],
|
"Glacial Peak": [],
|
||||||
"Cloud Ruins": ["Acro"],
|
"Cloud Ruins": [],
|
||||||
|
"Cloud Ruins Right": ["Acro"],
|
||||||
"Underworld": ["Pyro", "Key of Chaos"],
|
"Underworld": ["Pyro", "Key of Chaos"],
|
||||||
"Dark Cave": [],
|
"Dark Cave": [],
|
||||||
"Riviere Turquoise": ["Fairy Bottle"],
|
"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"""
|
"""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]] = {
|
REGION_CONNECTIONS: Dict[str, Set[str]] = {
|
||||||
"Menu": {"Tower HQ"},
|
"Menu": {"Tower HQ"},
|
||||||
|
@ -42,7 +61,8 @@ REGION_CONNECTIONS: Dict[str, Set[str]] = {
|
||||||
"Searing Crags": {"Searing Crags Upper", "Quillshroom Marsh", "Underworld"},
|
"Searing Crags": {"Searing Crags Upper", "Quillshroom Marsh", "Underworld"},
|
||||||
"Searing Crags Upper": {"Searing Crags", "Glacial Peak"},
|
"Searing Crags Upper": {"Searing Crags", "Glacial Peak"},
|
||||||
"Glacial Peak": {"Searing Crags Upper", "Tower HQ", "Cloud Ruins", "Elemental Skylands"},
|
"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(),
|
"Underworld": set(),
|
||||||
"Dark Cave": {"Catacombs", "Riviere Turquoise"},
|
"Dark Cave": {"Catacombs", "Riviere Turquoise"},
|
||||||
"Riviere Turquoise": set(),
|
"Riviere Turquoise": set(),
|
||||||
|
|
|
@ -27,7 +27,8 @@ class MessengerRules:
|
||||||
"Catacombs": self.has_wingsuit,
|
"Catacombs": self.has_wingsuit,
|
||||||
"Bamboo Creek": self.has_wingsuit,
|
"Bamboo Creek": self.has_wingsuit,
|
||||||
"Searing Crags Upper": self.has_vertical,
|
"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,
|
"Underworld": self.has_tabi,
|
||||||
"Forlorn Temple": lambda state: state.has_all({"Wingsuit", *PHOBEKINS}, self.player),
|
"Forlorn Temple": lambda state: state.has_all({"Wingsuit", *PHOBEKINS}, self.player),
|
||||||
"Glacial Peak": self.has_vertical,
|
"Glacial Peak": self.has_vertical,
|
||||||
|
@ -43,6 +44,7 @@ class MessengerRules:
|
||||||
# howling grotto
|
# howling grotto
|
||||||
"Howling Grotto Seal - Windy Saws and Balls": self.has_wingsuit,
|
"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),
|
"Howling Grotto Seal - Crushing Pits": lambda state: self.has_wingsuit(state) and self.has_dart(state),
|
||||||
|
"Emerald Golem": self.has_wingsuit,
|
||||||
# searing crags
|
# searing crags
|
||||||
"Astral Tea Leaves": lambda state: state.can_reach("Astral Seed", "Location", self.player),
|
"Astral Tea Leaves": lambda state: state.can_reach("Astral Seed", "Location", self.player),
|
||||||
"Key of Strength": lambda state: state.has("Power Thistle", 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),
|
"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 - Waterfall Paradise": self.has_tabi,
|
||||||
"Sunken Shrine Seal - Tabi Gauntlet": 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
|
# riviere turquoise
|
||||||
"Fairy Bottle": self.has_vertical,
|
"Fairy Bottle": self.has_vertical,
|
||||||
"Riviere Turquoise Seal - Flower Power": 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
|
# elemental skylands
|
||||||
"Key of Symbiosis": self.has_dart,
|
"Key of Symbiosis": self.has_dart,
|
||||||
"Elemental Skylands Seal - Air": self.has_wingsuit,
|
"Elemental Skylands Seal - Air": self.has_wingsuit,
|
||||||
"Elemental Skylands Seal - Water": self.has_dart,
|
"Elemental Skylands Seal - Water": self.has_dart,
|
||||||
"Elemental Skylands Seal - Fire": self.has_dart,
|
"Elemental Skylands Seal - Fire": self.has_dart,
|
||||||
|
"Earth Mega Shard": self.has_dart,
|
||||||
|
"Water Mega Shard": self.has_dart,
|
||||||
# corrupted future
|
# corrupted future
|
||||||
"Key of Courage": lambda state: state.has_all({"Demon King Crown", "Fairy Bottle"}, self.player),
|
"Key of Courage": lambda state: state.has_all({"Demon King Crown", "Fairy Bottle"}, self.player),
|
||||||
# the shop
|
# 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),
|
"Forlorn Temple": lambda state: self.has_vertical(state) and state.has_all(set(PHOBEKINS), self.player),
|
||||||
"Searing Crags Upper": self.true,
|
"Searing Crags Upper": self.true,
|
||||||
"Glacial Peak": 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({
|
self.location_rules.update({
|
||||||
"Howling Grotto Seal - Windy Saws and Balls": self.true,
|
"Howling Grotto Seal - Windy Saws and Balls": self.true,
|
||||||
"Glacial Peak Seal - Projectile Spike Pit": self.true,
|
"Glacial Peak Seal - Projectile Spike Pit": self.true,
|
||||||
"Claustro": self.has_wingsuit,
|
"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 = {
|
self.extra_rules = {
|
||||||
|
@ -156,6 +169,8 @@ class MessengerHardRules(MessengerRules):
|
||||||
for loc, rule in self.extra_rules.items():
|
for loc, rule in self.extra_rules.items():
|
||||||
if not self.world.multiworld.shuffle_seals[self.player] and "Seal" in loc:
|
if not self.world.multiworld.shuffle_seals[self.player] and "Seal" in loc:
|
||||||
continue
|
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")
|
add_rule(self.world.multiworld.get_location(loc, self.player), rule, "or")
|
||||||
|
|
||||||
|
|
||||||
|
@ -166,7 +181,8 @@ class MessengerChallengeRules(MessengerHardRules):
|
||||||
self.region_rules.update({
|
self.region_rules.update({
|
||||||
"Forlorn Temple": lambda state: (self.has_vertical(state) and state.has_all(set(PHOBEKINS), self.player))
|
"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),
|
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({
|
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")
|
allow_self_locking_items(multiworld.get_location("Key of Courage", player), "Demon King Crown")
|
||||||
|
|
||||||
# add these locations when seals aren't shuffled
|
# add these locations when seals aren't shuffled
|
||||||
if not multiworld.shuffle_seals[player]:
|
if not multiworld.shuffle_seals[player] and not multiworld.shuffle_shards[player]:
|
||||||
allow_self_locking_items(multiworld.get_region("Cloud Ruins", player), "Ruxxtin's Amulet")
|
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)
|
allow_self_locking_items(multiworld.get_region("Forlorn Temple", player), *PHOBEKINS)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import Set, TYPE_CHECKING, Optional, Dict
|
||||||
from BaseClasses import Region, Location, Item, ItemClassification, Entrance
|
from BaseClasses import Region, Location, Item, ItemClassification, Entrance
|
||||||
from .Constants import SEALS, NOTES, PROG_ITEMS, PHOBEKINS, USEFUL_ITEMS
|
from .Constants import SEALS, NOTES, PROG_ITEMS, PHOBEKINS, USEFUL_ITEMS
|
||||||
from .Options import Goal
|
from .Options import Goal
|
||||||
from .Regions import REGIONS
|
from .Regions import REGIONS, MEGA_SHARDS
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import MessengerWorld
|
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:
|
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)))
|
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
|
# 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"}:
|
if self.multiworld.shuffle_seals[self.player] and self.name not in {"Searing Crags", "Tower HQ", "Cloud Ruins"}:
|
||||||
for seal_loc in SEALS:
|
self.locations += [MessengerLocation(seal_loc, self, name_to_id.get(seal_loc, None))
|
||||||
if seal_loc.startswith(self.name.split(" ")[0]):
|
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_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:
|
def add_exits(self, exits: Set[str]) -> None:
|
||||||
for exit in exits:
|
for exit in exits:
|
||||||
|
|
|
@ -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 BaseClasses import Tutorial, ItemClassification
|
||||||
from worlds.AutoWorld import World, WebWorld
|
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 .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 .SubClasses import MessengerRegion, MessengerItem
|
||||||
from . import Rules
|
from . import Rules
|
||||||
|
|
||||||
|
@ -48,21 +49,28 @@ class MessengerWorld(World):
|
||||||
base_offset = 0xADD_000
|
base_offset = 0xADD_000
|
||||||
item_name_to_id = {item: item_id
|
item_name_to_id = {item: item_id
|
||||||
for item_id, item in enumerate(ALL_ITEMS, base_offset)}
|
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
|
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()
|
web = MessengerWeb()
|
||||||
|
|
||||||
total_seals: Optional[int] = None
|
total_seals: int = 0
|
||||||
required_seals: Optional[int] = None
|
required_seals: int = 0
|
||||||
|
|
||||||
def generate_early(self) -> None:
|
def generate_early(self) -> None:
|
||||||
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
||||||
self.multiworld.shuffle_seals[self.player].value = PowerSeals.option_true
|
self.multiworld.shuffle_seals[self.player].value = PowerSeals.option_true
|
||||||
self.total_seals = self.multiworld.total_seals[self.player].value
|
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:
|
def create_regions(self) -> None:
|
||||||
for region in [MessengerRegion(reg_name, self) for reg_name in REGIONS]:
|
for region in [MessengerRegion(reg_name, self) for reg_name in REGIONS]:
|
||||||
|
@ -71,12 +79,7 @@ class MessengerWorld(World):
|
||||||
|
|
||||||
def create_items(self) -> None:
|
def create_items(self) -> None:
|
||||||
itempool: List[MessengerItem] = []
|
itempool: List[MessengerItem] = []
|
||||||
if self.multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
if self.multiworld.goal[self.player] == Goal.option_open_music_box:
|
||||||
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:
|
|
||||||
notes = self.multiworld.random.sample(NOTES, k=len(NOTES))
|
notes = self.multiworld.random.sample(NOTES, k=len(NOTES))
|
||||||
precollected_notes_amount = NotesNeeded.range_end - self.multiworld.notes_needed[self.player]
|
precollected_notes_amount = NotesNeeded.range_end - self.multiworld.notes_needed[self.player]
|
||||||
if precollected_notes_amount:
|
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
|
# 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.
|
# 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()
|
itempool += [self.create_filler()
|
||||||
for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool))]
|
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,
|
"music_box": self.multiworld.music_box[self.player].value,
|
||||||
"required_seals": self.required_seals,
|
"required_seals": self.required_seals,
|
||||||
"locations": locations,
|
"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,
|
"logic": self.multiworld.logic_level[self.player].current_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,9 @@ You can find items wherever items can be picked up in the original game. This in
|
||||||
* Quest Item pickups
|
* Quest Item pickups
|
||||||
* Music Box notes
|
* Music Box notes
|
||||||
* Phobekins
|
* Phobekins
|
||||||
|
* Bosses
|
||||||
* Power seals
|
* Power seals
|
||||||
|
* Mega Time Shards
|
||||||
|
|
||||||
## What are the item name groups?
|
## 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
|
* 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
|
* 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.
|
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
|
* 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.
|
player. This may also cause a softlock.
|
||||||
* Text entry menus don't accept controller input
|
* Text entry menus don't accept controller input
|
||||||
|
|
|
@ -3,12 +3,17 @@ from ..Constants import NOTES, PHOBEKINS
|
||||||
|
|
||||||
|
|
||||||
class AccessTest(MessengerTestBase):
|
class AccessTest(MessengerTestBase):
|
||||||
|
options = {
|
||||||
|
"shuffle_shards": "true",
|
||||||
|
}
|
||||||
|
|
||||||
def testTabi(self) -> None:
|
def testTabi(self) -> None:
|
||||||
"""locations that hard require the Ninja Tabi"""
|
"""locations that hard require the Ninja Tabi"""
|
||||||
locations = ["Pyro", "Key of Chaos", "Underworld Seal - Sharp and Windy Climb", "Underworld Seal - Spike Wall",
|
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",
|
"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"]]
|
items = [["Ninja Tabi"]]
|
||||||
self.assertAccessDependency(locations, items)
|
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",
|
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",
|
"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",
|
"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"]]
|
items = [["Rope Dart"]]
|
||||||
self.assertAccessDependency(locations, items)
|
self.assertAccessDependency(locations, items)
|
||||||
|
|
||||||
|
@ -35,7 +41,11 @@ class AccessTest(MessengerTestBase):
|
||||||
"Tower of Time Seal - Lantern Climb", "Tower of Time Seal - Arcane Orbs",
|
"Tower of Time Seal - Lantern Climb", "Tower of Time Seal - Arcane Orbs",
|
||||||
"Underworld Seal - Sharp and Windy Climb", "Underworld Seal - Fireball Wave",
|
"Underworld Seal - Sharp and Windy Climb", "Underworld Seal - Fireball Wave",
|
||||||
"Elemental Skylands Seal - Air", "Forlorn Temple Seal - Rocket Maze",
|
"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"]]
|
items = [["Wingsuit"]]
|
||||||
self.assertAccessDependency(locations, items)
|
self.assertAccessDependency(locations, items)
|
||||||
|
|
||||||
|
@ -56,18 +66,26 @@ class AccessTest(MessengerTestBase):
|
||||||
"Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room",
|
"Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room",
|
||||||
"Tower of Time Seal - Lantern Climb", "Tower of Time Seal - Arcane Orbs",
|
"Tower of Time Seal - Lantern Climb", "Tower of Time Seal - Arcane Orbs",
|
||||||
"Underworld Seal - Sharp and Windy Climb", "Underworld Seal - Fireball Wave",
|
"Underworld Seal - Sharp and Windy Climb", "Underworld Seal - Fireball Wave",
|
||||||
"Elemental Skylands Seal - Air", "Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset",
|
"Elemental Skylands Seal - Air", "Forlorn Temple Seal - Rocket Maze",
|
||||||
"Power Thistle", "Key of Strength", "Glacial Peak Seal - Projectile Spike Pit",
|
"Forlorn Temple Seal - Rocket Sunset", "Power Thistle", "Key of Strength",
|
||||||
"Glacial Peak Seal - Glacial Air Swag", "Fairy Bottle", "Riviere Turquoise Seal - Flower Power",
|
"Glacial Peak Seal - Projectile Spike Pit", "Glacial Peak Seal - Glacial Air Swag",
|
||||||
"Searing Crags Seal - Triple Ball Spinner", "Searing Crags Seal - Raining Rocks",
|
"Fairy Bottle", "Riviere Turquoise Seal - Flower Power", "Searing Crags Seal - Triple Ball Spinner",
|
||||||
"Searing Crags Seal - Rhythm Rocks", "Astral Seed", "Astral Tea Leaves", "Rescue Phantom"]
|
"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"]]
|
items = [["Wingsuit", "Rope Dart"]]
|
||||||
self.assertAccessDependency(locations, items)
|
self.assertAccessDependency(locations, items)
|
||||||
|
|
||||||
def testAmulet(self) -> None:
|
def testAmulet(self) -> None:
|
||||||
"""Locations that require Ruxxtin's Amulet"""
|
"""Locations that require Ruxxtin's Amulet"""
|
||||||
locations = ["Acro", "Cloud Ruins Seal - Ghost Pit", "Cloud Ruins Seal - Toothbrush Alley",
|
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
|
# Cloud Ruins requires Ruxxtin's Amulet
|
||||||
items = [["Ruxxtin's Amulet"]]
|
items = [["Ruxxtin's Amulet"]]
|
||||||
self.assertAccessDependency(locations, items)
|
self.assertAccessDependency(locations, items)
|
||||||
|
@ -75,7 +93,7 @@ class AccessTest(MessengerTestBase):
|
||||||
def testBottle(self) -> None:
|
def testBottle(self) -> None:
|
||||||
"""Elemental Skylands and Corrupted Future require the Fairy Bottle"""
|
"""Elemental Skylands and Corrupted Future require the Fairy Bottle"""
|
||||||
locations = ["Key of Symbiosis", "Elemental Skylands Seal - Air", "Elemental Skylands Seal - Fire",
|
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"]]
|
items = [["Fairy Bottle"]]
|
||||||
self.assertAccessDependency(locations, items)
|
self.assertAccessDependency(locations, items)
|
||||||
|
|
||||||
|
|
|
@ -16,21 +16,21 @@ class HardLogicTest(MessengerTestBase):
|
||||||
# ninja village
|
# ninja village
|
||||||
"Candle", "Astral Seed", "Ninja Village Seal - Tree House", "Astral Tea Leaves",
|
"Candle", "Astral Seed", "Ninja Village Seal - Tree House", "Astral Tea Leaves",
|
||||||
# autumn hills
|
# 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 - Trip Saws", "Autumn Hills Seal - Double Swing Saws",
|
||||||
"Autumn Hills Seal - Spike Ball Swing", "Autumn Hills Seal - Spike Ball Darts",
|
"Autumn Hills Seal - Spike Ball Swing", "Autumn Hills Seal - Spike Ball Darts",
|
||||||
# forlorn temple
|
# forlorn temple
|
||||||
"Demon King Crown",
|
"Demon King Crown",
|
||||||
"Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset",
|
"Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset",
|
||||||
# catacombs
|
# catacombs
|
||||||
"Necro", "Ruxxtin's Amulet",
|
"Necro", "Ruxxtin's Amulet", "Ruxxtin",
|
||||||
"Catacombs Seal - Triple Spike Crushers", "Catacombs Seal - Crusher Gauntlet", "Catacombs Seal - Dirty Pond",
|
"Catacombs Seal - Triple Spike Crushers", "Catacombs Seal - Crusher Gauntlet", "Catacombs Seal - Dirty Pond",
|
||||||
# bamboo creek
|
# bamboo creek
|
||||||
"Claustro",
|
"Claustro",
|
||||||
"Bamboo Creek Seal - Spike Crushers and Doors", "Bamboo Creek Seal - Spike Ball Pits",
|
"Bamboo Creek Seal - Spike Crushers and Doors", "Bamboo Creek Seal - Spike Ball Pits",
|
||||||
"Bamboo Creek Seal - Spike Crushers and Doors v2",
|
"Bamboo Creek Seal - Spike Crushers and Doors v2",
|
||||||
# howling grotto
|
# 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
|
||||||
"Glacial Peak Seal - Ice Climbers",
|
"Glacial Peak Seal - Ice Climbers",
|
||||||
# cloud ruins
|
# cloud ruins
|
||||||
|
@ -41,7 +41,7 @@ class HardLogicTest(MessengerTestBase):
|
||||||
# riviere turquoise
|
# riviere turquoise
|
||||||
"Fairy Bottle", "Riviere Turquoise Seal - Flower Power",
|
"Fairy Bottle", "Riviere Turquoise Seal - Flower Power",
|
||||||
# elemental skylands
|
# elemental skylands
|
||||||
"Elemental Skylands Seal - Air", "Elemental Skylands Seal - Water", "Elemental Skylands Seal - Fire",
|
"Elemental Skylands Seal - Air",
|
||||||
# phantom
|
# phantom
|
||||||
"Rescue Phantom",
|
"Rescue Phantom",
|
||||||
]
|
]
|
||||||
|
|
|
@ -77,3 +77,33 @@ class ThirtyThirtySeals(MessengerTestBase):
|
||||||
required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing]
|
required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing]
|
||||||
self.assertEqual(len(total_seals), 30)
|
self.assertEqual(len(total_seals), 30)
|
||||||
self.assertEqual(len(required_seals), 10)
|
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)
|
||||||
|
|
Loading…
Reference in New Issue