mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-08 21:38:19 -07:00
Merge branch 'ArchipelagoMW:main' into main
This commit is contained in:
@@ -32,7 +32,6 @@ def set_rules(world):
|
||||
'WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
|
||||
|
||||
if world.players == 1:
|
||||
world.get_region('Menu', player).can_reach_private = lambda state: True
|
||||
no_logic_rules(world, player)
|
||||
for exit in world.get_region('Menu', player).exits:
|
||||
exit.hide_path = True
|
||||
@@ -196,7 +195,6 @@ def global_rules(world, player):
|
||||
add_item_rule(world.get_location(prize_location, player),
|
||||
lambda item: item.name in crystals_and_pendants and item.player == player)
|
||||
# determines which S&Q locations are available - hide from paths since it isn't an in-game location
|
||||
world.get_region('Menu', player).can_reach_private = lambda state: True
|
||||
for exit in world.get_region('Menu', player).exits:
|
||||
exit.hide_path = True
|
||||
|
||||
|
||||
@@ -8,10 +8,9 @@ class SongData(NamedTuple):
|
||||
code: Optional[int]
|
||||
song_is_free: bool
|
||||
streamer_mode: bool
|
||||
easy: str = Optional[int]
|
||||
hard: int = Optional[int]
|
||||
master: int = Optional[int]
|
||||
secret: int = Optional[int]
|
||||
easy: Optional[int]
|
||||
hard: Optional[int]
|
||||
master: Optional[int]
|
||||
|
||||
|
||||
class AlbumData(NamedTuple):
|
||||
|
||||
@@ -10,21 +10,22 @@ def load_text_file(name: str) -> str:
|
||||
|
||||
class MuseDashCollections:
|
||||
"""Contains all the data of Muse Dash, loaded from MuseDashData.txt."""
|
||||
STARTING_CODE = 2900000
|
||||
|
||||
MUSIC_SHEET_NAME: str = "Music Sheet"
|
||||
MUSIC_SHEET_CODE: int
|
||||
MUSIC_SHEET_CODE: int = STARTING_CODE
|
||||
|
||||
FREE_ALBUMS = [
|
||||
"Default Music",
|
||||
"Budget Is Burning: Nano Core",
|
||||
"Budget is Burning Vol.1"
|
||||
"Budget Is Burning Vol.1",
|
||||
]
|
||||
|
||||
DIFF_OVERRIDES = [
|
||||
"MuseDash ka nanika hi",
|
||||
"Rush-Hour",
|
||||
"Find this Month's Featured Playlist",
|
||||
"PeroPero in the Universe"
|
||||
"PeroPero in the Universe",
|
||||
]
|
||||
|
||||
album_items: Dict[str, AlbumData] = {}
|
||||
@@ -33,47 +34,43 @@ class MuseDashCollections:
|
||||
song_locations: Dict[str, int] = {}
|
||||
|
||||
vfx_trap_items: Dict[str, int] = {
|
||||
"Bad Apple Trap": 1,
|
||||
"Pixelate Trap": 2,
|
||||
"Random Wave Trap": 3,
|
||||
"Shadow Edge Trap": 4,
|
||||
"Chromatic Aberration Trap": 5,
|
||||
"Background Freeze Trap": 6,
|
||||
"Gray Scale Trap": 7,
|
||||
"Bad Apple Trap": STARTING_CODE + 1,
|
||||
"Pixelate Trap": STARTING_CODE + 2,
|
||||
"Random Wave Trap": STARTING_CODE + 3,
|
||||
"Shadow Edge Trap": STARTING_CODE + 4,
|
||||
"Chromatic Aberration Trap": STARTING_CODE + 5,
|
||||
"Background Freeze Trap": STARTING_CODE + 6,
|
||||
"Gray Scale Trap": STARTING_CODE + 7,
|
||||
}
|
||||
|
||||
sfx_trap_items: Dict[str, int] = {
|
||||
"Nyaa SFX Trap": 8,
|
||||
"Error SFX Trap": 9,
|
||||
"Nyaa SFX Trap": STARTING_CODE + 8,
|
||||
"Error SFX Trap": STARTING_CODE + 9,
|
||||
}
|
||||
|
||||
item_names_to_id = ChainMap({}, sfx_trap_items, vfx_trap_items)
|
||||
location_names_to_id = ChainMap(song_locations, album_locations)
|
||||
|
||||
def __init__(self, start_item_id: int, items_per_location: int):
|
||||
self.MUSIC_SHEET_CODE = start_item_id
|
||||
def __init__(self) -> None:
|
||||
self.item_names_to_id[self.MUSIC_SHEET_NAME] = self.MUSIC_SHEET_CODE
|
||||
|
||||
self.vfx_trap_items.update({k: (v + start_item_id) for (k, v) in self.vfx_trap_items.items()})
|
||||
self.sfx_trap_items.update({k: (v + start_item_id) for (k, v) in self.sfx_trap_items.items()})
|
||||
|
||||
item_id_index = start_item_id + 50
|
||||
location_id_index = start_item_id
|
||||
|
||||
item_id_index = self.STARTING_CODE + 50
|
||||
full_file = load_text_file("MuseDashData.txt")
|
||||
|
||||
seen_albums = set()
|
||||
for line in full_file.splitlines():
|
||||
line = line.strip()
|
||||
sections = line.split("|")
|
||||
|
||||
if sections[2] not in self.album_items:
|
||||
self.album_items[sections[2]] = AlbumData(item_id_index)
|
||||
album = sections[2]
|
||||
if album not in seen_albums:
|
||||
seen_albums.add(album)
|
||||
self.album_items[album] = AlbumData(item_id_index)
|
||||
item_id_index += 1
|
||||
|
||||
# Data is in the format 'Song|UID|Album|StreamerMode|EasyDiff|HardDiff|MasterDiff|SecretDiff'
|
||||
song_name = sections[0]
|
||||
# [1] is used in the client copy to make sure item id's match.
|
||||
song_is_free = sections[2] in self.FREE_ALBUMS
|
||||
song_is_free = album in self.FREE_ALBUMS
|
||||
steamer_mode = sections[3] == "True"
|
||||
|
||||
if song_name in self.DIFF_OVERRIDES:
|
||||
@@ -94,17 +91,16 @@ class MuseDashCollections:
|
||||
self.item_names_to_id.update({name: data.code for name, data in self.song_items.items()})
|
||||
self.item_names_to_id.update({name: data.code for name, data in self.album_items.items()})
|
||||
|
||||
location_id_index = self.STARTING_CODE
|
||||
for name in self.album_items.keys():
|
||||
for i in range(0, items_per_location):
|
||||
new_name = f"{name}-{i}"
|
||||
self.album_locations[new_name] = location_id_index
|
||||
location_id_index += 1
|
||||
self.album_locations[f"{name}-0"] = location_id_index
|
||||
self.album_locations[f"{name}-1"] = location_id_index + 1
|
||||
location_id_index += 2
|
||||
|
||||
for name in self.song_items.keys():
|
||||
for i in range(0, items_per_location):
|
||||
new_name = f"{name}-{i}"
|
||||
self.song_locations[new_name] = location_id_index
|
||||
location_id_index += 1
|
||||
self.song_locations[f"{name}-0"] = location_id_index
|
||||
self.song_locations[f"{name}-1"] = location_id_index + 1
|
||||
location_id_index += 2
|
||||
|
||||
def get_songs_with_settings(self, dlc_songs: bool, streamer_mode_active: bool,
|
||||
diff_lower: int, diff_higher: int) -> List[str]:
|
||||
|
||||
@@ -464,4 +464,8 @@ Songs Are Judged 90% by Chorus feat. Mameko|64-3|COSMIC RADIO PEROLIST|True|6|8|
|
||||
Kawai Splendid Space Thief|64-4|COSMIC RADIO PEROLIST|False|6|8|10|11
|
||||
Night City Runway|64-5|COSMIC RADIO PEROLIST|True|4|6|8|
|
||||
Chaos Shotgun feat. ChumuNote|64-6|COSMIC RADIO PEROLIST|True|6|8|10|
|
||||
mew mew magical summer|64-7|COSMIC RADIO PEROLIST|False|5|8|10|11
|
||||
mew mew magical summer|64-7|COSMIC RADIO PEROLIST|False|5|8|10|11
|
||||
BrainDance|65-0|Neon Abyss|True|3|6|9|
|
||||
My Focus!|65-1|Neon Abyss|True|5|7|10|
|
||||
ABABABA BURST|65-2|Neon Abyss|True|5|7|9|
|
||||
ULTRA HIGHER|65-3|Neon Abyss|True|4|7|10|
|
||||
@@ -40,14 +40,14 @@ class MuseDashWorld(World):
|
||||
game = "Muse Dash"
|
||||
option_definitions = musedash_options
|
||||
topology_present = False
|
||||
data_version = 8
|
||||
data_version = 9
|
||||
web = MuseDashWebWorld()
|
||||
|
||||
# Necessary Data
|
||||
md_collection = MuseDashCollections(2900000, 2)
|
||||
md_collection = MuseDashCollections()
|
||||
|
||||
item_name_to_id = md_collection.item_names_to_id
|
||||
location_name_to_id = md_collection.location_names_to_id
|
||||
item_name_to_id = {name: code for name, code in md_collection.item_names_to_id.items()}
|
||||
location_name_to_id = {name: code for name, code in md_collection.location_names_to_id.items()}
|
||||
|
||||
# Working Data
|
||||
victory_song_name: str = ""
|
||||
@@ -167,11 +167,12 @@ class MuseDashWorld(World):
|
||||
if trap:
|
||||
return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player)
|
||||
|
||||
song = self.md_collection.song_items.get(name)
|
||||
if song:
|
||||
return MuseDashSongItem(name, self.player, song)
|
||||
album = self.md_collection.album_items.get(name)
|
||||
if album:
|
||||
return MuseDashSongItem(name, self.player, album)
|
||||
|
||||
return MuseDashFixedItem(name, ItemClassification.filler, None, self.player)
|
||||
song = self.md_collection.song_items.get(name)
|
||||
return MuseDashSongItem(name, self.player, song)
|
||||
|
||||
def create_items(self) -> None:
|
||||
song_keys_in_pool = self.included_songs.copy()
|
||||
|
||||
49
worlds/musedash/test/TestCollection.py
Normal file
49
worlds/musedash/test/TestCollection.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import unittest
|
||||
from ..MuseDashCollection import MuseDashCollections
|
||||
|
||||
|
||||
class CollectionsTest(unittest.TestCase):
|
||||
REMOVED_SONGS = [
|
||||
"CHAOS Glitch",
|
||||
"FM 17314 SUGAR RADIO",
|
||||
]
|
||||
|
||||
def test_all_names_are_ascii(self) -> None:
|
||||
bad_names = list()
|
||||
collection = MuseDashCollections()
|
||||
for name in collection.song_items.keys():
|
||||
for c in name:
|
||||
# This is taken directly from OoT. Represents the generally excepted characters.
|
||||
if (0x20 <= ord(c) < 0x7e):
|
||||
continue
|
||||
|
||||
bad_names.append(name)
|
||||
break
|
||||
|
||||
self.assertEqual(len(bad_names), 0, f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}")
|
||||
|
||||
def test_ids_dont_change(self) -> None:
|
||||
collection = MuseDashCollections()
|
||||
itemsBefore = {name: code for name, code in collection.item_names_to_id.items()}
|
||||
locationsBefore = {name: code for name, code in collection.location_names_to_id.items()}
|
||||
|
||||
collection.__init__()
|
||||
itemsAfter = {name: code for name, code in collection.item_names_to_id.items()}
|
||||
locationsAfter = {name: code for name, code in collection.location_names_to_id.items()}
|
||||
|
||||
self.assertDictEqual(itemsBefore, itemsAfter, "Item ID changed after secondary init.")
|
||||
self.assertDictEqual(locationsBefore, locationsAfter, "Location ID changed after secondary init.")
|
||||
|
||||
def test_free_dlc_included_in_base_songs(self) -> None:
|
||||
collection = MuseDashCollections()
|
||||
songs = collection.get_songs_with_settings(False, False, 0, 11)
|
||||
|
||||
self.assertIn("Glimmer", songs, "Budget Is Burning Vol.1 is not being included in base songs")
|
||||
self.assertIn("Out of Sense", songs, "Budget Is Burning: Nano Core is not being included in base songs")
|
||||
|
||||
def test_remove_songs_are_not_generated(self) -> None:
|
||||
collection = MuseDashCollections()
|
||||
songs = collection.get_songs_with_settings(True, False, 0, 11)
|
||||
|
||||
for song_name in self.REMOVED_SONGS:
|
||||
self.assertNotIn(song_name, songs, f"Song '{song_name}' wasn't removed correctly.")
|
||||
@@ -9,8 +9,8 @@ class DifficultyRanges(MuseDashTestBase):
|
||||
difficulty_max = self.multiworld.song_difficulty_max[1]
|
||||
|
||||
def test_range(inputRange, lower, upper):
|
||||
assert inputRange[0] == lower and inputRange[1] == upper, \
|
||||
f"Output incorrect. Got: {inputRange[0]} to {inputRange[1]}. Expected: {lower} to {upper}"
|
||||
self.assertEqual(inputRange[0], lower)
|
||||
self.assertEqual(inputRange[1], upper)
|
||||
|
||||
songs = muse_dash_world.md_collection.get_songs_with_settings(True, False, inputRange[0], inputRange[1])
|
||||
for songKey in songs:
|
||||
@@ -24,7 +24,7 @@ class DifficultyRanges(MuseDashTestBase):
|
||||
if (song.master is not None and inputRange[0] <= song.master <= inputRange[1]):
|
||||
continue
|
||||
|
||||
assert False, f"Invalid song '{songKey}' was given for range '{inputRange[0]} to {inputRange[1]}'"
|
||||
self.fail(f"Invalid song '{songKey}' was given for range '{inputRange[0]} to {inputRange[1]}'")
|
||||
|
||||
#auto ranges
|
||||
difficulty_choice.value = 0
|
||||
@@ -65,5 +65,5 @@ class DifficultyRanges(MuseDashTestBase):
|
||||
for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES:
|
||||
song = muse_dash_world.md_collection.song_items[song_name]
|
||||
|
||||
assert song.easy is not None and song.hard is not None and song.master is not None, \
|
||||
f"Song '{song_name}' difficulty not set when it should be."
|
||||
self.assertTrue(song.easy is not None and song.hard is not None and song.master is not None,
|
||||
f"Song '{song_name}' difficulty not set when it should be.")
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import unittest
|
||||
from ..MuseDashCollection import MuseDashCollections
|
||||
|
||||
|
||||
class NamesTest(unittest.TestCase):
|
||||
def test_all_names_are_ascii(self) -> None:
|
||||
bad_names = list()
|
||||
collection = MuseDashCollections(0, 1)
|
||||
for name in collection.song_items.keys():
|
||||
for c in name:
|
||||
# This is taken directly from OoT. Represents the generally excepted characters.
|
||||
if (0x20 <= ord(c) < 0x7e):
|
||||
continue
|
||||
|
||||
bad_names.append(name)
|
||||
break
|
||||
|
||||
assert len(bad_names) == 0, f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}"
|
||||
@@ -1,7 +1,7 @@
|
||||
from . import MuseDashTestBase
|
||||
|
||||
|
||||
class TestIncludedSongSizeDoesntGrow(MuseDashTestBase):
|
||||
class TestPlandoSettings(MuseDashTestBase):
|
||||
options = {
|
||||
"additional_song_count": 15,
|
||||
"allow_just_as_planned_dlc_songs": True,
|
||||
@@ -14,14 +14,14 @@ class TestIncludedSongSizeDoesntGrow(MuseDashTestBase):
|
||||
|
||||
def test_included_songs_didnt_grow_item_count(self) -> None:
|
||||
muse_dash_world = self.multiworld.worlds[1]
|
||||
assert len(muse_dash_world.included_songs) == 15, \
|
||||
f"Logical songs size grew when it shouldn't. Expected 15. Got {len(muse_dash_world.included_songs)}"
|
||||
self.assertEqual(len(muse_dash_world.included_songs), 15,
|
||||
f"Logical songs size grew when it shouldn't. Expected 15. Got {len(muse_dash_world.included_songs)}")
|
||||
|
||||
def test_included_songs_plando(self) -> None:
|
||||
muse_dash_world = self.multiworld.worlds[1]
|
||||
songs = muse_dash_world.included_songs.copy()
|
||||
songs.append(muse_dash_world.victory_song_name)
|
||||
|
||||
assert "Operation Blade" in songs, "Logical songs is missing a plando song: Operation Blade"
|
||||
assert "Autumn Moods" in songs, "Logical songs is missing a plando song: Autumn Moods"
|
||||
assert "Fireflies" in songs, "Logical songs is missing a plando song: Fireflies"
|
||||
self.assertIn("Operation Blade", songs, "Logical songs is missing a plando song: Operation Blade")
|
||||
self.assertIn("Autumn Moods", songs, "Logical songs is missing a plando song: Autumn Moods")
|
||||
self.assertIn("Fireflies", songs, "Logical songs is missing a plando song: Fireflies")
|
||||
@@ -1,25 +0,0 @@
|
||||
from . import MuseDashTestBase
|
||||
|
||||
|
||||
class TestRemovedSongs(MuseDashTestBase):
|
||||
options = {
|
||||
"starting_song_count": 10,
|
||||
"allow_just_as_planned_dlc_songs": True,
|
||||
"additional_song_count": 500,
|
||||
}
|
||||
|
||||
removed_songs = [
|
||||
"CHAOS Glitch",
|
||||
"FM 17314 SUGAR RADIO"
|
||||
]
|
||||
|
||||
def test_remove_songs_are_not_generated(self) -> None:
|
||||
# This test is done on a world where every song should be added.
|
||||
muse_dash_world = self.multiworld.worlds[1]
|
||||
|
||||
for song_name in self.removed_songs:
|
||||
assert song_name not in muse_dash_world.starting_songs, \
|
||||
f"Song '{song_name}' was included into the starting songs when it shouldn't."
|
||||
|
||||
assert song_name not in muse_dash_world.included_songs, \
|
||||
f"Song '{song_name}' was included into the included songs when it shouldn't."
|
||||
@@ -1681,7 +1681,6 @@ def create_regions(self):
|
||||
connect(multiworld, player, "Fuchsia City", "Fuchsia Fishing", lambda state: state.has("Super Rod", player), one_way=True)
|
||||
connect(multiworld, player, "Pallet Town", "Old Rod Fishing", lambda state: state.has("Old Rod", player), one_way=True)
|
||||
connect(multiworld, player, "Pallet Town", "Good Rod Fishing", lambda state: state.has("Good Rod", player), one_way=True)
|
||||
connect(multiworld, player, "Cinnabar Lab Fossil Room", "Good Rod Fishing", one_way=True)
|
||||
connect(multiworld, player, "Cinnabar Lab Fossil Room", "Fossil Level", lambda state: logic.fossil_checks(state, 1, player), one_way=True)
|
||||
connect(multiworld, player, "Route 5 Gate-N", "Route 5 Gate-S", lambda state: logic.can_pass_guards(state, player))
|
||||
connect(multiworld, player, "Route 6 Gate-N", "Route 6 Gate-S", lambda state: logic.can_pass_guards(state, player))
|
||||
|
||||
@@ -492,35 +492,39 @@ class StardewLogic:
|
||||
})
|
||||
|
||||
self.special_order_rules.update({
|
||||
SpecialOrder.island_ingredients: self.has_island_transport() & self.can_farm_perfectly() &
|
||||
self.has(Vegetable.taro_root) & self.has(Fruit.pineapple) & self.has(Forageable.ginger),
|
||||
SpecialOrder.cave_patrol: self.can_mine_perfectly() & self.can_mine_to_floor(120),
|
||||
SpecialOrder.aquatic_overpopulation: self.can_fish_perfectly(),
|
||||
SpecialOrder.biome_balance: self.can_fish_perfectly(),
|
||||
SpecialOrder.rock_rejuivenation: self.has(Mineral.ruby) & self.has(Mineral.topaz) & self.has(Mineral.emerald) &
|
||||
self.has(Mineral.jade) & self.has(Mineral.amethyst) & self.has_relationship(NPC.emily, 4) &
|
||||
self.has(ArtisanGood.cloth) & self.can_reach_region(Region.haley_house),
|
||||
SpecialOrder.gifts_for_george: self.has_season(Season.spring) & self.has(Forageable.leek),
|
||||
SpecialOrder.fragments_of_the_past: self.can_reach_region(Region.dig_site),
|
||||
SpecialOrder.gus_famous_omelet: self.has(AnimalProduct.any_egg),
|
||||
SpecialOrder.crop_order: self.can_farm_perfectly(),
|
||||
SpecialOrder.community_cleanup: self.can_crab_pot(),
|
||||
SpecialOrder.the_strong_stuff: self.can_keg(Vegetable.potato),
|
||||
SpecialOrder.pierres_prime_produce: self.can_farm_perfectly(),
|
||||
SpecialOrder.robins_project: self.can_chop_perfectly() & self.has(Material.hardwood),
|
||||
SpecialOrder.robins_resource_rush: self.can_chop_perfectly() & self.has(Fertilizer.tree) & self.can_mine_perfectly(),
|
||||
SpecialOrder.juicy_bugs_wanted_yum: self.has(Loot.bug_meat),
|
||||
SpecialOrder.tropical_fish: self.has_island_transport() & self.has(Fish.stingray) & self.has(Fish.blue_discus) & self.has(Fish.lionfish),
|
||||
SpecialOrder.a_curious_substance: self.can_mine_perfectly() & self.can_mine_to_floor(80),
|
||||
SpecialOrder.prismatic_jelly: self.can_mine_perfectly() & self.can_mine_to_floor(40),
|
||||
SpecialOrder.island_ingredients: self.can_meet(NPC.caroline) & self.has_island_transport() & self.can_farm_perfectly() &
|
||||
self.can_ship(Vegetable.taro_root) & self.can_ship(Fruit.pineapple) & self.can_ship(Forageable.ginger),
|
||||
SpecialOrder.cave_patrol: self.can_meet(NPC.clint) & self.can_mine_perfectly() & self.can_mine_to_floor(120),
|
||||
SpecialOrder.aquatic_overpopulation: self.can_meet(NPC.demetrius) & self.can_fish_perfectly(),
|
||||
SpecialOrder.biome_balance: self.can_meet(NPC.demetrius) & self.can_fish_perfectly(),
|
||||
SpecialOrder.rock_rejuivenation: self.has_relationship(NPC.emily, 4) & self.has(Mineral.ruby) & self.has(Mineral.topaz) &
|
||||
self.has(Mineral.emerald) & self.has(Mineral.jade) & self.has(Mineral.amethyst) &
|
||||
self.has(ArtisanGood.cloth) & self.can_reach_region(Region.haley_house),
|
||||
SpecialOrder.gifts_for_george: self.can_reach_region(Region.alex_house) & self.has_season(Season.spring) & self.has(Forageable.leek),
|
||||
SpecialOrder.fragments_of_the_past: self.can_reach_region(Region.museum) & self.can_reach_region(Region.dig_site) & self.has_tool(Tool.pickaxe),
|
||||
SpecialOrder.gus_famous_omelet: self.can_reach_region(Region.saloon) & self.has(AnimalProduct.any_egg),
|
||||
SpecialOrder.crop_order: self.can_farm_perfectly() & self.can_ship(),
|
||||
SpecialOrder.community_cleanup: self.can_reach_region(Region.railroad) & self.can_crab_pot(),
|
||||
SpecialOrder.the_strong_stuff: self.can_reach_region(Region.trailer) & self.can_keg(Vegetable.potato),
|
||||
SpecialOrder.pierres_prime_produce: self.can_reach_region(Region.pierre_store) & self.can_farm_perfectly(),
|
||||
SpecialOrder.robins_project: self.can_meet(NPC.robin) & self.can_reach_region(Region.carpenter) & self.can_chop_perfectly() &
|
||||
self.has(Material.hardwood),
|
||||
SpecialOrder.robins_resource_rush: self.can_meet(NPC.robin) & self.can_reach_region(Region.carpenter) & self.can_chop_perfectly() &
|
||||
self.has(Fertilizer.tree) & self.can_mine_perfectly(),
|
||||
SpecialOrder.juicy_bugs_wanted_yum: self.can_reach_region(Region.beach) & self.has(Loot.bug_meat),
|
||||
SpecialOrder.tropical_fish: self.can_meet(NPC.willy) & self.received("Island Resort") & self.has_island_transport() &
|
||||
self.has(Fish.stingray) & self.has(Fish.blue_discus) & self.has(Fish.lionfish),
|
||||
SpecialOrder.a_curious_substance: self.can_reach_region(Region.wizard_tower) & self.can_mine_perfectly() & self.can_mine_to_floor(80),
|
||||
SpecialOrder.prismatic_jelly: self.can_reach_region(Region.wizard_tower) & self.can_mine_perfectly() & self.can_mine_to_floor(40),
|
||||
SpecialOrder.qis_crop: self.can_farm_perfectly() & self.can_reach_region(Region.greenhouse) &
|
||||
self.can_reach_region(Region.island_west) & self.has_total_skill_level(50) &
|
||||
self.has(Machine.seed_maker),
|
||||
self.has(Machine.seed_maker) & self.has_building(Building.shipping_bin),
|
||||
SpecialOrder.lets_play_a_game: self.has_junimo_kart_max_level(),
|
||||
SpecialOrder.four_precious_stones: self.has_lived_months(MAX_MONTHS) & self.has("Prismatic Shard") &
|
||||
self.can_mine_perfectly_in_the_skull_cavern(),
|
||||
SpecialOrder.qis_hungry_challenge: self.can_mine_perfectly_in_the_skull_cavern() & self.has_max_buffs(),
|
||||
SpecialOrder.qis_cuisine: self.can_cook() & (self.can_spend_money_at(Region.saloon, 205000) | self.can_spend_money_at(Region.pierre_store, 170000)),
|
||||
SpecialOrder.qis_cuisine: self.can_cook() & (self.can_spend_money_at(Region.saloon, 205000) | self.can_spend_money_at(Region.pierre_store, 170000)) &
|
||||
self.can_ship(),
|
||||
SpecialOrder.qis_kindness: self.can_give_loved_gifts_to_everyone(),
|
||||
SpecialOrder.extended_family: self.can_fish_perfectly() & self.has(Fish.angler) & self.has(Fish.glacierfish) &
|
||||
self.has(Fish.crimsonfish) & self.has(Fish.mutant_carp) & self.has(Fish.legend),
|
||||
@@ -1095,6 +1099,8 @@ class StardewLogic:
|
||||
rules = [self.can_reach_any_region(villager.locations)]
|
||||
if npc == NPC.kent:
|
||||
rules.append(self.has_year_two())
|
||||
elif npc == NPC.leo:
|
||||
rules.append(self.received("Island West Turtle"))
|
||||
|
||||
return And(rules)
|
||||
|
||||
@@ -1155,7 +1161,7 @@ class StardewLogic:
|
||||
item_rules.append(bundle_item.item.name)
|
||||
if bundle_item.quality > highest_quality_yet:
|
||||
highest_quality_yet = bundle_item.quality
|
||||
return self.has(item_rules, number_required) & self.can_grow_gold_quality(highest_quality_yet)
|
||||
return self.can_reach_region(Region.wizard_tower) & self.has(item_rules, number_required) & self.can_grow_gold_quality(highest_quality_yet)
|
||||
|
||||
def can_grow_gold_quality(self, quality: int) -> StardewRule:
|
||||
if quality <= 0:
|
||||
@@ -1603,3 +1609,9 @@ class StardewLogic:
|
||||
rules.append(self.received(f"Rarecrow #{rarecrow_number}"))
|
||||
return And(rules)
|
||||
|
||||
def can_ship(self, item: str = "") -> StardewRule:
|
||||
shipping_bin_rule = self.has_building(Building.shipping_bin)
|
||||
if item == "":
|
||||
return shipping_bin_rule
|
||||
return shipping_bin_rule & self.has(item)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ Warning: Currently it is not checked whether a loaded savegame belongs to the mu
|
||||
|
||||
The mod adds the following console commands:
|
||||
- `say` sends the text following it to Archipelago as a chat message.
|
||||
- `!` is not an allowed character, use `/` in its place. For example, to use the [`!hint` command](/tutorial/Archipelago/commands/en#remote-commands), type `say /hint`.
|
||||
- For example, to use the [`!hint` command](/tutorial/Archipelago/commands/en#remote-commands), type `say !hint`.
|
||||
- `silent` toggles Archipelago messages appearing.
|
||||
- `tracker` rotates through the possible settings for the in-game tracker that displays the closest uncollected location.
|
||||
- `deathlink` toggles death link.
|
||||
|
||||
@@ -10,7 +10,7 @@ joke_hints = [
|
||||
"You can do it!",
|
||||
"I believe in you!",
|
||||
"The person playing is cute. <3",
|
||||
"dash dot, dash dash dash, dash, dot dot dot dot, dot dot, dash dot, dash dash dot",
|
||||
"dash dot, dash dash dash,\ndash, dot dot dot dot, dot dot,\ndash dot, dash dash dot",
|
||||
"When you think about it, there are actually a lot of bubbles in a stream.",
|
||||
"Never gonna give you up\nNever gonna let you down\nNever gonna run around and desert you",
|
||||
"Thanks to the Archipelago developers for making this possible.",
|
||||
|
||||
Reference in New Issue
Block a user