Compare commits

..

1 Commits

Author SHA1 Message Date
NewSoupVi
eaf352daaf MultiServer: Correct tying of Context.groups 2025-01-11 22:01:05 +01:00
121 changed files with 1516 additions and 9915 deletions

View File

@@ -1,20 +1,8 @@
{
"include": [
"../BizHawkClient.py",
"../Patch.py",
"../test/general/test_groups.py",
"../test/general/test_helpers.py",
"../test/general/test_memory.py",
"../test/general/test_names.py",
"../test/multiworld/__init__.py",
"../test/multiworld/test_multiworlds.py",
"../test/netutils/__init__.py",
"../test/programs/__init__.py",
"../test/programs/test_multi_server.py",
"../test/utils/__init__.py",
"../test/webhost/test_descriptions.py",
"type_check.py",
"../worlds/AutoSNIClient.py",
"type_check.py"
"../Patch.py"
],
"exclude": [

View File

@@ -26,7 +26,7 @@ jobs:
- name: "Install dependencies"
run: |
python -m pip install --upgrade pip pyright==1.1.392.post0
python -m pip install --upgrade pip pyright==1.1.358
python ModuleUpdate.py --append "WebHostLib/requirements.txt" --force --yes
- name: "pyright: strict check on specific files"

View File

@@ -31,7 +31,6 @@ import ssl
if typing.TYPE_CHECKING:
import kvui
import argparse
logger = logging.getLogger("Client")
@@ -460,13 +459,6 @@ class CommonContext:
await self.send_msgs([payload])
await self.send_msgs([{"cmd": "Get", "keys": ["_read_race_mode"]}])
async def check_locations(self, locations: typing.Collection[int]) -> set[int]:
"""Send new location checks to the server. Returns the set of actually new locations that were sent."""
locations = set(locations) & self.missing_locations
if locations:
await self.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(locations)}])
return locations
async def console_input(self) -> str:
if self.ui:
self.ui.focus_textinput()
@@ -1049,32 +1041,6 @@ def get_base_parser(description: typing.Optional[str] = None):
return parser
def handle_url_arg(args: "argparse.Namespace",
parser: "typing.Optional[argparse.ArgumentParser]" = None) -> "argparse.Namespace":
"""
Parse the url arg "archipelago://name:pass@host:port" from launcher into correct launch args for CommonClient
If alternate data is required the urlparse response is saved back to args.url if valid
"""
if not args.url:
return args
url = urllib.parse.urlparse(args.url)
if url.scheme != "archipelago":
if not parser:
parser = get_base_parser()
parser.error(f"bad url, found {args.url}, expected url in form of archipelago://archipelago.gg:38281")
return args
args.url = url
args.connect = url.netloc
if url.username:
args.name = urllib.parse.unquote(url.username)
if url.password:
args.password = urllib.parse.unquote(url.password)
return args
def run_as_textclient(*args):
class TextContext(CommonContext):
# Text Mode to use !hint and such with games that have no text entry
@@ -1116,7 +1082,17 @@ def run_as_textclient(*args):
parser.add_argument("url", nargs="?", help="Archipelago connection url")
args = parser.parse_args(args)
args = handle_url_arg(args, parser=parser)
# handle if text client is launched using the "archipelago://name:pass@host:port" url from webhost
if args.url:
url = urllib.parse.urlparse(args.url)
if url.scheme == "archipelago":
args.connect = url.netloc
if url.username:
args.name = urllib.parse.unquote(url.username)
if url.password:
args.password = urllib.parse.unquote(url.password)
else:
parser.error(f"bad url, found {args.url}, expected url in form of archipelago://archipelago.gg:38281")
# use colorama to display colored text highlighting on windows
colorama.init()

20
Fill.py
View File

@@ -571,26 +571,6 @@ def distribute_items_restrictive(multiworld: MultiWorld,
print_data = {"items": items_counter, "locations": locations_counter}
logging.info(f"Per-Player counts: {print_data})")
more_locations = locations_counter - items_counter
more_items = items_counter - locations_counter
for player in multiworld.player_ids:
if more_locations[player]:
logging.error(
f"Player {multiworld.get_player_name(player)} had {more_locations[player]} more locations than items.")
elif more_items[player]:
logging.warning(
f"Player {multiworld.get_player_name(player)} had {more_items[player]} more items than locations.")
if unfilled:
raise FillError(
f"Unable to fill all locations.\n" +
f"Unfilled locations({len(unfilled)}): {unfilled}"
)
else:
logging.warning(
f"Unable to place all items.\n" +
f"Unplaced items({len(unplaced)}): {unplaced}"
)
def flood_items(multiworld: MultiWorld) -> None:
# get items to distribute

View File

@@ -42,9 +42,7 @@ def mystery_argparse():
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
parser.add_argument('--race', action='store_true', default=defaults.race)
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
parser.add_argument('--log_level', default=defaults.loglevel, help='Sets log level')
parser.add_argument('--log_time', help="Add timestamps to STDOUT",
default=defaults.logtime, action='store_true')
parser.add_argument('--log_level', default='info', help='Sets log level')
parser.add_argument("--csv_output", action="store_true",
help="Output rolled player options to csv (made for async multiworld).")
parser.add_argument("--plando", default=defaults.plando_options,
@@ -77,7 +75,7 @@ def main(args=None) -> Tuple[argparse.Namespace, int]:
seed = get_seed(args.seed)
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level, add_timestamp=args.log_time)
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level)
random.seed(seed)
seed_name = get_seed_name(random)
@@ -440,7 +438,7 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
if "linked_options" in weights:
weights = roll_linked_options(weights)
valid_keys = {"triggers"}
valid_keys = set()
if "triggers" in weights:
weights = roll_triggers(weights, weights["triggers"], valid_keys)
@@ -499,22 +497,15 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
for option_key, option in world_type.options_dataclass.type_hints.items():
handle_option(ret, game_weights, option_key, option, plando_options)
valid_keys.add(option_key)
# TODO remove plando_items after moving it to the options system
valid_keys.add("plando_items")
if PlandoOptions.items in plando_options:
ret.plando_items = copy.deepcopy(game_weights.get("plando_items", []))
if ret.game == "A Link to the Past":
# TODO there are still more LTTP options not on the options system
valid_keys |= {"sprite_pool", "sprite", "random_sprite_on_event"}
roll_alttp_settings(ret, game_weights)
# log a warning for options within a game section that aren't determined as valid
for option_key in game_weights:
if option_key in valid_keys:
if option_key in {"triggers", *valid_keys}:
continue
logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers "
f"for player {ret.name}.")
if PlandoOptions.items in plando_options:
ret.plando_items = copy.deepcopy(game_weights.get("plando_items", []))
if ret.game == "A Link to the Past":
roll_alttp_settings(ret, game_weights)
return ret

View File

@@ -560,10 +560,6 @@ class LinksAwakeningContext(CommonContext):
while self.client.auth == None:
await asyncio.sleep(0.1)
# Just return if we're closing
if self.exit_event.is_set():
return
self.auth = self.client.auth
await self.send_connect()

View File

@@ -743,17 +743,16 @@ class Context:
concerns[player].append(data)
if not hint.local and data not in concerns[hint.finding_player]:
concerns[hint.finding_player].append(data)
# remember hints in all cases
# only remember hints that were not already found at the time of creation
if not hint.found:
# since hints are bidirectional, finding player and receiving player,
# we can check once if hint already exists
if hint not in self.hints[team, hint.finding_player]:
self.hints[team, hint.finding_player].add(hint)
new_hint_events.add(hint.finding_player)
for player in self.slot_set(hint.receiving_player):
self.hints[team, player].add(hint)
new_hint_events.add(player)
# since hints are bidirectional, finding player and receiving player,
# we can check once if hint already exists
if hint not in self.hints[team, hint.finding_player]:
self.hints[team, hint.finding_player].add(hint)
new_hint_events.add(hint.finding_player)
for player in self.slot_set(hint.receiving_player):
self.hints[team, player].add(hint)
new_hint_events.add(player)
self.logger.info("Notice (Team #%d): %s" % (team + 1, format_hint(self, team, hint)))
for slot in new_hint_events:
@@ -1888,8 +1887,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
for location in args["locations"]:
if type(location) is not int:
await ctx.send_msgs(client,
[{'cmd': 'InvalidPacket', "type": "arguments",
"text": 'Locations has to be a list of integers',
[{'cmd': 'InvalidPacket', "type": "arguments", "text": 'LocationScouts',
"original_cmd": cmd}])
return
@@ -1992,7 +1990,6 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
args["cmd"] = "SetReply"
value = ctx.stored_data.get(args["key"], args.get("default", 0))
args["original_value"] = copy.copy(value)
args["slot"] = client.slot
for operation in args["operations"]:
func = modify_functions[operation["operation"]]
value = func(value, operation["value"])

View File

@@ -1,6 +1,7 @@
import tkinter as tk
import argparse
import logging
import random
import os
import zipfile
from itertools import chain
@@ -196,6 +197,7 @@ def set_icon(window):
def adjust(args):
# Create a fake multiworld and OOTWorld to use as a base
multiworld = MultiWorld(1)
multiworld.per_slot_randoms = {1: random}
ootworld = OOTWorld(multiworld, 1)
# Set options in the fake OOTWorld
for name, option in chain(cosmetic_options.items(), sfx_options.items()):

View File

@@ -137,7 +137,7 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
If this is False, the docstring is instead interpreted as plain text, and
displayed as-is on the WebHost with whitespace preserved.
If this is None, it inherits the value of `WebWorld.rich_text_options_doc`. For
If this is None, it inherits the value of `World.rich_text_options_doc`. For
backwards compatibility, this defaults to False, but worlds are encouraged to
set it to True and use reStructuredText for their Option documentation.
@@ -689,9 +689,9 @@ class Range(NumericOption):
@classmethod
def weighted_range(cls, text) -> Range:
if text == "random-low":
return cls(cls.triangular(cls.range_start, cls.range_end, 0.0))
return cls(cls.triangular(cls.range_start, cls.range_end, cls.range_start))
elif text == "random-high":
return cls(cls.triangular(cls.range_start, cls.range_end, 1.0))
return cls(cls.triangular(cls.range_start, cls.range_end, cls.range_end))
elif text == "random-middle":
return cls(cls.triangular(cls.range_start, cls.range_end))
elif text.startswith("random-range-"):
@@ -717,11 +717,11 @@ class Range(NumericOption):
f"{random_range[0]}-{random_range[1]} is outside allowed range "
f"{cls.range_start}-{cls.range_end} for option {cls.__name__}")
if text.startswith("random-range-low"):
return cls(cls.triangular(random_range[0], random_range[1], 0.0))
return cls(cls.triangular(random_range[0], random_range[1], random_range[0]))
elif text.startswith("random-range-middle"):
return cls(cls.triangular(random_range[0], random_range[1]))
elif text.startswith("random-range-high"):
return cls(cls.triangular(random_range[0], random_range[1], 1.0))
return cls(cls.triangular(random_range[0], random_range[1], random_range[1]))
else:
return cls(random.randint(random_range[0], random_range[1]))
@@ -739,16 +739,8 @@ class Range(NumericOption):
return str(self.value)
@staticmethod
def triangular(lower: int, end: int, tri: float = 0.5) -> int:
"""
Integer triangular distribution for `lower` inclusive to `end` inclusive.
Expects `lower <= end` and `0.0 <= tri <= 1.0`. The result of other inputs is undefined.
"""
# Use the continuous range [lower, end + 1) to produce an integer result in [lower, end].
# random.triangular is actually [a, b] and not [a, b), so there is a very small chance of getting exactly b even
# when a != b, so ensure the result is never more than `end`.
return min(end, math.floor(random.triangular(0.0, 1.0, tri) * (end - lower + 1) + lower))
def triangular(lower: int, end: int, tri: typing.Optional[int] = None) -> int:
return int(round(random.triangular(lower, end, tri), 0))
class NamedRange(Range):

View File

@@ -521,8 +521,8 @@ def init_logging(name: str, loglevel: typing.Union[str, int] = logging.INFO,
def filter(self, record: logging.LogRecord) -> bool:
return self.condition(record)
file_handler.addFilter(Filter("NoStream", lambda record: not getattr(record, "NoFile", False)))
file_handler.addFilter(Filter("NoCarriageReturn", lambda record: '\r' not in record.getMessage()))
file_handler.addFilter(Filter("NoStream", lambda record: not getattr(record, "NoFile", False)))
file_handler.addFilter(Filter("NoCarriageReturn", lambda record: '\r' not in record.msg))
root_logger.addHandler(file_handler)
if sys.stdout:
formatter = logging.Formatter(fmt='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
@@ -940,7 +940,7 @@ def freeze_support() -> None:
def visualize_regions(root_region: Region, file_name: str, *,
show_entrance_names: bool = False, show_locations: bool = True, show_other_regions: bool = True,
linetype_ortho: bool = True, regions_to_highlight: set[Region] | None = None) -> None:
linetype_ortho: bool = True) -> None:
"""Visualize the layout of a world as a PlantUML diagram.
:param root_region: The region from which to start the diagram from. (Usually the "Menu" region of your world.)
@@ -956,22 +956,16 @@ def visualize_regions(root_region: Region, file_name: str, *,
Items without ID will be shown in italics.
:param show_other_regions: (default True) If enabled, regions that can't be reached by traversing exits are shown.
:param linetype_ortho: (default True) If enabled, orthogonal straight line parts will be used; otherwise polylines.
:param regions_to_highlight: Regions that will be highlighted in green if they are reachable.
Example usage in World code:
from Utils import visualize_regions
state = self.multiworld.get_all_state(False)
state.update_reachable_regions(self.player)
visualize_regions(self.get_region("Menu"), "my_world.puml", show_entrance_names=True,
regions_to_highlight=state.reachable_regions[self.player])
visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml")
Example usage in Main code:
from Utils import visualize_regions
for player in multiworld.player_ids:
visualize_regions(multiworld.get_region("Menu", player), f"{multiworld.get_out_file_name_base(player)}.puml")
"""
if regions_to_highlight is None:
regions_to_highlight = set()
assert root_region.multiworld, "The multiworld attribute of root_region has to be filled"
from BaseClasses import Entrance, Item, Location, LocationProgressType, MultiWorld, Region
from collections import deque
@@ -1024,7 +1018,7 @@ def visualize_regions(root_region: Region, file_name: str, *,
uml.append(f"\"{fmt(region)}\" : {{field}} {lock}{fmt(location)}")
def visualize_region(region: Region) -> None:
uml.append(f"class \"{fmt(region)}\" {'#00FF00' if region in regions_to_highlight else ''}")
uml.append(f"class \"{fmt(region)}\"")
if show_locations:
visualize_locations(region)
visualize_exits(region)

View File

@@ -3,13 +3,13 @@ from typing import List, Tuple
from flask import Blueprint
from ..models import Seed, Slot
from ..models import Seed
api_endpoints = Blueprint('api', __name__, url_prefix="/api")
def get_players(seed: Seed) -> List[Tuple[str, str]]:
return [(slot.player_name, slot.game) for slot in seed.slots.order_by(Slot.player_id)]
return [(slot.player_name, slot.game) for slot in seed.slots]
from . import datapackage, generate, room, user # trigger registration

View File

@@ -30,4 +30,4 @@ def get_seeds():
"creation_time": seed.creation_time,
"players": get_players(seed.slots),
})
return jsonify(response)
return jsonify(response)

View File

@@ -121,14 +121,6 @@ Response:
Expected Response Type: `HASH_RESPONSE`
- `MEMORY_SIZE`
Returns the size in bytes of the specified memory domain.
Expected Response Type: `MEMORY_SIZE_RESPONSE`
Additional Fields:
- `domain` (`string`): The name of the memory domain to check
- `GUARD`
Checks a section of memory against `expected_data`. If the bytes starting
at `address` do not match `expected_data`, the response will have `value`
@@ -224,12 +216,6 @@ Response:
Additional Fields:
- `value` (`string`): The returned hash
- `MEMORY_SIZE_RESPONSE`
Contains the size in bytes of the specified memory domain.
Additional Fields:
- `value` (`number`): The size of the domain in bytes
- `GUARD_RESPONSE`
The result of an attempted `GUARD` request.
@@ -390,15 +376,6 @@ request_handlers = {
return res
end,
["MEMORY_SIZE"] = function (req)
local res = {}
res["type"] = "MEMORY_SIZE_RESPONSE"
res["value"] = memory.getmemorydomainsize(req["domain"])
return res
end,
["GUARD"] = function (req)
local res = {}
local expected_data = base64.decode(req["expected_data"])
@@ -636,11 +613,9 @@ end)
if bizhawk_major < 2 or (bizhawk_major == 2 and bizhawk_minor < 7) then
print("Must use BizHawk 2.7.0 or newer")
elseif bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor > 9) then
print("Warning: This version of BizHawk is newer than this script. If it doesn't work, consider downgrading to 2.9.")
else
if bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor > 10) then
print("Warning: This version of BizHawk is newer than this script. If it doesn't work, consider downgrading to 2.10.")
end
if emu.getsystemid() == "NULL" then
print("No ROM is loaded. Please load a ROM.")
while emu.getsystemid() == "NULL" do

View File

@@ -1816,7 +1816,7 @@ end
-- Main control handling: main loop and socket receive
function APreceive()
function receive()
l, e = ootSocket:receive()
-- Handle incoming message
if e == 'closed' then
@@ -1874,7 +1874,7 @@ function main()
end
if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
if (frame % 30 == 0) then
APreceive()
receive()
end
elseif (curstate == STATE_UNINITIALIZED) then
if (frame % 60 == 0) then

View File

@@ -99,9 +99,6 @@
# Lingo
/worlds/lingo/ @hatkirby
# Links Awakening DX
/worlds/ladx/ @threeandthreee
# Lufia II Ancient Cave
/worlds/lufia2ac/ @el-u
/worlds/lufia2ac/docs/ @wordfcuk @el-u
@@ -239,6 +236,9 @@
# Final Fantasy (1)
# /worlds/ff1/
# Links Awakening DX
# /worlds/ladx/
# Ocarina of Time
# /worlds/oot/

View File

@@ -261,7 +261,6 @@ Sent to clients in response to a [Set](#Set) package if want_reply was set to tr
| key | str | The key that was updated. |
| value | any | The new value for the key. |
| original_value | any | The value the key had before it was updated. Not present on "_read" prefixed special keys. |
| slot | int | The slot that originally sent the Set package causing this change. |
Additional arguments added to the [Set](#Set) package that triggered this [SetReply](#SetReply) will also be passed along.

View File

@@ -95,7 +95,7 @@ user hovers over the yellow "(?)" icon, and included in the YAML templates gener
The WebHost can display Option documentation either as plain text with all whitespace preserved (other than the base
indentation), or as HTML generated from the standard Python [reStructuredText] format. Although plain text is the
default for backwards compatibility, world authors are encouraged to write their Option documentation as
reStructuredText and enable rich text rendering by setting `WebWorld.rich_text_options_doc = True`.
reStructuredText and enable rich text rendering by setting `World.rich_text_options_doc = True`.
[reStructuredText]: https://docutils.sourceforge.io/rst.html

View File

@@ -2,6 +2,3 @@
python_files = test_*.py Test*.py # TODO: remove Test* once all worlds have been ported
python_classes = Test
python_functions = test
testpaths =
test
worlds

View File

@@ -678,8 +678,6 @@ class GeneratorOptions(Group):
race: Race = Race(0)
plando_options: PlandoOptions = PlandoOptions("bosses, connections, texts")
panic_method: PanicMethod = PanicMethod("swap")
loglevel: str = "info"
logtime: bool = False
class SNIOptions(Group):

View File

@@ -1,8 +1,6 @@
import unittest
from typing import Callable, Dict, Optional
from typing_extensions import override
from BaseClasses import CollectionState, MultiWorld, Region
@@ -10,7 +8,6 @@ class TestHelpers(unittest.TestCase):
multiworld: MultiWorld
player: int = 1
@override
def setUp(self) -> None:
self.multiworld = MultiWorld(self.player)
self.multiworld.game[self.player] = "helper_test_game"
@@ -41,15 +38,15 @@ class TestHelpers(unittest.TestCase):
"TestRegion1": {"TestRegion2": "connection"},
"TestRegion2": {"TestRegion1": None},
}
reg_exit_set: Dict[str, set[str]] = {
"TestRegion1": {"TestRegion3"}
}
exit_rules: Dict[str, Callable[[CollectionState], bool]] = {
"TestRegion1": lambda state: state.has("test_item", self.player)
}
self.multiworld.regions += [Region(region, self.player, self.multiworld, regions[region]) for region in regions]
with self.subTest("Test Location Creation Helper"):
@@ -76,7 +73,7 @@ class TestHelpers(unittest.TestCase):
entrance_name = exit_name if exit_name else f"{parent} -> {exit_reg}"
self.assertEqual(exit_rules[exit_reg],
self.multiworld.get_entrance(entrance_name, self.player).access_rule)
for region in reg_exit_set:
current_region = self.multiworld.get_region(region, self.player)
current_region.add_exits(reg_exit_set[region])

View File

@@ -39,7 +39,7 @@ class TestImplemented(unittest.TestCase):
"""Tests that if a world creates slot data, it's json serializable."""
for game_name, world_type in AutoWorldRegister.world_types.items():
# has an await for generate_output which isn't being called
if game_name in {"Ocarina of Time"}:
if game_name in {"Ocarina of Time", "Zillion"}:
continue
multiworld = setup_solo_multiworld(world_type)
with self.subTest(game=game_name, seed=multiworld.seed):
@@ -117,12 +117,3 @@ class TestImplemented(unittest.TestCase):
f"\nUnexpectedly reachable locations in sphere {sphere_num}:"
f"\n{reachable_only_with_explicit}")
self.fail("Unreachable")
def test_no_items_or_locations_or_regions_submitted_in_init(self):
"""Test that worlds don't submit items/locations/regions to the multiworld in __init__"""
for game_name, world_type in AutoWorldRegister.world_types.items():
with self.subTest("Game", game=game_name):
multiworld = setup_solo_multiworld(world_type, ())
self.assertEqual(len(multiworld.itempool), 0)
self.assertEqual(len(multiworld.get_locations()), 0)
self.assertEqual(len(multiworld.get_regions()), 0)

View File

@@ -5,7 +5,7 @@ from . import setup_solo_multiworld
class TestWorldMemory(unittest.TestCase):
def test_leak(self) -> None:
def test_leak(self):
"""Tests that worlds don't leak references to MultiWorld or themselves with default options."""
import gc
import weakref

View File

@@ -3,7 +3,7 @@ from worlds.AutoWorld import AutoWorldRegister
class TestNames(unittest.TestCase):
def test_item_names_format(self) -> None:
def test_item_names_format(self):
"""Item names must not be all numeric in order to differentiate between ID and name in !hint"""
for gamename, world_type in AutoWorldRegister.world_types.items():
with self.subTest(game=gamename):
@@ -11,7 +11,7 @@ class TestNames(unittest.TestCase):
self.assertFalse(item_name.isnumeric(),
f"Item name \"{item_name}\" is invalid. It must not be numeric.")
def test_location_name_format(self) -> None:
def test_location_name_format(self):
"""Location names must not be all numeric in order to differentiate between ID and name in !hint_location"""
for gamename, world_type in AutoWorldRegister.world_types.items():
with self.subTest(game=gamename):

View File

@@ -10,7 +10,7 @@ import base64
import enum
import json
import sys
from typing import Any, Sequence
import typing
BIZHAWK_SOCKET_PORT_RANGE_START = 43055
@@ -44,10 +44,10 @@ class SyncError(Exception):
class BizHawkContext:
streams: tuple[asyncio.StreamReader, asyncio.StreamWriter] | None
streams: typing.Optional[typing.Tuple[asyncio.StreamReader, asyncio.StreamWriter]]
connection_status: ConnectionStatus
_lock: asyncio.Lock
_port: int | None
_port: typing.Optional[int]
def __init__(self) -> None:
self.streams = None
@@ -122,12 +122,12 @@ async def get_script_version(ctx: BizHawkContext) -> int:
return int(await ctx._send_message("VERSION"))
async def send_requests(ctx: BizHawkContext, req_list: list[dict[str, Any]]) -> list[dict[str, Any]]:
async def send_requests(ctx: BizHawkContext, req_list: typing.List[typing.Dict[str, typing.Any]]) -> typing.List[typing.Dict[str, typing.Any]]:
"""Sends a list of requests to the BizHawk connector and returns their responses.
It's likely you want to use the wrapper functions instead of this."""
responses = json.loads(await ctx._send_message(json.dumps(req_list)))
errors: list[ConnectorError] = []
errors: typing.List[ConnectorError] = []
for response in responses:
if response["type"] == "ERROR":
@@ -151,7 +151,7 @@ async def ping(ctx: BizHawkContext) -> None:
async def get_hash(ctx: BizHawkContext) -> str:
"""Gets the hash value of the currently loaded ROM"""
"""Gets the system name for the currently loaded ROM"""
res = (await send_requests(ctx, [{"type": "HASH"}]))[0]
if res["type"] != "HASH_RESPONSE":
@@ -160,16 +160,6 @@ async def get_hash(ctx: BizHawkContext) -> str:
return res["value"]
async def get_memory_size(ctx: BizHawkContext, domain: str) -> int:
"""Gets the size in bytes of the specified memory domain"""
res = (await send_requests(ctx, [{"type": "MEMORY_SIZE", "domain": domain}]))[0]
if res["type"] != "MEMORY_SIZE_RESPONSE":
raise SyncError(f"Expected response of type MEMORY_SIZE_RESPONSE but got {res['type']}")
return res["value"]
async def get_system(ctx: BizHawkContext) -> str:
"""Gets the system name for the currently loaded ROM"""
res = (await send_requests(ctx, [{"type": "SYSTEM"}]))[0]
@@ -180,7 +170,7 @@ async def get_system(ctx: BizHawkContext) -> str:
return res["value"]
async def get_cores(ctx: BizHawkContext) -> dict[str, str]:
async def get_cores(ctx: BizHawkContext) -> typing.Dict[str, str]:
"""Gets the preferred cores for systems with multiple cores. Only systems with multiple available cores have
entries."""
res = (await send_requests(ctx, [{"type": "PREFERRED_CORES"}]))[0]
@@ -233,8 +223,8 @@ async def set_message_interval(ctx: BizHawkContext, value: float) -> None:
raise SyncError(f"Expected response of type SET_MESSAGE_INTERVAL_RESPONSE but got {res['type']}")
async def guarded_read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int, str]],
guard_list: Sequence[tuple[int, Sequence[int], str]]) -> list[bytes] | None:
async def guarded_read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tuple[int, int, str]],
guard_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> typing.Optional[typing.List[bytes]]:
"""Reads an array of bytes at 1 or more addresses if and only if every byte in guard_list matches its expected
value.
@@ -262,7 +252,7 @@ async def guarded_read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int,
"domain": domain
} for address, size, domain in read_list])
ret: list[bytes] = []
ret: typing.List[bytes] = []
for item in res:
if item["type"] == "GUARD_RESPONSE":
if not item["value"]:
@@ -276,7 +266,7 @@ async def guarded_read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int,
return ret
async def read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int, str]]) -> list[bytes]:
async def read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tuple[int, int, str]]) -> typing.List[bytes]:
"""Reads data at 1 or more addresses.
Items in `read_list` should be organized `(address, size, domain)` where
@@ -288,8 +278,8 @@ async def read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int, str]]) -
return await guarded_read(ctx, read_list, [])
async def guarded_write(ctx: BizHawkContext, write_list: Sequence[tuple[int, Sequence[int], str]],
guard_list: Sequence[tuple[int, Sequence[int], str]]) -> bool:
async def guarded_write(ctx: BizHawkContext, write_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]],
guard_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> bool:
"""Writes data to 1 or more addresses if and only if every byte in guard_list matches its expected value.
Items in `write_list` should be organized `(address, value, domain)` where
@@ -326,7 +316,7 @@ async def guarded_write(ctx: BizHawkContext, write_list: Sequence[tuple[int, Seq
return True
async def write(ctx: BizHawkContext, write_list: Sequence[tuple[int, Sequence[int], str]]) -> None:
async def write(ctx: BizHawkContext, write_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> None:
"""Writes data to 1 or more addresses.
Items in write_list should be organized `(address, value, domain)` where

View File

@@ -5,7 +5,7 @@ A module containing the BizHawkClient base class and metaclass
from __future__ import annotations
import abc
from typing import TYPE_CHECKING, Any, ClassVar
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple, Union
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess
@@ -24,9 +24,9 @@ components.append(component)
class AutoBizHawkClientRegister(abc.ABCMeta):
game_handlers: ClassVar[dict[tuple[str, ...], dict[str, BizHawkClient]]] = {}
game_handlers: ClassVar[Dict[Tuple[str, ...], Dict[str, BizHawkClient]]] = {}
def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any]) -> AutoBizHawkClientRegister:
def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> AutoBizHawkClientRegister:
new_class = super().__new__(cls, name, bases, namespace)
# Register handler
@@ -54,7 +54,7 @@ class AutoBizHawkClientRegister(abc.ABCMeta):
return new_class
@staticmethod
async def get_handler(ctx: "BizHawkClientContext", system: str) -> BizHawkClient | None:
async def get_handler(ctx: "BizHawkClientContext", system: str) -> Optional[BizHawkClient]:
for systems, handlers in AutoBizHawkClientRegister.game_handlers.items():
if system in systems:
for handler in handlers.values():
@@ -65,13 +65,13 @@ class AutoBizHawkClientRegister(abc.ABCMeta):
class BizHawkClient(abc.ABC, metaclass=AutoBizHawkClientRegister):
system: ClassVar[str | tuple[str, ...]]
system: ClassVar[Union[str, Tuple[str, ...]]]
"""The system(s) that the game this client is for runs on"""
game: ClassVar[str]
"""The game this client is for"""
patch_suffix: ClassVar[str | tuple[str, ...] | None]
patch_suffix: ClassVar[Optional[Union[str, Tuple[str, ...]]]]
"""The file extension(s) this client is meant to open and patch (e.g. ".apz3")"""
@abc.abstractmethod

View File

@@ -6,7 +6,7 @@ checking or launching the client, otherwise it will probably cause circular impo
import asyncio
import enum
import subprocess
from typing import Any
from typing import Any, Dict, Optional
from CommonClient import CommonContext, ClientCommandProcessor, get_base_parser, server_loop, logger, gui_enabled
import Patch
@@ -43,15 +43,15 @@ class BizHawkClientContext(CommonContext):
command_processor = BizHawkClientCommandProcessor
auth_status: AuthStatus
password_requested: bool
client_handler: BizHawkClient | None
slot_data: dict[str, Any] | None = None
rom_hash: str | None = None
client_handler: Optional[BizHawkClient]
slot_data: Optional[Dict[str, Any]] = None
rom_hash: Optional[str] = None
bizhawk_ctx: BizHawkContext
watcher_timeout: float
"""The maximum amount of time the game watcher loop will wait for an update from the server before executing"""
def __init__(self, server_address: str | None, password: str | None):
def __init__(self, server_address: Optional[str], password: Optional[str]):
super().__init__(server_address, password)
self.auth_status = AuthStatus.NOT_AUTHENTICATED
self.password_requested = False
@@ -231,27 +231,20 @@ async def _run_game(rom: str):
)
def _patch_and_run_game(patch_file: str):
async def _patch_and_run_game(patch_file: str):
try:
metadata, output_file = Patch.create_rom_file(patch_file)
Utils.async_start(_run_game(output_file))
return metadata
except Exception as exc:
logger.exception(exc)
return {}
def launch(*launch_args: str) -> None:
def launch(*launch_args) -> None:
async def main():
parser = get_base_parser()
parser.add_argument("patch_file", default="", type=str, nargs="?", help="Path to an Archipelago patch file")
args = parser.parse_args(launch_args)
if args.patch_file != "":
metadata = _patch_and_run_game(args.patch_file)
if "server" in metadata:
args.connect = metadata["server"]
ctx = BizHawkClientContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
@@ -259,6 +252,9 @@ def launch(*launch_args: str) -> None:
ctx.run_gui()
ctx.run_cli()
if args.patch_file != "":
Utils.async_start(_patch_and_run_game(args.patch_file))
watcher_task = asyncio.create_task(_game_watcher(ctx), name="GameWatcher")
try:

View File

@@ -1,8 +1,9 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict
from Options import Choice, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
from dataclasses import dataclass
from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
class FreeincarnateMax(Range):

View File

@@ -1,6 +1,6 @@
from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType
from Options import PerGameCommonOptions
from .Locations import location_table, AdventureLocation, dragon_room_to_region
from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region
def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,

View File

@@ -2,14 +2,14 @@ import hashlib
import json
import os
import zipfile
from typing import Any
import bsdiff4
from typing import Optional, Any
import Utils
from .Locations import AdventureLocation, LocationData
from settings import get_settings
from worlds.Files import APPatch, AutoPatchRegister
from .Locations import LocationData
import bsdiff4
ADVENTUREHASH: str = "157bddb7192754a45372be196797f284"

View File

@@ -1,24 +1,35 @@
import base64
import copy
import itertools
import math
import os
import typing
from typing import ClassVar, Dict, Optional, Tuple
import settings
from BaseClasses import Item, ItemClassification, MultiWorld, Tutorial, LocationProgressType
import typing
from enum import IntFlag
from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple
from BaseClasses import Entrance, Item, ItemClassification, MultiWorld, Region, Tutorial, \
LocationProgressType
from Utils import __version__
from Options import AssembleOptions
from worlds.AutoWorld import WebWorld, World
from worlds.LauncherComponents import Component, components, SuffixIdentifier
from Fill import fill_restrictive
from worlds.generic.Rules import add_rule, set_rule
from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, \
AdventureOptions
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \
AdventureAutoCollectLocation
from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max
from .Locations import location_table, base_location_id, LocationData, get_random_room_in_regions
from .Offsets import static_item_data_location, items_ram_start, static_item_element_size, item_position_table, \
static_first_dragon_index, connector_port_offset, yorgle_speed_data_location, grundle_speed_data_location, \
rhindle_speed_data_location, item_ram_addresses, start_castle_values, start_castle_offset
from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, AdventureOptions
from .Regions import create_regions
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, AdventureAutoCollectLocation
from .Rules import set_rules
from worlds.LauncherComponents import Component, components, SuffixIdentifier
# Adventure
components.append(Component('Adventure Client', 'AdventureClient', file_identifier=SuffixIdentifier('.apadvn')))

View File

@@ -141,12 +141,9 @@ def set_dw_rules(world: "HatInTimeWorld"):
add_dw_rules(world, all_clear)
add_rule(main_stamp, main_objective.access_rule)
add_rule(all_clear, main_objective.access_rule)
# Only set bonus stamp rules to require All Clear if we don't auto complete bonuses
# Only set bonus stamp rules if we don't auto complete bonuses
if not world.options.DWAutoCompleteBonuses and not world.is_bonus_excluded(all_clear.name):
add_rule(bonus_stamps, all_clear.access_rule)
else:
# As soon as the Main Objective is completed, the bonuses auto-complete.
add_rule(bonus_stamps, main_objective.access_rule)
if world.options.DWShuffle:
for i in range(len(world.dw_shuffle)-1):
@@ -346,7 +343,6 @@ def create_enemy_events(world: "HatInTimeWorld"):
def set_enemy_rules(world: "HatInTimeWorld"):
no_tourist = "Camera Tourist" in world.excluded_dws or "Camera Tourist" in world.excluded_bonuses
difficulty = get_difficulty(world)
for enemy, regions in hit_list.items():
if no_tourist and enemy in bosses:
@@ -376,14 +372,6 @@ def set_enemy_rules(world: "HatInTimeWorld"):
or state.has("Zipline Unlock - The Lava Cake Path", world.player)
or state.has("Zipline Unlock - The Windmill Path", world.player))
elif enemy == "Toilet":
if area == "Toilet of Doom":
# The boss firewall is in the way and can only be skipped on Expert logic using a cherry hover.
add_rule(event, lambda state: has_paintings(state, world, 1, allow_skip=difficulty == Difficulty.EXPERT))
if difficulty < Difficulty.HARD:
# Hard logic and above can cross the boss arena gap with a cherry bridge.
add_rule(event, lambda state: can_use_hookshot(state, world))
elif enemy == "Director":
if area == "Dead Bird Studio Basement":
add_rule(event, lambda state: can_use_hookshot(state, world))
@@ -442,7 +430,7 @@ hit_list = {
# Bosses
"Mafia Boss": ["Down with the Mafia!", "Encore! Encore!", "Boss Rush"],
"Director": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
"Conductor": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
"Toilet": ["Toilet of Doom", "Boss Rush"],
"Snatcher": ["Your Contract has Expired", "Breaching the Contract", "Boss Rush",
@@ -466,7 +454,7 @@ triple_enemy_locations = [
bosses = [
"Mafia Boss",
"Director",
"Conductor",
"Toilet",
"Snatcher",
"Toxic Flower",

View File

@@ -264,6 +264,7 @@ ahit_locations = {
required_hats=[HatType.DWELLER], paintings=3),
"Subcon Forest - Tall Tree Hookshot Swing": LocData(2000324766, "Subcon Forest Area",
required_hats=[HatType.DWELLER],
hookshot=True,
paintings=3),
@@ -322,7 +323,7 @@ ahit_locations = {
"Alpine Skyline - The Twilight Path": LocData(2000334434, "Alpine Skyline Area", required_hats=[HatType.DWELLER]),
"Alpine Skyline - The Twilight Bell: Wide Purple Platform": LocData(2000336478, "The Twilight Bell"),
"Alpine Skyline - The Twilight Bell: Ice Platform": LocData(2000335826, "The Twilight Bell"),
"Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area (TIHS)", hookshot=True),
"Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area"),
"Alpine Skyline - Windy Passage": LocData(2000334776, "Alpine Skyline Area (TIHS)", hookshot=True),
"Alpine Skyline - The Windmill: Inside Pon Cluster": LocData(2000336395, "The Windmill"),
"Alpine Skyline - The Windmill: Entrance": LocData(2000335783, "The Windmill"),
@@ -406,7 +407,7 @@ act_completions = {
hit_type=HitType.umbrella_or_brewing, hookshot=True, paintings=1),
"Act Completion (Queen Vanessa's Manor)": LocData(2000312017, "Queen Vanessa's Manor",
hit_type=HitType.dweller_bell, paintings=1),
hit_type=HitType.umbrella, paintings=1),
"Act Completion (Mail Delivery Service)": LocData(2000312032, "Mail Delivery Service",
required_hats=[HatType.SPRINT]),
@@ -877,7 +878,7 @@ snatcher_coins = {
dlc_flags=HatDLC.death_wish),
"Snatcher Coin - Top of HQ (DW: BTH)": LocData(0, "Beat the Heat", snatcher_coin="Snatcher Coin - Top of HQ",
hit_type=HitType.umbrella, dlc_flags=HatDLC.death_wish),
dlc_flags=HatDLC.death_wish),
"Snatcher Coin - Top of Tower": LocData(0, "Mafia Town Area (HUMT)", snatcher_coin="Snatcher Coin - Top of Tower",
dlc_flags=HatDLC.death_wish),

View File

@@ -414,7 +414,7 @@ def set_moderate_rules(world: "HatInTimeWorld"):
# Moderate: Mystifying Time Mesa time trial without hats
set_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player),
lambda state: True)
lambda state: can_use_hookshot(state, world))
# Moderate: Goat Refinery from TIHS with Sprint only
add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player),
@@ -493,6 +493,9 @@ def set_hard_rules(world: "HatInTimeWorld"):
lambda state: has_paintings(state, world, 3, True))
# SDJ
add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
lambda state: can_use_hat(state, world, HatType.SPRINT) and has_paintings(state, world, 2), "or")
add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player),
lambda state: can_use_hat(state, world, HatType.SPRINT), "or")
@@ -530,10 +533,7 @@ def set_expert_rules(world: "HatInTimeWorld"):
# Expert: Mafia Town - Above Boats, Top of Lighthouse, and Hot Air Balloon with nothing
set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True)
set_rule(world.multiworld.get_location("Mafia Town - Top of Lighthouse", world.player), lambda state: True)
# There are not enough buckets/beach balls to bucket/ball hover in Heating Up Mafia Town, so any other Mafia Town
# act is required.
add_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player),
lambda state: state.can_reach_region("Mafia Town Area", world.player), "or")
set_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player), lambda state: True)
# Expert: Clear Dead Bird Studio with nothing
for loc in world.multiworld.get_region("Dead Bird Studio - Post Elevator Area", world.player).locations:
@@ -590,7 +590,7 @@ def set_expert_rules(world: "HatInTimeWorld"):
if world.is_dlc2():
# Expert: clear Rush Hour with nothing
if world.options.NoTicketSkips != NoTicketSkips.option_true:
if not world.options.NoTicketSkips:
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True)
else:
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
@@ -739,7 +739,7 @@ def set_dlc1_rules(world: "HatInTimeWorld"):
# This particular item isn't present in Act 3 for some reason, yes in vanilla too
add_rule(world.multiworld.get_location("The Arctic Cruise - Toilet", world.player),
lambda state: (state.can_reach("Bon Voyage!", "Region", world.player) and can_use_hookshot(state, world))
lambda state: state.can_reach("Bon Voyage!", "Region", world.player)
or state.can_reach("Ship Shape", "Region", world.player))

View File

@@ -119,9 +119,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
def VitreousDefeatRule(state, player: int) -> bool:
return ((can_shoot_arrows(state, player) and can_use_bombs(state, player, 10))
or can_shoot_arrows(state, player, 35) or state.has("Silver Bow", player)
or has_melee_weapon(state, player))
return can_shoot_arrows(state, player) or has_melee_weapon(state, player)
def TrinexxDefeatRule(state, player: int) -> bool:

View File

@@ -464,7 +464,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
snes_logger.info(f"Discarding recent {len(new_locations)} checks as ROM Status has changed.")
return False
else:
await ctx.check_locations(new_locations)
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": new_locations}])
await snes_flush_writes(ctx)
return True

View File

@@ -484,7 +484,8 @@ def generate_itempool(world):
if multiworld.randomize_cost_types[player]:
# Heart and Arrow costs require all Heart Container/Pieces and Arrow Upgrades to be advancement items for logic
for item in items:
if item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart"):
if (item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart")
or "Arrow Upgrade" in item.name):
item.classification = ItemClassification.progression
else:
# Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
@@ -712,7 +713,7 @@ def get_pool_core(world, player: int):
pool.remove("Rupees (20)")
if retro_bow:
replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (70)'}
replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (50)'}
pool = ['Rupees (5)' if item in replace else item for item in pool]
if world.small_key_shuffle[player] == small_key_shuffle.option_universal:
pool.extend(diff.universal_keys)

View File

@@ -7,7 +7,7 @@ from worlds.AutoWorld import World
def GetBeemizerItem(world, player: int, item):
item_name = item if isinstance(item, str) else item.name
if item_name not in trap_replaceable or player in world.groups:
if item_name not in trap_replaceable:
return item
# first roll - replaceable item should be replaced, within beemizer_total_chance
@@ -110,9 +110,9 @@ item_table = {'Bow': ItemData(IC.progression, None, 0x0B, 'You have\nchosen the\
'Crystal 7': ItemData(IC.progression, 'Crystal', (0x08, 0x34, 0x64, 0x40, 0x7C, 0x06), None, None, None, None, None, None, "a blue crystal"),
'Single Arrow': ItemData(IC.filler, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
'Arrows (10)': ItemData(IC.filler, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack','stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again','ten arrows'),
'Arrow Upgrade (+10)': ItemData(IC.progression_skip_balancing, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (+5)': ItemData(IC.progression_skip_balancing, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (70)': ItemData(IC.progression_skip_balancing, None, 0x4D, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (+10)': ItemData(IC.useful, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (+5)': ItemData(IC.useful, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (70)': ItemData(IC.useful, None, 0x4D, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Single Bomb': ItemData(IC.filler, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
'Bombs (3)': ItemData(IC.filler, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
'Bombs (10)': ItemData(IC.filler, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),

View File

@@ -170,8 +170,7 @@ def push_shop_inventories(multiworld):
# Retro Bow arrows will already have been pushed
if (not multiworld.retro_bow[location.player]) or ((item_name, location.item.player)
!= ("Single Arrow", location.player)):
location.shop.push_inventory(location.shop_slot, item_name,
round(location.shop_price * get_price_modifier(location.item)),
location.shop.push_inventory(location.shop_slot, item_name, location.shop_price,
1, location.item.player if location.item.player != location.player else 0,
location.shop_price_type)
location.shop_price = location.shop.inventory[location.shop_slot]["price"] = min(location.shop_price,

View File

@@ -15,18 +15,18 @@ def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bo
def can_buy_unlimited(state: CollectionState, item: str, player: int) -> bool:
return any(shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(state) for
shop in state.multiworld.shops)
shop in state.multiworld.shops)
def can_buy(state: CollectionState, item: str, player: int) -> bool:
return any(shop.region.player == player and shop.has(item) and shop.region.can_reach(state) for
shop in state.multiworld.shops)
shop in state.multiworld.shops)
def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool:
def can_shoot_arrows(state: CollectionState, player: int) -> bool:
if state.multiworld.retro_bow[player]:
return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_buy(state, 'Single Arrow', player)
return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_hold_arrows(state, player, count)
return state.has('Bow', player) or state.has('Silver Bow', player)
def has_triforce_pieces(state: CollectionState, player: int) -> bool:
@@ -61,13 +61,13 @@ def heart_count(state: CollectionState, player: int) -> int:
# Warning: This only considers items that are marked as advancement items
diff = state.multiworld.worlds[player].difficulty_requirements
return min(state.count('Boss Heart Container', player), diff.boss_heart_container_limit) \
+ state.count('Sanctuary Heart Container', player) \
+ state.count('Sanctuary Heart Container', player) \
+ min(state.count('Piece of Heart', player), diff.heart_piece_limit) // 4 \
+ 3 # starting hearts
+ 3 # starting hearts
def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16,
fullrefill: bool = False): # This reflects the total magic Link has, not the total extra he has.
fullrefill: bool = False): # This reflects the total magic Link has, not the total extra he has.
basemagic = 8
if state.has('Magic Upgrade (1/4)', player):
basemagic = 32
@@ -84,18 +84,11 @@ def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16,
def can_hold_arrows(state: CollectionState, player: int, quantity: int):
if state.multiworld.worlds[player].options.shuffle_capacity_upgrades:
if quantity == 0:
return True
if state.has("Arrow Upgrade (70)", player):
arrows = 70
else:
arrows = (30 + (state.count("Arrow Upgrade (+5)", player) * 5)
+ (state.count("Arrow Upgrade (+10)", player) * 10))
# Arrow Upgrade (+5) beyond the 6th gives +10
arrows += max(0, ((state.count("Arrow Upgrade (+5)", player) - 6) * 10))
return min(70, arrows) >= quantity
return quantity <= 30 or state.has("Capacity Upgrade Shop", player)
arrows = 30 + ((state.count("Arrow Upgrade (+5)", player) * 5) + (state.count("Arrow Upgrade (+10)", player) * 10)
+ (state.count("Bomb Upgrade (50)", player) * 50))
# Arrow Upgrade (+5) beyond the 6th gives +10
arrows += max(0, ((state.count("Arrow Upgrade (+5)", player) - 6) * 10))
return min(70, arrows) >= quantity
def can_use_bombs(state: CollectionState, player: int, quantity: int = 1) -> bool:
@@ -153,19 +146,19 @@ def can_get_good_bee(state: CollectionState, player: int) -> bool:
def can_retrieve_tablet(state: CollectionState, player: int) -> bool:
return state.has('Book of Mudora', player) and (has_beam_sword(state, player) or
(state.multiworld.swordless[player] and
state.has("Hammer", player)))
state.has("Hammer", player)))
def has_sword(state: CollectionState, player: int) -> bool:
return state.has('Fighter Sword', player) \
or state.has('Master Sword', player) \
or state.has('Tempered Sword', player) \
or state.has('Golden Sword', player)
or state.has('Master Sword', player) \
or state.has('Tempered Sword', player) \
or state.has('Golden Sword', player)
def has_beam_sword(state: CollectionState, player: int) -> bool:
return state.has('Master Sword', player) or state.has('Tempered Sword', player) or state.has('Golden Sword',
player)
player)
def has_melee_weapon(state: CollectionState, player: int) -> bool:
@@ -178,9 +171,9 @@ def has_fire_source(state: CollectionState, player: int) -> bool:
def can_melt_things(state: CollectionState, player: int) -> bool:
return state.has('Fire Rod', player) or \
(state.has('Bombos', player) and
(state.multiworld.swordless[player] or
has_sword(state, player)))
(state.has('Bombos', player) and
(state.multiworld.swordless[player] or
has_sword(state, player)))
def has_misery_mire_medallion(state: CollectionState, player: int) -> bool:

View File

@@ -1,123 +1,224 @@
# Guía de instalación para A Link to the Past Randomizer Multiworld
<div id="tutorial-video-container">
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/mJKEHaiyR_Y" frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
</iframe>
</div>
## Software requerido
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
- [SNI](https://github.com/alttpo/sni/releases). Esto está incluido automáticamente en la instalación de Archipelago.
- SNI no es compatible con (Q)Usb2Snes.
- Hardware o software capaz de cargar y ejecutar archivos de ROM de SNES, por ejemplo:
- Un emulador capaz de conectarse a SNI
([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases),
[BSNES-plus](https://github.com/black-sliver/bsnes-plus),
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
- [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Incluido en Multiworld Utilities)
- Hardware o software capaz de cargar y ejecutar archivos de ROM de SNES
- Un emulador capaz de ejecutar scripts Lua
([snes9x rr](https://github.com/gocha/snes9x-rr/releases),
[BizHawk](https://tasvideos.org/BizHawk), o
[RetroArch](https://retroarch.com?page=platforms) 1.10.1 o más nuevo).
- Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), u otro hardware compatible. **nota:
Las SNES minis modificadas no tienen soporte de SNI. Algunos usuarios dicen haber tenido éxito con Qusb2Snes para esta consola,
pero no tiene soporte.**
[RetroArch](https://retroarch.com?page=platforms) 1.10.1 o más nuevo). O,
- Un flashcart SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), o otro hardware compatible
- Tu archivo ROM japones v1.0, probablemente se llame `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
## Procedimiento de instalación
1. Descarga e instala [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
**El archivo del instalador se encuentra en la sección de assets al final de la información de version**.
2. La primera vez que realices una generación local o parchees tu juego, se te pedirá que ubiques tu archivo ROM base.
Este es tu archivo ROM de Link to the Past japonés. Esto sólo debe hacerse una vez.
4. Si estás usando un emulador, deberías de asignar tu emulador con compatibilidad con Lua como el programa por defecto para abrir archivos
ROM.
1. Extrae la carpeta de tu emulador al Escritorio, o algún otro sitio que vayas a recordar.
2. Haz click derecho en un archivo ROM y selecciona **Abrir con...**
3. Marca la casilla junto a **Usar siempre este programa para abrir archivos .sfc**
4. Baja al final de la lista y haz click en el texto gris **Buscar otro programa en este PC**
5. Busca el archivo `.exe` de tu emulador y haz click en **Abrir**. Este archivo debería de encontrarse dentro de la carpeta que
extrajiste en el paso uno.
### Instalación en Windows
1. Descarga e instala MultiWorld Utilities desde el enlace anterior, asegurando que instalamos la versión más reciente.
**El archivo esta localizado en la sección "assets" en la parte inferior de la información de versión**. Si tu
intención es jugar la versión normal de multiworld, necesitarás el archivo `Setup.Archipelago.exe`
- Si estas interesado en jugar la variante que aleatoriza las puertas internas de las mazmorras, necesitaras bajar '
Setup.BerserkerMultiWorld.Doors.exe'
- Durante el proceso de instalación, se te pedirá donde esta situado tu archivo ROM japonés v1.0. Si ya habías
instalado este software con anterioridad y simplemente estas actualizando, no se te pedirá la localización del
archivo una segunda vez.
- Puede ser que el programa pida la instalación de Microsoft Visual C++. Si ya lo tienes en tu ordenador (
posiblemente por que un juego de Steam ya lo haya instalado), el instalador no te pedirá su instalación.
2. Si estas usando un emulador, deberías asignar la versión capaz de ejecutar scripts Lua como programa por defecto para
lanzar ficheros de ROM de SNES.
1. Extrae tu emulador al escritorio, o cualquier sitio que después recuerdes.
2. Haz click derecho en un fichero de ROM (ha de tener la extensión sfc) y selecciona **Abrir con...**
3. Marca la opción **Usar siempre esta aplicación para abrir los archivos .sfc**
4. Baja hasta el final de la lista y haz click en la opción **Buscar otra aplicación en el equipo** (Si usas Windows
10 es posible que debas hacer click en **Más aplicaciones**)
5. Busca el archivo .exe de tu emulador y haz click en **Abrir**. Este archivo debe estar en el directorio donde
extrajiste en el paso 1.
### Instalación en Macintosh
- ¡Necesitamos voluntarios para rellenar esta seccion! Contactad con **Farrak Kilhn** (en inglés) en Discord si queréis
ayudar.
## Configurar tu archivo YAML
### Que es un archivo YAML y por qué necesito uno?
Tu archivo YAML contiene un conjunto de opciones de configuración que proveen al generador con información sobre como
debe generar tu juego. Cada jugador en una partida de multiworld proveerá su propio fichero YAML. Esta configuración
permite que cada jugador disfrute de una experiencia personalizada a su gusto, y cada jugador dentro de la misma partida
de multiworld puede tener diferentes opciones.
### Donde puedo obtener un fichero YAML?
La página "[Generate Game](/games/A%20Link%20to%20the%20Past/player-options)" en el sitio web te permite configurar tu
configuración personal y descargar un fichero "YAML".
### Configuración YAML avanzada
Una version mas avanzada del fichero Yaml puede ser creada usando la pagina
["Weighted settings"](/games/A Link to the Past/weighted-options),
la cual te permite tener almacenadas hasta 3 preajustes. La pagina "Weighted Settings" tiene muchas opciones
representadas con controles deslizantes. Esto permite elegir cuan probable los valores de una categoría pueden ser
elegidos sobre otros de la misma.
Por ejemplo, imagina que el generador crea un cubo llamado "map_shuffle", y pone trozos de papel doblado en él por cada
sub-opción. Ademas imaginemos que tu valor elegido para "on" es 20 y el elegido para "off" es 40.
Por tanto, en este ejemplo, habrán 60 trozos de papel. 20 para "on" y 40 para "off". Cuando el generador esta decidiendo
si activar o no "map shuffle" para tu partida, meterá la mano en el cubo y sacara un trozo de papel al azar. En este
ejemplo, es mucho mas probable (2 de cada 3 veces (40/60)) que "map shuffle" esté desactivado.
Si quieres que una opción no pueda ser escogida, simplemente asigna el valor 0 a dicha opción. Recuerda que cada opción
debe tener al menos un valor mayor que cero, si no la generación fallará.
### Verificando tu archivo YAML
Si quieres validar que tu fichero YAML para asegurarte que funciona correctamente, puedes hacerlo en la pagina
[YAML Validator](/check).
## Generar una partida para un jugador
1. Navega a [la pagina Generate game](/games/A%20Link%20to%20the%20Past/player-options), configura tus opciones, haz
click en el boton "Generate game".
2. Se te redigirá a una pagina "Seed Info", donde puedes descargar tu archivo de parche.
3. Haz doble click en tu fichero de parche, y el emulador debería ejecutar tu juego automáticamente. Como el Cliente no
es necesario para partidas de un jugador, puedes cerrarlo junto a la pagina web (que tiene como titulo "Multiworld
WebUI") que se ha abierto automáticamente.
## Unirse a una partida MultiWorld
### Obtener el fichero de parche y crea tu ROM
Cuando te unas a una partida multiworld, se te pedirá enviarle tu archivo de configuración a quien quiera que esté creando. Una vez eso
Cuando te unes a una partida multiworld, debes proveer tu fichero YAML a quien sea el creador de la partida. Una vez
este hecho, el creador te devolverá un enlace para descargar el parche o un fichero zip conteniendo todos los ficheros
de parche de la partida. Tu fichero de parche debe de tener la extensión `.aplttp`.
de parche de la partida Tu fichero de parche debe tener la extensión `.aplttp`.
Pon tu fichero de parche en el escritorio o en algún sitio conveniente, y hazle doble click. Esto debería ejecutar
automáticamente el cliente, y además creará la rom en el mismo directorio donde este el fichero de parche.
Pon tu fichero de parche en el escritorio o en algún sitio conveniente, y haz doble click. Esto debería ejecutar
automáticamente el cliente, y ademas creara la rom en el mismo directorio donde este el fichero de parche.
### Conectar al cliente
#### Con emulador
Cuando el cliente se lance automáticamente, SNI debería de ejecutarse en segundo plano. Si es la
primera vez que se ejecuta, tal vez se te pida permitir que se comunique a través del firewall de Windows
#### snes9x-nwa
1. Haz click en el menu Network y marca 'Enable Emu Network Control
2. Carga tu archivo ROM si no lo habías hecho antes
Cuando el cliente se lance automáticamente, QUsb2Snes debería haberse ejecutado también. Si es la primera vez que lo
ejecutas, puedes ser que el firewall de Windows te pregunte si le permites la comunicación.
##### snes9x-rr
1. Carga tu fichero ROM, si no lo has hecho ya
1. Carga tu fichero de ROM, si no lo has hecho ya
2. Abre el menu "File" y situa el raton en **Lua Scripting**
3. Haz click en **New Lua Script Window...**
4. En la nueva ventana, haz click en **Browse...**
5. Selecciona el archivo lua conector incluido con tu cliente
- Busca en la carpeta de Archipelago `/SNI/lua/`.
6. Si ves un error mientras carga el script que dice `socket.dll missing` o algo similar, ve a la carpeta de
el lua que estas usando en tu gestor de archivos y copia el `socket.dll` a la raíz de tu instalación de snes9x.
##### BNES-Plus
1. Cargue su archivo ROM si aún no se ha cargado.
2. El emulador debería conectarse automáticamente mientras SNI se está ejecutando.
5. Navega hacia el directorio donde este situado snes9x-rr, entra en el directorio `lua`, y
escoge `multibridge.lua`
6. Observa que se ha asignado un nombre al dispositivo, y el cliente muestra "SNES Device: Connected", con el mismo
nombre en la esquina superior izquierda.
##### BizHawk
1. Asegurate que se ha cargado el núcleo BSNES. Se hace en la barra de menú principal, bajo:
- (≤ 2.8) `Config``Cores``SNES``BSNES`
- (≥ 2.9) `Config``Preferred Cores``SNES``BSNESv115+`
1. Asegurate que se ha cargado el nucleo BSNES. Debes hacer esto en el menu Tools y siguiento estas opciones:
`Config --> Cores --> SNES --> BSNES`
Una vez cambiado el nucleo cargado, BizHawk ha de ser reiniciado.
2. Carga tu fichero de ROM, si no lo has hecho ya.
Si has cambiado tu preferencia de núcleo tras haber cargado la ROM, no te olvides de volverlo a cargar (atajo por defecto: Ctrl+R).
3. Arrastra el archivo `Connector.lua` que has descargado a la ventana principal de EmuHawk.
- Busca en la carpeta de Archipelago `/SNI/lua/`.
- También podrías abrir la consola de Lua manualmente, hacer click en `Script``Open Script`, e ir a `Connector.lua`
con el selector de archivos.
3. Haz click en el menu Tools y en la opción **Lua Console**
4. Haz click en el botón para abrir un nuevo script Lua.
5. Navega al directorio de instalación de MultiWorld Utilities, y en los siguiente directorios:
`QUsb2Snes/Qusb2Snes/LuaBridge`
6. Selecciona `luabridge.lua` y haz click en Abrir.
7. Observa que se ha asignado un nombre al dispositivo, y el cliente muestra "SNES Device: Connected", con el mismo
nombre en la esquina superior izquierda.
##### RetroArch 1.10.1 o más nuevo
Sólo hay que seguir estos pasos una vez.
Sólo hay que segiur estos pasos una vez.
1. Comienza en la pantalla del menú principal de RetroArch.
2. Ve a Ajustes --> Interfaz de usario. Configura "Mostrar ajustes avanzados" en ON.
3. Ve a Ajustes --> Red. Pon "Comandos de red" en ON. (Se encuentra bajo Request Device 16.) Deja en 55355 el valor por defecto,
el Puerto de comandos de red.
3. Ve a Ajustes --> Red. Configura "Comandos de red" en ON. (Se encuentra bajo Request Device 16.) Deja en 55355 (el
default) el Puerto de comandos de red.
![Captura de pantalla del ajuste Comandos de red](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png)
4. Ve a Menú principal --> Actualizador en línea --> Descargador de núcleos. Desplázate y selecciona "Nintendo - SNES /
SFC (bsnes-mercury Performance)".
Cuando cargas un ROM, asegúrate de seleccionar un núcleo **bsnes-mercury**. Estos son los únicos núcleos que permiten
Cuando cargas un ROM, asegúrate de seleccionar un núcleo **bsnes-mercury**. Estos son los sólos núcleos que permiten
que herramientas externas lean datos del ROM.
#### Con Hardware
Esta guía asume que ya has descargado el firmware correcto para tu dispositivo. Si no lo has hecho ya, por favor hazlo ahora. Los
Esta guía asume que ya has descargado el firmware correcto para tu dispositivo. Si no lo has hecho ya, hazlo ahora. Los
usuarios de SD2SNES y FXPak Pro pueden descargar el firmware apropiado
[aqui](https://github.com/RedGuyyyy/sd2snes/releases). Puede que los usuarios de otros dispositivos encuentren informacion útil
[aqui](https://github.com/RedGuyyyy/sd2snes/releases). Los usuarios de otros dispositivos pueden encontrar información
[en esta página](http://usb2snes.com/#supported-platforms).
1. Cierra tu emulador, el cual debe haberse autoejecutado.
2. Enciende tu dispositivo y carga la ROM.
2. Cierra QUsb2Snes, el cual fue ejecutado junto al cliente.
3. Ejecuta la version correcta de QUsb2Snes (v0.7.16).
4. Enciende tu dispositivo y carga la ROM.
5. Observa en el cliente que ahora muestra "SNES Device: Connected", y aparece el nombre del dispositivo.
### Conecta al Servidor Archipelago
### Conecta al MultiServer
El fichero de parche que ha lanzado el cliente debería de haberte conectado automaticamente al MultiServer. Sin embargo hay algunas
razones por las que puede que esto no suceda, como que la partida este hospedada en la página web pero generada en otra parte. Si la
ventana del cliente muestra "Server Status: Not Connected", simplemente preguntale al creador de la partida la dirección
del servidor, cópiala en el campo "Server" y presiona Enter.
El fichero de parche que ha lanzado el cliente debe haberte conectado automaticamente al MultiServer. Hay algunas
razonas por las que esto puede que no pase, incluyendo que el juego este hospedado en el sitio web pero se genero en
algún otro sitio. Si el cliente muestra "Server Status: Not Connected", preguntale al creador de la partida la dirección
del servidor, copiala en el campo "Server" y presiona Enter.
El cliente intentará conectarse a esta nueva dirección, y debería mostrar "Server Status: Connected" momentáneamente.
El cliente intentara conectarse a esta nueva dirección, y debería mostrar "Server Status: Connected" en algún momento.
Si el cliente no se conecta al cabo de un rato, puede ser que necesites refrescar la pagina web.
### Jugar al juego
### Jugando
Cuando el cliente muestre tanto el dispositivo SNES como el servidor como conectados, estas listo para empezar a jugar. Felicidades por
haberte unido a una partida multiworld con exito! Puedes ejecutar varios comandos en tu cliente. Para mas informacion
acerca de estos comando puedes usar `/help` para comandos locales del cliente y `!help` para comandos de servidor.
Cuando ambos SNES Device and Server aparezcan como "connected", estas listo para empezar a jugar. Felicidades por unirte
satisfactoriamente a una partida de multiworld!
## Hospedando una partida de multiworld
La manera recomendad para hospedar una partida es usar el servicio proveído en
[el sitio web](/generate). El proceso es relativamente sencillo:
1. Recolecta los ficheros YAML de todos los jugadores que participen.
2. Crea un fichero ZIP conteniendo esos ficheros.
3. Carga el fichero zip en el sitio web enlazado anteriormente.
4. Espera a que la seed sea generada.
5. Cuando esto acabe, se te redigirá a una pagina titulada "Seed Info".
6. Haz click en "Create New Room". Esto te llevara a la pagina del servidor. Pasa el enlace a esta pagina a los
jugadores para que puedan descargar los ficheros de parche de ahi.
**Nota:** Los ficheros de parche de esta pagina permiten a los jugadores conectarse al servidor automaticamente,
mientras que los de la pagina "Seed info" no.
7. Hay un enlace a un MultiWorld Tracker en la parte superior de la pagina de la sala. Deberías pasar también este
enlace a los jugadores para que puedan ver el progreso de la partida. A los observadores también se les puede pasar
este enlace.
8. Una vez todos los jugadores se han unido, podeis empezar a jugar.
## Auto-Tracking
Si deseas usar auto-tracking para tu partida, varios programas ofrecen esta funcionalidad.
El programa recomentdado actualmente es:
[OpenTracker](https://github.com/trippsc2/OpenTracker/releases).
### Instalación
1. Descarga el fichero de instalacion apropiado para tu ordenador (Usuarios de windows quieren el fichero ".msi").
2. Durante el proceso de insatalación, puede que se te pida instalar Microsoft Visual Studio Build Tools. Un enlace este
programa se muestra durante la proceso, y debe ser ejecutado manualmente.
### Activar auto-tracking
1. Con OpenTracker ejecutado, haz click en el menu Tracking en la parte superior de la ventana, y elige **
AutoTracker...**
2. Click the **Get Devices** button
3. Selecciona tu "SNES device" de la lista
4. Si quieres que las llaves y los objetos de mazmorra tambien sean marcados, activa la caja con nombre **Race Illegal
Tracking**
5. Haz click en el boton **Start Autotracking**
6. Cierra la ventana AutoTracker, ya que deja de ser necesaria

View File

@@ -77,5 +77,5 @@ class TestMiseryMire(TestDungeon):
["Misery Mire - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Sword', 'Pegasus Boots']],
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Hammer', 'Pegasus Boots']],
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Arrow Upgrade (+5)', 'Pegasus Boots']],
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Pegasus Boots']],
])

View File

@@ -93,7 +93,7 @@ class AquariaWorld(World):
options: AquariaOptions
"Every options of the world"
regions: AquariaRegions | None
regions: AquariaRegions
"Used to manage Regions"
exclude: List[str]
@@ -101,17 +101,10 @@ class AquariaWorld(World):
def __init__(self, multiworld: MultiWorld, player: int):
"""Initialisation of the Aquaria World"""
super(AquariaWorld, self).__init__(multiworld, player)
self.regions = None
self.regions = AquariaRegions(multiworld, player)
self.ingredients_substitution = []
self.exclude = []
def generate_early(self) -> None:
"""
Run before any general steps of the MultiWorld other than options. Useful for getting and adjusting option
results and determining layouts for entrance rando etc. start inventory gets pushed after this step.
"""
self.regions = AquariaRegions(self.multiworld, self.player)
def create_regions(self) -> None:
"""
Create every Region in `regions`

View File

@@ -103,9 +103,6 @@ class BlasphemousWorld(World):
if not self.options.wall_climb_shuffle:
self.multiworld.push_precollected(self.create_item("Wall Climb Ability"))
if self.options.thorn_shuffle == "local_only":
self.options.local_items.value.add("Thorn Upgrade")
if not self.options.boots_of_pleading:
self.disabled_locations.append("RE401")
@@ -203,6 +200,9 @@ class BlasphemousWorld(World):
if not self.options.skill_randomizer:
self.place_items_from_dict(skill_dict)
if self.options.thorn_shuffle == "local_only":
self.options.local_items.value.add("Thorn Upgrade")
def place_items_from_set(self, location_set: Set[str], name: str):

View File

@@ -1,4 +1,5 @@
import logging
import asyncio
from NetUtils import ClientStatus, color
from worlds.AutoSNIClient import SNIClient
@@ -31,7 +32,7 @@ class DKC3SNIClient(SNIClient):
async def validate_rom(self, ctx):
from SNIClient import snes_read
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
rom_name = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE)
if rom_name is None or rom_name == bytes([0] * ROMHASH_SIZE) or rom_name[:2] != b"D3":

View File

@@ -1,6 +1,6 @@
import typing
from BaseClasses import Item
from BaseClasses import Item, ItemClassification
from .Names import ItemName

View File

@@ -1,6 +1,7 @@
from dataclasses import dataclass
import typing
from Options import Choice, Range, Toggle, DefaultOnToggle, OptionGroup, PerGameCommonOptions
from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionGroup, PerGameCommonOptions
class Goal(Choice):

View File

@@ -1,9 +1,10 @@
import typing
from BaseClasses import Region, Entrance
from worlds.AutoWorld import World
from BaseClasses import MultiWorld, Region, Entrance
from .Items import DKC3Item
from .Locations import DKC3Location
from .Names import LocationName, ItemName
from worlds.AutoWorld import World
def create_regions(world: World, active_locations):

View File

@@ -2,6 +2,7 @@ import Utils
from Utils import read_snes_rom
from worlds.AutoWorld import World
from worlds.Files import APDeltaPatch
from .Locations import lookup_id_to_name, all_locations
from .Levels import level_list, level_dict
USHASH = '120abf304f0c40fe059f6a192ed4f947'
@@ -435,7 +436,7 @@ level_music_ids = [
class LocalRom:
def __init__(self, file, name=None, hash=None):
def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None):
self.name = name
self.hash = hash
self.orig_buffer = None

View File

@@ -1,8 +1,8 @@
import math
from worlds.AutoWorld import World
from worlds.generic.Rules import add_rule
from .Names import LocationName, ItemName
from worlds.AutoWorld import LogicMixin, World
from worlds.generic.Rules import add_rule, set_rule
def set_rules(world: World):

View File

@@ -1,13 +1,15 @@
import dataclasses
import math
import os
import threading
import typing
import math
import threading
import settings
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
from Options import PerGameCommonOptions
import Patch
import settings
from worlds.AutoWorld import WebWorld, World
from .Client import DKC3SNIClient
from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table
from .Levels import level_list

View File

@@ -234,7 +234,8 @@ async def game_watcher(ctx: FactorioContext):
f"Connected Multiworld is not the expected one {data['seed_name']} != {ctx.seed_name}")
else:
data = data["info"]
research_data: set[int] = {int(tech_name.split("-")[1]) for tech_name in data["research_done"]}
research_data = data["research_done"]
research_data = {int(tech_name.split("-")[1]) for tech_name in research_data}
victory = data["victory"]
await ctx.update_death_link(data["death_link"])
ctx.multiplayer = data.get("multiplayer", False)
@@ -248,7 +249,7 @@ async def game_watcher(ctx: FactorioContext):
f"New researches done: "
f"{[ctx.location_names.lookup_in_game(rid) for rid in research_data - ctx.locations_checked]}")
ctx.locations_checked = research_data
await ctx.check_locations(research_data)
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
death_link_tick = data.get("death_link_tick", 0)
if death_link_tick != ctx.death_link_tick:
ctx.death_link_tick = death_link_tick

View File

@@ -3,23 +3,13 @@ from __future__ import annotations
from dataclasses import dataclass
import typing
from schema import Schema, Optional, And, Or, SchemaError
from schema import Schema, Optional, And, Or
from Options import Choice, OptionDict, OptionSet, DefaultOnToggle, Range, DeathLink, Toggle, \
StartInventoryPool, PerGameCommonOptions, OptionGroup
# schema helpers
class FloatRange:
def __init__(self, low, high):
self._low = low
self._high = high
def validate(self, value):
if not isinstance(value, (float, int)):
raise SchemaError(f"should be instance of float or int, but was {value!r}")
if not self._low <= value <= self._high:
raise SchemaError(f"{value} is not between {self._low} and {self._high}")
FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high)
LuaBool = Or(bool, And(int, lambda n: n in (0, 1)))

View File

@@ -717,10 +717,8 @@ TRAP_TABLE = {
game.surfaces["nauvis"].build_enemy_base(game.forces["player"].get_spawn_position(game.get_surface(1)), 25)
end,
["Evolution Trap"] = function ()
local new_factor = game.forces["enemy"].get_evolution_factor("nauvis") +
(TRAP_EVO_FACTOR * (1 - game.forces["enemy"].get_evolution_factor("nauvis")))
game.forces["enemy"].set_evolution_factor(new_factor, "nauvis")
game.print({"", "New evolution factor:", new_factor})
game.forces["enemy"].evolution_factor = game.forces["enemy"].evolution_factor + (TRAP_EVO_FACTOR * (1 - game.forces["enemy"].evolution_factor))
game.print({"", "New evolution factor:", game.forces["enemy"].evolution_factor})
end,
["Teleport Trap"] = function ()
for _, player in ipairs(game.forces["player"].players) do

View File

@@ -1,39 +0,0 @@
"""Tests for error messages from YAML validation."""
import os
import unittest
import WebHostLib.check
FACTORIO_YAML="""
game: Factorio
Factorio:
world_gen:
autoplace_controls:
coal:
richness: 1
frequency: {}
size: 1
"""
def yamlWithFrequency(f):
return FACTORIO_YAML.format(f)
class TestFileValidation(unittest.TestCase):
def test_out_of_range(self):
results, _ = WebHostLib.check.roll_options({"bob.yaml": yamlWithFrequency(1000)})
self.assertIn("between 0 and 6", results["bob.yaml"])
def test_bad_non_numeric(self):
results, _ = WebHostLib.check.roll_options({"bob.yaml": yamlWithFrequency("not numeric")})
self.assertIn("float", results["bob.yaml"])
self.assertIn("int", results["bob.yaml"])
def test_good_float(self):
results, _ = WebHostLib.check.roll_options({"bob.yaml": yamlWithFrequency(1.0)})
self.assertIs(results["bob.yaml"], True)
def test_good_int(self):
results, _ = WebHostLib.check.roll_options({"bob.yaml": yamlWithFrequency(1)})
self.assertIs(results["bob.yaml"], True)

View File

@@ -44,13 +44,8 @@ class FaxanaduWorld(World):
location_name_to_id = {loc.name: loc.id for loc in Locations.locations if loc.id is not None}
def __init__(self, world: MultiWorld, player: int):
self.filler_ratios: Dict[str, int] = {
item.name: item.count
for item in Items.items
if item.classification in [ItemClassification.filler, ItemClassification.trap]
}
# Remove poison by default to respect itemlinking
self.filler_ratios["Poison"] = 0
self.filler_ratios: Dict[str, int] = {}
super().__init__(world, player)
def create_regions(self):
@@ -165,13 +160,19 @@ class FaxanaduWorld(World):
for i in range(item.progression_count):
itempool.append(FaxanaduItem(item.name, ItemClassification.progression, item.id, self.player))
# Adjust filler ratios
# Set up filler ratios
self.filler_ratios = {
item.name: item.count
for item in Items.items
if item.classification in [ItemClassification.filler, ItemClassification.trap]
}
# If red potions are locked in shops, remove the count from the ratio.
self.filler_ratios["Red Potion"] -= red_potion_in_shop_count
# Add poisons if desired
if self.options.include_poisons:
self.filler_ratios["Poison"] = self.item_name_to_item["Poison"].count
# Remove poisons if not desired
if not self.options.include_poisons:
self.filler_ratios["Poison"] = 0
# Randomly add fillers to the pool with ratios based on og game occurrence counts.
filler_count = len(Locations.locations) - len(itempool) - prefilled_count

View File

@@ -1,4 +1,4 @@
from Options import Choice, FreeText, ItemsAccessibility, Toggle, Range, PerGameCommonOptions
from Options import Choice, FreeText, Toggle, Range, PerGameCommonOptions
from dataclasses import dataclass
@@ -324,7 +324,6 @@ class KaelisMomFightsMinotaur(Toggle):
@dataclass
class FFMQOptions(PerGameCommonOptions):
accessibility: ItemsAccessibility
logic: Logic
brown_boxes: BrownBoxes
sky_coin_mode: SkyCoinMode

View File

@@ -181,7 +181,6 @@ class HKWorld(World):
charm_costs: typing.List[int]
cached_filler_items = {}
grub_count: int
grub_player_count: typing.Dict[int, int]
def __init__(self, multiworld, player):
super(HKWorld, self).__init__(multiworld, player)
@@ -191,6 +190,7 @@ class HKWorld(World):
self.ranges = {}
self.created_shop_items = 0
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
self.grub_count = 0
def generate_early(self):
options = self.options
@@ -204,14 +204,7 @@ class HKWorld(World):
mini.value = min(mini.value, maxi.value)
self.ranges[term] = mini.value, maxi.value
self.multiworld.push_precollected(HKItem(starts[options.StartLocation.current_key],
True, None, "Event", self.player))
# defaulting so completion condition isn't incorrect before pre_fill
self.grub_count = (
46 if options.GrubHuntGoal == GrubHuntGoal.special_range_names["all"]
else options.GrubHuntGoal
)
self.grub_player_count = {self.player: self.grub_count}
True, None, "Event", self.player))
def white_palace_exclusions(self):
exclusions = set()
@@ -476,20 +469,25 @@ class HKWorld(World):
elif goal == Goal.option_godhome_flower:
multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player)
elif goal == Goal.option_grub_hunt:
multiworld.completion_condition[player] = lambda state: self.can_grub_goal(state)
pass # will set in stage_pre_fill()
else:
# Any goal
multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) and \
_hk_can_beat_radiance(state, player) and state.count("Godhome_Flower_Quest", player) and \
self.can_grub_goal(state)
_hk_can_beat_radiance(state, player) and state.count("Godhome_Flower_Quest", player)
set_rules(self)
def can_grub_goal(self, state: CollectionState) -> bool:
return all(state.has("Grub", owner, count) for owner, count in self.grub_player_count.items())
@classmethod
def stage_pre_fill(cls, multiworld: "MultiWorld"):
def set_goal(player, grub_rule: typing.Callable[[CollectionState], bool]):
world = multiworld.worlds[player]
if world.options.Goal == "grub_hunt":
multiworld.completion_condition[player] = grub_rule
else:
old_rule = multiworld.completion_condition[player]
multiworld.completion_condition[player] = lambda state: old_rule(state) and grub_rule(state)
worlds = [world for world in multiworld.get_game_worlds(cls.game) if world.options.Goal in ["any", "grub_hunt"]]
if worlds:
grubs = [item for item in multiworld.get_items() if item.name == "Grub"]
@@ -527,13 +525,13 @@ class HKWorld(World):
for player, grub_player_count in per_player_grubs_per_player.items():
if player in all_grub_players:
multiworld.worlds[player].grub_player_count = grub_player_count
set_goal(player, lambda state, g=grub_player_count: all(state.has("Grub", owner, count) for owner, count in g.items()))
for world in worlds:
if world.player not in all_grub_players:
world.grub_count = world.options.GrubHuntGoal.value
player = world.player
world.grub_player_count = {player: world.grub_count}
set_goal(player, lambda state, p=player, c=world.grub_count: state.has("Grub", p, c))
def fill_slot_data(self):
slot_data = {}

View File

@@ -110,7 +110,6 @@ class KH2Context(CommonContext):
18: TWTNW_Checks,
# 255: {}, # starting screen
}
self.last_world_int = -1
# 0x2A09C00+0x40 is the sve anchor. +1 is the last saved room
# self.sveroom = 0x2A09C00 + 0x41
# 0 not in battle 1 in yellow battle 2 red battle #short
@@ -346,12 +345,33 @@ class KH2Context(CommonContext):
self.lookup_id_to_item = {v: k for k, v in self.kh2_item_name_to_id.items()}
self.ability_code_list = [self.kh2_item_name_to_id[item] for item in exclusion_item_table["Ability"]]
if "KeybladeAbilities" in self.kh2slotdata.keys():
if "keyblade_abilities" in self.kh2slotdata.keys():
sora_ability_dict = self.kh2slotdata["KeybladeAbilities"]
# sora ability to slot
self.AbilityQuantityDict.update(self.kh2slotdata["KeybladeAbilities"])
# itemid:[slots that are available for that item]
self.AbilityQuantityDict.update(self.kh2slotdata["StaffAbilities"])
self.AbilityQuantityDict.update(self.kh2slotdata["ShieldAbilities"])
for k, v in sora_ability_dict.items():
if v >= 1:
if k not in self.sora_ability_to_slot.keys():
self.sora_ability_to_slot[k] = []
for _ in range(sora_ability_dict[k]):
self.sora_ability_to_slot[k].append(self.kh2_seed_save_cache["SoraInvo"][0])
self.kh2_seed_save_cache["SoraInvo"][0] -= 2
donald_ability_dict = self.kh2slotdata["StaffAbilities"]
for k, v in donald_ability_dict.items():
if v >= 1:
if k not in self.donald_ability_to_slot.keys():
self.donald_ability_to_slot[k] = []
for _ in range(donald_ability_dict[k]):
self.donald_ability_to_slot[k].append(self.kh2_seed_save_cache["DonaldInvo"][0])
self.kh2_seed_save_cache["DonaldInvo"][0] -= 2
goofy_ability_dict = self.kh2slotdata["ShieldAbilities"]
for k, v in goofy_ability_dict.items():
if v >= 1:
if k not in self.goofy_ability_to_slot.keys():
self.goofy_ability_to_slot[k] = []
for _ in range(goofy_ability_dict[k]):
self.goofy_ability_to_slot[k].append(self.kh2_seed_save_cache["GoofyInvo"][0])
self.kh2_seed_save_cache["GoofyInvo"][0] -= 2
all_weapon_location_id = []
for weapon_location in all_weapon_slot:
@@ -388,15 +408,13 @@ class KH2Context(CommonContext):
async def checkWorldLocations(self):
try:
currentworldint = self.kh2_read_byte(self.Now)
if self.last_world_int != currentworldint:
self.last_world_int = currentworldint
await self.send_msgs([{
"cmd": "Set", "key": "Slot: " + str(self.slot) + " :CurrentWorld",
"default": 0, "want_reply": False, "operations": [{
"operation": "replace",
"value": currentworldint
}]
}])
await self.send_msgs([{
"cmd": "Set", "key": "Slot: " + str(self.slot) + " :CurrentWorld",
"default": 0, "want_reply": True, "operations": [{
"operation": "replace",
"value": currentworldint
}]
}])
if currentworldint in self.worldid_to_locations:
curworldid = self.worldid_to_locations[currentworldint]
for location, data in curworldid.items():
@@ -507,7 +525,27 @@ class KH2Context(CommonContext):
if itemname not in self.kh2_seed_save_cache["AmountInvo"]["Ability"]:
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname] = []
# appending the slot that the ability should be in
if len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < \
# for non beta. remove after 4.3
if "PoptrackerVersion" in self.kh2slotdata:
if self.kh2slotdata["PoptrackerVersionCheck"] < 4.3:
if (itemname in self.sora_ability_set
and len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < self.item_name_to_data[itemname].quantity) \
and self.kh2_seed_save_cache["SoraInvo"][1] > 0x254C:
ability_slot = self.kh2_seed_save_cache["SoraInvo"][1]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["SoraInvo"][1] -= 2
elif itemname in self.donald_ability_set:
ability_slot = self.kh2_seed_save_cache["DonaldInvo"][1]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["DonaldInvo"][1] -= 2
else:
ability_slot = self.kh2_seed_save_cache["GoofyInvo"][1]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["GoofyInvo"][1] -= 2
if ability_slot in self.front_ability_slots:
self.front_ability_slots.remove(ability_slot)
elif len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < \
self.AbilityQuantityDict[itemname]:
if itemname in self.sora_ability_set:
ability_slot = self.kh2_seed_save_cache["SoraInvo"][0]
@@ -807,7 +845,7 @@ class KH2Context(CommonContext):
logger.info("line 840")
def finishedGame(ctx: KH2Context):
def finishedGame(ctx: KH2Context, message):
if ctx.kh2slotdata['FinalXemnas'] == 1:
if not ctx.final_xemnas and ctx.kh2_read_byte(ctx.Save + all_world_locations[LocationName.FinalXemnas].addrObtained) \
& 0x1 << all_world_locations[LocationName.FinalXemnas].bitIndex > 0:
@@ -839,9 +877,8 @@ def finishedGame(ctx: KH2Context):
elif ctx.kh2slotdata['Goal'] == 2:
# for backwards compat
if "hitlist" in ctx.kh2slotdata:
locations = ctx.sending
for boss in ctx.kh2slotdata["hitlist"]:
if boss in locations:
if boss in message[0]["locations"]:
ctx.hitlist_bounties += 1
if ctx.hitlist_bounties >= ctx.kh2slotdata["BountyRequired"] or ctx.kh2_seed_save_cache["AmountInvo"]["Amount"]["Bounty"] >= ctx.kh2slotdata["BountyRequired"]:
if ctx.kh2_read_byte(ctx.Save + 0x36B3) < 1:
@@ -882,12 +919,11 @@ async def kh2_watcher(ctx: KH2Context):
await asyncio.create_task(ctx.verifyChests())
await asyncio.create_task(ctx.verifyItems())
await asyncio.create_task(ctx.verifyLevel())
if finishedGame(ctx) and not ctx.kh2_finished_game:
message = [{"cmd": 'LocationChecks', "locations": ctx.sending}]
if finishedGame(ctx, message) and not ctx.kh2_finished_game:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.kh2_finished_game = True
if ctx.sending:
message = [{"cmd": 'LocationChecks', "locations": ctx.sending}]
await ctx.send_msgs(message)
await ctx.send_msgs(message)
elif not ctx.kh2connected and ctx.serverconneced:
logger.info("Game Connection lost. waiting 15 seconds until trying to reconnect.")
ctx.kh2 = None

View File

@@ -103,7 +103,6 @@ def generateRom(args, world: "LinksAwakeningWorld"):
assembler.const("wGoldenLeaves", 0xDB42) # New memory location where to store the golden leaf counter
assembler.const("wCollectedTunics", 0xDB6D) # Memory location where to store which tunic options are available (and boots)
assembler.const("wCustomMessage", 0xC0A0)
assembler.const("wOverworldRoomStatus", 0xD800)
# We store the link info in unused color dungeon flags, so it gets preserved in the savegame.
assembler.const("wLinkSyncSequenceNumber", 0xDDF6)

View File

@@ -68,12 +68,10 @@ DEFAULT_ITEM_POOL = {
class ItemPool:
def __init__(self, logic, settings, rnd, stabilize_item_pool: bool):
def __init__(self, logic, settings, rnd):
self.__pool = {}
self.__setup(logic, settings)
if not stabilize_item_pool:
self.__randomizeRupees(settings, rnd)
self.__randomizeRupees(settings, rnd)
def add(self, item, count=1):
self.__pool[item] = self.__pool.get(item, 0) + count

View File

@@ -2,10 +2,6 @@ import typing
from ..checkMetadata import checkMetadataTable
from .constants import *
custom_name_replacements = {
'"':"'",
'_':' ',
}
class ItemInfo:
MULTIWORLD = True
@@ -27,11 +23,6 @@ class ItemInfo:
def setLocation(self, location):
self._location = location
def setCustomItemName(self, name):
for key, val in custom_name_replacements.items():
name = name.replace(key, val)
self.custom_item_name = name
def getOptions(self):
return self.OPTIONS

View File

@@ -716,7 +716,9 @@ def addWarpImprovements(rom, extra_warps):
# Allow cursor to move over black squares
# This allows warping to undiscovered areas - a fine cheat, but needs a check for wOverworldRoomStatus in the warp code
rom.patch(0x01, 0x1AE8, None, ASM("jp $5AF5"))
CHEAT_WARP_ANYWHERE = False
if CHEAT_WARP_ANYWHERE:
rom.patch(0x01, 0x1AE8, None, ASM("jp $5AF5"))
# This disables the arrows around the selection bubble
#rom.patch(0x01, 0x1B6F, None, ASM("ret"), fill_nop=True)
@@ -795,14 +797,8 @@ def addWarpImprovements(rom, extra_warps):
TeleportHandler:
ld a, [$DBB4] ; Load the current selected tile
ld hl, wOverworldRoomStatus
ld e, a ; $5D38: $5F
ld d, $00 ; $5D39: $16 $00
add hl, de ; $5D3B: $19
ld a, [hl]
and $80
jr z, exit
ld a, [$DBB4] ; Load the current selected tile
; TODO: check if actually revealed so we can have free movement
; Check cursor against different tiles to see if we are selecting a warp
{warp_jump}
jr exit

View File

@@ -527,13 +527,6 @@ class InGameHints(DefaultOnToggle):
display_name = "In-game Hints"
class StabilizeItemPool(DefaultOffToggle):
"""
By default, rupees in the item pool may be randomly swapped with bombs, arrows, powders, or capacity upgrades. This option disables that swapping, which is useful for plando.
"""
display_name = "Stabilize Item Pool"
class ForeignItemIcons(Choice):
"""
Choose how to display foreign items.
@@ -569,7 +562,6 @@ ladx_option_groups = [
TrendyGame,
InGameHints,
NagMessages,
StabilizeItemPool,
Quickswap,
HardMode,
BootsControls
@@ -639,7 +631,6 @@ class LinksAwakeningOptions(PerGameCommonOptions):
no_flash: NoFlash
in_game_hints: InGameHints
overworld: Overworld
stabilize_item_pool: StabilizeItemPool
warp_improvements: Removed
additional_warp_points: Removed

View File

@@ -138,30 +138,7 @@ class LinksAwakeningWorld(World):
world_setup = LADXRWorldSetup()
world_setup.randomize(self.ladxr_settings, self.random)
self.ladxr_logic = LADXRLogic(configuration_options=self.ladxr_settings, world_setup=world_setup)
self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.ladxr_settings, self.random, bool(self.options.stabilize_item_pool)).toDict()
def generate_early(self) -> None:
self.dungeon_item_types = {
}
for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]:
option_name = "shuffle_" + dungeon_item_type
option: DungeonItemShuffle = getattr(self.options, option_name)
self.dungeon_item_types[option.ladxr_item] = option.value
# The color dungeon does not contain an instrument
num_items = 8 if dungeon_item_type == "instruments" else 9
# For any and different world, set item rule instead
if option.value == DungeonItemShuffle.option_own_world:
self.options.local_items.value |= {
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
}
elif option.value == DungeonItemShuffle.option_different_world:
self.options.non_local_items.value |= {
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
}
self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.ladxr_settings, self.random).toDict()
def create_regions(self) -> None:
# Initialize
@@ -208,9 +185,32 @@ class LinksAwakeningWorld(World):
def create_items(self) -> None:
exclude = [item.name for item in self.multiworld.precollected_items[self.player]]
dungeon_item_types = {
}
self.prefill_original_dungeon = [ [], [], [], [], [], [], [], [], [] ]
self.prefill_own_dungeons = []
self.pre_fill_items = []
# For any and different world, set item rule instead
for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]:
option_name = "shuffle_" + dungeon_item_type
option: DungeonItemShuffle = getattr(self.options, option_name)
dungeon_item_types[option.ladxr_item] = option.value
# The color dungeon does not contain an instrument
num_items = 8 if dungeon_item_type == "instruments" else 9
if option.value == DungeonItemShuffle.option_own_world:
self.options.local_items.value |= {
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
}
elif option.value == DungeonItemShuffle.option_different_world:
self.options.non_local_items.value |= {
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
}
# option_original_dungeon = 0
# option_own_dungeons = 1
# option_own_world = 2
@@ -226,7 +226,7 @@ class LinksAwakeningWorld(World):
for _ in range(count):
if item_name in exclude:
exclude.remove(item_name) # this is destructive. create unique list above
self.multiworld.itempool.append(self.create_item(self.get_filler_item_name()))
self.multiworld.itempool.append(self.create_item("Nothing"))
else:
item = self.create_item(item_name)
@@ -238,7 +238,7 @@ class LinksAwakeningWorld(World):
if isinstance(item.item_data, DungeonItemData):
item_type = item.item_data.ladxr_id[:-1]
shuffle_type = self.dungeon_item_types[item_type]
shuffle_type = dungeon_item_types[item_type]
if item.item_data.dungeon_item_type == DungeonItemType.INSTRUMENT and shuffle_type == ShuffleInstruments.option_vanilla:
# Find instrument, lock
@@ -439,7 +439,7 @@ class LinksAwakeningWorld(World):
# Otherwise, use a cute letter as the icon
elif self.options.foreign_item_icons == 'guess_by_name':
loc.ladxr_item.item = self.guess_icon_for_other_world(loc.item)
loc.ladxr_item.setCustomItemName(loc.item.name)
loc.ladxr_item.custom_item_name = loc.item.name
else:
if loc.item.advancement:
@@ -500,14 +500,8 @@ class LinksAwakeningWorld(World):
state.prog_items[self.player]["RUPEES"] -= self.rupees[item.name]
return change
# Same fill choices and weights used in LADXR.itempool.__randomizeRupees
filler_choices = ("Bomb", "Single Arrow", "10 Arrows", "Magic Powder", "Medicine")
filler_weights = ( 10, 5, 10, 10, 1)
def get_filler_item_name(self) -> str:
if self.options.stabilize_item_pool:
return "Nothing"
return self.random.choices(self.filler_choices, self.filler_weights)[0]
return "Nothing"
def fill_slot_data(self):
slot_data = {}

View File

@@ -128,9 +128,6 @@ class LingoWorld(World):
pool.append(self.create_item("Puzzle Skip"))
if traps:
if self.options.speed_boost_mode:
self.options.trap_weights.value["Slowness Trap"] = 0
total_weight = sum(self.options.trap_weights.values())
if total_weight == 0:
@@ -174,7 +171,7 @@ class LingoWorld(World):
"death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels",
"enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks",
"early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps",
"group_doors", "speed_boost_mode"
"group_doors"
]
slot_data = {
@@ -191,8 +188,5 @@ class LingoWorld(World):
return slot_data
def get_filler_item_name(self) -> str:
if self.options.speed_boost_mode:
return "Speed Boost"
else:
filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
return self.random.choice(filler_list)
filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
return self.random.choice(filler_list)

Binary file not shown.

View File

@@ -17,7 +17,6 @@ special_items:
Iceland Trap: 444411
Atbash Trap: 444412
Puzzle Skip: 444413
Speed Boost: 444680
panels:
Starting Room:
HI: 444400

View File

@@ -85,7 +85,6 @@ def load_item_data():
"The Feeling of Being Lost": ItemClassification.filler,
"Wanderlust": ItemClassification.filler,
"Empty White Hallways": ItemClassification.filler,
"Speed Boost": ItemClassification.filler,
**{trap_name: ItemClassification.trap for trap_name in TRAP_ITEMS},
"Puzzle Skip": ItemClassification.useful,
}

View File

@@ -232,14 +232,6 @@ class TrapWeights(OptionDict):
default = {trap_name: 1 for trap_name in TRAP_ITEMS}
class SpeedBoostMode(Toggle):
"""
If on, the player's default speed is halved, as if affected by a Slowness Trap. Speed Boosts are added to
the item pool, which temporarily return the player to normal speed. Slowness Traps are removed from the pool.
"""
display_name = "Speed Boost Mode"
class PuzzleSkipPercentage(Range):
"""Replaces junk items with puzzle skips, at the specified rate."""
display_name = "Puzzle Skip Percentage"
@@ -268,7 +260,6 @@ lingo_option_groups = [
Level2Requirement,
TrapPercentage,
TrapWeights,
SpeedBoostMode,
PuzzleSkipPercentage,
])
]
@@ -296,7 +287,6 @@ class LingoOptions(PerGameCommonOptions):
shuffle_postgame: ShufflePostgame
trap_percentage: TrapPercentage
trap_weights: TrapWeights
speed_boost_mode: SpeedBoostMode
puzzle_skip_percentage: PuzzleSkipPercentage
death_link: DeathLink
start_inventory_from_pool: StartInventoryPool

View File

@@ -59,11 +59,4 @@ class TestShuffleSunwarpsAccess(LingoTestBase):
"victory_condition": "pilgrimage",
"shuffle_sunwarps": "true",
"sunwarp_access": "individual"
}
class TestSpeedBoostMode(LingoTestBase):
options = {
"location_checks": "insanity",
"speed_boost_mode": "true",
}
}

View File

@@ -216,6 +216,3 @@ config.each do |room_name, room_data|
end
File.write(outputpath, old_generated.to_yaml)
puts "Next item ID: #{next_item_id}"
puts "Next location ID: #{next_location_id}"

View File

@@ -214,19 +214,10 @@ class MegaMan2Client(BizHawkClient):
last_wily: Optional[int] = None # default to wily 1
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
from worlds._bizhawk import RequestFailedError, read, get_memory_size
from worlds._bizhawk import RequestFailedError, read
from . import MM2World
try:
if (await get_memory_size(ctx.bizhawk_ctx, "PRG ROM")) < 0x3FFB0:
if "pool" in ctx.command_processor.commands:
ctx.command_processor.commands.pop("pool")
if "request" in ctx.command_processor.commands:
ctx.command_processor.commands.pop("request")
if "autoheal" in ctx.command_processor.commands:
ctx.command_processor.commands.pop("autoheal")
return False
game_name, version = (await read(ctx.bizhawk_ctx, [(0x3FFB0, 21, "PRG ROM"),
(0x3FFC8, 3, "PRG ROM")]))
if game_name[:3] != b"MM2" or version != bytes(MM2World.world_version):

View File

@@ -85,7 +85,7 @@ keyItemList: typing.List[ItemData] = [
]
subChipList: typing.List[ItemData] = [
ItemData(0xB31018, ItemName.Unlocker, ItemClassification.progression, ItemType.SubChip, 117),
ItemData(0xB31018, ItemName.Unlocker, ItemClassification.useful, ItemType.SubChip, 117),
ItemData(0xB31019, ItemName.Untrap, ItemClassification.filler, ItemType.SubChip, 115),
ItemData(0xB3101A, ItemName.LockEnmy, ItemClassification.filler, ItemType.SubChip, 116),
ItemData(0xB3101B, ItemName.MiniEnrg, ItemClassification.filler, ItemType.SubChip, 112),
@@ -290,9 +290,7 @@ programList: typing.List[ItemData] = [
ItemData(0xB31099, ItemName.WpnLV_plus_Yellow, ItemClassification.filler, ItemType.Program, 35, ProgramColor.Yellow),
ItemData(0xB3109A, ItemName.Press, ItemClassification.progression, ItemType.Program, 20, ProgramColor.White),
ItemData(0xB310B7, ItemName.UnderSht, ItemClassification.useful, ItemType.Program, 30, ProgramColor.White),
ItemData(0xB310E0, ItemName.Humor, ItemClassification.progression, ItemType.Program, 45, ProgramColor.Pink),
ItemData(0xB310E1, ItemName.BlckMnd, ItemClassification.progression, ItemType.Program, 46, ProgramColor.White)
ItemData(0xB310B7, ItemName.UnderSht, ItemClassification.useful, ItemType.Program, 30, ProgramColor.White)
]
zennyList: typing.List[ItemData] = [
@@ -340,29 +338,8 @@ item_frequencies: typing.Dict[str, int] = {
ItemName.zenny_800z: 2,
ItemName.zenny_1000z: 2,
ItemName.zenny_1200z: 2,
ItemName.bugfrag_01: 10,
ItemName.bugfrag_10: 5
ItemName.bugfrag_01: 5,
}
item_groups: typing.Dict[str, typing.Set[str]] = {
"Key Items": {loc.itemName for loc in keyItemList},
"Subchips": {loc.itemName for loc in subChipList},
"Programs": {loc.itemName for loc in programList},
"BattleChips": {loc.itemName for loc in chipList},
"Zenny": {loc.itemName for loc in zennyList},
"BugFrags": {loc.itemName for loc in bugFragList},
"Navi Chips": {
ItemName.Roll_R, ItemName.RollV2_R, ItemName.RollV3_R, ItemName.GutsMan_G, ItemName.GutsManV2_G,
ItemName.GutsManV3_G, ItemName.ProtoMan_B, ItemName.ProtoManV2_B, ItemName.ProtoManV3_B, ItemName.FlashMan_F,
ItemName.FlashManV2_F, ItemName.FlashManV3_F, ItemName.BeastMan_B, ItemName.BeastManV2_B, ItemName.BeastManV3_B,
ItemName.BubblMan_B, ItemName.BubblManV2_B, ItemName.BubblManV3_B, ItemName.DesertMan_D, ItemName.DesertManV2_D,
ItemName.DesertManV3_D, ItemName.PlantMan_P, ItemName.PlantManV2_P, ItemName.PlantManV3_P, ItemName.FlamMan_F,
ItemName.FlamManV2_F, ItemName.FlamManV3_F, ItemName.DrillMan_D, ItemName.DrillManV2_D, ItemName.DrillManV3_D,
ItemName.MetalMan_M, ItemName.MetalManV2_M, ItemName.MetalManV3_M, ItemName.KingMan_K, ItemName.KingManV2_K,
ItemName.KingManV3_K, ItemName.BowlMan_B, ItemName.BowlManV2_B, ItemName.BowlManV3_B
}
}
all_items: typing.List[ItemData] = keyItemList + subChipList + chipList + programList + zennyList + bugFragList
item_table: typing.Dict[str, ItemData] = {item.itemName: item for item in all_items}
items_by_id: typing.Dict[int, ItemData] = {item.code: item for item in all_items}

View File

@@ -221,8 +221,7 @@ overworlds = [
LocationData(LocationName.Hades_Boat_Dock, 0xb310ab, 0x200024c, 0x10, 0x7519B0, 223, [3]),
LocationData(LocationName.WWW_Control_Room_1_Screen, 0xb310ac, 0x200024d, 0x40, 0x7596C4, 222, [3, 4]),
LocationData(LocationName.WWW_Wilys_Desk, 0xb310ad, 0x200024d, 0x2, 0x759384, 229, [3]),
LocationData(LocationName.Undernet_4_Pillar_Prog, 0xb310ae, 0x2000161, 0x1, 0x7746C8, 191, [0, 1]),
LocationData(LocationName.Serenade, 0xb3110f, 0x2000178, 0x40, 0x7B3C74, 1, [0])
LocationData(LocationName.Undernet_4_Pillar_Prog, 0xb310ae, 0x2000161, 0x1, 0x7746C8, 191, [0, 1])
]
jobs = [
@@ -241,8 +240,7 @@ jobs = [
# LocationData(LocationName.Gathering_Data, 0xb310bb, 0x2000300, 0x10, 0x739580, 193, [0]),
LocationData(LocationName.Somebody_please_help, 0xb310bc, 0x2000301, 0x4, 0x73A14C, 193, [0]),
LocationData(LocationName.Looking_for_condor, 0xb310bd, 0x2000301, 0x2, 0x749444, 203, [0]),
LocationData(LocationName.Help_with_rehab, 0xb310be, 0x2000301, 0x1, 0x762CF0, 192, [0]),
LocationData(LocationName.Help_with_rehab_bonus, 0xb3110e, 0x2000301, 0x1, 0x762CF0, 192, [3]),
LocationData(LocationName.Help_with_rehab, 0xb310be, 0x2000301, 0x1, 0x762CF0, 192, [3]),
LocationData(LocationName.Old_Master, 0xb310bf, 0x2000302, 0x80, 0x760E80, 193, [0]),
LocationData(LocationName.Catching_gang_members, 0xb310c0, 0x2000302, 0x40, 0x76EAE4, 193, [0]),
LocationData(LocationName.Please_adopt_a_virus, 0xb310c1, 0x2000302, 0x20, 0x76A4F4, 193, [0]),
@@ -252,7 +250,7 @@ jobs = [
LocationData(LocationName.Hide_and_seek_Second_Child, 0xb310c5, 0x2000188, 0x2, 0x75ADA8, 191, [0]),
LocationData(LocationName.Hide_and_seek_Third_Child, 0xb310c6, 0x2000188, 0x1, 0x75B5EC, 191, [0]),
LocationData(LocationName.Hide_and_seek_Fourth_Child, 0xb310c7, 0x2000189, 0x80, 0x75BEB0, 191, [0]),
LocationData(LocationName.Hide_and_seek_Completion, 0xb310c8, 0x2000302, 0x8, 0x742D40, 193, [0]),
LocationData(LocationName.Hide_and_seek_Completion, 0xb310c8, 0x2000302, 0x8, 0x7406A0, 193, [0]),
LocationData(LocationName.Finding_the_blue_Navi, 0xb310c9, 0x2000302, 0x4, 0x773700, 192, [0]),
LocationData(LocationName.Give_your_support, 0xb310ca, 0x2000302, 0x2, 0x752D80, 192, [0]),
LocationData(LocationName.Stamp_collecting, 0xb310cb, 0x2000302, 0x1, 0x756074, 193, [0]),
@@ -331,7 +329,10 @@ chocolate_shop = [
LocationData(LocationName.Chocolate_Shop_32, 0xb3110d, 0x20001c3, 0x01, 0x73F8FC, 181, [0]),
]
secret_locations = {
always_excluded_locations = [
LocationName.Undernet_7_PMD,
LocationName.Undernet_7_Northeast_BMD,
LocationName.Undernet_7_Northwest_BMD,
LocationName.Secret_1_Northwest_BMD,
LocationName.Secret_1_Northeast_BMD,
LocationName.Secret_1_South_BMD,
@@ -340,23 +341,19 @@ secret_locations = {
LocationName.Secret_2_Island_BMD,
LocationName.Secret_3_Island_BMD,
LocationName.Secret_3_BugFrag_BMD,
LocationName.Secret_3_South_BMD,
LocationName.Serenade
}
LocationName.Secret_3_South_BMD
]
location_groups: typing.Dict[str, typing.Set[str]] = {
"BMDs": {loc.name for loc in bmds},
"PMDs": {loc.name for loc in pmds},
"Jobs": {loc.name for loc in jobs},
"Number Trader": {loc.name for loc in number_traders},
"Bugfrag Trader": {loc.name for loc in chocolate_shop},
"Secret Area": {LocationName.Secret_1_Northwest_BMD, LocationName.Secret_1_Northeast_BMD,
LocationName.Secret_1_South_BMD, LocationName.Secret_2_Upper_BMD, LocationName.Secret_2_Lower_BMD,
LocationName.Secret_2_Island_BMD, LocationName.Secret_3_Island_BMD,
LocationName.Secret_3_BugFrag_BMD, LocationName.Secret_3_South_BMD, LocationName.Serenade},
}
all_locations: typing.List[LocationData] = bmds + pmds + overworlds + jobs + number_traders + chocolate_shop
scoutable_locations: typing.List[LocationData] = [loc for loc in all_locations if loc.hint_flag is not None]
location_table: typing.Dict[str, int] = {locData.name: locData.id for locData in all_locations}
location_data_table: typing.Dict[str, LocationData] = {locData.name: locData for locData in all_locations}
"""
def setup_locations(world, player: int):
# If we later include options to change what gets added to the random pool,
# this is where they would be changed
return {locData.name: locData.id for locData in all_locations}
"""

View File

@@ -173,8 +173,6 @@ class ItemName():
WpnLV_plus_White = "WpnLV+1 (White)"
Press = "Press"
UnderSht = "UnderSht"
Humor = "Humor"
BlckMnd = "BlckMnd"
## Currency
zenny_200z = "200z"

View File

@@ -210,7 +210,6 @@ class LocationName():
WWW_Control_Room_1_Screen = "WWW Control Room 1 Screen"
WWW_Wilys_Desk = "WWW Wily's Desk"
Undernet_4_Pillar_Prog = "Undernet 4 Pillar Prog"
Serenade = "Serenade"
## Numberman Codes
Numberman_Code_01 = "Numberman Code 01"
@@ -262,7 +261,6 @@ class LocationName():
Somebody_please_help = "Job: Somebody, please help!"
Looking_for_condor = "Job: Looking for condor"
Help_with_rehab = "Job: Help with rehab"
Help_with_rehab_bonus = "Job: Help with rehab bonus"
Old_Master = "Job: Old Master"
Catching_gang_members = "Job: Catching gang members"
Please_adopt_a_virus = "Job: Please adopt a virus!"

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass
from Options import Choice, Range, DefaultOnToggle, Toggle, PerGameCommonOptions
from Options import Choice, Range, DefaultOnToggle, PerGameCommonOptions
class ExtraRanks(Range):
@@ -17,17 +17,10 @@ class ExtraRanks(Range):
class IncludeJobs(DefaultOnToggle):
"""
Whether Jobs can contain progression or useful items.
Whether Jobs can be included in logic.
"""
display_name = "Include Jobs"
class IncludeSecretArea(Toggle):
"""
Whether the Secret Area (including Serenade) can contain progression or useful items.
"""
display_name = "Include Secret Area"
# Possible logic options:
# - Include Number Trader
# - Include Secret Area
@@ -53,6 +46,5 @@ class TradeQuestHinting(Choice):
class MMBN3Options(PerGameCommonOptions):
extra_ranks: ExtraRanks
include_jobs: IncludeJobs
include_secret: IncludeSecretArea
trade_quest_hinting: TradeQuestHinting

View File

@@ -135,7 +135,6 @@ regions = [
LocationName.Somebody_please_help,
LocationName.Looking_for_condor,
LocationName.Help_with_rehab,
LocationName.Help_with_rehab_bonus,
LocationName.Old_Master,
LocationName.Catching_gang_members,
LocationName.Please_adopt_a_virus,
@@ -350,7 +349,6 @@ regions = [
LocationName.Secret_2_Upper_BMD,
LocationName.Secret_3_Island_BMD,
LocationName.Secret_3_South_BMD,
LocationName.Secret_3_BugFrag_BMD,
LocationName.Serenade
LocationName.Secret_3_BugFrag_BMD
])
]

View File

@@ -9,14 +9,14 @@ from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification, Region,
from worlds.AutoWorld import WebWorld, World
from .Rom import MMBN3DeltaPatch, LocalRom, get_base_rom_path
from .Items import MMBN3Item, ItemData, item_table, all_items, item_frequencies, items_by_id, ItemType, item_groups
from .Items import MMBN3Item, ItemData, item_table, all_items, item_frequencies, items_by_id, ItemType
from .Locations import Location, MMBN3Location, all_locations, location_table, location_data_table, \
secret_locations, jobs, location_groups
always_excluded_locations, jobs
from .Options import MMBN3Options
from .Regions import regions, RegionName
from .Names.ItemName import ItemName
from .Names.LocationName import LocationName
from worlds.generic.Rules import add_item_rule, add_rule
from worlds.generic.Rules import add_item_rule
class MMBN3Settings(settings.Group):
@@ -57,16 +57,12 @@ class MMBN3World(World):
settings: typing.ClassVar[MMBN3Settings]
topology_present = False
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations}
excluded_locations: typing.Set[str]
excluded_locations: typing.List[str]
item_frequencies: typing.Dict[str, int]
location_name_groups = location_groups
item_name_groups = item_groups
web = MMBN3Web()
def generate_early(self) -> None:
@@ -78,11 +74,10 @@ class MMBN3World(World):
if self.options.extra_ranks > 0:
self.item_frequencies[ItemName.Progressive_Undernet_Rank] = 8 + self.options.extra_ranks
self.excluded_locations = set()
if not self.options.include_secret:
self.excluded_locations |= secret_locations
if not self.options.include_jobs:
self.excluded_locations |= {job.name for job in jobs}
self.excluded_locations = always_excluded_locations + [job.name for job in jobs]
else:
self.excluded_locations = always_excluded_locations
def create_regions(self) -> None:
"""
@@ -145,19 +140,19 @@ class MMBN3World(World):
if connection == RegionName.SciLab_Cyberworld:
entrance.access_rule = lambda state: \
state.has(ItemName.CSciPas, self.player) or \
state.can_reach_region(RegionName.SciLab_Overworld, self.player)
state.can_reach(RegionName.SciLab_Overworld, "Region", self.player)
self.multiworld.register_indirect_condition(self.get_region(RegionName.SciLab_Overworld), entrance)
if connection == RegionName.Yoka_Cyberworld:
entrance.access_rule = lambda state: \
state.has(ItemName.CYokaPas, self.player) or \
(
state.can_reach_region(RegionName.SciLab_Overworld, self.player) and
state.can_reach(RegionName.SciLab_Overworld, "Region", self.player) and
state.has(ItemName.Press, self.player)
)
self.multiworld.register_indirect_condition(self.get_region(RegionName.SciLab_Overworld), entrance)
if connection == RegionName.Beach_Cyberworld:
entrance.access_rule = lambda state: state.has(ItemName.CBeacPas, self.player) and\
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
self.multiworld.register_indirect_condition(self.get_region(RegionName.Yoka_Overworld), entrance)
if connection == RegionName.Undernet:
entrance.access_rule = lambda state: self.explore_score(state) > 8 and\
@@ -203,138 +198,122 @@ class MMBN3World(World):
# Set WWW ID requirements
def has_www_id(state): return state.has(ItemName.WWW_ID, self.player)
add_rule(self.multiworld.get_location(LocationName.ACDC_1_PMD, self.player), has_www_id)
add_rule(self.multiworld.get_location(LocationName.SciLab_1_WWW_BMD, self.player), has_www_id)
add_rule(self.multiworld.get_location(LocationName.Yoka_1_WWW_BMD, self.player), has_www_id)
add_rule(self.multiworld.get_location(LocationName.Undernet_1_WWW_BMD, self.player), has_www_id)
self.multiworld.get_location(LocationName.ACDC_1_PMD, self.player).access_rule = has_www_id
self.multiworld.get_location(LocationName.SciLab_1_WWW_BMD, self.player).access_rule = has_www_id
self.multiworld.get_location(LocationName.Yoka_1_WWW_BMD, self.player).access_rule = has_www_id
self.multiworld.get_location(LocationName.Undernet_1_WWW_BMD, self.player).access_rule = has_www_id
# Set Press Program requirements
def has_press(state): return state.has(ItemName.Press, self.player)
add_rule(self.multiworld.get_location(LocationName.Yoka_1_PMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Yoka_2_Upper_BMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Beach_2_East_BMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Hades_South_BMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Secret_3_BugFrag_BMD, self.player), has_press)
add_rule(self.multiworld.get_location(LocationName.Secret_3_Island_BMD, self.player), has_press)
# Set Purple Mystery Data Unlocker access
def can_unlock(state): return state.can_reach_region(RegionName.SciLab_Overworld, self.player) or \
state.can_reach_region(RegionName.SciLab_Cyberworld, self.player) or \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player) or \
state.has(ItemName.Unlocker, self.player, 8) # There are 8 PMDs that aren't in one of the above areas
add_rule(self.multiworld.get_location(LocationName.ACDC_1_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Yoka_1_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Beach_1_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Undernet_7_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Mayls_HP_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.SciLab_Dads_Computer_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Zoo_Panda_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Beach_DNN_Security_Panel_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Beach_DNN_Main_Console_PMD, self.player), can_unlock)
add_rule(self.multiworld.get_location(LocationName.Tamakos_HP_PMD, self.player), can_unlock)
self.multiworld.get_location(LocationName.Yoka_1_PMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Yoka_2_Upper_BMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Beach_2_East_BMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Hades_South_BMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Secret_3_BugFrag_BMD, self.player).access_rule = has_press
self.multiworld.get_location(LocationName.Secret_3_Island_BMD, self.player).access_rule = has_press
# Set Job additional area access
self.multiworld.get_location(LocationName.Please_deliver_this, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.ACDC_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player)
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player)
self.multiworld.get_location(LocationName.My_Navi_is_sick, self.player).access_rule =\
lambda state: \
state.has(ItemName.Recov30_star, self.player)
self.multiworld.get_location(LocationName.Help_me_with_my_son, self.player).access_rule =\
lambda state:\
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player)
self.multiworld.get_location(LocationName.Transmission_error, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Chip_Prices, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player) and \
state.can_reach_region(RegionName.SciLab_Cyberworld, self.player)
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player)
self.multiworld.get_location(LocationName.Im_broke, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player)
self.multiworld.get_location(LocationName.Rare_chips_for_cheap, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.ACDC_Overworld, self.player)
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Be_my_boyfriend, self.player).access_rule =\
lambda state: \
state.can_reach_region(RegionName.Beach_Cyberworld, self.player)
state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player)
self.multiworld.get_location(LocationName.Will_you_deliver, self.player).access_rule=\
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player)
self.multiworld.get_location(LocationName.Somebody_please_help, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.ACDC_Overworld, self.player)
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Looking_for_condor, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Overworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Help_with_rehab, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Beach_Overworld, self.player)
state.can_reach(RegionName.Beach_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Old_Master, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.ACDC_Overworld, self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player)
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Catching_gang_members, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player) and \
state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \
state.has(ItemName.Press, self.player)
self.multiworld.get_location(LocationName.Please_adopt_a_virus, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.SciLab_Cyberworld, self.player)
state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player)
self.multiworld.get_location(LocationName.Legendary_Tomes, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.Undernet, self.player) and \
state.can_reach_region(RegionName.Deep_Undernet, self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Undernet, "Region", self.player) and \
state.can_reach(RegionName.Deep_Undernet, "Region", self.player) and \
state.has_all({ItemName.Press, ItemName.Magnum1_A}, self.player)
self.multiworld.get_location(LocationName.Legendary_Tomes_Treasure, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.ACDC_Overworld, self.player) and \
state.can_reach_location(LocationName.Legendary_Tomes, self.player)
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \
state.can_reach(LocationName.Legendary_Tomes, "Location", self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_First_Child, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_Second_Child, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_Third_Child, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_Fourth_Child, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Hide_and_seek_Completion, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Yoka_Overworld, self.player)
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Finding_the_blue_Navi, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Undernet, self.player)
state.can_reach(RegionName.Undernet, "Region", self.player)
self.multiworld.get_location(LocationName.Give_your_support, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Beach_Overworld, self.player)
state.can_reach(RegionName.Beach_Overworld, "Region", self.player)
self.multiworld.get_location(LocationName.Stamp_collecting, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player) and \
state.can_reach_region(RegionName.SciLab_Cyberworld, self.player) and \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player) and \
state.can_reach_region(RegionName.Beach_Cyberworld, self.player)
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player)
self.multiworld.get_location(LocationName.Help_with_a_will, self.player).access_rule = \
lambda state: \
state.can_reach_region(RegionName.ACDC_Overworld, self.player) and \
state.can_reach_region(RegionName.ACDC_Cyberworld, self.player) and \
state.can_reach_region(RegionName.Yoka_Overworld, self.player) and \
state.can_reach_region(RegionName.Yoka_Cyberworld, self.player) and \
state.can_reach_region(RegionName.Beach_Overworld, self.player) and \
state.can_reach_region(RegionName.Undernet, self.player)
state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \
state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \
state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \
state.can_reach(RegionName.Undernet, "Region", self.player)
# Set Trade quests
self.multiworld.get_location(LocationName.ACDC_SonicWav_W_Trade, self.player).access_rule =\
@@ -411,11 +390,6 @@ class MMBN3World(World):
self.multiworld.get_location(LocationName.Numberman_Code_31, self.player).access_rule =\
lambda state: self.explore_score(state) > 10
#miscellaneous locations with extra requirements
add_rule(self.multiworld.get_location(LocationName.Comedian, self.player),
lambda state: state.has(ItemName.Humor, self.player))
add_rule(self.multiworld.get_location(LocationName.Villain, self.player),
lambda state: state.has(ItemName.BlckMnd, self.player))
def not_undernet(item): return item.code != item_table[ItemName.Progressive_Undernet_Rank].code or item.player != self.player
self.multiworld.get_location(LocationName.WWW_1_Central_BMD, self.player).item_rule = not_undernet
self.multiworld.get_location(LocationName.WWW_1_East_BMD, self.player).item_rule = not_undernet
@@ -526,24 +500,24 @@ class MMBN3World(World):
Determine roughly how much of the game you can explore to make certain checks not restrict much movement
"""
score = 0
if state.can_reach_region(RegionName.WWW_Island, self.player):
if state.can_reach(RegionName.WWW_Island, "Region", self.player):
return 999
if state.can_reach_region(RegionName.SciLab_Overworld, self.player):
if state.can_reach(RegionName.SciLab_Overworld, "Region", self.player):
score += 3
if state.can_reach_region(RegionName.SciLab_Cyberworld, self.player):
if state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player):
score += 1
if state.can_reach_region(RegionName.Yoka_Overworld, self.player):
if state.can_reach(RegionName.Yoka_Overworld, "Region", self.player):
score += 2
if state.can_reach_region(RegionName.Yoka_Cyberworld, self.player):
if state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player):
score += 1
if state.can_reach_region(RegionName.Beach_Overworld, self.player):
if state.can_reach(RegionName.Beach_Overworld, "Region", self.player):
score += 3
if state.can_reach_region(RegionName.Beach_Cyberworld, self.player):
if state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player):
score += 1
if state.can_reach_region(RegionName.Undernet, self.player):
if state.can_reach(RegionName.Undernet, "Region", self.player):
score += 2
if state.can_reach_region(RegionName.Deep_Undernet, self.player):
if state.can_reach(RegionName.Deep_Undernet, "Region", self.player):
score += 1
if state.can_reach_region(RegionName.Secret_Area, self.player):
if state.can_reach(RegionName.Secret_Area, "Region", self.player):
score += 1
return score

View File

@@ -6,7 +6,6 @@ class SongData(NamedTuple):
"""Special data container to contain the metadata of each song to make filtering work."""
code: Optional[int]
uid: str
album: str
streamer_mode: bool
easy: Optional[int]

View File

@@ -1,9 +1,13 @@
from .Items import SongData
from .MuseDashData import SONG_DATA
from typing import Dict, List, Set
from .Items import SongData, AlbumData
from typing import Dict, List, Set, Optional
from collections import ChainMap
def load_text_file(name: str) -> str:
import pkgutil
return pkgutil.get_data(__name__, name).decode()
class MuseDashCollections:
"""Contains all the data of Muse Dash, loaded from MuseDashData.txt."""
STARTING_CODE = 2900000
@@ -29,6 +33,15 @@ class MuseDashCollections:
"Rin Len's Mirrorland", # Paid DLC not included in Muse Plus
]
DIFF_OVERRIDES: List[str] = [
"MuseDash ka nanika hi",
"Rush-Hour",
"Find this Month's Featured Playlist",
"PeroPero in the Universe",
"umpopoff",
"P E R O P E R O Brother Dance",
]
REMOVED_SONGS = [
"CHAOS Glitch",
"FM 17314 SUGAR RADIO",
@@ -37,7 +50,9 @@ class MuseDashCollections:
"Tsukuyomi Ni Naru Replaced",
]
song_items = SONG_DATA
album_items: Dict[str, AlbumData] = {}
album_locations: Dict[str, int] = {}
song_items: Dict[str, SongData] = {}
song_locations: Dict[str, int] = {}
trap_items: Dict[str, int] = {
@@ -50,7 +65,7 @@ class MuseDashCollections:
"Gray Scale Trap": STARTING_CODE + 7,
"Nyaa SFX Trap": STARTING_CODE + 8,
"Error SFX Trap": STARTING_CODE + 9,
"Focus Line Trap": STARTING_CODE + 10,
"Focus Line Trap": STARTING_CODE + 10,
}
sfx_trap_items: List[str] = [
@@ -70,13 +85,65 @@ class MuseDashCollections:
"Extra Life": 1,
}
item_names_to_id: ChainMap = ChainMap({k: v.code for k, v in SONG_DATA.items()}, filler_items, trap_items)
location_names_to_id: ChainMap = ChainMap(song_locations)
item_names_to_id: ChainMap = ChainMap({}, filler_items, trap_items)
location_names_to_id: ChainMap = ChainMap(song_locations, album_locations)
def __init__(self) -> None:
self.item_names_to_id[self.MUSIC_SHEET_NAME] = self.MUSIC_SHEET_CODE
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("|")
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.
steamer_mode = sections[3] == "True"
if song_name in self.DIFF_OVERRIDES:
# These songs use non-standard difficulty values. Which are being overriden with standard values.
# But also avoid filling any missing difficulties (i.e. 0s) with a difficulty value.
if sections[4] != '0':
diff_of_easy = 4
else:
diff_of_easy = None
if sections[5] != '0':
diff_of_hard = 7
else:
diff_of_hard = None
if sections[6] != '0':
diff_of_master = 10
else:
diff_of_master = None
else:
diff_of_easy = self.parse_song_difficulty(sections[4])
diff_of_hard = self.parse_song_difficulty(sections[5])
diff_of_master = self.parse_song_difficulty(sections[6])
self.song_items[song_name] = SongData(item_id_index, album, steamer_mode,
diff_of_easy, diff_of_hard, diff_of_master)
item_id_index += 1
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():
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():
self.song_locations[f"{name}-0"] = location_id_index
self.song_locations[f"{name}-1"] = location_id_index + 1
@@ -90,7 +157,7 @@ class MuseDashCollections:
for songKey, songData in self.song_items.items():
if not self.song_matches_dlc_filter(songData, dlc_songs):
continue
if songKey in self.REMOVED_SONGS:
continue
@@ -126,3 +193,18 @@ class MuseDashCollections:
return True
return False
def parse_song_difficulty(self, difficulty: str) -> Optional[int]:
"""Attempts to parse the song difficulty."""
if len(difficulty) <= 0 or difficulty == "?" or difficulty == "¿":
return None
# 0 is used as a filler and no songs actually have a 0 difficulty song.
if difficulty == "0":
return None
# Curse the 2023 april fools update. Used on 3rd Avenue.
if difficulty == "":
return 10
return int(difficulty)

View File

@@ -1,615 +0,0 @@
from .Items import SongData
from typing import Dict
# Auto Generated
SONG_DATA: Dict[str, SongData] = {
"Magical Wonderland": SongData(2900051, "0-48", "Default Music", True, 1, 3, None),
"Iyaiya": SongData(2900052, "0-0", "Default Music", True, 1, 4, None),
"Wonderful Pain": SongData(2900053, "0-2", "Default Music", False, 1, 3, None),
"Breaking Dawn": SongData(2900054, "0-3", "Default Music", True, 2, 4, None),
"One-Way Subway": SongData(2900055, "0-4", "Default Music", True, 1, 4, None),
"Frost Land": SongData(2900056, "0-1", "Default Music", False, 1, 3, 6),
"Heart-Pounding Flight": SongData(2900057, "0-5", "Default Music", True, 2, 5, None),
"Pancake is Love": SongData(2900058, "0-29", "Default Music", True, 2, 4, 7),
"Shiguang Tuya": SongData(2900059, "0-6", "Default Music", True, 2, 5, None),
"Evolution": SongData(2900060, "0-37", "Default Music", False, 2, 4, 7),
"Dolphin and Broadcast": SongData(2900061, "0-7", "Default Music", True, 2, 5, None),
"Yuki no Shizuku Ame no Oto": SongData(2900062, "0-8", "Default Music", True, 2, 4, 6),
"Best One feat.tooko": SongData(2900063, "0-43", "Default Music", False, 3, 5, None),
"Candy-coloured Love Theory": SongData(2900064, "0-31", "Default Music", False, 2, 4, 6),
"Night Wander": SongData(2900065, "0-38", "Default Music", False, 3, 5, 7),
"Dohna Dohna no Uta": SongData(2900066, "0-46", "Default Music", False, 2, 4, 6),
"Spring Carnival": SongData(2900067, "0-9", "Default Music", False, 2, 4, 7),
"DISCO NIGHT": SongData(2900068, "0-30", "Default Music", True, 2, 4, 7),
"Koi no Moonlight": SongData(2900069, "0-49", "Default Music", False, 2, 5, 8),
"Lian Ai Audio Navigation": SongData(2900070, "0-10", "Default Music", False, 3, 5, 7),
"Lights of Muse": SongData(2900071, "0-11", "Default Music", True, 4, 6, 8),
"midstream jam": SongData(2900072, "0-12", "Default Music", False, 2, 5, 8),
"Nihao": SongData(2900073, "0-40", "Default Music", False, 3, 5, 7),
"Confession": SongData(2900074, "0-13", "Default Music", False, 3, 5, 8),
"Galaxy Striker": SongData(2900075, "0-32", "Default Music", False, 4, 7, 9),
"Departure Road": SongData(2900076, "0-14", "Default Music", True, 2, 5, 8),
"Bass Telekinesis": SongData(2900077, "0-15", "Default Music", False, 2, 5, 8),
"Cage of Almeria": SongData(2900078, "0-16", "Default Music", True, 3, 5, 7),
"Ira": SongData(2900079, "0-17", "Default Music", True, 4, 6, 8),
"Blackest Luxury Car": SongData(2900080, "0-18", "Default Music", True, 3, 6, 8),
"Medicine of Sing": SongData(2900081, "0-19", "Default Music", False, 3, 6, 8),
"irregulyze": SongData(2900082, "0-20", "Default Music", True, 3, 6, 8),
"I don't care about Christmas though": SongData(2900083, "0-47", "Default Music", False, 4, 6, 8),
"Imaginary World": SongData(2900084, "0-21", "Default Music", True, 4, 6, 8),
"Dysthymia": SongData(2900085, "0-22", "Default Music", True, 4, 7, 9),
"From the New World": SongData(2900086, "0-42", "Default Music", False, 2, 5, 7),
"NISEGAO": SongData(2900087, "0-33", "Default Music", True, 4, 7, 9),
"Say! Fanfare!": SongData(2900088, "0-44", "Default Music", False, 4, 6, 9),
"Star Driver": SongData(2900089, "0-34", "Default Music", True, 5, 7, 9),
"Formation": SongData(2900090, "0-23", "Default Music", True, 4, 6, 9),
"Shinsou Masui": SongData(2900091, "0-24", "Default Music", True, 4, 6, 10),
"Mezame Eurythmics": SongData(2900092, "0-50", "Default Music", False, 4, 6, 9),
"Shenri Kuaira -repeat-": SongData(2900093, "0-51", "Default Music", False, 5, 7, 9),
"Latitude": SongData(2900094, "0-25", "Default Music", True, 3, 6, 9),
"Aqua Stars": SongData(2900095, "0-39", "Default Music", False, 5, 7, 10),
"Funkotsu Saishin Casino": SongData(2900096, "0-26", "Default Music", False, 5, 7, 10),
"Clock Room & Spiritual World": SongData(2900097, "0-27", "Default Music", True, 4, 6, 9),
"INTERNET OVERDOSE": SongData(2900098, "0-52", "Default Music", False, 3, 6, 9),
"Tu Hua": SongData(2900099, "0-35", "Default Music", True, 4, 7, 9),
"Mujinku-Vacuum": SongData(2900100, "0-28", "Default Music", False, 5, 7, 11),
"MilK": SongData(2900101, "0-36", "Default Music", False, 5, 7, 9),
"umpopoff": SongData(2900102, "0-41", "Default Music", False, None, 7, None),
"Mopemope": SongData(2900103, "0-45", "Default Music", False, 4, 7, 9),
"The Happycore Idol": SongData(2900105, "43-0", "MD Plus Project", True, 2, 5, 7),
"Amatsumikaboshi": SongData(2900106, "43-1", "MD Plus Project", True, 4, 6, 8),
"ARIGA THESIS": SongData(2900107, "43-2", "MD Plus Project", True, 3, 6, 10),
"Night of Nights": SongData(2900108, "43-3", "MD Plus Project", False, 4, 7, 10),
"#Psychedelic_Meguro_River": SongData(2900109, "43-4", "MD Plus Project", False, 3, 6, 8),
"can you feel it": SongData(2900110, "43-5", "MD Plus Project", False, 4, 6, 8),
"Midnight O'clock": SongData(2900111, "43-6", "MD Plus Project", True, 3, 6, 8),
"Rin": SongData(2900112, "43-7", "MD Plus Project", True, 5, 7, 10),
"Smile-mileS": SongData(2900113, "43-8", "MD Plus Project", False, 6, 8, 10),
"Believing and Being": SongData(2900114, "43-9", "MD Plus Project", True, 4, 6, 9),
"Catalyst": SongData(2900115, "43-10", "MD Plus Project", False, 5, 7, 9),
"don't!stop!eroero!": SongData(2900116, "43-11", "MD Plus Project", True, 5, 7, 9),
"pa pi pu pi pu pi pa": SongData(2900117, "43-12", "MD Plus Project", False, 6, 8, 10),
"Sand Maze": SongData(2900118, "43-13", "MD Plus Project", True, 6, 8, 10),
"Diffraction": SongData(2900119, "43-14", "MD Plus Project", True, 5, 8, 10),
"AKUMU": SongData(2900120, "43-15", "MD Plus Project", False, 4, 6, 8),
"Queen Aluett": SongData(2900121, "43-16", "MD Plus Project", True, 7, 9, 11),
"DROPS": SongData(2900122, "43-17", "MD Plus Project", False, 2, 5, 8),
"Frightfully-insane Flan-chan's frightful song": SongData(2900123, "43-18", "MD Plus Project", False, 5, 7, 10),
"snooze": SongData(2900124, "43-19", "MD Plus Project", False, 5, 7, 10),
"Kuishinbo Hacker feat.Kuishinbo Akachan": SongData(2900125, "43-20", "MD Plus Project", True, 5, 7, 9),
"Inu no outa": SongData(2900126, "43-21", "MD Plus Project", True, 3, 5, 7),
"Prism Fountain": SongData(2900127, "43-22", "MD Plus Project", True, 7, 9, 11),
"Gospel": SongData(2900128, "43-23", "MD Plus Project", False, 4, 6, 9),
"East Ai Li Lovely": SongData(2900130, "62-0", "Happy Otaku Pack Vol.17", False, 2, 4, 7),
"Mori Umi no Fune": SongData(2900131, "62-1", "Happy Otaku Pack Vol.17", True, 5, 7, 9),
"Ooi": SongData(2900132, "62-2", "Happy Otaku Pack Vol.17", True, 5, 7, 10),
"Numatta!!": SongData(2900133, "62-3", "Happy Otaku Pack Vol.17", True, 5, 7, 9),
"SATELLITE": SongData(2900134, "62-4", "Happy Otaku Pack Vol.17", False, 5, 7, 9),
"Fantasia Sonata Colorful feat. V!C": SongData(2900135, "62-5", "Happy Otaku Pack Vol.17", True, 6, 8, 11),
"MuseDash ka nanika hi": SongData(2900137, "61-0", "Ola Dash", True, 4, 7, 10),
"Aleph-0": SongData(2900138, "61-1", "Ola Dash", True, 7, 9, 11),
"Buttoba Supernova": SongData(2900139, "61-2", "Ola Dash", False, 5, 7, 10),
"Rush-Hour": SongData(2900140, "61-3", "Ola Dash", False, 4, 7, 10),
"3rd Avenue": SongData(2900141, "61-4", "Ola Dash", False, 3, 5, 10),
"WORLDINVADER": SongData(2900142, "61-5", "Ola Dash", True, 5, 8, 10),
"N3V3R G3T OV3R": SongData(2900144, "60-0", "maimai DX Limited-time Suite", True, 4, 7, 10),
"Oshama Scramble!": SongData(2900145, "60-1", "maimai DX Limited-time Suite", True, 5, 7, 10),
"Valsqotch": SongData(2900146, "60-2", "maimai DX Limited-time Suite", True, 5, 9, 11),
"Paranormal My Mind": SongData(2900147, "60-3", "maimai DX Limited-time Suite", True, 5, 7, 9),
"Flower, snow and Drum'n'bass.": SongData(2900148, "60-4", "maimai DX Limited-time Suite", True, 5, 8, 10),
"Amenohoakari": SongData(2900149, "60-5", "maimai DX Limited-time Suite", True, 6, 8, 10),
"Boiling Blood": SongData(2900151, "59-0", "MSR Anthology", True, 5, 8, 10),
"ManiFesto": SongData(2900152, "59-1", "MSR Anthology", True, 4, 6, 9),
"Operation Blade": SongData(2900153, "59-2", "MSR Anthology", True, 3, 5, 7),
"Radiant": SongData(2900154, "59-3", "MSR Anthology", True, 3, 5, 8),
"Renegade": SongData(2900155, "59-4", "MSR Anthology", True, 3, 5, 8),
"Speed of Light": SongData(2900156, "59-5", "MSR Anthology", False, 1, 4, 7),
"Dossoles Holiday": SongData(2900157, "59-6", "MSR Anthology", True, 5, 7, 9),
"Autumn Moods": SongData(2900158, "59-7", "MSR Anthology", True, 3, 5, 7),
"People People": SongData(2900160, "58-0", "Nanahira Paradise", True, 5, 7, 9),
"Endless Error Loop": SongData(2900161, "58-1", "Nanahira Paradise", True, 4, 7, 9),
"Forbidden Pizza!": SongData(2900162, "58-2", "Nanahira Paradise", True, 5, 7, 9),
"Don't Make the Vocalist do Anything Insane": SongData(2900163, "58-3", "Nanahira Paradise", True, 5, 8, 9),
"Tokimeki*Meteostrike": SongData(2900165, "57-0", "Happy Otaku Pack Vol.16", True, 3, 6, 8),
"Down Low": SongData(2900166, "57-1", "Happy Otaku Pack Vol.16", True, 4, 6, 8),
"LOUDER MACHINE": SongData(2900167, "57-2", "Happy Otaku Pack Vol.16", True, 5, 7, 9),
"Sorewa mo Lovechu": SongData(2900168, "57-3", "Happy Otaku Pack Vol.16", True, 5, 7, 10),
"Rave_Tech": SongData(2900169, "57-4", "Happy Otaku Pack Vol.16", True, 5, 8, 10),
"Brilliant & Shining!": SongData(2900170, "57-5", "Happy Otaku Pack Vol.16", False, 5, 8, 10),
"Psyched Fevereiro": SongData(2900172, "56-0", "Give Up TREATMENT Vol.11", False, 5, 8, 10),
"Inferno City": SongData(2900173, "56-1", "Give Up TREATMENT Vol.11", False, 6, 8, 10),
"Paradigm Shift": SongData(2900174, "56-2", "Give Up TREATMENT Vol.11", False, 4, 7, 10),
"Snapdragon": SongData(2900175, "56-3", "Give Up TREATMENT Vol.11", False, 5, 7, 10),
"Prestige and Vestige": SongData(2900176, "56-4", "Give Up TREATMENT Vol.11", True, 6, 8, 11),
"Tiny Fate": SongData(2900177, "56-5", "Give Up TREATMENT Vol.11", False, 7, 9, 11),
"Tsuki ni Murakumo Hana ni Kaze": SongData(2900179, "55-0", "Touhou Mugakudan -II-", False, 3, 5, 7),
"Patchouli's - Best Hit GSK": SongData(2900180, "55-1", "Touhou Mugakudan -II-", False, 3, 5, 8),
"Monosugoi Space Shuttle de Koishi ga Monosugoi uta": SongData(2900181, "55-2", "Touhou Mugakudan -II-", False, 3, 5, 7),
"Kakoinaki Yo wa Ichigo no Tsukikage": SongData(2900182, "55-3", "Touhou Mugakudan -II-", False, 3, 6, 8),
"Psychedelic Kizakura Doumei": SongData(2900183, "55-4", "Touhou Mugakudan -II-", False, 4, 7, 10),
"Mischievous Sensation": SongData(2900184, "55-5", "Touhou Mugakudan -II-", False, 5, 7, 9),
"White Canvas": SongData(2900186, "54-0", "MEGAREX THE FUTURE", False, 3, 6, 8),
"Gloomy Flash": SongData(2900187, "54-1", "MEGAREX THE FUTURE", False, 5, 8, 10),
"Find this Month's Featured Playlist": SongData(2900188, "54-2", "MEGAREX THE FUTURE", False, 4, 7, 10),
"Sunday Night": SongData(2900189, "54-3", "MEGAREX THE FUTURE", False, 3, 6, 9),
"Goodbye Goodnight": SongData(2900190, "54-4", "MEGAREX THE FUTURE", False, 4, 6, 9),
"ENDLESS CIDER": SongData(2900191, "54-5", "MEGAREX THE FUTURE", False, 4, 6, 8),
"On And On!!": SongData(2900193, "53-0", "Happy Otaku Pack Vol.15", True, 4, 7, 9),
"Trip!": SongData(2900194, "53-1", "Happy Otaku Pack Vol.15", True, 3, 5, 7),
"Hoshi no otoshimono": SongData(2900195, "53-2", "Happy Otaku Pack Vol.15", False, 5, 7, 9),
"Plucky Race": SongData(2900196, "53-3", "Happy Otaku Pack Vol.15", True, 5, 8, 10),
"Fantasia Sonata Destiny": SongData(2900197, "53-4", "Happy Otaku Pack Vol.15", True, 3, 7, 10),
"Run through": SongData(2900198, "53-5", "Happy Otaku Pack Vol.15", False, 5, 8, 10),
"marooned night": SongData(2900200, "52-0", "MUSE RADIO FM103", False, 2, 4, 6),
"daydream girl": SongData(2900201, "52-1", "MUSE RADIO FM103", False, 3, 6, 8),
"Not Ornament": SongData(2900202, "52-2", "MUSE RADIO FM103", True, 3, 5, 8),
"Baby Pink": SongData(2900203, "52-3", "MUSE RADIO FM103", False, 3, 5, 8),
"I'm Here": SongData(2900204, "52-4", "MUSE RADIO FM103", False, 4, 6, 8),
"Masquerade Diary": SongData(2900206, "51-0", "Virtual Idol Production", True, 2, 5, 8),
"Reminiscence": SongData(2900207, "51-1", "Virtual Idol Production", True, 5, 7, 9),
"DarakuDatenshi": SongData(2900208, "51-2", "Virtual Idol Production", True, 3, 6, 9),
"D.I.Y.": SongData(2900209, "51-3", "Virtual Idol Production", False, 4, 6, 9),
"Boys in Virtual Land": SongData(2900210, "51-4", "Virtual Idol Production", False, 4, 7, 9),
"kui": SongData(2900211, "51-5", "Virtual Idol Production", True, 5, 7, 9),
"Nyan Cat": SongData(2900213, "50-0", "Nyanya Universe!", False, 4, 7, 9),
"PeroPero in the Universe": SongData(2900214, "50-1", "Nyanya Universe!", True, 4, 7, 10),
"In-kya Yo-kya Onmyoji": SongData(2900215, "50-2", "Nyanya Universe!", False, 6, 8, 10),
"KABOOOOOM!!!!": SongData(2900216, "50-3", "Nyanya Universe!", True, 4, 6, 8),
"Doppelganger": SongData(2900217, "50-4", "Nyanya Universe!", True, 5, 7, 9),
"Pray a LOVE": SongData(2900219, "49-0", "DokiDoki! Valentine!", False, 2, 5, 8),
"Love-Avoidance Addiction": SongData(2900220, "49-1", "DokiDoki! Valentine!", False, 3, 5, 7),
"Daisuki Dayo feat.Wotoha": SongData(2900221, "49-2", "DokiDoki! Valentine!", False, 5, 7, 10),
"glory day": SongData(2900223, "48-0", "DJMAX Reflect", False, 2, 5, 7),
"Bright Dream": SongData(2900224, "48-1", "DJMAX Reflect", False, 2, 4, 7),
"Groovin Up": SongData(2900225, "48-2", "DJMAX Reflect", False, 4, 6, 8),
"I Want You": SongData(2900226, "48-3", "DJMAX Reflect", False, 3, 6, 8),
"OBLIVION": SongData(2900227, "48-4", "DJMAX Reflect", False, 3, 6, 9),
"Elastic STAR": SongData(2900228, "48-5", "DJMAX Reflect", False, 4, 6, 8),
"U.A.D": SongData(2900229, "48-6", "DJMAX Reflect", False, 4, 6, 8),
"Jealousy": SongData(2900230, "48-7", "DJMAX Reflect", False, 3, 5, 7),
"Memory of Beach": SongData(2900231, "48-8", "DJMAX Reflect", False, 3, 6, 8),
"Don't Die": SongData(2900232, "48-9", "DJMAX Reflect", False, 6, 8, 10),
"Y CE Ver.": SongData(2900233, "48-10", "DJMAX Reflect", False, 4, 6, 9),
"Fancy Night": SongData(2900234, "48-11", "DJMAX Reflect", False, 4, 6, 8),
"Can We Talk": SongData(2900235, "48-12", "DJMAX Reflect", False, 4, 6, 8),
"Give Me 5": SongData(2900236, "48-13", "DJMAX Reflect", False, 2, 6, 8),
"Nightmare": SongData(2900237, "48-14", "DJMAX Reflect", False, 7, 9, 11),
"Haze of Autumn": SongData(2900239, "47-0", "Arcaea", True, 3, 6, 9),
"GIMME DA BLOOD": SongData(2900240, "47-1", "Arcaea", False, 3, 6, 9),
"Libertas": SongData(2900241, "47-2", "Arcaea", False, 4, 7, 10),
"Cyaegha": SongData(2900242, "47-3", "Arcaea", False, 5, 7, 9),
"Bang!!": SongData(2900244, "46-0", "Happy Otaku Pack Vol.14", False, 4, 6, 8),
"Paradise 2": SongData(2900245, "46-1", "Happy Otaku Pack Vol.14", False, 4, 6, 8),
"Symbol": SongData(2900246, "46-2", "Happy Otaku Pack Vol.14", False, 5, 7, 9),
"Nekojarashi": SongData(2900247, "46-3", "Happy Otaku Pack Vol.14", False, 5, 8, 10),
"A Philosophical Wanderer": SongData(2900248, "46-4", "Happy Otaku Pack Vol.14", False, 4, 6, 10),
"Isouten": SongData(2900249, "46-5", "Happy Otaku Pack Vol.14", True, 6, 8, 10),
"ONOMATO Pairing!!!": SongData(2900251, "45-0", "WACCA Horizon", False, 4, 6, 9),
"with U": SongData(2900252, "45-1", "WACCA Horizon", False, 6, 8, 10),
"Chariot": SongData(2900253, "45-2", "WACCA Horizon", False, 3, 6, 9),
"GASHATT": SongData(2900254, "45-3", "WACCA Horizon", False, 5, 7, 10),
"LIN NE KRO NE feat. lasah": SongData(2900255, "45-4", "WACCA Horizon", False, 6, 8, 10),
"ANGEL HALO": SongData(2900256, "45-5", "WACCA Horizon", False, 5, 8, 11),
"Party in the HOLLOWood": SongData(2900258, "44-0", "Happy Otaku Pack Vol.13", False, 3, 6, 8),
"Ying Ying da Zuozhan": SongData(2900259, "44-1", "Happy Otaku Pack Vol.13", True, 5, 7, 9),
"Howlin' Pumpkin": SongData(2900260, "44-2", "Happy Otaku Pack Vol.13", True, 4, 6, 8),
"Bad Apple!! feat. Nomico": SongData(2900262, "42-0", "Touhou Mugakudan -I-", False, 1, 3, 6),
"Iro wa Nioedo, Chirinuru wo": SongData(2900263, "42-1", "Touhou Mugakudan -I-", False, 2, 4, 7),
"Cirno's Perfect Math Class": SongData(2900264, "42-2", "Touhou Mugakudan -I-", False, 4, 7, 9),
"Hiiro Gekka Kyousai no Zetsu": SongData(2900265, "42-3", "Touhou Mugakudan -I-", False, 4, 6, 8),
"Flowery Moonlit Night": SongData(2900266, "42-4", "Touhou Mugakudan -I-", False, 3, 6, 8),
"Unconscious Requiem": SongData(2900267, "42-5", "Touhou Mugakudan -I-", False, 3, 6, 8),
"Super Battleworn Insomniac": SongData(2900269, "41-0", "7th Beat Games", True, 4, 7, 9),
"Bomb-Sniffing Pomeranian": SongData(2900270, "41-1", "7th Beat Games", True, 4, 6, 8),
"Rollerdisco Rumble": SongData(2900271, "41-2", "7th Beat Games", True, 4, 6, 9),
"Rose Garden": SongData(2900272, "41-3", "7th Beat Games", False, 5, 8, 9),
"EMOMOMO": SongData(2900273, "41-4", "7th Beat Games", True, 4, 7, 10),
"Heracles": SongData(2900274, "41-5", "7th Beat Games", False, 6, 8, 10),
"Rush-More": SongData(2900276, "40-0", "Happy Otaku Pack Vol.12", False, 4, 7, 9),
"Kill My Fortune": SongData(2900277, "40-1", "Happy Otaku Pack Vol.12", False, 5, 7, 10),
"Yosari Tsukibotaru Suminoborite": SongData(2900278, "40-2", "Happy Otaku Pack Vol.12", False, 5, 7, 9),
"JUMP! HardCandy": SongData(2900279, "40-3", "Happy Otaku Pack Vol.12", False, 3, 6, 8),
"Hibari": SongData(2900280, "40-4", "Happy Otaku Pack Vol.12", False, 3, 5, 8),
"OCCHOCO-REST-LESS": SongData(2900281, "40-5", "Happy Otaku Pack Vol.12", True, 4, 7, 9),
"See-Saw Day": SongData(2900283, "39-0", "MUSE RADIO FM102", True, 1, 3, 6),
"happy hour": SongData(2900284, "39-1", "MUSE RADIO FM102", True, 2, 4, 7),
"Seikimatsu no Natsu": SongData(2900285, "39-2", "MUSE RADIO FM102", True, 4, 6, 8),
"twinkle night": SongData(2900286, "39-3", "MUSE RADIO FM102", False, 3, 6, 8),
"ARUYA HARERUYA": SongData(2900287, "39-4", "MUSE RADIO FM102", False, 2, 5, 7),
"Blush": SongData(2900288, "39-5", "MUSE RADIO FM102", False, 2, 4, 7),
"Naked Summer": SongData(2900289, "39-6", "MUSE RADIO FM102", True, 4, 6, 8),
"BLESS ME": SongData(2900290, "39-7", "MUSE RADIO FM102", True, 2, 5, 7),
"FM 17314 SUGAR RADIO": SongData(2900291, "39-8", "MUSE RADIO FM102", True, None, None, None),
"NO ONE YES MAN": SongData(2900293, "38-0", "Phigros", False, 5, 7, 9),
"Snowfall, Merry Christmas": SongData(2900294, "38-1", "Phigros", False, 5, 8, 10),
"Igallta": SongData(2900295, "38-2", "Phigros", False, 6, 8, 10),
"Colored Glass": SongData(2900297, "37-0", "Cute Is Everything Vol.7", False, 1, 4, 7),
"Neonlights": SongData(2900298, "37-1", "Cute Is Everything Vol.7", False, 4, 7, 9),
"Hope for the flowers": SongData(2900299, "37-2", "Cute Is Everything Vol.7", False, 4, 7, 9),
"Seaside Cycling on May 30": SongData(2900300, "37-3", "Cute Is Everything Vol.7", False, 3, 6, 8),
"SKY HIGH": SongData(2900301, "37-4", "Cute Is Everything Vol.7", False, 2, 4, 6),
"Mousou Chu!!": SongData(2900302, "37-5", "Cute Is Everything Vol.7", False, 4, 7, 8),
"NightTheater": SongData(2900304, "36-0", "Give Up TREATMENT Vol.10", True, 6, 8, 11),
"Cutter": SongData(2900305, "36-1", "Give Up TREATMENT Vol.10", False, 4, 7, 10),
"bamboo": SongData(2900306, "36-2", "Give Up TREATMENT Vol.10", False, 6, 8, 10),
"enchanted love": SongData(2900307, "36-3", "Give Up TREATMENT Vol.10", False, 2, 6, 9),
"c.s.q.n.": SongData(2900308, "36-4", "Give Up TREATMENT Vol.10", False, 5, 8, 11),
"Booouncing!!": SongData(2900309, "36-5", "Give Up TREATMENT Vol.10", False, 5, 7, 10),
"PeroPeroGames goes Bankrupt": SongData(2900311, "35-0", "Happy Otaku Pack SP", True, 6, 8, 10),
"MARENOL": SongData(2900312, "35-1", "Happy Otaku Pack SP", False, 4, 7, 10),
"I am really good at Japanese style": SongData(2900313, "35-2", "Happy Otaku Pack SP", True, 6, 8, 10),
"Rush B": SongData(2900314, "35-3", "Happy Otaku Pack SP", True, 4, 7, 9),
"DataErr0r": SongData(2900315, "35-4", "Happy Otaku Pack SP", False, 5, 7, 9),
"Burn": SongData(2900316, "35-5", "Happy Otaku Pack SP", True, 4, 7, 9),
"ALiVE": SongData(2900318, "34-0", "HARDCORE TANO*C", False, 5, 7, 10),
"BATTLE NO.1": SongData(2900319, "34-1", "HARDCORE TANO*C", False, 5, 8, 10),
"Cthugha": SongData(2900320, "34-2", "HARDCORE TANO*C", False, 6, 8, 10),
"TWINKLE*MAGIC": SongData(2900321, "34-3", "HARDCORE TANO*C", False, 4, 7, 10),
"Comet Coaster": SongData(2900322, "34-4", "HARDCORE TANO*C", False, 6, 8, 10),
"XODUS": SongData(2900323, "34-5", "HARDCORE TANO*C", False, 7, 9, 11),
"Fireflies": SongData(2900325, "33-0", "cyTus", True, 1, 4, 7),
"Light up my love!!": SongData(2900326, "33-1", "cyTus", True, 3, 5, 7),
"Happiness Breeze": SongData(2900327, "33-2", "cyTus", True, 4, 6, 8),
"Chrome VOX": SongData(2900328, "33-3", "cyTus", True, 6, 8, 10),
"CHAOS": SongData(2900329, "33-4", "cyTus", True, 3, 6, 9),
"Saika": SongData(2900330, "33-5", "cyTus", True, 3, 5, 8),
"Standby for Action": SongData(2900331, "33-6", "cyTus", True, 4, 6, 8),
"Hydrangea": SongData(2900332, "33-7", "cyTus", True, 5, 7, 9),
"Amenemhat": SongData(2900333, "33-8", "cyTus", True, 6, 8, 10),
"Santouka": SongData(2900334, "33-9", "cyTus", True, 2, 5, 8),
"HEXENNACHTROCK-katashihaya-": SongData(2900335, "33-10", "cyTus", True, 4, 8, 10),
"Blah!!": SongData(2900336, "33-11", "cyTus", True, 5, 8, 11),
"CHAOS Glitch": SongData(2900337, "33-12", "cyTus", True, None, None, None),
"Preparara": SongData(2900339, "32-0", "Let's Do Bad Things Together", False, 1, 4, 6),
"Whatcha;Whatcha Doin'": SongData(2900340, "32-1", "Let's Do Bad Things Together", False, 3, 6, 9),
"Madara": SongData(2900341, "32-2", "Let's Do Bad Things Together", False, 4, 6, 9),
"pICARESq": SongData(2900342, "32-3", "Let's Do Bad Things Together", False, 4, 6, 8),
"Desastre": SongData(2900343, "32-4", "Let's Do Bad Things Together", False, 4, 6, 8),
"Shoot for the Moon": SongData(2900344, "32-5", "Let's Do Bad Things Together", False, 2, 5, 8),
"The 90's Decision": SongData(2900346, "31-0", "Happy Otaku Pack Vol.11", True, 5, 7, 9),
"Medusa": SongData(2900347, "31-1", "Happy Otaku Pack Vol.11", False, 4, 6, 8),
"Final Step!": SongData(2900348, "31-2", "Happy Otaku Pack Vol.11", False, 5, 7, 10),
"MAGENTA POTION": SongData(2900349, "31-3", "Happy Otaku Pack Vol.11", False, 4, 7, 9),
"Cross Ray": SongData(2900350, "31-4", "Happy Otaku Pack Vol.11", False, 3, 6, 9),
"Square Lake": SongData(2900351, "31-5", "Happy Otaku Pack Vol.11", False, 6, 8, 9),
"Girly Cupid": SongData(2900353, "30-0", "Cute Is Everything Vol.6", False, 3, 6, 8),
"sheep in the light": SongData(2900354, "30-1", "Cute Is Everything Vol.6", False, 2, 5, 8),
"Breaker city": SongData(2900355, "30-2", "Cute Is Everything Vol.6", False, 4, 6, 9),
"heterodoxy": SongData(2900356, "30-3", "Cute Is Everything Vol.6", False, 4, 6, 8),
"Computer Music Girl": SongData(2900357, "30-4", "Cute Is Everything Vol.6", False, 3, 5, 7),
"Focus Point": SongData(2900358, "30-5", "Cute Is Everything Vol.6", True, 2, 5, 7),
"Groove Prayer": SongData(2900360, "29-0", "Let' s GROOVE!", True, 3, 5, 7),
"FUJIN Rumble": SongData(2900361, "29-1", "Let' s GROOVE!", True, 5, 7, 10),
"Marry me, Nightmare": SongData(2900362, "29-2", "Let' s GROOVE!", False, 6, 8, 11),
"HG Makaizou Polyvinyl Shounen": SongData(2900363, "29-3", "Let' s GROOVE!", True, 4, 7, 9),
"Seizya no Ibuki": SongData(2900364, "29-4", "Let' s GROOVE!", True, 6, 8, 10),
"ouroboros -twin stroke of the end-": SongData(2900365, "29-5", "Let' s GROOVE!", True, 4, 6, 9),
"Heisha Onsha": SongData(2900367, "28-0", "Happy Otaku Pack Vol.10", False, 4, 6, 8),
"Ginevra": SongData(2900368, "28-1", "Happy Otaku Pack Vol.10", True, 5, 7, 10),
"Paracelestia": SongData(2900369, "28-2", "Happy Otaku Pack Vol.10", False, 5, 8, 10),
"un secret": SongData(2900370, "28-3", "Happy Otaku Pack Vol.10", False, 2, 4, 6),
"Good Life": SongData(2900371, "28-4", "Happy Otaku Pack Vol.10", False, 4, 6, 8),
"nini-nini-": SongData(2900372, "28-5", "Happy Otaku Pack Vol.10", False, 4, 7, 9),
"Can I friend you on Bassbook? lol": SongData(2900374, "27-0", "Nanahira Festival", False, 3, 6, 8),
"Gaming*Everything": SongData(2900375, "27-1", "Nanahira Festival", False, 5, 8, 11),
"Renji de haochi": SongData(2900376, "27-2", "Nanahira Festival", False, 5, 7, 9),
"You Make My Life 1UP": SongData(2900377, "27-3", "Nanahira Festival", False, 4, 6, 8),
"Newbies, Geeks, Internets": SongData(2900378, "27-4", "Nanahira Festival", False, 6, 8, 10),
"Onegai!Kon kon Oinarisama": SongData(2900379, "27-5", "Nanahira Festival", False, 3, 6, 9),
"Legend of Eastern Rabbit -SKY DEFENDER-": SongData(2900381, "26-0", "Give Up TREATMENT Vol.9", False, 4, 6, 9),
"ENERGY SYNERGY MATRIX": SongData(2900382, "26-1", "Give Up TREATMENT Vol.9", False, 6, 8, 10),
"Punai Punai Genso": SongData(2900383, "26-2", "Give Up TREATMENT Vol.9", False, 2, 7, 11),
"Better Graphic Animation": SongData(2900384, "26-3", "Give Up TREATMENT Vol.9", False, 5, 8, 11),
"Variant Cross": SongData(2900385, "26-4", "Give Up TREATMENT Vol.9", False, 4, 7, 10),
"Ultra Happy Miracle Bazoooooka!!": SongData(2900386, "26-5", "Give Up TREATMENT Vol.9", False, 7, 9, 11),
"tape/stop/night": SongData(2900388, "25-0", "MUSE RADIO FM101", True, 3, 5, 7),
"Pixel Galaxy": SongData(2900389, "25-1", "MUSE RADIO FM101", False, 2, 5, 8),
"Notice": SongData(2900390, "25-2", "MUSE RADIO FM101", False, 4, 7, 10),
"Strawberry Godzilla": SongData(2900391, "25-3", "MUSE RADIO FM101", True, 2, 5, 7),
"OKIMOCHI EXPRESSION": SongData(2900392, "25-4", "MUSE RADIO FM101", False, 4, 6, 10),
"Kimi to pool disco": SongData(2900393, "25-5", "MUSE RADIO FM101", False, 4, 6, 8),
"The Last Page": SongData(2900395, "24-0", "Happy Otaku Pack Vol.9", False, 3, 5, 7),
"IKAROS": SongData(2900396, "24-1", "Happy Otaku Pack Vol.9", False, 4, 7, 10),
"Tsukuyomi": SongData(2900397, "24-2", "Happy Otaku Pack Vol.9", False, 3, 6, 9),
"Future Stream": SongData(2900398, "24-3", "Happy Otaku Pack Vol.9", False, 4, 6, 8),
"FULi AUTO SHOOTER": SongData(2900399, "24-4", "Happy Otaku Pack Vol.9", True, 4, 7, 9),
"GOODFORTUNE": SongData(2900400, "24-5", "Happy Otaku Pack Vol.9", False, 5, 7, 9),
"The Dessert After Rain": SongData(2900402, "23-0", "Cute Is Everything Vol.5", True, 2, 4, 6),
"Confession Support Formula": SongData(2900403, "23-1", "Cute Is Everything Vol.5", False, 3, 5, 7),
"Omatsuri": SongData(2900404, "23-2", "Cute Is Everything Vol.5", False, 1, 3, 6),
"FUTUREPOP": SongData(2900405, "23-3", "Cute Is Everything Vol.5", True, 2, 5, 7),
"The Breeze": SongData(2900406, "23-4", "Cute Is Everything Vol.5", False, 1, 4, 6),
"I LOVE LETTUCE FRIED RICE!!": SongData(2900407, "23-5", "Cute Is Everything Vol.5", False, 3, 7, 9),
"The NightScape": SongData(2900409, "22-0", "Give Up TREATMENT Vol.8", False, 4, 7, 9),
"FREEDOM DiVE": SongData(2900410, "22-1", "Give Up TREATMENT Vol.8", False, 6, 8, 10),
"Phi": SongData(2900411, "22-2", "Give Up TREATMENT Vol.8", False, 5, 8, 10),
"Lueur de la nuit": SongData(2900412, "22-3", "Give Up TREATMENT Vol.8", False, 6, 8, 11),
"Creamy Sugary OVERDRIVE!!!": SongData(2900413, "22-4", "Give Up TREATMENT Vol.8", True, 4, 7, 10),
"Disorder": SongData(2900414, "22-5", "Give Up TREATMENT Vol.8", False, 5, 7, 11),
"Glimmer": SongData(2900416, "21-0", "Budget Is Burning: Nano Core", False, 2, 5, 8),
"EXIST": SongData(2900417, "21-1", "Budget Is Burning: Nano Core", False, 3, 5, 8),
"Irreplaceable": SongData(2900418, "21-2", "Budget Is Burning: Nano Core", False, 4, 6, 8),
"Moonlight Banquet": SongData(2900420, "20-0", "Happy Otaku Pack Vol.8", True, 2, 5, 8),
"Flashdance": SongData(2900421, "20-1", "Happy Otaku Pack Vol.8", False, 3, 6, 9),
"INFiNiTE ENERZY -Overdoze-": SongData(2900422, "20-2", "Happy Otaku Pack Vol.8", False, 4, 7, 9),
"One Way Street": SongData(2900423, "20-3", "Happy Otaku Pack Vol.8", False, 3, 6, 10),
"This Club is Not 4 U": SongData(2900424, "20-4", "Happy Otaku Pack Vol.8", False, 4, 7, 9),
"ULTRA MEGA HAPPY PARTY!!!": SongData(2900425, "20-5", "Happy Otaku Pack Vol.8", False, 5, 7, 10),
"INFINITY": SongData(2900427, "19-0", "Give Up TREATMENT Vol.7", True, 5, 8, 10),
"Punai Punai Senso": SongData(2900428, "19-1", "Give Up TREATMENT Vol.7", False, 2, 7, 11),
"Maxi": SongData(2900429, "19-2", "Give Up TREATMENT Vol.7", False, 5, 8, 10),
"YInMn Blue": SongData(2900430, "19-3", "Give Up TREATMENT Vol.7", False, 6, 8, 10),
"Plumage": SongData(2900431, "19-4", "Give Up TREATMENT Vol.7", False, 4, 7, 10),
"Dr.Techro": SongData(2900432, "19-5", "Give Up TREATMENT Vol.7", False, 7, 9, 11),
"SWEETSWEETSWEET": SongData(2900434, "18-0", "Cute Is Everything Vol.4", True, 2, 5, 7),
"Deep Blue and the Breaths of the Night": SongData(2900435, "18-1", "Cute Is Everything Vol.4", True, 2, 4, 6),
"Joy Connection": SongData(2900436, "18-2", "Cute Is Everything Vol.4", False, 3, 6, 8),
"Self Willed Girl Ver.B": SongData(2900437, "18-3", "Cute Is Everything Vol.4", True, 4, 6, 8),
"Just Disobedient": SongData(2900438, "18-4", "Cute Is Everything Vol.4", False, 3, 6, 8),
"Holy Sh*t Grass Snake": SongData(2900439, "18-5", "Cute Is Everything Vol.4", False, 2, 6, 9),
"Cotton Candy Wonderland": SongData(2900441, "17-0", "Happy Otaku Pack Vol.7", False, 2, 5, 8),
"Punai Punai Taiso": SongData(2900442, "17-1", "Happy Otaku Pack Vol.7", False, 2, 7, 10),
"Fly High": SongData(2900443, "17-2", "Happy Otaku Pack Vol.7", False, 3, 5, 7),
"prejudice": SongData(2900444, "17-3", "Happy Otaku Pack Vol.7", True, 4, 6, 9),
"The 89's Momentum": SongData(2900445, "17-4", "Happy Otaku Pack Vol.7", True, 5, 7, 9),
"energy night": SongData(2900446, "17-5", "Happy Otaku Pack Vol.7", True, 5, 7, 10),
"Future Dive": SongData(2900448, "16-0", "Give Up TREATMENT Vol.6", True, 4, 6, 9),
"Re End of a Dream": SongData(2900449, "16-1", "Give Up TREATMENT Vol.6", False, 5, 8, 11),
"Etude -Storm-": SongData(2900450, "16-2", "Give Up TREATMENT Vol.6", True, 6, 8, 10),
"Unlimited Katharsis": SongData(2900451, "16-3", "Give Up TREATMENT Vol.6", False, 4, 6, 10),
"Magic Knight Girl": SongData(2900452, "16-4", "Give Up TREATMENT Vol.6", False, 4, 7, 9),
"Eeliaas": SongData(2900453, "16-5", "Give Up TREATMENT Vol.6", False, 6, 9, 11),
"Magic Spell": SongData(2900455, "15-0", "Cute Is Everything Vol.3", True, 2, 5, 7),
"Colorful Star, Colored Drawing, Travel Poem": SongData(2900456, "15-1", "Cute Is Everything Vol.3", False, 3, 4, 6),
"Satell Knight": SongData(2900457, "15-2", "Cute Is Everything Vol.3", False, 3, 6, 8),
"Black River Feat.Mes": SongData(2900458, "15-3", "Cute Is Everything Vol.3", True, 1, 4, 6),
"I am sorry": SongData(2900459, "15-4", "Cute Is Everything Vol.3", False, 2, 5, 8),
"Ueta Tori Tachi": SongData(2900460, "15-5", "Cute Is Everything Vol.3", False, 3, 6, 8),
"Elysion's Old Mans": SongData(2900462, "14-0", "Happy Otaku Pack Vol.6", False, 3, 5, 8),
"AXION": SongData(2900463, "14-1", "Happy Otaku Pack Vol.6", False, 4, 5, 8),
"Amnesia": SongData(2900464, "14-2", "Happy Otaku Pack Vol.6", True, 3, 6, 9),
"Onsen Dai Sakusen": SongData(2900465, "14-3", "Happy Otaku Pack Vol.6", True, 4, 6, 8),
"Gleam stone": SongData(2900466, "14-4", "Happy Otaku Pack Vol.6", False, 4, 7, 9),
"GOODWORLD": SongData(2900467, "14-5", "Happy Otaku Pack Vol.6", False, 4, 7, 10),
"Instant Soluble Neon": SongData(2900469, "13-0", "Cute Is Everything Vol.2", True, 2, 4, 7),
"Retrospective Poem on the Planet": SongData(2900470, "13-1", "Cute Is Everything Vol.2", False, 3, 5, 7),
"I'm Gonna Buy! Buy! Buy!": SongData(2900471, "13-2", "Cute Is Everything Vol.2", True, 4, 6, 8),
"Dating Manifesto": SongData(2900472, "13-3", "Cute Is Everything Vol.2", True, 2, 4, 6),
"First Snow": SongData(2900473, "13-4", "Cute Is Everything Vol.2", True, 2, 3, 6),
"Xin Shang Huahai": SongData(2900474, "13-5", "Cute Is Everything Vol.2", False, 3, 6, 8),
"Gaikan Chrysalis": SongData(2900476, "12-0", "Give Up TREATMENT Vol.5", False, 4, 6, 8),
"Sterelogue": SongData(2900477, "12-1", "Give Up TREATMENT Vol.5", True, 5, 7, 10),
"Cheshire's Dance": SongData(2900478, "12-2", "Give Up TREATMENT Vol.5", True, 4, 7, 10),
"Skrik": SongData(2900479, "12-3", "Give Up TREATMENT Vol.5", True, 5, 7, 11),
"Soda Pop Canva5!": SongData(2900480, "12-4", "Give Up TREATMENT Vol.5", False, 5, 8, 10),
"RUBY LINTe": SongData(2900481, "12-5", "Give Up TREATMENT Vol.5", False, 5, 8, 11),
"Brave My Heart": SongData(2900483, "11-0", "Happy Otaku Pack Vol.5", True, 3, 5, 7),
"Sakura Fubuki": SongData(2900484, "11-1", "Happy Otaku Pack Vol.5", False, 4, 7, 10),
"8bit Adventurer": SongData(2900485, "11-2", "Happy Otaku Pack Vol.5", False, 6, 8, 10),
"Suffering of screw": SongData(2900486, "11-3", "Happy Otaku Pack Vol.5", False, 3, 5, 8),
"tiny lady": SongData(2900487, "11-4", "Happy Otaku Pack Vol.5", True, 4, 6, 9),
"Power Attack": SongData(2900488, "11-5", "Happy Otaku Pack Vol.5", False, 5, 7, 10),
"Destr0yer": SongData(2900490, "10-0", "Give Up TREATMENT Vol.4", False, 4, 7, 9),
"Noel": SongData(2900491, "10-1", "Give Up TREATMENT Vol.4", False, 5, 8, 10),
"Kyoukiranbu": SongData(2900492, "10-2", "Give Up TREATMENT Vol.4", False, 7, 9, 11),
"Two Phace": SongData(2900493, "10-3", "Give Up TREATMENT Vol.4", True, 4, 7, 10),
"Fly Again": SongData(2900494, "10-4", "Give Up TREATMENT Vol.4", False, 5, 7, 10),
"ouroVoros": SongData(2900495, "10-5", "Give Up TREATMENT Vol.4", False, 7, 9, 11),
"Leave It Alone": SongData(2900497, "9-0", "Happy Otaku Pack Vol.4", True, 2, 5, 8),
"Tsubasa no Oreta Tenshitachi no Requiem": SongData(2900498, "9-1", "Happy Otaku Pack Vol.4", False, 4, 7, 9),
"Chronomia": SongData(2900499, "9-2", "Happy Otaku Pack Vol.4", False, 5, 7, 10),
"Dandelion's Daydream": SongData(2900500, "9-3", "Happy Otaku Pack Vol.4", True, 5, 7, 8),
"Lorikeet Flat design": SongData(2900501, "9-4", "Happy Otaku Pack Vol.4", True, 5, 7, 10),
"GOODRAGE": SongData(2900502, "9-5", "Happy Otaku Pack Vol.4", False, 6, 9, 11),
"Altale": SongData(2900504, "8-0", "Give Up TREATMENT Vol.3", False, 3, 5, 7),
"Brain Power": SongData(2900505, "8-1", "Give Up TREATMENT Vol.3", False, 4, 7, 10),
"Berry Go!!": SongData(2900506, "8-2", "Give Up TREATMENT Vol.3", False, 3, 6, 9),
"Sweet* Witch* Girl*": SongData(2900507, "8-3", "Give Up TREATMENT Vol.3", False, 6, 8, 10),
"trippers feeling!": SongData(2900508, "8-4", "Give Up TREATMENT Vol.3", True, 5, 7, 9),
"Lilith ambivalence lovers": SongData(2900509, "8-5", "Give Up TREATMENT Vol.3", False, 5, 8, 10),
"Brave My Soul": SongData(2900511, "7-0", "Give Up TREATMENT Vol.2", False, 4, 6, 8),
"Halcyon": SongData(2900512, "7-1", "Give Up TREATMENT Vol.2", False, 4, 7, 10),
"Crimson Nightingale": SongData(2900513, "7-2", "Give Up TREATMENT Vol.2", True, 4, 7, 10),
"Invader": SongData(2900514, "7-3", "Give Up TREATMENT Vol.2", True, 3, 7, 11),
"Lyrith": SongData(2900515, "7-4", "Give Up TREATMENT Vol.2", False, 5, 7, 10),
"GOODBOUNCE": SongData(2900516, "7-5", "Give Up TREATMENT Vol.2", False, 4, 6, 9),
"Out of Sense": SongData(2900518, "6-0", "Budget Is Burning Vol.1", False, 3, 5, 8),
"My Life Is For You": SongData(2900519, "6-1", "Budget Is Burning Vol.1", False, 2, 4, 7),
"Etude -Sunset-": SongData(2900520, "6-2", "Budget Is Burning Vol.1", True, 5, 7, 9),
"Goodbye Boss": SongData(2900521, "6-3", "Budget Is Burning Vol.1", False, 4, 6, 8),
"Stargazer": SongData(2900522, "6-4", "Budget Is Burning Vol.1", True, 2, 5, 8),
"Lys Tourbillon": SongData(2900523, "6-5", "Budget Is Burning Vol.1", True, 4, 6, 8),
"Thirty Million Persona": SongData(2900525, "5-0", "Happy Otaku Pack Vol.3", False, 2, 4, 6),
"conflict": SongData(2900526, "5-1", "Happy Otaku Pack Vol.3", False, 2, 6, 9),
"Enka Dance Music": SongData(2900527, "5-2", "Happy Otaku Pack Vol.3", False, 3, 5, 7),
"XING": SongData(2900528, "5-3", "Happy Otaku Pack Vol.3", True, 4, 6, 8),
"Amakakeru Soukyuu no Serenade": SongData(2900529, "5-4", "Happy Otaku Pack Vol.3", False, 3, 6, 9),
"Gift box": SongData(2900530, "5-5", "Happy Otaku Pack Vol.3", False, 5, 7, 10),
"MUSEDASH!!!!": SongData(2900532, "4-0", "Happy Otaku Pack Vol.2", False, 2, 6, 9),
"Imprinting": SongData(2900533, "4-1", "Happy Otaku Pack Vol.2", False, 3, 6, 9),
"Skyward": SongData(2900534, "4-2", "Happy Otaku Pack Vol.2", True, 4, 7, 10),
"La nuit de vif": SongData(2900535, "4-3", "Happy Otaku Pack Vol.2", True, 2, 5, 8),
"Bit-alize": SongData(2900536, "4-4", "Happy Otaku Pack Vol.2", False, 3, 6, 8),
"GOODTEK": SongData(2900537, "4-5", "Happy Otaku Pack Vol.2", False, 4, 6, 9),
"Maharajah": SongData(2900539, "3-0", "Happy Otaku Pack Vol.1", False, 1, 3, 6),
"keep on running": SongData(2900540, "3-1", "Happy Otaku Pack Vol.1", False, 5, 7, 9),
"Kafig": SongData(2900541, "3-2", "Happy Otaku Pack Vol.1", True, 4, 6, 8),
"-+": SongData(2900542, "3-3", "Happy Otaku Pack Vol.1", True, 4, 6, 8),
"Tenri Kaku Jou": SongData(2900543, "3-4", "Happy Otaku Pack Vol.1", True, 3, 6, 9),
"Adjudicatorz-DanZai-": SongData(2900544, "3-5", "Happy Otaku Pack Vol.1", False, 3, 7, 10),
"Oriens": SongData(2900546, "2-0", "Give Up TREATMENT Vol.1", True, 3, 7, 9),
"PUPA": SongData(2900547, "2-1", "Give Up TREATMENT Vol.1", False, 6, 8, 11),
"Luna Express 2032": SongData(2900548, "2-2", "Give Up TREATMENT Vol.1", False, 4, 6, 8),
"Ukiyoe Yokochou": SongData(2900549, "2-3", "Give Up TREATMENT Vol.1", False, 6, 7, 9),
"Alice in Misanthrope": SongData(2900550, "2-4", "Give Up TREATMENT Vol.1", False, 5, 7, 10),
"GOODMEN": SongData(2900551, "2-5", "Give Up TREATMENT Vol.1", False, 5, 7, 10),
"Sunshine and Rainbow after August Rain": SongData(2900553, "1-0", "Cute Is Everything Vol.1", False, 2, 5, 8),
"Magical Number": SongData(2900554, "1-1", "Cute Is Everything Vol.1", False, 2, 5, 8),
"Dreaming Girl": SongData(2900555, "1-2", "Cute Is Everything Vol.1", False, 2, 5, 6),
"Daruma-san Fell Over": SongData(2900556, "1-3", "Cute Is Everything Vol.1", False, 3, 4, 6),
"Different": SongData(2900557, "1-4", "Cute Is Everything Vol.1", False, 1, 3, 6),
"The Future of the Phantom": SongData(2900558, "1-5", "Cute Is Everything Vol.1", False, 1, 3, 5),
"Doki Doki Jump!": SongData(2900560, "63-0", "MUSE RADIO FM104", True, 3, 5, 7),
"Centennial Streamers High": SongData(2900561, "63-1", "MUSE RADIO FM104", False, 4, 7, 9),
"Love Patrol": SongData(2900562, "63-2", "MUSE RADIO FM104", True, 3, 5, 7),
"Mahorova": SongData(2900563, "63-3", "MUSE RADIO FM104", True, 3, 5, 8),
"Yoru no machi": SongData(2900564, "63-4", "MUSE RADIO FM104", True, 1, 4, 7),
"INTERNET YAMERO": SongData(2900565, "63-5", "MUSE RADIO FM104", True, 6, 8, 10),
"Abracadabra": SongData(2900566, "43-24", "MD Plus Project", False, 6, 8, 10),
"Squalldecimator feat. EZ-Ven": SongData(2900567, "43-25", "MD Plus Project", True, 5, 7, 9),
"Amateras Rhythm": SongData(2900568, "43-26", "MD Plus Project", True, 6, 8, 11),
"Record one's Dream": SongData(2900569, "43-27", "MD Plus Project", False, 4, 7, 10),
"Lunatic": SongData(2900570, "43-28", "MD Plus Project", True, 5, 8, 10),
"Jiumeng": SongData(2900571, "43-29", "MD Plus Project", True, 3, 6, 8),
"The Day We Become Family": SongData(2900572, "43-30", "MD Plus Project", True, 3, 5, 8),
"Sutori ma FIRE!?!?": SongData(2900574, "64-0", "COSMIC RADIO PEROLIST", True, 3, 5, 8),
"Tanuki Step": SongData(2900575, "64-1", "COSMIC RADIO PEROLIST", True, 5, 7, 10),
"Space Stationery": SongData(2900576, "64-2", "COSMIC RADIO PEROLIST", True, 5, 7, 10),
"Songs Are Judged 90% by Chorus feat. Mameko": SongData(2900577, "64-3", "COSMIC RADIO PEROLIST", True, 6, 8, 10),
"Kawai Splendid Space Thief": SongData(2900578, "64-4", "COSMIC RADIO PEROLIST", False, 6, 8, 10),
"Night City Runway": SongData(2900579, "64-5", "COSMIC RADIO PEROLIST", True, 4, 6, 8),
"Chaos Shotgun feat. ChumuNote": SongData(2900580, "64-6", "COSMIC RADIO PEROLIST", True, 6, 8, 10),
"mew mew magical summer": SongData(2900581, "64-7", "COSMIC RADIO PEROLIST", False, 5, 8, 10),
"BrainDance": SongData(2900583, "65-0", "NeonAbyss", True, 3, 6, 9),
"My Focus!": SongData(2900584, "65-1", "NeonAbyss", True, 5, 7, 10),
"ABABABA BURST": SongData(2900585, "65-2", "NeonAbyss", True, 5, 7, 9),
"ULTRA HIGHER": SongData(2900586, "65-3", "NeonAbyss", True, 4, 7, 10),
"Silver Bullet": SongData(2900587, "43-31", "MD Plus Project", True, 5, 7, 10),
"Random": SongData(2900588, "43-32", "MD Plus Project", True, 4, 7, 9),
"OTOGE-BOSS-KYOKU-CHAN": SongData(2900589, "43-33", "MD Plus Project", False, 6, 8, 10),
"Crow Rabbit": SongData(2900590, "43-34", "MD Plus Project", True, 7, 9, 11),
"SyZyGy": SongData(2900591, "43-35", "MD Plus Project", True, 6, 8, 10),
"Mermaid Radio": SongData(2900592, "43-36", "MD Plus Project", True, 3, 5, 7),
"Helixir": SongData(2900593, "43-37", "MD Plus Project", False, 6, 8, 10),
"Highway Cruisin'": SongData(2900594, "43-38", "MD Plus Project", False, 3, 5, 8),
"JACK PT BOSS": SongData(2900595, "43-39", "MD Plus Project", False, 6, 8, 10),
"Time Capsule": SongData(2900596, "43-40", "MD Plus Project", False, 7, 9, 11),
"39 Music!": SongData(2900598, "66-0", "Miku in Museland", False, 3, 5, 8),
"Hand in Hand": SongData(2900599, "66-1", "Miku in Museland", False, 1, 3, 6),
"Cynical Night Plan": SongData(2900600, "66-2", "Miku in Museland", False, 4, 6, 8),
"God-ish": SongData(2900601, "66-3", "Miku in Museland", False, 4, 7, 10),
"Darling Dance": SongData(2900602, "66-4", "Miku in Museland", False, 4, 7, 9),
"Hatsune Creation Myth": SongData(2900603, "66-5", "Miku in Museland", False, 6, 8, 10),
"The Vampire": SongData(2900604, "66-6", "Miku in Museland", False, 4, 6, 9),
"Future Eve": SongData(2900605, "66-7", "Miku in Museland", False, 4, 8, 11),
"Unknown Mother Goose": SongData(2900606, "66-8", "Miku in Museland", False, 4, 8, 10),
"Shun-ran": SongData(2900607, "66-9", "Miku in Museland", False, 4, 7, 9),
"NICE TYPE feat. monii": SongData(2900608, "43-41", "MD Plus Project", True, 3, 6, 8),
"Rainy Angel": SongData(2900610, "67-0", "Happy Otaku Pack Vol.18", True, 4, 6, 9),
"Gullinkambi": SongData(2900611, "67-1", "Happy Otaku Pack Vol.18", True, 4, 7, 10),
"RakiRaki Rebuilders!!!": SongData(2900612, "67-2", "Happy Otaku Pack Vol.18", True, 5, 7, 10),
"Laniakea": SongData(2900613, "67-3", "Happy Otaku Pack Vol.18", False, 5, 8, 10),
"OTTAMA GAZER": SongData(2900614, "67-4", "Happy Otaku Pack Vol.18", True, 5, 8, 10),
"Sleep Tight feat.Macoto": SongData(2900615, "67-5", "Happy Otaku Pack Vol.18", True, 3, 5, 8),
"New York Back Raise": SongData(2900617, "68-0", "Gambler's Tricks", True, 6, 8, 10),
"slic.hertz": SongData(2900618, "68-1", "Gambler's Tricks", True, 5, 7, 9),
"Fuzzy-Navel": SongData(2900619, "68-2", "Gambler's Tricks", True, 6, 8, 10),
"Swing Edge": SongData(2900620, "68-3", "Gambler's Tricks", True, 4, 8, 10),
"Twisted Escape": SongData(2900621, "68-4", "Gambler's Tricks", True, 5, 8, 10),
"Swing Sweet Twee Dance": SongData(2900622, "68-5", "Gambler's Tricks", False, 4, 7, 10),
"Sanyousei SAY YA!!!": SongData(2900623, "43-42", "MD Plus Project", False, 4, 6, 8),
"YUKEMURI TAMAONSEN II": SongData(2900624, "43-43", "MD Plus Project", False, 3, 6, 9),
"Samayoi no mei Amatsu": SongData(2900626, "69-0", "Touhou Mugakudan -III-", False, 4, 6, 9),
"INTERNET SURVIVOR": SongData(2900627, "69-1", "Touhou Mugakudan -III-", False, 5, 8, 10),
"Shuki*RaiRai": SongData(2900628, "69-2", "Touhou Mugakudan -III-", False, 5, 7, 9),
"HELLOHELL": SongData(2900629, "69-3", "Touhou Mugakudan -III-", False, 4, 7, 10),
"Calamity Fortune": SongData(2900630, "69-4", "Touhou Mugakudan -III-", True, 6, 8, 10),
"Tsurupettan": SongData(2900631, "69-5", "Touhou Mugakudan -III-", True, 2, 5, 8),
"Twilight Poems": SongData(2900632, "43-44", "MD Plus Project", True, 3, 6, 8),
"All My Friends feat. RANASOL": SongData(2900633, "43-45", "MD Plus Project", True, 4, 7, 9),
"Heartache": SongData(2900634, "43-46", "MD Plus Project", True, 5, 7, 10),
"Blue Lemonade": SongData(2900635, "43-47", "MD Plus Project", True, 3, 6, 8),
"Haunted Dance": SongData(2900636, "43-48", "MD Plus Project", False, 6, 9, 11),
"Hey Vincent.": SongData(2900637, "43-49", "MD Plus Project", True, 6, 8, 10),
"Meteor feat. TEA": SongData(2900638, "43-50", "MD Plus Project", True, 3, 6, 9),
"Narcissism Angel": SongData(2900639, "43-51", "MD Plus Project", True, 1, 3, 6),
"AlterLuna": SongData(2900640, "43-52", "MD Plus Project", True, 6, 8, 11),
"Niki Tousen": SongData(2900641, "43-53", "MD Plus Project", True, 6, 8, 10),
"Rettou Joutou": SongData(2900643, "70-0", "Rin Len's Mirrorland", False, 4, 7, 9),
"Telecaster B-Boy": SongData(2900644, "70-1", "Rin Len's Mirrorland", False, 5, 7, 10),
"Iya Iya Iya": SongData(2900645, "70-2", "Rin Len's Mirrorland", False, 2, 4, 7),
"Nee Nee Nee": SongData(2900646, "70-3", "Rin Len's Mirrorland", False, 4, 6, 8),
"Chaotic Love Revolution": SongData(2900647, "70-4", "Rin Len's Mirrorland", False, 4, 6, 8),
"Dance of the Corpses": SongData(2900648, "70-5", "Rin Len's Mirrorland", False, 2, 5, 8),
"Bitter Choco Decoration": SongData(2900649, "70-6", "Rin Len's Mirrorland", False, 3, 6, 9),
"Dance Robot Dance": SongData(2900650, "70-7", "Rin Len's Mirrorland", False, 4, 7, 10),
"Sweet Devil": SongData(2900651, "70-8", "Rin Len's Mirrorland", False, 5, 7, 9),
"Someday'z Coming": SongData(2900652, "70-9", "Rin Len's Mirrorland", False, 5, 7, 9),
"Yume Ou Mono Yo Secret": SongData(2900653, "0-53", "Default Music", True, 6, 8, 10),
"Yume Ou Mono Yo": SongData(2900654, "0-54", "Default Music", True, 1, 4, None),
"Sweet Dream VIVINOS": SongData(2900656, "71-0", "Valentine Stage", False, 1, 4, 7),
"Ruler Of My Heart VIVINOS": SongData(2900657, "71-1", "Valentine Stage", False, 2, 4, 6),
"Reality Show": SongData(2900658, "71-2", "Valentine Stage", False, 5, 7, 10),
"SIG feat.Tobokegao": SongData(2900659, "71-3", "Valentine Stage", True, 3, 6, 8),
"Rose Love": SongData(2900660, "71-4", "Valentine Stage", True, 2, 4, 7),
"Euphoria": SongData(2900661, "71-5", "Valentine Stage", True, 1, 3, 6),
"P E R O P E R O Brother Dance": SongData(2900663, "72-0", "Legends of Muse Warriors", True, None, 7, None),
"PA PPA PANIC": SongData(2900664, "72-1", "Legends of Muse Warriors", False, 4, 8, 10),
"How To Make Music Game Song!": SongData(2900665, "72-2", "Legends of Muse Warriors", True, 6, 8, 10),
"Re Re": SongData(2900666, "72-3", "Legends of Muse Warriors", True, 7, 9, 11),
"Marmalade Twins": SongData(2900667, "72-4", "Legends of Muse Warriors", True, 5, 8, 10),
"DOMINATOR": SongData(2900668, "72-5", "Legends of Muse Warriors", True, 7, 9, 11),
"Teshikani TESHiKANi": SongData(2900669, "72-6", "Legends of Muse Warriors", True, 5, 7, 9),
"Urban Magic": SongData(2900671, "73-0", "Happy Otaku Pack Vol.19", True, 3, 5, 7),
"Maid's Prank": SongData(2900672, "73-1", "Happy Otaku Pack Vol.19", True, 5, 7, 10),
"Dance Dance Good Night Dance": SongData(2900673, "73-2", "Happy Otaku Pack Vol.19", True, 2, 4, 7),
"Ops Limone": SongData(2900674, "73-3", "Happy Otaku Pack Vol.19", True, 5, 8, 11),
"NOVA": SongData(2900675, "73-4", "Happy Otaku Pack Vol.19", True, 6, 8, 10),
"Heaven's Gradius": SongData(2900676, "73-5", "Happy Otaku Pack Vol.19", True, 6, 8, 10),
"Ray Tuning": SongData(2900678, "74-0", "CHUNITHM COURSE MUSE", True, 6, 8, 10),
"World Vanquisher": SongData(2900679, "74-1", "CHUNITHM COURSE MUSE", True, 6, 8, 10),
"Tsukuyomi Ni Naru Replaced": SongData(2900680, "74-2", "CHUNITHM COURSE MUSE", True, 5, 7, 9),
"The wheel to the right": SongData(2900681, "74-3", "CHUNITHM COURSE MUSE", True, 5, 7, 9),
"Climax": SongData(2900682, "74-4", "CHUNITHM COURSE MUSE", True, 4, 8, 11),
"Spider's Thread": SongData(2900683, "74-5", "CHUNITHM COURSE MUSE", True, 5, 8, 10),
"HIT ME UP": SongData(2900684, "43-54", "MD Plus Project", True, 4, 6, 8),
"Test Me feat. Uyeon": SongData(2900685, "43-55", "MD Plus Project", True, 3, 5, 9),
"Assault TAXI": SongData(2900686, "43-56", "MD Plus Project", True, 4, 7, 10),
"No": SongData(2900687, "43-57", "MD Plus Project", False, 4, 6, 9),
"Pop it": SongData(2900688, "43-58", "MD Plus Project", True, 1, 3, 6),
"HEARTBEAT! KyunKyun!": SongData(2900689, "43-59", "MD Plus Project", True, 4, 6, 9),
"SUPERHERO": SongData(2900691, "75-0", "Novice Rider Pack", False, 2, 4, 7),
"Highway_Summer": SongData(2900692, "75-1", "Novice Rider Pack", True, 2, 4, 6),
"Mx. Black Box": SongData(2900693, "75-2", "Novice Rider Pack", True, 5, 7, 9),
"Sweet Encounter": SongData(2900694, "75-3", "Novice Rider Pack", True, 2, 4, 7),
"Echo over you... Secret": SongData(2900695, "0-55", "Default Music", False, 6, 8, 10),
"Echo over you...": SongData(2900696, "0-56", "Default Music", False, 1, 4, None),
"Tsukuyomi Ni Naru": SongData(2900697, "74-6", "CHUNITHM COURSE MUSE", True, 5, 8, 10),
"disco light": SongData(2900699, "76-0", "MUSE RADIO FM105", True, 5, 7, 9),
"room light feat.chancylemon": SongData(2900700, "76-1", "MUSE RADIO FM105", True, 3, 5, 7),
"Invisible": SongData(2900701, "76-2", "MUSE RADIO FM105", True, 3, 5, 8),
"Christmas Season-LLABB": SongData(2900702, "76-3", "MUSE RADIO FM105", True, 1, 4, 7),
"Hyouryu": SongData(2900704, "77-0", "Let's Rhythm Jam!", False, 6, 8, 10),
"The Whole Rest": SongData(2900705, "77-1", "Let's Rhythm Jam!", False, 5, 8, 10),
"Hydra": SongData(2900706, "77-2", "Let's Rhythm Jam!", False, 4, 7, 11),
"Pastel Lines": SongData(2900707, "77-3", "Let's Rhythm Jam!", False, 3, 6, 9),
"LINK x LIN#S": SongData(2900708, "77-4", "Let's Rhythm Jam!", False, 3, 6, 9),
"Arcade ViruZ": SongData(2900709, "77-5", "Let's Rhythm Jam!", False, 6, 8, 11),
"Eve Avenir": SongData(2900711, "78-0", "Endless Pirouette", True, 6, 8, 10),
"Silverstring": SongData(2900712, "78-1", "Endless Pirouette", True, 5, 7, 10),
"Melusia": SongData(2900713, "78-2", "Endless Pirouette", False, 5, 7, 10),
"Devil's Castle": SongData(2900714, "78-3", "Endless Pirouette", True, 4, 7, 10),
"Abatement": SongData(2900715, "78-4", "Endless Pirouette", True, 6, 8, 10),
"Azalea": SongData(2900716, "78-5", "Endless Pirouette", False, 4, 8, 10),
"Brightly World": SongData(2900717, "78-6", "Endless Pirouette", True, 6, 8, 10),
"We'll meet in every world ***": SongData(2900718, "78-7", "Endless Pirouette", True, 7, 9, 11),
"Collapsar": SongData(2900719, "78-8", "Endless Pirouette", True, 7, 9, 10),
"Parousia": SongData(2900720, "78-9", "Endless Pirouette", False, 6, 8, 10),
"Gunners in the Rain": SongData(2900722, "79-0", "Ensemble Arcanum", False, 5, 8, 10),
"Halzion": SongData(2900723, "79-1", "Ensemble Arcanum", False, 2, 5, 8),
"SHOWTIME!!": SongData(2900724, "79-2", "Ensemble Arcanum", False, 6, 8, 10),
"Achromic Riddle": SongData(2900725, "79-3", "Ensemble Arcanum", False, 6, 8, 10),
"karanosu": SongData(2900726, "79-4", "Ensemble Arcanum", False, 3, 6, 8),
"Necromantic": SongData(2900727, "43-60", "MD Plus Project", False, 6, 8, 10),
"Saishuu kichiku imouto Flandre-S": SongData(2900729, "80-0", "Touhou Mugakudan -IV-", False, 6, 8, 10),
"Kachoufuugetsu": SongData(2900730, "80-1", "Touhou Mugakudan -IV-", False, 2, 6, 8),
"Maid heart is a puppet": SongData(2900731, "80-2", "Touhou Mugakudan -IV-", False, 5, 7, 9),
"Trance dance anarchy": SongData(2900732, "80-3", "Touhou Mugakudan -IV-", False, 4, 7, 10),
"fairy stage": SongData(2900733, "80-4", "Touhou Mugakudan -IV-", False, 4, 6, 9),
"Scarlet Police on Ghetto Patrol": SongData(2900734, "80-5", "Touhou Mugakudan -IV-", False, 5, 7, 10),
"Unwelcome School": SongData(2900735, "81-0", "MD-level Tactical Training Blu-ray", False, 6, 8, 10),
"Usagi Flap": SongData(2900736, "81-1", "MD-level Tactical Training Blu-ray", False, 3, 6, 8),
"RE Aoharu": SongData(2900737, "81-2", "MD-level Tactical Training Blu-ray", False, 3, 5, 8),
"Operation*DOTABATA!": SongData(2900738, "81-3", "MD-level Tactical Training Blu-ray", False, 5, 7, 10),
}

View File

@@ -0,0 +1,597 @@
Magical Wonderland|0-48|Default Music|True|1|3|0|
Iyaiya|0-0|Default Music|True|1|4|0|
Wonderful Pain|0-2|Default Music|False|1|3|0|
Breaking Dawn|0-3|Default Music|True|2|4|0|
One-Way Subway|0-4|Default Music|True|1|4|0|
Frost Land|0-1|Default Music|False|1|3|6|
Heart-Pounding Flight|0-5|Default Music|True|2|5|0|
Pancake is Love|0-29|Default Music|True|2|4|7|
Shiguang Tuya|0-6|Default Music|True|2|5|0|
Evolution|0-37|Default Music|False|2|4|7|
Dolphin and Broadcast|0-7|Default Music|True|2|5|0|
Yuki no Shizuku Ame no Oto|0-8|Default Music|True|2|4|6|
Best One feat.tooko|0-43|Default Music|False|3|5|0|
Candy-coloured Love Theory|0-31|Default Music|False|2|4|6|
Night Wander|0-38|Default Music|False|3|5|7|
Dohna Dohna no Uta|0-46|Default Music|False|2|4|6|
Spring Carnival|0-9|Default Music|False|2|4|7|
DISCO NIGHT|0-30|Default Music|True|2|4|7|
Koi no Moonlight|0-49|Default Music|False|2|5|8|
Lian Ai Audio Navigation|0-10|Default Music|False|3|5|7|
Lights of Muse|0-11|Default Music|True|4|6|8|10
midstream jam|0-12|Default Music|False|2|5|8|
Nihao|0-40|Default Music|False|3|5|7|
Confession|0-13|Default Music|False|3|5|8|
Galaxy Striker|0-32|Default Music|False|4|7|9|
Departure Road|0-14|Default Music|True|2|5|8|
Bass Telekinesis|0-15|Default Music|False|2|5|8|
Cage of Almeria|0-16|Default Music|True|3|5|7|
Ira|0-17|Default Music|True|4|6|8|
Blackest Luxury Car|0-18|Default Music|True|3|6|8|
Medicine of Sing|0-19|Default Music|False|3|6|8|
irregulyze|0-20|Default Music|True|3|6|8|
I don't care about Christmas though|0-47|Default Music|False|4|6|8|
Imaginary World|0-21|Default Music|True|4|6|8|10
Dysthymia|0-22|Default Music|True|4|7|9|
From the New World|0-42|Default Music|False|2|5|7|
NISEGAO|0-33|Default Music|True|4|7|9|
Say! Fanfare!|0-44|Default Music|False|4|6|9|
Star Driver|0-34|Default Music|True|5|7|9|
Formation|0-23|Default Music|True|4|6|9|
Shinsou Masui|0-24|Default Music|True|4|6|10|
Mezame Eurythmics|0-50|Default Music|False|4|6|9|
Shenri Kuaira -repeat-|0-51|Default Music|False|5|7|9|
Latitude|0-25|Default Music|True|3|6|9|
Aqua Stars|0-39|Default Music|False|5|7|10|
Funkotsu Saishin Casino|0-26|Default Music|False|5|7|10|
Clock Room & Spiritual World|0-27|Default Music|True|4|6|9|
INTERNET OVERDOSE|0-52|Default Music|False|3|6|9|
Tu Hua|0-35|Default Music|True|4|7|9|
Mujinku-Vacuum|0-28|Default Music|False|5|7|11|
MilK|0-36|Default Music|False|5|7|9|
umpopoff|0-41|Default Music|False|0|?|0|
Mopemope|0-45|Default Music|False|4|7|9|11
The Happycore Idol|43-0|MD Plus Project|True|2|5|7|
Amatsumikaboshi|43-1|MD Plus Project|True|4|6|8|10
ARIGA THESIS|43-2|MD Plus Project|True|3|6|10|
Night of Nights|43-3|MD Plus Project|False|4|7|10|
#Psychedelic_Meguro_River|43-4|MD Plus Project|False|3|6|8|
can you feel it|43-5|MD Plus Project|False|4|6|8|9
Midnight O'clock|43-6|MD Plus Project|True|3|6|8|
Rin|43-7|MD Plus Project|True|5|7|10|
Smile-mileS|43-8|MD Plus Project|False|6|8|10|
Believing and Being|43-9|MD Plus Project|True|4|6|9|
Catalyst|43-10|MD Plus Project|False|5|7|9|
don't!stop!eroero!|43-11|MD Plus Project|True|5|7|9|
pa pi pu pi pu pi pa|43-12|MD Plus Project|False|6|8|10|
Sand Maze|43-13|MD Plus Project|True|6|8|10|11
Diffraction|43-14|MD Plus Project|True|5|8|10|
AKUMU|43-15|MD Plus Project|False|4|6|8|
Queen Aluett|43-16|MD Plus Project|True|7|9|11|
DROPS|43-17|MD Plus Project|False|2|5|8|
Frightfully-insane Flan-chan's frightful song|43-18|MD Plus Project|False|5|7|10|
snooze|43-19|MD Plus Project|False|5|7|10|
Kuishinbo Hacker feat.Kuishinbo Akachan|43-20|MD Plus Project|True|5|7|9|
Inu no outa|43-21|MD Plus Project|True|3|5|7|
Prism Fountain|43-22|MD Plus Project|True|7|9|11|
Gospel|43-23|MD Plus Project|False|4|6|9|
East Ai Li Lovely|62-0|Happy Otaku Pack Vol.17|False|2|4|7|
Mori Umi no Fune|62-1|Happy Otaku Pack Vol.17|True|5|7|9|
Ooi|62-2|Happy Otaku Pack Vol.17|True|5|7|10|
Numatta!!|62-3|Happy Otaku Pack Vol.17|True|5|7|9|
SATELLITE|62-4|Happy Otaku Pack Vol.17|False|5|7|9|10
Fantasia Sonata Colorful feat. V!C|62-5|Happy Otaku Pack Vol.17|True|6|8|11|
MuseDash ka nanika hi|61-0|Ola Dash|True|?|?|¿|
Aleph-0|61-1|Ola Dash|True|7|9|11|
Buttoba Supernova|61-2|Ola Dash|False|5|7|10|11
Rush-Hour|61-3|Ola Dash|False|IG|Jh|a2|Eh
3rd Avenue|61-4|Ola Dash|False|3|5||
WORLDINVADER|61-5|Ola Dash|True|5|8|10|11
N3V3R G3T OV3R|60-0|maimai DX Limited-time Suite|True|4|7|10|
Oshama Scramble!|60-1|maimai DX Limited-time Suite|True|5|7|10|
Valsqotch|60-2|maimai DX Limited-time Suite|True|5|9|11|
Paranormal My Mind|60-3|maimai DX Limited-time Suite|True|5|7|9|
Flower, snow and Drum'n'bass.|60-4|maimai DX Limited-time Suite|True|5|8|10|?
Amenohoakari|60-5|maimai DX Limited-time Suite|True|6|8|10|
Boiling Blood|59-0|MSR Anthology|True|5|8|10|
ManiFesto|59-1|MSR Anthology|True|4|6|9|
Operation Blade|59-2|MSR Anthology|True|3|5|7|
Radiant|59-3|MSR Anthology|True|3|5|8|
Renegade|59-4|MSR Anthology|True|3|5|8|
Speed of Light|59-5|MSR Anthology|False|1|4|7|
Dossoles Holiday|59-6|MSR Anthology|True|5|7|9|
Autumn Moods|59-7|MSR Anthology|True|3|5|7|
People People|58-0|Nanahira Paradise|True|5|7|9|11
Endless Error Loop|58-1|Nanahira Paradise|True|4|7|9|
Forbidden Pizza!|58-2|Nanahira Paradise|True|5|7|9|
Don't Make the Vocalist do Anything Insane|58-3|Nanahira Paradise|True|5|8|9|
Tokimeki*Meteostrike|57-0|Happy Otaku Pack Vol.16|True|3|6|8|
Down Low|57-1|Happy Otaku Pack Vol.16|True|4|6|8|
LOUDER MACHINE|57-2|Happy Otaku Pack Vol.16|True|5|7|9|
Sorewa mo Lovechu|57-3|Happy Otaku Pack Vol.16|True|5|7|10|
Rave_Tech|57-4|Happy Otaku Pack Vol.16|True|5|8|10|
Brilliant & Shining!|57-5|Happy Otaku Pack Vol.16|False|5|8|10|
Psyched Fevereiro|56-0|Give Up TREATMENT Vol.11|False|5|8|10|
Inferno City|56-1|Give Up TREATMENT Vol.11|False|6|8|10|
Paradigm Shift|56-2|Give Up TREATMENT Vol.11|False|4|7|10|
Snapdragon|56-3|Give Up TREATMENT Vol.11|False|5|7|10|
Prestige and Vestige|56-4|Give Up TREATMENT Vol.11|True|6|8|11|
Tiny Fate|56-5|Give Up TREATMENT Vol.11|False|7|9|11|
Tsuki ni Murakumo Hana ni Kaze|55-0|Touhou Mugakudan -2-|False|3|5|7|
Patchouli's - Best Hit GSK|55-1|Touhou Mugakudan -2-|False|3|5|8|
Monosugoi Space Shuttle de Koishi ga Monosugoi uta|55-2|Touhou Mugakudan -2-|False|3|5|7|11
Kakoinaki Yo wa Ichigo no Tsukikage|55-3|Touhou Mugakudan -2-|False|3|6|8|
Psychedelic Kizakura Doumei|55-4|Touhou Mugakudan -2-|False|4|7|10|
Mischievous Sensation|55-5|Touhou Mugakudan -2-|False|5|7|9|
White Canvas|54-0|MEGAREX THE FUTURE|False|3|6|8|
Gloomy Flash|54-1|MEGAREX THE FUTURE|False|5|8|10|
Find this Month's Featured Playlist|54-2|MEGAREX THE FUTURE|False|?|?|¿|
Sunday Night|54-3|MEGAREX THE FUTURE|False|3|6|9|
Goodbye Goodnight|54-4|MEGAREX THE FUTURE|False|4|6|9|
ENDLESS CIDER|54-5|MEGAREX THE FUTURE|False|4|6|8|
On And On!!|53-0|Happy Otaku Pack Vol.15|True|4|7|9|11
Trip!|53-1|Happy Otaku Pack Vol.15|True|3|5|7|
Hoshi no otoshimono|53-2|Happy Otaku Pack Vol.15|False|5|7|9|
Plucky Race|53-3|Happy Otaku Pack Vol.15|True|5|8|10|11
Fantasia Sonata Destiny|53-4|Happy Otaku Pack Vol.15|True|3|7|10|
Run through|53-5|Happy Otaku Pack Vol.15|False|5|8|10|
marooned night|52-0|MUSE RADIO FM103|False|2|4|6|
daydream girl|52-1|MUSE RADIO FM103|False|3|6|8|
Not Ornament|52-2|MUSE RADIO FM103|True|3|5|8|
Baby Pink|52-3|MUSE RADIO FM103|False|3|5|8|
I'm Here|52-4|MUSE RADIO FM103|False|4|6|8|
Masquerade Diary|51-0|Virtual Idol Production|True|2|5|8|
Reminiscence|51-1|Virtual Idol Production|True|5|7|9|
DarakuDatenshi|51-2|Virtual Idol Production|True|3|6|9|
D.I.Y.|51-3|Virtual Idol Production|False|4|6|9|
Boys in Virtual Land|51-4|Virtual Idol Production|False|4|7|9|
kui|51-5|Virtual Idol Production|True|5|7|9|11
Nyan Cat|50-0|Nyanya Universe!|False|4|7|9|
PeroPero in the Universe|50-1|Nyanya Universe!|True|?|?|¿|
In-kya Yo-kya Onmyoji|50-2|Nyanya Universe!|False|6|8|10|
KABOOOOOM!!!!|50-3|Nyanya Universe!|True|4|6|8|
Doppelganger|50-4|Nyanya Universe!|True|5|7|9|12
Pray a LOVE|49-0|DokiDoki! Valentine!|False|2|5|8|
Love-Avoidance Addiction|49-1|DokiDoki! Valentine!|False|3|5|7|
Daisuki Dayo feat.Wotoha|49-2|DokiDoki! Valentine!|False|5|7|10|
glory day|48-0|DJMAX Reflect|False|2|5|7|
Bright Dream|48-1|DJMAX Reflect|False|2|4|7|
Groovin Up|48-2|DJMAX Reflect|False|4|6|8|
I Want You|48-3|DJMAX Reflect|False|3|6|8|
OBLIVION|48-4|DJMAX Reflect|False|3|6|9|
Elastic STAR|48-5|DJMAX Reflect|False|4|6|8|
U.A.D|48-6|DJMAX Reflect|False|4|6|8|10
Jealousy|48-7|DJMAX Reflect|False|3|5|7|
Memory of Beach|48-8|DJMAX Reflect|False|3|6|8|
Don't Die|48-9|DJMAX Reflect|False|6|8|10|
Y CE Ver.|48-10|DJMAX Reflect|False|4|6|9|
Fancy Night|48-11|DJMAX Reflect|False|4|6|8|
Can We Talk|48-12|DJMAX Reflect|False|4|6|8|
Give Me 5|48-13|DJMAX Reflect|False|2|6|8|
Nightmare|48-14|DJMAX Reflect|False|7|9|11|
Haze of Autumn|47-0|Arcaea|True|3|6|9|
GIMME DA BLOOD|47-1|Arcaea|False|3|6|9|
Libertas|47-2|Arcaea|False|4|7|10|
Cyaegha|47-3|Arcaea|False|5|7|9|11
Bang!!|46-0|Happy Otaku Pack Vol.14|False|4|6|8|
Paradise 2|46-1|Happy Otaku Pack Vol.14|False|4|6|8|
Symbol|46-2|Happy Otaku Pack Vol.14|False|5|7|9|
Nekojarashi|46-3|Happy Otaku Pack Vol.14|False|5|8|10|11
A Philosophical Wanderer|46-4|Happy Otaku Pack Vol.14|False|4|6|10|
Isouten|46-5|Happy Otaku Pack Vol.14|True|6|8|10|11
ONOMATO Pairing!!!|45-0|WACCA Horizon|False|4|6|9|
with U|45-1|WACCA Horizon|False|6|8|10|11
Chariot|45-2|WACCA Horizon|False|3|6|9|
GASHATT|45-3|WACCA Horizon|False|5|7|10|
LIN NE KRO NE feat. lasah|45-4|WACCA Horizon|False|6|8|10|
ANGEL HALO|45-5|WACCA Horizon|False|5|8|11|
Party in the HOLLOWood|44-0|Happy Otaku Pack Vol.13|False|3|6|8|
Ying Ying da Zuozhan|44-1|Happy Otaku Pack Vol.13|True|5|7|9|
Howlin' Pumpkin|44-2|Happy Otaku Pack Vol.13|True|4|6|8|
Bad Apple!! feat. Nomico|42-0|Touhou Mugakudan -1-|False|1|3|6|8
Iro wa Nioedo, Chirinuru wo|42-1|Touhou Mugakudan -1-|False|2|4|7|
Cirno's Perfect Math Class|42-2|Touhou Mugakudan -1-|False|4|7|9|
Hiiro Gekka Kyousai no Zetsu|42-3|Touhou Mugakudan -1-|False|4|6|8|
Flowery Moonlit Night|42-4|Touhou Mugakudan -1-|False|3|6|8|
Unconscious Requiem|42-5|Touhou Mugakudan -1-|False|3|6|8|
Super Battleworn Insomniac|41-0|7th Beat Games|True|4|7|9|?
Bomb-Sniffing Pomeranian|41-1|7th Beat Games|True|4|6|8|
Rollerdisco Rumble|41-2|7th Beat Games|True|4|6|9|
Rose Garden|41-3|7th Beat Games|False|5|8|9|
EMOMOMO|41-4|7th Beat Games|True|4|7|10|
Heracles|41-5|7th Beat Games|False|6|8|10|?
Rush-More|40-0|Happy Otaku Pack Vol.12|False|4|7|9|
Kill My Fortune|40-1|Happy Otaku Pack Vol.12|False|5|7|10|
Yosari Tsukibotaru Suminoborite|40-2|Happy Otaku Pack Vol.12|False|5|7|9|
JUMP! HardCandy|40-3|Happy Otaku Pack Vol.12|False|3|6|8|
Hibari|40-4|Happy Otaku Pack Vol.12|False|3|5|8|
OCCHOCO-REST-LESS|40-5|Happy Otaku Pack Vol.12|True|4|7|9|
See-Saw Day|39-0|MUSE RADIO FM102|True|1|3|6|
happy hour|39-1|MUSE RADIO FM102|True|2|4|7|
Seikimatsu no Natsu|39-2|MUSE RADIO FM102|True|4|6|8|
twinkle night|39-3|MUSE RADIO FM102|False|3|6|8|
ARUYA HARERUYA|39-4|MUSE RADIO FM102|False|2|5|7|
Blush|39-5|MUSE RADIO FM102|False|2|4|7|
Naked Summer|39-6|MUSE RADIO FM102|True|4|6|8|
BLESS ME|39-7|MUSE RADIO FM102|True|2|5|7|
FM 17314 SUGAR RADIO|39-8|MUSE RADIO FM102|True|?|?|?|
NO ONE YES MAN|38-0|Phigros|False|5|7|9|
Snowfall, Merry Christmas|38-1|Phigros|False|5|8|10|
Igallta|38-2|Phigros|False|6|8|10|11
Colored Glass|37-0|Cute Is Everything Vol.7|False|1|4|7|
Neonlights|37-1|Cute Is Everything Vol.7|False|4|7|9|
Hope for the flowers|37-2|Cute Is Everything Vol.7|False|4|7|9|
Seaside Cycling on May 30|37-3|Cute Is Everything Vol.7|False|3|6|8|
SKY HIGH|37-4|Cute Is Everything Vol.7|False|2|4|6|
Mousou Chu!!|37-5|Cute Is Everything Vol.7|False|4|7|8|
NightTheater|36-0|Give Up TREATMENT Vol.10|True|6|8|11|
Cutter|36-1|Give Up TREATMENT Vol.10|False|4|7|10|
bamboo|36-2|Give Up TREATMENT Vol.10|False|6|8|10|11
enchanted love|36-3|Give Up TREATMENT Vol.10|False|2|6|9|
c.s.q.n.|36-4|Give Up TREATMENT Vol.10|False|5|8|11|
Booouncing!!|36-5|Give Up TREATMENT Vol.10|False|5|7|10|
PeroPeroGames goes Bankrupt|35-0|Happy Otaku Pack SP|True|6|8|10|
MARENOL|35-1|Happy Otaku Pack SP|False|4|7|10|
I am really good at Japanese style|35-2|Happy Otaku Pack SP|True|6|8|10|
Rush B|35-3|Happy Otaku Pack SP|True|4|7|9|
DataErr0r|35-4|Happy Otaku Pack SP|False|5|7|9|?
Burn|35-5|Happy Otaku Pack SP|True|4|7|9|
ALiVE|34-0|HARDCORE TANO*C|False|5|7|10|
BATTLE NO.1|34-1|HARDCORE TANO*C|False|5|8|10|11
Cthugha|34-2|HARDCORE TANO*C|False|6|8|10|11
TWINKLE*MAGIC|34-3|HARDCORE TANO*C|False|4|7|10|11
Comet Coaster|34-4|HARDCORE TANO*C|False|6|8|10|11
XODUS|34-5|HARDCORE TANO*C|False|7|9|11|12
Fireflies|33-0|cyTus|True|1|4|7|
Light up my love!!|33-1|cyTus|True|3|5|7|
Happiness Breeze|33-2|cyTus|True|4|6|8|9
Chrome VOX|33-3|cyTus|True|6|8|10|11
CHAOS|33-4|cyTus|True|3|6|9|
Saika|33-5|cyTus|True|3|5|8|
Standby for Action|33-6|cyTus|True|4|6|8|
Hydrangea|33-7|cyTus|True|5|7|9|
Amenemhat|33-8|cyTus|True|6|8|10|
Santouka|33-9|cyTus|True|2|5|8|
HEXENNACHTROCK-katashihaya-|33-10|cyTus|True|4|8|10|
Blah!!|33-11|cyTus|True|5|8|11|
CHAOS Glitch|33-12|cyTus|True|0|?|0|
Preparara|32-0|Let's Do Bad Things Together|False|1|4|6|
Whatcha;Whatcha Doin'|32-1|Let's Do Bad Things Together|False|3|6|9|
Madara|32-2|Let's Do Bad Things Together|False|4|6|9|
pICARESq|32-3|Let's Do Bad Things Together|False|4|6|8|
Desastre|32-4|Let's Do Bad Things Together|False|4|6|8|
Shoot for the Moon|32-5|Let's Do Bad Things Together|False|2|5|8|
The 90's Decision|31-0|Happy Otaku Pack Vol.11|True|5|7|9|
Medusa|31-1|Happy Otaku Pack Vol.11|False|4|6|8|10
Final Step!|31-2|Happy Otaku Pack Vol.11|False|5|7|10|
MAGENTA POTION|31-3|Happy Otaku Pack Vol.11|False|4|7|9|
Cross Ray|31-4|Happy Otaku Pack Vol.11|False|3|6|9|
Square Lake|31-5|Happy Otaku Pack Vol.11|False|6|8|9|11
Girly Cupid|30-0|Cute Is Everything Vol.6|False|3|6|8|
sheep in the light|30-1|Cute Is Everything Vol.6|False|2|5|8|
Breaker city|30-2|Cute Is Everything Vol.6|False|4|6|9|
heterodoxy|30-3|Cute Is Everything Vol.6|False|4|6|8|
Computer Music Girl|30-4|Cute Is Everything Vol.6|False|3|5|7|
Focus Point|30-5|Cute Is Everything Vol.6|True|2|5|7|
Groove Prayer|29-0|Let' s GROOVE!|True|3|5|7|
FUJIN Rumble|29-1|Let' s GROOVE!|True|5|7|10|11
Marry me, Nightmare|29-2|Let' s GROOVE!|False|6|8|11|
HG Makaizou Polyvinyl Shounen|29-3|Let' s GROOVE!|True|4|7|9|10
Seizya no Ibuki|29-4|Let' s GROOVE!|True|6|8|10|
ouroboros -twin stroke of the end-|29-5|Let' s GROOVE!|True|4|6|9|12
Heisha Onsha|28-0|Happy Otaku Pack Vol.10|False|4|6|8|
Ginevra|28-1|Happy Otaku Pack Vol.10|True|5|7|10|10
Paracelestia|28-2|Happy Otaku Pack Vol.10|False|5|8|10|
un secret|28-3|Happy Otaku Pack Vol.10|False|2|4|6|
Good Life|28-4|Happy Otaku Pack Vol.10|False|4|6|8|
nini-nini-|28-5|Happy Otaku Pack Vol.10|False|4|7|9|
Can I friend you on Bassbook? lol|27-0|Nanahira Festival|False|3|6|8|
Gaming*Everything|27-1|Nanahira Festival|False|5|8|11|
Renji de haochi|27-2|Nanahira Festival|False|5|7|9|
You Make My Life 1UP|27-3|Nanahira Festival|False|4|6|8|
Newbies, Geeks, Internets|27-4|Nanahira Festival|False|6|8|10|
Onegai!Kon kon Oinarisama|27-5|Nanahira Festival|False|3|6|9|
Legend of Eastern Rabbit -SKY DEFENDER-|26-0|Give Up TREATMENT Vol.9|False|4|6|9|
ENERGY SYNERGY MATRIX|26-1|Give Up TREATMENT Vol.9|False|6|8|10|
Punai Punai Genso|26-2|Give Up TREATMENT Vol.9|False|2|7|11|
Better Graphic Animation|26-3|Give Up TREATMENT Vol.9|False|5|8|11|
Variant Cross|26-4|Give Up TREATMENT Vol.9|False|4|7|10|
Ultra Happy Miracle Bazoooooka!!|26-5|Give Up TREATMENT Vol.9|False|7|9|11|
tape/stop/night|25-0|MUSE RADIO FM101|True|3|5|7|
Pixel Galaxy|25-1|MUSE RADIO FM101|False|2|5|8|
Notice|25-2|MUSE RADIO FM101|False|4|7|10|
Strawberry Godzilla|25-3|MUSE RADIO FM101|True|2|5|7|
OKIMOCHI EXPRESSION|25-4|MUSE RADIO FM101|False|4|6|10|
Kimi to pool disco|25-5|MUSE RADIO FM101|False|4|6|8|
The Last Page|24-0|Happy Otaku Pack Vol.9|False|3|5|7|
IKAROS|24-1|Happy Otaku Pack Vol.9|False|4|7|10|
Tsukuyomi|24-2|Happy Otaku Pack Vol.9|False|3|6|9|
Future Stream|24-3|Happy Otaku Pack Vol.9|False|4|6|8|
FULi AUTO SHOOTER|24-4|Happy Otaku Pack Vol.9|True|4|7|9|
GOODFORTUNE|24-5|Happy Otaku Pack Vol.9|False|5|7|9|
The Dessert After Rain|23-0|Cute Is Everything Vol.5|True|2|4|6|
Confession Support Formula|23-1|Cute Is Everything Vol.5|False|3|5|7|
Omatsuri|23-2|Cute Is Everything Vol.5|False|1|3|6|
FUTUREPOP|23-3|Cute Is Everything Vol.5|True|2|5|7|
The Breeze|23-4|Cute Is Everything Vol.5|False|1|4|6|
I LOVE LETTUCE FRIED RICE!!|23-5|Cute Is Everything Vol.5|False|3|7|9|
The NightScape|22-0|Give Up TREATMENT Vol.8|False|4|7|9|
FREEDOM DiVE|22-1|Give Up TREATMENT Vol.8|False|6|8|10|12
Phi|22-2|Give Up TREATMENT Vol.8|False|5|8|10|
Lueur de la nuit|22-3|Give Up TREATMENT Vol.8|False|6|8|11|
Creamy Sugary OVERDRIVE!!!|22-4|Give Up TREATMENT Vol.8|True|4|7|10|
Disorder|22-5|Give Up TREATMENT Vol.8|False|5|7|11|
Glimmer|21-0|Budget Is Burning: Nano Core|False|2|5|8|
EXIST|21-1|Budget Is Burning: Nano Core|False|3|5|8|
Irreplaceable|21-2|Budget Is Burning: Nano Core|False|4|6|8|
Moonlight Banquet|20-0|Happy Otaku Pack Vol.8|True|2|5|8|
Flashdance|20-1|Happy Otaku Pack Vol.8|False|3|6|9|
INFiNiTE ENERZY -Overdoze-|20-2|Happy Otaku Pack Vol.8|False|4|7|9|10
One Way Street|20-3|Happy Otaku Pack Vol.8|False|3|6|10|
This Club is Not 4 U|20-4|Happy Otaku Pack Vol.8|False|4|7|9|
ULTRA MEGA HAPPY PARTY!!!|20-5|Happy Otaku Pack Vol.8|False|5|7|10|
INFINITY|19-0|Give Up TREATMENT Vol.7|True|5|8|10|
Punai Punai Senso|19-1|Give Up TREATMENT Vol.7|False|2|7|11|
Maxi|19-2|Give Up TREATMENT Vol.7|False|5|8|10|
YInMn Blue|19-3|Give Up TREATMENT Vol.7|False|6|8|10|
Plumage|19-4|Give Up TREATMENT Vol.7|False|4|7|10|
Dr.Techro|19-5|Give Up TREATMENT Vol.7|False|7|9|11|
SWEETSWEETSWEET|18-0|Cute Is Everything Vol.4|True|2|5|7|
Deep Blue and the Breaths of the Night|18-1|Cute Is Everything Vol.4|True|2|4|6|
Joy Connection|18-2|Cute Is Everything Vol.4|False|3|6|8|
Self Willed Girl Ver.B|18-3|Cute Is Everything Vol.4|True|4|6|8|
Just Disobedient|18-4|Cute Is Everything Vol.4|False|3|6|8|
Holy Sh*t Grass Snake|18-5|Cute Is Everything Vol.4|False|2|6|9|
Cotton Candy Wonderland|17-0|Happy Otaku Pack Vol.7|False|2|5|8|
Punai Punai Taiso|17-1|Happy Otaku Pack Vol.7|False|2|7|10|
Fly High|17-2|Happy Otaku Pack Vol.7|False|3|5|7|
prejudice|17-3|Happy Otaku Pack Vol.7|True|4|6|9|
The 89's Momentum|17-4|Happy Otaku Pack Vol.7|True|5|7|9|
energy night|17-5|Happy Otaku Pack Vol.7|True|5|7|10|
Future Dive|16-0|Give Up TREATMENT Vol.6|True|4|6|9|
Re End of a Dream|16-1|Give Up TREATMENT Vol.6|False|5|8|11|
Etude -Storm-|16-2|Give Up TREATMENT Vol.6|True|6|8|10|
Unlimited Katharsis|16-3|Give Up TREATMENT Vol.6|False|4|6|10|
Magic Knight Girl|16-4|Give Up TREATMENT Vol.6|False|4|7|9|
Eeliaas|16-5|Give Up TREATMENT Vol.6|False|6|9|11|
Magic Spell|15-0|Cute Is Everything Vol.3|True|2|5|7|
Colorful Star, Colored Drawing, Travel Poem|15-1|Cute Is Everything Vol.3|False|3|4|6|
Satell Knight|15-2|Cute Is Everything Vol.3|False|3|6|8|
Black River Feat.Mes|15-3|Cute Is Everything Vol.3|True|1|4|6|
I am sorry|15-4|Cute Is Everything Vol.3|False|2|5|8|
Ueta Tori Tachi|15-5|Cute Is Everything Vol.3|False|3|6|8|
Elysion's Old Mans|14-0|Happy Otaku Pack Vol.6|False|3|5|8|
AXION|14-1|Happy Otaku Pack Vol.6|False|4|5|8|
Amnesia|14-2|Happy Otaku Pack Vol.6|True|3|6|9|
Onsen Dai Sakusen|14-3|Happy Otaku Pack Vol.6|True|4|6|8|
Gleam stone|14-4|Happy Otaku Pack Vol.6|False|4|7|9|
GOODWORLD|14-5|Happy Otaku Pack Vol.6|False|4|7|10|
Instant Soluble Neon|13-0|Cute Is Everything Vol.2|True|2|4|7|
Retrospective Poem on the Planet|13-1|Cute Is Everything Vol.2|False|3|5|7|
I'm Gonna Buy! Buy! Buy!|13-2|Cute Is Everything Vol.2|True|4|6|8|
Dating Manifesto|13-3|Cute Is Everything Vol.2|True|2|4|6|
First Snow|13-4|Cute Is Everything Vol.2|True|2|3|6|
Xin Shang Huahai|13-5|Cute Is Everything Vol.2|False|3|6|8|
Gaikan Chrysalis|12-0|Give Up TREATMENT Vol.5|False|4|6|8|
Sterelogue|12-1|Give Up TREATMENT Vol.5|True|5|7|10|
Cheshire's Dance|12-2|Give Up TREATMENT Vol.5|True|4|7|10|
Skrik|12-3|Give Up TREATMENT Vol.5|True|5|7|11|
Soda Pop Canva5!|12-4|Give Up TREATMENT Vol.5|False|5|8|10|
RUBY LINTe|12-5|Give Up TREATMENT Vol.5|False|5|8|11|
Brave My Heart|11-0|Happy Otaku Pack Vol.5|True|3|5|7|
Sakura Fubuki|11-1|Happy Otaku Pack Vol.5|False|4|7|10|
8bit Adventurer|11-2|Happy Otaku Pack Vol.5|False|6|8|10|
Suffering of screw|11-3|Happy Otaku Pack Vol.5|False|3|5|8|
tiny lady|11-4|Happy Otaku Pack Vol.5|True|4|6|9|
Power Attack|11-5|Happy Otaku Pack Vol.5|False|5|7|10|
Destr0yer|10-0|Give Up TREATMENT Vol.4|False|4|7|9|
Noel|10-1|Give Up TREATMENT Vol.4|False|5|8|10|
Kyoukiranbu|10-2|Give Up TREATMENT Vol.4|False|7|9|11|
Two Phace|10-3|Give Up TREATMENT Vol.4|True|4|7|10|
Fly Again|10-4|Give Up TREATMENT Vol.4|False|5|7|10|
ouroVoros|10-5|Give Up TREATMENT Vol.4|False|7|9|11|
Leave It Alone|9-0|Happy Otaku Pack Vol.4|True|2|5|8|
Tsubasa no Oreta Tenshitachi no Requiem|9-1|Happy Otaku Pack Vol.4|False|4|7|9|
Chronomia|9-2|Happy Otaku Pack Vol.4|False|5|7|10|
Dandelion's Daydream|9-3|Happy Otaku Pack Vol.4|True|5|7|8|
Lorikeet Flat design|9-4|Happy Otaku Pack Vol.4|True|5|7|10|
GOODRAGE|9-5|Happy Otaku Pack Vol.4|False|6|9|11|
Altale|8-0|Give Up TREATMENT Vol.3|False|3|5|7|10
Brain Power|8-1|Give Up TREATMENT Vol.3|False|4|7|10|
Berry Go!!|8-2|Give Up TREATMENT Vol.3|False|3|6|9|
Sweet* Witch* Girl*|8-3|Give Up TREATMENT Vol.3|False|6|8|10|?
trippers feeling!|8-4|Give Up TREATMENT Vol.3|True|5|7|9|11
Lilith ambivalence lovers|8-5|Give Up TREATMENT Vol.3|False|5|8|10|
Brave My Soul|7-0|Give Up TREATMENT Vol.2|False|4|6|8|
Halcyon|7-1|Give Up TREATMENT Vol.2|False|4|7|10|
Crimson Nightingale|7-2|Give Up TREATMENT Vol.2|True|4|7|10|
Invader|7-3|Give Up TREATMENT Vol.2|True|3|7|11|
Lyrith|7-4|Give Up TREATMENT Vol.2|False|5|7|10|
GOODBOUNCE|7-5|Give Up TREATMENT Vol.2|False|4|6|9|
Out of Sense|6-0|Budget Is Burning Vol.1|False|3|5|8|
My Life Is For You|6-1|Budget Is Burning Vol.1|False|2|4|7|
Etude -Sunset-|6-2|Budget Is Burning Vol.1|True|5|7|9|
Goodbye Boss|6-3|Budget Is Burning Vol.1|False|4|6|8|
Stargazer|6-4|Budget Is Burning Vol.1|True|2|5|8|9
Lys Tourbillon|6-5|Budget Is Burning Vol.1|True|4|6|8|
Thirty Million Persona|5-0|Happy Otaku Pack Vol.3|False|2|4|6|
conflict|5-1|Happy Otaku Pack Vol.3|False|2|6|9|10
Enka Dance Music|5-2|Happy Otaku Pack Vol.3|False|3|5|7|
XING|5-3|Happy Otaku Pack Vol.3|True|4|6|8|9
Amakakeru Soukyuu no Serenade|5-4|Happy Otaku Pack Vol.3|False|3|6|9|
Gift box|5-5|Happy Otaku Pack Vol.3|False|5|7|10|
MUSEDASH!!!!|4-0|Happy Otaku Pack Vol.2|False|2|6|9|0
Imprinting|4-1|Happy Otaku Pack Vol.2|False|3|6|9|0
Skyward|4-2|Happy Otaku Pack Vol.2|True|4|7|10|0
La nuit de vif|4-3|Happy Otaku Pack Vol.2|True|2|5|8|0
Bit-alize|4-4|Happy Otaku Pack Vol.2|False|3|6|8|0
GOODTEK|4-5|Happy Otaku Pack Vol.2|False|4|6|9|?
Maharajah|3-0|Happy Otaku Pack Vol.1|False|1|3|6|
keep on running|3-1|Happy Otaku Pack Vol.1|False|5|7|9|
Kafig|3-2|Happy Otaku Pack Vol.1|True|4|6|8|
-+|3-3|Happy Otaku Pack Vol.1|True|4|6|8|
Tenri Kaku Jou|3-4|Happy Otaku Pack Vol.1|True|3|6|9|
Adjudicatorz-DanZai-|3-5|Happy Otaku Pack Vol.1|False|3|7|10|
Oriens|2-0|Give Up TREATMENT Vol.1|True|3|7|9|
PUPA|2-1|Give Up TREATMENT Vol.1|False|6|8|11|
Luna Express 2032|2-2|Give Up TREATMENT Vol.1|False|4|6|8|
Ukiyoe Yokochou|2-3|Give Up TREATMENT Vol.1|False|6|7|9|
Alice in Misanthrope|2-4|Give Up TREATMENT Vol.1|False|5|7|10|
GOODMEN|2-5|Give Up TREATMENT Vol.1|False|5|7|10|
Sunshine and Rainbow after August Rain|1-0|Cute Is Everything Vol.1|False|2|5|8|
Magical Number|1-1|Cute Is Everything Vol.1|False|2|5|8|
Dreaming Girl|1-2|Cute Is Everything Vol.1|False|2|5|6|
Daruma-san Fell Over|1-3|Cute Is Everything Vol.1|False|3|4|6|
Different|1-4|Cute Is Everything Vol.1|False|1|3|6|
The Future of the Phantom|1-5|Cute Is Everything Vol.1|False|1|3|5|
Doki Doki Jump!|63-0|MUSE RADIO FM104|True|3|5|7|
Centennial Streamers High|63-1|MUSE RADIO FM104|False|4|7|9|
Love Patrol|63-2|MUSE RADIO FM104|True|3|5|7|
Mahorova|63-3|MUSE RADIO FM104|True|3|5|8|
Yoru no machi|63-4|MUSE RADIO FM104|True|1|4|7|
INTERNET YAMERO|63-5|MUSE RADIO FM104|True|6|8|10|
Abracadabra|43-24|MD Plus Project|False|6|8|10|
Squalldecimator feat. EZ-Ven|43-25|MD Plus Project|True|5|7|9|
Amateras Rhythm|43-26|MD Plus Project|True|6|8|11|
Record one's Dream|43-27|MD Plus Project|False|4|7|10|
Lunatic|43-28|MD Plus Project|True|5|8|10|
Jiumeng|43-29|MD Plus Project|True|3|6|8|
The Day We Become Family|43-30|MD Plus Project|True|3|5|8|
Sutori ma FIRE!?!?|64-0|COSMIC RADIO PEROLIST|True|3|5|8|
Tanuki Step|64-1|COSMIC RADIO PEROLIST|True|5|7|10|11
Space Stationery|64-2|COSMIC RADIO PEROLIST|True|5|7|10|
Songs Are Judged 90% by Chorus feat. Mameko|64-3|COSMIC RADIO PEROLIST|True|6|8|10|
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
BrainDance|65-0|NeonAbyss|True|3|6|9|
My Focus!|65-1|NeonAbyss|True|5|7|10|
ABABABA BURST|65-2|NeonAbyss|True|5|7|9|
ULTRA HIGHER|65-3|NeonAbyss|True|4|7|10|
Silver Bullet|43-31|MD Plus Project|True|5|7|10|
Random|43-32|MD Plus Project|True|4|7|9|
OTOGE-BOSS-KYOKU-CHAN|43-33|MD Plus Project|False|6|8|10|11
Crow Rabbit|43-34|MD Plus Project|True|7|9|11|
SyZyGy|43-35|MD Plus Project|True|6|8|10|11
Mermaid Radio|43-36|MD Plus Project|True|3|5|7|
Helixir|43-37|MD Plus Project|False|6|8|10|
Highway Cruisin'|43-38|MD Plus Project|False|3|5|8|
JACK PT BOSS|43-39|MD Plus Project|False|6|8|10|
Time Capsule|43-40|MD Plus Project|False|7|9|11|
39 Music!|66-0|Miku in Museland|False|3|5|8|
Hand in Hand|66-1|Miku in Museland|False|1|3|6|
Cynical Night Plan|66-2|Miku in Museland|False|4|6|8|
God-ish|66-3|Miku in Museland|False|4|7|10|
Darling Dance|66-4|Miku in Museland|False|4|7|9|
Hatsune Creation Myth|66-5|Miku in Museland|False|6|8|10|11
The Vampire|66-6|Miku in Museland|False|4|6|9|
Future Eve|66-7|Miku in Museland|False|4|8|11|
Unknown Mother Goose|66-8|Miku in Museland|False|4|8|10|
Shun-ran|66-9|Miku in Museland|False|4|7|9|
NICE TYPE feat. monii|43-41|MD Plus Project|True|3|6|8|
Rainy Angel|67-0|Happy Otaku Pack Vol.18|True|4|6|9|11
Gullinkambi|67-1|Happy Otaku Pack Vol.18|True|4|7|10|
RakiRaki Rebuilders!!!|67-2|Happy Otaku Pack Vol.18|True|5|7|10|
Laniakea|67-3|Happy Otaku Pack Vol.18|False|5|8|10|
OTTAMA GAZER|67-4|Happy Otaku Pack Vol.18|True|5|8|10|
Sleep Tight feat.Macoto|67-5|Happy Otaku Pack Vol.18|True|3|5|8|
New York Back Raise|68-0|Gambler's Tricks|True|6|8|10|
slic.hertz|68-1|Gambler's Tricks|True|5|7|9|
Fuzzy-Navel|68-2|Gambler's Tricks|True|6|8|10|11
Swing Edge|68-3|Gambler's Tricks|True|4|8|10|
Twisted Escape|68-4|Gambler's Tricks|True|5|8|10|11
Swing Sweet Twee Dance|68-5|Gambler's Tricks|False|4|7|10|
Sanyousei SAY YA!!!|43-42|MD Plus Project|False|4|6|8|
YUKEMURI TAMAONSEN II|43-43|MD Plus Project|False|3|6|9|
Samayoi no mei Amatsu|69-0|Touhou Mugakudan -3-|False|4|6|9|
INTERNET SURVIVOR|69-1|Touhou Mugakudan -3-|False|5|8|10|
Shuki*RaiRai|69-2|Touhou Mugakudan -3-|False|5|7|9|
HELLOHELL|69-3|Touhou Mugakudan -3-|False|4|7|10|
Calamity Fortune|69-4|Touhou Mugakudan -3-|True|6|8|10|11
Tsurupettan|69-5|Touhou Mugakudan -3-|True|2|5|8|
Twilight Poems|43-44|MD Plus Project|True|3|6|8|
All My Friends feat. RANASOL|43-45|MD Plus Project|True|4|7|9|
Heartache|43-46|MD Plus Project|True|5|7|10|
Blue Lemonade|43-47|MD Plus Project|True|3|6|8|
Haunted Dance|43-48|MD Plus Project|False|6|9|11|
Hey Vincent.|43-49|MD Plus Project|True|6|8|10|
Meteor feat. TEA|43-50|MD Plus Project|True|3|6|9|
Narcissism Angel|43-51|MD Plus Project|True|1|3|6|
AlterLuna|43-52|MD Plus Project|True|6|8|11|12
Niki Tousen|43-53|MD Plus Project|True|6|8|10|12
Rettou Joutou|70-0|Rin Len's Mirrorland|False|4|7|9|
Telecaster B-Boy|70-1|Rin Len's Mirrorland|False|5|7|10|
Iya Iya Iya|70-2|Rin Len's Mirrorland|False|2|4|7|
Nee Nee Nee|70-3|Rin Len's Mirrorland|False|4|6|8|
Chaotic Love Revolution|70-4|Rin Len's Mirrorland|False|4|6|8|
Dance of the Corpses|70-5|Rin Len's Mirrorland|False|2|5|8|
Bitter Choco Decoration|70-6|Rin Len's Mirrorland|False|3|6|9|
Dance Robot Dance|70-7|Rin Len's Mirrorland|False|4|7|10|
Sweet Devil|70-8|Rin Len's Mirrorland|False|5|7|9|
Someday'z Coming|70-9|Rin Len's Mirrorland|False|5|7|9|
Yume Ou Mono Yo Secret|0-53|Default Music|True|6|8|10|
Yume Ou Mono Yo|0-54|Default Music|True|1|4|0|
Sweet Dream VIVINOS|71-0|Valentine Stage|False|1|4|7|
Ruler Of My Heart VIVINOS|71-1|Valentine Stage|False|2|4|6|
Reality Show|71-2|Valentine Stage|False|5|7|10|
SIG feat.Tobokegao|71-3|Valentine Stage|True|3|6|8|
Rose Love|71-4|Valentine Stage|True|2|4|7|
Euphoria|71-5|Valentine Stage|True|1|3|6|
P E R O P E R O Brother Dance|72-0|Legends of Muse Warriors|True|0|?|0|
PA PPA PANIC|72-1|Legends of Muse Warriors|False|4|8|10|
How To Make Music Game Song!|72-2|Legends of Muse Warriors|True|6|8|10|11
Re Re|72-3|Legends of Muse Warriors|True|7|9|11|12
Marmalade Twins|72-4|Legends of Muse Warriors|True|5|8|10|
DOMINATOR|72-5|Legends of Muse Warriors|True|7|9|11|
Teshikani TESHiKANi|72-6|Legends of Muse Warriors|True|5|7|9|
Urban Magic|73-0|Happy Otaku Pack Vol.19|True|3|5|7|
Maid's Prank|73-1|Happy Otaku Pack Vol.19|True|5|7|10|
Dance Dance Good Night Dance|73-2|Happy Otaku Pack Vol.19|True|2|4|7|
Ops Limone|73-3|Happy Otaku Pack Vol.19|True|5|8|11|
NOVA|73-4|Happy Otaku Pack Vol.19|True|6|8|10|
Heaven's Gradius|73-5|Happy Otaku Pack Vol.19|True|6|8|10|
Ray Tuning|74-0|CHUNITHM COURSE MUSE|True|6|8|10|
World Vanquisher|74-1|CHUNITHM COURSE MUSE|True|6|8|10|11
Tsukuyomi Ni Naru Replaced|74-2|CHUNITHM COURSE MUSE|True|5|7|9|
The wheel to the right|74-3|CHUNITHM COURSE MUSE|True|5|7|9|11
Climax|74-4|CHUNITHM COURSE MUSE|True|4|8|11|11
Spider's Thread|74-5|CHUNITHM COURSE MUSE|True|5|8|10|12
HIT ME UP|43-54|MD Plus Project|True|4|6|8|
Test Me feat. Uyeon|43-55|MD Plus Project|True|3|5|9|
Assault TAXI|43-56|MD Plus Project|True|4|7|10|
No|43-57|MD Plus Project|False|4|6|9|
Pop it|43-58|MD Plus Project|True|1|3|6|
HEARTBEAT! KyunKyun!|43-59|MD Plus Project|True|4|6|9|
SUPERHERO|75-0|Novice Rider Pack|False|2|4|7|
Highway_Summer|75-1|Novice Rider Pack|True|2|4|6|
Mx. Black Box|75-2|Novice Rider Pack|True|5|7|9|
Sweet Encounter|75-3|Novice Rider Pack|True|2|4|7|
Echo over you... Secret|0-55|Default Music|False|6|8|10|
Echo over you...|0-56|Default Music|False|1|4|0|
Tsukuyomi Ni Naru|74-6|CHUNITHM COURSE MUSE|True|5|8|10|
disco light|76-0|MUSE RADIO FM105|True|5|7|9|
room light feat.chancylemon|76-1|MUSE RADIO FM105|True|3|5|7|
Invisible|76-2|MUSE RADIO FM105|True|3|5|8|
Christmas Season-LLABB|76-3|MUSE RADIO FM105|True|1|4|7|
Hyouryu|77-0|Let's Rhythm Jam!|False|6|8|10|
The Whole Rest|77-1|Let's Rhythm Jam!|False|5|8|10|11
Hydra|77-2|Let's Rhythm Jam!|False|4|7|11|
Pastel Lines|77-3|Let's Rhythm Jam!|False|3|6|9|
LINK x LIN#S|77-4|Let's Rhythm Jam!|False|3|6|9|
Arcade ViruZ|77-5|Let's Rhythm Jam!|False|6|8|11|
Eve Avenir|78-0|Endless Pirouette|True|6|8|10|
Silverstring|78-1|Endless Pirouette|True|5|7|10|
Melusia|78-2|Endless Pirouette|False|5|7|10|11
Devil's Castle|78-3|Endless Pirouette|True|4|7|10|
Abatement|78-4|Endless Pirouette|True|6|8|10|11
Azalea|78-5|Endless Pirouette|False|4|8|10|
Brightly World|78-6|Endless Pirouette|True|6|8|10|
We'll meet in every world ***|78-7|Endless Pirouette|True|7|9|11|
Collapsar|78-8|Endless Pirouette|True|7|9|10|11
Parousia|78-9|Endless Pirouette|False|6|8|10|
Gunners in the Rain|79-0|Ensemble Arcanum|False|5|8|10|
Halzion|79-1|Ensemble Arcanum|False|2|5|8|
SHOWTIME!!|79-2|Ensemble Arcanum|False|6|8|10|
Achromic Riddle|79-3|Ensemble Arcanum|False|6|8|10|11
karanosu|79-4|Ensemble Arcanum|False|3|6|8|

View File

@@ -1,14 +1,13 @@
from Options import Toggle, Range, Choice, DeathLink, OptionSet, PerGameCommonOptions, OptionGroup, Removed
from Options import Toggle, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions, OptionGroup, Removed
from dataclasses import dataclass
from .MuseDashCollection import MuseDashCollections
from .MuseDashData import SONG_DATA
class DLCMusicPacks(OptionSet):
"""
Choose which DLC Packs will be included in the pool of chooseable songs.
Note: The [Just As Planned] DLC contains all [Muse Plus] songs.
"""
display_name = "DLC Packs"
@@ -18,7 +17,7 @@ class DLCMusicPacks(OptionSet):
class StreamerModeEnabled(Toggle):
"""
In Muse Dash, an option named 'Streamer Mode' removes songs which may trigger copyright issues when streaming.
If this is enabled, only songs available under Streamer Mode will be available for randomization.
"""
display_name = "Streamer Mode Only Songs"
@@ -70,7 +69,7 @@ class DifficultyMode(Choice):
class DifficultyModeOverrideMin(Range):
"""
Ensures that 1 difficulty has at least 1 this value or higher per song.
Note: Difficulty Mode must be set to Manual.
"""
display_name = "Manual Difficulty Min"
@@ -83,7 +82,7 @@ class DifficultyModeOverrideMin(Range):
class DifficultyModeOverrideMax(Range):
"""
Ensures that 1 difficulty has at least 1 this value or lower per song.
Note: Difficulty Mode must be set to Manual.
"""
display_name = "Manual Difficulty Max"
@@ -115,7 +114,7 @@ class GradeNeeded(Choice):
class MusicSheetCountPercentage(Range):
"""
Controls how many music sheets are added to the pool based on the number of songs, including starting songs.
Higher numbers leads to more consistent game lengths, but will cause individual music sheets to be less important.
"""
range_start = 10
@@ -138,7 +137,7 @@ class ChosenTraps(OptionSet):
- Traps last the length of a song, or until you die.
- VFX Traps consist of visual effects that play over the song. (i.e. Grayscale.)
- SFX Traps consist of changing your sfx setting to one possibly more annoying sfx.
Note: SFX traps are only available if [Just as Planned] DLC songs are enabled.
"""
display_name = "Chosen Traps"
@@ -153,26 +152,24 @@ class TrapCountPercentage(Range):
display_name = "Trap Percentage"
class SongSet(OptionSet):
valid_keys = SONG_DATA.keys()
class IncludeSongs(SongSet):
class IncludeSongs(ItemSet):
"""
These songs will be guaranteed to show up within the seed.
- You must have the DLC enabled to play these songs.
- Difficulty options will not affect these songs.
- If there are too many included songs, this will act as a whitelist ignoring song difficulty.
"""
verify_item_name = True
display_name = "Include Songs"
class ExcludeSongs(SongSet):
class ExcludeSongs(ItemSet):
"""
These songs will be guaranteed to not show up within the seed.
Note: Does not affect songs within the "Include Songs" list.
"""
verify_item_name = True
display_name = "Exclude Songs"
@@ -214,7 +211,7 @@ class MuseDashOptions(PerGameCommonOptions):
death_link: DeathLink
include_songs: IncludeSongs
exclude_songs: ExcludeSongs
# Removed
allow_just_as_planned_dlc_songs: Removed
available_trap_types: Removed

View File

@@ -63,11 +63,6 @@ class MuseDashWorld(World):
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()}
item_name_groups = {
"Songs": {name for name in md_collection.song_items.keys()},
"Filler Items": {name for name in md_collection.filler_items.keys()},
"Traps": {name for name in md_collection.trap_items.keys()}
}
# Working Data
victory_song_name: str = ""
@@ -184,6 +179,10 @@ class MuseDashWorld(World):
if trap:
return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player)
album = self.md_collection.album_items.get(name)
if album:
return MuseDashSongItem(name, self.player, album)
song = self.md_collection.song_items[name]
return MuseDashSongItem(name, self.player, song)

View File

@@ -1,17 +1,7 @@
from . import MuseDashTestBase
from typing import List
class DifficultyRanges(MuseDashTestBase):
DIFF_OVERRIDES: List[str] = [
"MuseDash ka nanika hi",
"Rush-Hour",
"Find this Month's Featured Playlist",
"PeroPero in the Universe",
"umpopoff",
"P E R O P E R O Brother Dance",
]
def test_all_difficulty_ranges(self) -> None:
muse_dash_world = self.get_world()
dlc_set = {x for x in muse_dash_world.md_collection.DLC}
@@ -73,7 +63,7 @@ class DifficultyRanges(MuseDashTestBase):
def test_songs_have_difficulty(self) -> None:
muse_dash_world = self.get_world()
for song_name in self.DIFF_OVERRIDES:
for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES:
song = muse_dash_world.md_collection.song_items[song_name]
# Some songs are weird and have less than the usual 3 difficulties.

View File

@@ -2200,7 +2200,7 @@ def patch_rom(world, rom):
elif world.shuffle_bosses != 'off':
vanilla_reward = world.get_location(boss_name).vanilla_item
vanilla_reward_location = world.multiworld.find_item(vanilla_reward, world.player) # hinted_dungeon_reward_locations[vanilla_reward.name]
area = HintArea.at(vanilla_reward_location).text(world.hint_rng, world.clearer_hints, preposition=True)
area = HintArea.at(vanilla_reward_location).text(world.clearer_hints, preposition=True)
compass_message = "\x13\x75\x08You found the \x05\x41Compass\x05\x40\x01for %s\x05\x40!\x01The %s can be found\x01%s!\x09" % (dungeon_name, vanilla_reward, area)
else:
boss_location = next(filter(lambda loc: loc.type == 'Boss', world.get_entrance(f'{dungeon} Boss Door -> {boss_name} Boss Room').connected_region.locations))

View File

@@ -582,7 +582,8 @@ class OOTWorld(World):
new_exit = OOTEntrance(self.player, self.multiworld, '%s -> %s' % (new_region.name, exit), new_region)
new_exit.vanilla_connected_region = exit
new_exit.rule_string = rule
self.parser.parse_spot_rule(new_exit)
if self.options.logic_rules != 'no_logic':
self.parser.parse_spot_rule(new_exit)
if new_exit.never:
logger.debug('Dropping unreachable exit: %s', new_exit.name)
else:

View File

@@ -287,7 +287,7 @@ class PokemonEmeraldClient(BizHawkClient):
pokedex_caught_bytes = read_result[0]
game_clear = False
local_checked_locations: set[int] = set()
local_checked_locations = set()
local_set_events = {flag_name: False for flag_name in TRACKER_EVENT_FLAGS}
local_found_key_items = {location_name: False for location_name in KEY_LOCATION_FLAGS}
defeated_legendaries = {legendary_name: False for legendary_name in LEGENDARY_NAMES.values()}
@@ -350,7 +350,10 @@ class PokemonEmeraldClient(BizHawkClient):
self.local_checked_locations = local_checked_locations
if local_checked_locations is not None:
await ctx.check_locations(local_checked_locations)
await ctx.send_msgs([{
"cmd": "LocationChecks",
"locations": list(local_checked_locations),
}])
# Send game clear
if not ctx.finished_game and game_clear:

View File

@@ -87,21 +87,6 @@ class SMZ3World(World):
self.rom_name_available_event = threading.Event()
self.locations: Dict[str, Location] = {}
self.unreachable = []
self.junkItemsNames = [item.name for item in [
ItemType.Arrow,
ItemType.OneHundredRupees,
ItemType.TenArrows,
ItemType.ThreeBombs,
ItemType.OneRupee,
ItemType.FiveRupees,
ItemType.TwentyRupees,
ItemType.FiftyRupees,
ItemType.ThreeHundredRupees,
ItemType.ETank,
ItemType.Missile,
ItemType.Super,
ItemType.PowerBomb
]]
super().__init__(world, player)
@classmethod

View File

@@ -309,7 +309,7 @@ class SoEOptions(PerGameCommonOptions):
@property
def flags(self) -> str:
flags = 'AGBo' # configures auto-tracker to AP's fill
flags = ''
for field in fields(self):
option = getattr(self, field.name)
if isinstance(option, (EvermizerFlag, EvermizerFlags)):

View File

@@ -1,37 +1,36 @@
pyevermizer==0.50.1 \
--hash=sha256:4d1f43d5f8016e7bfcb5cd80b447a4f278b60b1b250a6153e66150230bf280e8 \
--hash=sha256:06af4f66ae1f21932a936bf741a0547bbb8ff92eea8fb8efece6bc1760a8a999 \
--hash=sha256:1ddbc36860704385a767d24364eac6504acc74f185c98b50cf52219c6e0148c6 \
--hash=sha256:61f0adc4f615867e51bfcd7d7c90f19779a61391a995c721e7393005e8413950 \
--hash=sha256:d84761ee03ebdaf011befe01638db1fff128b1c37405088868f0025e064977f3 \
--hash=sha256:0433507dd8ad96375f3b64534faefdf9d325b69a19e108db1414fc75d6e72160 \
--hash=sha256:e8857f719da9eaaa54f564886ff1b36cb89b8ccf08aa6ccca2d5d3c41da0b067 \
--hash=sha256:40e76a30968b1fce3d727b47b2693d4151a9ad29b053a33bf06cde8fa63c3d15 \
--hash=sha256:09ced5349a183656c1f8dcb85e41bdd496d1c5f2bb8f712d12a055d6efa7b917 \
--hash=sha256:162806e7b0156e25612e60d25af68772cf553b3352a5cf31866d838295ccb591 \
--hash=sha256:79750965bc63ffa351c167672b51c32f2a8d3242e07e769f925d1f306564a18d \
--hash=sha256:b1875eb79c8800352f30180db296036d8b512082d6609e2368aa7032c1cf7e27 \
--hash=sha256:7989e6f06c1ea38687a6b14416b179f459282ea81edbb86086d426fe0d63bf7a \
--hash=sha256:8a4c5c62997e7378457624a88c12b27b52d345b365c3cfae7fee77ee46eb7cd0 \
--hash=sha256:a22557f56ada1ace61b781e731e06466c22b6cc605c1aa9dec10e3697b10f5e6 \
--hash=sha256:d1057e70be839e9c3a91f0f173bc795fc0014cf560767d699cc26eba5f5cfc6f \
--hash=sha256:8540bd8e8ec49422b494beece1f6bf4cca61aa452a4c0f85c3a8b77283b24753 \
--hash=sha256:569b98352fc6e1fae85a8c2ee3f2e61276762bc158ac5b7e07a476ee0f9e2617 \
--hash=sha256:1b21eed21eb9338a6e7024b015d0107eaecf78c61f8ece8e6553d77f7f0ba726 \
--hash=sha256:51ff863e92c7b608d464da10c775b5df5ad3651a05c2d316c1d60a46572fdef9 \
--hash=sha256:0f920d745df15e3171412cbda05fc21c9354323d0e8dfc066ed6051fa7df9879 \
--hash=sha256:d78970415fb03c1dd22aef8da7526e5b33eaf4c9848f5cbad843ad159254f526 \
--hash=sha256:50536924bbf702d310b92d307d7c5060f6a4307bf99b61f79571ba2675ebb1ff \
--hash=sha256:1123f8f87ce6415183126842eca1fff98362ff545204adfd4c7b6cf1c396b738 \
--hash=sha256:1b248af5aa7321e46ae05675b15a5993e28311dfabc68cee2e398ce571f28eb2 \
--hash=sha256:a76e9d17ec3af9317b3a9d5e9f9f04aea80a5902c33f6fe82d02381f2fd2cb69 \
--hash=sha256:081ed52f8e1693ca48262cb5a9687ee62c4f9a50c667a487192c72be4c1b7fac \
--hash=sha256:2978aa13826337d59799f41dda41fa4cecd9f59fae8843613230cf298b06fa6e \
--hash=sha256:d377c2fd68c3d529d89ba40a762b6424c3b04c0d58593c02f06adbdf236f72ad \
--hash=sha256:800d6c30eab6ca3ee39a6c297d08cb74cfa5a4bce498aa3f05a612596f8c513b \
--hash=sha256:0cf40413f4b7ae5d561e47706f446b91440a1b74abe33b8fabc995d92c3325ca \
--hash=sha256:97791b8695aa215ef407824d1e6c0582a2a2f89f3a0f254f5d791a5a84a0ad00 \
--hash=sha256:2174db5e4550f94cb63e17584973c9f9afdc23e5230cb556de8bf87bd72145ff \
--hash=sha256:f3a4cd6a9b292e7385722d8200e834a936886136ddaef2069035f7ec5eb50d34 \
--hash=sha256:7646efdf7e091c75dac9aebb6c9faf215de4f6b8567c049944790e43cbe63d51 \
--hash=sha256:cd56cca26ed9675790154dd70402ad28a381fc3c9031bd02eb9b1dad8c317398 \
pyevermizer==0.48.0 \
--hash=sha256:069ce348e480e04fd6208cfd0f789c600b18d7c34b5272375b95823be191ed57 \
--hash=sha256:58164dddaba2f340b0a8b4f39605e9dac46d8b0ffb16120e2e57bef2bfc1d683 \
--hash=sha256:115dd09d38a10f11d4629b340dfd75e2ba4089a1ff9e9748a11619829e02c876 \
--hash=sha256:b5e79cfe721e75cd7dec306b5eecd6385ce059e31ef7523ba7f677e22161ec6f \
--hash=sha256:382882fa9d641b9969a6c3ed89449a814bdabcb6b17b558872d95008a6cc908b \
--hash=sha256:92f67700e9132064a90858d391dd0b8fb111aff6dfd472befed57772d89ae567 \
--hash=sha256:fe4c453b7dbd5aa834b81f9a7aedb949a605455650b938b8b304d8e5a7edcbf7 \
--hash=sha256:c6bdbc45daf73818f763ed59ad079f16494593395d806f772dd62605c722b3e9 \
--hash=sha256:bb09f45448fdfd28566ae6fcc38c35a6632f4c31a9de2483848f6ce17b2359b5 \
--hash=sha256:00a8b9014744bd1528d0d39c33ede7c0d1713ad797a331cebb33d377a5bc1064 \
--hash=sha256:64ee69edc0a7d3b3caded78f2e46975f9beaff1ff8feaf29b87da44c45f38d7d \
--hash=sha256:9211bdb1313e9f4869ed5bdc61f3831d39679bd08bb4087f1c1e5475d9e3018b \
--hash=sha256:4a57821e422a1d75fe3307931a78db7a65e76955f8e401c4b347db6570390d09 \
--hash=sha256:04670cee0a0b913f24d2b9a1e771781560e2485bda31e6cd372a08421cf85cfa \
--hash=sha256:971fe77d0a20a1db984020ad253b613d0983f5e23ff22cba60ee5ac00d8128de \
--hash=sha256:127265fdb49f718f54706bf15604af1cec23590afd00d423089dea4331dcfc61 \
--hash=sha256:d47576360337c1a23f424cd49944a8d68fc4f3338e00719c9f89972c84604bef \
--hash=sha256:879659603e51130a0de8d9885d815a2fa1df8bd6cebe6d520d1c6002302adfdb \
--hash=sha256:6a91bfc53dd130db6424adf8ac97a1133e97b4157ed00f889d8cbd26a2a4b340 \
--hash=sha256:f3bf35fc5eef4cda49d2de77339fc201dd3206660a3dc15db005625b15bb806c \
--hash=sha256:e7c8d5bf59a3c16db20411bc5d8e9c9087a30b6b4edf1b5ed9f4c013291427e4 \
--hash=sha256:054a4d84ffe75448d41e88e1e0642ef719eb6111be5fe608e71e27a558c59069 \
--hash=sha256:e6f141ca367469c69ba7fbf65836c479ec6672c598cfcb6b39e8098c60d346bc \
--hash=sha256:6e65eb88f0c1ff4acde1c13b24ce649b0fe3d1d3916d02d96836c781a5022571 \
--hash=sha256:e61e8f476b6da809cf38912755ed8bb009665f589e913eb8df877e9fa763024b \
--hash=sha256:7e7c5484c0a2e3da6064de3f73d8d988d6703db58ab0be4730cbbf1a82319237 \
--hash=sha256:9033b954e5f4878fd94af6d2056c78e3316115521fb1c24a4416d5cbf2ad66ad \
--hash=sha256:824c623fff8ae4da176306c458ad63ad16a06a495a16db700665eca3c115924f \
--hash=sha256:8e31031409a8386c6a63b79d480393481badb3ba29f32ff7a0db2b4abed20ac8 \
--hash=sha256:7dbb7bb13e1e94f69f7ccdbcf4d35776424555fce5af1ca29d0256f91fdf087a \
--hash=sha256:3a24e331b259407b6912d6e0738aa8a675831db3b7493fcf54dc17cb0cb80d37 \
--hash=sha256:fdda06662a994271e96633cba100dd92b2fcd524acef8b2f664d1aaa14503cbd \
--hash=sha256:0f0fc81bef3dbb78ba6a7622dd4296f23c59825968a0bb0448beb16eb3397cc2 \
--hash=sha256:e07cbef776a7468669211546887357cc88e9afcf1578b23a4a4f2480517b15d9 \
--hash=sha256:e442212695bdf60e455673b7b9dd83a5d4b830d714376477093d2c9054d92832

View File

@@ -12,13 +12,13 @@ class OoBTest(SoETestBase):
# some locations that just need a weapon + OoB
oob_reachable = [
"Aquagoth", "Sons of Sth.", "Mad Monk", "Magmar", # OoB can use volcano shop to skip rock skip
"Levitate", "Fireball", "Speed",
"Levitate", "Fireball", "Drain", "Speed",
"E. Crustacia #107", "Energy Core #285", "Vanilla Gauge #57",
]
# some locations that should still be unreachable
oob_unreachable = [
"Tiny", "Rimsala",
"Barrier", "Drain", "Call Up", "Reflect", "Force Field", "Stop", # Stop guy only spawns from one entrance
"Barrier", "Call Up", "Reflect", "Force Field", "Stop", # Stop guy doesn't spawn for the other entrances
"Pyramid bottom #118", "Tiny's hideout #160", "Tiny's hideout #161", "Greenhouse #275",
]
# OoB + Diamond Eyes
@@ -31,42 +31,11 @@ class OoBTest(SoETestBase):
"Tiny's hideout #161",
]
with self.subTest("No items", oob_logic=in_logic):
self.assertLocationReachability(reachable=oob_reachable, unreachable=oob_unreachable, satisfied=False)
with self.subTest("Cutting Weapon", oob_logic=in_logic):
self.collect_by_name("Gladiator Sword")
self.assertLocationReachability(reachable=oob_reachable, unreachable=oob_unreachable, satisfied=in_logic)
with self.subTest("Cutting Weapon + DEs", oob_logic=in_logic):
self.collect_by_name("Diamond Eye")
self.assertLocationReachability(reachable=de_reachable, unreachable=de_unreachable, satisfied=in_logic)
def test_real_axe(self) -> None:
in_logic = self.options["out_of_bounds"] == "logic"
# needs real Bronze Axe+, regardless of OoB
real_axe_required = [
"Drain",
"Drain Cave #180",
"Drain Cave #181",
]
also_des_required = [
"Double Drain",
]
with self.subTest("No Axe", oob_logic=in_logic):
self.collect_by_name("Gladiator Sword")
self.assertLocationReachability(reachable=real_axe_required, satisfied=False)
with self.subTest("Bronze Axe", oob_logic=in_logic):
self.collect_by_name("Bronze Axe")
self.assertLocationReachability(reachable=real_axe_required, satisfied=True)
with self.subTest("Knight Basher", oob_logic=in_logic):
self.remove_by_name("Bronze Axe")
self.collect_by_name("Knight Basher")
self.assertLocationReachability(reachable=real_axe_required, satisfied=True)
self.assertLocationReachability(reachable=also_des_required, satisfied=False)
with self.subTest("Knight Basher + DEs", oob_logic=in_logic):
self.collect_by_name("Diamond Eye")
self.assertLocationReachability(reachable=also_des_required, satisfied=True)
self.assertLocationReachability(reachable=oob_reachable, unreachable=oob_unreachable, satisfied=False)
self.collect_by_name("Gladiator Sword")
self.assertLocationReachability(reachable=oob_reachable, unreachable=oob_unreachable, satisfied=in_logic)
self.collect_by_name("Diamond Eye")
self.assertLocationReachability(reachable=de_reachable, unreachable=de_unreachable, satisfied=in_logic)
def test_oob_goal(self) -> None:
# still need Energy Core with OoB if sequence breaks are not in logic

View File

@@ -1,27 +1,33 @@
import logging
from random import Random
from typing import Dict, Any, Iterable, Optional, List, TextIO
from typing import Dict, Any, Iterable, Optional, Union, List, TextIO
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState
from Options import PerGameCommonOptions
from worlds.AutoWorld import World, WebWorld
from . import rules
from .bundles.bundle_room import BundleRoom
from .bundles.bundles import get_all_bundles
from .content import StardewContent, create_content
from .content import content_packs, StardewContent, unpack_content, create_content
from .early_items import setup_early_items
from .items import item_table, create_items, ItemData, Group, items_by_group, get_all_filler_items, remove_limited_amount_packs
from .locations import location_table, create_locations, LocationData, locations_by_tag
from .logic.bundle_logic import BundleLogic
from .logic.logic import StardewLogic
from .logic.time_logic import MAX_MONTHS
from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, EnabledFillerBuffs, NumberOfMovementBuffs, \
BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization, FarmType
BuildingProgression, ExcludeGingerIsland, TrapItems, EntranceRandomization, FarmType, Walnutsanity
from .options.forced_options import force_change_options_if_incompatible
from .options.option_groups import sv_option_groups
from .options.presets import sv_options_presets
from .regions import create_regions
from .rules import set_rules
from .stardew_rule import True_, StardewRule, HasProgressionPercent
from .stardew_rule import True_, StardewRule, HasProgressionPercent, true_
from .strings.ap_names.event_names import Event
from .strings.entrance_names import Entrance as EntranceName
from .strings.goal_names import Goal as GoalName
from .strings.metal_names import Ore
from .strings.region_names import Region as RegionName, LogicRegion
logger = logging.getLogger(__name__)
@@ -88,6 +94,7 @@ class StardewValleyWorld(World):
randomized_entrances: Dict[str, str]
total_progression_items: int
excluded_from_total_progression_items: List[str] = [Event.received_walnuts]
def __init__(self, multiworld: MultiWorld, player: int):
super().__init__(multiworld, player)
@@ -152,7 +159,7 @@ class StardewValleyWorld(World):
self.multiworld.itempool += created_items
setup_early_items(self.multiworld, self.options, self.content, self.player, self.random)
self.setup_logic_events()
self.setup_player_events()
self.setup_victory()
# This is really a best-effort to get the total progression items count. It is mostly used to spread grinds across spheres are push back locations that
@@ -175,7 +182,7 @@ class StardewValleyWorld(World):
if self.options.season_randomization == SeasonRandomization.option_disabled:
for season in season_pool:
self.multiworld.push_precollected(self.create_item(season))
self.multiworld.push_precollected(self.create_starting_item(season))
return
if [item for item in self.multiworld.precollected_items[self.player]
@@ -185,12 +192,26 @@ class StardewValleyWorld(World):
if self.options.season_randomization == SeasonRandomization.option_randomized_not_winter:
season_pool = [season for season in season_pool if season.name != "Winter"]
starting_season = self.create_item(self.random.choice(season_pool))
starting_season = self.create_starting_item(self.random.choice(season_pool))
self.multiworld.push_precollected(starting_season)
def precollect_farm_type_items(self):
if self.options.farm_type == FarmType.option_meadowlands and self.options.building_progression & BuildingProgression.option_progressive:
self.multiworld.push_precollected(self.create_item("Progressive Coop"))
self.multiworld.push_precollected(self.create_starting_item("Progressive Coop"))
def setup_player_events(self):
self.setup_action_events()
self.setup_logic_events()
def setup_action_events(self):
spring_farming = LocationData(None, LogicRegion.spring_farming, Event.spring_farming)
self.create_event_location(spring_farming, true_, Event.spring_farming)
summer_farming = LocationData(None, LogicRegion.summer_farming, Event.summer_farming)
self.create_event_location(summer_farming, true_, Event.summer_farming)
fall_farming = LocationData(None, LogicRegion.fall_farming, Event.fall_farming)
self.create_event_location(fall_farming, true_, Event.fall_farming)
winter_farming = LocationData(None, LogicRegion.winter_farming, Event.winter_farming)
self.create_event_location(winter_farming, true_, Event.winter_farming)
def setup_logic_events(self):
def register_event(name: str, region: str, rule: StardewRule):
@@ -270,7 +291,7 @@ class StardewValleyWorld(World):
def get_all_location_names(self) -> List[str]:
return list(location.name for location in self.multiworld.get_locations(self.player))
def create_item(self, item: str | ItemData, override_classification: ItemClassification = None) -> StardewItem:
def create_item(self, item: Union[str, ItemData], override_classification: ItemClassification = None) -> StardewItem:
if isinstance(item, str):
item = item_table[item]
@@ -279,6 +300,12 @@ class StardewValleyWorld(World):
return StardewItem(item.name, override_classification, item.code, self.player)
def create_starting_item(self, item: Union[str, ItemData]) -> StardewItem:
if isinstance(item, str):
item = item_table[item]
return StardewItem(item.name, item.classification, item.code, self.player)
def create_event_location(self, location_data: LocationData, rule: StardewRule = None, item: Optional[str] = None):
if rule is None:
rule = True_()
@@ -386,19 +413,9 @@ class StardewValleyWorld(World):
if not change:
return False
player_state = state.prog_items[self.player]
received_progression_count = player_state[Event.received_progression_item]
received_progression_count += 1
if self.total_progression_items:
# Total progression items is not set until all items are created, but collect will be called during the item creation when an item is precollected.
# We can't update the percentage if we don't know the total progression items, can't divide by 0.
player_state[Event.received_progression_percent] = received_progression_count * 100 // self.total_progression_items
player_state[Event.received_progression_item] = received_progression_count
walnut_amount = self.get_walnut_amount(item.name)
if walnut_amount:
player_state[Event.received_walnuts] += walnut_amount
state.prog_items[self.player][Event.received_walnuts] += walnut_amount
return True
@@ -407,18 +424,9 @@ class StardewValleyWorld(World):
if not change:
return False
player_state = state.prog_items[self.player]
received_progression_count = player_state[Event.received_progression_item]
received_progression_count -= 1
if self.total_progression_items:
# We can't update the percentage if we don't know the total progression items, can't divide by 0.
player_state[Event.received_progression_percent] = received_progression_count * 100 // self.total_progression_items
player_state[Event.received_progression_item] = received_progression_count
walnut_amount = self.get_walnut_amount(item.name)
if walnut_amount:
player_state[Event.received_walnuts] -= walnut_amount
state.prog_items[self.player][Event.received_walnuts] -= walnut_amount
return True

View File

@@ -11,7 +11,7 @@ from ..strings.craftable_names import Bomb, Fence, Sprinkler, WildSeeds, Floor,
from ..strings.crop_names import Fruit, Vegetable
from ..strings.currency_names import Currency
from ..strings.fertilizer_names import Fertilizer, RetainingSoil, SpeedGro
from ..strings.fish_names import Fish, WaterItem, ModTrash, Trash
from ..strings.fish_names import Fish, WaterItem, ModTrash
from ..strings.flower_names import Flower
from ..strings.food_names import Meal
from ..strings.forageable_names import Forageable, SVEForage, DistantLandsForageable, Mushroom
@@ -378,12 +378,4 @@ recycling_bin = skill_recipe(ModMachine.recycling_bin, ModSkill.binning, 7, {Met
advanced_recycling_machine = skill_recipe(ModMachine.advanced_recycling_machine, ModSkill.binning, 9,
{MetalBar.iridium: 5, ArtisanGood.battery_pack: 2, MetalBar.quartz: 10}, ModNames.binning_skill)
coppper_slot_machine = skill_recipe(ModMachine.copper_slot_machine, ModSkill.luck, 2, {MetalBar.copper: 15, Material.stone: 1, Material.wood: 1,
Material.fiber: 1, Material.sap: 1, Loot.slime: 1,
Forageable.salmonberry: 1, Material.clay: 1, Trash.joja_cola: 1}, ModNames.luck_skill)
gold_slot_machine = skill_recipe(ModMachine.gold_slot_machine, ModSkill.luck, 4, {MetalBar.gold: 15, ModMachine.copper_slot_machine: 1}, ModNames.luck_skill)
iridium_slot_machine = skill_recipe(ModMachine.iridium_slot_machine, ModSkill.luck, 4, {MetalBar.iridium: 15, ModMachine.gold_slot_machine: 1}, ModNames.luck_skill)
radioactive_slot_machine = skill_recipe(ModMachine.radioactive_slot_machine, ModSkill.luck, 4, {MetalBar.radioactive: 15, ModMachine.iridium_slot_machine: 1}, ModNames.luck_skill)
all_crafting_recipes_by_name = {recipe.item: recipe for recipe in all_crafting_recipes}

View File

@@ -2935,10 +2935,6 @@ id,region,name,tags,mod_name
7433,Farm,Craft Composter,CRAFTSANITY,Binning Skill
7434,Farm,Craft Recycling Bin,CRAFTSANITY,Binning Skill
7435,Farm,Craft Advanced Recycling Machine,CRAFTSANITY,Binning Skill
7440,Farm,Craft Copper Slot Machine,"CRAFTSANITY",Luck Skill
7441,Farm,Craft Gold Slot Machine,"CRAFTSANITY",Luck Skill
7442,Farm,Craft Iridium Slot Machine,"CRAFTSANITY",Luck Skill
7443,Farm,Craft Radioactive Slot Machine,"CRAFTSANITY",Luck Skill
7451,Adventurer's Guild,Magic Elixir Recipe,"CHEFSANITY,CHEFSANITY_PURCHASE",Magic
7452,Adventurer's Guild,Travel Core Recipe,CRAFTSANITY,Magic
7453,Alesia Shop,Haste Elixir Recipe,CRAFTSANITY,Stardew Valley Expanded
@@ -3245,7 +3241,7 @@ id,region,name,tags,mod_name
8199,Shipping,Shipsanity: Hardwood Display,SHIPSANITY,Archaeology
8200,Shipping,Shipsanity: Wooden Display,SHIPSANITY,Archaeology
8201,Shipping,Shipsanity: Dwarf Gadget: Infinite Volcano Simulation,"SHIPSANITY,GINGER_ISLAND",Archaeology
8202,Shipping,Shipsanity: Water Shifter,"SHIPSANITY,DEPRECATED",Archaeology
8202,Shipping,Shipsanity: Water Shifter,SHIPSANITY,Archaeology
8203,Shipping,Shipsanity: Brown Amanita,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul
8204,Shipping,Shipsanity: Swamp Herb,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",Distant Lands - Witch Swamp Overhaul
8205,Shipping,Shipsanity: Void Mint Seeds,SHIPSANITY,Distant Lands - Witch Swamp Overhaul
1 id region name tags mod_name
2935 7433 Farm Craft Composter CRAFTSANITY Binning Skill
2936 7434 Farm Craft Recycling Bin CRAFTSANITY Binning Skill
2937 7435 Farm Craft Advanced Recycling Machine CRAFTSANITY Binning Skill
7440 Farm Craft Copper Slot Machine CRAFTSANITY Luck Skill
7441 Farm Craft Gold Slot Machine CRAFTSANITY Luck Skill
7442 Farm Craft Iridium Slot Machine CRAFTSANITY Luck Skill
7443 Farm Craft Radioactive Slot Machine CRAFTSANITY Luck Skill
2938 7451 Adventurer's Guild Magic Elixir Recipe CHEFSANITY,CHEFSANITY_PURCHASE Magic
2939 7452 Adventurer's Guild Travel Core Recipe CRAFTSANITY Magic
2940 7453 Alesia Shop Haste Elixir Recipe CRAFTSANITY Stardew Valley Expanded
3241 8199 Shipping Shipsanity: Hardwood Display SHIPSANITY Archaeology
3242 8200 Shipping Shipsanity: Wooden Display SHIPSANITY Archaeology
3243 8201 Shipping Shipsanity: Dwarf Gadget: Infinite Volcano Simulation SHIPSANITY,GINGER_ISLAND Archaeology
3244 8202 Shipping Shipsanity: Water Shifter SHIPSANITY,DEPRECATED SHIPSANITY Archaeology
3245 8203 Shipping Shipsanity: Brown Amanita SHIPSANITY,SHIPSANITY_FULL_SHIPMENT Distant Lands - Witch Swamp Overhaul
3246 8204 Shipping Shipsanity: Swamp Herb SHIPSANITY,SHIPSANITY_FULL_SHIPMENT Distant Lands - Witch Swamp Overhaul
3247 8205 Shipping Shipsanity: Void Mint Seeds SHIPSANITY Distant Lands - Witch Swamp Overhaul

View File

@@ -110,8 +110,6 @@ class LocationTags(enum.Enum):
MAGIC_LEVEL = enum.auto()
ARCHAEOLOGY_LEVEL = enum.auto()
DEPRECATED = enum.auto()
@dataclass(frozen=True)
class LocationData:
@@ -521,10 +519,6 @@ def create_locations(location_collector: StardewLocationCollector,
location_collector(location_data.name, location_data.code, location_data.region)
def filter_deprecated_locations(locations: Iterable[LocationData]) -> Iterable[LocationData]:
return [location for location in locations if LocationTags.DEPRECATED not in location.tags]
def filter_farm_type(options: StardewValleyOptions, locations: Iterable[LocationData]) -> Iterable[LocationData]:
# On Meadowlands, "Feeding Animals" replaces "Raising Animals"
if options.farm_type == FarmType.option_meadowlands:
@@ -555,8 +549,7 @@ def filter_modded_locations(options: StardewValleyOptions, locations: Iterable[L
def filter_disabled_locations(options: StardewValleyOptions, content: StardewContent, locations: Iterable[LocationData]) -> Iterable[LocationData]:
locations_deprecated_filter = filter_deprecated_locations(locations)
locations_farm_filter = filter_farm_type(options, locations_deprecated_filter)
locations_farm_filter = filter_farm_type(options, locations)
locations_island_filter = filter_ginger_island(options, locations_farm_filter)
locations_qi_filter = filter_qi_order_locations(options, locations_island_filter)
locations_masteries_filter = filter_masteries_locations(content, locations_qi_filter)

View File

@@ -10,16 +10,17 @@ from .season_logic import SeasonLogicMixin
from .tool_logic import ToolLogicMixin
from .. import options
from ..stardew_rule import StardewRule, True_, false_
from ..strings.ap_names.event_names import Event
from ..strings.fertilizer_names import Fertilizer
from ..strings.region_names import Region, LogicRegion
from ..strings.region_names import Region
from ..strings.season_names import Season
from ..strings.tool_names import Tool
farming_region_by_season = {
Season.spring: LogicRegion.spring_farming,
Season.summer: LogicRegion.summer_farming,
Season.fall: LogicRegion.fall_farming,
Season.winter: LogicRegion.winter_farming,
farming_event_by_season = {
Season.spring: Event.spring_farming,
Season.summer: Event.summer_farming,
Season.fall: Event.fall_farming,
Season.winter: Event.winter_farming,
}
@@ -53,7 +54,7 @@ class FarmingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogi
if isinstance(seasons, str):
seasons = (seasons,)
return self.logic.or_(*(self.logic.region.can_reach(farming_region_by_season[season]) for season in seasons))
return self.logic.or_(*(self.logic.received(farming_event_by_season[season]) for season in seasons))
def has_island_farm(self) -> StardewRule:
if self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_false:

View File

@@ -757,14 +757,6 @@ class Gifting(Toggle):
default = 1
all_mods = {ModNames.deepwoods, ModNames.tractor, ModNames.big_backpack,
ModNames.luck_skill, ModNames.magic, ModNames.socializing_skill, ModNames.archaeology,
ModNames.cooking_skill, ModNames.binning_skill, ModNames.juna,
ModNames.jasper, ModNames.alec, ModNames.yoba, ModNames.eugene,
ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores,
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator, ModNames.sve, ModNames.distant_lands,
ModNames.alecto, ModNames.lacey, ModNames.boarding_house}
# These mods have been disabled because either they are not updated for the current supported version of Stardew Valley,
# or we didn't find the time to validate that they work or fix compatibility issues if they do.
# Once a mod is validated to be functional, it can simply be removed from this list
@@ -774,7 +766,8 @@ disabled_mods = {ModNames.deepwoods, ModNames.magic,
ModNames.wellwick, ModNames.shiko, ModNames.delores, ModNames.riley,
ModNames.boarding_house}
enabled_mods = all_mods.difference(disabled_mods)
if 'unittest' in sys.modules.keys() or 'pytest' in sys.modules.keys():
disabled_mods = {}
class Mods(OptionSet):
@@ -782,11 +775,13 @@ class Mods(OptionSet):
visibility = Visibility.all & ~Visibility.simple_ui
internal_name = "mods"
display_name = "Mods"
valid_keys = enabled_mods
# In tests, we keep even the disabled mods active, because we expect some of them to eventually get updated for SV 1.6
# In that case, we want to maintain content and logic for them, and therefore keep testing them
if 'unittest' in sys.modules.keys() or 'pytest' in sys.modules.keys():
valid_keys = all_mods
valid_keys = {ModNames.deepwoods, ModNames.tractor, ModNames.big_backpack,
ModNames.luck_skill, ModNames.magic, ModNames.socializing_skill, ModNames.archaeology,
ModNames.cooking_skill, ModNames.binning_skill, ModNames.juna,
ModNames.jasper, ModNames.alec, ModNames.yoba, ModNames.eugene,
ModNames.wellwick, ModNames.ginger, ModNames.shiko, ModNames.delores,
ModNames.ayeisha, ModNames.riley, ModNames.skull_cavern_elevator, ModNames.sve, ModNames.distant_lands,
ModNames.alecto, ModNames.lacey, ModNames.boarding_house}.difference(disabled_mods)
class BundlePlando(OptionSet):

Some files were not shown because too many files have changed in this diff Show More