mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 15:13:52 -08:00
Compare commits
16 Commits
empty-deat
...
revert-473
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e08c48238 | ||
|
|
57b94dba6f | ||
|
|
0dd188e108 | ||
|
|
bf8c840293 | ||
|
|
c0244f3018 | ||
|
|
8af8502202 | ||
|
|
42eaeb92f0 | ||
|
|
7f35eb8867 | ||
|
|
785569c40c | ||
|
|
a9eb70a881 | ||
|
|
5d3d0c8625 | ||
|
|
7e32feeea3 | ||
|
|
0d1935e757 | ||
|
|
9b3ee018e9 | ||
|
|
1de411ec89 | ||
|
|
3192799bbf |
1
.github/pyright-config.json
vendored
1
.github/pyright-config.json
vendored
@@ -2,6 +2,7 @@
|
||||
"include": [
|
||||
"../BizHawkClient.py",
|
||||
"../Patch.py",
|
||||
"../test/param.py",
|
||||
"../test/general/test_groups.py",
|
||||
"../test/general/test_helpers.py",
|
||||
"../test/general/test_memory.py",
|
||||
|
||||
4
.github/workflows/ctest.yml
vendored
4
.github/workflows/ctest.yml
vendored
@@ -36,9 +36,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756
|
||||
if: startsWith(matrix.os,'windows')
|
||||
- uses: Bacondish2023/setup-googletest@v1
|
||||
- uses: Bacondish2023/setup-googletest@49065d1f7a6d21f6134864dd65980fe5dbe06c73
|
||||
with:
|
||||
build-type: 'Release'
|
||||
- name: Build tests
|
||||
|
||||
@@ -511,7 +511,7 @@ if __name__ == '__main__':
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -1128,7 +1128,7 @@ def run_as_textclient(*args):
|
||||
args = handle_url_arg(args, parser=parser)
|
||||
|
||||
# use colorama to display colored text highlighting on windows
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -261,7 +261,7 @@ if __name__ == '__main__':
|
||||
|
||||
parser = get_base_parser()
|
||||
args = parser.parse_args()
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -803,6 +803,6 @@ async def main():
|
||||
await ctx.shutdown()
|
||||
|
||||
if __name__ == '__main__':
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -370,7 +370,7 @@ if __name__ == "__main__":
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -47,7 +47,7 @@ from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, Networ
|
||||
from BaseClasses import ItemClassification
|
||||
|
||||
min_client_version = Version(0, 1, 6)
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
|
||||
def remove_from_list(container, value):
|
||||
|
||||
@@ -346,7 +346,7 @@ if __name__ == '__main__':
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -1579,6 +1579,7 @@ def dump_player_options(multiworld: MultiWorld) -> None:
|
||||
player_output = {
|
||||
"Game": multiworld.game[player],
|
||||
"Name": multiworld.get_player_name(player),
|
||||
"ID": player,
|
||||
}
|
||||
output.append(player_output)
|
||||
for option_key, option in world.options_dataclass.type_hints.items():
|
||||
@@ -1591,7 +1592,7 @@ def dump_player_options(multiworld: MultiWorld) -> None:
|
||||
game_option_names.append(display_name)
|
||||
|
||||
with open(output_path(f"generate_{multiworld.seed_name}.csv"), mode="w", newline="") as file:
|
||||
fields = ["Game", "Name", *all_option_names]
|
||||
fields = ["ID", "Game", "Name", *all_option_names]
|
||||
writer = DictWriter(file, fields)
|
||||
writer.writeheader()
|
||||
writer.writerows(output)
|
||||
|
||||
@@ -735,6 +735,6 @@ async def main() -> None:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -500,7 +500,7 @@ def main():
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
asyncio.run(_main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -446,6 +446,6 @@ if __name__ == '__main__':
|
||||
parser = get_base_parser(description="Wargroove Client, for text interfacing.")
|
||||
|
||||
args, rest = parser.parse_known_args()
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro RandomizeButton(option_name, option) %}
|
||||
<div class="randomize-button" data-tooltip="Toggle randomization for this option!">
|
||||
<div class="randomize-button" data-tooltip="Pick a random value for this option.">
|
||||
<label for="random-{{ option_name }}">
|
||||
<input type="checkbox" id="random-{{ option_name }}" name="random-{{ option_name }}" class="randomize-checkbox" data-option-name="{{ option_name }}" {{ "checked" if option.default == "random" }} />
|
||||
🎲
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
{% else %}
|
||||
<div class="unsupported-option">
|
||||
This option is not supported. Please edit your .yaml file manually.
|
||||
This option cannot be modified here. Please edit your .yaml file manually.
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -386,7 +386,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('diff_file', default="", type=str, nargs="?",
|
||||
help='Path to a Archipelago Binary Patch file')
|
||||
args = parser.parse_args()
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -82,6 +82,38 @@ 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
|
||||
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
|
||||
|
||||
#### Using Pycharm
|
||||
@@ -100,3 +132,11 @@ next to the run and debug buttons.
|
||||
#### Running Tests without Pycharm
|
||||
|
||||
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,6 +358,34 @@ def randomize_entrances(
|
||||
if on_connect:
|
||||
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:
|
||||
nonlocal perform_validity_check
|
||||
placeable_exits = er_state.find_placeable_exits(perform_validity_check, exits)
|
||||
@@ -371,11 +399,9 @@ def randomize_entrances(
|
||||
# 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.
|
||||
exit_requirement_satisfied = (not perform_validity_check or not require_new_exits
|
||||
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)
|
||||
or target_entrance.connected_region not in er_state.placed_regions)
|
||||
if exit_requirement_satisfied and source_exit.can_connect_to(target_entrance, dead_end, er_state):
|
||||
if (needs_speculative_sweep
|
||||
if (needs_speculative_sweep(dead_end, require_new_exits, placeable_exits)
|
||||
and not er_state.test_speculative_connection(source_exit, target_entrance, exits_set)):
|
||||
continue
|
||||
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.placements))
|
||||
|
||||
def test_coupling(self):
|
||||
def test_coupled(self):
|
||||
"""tests that in coupled mode, all 2 way transitions have an inverse"""
|
||||
multiworld = generate_test_multiworld()
|
||||
generate_disconnected_region_grid(multiworld, 5)
|
||||
@@ -236,6 +236,36 @@ class TestRandomizeEntrances(unittest.TestCase):
|
||||
# if we didn't visit every placement the verification on_connect doesn't really mean much
|
||||
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):
|
||||
"""tests that in uncoupled mode, no transitions have an (intentional) inverse"""
|
||||
multiworld = generate_test_multiworld()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import unittest
|
||||
from typing import List, Tuple
|
||||
from typing import ClassVar, List, Tuple
|
||||
from unittest import TestCase
|
||||
|
||||
from BaseClasses import CollectionState, Location, MultiWorld
|
||||
@@ -7,6 +7,7 @@ from Fill import distribute_items_restrictive
|
||||
from Options import Accessibility
|
||||
from worlds.AutoWorld import AutoWorldRegister, call_all, call_single
|
||||
from ..general import gen_steps, setup_multiworld
|
||||
from ..param import classvar_matrix
|
||||
|
||||
|
||||
class MultiworldTestBase(TestCase):
|
||||
@@ -63,15 +64,18 @@ class TestAllGamesMultiworld(MultiworldTestBase):
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
|
||||
|
||||
@classvar_matrix(game=AutoWorldRegister.world_types.keys())
|
||||
class TestTwoPlayerMulti(MultiworldTestBase):
|
||||
game: ClassVar[str]
|
||||
|
||||
def test_two_player_single_game_fills(self) -> None:
|
||||
"""Tests that a multiworld of two players for each registered game world can generate."""
|
||||
for world_type in AutoWorldRegister.world_types.values():
|
||||
self.multiworld = setup_multiworld([world_type, world_type], ())
|
||||
for world in self.multiworld.worlds.values():
|
||||
world.options.accessibility.value = Accessibility.option_full
|
||||
self.assertSteps(gen_steps)
|
||||
with self.subTest("filling multiworld", games=world_type.game, seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
call_all(self.multiworld, "post_fill")
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
world_type = AutoWorldRegister.world_types[self.game]
|
||||
self.multiworld = setup_multiworld([world_type, world_type], ())
|
||||
for world in self.multiworld.worlds.values():
|
||||
world.options.accessibility.value = Accessibility.option_full
|
||||
self.assertSteps(gen_steps)
|
||||
with self.subTest("filling multiworld", games=world_type.game, seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
call_all(self.multiworld, "post_fill")
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
|
||||
46
test/param.py
Normal file
46
test/param.py
Normal file
@@ -0,0 +1,46 @@
|
||||
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
|
||||
@@ -276,6 +276,6 @@ def launch(*launch_args: str) -> None:
|
||||
|
||||
Utils.init_logging("BizHawkClient", exception_logger="Client")
|
||||
import colorama
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -261,6 +261,6 @@ def launch():
|
||||
# options = Utils.get_options()
|
||||
|
||||
import colorama
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
# 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
|
||||
|
||||
### Features:
|
||||
|
||||
@@ -39,6 +39,22 @@ move_item_data_table: Dict[str, Celeste64ItemData] = {
|
||||
ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, ItemClassification.progression),
|
||||
}
|
||||
|
||||
item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table, **unlockable_item_data_table, **move_item_data_table}
|
||||
checkpoint_item_data_table: Dict[str, Celeste64ItemData] = {
|
||||
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}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Dict, NamedTuple, Optional
|
||||
|
||||
from BaseClasses import Location
|
||||
from .Names import LocationName
|
||||
from .Names import LocationName, RegionName
|
||||
|
||||
|
||||
celeste_64_base_id: int = 0xCA0000
|
||||
@@ -17,66 +17,80 @@ class Celeste64LocationData(NamedTuple):
|
||||
|
||||
|
||||
strawberry_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.strawberry_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x00),
|
||||
LocationName.strawberry_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x01),
|
||||
LocationName.strawberry_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x02),
|
||||
LocationName.strawberry_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x03),
|
||||
LocationName.strawberry_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x04),
|
||||
LocationName.strawberry_6: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x05),
|
||||
LocationName.strawberry_7: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x06),
|
||||
LocationName.strawberry_8: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x07),
|
||||
LocationName.strawberry_9: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x08),
|
||||
LocationName.strawberry_10: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x09),
|
||||
LocationName.strawberry_11: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0A),
|
||||
LocationName.strawberry_12: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0B),
|
||||
LocationName.strawberry_13: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0C),
|
||||
LocationName.strawberry_14: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0D),
|
||||
LocationName.strawberry_15: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0E),
|
||||
LocationName.strawberry_16: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0F),
|
||||
LocationName.strawberry_17: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x10),
|
||||
LocationName.strawberry_18: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x11),
|
||||
LocationName.strawberry_19: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x12),
|
||||
LocationName.strawberry_20: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x13),
|
||||
LocationName.strawberry_21: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x14),
|
||||
LocationName.strawberry_22: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x15),
|
||||
LocationName.strawberry_23: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x16),
|
||||
LocationName.strawberry_24: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x17),
|
||||
LocationName.strawberry_25: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x18),
|
||||
LocationName.strawberry_26: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x19),
|
||||
LocationName.strawberry_27: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1A),
|
||||
LocationName.strawberry_28: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1B),
|
||||
LocationName.strawberry_29: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1C),
|
||||
LocationName.strawberry_30: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1D),
|
||||
LocationName.strawberry_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x00),
|
||||
LocationName.strawberry_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x01),
|
||||
LocationName.strawberry_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x02),
|
||||
LocationName.strawberry_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x03),
|
||||
LocationName.strawberry_5: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x04),
|
||||
LocationName.strawberry_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x05),
|
||||
LocationName.strawberry_7: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x06),
|
||||
LocationName.strawberry_8: Celeste64LocationData(RegionName.nw_girders_island, celeste_64_base_id + 0x07),
|
||||
LocationName.strawberry_9: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x08),
|
||||
LocationName.strawberry_10: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x09),
|
||||
LocationName.strawberry_11: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x0A),
|
||||
LocationName.strawberry_12: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x0B),
|
||||
LocationName.strawberry_13: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x0C),
|
||||
LocationName.strawberry_14: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0D),
|
||||
LocationName.strawberry_15: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0E),
|
||||
LocationName.strawberry_16: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0F),
|
||||
LocationName.strawberry_17: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x10),
|
||||
LocationName.strawberry_18: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x11),
|
||||
LocationName.strawberry_19: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x12),
|
||||
LocationName.strawberry_20: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x13),
|
||||
LocationName.strawberry_21: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x14),
|
||||
LocationName.strawberry_22: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x15),
|
||||
LocationName.strawberry_23: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x16),
|
||||
LocationName.strawberry_24: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x17),
|
||||
LocationName.strawberry_25: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x18),
|
||||
LocationName.strawberry_26: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x19),
|
||||
LocationName.strawberry_27: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1A),
|
||||
LocationName.strawberry_28: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1B),
|
||||
LocationName.strawberry_29: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x1C),
|
||||
LocationName.strawberry_30: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x1D),
|
||||
}
|
||||
|
||||
friend_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.granny_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x00),
|
||||
LocationName.granny_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x01),
|
||||
LocationName.granny_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x02),
|
||||
LocationName.theo_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x03),
|
||||
LocationName.theo_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x04),
|
||||
LocationName.theo_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x05),
|
||||
LocationName.badeline_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x06),
|
||||
LocationName.badeline_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x07),
|
||||
LocationName.badeline_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x08),
|
||||
LocationName.granny_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x00),
|
||||
LocationName.granny_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x01),
|
||||
LocationName.granny_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x02),
|
||||
LocationName.theo_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x03),
|
||||
LocationName.theo_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x04),
|
||||
LocationName.theo_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x05),
|
||||
LocationName.badeline_1: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x06),
|
||||
LocationName.badeline_2: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x07),
|
||||
LocationName.badeline_3: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x08),
|
||||
}
|
||||
|
||||
sign_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.sign_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x00),
|
||||
LocationName.sign_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x01),
|
||||
LocationName.sign_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x02),
|
||||
LocationName.sign_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x03),
|
||||
LocationName.sign_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x04),
|
||||
LocationName.sign_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x00),
|
||||
LocationName.sign_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x01),
|
||||
LocationName.sign_3: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x200 + 0x02),
|
||||
LocationName.sign_4: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x200 + 0x03),
|
||||
LocationName.sign_5: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x200 + 0x04),
|
||||
}
|
||||
|
||||
car_location_data_table: Dict[str, Celeste64LocationData] = {
|
||||
LocationName.car_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x00),
|
||||
LocationName.car_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x01),
|
||||
LocationName.car_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x300 + 0x00),
|
||||
LocationName.car_2: Celeste64LocationData(RegionName.granny_island, 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,
|
||||
**friend_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}
|
||||
|
||||
@@ -15,3 +15,18 @@ ground_dash = "Ground Dash"
|
||||
air_dash = "Air Dash"
|
||||
skid_jump = "Skid Jump"
|
||||
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_10 = "South-East Tower Side Strawberry"
|
||||
strawberry_11 = "Girders Strawberry"
|
||||
strawberry_12 = "North-East Tower Bottom Strawberry"
|
||||
strawberry_12 = "Badeline Tower Bottom Strawberry"
|
||||
strawberry_13 = "Breakable Blocks Strawberry"
|
||||
strawberry_14 = "Feather Maze Strawberry"
|
||||
strawberry_15 = "Feather Chain Strawberry"
|
||||
@@ -18,7 +18,7 @@ strawberry_16 = "Feather Hidden Strawberry"
|
||||
strawberry_17 = "Double Dash Puzzle Strawberry"
|
||||
strawberry_18 = "Double Dash Spike Climb Strawberry"
|
||||
strawberry_19 = "Double Dash Spring Strawberry"
|
||||
strawberry_20 = "North-East Tower Breakable Bottom Strawberry"
|
||||
strawberry_20 = "Badeline Tower Breakable Bottom Strawberry"
|
||||
strawberry_21 = "Theo Tower Lower Cassette Strawberry"
|
||||
strawberry_22 = "Theo Tower Upper 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_27 = "Distant Feather Cassette Strawberry"
|
||||
strawberry_28 = "Feather Arches Cassette Strawberry"
|
||||
strawberry_29 = "North-East Tower Cassette Strawberry"
|
||||
strawberry_30 = "Badeline Cassette Strawberry"
|
||||
strawberry_29 = "Badeline Tower Cassette Strawberry"
|
||||
strawberry_30 = "Badeline Island Cassette Strawberry"
|
||||
|
||||
# Friend Locations
|
||||
granny_1 = "Granny Conversation 1"
|
||||
@@ -51,3 +51,15 @@ sign_5 = "Credits Sign"
|
||||
# Car Locations
|
||||
car_1 = "Intro 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"
|
||||
|
||||
13
worlds/celeste64/Names/RegionName.py
Normal file
13
worlds/celeste64/Names/RegionName.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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,6 +1,8 @@
|
||||
from dataclasses import dataclass
|
||||
import random
|
||||
|
||||
from Options import Choice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions
|
||||
from Options import Choice, TextChoice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions, OptionError
|
||||
from worlds.AutoWorld import World
|
||||
|
||||
|
||||
class DeathLinkAmnesty(Range):
|
||||
@@ -18,7 +20,7 @@ class TotalStrawberries(Range):
|
||||
"""
|
||||
display_name = "Total Strawberries"
|
||||
range_start = 0
|
||||
range_end = 46
|
||||
range_end = 55
|
||||
default = 20
|
||||
|
||||
class StrawberriesRequiredPercentage(Range):
|
||||
@@ -73,6 +75,93 @@ class Carsanity(Toggle):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@@ -119,6 +208,13 @@ celeste_64_option_groups = [
|
||||
Friendsanity,
|
||||
Signsanity,
|
||||
Carsanity,
|
||||
Checkpointsanity,
|
||||
]),
|
||||
OptionGroup("Aesthetic Options", [
|
||||
MadelineOneDashHairColor,
|
||||
MadelineTwoDashHairColor,
|
||||
MadelineNoDashHairColor,
|
||||
MadelineFeatherHairColor,
|
||||
]),
|
||||
OptionGroup("Badeline Chasers", [
|
||||
BadelineChaserSource,
|
||||
@@ -142,7 +238,68 @@ class Celeste64Options(PerGameCommonOptions):
|
||||
friendsanity: Friendsanity
|
||||
signsanity: Signsanity
|
||||
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_frequency: BadelineChaserFrequency
|
||||
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,11 +1,23 @@
|
||||
from typing import Dict, List, NamedTuple
|
||||
|
||||
from .Names import RegionName
|
||||
|
||||
class Celeste64RegionData(NamedTuple):
|
||||
connecting_regions: List[str] = []
|
||||
|
||||
|
||||
region_data_table: Dict[str, Celeste64RegionData] = {
|
||||
"Menu": Celeste64RegionData(["Forsaken City"]),
|
||||
"Forsaken City": Celeste64RegionData(),
|
||||
"Menu": Celeste64RegionData([RegionName.forsaken_city]),
|
||||
|
||||
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,265 +1,85 @@
|
||||
from typing import Dict, List
|
||||
from typing import Dict, List, Tuple, Callable
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from BaseClasses import CollectionState, Region
|
||||
from worlds.generic.Rules import set_rule
|
||||
|
||||
from . import Celeste64World
|
||||
from .Names import ItemName, LocationName
|
||||
from .Names import ItemName, LocationName, RegionName
|
||||
|
||||
|
||||
def set_rules(world: Celeste64World):
|
||||
if world.options.logic_difficulty == "standard":
|
||||
if world.options.move_shuffle:
|
||||
world.active_logic_mapping = location_standard_moves_logic
|
||||
else:
|
||||
world.active_logic_mapping = location_standard_logic
|
||||
world.active_logic_mapping = location_standard_moves_logic
|
||||
world.active_region_logic_mapping = region_standard_moves_logic
|
||||
else:
|
||||
if world.options.move_shuffle:
|
||||
world.active_logic_mapping = location_hard_moves_logic
|
||||
else:
|
||||
world.active_logic_mapping = location_hard_logic
|
||||
world.active_logic_mapping = location_hard_moves_logic
|
||||
world.active_region_logic_mapping = region_hard_moves_logic
|
||||
|
||||
for location in world.multiworld.get_locations(world.player):
|
||||
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.
|
||||
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]]] = {
|
||||
LocationName.strawberry_1: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump],
|
||||
[ItemName.climb]],
|
||||
LocationName.strawberry_2: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump],
|
||||
[ItemName.climb]],
|
||||
LocationName.strawberry_2: [[ItemName.air_dash],
|
||||
[ItemName.skid_jump]],
|
||||
LocationName.strawberry_3: [[ItemName.air_dash],
|
||||
[ItemName.skid_jump]],
|
||||
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, 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_10: [[ItemName.climb]],
|
||||
LocationName.strawberry_11: [[ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.traffic_block, ItemName.climb]],
|
||||
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.air_dash]],
|
||||
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash],
|
||||
[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
|
||||
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
|
||||
[ItemName.traffic_block, ItemName.feather, 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_11: [[ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_13: [[ItemName.breakables, ItemName.air_dash],
|
||||
[ItemName.breakables, ItemName.ground_dash]],
|
||||
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash]],
|
||||
LocationName.strawberry_15: [[ItemName.feather, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_16: [[ItemName.feather]],
|
||||
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]],
|
||||
LocationName.strawberry_18: [[ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_19: [[ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash, ItemName.skid_jump]],
|
||||
LocationName.strawberry_20: [[ItemName.feather, 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_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_23: [[ItemName.cassette, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block, ItemName.air_dash]],
|
||||
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
|
||||
[ItemName.cassette, ItemName.traffic_block, ItemName.air_dash, ItemName.climb]],
|
||||
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_25: [[ItemName.cassette, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_26: [[ItemName.cassette, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_27: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
|
||||
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, 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.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_2: [[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],
|
||||
[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.breakables, ItemName.air_dash]],
|
||||
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash, ItemName.climb],
|
||||
[ItemName.breakables, ItemName.air_dash, ItemName.climb]],
|
||||
}
|
||||
|
||||
location_hard_moves_logic: Dict[str, List[List[str]]] = {
|
||||
LocationName.strawberry_3: [[ItemName.air_dash],
|
||||
[ItemName.skid_jump]],
|
||||
LocationName.strawberry_5: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash]],
|
||||
LocationName.strawberry_8: [[ItemName.traffic_block],
|
||||
[ItemName.ground_dash, ItemName.air_dash]],
|
||||
LocationName.strawberry_10: [[ItemName.air_dash],
|
||||
[ItemName.climb]],
|
||||
LocationName.strawberry_11: [[ItemName.ground_dash],
|
||||
[ItemName.air_dash],
|
||||
[ItemName.skid_jump]],
|
||||
LocationName.strawberry_12: [[ItemName.feather],
|
||||
[ItemName.ground_dash],
|
||||
[ItemName.air_dash]],
|
||||
LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash],
|
||||
[ItemName.breakables, 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],
|
||||
[ItemName.ground_dash, ItemName.air_dash]],
|
||||
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
|
||||
@@ -287,42 +107,94 @@ location_hard_moves_logic: Dict[str, List[List[str]]] = {
|
||||
[ItemName.cassette, ItemName.feather, ItemName.climb]],
|
||||
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump],
|
||||
[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.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.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.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, 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]],
|
||||
|
||||
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_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],
|
||||
[ItemName.breakables, ItemName.air_dash]],
|
||||
}
|
||||
|
||||
|
||||
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool:
|
||||
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:
|
||||
if loc not in world.active_logic_mapping:
|
||||
return True
|
||||
|
||||
@@ -332,12 +204,28 @@ def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bo
|
||||
|
||||
return False
|
||||
|
||||
def goal_rule(state: CollectionState, world: Celeste64World) -> bool:
|
||||
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
|
||||
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.goal_logic_mapping:
|
||||
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:
|
||||
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
|
||||
return False
|
||||
|
||||
goal_region: Region = world.multiworld.get_region(RegionName.badeline_island, world.player)
|
||||
return state.can_reach(goal_region)
|
||||
|
||||
def connect_region(world: Celeste64World, region: Region, dest_regions: List[str]):
|
||||
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,13 +1,15 @@
|
||||
from copy import deepcopy
|
||||
from typing import Dict, List
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from BaseClasses import ItemClassification, Location, Region, Tutorial
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table, item_table
|
||||
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table,\
|
||||
checkpoint_item_data_table, item_table
|
||||
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
|
||||
sign_location_data_table, car_location_data_table, location_table
|
||||
sign_location_data_table, car_location_data_table, checkpoint_location_data_table,\
|
||||
location_table
|
||||
from .Names import ItemName, LocationName
|
||||
from .Options import Celeste64Options, celeste_64_option_groups
|
||||
from .Options import Celeste64Options, celeste_64_option_groups, resolve_options
|
||||
|
||||
|
||||
class Celeste64WebWorld(WebWorld):
|
||||
@@ -42,8 +44,15 @@ class Celeste64World(World):
|
||||
# Instance Data
|
||||
strawberries_required: int
|
||||
active_logic_mapping: Dict[str, List[List[str]]]
|
||||
goal_logic_mapping: Dict[str, List[List[str]]]
|
||||
active_region_logic_mapping: Dict[Tuple[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:
|
||||
# Only make required amount of strawberries be Progression
|
||||
@@ -76,25 +85,49 @@ class Celeste64World(World):
|
||||
for name in unlockable_item_data_table.keys()
|
||||
if name not in self.options.start_inventory]
|
||||
|
||||
if self.options.move_shuffle:
|
||||
move_items_for_itempool: List[str] = deepcopy(list(move_item_data_table.keys()))
|
||||
chosen_start_item: str = ""
|
||||
|
||||
if self.options.move_shuffle:
|
||||
if self.options.logic_difficulty == "standard":
|
||||
# If the start_inventory already includes a move, don't worry about giving it one
|
||||
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)
|
||||
move_items_for_itempool.remove(chosen_start_move)
|
||||
possible_unwalls: List[str] = [name for name in move_item_data_table.keys()
|
||||
if name != ItemName.skid_jump]
|
||||
|
||||
if self.options.checkpointsanity:
|
||||
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:
|
||||
intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player)
|
||||
intro_car_loc.place_locked_item(self.create_item(chosen_start_move))
|
||||
intro_car_loc.place_locked_item(self.create_item(chosen_start_item))
|
||||
location_count -= 1
|
||||
else:
|
||||
self.multiworld.push_precollected(self.create_item(chosen_start_move))
|
||||
self.multiworld.push_precollected(self.create_item(chosen_start_item))
|
||||
|
||||
item_pool += [self.create_item(name)
|
||||
for name in move_items_for_itempool
|
||||
if name not in self.options.start_inventory]
|
||||
for name in move_item_data_table.keys()
|
||||
if name not in self.multiworld.precollected_items[self.player]
|
||||
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))
|
||||
self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100))
|
||||
@@ -140,18 +173,23 @@ class Celeste64World(World):
|
||||
if location_data.region == region_name
|
||||
}, Celeste64Location)
|
||||
|
||||
region.add_exits(region_data_table[region_name].connecting_regions)
|
||||
region.add_locations({
|
||||
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:
|
||||
return ItemName.raspberry
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
from .Rules import set_rules
|
||||
set_rules(self)
|
||||
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"death_link": self.options.death_link.value,
|
||||
@@ -161,6 +199,11 @@ class Celeste64World(World):
|
||||
"friendsanity": self.options.friendsanity.value,
|
||||
"signsanity": self.options.signsanity.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_frequency": self.options.badeline_chaser_frequency.value,
|
||||
"badeline_chaser_speed": self.options.badeline_chaser_speed.value,
|
||||
|
||||
@@ -19,8 +19,8 @@ from worlds.AutoWorld import WebWorld, World
|
||||
|
||||
from .aesthetics import shuffle_sub_weapons, get_location_data, get_countdown_flags, populate_enemy_drops, \
|
||||
get_start_inventory_data
|
||||
from .rom import RomData, patch_rom, get_base_rom_path, CVCotMProcedurePatch, CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, \
|
||||
CVCOTM_VC_US_HASH
|
||||
from .rom import RomData, patch_rom, get_base_rom_path, CVCotMProcedurePatch, CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH
|
||||
# CVCOTM_VC_US_HASH
|
||||
from .client import CastlevaniaCotMClient
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ class CVCotMSettings(settings.Group):
|
||||
"""File name of the Castlevania CotM US rom"""
|
||||
copy_to = "Castlevania - Circle of the Moon (USA).gba"
|
||||
description = "Castlevania CotM (US) ROM File"
|
||||
md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
|
||||
# md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
|
||||
md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]
|
||||
|
||||
rom_file: RomFile = RomFile(RomFile.copy_to)
|
||||
|
||||
|
||||
@@ -153,11 +153,10 @@ Advance Collection ROM; most notably the fact that the audio does not function w
|
||||
which is currently a requirement to connect to a multiworld. This happens because all audio code was stripped
|
||||
from the ROM, and all sound is instead played by the collection through external means.
|
||||
|
||||
For this reason, it is most recommended to obtain the ROM by dumping it from an original cartridge of the game that you legally own.
|
||||
Though, the Advance Collection *can* still technically be an option if you cannot do that and don't mind the lack of sound.
|
||||
The Wii U Virtual Console version does not work due to changes in the code in that version.
|
||||
|
||||
The Wii U Virtual Console version is currently untested. If you happen to have purchased it before the Wii U eShop shut down, you can try
|
||||
dumping and playing with it. However, at the moment, we cannot guarantee that it will work well due to it being untested.
|
||||
Due to the reasons mentioned above, it is most recommended to obtain the ROM by dumping it from an original cartridge of the
|
||||
game that you legally own. However, the Advance Collection *is* an option if you cannot do that and don't mind the lack of sound.
|
||||
|
||||
Regardless of which released ROM you intend to try playing with, the US version of the game is required.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
|
||||
- A Castlevania: Circle of the Moon ROM of the US version specifically. The Archipelago community cannot provide this.
|
||||
The Castlevania Advance Collection ROM can technically be used, but it has no audio. The Wii U Virtual Console ROM is untested.
|
||||
The Castlevania Advance Collection ROM can be used, but it has no audio. The Wii U Virtual Console ROM does not work.
|
||||
- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 or later.
|
||||
|
||||
### Configuring BizHawk
|
||||
|
||||
@@ -22,11 +22,9 @@ if TYPE_CHECKING:
|
||||
|
||||
CVCOTM_CT_US_HASH = "50a1089600603a94e15ecf287f8d5a1f" # Original GBA cartridge ROM
|
||||
CVCOTM_AC_US_HASH = "87a1bd6577b6702f97a60fc55772ad74" # Castlevania Advance Collection ROM
|
||||
CVCOTM_VC_US_HASH = "2cc38305f62b337281663bad8c901cf9" # Wii U Virtual Console ROM
|
||||
# CVCOTM_VC_US_HASH = "2cc38305f62b337281663bad8c901cf9" # Wii U Virtual Console ROM
|
||||
|
||||
# NOTE: The Wii U VC version is untested as of when this comment was written. I am only including its hash in case it
|
||||
# does work. If someone who has it can confirm it does indeed work, this comment should be removed. If it doesn't, the
|
||||
# hash should be removed in addition. See the Game Page for more information about supported versions.
|
||||
# The Wii U VC version is not currently supported. See the Game Page for more info.
|
||||
|
||||
ARCHIPELAGO_IDENTIFIER_START = 0x7FFF00
|
||||
ARCHIPELAGO_IDENTIFIER = "ARCHIPELAG03"
|
||||
@@ -518,7 +516,8 @@ class CVCotMPatchExtensions(APPatchExtension):
|
||||
|
||||
|
||||
class CVCotMProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
|
||||
# hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
|
||||
hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]
|
||||
patch_file_ending: str = ".apcvcotm"
|
||||
result_file_ending: str = ".gba"
|
||||
|
||||
@@ -585,7 +584,8 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(base_rom_bytes)
|
||||
if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]:
|
||||
# if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]:
|
||||
if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]:
|
||||
raise Exception("Supplied Base ROM does not match known MD5s for Castlevania: Circle of the Moon USA."
|
||||
"Get the correct game and version, then dump it.")
|
||||
setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes)
|
||||
|
||||
@@ -530,7 +530,7 @@ server_args = ("--rcon-port", rcon_port, "--rcon-password", rcon_password)
|
||||
def launch():
|
||||
import colorama
|
||||
global executable, server_settings, server_args
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
if server_settings:
|
||||
server_settings = os.path.abspath(server_settings)
|
||||
|
||||
@@ -295,6 +295,6 @@ def launch():
|
||||
parser = get_base_parser(description="KH1 Client, for text interfacing.")
|
||||
|
||||
args, rest = parser.parse_known_args()
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -981,6 +981,6 @@ def launch():
|
||||
parser = get_base_parser(description="KH2 Client, for text interfacing.")
|
||||
|
||||
args, rest = parser.parse_known_args()
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -107,6 +107,7 @@ SYNONYMS = {
|
||||
'JUMP': 'FEATHER',
|
||||
'PLUME': 'FEATHER',
|
||||
'WING': 'FEATHER',
|
||||
"QUILL": "FEATHER",
|
||||
|
||||
# SHOVEL
|
||||
'DIG': 'SHOVEL',
|
||||
@@ -343,6 +344,8 @@ SYNONYMS = {
|
||||
# TRADING_ITEM_LETTER
|
||||
'CARD': 'TRADING_ITEM_LETTER',
|
||||
'MESSAGE': 'TRADING_ITEM_LETTER',
|
||||
"TICKET": 'TRADING_ITEM_LETTER',
|
||||
"PASS": 'TRADING_ITEM_LETTER',
|
||||
|
||||
# TRADING_ITEM_BROOM
|
||||
'SWEEP': 'TRADING_ITEM_BROOM',
|
||||
@@ -365,6 +368,8 @@ SYNONYMS = {
|
||||
'MIRROR': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
||||
'SCOPE': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
||||
'XRAY': 'TRADING_ITEM_MAGNIFYING_GLASS',
|
||||
"DETECTOR": 'TRADING_ITEM_MAGNIFYING_GLASS',
|
||||
"ITEMFINDER": 'TRADING_ITEM_MAGNIFYING_GLASS',
|
||||
|
||||
# PIECE_OF_POWER
|
||||
'TRIANGLE': 'PIECE_OF_POWER',
|
||||
@@ -378,6 +383,7 @@ PHRASES = {
|
||||
'BOSS KEY': 'NIGHTMARE_KEY',
|
||||
'HEART PIECE': 'HEART_PIECE',
|
||||
'PIECE OF HEART': 'HEART_PIECE',
|
||||
"ROCK SMASH": 'BOMB',
|
||||
}
|
||||
|
||||
# All following will only be used to match items for the specific game.
|
||||
@@ -404,6 +410,16 @@ GAME_SPECIFIC_PHRASES = {
|
||||
|
||||
'Ocarina of Time': {
|
||||
'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': {
|
||||
@@ -417,10 +433,14 @@ GAME_SPECIFIC_PHRASES = {
|
||||
|
||||
'Sonic Adventure 2 Battle': {
|
||||
'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': {
|
||||
'POWER STAR': 'PIECE_OF_POWER',
|
||||
"Key": "NIGHTMARE_KEY" # Affect 2nd Floor / Basement / Progressive keys
|
||||
},
|
||||
|
||||
'Super Mario World': {
|
||||
@@ -528,4 +548,336 @@ GAME_SPECIFIC_PHRASES = {
|
||||
'2500 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])
|
||||
|
||||
for phrase, icon in phrases.items():
|
||||
if phrase in uppered:
|
||||
if phrase.upper() in uppered:
|
||||
return icon
|
||||
# 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)")
|
||||
|
||||
@@ -100,14 +100,15 @@ class OOTWeb(WebWorld):
|
||||
["Edos"]
|
||||
)
|
||||
|
||||
setup_es = Tutorial(
|
||||
setup.tutorial_name,
|
||||
setup.description,
|
||||
"Español",
|
||||
"setup_es.md",
|
||||
"setup/es",
|
||||
setup.authors
|
||||
)
|
||||
# Very out of date, requires updating to match current
|
||||
# setup_es = Tutorial(
|
||||
# setup.tutorial_name,
|
||||
# setup.description,
|
||||
# "Español",
|
||||
# "setup_es.md",
|
||||
# "setup/es",
|
||||
# setup.authors
|
||||
# )
|
||||
|
||||
setup_fr = Tutorial(
|
||||
setup.tutorial_name,
|
||||
@@ -127,7 +128,7 @@ class OOTWeb(WebWorld):
|
||||
["Held_der_Zeit"]
|
||||
)
|
||||
|
||||
tutorials = [setup, setup_es, setup_fr, setup_de]
|
||||
tutorials = [setup, setup_fr, setup_de]
|
||||
option_groups = oot_option_groups
|
||||
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ class PokemonRedBlueWorld(World):
|
||||
self.dexsanity_table = []
|
||||
self.trainersanity_table = []
|
||||
self.local_locs = []
|
||||
self.pc_item = None
|
||||
|
||||
@classmethod
|
||||
def stage_assert_generate(cls, multiworld: MultiWorld):
|
||||
@@ -289,7 +290,9 @@ class PokemonRedBlueWorld(World):
|
||||
multiworld.random.shuffle(itempool)
|
||||
unplaced_items = []
|
||||
for i, item in enumerate(itempool):
|
||||
if item.player == loc.player and loc.can_fill(multiworld.state, item, False):
|
||||
if ((item.player == loc.player or (item.player in multiworld.groups
|
||||
and loc.player in multiworld.groups[item.player]["players"]))
|
||||
and loc.can_fill(multiworld.state, item, False)):
|
||||
if item.advancement:
|
||||
pool = progitempool
|
||||
elif item.useful:
|
||||
@@ -308,8 +311,6 @@ class PokemonRedBlueWorld(World):
|
||||
break
|
||||
else:
|
||||
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]
|
||||
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)]
|
||||
@@ -446,15 +447,12 @@ class PokemonRedBlueWorld(World):
|
||||
if loc.item is None:
|
||||
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):
|
||||
if loc.name in self.options.priority_locations.value:
|
||||
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":
|
||||
add_item_rule(loc, lambda i: i.name != "Oak's Parcel")
|
||||
|
||||
@@ -520,6 +518,14 @@ class PokemonRedBlueWorld(World):
|
||||
else:
|
||||
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
|
||||
def stage_post_fill(cls, multiworld):
|
||||
# Convert all but one of each instance of a wild Pokemon to useful classification.
|
||||
|
||||
@@ -1579,6 +1579,18 @@ def create_regions(world):
|
||||
world.item_pool.append(item)
|
||||
|
||||
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] \
|
||||
+ [item.name for item in world.multiworld.precollected_items[world.player] if
|
||||
item.advancement]
|
||||
|
||||
@@ -1625,6 +1625,6 @@ def get_location_offset(mission_id):
|
||||
|
||||
|
||||
def launch():
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# StarCraft 2
|
||||
|
||||
## Game page in other languages:
|
||||
|
||||
* [Français](/games/Starcraft%202/info/fr)
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
### Items and locations
|
||||
|
||||
The following unlocks are randomized as items:
|
||||
1. Your ability to build any non-worker unit.
|
||||
2. Unit specific upgrades including some combinations not available in the vanilla campaigns, such as both strain
|
||||
@@ -34,18 +37,28 @@ When you receive items, they will immediately become available, even during a mi
|
||||
notified via a text box in the top-right corner of the game screen.
|
||||
Item unlocks are also logged in the Archipelago client.
|
||||
|
||||
### Mission order
|
||||
|
||||
The missions and the order in which they need to be completed, referred to as the mission order, can also be randomized.
|
||||
The four StarCraft 2 campaigns can be used to populate the mission order.
|
||||
Note that the evolution missions from Heart of the Swarm are not included in the randomizer.
|
||||
The default mission order follows the structure of the selected campaigns but several other options are available,
|
||||
e.g., blitz, grid, etc.
|
||||
|
||||
Missions are launched through the StarCraft 2 Archipelago client, through the StarCraft 2 Launcher tab.
|
||||
The between mission segments on the Hyperion, the Leviathan, and the Spear of Adun are not included.
|
||||
Additionally, metaprogression currencies such as credits and Solarite are not used.
|
||||
Available missions are in blue; missions where all locations were collected are in white.
|
||||
If you move your mouse over a mission, the uncollected locations will be displayed, categorized by type.
|
||||
Unavailable missions are in grey; their requirements will also be shown there.
|
||||
|
||||
## What is the goal of this game when randomized?
|
||||
|
||||
The goal is to beat the final mission in the mission order.
|
||||
The yaml configuration file controls the mission order (e.g. blitz, grid, etc.), which combination of the four
|
||||
StarCraft 2 campaigns can be used to populate the mission order and how missions are shuffled.
|
||||
The yaml configuration file controls the mission order, which combination of the four StarCraft 2 campaigns can be
|
||||
used, and how missions are shuffled.
|
||||
Since the first two options determine the number of missions in a StarCraft 2 world, they can be used to customize the
|
||||
expected time to complete the world.
|
||||
Note that the evolution missions from Heart of the Swarm are not included in the randomizer.
|
||||
|
||||
## What non-randomized changes are there from vanilla StarCraft 2?
|
||||
|
||||
@@ -78,9 +91,7 @@ Will overwrite existing files
|
||||
* `/game_speed [game_speed]` Overrides the game speed for the world
|
||||
* Options: default, slower, slow, normal, fast, faster
|
||||
* `/color [faction] [color]` Changes your color for one of your playable factions.
|
||||
* Faction options: raynor, kerrigan, primal, protoss, nova
|
||||
* Color options: white, red, blue, teal, purple, yellow, orange, green, lightpink, violet, lightgrey, darkgreen,
|
||||
brown, lightgreen, darkgrey, pink, rainbow, random, default
|
||||
* Run without arguments to list all factions and colors that are available.
|
||||
* `/option [option_name] [option_value]` Sets an option normally controlled by your yaml after generation.
|
||||
* Run without arguments to list all options.
|
||||
* Options pertain to automatic cutscene skipping, Kerrigan presence, Spear of Adun presence, starting resource
|
||||
@@ -100,6 +111,19 @@ Additionally, upgrades are grouped beneath their corresponding units or building
|
||||
A filter parameter can be provided, e.g., `/received Thor`, to limit the number of items shown.
|
||||
Every item whose name, race, or group name contains the provided parameter will be shown.
|
||||
|
||||
## Particularities in a multiworld
|
||||
|
||||
### Collect on goal completion
|
||||
|
||||
One of the default options of multiworlds is that once a world has achieved its goal, it collects its items from all
|
||||
other worlds.
|
||||
If you do not want this to happen, you should ask the person generating the multiworld to set the `Collect Permission`
|
||||
option to something else, e.g., manual.
|
||||
If the generation is not done via the website, the person that does the generation should modify the `collect_mode`
|
||||
option in their `host.yaml` file prior to generation.
|
||||
If the multiworld has already been generated, the host can use the command `/option collect_mode [value]` to change
|
||||
this option.
|
||||
|
||||
## Known issues
|
||||
|
||||
- StarCraft 2 Archipelago does not support loading a saved game.
|
||||
@@ -108,3 +132,7 @@ For this reason, it is recommended to play on a difficulty level lower than what
|
||||
To restart a mission, use the StarCraft 2 Client.
|
||||
- A crash report is often generated when a mission is closed.
|
||||
This does not affect the game and can be ignored.
|
||||
- Currently, the StarCraft 2 client uses the Victory locations to determine which missions have been completed.
|
||||
As a result, the Archipelago collect feature can sometime grant access to missions that are connected to a mission that
|
||||
you did not complete.
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## Quel est l'effet de la *randomization* sur ce jeu ?
|
||||
|
||||
### *Items* et *locations*
|
||||
|
||||
Les éléments qui suivent sont les *items* qui sont *randomized* et qui doivent être débloqués pour être utilisés dans
|
||||
le jeu:
|
||||
1. La capacité de produire des unités, excepté les drones/probes/scv.
|
||||
@@ -37,21 +39,33 @@ Quand vous recevez un *item*, il devient immédiatement disponible, même pendan
|
||||
la boîte de texte situé dans le coin en haut à droite de *StarCraft 2*.
|
||||
L'acquisition d'un *item* est aussi indiquée dans le client d'Archipelago.
|
||||
|
||||
### *Mission order*
|
||||
|
||||
Les missions et l'ordre dans lequel elles doivent être complétées, dénoté *mission order*, peuvent également être
|
||||
*randomized*.
|
||||
Les quatre campagnes de *StarCraft 2* peuvent être utilisées pour remplir le *mission order*.
|
||||
Notez que les missions d'évolution de *Heart of the Swarm* ne sont pas incluses dans le *randomizer*.
|
||||
Par défaut, le *mission order* suit la structure des campagnes sélectionnées, mais plusieurs autres options sont
|
||||
disponibles, comme *blitz*, *grid*, etc.
|
||||
|
||||
Les missions peuvent être lancées par le client *StarCraft 2 Archipelago*, via l'interface graphique de l'onglet
|
||||
*StarCraft 2 Launcher*.
|
||||
Les segments qui se passent sur l'*Hyperion*, un Léviathan et la *Spear of Adun* ne sont pas inclus.
|
||||
De plus, les points de progression tels que les crédits ou la Solarite ne sont pas utilisés dans *StarCraft 2
|
||||
De plus, les points de progression, tels que les crédits ou la Solarite, ne sont pas utilisés dans *StarCraft 2
|
||||
Archipelago*.
|
||||
Les missions accessibles ont leur nom en bleu, tandis que celles où toutes les *locations* ont été collectées
|
||||
apparaissent en blanc.
|
||||
En plaçant votre souris sur une mission, les *locations* non collectées s’affichent, classées par catégorie.
|
||||
Les missions qui ne sont pas accessibles ont leur nom en gris et leurs prérequis seront également affichés à cet endroit.
|
||||
|
||||
|
||||
## Quel est le but de ce jeu quand il est *randomized*?
|
||||
|
||||
Le but est de réussir la mission finale du *mission order* (e.g. *blitz*, *grid*, etc.).
|
||||
Le fichier de configuration yaml permet de spécifier le *mission order*, lesquelles des quatre campagnes de
|
||||
*StarCraft 2* peuvent être utilisées pour remplir le *mission order* et comment les missions sont distribuées dans le
|
||||
*mission order*.
|
||||
Le fichier de configuration yaml permet de spécifier le *mission order*, quelle combinaison des quatre campagnes de
|
||||
*StarCraft 2* peuvent être utilisée et comment les missions sont distribuées dans le *mission order*.
|
||||
Étant donné que les deux premières options déterminent le nombre de missions dans un monde de *StarCraft 2*, elles
|
||||
peuvent être utilisées pour moduler le temps nécessaire pour terminer le monde.
|
||||
Notez que les missions d'évolution de Heart of the Swarm ne sont pas incluses dans le *randomizer*.
|
||||
|
||||
## Quelles sont les modifications non aléatoires comparativement à la version de base de *StarCraft 2*
|
||||
|
||||
@@ -89,9 +103,7 @@ Les fichiers existants vont être écrasés.
|
||||
* `/game_speed [game_speed]` Remplace la vitesse du jeu pour le monde.
|
||||
* Les options sont *default*, *slower*, *slow*, *normal*, *fast*, and *faster*.
|
||||
* `/color [faction] [color]` Remplace la couleur d'une des *factions* qui est jouable.
|
||||
* Les options de *faction*: raynor, kerrigan, primal, protoss, nova.
|
||||
* Les options de couleur: *white*, *red*, *blue*, *teal*, *purple*, *yellow*, *orange*, *green*, *lightpink*,
|
||||
*violet*, *lightgrey*, *darkgreen*, *brown*, *lightgreen*, *darkgrey*, *pink*, *rainbow*, *random*, *default*.
|
||||
* Si la commande est lancée sans option, la liste des *factions* et des couleurs disponibles sera affichée.
|
||||
* `/option [option_name] [option_value]` Permet de changer un option normalement définit dans le *yaml*.
|
||||
* Si la commande est lancée sans option, la liste des options qui sont modifiables va être affichée.
|
||||
* Les options qui peuvent être changées avec cette commande incluent sauter les cinématiques automatiquement, la
|
||||
@@ -114,6 +126,19 @@ De plus, les améliorations sont regroupées sous leurs unités/bâtiments corre
|
||||
Un paramètre de filtrage peut aussi être fourni, e.g., `/received Thor`, pour limiter le nombre d'*items* affichés.
|
||||
Tous les *items* dont le nom, la race ou le nom de groupe contient le paramètre fourni seront affichés.
|
||||
|
||||
## Particularités dans un multiworld
|
||||
|
||||
### *Collect on goal completion*
|
||||
|
||||
L'une des options par défaut des *multiworlds* est qu'une fois qu'un monde a atteint son objectif final, il collecte
|
||||
tous ses *items*, incluant ceux dans les autres mondes.
|
||||
Si vous ne souhaitez pas que cela se produise, vous devez demander à la personne générant le *multiworld* de changer
|
||||
l'option *Collect Permission*.
|
||||
Si la génération n'est pas effectuée via le site web, la personne qui effectue la génération doit modifier l'option
|
||||
`collect_mode` dans son fichier *host.yaml* avant la génération.
|
||||
Si le *multiworld* a déjà été généré, l'hôte peut utiliser la commande `/option collect_mode [valeur]` pour modifier
|
||||
cette option.
|
||||
|
||||
## Problèmes connus
|
||||
|
||||
- *StarCraft 2 Archipelago* ne supporte pas le chargement d'une sauvegarde.
|
||||
@@ -123,3 +148,7 @@ normalement à l'aise.
|
||||
Pour redémarrer une mission, utilisez le client de *StarCraft 2 Archipelago*.
|
||||
- Un rapport d'erreur est souvent généré lorsqu'une mission est fermée.
|
||||
Cela n'affecte pas le jeu et peut être ignoré.
|
||||
- Actuellement, le client de *StarCraft 2* utilise la *location* associée à la victoire d'une mission pour déterminer
|
||||
si celle-ci a été complétée.
|
||||
En conséquence, la fonctionnalité *collect* d'*Archipelago* peut rendre accessible des missions connectées à une
|
||||
mission que vous n'avez pas terminée.
|
||||
@@ -41,6 +41,7 @@ Remember the name you enter in the options page or in the yaml file, you'll need
|
||||
Check out [Creating a YAML](/tutorial/Archipelago/setup/en#creating-a-yaml) for more game-agnostic information.
|
||||
|
||||
### Common yaml questions
|
||||
|
||||
#### How do I know I set my yaml up correctly?
|
||||
|
||||
The simplest way to check is to use the website [validator](/check).
|
||||
|
||||
@@ -49,6 +49,7 @@ Si vous désirez des informations et/ou instructions générales sur l'utilisati
|
||||
veuillez consulter [*Creating a YAML*](/tutorial/Archipelago/setup/en#creating-a-yaml).
|
||||
|
||||
### Questions récurrentes à propos du fichier *yaml*
|
||||
|
||||
#### Comment est-ce que je sais que mon *yaml* est bien défini?
|
||||
|
||||
La manière la plus simple de valider votre *yaml* est d'utiliser le
|
||||
|
||||
33
worlds/witness/data/definition_classes.py
Normal file
33
worlds/witness/data/definition_classes.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import FrozenSet, List, NamedTuple
|
||||
|
||||
# A WitnessRule is just an or-chain of and-conditions.
|
||||
# It represents the set of all options that could fulfill this requirement.
|
||||
# E.g. if something requires "Dots or (Shapers and Stars)", it'd be represented as: {{"Dots"}, {"Shapers, "Stars"}}
|
||||
# {} is an unusable requirement.
|
||||
# {{}} is an always usable requirement.
|
||||
WitnessRule = FrozenSet[FrozenSet[str]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AreaDefinition:
|
||||
name: str
|
||||
regions: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RegionDefinition:
|
||||
name: str
|
||||
short_name: str
|
||||
area: AreaDefinition
|
||||
logical_entities: List[str] = field(default_factory=list)
|
||||
physical_entities: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class ConnectionDefinition(NamedTuple):
|
||||
target_region: str
|
||||
traversal_rule: WitnessRule
|
||||
|
||||
@property
|
||||
def can_be_traversed(self) -> bool:
|
||||
return bool(self.traversal_rule)
|
||||
@@ -486,5 +486,5 @@ for key, item in ALL_LOCATIONS_TO_IDS.items():
|
||||
ALL_LOCATIONS_TO_ID[key] = item
|
||||
|
||||
for loc in ALL_LOCATIONS_TO_IDS:
|
||||
area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"]["name"]
|
||||
area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"].name
|
||||
AREA_LOCATION_GROUPS.setdefault(area, set()).add(loc)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from collections import Counter, defaultdict
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
from typing import Any, Dict, FrozenSet, List, Optional, Set
|
||||
|
||||
from Utils import cache_argsless
|
||||
|
||||
from .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule
|
||||
from .item_definition_classes import (
|
||||
CATEGORY_NAME_MAPPINGS,
|
||||
DoorItemDefinition,
|
||||
@@ -13,7 +14,6 @@ from .item_definition_classes import (
|
||||
)
|
||||
from .settings.easter_eggs import EASTER_EGGS
|
||||
from .utils import (
|
||||
WitnessRule,
|
||||
define_new_region,
|
||||
get_items,
|
||||
get_sigma_expert_logic,
|
||||
@@ -21,7 +21,7 @@ from .utils import (
|
||||
get_umbra_variety_logic,
|
||||
get_vanilla_logic,
|
||||
logical_or_witness_rules,
|
||||
parse_lambda,
|
||||
parse_witness_rule,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ class StaticWitnessLogicObj:
|
||||
lines = get_sigma_normal_logic()
|
||||
|
||||
# All regions with a list of panels in them and the connections to other regions, before logic adjustments
|
||||
self.ALL_REGIONS_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
||||
self.ALL_AREAS_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
||||
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, Dict[str, Set[WitnessRule]]] = defaultdict(lambda: defaultdict(set))
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = {}
|
||||
self.ALL_REGIONS_BY_NAME: Dict[str, RegionDefinition] = {}
|
||||
self.ALL_AREAS_BY_NAME: Dict[str, AreaDefinition] = {}
|
||||
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, List[ConnectionDefinition]] = defaultdict(list)
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, List[ConnectionDefinition]] = {}
|
||||
|
||||
self.ENTITIES_BY_HEX: Dict[str, Dict[str, Any]] = {}
|
||||
self.ENTITIES_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
||||
@@ -55,15 +55,15 @@ class StaticWitnessLogicObj:
|
||||
area_counts: Dict[str, int] = Counter()
|
||||
for region_name, entity_amount in EASTER_EGGS.items():
|
||||
region_object = self.ALL_REGIONS_BY_NAME[region_name]
|
||||
correct_area = region_object["area"]
|
||||
correct_area = region_object.area
|
||||
|
||||
for _ in range(entity_amount):
|
||||
location_id = 160200 + egg_counter
|
||||
entity_hex = hex(0xEE000 + egg_counter)
|
||||
egg_counter += 1
|
||||
|
||||
area_counts[correct_area["name"]] += 1
|
||||
full_entity_name = f"{correct_area['name']} Easter Egg {area_counts[correct_area['name']]}"
|
||||
area_counts[correct_area.name] += 1
|
||||
full_entity_name = f"{correct_area.name} Easter Egg {area_counts[correct_area.name]}"
|
||||
|
||||
self.ENTITIES_BY_HEX[entity_hex] = {
|
||||
"checkName": full_entity_name,
|
||||
@@ -81,11 +81,11 @@ class StaticWitnessLogicObj:
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||
"entities": frozenset({frozenset({})})
|
||||
}
|
||||
region_object["entities"].append(entity_hex)
|
||||
region_object["physical_entities"].append(entity_hex)
|
||||
region_object.logical_entities.append(entity_hex)
|
||||
region_object.physical_entities.append(entity_hex)
|
||||
|
||||
easter_egg_region = self.ALL_REGIONS_BY_NAME["Easter Eggs"]
|
||||
easter_egg_area = easter_egg_region["area"]
|
||||
easter_egg_area = easter_egg_region.area
|
||||
for i in range(sum(EASTER_EGGS.values())):
|
||||
location_id = 160000 + i
|
||||
entity_hex = hex(0xEE200 + i)
|
||||
@@ -111,19 +111,15 @@ class StaticWitnessLogicObj:
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||
"entities": frozenset({frozenset({})})
|
||||
}
|
||||
easter_egg_region["entities"].append(entity_hex)
|
||||
easter_egg_region["physical_entities"].append(entity_hex)
|
||||
easter_egg_region.logical_entities.append(entity_hex)
|
||||
easter_egg_region.physical_entities.append(entity_hex)
|
||||
|
||||
def read_logic_file(self, lines: List[str]) -> None:
|
||||
"""
|
||||
Reads the logic file and does the initial population of data structures
|
||||
"""
|
||||
|
||||
current_region = {}
|
||||
current_area: Dict[str, Any] = {
|
||||
"name": "Misc",
|
||||
"regions": [],
|
||||
}
|
||||
current_area = AreaDefinition("Misc")
|
||||
current_region = RegionDefinition("Fake", "Fake", current_area) # Unused, but makes PyCharm & mypy shut up
|
||||
self.ALL_AREAS_BY_NAME["Misc"] = current_area
|
||||
|
||||
for line in lines:
|
||||
@@ -133,19 +129,16 @@ class StaticWitnessLogicObj:
|
||||
if line[-1] == ":":
|
||||
new_region_and_connections = define_new_region(line, current_area)
|
||||
current_region = new_region_and_connections[0]
|
||||
region_name = current_region["name"]
|
||||
region_name = current_region.name
|
||||
self.ALL_REGIONS_BY_NAME[region_name] = current_region
|
||||
for connection in new_region_and_connections[1]:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[region_name][connection[0]].add(connection[1])
|
||||
current_area["regions"].append(region_name)
|
||||
self.CONNECTIONS_WITH_DUPLICATES[region_name].append(connection)
|
||||
current_area.regions.append(region_name)
|
||||
continue
|
||||
|
||||
if line[0] == "=":
|
||||
area_name = line[2:-2]
|
||||
current_area = {
|
||||
"name": area_name,
|
||||
"regions": [],
|
||||
}
|
||||
current_area = AreaDefinition(area_name, [])
|
||||
self.ALL_AREAS_BY_NAME[area_name] = current_area
|
||||
continue
|
||||
|
||||
@@ -158,9 +151,9 @@ class StaticWitnessLogicObj:
|
||||
entity_hex = entity_name_full[0:7]
|
||||
entity_name = entity_name_full[9:-1]
|
||||
|
||||
required_panel_lambda = line_split.pop(0)
|
||||
entity_requirement_string = line_split.pop(0)
|
||||
|
||||
full_entity_name = current_region["shortName"] + " " + entity_name
|
||||
full_entity_name = current_region.short_name + " " + entity_name
|
||||
|
||||
if location_id == "Door" or location_id == "Laser":
|
||||
self.ENTITIES_BY_HEX[entity_hex] = {
|
||||
@@ -177,18 +170,18 @@ class StaticWitnessLogicObj:
|
||||
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
|
||||
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||
"entities": parse_lambda(required_panel_lambda)
|
||||
"entities": parse_witness_rule(entity_requirement_string)
|
||||
}
|
||||
|
||||
# Lasers and Doors exist in a region, but don't have a regional *requirement*
|
||||
# If a laser is activated, you don't need to physically walk up to it for it to count
|
||||
# As such, logically, they behave more as if they were part of the "Entry" region
|
||||
self.ALL_REGIONS_BY_NAME["Entry"]["entities"].append(entity_hex)
|
||||
self.ALL_REGIONS_BY_NAME["Entry"].logical_entities.append(entity_hex)
|
||||
# However, it will also be important to keep track of their physical location for postgame purposes.
|
||||
current_region["physical_entities"].append(entity_hex)
|
||||
current_region.physical_entities.append(entity_hex)
|
||||
continue
|
||||
|
||||
required_item_lambda = line_split.pop(0)
|
||||
item_requirement_string = line_split.pop(0)
|
||||
|
||||
laser_names = {
|
||||
"Laser",
|
||||
@@ -224,18 +217,18 @@ class StaticWitnessLogicObj:
|
||||
entity_type = "Panel"
|
||||
location_type = "General"
|
||||
|
||||
required_items = parse_lambda(required_item_lambda)
|
||||
required_panels = parse_lambda(required_panel_lambda)
|
||||
required_items = parse_witness_rule(item_requirement_string)
|
||||
required_entities = parse_witness_rule(entity_requirement_string)
|
||||
|
||||
required_items = frozenset(required_items)
|
||||
|
||||
requirement = {
|
||||
"entities": required_panels,
|
||||
"entities": required_entities,
|
||||
"items": required_items
|
||||
}
|
||||
|
||||
if entity_type == "Obelisk Side":
|
||||
eps = set(next(iter(required_panels)))
|
||||
eps = set(next(iter(required_entities)))
|
||||
eps -= {"Theater to Tunnels"}
|
||||
|
||||
eps_ints = {int(h, 16) for h in eps}
|
||||
@@ -260,39 +253,43 @@ class StaticWitnessLogicObj:
|
||||
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = requirement
|
||||
|
||||
current_region["entities"].append(entity_hex)
|
||||
current_region["physical_entities"].append(entity_hex)
|
||||
current_region.logical_entities.append(entity_hex)
|
||||
current_region.physical_entities.append(entity_hex)
|
||||
|
||||
self.add_easter_eggs()
|
||||
|
||||
def reverse_connection(self, source_region: str, connection: Tuple[str, Set[WitnessRule]]) -> None:
|
||||
target = connection[0]
|
||||
traversal_options = connection[1]
|
||||
|
||||
def reverse_connection(self, source_region: str, connection: ConnectionDefinition) -> None:
|
||||
# Reverse this connection with all its possibilities, except the ones marked as "OneWay".
|
||||
for requirement in traversal_options:
|
||||
remaining_options = set()
|
||||
for option in requirement:
|
||||
if not any(req == "TrueOneWay" for req in option):
|
||||
remaining_options.add(option)
|
||||
remaining_options: Set[FrozenSet[str]] = set()
|
||||
for sub_option in connection.traversal_rule:
|
||||
if not any(req == "TrueOneWay" for req in sub_option):
|
||||
remaining_options.add(sub_option)
|
||||
|
||||
if remaining_options:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[target][source_region].add(frozenset(remaining_options))
|
||||
reversed_connection = ConnectionDefinition(source_region, frozenset(remaining_options))
|
||||
if reversed_connection.can_be_traversed:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[connection.target_region].append(reversed_connection)
|
||||
|
||||
def reverse_connections(self) -> None:
|
||||
# Iterate all connections
|
||||
for region_name, connections in list(self.CONNECTIONS_WITH_DUPLICATES.items()):
|
||||
for connection in connections.items():
|
||||
for connection in connections:
|
||||
self.reverse_connection(region_name, connection)
|
||||
|
||||
def combine_connections(self) -> None:
|
||||
# All regions need to be present, and this dict is copied later - Thus, defaultdict is not the correct choice.
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: set() for region_name in self.ALL_REGIONS_BY_NAME}
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: [] for region_name in self.ALL_REGIONS_BY_NAME}
|
||||
|
||||
for source, connections in self.CONNECTIONS_WITH_DUPLICATES.items():
|
||||
for target, requirement in connections.items():
|
||||
combined_req = logical_or_witness_rules(requirement)
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME[source].add((target, combined_req))
|
||||
# Organize rules by target region
|
||||
traversal_options_by_target_region = defaultdict(list)
|
||||
for target_region, traversal_option in connections:
|
||||
traversal_options_by_target_region[target_region].append(traversal_option)
|
||||
|
||||
# Combine connections to the same target region into one connection
|
||||
for target, traversal_rules in traversal_options_by_target_region.items():
|
||||
combined_rule = logical_or_witness_rules(traversal_rules)
|
||||
combined_connection = ConnectionDefinition(target, combined_rule)
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME[source].append(combined_connection)
|
||||
|
||||
|
||||
# Item data parsed from WitnessItems.txt
|
||||
|
||||
@@ -2,17 +2,12 @@ from datetime import date
|
||||
from math import floor
|
||||
from pkgutil import get_data
|
||||
from random import Random
|
||||
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar
|
||||
from typing import Collection, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar
|
||||
|
||||
from .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# A WitnessRule is just an or-chain of and-conditions.
|
||||
# It represents the set of all options that could fulfill this requirement.
|
||||
# E.g. if something requires "Dots or (Shapers and Stars)", it'd be represented as: {{"Dots"}, {"Shapers, "Stars"}}
|
||||
# {} is an unusable requirement.
|
||||
# {{}} is an always usable requirement.
|
||||
WitnessRule = FrozenSet[FrozenSet[str]]
|
||||
|
||||
|
||||
def cast_not_none(value: Optional[T]) -> T:
|
||||
assert value is not None
|
||||
@@ -62,7 +57,7 @@ def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]:
|
||||
return rounded_output
|
||||
|
||||
|
||||
def define_new_region(region_string: str, area: dict[str, Any]) -> Tuple[Dict[str, Any], Set[Tuple[str, WitnessRule]]]:
|
||||
def define_new_region(region_string: str, area: AreaDefinition) -> Tuple[RegionDefinition, List[ConnectionDefinition]]:
|
||||
"""
|
||||
Returns a region object by parsing a line in the logic file
|
||||
"""
|
||||
@@ -77,35 +72,28 @@ def define_new_region(region_string: str, area: dict[str, Any]) -> Tuple[Dict[st
|
||||
region_name = region_name_split[0]
|
||||
region_name_simple = region_name_split[1][:-1]
|
||||
|
||||
options = set()
|
||||
options = []
|
||||
|
||||
for _ in range(len(line_split) // 2):
|
||||
connected_region = line_split.pop(0)
|
||||
corresponding_lambda = line_split.pop(0)
|
||||
traversal_rule_string = line_split.pop(0)
|
||||
|
||||
options.add(
|
||||
(connected_region, parse_lambda(corresponding_lambda))
|
||||
)
|
||||
options.append(ConnectionDefinition(connected_region, parse_witness_rule(traversal_rule_string)))
|
||||
|
||||
region_obj = RegionDefinition(region_name, region_name_simple, area)
|
||||
|
||||
region_obj = {
|
||||
"name": region_name,
|
||||
"shortName": region_name_simple,
|
||||
"entities": [],
|
||||
"physical_entities": [],
|
||||
"area": area,
|
||||
}
|
||||
return region_obj, options
|
||||
|
||||
|
||||
def parse_lambda(lambda_string: str) -> WitnessRule:
|
||||
def parse_witness_rule(rule_string: str) -> WitnessRule:
|
||||
"""
|
||||
Turns a lambda String literal like this: a | b & c
|
||||
into a set of sets like this: {{a}, {b, c}}
|
||||
The lambda has to be in DNF.
|
||||
Turns a rule string literal like this: a | b & c
|
||||
into a set of sets (called "WitnessRule") like this: {{a}, {b, c}}
|
||||
The rule string has to be in DNF.
|
||||
"""
|
||||
if lambda_string == "True":
|
||||
if rule_string == "True":
|
||||
return frozenset([frozenset()])
|
||||
split_ands = set(lambda_string.split(" | "))
|
||||
split_ands = set(rule_string.split(" | "))
|
||||
return frozenset({frozenset(a.split(" & ")) for a in split_ands})
|
||||
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class EntityHuntPicker:
|
||||
|
||||
eligible_panels_by_area = defaultdict(set)
|
||||
for eligible_panel in all_eligible_panels:
|
||||
associated_area = static_witness_logic.ENTITIES_BY_HEX[eligible_panel]["area"]["name"]
|
||||
associated_area = static_witness_logic.ENTITIES_BY_HEX[eligible_panel]["area"].name
|
||||
eligible_panels_by_area[associated_area].add(eligible_panel)
|
||||
|
||||
return all_eligible_panels, eligible_panels_by_area
|
||||
|
||||
@@ -18,7 +18,7 @@ if __name__ == "__main__":
|
||||
for entity_id, entity_object in static_witness_logic.ENTITIES_BY_HEX.items():
|
||||
location_id = entity_object["id"]
|
||||
|
||||
area = entity_object["area"]["name"]
|
||||
area = entity_object["area"].name
|
||||
area_to_entity_ids[area].append(entity_id)
|
||||
|
||||
if location_id is None:
|
||||
|
||||
@@ -464,7 +464,7 @@ def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[st
|
||||
|
||||
|
||||
def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]:
|
||||
potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.keys())
|
||||
potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.values())
|
||||
|
||||
locations_per_area = {}
|
||||
items_per_area = {}
|
||||
@@ -472,14 +472,14 @@ def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]]
|
||||
for area in potential_areas:
|
||||
regions = [
|
||||
world.get_region(region)
|
||||
for region in static_witness_logic.ALL_AREAS_BY_NAME[area]["regions"]
|
||||
for region in area.regions
|
||||
if region in world.player_regions.created_region_names
|
||||
]
|
||||
locations = [location for region in regions for location in region.get_locations() if not location.is_event]
|
||||
|
||||
if locations:
|
||||
locations_per_area[area] = locations
|
||||
items_per_area[area] = [location.item for location in locations]
|
||||
locations_per_area[area.name] = locations
|
||||
items_per_area[area.name] = [location.item for location in locations]
|
||||
|
||||
return locations_per_area, items_per_area
|
||||
|
||||
@@ -516,7 +516,7 @@ def word_area_hint(world: "WitnessWorld", hinted_area: str, area_items: List[Ite
|
||||
hunt_panels = None
|
||||
if world.options.victory_condition == "panel_hunt" and hinted_area != "Easter Eggs":
|
||||
hunt_panels = sum(
|
||||
static_witness_logic.ENTITIES_BY_HEX[hunt_entity]["area"]["name"] == hinted_area
|
||||
static_witness_logic.ENTITIES_BY_HEX[hunt_entity]["area"].name == hinted_area
|
||||
for hunt_entity in world.player_logic.HUNT_ENTITIES
|
||||
)
|
||||
|
||||
@@ -620,7 +620,7 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int,
|
||||
|
||||
already_hinted_locations |= {
|
||||
loc for loc in world.multiworld.get_reachable_locations(state, world.player)
|
||||
if loc.address and static_witness_logic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)"
|
||||
if loc.address and static_witness_logic.ENTITIES_BY_NAME[loc.name]["area"].name == "Tutorial (Inside)"
|
||||
}
|
||||
|
||||
intended_location_hints = hint_amount - area_hints
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Tuple
|
||||
|
||||
from schema import And, Schema
|
||||
|
||||
@@ -20,10 +20,10 @@ from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, cast
|
||||
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.definition_classes import ConnectionDefinition, WitnessRule
|
||||
from .data.item_definition_classes import DoorItemDefinition, ItemCategory, ProgressiveItemDefinition
|
||||
from .data.static_logic import StaticWitnessLogicObj
|
||||
from .data.utils import (
|
||||
WitnessRule,
|
||||
get_boat,
|
||||
get_caves_except_path_to_challenge_exclusion_list,
|
||||
get_complex_additional_panels,
|
||||
@@ -47,7 +47,7 @@ from .data.utils import (
|
||||
get_vault_exclusion_list,
|
||||
logical_and_witness_rules,
|
||||
logical_or_witness_rules,
|
||||
parse_lambda,
|
||||
parse_witness_rule,
|
||||
)
|
||||
from .entity_hunt import EntityHuntPicker
|
||||
|
||||
@@ -97,10 +97,10 @@ class WitnessPlayerLogic:
|
||||
elif self.DIFFICULTY == "none":
|
||||
self.REFERENCE_LOGIC = static_witness_logic.vanilla
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy(
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL: Dict[str, List[ConnectionDefinition]] = copy.deepcopy(
|
||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||
)
|
||||
self.CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy(
|
||||
self.CONNECTIONS_BY_REGION_NAME: Dict[str, List[ConnectionDefinition]] = copy.deepcopy(
|
||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||
)
|
||||
self.DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = copy.deepcopy(
|
||||
@@ -178,7 +178,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
entity_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity_hex]
|
||||
|
||||
if entity_obj["region"] is not None and entity_obj["region"]["name"] in self.UNREACHABLE_REGIONS:
|
||||
if entity_obj["region"] is not None and entity_obj["region"].name in self.UNREACHABLE_REGIONS:
|
||||
return frozenset()
|
||||
|
||||
# For the requirement of an entity, we consider two things:
|
||||
@@ -270,7 +270,7 @@ class WitnessPlayerLogic:
|
||||
new_items = theoretical_new_items
|
||||
if dep_obj["region"] and entity_obj["region"] != dep_obj["region"]:
|
||||
new_items = frozenset(
|
||||
frozenset(possibility | {dep_obj["region"]["name"]})
|
||||
frozenset(possibility | {dep_obj["region"].name})
|
||||
for possibility in new_items
|
||||
)
|
||||
|
||||
@@ -359,11 +359,11 @@ class WitnessPlayerLogic:
|
||||
line_split = line.split(" - ")
|
||||
|
||||
requirement = {
|
||||
"entities": parse_lambda(line_split[1]),
|
||||
"entities": parse_witness_rule(line_split[1]),
|
||||
}
|
||||
|
||||
if len(line_split) > 2:
|
||||
required_items = parse_lambda(line_split[2])
|
||||
required_items = parse_witness_rule(line_split[2])
|
||||
items_actually_in_the_game = [
|
||||
item_name for item_name, item_definition in static_witness_logic.ALL_ITEMS.items()
|
||||
if item_definition.category is ItemCategory.SYMBOL
|
||||
@@ -394,26 +394,31 @@ class WitnessPlayerLogic:
|
||||
return
|
||||
|
||||
if adj_type == "New Connections":
|
||||
# This adjustment type does not actually reverse the connection if it could be reversed.
|
||||
# If needed, this might be added later
|
||||
line_split = line.split(" - ")
|
||||
source_region = line_split[0]
|
||||
target_region = line_split[1]
|
||||
panel_set_string = line_split[2]
|
||||
|
||||
for connection in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region]:
|
||||
if connection[0] == target_region:
|
||||
if connection.target_region == target_region:
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].remove(connection)
|
||||
|
||||
if panel_set_string == "TrueOneWay":
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(
|
||||
(target_region, frozenset({frozenset(["TrueOneWay"])}))
|
||||
)
|
||||
# This means the connection can be completely replaced
|
||||
only_connection = ConnectionDefinition(target_region, frozenset({frozenset(["TrueOneWay"])}))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(only_connection)
|
||||
else:
|
||||
new_lambda = logical_or_witness_rules([connection[1], parse_lambda(panel_set_string)])
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add((target_region, new_lambda))
|
||||
combined_rule = logical_or_witness_rules(
|
||||
[connection.traversal_rule, parse_witness_rule(panel_set_string)]
|
||||
)
|
||||
combined_connection = ConnectionDefinition(target_region, combined_rule)
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(combined_connection)
|
||||
break
|
||||
else:
|
||||
new_conn = (target_region, parse_lambda(panel_set_string))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(new_conn)
|
||||
new_connection = ConnectionDefinition(target_region, parse_witness_rule(panel_set_string))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(new_connection)
|
||||
|
||||
if adj_type == "Added Locations":
|
||||
if "0x" in line:
|
||||
@@ -558,7 +563,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
self.AVAILABLE_EASTER_EGGS_PER_REGION = defaultdict(int)
|
||||
for entity_hex in self.AVAILABLE_EASTER_EGGS:
|
||||
region_name = static_witness_logic.ENTITIES_BY_HEX[entity_hex]["region"]["name"]
|
||||
region_name = static_witness_logic.ENTITIES_BY_HEX[entity_hex]["region"].name
|
||||
self.AVAILABLE_EASTER_EGGS_PER_REGION[region_name] += 1
|
||||
|
||||
eggs_per_check, logically_required_eggs_per_check = world.options.easter_egg_hunt.get_step_and_logical_step()
|
||||
@@ -796,7 +801,7 @@ class WitnessPlayerLogic:
|
||||
next_region = regions_to_check.pop()
|
||||
|
||||
for region_exit in self.CONNECTIONS_BY_REGION_NAME[next_region]:
|
||||
target = region_exit[0]
|
||||
target = region_exit.target_region
|
||||
|
||||
if target in reachable_regions:
|
||||
continue
|
||||
@@ -844,7 +849,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
# First, entities in unreachable regions are obviously themselves unreachable.
|
||||
for region in new_unreachable_regions:
|
||||
for entity in static_witness_logic.ALL_REGIONS_BY_NAME[region]["physical_entities"]:
|
||||
for entity in static_witness_logic.ALL_REGIONS_BY_NAME[region].physical_entities:
|
||||
# Never disable the Victory Location.
|
||||
if entity == self.VICTORY_LOCATION:
|
||||
continue
|
||||
@@ -879,11 +884,11 @@ class WitnessPlayerLogic:
|
||||
if not new_unreachable_regions and not newly_discovered_disabled_entities:
|
||||
return
|
||||
|
||||
def reduce_connection_requirement(self, connection: Tuple[str, WitnessRule]) -> WitnessRule:
|
||||
def reduce_connection_requirement(self, connection: ConnectionDefinition) -> ConnectionDefinition:
|
||||
all_possibilities = []
|
||||
|
||||
# Check each traversal option individually
|
||||
for option in connection[1]:
|
||||
for option in connection.traversal_rule:
|
||||
individual_entity_requirements: List[WitnessRule] = []
|
||||
for entity in option:
|
||||
# If a connection requires solving a disabled entity, it is not valid.
|
||||
@@ -901,7 +906,7 @@ class WitnessPlayerLogic:
|
||||
entity_req = self.get_entity_requirement(entity)
|
||||
|
||||
if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]:
|
||||
region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]["name"]
|
||||
region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"].name
|
||||
entity_req = logical_and_witness_rules([entity_req, frozenset({frozenset({region_name})})])
|
||||
|
||||
individual_entity_requirements.append(entity_req)
|
||||
@@ -909,7 +914,7 @@ class WitnessPlayerLogic:
|
||||
# Merge all possible requirements into one DNF condition.
|
||||
all_possibilities.append(logical_and_witness_rules(individual_entity_requirements))
|
||||
|
||||
return logical_or_witness_rules(all_possibilities)
|
||||
return ConnectionDefinition(connection.target_region, logical_or_witness_rules(all_possibilities))
|
||||
|
||||
def make_dependency_reduced_checklist(self) -> None:
|
||||
"""
|
||||
@@ -942,14 +947,14 @@ class WitnessPlayerLogic:
|
||||
|
||||
# Make independent region connection requirements based on the entities they require
|
||||
for region, connections in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL.items():
|
||||
new_connections = set()
|
||||
new_connections = []
|
||||
|
||||
for connection in connections:
|
||||
overall_requirement = self.reduce_connection_requirement(connection)
|
||||
reduced_connection = self.reduce_connection_requirement(connection)
|
||||
|
||||
# If there is a way to use this connection, add it.
|
||||
if overall_requirement:
|
||||
new_connections.add((connection[0], overall_requirement))
|
||||
if reduced_connection.can_be_traversed:
|
||||
new_connections.append(reduced_connection)
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME[region] = new_connections
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ from BaseClasses import Entrance, Region
|
||||
from worlds.generic.Rules import CollectionRule
|
||||
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.definition_classes import WitnessRule
|
||||
from .data.static_logic import StaticWitnessLogicObj
|
||||
from .data.utils import WitnessRule, optimize_witness_rule
|
||||
from .data.utils import optimize_witness_rule
|
||||
from .locations import WitnessPlayerLocations
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
|
||||
@@ -114,7 +115,7 @@ class WitnessPlayerRegions:
|
||||
if k not in player_logic.UNREACHABLE_REGIONS
|
||||
}
|
||||
|
||||
event_locations_per_region = defaultdict(dict)
|
||||
event_locations_per_region: Dict[str, Dict[str, int]] = defaultdict(dict)
|
||||
|
||||
for event_location, event_item_and_entity in player_logic.EVENT_ITEM_PAIRS.items():
|
||||
entity_or_region = event_item_and_entity[1]
|
||||
@@ -126,13 +127,13 @@ class WitnessPlayerRegions:
|
||||
if region is None:
|
||||
region_name = "Entry"
|
||||
else:
|
||||
region_name = region["name"]
|
||||
region_name = region.name
|
||||
order = self.reference_logic.ENTITIES_BY_HEX[entity_or_region]["order"]
|
||||
event_locations_per_region[region_name][event_location] = order
|
||||
|
||||
for region_name, region in regions_to_create.items():
|
||||
location_entities_for_this_region = [
|
||||
self.reference_logic.ENTITIES_BY_HEX[entity] for entity in region["entities"]
|
||||
self.reference_logic.ENTITIES_BY_HEX[entity] for entity in region.logical_entities
|
||||
]
|
||||
locations_for_this_region = {
|
||||
entity["checkName"]: entity["order"] for entity in location_entities_for_this_region
|
||||
|
||||
@@ -10,7 +10,7 @@ from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import CollectionRule, set_rule
|
||||
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.utils import WitnessRule
|
||||
from .data.definition_classes import WitnessRule
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -516,6 +516,6 @@ async def main() -> None:
|
||||
|
||||
|
||||
def launch() -> None:
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -177,7 +177,7 @@ def main() -> None:
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
|
||||
asyncio.run(_main())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user