mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-04 05:23:41 -07:00
Compare commits
2 Commits
NewSoupVi-
...
plando-con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8732ef00f | ||
|
|
141660db80 |
1
.github/pyright-config.json
vendored
1
.github/pyright-config.json
vendored
@@ -2,7 +2,6 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"../BizHawkClient.py",
|
"../BizHawkClient.py",
|
||||||
"../Patch.py",
|
"../Patch.py",
|
||||||
"../test/param.py",
|
|
||||||
"../test/general/test_groups.py",
|
"../test/general/test_groups.py",
|
||||||
"../test/general/test_helpers.py",
|
"../test/general/test_helpers.py",
|
||||||
"../test/general/test_memory.py",
|
"../test/general/test_memory.py",
|
||||||
|
|||||||
4
.github/workflows/ctest.yml
vendored
4
.github/workflows/ctest.yml
vendored
@@ -36,9 +36,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
if: startsWith(matrix.os,'windows')
|
if: startsWith(matrix.os,'windows')
|
||||||
- uses: Bacondish2023/setup-googletest@49065d1f7a6d21f6134864dd65980fe5dbe06c73
|
- uses: Bacondish2023/setup-googletest@v1
|
||||||
with:
|
with:
|
||||||
build-type: 'Release'
|
build-type: 'Release'
|
||||||
- name: Build tests
|
- name: Build tests
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ class LinksAwakeningContext(CommonContext):
|
|||||||
la_task = None
|
la_task = None
|
||||||
client = None
|
client = None
|
||||||
# TODO: does this need to re-read on reset?
|
# TODO: does this need to re-read on reset?
|
||||||
found_checks = set()
|
found_checks = []
|
||||||
last_resend = time.time()
|
last_resend = time.time()
|
||||||
|
|
||||||
magpie_enabled = False
|
magpie_enabled = False
|
||||||
@@ -558,6 +558,10 @@ class LinksAwakeningContext(CommonContext):
|
|||||||
|
|
||||||
self.ui = LADXManager(self)
|
self.ui = LADXManager(self)
|
||||||
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
|
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
|
||||||
|
|
||||||
|
async def send_checks(self):
|
||||||
|
message = [{"cmd": "LocationChecks", "locations": self.found_checks}]
|
||||||
|
await self.send_msgs(message)
|
||||||
|
|
||||||
async def send_new_entrances(self, entrances: typing.Dict[str, str]):
|
async def send_new_entrances(self, entrances: typing.Dict[str, str]):
|
||||||
# Store the entrances we find on the server for future sessions
|
# Store the entrances we find on the server for future sessions
|
||||||
@@ -609,8 +613,8 @@ class LinksAwakeningContext(CommonContext):
|
|||||||
self.client.pending_deathlink = True
|
self.client.pending_deathlink = True
|
||||||
|
|
||||||
def new_checks(self, item_ids, ladxr_ids):
|
def new_checks(self, item_ids, ladxr_ids):
|
||||||
self.found_checks.update(item_ids)
|
self.found_checks += item_ids
|
||||||
create_task_log_exception(self.check_locations(self.found_checks))
|
create_task_log_exception(self.send_checks())
|
||||||
if self.magpie_enabled:
|
if self.magpie_enabled:
|
||||||
create_task_log_exception(self.magpie.send_new_checks(ladxr_ids))
|
create_task_log_exception(self.magpie.send_new_checks(ladxr_ids))
|
||||||
|
|
||||||
@@ -717,7 +721,7 @@ class LinksAwakeningContext(CommonContext):
|
|||||||
|
|
||||||
if self.last_resend + 5.0 < now:
|
if self.last_resend + 5.0 < now:
|
||||||
self.last_resend = now
|
self.last_resend = now
|
||||||
await self.check_locations(self.found_checks)
|
await self.send_checks()
|
||||||
if self.magpie_enabled:
|
if self.magpie_enabled:
|
||||||
try:
|
try:
|
||||||
self.magpie.set_checks(self.client.tracker.all_checks)
|
self.magpie.set_checks(self.client.tracker.all_checks)
|
||||||
|
|||||||
@@ -1579,7 +1579,6 @@ def dump_player_options(multiworld: MultiWorld) -> None:
|
|||||||
player_output = {
|
player_output = {
|
||||||
"Game": multiworld.game[player],
|
"Game": multiworld.game[player],
|
||||||
"Name": multiworld.get_player_name(player),
|
"Name": multiworld.get_player_name(player),
|
||||||
"ID": player,
|
|
||||||
}
|
}
|
||||||
output.append(player_output)
|
output.append(player_output)
|
||||||
for option_key, option in world.options_dataclass.type_hints.items():
|
for option_key, option in world.options_dataclass.type_hints.items():
|
||||||
@@ -1592,7 +1591,7 @@ def dump_player_options(multiworld: MultiWorld) -> None:
|
|||||||
game_option_names.append(display_name)
|
game_option_names.append(display_name)
|
||||||
|
|
||||||
with open(output_path(f"generate_{multiworld.seed_name}.csv"), mode="w", newline="") as file:
|
with open(output_path(f"generate_{multiworld.seed_name}.csv"), mode="w", newline="") as file:
|
||||||
fields = ["ID", "Game", "Name", *all_option_names]
|
fields = ["Game", "Name", *all_option_names]
|
||||||
writer = DictWriter(file, fields)
|
writer = DictWriter(file, fields)
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
writer.writerows(output)
|
writer.writerows(output)
|
||||||
|
|||||||
@@ -82,38 +82,6 @@ Unit tests can also be created using [TestBase](/test/bases.py#L16) or
|
|||||||
may be useful for generating a multiworld under very specific constraints without using the generic world setup, or for
|
may be useful for generating a multiworld under very specific constraints without using the generic world setup, or for
|
||||||
testing portions of your code that can be tested without relying on a multiworld to be created first.
|
testing portions of your code that can be tested without relying on a multiworld to be created first.
|
||||||
|
|
||||||
#### Parametrization
|
|
||||||
|
|
||||||
When defining a test that needs to cover a range of inputs it is useful to parameterize (to run the same test
|
|
||||||
for multiple inputs) the base test. Some important things to consider when attempting to parametrize your test are:
|
|
||||||
|
|
||||||
* [Subtests](https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests)
|
|
||||||
can be used to have parametrized assertions that show up similar to individual tests but without the overhead
|
|
||||||
of needing to instantiate multiple tests; however, subtests can not be multithreaded and do not have individual
|
|
||||||
timing data, so they are not suitable for slow tests.
|
|
||||||
|
|
||||||
* Archipelago's tests are test-runner-agnostic. That means tests are not allowed to use e.g. `@pytest.mark.parametrize`.
|
|
||||||
Instead, we define our own parametrization helpers in [test.param](/test/param.py).
|
|
||||||
|
|
||||||
* Classes inheriting from `WorldTestBase`, including those created by the helpers in `test.param`, will run all
|
|
||||||
base tests by default, make sure the produced tests actually do what you aim for and do not waste a lot of
|
|
||||||
extra CPU time. Consider using `TestBase` or `unittest.TestCase` directly
|
|
||||||
or setting `WorldTestBase.run_default_tests` to False.
|
|
||||||
|
|
||||||
#### Performance Considerations
|
|
||||||
|
|
||||||
Archipelago is big enough that the runtime of unittests can have an impact on productivity.
|
|
||||||
|
|
||||||
Individual tests should take less than a second, so they can be properly multithreaded.
|
|
||||||
|
|
||||||
Ideally, thorough tests are directed at actual code/functionality. Do not just create and/or fill a ton of individual
|
|
||||||
Multiworlds that spend most of the test time outside what you actually want to test.
|
|
||||||
|
|
||||||
Consider generating/validating "random" games as part of your APWorld release workflow rather than having that be part
|
|
||||||
of continuous integration, and add minimal reproducers to the "normal" tests for problems that were found.
|
|
||||||
You can use [@unittest.skipIf](https://docs.python.org/3/library/unittest.html#unittest.skipIf) with an environment
|
|
||||||
variable to keep all the benefits of the test framework while not running the marked tests by default.
|
|
||||||
|
|
||||||
## Running Tests
|
## Running Tests
|
||||||
|
|
||||||
#### Using Pycharm
|
#### Using Pycharm
|
||||||
@@ -132,11 +100,3 @@ next to the run and debug buttons.
|
|||||||
#### Running Tests without Pycharm
|
#### Running Tests without Pycharm
|
||||||
|
|
||||||
Run `pip install pytest pytest-subtests`, then use your IDE to run tests or run `pytest` from the source folder.
|
Run `pip install pytest pytest-subtests`, then use your IDE to run tests or run `pytest` from the source folder.
|
||||||
|
|
||||||
#### Running Tests Multithreaded
|
|
||||||
|
|
||||||
pytest can run multiple test runners in parallel with the pytest-xdist extension.
|
|
||||||
|
|
||||||
Install with `pip install pytest-xdist`.
|
|
||||||
|
|
||||||
Run with `pytest -n12` to spawn 12 process that each run 1/12th of the tests.
|
|
||||||
|
|||||||
@@ -358,34 +358,6 @@ def randomize_entrances(
|
|||||||
if on_connect:
|
if on_connect:
|
||||||
on_connect(er_state, placed_exits)
|
on_connect(er_state, placed_exits)
|
||||||
|
|
||||||
def needs_speculative_sweep(dead_end: bool, require_new_exits: bool, placeable_exits: list[Entrance]) -> bool:
|
|
||||||
# speculative sweep is expensive. We currently only do it as a last resort, if we might cap off the graph
|
|
||||||
# entirely
|
|
||||||
if len(placeable_exits) > 1:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# in certain stages of randomization we either expect or don't care if the search space shrinks.
|
|
||||||
# we should never speculative sweep here.
|
|
||||||
if dead_end or not require_new_exits or not perform_validity_check:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# edge case - if all dead ends have pre-placed progression or indirect connections, they are pulled forward
|
|
||||||
# into the non dead end stage. In this case, and only this case, it's possible that the last connection may
|
|
||||||
# actually be placeable in stage 1. We need to skip speculative sweep in this case because we expect the graph
|
|
||||||
# to get capped off.
|
|
||||||
|
|
||||||
# check to see if we are proposing the last placement
|
|
||||||
if not coupled:
|
|
||||||
# in uncoupled, this check is easy as there will only be one target.
|
|
||||||
is_last_placement = len(entrance_lookup) == 1
|
|
||||||
else:
|
|
||||||
# a bit harder, there may be 1 or 2 targets depending on if the exit to place is one way or two way.
|
|
||||||
# if it is two way, we can safely assume that one of the targets is the logical pair of the exit.
|
|
||||||
desired_target_count = 2 if placeable_exits[0].randomization_type == EntranceType.TWO_WAY else 1
|
|
||||||
is_last_placement = len(entrance_lookup) == desired_target_count
|
|
||||||
# if it's not the last placement, we need a sweep
|
|
||||||
return not is_last_placement
|
|
||||||
|
|
||||||
def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
|
def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
|
||||||
nonlocal perform_validity_check
|
nonlocal perform_validity_check
|
||||||
placeable_exits = er_state.find_placeable_exits(perform_validity_check, exits)
|
placeable_exits = er_state.find_placeable_exits(perform_validity_check, exits)
|
||||||
@@ -399,9 +371,11 @@ def randomize_entrances(
|
|||||||
# very last exit and check whatever exits we open up are functionally accessible.
|
# very last exit and check whatever exits we open up are functionally accessible.
|
||||||
# this requirement can be ignored on a beaten minimal, islands are no issue there.
|
# this requirement can be ignored on a beaten minimal, islands are no issue there.
|
||||||
exit_requirement_satisfied = (not perform_validity_check or not require_new_exits
|
exit_requirement_satisfied = (not perform_validity_check or not require_new_exits
|
||||||
or target_entrance.connected_region not in er_state.placed_regions)
|
or target_entrance.connected_region not in er_state.placed_regions)
|
||||||
|
needs_speculative_sweep = (not dead_end and require_new_exits and perform_validity_check
|
||||||
|
and len(placeable_exits) == 1)
|
||||||
if exit_requirement_satisfied and source_exit.can_connect_to(target_entrance, dead_end, er_state):
|
if exit_requirement_satisfied and source_exit.can_connect_to(target_entrance, dead_end, er_state):
|
||||||
if (needs_speculative_sweep(dead_end, require_new_exits, placeable_exits)
|
if (needs_speculative_sweep
|
||||||
and not er_state.test_speculative_connection(source_exit, target_entrance, exits_set)):
|
and not er_state.test_speculative_connection(source_exit, target_entrance, exits_set)):
|
||||||
continue
|
continue
|
||||||
do_placement(source_exit, target_entrance)
|
do_placement(source_exit, target_entrance)
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ class TestRandomizeEntrances(unittest.TestCase):
|
|||||||
self.assertEqual(80, len(result.pairings))
|
self.assertEqual(80, len(result.pairings))
|
||||||
self.assertEqual(80, len(result.placements))
|
self.assertEqual(80, len(result.placements))
|
||||||
|
|
||||||
def test_coupled(self):
|
def test_coupling(self):
|
||||||
"""tests that in coupled mode, all 2 way transitions have an inverse"""
|
"""tests that in coupled mode, all 2 way transitions have an inverse"""
|
||||||
multiworld = generate_test_multiworld()
|
multiworld = generate_test_multiworld()
|
||||||
generate_disconnected_region_grid(multiworld, 5)
|
generate_disconnected_region_grid(multiworld, 5)
|
||||||
@@ -236,36 +236,6 @@ class TestRandomizeEntrances(unittest.TestCase):
|
|||||||
# if we didn't visit every placement the verification on_connect doesn't really mean much
|
# if we didn't visit every placement the verification on_connect doesn't really mean much
|
||||||
self.assertEqual(len(result.placements), seen_placement_count)
|
self.assertEqual(len(result.placements), seen_placement_count)
|
||||||
|
|
||||||
def test_uncoupled_succeeds_stage1_indirect_condition(self):
|
|
||||||
multiworld = generate_test_multiworld()
|
|
||||||
menu = multiworld.get_region("Menu", 1)
|
|
||||||
generate_entrance_pair(menu, "_right", ERTestGroups.RIGHT)
|
|
||||||
end = Region("End", 1, multiworld)
|
|
||||||
multiworld.regions.append(end)
|
|
||||||
generate_entrance_pair(end, "_left", ERTestGroups.LEFT)
|
|
||||||
multiworld.register_indirect_condition(end, None)
|
|
||||||
|
|
||||||
result = randomize_entrances(multiworld.worlds[1], False, directionally_matched_group_lookup)
|
|
||||||
self.assertSetEqual({
|
|
||||||
("Menu_right", "End_left"),
|
|
||||||
("End_left", "Menu_right")
|
|
||||||
}, set(result.pairings))
|
|
||||||
|
|
||||||
def test_coupled_succeeds_stage1_indirect_condition(self):
|
|
||||||
multiworld = generate_test_multiworld()
|
|
||||||
menu = multiworld.get_region("Menu", 1)
|
|
||||||
generate_entrance_pair(menu, "_right", ERTestGroups.RIGHT)
|
|
||||||
end = Region("End", 1, multiworld)
|
|
||||||
multiworld.regions.append(end)
|
|
||||||
generate_entrance_pair(end, "_left", ERTestGroups.LEFT)
|
|
||||||
multiworld.register_indirect_condition(end, None)
|
|
||||||
|
|
||||||
result = randomize_entrances(multiworld.worlds[1], True, directionally_matched_group_lookup)
|
|
||||||
self.assertSetEqual({
|
|
||||||
("Menu_right", "End_left"),
|
|
||||||
("End_left", "Menu_right")
|
|
||||||
}, set(result.pairings))
|
|
||||||
|
|
||||||
def test_uncoupled(self):
|
def test_uncoupled(self):
|
||||||
"""tests that in uncoupled mode, no transitions have an (intentional) inverse"""
|
"""tests that in uncoupled mode, no transitions have an (intentional) inverse"""
|
||||||
multiworld = generate_test_multiworld()
|
multiworld = generate_test_multiworld()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from typing import ClassVar, List, Tuple
|
from typing import List, Tuple
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from BaseClasses import CollectionState, Location, MultiWorld
|
from BaseClasses import CollectionState, Location, MultiWorld
|
||||||
@@ -7,7 +7,6 @@ from Fill import distribute_items_restrictive
|
|||||||
from Options import Accessibility
|
from Options import Accessibility
|
||||||
from worlds.AutoWorld import AutoWorldRegister, call_all, call_single
|
from worlds.AutoWorld import AutoWorldRegister, call_all, call_single
|
||||||
from ..general import gen_steps, setup_multiworld
|
from ..general import gen_steps, setup_multiworld
|
||||||
from ..param import classvar_matrix
|
|
||||||
|
|
||||||
|
|
||||||
class MultiworldTestBase(TestCase):
|
class MultiworldTestBase(TestCase):
|
||||||
@@ -64,18 +63,15 @@ class TestAllGamesMultiworld(MultiworldTestBase):
|
|||||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||||
|
|
||||||
|
|
||||||
@classvar_matrix(game=AutoWorldRegister.world_types.keys())
|
|
||||||
class TestTwoPlayerMulti(MultiworldTestBase):
|
class TestTwoPlayerMulti(MultiworldTestBase):
|
||||||
game: ClassVar[str]
|
|
||||||
|
|
||||||
def test_two_player_single_game_fills(self) -> None:
|
def test_two_player_single_game_fills(self) -> None:
|
||||||
"""Tests that a multiworld of two players for each registered game world can generate."""
|
"""Tests that a multiworld of two players for each registered game world can generate."""
|
||||||
world_type = AutoWorldRegister.world_types[self.game]
|
for world_type in AutoWorldRegister.world_types.values():
|
||||||
self.multiworld = setup_multiworld([world_type, world_type], ())
|
self.multiworld = setup_multiworld([world_type, world_type], ())
|
||||||
for world in self.multiworld.worlds.values():
|
for world in self.multiworld.worlds.values():
|
||||||
world.options.accessibility.value = Accessibility.option_full
|
world.options.accessibility.value = Accessibility.option_full
|
||||||
self.assertSteps(gen_steps)
|
self.assertSteps(gen_steps)
|
||||||
with self.subTest("filling multiworld", games=world_type.game, seed=self.multiworld.seed):
|
with self.subTest("filling multiworld", games=world_type.game, seed=self.multiworld.seed):
|
||||||
distribute_items_restrictive(self.multiworld)
|
distribute_items_restrictive(self.multiworld)
|
||||||
call_all(self.multiworld, "post_fill")
|
call_all(self.multiworld, "post_fill")
|
||||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import itertools
|
|
||||||
import sys
|
|
||||||
from typing import Any, Callable, Iterable
|
|
||||||
|
|
||||||
|
|
||||||
def classvar_matrix(**kwargs: Iterable[Any]) -> Callable[[type], None]:
|
|
||||||
"""
|
|
||||||
Create a new class for each variation of input, allowing to generate a TestCase matrix / parametrization that
|
|
||||||
supports multi-threading and has better reporting for ``unittest --durations=...`` and ``pytest --durations=...``
|
|
||||||
than subtests.
|
|
||||||
|
|
||||||
The kwargs will be set as ClassVars in the newly created classes. Use as ::
|
|
||||||
|
|
||||||
@classvar_matrix(var_name=[value1, value2])
|
|
||||||
class MyTestCase(unittest.TestCase):
|
|
||||||
var_name: typing.ClassVar[...]
|
|
||||||
|
|
||||||
:param kwargs: A dict of ClassVars to set, where key is the variable name and value is a list of all values.
|
|
||||||
:return: A decorator to be applied to a class.
|
|
||||||
"""
|
|
||||||
keys: tuple[str]
|
|
||||||
values: Iterable[Iterable[Any]]
|
|
||||||
keys, values = zip(*kwargs.items())
|
|
||||||
values = map(lambda v: sorted(v) if isinstance(v, (set, frozenset)) else v, values)
|
|
||||||
permutations_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]
|
|
||||||
|
|
||||||
def decorator(cls: type) -> None:
|
|
||||||
mod = sys.modules[cls.__module__]
|
|
||||||
|
|
||||||
for permutation in permutations_dicts:
|
|
||||||
|
|
||||||
class Unrolled(cls): # type: ignore
|
|
||||||
pass
|
|
||||||
|
|
||||||
for k, v in permutation.items():
|
|
||||||
setattr(Unrolled, k, v)
|
|
||||||
params = ", ".join([f"{k}={repr(v)}" for k, v in permutation.items()])
|
|
||||||
params = f"{{{params}}}"
|
|
||||||
|
|
||||||
Unrolled.__module__ = cls.__module__
|
|
||||||
Unrolled.__qualname__ = f"{cls.__qualname__}{params}"
|
|
||||||
setattr(mod, f"{cls.__name__}{params}", Unrolled)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
@@ -515,15 +515,10 @@ def _populate_sprite_table():
|
|||||||
logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.")
|
logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.")
|
||||||
|
|
||||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||||
sprite_paths = [user_path('data', 'sprites', 'alttpr'), user_path('data', 'sprites', 'custom')]
|
for dir in [user_path('data', 'sprites', 'alttpr'), user_path('data', 'sprites', 'custom')]:
|
||||||
for dir in [dir for dir in sprite_paths if os.path.isdir(dir)]:
|
|
||||||
for file in os.listdir(dir):
|
for file in os.listdir(dir):
|
||||||
pool.submit(load_sprite_from_file, os.path.join(dir, file))
|
pool.submit(load_sprite_from_file, os.path.join(dir, file))
|
||||||
|
|
||||||
if "link" not in _sprite_table:
|
|
||||||
logging.info("Link sprite was not loaded. Loading link from base rom")
|
|
||||||
load_sprite_from_file(get_base_rom_path())
|
|
||||||
|
|
||||||
|
|
||||||
class Sprite():
|
class Sprite():
|
||||||
sprite_size = 28672
|
sprite_size = 28672
|
||||||
@@ -559,11 +554,6 @@ class Sprite():
|
|||||||
self.sprite = filedata[0x80000:0x87000]
|
self.sprite = filedata[0x80000:0x87000]
|
||||||
self.palette = filedata[0xDD308:0xDD380]
|
self.palette = filedata[0xDD308:0xDD380]
|
||||||
self.glove_palette = filedata[0xDEDF5:0xDEDF9]
|
self.glove_palette = filedata[0xDEDF5:0xDEDF9]
|
||||||
h = hashlib.md5()
|
|
||||||
h.update(filedata)
|
|
||||||
if h.hexdigest() == LTTPJPN10HASH:
|
|
||||||
self.name = "Link"
|
|
||||||
self.author_name = "Nintendo"
|
|
||||||
elif filedata.startswith(b'ZSPR'):
|
elif filedata.startswith(b'ZSPR'):
|
||||||
self.from_zspr(filedata, filename)
|
self.from_zspr(filedata, filename)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,31 +1,6 @@
|
|||||||
# Celeste 64 - Changelog
|
# Celeste 64 - Changelog
|
||||||
|
|
||||||
|
|
||||||
## v1.3
|
|
||||||
|
|
||||||
### Features:
|
|
||||||
|
|
||||||
- New optional Location Checks
|
|
||||||
- Checkpointsanity
|
|
||||||
- Hair Color
|
|
||||||
- Allows for setting of Maddy's hair color in each of No Dash, One Dash, Two Dash, and Feather states
|
|
||||||
- Other Player Ghosts
|
|
||||||
- A game config option allows you to see ghosts of other Celeste 64 players in the multiworld
|
|
||||||
|
|
||||||
### Quality of Life:
|
|
||||||
|
|
||||||
- Checkpoint Warping
|
|
||||||
- Received Checkpoint items allow for warping to their respective checkpoint
|
|
||||||
- These items are on their respective checkpoint location if Checkpointsanity is disabled
|
|
||||||
- Logic accounts for being able to warp to otherwise inaccessible areas
|
|
||||||
- Checkpoints are a possible option for a starting item on Standard Logic + Move Shuffle + Checkpointsanity
|
|
||||||
- New Options toggle to enable/disable background input
|
|
||||||
|
|
||||||
### Bug Fixes:
|
|
||||||
|
|
||||||
- Traffic Blocks now correctly appear disabled within Cassettes
|
|
||||||
|
|
||||||
|
|
||||||
## v1.2
|
## v1.2
|
||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
|
|||||||
@@ -39,22 +39,6 @@ move_item_data_table: Dict[str, Celeste64ItemData] = {
|
|||||||
ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, ItemClassification.progression),
|
ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, ItemClassification.progression),
|
||||||
}
|
}
|
||||||
|
|
||||||
checkpoint_item_data_table: Dict[str, Celeste64ItemData] = {
|
item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table, **unlockable_item_data_table, **move_item_data_table}
|
||||||
ItemName.checkpoint_1: Celeste64ItemData(celeste_64_base_id + 0x20, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_2: Celeste64ItemData(celeste_64_base_id + 0x21, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_3: Celeste64ItemData(celeste_64_base_id + 0x22, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_4: Celeste64ItemData(celeste_64_base_id + 0x23, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_5: Celeste64ItemData(celeste_64_base_id + 0x24, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_6: Celeste64ItemData(celeste_64_base_id + 0x25, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_7: Celeste64ItemData(celeste_64_base_id + 0x26, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_8: Celeste64ItemData(celeste_64_base_id + 0x27, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_9: Celeste64ItemData(celeste_64_base_id + 0x28, ItemClassification.progression),
|
|
||||||
ItemName.checkpoint_10: Celeste64ItemData(celeste_64_base_id + 0x29, ItemClassification.progression),
|
|
||||||
}
|
|
||||||
|
|
||||||
item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table,
|
|
||||||
**unlockable_item_data_table,
|
|
||||||
**move_item_data_table,
|
|
||||||
**checkpoint_item_data_table}
|
|
||||||
|
|
||||||
item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None}
|
item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Dict, NamedTuple, Optional
|
from typing import Dict, NamedTuple, Optional
|
||||||
|
|
||||||
from BaseClasses import Location
|
from BaseClasses import Location
|
||||||
from .Names import LocationName, RegionName
|
from .Names import LocationName
|
||||||
|
|
||||||
|
|
||||||
celeste_64_base_id: int = 0xCA0000
|
celeste_64_base_id: int = 0xCA0000
|
||||||
@@ -17,80 +17,66 @@ class Celeste64LocationData(NamedTuple):
|
|||||||
|
|
||||||
|
|
||||||
strawberry_location_data_table: Dict[str, Celeste64LocationData] = {
|
strawberry_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||||
LocationName.strawberry_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x00),
|
LocationName.strawberry_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x00),
|
||||||
LocationName.strawberry_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x01),
|
LocationName.strawberry_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x01),
|
||||||
LocationName.strawberry_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x02),
|
LocationName.strawberry_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x02),
|
||||||
LocationName.strawberry_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x03),
|
LocationName.strawberry_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x03),
|
||||||
LocationName.strawberry_5: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x04),
|
LocationName.strawberry_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x04),
|
||||||
LocationName.strawberry_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x05),
|
LocationName.strawberry_6: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x05),
|
||||||
LocationName.strawberry_7: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x06),
|
LocationName.strawberry_7: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x06),
|
||||||
LocationName.strawberry_8: Celeste64LocationData(RegionName.nw_girders_island, celeste_64_base_id + 0x07),
|
LocationName.strawberry_8: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x07),
|
||||||
LocationName.strawberry_9: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x08),
|
LocationName.strawberry_9: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x08),
|
||||||
LocationName.strawberry_10: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x09),
|
LocationName.strawberry_10: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x09),
|
||||||
LocationName.strawberry_11: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x0A),
|
LocationName.strawberry_11: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0A),
|
||||||
LocationName.strawberry_12: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x0B),
|
LocationName.strawberry_12: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0B),
|
||||||
LocationName.strawberry_13: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x0C),
|
LocationName.strawberry_13: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0C),
|
||||||
LocationName.strawberry_14: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0D),
|
LocationName.strawberry_14: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0D),
|
||||||
LocationName.strawberry_15: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0E),
|
LocationName.strawberry_15: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0E),
|
||||||
LocationName.strawberry_16: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0F),
|
LocationName.strawberry_16: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0F),
|
||||||
LocationName.strawberry_17: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x10),
|
LocationName.strawberry_17: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x10),
|
||||||
LocationName.strawberry_18: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x11),
|
LocationName.strawberry_18: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x11),
|
||||||
LocationName.strawberry_19: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x12),
|
LocationName.strawberry_19: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x12),
|
||||||
LocationName.strawberry_20: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x13),
|
LocationName.strawberry_20: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x13),
|
||||||
LocationName.strawberry_21: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x14),
|
LocationName.strawberry_21: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x14),
|
||||||
LocationName.strawberry_22: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x15),
|
LocationName.strawberry_22: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x15),
|
||||||
LocationName.strawberry_23: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x16),
|
LocationName.strawberry_23: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x16),
|
||||||
LocationName.strawberry_24: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x17),
|
LocationName.strawberry_24: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x17),
|
||||||
LocationName.strawberry_25: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x18),
|
LocationName.strawberry_25: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x18),
|
||||||
LocationName.strawberry_26: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x19),
|
LocationName.strawberry_26: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x19),
|
||||||
LocationName.strawberry_27: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1A),
|
LocationName.strawberry_27: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1A),
|
||||||
LocationName.strawberry_28: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1B),
|
LocationName.strawberry_28: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1B),
|
||||||
LocationName.strawberry_29: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x1C),
|
LocationName.strawberry_29: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1C),
|
||||||
LocationName.strawberry_30: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x1D),
|
LocationName.strawberry_30: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1D),
|
||||||
}
|
}
|
||||||
|
|
||||||
friend_location_data_table: Dict[str, Celeste64LocationData] = {
|
friend_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||||
LocationName.granny_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x00),
|
LocationName.granny_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x00),
|
||||||
LocationName.granny_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x01),
|
LocationName.granny_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x01),
|
||||||
LocationName.granny_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x02),
|
LocationName.granny_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x02),
|
||||||
LocationName.theo_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x03),
|
LocationName.theo_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x03),
|
||||||
LocationName.theo_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x04),
|
LocationName.theo_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x04),
|
||||||
LocationName.theo_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x05),
|
LocationName.theo_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x05),
|
||||||
LocationName.badeline_1: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x06),
|
LocationName.badeline_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x06),
|
||||||
LocationName.badeline_2: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x07),
|
LocationName.badeline_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x07),
|
||||||
LocationName.badeline_3: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x08),
|
LocationName.badeline_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x08),
|
||||||
}
|
}
|
||||||
|
|
||||||
sign_location_data_table: Dict[str, Celeste64LocationData] = {
|
sign_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||||
LocationName.sign_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x00),
|
LocationName.sign_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x00),
|
||||||
LocationName.sign_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x01),
|
LocationName.sign_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x01),
|
||||||
LocationName.sign_3: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x200 + 0x02),
|
LocationName.sign_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x02),
|
||||||
LocationName.sign_4: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x200 + 0x03),
|
LocationName.sign_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x03),
|
||||||
LocationName.sign_5: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x200 + 0x04),
|
LocationName.sign_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x04),
|
||||||
}
|
}
|
||||||
|
|
||||||
car_location_data_table: Dict[str, Celeste64LocationData] = {
|
car_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||||
LocationName.car_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x300 + 0x00),
|
LocationName.car_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x00),
|
||||||
LocationName.car_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x300 + 0x01),
|
LocationName.car_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x01),
|
||||||
}
|
|
||||||
|
|
||||||
checkpoint_location_data_table: Dict[str, Celeste64LocationData] = {
|
|
||||||
LocationName.checkpoint_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x400 + 0x00),
|
|
||||||
LocationName.checkpoint_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x01),
|
|
||||||
LocationName.checkpoint_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x02),
|
|
||||||
LocationName.checkpoint_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x03),
|
|
||||||
LocationName.checkpoint_5: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x400 + 0x04),
|
|
||||||
LocationName.checkpoint_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x400 + 0x05),
|
|
||||||
LocationName.checkpoint_7: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x400 + 0x06),
|
|
||||||
LocationName.checkpoint_8: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x400 + 0x07),
|
|
||||||
LocationName.checkpoint_9: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x400 + 0x08),
|
|
||||||
LocationName.checkpoint_10: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x400 + 0x09),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table,
|
location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table,
|
||||||
**friend_location_data_table,
|
**friend_location_data_table,
|
||||||
**sign_location_data_table,
|
**sign_location_data_table,
|
||||||
**car_location_data_table,
|
**car_location_data_table}
|
||||||
**checkpoint_location_data_table}
|
|
||||||
|
|
||||||
location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None}
|
location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None}
|
||||||
|
|||||||
@@ -15,18 +15,3 @@ ground_dash = "Ground Dash"
|
|||||||
air_dash = "Air Dash"
|
air_dash = "Air Dash"
|
||||||
skid_jump = "Skid Jump"
|
skid_jump = "Skid Jump"
|
||||||
climb = "Climb"
|
climb = "Climb"
|
||||||
|
|
||||||
# Checkpoint Items
|
|
||||||
checkpoint_1 = "Intro Checkpoint"
|
|
||||||
checkpoint_2 = "Granny Checkpoint"
|
|
||||||
checkpoint_3 = "South-East Tower Checkpoint"
|
|
||||||
checkpoint_4 = "Climb Sign Checkpoint"
|
|
||||||
checkpoint_5 = "Freeway Checkpoint"
|
|
||||||
checkpoint_6 = "Freeway Feather Checkpoint"
|
|
||||||
checkpoint_7 = "Feather Maze Checkpoint"
|
|
||||||
checkpoint_8 = "Double Dash House Checkpoint"
|
|
||||||
checkpoint_9 = "Badeline Tower Checkpoint"
|
|
||||||
checkpoint_10 = "Badeline Island Checkpoint"
|
|
||||||
|
|
||||||
# Item used for logic definitions that are not possible with the given options
|
|
||||||
cannot_access = "CANNOT ACCESS"
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ strawberry_8 = "Traffic Block Strawberry"
|
|||||||
strawberry_9 = "South-West Dash Refills Strawberry"
|
strawberry_9 = "South-West Dash Refills Strawberry"
|
||||||
strawberry_10 = "South-East Tower Side Strawberry"
|
strawberry_10 = "South-East Tower Side Strawberry"
|
||||||
strawberry_11 = "Girders Strawberry"
|
strawberry_11 = "Girders Strawberry"
|
||||||
strawberry_12 = "Badeline Tower Bottom Strawberry"
|
strawberry_12 = "North-East Tower Bottom Strawberry"
|
||||||
strawberry_13 = "Breakable Blocks Strawberry"
|
strawberry_13 = "Breakable Blocks Strawberry"
|
||||||
strawberry_14 = "Feather Maze Strawberry"
|
strawberry_14 = "Feather Maze Strawberry"
|
||||||
strawberry_15 = "Feather Chain Strawberry"
|
strawberry_15 = "Feather Chain Strawberry"
|
||||||
@@ -18,7 +18,7 @@ strawberry_16 = "Feather Hidden Strawberry"
|
|||||||
strawberry_17 = "Double Dash Puzzle Strawberry"
|
strawberry_17 = "Double Dash Puzzle Strawberry"
|
||||||
strawberry_18 = "Double Dash Spike Climb Strawberry"
|
strawberry_18 = "Double Dash Spike Climb Strawberry"
|
||||||
strawberry_19 = "Double Dash Spring Strawberry"
|
strawberry_19 = "Double Dash Spring Strawberry"
|
||||||
strawberry_20 = "Badeline Tower Breakable Bottom Strawberry"
|
strawberry_20 = "North-East Tower Breakable Bottom Strawberry"
|
||||||
strawberry_21 = "Theo Tower Lower Cassette Strawberry"
|
strawberry_21 = "Theo Tower Lower Cassette Strawberry"
|
||||||
strawberry_22 = "Theo Tower Upper Cassette Strawberry"
|
strawberry_22 = "Theo Tower Upper Cassette Strawberry"
|
||||||
strawberry_23 = "South End of Bridge Cassette Strawberry"
|
strawberry_23 = "South End of Bridge Cassette Strawberry"
|
||||||
@@ -27,8 +27,8 @@ strawberry_25 = "Cassette Hidden in the House Strawberry"
|
|||||||
strawberry_26 = "North End of Bridge Cassette Strawberry"
|
strawberry_26 = "North End of Bridge Cassette Strawberry"
|
||||||
strawberry_27 = "Distant Feather Cassette Strawberry"
|
strawberry_27 = "Distant Feather Cassette Strawberry"
|
||||||
strawberry_28 = "Feather Arches Cassette Strawberry"
|
strawberry_28 = "Feather Arches Cassette Strawberry"
|
||||||
strawberry_29 = "Badeline Tower Cassette Strawberry"
|
strawberry_29 = "North-East Tower Cassette Strawberry"
|
||||||
strawberry_30 = "Badeline Island Cassette Strawberry"
|
strawberry_30 = "Badeline Cassette Strawberry"
|
||||||
|
|
||||||
# Friend Locations
|
# Friend Locations
|
||||||
granny_1 = "Granny Conversation 1"
|
granny_1 = "Granny Conversation 1"
|
||||||
@@ -51,15 +51,3 @@ sign_5 = "Credits Sign"
|
|||||||
# Car Locations
|
# Car Locations
|
||||||
car_1 = "Intro Car"
|
car_1 = "Intro Car"
|
||||||
car_2 = "Secret Car"
|
car_2 = "Secret Car"
|
||||||
|
|
||||||
# Checkpoint Locations
|
|
||||||
checkpoint_1 = "Intro Checkpoint"
|
|
||||||
checkpoint_2 = "Granny Checkpoint"
|
|
||||||
checkpoint_3 = "South-East Tower Checkpoint"
|
|
||||||
checkpoint_4 = "Climb Sign Checkpoint"
|
|
||||||
checkpoint_5 = "Freeway Checkpoint"
|
|
||||||
checkpoint_6 = "Freeway Feather Checkpoint"
|
|
||||||
checkpoint_7 = "Feather Maze Checkpoint"
|
|
||||||
checkpoint_8 = "Double Dash House Checkpoint"
|
|
||||||
checkpoint_9 = "Badeline Tower Checkpoint"
|
|
||||||
checkpoint_10 = "Badeline Island Checkpoint"
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
# Level Base Regions
|
|
||||||
forsaken_city = "Forsaken City"
|
|
||||||
|
|
||||||
# Forsaken City Regions
|
|
||||||
intro_islands = "Intro Islands"
|
|
||||||
granny_island = "Granny Island"
|
|
||||||
highway_island = "Freeway Island"
|
|
||||||
nw_girders_island = "North-West Girders Island"
|
|
||||||
ne_feathers_island = "North-East Feathers Island"
|
|
||||||
se_house_island = "South-East House Island"
|
|
||||||
badeline_tower_lower = "Badeline Tower Lower"
|
|
||||||
badeline_tower_upper = "Badeline Tower Upper"
|
|
||||||
badeline_island = "Badeline Island"
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import random
|
|
||||||
|
|
||||||
from Options import Choice, TextChoice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions, OptionError
|
from Options import Choice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions
|
||||||
from worlds.AutoWorld import World
|
|
||||||
|
|
||||||
|
|
||||||
class DeathLinkAmnesty(Range):
|
class DeathLinkAmnesty(Range):
|
||||||
@@ -20,7 +18,7 @@ class TotalStrawberries(Range):
|
|||||||
"""
|
"""
|
||||||
display_name = "Total Strawberries"
|
display_name = "Total Strawberries"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 55
|
range_end = 46
|
||||||
default = 20
|
default = 20
|
||||||
|
|
||||||
class StrawberriesRequiredPercentage(Range):
|
class StrawberriesRequiredPercentage(Range):
|
||||||
@@ -75,93 +73,6 @@ class Carsanity(Toggle):
|
|||||||
"""
|
"""
|
||||||
display_name = "Carsanity"
|
display_name = "Carsanity"
|
||||||
|
|
||||||
class Checkpointsanity(Toggle):
|
|
||||||
"""
|
|
||||||
Whether activating Checkpoints grants location checks
|
|
||||||
|
|
||||||
Activating this will also shuffle items into the pool which allow usage and warping to each Checkpoint
|
|
||||||
"""
|
|
||||||
display_name = "Checkpointsanity"
|
|
||||||
|
|
||||||
|
|
||||||
class ColorChoice(TextChoice):
|
|
||||||
option_strawberry = 0xDB2C00
|
|
||||||
option_empty = 0x6EC0FF
|
|
||||||
option_double = 0xFA91FF
|
|
||||||
option_golden = 0xF2D450
|
|
||||||
option_baddy = 0x9B3FB5
|
|
||||||
option_fire_red = 0xFF0000
|
|
||||||
option_maroon = 0x800000
|
|
||||||
option_salmon = 0xFF3A65
|
|
||||||
option_orange = 0xD86E0A
|
|
||||||
option_lime_green = 0x8DF920
|
|
||||||
option_bright_green = 0x0DAF05
|
|
||||||
option_forest_green = 0x132818
|
|
||||||
option_royal_blue = 0x0036BF
|
|
||||||
option_brown = 0xB78726
|
|
||||||
option_black = 0x000000
|
|
||||||
option_white = 0xFFFFFF
|
|
||||||
option_grey = 0x808080
|
|
||||||
option_any_color = -1
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_text(cls, text: str) -> Choice:
|
|
||||||
text = text.lower()
|
|
||||||
if text == "random":
|
|
||||||
choice_list = list(cls.name_lookup)
|
|
||||||
choice_list.remove(cls.option_any_color)
|
|
||||||
return cls(random.choice(choice_list))
|
|
||||||
return super().from_text(text)
|
|
||||||
|
|
||||||
|
|
||||||
class MadelineOneDashHairColor(ColorChoice):
|
|
||||||
"""
|
|
||||||
What color Madeline's hair is when she has one dash
|
|
||||||
|
|
||||||
The `any_color` option will choose a fully random color
|
|
||||||
|
|
||||||
A custom color entry may be supplied as a 6-character RGB hex color code
|
|
||||||
e.g. F542C8
|
|
||||||
"""
|
|
||||||
display_name = "Madeline One Dash Hair Color"
|
|
||||||
default = ColorChoice.option_strawberry
|
|
||||||
|
|
||||||
class MadelineTwoDashHairColor(ColorChoice):
|
|
||||||
"""
|
|
||||||
What color Madeline's hair is when she has two dashes
|
|
||||||
|
|
||||||
The `any_color` option will choose a fully random color
|
|
||||||
|
|
||||||
A custom color entry may be supplied as a 6-character RGB hex color code
|
|
||||||
e.g. F542C8
|
|
||||||
"""
|
|
||||||
display_name = "Madeline Two Dash Hair Color"
|
|
||||||
default = ColorChoice.option_double
|
|
||||||
|
|
||||||
class MadelineNoDashHairColor(ColorChoice):
|
|
||||||
"""
|
|
||||||
What color Madeline's hair is when she has no dashes
|
|
||||||
|
|
||||||
The `any_color` option will choose a fully random color
|
|
||||||
|
|
||||||
A custom color entry may be supplied as a 6-character RGB hex color code
|
|
||||||
e.g. F542C8
|
|
||||||
"""
|
|
||||||
display_name = "Madeline No Dash Hair Color"
|
|
||||||
default = ColorChoice.option_empty
|
|
||||||
|
|
||||||
class MadelineFeatherHairColor(ColorChoice):
|
|
||||||
"""
|
|
||||||
What color Madeline's hair is when she has a feather
|
|
||||||
|
|
||||||
The `any_color` option will choose a fully random color
|
|
||||||
|
|
||||||
A custom color entry may be supplied as a 6-character RGB hex color code
|
|
||||||
e.g. F542C8
|
|
||||||
"""
|
|
||||||
display_name = "Madeline Feather Hair Color"
|
|
||||||
default = ColorChoice.option_golden
|
|
||||||
|
|
||||||
|
|
||||||
class BadelineChaserSource(Choice):
|
class BadelineChaserSource(Choice):
|
||||||
"""
|
"""
|
||||||
@@ -208,13 +119,6 @@ celeste_64_option_groups = [
|
|||||||
Friendsanity,
|
Friendsanity,
|
||||||
Signsanity,
|
Signsanity,
|
||||||
Carsanity,
|
Carsanity,
|
||||||
Checkpointsanity,
|
|
||||||
]),
|
|
||||||
OptionGroup("Aesthetic Options", [
|
|
||||||
MadelineOneDashHairColor,
|
|
||||||
MadelineTwoDashHairColor,
|
|
||||||
MadelineNoDashHairColor,
|
|
||||||
MadelineFeatherHairColor,
|
|
||||||
]),
|
]),
|
||||||
OptionGroup("Badeline Chasers", [
|
OptionGroup("Badeline Chasers", [
|
||||||
BadelineChaserSource,
|
BadelineChaserSource,
|
||||||
@@ -238,68 +142,7 @@ class Celeste64Options(PerGameCommonOptions):
|
|||||||
friendsanity: Friendsanity
|
friendsanity: Friendsanity
|
||||||
signsanity: Signsanity
|
signsanity: Signsanity
|
||||||
carsanity: Carsanity
|
carsanity: Carsanity
|
||||||
checkpointsanity: Checkpointsanity
|
|
||||||
|
|
||||||
madeline_one_dash_hair_color: MadelineOneDashHairColor
|
|
||||||
madeline_two_dash_hair_color: MadelineTwoDashHairColor
|
|
||||||
madeline_no_dash_hair_color: MadelineNoDashHairColor
|
|
||||||
madeline_feather_hair_color: MadelineFeatherHairColor
|
|
||||||
|
|
||||||
badeline_chaser_source: BadelineChaserSource
|
badeline_chaser_source: BadelineChaserSource
|
||||||
badeline_chaser_frequency: BadelineChaserFrequency
|
badeline_chaser_frequency: BadelineChaserFrequency
|
||||||
badeline_chaser_speed: BadelineChaserSpeed
|
badeline_chaser_speed: BadelineChaserSpeed
|
||||||
|
|
||||||
|
|
||||||
def resolve_options(world: World):
|
|
||||||
# One Dash Hair
|
|
||||||
if isinstance(world.options.madeline_one_dash_hair_color.value, str):
|
|
||||||
try:
|
|
||||||
world.madeline_one_dash_hair_color = int(world.options.madeline_one_dash_hair_color.value.strip("#")[:6], 16)
|
|
||||||
except ValueError:
|
|
||||||
raise OptionError(f"Invalid input for option `madeline_one_dash_hair_color`:"
|
|
||||||
f"{world.options.madeline_one_dash_hair_color.value} for "
|
|
||||||
f"{world.player_name}")
|
|
||||||
elif world.options.madeline_one_dash_hair_color.value == ColorChoice.option_any_color:
|
|
||||||
world.madeline_one_dash_hair_color = world.random.randint(0, 0xFFFFFF)
|
|
||||||
else:
|
|
||||||
world.madeline_one_dash_hair_color = world.options.madeline_one_dash_hair_color.value
|
|
||||||
|
|
||||||
# Two Dash Hair
|
|
||||||
if isinstance(world.options.madeline_two_dash_hair_color.value, str):
|
|
||||||
try:
|
|
||||||
world.madeline_two_dash_hair_color = int(world.options.madeline_two_dash_hair_color.value.strip("#")[:6], 16)
|
|
||||||
except ValueError:
|
|
||||||
raise OptionError(f"Invalid input for option `madeline_two_dash_hair_color`:"
|
|
||||||
f"{world.options.madeline_two_dash_hair_color.value} for "
|
|
||||||
f"{world.player_name}")
|
|
||||||
elif world.options.madeline_two_dash_hair_color.value == ColorChoice.option_any_color:
|
|
||||||
world.madeline_two_dash_hair_color = world.random.randint(0, 0xFFFFFF)
|
|
||||||
else:
|
|
||||||
world.madeline_two_dash_hair_color = world.options.madeline_two_dash_hair_color.value
|
|
||||||
|
|
||||||
# No Dash Hair
|
|
||||||
if isinstance(world.options.madeline_no_dash_hair_color.value, str):
|
|
||||||
try:
|
|
||||||
world.madeline_no_dash_hair_color = int(world.options.madeline_no_dash_hair_color.value.strip("#")[:6], 16)
|
|
||||||
except ValueError:
|
|
||||||
raise OptionError(f"Invalid input for option `madeline_no_dash_hair_color`:"
|
|
||||||
f"{world.options.madeline_no_dash_hair_color.value} for "
|
|
||||||
f"{world.player_name}")
|
|
||||||
elif world.options.madeline_no_dash_hair_color.value == ColorChoice.option_any_color:
|
|
||||||
world.madeline_no_dash_hair_color = world.random.randint(0, 0xFFFFFF)
|
|
||||||
else:
|
|
||||||
world.madeline_no_dash_hair_color = world.options.madeline_no_dash_hair_color.value
|
|
||||||
|
|
||||||
# Feather Hair
|
|
||||||
if isinstance(world.options.madeline_feather_hair_color.value, str):
|
|
||||||
try:
|
|
||||||
world.madeline_feather_hair_color = int(world.options.madeline_feather_hair_color.value.strip("#")[:6], 16)
|
|
||||||
except ValueError:
|
|
||||||
raise OptionError(f"Invalid input for option `madeline_feather_hair_color`:"
|
|
||||||
f"{world.options.madeline_feather_hair_color.value} for "
|
|
||||||
f"{world.player_name}")
|
|
||||||
elif world.options.madeline_feather_hair_color.value == ColorChoice.option_any_color:
|
|
||||||
world.madeline_feather_hair_color = world.random.randint(0, 0xFFFFFF)
|
|
||||||
else:
|
|
||||||
world.madeline_feather_hair_color = world.options.madeline_feather_hair_color.value
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
from typing import Dict, List, NamedTuple
|
from typing import Dict, List, NamedTuple
|
||||||
|
|
||||||
from .Names import RegionName
|
|
||||||
|
|
||||||
class Celeste64RegionData(NamedTuple):
|
class Celeste64RegionData(NamedTuple):
|
||||||
connecting_regions: List[str] = []
|
connecting_regions: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
region_data_table: Dict[str, Celeste64RegionData] = {
|
region_data_table: Dict[str, Celeste64RegionData] = {
|
||||||
"Menu": Celeste64RegionData([RegionName.forsaken_city]),
|
"Menu": Celeste64RegionData(["Forsaken City"]),
|
||||||
|
"Forsaken City": Celeste64RegionData(),
|
||||||
RegionName.forsaken_city: Celeste64RegionData([RegionName.intro_islands, RegionName.granny_island, RegionName.highway_island, RegionName.ne_feathers_island, RegionName.se_house_island, RegionName.badeline_tower_upper, RegionName.badeline_island]),
|
|
||||||
|
|
||||||
RegionName.intro_islands: Celeste64RegionData([RegionName.granny_island]),
|
|
||||||
RegionName.granny_island: Celeste64RegionData([RegionName.highway_island, RegionName.nw_girders_island, RegionName.badeline_tower_lower, RegionName.se_house_island]),
|
|
||||||
RegionName.highway_island: Celeste64RegionData([RegionName.granny_island, RegionName.ne_feathers_island, RegionName.nw_girders_island]),
|
|
||||||
RegionName.nw_girders_island: Celeste64RegionData([RegionName.highway_island]),
|
|
||||||
RegionName.ne_feathers_island: Celeste64RegionData([RegionName.se_house_island, RegionName.highway_island, RegionName.badeline_tower_lower, RegionName.badeline_tower_upper]),
|
|
||||||
RegionName.se_house_island: Celeste64RegionData([RegionName.ne_feathers_island, RegionName.granny_island, RegionName.badeline_tower_lower]),
|
|
||||||
RegionName.badeline_tower_lower: Celeste64RegionData([RegionName.se_house_island, RegionName.ne_feathers_island, RegionName.granny_island, RegionName.badeline_tower_upper]),
|
|
||||||
RegionName.badeline_tower_upper: Celeste64RegionData([RegionName.badeline_island, RegionName.badeline_tower_lower, RegionName.se_house_island, RegionName.ne_feathers_island, RegionName.granny_island]),
|
|
||||||
RegionName.badeline_island: Celeste64RegionData([RegionName.badeline_tower_upper, RegionName.granny_island, RegionName.highway_island]),
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +1,265 @@
|
|||||||
from typing import Dict, List, Tuple, Callable
|
from typing import Dict, List
|
||||||
|
|
||||||
from BaseClasses import CollectionState, Region
|
from BaseClasses import CollectionState
|
||||||
from worlds.generic.Rules import set_rule
|
from worlds.generic.Rules import set_rule
|
||||||
|
|
||||||
from . import Celeste64World
|
from . import Celeste64World
|
||||||
from .Names import ItemName, LocationName, RegionName
|
from .Names import ItemName, LocationName
|
||||||
|
|
||||||
|
|
||||||
def set_rules(world: Celeste64World):
|
def set_rules(world: Celeste64World):
|
||||||
if world.options.logic_difficulty == "standard":
|
if world.options.logic_difficulty == "standard":
|
||||||
world.active_logic_mapping = location_standard_moves_logic
|
if world.options.move_shuffle:
|
||||||
world.active_region_logic_mapping = region_standard_moves_logic
|
world.active_logic_mapping = location_standard_moves_logic
|
||||||
|
else:
|
||||||
|
world.active_logic_mapping = location_standard_logic
|
||||||
else:
|
else:
|
||||||
world.active_logic_mapping = location_hard_moves_logic
|
if world.options.move_shuffle:
|
||||||
world.active_region_logic_mapping = region_hard_moves_logic
|
world.active_logic_mapping = location_hard_moves_logic
|
||||||
|
else:
|
||||||
|
world.active_logic_mapping = location_hard_logic
|
||||||
|
|
||||||
for location in world.multiworld.get_locations(world.player):
|
for location in world.multiworld.get_locations(world.player):
|
||||||
set_rule(location, lambda state, location=location: location_rule(state, world, location.name))
|
set_rule(location, lambda state, location=location: location_rule(state, world, location.name))
|
||||||
|
|
||||||
|
if world.options.logic_difficulty == "standard":
|
||||||
|
if world.options.move_shuffle:
|
||||||
|
world.goal_logic_mapping = goal_standard_moves_logic
|
||||||
|
else:
|
||||||
|
world.goal_logic_mapping = goal_standard_logic
|
||||||
|
else:
|
||||||
|
if world.options.move_shuffle:
|
||||||
|
world.goal_logic_mapping = goal_hard_moves_logic
|
||||||
|
else:
|
||||||
|
world.goal_logic_mapping = goal_hard_logic
|
||||||
|
|
||||||
# Completion condition.
|
# Completion condition.
|
||||||
world.multiworld.completion_condition[world.player] = lambda state: goal_rule(state, world)
|
world.multiworld.completion_condition[world.player] = lambda state: goal_rule(state, world)
|
||||||
|
|
||||||
|
|
||||||
|
goal_standard_logic: List[List[str]] = [[ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.double_dash_refill]]
|
||||||
|
goal_hard_logic: List[List[str]] = [[]]
|
||||||
|
goal_standard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]]
|
||||||
|
goal_hard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]]
|
||||||
|
|
||||||
|
|
||||||
|
location_standard_logic: Dict[str, List[List[str]]] = {
|
||||||
|
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.strawberry_6: [[ItemName.dash_refill],
|
||||||
|
[ItemName.traffic_block]],
|
||||||
|
LocationName.strawberry_7: [[ItemName.dash_refill],
|
||||||
|
[ItemName.traffic_block]],
|
||||||
|
LocationName.strawberry_8: [[ItemName.traffic_block]],
|
||||||
|
LocationName.strawberry_9: [[ItemName.dash_refill]],
|
||||||
|
LocationName.strawberry_11: [[ItemName.dash_refill],
|
||||||
|
[ItemName.traffic_block]],
|
||||||
|
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill],
|
||||||
|
[ItemName.traffic_block, ItemName.double_dash_refill]],
|
||||||
|
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables],
|
||||||
|
[ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather],
|
||||||
|
[ItemName.traffic_block, ItemName.feather]],
|
||||||
|
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather],
|
||||||
|
[ItemName.traffic_block, ItemName.feather]],
|
||||||
|
LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather],
|
||||||
|
[ItemName.traffic_block, ItemName.feather]],
|
||||||
|
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]],
|
||||||
|
LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]],
|
||||||
|
LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring],
|
||||||
|
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring]],
|
||||||
|
LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.breakables]],
|
||||||
|
|
||||||
|
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables]],
|
||||||
|
LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin],
|
||||||
|
[ItemName.cassette, ItemName.traffic_block, ItemName.coin]],
|
||||||
|
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block]],
|
||||||
|
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill],
|
||||||
|
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]],
|
||||||
|
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill],
|
||||||
|
[ItemName.cassette, ItemName.traffic_block]],
|
||||||
|
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin],
|
||||||
|
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]],
|
||||||
|
LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin],
|
||||||
|
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]],
|
||||||
|
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin]],
|
||||||
|
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables]],
|
||||||
|
|
||||||
|
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
|
||||||
|
LocationName.sign_2: [[ItemName.breakables]],
|
||||||
|
LocationName.sign_3: [[ItemName.dash_refill],
|
||||||
|
[ItemName.traffic_block]],
|
||||||
|
LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill],
|
||||||
|
[ItemName.dash_refill, ItemName.feather],
|
||||||
|
[ItemName.traffic_block, ItemName.feather]],
|
||||||
|
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
|
||||||
|
LocationName.car_2: [[ItemName.breakables]],
|
||||||
|
}
|
||||||
|
|
||||||
|
location_hard_logic: Dict[str, List[List[str]]] = {
|
||||||
|
LocationName.strawberry_13: [[ItemName.breakables]],
|
||||||
|
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
|
||||||
|
LocationName.strawberry_20: [[ItemName.breakables]],
|
||||||
|
|
||||||
|
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
LocationName.strawberry_22: [[ItemName.cassette]],
|
||||||
|
LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin]],
|
||||||
|
LocationName.strawberry_24: [[ItemName.cassette]],
|
||||||
|
LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill]],
|
||||||
|
LocationName.strawberry_26: [[ItemName.cassette]],
|
||||||
|
LocationName.strawberry_27: [[ItemName.cassette]],
|
||||||
|
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather]],
|
||||||
|
LocationName.strawberry_29: [[ItemName.cassette]],
|
||||||
|
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables]],
|
||||||
|
|
||||||
|
LocationName.sign_2: [[ItemName.breakables]],
|
||||||
|
|
||||||
|
LocationName.car_2: [[ItemName.breakables]],
|
||||||
|
}
|
||||||
|
|
||||||
location_standard_moves_logic: Dict[str, List[List[str]]] = {
|
location_standard_moves_logic: Dict[str, List[List[str]]] = {
|
||||||
LocationName.strawberry_1: [[ItemName.ground_dash],
|
LocationName.strawberry_1: [[ItemName.ground_dash],
|
||||||
[ItemName.air_dash],
|
[ItemName.air_dash],
|
||||||
|
[ItemName.skid_jump],
|
||||||
|
[ItemName.climb]],
|
||||||
|
LocationName.strawberry_2: [[ItemName.ground_dash],
|
||||||
|
[ItemName.air_dash],
|
||||||
|
[ItemName.skid_jump],
|
||||||
[ItemName.climb]],
|
[ItemName.climb]],
|
||||||
LocationName.strawberry_2: [[ItemName.air_dash],
|
|
||||||
[ItemName.skid_jump]],
|
|
||||||
LocationName.strawberry_3: [[ItemName.air_dash],
|
LocationName.strawberry_3: [[ItemName.air_dash],
|
||||||
[ItemName.skid_jump]],
|
[ItemName.skid_jump]],
|
||||||
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||||
LocationName.strawberry_5: [[ItemName.air_dash]],
|
LocationName.strawberry_5: [[ItemName.air_dash]],
|
||||||
|
LocationName.strawberry_6: [[ItemName.dash_refill, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.ground_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.skid_jump],
|
||||||
|
[ItemName.traffic_block, ItemName.climb]],
|
||||||
|
LocationName.strawberry_7: [[ItemName.dash_refill, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.ground_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.skid_jump],
|
||||||
|
[ItemName.traffic_block, ItemName.climb]],
|
||||||
|
LocationName.strawberry_8: [[ItemName.traffic_block, ItemName.ground_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.skid_jump],
|
||||||
|
[ItemName.traffic_block, ItemName.climb]],
|
||||||
LocationName.strawberry_9: [[ItemName.dash_refill, ItemName.air_dash]],
|
LocationName.strawberry_9: [[ItemName.dash_refill, ItemName.air_dash]],
|
||||||
LocationName.strawberry_10: [[ItemName.climb]],
|
LocationName.strawberry_10: [[ItemName.climb]],
|
||||||
LocationName.strawberry_11: [[ItemName.air_dash, ItemName.climb]],
|
LocationName.strawberry_11: [[ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
|
||||||
LocationName.strawberry_13: [[ItemName.breakables, ItemName.air_dash],
|
[ItemName.traffic_block, ItemName.climb]],
|
||||||
[ItemName.breakables, ItemName.ground_dash]],
|
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
|
||||||
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash]],
|
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.air_dash]],
|
||||||
LocationName.strawberry_15: [[ItemName.feather, ItemName.air_dash, ItemName.climb]],
|
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables, ItemName.air_dash],
|
||||||
LocationName.strawberry_16: [[ItemName.feather]],
|
[ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash],
|
||||||
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]],
|
[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||||
LocationName.strawberry_18: [[ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
|
||||||
LocationName.strawberry_19: [[ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash, ItemName.skid_jump]],
|
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash]],
|
||||||
LocationName.strawberry_20: [[ItemName.feather, ItemName.breakables, ItemName.air_dash]],
|
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.climb]],
|
||||||
|
LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.feather]],
|
||||||
|
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.ground_dash],
|
||||||
|
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.skid_jump],
|
||||||
|
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.climb]],
|
||||||
|
LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
||||||
|
LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring, ItemName.air_dash]],
|
||||||
|
LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.breakables, ItemName.air_dash]],
|
||||||
|
|
||||||
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||||
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables, ItemName.air_dash]],
|
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables, ItemName.air_dash]],
|
||||||
LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
||||||
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block, ItemName.air_dash]],
|
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block, ItemName.air_dash]],
|
||||||
LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
|
||||||
LocationName.strawberry_26: [[ItemName.cassette, ItemName.air_dash, ItemName.climb]],
|
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
||||||
LocationName.strawberry_27: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
|
||||||
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
[ItemName.cassette, ItemName.traffic_block, ItemName.air_dash, ItemName.climb]],
|
||||||
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]],
|
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash],
|
||||||
|
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash]],
|
||||||
|
LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
||||||
|
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]],
|
||||||
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||||
|
|
||||||
|
LocationName.granny_1: [[ItemName.ground_dash],
|
||||||
|
[ItemName.air_dash],
|
||||||
|
[ItemName.skid_jump],
|
||||||
|
[ItemName.climb]],
|
||||||
|
LocationName.granny_2: [[ItemName.ground_dash],
|
||||||
|
[ItemName.air_dash],
|
||||||
|
[ItemName.skid_jump],
|
||||||
|
[ItemName.climb]],
|
||||||
|
LocationName.granny_3: [[ItemName.ground_dash],
|
||||||
|
[ItemName.air_dash],
|
||||||
|
[ItemName.skid_jump],
|
||||||
|
[ItemName.climb]],
|
||||||
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||||
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||||
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||||
|
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||||
|
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||||
|
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||||
|
|
||||||
|
LocationName.sign_1: [[ItemName.ground_dash],
|
||||||
|
[ItemName.air_dash],
|
||||||
|
[ItemName.skid_jump],
|
||||||
|
[ItemName.climb]],
|
||||||
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
|
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
|
||||||
[ItemName.breakables, ItemName.air_dash]],
|
[ItemName.breakables, ItemName.air_dash]],
|
||||||
|
LocationName.sign_3: [[ItemName.dash_refill, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.ground_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.skid_jump],
|
||||||
|
[ItemName.traffic_block, ItemName.climb]],
|
||||||
|
LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
|
||||||
|
[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.ground_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.skid_jump],
|
||||||
|
[ItemName.traffic_block, ItemName.feather, ItemName.climb]],
|
||||||
|
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||||
|
|
||||||
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash, ItemName.climb],
|
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
|
||||||
[ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
[ItemName.breakables, ItemName.air_dash]],
|
||||||
}
|
}
|
||||||
|
|
||||||
location_hard_moves_logic: Dict[str, List[List[str]]] = {
|
location_hard_moves_logic: Dict[str, List[List[str]]] = {
|
||||||
|
LocationName.strawberry_3: [[ItemName.air_dash],
|
||||||
|
[ItemName.skid_jump]],
|
||||||
LocationName.strawberry_5: [[ItemName.ground_dash],
|
LocationName.strawberry_5: [[ItemName.ground_dash],
|
||||||
[ItemName.air_dash]],
|
[ItemName.air_dash]],
|
||||||
|
LocationName.strawberry_8: [[ItemName.traffic_block],
|
||||||
|
[ItemName.ground_dash, ItemName.air_dash]],
|
||||||
LocationName.strawberry_10: [[ItemName.air_dash],
|
LocationName.strawberry_10: [[ItemName.air_dash],
|
||||||
[ItemName.climb]],
|
[ItemName.climb]],
|
||||||
LocationName.strawberry_11: [[ItemName.ground_dash],
|
LocationName.strawberry_11: [[ItemName.ground_dash],
|
||||||
[ItemName.air_dash],
|
[ItemName.air_dash],
|
||||||
[ItemName.skid_jump]],
|
[ItemName.skid_jump]],
|
||||||
|
LocationName.strawberry_12: [[ItemName.feather],
|
||||||
|
[ItemName.ground_dash],
|
||||||
|
[ItemName.air_dash]],
|
||||||
LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash],
|
LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash],
|
||||||
[ItemName.breakables, ItemName.air_dash]],
|
[ItemName.breakables, ItemName.air_dash]],
|
||||||
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash],
|
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash],
|
||||||
[ItemName.air_dash, ItemName.climb],
|
[ItemName.air_dash, ItemName.climb]],
|
||||||
[ItemName.double_dash_refill, ItemName.air_dash]],
|
|
||||||
LocationName.strawberry_15: [[ItemName.feather],
|
LocationName.strawberry_15: [[ItemName.feather],
|
||||||
[ItemName.ground_dash, ItemName.air_dash]],
|
[ItemName.ground_dash, ItemName.air_dash]],
|
||||||
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
|
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
|
||||||
@@ -107,94 +287,42 @@ location_hard_moves_logic: Dict[str, List[List[str]]] = {
|
|||||||
[ItemName.cassette, ItemName.feather, ItemName.climb]],
|
[ItemName.cassette, ItemName.feather, ItemName.climb]],
|
||||||
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump],
|
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump],
|
||||||
[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash]],
|
[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash]],
|
||||||
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
|
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
|
||||||
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.air_dash, ItemName.climb]],
|
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.feather, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
|
||||||
|
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.ground_dash, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.feather, ItemName.air_dash, ItemName.climb]],
|
||||||
|
|
||||||
|
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
|
||||||
|
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
|
||||||
|
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
|
||||||
|
|
||||||
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
|
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
|
||||||
[ItemName.breakables, ItemName.air_dash]],
|
[ItemName.breakables, ItemName.air_dash]],
|
||||||
|
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
|
||||||
|
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
|
||||||
|
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
|
||||||
|
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
|
||||||
|
|
||||||
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
|
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
|
||||||
[ItemName.breakables, ItemName.air_dash]],
|
[ItemName.breakables, ItemName.air_dash]],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
region_standard_moves_logic: Dict[Tuple[str], List[List[str]]] = {
|
|
||||||
(RegionName.forsaken_city, RegionName.granny_island): [[ItemName.checkpoint_2], [ItemName.checkpoint_3], [ItemName.checkpoint_4]],
|
|
||||||
(RegionName.forsaken_city, RegionName.highway_island): [[ItemName.checkpoint_5], [ItemName.checkpoint_6]],
|
|
||||||
(RegionName.forsaken_city, RegionName.ne_feathers_island): [[ItemName.checkpoint_7]],
|
|
||||||
(RegionName.forsaken_city, RegionName.se_house_island): [[ItemName.checkpoint_8]],
|
|
||||||
(RegionName.forsaken_city, RegionName.badeline_tower_upper): [[ItemName.checkpoint_9]],
|
|
||||||
(RegionName.forsaken_city, RegionName.badeline_island): [[ItemName.checkpoint_10]],
|
|
||||||
|
|
||||||
(RegionName.intro_islands, RegionName.granny_island): [[ItemName.ground_dash],
|
|
||||||
[ItemName.air_dash],
|
|
||||||
[ItemName.skid_jump],
|
|
||||||
[ItemName.climb]],
|
|
||||||
|
|
||||||
(RegionName.granny_island, RegionName.highway_island): [[ItemName.air_dash, ItemName.dash_refill]],
|
|
||||||
(RegionName.granny_island, RegionName.nw_girders_island): [[ItemName.traffic_block]],
|
|
||||||
(RegionName.granny_island, RegionName.badeline_tower_lower): [[ItemName.air_dash, ItemName.climb, ItemName.dash_refill]],
|
|
||||||
(RegionName.granny_island, RegionName.se_house_island): [[ItemName.air_dash, ItemName.climb, ItemName.double_dash_refill]],
|
|
||||||
|
|
||||||
(RegionName.highway_island, RegionName.granny_island): [[ItemName.traffic_block], [ItemName.air_dash, ItemName.dash_refill]],
|
|
||||||
(RegionName.highway_island, RegionName.ne_feathers_island): [[ItemName.feather]],
|
|
||||||
(RegionName.highway_island, RegionName.nw_girders_island): [[ItemName.cannot_access]],
|
|
||||||
|
|
||||||
(RegionName.nw_girders_island, RegionName.highway_island): [[ItemName.traffic_block]],
|
|
||||||
|
|
||||||
(RegionName.ne_feathers_island, RegionName.highway_island): [[ItemName.feather]],
|
|
||||||
(RegionName.ne_feathers_island, RegionName.badeline_tower_lower): [[ItemName.feather]],
|
|
||||||
(RegionName.ne_feathers_island, RegionName.badeline_tower_upper): [[ItemName.climb, ItemName.air_dash, ItemName.feather]],
|
|
||||||
|
|
||||||
(RegionName.se_house_island, RegionName.granny_island): [[ItemName.air_dash, ItemName.traffic_block, ItemName.double_dash_refill]],
|
|
||||||
(RegionName.se_house_island, RegionName.badeline_tower_lower): [[ItemName.air_dash, ItemName.double_dash_refill]],
|
|
||||||
|
|
||||||
(RegionName.badeline_tower_lower, RegionName.se_house_island): [[ItemName.cannot_access]],
|
|
||||||
(RegionName.badeline_tower_lower, RegionName.ne_feathers_island): [[ItemName.air_dash, ItemName.breakables, ItemName.feather]],
|
|
||||||
(RegionName.badeline_tower_lower, RegionName.granny_island): [[ItemName.cannot_access]],
|
|
||||||
(RegionName.badeline_tower_lower, RegionName.badeline_tower_upper): [[ItemName.cannot_access]],
|
|
||||||
|
|
||||||
(RegionName.badeline_tower_upper, RegionName.badeline_island): [[ItemName.air_dash, ItemName.climb, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
|
|
||||||
(RegionName.badeline_tower_upper, RegionName.se_house_island): [[ItemName.air_dash], [ItemName.ground_dash]],
|
|
||||||
(RegionName.badeline_tower_upper, RegionName.ne_feathers_island): [[ItemName.air_dash], [ItemName.ground_dash]],
|
|
||||||
(RegionName.badeline_tower_upper, RegionName.granny_island): [[ItemName.dash_refill]],
|
|
||||||
|
|
||||||
(RegionName.badeline_island, RegionName.badeline_tower_upper): [[ItemName.air_dash], [ItemName.ground_dash]],
|
|
||||||
}
|
|
||||||
|
|
||||||
region_hard_moves_logic: Dict[Tuple[str], List[List[str]]] = {
|
|
||||||
(RegionName.forsaken_city, RegionName.granny_island): [[ItemName.checkpoint_2], [ItemName.checkpoint_3], [ItemName.checkpoint_4]],
|
|
||||||
(RegionName.forsaken_city, RegionName.highway_island): [[ItemName.checkpoint_5], [ItemName.checkpoint_6]],
|
|
||||||
(RegionName.forsaken_city, RegionName.ne_feathers_island): [[ItemName.checkpoint_7]],
|
|
||||||
(RegionName.forsaken_city, RegionName.se_house_island): [[ItemName.checkpoint_8]],
|
|
||||||
(RegionName.forsaken_city, RegionName.badeline_tower_upper): [[ItemName.checkpoint_9]],
|
|
||||||
(RegionName.forsaken_city, RegionName.badeline_island): [[ItemName.checkpoint_10]],
|
|
||||||
|
|
||||||
(RegionName.granny_island, RegionName.nw_girders_island): [[ItemName.traffic_block]],
|
|
||||||
(RegionName.granny_island, RegionName.badeline_tower_lower): [[ItemName.air_dash], [ItemName.ground_dash]],
|
|
||||||
(RegionName.granny_island, RegionName.se_house_island): [[ItemName.air_dash, ItemName.double_dash_refill], [ItemName.ground_dash]],
|
|
||||||
|
|
||||||
(RegionName.highway_island, RegionName.nw_girders_island): [[ItemName.air_dash, ItemName.ground_dash]],
|
|
||||||
|
|
||||||
(RegionName.nw_girders_island, RegionName.highway_island): [[ItemName.traffic_block], [ItemName.air_dash, ItemName.ground_dash]],
|
|
||||||
|
|
||||||
(RegionName.ne_feathers_island, RegionName.highway_island): [[ItemName.feather], [ItemName.air_dash], [ItemName.ground_dash], [ItemName.skid_jump]],
|
|
||||||
(RegionName.ne_feathers_island, RegionName.badeline_tower_lower): [[ItemName.feather], [ItemName.air_dash], [ItemName.ground_dash]],
|
|
||||||
(RegionName.ne_feathers_island, RegionName.badeline_tower_upper): [[ItemName.feather]],
|
|
||||||
|
|
||||||
(RegionName.se_house_island, RegionName.granny_island): [[ItemName.traffic_block]],
|
|
||||||
(RegionName.se_house_island, RegionName.badeline_tower_lower): [[ItemName.air_dash], [ItemName.ground_dash]],
|
|
||||||
|
|
||||||
(RegionName.badeline_tower_upper, RegionName.badeline_island): [[ItemName.air_dash, ItemName.climb, ItemName.feather, ItemName.traffic_block],
|
|
||||||
[ItemName.air_dash, ItemName.climb, ItemName.feather, ItemName.skid_jump],
|
|
||||||
[ItemName.air_dash, ItemName.climb, ItemName.ground_dash, ItemName.traffic_block],
|
|
||||||
[ItemName.air_dash, ItemName.climb, ItemName.ground_dash, ItemName.skid_jump]],
|
|
||||||
|
|
||||||
(RegionName.badeline_island, RegionName.badeline_tower_upper): [[ItemName.air_dash], [ItemName.ground_dash]],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool:
|
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool:
|
||||||
|
|
||||||
if loc not in world.active_logic_mapping:
|
if loc not in world.active_logic_mapping:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -204,28 +332,12 @@ def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bo
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def region_connection_rule(state: CollectionState, world: Celeste64World, region_connection: Tuple[str]) -> bool:
|
|
||||||
if region_connection not in world.active_region_logic_mapping:
|
|
||||||
return True
|
|
||||||
|
|
||||||
for possible_access in world.active_region_logic_mapping[region_connection]:
|
|
||||||
if state.has_all(possible_access, world.player):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def goal_rule(state: CollectionState, world: Celeste64World) -> bool:
|
def goal_rule(state: CollectionState, world: Celeste64World) -> bool:
|
||||||
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
|
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
goal_region: Region = world.multiworld.get_region(RegionName.badeline_island, world.player)
|
for possible_access in world.goal_logic_mapping:
|
||||||
return state.can_reach(goal_region)
|
if state.has_all(possible_access, world.player):
|
||||||
|
return True
|
||||||
|
|
||||||
def connect_region(world: Celeste64World, region: Region, dest_regions: List[str]):
|
return False
|
||||||
rules: Dict[str, Callable[[CollectionState], bool]] = {}
|
|
||||||
|
|
||||||
for dest_region in dest_regions:
|
|
||||||
region_connection: Tuple[str] = (region.name, dest_region)
|
|
||||||
rules[dest_region] = lambda state, region_connection=region_connection: region_connection_rule(state, world, region_connection)
|
|
||||||
|
|
||||||
region.add_exits(dest_regions, rules)
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List
|
||||||
|
|
||||||
from BaseClasses import ItemClassification, Location, Region, Tutorial
|
from BaseClasses import ItemClassification, Location, Region, Tutorial
|
||||||
from worlds.AutoWorld import WebWorld, World
|
from worlds.AutoWorld import WebWorld, World
|
||||||
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table,\
|
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table, item_table
|
||||||
checkpoint_item_data_table, item_table
|
|
||||||
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
|
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
|
||||||
sign_location_data_table, car_location_data_table, checkpoint_location_data_table,\
|
sign_location_data_table, car_location_data_table, location_table
|
||||||
location_table
|
|
||||||
from .Names import ItemName, LocationName
|
from .Names import ItemName, LocationName
|
||||||
from .Options import Celeste64Options, celeste_64_option_groups, resolve_options
|
from .Options import Celeste64Options, celeste_64_option_groups
|
||||||
|
|
||||||
|
|
||||||
class Celeste64WebWorld(WebWorld):
|
class Celeste64WebWorld(WebWorld):
|
||||||
@@ -44,15 +42,8 @@ class Celeste64World(World):
|
|||||||
# Instance Data
|
# Instance Data
|
||||||
strawberries_required: int
|
strawberries_required: int
|
||||||
active_logic_mapping: Dict[str, List[List[str]]]
|
active_logic_mapping: Dict[str, List[List[str]]]
|
||||||
active_region_logic_mapping: Dict[Tuple[str], List[List[str]]]
|
goal_logic_mapping: Dict[str, List[List[str]]]
|
||||||
|
|
||||||
madeline_one_dash_hair_color: int
|
|
||||||
madeline_two_dash_hair_color: int
|
|
||||||
madeline_no_dash_hair_color: int
|
|
||||||
madeline_feather_hair_color: int
|
|
||||||
|
|
||||||
def generate_early(self) -> None:
|
|
||||||
resolve_options(self)
|
|
||||||
|
|
||||||
def create_item(self, name: str) -> Celeste64Item:
|
def create_item(self, name: str) -> Celeste64Item:
|
||||||
# Only make required amount of strawberries be Progression
|
# Only make required amount of strawberries be Progression
|
||||||
@@ -85,49 +76,25 @@ class Celeste64World(World):
|
|||||||
for name in unlockable_item_data_table.keys()
|
for name in unlockable_item_data_table.keys()
|
||||||
if name not in self.options.start_inventory]
|
if name not in self.options.start_inventory]
|
||||||
|
|
||||||
chosen_start_item: str = ""
|
|
||||||
|
|
||||||
if self.options.move_shuffle:
|
if self.options.move_shuffle:
|
||||||
|
move_items_for_itempool: List[str] = deepcopy(list(move_item_data_table.keys()))
|
||||||
|
|
||||||
if self.options.logic_difficulty == "standard":
|
if self.options.logic_difficulty == "standard":
|
||||||
possible_unwalls: List[str] = [name for name in move_item_data_table.keys()
|
# If the start_inventory already includes a move, don't worry about giving it one
|
||||||
if name != ItemName.skid_jump]
|
if not [move for move in move_items_for_itempool if move in self.options.start_inventory]:
|
||||||
|
chosen_start_move = self.random.choice(move_items_for_itempool)
|
||||||
if self.options.checkpointsanity:
|
move_items_for_itempool.remove(chosen_start_move)
|
||||||
possible_unwalls.extend([name for name in checkpoint_item_data_table.keys()
|
|
||||||
if name != ItemName.checkpoint_1 and name != ItemName.checkpoint_10])
|
|
||||||
|
|
||||||
# If the start_inventory already includes a move or checkpoint, don't worry about giving it one
|
|
||||||
if not [item for item in possible_unwalls if item in self.multiworld.precollected_items[self.player]]:
|
|
||||||
chosen_start_item = self.random.choice(possible_unwalls)
|
|
||||||
|
|
||||||
if self.options.carsanity:
|
if self.options.carsanity:
|
||||||
intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player)
|
intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player)
|
||||||
intro_car_loc.place_locked_item(self.create_item(chosen_start_item))
|
intro_car_loc.place_locked_item(self.create_item(chosen_start_move))
|
||||||
location_count -= 1
|
location_count -= 1
|
||||||
else:
|
else:
|
||||||
self.multiworld.push_precollected(self.create_item(chosen_start_item))
|
self.multiworld.push_precollected(self.create_item(chosen_start_move))
|
||||||
|
|
||||||
item_pool += [self.create_item(name)
|
item_pool += [self.create_item(name)
|
||||||
for name in move_item_data_table.keys()
|
for name in move_items_for_itempool
|
||||||
if name not in self.multiworld.precollected_items[self.player]
|
if name not in self.options.start_inventory]
|
||||||
and name != chosen_start_item]
|
|
||||||
else:
|
|
||||||
for start_move in move_item_data_table.keys():
|
|
||||||
self.multiworld.push_precollected(self.create_item(start_move))
|
|
||||||
|
|
||||||
if self.options.checkpointsanity:
|
|
||||||
location_count += 9
|
|
||||||
goal_checkpoint_loc: Location = self.multiworld.get_location(LocationName.checkpoint_10, self.player)
|
|
||||||
goal_checkpoint_loc.place_locked_item(self.create_item(ItemName.checkpoint_10))
|
|
||||||
item_pool += [self.create_item(name)
|
|
||||||
for name in checkpoint_item_data_table.keys()
|
|
||||||
if name not in self.multiworld.precollected_items[self.player]
|
|
||||||
and name != ItemName.checkpoint_10
|
|
||||||
and name != chosen_start_item]
|
|
||||||
else:
|
|
||||||
for item_name in checkpoint_item_data_table.keys():
|
|
||||||
checkpoint_loc: Location = self.multiworld.get_location(item_name, self.player)
|
|
||||||
checkpoint_loc.place_locked_item(self.create_item(item_name))
|
|
||||||
|
|
||||||
real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - len(item_pool))
|
real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - len(item_pool))
|
||||||
self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100))
|
self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100))
|
||||||
@@ -173,23 +140,18 @@ class Celeste64World(World):
|
|||||||
if location_data.region == region_name
|
if location_data.region == region_name
|
||||||
}, Celeste64Location)
|
}, Celeste64Location)
|
||||||
|
|
||||||
region.add_locations({
|
region.add_exits(region_data_table[region_name].connecting_regions)
|
||||||
location_name: location_data.address for location_name, location_data in checkpoint_location_data_table.items()
|
|
||||||
if location_data.region == region_name
|
|
||||||
}, Celeste64Location)
|
|
||||||
|
|
||||||
from .Rules import connect_region
|
|
||||||
connect_region(self, region, region_data_table[region_name].connecting_regions)
|
|
||||||
|
|
||||||
# Have to do this here because of other games using State in a way that's bad
|
|
||||||
from .Rules import set_rules
|
|
||||||
set_rules(self)
|
|
||||||
|
|
||||||
|
|
||||||
def get_filler_item_name(self) -> str:
|
def get_filler_item_name(self) -> str:
|
||||||
return ItemName.raspberry
|
return ItemName.raspberry
|
||||||
|
|
||||||
|
|
||||||
|
def set_rules(self) -> None:
|
||||||
|
from .Rules import set_rules
|
||||||
|
set_rules(self)
|
||||||
|
|
||||||
|
|
||||||
def fill_slot_data(self):
|
def fill_slot_data(self):
|
||||||
return {
|
return {
|
||||||
"death_link": self.options.death_link.value,
|
"death_link": self.options.death_link.value,
|
||||||
@@ -199,11 +161,6 @@ class Celeste64World(World):
|
|||||||
"friendsanity": self.options.friendsanity.value,
|
"friendsanity": self.options.friendsanity.value,
|
||||||
"signsanity": self.options.signsanity.value,
|
"signsanity": self.options.signsanity.value,
|
||||||
"carsanity": self.options.carsanity.value,
|
"carsanity": self.options.carsanity.value,
|
||||||
"checkpointsanity": self.options.checkpointsanity.value,
|
|
||||||
"madeline_one_dash_hair_color": self.madeline_one_dash_hair_color,
|
|
||||||
"madeline_two_dash_hair_color": self.madeline_two_dash_hair_color,
|
|
||||||
"madeline_no_dash_hair_color": self.madeline_no_dash_hair_color,
|
|
||||||
"madeline_feather_hair_color": self.madeline_feather_hair_color,
|
|
||||||
"badeline_chaser_source": self.options.badeline_chaser_source.value,
|
"badeline_chaser_source": self.options.badeline_chaser_source.value,
|
||||||
"badeline_chaser_frequency": self.options.badeline_chaser_frequency.value,
|
"badeline_chaser_frequency": self.options.badeline_chaser_frequency.value,
|
||||||
"badeline_chaser_speed": self.options.badeline_chaser_speed.value,
|
"badeline_chaser_speed": self.options.badeline_chaser_speed.value,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import os
|
import os
|
||||||
import io
|
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, cast
|
from typing import TYPE_CHECKING, Dict, List, Optional, cast
|
||||||
import zipfile
|
import zipfile
|
||||||
from BaseClasses import Location
|
from BaseClasses import Location
|
||||||
from worlds.Files import APContainer, AutoPatchRegister
|
from worlds.Files import APContainer
|
||||||
|
|
||||||
from .Enum import CivVICheckType
|
from .Enum import CivVICheckType
|
||||||
from .Locations import CivVILocation, CivVILocationData
|
from .Locations import CivVILocation, CivVILocationData
|
||||||
@@ -26,22 +25,18 @@ class CivTreeItem:
|
|||||||
ui_tree_row: int
|
ui_tree_row: int
|
||||||
|
|
||||||
|
|
||||||
class CivVIContainer(APContainer, metaclass=AutoPatchRegister):
|
class CivVIContainer(APContainer):
|
||||||
"""
|
"""
|
||||||
Responsible for generating the dynamic mod files for the Civ VI multiworld
|
Responsible for generating the dynamic mod files for the Civ VI multiworld
|
||||||
"""
|
"""
|
||||||
game: Optional[str] = "Civilization VI"
|
game: Optional[str] = "Civilization VI"
|
||||||
patch_file_ending = ".apcivvi"
|
|
||||||
|
|
||||||
def __init__(self, patch_data: Dict[str, str] | io.BytesIO, base_path: str = "", output_directory: str = "",
|
def __init__(self, patch_data: Dict[str, str], base_path: str, output_directory: str,
|
||||||
player: Optional[int] = None, player_name: str = "", server: str = ""):
|
player: Optional[int] = None, player_name: str = "", server: str = ""):
|
||||||
if isinstance(patch_data, io.BytesIO):
|
self.patch_data = patch_data
|
||||||
super().__init__(patch_data, player, player_name, server)
|
self.file_path = base_path
|
||||||
else:
|
container_path = os.path.join(output_directory, base_path + ".apcivvi")
|
||||||
self.patch_data = patch_data
|
super().__init__(container_path, player, player_name, server)
|
||||||
self.file_path = base_path
|
|
||||||
container_path = os.path.join(output_directory, base_path + ".apcivvi")
|
|
||||||
super().__init__(container_path, player, player_name, server)
|
|
||||||
|
|
||||||
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
||||||
for filename, yml in self.patch_data.items():
|
for filename, yml in self.patch_data.items():
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en#connections).
|
|||||||
* `percentage` is the percentage chance for this connection from 0 to 100 and defaults to 100.
|
* `percentage` is the percentage chance for this connection from 0 to 100 and defaults to 100.
|
||||||
* Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance
|
* Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance
|
||||||
shuffle.
|
shuffle.
|
||||||
* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate. `direction` defaults to `both`.
|
* `direction` can be `both`, `entrance`, or `exit`. How this is handled varies between games. `direction` defaults to `both`.
|
||||||
|
|
||||||
[A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852)
|
[A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852)
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,8 @@
|
|||||||
|
|
||||||
## Installing the Archipelago Mod using Lumafly
|
## Installing the Archipelago Mod using Lumafly
|
||||||
1. Launch Lumafly and ensure it locates your Hollow Knight installation directory.
|
1. Launch Lumafly and ensure it locates your Hollow Knight installation directory.
|
||||||
2. Install the Archipelago mods by doing either of the following:
|
2. Click the "Install" button near the "Archipelago" mod entry.
|
||||||
* Click one of the links below to allow Lumafly to install the mods. Lumafly will prompt for confirmation.
|
* If desired, also install "Archipelago Map Mod" to use as an in-game tracker.
|
||||||
* [Archipelago and dependencies only](https://themulhima.github.io/Lumafly/commands/download/?mods=Archipelago)
|
|
||||||
* [Archipelago with rando essentials](https://themulhima.github.io/Lumafly/commands/download/?mods=Archipelago/Archipelago%20Map%20Mod/RecentItemsDisplay/DebugMod/RandoStats/Additional%20Timelines/CompassAlwaysOn/AdditionalMaps/)
|
|
||||||
(includes Archipelago Map Mod, RecentItemsDisplay, DebugMod, RandoStats, AdditionalTimelines, CompassAlwaysOn,
|
|
||||||
and AdditionalMaps).
|
|
||||||
* Click the "Install" button near the "Archipelago" mod entry. If desired, also install "Archipelago Map Mod"
|
|
||||||
to use as an in-game tracker.
|
|
||||||
3. Launch the game, you're all set!
|
3. Launch the game, you're all set!
|
||||||
|
|
||||||
### What to do if Lumafly fails to find your installation directory
|
### What to do if Lumafly fails to find your installation directory
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ SYNONYMS = {
|
|||||||
'JUMP': 'FEATHER',
|
'JUMP': 'FEATHER',
|
||||||
'PLUME': 'FEATHER',
|
'PLUME': 'FEATHER',
|
||||||
'WING': 'FEATHER',
|
'WING': 'FEATHER',
|
||||||
"QUILL": "FEATHER",
|
|
||||||
|
|
||||||
# SHOVEL
|
# SHOVEL
|
||||||
'DIG': 'SHOVEL',
|
'DIG': 'SHOVEL',
|
||||||
@@ -344,8 +343,6 @@ SYNONYMS = {
|
|||||||
# TRADING_ITEM_LETTER
|
# TRADING_ITEM_LETTER
|
||||||
'CARD': 'TRADING_ITEM_LETTER',
|
'CARD': 'TRADING_ITEM_LETTER',
|
||||||
'MESSAGE': 'TRADING_ITEM_LETTER',
|
'MESSAGE': 'TRADING_ITEM_LETTER',
|
||||||
"TICKET": 'TRADING_ITEM_LETTER',
|
|
||||||
"PASS": 'TRADING_ITEM_LETTER',
|
|
||||||
|
|
||||||
# TRADING_ITEM_BROOM
|
# TRADING_ITEM_BROOM
|
||||||
'SWEEP': 'TRADING_ITEM_BROOM',
|
'SWEEP': 'TRADING_ITEM_BROOM',
|
||||||
@@ -368,8 +365,6 @@ SYNONYMS = {
|
|||||||
'MIRROR': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
'MIRROR': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
||||||
'SCOPE': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
'SCOPE': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
||||||
'XRAY': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
'XRAY': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
||||||
"DETECTOR": 'TRADING_ITEM_MAGNIFYING_GLASS',
|
|
||||||
"ITEMFINDER": 'TRADING_ITEM_MAGNIFYING_GLASS',
|
|
||||||
|
|
||||||
# PIECE_OF_POWER
|
# PIECE_OF_POWER
|
||||||
'TRIANGLE': 'PIECE_OF_POWER',
|
'TRIANGLE': 'PIECE_OF_POWER',
|
||||||
@@ -383,7 +378,6 @@ PHRASES = {
|
|||||||
'BOSS KEY': 'NIGHTMARE_KEY',
|
'BOSS KEY': 'NIGHTMARE_KEY',
|
||||||
'HEART PIECE': 'HEART_PIECE',
|
'HEART PIECE': 'HEART_PIECE',
|
||||||
'PIECE OF HEART': 'HEART_PIECE',
|
'PIECE OF HEART': 'HEART_PIECE',
|
||||||
"ROCK SMASH": 'BOMB',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# All following will only be used to match items for the specific game.
|
# All following will only be used to match items for the specific game.
|
||||||
@@ -410,16 +404,6 @@ GAME_SPECIFIC_PHRASES = {
|
|||||||
|
|
||||||
'Ocarina of Time': {
|
'Ocarina of Time': {
|
||||||
'COJIRO': 'ROOSTER',
|
'COJIRO': 'ROOSTER',
|
||||||
"Goron Tunic": "RED_TUNIC",
|
|
||||||
"Zora Tunic": "BLUE_TUNIC",
|
|
||||||
"Wallet": "MAGIC_POWDER",
|
|
||||||
"Medallion": "PIECE_OF_POWER",
|
|
||||||
"Kokiri Emerald": "RUPEES_500",
|
|
||||||
"Goron Ruby": "RUPEES_500",
|
|
||||||
"Zora Sapphire": "RUPEES_500",
|
|
||||||
"Dins Fire": "MAGIC_ROD", # Fire shield
|
|
||||||
"Nayrus Love": "MAGIC_ROD", # Protective barrier
|
|
||||||
"Farores Wind": "MAGIC_ROD", # Create/use warp point in dungeons
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'SMZ3': {
|
'SMZ3': {
|
||||||
@@ -433,14 +417,10 @@ GAME_SPECIFIC_PHRASES = {
|
|||||||
|
|
||||||
'Sonic Adventure 2 Battle': {
|
'Sonic Adventure 2 Battle': {
|
||||||
'CHAOS EMERALD': 'PIECE_OF_POWER',
|
'CHAOS EMERALD': 'PIECE_OF_POWER',
|
||||||
"Rings": "RUPEES_20", # This should only affect filler Rings currency, not Flame Ring upgrade
|
|
||||||
"Grapes": "TRADING_ITEM_PINEAPPLE",
|
|
||||||
"Pick Nails": "SHOVEL", # Digging upgrade
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'Super Mario 64': {
|
'Super Mario 64': {
|
||||||
'POWER STAR': 'PIECE_OF_POWER',
|
'POWER STAR': 'PIECE_OF_POWER',
|
||||||
"Key": "NIGHTMARE_KEY" # Affect 2nd Floor / Basement / Progressive keys
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'Super Mario World': {
|
'Super Mario World': {
|
||||||
@@ -548,336 +528,4 @@ GAME_SPECIFIC_PHRASES = {
|
|||||||
'2500 Tokens': 'RUPEES_500',
|
'2500 Tokens': 'RUPEES_500',
|
||||||
'5000 Tokens': 'RUPEES_500',
|
'5000 Tokens': 'RUPEES_500',
|
||||||
},
|
},
|
||||||
|
|
||||||
"Donkey Kong Country 3": {
|
|
||||||
"Flupperius Petallus Pongus": "TRADING_ITEM_HIBISCUS", # It's a flower in the game
|
|
||||||
"Banana Bird": "ROOSTER", # Made sure this is a BIRD, not a BANANA
|
|
||||||
},
|
|
||||||
|
|
||||||
"Pokemon Red and Blue": {
|
|
||||||
|
|
||||||
# Key Items
|
|
||||||
|
|
||||||
"Old Amber": "STONE_BEAK", # Aerodactyl's fossil should still be a fossil
|
|
||||||
"Coin Case": "MAGIC_POWDER", # This shouldn't spawn as RUPEES
|
|
||||||
"Bike Voucher": "TRADING_ITEM_LETTER",
|
|
||||||
"Oak's Parcel": "TRADING_ITEM_LETTER",
|
|
||||||
|
|
||||||
# Drinks always get converted to MEDICINE
|
|
||||||
"Soda Pop": "MEDICINE",
|
|
||||||
"Fresh Water": "MEDICINE",
|
|
||||||
|
|
||||||
# Consumables
|
|
||||||
"Elixir": "MEDICINE",
|
|
||||||
"Ether": "MEDICINE",
|
|
||||||
"Antidote": "MEDICINE",
|
|
||||||
"Awakening": "MEDICINE",
|
|
||||||
"Burn Heal": "MEDICINE",
|
|
||||||
"Ice Heal": "MEDICINE",
|
|
||||||
"Paralyze Heal": "MEDICINE",
|
|
||||||
"Full Heal": "MEDICINE",
|
|
||||||
"Full Restore": "MEDICINE",
|
|
||||||
},
|
|
||||||
|
|
||||||
"Pokemon Emerald": {
|
|
||||||
|
|
||||||
"Coin Case": "MAGIC_POWDER", # This shouldn't spawn as RUPEES
|
|
||||||
|
|
||||||
# Drinks always get converted to MEDICINE
|
|
||||||
|
|
||||||
"Soda Pop": "MEDICINE",
|
|
||||||
"Fresh Water": "MEDICINE",
|
|
||||||
|
|
||||||
# Consumables
|
|
||||||
"Elixir": "MEDICINE",
|
|
||||||
"Ether": "MEDICINE",
|
|
||||||
"Antidote": "MEDICINE",
|
|
||||||
"Awakening": "MEDICINE",
|
|
||||||
"Burn Heal": "MEDICINE",
|
|
||||||
"Ice Heal": "MEDICINE",
|
|
||||||
"Paralyze Heal": "MEDICINE",
|
|
||||||
"Full Heal": "MEDICINE",
|
|
||||||
"Full Restore": "MEDICINE",
|
|
||||||
"Nanab Berry": "TRADING_ITEM_BANANAS", # Special exception for Nanab Berry, which look like bananas
|
|
||||||
"Berry": "TRADING_ITEM_PINEAPPLE",
|
|
||||||
"Mail": "TRADING_ITEM_LETTER", # Snail mail, not chain mail
|
|
||||||
},
|
|
||||||
|
|
||||||
"Mario & Luigi Superstar Saga": {
|
|
||||||
|
|
||||||
# Key Items
|
|
||||||
"Peach's Extra Dress": "RED_TUNIC",
|
|
||||||
"Peasley's Rose": "TRADING_ITEM_HIBISCUS",
|
|
||||||
"Beanstar": "PIECE_OF_POWER", # Hits both Fake Beanstar and pieces of the real Beanstar, hopefully
|
|
||||||
"Beanstone": "RUPEES_500", # They're gemstones
|
|
||||||
"Firebrand": "POWER_BRACELET", # Magic power that affects Mario/Luigi's hands, either this or MAGIC_ROD would be okay
|
|
||||||
"Thunderhand": "POWER_BRACELET", # Ditto
|
|
||||||
|
|
||||||
# 1-UP Super fix
|
|
||||||
"1-UP Super": "TOADSTOOL",
|
|
||||||
|
|
||||||
# Drinks --> medicine
|
|
||||||
|
|
||||||
# Syrup bottles
|
|
||||||
"Syrup": "MEDICINE",
|
|
||||||
|
|
||||||
# Coffee blends
|
|
||||||
"Hoolumbian": "MEDICINE",
|
|
||||||
"Chuckoccino": "MEDICINE",
|
|
||||||
"Teeheespresso": "MEDICINE",
|
|
||||||
"Blend": "MEDICINE", # for all coffee blends
|
|
||||||
|
|
||||||
# Secret Scrolls --> MESSAGE
|
|
||||||
"Secret Scroll": "TRADING_ITEM_LETTER",
|
|
||||||
|
|
||||||
# Goblets --> MEDICINE
|
|
||||||
"Goblet": "MEDICINE",
|
|
||||||
|
|
||||||
# Pearl Beans --> Fruit
|
|
||||||
"Pearl Bean": 'TRADING_ITEM_PINEAPPLE',
|
|
||||||
|
|
||||||
# Bros. Armor --> Blue Tunic
|
|
||||||
"Pants": "BLUE_TUNIC",
|
|
||||||
"Jeans": "BLUE_TUNIC",
|
|
||||||
"Trousers": "BLUE_TUNIC",
|
|
||||||
"Slacks": "BLUE_TUNIC",
|
|
||||||
"Casual Coral": "BLUE_TUNIC",
|
|
||||||
"Shroom Bells": "BLUE_TUNIC",
|
|
||||||
|
|
||||||
# Badges --> Ribbon
|
|
||||||
"Badge": "TRADING_ITEM_RIBBON",
|
|
||||||
"Soulful Bros.": "TRADING_ITEM_RIBBON",
|
|
||||||
"Bros. Rock": "TRADING_ITEM_RIBBON",
|
|
||||||
|
|
||||||
# Misc. Beans --> Acorns
|
|
||||||
"Hoo Bean": "GUARDIAN_ACORN", # Beans and nuts are similar enough, right?
|
|
||||||
"Chuckle Bean": "GUARDIAN_ACORN",
|
|
||||||
"Hee Bean": "GUARDIAN_ACORN",
|
|
||||||
"Woo Bean": "GUARDIAN_ACORN",
|
|
||||||
},
|
|
||||||
|
|
||||||
"DOOM 1993": {
|
|
||||||
"Keycard": "KEY",
|
|
||||||
"Computer area map": "MAP",
|
|
||||||
"Box of": "SINGLE_ARROW", # bullets, rockets, or shotgun shells
|
|
||||||
"Energy cell pack": "SINGLE_ARROW",
|
|
||||||
"Chainsaw": "SWORD",
|
|
||||||
"Medikit": "MEDICINE",
|
|
||||||
"Skull key": "NIGHTMARE_KEY",
|
|
||||||
},
|
|
||||||
|
|
||||||
"DOOM II": {
|
|
||||||
"Keycard": "KEY",
|
|
||||||
"Computer area map": "MAP",
|
|
||||||
"Box of": "SINGLE_ARROW", # bullets, rockets, or shotgun shells
|
|
||||||
"Energy cell pack": "SINGLE_ARROW",
|
|
||||||
"Chainsaw": "SWORD",
|
|
||||||
"Medikit": "MEDICINE",
|
|
||||||
"Skull key": "NIGHTMARE_KEY",
|
|
||||||
},
|
|
||||||
|
|
||||||
"Inscryption": {
|
|
||||||
"Extra Candle": "HEART_CONTAINER", # Candles act as extra health
|
|
||||||
"Magnificus Eye": "TRADING_ITEM_MAGNIFYING_GLASS", # Needed to see hidden drawings / messages
|
|
||||||
"Monocle": "TRADING_ITEM_MAGNIFYING_GLASS", # Ditto
|
|
||||||
"Pile Of Meat": "TRADING_ITEM_DOG_FOOD",
|
|
||||||
"Angler Hook": "TRADING_ITEM_FISHING_HOOK", # Good fish.
|
|
||||||
"Currency": "RUPEES_20",
|
|
||||||
},
|
|
||||||
|
|
||||||
"Minecraft": {
|
|
||||||
"Progressive Weapons": "SWORD",
|
|
||||||
"Progressive Tools": "SHOVEL",
|
|
||||||
"Archery": "BOW",
|
|
||||||
"Emerald": "RUPEES_20",
|
|
||||||
"Brewing": "MEDICINE",
|
|
||||||
"Spyglass": 'TRADING_ITEM_MAGNIFYING_GLASS',
|
|
||||||
"Porkchop": "TRADING_ITEM_DOG_FOOD"
|
|
||||||
},
|
|
||||||
"VVVVVV": {
|
|
||||||
"Trinket": "PIECE_OF_POWER",
|
|
||||||
},
|
|
||||||
|
|
||||||
"A Hat in Time": {
|
|
||||||
"Time Piece": "PIECE_OF_POWER",
|
|
||||||
"Metro Ticket": "TRADING_ITEM_LETTER",
|
|
||||||
"Snatcher's Contract": "TRADING_ITEM_LETTER",
|
|
||||||
"Pon": "RUPEES_20",
|
|
||||||
},
|
|
||||||
|
|
||||||
"Kingdom Hearts 2": {
|
|
||||||
# Goal items / Collectibles
|
|
||||||
"Proof of": "PIECE_OF_POWER",
|
|
||||||
"Lucky Emblem": "PIECE_OF_POWER",
|
|
||||||
"Secret Ansem's Report": "TRADING_ITEM_LETTER",
|
|
||||||
|
|
||||||
# Sora Keyblades
|
|
||||||
"Bond of Flame": "SWORD",
|
|
||||||
"Circle of Life": "SWORD",
|
|
||||||
"Decisive Pumpkin": "SWORD",
|
|
||||||
"Fatal Crest": "SWORD",
|
|
||||||
"Fenrir": "SWORD",
|
|
||||||
"Follow the Wind": "SWORD",
|
|
||||||
"Guardian Soul": "SWORD",
|
|
||||||
"Gull Wing": "SWORD",
|
|
||||||
"Hero's Crest": "SWORD",
|
|
||||||
"Hidden Dragon": "SWORD",
|
|
||||||
"Monochrome": "SWORD",
|
|
||||||
"Mysterious Abyss": "SWORD",
|
|
||||||
"Oathkeeper": "SWORD",
|
|
||||||
"Oblivion": "SWORD",
|
|
||||||
"Photon Debugger": "SWORD",
|
|
||||||
"Pureblood": "SWORD",
|
|
||||||
"Rumbling Rose": "SWORD",
|
|
||||||
"Sleeping Lion": "SWORD",
|
|
||||||
"Star Seeker": "SWORD",
|
|
||||||
"Sweet Memories": "SWORD",
|
|
||||||
"Two Become One": "SWORD",
|
|
||||||
"Ultima Weapon": "SWORD",
|
|
||||||
"Winner's Proof": "SWORD",
|
|
||||||
"Wishing Lamp": "SWORD",
|
|
||||||
|
|
||||||
# Donald Staves
|
|
||||||
"Centurion+": "MAGIC_ROD",
|
|
||||||
"Nobody Lance": "MAGIC_ROD",
|
|
||||||
"Precious Mushroom": "MAGIC_ROD",
|
|
||||||
"Precious Mushroom+": "MAGIC_ROD",
|
|
||||||
"Premium Mushroom": "MAGIC_ROD",
|
|
||||||
"Rising Dragon": "MAGIC_ROD",
|
|
||||||
"Save The Queen+": "MAGIC_ROD",
|
|
||||||
"Shaman's Relic": "MAGIC_ROD",
|
|
||||||
"Victory Bell": "MAGIC_ROD",
|
|
||||||
|
|
||||||
# Goofy Shields
|
|
||||||
"Akashic Record": "SHIELD",
|
|
||||||
"Frozen Pride+": "SHIELD",
|
|
||||||
"Majestic Mushroom": "SHIELD",
|
|
||||||
"Majestic Mushroom+": "SHIELD",
|
|
||||||
"Nobody Guard": "SHIELD",
|
|
||||||
"Ogre Shield": "SHIELD",
|
|
||||||
"Save The King+": "SHIELD",
|
|
||||||
"Ultimate Mushroom": "SHIELD",
|
|
||||||
|
|
||||||
# Accessories as RIBBON
|
|
||||||
"Star Charm": "TRADING_ITEM_RIBBON",
|
|
||||||
"Ring": "TRADING_ITEM_RIBBON",
|
|
||||||
"Earring": "TRADING_ITEM_RIBBON",
|
|
||||||
"Shadow Archive": "TRADING_ITEM_RIBBON",
|
|
||||||
"Shadow Archive+": "TRADING_ITEM_RIBBON",
|
|
||||||
"Full Bloom": "TRADING_ITEM_RIBBON",
|
|
||||||
"Full Bloom+": "TRADING_ITEM_RIBBON",
|
|
||||||
|
|
||||||
# Armor as BLUE_TUNIC
|
|
||||||
"Bandanna": "BLUE_TUNIC",
|
|
||||||
"Belt": "BLUE_TUNIC",
|
|
||||||
"Band": "BLUE_TUNIC",
|
|
||||||
"Bangle": "BLUE_TUNIC",
|
|
||||||
"Armlet": "BLUE_TUNIC",
|
|
||||||
"Trinket": "BLUE_TUNIC",
|
|
||||||
"Charm": "BLUE_TUNIC",
|
|
||||||
"Anklet": "BLUE_TUNIC",
|
|
||||||
"Chain": "BLUE_TUNIC",
|
|
||||||
"Acrisius": "BLUE_TUNIC",
|
|
||||||
"Ribbon": "BLUE_TUNIC",
|
|
||||||
|
|
||||||
# Magic
|
|
||||||
"Element": "MAGIC_ROD",
|
|
||||||
|
|
||||||
# Other
|
|
||||||
"Munny Pouch": "MAGIC_POWDER",
|
|
||||||
"Ether": "MEDICINE",
|
|
||||||
"Elixir": "MEDICINE",
|
|
||||||
"Megalixir": "MEDICINE",
|
|
||||||
},
|
|
||||||
|
|
||||||
"Kingdom Hearts": {
|
|
||||||
# Goal/collectible items
|
|
||||||
"Ansem's Report": "TRADING_ITEM_LETTER",
|
|
||||||
|
|
||||||
# Dalmatian puppies
|
|
||||||
"Puppy": "BOWWOW",
|
|
||||||
"Puppies": "BOWWOW",
|
|
||||||
|
|
||||||
# Sora Keyblades
|
|
||||||
"Jungle King": "SWORD",
|
|
||||||
"Three Wishes": "SWORD",
|
|
||||||
"Fairy Harp": "SWORD",
|
|
||||||
"Pumpkinhead": "SWORD",
|
|
||||||
"Crabclaw": "SWORD",
|
|
||||||
"Divine Rose": "SWORD",
|
|
||||||
"Spellbinder": "SWORD",
|
|
||||||
"Olympia": "SWORD",
|
|
||||||
"Lionheart": "SWORD",
|
|
||||||
"Metal Chocobo": "SWORD",
|
|
||||||
"Oathkeeper": "SWORD",
|
|
||||||
"Oblivion": "SWORD",
|
|
||||||
"Lady Luck": "SWORD",
|
|
||||||
"Wishing Star": "SWORD",
|
|
||||||
"Ultima Weapon": "SWORD",
|
|
||||||
"Diamond Dust": "SWORD",
|
|
||||||
"One-Winged Angel": "SWORD",
|
|
||||||
|
|
||||||
# Donald Staves
|
|
||||||
"Morning Star": "MAGIC_ROD",
|
|
||||||
"Shooting Star": "MAGIC_ROD",
|
|
||||||
"Warhammer": "MAGIC_ROD",
|
|
||||||
"Silver Mallet": "MAGIC_ROD",
|
|
||||||
"Grand Mallet": "MAGIC_ROD",
|
|
||||||
"Lord Fortune": "MAGIC_ROD",
|
|
||||||
"Violetta": "MAGIC_ROD",
|
|
||||||
"Save the Queen": "MAGIC_ROD",
|
|
||||||
"Wizard's Relic": "MAGIC_ROD",
|
|
||||||
"Meteor Strike": "MAGIC_ROD",
|
|
||||||
"Fantasista": "MAGIC_ROD",
|
|
||||||
|
|
||||||
# Goofy Shields
|
|
||||||
"Smasher": "SHIELD",
|
|
||||||
"Gigas Fist": "SHIELD",
|
|
||||||
"Save the King": "SHIELD",
|
|
||||||
"Defender": "SHIELD",
|
|
||||||
"Seven Elements": "SHIELD",
|
|
||||||
|
|
||||||
# Magic
|
|
||||||
"Progressive Fire": "MAGIC_ROD",
|
|
||||||
"Progressive Blizzard": "MAGIC_ROD",
|
|
||||||
"Progressive Thunder": "MAGIC_ROD",
|
|
||||||
"Progressive Cure": "MAGIC_ROD",
|
|
||||||
"Progressive Gravity": "MAGIC_ROD",
|
|
||||||
"Progressive Stop": "MAGIC_ROD",
|
|
||||||
"Progressive Aero": "MAGIC_ROD",
|
|
||||||
|
|
||||||
# Accessories / armor (Let's go with BLUE_TUNIC for these, these items are closer to RPG armor anyways)
|
|
||||||
"Chain": "BLUE_TUNIC",
|
|
||||||
"Ring": "BLUE_TUNIC",
|
|
||||||
"Band": "BLUE_TUNIC",
|
|
||||||
"Three Stars": "BLUE_TUNIC",
|
|
||||||
"Stud": "BLUE_TUNIC",
|
|
||||||
"Earring": "BLUE_TUNIC",
|
|
||||||
"Bangle": "BLUE_TUNIC",
|
|
||||||
"Armlet": "BLUE_TUNIC",
|
|
||||||
"Moogle Badge": "BLUE_TUNIC",
|
|
||||||
"Cosmic Arts": "BLUE_TUNIC",
|
|
||||||
"Heartguard": "BLUE_TUNIC",
|
|
||||||
"Crystal Crown": "BLUE_TUNIC",
|
|
||||||
"Ribbon": "BLUE_TUNIC",
|
|
||||||
"Brave Warrior": "BLUE_TUNIC",
|
|
||||||
"Ifrit's Horn": "BLUE_TUNIC",
|
|
||||||
"White Fang": "BLUE_TUNIC",
|
|
||||||
"Ray of Light": "BLUE_TUNIC",
|
|
||||||
"Circlet": "BLUE_TUNIC",
|
|
||||||
"Raven's Claw": "BLUE_TUNIC",
|
|
||||||
"Omega Arts": "BLUE_TUNIC",
|
|
||||||
"Royal Crown": "BLUE_TUNIC",
|
|
||||||
"Prime Cap": "BLUE_TUNIC",
|
|
||||||
"Belt": "BLUE_TUNIC",
|
|
||||||
"EXP Bracelet": "BLUE_TUNIC",
|
|
||||||
"EXP Necklace": "BLUE_TUNIC",
|
|
||||||
|
|
||||||
# Other
|
|
||||||
"Glide": "FEATHER",
|
|
||||||
"Ether": "MEDICINE",
|
|
||||||
"Elixir": "MEDICINE",
|
|
||||||
"Megalixir": "MEDICINE",
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ class LinksAwakeningWorld(World):
|
|||||||
phrases.update(ItemIconGuessing.GAME_SPECIFIC_PHRASES[foreign_game])
|
phrases.update(ItemIconGuessing.GAME_SPECIFIC_PHRASES[foreign_game])
|
||||||
|
|
||||||
for phrase, icon in phrases.items():
|
for phrase, icon in phrases.items():
|
||||||
if phrase.upper() in uppered:
|
if phrase in uppered:
|
||||||
return icon
|
return icon
|
||||||
# pattern for breaking down camelCase, also separates out digits
|
# pattern for breaking down camelCase, also separates out digits
|
||||||
pattern = re.compile(r"(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=\d)")
|
pattern = re.compile(r"(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=\d)")
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class PortalPlando(PlandoConnections):
|
|||||||
- entrance: Searing Crags
|
- entrance: Searing Crags
|
||||||
exit: Glacial Peak Portal
|
exit: Glacial Peak Portal
|
||||||
"""
|
"""
|
||||||
display_name = "Portal Plando Connections"
|
|
||||||
portals = [f"{portal} Portal" for portal in PORTALS]
|
portals = [f"{portal} Portal" for portal in PORTALS]
|
||||||
shop_points = [point for points in SHOP_POINTS.values() for point in points]
|
shop_points = [point for points in SHOP_POINTS.values() for point in points]
|
||||||
checkpoints = [point for points in CHECKPOINTS.values() for point in points]
|
checkpoints = [point for points in CHECKPOINTS.values() for point in points]
|
||||||
@@ -49,7 +48,6 @@ class TransitionPlando(PlandoConnections):
|
|||||||
exit: Dark Cave - Right
|
exit: Dark Cave - Right
|
||||||
direction: both
|
direction: both
|
||||||
"""
|
"""
|
||||||
display_name = "Transition Plando Connections"
|
|
||||||
entrances = frozenset(RANDOMIZED_CONNECTIONS.keys())
|
entrances = frozenset(RANDOMIZED_CONNECTIONS.keys())
|
||||||
exits = frozenset(RANDOMIZED_CONNECTIONS.values())
|
exits = frozenset(RANDOMIZED_CONNECTIONS.values())
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class MessengerRules:
|
|||||||
self.connection_rules = {
|
self.connection_rules = {
|
||||||
# from ToTHQ
|
# from ToTHQ
|
||||||
"Artificer's Portal":
|
"Artificer's Portal":
|
||||||
lambda state: state.has("Demon King Crown", self.player),
|
lambda state: state.has_all({"Demon King Crown", "Magic Firefly"}, self.player),
|
||||||
"Shrink Down":
|
"Shrink Down":
|
||||||
lambda state: state.has_all(NOTES, self.player),
|
lambda state: state.has_all(NOTES, self.player),
|
||||||
# the shop
|
# the shop
|
||||||
@@ -267,8 +267,6 @@ class MessengerRules:
|
|||||||
# tower of time
|
# tower of time
|
||||||
"Tower of Time Seal - Time Waster":
|
"Tower of Time Seal - Time Waster":
|
||||||
self.has_dart,
|
self.has_dart,
|
||||||
# corrupted future
|
|
||||||
"Corrupted Future - Key of Courage": lambda state: state.has("Magic Firefly", self.player),
|
|
||||||
# cloud ruins
|
# cloud ruins
|
||||||
"Time Warp Mega Shard":
|
"Time Warp Mega Shard":
|
||||||
lambda state: self.has_vertical(state) or self.can_dboost(state),
|
lambda state: self.has_vertical(state) or self.can_dboost(state),
|
||||||
@@ -372,7 +370,7 @@ class MessengerRules:
|
|||||||
add_rule(multiworld.get_entrance("Shrink Down", self.player), self.has_dart)
|
add_rule(multiworld.get_entrance("Shrink Down", self.player), self.has_dart)
|
||||||
multiworld.completion_condition[self.player] = lambda state: state.has("Do the Thing!", self.player)
|
multiworld.completion_condition[self.player] = lambda state: state.has("Do the Thing!", self.player)
|
||||||
if self.world.options.accessibility: # not locations accessibility
|
if self.world.options.accessibility: # not locations accessibility
|
||||||
set_self_locking_items(self.world)
|
set_self_locking_items(self.world, self.player)
|
||||||
|
|
||||||
|
|
||||||
class MessengerHardRules(MessengerRules):
|
class MessengerHardRules(MessengerRules):
|
||||||
@@ -532,11 +530,9 @@ class MessengerOOBRules(MessengerRules):
|
|||||||
self.world.options.accessibility.value = MessengerAccessibility.option_minimal
|
self.world.options.accessibility.value = MessengerAccessibility.option_minimal
|
||||||
|
|
||||||
|
|
||||||
def set_self_locking_items(world: "MessengerWorld") -> None:
|
def set_self_locking_items(world: "MessengerWorld", player: int) -> None:
|
||||||
# locations where these placements are always valid
|
# locations where these placements are always valid
|
||||||
allow_self_locking_items(world.get_location("Searing Crags - Key of Strength").parent_region, "Power Thistle")
|
allow_self_locking_items(world.get_location("Searing Crags - Key of Strength").parent_region, "Power Thistle")
|
||||||
allow_self_locking_items(world.get_location("Sunken Shrine - Key of Love"), "Sun Crest", "Moon Crest")
|
allow_self_locking_items(world.get_location("Sunken Shrine - Key of Love"), "Sun Crest", "Moon Crest")
|
||||||
|
allow_self_locking_items(world.get_location("Corrupted Future - Key of Courage").parent_region, "Demon King Crown")
|
||||||
allow_self_locking_items(world.get_location("Elemental Skylands Seal - Water"), "Currents Master")
|
allow_self_locking_items(world.get_location("Elemental Skylands Seal - Water"), "Currents Master")
|
||||||
if not world.options.shuffle_transitions:
|
|
||||||
allow_self_locking_items(world.get_location("Corrupted Future - Key of Courage").parent_region,
|
|
||||||
"Demon King Crown")
|
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
- New option `free_fly_blacklist` limits which cities can show up as a free fly location.
|
- New option `free_fly_blacklist` limits which cities can show up as a free fly location.
|
||||||
- Spoiler log and hint text for maps where a species can be found now use human-friendly labels.
|
- Spoiler log and hint text for maps where a species can be found now use human-friendly labels.
|
||||||
- Added many item and location groups based on item type, location type, and location geography.
|
- Added many item and location groups based on item type, location type, and location geography.
|
||||||
- Dexsanity locations for species which evolve via item use (Fire Stone, Metal Coat, etc.) now contribute those items to
|
|
||||||
the randomized item pool instead of Great Balls.
|
|
||||||
- Rock smash encounters are now randomized according to your wild pokemon randomization option. These encounters are
|
|
||||||
_not_ used for logical access (the seed will never require you to catch something through one of these encounters).
|
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ class PokemonRedBlueWorld(World):
|
|||||||
self.dexsanity_table = []
|
self.dexsanity_table = []
|
||||||
self.trainersanity_table = []
|
self.trainersanity_table = []
|
||||||
self.local_locs = []
|
self.local_locs = []
|
||||||
self.pc_item = None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage_assert_generate(cls, multiworld: MultiWorld):
|
def stage_assert_generate(cls, multiworld: MultiWorld):
|
||||||
@@ -290,9 +289,7 @@ class PokemonRedBlueWorld(World):
|
|||||||
multiworld.random.shuffle(itempool)
|
multiworld.random.shuffle(itempool)
|
||||||
unplaced_items = []
|
unplaced_items = []
|
||||||
for i, item in enumerate(itempool):
|
for i, item in enumerate(itempool):
|
||||||
if ((item.player == loc.player or (item.player in multiworld.groups
|
if item.player == loc.player and loc.can_fill(multiworld.state, item, False):
|
||||||
and loc.player in multiworld.groups[item.player]["players"]))
|
|
||||||
and loc.can_fill(multiworld.state, item, False)):
|
|
||||||
if item.advancement:
|
if item.advancement:
|
||||||
pool = progitempool
|
pool = progitempool
|
||||||
elif item.useful:
|
elif item.useful:
|
||||||
@@ -311,6 +308,8 @@ class PokemonRedBlueWorld(World):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
unplaced_items.append(item)
|
unplaced_items.append(item)
|
||||||
|
else:
|
||||||
|
raise FillError(f"Pokemon Red and Blue local item fill failed for player {loc.player}: could not place {item.name}")
|
||||||
progitempool += [item for item in unplaced_items if item.advancement]
|
progitempool += [item for item in unplaced_items if item.advancement]
|
||||||
usefulitempool += [item for item in unplaced_items if item.useful]
|
usefulitempool += [item for item in unplaced_items if item.useful]
|
||||||
filleritempool += [item for item in unplaced_items if (not item.advancement) and (not item.useful)]
|
filleritempool += [item for item in unplaced_items if (not item.advancement) and (not item.useful)]
|
||||||
@@ -447,12 +446,15 @@ class PokemonRedBlueWorld(World):
|
|||||||
if loc.item is None:
|
if loc.item is None:
|
||||||
locs.add(loc)
|
locs.add(loc)
|
||||||
|
|
||||||
|
if not self.options.key_items_only:
|
||||||
|
loc = self.multiworld.get_location("Player's House 2F - Player's PC", self.player)
|
||||||
|
if loc.item is None:
|
||||||
|
locs.add(loc)
|
||||||
|
|
||||||
for loc in sorted(locs):
|
for loc in sorted(locs):
|
||||||
if loc.name in self.options.priority_locations.value:
|
if loc.name in self.options.priority_locations.value:
|
||||||
add_item_rule(loc, lambda i: i.advancement)
|
add_item_rule(loc, lambda i: i.advancement)
|
||||||
add_item_rule(loc, lambda i: i.player == self.player
|
add_item_rule(loc, lambda i: i.player == self.player)
|
||||||
or (i.player in self.multiworld.groups
|
|
||||||
and self.player in self.multiworld.groups[i.player]["players"]))
|
|
||||||
if self.options.old_man == "early_parcel" and loc.name != "Player's House 2F - Player's PC":
|
if self.options.old_man == "early_parcel" and loc.name != "Player's House 2F - Player's PC":
|
||||||
add_item_rule(loc, lambda i: i.name != "Oak's Parcel")
|
add_item_rule(loc, lambda i: i.name != "Oak's Parcel")
|
||||||
|
|
||||||
@@ -518,14 +520,6 @@ class PokemonRedBlueWorld(World):
|
|||||||
else:
|
else:
|
||||||
raise Exception("Failed to remove corresponding item while deleting unreachable Dexsanity location")
|
raise Exception("Failed to remove corresponding item while deleting unreachable Dexsanity location")
|
||||||
|
|
||||||
if not self.options.key_items_only:
|
|
||||||
loc = self.multiworld.get_location("Player's House 2F - Player's PC", self.player)
|
|
||||||
# Absolutely cannot have another player's item
|
|
||||||
if loc.item is not None and loc.item.player != self.player:
|
|
||||||
self.multiworld.itempool.append(loc.item)
|
|
||||||
loc.item = None
|
|
||||||
loc.place_locked_item(self.pc_item)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage_post_fill(cls, multiworld):
|
def stage_post_fill(cls, multiworld):
|
||||||
# Convert all but one of each instance of a wild Pokemon to useful classification.
|
# Convert all but one of each instance of a wild Pokemon to useful classification.
|
||||||
|
|||||||
@@ -1579,18 +1579,6 @@ def create_regions(world):
|
|||||||
world.item_pool.append(item)
|
world.item_pool.append(item)
|
||||||
|
|
||||||
world.random.shuffle(world.item_pool)
|
world.random.shuffle(world.item_pool)
|
||||||
if not world.options.key_items_only:
|
|
||||||
if "Player's House 2F - Player's PC" in world.options.exclude_locations:
|
|
||||||
acceptable_item = lambda item: item.excludable
|
|
||||||
elif "Player's House 2F - Player's PC" in world.options.priority_locations:
|
|
||||||
acceptable_item = lambda item: item.advancement
|
|
||||||
else:
|
|
||||||
acceptable_item = lambda item: True
|
|
||||||
for i, item in enumerate(world.item_pool):
|
|
||||||
if acceptable_item(item):
|
|
||||||
world.pc_item = world.item_pool.pop(i)
|
|
||||||
break
|
|
||||||
|
|
||||||
advancement_items = [item.name for item in world.item_pool if item.advancement] \
|
advancement_items = [item.name for item in world.item_pool if item.advancement] \
|
||||||
+ [item.name for item in world.multiworld.precollected_items[world.player] if
|
+ [item.name for item in world.multiworld.precollected_items[world.player] if
|
||||||
item.advancement]
|
item.advancement]
|
||||||
|
|||||||
@@ -150,26 +150,6 @@ sample_chao_names = [
|
|||||||
"Hubert",
|
"Hubert",
|
||||||
"Corvus",
|
"Corvus",
|
||||||
"Nigel",
|
"Nigel",
|
||||||
"Benjamin",
|
|
||||||
"Gooey",
|
|
||||||
"Maddy",
|
|
||||||
"AFGNCAAP",
|
|
||||||
"Reinhardt",
|
|
||||||
"Claire",
|
|
||||||
"Yoshi",
|
|
||||||
"Peasley",
|
|
||||||
"Faux",
|
|
||||||
"Naija",
|
|
||||||
"Kaiba",
|
|
||||||
"Hat Kid",
|
|
||||||
"TzTokJad",
|
|
||||||
"Sora",
|
|
||||||
"WoodMan",
|
|
||||||
"Yachty",
|
|
||||||
"Grieve",
|
|
||||||
"Portia",
|
|
||||||
"Graves",
|
|
||||||
"Kaycee",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
totally_real_item_names = [
|
totally_real_item_names = [
|
||||||
@@ -260,35 +240,6 @@ totally_real_item_names = [
|
|||||||
"Ladder",
|
"Ladder",
|
||||||
|
|
||||||
"Visible Dots",
|
"Visible Dots",
|
||||||
|
|
||||||
"CooCoo",
|
|
||||||
|
|
||||||
"Blueberry",
|
|
||||||
|
|
||||||
"Ear of Luigi",
|
|
||||||
|
|
||||||
"Mega Nut",
|
|
||||||
|
|
||||||
"DUELIST ALLIANCE",
|
|
||||||
"DUEL OVERLOAD",
|
|
||||||
"POWER OF THE ELEMENTS",
|
|
||||||
"S:P Little Knight",
|
|
||||||
"Red-Eyes Dark Dragoon",
|
|
||||||
|
|
||||||
"Fire Hat",
|
|
||||||
|
|
||||||
"Area: Taverly",
|
|
||||||
"Area: Meiyerditch",
|
|
||||||
"Fire Cape",
|
|
||||||
|
|
||||||
"Donald Zeta Flare",
|
|
||||||
|
|
||||||
"Category One of a Kind",
|
|
||||||
"Category Fuller House",
|
|
||||||
|
|
||||||
"Passive Camoflage",
|
|
||||||
|
|
||||||
"Earth Card",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
all_exits = [
|
all_exits = [
|
||||||
|
|||||||
@@ -1,82 +1,6 @@
|
|||||||
# Sonic Adventure 2 Battle - Changelog
|
# Sonic Adventure 2 Battle - Changelog
|
||||||
|
|
||||||
|
|
||||||
## v2.4 - Minigame Madness
|
|
||||||
|
|
||||||
### Features:
|
|
||||||
- New Goal
|
|
||||||
- Minigame Madness
|
|
||||||
- Win a certain number of each type of Minigame Trap, then defeat the Finalhazard to win!
|
|
||||||
- How many of each Minigame are required can be set by an Option
|
|
||||||
- When the required amount of a Minigame has been received, that Minigame can be replayed in the Chao World Lobby
|
|
||||||
- New optional Location Checks
|
|
||||||
- Bigsanity
|
|
||||||
- Go fishing with Big in each stage for a Location Check
|
|
||||||
- Itemboxsanity
|
|
||||||
- Either Extra Life Boxes or All Item Boxes
|
|
||||||
- New Items
|
|
||||||
- New Traps
|
|
||||||
- Literature Trap
|
|
||||||
- Controller Drift Trap
|
|
||||||
- Poison Trap
|
|
||||||
- Bee Trap
|
|
||||||
- New Minigame Traps
|
|
||||||
- Breakout Trap
|
|
||||||
- Fishing Trap
|
|
||||||
- Trivia Trap
|
|
||||||
- Pokemon Trivia Trap
|
|
||||||
- Pokemon Count Trap
|
|
||||||
- Number Sequence Trap
|
|
||||||
- Light Up Path Trap
|
|
||||||
- Pinball Trap
|
|
||||||
- Math Quiz Trap
|
|
||||||
- Snake Trap
|
|
||||||
- Input Sequence Trap
|
|
||||||
- Trap Link
|
|
||||||
- When you receive a trap, you send a copy of it to every other player with Trap Link enabled
|
|
||||||
- Boss Gate Plando
|
|
||||||
- Expert Logic Difficulty
|
|
||||||
- Use at your own risk. This difficulty requires complete mastery of SA2.
|
|
||||||
- Missions can now be enabled and disabled per-character, instead of just per-style
|
|
||||||
- Minigame Difficulty can now be set to "Chaos", which selects a new difficulty randomly per-trap received
|
|
||||||
|
|
||||||
### Quality of Life:
|
|
||||||
- Gate Stages and Mission Orders are now displayed in the spoiler log
|
|
||||||
- Additional play stats are saved and displayed with the randomizer credits
|
|
||||||
- Stage Locations progress UI now displays in multiple pages when Itemboxsanity is enabled
|
|
||||||
- Current stage mission order and progress are now shown when paused in-level
|
|
||||||
- Chaos Emeralds are now shown when paused in-level
|
|
||||||
- Location Name Groups were created
|
|
||||||
- Moved SA2B to the new Options system
|
|
||||||
- Option Presets were created
|
|
||||||
- Error Messages are more obvious
|
|
||||||
|
|
||||||
### Bug Fixes:
|
|
||||||
- Added missing `Dry Lagoon - 12 Animals` location
|
|
||||||
- Flying Dog boss should no longer crash when you have done at least 3 Intermediate Kart Races
|
|
||||||
- Invincibility can no longer be received in the King Boom Boo fight, preventing a crash
|
|
||||||
- Chaos Emeralds should no longer disproportionately end up in Cannon's Core or the final Level Gate
|
|
||||||
- Going into submenus from the pause menu should no longer reset traps
|
|
||||||
- `Sonic - Magic Gloves` are now plural
|
|
||||||
- Junk items will no longer cause a crash when in a falling state
|
|
||||||
- Chao Garden:
|
|
||||||
- Prevent races from occasionally becoming uncompletable when using the "Prize Only" option
|
|
||||||
- Properly allow Hero Chao to participate in Dark Races
|
|
||||||
- Don't allow the Chao Garden to send locations when connected to an invalid server
|
|
||||||
- Prevent the Chao Garden from resetting your life count
|
|
||||||
- Fix Chao World Entrance Shuffle causing inaccessible Neutral Garden
|
|
||||||
- Fix pressing the 'B' button to take you to the proper location in Chao World Entrance Shuffle
|
|
||||||
- Prevent Chao Karate progress icon overflow
|
|
||||||
- Prevent changing Chao Timescale while paused or while a Minigame is active
|
|
||||||
- Logic Fixes:
|
|
||||||
- `Mission Street - Chao Key 1` (Hard Logic) now requires no upgrades
|
|
||||||
- `Mission Street - Chao Key 2` (Hard Logic) now requires no upgrades
|
|
||||||
- `Crazy Gadget - Hidden 1` (Standard Logic) now requires `Sonic - Bounce Bracelet` instead of `Sonic - Light Shoes`
|
|
||||||
- `Lost Colony - Hidden 1` (Standard Logic) now requires `Eggman - Jet Engine`
|
|
||||||
- `Mad Space - Gold Beetle` (Standard Logic) now only requires `Rouge - Iron Boots`
|
|
||||||
- `Cosmic Wall - Gold Beetle` (Standard and Hard Logic) now only requires `Eggman - Jet Engine`
|
|
||||||
|
|
||||||
|
|
||||||
## v2.3 - The Chao Update
|
## v2.3 - The Chao Update
|
||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from worlds.AutoWorld import World
|
from worlds.AutoWorld import World
|
||||||
|
|
||||||
from .Names import LocationName
|
speed_characters_1 = "Sonic vs Shadow 1"
|
||||||
from .Options import GateBossPlando
|
speed_characters_2 = "Sonic vs Shadow 2"
|
||||||
|
mech_characters_1 = "Tails vs Eggman 1"
|
||||||
|
mech_characters_2 = "Tails vs Eggman 2"
|
||||||
speed_characters_1 = "sonic vs shadow 1"
|
hunt_characters_1 = "Knuckles vs Rouge 1"
|
||||||
speed_characters_2 = "sonic vs shadow 2"
|
big_foot = "F-6t BIG FOOT"
|
||||||
mech_characters_1 = "tails vs eggman 1"
|
hot_shot = "B-3x HOT SHOT"
|
||||||
mech_characters_2 = "tails vs eggman 2"
|
flying_dog = "R-1/A FLYING DOG"
|
||||||
hunt_characters_1 = "knuckles vs rouge 1"
|
egg_golem_sonic = "Egg Golem (Sonic)"
|
||||||
big_foot = "big foot"
|
egg_golem_eggman = "Egg Golem (Eggman)"
|
||||||
hot_shot = "hot shot"
|
king_boom_boo = "King Boom Boo"
|
||||||
flying_dog = "flying dog"
|
|
||||||
egg_golem_sonic = "egg golem (sonic)"
|
|
||||||
egg_golem_eggman = "egg golem (eggman)"
|
|
||||||
king_boom_boo = "king boom boo"
|
|
||||||
|
|
||||||
gate_bosses_no_requirements_table = {
|
gate_bosses_no_requirements_table = {
|
||||||
speed_characters_1: 0,
|
speed_characters_1: 0,
|
||||||
@@ -50,83 +45,44 @@ all_gate_bosses_table = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
boss_id_to_name = {
|
|
||||||
0: "Sonic vs Shadow 1",
|
|
||||||
1: "Sonic vs Shadow 2",
|
|
||||||
2: "Tails vs Eggman 1",
|
|
||||||
3: "Tails vs Eggman 2",
|
|
||||||
4: "Knuckles vs Rouge 1",
|
|
||||||
5: "F-6t BIG FOOT",
|
|
||||||
6: "B-3x HOT SHOT",
|
|
||||||
7: "R-1/A FLYING DOG",
|
|
||||||
8: "Egg Golem (Sonic)",
|
|
||||||
9: "Egg Golem (Eggman)",
|
|
||||||
10: "King Boom Boo",
|
|
||||||
11: "Sonic vs Shadow 1",
|
|
||||||
12: "Sonic vs Shadow 2",
|
|
||||||
13: "Tails vs Eggman 1",
|
|
||||||
14: "Tails vs Eggman 2",
|
|
||||||
15: "Knuckles vs Rouge 1",
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_boss_name(boss: int):
|
def get_boss_name(boss: int):
|
||||||
return boss_id_to_name[boss]
|
for key, value in gate_bosses_no_requirements_table.items():
|
||||||
|
if value == boss:
|
||||||
|
return key
|
||||||
|
for key, value in gate_bosses_with_requirements_table.items():
|
||||||
|
if value == boss:
|
||||||
|
return key
|
||||||
|
for key, value in extra_boss_rush_bosses_table.items():
|
||||||
|
if value == boss:
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
def boss_has_requirement(boss: int):
|
def boss_has_requirement(boss: int):
|
||||||
return boss >= len(gate_bosses_no_requirements_table)
|
return boss >= len(gate_bosses_no_requirements_table)
|
||||||
|
|
||||||
|
|
||||||
def get_gate_bosses(world: World):
|
def get_gate_bosses(multiworld: MultiWorld, world: World):
|
||||||
selected_bosses: typing.List[int] = []
|
selected_bosses: typing.List[int] = []
|
||||||
boss_gates: typing.List[int] = []
|
boss_gates: typing.List[int] = []
|
||||||
available_bosses: typing.List[str] = list(gate_bosses_no_requirements_table.keys())
|
available_bosses: typing.List[str] = list(gate_bosses_no_requirements_table.keys())
|
||||||
world.random.shuffle(available_bosses)
|
multiworld.random.shuffle(available_bosses)
|
||||||
|
halfway = False
|
||||||
gate_boss_plando: typing.Union[int, str] = world.options.gate_boss_plando.value
|
|
||||||
plando_bosses = ["None", "None", "None", "None", "None"]
|
|
||||||
if isinstance(gate_boss_plando, str):
|
|
||||||
# boss plando
|
|
||||||
options = gate_boss_plando.split(";")
|
|
||||||
gate_boss_plando = GateBossPlando.options[options.pop()]
|
|
||||||
for option in options:
|
|
||||||
if "-" in option:
|
|
||||||
loc, boss = option.split("-")
|
|
||||||
boss_num = LocationName.boss_gate_names[loc]
|
|
||||||
|
|
||||||
if boss_num >= world.options.number_of_level_gates.value:
|
|
||||||
# Don't reject bosses plando'd into gate bosses that won't exist
|
|
||||||
pass
|
|
||||||
|
|
||||||
if boss in plando_bosses:
|
|
||||||
# TODO: Raise error here. Duplicates not allowed
|
|
||||||
pass
|
|
||||||
|
|
||||||
plando_bosses[boss_num] = boss
|
|
||||||
|
|
||||||
if boss in available_bosses:
|
|
||||||
available_bosses.remove(boss)
|
|
||||||
|
|
||||||
for x in range(world.options.number_of_level_gates):
|
for x in range(world.options.number_of_level_gates):
|
||||||
if ("king boom boo" not in selected_bosses) and ("king boom boo" not in available_bosses) and ((x + 1) / world.options.number_of_level_gates) > 0.5:
|
if (not halfway) and ((x + 1) / world.options.number_of_level_gates) > 0.5:
|
||||||
available_bosses.extend(gate_bosses_with_requirements_table)
|
available_bosses.extend(gate_bosses_with_requirements_table)
|
||||||
world.random.shuffle(available_bosses)
|
multiworld.random.shuffle(available_bosses)
|
||||||
|
halfway = True
|
||||||
chosen_boss = available_bosses[0]
|
selected_bosses.append(all_gate_bosses_table[available_bosses[0]])
|
||||||
if plando_bosses[x] != "None":
|
|
||||||
available_bosses.append(plando_bosses[x])
|
|
||||||
chosen_boss = plando_bosses[x]
|
|
||||||
|
|
||||||
selected_bosses.append(all_gate_bosses_table[chosen_boss])
|
|
||||||
boss_gates.append(x + 1)
|
boss_gates.append(x + 1)
|
||||||
available_bosses.remove(chosen_boss)
|
available_bosses.remove(available_bosses[0])
|
||||||
|
|
||||||
bosses: typing.Dict[int, int] = dict(zip(boss_gates, selected_bosses))
|
bosses: typing.Dict[int, int] = dict(zip(boss_gates, selected_bosses))
|
||||||
|
|
||||||
return bosses
|
return bosses
|
||||||
|
|
||||||
|
|
||||||
def get_boss_rush_bosses(world: World):
|
def get_boss_rush_bosses(multiworld: MultiWorld, world: World):
|
||||||
|
|
||||||
if world.options.boss_rush_shuffle == 0:
|
if world.options.boss_rush_shuffle == 0:
|
||||||
boss_list_o = list(range(0, 16))
|
boss_list_o = list(range(0, 16))
|
||||||
@@ -136,21 +92,21 @@ def get_boss_rush_bosses(world: World):
|
|||||||
elif world.options.boss_rush_shuffle == 1:
|
elif world.options.boss_rush_shuffle == 1:
|
||||||
boss_list_o = list(range(0, 16))
|
boss_list_o = list(range(0, 16))
|
||||||
boss_list_s = boss_list_o.copy()
|
boss_list_s = boss_list_o.copy()
|
||||||
world.random.shuffle(boss_list_s)
|
multiworld.random.shuffle(boss_list_s)
|
||||||
|
|
||||||
return dict(zip(boss_list_o, boss_list_s))
|
return dict(zip(boss_list_o, boss_list_s))
|
||||||
elif world.options.boss_rush_shuffle == 2:
|
elif world.options.boss_rush_shuffle == 2:
|
||||||
boss_list_o = list(range(0, 16))
|
boss_list_o = list(range(0, 16))
|
||||||
boss_list_s = [world.random.choice(boss_list_o) for i in range(0, 16)]
|
boss_list_s = [multiworld.random.choice(boss_list_o) for i in range(0, 16)]
|
||||||
if 10 not in boss_list_s:
|
if 10 not in boss_list_s:
|
||||||
boss_list_s[world.random.randint(0, 15)] = 10
|
boss_list_s[multiworld.random.randint(0, 15)] = 10
|
||||||
|
|
||||||
return dict(zip(boss_list_o, boss_list_s))
|
return dict(zip(boss_list_o, boss_list_s))
|
||||||
elif world.options.boss_rush_shuffle == 3:
|
elif world.options.boss_rush_shuffle == 3:
|
||||||
boss_list_o = list(range(0, 16))
|
boss_list_o = list(range(0, 16))
|
||||||
boss_list_s = [world.random.choice(boss_list_o)] * len(boss_list_o)
|
boss_list_s = [multiworld.random.choice(boss_list_o)] * len(boss_list_o)
|
||||||
if 10 not in boss_list_s:
|
if 10 not in boss_list_s:
|
||||||
boss_list_s[world.random.randint(0, 15)] = 10
|
boss_list_s[multiworld.random.randint(0, 15)] = 10
|
||||||
|
|
||||||
return dict(zip(boss_list_o, boss_list_s))
|
return dict(zip(boss_list_o, boss_list_s))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import typing
|
|||||||
|
|
||||||
from BaseClasses import Item, ItemClassification
|
from BaseClasses import Item, ItemClassification
|
||||||
from .Names import ItemName
|
from .Names import ItemName
|
||||||
|
from worlds.alttp import ALTTPWorld
|
||||||
|
|
||||||
|
|
||||||
class ItemData(typing.NamedTuple):
|
class ItemData(typing.NamedTuple):
|
||||||
@@ -13,7 +14,7 @@ class ItemData(typing.NamedTuple):
|
|||||||
|
|
||||||
|
|
||||||
class SA2BItem(Item):
|
class SA2BItem(Item):
|
||||||
game: str = "Sonic Adventure 2 Battle"
|
game: str = "Sonic Adventure 2: Battle"
|
||||||
|
|
||||||
def __init__(self, name, classification: ItemClassification, code: int = None, player: int = None):
|
def __init__(self, name, classification: ItemClassification, code: int = None, player: int = None):
|
||||||
super(SA2BItem, self).__init__(name, classification, code, player)
|
super(SA2BItem, self).__init__(name, classification, code, player)
|
||||||
@@ -72,36 +73,19 @@ junk_table = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trap_table = {
|
trap_table = {
|
||||||
ItemName.omochao_trap: ItemData(0xFF0030, False, True),
|
ItemName.omochao_trap: ItemData(0xFF0030, False, True),
|
||||||
ItemName.timestop_trap: ItemData(0xFF0031, False, True),
|
ItemName.timestop_trap: ItemData(0xFF0031, False, True),
|
||||||
ItemName.confuse_trap: ItemData(0xFF0032, False, True),
|
ItemName.confuse_trap: ItemData(0xFF0032, False, True),
|
||||||
ItemName.tiny_trap: ItemData(0xFF0033, False, True),
|
ItemName.tiny_trap: ItemData(0xFF0033, False, True),
|
||||||
ItemName.gravity_trap: ItemData(0xFF0034, False, True),
|
ItemName.gravity_trap: ItemData(0xFF0034, False, True),
|
||||||
ItemName.exposition_trap: ItemData(0xFF0035, False, True),
|
ItemName.exposition_trap: ItemData(0xFF0035, False, True),
|
||||||
#ItemName.darkness_trap: ItemData(0xFF0036, False, True),
|
#ItemName.darkness_trap: ItemData(0xFF0036, False, True),
|
||||||
ItemName.ice_trap: ItemData(0xFF0037, False, True),
|
ItemName.ice_trap: ItemData(0xFF0037, False, True),
|
||||||
ItemName.slow_trap: ItemData(0xFF0038, False, True),
|
ItemName.slow_trap: ItemData(0xFF0038, False, True),
|
||||||
ItemName.cutscene_trap: ItemData(0xFF0039, False, True),
|
ItemName.cutscene_trap: ItemData(0xFF0039, False, True),
|
||||||
ItemName.reverse_trap: ItemData(0xFF003A, False, True),
|
ItemName.reverse_trap: ItemData(0xFF003A, False, True),
|
||||||
ItemName.literature_trap: ItemData(0xFF003B, False, True),
|
|
||||||
ItemName.controller_drift_trap: ItemData(0xFF003C, False, True),
|
|
||||||
ItemName.poison_trap: ItemData(0xFF003D, False, True),
|
|
||||||
ItemName.bee_trap: ItemData(0xFF003E, False, True),
|
|
||||||
}
|
|
||||||
|
|
||||||
minigame_trap_table = {
|
ItemName.pong_trap: ItemData(0xFF0050, False, True),
|
||||||
ItemName.pong_trap: ItemData(0xFF0050, False, True),
|
|
||||||
ItemName.breakout_trap: ItemData(0xFF0051, False, True),
|
|
||||||
ItemName.fishing_trap: ItemData(0xFF0052, False, True),
|
|
||||||
ItemName.trivia_trap: ItemData(0xFF0053, False, True),
|
|
||||||
ItemName.pokemon_trivia_trap: ItemData(0xFF0054, False, True),
|
|
||||||
ItemName.pokemon_count_trap: ItemData(0xFF0055, False, True),
|
|
||||||
ItemName.number_sequence_trap: ItemData(0xFF0056, False, True),
|
|
||||||
ItemName.light_up_path_trap: ItemData(0xFF0057, False, True),
|
|
||||||
ItemName.pinball_trap: ItemData(0xFF0058, False, True),
|
|
||||||
ItemName.math_quiz_trap: ItemData(0xFF0059, False, True),
|
|
||||||
ItemName.snake_trap: ItemData(0xFF005A, False, True),
|
|
||||||
ItemName.input_sequence_trap: ItemData(0xFF005B, False, True),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emeralds_table = {
|
emeralds_table = {
|
||||||
@@ -251,7 +235,7 @@ chaos_drives_table = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event_table = {
|
event_table = {
|
||||||
ItemName.maria: ItemData(None, True),
|
ItemName.maria: ItemData(0xFF001D, True),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Complete item table.
|
# Complete item table.
|
||||||
@@ -260,7 +244,6 @@ item_table = {
|
|||||||
**upgrades_table,
|
**upgrades_table,
|
||||||
**junk_table,
|
**junk_table,
|
||||||
**trap_table,
|
**trap_table,
|
||||||
**minigame_trap_table,
|
|
||||||
**emeralds_table,
|
**emeralds_table,
|
||||||
**eggs_table,
|
**eggs_table,
|
||||||
**fruits_table,
|
**fruits_table,
|
||||||
@@ -268,6 +251,7 @@ item_table = {
|
|||||||
**hats_table,
|
**hats_table,
|
||||||
**animals_table,
|
**animals_table,
|
||||||
**chaos_drives_table,
|
**chaos_drives_table,
|
||||||
|
**event_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
|
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
|
||||||
@@ -279,12 +263,7 @@ item_groups: typing.Dict[str, str] = {
|
|||||||
"Seeds": list(seeds_table.keys()),
|
"Seeds": list(seeds_table.keys()),
|
||||||
"Hats": list(hats_table.keys()),
|
"Hats": list(hats_table.keys()),
|
||||||
"Traps": list(trap_table.keys()),
|
"Traps": list(trap_table.keys()),
|
||||||
"Minigames": list(minigame_trap_table.keys()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
ALTTPWorld.pedestal_credit_texts[item_table[ItemName.sonic_light_shoes].code] = "and the Soap Shoes"
|
||||||
from worlds.alttp import ALTTPWorld
|
ALTTPWorld.pedestal_credit_texts[item_table[ItemName.shadow_air_shoes].code] = "and the Soap Shoes"
|
||||||
ALTTPWorld.pedestal_credit_texts[item_table[ItemName.sonic_light_shoes].code] = "and the Soap Shoes"
|
|
||||||
ALTTPWorld.pedestal_credit_texts[item_table[ItemName.shadow_air_shoes].code] = "and the Soap Shoes"
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -119,14 +119,11 @@ mission_orders: typing.List[typing.List[int]] = [
|
|||||||
[4, 5, 3, 2, 1],
|
[4, 5, 3, 2, 1],
|
||||||
]
|
]
|
||||||
|
|
||||||
### 0: Sonic
|
### 0: Speed
|
||||||
### 1: Tails
|
### 1: Mech
|
||||||
### 2: Knuckles
|
### 2: Hunt
|
||||||
### 3: Shadow
|
### 3: Kart
|
||||||
### 4: Eggman
|
### 4: Cannon's Core
|
||||||
### 5: Rouge
|
|
||||||
### 6: Kart
|
|
||||||
### 7: Cannon's Core
|
|
||||||
level_styles: typing.List[int] = [
|
level_styles: typing.List[int] = [
|
||||||
0,
|
0,
|
||||||
2,
|
2,
|
||||||
@@ -136,7 +133,7 @@ level_styles: typing.List[int] = [
|
|||||||
2,
|
2,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
6,
|
3,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
2,
|
2,
|
||||||
@@ -145,22 +142,22 @@ level_styles: typing.List[int] = [
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
|
||||||
4,
|
1,
|
||||||
5,
|
2,
|
||||||
4,
|
1,
|
||||||
3,
|
0,
|
||||||
5,
|
2,
|
||||||
4,
|
1,
|
||||||
4,
|
1,
|
||||||
5,
|
2,
|
||||||
3,
|
0,
|
||||||
6,
|
|
||||||
3,
|
|
||||||
5,
|
|
||||||
4,
|
|
||||||
3,
|
3,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
|
||||||
7,
|
4,
|
||||||
]
|
]
|
||||||
|
|
||||||
stage_name_prefixes: typing.List[str] = [
|
stage_name_prefixes: typing.List[str] = [
|
||||||
@@ -204,33 +201,21 @@ def get_mission_count_table(multiworld: MultiWorld, world: World, player: int):
|
|||||||
for level in range(31):
|
for level in range(31):
|
||||||
mission_count_table[level] = 0
|
mission_count_table[level] = 0
|
||||||
else:
|
else:
|
||||||
sonic_active_missions = 1
|
speed_active_missions = 1
|
||||||
tails_active_missions = 1
|
mech_active_missions = 1
|
||||||
knuckles_active_missions = 1
|
hunt_active_missions = 1
|
||||||
shadow_active_missions = 1
|
|
||||||
eggman_active_missions = 1
|
|
||||||
rouge_active_missions = 1
|
|
||||||
kart_active_missions = 1
|
kart_active_missions = 1
|
||||||
cannons_core_active_missions = 1
|
cannons_core_active_missions = 1
|
||||||
|
|
||||||
for i in range(2,6):
|
for i in range(2,6):
|
||||||
if getattr(world.options, "sonic_mission_" + str(i), None):
|
if getattr(world.options, "speed_mission_" + str(i), None):
|
||||||
sonic_active_missions += 1
|
speed_active_missions += 1
|
||||||
|
|
||||||
if getattr(world.options, "tails_mission_" + str(i), None):
|
if getattr(world.options, "mech_mission_" + str(i), None):
|
||||||
tails_active_missions += 1
|
mech_active_missions += 1
|
||||||
|
|
||||||
if getattr(world.options, "knuckles_mission_" + str(i), None):
|
if getattr(world.options, "hunt_mission_" + str(i), None):
|
||||||
knuckles_active_missions += 1
|
hunt_active_missions += 1
|
||||||
|
|
||||||
if getattr(world.options, "shadow_mission_" + str(i), None):
|
|
||||||
shadow_active_missions += 1
|
|
||||||
|
|
||||||
if getattr(world.options, "eggman_mission_" + str(i), None):
|
|
||||||
eggman_active_missions += 1
|
|
||||||
|
|
||||||
if getattr(world.options, "rouge_mission_" + str(i), None):
|
|
||||||
rouge_active_missions += 1
|
|
||||||
|
|
||||||
if getattr(world.options, "kart_mission_" + str(i), None):
|
if getattr(world.options, "kart_mission_" + str(i), None):
|
||||||
kart_active_missions += 1
|
kart_active_missions += 1
|
||||||
@@ -238,22 +223,16 @@ def get_mission_count_table(multiworld: MultiWorld, world: World, player: int):
|
|||||||
if getattr(world.options, "cannons_core_mission_" + str(i), None):
|
if getattr(world.options, "cannons_core_mission_" + str(i), None):
|
||||||
cannons_core_active_missions += 1
|
cannons_core_active_missions += 1
|
||||||
|
|
||||||
sonic_active_missions = min(sonic_active_missions, world.options.sonic_mission_count.value)
|
speed_active_missions = min(speed_active_missions, world.options.speed_mission_count.value)
|
||||||
tails_active_missions = min(tails_active_missions, world.options.tails_mission_count.value)
|
mech_active_missions = min(mech_active_missions, world.options.mech_mission_count.value)
|
||||||
knuckles_active_missions = min(knuckles_active_missions, world.options.knuckles_mission_count.value)
|
hunt_active_missions = min(hunt_active_missions, world.options.hunt_mission_count.value)
|
||||||
shadow_active_missions = min(shadow_active_missions, world.options.sonic_mission_count.value)
|
|
||||||
eggman_active_missions = min(eggman_active_missions, world.options.eggman_mission_count.value)
|
|
||||||
rouge_active_missions = min(rouge_active_missions, world.options.rouge_mission_count.value)
|
|
||||||
kart_active_missions = min(kart_active_missions, world.options.kart_mission_count.value)
|
kart_active_missions = min(kart_active_missions, world.options.kart_mission_count.value)
|
||||||
cannons_core_active_missions = min(cannons_core_active_missions, world.options.cannons_core_mission_count.value)
|
cannons_core_active_missions = min(cannons_core_active_missions, world.options.cannons_core_mission_count.value)
|
||||||
|
|
||||||
active_missions: typing.List[typing.List[int]] = [
|
active_missions: typing.List[typing.List[int]] = [
|
||||||
sonic_active_missions,
|
speed_active_missions,
|
||||||
tails_active_missions,
|
mech_active_missions,
|
||||||
knuckles_active_missions,
|
hunt_active_missions,
|
||||||
shadow_active_missions,
|
|
||||||
eggman_active_missions,
|
|
||||||
rouge_active_missions,
|
|
||||||
kart_active_missions,
|
kart_active_missions,
|
||||||
cannons_core_active_missions
|
cannons_core_active_missions
|
||||||
]
|
]
|
||||||
@@ -273,34 +252,22 @@ def get_mission_table(multiworld: MultiWorld, world: World, player: int):
|
|||||||
for level in range(31):
|
for level in range(31):
|
||||||
mission_table[level] = 0
|
mission_table[level] = 0
|
||||||
else:
|
else:
|
||||||
sonic_active_missions: typing.List[int] = [1]
|
speed_active_missions: typing.List[int] = [1]
|
||||||
tails_active_missions: typing.List[int] = [1]
|
mech_active_missions: typing.List[int] = [1]
|
||||||
knuckles_active_missions: typing.List[int] = [1]
|
hunt_active_missions: typing.List[int] = [1]
|
||||||
shadow_active_missions: typing.List[int] = [1]
|
|
||||||
eggman_active_missions: typing.List[int] = [1]
|
|
||||||
rouge_active_missions: typing.List[int] = [1]
|
|
||||||
kart_active_missions: typing.List[int] = [1]
|
kart_active_missions: typing.List[int] = [1]
|
||||||
cannons_core_active_missions: typing.List[int] = [1]
|
cannons_core_active_missions: typing.List[int] = [1]
|
||||||
|
|
||||||
# Add included missions
|
# Add included missions
|
||||||
for i in range(2,6):
|
for i in range(2,6):
|
||||||
if getattr(world.options, "sonic_mission_" + str(i), None):
|
if getattr(world.options, "speed_mission_" + str(i), None):
|
||||||
sonic_active_missions.append(i)
|
speed_active_missions.append(i)
|
||||||
|
|
||||||
if getattr(world.options, "tails_mission_" + str(i), None):
|
if getattr(world.options, "mech_mission_" + str(i), None):
|
||||||
tails_active_missions.append(i)
|
mech_active_missions.append(i)
|
||||||
|
|
||||||
if getattr(world.options, "knuckles_mission_" + str(i), None):
|
if getattr(world.options, "hunt_mission_" + str(i), None):
|
||||||
knuckles_active_missions.append(i)
|
hunt_active_missions.append(i)
|
||||||
|
|
||||||
if getattr(world.options, "shadow_mission_" + str(i), None):
|
|
||||||
shadow_active_missions.append(i)
|
|
||||||
|
|
||||||
if getattr(world.options, "eggman_mission_" + str(i), None):
|
|
||||||
eggman_active_missions.append(i)
|
|
||||||
|
|
||||||
if getattr(world.options, "rouge_mission_" + str(i), None):
|
|
||||||
rouge_active_missions.append(i)
|
|
||||||
|
|
||||||
if getattr(world.options, "kart_mission_" + str(i), None):
|
if getattr(world.options, "kart_mission_" + str(i), None):
|
||||||
kart_active_missions.append(i)
|
kart_active_missions.append(i)
|
||||||
@@ -309,12 +276,9 @@ def get_mission_table(multiworld: MultiWorld, world: World, player: int):
|
|||||||
cannons_core_active_missions.append(i)
|
cannons_core_active_missions.append(i)
|
||||||
|
|
||||||
active_missions: typing.List[typing.List[int]] = [
|
active_missions: typing.List[typing.List[int]] = [
|
||||||
sonic_active_missions,
|
speed_active_missions,
|
||||||
tails_active_missions,
|
mech_active_missions,
|
||||||
knuckles_active_missions,
|
hunt_active_missions,
|
||||||
shadow_active_missions,
|
|
||||||
eggman_active_missions,
|
|
||||||
rouge_active_missions,
|
|
||||||
kart_active_missions,
|
kart_active_missions,
|
||||||
cannons_core_active_missions
|
cannons_core_active_missions
|
||||||
]
|
]
|
||||||
@@ -364,60 +328,13 @@ def get_mission_table(multiworld: MultiWorld, world: World, player: int):
|
|||||||
|
|
||||||
|
|
||||||
def get_first_and_last_cannons_core_missions(mission_map: typing.Dict[int, int], mission_count_map: typing.Dict[int, int]):
|
def get_first_and_last_cannons_core_missions(mission_map: typing.Dict[int, int], mission_count_map: typing.Dict[int, int]):
|
||||||
mission_count = mission_count_map[30]
|
mission_count = mission_count_map[30]
|
||||||
mission_order: typing.List[int] = mission_orders[mission_map[30]]
|
mission_order: typing.List[int] = mission_orders[mission_map[30]]
|
||||||
stage_prefix: str = stage_name_prefixes[30]
|
stage_prefix: str = stage_name_prefixes[30]
|
||||||
|
|
||||||
first_mission_number = mission_order[0]
|
first_mission_number = mission_order[0]
|
||||||
last_mission_number = mission_order[mission_count - 1]
|
last_mission_number = mission_order[mission_count - 1]
|
||||||
first_location_name: str = stage_prefix + str(first_mission_number)
|
first_location_name: str = stage_prefix + str(first_mission_number)
|
||||||
last_location_name: str = stage_prefix + str(last_mission_number)
|
last_location_name: str = stage_prefix + str(last_mission_number)
|
||||||
|
|
||||||
return first_location_name, last_location_name
|
return first_location_name, last_location_name
|
||||||
|
|
||||||
|
|
||||||
def print_mission_orders_to_spoiler(mission_map: typing.Dict[int, int],
|
|
||||||
mission_count_map: typing.Dict[int, int],
|
|
||||||
shuffled_region_list: typing.Dict[int, int],
|
|
||||||
levels_per_gate: typing.Dict[int, int],
|
|
||||||
player_name: str,
|
|
||||||
spoiler_handle: typing.TextIO):
|
|
||||||
spoiler_handle.write("\n")
|
|
||||||
header_text = "SA2 Mission Orders for {}:\n"
|
|
||||||
header_text = header_text.format(player_name)
|
|
||||||
spoiler_handle.write(header_text)
|
|
||||||
|
|
||||||
level_index = 0
|
|
||||||
for gate_idx in range(len(levels_per_gate)):
|
|
||||||
gate_len = levels_per_gate[gate_idx]
|
|
||||||
gate_levels = shuffled_region_list[int(level_index):int(level_index+gate_len)]
|
|
||||||
gate_levels.sort()
|
|
||||||
|
|
||||||
gate_text = "Gate {}:\n"
|
|
||||||
gate_text = gate_text.format(gate_idx)
|
|
||||||
spoiler_handle.write(gate_text)
|
|
||||||
|
|
||||||
for i in range(len(gate_levels)):
|
|
||||||
stage = gate_levels[i]
|
|
||||||
mission_count = mission_count_map[stage]
|
|
||||||
mission_order: typing.List[int] = mission_orders[mission_map[stage]]
|
|
||||||
stage_prefix: str = stage_name_prefixes[stage]
|
|
||||||
|
|
||||||
for mission in range(mission_count):
|
|
||||||
stage_prefix += str(mission_order[mission]) + " "
|
|
||||||
|
|
||||||
spoiler_handle.write(stage_prefix)
|
|
||||||
spoiler_handle.write("\n")
|
|
||||||
|
|
||||||
level_index += gate_len
|
|
||||||
spoiler_handle.write("\n")
|
|
||||||
|
|
||||||
mission_count = mission_count_map[30]
|
|
||||||
mission_order: typing.List[int] = mission_orders[mission_map[30]]
|
|
||||||
stage_prefix: str = stage_name_prefixes[30]
|
|
||||||
|
|
||||||
for mission in range(mission_count):
|
|
||||||
stage_prefix += str(mission_order[mission]) + " "
|
|
||||||
|
|
||||||
spoiler_handle.write(stage_prefix)
|
|
||||||
spoiler_handle.write("\n\n")
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ emblem = "Emblem"
|
|||||||
market_token = "Chao Coin"
|
market_token = "Chao Coin"
|
||||||
|
|
||||||
# Upgrade Definitions
|
# Upgrade Definitions
|
||||||
sonic_gloves = "Sonic - Magic Gloves"
|
sonic_gloves = "Sonic - Magic Glove"
|
||||||
sonic_light_shoes = "Sonic - Light Shoes"
|
sonic_light_shoes = "Sonic - Light Shoes"
|
||||||
sonic_ancient_light = "Sonic - Ancient Light"
|
sonic_ancient_light = "Sonic - Ancient Light"
|
||||||
sonic_bounce_bracelet = "Sonic - Bounce Bracelet"
|
sonic_bounce_bracelet = "Sonic - Bounce Bracelet"
|
||||||
@@ -51,34 +51,19 @@ invincibility = "Invincibility"
|
|||||||
|
|
||||||
|
|
||||||
# Traps
|
# Traps
|
||||||
omochao_trap = "OmoTrap"
|
omochao_trap = "OmoTrap"
|
||||||
timestop_trap = "Chaos Control Trap"
|
timestop_trap = "Chaos Control Trap"
|
||||||
confuse_trap = "Confusion Trap"
|
confuse_trap = "Confusion Trap"
|
||||||
tiny_trap = "Tiny Trap"
|
tiny_trap = "Tiny Trap"
|
||||||
gravity_trap = "Gravity Trap"
|
gravity_trap = "Gravity Trap"
|
||||||
exposition_trap = "Exposition Trap"
|
exposition_trap = "Exposition Trap"
|
||||||
darkness_trap = "Darkness Trap"
|
darkness_trap = "Darkness Trap"
|
||||||
ice_trap = "Ice Trap"
|
ice_trap = "Ice Trap"
|
||||||
slow_trap = "Slow Trap"
|
slow_trap = "Slow Trap"
|
||||||
cutscene_trap = "Cutscene Trap"
|
cutscene_trap = "Cutscene Trap"
|
||||||
reverse_trap = "Reverse Trap"
|
reverse_trap = "Reverse Trap"
|
||||||
literature_trap = "Literature Trap"
|
|
||||||
controller_drift_trap = "Controller Drift Trap"
|
|
||||||
poison_trap = "Poison Trap"
|
|
||||||
bee_trap = "Bee Trap"
|
|
||||||
|
|
||||||
pong_trap = "Pong Trap"
|
pong_trap = "Pong Trap"
|
||||||
breakout_trap = "Breakout Trap"
|
|
||||||
fishing_trap = "Fishing Trap"
|
|
||||||
trivia_trap = "Trivia Trap"
|
|
||||||
pokemon_trivia_trap = "Pokemon Trivia Trap"
|
|
||||||
pokemon_count_trap = "Pokemon Count Trap"
|
|
||||||
number_sequence_trap = "Number Sequence Trap"
|
|
||||||
light_up_path_trap = "Light Up Path Trap"
|
|
||||||
pinball_trap = "Pinball Trap"
|
|
||||||
math_quiz_trap = "Math Quiz Trap"
|
|
||||||
snake_trap = "Snake Trap"
|
|
||||||
input_sequence_trap = "Input Sequence Trap"
|
|
||||||
|
|
||||||
|
|
||||||
# Chaos Emeralds
|
# Chaos Emeralds
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from Options import Choice, Range, Option, OptionGroup, Toggle, DeathLink, DefaultOnToggle, PerGameCommonOptions, PlandoBosses
|
from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionGroup, PerGameCommonOptions
|
||||||
|
|
||||||
from .Names import LocationName
|
|
||||||
|
|
||||||
|
|
||||||
class Goal(Choice):
|
class Goal(Choice):
|
||||||
@@ -24,8 +22,6 @@ class Goal(Choice):
|
|||||||
Boss Rush Chaos Emerald Hunt: Find the Seven Chaos Emeralds, then beat all of the bosses in the Boss Rush, ending with Finalhazard
|
Boss Rush Chaos Emerald Hunt: Find the Seven Chaos Emeralds, then beat all of the bosses in the Boss Rush, ending with Finalhazard
|
||||||
|
|
||||||
Chaos Chao: Raise a Chaos Chao to win
|
Chaos Chao: Raise a Chaos Chao to win
|
||||||
|
|
||||||
Minigame Madness: Win a certain amount of each Minigame Trap, then defeat Finalhazard
|
|
||||||
"""
|
"""
|
||||||
display_name = "Goal"
|
display_name = "Goal"
|
||||||
option_biolizard = 0
|
option_biolizard = 0
|
||||||
@@ -36,7 +32,6 @@ class Goal(Choice):
|
|||||||
option_cannons_core_boss_rush = 5
|
option_cannons_core_boss_rush = 5
|
||||||
option_boss_rush_chaos_emerald_hunt = 6
|
option_boss_rush_chaos_emerald_hunt = 6
|
||||||
option_chaos_chao = 7
|
option_chaos_chao = 7
|
||||||
option_minigame_madness = 8
|
|
||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -76,66 +71,6 @@ class BossRushShuffle(Choice):
|
|||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class GateBossPlando(PlandoBosses):
|
|
||||||
"""
|
|
||||||
Possible Locations:
|
|
||||||
"Gate 1 Boss"
|
|
||||||
"Gate 2 Boss"
|
|
||||||
"Gate 3 Boss"
|
|
||||||
"Gate 4 Boss"
|
|
||||||
"Gate 5 Boss"
|
|
||||||
|
|
||||||
Possible Bosses:
|
|
||||||
"Sonic vs Shadow 1"
|
|
||||||
"Sonic vs Shadow 2"
|
|
||||||
"Tails vs Eggman 1"
|
|
||||||
"Tails vs Eggman 2"
|
|
||||||
"Knuckles vs Rouge 1"
|
|
||||||
"BIG FOOT"
|
|
||||||
"HOT SHOT"
|
|
||||||
"FLYING DOG"
|
|
||||||
"Egg Golem (Sonic)"
|
|
||||||
"Egg Golem (Eggman)"
|
|
||||||
"King Boom Boo"
|
|
||||||
"""
|
|
||||||
bosses = frozenset(LocationName.boss_names.keys())
|
|
||||||
|
|
||||||
locations = frozenset(LocationName.boss_gate_names.keys())
|
|
||||||
|
|
||||||
duplicate_bosses = False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def can_place_boss(cls, boss: str, location: str) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
display_name = "Boss Shuffle"
|
|
||||||
option_plando = 0
|
|
||||||
|
|
||||||
|
|
||||||
class MinigameMadnessRequirement(Range):
|
|
||||||
"""
|
|
||||||
Determines how many of each Minigame Trap must be won (for Minigame Madness goal)
|
|
||||||
|
|
||||||
Receiving this many of a Minigame Trap will allow you to replay that minigame at-will in the Chao World lobby
|
|
||||||
"""
|
|
||||||
display_name = "Minigame Madness Trap Requirement"
|
|
||||||
range_start = 1
|
|
||||||
range_end = 10
|
|
||||||
default = 3
|
|
||||||
|
|
||||||
|
|
||||||
class MinigameMadnessMinimum(Range):
|
|
||||||
"""
|
|
||||||
Determines the minimum number of each Minigame Trap that are created (for Minigame Madness goal)
|
|
||||||
|
|
||||||
At least this many of each trap will be created as "Progression Traps", regardless of other trap option selections
|
|
||||||
"""
|
|
||||||
display_name = "Minigame Madness Trap Minimum"
|
|
||||||
range_start = 1
|
|
||||||
range_end = 10
|
|
||||||
default = 5
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTrapWeight(Choice):
|
class BaseTrapWeight(Choice):
|
||||||
"""
|
"""
|
||||||
Base Class for Trap Weights
|
Base Class for Trap Weights
|
||||||
@@ -224,34 +159,6 @@ class ReverseTrapWeight(BaseTrapWeight):
|
|||||||
display_name = "Reverse Trap Weight"
|
display_name = "Reverse Trap Weight"
|
||||||
|
|
||||||
|
|
||||||
class LiteratureTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to read
|
|
||||||
"""
|
|
||||||
display_name = "Literature Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class ControllerDriftTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which causes your control sticks to drift
|
|
||||||
"""
|
|
||||||
display_name = "Controller Drift Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class PoisonTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which causes you to lose rings over time
|
|
||||||
"""
|
|
||||||
display_name = "Poison Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class BeeTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which spawns a swarm of bees
|
|
||||||
"""
|
|
||||||
display_name = "Bee Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class PongTrapWeight(BaseTrapWeight):
|
class PongTrapWeight(BaseTrapWeight):
|
||||||
"""
|
"""
|
||||||
Likelihood of receiving a trap which forces you to play a Pong minigame
|
Likelihood of receiving a trap which forces you to play a Pong minigame
|
||||||
@@ -259,106 +166,14 @@ class PongTrapWeight(BaseTrapWeight):
|
|||||||
display_name = "Pong Trap Weight"
|
display_name = "Pong Trap Weight"
|
||||||
|
|
||||||
|
|
||||||
class BreakoutTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Breakout minigame
|
|
||||||
"""
|
|
||||||
display_name = "Breakout Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class FishingTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Fishing minigame
|
|
||||||
"""
|
|
||||||
display_name = "Fishing Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class TriviaTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Trivia minigame
|
|
||||||
"""
|
|
||||||
display_name = "Trivia Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class PokemonTriviaTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Pokemon Trivia minigame
|
|
||||||
"""
|
|
||||||
display_name = "Pokemon Trivia Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class PokemonCountTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Pokemon Count minigame
|
|
||||||
"""
|
|
||||||
display_name = "Pokemon Count Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class NumberSequenceTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Number Sequence minigame
|
|
||||||
"""
|
|
||||||
display_name = "Number Sequence Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class LightUpPathTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Light Up Path minigame
|
|
||||||
"""
|
|
||||||
display_name = "Light Up Path Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class PinballTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Pinball minigame
|
|
||||||
"""
|
|
||||||
display_name = "Pinball Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class MathQuizTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to solve a math problem
|
|
||||||
"""
|
|
||||||
display_name = "Math Quiz Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class SnakeTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to play a Snake minigame
|
|
||||||
"""
|
|
||||||
display_name = "Snake Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class InputSequenceTrapWeight(BaseTrapWeight):
|
|
||||||
"""
|
|
||||||
Likelihood of receiving a trap which forces you to press a sequence of inputs
|
|
||||||
"""
|
|
||||||
display_name = "Input Sequence Trap Weight"
|
|
||||||
|
|
||||||
|
|
||||||
class MinigameTrapDifficulty(Choice):
|
class MinigameTrapDifficulty(Choice):
|
||||||
"""
|
"""
|
||||||
How difficult any Minigame-style traps are
|
How difficult any Minigame-style traps are
|
||||||
Chaos causes the difficulty to be random per-minigame
|
|
||||||
"""
|
"""
|
||||||
display_name = "Minigame Trap Difficulty"
|
display_name = "Minigame Trap Difficulty"
|
||||||
option_easy = 0
|
option_easy = 0
|
||||||
option_medium = 1
|
option_medium = 1
|
||||||
option_hard = 2
|
option_hard = 2
|
||||||
option_chaos = 3
|
|
||||||
default = 1
|
|
||||||
|
|
||||||
|
|
||||||
class BigFishingDifficulty(Choice):
|
|
||||||
"""
|
|
||||||
How difficult Big's Fishing Minigames are
|
|
||||||
Chaos causes the difficulty to be random per-minigame
|
|
||||||
"""
|
|
||||||
display_name = "Big Fishing Difficulty"
|
|
||||||
option_easy = 0
|
|
||||||
option_medium = 1
|
|
||||||
option_hard = 2
|
|
||||||
option_chaos = 3
|
|
||||||
default = 1
|
default = 1
|
||||||
|
|
||||||
|
|
||||||
@@ -382,7 +197,7 @@ class TrapFillPercentage(Range):
|
|||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class Keysanity(DefaultOnToggle):
|
class Keysanity(Toggle):
|
||||||
"""
|
"""
|
||||||
Determines whether picking up Chao Keys grants checks
|
Determines whether picking up Chao Keys grants checks
|
||||||
(86 Locations)
|
(86 Locations)
|
||||||
@@ -410,7 +225,7 @@ class Whistlesanity(Choice):
|
|||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class Beetlesanity(DefaultOnToggle):
|
class Beetlesanity(Toggle):
|
||||||
"""
|
"""
|
||||||
Determines whether destroying Gold Beetles grants checks
|
Determines whether destroying Gold Beetles grants checks
|
||||||
(27 Locations)
|
(27 Locations)
|
||||||
@@ -429,35 +244,13 @@ class Omosanity(Toggle):
|
|||||||
class Animalsanity(Toggle):
|
class Animalsanity(Toggle):
|
||||||
"""
|
"""
|
||||||
Determines whether unique counts of animals grant checks.
|
Determines whether unique counts of animals grant checks.
|
||||||
(422 Locations)
|
(421 Locations)
|
||||||
|
|
||||||
ALL animals must be collected in a single run of a mission to get all checks.
|
ALL animals must be collected in a single run of a mission to get all checks.
|
||||||
"""
|
"""
|
||||||
display_name = "Animalsanity"
|
display_name = "Animalsanity"
|
||||||
|
|
||||||
|
|
||||||
class ItemBoxsanity(Choice):
|
|
||||||
"""
|
|
||||||
Determines whether collecting Item Boxes grants checks
|
|
||||||
None: No Item Boxes grant checks
|
|
||||||
Extra Lives: Extra Life Boxes grant checks (94 Locations)
|
|
||||||
All: All Item Boxes grant checks (502 Locations Total)
|
|
||||||
"""
|
|
||||||
display_name = "Itemboxsanity"
|
|
||||||
option_none = 0
|
|
||||||
option_extra_lives = 1
|
|
||||||
option_all = 2
|
|
||||||
default = 0
|
|
||||||
|
|
||||||
|
|
||||||
class Bigsanity(Toggle):
|
|
||||||
"""
|
|
||||||
Determines whether helping Big fish grants checks.
|
|
||||||
(32 Locations)
|
|
||||||
"""
|
|
||||||
display_name = "Bigsanity"
|
|
||||||
|
|
||||||
|
|
||||||
class KartRaceChecks(Choice):
|
class KartRaceChecks(Choice):
|
||||||
"""
|
"""
|
||||||
Determines whether Kart Race Mode grants checks
|
Determines whether Kart Race Mode grants checks
|
||||||
@@ -520,7 +313,7 @@ class LevelGateCosts(Choice):
|
|||||||
option_low = 0
|
option_low = 0
|
||||||
option_medium = 1
|
option_medium = 1
|
||||||
option_high = 2
|
option_high = 2
|
||||||
default = 0
|
default = 2
|
||||||
|
|
||||||
|
|
||||||
class MaximumEmblemCap(Range):
|
class MaximumEmblemCap(Range):
|
||||||
@@ -730,214 +523,109 @@ class BaseMissionCount(Range):
|
|||||||
default = 2
|
default = 2
|
||||||
|
|
||||||
|
|
||||||
class SonicMissionCount(BaseMissionCount):
|
class SpeedMissionCount(BaseMissionCount):
|
||||||
"""
|
"""
|
||||||
The number of active missions to include for Sonic stages
|
The number of active missions to include for Sonic and Shadow stages
|
||||||
"""
|
"""
|
||||||
display_name = "Sonic Mission Count"
|
display_name = "Speed Mission Count"
|
||||||
|
|
||||||
|
|
||||||
class SonicMission2(DefaultOnToggle):
|
class SpeedMission2(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Sonic 100 rings missions should be included
|
Determines if the Sonic and Shadow 100 rings missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Sonic Mission 2"
|
display_name = "Speed Mission 2"
|
||||||
|
|
||||||
|
|
||||||
class SonicMission3(DefaultOnToggle):
|
class SpeedMission3(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Sonic lost chao missions should be included
|
Determines if the Sonic and Shadow lost chao missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Sonic Mission 3"
|
display_name = "Speed Mission 3"
|
||||||
|
|
||||||
|
|
||||||
class SonicMission4(DefaultOnToggle):
|
class SpeedMission4(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Sonic time trial missions should be included
|
Determines if the Sonic and Shadow time trial missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Sonic Mission 4"
|
display_name = "Speed Mission 4"
|
||||||
|
|
||||||
|
|
||||||
class SonicMission5(DefaultOnToggle):
|
class SpeedMission5(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Sonic hard missions should be included
|
Determines if the Sonic and Shadow hard missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Sonic Mission 5"
|
display_name = "Speed Mission 5"
|
||||||
|
|
||||||
|
|
||||||
class ShadowMissionCount(BaseMissionCount):
|
class MechMissionCount(BaseMissionCount):
|
||||||
"""
|
"""
|
||||||
The number of active missions to include for Shadow stages
|
The number of active missions to include for Tails and Eggman stages
|
||||||
"""
|
"""
|
||||||
display_name = "Shadow Mission Count"
|
display_name = "Mech Mission Count"
|
||||||
|
|
||||||
|
|
||||||
class ShadowMission2(DefaultOnToggle):
|
class MechMission2(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Shadow 100 rings missions should be included
|
Determines if the Tails and Eggman 100 rings missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Shadow Mission 2"
|
display_name = "Mech Mission 2"
|
||||||
|
|
||||||
|
|
||||||
class ShadowMission3(DefaultOnToggle):
|
class MechMission3(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Shadow lost chao missions should be included
|
Determines if the Tails and Eggman lost chao missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Shadow Mission 3"
|
display_name = "Mech Mission 3"
|
||||||
|
|
||||||
|
|
||||||
class ShadowMission4(DefaultOnToggle):
|
class MechMission4(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Shadow time trial missions should be included
|
Determines if the Tails and Eggman time trial missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Shadow Mission 4"
|
display_name = "Mech Mission 4"
|
||||||
|
|
||||||
|
|
||||||
class ShadowMission5(DefaultOnToggle):
|
class MechMission5(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Shadow hard missions should be included
|
Determines if the Tails and Eggman hard missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Shadow Mission 5"
|
display_name = "Mech Mission 5"
|
||||||
|
|
||||||
|
|
||||||
class TailsMissionCount(BaseMissionCount):
|
class HuntMissionCount(BaseMissionCount):
|
||||||
"""
|
"""
|
||||||
The number of active missions to include for Tails stages
|
The number of active missions to include for Knuckles and Rouge stages
|
||||||
"""
|
"""
|
||||||
display_name = "Tails Mission Count"
|
display_name = "Hunt Mission Count"
|
||||||
|
|
||||||
|
|
||||||
class TailsMission2(DefaultOnToggle):
|
class HuntMission2(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Tails 100 rings missions should be included
|
Determines if the Knuckles and Rouge 100 rings missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Tails Mission 2"
|
display_name = "Hunt Mission 2"
|
||||||
|
|
||||||
|
|
||||||
class TailsMission3(DefaultOnToggle):
|
class HuntMission3(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Tails lost chao missions should be included
|
Determines if the Knuckles and Rouge lost chao missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Tails Mission 3"
|
display_name = "Hunt Mission 3"
|
||||||
|
|
||||||
|
|
||||||
class TailsMission4(DefaultOnToggle):
|
class HuntMission4(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Tails time trial missions should be included
|
Determines if the Knuckles and Rouge time trial missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Tails Mission 4"
|
display_name = "Hunt Mission 4"
|
||||||
|
|
||||||
|
|
||||||
class TailsMission5(DefaultOnToggle):
|
class HuntMission5(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Determines if the Tails hard missions should be included
|
Determines if the Knuckles and Rouge hard missions should be included
|
||||||
"""
|
"""
|
||||||
display_name = "Tails Mission 5"
|
display_name = "Hunt Mission 5"
|
||||||
|
|
||||||
|
|
||||||
class EggmanMissionCount(BaseMissionCount):
|
|
||||||
"""
|
|
||||||
The number of active missions to include for Eggman stages
|
|
||||||
"""
|
|
||||||
display_name = "Eggman Mission Count"
|
|
||||||
|
|
||||||
|
|
||||||
class EggmanMission2(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Eggman 100 rings missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Eggman Mission 2"
|
|
||||||
|
|
||||||
|
|
||||||
class EggmanMission3(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Eggman lost chao missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Eggman Mission 3"
|
|
||||||
|
|
||||||
|
|
||||||
class EggmanMission4(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Eggman time trial missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Eggman Mission 4"
|
|
||||||
|
|
||||||
|
|
||||||
class EggmanMission5(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Eggman hard missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Eggman Mission 5"
|
|
||||||
|
|
||||||
|
|
||||||
class KnucklesMissionCount(BaseMissionCount):
|
|
||||||
"""
|
|
||||||
The number of active missions to include for Knuckles stages
|
|
||||||
"""
|
|
||||||
display_name = "Knuckles Mission Count"
|
|
||||||
|
|
||||||
|
|
||||||
class KnucklesMission2(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Knuckles 100 rings missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Knuckles Mission 2"
|
|
||||||
|
|
||||||
|
|
||||||
class KnucklesMission3(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Knuckles lost chao missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Knuckles Mission 3"
|
|
||||||
|
|
||||||
|
|
||||||
class KnucklesMission4(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Knuckles time trial missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Knuckles Mission 4"
|
|
||||||
|
|
||||||
|
|
||||||
class KnucklesMission5(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Knuckles hard missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Knuckles Mission 5"
|
|
||||||
|
|
||||||
|
|
||||||
class RougeMissionCount(BaseMissionCount):
|
|
||||||
"""
|
|
||||||
The number of active missions to include for Rouge stages
|
|
||||||
"""
|
|
||||||
display_name = "Rouge Mission Count"
|
|
||||||
|
|
||||||
|
|
||||||
class RougeMission2(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Rouge 100 rings missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Rouge Mission 2"
|
|
||||||
|
|
||||||
|
|
||||||
class RougeMission3(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Rouge lost chao missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Rouge Mission 3"
|
|
||||||
|
|
||||||
|
|
||||||
class RougeMission4(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Rouge time trial missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Rouge Mission 4"
|
|
||||||
|
|
||||||
|
|
||||||
class RougeMission5(DefaultOnToggle):
|
|
||||||
"""
|
|
||||||
Determines if the Rouge hard missions should be included
|
|
||||||
"""
|
|
||||||
display_name = "Rouge Mission 5"
|
|
||||||
|
|
||||||
|
|
||||||
class KartMissionCount(BaseMissionCount):
|
class KartMissionCount(BaseMissionCount):
|
||||||
@@ -1018,7 +706,7 @@ class RingLoss(Choice):
|
|||||||
|
|
||||||
Modern: You lose 20 rings when hit
|
Modern: You lose 20 rings when hit
|
||||||
|
|
||||||
OHKO: You die immediately when hit (NOTE: Some Hard or Expert Logic tricks may require damage boosts!)
|
OHKO: You die immediately when hit (NOTE: Some Hard Logic tricks may require damage boosts!)
|
||||||
"""
|
"""
|
||||||
display_name = "Ring Loss"
|
display_name = "Ring Loss"
|
||||||
option_classic = 0
|
option_classic = 0
|
||||||
@@ -1041,16 +729,6 @@ class RingLink(Toggle):
|
|||||||
display_name = "Ring Link"
|
display_name = "Ring Link"
|
||||||
|
|
||||||
|
|
||||||
class TrapLink(Toggle):
|
|
||||||
"""
|
|
||||||
Whether your received traps are linked to other players
|
|
||||||
|
|
||||||
You will also receive any linked traps from other players with Trap Link enabled,
|
|
||||||
if you have a weight above "none" set for that trap
|
|
||||||
"""
|
|
||||||
display_name = "Trap Link"
|
|
||||||
|
|
||||||
|
|
||||||
class SADXMusic(Choice):
|
class SADXMusic(Choice):
|
||||||
"""
|
"""
|
||||||
Whether the randomizer will include Sonic Adventure DX Music in the music pool
|
Whether the randomizer will include Sonic Adventure DX Music in the music pool
|
||||||
@@ -1145,14 +823,11 @@ class LogicDifficulty(Choice):
|
|||||||
|
|
||||||
Standard: The logic assumes the "intended" usage of Upgrades to progress through levels
|
Standard: The logic assumes the "intended" usage of Upgrades to progress through levels
|
||||||
|
|
||||||
Hard: Some simple skips or sequence breaks may be required, but no out-of-bounds
|
Hard: Some simple skips or sequence breaks may be required
|
||||||
|
|
||||||
Expert: If it is humanly possible, it may be required
|
|
||||||
"""
|
"""
|
||||||
display_name = "Logic Difficulty"
|
display_name = "Logic Difficulty"
|
||||||
option_standard = 0
|
option_standard = 0
|
||||||
option_hard = 1
|
option_hard = 1
|
||||||
option_expert = 2
|
|
||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
@@ -1160,8 +835,6 @@ sa2b_option_groups = [
|
|||||||
OptionGroup("General Options", [
|
OptionGroup("General Options", [
|
||||||
Goal,
|
Goal,
|
||||||
BossRushShuffle,
|
BossRushShuffle,
|
||||||
MinigameMadnessRequirement,
|
|
||||||
MinigameMadnessMinimum,
|
|
||||||
LogicDifficulty,
|
LogicDifficulty,
|
||||||
RequiredRank,
|
RequiredRank,
|
||||||
MaximumEmblemCap,
|
MaximumEmblemCap,
|
||||||
@@ -1181,8 +854,6 @@ sa2b_option_groups = [
|
|||||||
Beetlesanity,
|
Beetlesanity,
|
||||||
Omosanity,
|
Omosanity,
|
||||||
Animalsanity,
|
Animalsanity,
|
||||||
ItemBoxsanity,
|
|
||||||
Bigsanity,
|
|
||||||
KartRaceChecks,
|
KartRaceChecks,
|
||||||
]),
|
]),
|
||||||
OptionGroup("Chao", [
|
OptionGroup("Chao", [
|
||||||
@@ -1214,68 +885,29 @@ sa2b_option_groups = [
|
|||||||
SlowTrapWeight,
|
SlowTrapWeight,
|
||||||
CutsceneTrapWeight,
|
CutsceneTrapWeight,
|
||||||
ReverseTrapWeight,
|
ReverseTrapWeight,
|
||||||
LiteratureTrapWeight,
|
|
||||||
ControllerDriftTrapWeight,
|
|
||||||
PoisonTrapWeight,
|
|
||||||
BeeTrapWeight,
|
|
||||||
]),
|
|
||||||
OptionGroup("Minigames", [
|
|
||||||
PongTrapWeight,
|
PongTrapWeight,
|
||||||
BreakoutTrapWeight,
|
|
||||||
FishingTrapWeight,
|
|
||||||
TriviaTrapWeight,
|
|
||||||
PokemonTriviaTrapWeight,
|
|
||||||
PokemonCountTrapWeight,
|
|
||||||
NumberSequenceTrapWeight,
|
|
||||||
LightUpPathTrapWeight,
|
|
||||||
PinballTrapWeight,
|
|
||||||
MathQuizTrapWeight,
|
|
||||||
SnakeTrapWeight,
|
|
||||||
InputSequenceTrapWeight,
|
|
||||||
MinigameTrapDifficulty,
|
MinigameTrapDifficulty,
|
||||||
BigFishingDifficulty,
|
|
||||||
]),
|
]),
|
||||||
OptionGroup("Sonic Missions", [
|
OptionGroup("Speed Missions", [
|
||||||
SonicMissionCount,
|
SpeedMissionCount,
|
||||||
SonicMission2,
|
SpeedMission2,
|
||||||
SonicMission3,
|
SpeedMission3,
|
||||||
SonicMission4,
|
SpeedMission4,
|
||||||
SonicMission5,
|
SpeedMission5,
|
||||||
]),
|
]),
|
||||||
OptionGroup("Shadow Missions", [
|
OptionGroup("Mech Missions", [
|
||||||
ShadowMissionCount,
|
MechMissionCount,
|
||||||
ShadowMission2,
|
MechMission2,
|
||||||
ShadowMission3,
|
MechMission3,
|
||||||
ShadowMission4,
|
MechMission4,
|
||||||
ShadowMission5,
|
MechMission5,
|
||||||
]),
|
]),
|
||||||
OptionGroup("Tails Missions", [
|
OptionGroup("Hunt Missions", [
|
||||||
TailsMissionCount,
|
HuntMissionCount,
|
||||||
TailsMission2,
|
HuntMission2,
|
||||||
TailsMission3,
|
HuntMission3,
|
||||||
TailsMission4,
|
HuntMission4,
|
||||||
TailsMission5,
|
HuntMission5,
|
||||||
]),
|
|
||||||
OptionGroup("Eggman Missions", [
|
|
||||||
EggmanMissionCount,
|
|
||||||
EggmanMission2,
|
|
||||||
EggmanMission3,
|
|
||||||
EggmanMission4,
|
|
||||||
EggmanMission5,
|
|
||||||
]),
|
|
||||||
OptionGroup("Knuckles Missions", [
|
|
||||||
KnucklesMissionCount,
|
|
||||||
KnucklesMission2,
|
|
||||||
KnucklesMission3,
|
|
||||||
KnucklesMission4,
|
|
||||||
KnucklesMission5,
|
|
||||||
]),
|
|
||||||
OptionGroup("Rouge Missions", [
|
|
||||||
RougeMissionCount,
|
|
||||||
RougeMission2,
|
|
||||||
RougeMission3,
|
|
||||||
RougeMission4,
|
|
||||||
RougeMission5,
|
|
||||||
]),
|
]),
|
||||||
OptionGroup("Kart Missions", [
|
OptionGroup("Kart Missions", [
|
||||||
KartMissionCount,
|
KartMissionCount,
|
||||||
@@ -1299,13 +931,11 @@ sa2b_option_groups = [
|
|||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SA2BOptions(PerGameCommonOptions):
|
class SA2BOptions(PerGameCommonOptions):
|
||||||
goal: Goal
|
goal: Goal
|
||||||
boss_rush_shuffle: BossRushShuffle
|
boss_rush_shuffle: BossRushShuffle
|
||||||
minigame_madness_requirement: MinigameMadnessRequirement
|
|
||||||
minigame_madness_minimum: MinigameMadnessMinimum
|
|
||||||
gate_boss_plando: GateBossPlando
|
|
||||||
logic_difficulty: LogicDifficulty
|
logic_difficulty: LogicDifficulty
|
||||||
required_rank: RequiredRank
|
required_rank: RequiredRank
|
||||||
max_emblem_cap: MaximumEmblemCap
|
max_emblem_cap: MaximumEmblemCap
|
||||||
@@ -1323,8 +953,6 @@ class SA2BOptions(PerGameCommonOptions):
|
|||||||
beetlesanity: Beetlesanity
|
beetlesanity: Beetlesanity
|
||||||
omosanity: Omosanity
|
omosanity: Omosanity
|
||||||
animalsanity: Animalsanity
|
animalsanity: Animalsanity
|
||||||
itemboxsanity: ItemBoxsanity
|
|
||||||
bigsanity: Bigsanity
|
|
||||||
kart_race_checks: KartRaceChecks
|
kart_race_checks: KartRaceChecks
|
||||||
|
|
||||||
black_market_slots: BlackMarketSlots
|
black_market_slots: BlackMarketSlots
|
||||||
@@ -1355,65 +983,31 @@ class SA2BOptions(PerGameCommonOptions):
|
|||||||
slow_trap_weight: SlowTrapWeight
|
slow_trap_weight: SlowTrapWeight
|
||||||
cutscene_trap_weight: CutsceneTrapWeight
|
cutscene_trap_weight: CutsceneTrapWeight
|
||||||
reverse_trap_weight: ReverseTrapWeight
|
reverse_trap_weight: ReverseTrapWeight
|
||||||
literature_trap_weight: LiteratureTrapWeight
|
|
||||||
controller_drift_trap_weight: ControllerDriftTrapWeight
|
|
||||||
poison_trap_weight: PoisonTrapWeight
|
|
||||||
bee_trap_weight: BeeTrapWeight
|
|
||||||
pong_trap_weight: PongTrapWeight
|
pong_trap_weight: PongTrapWeight
|
||||||
breakout_trap_weight: BreakoutTrapWeight
|
|
||||||
fishing_trap_weight: FishingTrapWeight
|
|
||||||
trivia_trap_weight: TriviaTrapWeight
|
|
||||||
pokemon_trivia_trap_weight: PokemonTriviaTrapWeight
|
|
||||||
pokemon_count_trap_weight: PokemonCountTrapWeight
|
|
||||||
number_sequence_trap_weight: NumberSequenceTrapWeight
|
|
||||||
light_up_path_trap_weight: LightUpPathTrapWeight
|
|
||||||
pinball_trap_weight: PinballTrapWeight
|
|
||||||
math_quiz_trap_weight: MathQuizTrapWeight
|
|
||||||
snake_trap_weight: SnakeTrapWeight
|
|
||||||
input_sequence_trap_weight: InputSequenceTrapWeight
|
|
||||||
minigame_trap_difficulty: MinigameTrapDifficulty
|
minigame_trap_difficulty: MinigameTrapDifficulty
|
||||||
big_fishing_difficulty: BigFishingDifficulty
|
|
||||||
|
|
||||||
sadx_music: SADXMusic
|
sadx_music: SADXMusic
|
||||||
music_shuffle: MusicShuffle
|
music_shuffle: MusicShuffle
|
||||||
voice_shuffle: VoiceShuffle
|
voice_shuffle: VoiceShuffle
|
||||||
narrator: Narrator
|
narrator: Narrator
|
||||||
|
|
||||||
sonic_mission_count: SonicMissionCount
|
speed_mission_count: SpeedMissionCount
|
||||||
sonic_mission_2: SonicMission2
|
speed_mission_2: SpeedMission2
|
||||||
sonic_mission_3: SonicMission3
|
speed_mission_3: SpeedMission3
|
||||||
sonic_mission_4: SonicMission4
|
speed_mission_4: SpeedMission4
|
||||||
sonic_mission_5: SonicMission5
|
speed_mission_5: SpeedMission5
|
||||||
|
|
||||||
shadow_mission_count: ShadowMissionCount
|
mech_mission_count: MechMissionCount
|
||||||
shadow_mission_2: ShadowMission2
|
mech_mission_2: MechMission2
|
||||||
shadow_mission_3: ShadowMission3
|
mech_mission_3: MechMission3
|
||||||
shadow_mission_4: ShadowMission4
|
mech_mission_4: MechMission4
|
||||||
shadow_mission_5: ShadowMission5
|
mech_mission_5: MechMission5
|
||||||
|
|
||||||
tails_mission_count: TailsMissionCount
|
hunt_mission_count: HuntMissionCount
|
||||||
tails_mission_2: TailsMission2
|
hunt_mission_2: HuntMission2
|
||||||
tails_mission_3: TailsMission3
|
hunt_mission_3: HuntMission3
|
||||||
tails_mission_4: TailsMission4
|
hunt_mission_4: HuntMission4
|
||||||
tails_mission_5: TailsMission5
|
hunt_mission_5: HuntMission5
|
||||||
|
|
||||||
eggman_mission_count: EggmanMissionCount
|
|
||||||
eggman_mission_2: EggmanMission2
|
|
||||||
eggman_mission_3: EggmanMission3
|
|
||||||
eggman_mission_4: EggmanMission4
|
|
||||||
eggman_mission_5: EggmanMission5
|
|
||||||
|
|
||||||
knuckles_mission_count: KnucklesMissionCount
|
|
||||||
knuckles_mission_2: KnucklesMission2
|
|
||||||
knuckles_mission_3: KnucklesMission3
|
|
||||||
knuckles_mission_4: KnucklesMission4
|
|
||||||
knuckles_mission_5: KnucklesMission5
|
|
||||||
|
|
||||||
rouge_mission_count: RougeMissionCount
|
|
||||||
rouge_mission_2: RougeMission2
|
|
||||||
rouge_mission_3: RougeMission3
|
|
||||||
rouge_mission_4: RougeMission4
|
|
||||||
rouge_mission_5: RougeMission5
|
|
||||||
|
|
||||||
kart_mission_count: KartMissionCount
|
kart_mission_count: KartMissionCount
|
||||||
kart_mission_2: KartMission2
|
kart_mission_2: KartMission2
|
||||||
@@ -1428,5 +1022,4 @@ class SA2BOptions(PerGameCommonOptions):
|
|||||||
cannons_core_mission_5: CannonsCoreMission5
|
cannons_core_mission_5: CannonsCoreMission5
|
||||||
|
|
||||||
ring_link: RingLink
|
ring_link: RingLink
|
||||||
trap_link: TrapLink
|
|
||||||
death_link: DeathLink
|
death_link: DeathLink
|
||||||
|
|||||||
@@ -1,502 +0,0 @@
|
|||||||
from typing import Dict, Any
|
|
||||||
|
|
||||||
from .Options import *
|
|
||||||
|
|
||||||
minsanity = {
|
|
||||||
"goal": Goal.option_chaos_chao,
|
|
||||||
"max_emblem_cap": MaximumEmblemCap.range_start,
|
|
||||||
|
|
||||||
"keysanity": False,
|
|
||||||
"whistlesanity": Whistlesanity.option_none,
|
|
||||||
"beetlesanity": False,
|
|
||||||
"omosanity": False,
|
|
||||||
"animalsanity": False,
|
|
||||||
"itemboxsanity": ItemBoxsanity.option_none,
|
|
||||||
"bigsanity": False,
|
|
||||||
"kart_race_checks": KartRaceChecks.option_none,
|
|
||||||
|
|
||||||
"junk_fill_percentage": 0,
|
|
||||||
|
|
||||||
"sonic_mission_count": BaseMissionCount.range_start,
|
|
||||||
"sonic_mission_2": False,
|
|
||||||
"sonic_mission_3": False,
|
|
||||||
"sonic_mission_4": False,
|
|
||||||
"sonic_mission_5": False,
|
|
||||||
|
|
||||||
"shadow_mission_count": BaseMissionCount.range_start,
|
|
||||||
"shadow_mission_2": False,
|
|
||||||
"shadow_mission_3": False,
|
|
||||||
"shadow_mission_4": False,
|
|
||||||
"shadow_mission_5": False,
|
|
||||||
|
|
||||||
"tails_mission_count": BaseMissionCount.range_start,
|
|
||||||
"tails_mission_2": False,
|
|
||||||
"tails_mission_3": False,
|
|
||||||
"tails_mission_4": False,
|
|
||||||
"tails_mission_5": False,
|
|
||||||
|
|
||||||
"eggman_mission_count": BaseMissionCount.range_start,
|
|
||||||
"eggman_mission_2": False,
|
|
||||||
"eggman_mission_3": False,
|
|
||||||
"eggman_mission_4": False,
|
|
||||||
"eggman_mission_5": False,
|
|
||||||
|
|
||||||
"knuckles_mission_count": BaseMissionCount.range_start,
|
|
||||||
"knuckles_mission_2": False,
|
|
||||||
"knuckles_mission_3": False,
|
|
||||||
"knuckles_mission_4": False,
|
|
||||||
"knuckles_mission_5": False,
|
|
||||||
|
|
||||||
"rouge_mission_count": BaseMissionCount.range_start,
|
|
||||||
"rouge_mission_2": False,
|
|
||||||
"rouge_mission_3": False,
|
|
||||||
"rouge_mission_4": False,
|
|
||||||
"rouge_mission_5": False,
|
|
||||||
|
|
||||||
"kart_mission_count": BaseMissionCount.range_start,
|
|
||||||
"kart_mission_2": False,
|
|
||||||
"kart_mission_3": False,
|
|
||||||
"kart_mission_4": False,
|
|
||||||
"kart_mission_5": False,
|
|
||||||
|
|
||||||
"cannons_core_mission_count": BaseMissionCount.range_start,
|
|
||||||
"cannons_core_mission_2": False,
|
|
||||||
"cannons_core_mission_3": False,
|
|
||||||
"cannons_core_mission_4": False,
|
|
||||||
"cannons_core_mission_5": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
chao_centric = {
|
|
||||||
"goal": Goal.option_chaos_chao,
|
|
||||||
|
|
||||||
"keysanity": False,
|
|
||||||
"whistlesanity": Whistlesanity.option_none,
|
|
||||||
"beetlesanity": False,
|
|
||||||
"omosanity": False,
|
|
||||||
"animalsanity": False,
|
|
||||||
"itemboxsanity": ItemBoxsanity.option_none,
|
|
||||||
"bigsanity": False,
|
|
||||||
"kart_race_checks": KartRaceChecks.option_none,
|
|
||||||
|
|
||||||
"black_market_slots": BlackMarketSlots.range_end,
|
|
||||||
"black_market_unlock_costs": BlackMarketUnlockCosts.option_high,
|
|
||||||
"chao_race_difficulty": ChaoRaceDifficulty.option_expert,
|
|
||||||
"chao_karate_difficulty": ChaoKarateDifficulty.option_super,
|
|
||||||
"chao_stadium_checks": ChaoStadiumChecks.option_all,
|
|
||||||
"chao_animal_parts": True,
|
|
||||||
"chao_stats": ChaoStats.range_end,
|
|
||||||
"chao_stats_frequency": 1,
|
|
||||||
"chao_stats_stamina": True,
|
|
||||||
"chao_stats_hidden": True,
|
|
||||||
"chao_kindergarten": ChaoKindergarten.option_full,
|
|
||||||
|
|
||||||
"junk_fill_percentage": 50,
|
|
||||||
|
|
||||||
"sonic_mission_count": BaseMissionCount.range_start,
|
|
||||||
"sonic_mission_2": False,
|
|
||||||
"sonic_mission_3": False,
|
|
||||||
"sonic_mission_4": False,
|
|
||||||
"sonic_mission_5": False,
|
|
||||||
|
|
||||||
"shadow_mission_count": BaseMissionCount.range_start,
|
|
||||||
"shadow_mission_2": False,
|
|
||||||
"shadow_mission_3": False,
|
|
||||||
"shadow_mission_4": False,
|
|
||||||
"shadow_mission_5": False,
|
|
||||||
|
|
||||||
"tails_mission_count": BaseMissionCount.range_start,
|
|
||||||
"tails_mission_2": False,
|
|
||||||
"tails_mission_3": False,
|
|
||||||
"tails_mission_4": False,
|
|
||||||
"tails_mission_5": False,
|
|
||||||
|
|
||||||
"eggman_mission_count": BaseMissionCount.range_start,
|
|
||||||
"eggman_mission_2": False,
|
|
||||||
"eggman_mission_3": False,
|
|
||||||
"eggman_mission_4": False,
|
|
||||||
"eggman_mission_5": False,
|
|
||||||
|
|
||||||
"knuckles_mission_count": BaseMissionCount.range_start,
|
|
||||||
"knuckles_mission_2": False,
|
|
||||||
"knuckles_mission_3": False,
|
|
||||||
"knuckles_mission_4": False,
|
|
||||||
"knuckles_mission_5": False,
|
|
||||||
|
|
||||||
"rouge_mission_count": BaseMissionCount.range_start,
|
|
||||||
"rouge_mission_2": False,
|
|
||||||
"rouge_mission_3": False,
|
|
||||||
"rouge_mission_4": False,
|
|
||||||
"rouge_mission_5": False,
|
|
||||||
|
|
||||||
"kart_mission_count": BaseMissionCount.range_start,
|
|
||||||
"kart_mission_2": False,
|
|
||||||
"kart_mission_3": False,
|
|
||||||
"kart_mission_4": False,
|
|
||||||
"kart_mission_5": False,
|
|
||||||
|
|
||||||
"cannons_core_mission_count": BaseMissionCount.range_start,
|
|
||||||
"cannons_core_mission_2": False,
|
|
||||||
"cannons_core_mission_3": False,
|
|
||||||
"cannons_core_mission_4": False,
|
|
||||||
"cannons_core_mission_5": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
allsanity_no_chao = {
|
|
||||||
"goal": Goal.option_cannons_core_boss_rush,
|
|
||||||
"boss_rush_shuffle": BossRushShuffle.option_chaos,
|
|
||||||
"minigame_madness_requirement": MinigameMadnessRequirement.range_end,
|
|
||||||
"minigame_madness_minimum": MinigameMadnessMinimum.range_end,
|
|
||||||
"max_emblem_cap": MaximumEmblemCap.range_end,
|
|
||||||
|
|
||||||
"mission_shuffle": True,
|
|
||||||
"required_cannons_core_missions": RequiredCannonsCoreMissions.option_all_active,
|
|
||||||
"emblem_percentage_for_cannons_core": EmblemPercentageForCannonsCore.range_end,
|
|
||||||
"number_of_level_gates": NumberOfLevelGates.range_end,
|
|
||||||
"level_gate_costs": LevelGateCosts.option_high,
|
|
||||||
|
|
||||||
"keysanity": True,
|
|
||||||
"whistlesanity": Whistlesanity.option_both,
|
|
||||||
"beetlesanity": True,
|
|
||||||
"omosanity": True,
|
|
||||||
"animalsanity": True,
|
|
||||||
"itemboxsanity": ItemBoxsanity.option_all,
|
|
||||||
"bigsanity": True,
|
|
||||||
"kart_race_checks": KartRaceChecks.option_full,
|
|
||||||
|
|
||||||
"junk_fill_percentage": 25,
|
|
||||||
"trap_fill_percentage": 25,
|
|
||||||
"omochao_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"timestop_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"confusion_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"tiny_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"gravity_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"exposition_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"ice_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"slow_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"cutscene_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"reverse_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"literature_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"controller_drift_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"poison_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"bee_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"pong_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"breakout_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"fishing_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"trivia_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"pokemon_trivia_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"pokemon_count_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"number_sequence_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"light_up_path_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"pinball_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"math_quiz_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"snake_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"input_sequence_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"minigame_trap_difficulty": MinigameTrapDifficulty.option_chaos,
|
|
||||||
"big_fishing_difficulty": BigFishingDifficulty.option_chaos,
|
|
||||||
|
|
||||||
"music_shuffle": MusicShuffle.option_full,
|
|
||||||
"voice_shuffle": VoiceShuffle.option_shuffled,
|
|
||||||
|
|
||||||
"sonic_mission_count": BaseMissionCount.range_end,
|
|
||||||
"sonic_mission_2": True,
|
|
||||||
"sonic_mission_3": True,
|
|
||||||
"sonic_mission_4": True,
|
|
||||||
"sonic_mission_5": True,
|
|
||||||
|
|
||||||
"shadow_mission_count": BaseMissionCount.range_end,
|
|
||||||
"shadow_mission_2": True,
|
|
||||||
"shadow_mission_3": True,
|
|
||||||
"shadow_mission_4": True,
|
|
||||||
"shadow_mission_5": True,
|
|
||||||
|
|
||||||
"tails_mission_count": BaseMissionCount.range_end,
|
|
||||||
"tails_mission_2": True,
|
|
||||||
"tails_mission_3": True,
|
|
||||||
"tails_mission_4": True,
|
|
||||||
"tails_mission_5": True,
|
|
||||||
|
|
||||||
"eggman_mission_count": BaseMissionCount.range_end,
|
|
||||||
"eggman_mission_2": True,
|
|
||||||
"eggman_mission_3": True,
|
|
||||||
"eggman_mission_4": True,
|
|
||||||
"eggman_mission_5": True,
|
|
||||||
|
|
||||||
"knuckles_mission_count": BaseMissionCount.range_end,
|
|
||||||
"knuckles_mission_2": True,
|
|
||||||
"knuckles_mission_3": True,
|
|
||||||
"knuckles_mission_4": True,
|
|
||||||
"knuckles_mission_5": True,
|
|
||||||
|
|
||||||
"rouge_mission_count": BaseMissionCount.range_end,
|
|
||||||
"rouge_mission_2": True,
|
|
||||||
"rouge_mission_3": True,
|
|
||||||
"rouge_mission_4": True,
|
|
||||||
"rouge_mission_5": True,
|
|
||||||
|
|
||||||
"kart_mission_count": BaseMissionCount.range_end,
|
|
||||||
"kart_mission_2": True,
|
|
||||||
"kart_mission_3": True,
|
|
||||||
"kart_mission_4": True,
|
|
||||||
"kart_mission_5": True,
|
|
||||||
|
|
||||||
"cannons_core_mission_count": BaseMissionCount.range_end,
|
|
||||||
"cannons_core_mission_2": True,
|
|
||||||
"cannons_core_mission_3": True,
|
|
||||||
"cannons_core_mission_4": True,
|
|
||||||
"cannons_core_mission_5": True,
|
|
||||||
}
|
|
||||||
|
|
||||||
allsanity = {
|
|
||||||
"goal": Goal.option_cannons_core_boss_rush,
|
|
||||||
"boss_rush_shuffle": BossRushShuffle.option_chaos,
|
|
||||||
"minigame_madness_requirement": MinigameMadnessRequirement.range_end,
|
|
||||||
"minigame_madness_minimum": MinigameMadnessMinimum.range_end,
|
|
||||||
"max_emblem_cap": MaximumEmblemCap.range_end,
|
|
||||||
|
|
||||||
"mission_shuffle": True,
|
|
||||||
"required_cannons_core_missions": RequiredCannonsCoreMissions.option_all_active,
|
|
||||||
"emblem_percentage_for_cannons_core": EmblemPercentageForCannonsCore.range_end,
|
|
||||||
"number_of_level_gates": NumberOfLevelGates.range_end,
|
|
||||||
"level_gate_costs": LevelGateCosts.option_high,
|
|
||||||
|
|
||||||
"keysanity": True,
|
|
||||||
"whistlesanity": Whistlesanity.option_both,
|
|
||||||
"beetlesanity": True,
|
|
||||||
"omosanity": True,
|
|
||||||
"animalsanity": True,
|
|
||||||
"itemboxsanity": ItemBoxsanity.option_all,
|
|
||||||
"bigsanity": True,
|
|
||||||
"kart_race_checks": KartRaceChecks.option_full,
|
|
||||||
|
|
||||||
"black_market_slots": BlackMarketSlots.range_end,
|
|
||||||
"black_market_unlock_costs": BlackMarketUnlockCosts.option_high,
|
|
||||||
"chao_race_difficulty": ChaoRaceDifficulty.option_expert,
|
|
||||||
"chao_karate_difficulty": ChaoKarateDifficulty.option_super,
|
|
||||||
"chao_stadium_checks": ChaoStadiumChecks.option_all,
|
|
||||||
"chao_animal_parts": True,
|
|
||||||
"chao_stats": ChaoStats.range_end,
|
|
||||||
"chao_stats_frequency": 1,
|
|
||||||
"chao_stats_stamina": True,
|
|
||||||
"chao_stats_hidden": True,
|
|
||||||
"chao_kindergarten": ChaoKindergarten.option_full,
|
|
||||||
|
|
||||||
"junk_fill_percentage": 25,
|
|
||||||
"trap_fill_percentage": 25,
|
|
||||||
"omochao_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"timestop_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"confusion_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"tiny_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"gravity_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"exposition_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"ice_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"slow_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"cutscene_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"reverse_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"literature_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"controller_drift_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"poison_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"bee_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"pong_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"breakout_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"fishing_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"trivia_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"pokemon_trivia_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"pokemon_count_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"number_sequence_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"light_up_path_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"pinball_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"math_quiz_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"snake_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"input_sequence_trap_weight": BaseTrapWeight.option_high,
|
|
||||||
"minigame_trap_difficulty": MinigameTrapDifficulty.option_chaos,
|
|
||||||
"big_fishing_difficulty": BigFishingDifficulty.option_chaos,
|
|
||||||
|
|
||||||
"music_shuffle": MusicShuffle.option_full,
|
|
||||||
"voice_shuffle": VoiceShuffle.option_shuffled,
|
|
||||||
|
|
||||||
"sonic_mission_count": BaseMissionCount.range_end,
|
|
||||||
"sonic_mission_2": True,
|
|
||||||
"sonic_mission_3": True,
|
|
||||||
"sonic_mission_4": True,
|
|
||||||
"sonic_mission_5": True,
|
|
||||||
|
|
||||||
"shadow_mission_count": BaseMissionCount.range_end,
|
|
||||||
"shadow_mission_2": True,
|
|
||||||
"shadow_mission_3": True,
|
|
||||||
"shadow_mission_4": True,
|
|
||||||
"shadow_mission_5": True,
|
|
||||||
|
|
||||||
"tails_mission_count": BaseMissionCount.range_end,
|
|
||||||
"tails_mission_2": True,
|
|
||||||
"tails_mission_3": True,
|
|
||||||
"tails_mission_4": True,
|
|
||||||
"tails_mission_5": True,
|
|
||||||
|
|
||||||
"eggman_mission_count": BaseMissionCount.range_end,
|
|
||||||
"eggman_mission_2": True,
|
|
||||||
"eggman_mission_3": True,
|
|
||||||
"eggman_mission_4": True,
|
|
||||||
"eggman_mission_5": True,
|
|
||||||
|
|
||||||
"knuckles_mission_count": BaseMissionCount.range_end,
|
|
||||||
"knuckles_mission_2": True,
|
|
||||||
"knuckles_mission_3": True,
|
|
||||||
"knuckles_mission_4": True,
|
|
||||||
"knuckles_mission_5": True,
|
|
||||||
|
|
||||||
"rouge_mission_count": BaseMissionCount.range_end,
|
|
||||||
"rouge_mission_2": True,
|
|
||||||
"rouge_mission_3": True,
|
|
||||||
"rouge_mission_4": True,
|
|
||||||
"rouge_mission_5": True,
|
|
||||||
|
|
||||||
"kart_mission_count": BaseMissionCount.range_end,
|
|
||||||
"kart_mission_2": True,
|
|
||||||
"kart_mission_3": True,
|
|
||||||
"kart_mission_4": True,
|
|
||||||
"kart_mission_5": True,
|
|
||||||
|
|
||||||
"cannons_core_mission_count": BaseMissionCount.range_end,
|
|
||||||
"cannons_core_mission_2": True,
|
|
||||||
"cannons_core_mission_3": True,
|
|
||||||
"cannons_core_mission_4": True,
|
|
||||||
"cannons_core_mission_5": True,
|
|
||||||
}
|
|
||||||
|
|
||||||
all_random = {
|
|
||||||
"goal": "random",
|
|
||||||
"boss_rush_shuffle": "random",
|
|
||||||
"minigame_madness_requirement": "random",
|
|
||||||
"minigame_madness_minimum": "random",
|
|
||||||
"logic_difficulty": "random",
|
|
||||||
"required_rank": "random",
|
|
||||||
"max_emblem_cap": "random",
|
|
||||||
"ring_loss": "random",
|
|
||||||
|
|
||||||
"mission_shuffle": "random",
|
|
||||||
"required_cannons_core_missions": "random",
|
|
||||||
"emblem_percentage_for_cannons_core": "random",
|
|
||||||
"number_of_level_gates": "random",
|
|
||||||
"level_gate_distribution": "random",
|
|
||||||
"level_gate_costs": "random",
|
|
||||||
|
|
||||||
"keysanity": "random",
|
|
||||||
"whistlesanity": "random",
|
|
||||||
"beetlesanity": "random",
|
|
||||||
"omosanity": "random",
|
|
||||||
"animalsanity": "random",
|
|
||||||
"itemboxsanity": "random",
|
|
||||||
"bigsanity": "random",
|
|
||||||
"kart_race_checks": "random",
|
|
||||||
|
|
||||||
"black_market_slots": "random",
|
|
||||||
"black_market_unlock_costs": "random",
|
|
||||||
"black_market_price_multiplier": "random",
|
|
||||||
"chao_race_difficulty": "random",
|
|
||||||
"chao_karate_difficulty": "random",
|
|
||||||
"chao_stadium_checks": "random",
|
|
||||||
"chao_animal_parts": "random",
|
|
||||||
"chao_stats": "random",
|
|
||||||
"chao_stats_frequency": "random",
|
|
||||||
"chao_stats_stamina": "random",
|
|
||||||
"chao_stats_hidden": "random",
|
|
||||||
"chao_kindergarten": "random",
|
|
||||||
"shuffle_starting_chao_eggs": "random",
|
|
||||||
"chao_entrance_randomization": "random",
|
|
||||||
|
|
||||||
"junk_fill_percentage": "random",
|
|
||||||
"trap_fill_percentage": "random",
|
|
||||||
"omochao_trap_weight": "random",
|
|
||||||
"timestop_trap_weight": "random",
|
|
||||||
"confusion_trap_weight": "random",
|
|
||||||
"tiny_trap_weight": "random",
|
|
||||||
"gravity_trap_weight": "random",
|
|
||||||
"exposition_trap_weight": "random",
|
|
||||||
"ice_trap_weight": "random",
|
|
||||||
"slow_trap_weight": "random",
|
|
||||||
"cutscene_trap_weight": "random",
|
|
||||||
"reverse_trap_weight": "random",
|
|
||||||
"literature_trap_weight": "random",
|
|
||||||
"controller_drift_trap_weight": "random",
|
|
||||||
"poison_trap_weight": "random",
|
|
||||||
"bee_trap_weight": "random",
|
|
||||||
"pong_trap_weight": "random",
|
|
||||||
"breakout_trap_weight": "random",
|
|
||||||
"fishing_trap_weight": "random",
|
|
||||||
"trivia_trap_weight": "random",
|
|
||||||
"pokemon_trivia_trap_weight": "random",
|
|
||||||
"pokemon_count_trap_weight": "random",
|
|
||||||
"number_sequence_trap_weight": "random",
|
|
||||||
"light_up_path_trap_weight": "random",
|
|
||||||
"pinball_trap_weight": "random",
|
|
||||||
"math_quiz_trap_weight": "random",
|
|
||||||
"snake_trap_weight": "random",
|
|
||||||
"input_sequence_trap_weight": "random",
|
|
||||||
"minigame_trap_difficulty": "random",
|
|
||||||
"big_fishing_difficulty": "random",
|
|
||||||
|
|
||||||
"sadx_music": "random",
|
|
||||||
"music_shuffle": "random",
|
|
||||||
"voice_shuffle": "random",
|
|
||||||
"narrator": "random",
|
|
||||||
|
|
||||||
"sonic_mission_count": "random",
|
|
||||||
"sonic_mission_2": "random",
|
|
||||||
"sonic_mission_3": "random",
|
|
||||||
"sonic_mission_4": "random",
|
|
||||||
"sonic_mission_5": "random",
|
|
||||||
|
|
||||||
"shadow_mission_count": "random",
|
|
||||||
"shadow_mission_2": "random",
|
|
||||||
"shadow_mission_3": "random",
|
|
||||||
"shadow_mission_4": "random",
|
|
||||||
"shadow_mission_5": "random",
|
|
||||||
|
|
||||||
"tails_mission_count": "random",
|
|
||||||
"tails_mission_2": "random",
|
|
||||||
"tails_mission_3": "random",
|
|
||||||
"tails_mission_4": "random",
|
|
||||||
"tails_mission_5": "random",
|
|
||||||
|
|
||||||
"eggman_mission_count": "random",
|
|
||||||
"eggman_mission_2": "random",
|
|
||||||
"eggman_mission_3": "random",
|
|
||||||
"eggman_mission_4": "random",
|
|
||||||
"eggman_mission_5": "random",
|
|
||||||
|
|
||||||
"knuckles_mission_count": "random",
|
|
||||||
"knuckles_mission_2": "random",
|
|
||||||
"knuckles_mission_3": "random",
|
|
||||||
"knuckles_mission_4": "random",
|
|
||||||
"knuckles_mission_5": "random",
|
|
||||||
|
|
||||||
"rouge_mission_count": "random",
|
|
||||||
"rouge_mission_2": "random",
|
|
||||||
"rouge_mission_3": "random",
|
|
||||||
"rouge_mission_4": "random",
|
|
||||||
"rouge_mission_5": "random",
|
|
||||||
|
|
||||||
"kart_mission_count": "random",
|
|
||||||
"kart_mission_2": "random",
|
|
||||||
"kart_mission_3": "random",
|
|
||||||
"kart_mission_4": "random",
|
|
||||||
"kart_mission_5": "random",
|
|
||||||
|
|
||||||
"cannons_core_mission_count": "random",
|
|
||||||
"cannons_core_mission_2": "random",
|
|
||||||
"cannons_core_mission_3": "random",
|
|
||||||
"cannons_core_mission_4": "random",
|
|
||||||
"cannons_core_mission_5": "random",
|
|
||||||
|
|
||||||
"ring_link": "random",
|
|
||||||
"trap_link": "random",
|
|
||||||
"death_link": "random",
|
|
||||||
}
|
|
||||||
|
|
||||||
sa2b_options_presets: Dict[str, Dict[str, Any]] = {
|
|
||||||
"Minsanity": minsanity,
|
|
||||||
"Chao-centric": chao_centric,
|
|
||||||
"Allsanity No Chao": allsanity_no_chao,
|
|
||||||
"Allsanity": allsanity,
|
|
||||||
"All Random": all_random,
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
2115
worlds/sa2b/Rules.py
2115
worlds/sa2b/Rules.py
File diff suppressed because it is too large
Load Diff
@@ -8,13 +8,12 @@ from worlds.AutoWorld import WebWorld, World
|
|||||||
from .AestheticData import chao_name_conversion, sample_chao_names, totally_real_item_names, \
|
from .AestheticData import chao_name_conversion, sample_chao_names, totally_real_item_names, \
|
||||||
all_exits, all_destinations, multi_rooms, single_rooms, room_to_exits_map, exit_to_room_map, valid_kindergarten_exits
|
all_exits, all_destinations, multi_rooms, single_rooms, room_to_exits_map, exit_to_room_map, valid_kindergarten_exits
|
||||||
from .GateBosses import get_gate_bosses, get_boss_rush_bosses, get_boss_name
|
from .GateBosses import get_gate_bosses, get_boss_rush_bosses, get_boss_name
|
||||||
from .Items import SA2BItem, ItemData, item_table, upgrades_table, emeralds_table, junk_table, minigame_trap_table, item_groups, \
|
from .Items import SA2BItem, ItemData, item_table, upgrades_table, emeralds_table, junk_table, trap_table, item_groups, \
|
||||||
eggs_table, fruits_table, seeds_table, hats_table, animals_table, chaos_drives_table, event_table
|
eggs_table, fruits_table, seeds_table, hats_table, animals_table, chaos_drives_table
|
||||||
from .Locations import SA2BLocation, all_locations, location_groups, setup_locations, chao_animal_event_location_table, black_market_location_table
|
from .Locations import SA2BLocation, all_locations, setup_locations, chao_animal_event_location_table, black_market_location_table
|
||||||
from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions, print_mission_orders_to_spoiler
|
from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions
|
||||||
from .Names import ItemName, LocationName
|
from .Names import ItemName, LocationName
|
||||||
from .Options import SA2BOptions, sa2b_option_groups
|
from .Options import SA2BOptions, sa2b_option_groups
|
||||||
from .Presets import sa2b_options_presets
|
|
||||||
from .Regions import create_regions, shuffleable_regions, connect_regions, LevelGate, gate_0_whitelist_regions, \
|
from .Regions import create_regions, shuffleable_regions, connect_regions, LevelGate, gate_0_whitelist_regions, \
|
||||||
gate_0_blacklist_regions
|
gate_0_blacklist_regions
|
||||||
from .Rules import set_rules
|
from .Rules import set_rules
|
||||||
@@ -34,7 +33,6 @@ class SA2BWeb(WebWorld):
|
|||||||
|
|
||||||
tutorials = [setup_en]
|
tutorials = [setup_en]
|
||||||
option_groups = sa2b_option_groups
|
option_groups = sa2b_option_groups
|
||||||
options_presets = sa2b_options_presets
|
|
||||||
|
|
||||||
|
|
||||||
def check_for_impossible_shuffle(shuffled_levels: typing.List[int], gate_0_range: int, multiworld: MultiWorld):
|
def check_for_impossible_shuffle(shuffled_levels: typing.List[int], gate_0_range: int, multiworld: MultiWorld):
|
||||||
@@ -62,14 +60,11 @@ class SA2BWorld(World):
|
|||||||
topology_present = False
|
topology_present = False
|
||||||
|
|
||||||
item_name_groups = item_groups
|
item_name_groups = item_groups
|
||||||
location_name_groups = location_groups
|
|
||||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||||
location_name_to_id = all_locations
|
location_name_to_id = all_locations
|
||||||
|
|
||||||
location_table: typing.Dict[str, int]
|
location_table: typing.Dict[str, int]
|
||||||
|
|
||||||
shuffled_region_list: typing.List[int]
|
|
||||||
levels_per_gate: typing.List[int]
|
|
||||||
mission_map: typing.Dict[int, int]
|
mission_map: typing.Dict[int, int]
|
||||||
mission_count_map: typing.Dict[int, int]
|
mission_count_map: typing.Dict[int, int]
|
||||||
emblems_for_cannons_core: int
|
emblems_for_cannons_core: int
|
||||||
@@ -83,7 +78,7 @@ class SA2BWorld(World):
|
|||||||
|
|
||||||
def fill_slot_data(self) -> dict:
|
def fill_slot_data(self) -> dict:
|
||||||
return {
|
return {
|
||||||
"ModVersion": 204,
|
"ModVersion": 203,
|
||||||
"Goal": self.options.goal.value,
|
"Goal": self.options.goal.value,
|
||||||
"MusicMap": self.generate_music_data(),
|
"MusicMap": self.generate_music_data(),
|
||||||
"VoiceMap": self.generate_voice_data(),
|
"VoiceMap": self.generate_voice_data(),
|
||||||
@@ -94,20 +89,14 @@ class SA2BWorld(World):
|
|||||||
"MusicShuffle": self.options.music_shuffle.value,
|
"MusicShuffle": self.options.music_shuffle.value,
|
||||||
"Narrator": self.options.narrator.value,
|
"Narrator": self.options.narrator.value,
|
||||||
"MinigameTrapDifficulty": self.options.minigame_trap_difficulty.value,
|
"MinigameTrapDifficulty": self.options.minigame_trap_difficulty.value,
|
||||||
"BigFishingDifficulty": self.options.big_fishing_difficulty.value,
|
|
||||||
"RingLoss": self.options.ring_loss.value,
|
"RingLoss": self.options.ring_loss.value,
|
||||||
"RingLink": self.options.ring_link.value,
|
"RingLink": self.options.ring_link.value,
|
||||||
"TrapLink": self.options.trap_link.value,
|
|
||||||
"RequiredRank": self.options.required_rank.value,
|
"RequiredRank": self.options.required_rank.value,
|
||||||
"MinigameMadnessAmount": self.options.minigame_madness_requirement.value,
|
|
||||||
"LogicDifficulty": self.options.logic_difficulty.value,
|
|
||||||
"ChaoKeys": self.options.keysanity.value,
|
"ChaoKeys": self.options.keysanity.value,
|
||||||
"Whistlesanity": self.options.whistlesanity.value,
|
"Whistlesanity": self.options.whistlesanity.value,
|
||||||
"GoldBeetles": self.options.beetlesanity.value,
|
"GoldBeetles": self.options.beetlesanity.value,
|
||||||
"OmochaoChecks": self.options.omosanity.value,
|
"OmochaoChecks": self.options.omosanity.value,
|
||||||
"AnimalChecks": self.options.animalsanity.value,
|
"AnimalChecks": self.options.animalsanity.value,
|
||||||
"ItemBoxChecks": self.options.itemboxsanity.value,
|
|
||||||
"BigChecks": self.options.bigsanity.value,
|
|
||||||
"KartRaceChecks": self.options.kart_race_checks.value,
|
"KartRaceChecks": self.options.kart_race_checks.value,
|
||||||
"ChaoStadiumChecks": self.options.chao_stadium_checks.value,
|
"ChaoStadiumChecks": self.options.chao_stadium_checks.value,
|
||||||
"ChaoRaceDifficulty": self.options.chao_race_difficulty.value,
|
"ChaoRaceDifficulty": self.options.chao_race_difficulty.value,
|
||||||
@@ -133,7 +122,6 @@ class SA2BWorld(World):
|
|||||||
"GateCosts": self.gate_costs,
|
"GateCosts": self.gate_costs,
|
||||||
"GateBosses": self.gate_bosses,
|
"GateBosses": self.gate_bosses,
|
||||||
"BossRushMap": self.boss_rush_map,
|
"BossRushMap": self.boss_rush_map,
|
||||||
"ActiveTraps": self.output_active_traps(),
|
|
||||||
"PlayerNum": self.player,
|
"PlayerNum": self.player,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,42 +151,12 @@ class SA2BWorld(World):
|
|||||||
|
|
||||||
valid_trap_weights = self.options.exposition_trap_weight.value + \
|
valid_trap_weights = self.options.exposition_trap_weight.value + \
|
||||||
self.options.reverse_trap_weight.value + \
|
self.options.reverse_trap_weight.value + \
|
||||||
self.options.literature_trap_weight.value + \
|
self.options.pong_trap_weight.value
|
||||||
self.options.controller_drift_trap_weight.value + \
|
|
||||||
self.options.poison_trap_weight.value + \
|
|
||||||
self.options.bee_trap_weight.value + \
|
|
||||||
self.options.pong_trap_weight.value + \
|
|
||||||
self.options.breakout_trap_weight.value + \
|
|
||||||
self.options.fishing_trap_weight.value + \
|
|
||||||
self.options.trivia_trap_weight.value + \
|
|
||||||
self.options.pokemon_trivia_trap_weight.value + \
|
|
||||||
self.options.pokemon_count_trap_weight.value + \
|
|
||||||
self.options.number_sequence_trap_weight.value + \
|
|
||||||
self.options.light_up_path_trap_weight.value + \
|
|
||||||
self.options.pinball_trap_weight.value + \
|
|
||||||
self.options.math_quiz_trap_weight.value + \
|
|
||||||
self.options.snake_trap_weight.value + \
|
|
||||||
self.options.input_sequence_trap_weight.value
|
|
||||||
|
|
||||||
if valid_trap_weights == 0:
|
if valid_trap_weights == 0:
|
||||||
self.options.exposition_trap_weight.value = 4
|
self.options.exposition_trap_weight.value = 4
|
||||||
self.options.reverse_trap_weight.value = 4
|
self.options.reverse_trap_weight.value = 4
|
||||||
self.options.literature_trap_weight.value = 4
|
|
||||||
self.options.controller_drift_trap_weight.value = 4
|
|
||||||
self.options.poison_trap_weight.value = 4
|
|
||||||
self.options.bee_trap_weight.value = 4
|
|
||||||
self.options.pong_trap_weight.value = 4
|
self.options.pong_trap_weight.value = 4
|
||||||
self.options.breakout_trap_weight.value = 4
|
|
||||||
self.options.fishing_trap_weight.value = 4
|
|
||||||
self.options.trivia_trap_weight.value = 4
|
|
||||||
self.options.pokemon_trivia_trap_weight.value = 4
|
|
||||||
self.options.pokemon_count_trap_weight.value = 4
|
|
||||||
self.options.number_sequence_trap_weight.value = 4
|
|
||||||
self.options.light_up_path_trap_weight.value = 4
|
|
||||||
self.options.pinball_trap_weight.value = 4
|
|
||||||
self.options.math_quiz_trap_weight.value = 4
|
|
||||||
self.options.snake_trap_weight.value = 4
|
|
||||||
self.options.input_sequence_trap_weight.value = 4
|
|
||||||
|
|
||||||
if self.options.kart_race_checks.value == 0:
|
if self.options.kart_race_checks.value == 0:
|
||||||
self.options.kart_race_checks.value = 2
|
self.options.kart_race_checks.value = 2
|
||||||
@@ -206,8 +164,8 @@ class SA2BWorld(World):
|
|||||||
self.gate_bosses = {}
|
self.gate_bosses = {}
|
||||||
self.boss_rush_map = {}
|
self.boss_rush_map = {}
|
||||||
else:
|
else:
|
||||||
self.gate_bosses = get_gate_bosses(self)
|
self.gate_bosses = get_gate_bosses(self.multiworld, self)
|
||||||
self.boss_rush_map = get_boss_rush_bosses(self)
|
self.boss_rush_map = get_boss_rush_bosses(self.multiworld, self)
|
||||||
|
|
||||||
def create_regions(self):
|
def create_regions(self):
|
||||||
self.mission_map = get_mission_table(self.multiworld, self, self.player)
|
self.mission_map = get_mission_table(self.multiworld, self, self.player)
|
||||||
@@ -219,7 +177,7 @@ class SA2BWorld(World):
|
|||||||
# Not Generate Basic
|
# Not Generate Basic
|
||||||
self.black_market_costs = dict()
|
self.black_market_costs = dict()
|
||||||
|
|
||||||
if self.options.goal.value in [0, 2, 4, 5, 6, 8]:
|
if self.options.goal.value in [0, 2, 4, 5, 6]:
|
||||||
self.multiworld.get_location(LocationName.finalhazard, self.player).place_locked_item(self.create_item(ItemName.maria))
|
self.multiworld.get_location(LocationName.finalhazard, self.player).place_locked_item(self.create_item(ItemName.maria))
|
||||||
elif self.options.goal.value == 1:
|
elif self.options.goal.value == 1:
|
||||||
self.multiworld.get_location(LocationName.green_hill, self.player).place_locked_item(self.create_item(ItemName.maria))
|
self.multiworld.get_location(LocationName.green_hill, self.player).place_locked_item(self.create_item(ItemName.maria))
|
||||||
@@ -244,7 +202,7 @@ class SA2BWorld(World):
|
|||||||
if self.options.goal.value != 3:
|
if self.options.goal.value != 3:
|
||||||
# Fill item pool with all required items
|
# Fill item pool with all required items
|
||||||
for item in {**upgrades_table}:
|
for item in {**upgrades_table}:
|
||||||
itempool += [self.create_item(item, None, self.options.goal.value)]
|
itempool += [self.create_item(item, False, self.options.goal.value)]
|
||||||
|
|
||||||
if self.options.goal.value in [1, 2, 6]:
|
if self.options.goal.value in [1, 2, 6]:
|
||||||
# Some flavor of Chaos Emerald Hunt
|
# Some flavor of Chaos Emerald Hunt
|
||||||
@@ -254,25 +212,6 @@ class SA2BWorld(World):
|
|||||||
# Black Market
|
# Black Market
|
||||||
itempool += [self.create_item(ItemName.market_token) for _ in range(self.options.black_market_slots.value)]
|
itempool += [self.create_item(ItemName.market_token) for _ in range(self.options.black_market_slots.value)]
|
||||||
|
|
||||||
if self.options.goal.value in [8]:
|
|
||||||
available_locations: int = total_required_locations - len(itempool) - self.options.number_of_level_gates.value
|
|
||||||
|
|
||||||
while (self.options.minigame_madness_requirement.value * len(minigame_trap_table)) > available_locations:
|
|
||||||
self.options.minigame_madness_requirement.value -= 1
|
|
||||||
|
|
||||||
while (self.options.minigame_madness_minimum.value * len(minigame_trap_table)) > available_locations:
|
|
||||||
self.options.minigame_madness_minimum.value -= 1
|
|
||||||
|
|
||||||
traps_to_create: int = max(self.options.minigame_madness_minimum.value, self.options.minigame_madness_requirement.value)
|
|
||||||
|
|
||||||
# Minigame Madness
|
|
||||||
for item in {**minigame_trap_table}:
|
|
||||||
for i in range(traps_to_create):
|
|
||||||
classification: ItemClassification = ItemClassification.trap
|
|
||||||
if i < self.options.minigame_madness_requirement.value:
|
|
||||||
classification |= ItemClassification.progression
|
|
||||||
itempool.append(self.create_item(item, classification))
|
|
||||||
|
|
||||||
black_market_unlock_mult = 1.0
|
black_market_unlock_mult = 1.0
|
||||||
if self.options.black_market_unlock_costs.value == 0:
|
if self.options.black_market_unlock_costs.value == 0:
|
||||||
black_market_unlock_mult = 0.5
|
black_market_unlock_mult = 0.5
|
||||||
@@ -296,12 +235,12 @@ class SA2BWorld(World):
|
|||||||
elif self.options.level_gate_costs.value == 1:
|
elif self.options.level_gate_costs.value == 1:
|
||||||
gate_cost_mult = 0.8
|
gate_cost_mult = 0.8
|
||||||
|
|
||||||
self.shuffled_region_list = list(range(30))
|
shuffled_region_list = list(range(30))
|
||||||
emblem_requirement_list = list()
|
emblem_requirement_list = list()
|
||||||
self.multiworld.random.shuffle(self.shuffled_region_list)
|
self.multiworld.random.shuffle(shuffled_region_list)
|
||||||
self.levels_per_gate = self.get_levels_per_gate()
|
levels_per_gate = self.get_levels_per_gate()
|
||||||
|
|
||||||
check_for_impossible_shuffle(self.shuffled_region_list, math.ceil(self.levels_per_gate[0]), self.multiworld)
|
check_for_impossible_shuffle(shuffled_region_list, math.ceil(levels_per_gate[0]), self.multiworld)
|
||||||
levels_added_to_gate = 0
|
levels_added_to_gate = 0
|
||||||
total_levels_added = 0
|
total_levels_added = 0
|
||||||
current_gate = 0
|
current_gate = 0
|
||||||
@@ -311,11 +250,11 @@ class SA2BWorld(World):
|
|||||||
gates = list()
|
gates = list()
|
||||||
gates.append(LevelGate(0))
|
gates.append(LevelGate(0))
|
||||||
for i in range(30):
|
for i in range(30):
|
||||||
gates[current_gate].gate_levels.append(self.shuffled_region_list[i])
|
gates[current_gate].gate_levels.append(shuffled_region_list[i])
|
||||||
emblem_requirement_list.append(current_gate_emblems)
|
emblem_requirement_list.append(current_gate_emblems)
|
||||||
levels_added_to_gate += 1
|
levels_added_to_gate += 1
|
||||||
total_levels_added += 1
|
total_levels_added += 1
|
||||||
if levels_added_to_gate >= self.levels_per_gate[current_gate]:
|
if levels_added_to_gate >= levels_per_gate[current_gate]:
|
||||||
current_gate += 1
|
current_gate += 1
|
||||||
if current_gate > self.options.number_of_level_gates.value:
|
if current_gate > self.options.number_of_level_gates.value:
|
||||||
current_gate = self.options.number_of_level_gates.value
|
current_gate = self.options.number_of_level_gates.value
|
||||||
@@ -326,19 +265,18 @@ class SA2BWorld(World):
|
|||||||
self.gate_costs[current_gate] = current_gate_emblems
|
self.gate_costs[current_gate] = current_gate_emblems
|
||||||
levels_added_to_gate = 0
|
levels_added_to_gate = 0
|
||||||
|
|
||||||
self.region_emblem_map = dict(zip(self.shuffled_region_list, emblem_requirement_list))
|
self.region_emblem_map = dict(zip(shuffled_region_list, emblem_requirement_list))
|
||||||
|
|
||||||
first_cannons_core_mission, final_cannons_core_mission = get_first_and_last_cannons_core_missions(self.mission_map, self.mission_count_map)
|
first_cannons_core_mission, final_cannons_core_mission = get_first_and_last_cannons_core_missions(self.mission_map, self.mission_count_map)
|
||||||
|
|
||||||
connect_regions(self.multiworld, self, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses, self.boss_rush_map, first_cannons_core_mission, final_cannons_core_mission)
|
connect_regions(self.multiworld, self, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses, self.boss_rush_map, first_cannons_core_mission, final_cannons_core_mission)
|
||||||
|
|
||||||
max_required_emblems = max(max(emblem_requirement_list), self.emblems_for_cannons_core)
|
max_required_emblems = max(max(emblem_requirement_list), self.emblems_for_cannons_core)
|
||||||
max_required_emblems = min(int(max_required_emblems * 1.1), total_emblem_count)
|
|
||||||
itempool += [self.create_item(ItemName.emblem) for _ in range(max_required_emblems)]
|
itempool += [self.create_item(ItemName.emblem) for _ in range(max_required_emblems)]
|
||||||
|
|
||||||
non_required_emblems = (total_emblem_count - max_required_emblems)
|
non_required_emblems = (total_emblem_count - max_required_emblems)
|
||||||
junk_count = math.floor(non_required_emblems * (self.options.junk_fill_percentage.value / 100.0))
|
junk_count = math.floor(non_required_emblems * (self.options.junk_fill_percentage.value / 100.0))
|
||||||
itempool += [self.create_item(ItemName.emblem, ItemClassification.filler) for _ in range(non_required_emblems - junk_count)]
|
itempool += [self.create_item(ItemName.emblem, True) for _ in range(non_required_emblems - junk_count)]
|
||||||
|
|
||||||
# Carve Traps out of junk_count
|
# Carve Traps out of junk_count
|
||||||
trap_weights = []
|
trap_weights = []
|
||||||
@@ -353,22 +291,7 @@ class SA2BWorld(World):
|
|||||||
trap_weights += ([ItemName.slow_trap] * self.options.slow_trap_weight.value)
|
trap_weights += ([ItemName.slow_trap] * self.options.slow_trap_weight.value)
|
||||||
trap_weights += ([ItemName.cutscene_trap] * self.options.cutscene_trap_weight.value)
|
trap_weights += ([ItemName.cutscene_trap] * self.options.cutscene_trap_weight.value)
|
||||||
trap_weights += ([ItemName.reverse_trap] * self.options.reverse_trap_weight.value)
|
trap_weights += ([ItemName.reverse_trap] * self.options.reverse_trap_weight.value)
|
||||||
trap_weights += ([ItemName.literature_trap] * self.options.literature_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.controller_drift_trap] * self.options.controller_drift_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.poison_trap] * self.options.poison_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.bee_trap] * self.options.bee_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.pong_trap] * self.options.pong_trap_weight.value)
|
trap_weights += ([ItemName.pong_trap] * self.options.pong_trap_weight.value)
|
||||||
trap_weights += ([ItemName.breakout_trap] * self.options.breakout_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.fishing_trap] * self.options.fishing_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.trivia_trap] * self.options.trivia_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.pokemon_trivia_trap] * self.options.pokemon_trivia_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.pokemon_count_trap] * self.options.pokemon_count_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.number_sequence_trap] * self.options.number_sequence_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.light_up_path_trap] * self.options.light_up_path_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.pinball_trap] * self.options.pinball_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.math_quiz_trap] * self.options.math_quiz_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.snake_trap] * self.options.snake_trap_weight.value)
|
|
||||||
trap_weights += ([ItemName.input_sequence_trap] * self.options.input_sequence_trap_weight.value)
|
|
||||||
|
|
||||||
junk_count += extra_junk_count
|
junk_count += extra_junk_count
|
||||||
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.options.trap_fill_percentage.value / 100.0))
|
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.options.trap_fill_percentage.value / 100.0))
|
||||||
@@ -424,15 +347,11 @@ class SA2BWorld(World):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_item(self, name: str, force_classification=None, goal=0) -> Item:
|
def create_item(self, name: str, force_non_progression=False, goal=0) -> Item:
|
||||||
data = None
|
data = item_table[name]
|
||||||
if name in event_table:
|
|
||||||
data = event_table[name]
|
|
||||||
else:
|
|
||||||
data = item_table[name]
|
|
||||||
|
|
||||||
if force_classification is not None:
|
if force_non_progression:
|
||||||
classification = force_classification
|
classification = ItemClassification.filler
|
||||||
elif name == ItemName.emblem or \
|
elif name == ItemName.emblem or \
|
||||||
name in emeralds_table.keys() or \
|
name in emeralds_table.keys() or \
|
||||||
(name == ItemName.knuckles_shovel_claws and goal in [4, 5]):
|
(name == ItemName.knuckles_shovel_claws and goal in [4, 5]):
|
||||||
@@ -461,16 +380,9 @@ class SA2BWorld(World):
|
|||||||
set_rules(self.multiworld, self, self.player, self.gate_bosses, self.boss_rush_map, self.mission_map, self.mission_count_map, self.black_market_costs)
|
set_rules(self.multiworld, self, self.player, self.gate_bosses, self.boss_rush_map, self.mission_map, self.mission_count_map, self.black_market_costs)
|
||||||
|
|
||||||
def write_spoiler(self, spoiler_handle: typing.TextIO):
|
def write_spoiler(self, spoiler_handle: typing.TextIO):
|
||||||
print_mission_orders_to_spoiler(self.mission_map,
|
|
||||||
self.mission_count_map,
|
|
||||||
self.shuffled_region_list,
|
|
||||||
self.levels_per_gate,
|
|
||||||
self.multiworld.player_name[self.player],
|
|
||||||
spoiler_handle)
|
|
||||||
|
|
||||||
if self.options.number_of_level_gates.value > 0 or self.options.goal.value in [4, 5, 6]:
|
if self.options.number_of_level_gates.value > 0 or self.options.goal.value in [4, 5, 6]:
|
||||||
spoiler_handle.write("\n")
|
spoiler_handle.write("\n")
|
||||||
header_text = "SA2 Bosses for {}:\n"
|
header_text = "Sonic Adventure 2 Bosses for {}:\n"
|
||||||
header_text = header_text.format(self.multiworld.player_name[self.player])
|
header_text = header_text.format(self.multiworld.player_name[self.player])
|
||||||
spoiler_handle.write(header_text)
|
spoiler_handle.write(header_text)
|
||||||
|
|
||||||
@@ -523,20 +435,20 @@ class SA2BWorld(World):
|
|||||||
continue
|
continue
|
||||||
level_region = exit.connected_region
|
level_region = exit.connected_region
|
||||||
for location in level_region.locations:
|
for location in level_region.locations:
|
||||||
if location.address != None:
|
er_hint_data[location.address] = gate_name
|
||||||
er_hint_data[location.address] = gate_name
|
|
||||||
|
|
||||||
for i in range(self.options.black_market_slots.value):
|
for i in range(self.options.black_market_slots.value):
|
||||||
location = self.multiworld.get_location(LocationName.chao_black_market_base + str(i + 1), self.player)
|
location = self.multiworld.get_location(LocationName.chao_black_market_base + str(i + 1), self.player)
|
||||||
er_hint_data[location.address] = str(self.black_market_costs[i]) + " " + str(ItemName.market_token)
|
er_hint_data[location.address] = str(self.black_market_costs[i]) + " " + str(ItemName.market_token)
|
||||||
|
|
||||||
|
|
||||||
hint_data[self.player] = er_hint_data
|
hint_data[self.player] = er_hint_data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage_fill_hook(cls, multiworld: MultiWorld, progitempool, usefulitempool, filleritempool, fill_locations):
|
def stage_fill_hook(cls, multiworld: MultiWorld, progitempool, usefulitempool, filleritempool, fill_locations):
|
||||||
if multiworld.get_game_players("Sonic Adventure 2 Battle"):
|
if multiworld.get_game_players("Sonic Adventure 2 Battle"):
|
||||||
progitempool.sort(
|
progitempool.sort(
|
||||||
key=lambda item: 0 if ("Emblem" in item.name and item.game == "Sonic Adventure 2 Battle") else 1)
|
key=lambda item: 0 if (item.name != 'Emblem') else 1)
|
||||||
|
|
||||||
def get_levels_per_gate(self) -> list:
|
def get_levels_per_gate(self) -> list:
|
||||||
levels_per_gate = list()
|
levels_per_gate = list()
|
||||||
@@ -574,39 +486,6 @@ class SA2BWorld(World):
|
|||||||
|
|
||||||
return levels_per_gate
|
return levels_per_gate
|
||||||
|
|
||||||
def output_active_traps(self) -> typing.Dict[int, int]:
|
|
||||||
trap_data = {}
|
|
||||||
|
|
||||||
trap_data[0x30] = self.options.omochao_trap_weight.value
|
|
||||||
trap_data[0x31] = self.options.timestop_trap_weight.value
|
|
||||||
trap_data[0x32] = self.options.confusion_trap_weight.value
|
|
||||||
trap_data[0x33] = self.options.tiny_trap_weight.value
|
|
||||||
trap_data[0x34] = self.options.gravity_trap_weight.value
|
|
||||||
trap_data[0x35] = self.options.exposition_trap_weight.value
|
|
||||||
trap_data[0x37] = self.options.ice_trap_weight.value
|
|
||||||
trap_data[0x38] = self.options.slow_trap_weight.value
|
|
||||||
trap_data[0x39] = self.options.cutscene_trap_weight.value
|
|
||||||
trap_data[0x3A] = self.options.reverse_trap_weight.value
|
|
||||||
trap_data[0x3B] = self.options.literature_trap_weight.value
|
|
||||||
trap_data[0x3C] = self.options.controller_drift_trap_weight.value
|
|
||||||
trap_data[0x3D] = self.options.poison_trap_weight.value
|
|
||||||
trap_data[0x3E] = self.options.bee_trap_weight.value
|
|
||||||
|
|
||||||
trap_data[0x50] = self.options.pong_trap_weight.value
|
|
||||||
trap_data[0x51] = self.options.breakout_trap_weight.value
|
|
||||||
trap_data[0x52] = self.options.fishing_trap_weight.value
|
|
||||||
trap_data[0x53] = self.options.trivia_trap_weight.value
|
|
||||||
trap_data[0x54] = self.options.pokemon_trivia_trap_weight.value
|
|
||||||
trap_data[0x55] = self.options.pokemon_count_trap_weight.value
|
|
||||||
trap_data[0x56] = self.options.number_sequence_trap_weight.value
|
|
||||||
trap_data[0x57] = self.options.light_up_path_trap_weight.value
|
|
||||||
trap_data[0x58] = self.options.pinball_trap_weight.value
|
|
||||||
trap_data[0x59] = self.options.math_quiz_trap_weight.value
|
|
||||||
trap_data[0x5A] = self.options.snake_trap_weight.value
|
|
||||||
trap_data[0x5B] = self.options.input_sequence_trap_weight.value
|
|
||||||
|
|
||||||
return trap_data
|
|
||||||
|
|
||||||
def any_chao_locations_active(self) -> bool:
|
def any_chao_locations_active(self) -> bool:
|
||||||
if self.options.chao_race_difficulty.value > 0 or \
|
if self.options.chao_race_difficulty.value > 0 or \
|
||||||
self.options.chao_karate_difficulty.value > 0 or \
|
self.options.chao_karate_difficulty.value > 0 or \
|
||||||
@@ -807,6 +686,7 @@ class SA2BWorld(World):
|
|||||||
exit_choice = self.random.choice(valid_kindergarten_exits)
|
exit_choice = self.random.choice(valid_kindergarten_exits)
|
||||||
exit_room = exit_to_room_map[exit_choice]
|
exit_room = exit_to_room_map[exit_choice]
|
||||||
all_exits_copy.remove(exit_choice)
|
all_exits_copy.remove(exit_choice)
|
||||||
|
multi_rooms_copy.remove(exit_room)
|
||||||
|
|
||||||
destination = 0x06
|
destination = 0x06
|
||||||
single_rooms_copy.remove(destination)
|
single_rooms_copy.remove(destination)
|
||||||
@@ -843,8 +723,7 @@ class SA2BWorld(World):
|
|||||||
|
|
||||||
er_layout[exit_choice] = destination
|
er_layout[exit_choice] = destination
|
||||||
|
|
||||||
possible_reverse_exits = [exit for exit in room_to_exits_map[destination] if exit in all_exits_copy]
|
reverse_exit = self.random.choice(room_to_exits_map[destination])
|
||||||
reverse_exit = self.random.choice(possible_reverse_exits)
|
|
||||||
|
|
||||||
er_layout[reverse_exit] = exit_room
|
er_layout[reverse_exit] = exit_room
|
||||||
|
|
||||||
|
|||||||
@@ -214,67 +214,67 @@ class StardewValleyWorld(World):
|
|||||||
def setup_victory(self):
|
def setup_victory(self):
|
||||||
if self.options.goal == Goal.option_community_center:
|
if self.options.goal == Goal.option_community_center:
|
||||||
self.create_event_location(location_table[GoalName.community_center],
|
self.create_event_location(location_table[GoalName.community_center],
|
||||||
self.logic.goal.can_complete_community_center(),
|
self.logic.bundle.can_complete_community_center,
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_grandpa_evaluation:
|
elif self.options.goal == Goal.option_grandpa_evaluation:
|
||||||
self.create_event_location(location_table[GoalName.grandpa_evaluation],
|
self.create_event_location(location_table[GoalName.grandpa_evaluation],
|
||||||
self.logic.goal.can_finish_grandpa_evaluation(),
|
self.logic.can_finish_grandpa_evaluation(),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_bottom_of_the_mines:
|
elif self.options.goal == Goal.option_bottom_of_the_mines:
|
||||||
self.create_event_location(location_table[GoalName.bottom_of_the_mines],
|
self.create_event_location(location_table[GoalName.bottom_of_the_mines],
|
||||||
self.logic.goal.can_complete_bottom_of_the_mines(),
|
True_(),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_cryptic_note:
|
elif self.options.goal == Goal.option_cryptic_note:
|
||||||
self.create_event_location(location_table[GoalName.cryptic_note],
|
self.create_event_location(location_table[GoalName.cryptic_note],
|
||||||
self.logic.goal.can_complete_cryptic_note(),
|
self.logic.quest.can_complete_quest("Cryptic Note"),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_master_angler:
|
elif self.options.goal == Goal.option_master_angler:
|
||||||
self.create_event_location(location_table[GoalName.master_angler],
|
self.create_event_location(location_table[GoalName.master_angler],
|
||||||
self.logic.goal.can_complete_master_angler(),
|
self.logic.fishing.can_catch_every_fish_for_fishsanity(),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_complete_collection:
|
elif self.options.goal == Goal.option_complete_collection:
|
||||||
self.create_event_location(location_table[GoalName.complete_museum],
|
self.create_event_location(location_table[GoalName.complete_museum],
|
||||||
self.logic.goal.can_complete_complete_collection(),
|
self.logic.museum.can_complete_museum(),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_full_house:
|
elif self.options.goal == Goal.option_full_house:
|
||||||
self.create_event_location(location_table[GoalName.full_house],
|
self.create_event_location(location_table[GoalName.full_house],
|
||||||
self.logic.goal.can_complete_full_house(),
|
(self.logic.relationship.has_children(2) & self.logic.relationship.can_reproduce()),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_greatest_walnut_hunter:
|
elif self.options.goal == Goal.option_greatest_walnut_hunter:
|
||||||
self.create_event_location(location_table[GoalName.greatest_walnut_hunter],
|
self.create_event_location(location_table[GoalName.greatest_walnut_hunter],
|
||||||
self.logic.goal.can_complete_greatest_walnut_hunter(),
|
self.logic.walnut.has_walnut(130),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_protector_of_the_valley:
|
elif self.options.goal == Goal.option_protector_of_the_valley:
|
||||||
self.create_event_location(location_table[GoalName.protector_of_the_valley],
|
self.create_event_location(location_table[GoalName.protector_of_the_valley],
|
||||||
self.logic.goal.can_complete_protector_of_the_valley(),
|
self.logic.monster.can_complete_all_monster_slaying_goals(),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_full_shipment:
|
elif self.options.goal == Goal.option_full_shipment:
|
||||||
self.create_event_location(location_table[GoalName.full_shipment],
|
self.create_event_location(location_table[GoalName.full_shipment],
|
||||||
self.logic.goal.can_complete_full_shipment(self.get_all_location_names()),
|
self.logic.shipping.can_ship_everything_in_slot(self.get_all_location_names()),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_gourmet_chef:
|
elif self.options.goal == Goal.option_gourmet_chef:
|
||||||
self.create_event_location(location_table[GoalName.gourmet_chef],
|
self.create_event_location(location_table[GoalName.gourmet_chef],
|
||||||
self.logic.goal.can_complete_gourmet_chef(),
|
self.logic.cooking.can_cook_everything,
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_craft_master:
|
elif self.options.goal == Goal.option_craft_master:
|
||||||
self.create_event_location(location_table[GoalName.craft_master],
|
self.create_event_location(location_table[GoalName.craft_master],
|
||||||
self.logic.goal.can_complete_craft_master(),
|
self.logic.crafting.can_craft_everything,
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_legend:
|
elif self.options.goal == Goal.option_legend:
|
||||||
self.create_event_location(location_table[GoalName.legend],
|
self.create_event_location(location_table[GoalName.legend],
|
||||||
self.logic.goal.can_complete_legend(),
|
self.logic.money.can_have_earned_total(10_000_000),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_mystery_of_the_stardrops:
|
elif self.options.goal == Goal.option_mystery_of_the_stardrops:
|
||||||
self.create_event_location(location_table[GoalName.mystery_of_the_stardrops],
|
self.create_event_location(location_table[GoalName.mystery_of_the_stardrops],
|
||||||
self.logic.goal.can_complete_mystery_of_the_stardrop(),
|
self.logic.has_all_stardrops(),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_allsanity:
|
elif self.options.goal == Goal.option_allsanity:
|
||||||
self.create_event_location(location_table[GoalName.allsanity],
|
self.create_event_location(location_table[GoalName.allsanity],
|
||||||
self.logic.goal.can_complete_allsanity(),
|
HasProgressionPercent(self.player, 100),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
elif self.options.goal == Goal.option_perfection:
|
elif self.options.goal == Goal.option_perfection:
|
||||||
self.create_event_location(location_table[GoalName.perfection],
|
self.create_event_location(location_table[GoalName.perfection],
|
||||||
self.logic.goal.can_complete_perfection(),
|
HasProgressionPercent(self.player, 100),
|
||||||
Event.victory)
|
Event.victory)
|
||||||
|
|
||||||
self.multiworld.completion_condition[self.player] = lambda state: state.has(Event.victory, self.player)
|
self.multiworld.completion_condition[self.player] = lambda state: state.has(Event.victory, self.player)
|
||||||
|
|||||||
@@ -13,9 +13,12 @@ from .relationship_logic import RelationshipLogicMixin
|
|||||||
from .season_logic import SeasonLogicMixin
|
from .season_logic import SeasonLogicMixin
|
||||||
from .skill_logic import SkillLogicMixin
|
from .skill_logic import SkillLogicMixin
|
||||||
from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSource, FriendshipSource, \
|
from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSource, FriendshipSource, \
|
||||||
QueenOfSauceSource, CookingRecipe, ShopFriendshipSource
|
QueenOfSauceSource, CookingRecipe, ShopFriendshipSource, \
|
||||||
|
all_cooking_recipes_by_name
|
||||||
from ..data.recipe_source import CutsceneSource, ShopTradeSource
|
from ..data.recipe_source import CutsceneSource, ShopTradeSource
|
||||||
|
from ..locations import locations_by_tag, LocationTags
|
||||||
from ..options import Chefsanity
|
from ..options import Chefsanity
|
||||||
|
from ..options import ExcludeGingerIsland
|
||||||
from ..stardew_rule import StardewRule, True_, False_
|
from ..stardew_rule import StardewRule, True_, False_
|
||||||
from ..strings.region_names import LogicRegion
|
from ..strings.region_names import LogicRegion
|
||||||
from ..strings.skill_names import Skill
|
from ..strings.skill_names import Skill
|
||||||
@@ -89,3 +92,17 @@ BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]
|
|||||||
@cache_self1
|
@cache_self1
|
||||||
def received_recipe(self, meal_name: str):
|
def received_recipe(self, meal_name: str):
|
||||||
return self.logic.received(f"{meal_name} Recipe")
|
return self.logic.received(f"{meal_name} Recipe")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def can_cook_everything(self) -> StardewRule:
|
||||||
|
cooksanity_prefix = "Cook "
|
||||||
|
all_recipes_names = []
|
||||||
|
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||||
|
for location in locations_by_tag[LocationTags.COOKSANITY]:
|
||||||
|
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
|
||||||
|
continue
|
||||||
|
if location.mod_name and location.mod_name not in self.options.mods:
|
||||||
|
continue
|
||||||
|
all_recipes_names.append(location.name[len(cooksanity_prefix):])
|
||||||
|
all_recipes = [all_cooking_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
|
||||||
|
return self.logic.and_(*(self.logic.cooking.can_cook(recipe) for recipe in all_recipes))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from functools import cached_property
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from Utils import cache_self1
|
from Utils import cache_self1
|
||||||
@@ -11,10 +12,11 @@ from .relationship_logic import RelationshipLogicMixin
|
|||||||
from .skill_logic import SkillLogicMixin
|
from .skill_logic import SkillLogicMixin
|
||||||
from .special_order_logic import SpecialOrderLogicMixin
|
from .special_order_logic import SpecialOrderLogicMixin
|
||||||
from .. import options
|
from .. import options
|
||||||
from ..data.craftable_data import CraftingRecipe
|
from ..data.craftable_data import CraftingRecipe, all_crafting_recipes_by_name
|
||||||
from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \
|
from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \
|
||||||
FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource, SkillCraftsanitySource
|
FestivalShopSource, QuestSource, StarterSource, ShopSource, SkillSource, MasterySource, FriendshipSource, SkillCraftsanitySource
|
||||||
from ..options import Craftsanity, SpecialOrderLocations
|
from ..locations import locations_by_tag, LocationTags
|
||||||
|
from ..options import Craftsanity, SpecialOrderLocations, ExcludeGingerIsland
|
||||||
from ..stardew_rule import StardewRule, True_, False_
|
from ..stardew_rule import StardewRule, True_, False_
|
||||||
from ..strings.region_names import Region
|
from ..strings.region_names import Region
|
||||||
|
|
||||||
@@ -69,8 +71,7 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
|
|||||||
if isinstance(recipe.source, ShopSource):
|
if isinstance(recipe.source, ShopSource):
|
||||||
return self.logic.money.can_spend_at(recipe.source.region, recipe.source.price)
|
return self.logic.money.can_spend_at(recipe.source.region, recipe.source.price)
|
||||||
if isinstance(recipe.source, SkillCraftsanitySource):
|
if isinstance(recipe.source, SkillCraftsanitySource):
|
||||||
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level) & self.logic.skill.can_earn_level(recipe.source.skill,
|
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level) & self.logic.skill.can_earn_level(recipe.source.skill, recipe.source.level)
|
||||||
recipe.source.level)
|
|
||||||
if isinstance(recipe.source, SkillSource):
|
if isinstance(recipe.source, SkillSource):
|
||||||
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level)
|
return self.logic.skill.has_level(recipe.source.skill, recipe.source.level)
|
||||||
if isinstance(recipe.source, MasterySource):
|
if isinstance(recipe.source, MasterySource):
|
||||||
@@ -94,3 +95,23 @@ SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]):
|
|||||||
@cache_self1
|
@cache_self1
|
||||||
def received_recipe(self, item_name: str):
|
def received_recipe(self, item_name: str):
|
||||||
return self.logic.received(f"{item_name} Recipe")
|
return self.logic.received(f"{item_name} Recipe")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def can_craft_everything(self) -> StardewRule:
|
||||||
|
craftsanity_prefix = "Craft "
|
||||||
|
all_recipes_names = []
|
||||||
|
exclude_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
|
||||||
|
exclude_masteries = not self.content.features.skill_progression.are_masteries_shuffled
|
||||||
|
for location in locations_by_tag[LocationTags.CRAFTSANITY]:
|
||||||
|
if not location.name.startswith(craftsanity_prefix):
|
||||||
|
continue
|
||||||
|
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
|
||||||
|
continue
|
||||||
|
# FIXME Remove when recipes are in content packs
|
||||||
|
if exclude_masteries and LocationTags.REQUIRES_MASTERIES in location.tags:
|
||||||
|
continue
|
||||||
|
if location.mod_name and location.mod_name not in self.options.mods:
|
||||||
|
continue
|
||||||
|
all_recipes_names.append(location.name[len(craftsanity_prefix):])
|
||||||
|
all_recipes = [all_crafting_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
|
||||||
|
return self.logic.and_(*(self.logic.crafting.can_craft(recipe) for recipe in all_recipes))
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class FishingLogicMixin(BaseLogicMixin):
|
|||||||
|
|
||||||
|
|
||||||
class FishingLogic(BaseLogic[Union[HasLogicMixin, FishingLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin,
|
class FishingLogic(BaseLogic[Union[HasLogicMixin, FishingLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin,
|
||||||
SkillLogicMixin]]):
|
SkillLogicMixin]]):
|
||||||
def can_fish_in_freshwater(self) -> StardewRule:
|
def can_fish_in_freshwater(self) -> StardewRule:
|
||||||
return self.logic.skill.can_fish() & self.logic.region.can_reach_any((Region.forest, Region.town, Region.mountain))
|
return self.logic.skill.can_fish() & self.logic.region.can_reach_any((Region.forest, Region.town, Region.mountain))
|
||||||
|
|
||||||
@@ -97,5 +97,19 @@ SkillLogicMixin]]):
|
|||||||
|
|
||||||
return self.logic.and_(*rules)
|
return self.logic.and_(*rules)
|
||||||
|
|
||||||
|
def can_catch_every_fish_for_fishsanity(self) -> StardewRule:
|
||||||
|
if not self.content.features.fishsanity.is_enabled:
|
||||||
|
return self.can_catch_every_fish()
|
||||||
|
|
||||||
|
rules = [self.has_max_fishing()]
|
||||||
|
|
||||||
|
rules.extend(
|
||||||
|
self.logic.fishing.can_catch_fish_for_fishsanity(fish)
|
||||||
|
for fish in self.content.fishes.values()
|
||||||
|
if self.content.features.fishsanity.is_included(fish)
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.logic.and_(*rules)
|
||||||
|
|
||||||
def has_specific_bait(self, fish: FishItem) -> StardewRule:
|
def has_specific_bait(self, fish: FishItem) -> StardewRule:
|
||||||
return self.can_catch_fish(fish) & self.logic.has(Machine.bait_maker)
|
return self.can_catch_fish(fish) & self.logic.has(Machine.bait_maker)
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
import typing
|
|
||||||
|
|
||||||
from .base_logic import BaseLogic, BaseLogicMixin
|
|
||||||
from ..data.craftable_data import all_crafting_recipes_by_name
|
|
||||||
from ..data.recipe_data import all_cooking_recipes_by_name
|
|
||||||
from ..locations import LocationTags, locations_by_tag
|
|
||||||
from ..mods.mod_data import ModNames
|
|
||||||
from ..options import options
|
|
||||||
from ..stardew_rule import StardewRule
|
|
||||||
from ..strings.building_names import Building
|
|
||||||
from ..strings.quest_names import Quest
|
|
||||||
from ..strings.season_names import Season
|
|
||||||
from ..strings.wallet_item_names import Wallet
|
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
|
||||||
from .logic import StardewLogic
|
|
||||||
else:
|
|
||||||
StardewLogic = object
|
|
||||||
|
|
||||||
|
|
||||||
class GoalLogicMixin(BaseLogicMixin):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.goal = GoalLogic(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class GoalLogic(BaseLogic[StardewLogic]):
|
|
||||||
|
|
||||||
def can_complete_community_center(self) -> StardewRule:
|
|
||||||
return self.logic.bundle.can_complete_community_center
|
|
||||||
|
|
||||||
def can_finish_grandpa_evaluation(self) -> StardewRule:
|
|
||||||
# https://stardewvalleywiki.com/Grandpa
|
|
||||||
rules_worth_a_point = [
|
|
||||||
self.logic.money.can_have_earned_total(50_000),
|
|
||||||
self.logic.money.can_have_earned_total(100_000),
|
|
||||||
self.logic.money.can_have_earned_total(200_000),
|
|
||||||
self.logic.money.can_have_earned_total(300_000),
|
|
||||||
self.logic.money.can_have_earned_total(500_000),
|
|
||||||
self.logic.money.can_have_earned_total(1_000_000), # first point
|
|
||||||
self.logic.money.can_have_earned_total(1_000_000), # second point
|
|
||||||
self.logic.skill.has_total_level(30),
|
|
||||||
self.logic.skill.has_total_level(50),
|
|
||||||
self.logic.museum.can_complete_museum(),
|
|
||||||
# Catching every fish not expected
|
|
||||||
# Shipping every item not expected
|
|
||||||
self.logic.relationship.can_get_married() & self.logic.building.has_house(2),
|
|
||||||
self.logic.relationship.has_hearts_with_n(5, 8), # 5 Friends
|
|
||||||
self.logic.relationship.has_hearts_with_n(10, 8), # 10 friends
|
|
||||||
self.logic.pet.has_pet_hearts(5), # Max Pet
|
|
||||||
self.logic.bundle.can_complete_community_center, # 1 point for Community Center Completion
|
|
||||||
self.logic.bundle.can_complete_community_center, # Ceremony first point
|
|
||||||
self.logic.bundle.can_complete_community_center, # Ceremony second point
|
|
||||||
self.logic.received(Wallet.skull_key),
|
|
||||||
self.logic.wallet.has_rusty_key(),
|
|
||||||
]
|
|
||||||
return self.logic.count(12, *rules_worth_a_point)
|
|
||||||
|
|
||||||
def can_complete_bottom_of_the_mines(self) -> StardewRule:
|
|
||||||
# The location is in the bottom of the mines region, so no actual rule is required
|
|
||||||
return self.logic.true_
|
|
||||||
|
|
||||||
def can_complete_cryptic_note(self) -> StardewRule:
|
|
||||||
return self.logic.quest.can_complete_quest(Quest.cryptic_note)
|
|
||||||
|
|
||||||
def can_complete_master_angler(self) -> StardewRule:
|
|
||||||
if not self.content.features.fishsanity.is_enabled:
|
|
||||||
return self.logic.fishing.can_catch_every_fish()
|
|
||||||
|
|
||||||
rules = [self.logic.fishing.has_max_fishing()]
|
|
||||||
|
|
||||||
rules.extend(
|
|
||||||
self.logic.fishing.can_catch_fish_for_fishsanity(fish)
|
|
||||||
for fish in self.content.fishes.values()
|
|
||||||
if self.content.features.fishsanity.is_included(fish)
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.logic.and_(*rules)
|
|
||||||
|
|
||||||
def can_complete_complete_collection(self) -> StardewRule:
|
|
||||||
return self.logic.museum.can_complete_museum()
|
|
||||||
|
|
||||||
def can_complete_full_house(self) -> StardewRule:
|
|
||||||
return self.logic.relationship.has_children(2) & self.logic.relationship.can_reproduce()
|
|
||||||
|
|
||||||
def can_complete_greatest_walnut_hunter(self) -> StardewRule:
|
|
||||||
return self.logic.walnut.has_walnut(130)
|
|
||||||
|
|
||||||
def can_complete_protector_of_the_valley(self) -> StardewRule:
|
|
||||||
return self.logic.monster.can_complete_all_monster_slaying_goals()
|
|
||||||
|
|
||||||
def can_complete_full_shipment(self, all_location_names_in_slot: list[str]) -> StardewRule:
|
|
||||||
if self.options.shipsanity == options.Shipsanity.option_none:
|
|
||||||
return self.logic.shipping.can_ship_everything()
|
|
||||||
|
|
||||||
rules = [self.logic.building.has_building(Building.shipping_bin)]
|
|
||||||
|
|
||||||
for shipsanity_location in locations_by_tag[LocationTags.SHIPSANITY]:
|
|
||||||
if shipsanity_location.name not in all_location_names_in_slot:
|
|
||||||
continue
|
|
||||||
rules.append(self.logic.region.can_reach_location(shipsanity_location.name))
|
|
||||||
return self.logic.and_(*rules)
|
|
||||||
|
|
||||||
def can_complete_gourmet_chef(self) -> StardewRule:
|
|
||||||
cooksanity_prefix = "Cook "
|
|
||||||
all_recipes_names = []
|
|
||||||
exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true
|
|
||||||
for location in locations_by_tag[LocationTags.COOKSANITY]:
|
|
||||||
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
|
|
||||||
continue
|
|
||||||
if location.mod_name and location.mod_name not in self.options.mods:
|
|
||||||
continue
|
|
||||||
all_recipes_names.append(location.name[len(cooksanity_prefix):])
|
|
||||||
all_recipes = [all_cooking_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
|
|
||||||
return self.logic.and_(*(self.logic.cooking.can_cook(recipe) for recipe in all_recipes))
|
|
||||||
|
|
||||||
def can_complete_craft_master(self) -> StardewRule:
|
|
||||||
craftsanity_prefix = "Craft "
|
|
||||||
all_recipes_names = []
|
|
||||||
exclude_island = self.options.exclude_ginger_island == options.ExcludeGingerIsland.option_true
|
|
||||||
exclude_masteries = not self.content.features.skill_progression.are_masteries_shuffled
|
|
||||||
for location in locations_by_tag[LocationTags.CRAFTSANITY]:
|
|
||||||
if not location.name.startswith(craftsanity_prefix):
|
|
||||||
continue
|
|
||||||
if exclude_island and LocationTags.GINGER_ISLAND in location.tags:
|
|
||||||
continue
|
|
||||||
# FIXME Remove when recipes are in content packs
|
|
||||||
if exclude_masteries and LocationTags.REQUIRES_MASTERIES in location.tags:
|
|
||||||
continue
|
|
||||||
if location.mod_name and location.mod_name not in self.options.mods:
|
|
||||||
continue
|
|
||||||
all_recipes_names.append(location.name[len(craftsanity_prefix):])
|
|
||||||
all_recipes = [all_crafting_recipes_by_name[recipe_name] for recipe_name in all_recipes_names]
|
|
||||||
return self.logic.and_(*(self.logic.crafting.can_craft(recipe) for recipe in all_recipes))
|
|
||||||
|
|
||||||
def can_complete_legend(self) -> StardewRule:
|
|
||||||
return self.logic.money.can_have_earned_total(10_000_000)
|
|
||||||
|
|
||||||
def can_complete_mystery_of_the_stardrop(self) -> StardewRule:
|
|
||||||
other_rules = []
|
|
||||||
number_of_stardrops_to_receive = 0
|
|
||||||
number_of_stardrops_to_receive += 1 # The Mines level 100
|
|
||||||
number_of_stardrops_to_receive += 1 # Old Master Cannoli
|
|
||||||
number_of_stardrops_to_receive += 1 # Museum Stardrop
|
|
||||||
number_of_stardrops_to_receive += 1 # Krobus Stardrop
|
|
||||||
|
|
||||||
# Master Angler Stardrop
|
|
||||||
if self.content.features.fishsanity.is_enabled:
|
|
||||||
number_of_stardrops_to_receive += 1
|
|
||||||
else:
|
|
||||||
other_rules.append(self.logic.fishing.can_catch_every_fish())
|
|
||||||
|
|
||||||
if self.options.festival_locations == options.FestivalLocations.option_disabled: # Fair Stardrop
|
|
||||||
other_rules.append(self.logic.season.has(Season.fall))
|
|
||||||
else:
|
|
||||||
number_of_stardrops_to_receive += 1
|
|
||||||
|
|
||||||
# Spouse Stardrop
|
|
||||||
if self.content.features.friendsanity.is_enabled:
|
|
||||||
number_of_stardrops_to_receive += 1
|
|
||||||
else:
|
|
||||||
other_rules.append(self.logic.relationship.has_hearts_with_any_bachelor(13))
|
|
||||||
|
|
||||||
if ModNames.deepwoods in self.options.mods: # Petting the Unicorn
|
|
||||||
number_of_stardrops_to_receive += 1
|
|
||||||
|
|
||||||
return self.logic.received("Stardrop", number_of_stardrops_to_receive) & self.logic.and_(*other_rules, allow_empty=True)
|
|
||||||
|
|
||||||
def can_complete_allsanity(self) -> StardewRule:
|
|
||||||
return self.logic.has_progress_percent(100)
|
|
||||||
|
|
||||||
def can_complete_perfection(self) -> StardewRule:
|
|
||||||
return self.logic.has_progress_percent(100)
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from .base_logic import BaseLogic
|
from .base_logic import BaseLogic
|
||||||
from ..stardew_rule import StardewRule, And, Or, Has, Count, true_, false_, HasProgressionPercent
|
from ..stardew_rule import StardewRule, And, Or, Has, Count, true_, false_
|
||||||
|
|
||||||
|
|
||||||
class HasLogicMixin(BaseLogic[None]):
|
class HasLogicMixin(BaseLogic[None]):
|
||||||
@@ -23,12 +23,6 @@ class HasLogicMixin(BaseLogic[None]):
|
|||||||
def has_n(self, *items: str, count: int):
|
def has_n(self, *items: str, count: int):
|
||||||
return self.count(count, *(self.has(item) for item in items))
|
return self.count(count, *(self.has(item) for item in items))
|
||||||
|
|
||||||
def has_progress_percent(self, percent: int):
|
|
||||||
assert percent >= 0, "Can't have a negative progress percent"
|
|
||||||
assert percent <= 100, "Can't have a progress percent over 100"
|
|
||||||
|
|
||||||
return HasProgressionPercent(self.player, percent)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def count(count: int, *rules: StardewRule) -> StardewRule:
|
def count(count: int, *rules: StardewRule) -> StardewRule:
|
||||||
assert rules, "Can't create a Count conditions without rules"
|
assert rules, "Can't create a Count conditions without rules"
|
||||||
@@ -53,14 +47,8 @@ class HasLogicMixin(BaseLogic[None]):
|
|||||||
return Count(rules, count)
|
return Count(rules, count)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def and_(*rules: StardewRule, allow_empty: bool = False) -> StardewRule:
|
def and_(*rules: StardewRule) -> StardewRule:
|
||||||
"""
|
assert rules, "Can't create a And conditions without rules"
|
||||||
:param rules: The rules to combine
|
|
||||||
:param allow_empty: If True, return true_ when no rules are given. Otherwise, raise an error.
|
|
||||||
"""
|
|
||||||
if not rules:
|
|
||||||
assert allow_empty, "Can't create a And conditions without rules"
|
|
||||||
return true_
|
|
||||||
|
|
||||||
if len(rules) == 1:
|
if len(rules) == 1:
|
||||||
return rules[0]
|
return rules[0]
|
||||||
@@ -68,14 +56,8 @@ class HasLogicMixin(BaseLogic[None]):
|
|||||||
return And(*rules)
|
return And(*rules)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def or_(*rules: StardewRule, allow_empty: bool = False) -> StardewRule:
|
def or_(*rules: StardewRule) -> StardewRule:
|
||||||
"""
|
assert rules, "Can't create a Or conditions without rules"
|
||||||
:param rules: The rules to combine
|
|
||||||
:param allow_empty: If True, return false_ when no rules are given. Otherwise, raise an error.
|
|
||||||
"""
|
|
||||||
if not rules:
|
|
||||||
assert allow_empty, "Can't create a Or conditions without rules"
|
|
||||||
return false_
|
|
||||||
|
|
||||||
if len(rules) == 1:
|
if len(rules) == 1:
|
||||||
return rules[0]
|
return rules[0]
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from .farming_logic import FarmingLogicMixin
|
|||||||
from .festival_logic import FestivalLogicMixin
|
from .festival_logic import FestivalLogicMixin
|
||||||
from .fishing_logic import FishingLogicMixin
|
from .fishing_logic import FishingLogicMixin
|
||||||
from .gift_logic import GiftLogicMixin
|
from .gift_logic import GiftLogicMixin
|
||||||
from .goal_logic import GoalLogicMixin
|
|
||||||
from .grind_logic import GrindLogicMixin
|
from .grind_logic import GrindLogicMixin
|
||||||
from .harvesting_logic import HarvestingLogicMixin
|
from .harvesting_logic import HarvestingLogicMixin
|
||||||
from .has_logic import HasLogicMixin
|
from .has_logic import HasLogicMixin
|
||||||
@@ -51,7 +50,8 @@ from ..data.museum_data import all_museum_items
|
|||||||
from ..data.recipe_data import all_cooking_recipes
|
from ..data.recipe_data import all_cooking_recipes
|
||||||
from ..mods.logic.magic_logic import MagicLogicMixin
|
from ..mods.logic.magic_logic import MagicLogicMixin
|
||||||
from ..mods.logic.mod_logic import ModLogicMixin
|
from ..mods.logic.mod_logic import ModLogicMixin
|
||||||
from ..options import ExcludeGingerIsland, StardewValleyOptions
|
from ..mods.mod_data import ModNames
|
||||||
|
from ..options import ExcludeGingerIsland, FestivalLocations, StardewValleyOptions
|
||||||
from ..stardew_rule import False_, True_, StardewRule
|
from ..stardew_rule import False_, True_, StardewRule
|
||||||
from ..strings.animal_names import Animal
|
from ..strings.animal_names import Animal
|
||||||
from ..strings.animal_product_names import AnimalProduct
|
from ..strings.animal_product_names import AnimalProduct
|
||||||
@@ -93,7 +93,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin
|
|||||||
CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin,
|
CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin,
|
||||||
SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin,
|
SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin,
|
||||||
SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin,
|
SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin,
|
||||||
RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, FestivalLogicMixin, WalnutLogicMixin, GoalLogicMixin):
|
RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, FestivalLogicMixin, WalnutLogicMixin):
|
||||||
player: int
|
player: int
|
||||||
options: StardewValleyOptions
|
options: StardewValleyOptions
|
||||||
content: StardewContent
|
content: StardewContent
|
||||||
@@ -375,11 +375,71 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin
|
|||||||
def can_smelt(self, item: str) -> StardewRule:
|
def can_smelt(self, item: str) -> StardewRule:
|
||||||
return self.has(Machine.furnace) & self.has(item)
|
return self.has(Machine.furnace) & self.has(item)
|
||||||
|
|
||||||
|
def can_finish_grandpa_evaluation(self) -> StardewRule:
|
||||||
|
# https://stardewvalleywiki.com/Grandpa
|
||||||
|
rules_worth_a_point = [
|
||||||
|
self.money.can_have_earned_total(50000), # 50 000g
|
||||||
|
self.money.can_have_earned_total(100000), # 100 000g
|
||||||
|
self.money.can_have_earned_total(200000), # 200 000g
|
||||||
|
self.money.can_have_earned_total(300000), # 300 000g
|
||||||
|
self.money.can_have_earned_total(500000), # 500 000g
|
||||||
|
self.money.can_have_earned_total(1000000), # 1 000 000g first point
|
||||||
|
self.money.can_have_earned_total(1000000), # 1 000 000g second point
|
||||||
|
self.skill.has_total_level(30), # Total Skills: 30
|
||||||
|
self.skill.has_total_level(50), # Total Skills: 50
|
||||||
|
self.museum.can_complete_museum(), # Completing the museum for a point
|
||||||
|
# Catching every fish not expected
|
||||||
|
# Shipping every item not expected
|
||||||
|
self.relationship.can_get_married() & self.building.has_house(2),
|
||||||
|
self.relationship.has_hearts_with_n(5, 8), # 5 Friends
|
||||||
|
self.relationship.has_hearts_with_n(10, 8), # 10 friends
|
||||||
|
self.pet.has_pet_hearts(5), # Max Pet
|
||||||
|
self.bundle.can_complete_community_center, # Community Center Completion
|
||||||
|
self.bundle.can_complete_community_center, # CC Ceremony first point
|
||||||
|
self.bundle.can_complete_community_center, # CC Ceremony second point
|
||||||
|
self.received(Wallet.skull_key), # Skull Key obtained
|
||||||
|
self.wallet.has_rusty_key(), # Rusty key obtained
|
||||||
|
]
|
||||||
|
return self.count(12, *rules_worth_a_point)
|
||||||
|
|
||||||
def has_island_trader(self) -> StardewRule:
|
def has_island_trader(self) -> StardewRule:
|
||||||
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
|
||||||
return False_()
|
return False_()
|
||||||
return self.region.can_reach(Region.island_trader)
|
return self.region.can_reach(Region.island_trader)
|
||||||
|
|
||||||
|
def has_all_stardrops(self) -> StardewRule:
|
||||||
|
other_rules = []
|
||||||
|
number_of_stardrops_to_receive = 0
|
||||||
|
number_of_stardrops_to_receive += 1 # The Mines level 100
|
||||||
|
number_of_stardrops_to_receive += 1 # Old Master Cannoli
|
||||||
|
number_of_stardrops_to_receive += 1 # Museum Stardrop
|
||||||
|
number_of_stardrops_to_receive += 1 # Krobus Stardrop
|
||||||
|
|
||||||
|
# Master Angler Stardrop
|
||||||
|
if self.content.features.fishsanity.is_enabled:
|
||||||
|
number_of_stardrops_to_receive += 1
|
||||||
|
else:
|
||||||
|
other_rules.append(self.fishing.can_catch_every_fish())
|
||||||
|
|
||||||
|
if self.options.festival_locations == FestivalLocations.option_disabled: # Fair Stardrop
|
||||||
|
other_rules.append(self.season.has(Season.fall))
|
||||||
|
else:
|
||||||
|
number_of_stardrops_to_receive += 1
|
||||||
|
|
||||||
|
# Spouse Stardrop
|
||||||
|
if self.content.features.friendsanity.is_enabled:
|
||||||
|
number_of_stardrops_to_receive += 1
|
||||||
|
else:
|
||||||
|
other_rules.append(self.relationship.has_hearts_with_any_bachelor(13))
|
||||||
|
|
||||||
|
if ModNames.deepwoods in self.options.mods: # Petting the Unicorn
|
||||||
|
number_of_stardrops_to_receive += 1
|
||||||
|
|
||||||
|
if not other_rules:
|
||||||
|
return self.received("Stardrop", number_of_stardrops_to_receive)
|
||||||
|
|
||||||
|
return self.received("Stardrop", number_of_stardrops_to_receive) & self.logic.and_(*other_rules)
|
||||||
|
|
||||||
def has_abandoned_jojamart(self) -> StardewRule:
|
def has_abandoned_jojamart(self) -> StardewRule:
|
||||||
return self.received(CommunityUpgrade.movie_theater, 1)
|
return self.received(CommunityUpgrade.movie_theater, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Union
|
from typing import Union, List
|
||||||
|
|
||||||
from Utils import cache_self1
|
from Utils import cache_self1
|
||||||
from .base_logic import BaseLogic, BaseLogicMixin
|
from .base_logic import BaseLogic, BaseLogicMixin
|
||||||
@@ -8,7 +8,7 @@ from .has_logic import HasLogicMixin
|
|||||||
from .received_logic import ReceivedLogicMixin
|
from .received_logic import ReceivedLogicMixin
|
||||||
from .region_logic import RegionLogicMixin
|
from .region_logic import RegionLogicMixin
|
||||||
from ..locations import LocationTags, locations_by_tag
|
from ..locations import LocationTags, locations_by_tag
|
||||||
from ..options import ExcludeGingerIsland
|
from ..options import ExcludeGingerIsland, Shipsanity
|
||||||
from ..options import SpecialOrderLocations
|
from ..options import SpecialOrderLocations
|
||||||
from ..stardew_rule import StardewRule
|
from ..stardew_rule import StardewRule
|
||||||
from ..strings.building_names import Building
|
from ..strings.building_names import Building
|
||||||
@@ -45,3 +45,15 @@ class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, Buil
|
|||||||
continue
|
continue
|
||||||
all_items_to_ship.append(location.name[len(shipsanity_prefix):])
|
all_items_to_ship.append(location.name[len(shipsanity_prefix):])
|
||||||
return self.logic.building.has_building(Building.shipping_bin) & self.logic.has_all(*all_items_to_ship)
|
return self.logic.building.has_building(Building.shipping_bin) & self.logic.has_all(*all_items_to_ship)
|
||||||
|
|
||||||
|
def can_ship_everything_in_slot(self, all_location_names_in_slot: List[str]) -> StardewRule:
|
||||||
|
if self.options.shipsanity == Shipsanity.option_none:
|
||||||
|
return self.logic.shipping.can_ship_everything()
|
||||||
|
|
||||||
|
rules = [self.logic.building.has_building(Building.shipping_bin)]
|
||||||
|
|
||||||
|
for shipsanity_location in locations_by_tag[LocationTags.SHIPSANITY]:
|
||||||
|
if shipsanity_location.name not in all_location_names_in_slot:
|
||||||
|
continue
|
||||||
|
rules.append(self.logic.region.can_reach_location(shipsanity_location.name))
|
||||||
|
return self.logic.and_(*rules)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class WitnessWorld(World):
|
|||||||
item_name_groups = static_witness_items.ITEM_GROUPS
|
item_name_groups = static_witness_items.ITEM_GROUPS
|
||||||
location_name_groups = static_witness_locations.AREA_LOCATION_GROUPS
|
location_name_groups = static_witness_locations.AREA_LOCATION_GROUPS
|
||||||
|
|
||||||
required_client_version = (0, 6, 0)
|
required_client_version = (0, 5, 1)
|
||||||
|
|
||||||
player_logic: WitnessPlayerLogic
|
player_logic: WitnessPlayerLogic
|
||||||
player_locations: WitnessPlayerLocations
|
player_locations: WitnessPlayerLocations
|
||||||
|
|||||||
Reference in New Issue
Block a user