mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 23:25:51 -08:00
Compare commits
28 Commits
NewSoupVi-
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66927171bc | ||
|
|
74ed14b1c7 | ||
|
|
41f1ab054c | ||
|
|
8019687861 | ||
|
|
32803448df | ||
|
|
d665d8f944 | ||
|
|
726144a573 | ||
|
|
55f1c08291 | ||
|
|
9f2717bfc9 | ||
|
|
898509e7ee | ||
|
|
1f685b4272 | ||
|
|
c622240730 | ||
|
|
1d314374d7 | ||
|
|
753eb8683f | ||
|
|
e8542b8acd | ||
|
|
2a11d610b6 | ||
|
|
92023a2cb5 | ||
|
|
df94271d30 | ||
|
|
0354315c22 | ||
|
|
e796f0ae64 | ||
|
|
c61505baf6 | ||
|
|
3972b1b257 | ||
|
|
1fe3d842c8 | ||
|
|
e9ad7cb797 | ||
|
|
533395d336 | ||
|
|
2ae51364d9 | ||
|
|
f6e3113af6 | ||
|
|
da34800f43 |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -36,6 +36,7 @@ jobs:
|
||||
run: |
|
||||
Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/${Env:ENEMIZER_VERSION}/win-x64.zip -OutFile enemizer.zip
|
||||
Expand-Archive -Path enemizer.zip -DestinationPath EnemizerCLI -Force
|
||||
choco install innosetup --version=6.2.2 --allow-downgrade
|
||||
- name: Build
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
@@ -112,7 +112,7 @@ class AdventureContext(CommonContext):
|
||||
if ': !' not in msg:
|
||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||
elif cmd == "ReceivedItems":
|
||||
msg = f"Received {', '.join([self.item_names.lookup_in_slot(item.item) for item in args['items']])}"
|
||||
msg = f"Received {', '.join([self.item_names.lookup_in_game(item.item) for item in args['items']])}"
|
||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||
elif cmd == "Retrieved":
|
||||
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:
|
||||
|
||||
@@ -225,6 +225,9 @@ class CommonContext:
|
||||
def lookup_in_slot(self, code: int, slot: typing.Optional[int] = None) -> str:
|
||||
"""Returns the name for an item/location id in the context of a specific slot or own slot if `slot` is
|
||||
omitted.
|
||||
|
||||
Use of `lookup_in_slot` should not be used when not connected to a server. If looking in own game, set
|
||||
`ctx.game` and use `lookup_in_game` method instead.
|
||||
"""
|
||||
if slot is None:
|
||||
slot = self.ctx.slot
|
||||
|
||||
@@ -65,7 +65,7 @@ def get_seed_name(random_source) -> str:
|
||||
return f"{random_source.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)
|
||||
|
||||
|
||||
def main(args=None):
|
||||
def main(args=None) -> Tuple[argparse.Namespace, int]:
|
||||
# __name__ == "__main__" check so unittests that already imported worlds don't trip this.
|
||||
if __name__ == "__main__" and "worlds" in sys.modules:
|
||||
raise Exception("Worlds system should not be loaded before logging init.")
|
||||
@@ -237,8 +237,7 @@ def main(args=None):
|
||||
with open(os.path.join(args.outputpath if args.outputpath else ".", f"generate_{seed_name}.yaml"), "wt") as f:
|
||||
yaml.dump(important, f)
|
||||
|
||||
from Main import main as ERmain
|
||||
return ERmain(erargs, seed)
|
||||
return erargs, seed
|
||||
|
||||
|
||||
def read_weights_yamls(path) -> Tuple[Any, ...]:
|
||||
@@ -547,7 +546,9 @@ def roll_alttp_settings(ret: argparse.Namespace, weights):
|
||||
if __name__ == '__main__':
|
||||
import atexit
|
||||
confirmation = atexit.register(input, "Press enter to close.")
|
||||
multiworld = main()
|
||||
erargs, seed = main()
|
||||
from Main import main as ERmain
|
||||
multiworld = ERmain(erargs, seed)
|
||||
if __debug__:
|
||||
import gc
|
||||
import sys
|
||||
|
||||
75
Options.py
75
Options.py
@@ -53,8 +53,8 @@ class AssembleOptions(abc.ABCMeta):
|
||||
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
|
||||
options.update(new_options)
|
||||
# apply aliases, without name_lookup
|
||||
aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if
|
||||
name.startswith("alias_")}
|
||||
aliases = attrs["aliases"] = {name[6:].lower(): option_id for name, option_id in attrs.items() if
|
||||
name.startswith("alias_")}
|
||||
|
||||
assert (
|
||||
name in {"Option", "VerifyKeys"} or # base abstract classes don't need default
|
||||
@@ -126,10 +126,28 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
|
||||
# can be weighted between selections
|
||||
supports_weighting = True
|
||||
|
||||
rich_text_doc: typing.Optional[bool] = None
|
||||
"""Whether the WebHost should render the Option's docstring as rich text.
|
||||
|
||||
If this is True, the Option's docstring is interpreted as reStructuredText_,
|
||||
the standard Python markup format. In the WebHost, it's rendered to HTML so
|
||||
that lists, emphasis, and other rich text features are displayed properly.
|
||||
|
||||
If this is False, the docstring is instead interpreted as plain text, and
|
||||
displayed as-is on the WebHost with whitespace preserved.
|
||||
|
||||
If this is None, it inherits the value of `World.rich_text_options_doc`. For
|
||||
backwards compatibility, this defaults to False, but worlds are encouraged to
|
||||
set it to True and use reStructuredText for their Option documentation.
|
||||
|
||||
.. _reStructuredText: https://docutils.sourceforge.io/rst.html
|
||||
"""
|
||||
|
||||
# filled by AssembleOptions:
|
||||
name_lookup: typing.ClassVar[typing.Dict[T, str]] # type: ignore
|
||||
# https://github.com/python/typing/discussions/1460 the reason for this type: ignore
|
||||
options: typing.ClassVar[typing.Dict[str, int]]
|
||||
aliases: typing.ClassVar[typing.Dict[str, int]]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.current_option_name})"
|
||||
@@ -735,6 +753,12 @@ class NamedRange(Range):
|
||||
elif value > self.range_end and value not in self.special_range_names.values():
|
||||
raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__} " +
|
||||
f"and is also not one of the supported named special values: {self.special_range_names}")
|
||||
|
||||
# See docstring
|
||||
for key in self.special_range_names:
|
||||
if key != key.lower():
|
||||
raise Exception(f"{self.__class__.__name__} has an invalid special_range_names key: {key}. "
|
||||
f"NamedRange keys must use only lowercase letters, and ideally should be snake_case.")
|
||||
self.value = value
|
||||
|
||||
@classmethod
|
||||
@@ -1121,10 +1145,13 @@ class PlandoConnections(Option[typing.List[PlandoConnection]], metaclass=Connect
|
||||
|
||||
class Accessibility(Choice):
|
||||
"""Set rules for reachability of your items/locations.
|
||||
Locations: ensure everything can be reached and acquired.
|
||||
Items: ensure all logically relevant items can be acquired.
|
||||
Minimal: ensure what is needed to reach your goal can be acquired."""
|
||||
|
||||
- **Locations:** ensure everything can be reached and acquired.
|
||||
- **Items:** ensure all logically relevant items can be acquired.
|
||||
- **Minimal:** ensure what is needed to reach your goal can be acquired.
|
||||
"""
|
||||
display_name = "Accessibility"
|
||||
rich_text_doc = True
|
||||
option_locations = 0
|
||||
option_items = 1
|
||||
option_minimal = 2
|
||||
@@ -1133,14 +1160,15 @@ class Accessibility(Choice):
|
||||
|
||||
|
||||
class ProgressionBalancing(NamedRange):
|
||||
"""
|
||||
A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
|
||||
"""A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
|
||||
|
||||
A lower setting means more getting stuck. A higher setting means less getting stuck.
|
||||
"""
|
||||
default = 50
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
display_name = "Progression Balancing"
|
||||
rich_text_doc = True
|
||||
special_range_names = {
|
||||
"disabled": 0,
|
||||
"normal": 50,
|
||||
@@ -1205,29 +1233,36 @@ class CommonOptions(metaclass=OptionsMetaProperty):
|
||||
class LocalItems(ItemSet):
|
||||
"""Forces these items to be in their native world."""
|
||||
display_name = "Local Items"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class NonLocalItems(ItemSet):
|
||||
"""Forces these items to be outside their native world."""
|
||||
display_name = "Non-local Items"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class StartInventory(ItemDict):
|
||||
"""Start with these items."""
|
||||
verify_item_name = True
|
||||
display_name = "Start Inventory"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class StartInventoryPool(StartInventory):
|
||||
"""Start with these items and don't place them in the world.
|
||||
The game decides what the replacement items will be."""
|
||||
|
||||
The game decides what the replacement items will be.
|
||||
"""
|
||||
verify_item_name = True
|
||||
display_name = "Start Inventory from Pool"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class StartHints(ItemSet):
|
||||
"""Start with these item's locations prefilled into the !hint command."""
|
||||
"""Start with these item's locations prefilled into the ``!hint`` command."""
|
||||
display_name = "Start Hints"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class LocationSet(OptionSet):
|
||||
@@ -1236,28 +1271,33 @@ class LocationSet(OptionSet):
|
||||
|
||||
|
||||
class StartLocationHints(LocationSet):
|
||||
"""Start with these locations and their item prefilled into the !hint command"""
|
||||
"""Start with these locations and their item prefilled into the ``!hint`` command."""
|
||||
display_name = "Start Location Hints"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class ExcludeLocations(LocationSet):
|
||||
"""Prevent these locations from having an important item"""
|
||||
"""Prevent these locations from having an important item."""
|
||||
display_name = "Excluded Locations"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class PriorityLocations(LocationSet):
|
||||
"""Prevent these locations from having an unimportant item"""
|
||||
"""Prevent these locations from having an unimportant item."""
|
||||
display_name = "Priority Locations"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class DeathLink(Toggle):
|
||||
"""When you die, everyone dies. Of course the reverse is true too."""
|
||||
display_name = "Death Link"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class ItemLinks(OptionList):
|
||||
"""Share part of your item pool with other players."""
|
||||
display_name = "Item Links"
|
||||
rich_text_doc = True
|
||||
default = []
|
||||
schema = Schema([
|
||||
{
|
||||
@@ -1324,6 +1364,7 @@ class ItemLinks(OptionList):
|
||||
|
||||
class Removed(FreeText):
|
||||
"""This Option has been Removed."""
|
||||
rich_text_doc = True
|
||||
default = ""
|
||||
visibility = Visibility.none
|
||||
|
||||
@@ -1426,14 +1467,18 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||
|
||||
return data, notes
|
||||
|
||||
def yaml_dump_scalar(scalar) -> str:
|
||||
# yaml dump may add end of document marker and newlines.
|
||||
return yaml.dump(scalar).replace("...\n", "").strip()
|
||||
|
||||
for game_name, world in AutoWorldRegister.world_types.items():
|
||||
if not world.hidden or generate_hidden:
|
||||
grouped_options = get_option_groups(world)
|
||||
option_groups = get_option_groups(world)
|
||||
with open(local_path("data", "options.yaml")) as f:
|
||||
file_data = f.read()
|
||||
res = Template(file_data).render(
|
||||
option_groups=grouped_options,
|
||||
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
|
||||
option_groups=option_groups,
|
||||
__version__=__version__, game=game_name, yaml_dump=yaml_dump_scalar,
|
||||
dictify_range=dictify_range,
|
||||
)
|
||||
|
||||
|
||||
@@ -247,8 +247,8 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
|
||||
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||
toDraw = ""
|
||||
for i in range(20):
|
||||
if i < len(str(ctx.item_names.lookup_in_slot(l.item))):
|
||||
toDraw += str(ctx.item_names.lookup_in_slot(l.item))[i]
|
||||
if i < len(str(ctx.item_names.lookup_in_game(l.item))):
|
||||
toDraw += str(ctx.item_names.lookup_in_game(l.item))[i]
|
||||
else:
|
||||
break
|
||||
f.write(toDraw)
|
||||
|
||||
@@ -176,7 +176,7 @@ class WargrooveContext(CommonContext):
|
||||
if not os.path.isfile(path):
|
||||
open(path, 'w').close()
|
||||
# Announcing commander unlocks
|
||||
item_name = self.item_names.lookup_in_slot(network_item.item)
|
||||
item_name = self.item_names.lookup_in_game(network_item.item)
|
||||
if item_name in faction_table.keys():
|
||||
for commander in faction_table[item_name]:
|
||||
logger.info(f"{commander.name} has been unlocked!")
|
||||
@@ -197,7 +197,7 @@ class WargrooveContext(CommonContext):
|
||||
open(print_path, 'w').close()
|
||||
with open(print_path, 'w') as f:
|
||||
f.write("Received " +
|
||||
self.item_names.lookup_in_slot(network_item.item) +
|
||||
self.item_names.lookup_in_game(network_item.item) +
|
||||
" from " +
|
||||
self.player_names[network_item.player])
|
||||
f.close()
|
||||
@@ -342,7 +342,7 @@ class WargrooveContext(CommonContext):
|
||||
faction_items = 0
|
||||
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
|
||||
for network_item in self.items_received:
|
||||
if self.item_names.lookup_in_slot(network_item.item) in faction_item_names:
|
||||
if self.item_names.lookup_in_game(network_item.item) in faction_item_names:
|
||||
faction_items += 1
|
||||
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
|
||||
# Must be an integer larger than 0
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
from textwrap import dedent
|
||||
from typing import Dict, Union
|
||||
from docutils.core import publish_parts
|
||||
|
||||
import yaml
|
||||
from flask import redirect, render_template, request, Response
|
||||
@@ -66,6 +67,22 @@ def filter_dedent(text: str) -> str:
|
||||
return dedent(text).strip("\n ")
|
||||
|
||||
|
||||
@app.template_filter("rst_to_html")
|
||||
def filter_rst_to_html(text: str) -> str:
|
||||
"""Converts reStructuredText (such as a Python docstring) to HTML."""
|
||||
if text.startswith(" ") or text.startswith("\t"):
|
||||
text = dedent(text)
|
||||
elif "\n" in text:
|
||||
lines = text.splitlines()
|
||||
text = lines[0] + "\n" + dedent("\n".join(lines[1:]))
|
||||
|
||||
return publish_parts(text, writer_name='html', settings=None, settings_overrides={
|
||||
'raw_enable': False,
|
||||
'file_insertion_enabled': False,
|
||||
'output_encoding': 'unicode'
|
||||
})['body']
|
||||
|
||||
|
||||
@app.template_test("ordered")
|
||||
def test_ordered(obj):
|
||||
return isinstance(obj, collections.abc.Sequence)
|
||||
|
||||
@@ -12,12 +12,12 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
*/
|
||||
|
||||
/* Base styles for the element that has a tooltip */
|
||||
[data-tooltip], .tooltip {
|
||||
[data-tooltip], .tooltip-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Base styles for the entire tooltip */
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after {
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
@@ -39,14 +39,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip:hover:before, .tooltip:hover:after{
|
||||
[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip-container:hover:before,
|
||||
.tooltip-container:hover .tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/** Directional arrow styles */
|
||||
.tooltip:before, [data-tooltip]:before {
|
||||
[data-tooltip]:before, .tooltip-container:before {
|
||||
z-index: 10000;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
@@ -54,7 +55,7 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
}
|
||||
|
||||
/** Content styles */
|
||||
.tooltip:after, [data-tooltip]:after {
|
||||
[data-tooltip]:after, .tooltip {
|
||||
width: 260px;
|
||||
z-index: 10000;
|
||||
padding: 8px;
|
||||
@@ -63,24 +64,26 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
background-color: hsla(0, 0%, 20%, 0.9);
|
||||
color: #fff;
|
||||
content: attr(data-tooltip);
|
||||
white-space: pre-wrap;
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
[data-tooltip]:before, [data-tooltip]:after{
|
||||
[data-tooltip]:after {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after,
|
||||
.tooltip-top:before, .tooltip-top:after {
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
[data-tooltip]:before, .tooltip:before, .tooltip-top:before {
|
||||
[data-tooltip]:before, .tooltip-container:before {
|
||||
margin-left: -6px;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #000;
|
||||
@@ -88,19 +91,19 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
}
|
||||
|
||||
/** Horizontally align tooltips on the top and bottom */
|
||||
[data-tooltip]:after, .tooltip:after, .tooltip-top:after {
|
||||
[data-tooltip]:after, .tooltip {
|
||||
margin-left: -80px;
|
||||
}
|
||||
|
||||
[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip:hover:before, .tooltip:hover:after,
|
||||
.tooltip-top:hover:before, .tooltip-top:hover:after {
|
||||
[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip-container:hover:before,
|
||||
.tooltip-container:hover .tooltip {
|
||||
-webkit-transform: translateY(-12px);
|
||||
-moz-transform: translateY(-12px);
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
|
||||
/** Tooltips on the left */
|
||||
.tooltip-left:before, .tooltip-left:after {
|
||||
.tooltip-left:before, [data-tooltip].tooltip-left:after, .tooltip-left .tooltip {
|
||||
right: 100%;
|
||||
bottom: 50%;
|
||||
left: auto;
|
||||
@@ -115,14 +118,14 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
border-left-color: hsla(0, 0%, 20%, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-left:hover:before, .tooltip-left:hover:after {
|
||||
.tooltip-left:hover:before, [data-tooltip].tooltip-left:hover:after, .tooltip-left:hover .tooltip {
|
||||
-webkit-transform: translateX(-12px);
|
||||
-moz-transform: translateX(-12px);
|
||||
transform: translateX(-12px);
|
||||
}
|
||||
|
||||
/** Tooltips on the bottom */
|
||||
.tooltip-bottom:before, .tooltip-bottom:after {
|
||||
.tooltip-bottom:before, [data-tooltip].tooltip-bottom:after, .tooltip-bottom .tooltip {
|
||||
top: 100%;
|
||||
bottom: auto;
|
||||
left: 50%;
|
||||
@@ -136,14 +139,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
border-bottom-color: hsla(0, 0%, 20%, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-bottom:hover:before, .tooltip-bottom:hover:after {
|
||||
.tooltip-bottom:hover:before, [data-tooltip].tooltip-bottom:hover:after,
|
||||
.tooltip-bottom:hover .tooltip {
|
||||
-webkit-transform: translateY(12px);
|
||||
-moz-transform: translateY(12px);
|
||||
transform: translateY(12px);
|
||||
}
|
||||
|
||||
/** Tooltips on the right */
|
||||
.tooltip-right:before, .tooltip-right:after {
|
||||
.tooltip-right:before, [data-tooltip].tooltip-right:after, .tooltip-right .tooltip {
|
||||
bottom: 50%;
|
||||
left: 100%;
|
||||
}
|
||||
@@ -156,7 +160,8 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
border-right-color: hsla(0, 0%, 20%, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-right:hover:before, .tooltip-right:hover:after {
|
||||
.tooltip-right:hover:before, [data-tooltip].tooltip-right:hover:after,
|
||||
.tooltip-right:hover .tooltip {
|
||||
-webkit-transform: translateX(12px);
|
||||
-moz-transform: translateX(12px);
|
||||
transform: translateX(12px);
|
||||
@@ -168,7 +173,16 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
}
|
||||
|
||||
/** Center content vertically for tooltips ont he left and right */
|
||||
.tooltip-left:after, .tooltip-right:after {
|
||||
[data-tooltip].tooltip-left:after, [data-tooltip].tooltip-right:after,
|
||||
.tooltip-left .tooltip, .tooltip-right .tooltip {
|
||||
margin-left: 0;
|
||||
margin-bottom: -16px;
|
||||
}
|
||||
|
||||
.tooltip ul, .tooltip ol {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.tooltip :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@
|
||||
<select id="{{ option_name }}-select" data-option-name="{{ option_name }}" {{ "disabled" if option.default == "random" }}>
|
||||
{% for key, val in option.special_range_names.items() %}
|
||||
{% if option.default == val %}
|
||||
<option value="{{ val }}" selected>{{ key }} ({{ val }})</option>
|
||||
<option value="{{ val }}" selected>{{ key|replace("_", " ")|title }} ({{ val }})</option>
|
||||
{% else %}
|
||||
<option value="{{ val }}">{{ key }} ({{ val }})</option>
|
||||
<option value="{{ val }}">{{ key|replace("_", " ")|title }} ({{ val }})</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<option value="custom" hidden>Custom</option>
|
||||
@@ -111,7 +111,7 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro ItemDict(option_name, option, world) %}
|
||||
{% macro ItemDict(option_name, option) %}
|
||||
{{ OptionTitle(option_name, option) }}
|
||||
<div class="option-container">
|
||||
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %}
|
||||
@@ -135,7 +135,7 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro LocationSet(option_name, option, world) %}
|
||||
{% macro LocationSet(option_name, option) %}
|
||||
{{ OptionTitle(option_name, option) }}
|
||||
<div class="option-container">
|
||||
{% for group_name in world.location_name_groups.keys()|sort %}
|
||||
@@ -158,7 +158,7 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro ItemSet(option_name, option, world) %}
|
||||
{% macro ItemSet(option_name, option) %}
|
||||
{{ OptionTitle(option_name, option) }}
|
||||
<div class="option-container">
|
||||
{% for group_name in world.item_name_groups.keys()|sort %}
|
||||
@@ -196,7 +196,18 @@
|
||||
{% macro OptionTitle(option_name, option) %}
|
||||
<label for="{{ option_name }}">
|
||||
{{ option.display_name|default(option_name) }}:
|
||||
<span class="interactive" data-tooltip="{% filter dedent %}{{(option.__doc__ | default("Please document me!"))|escape }}{% endfilter %}">(?)</span>
|
||||
<span
|
||||
class="interactive tooltip-container"
|
||||
{% if not (option.rich_text_doc | default(world.web.rich_text_options_doc, true)) %}
|
||||
data-tooltip="{{(option.__doc__ | default("Please document me!"))|replace('\n ', '\n')|escape|trim}}"
|
||||
{% endif %}>
|
||||
(?)
|
||||
{% if option.rich_text_doc | default(world.web.rich_text_options_doc, true) %}
|
||||
<div class="tooltip">
|
||||
{{ option.__doc__ | default("**Please document me!**") | rst_to_html | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</span>
|
||||
</label>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'pageWrapper.html' %}
|
||||
{% import 'playerOptions/macros.html' as inputs %}
|
||||
{% import 'playerOptions/macros.html' as inputs with context %}
|
||||
|
||||
{% block head %}
|
||||
<title>{{ world_name }} Options</title>
|
||||
@@ -94,16 +94,16 @@
|
||||
{{ inputs.FreeText(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.ItemDict) and option.verify_item_name %}
|
||||
{{ inputs.ItemDict(option_name, option, world) }}
|
||||
{{ inputs.ItemDict(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.OptionList) and option.valid_keys %}
|
||||
{{ inputs.OptionList(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.LocationSet) and option.verify_location_name %}
|
||||
{{ inputs.LocationSet(option_name, option, world) }}
|
||||
{{ inputs.LocationSet(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.ItemSet) and option.verify_item_name %}
|
||||
{{ inputs.ItemSet(option_name, option, world) }}
|
||||
{{ inputs.ItemSet(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.OptionSet) and option.valid_keys %}
|
||||
{{ inputs.OptionSet(option_name, option) }}
|
||||
@@ -134,16 +134,16 @@
|
||||
{{ inputs.FreeText(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.ItemDict) and option.verify_item_name %}
|
||||
{{ inputs.ItemDict(option_name, option, world) }}
|
||||
{{ inputs.ItemDict(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.OptionList) and option.valid_keys %}
|
||||
{{ inputs.OptionList(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.LocationSet) and option.verify_location_name %}
|
||||
{{ inputs.LocationSet(option_name, option, world) }}
|
||||
{{ inputs.LocationSet(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.ItemSet) and option.verify_item_name %}
|
||||
{{ inputs.ItemSet(option_name, option, world) }}
|
||||
{{ inputs.ItemSet(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.OptionSet) and option.valid_keys %}
|
||||
{{ inputs.OptionSet(option_name, option) }}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{% for id, name in option.name_lookup.items() %}
|
||||
{% if name != 'random' %}
|
||||
{% if option.default != 'random' %}
|
||||
{{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.get_option_name(option.default)|lower == name else None) }}
|
||||
{{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.default == id else None) }}
|
||||
{% else %}
|
||||
{{ RangeRow(option_name, option, option.get_option_name(id), name) }}
|
||||
{% endif %}
|
||||
@@ -41,7 +41,7 @@
|
||||
The following values have special meanings, and may fall outside the normal range.
|
||||
<ul>
|
||||
{% for name, value in option.special_range_names.items() %}
|
||||
<li>{{ value }}: {{ name }}</li>
|
||||
<li>{{ value }}: {{ name|replace("_", " ")|title }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
@@ -97,7 +97,7 @@
|
||||
{% for id, name in option.name_lookup.items() %}
|
||||
{% if name != 'random' %}
|
||||
{% if option.default != 'random' %}
|
||||
{{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.get_option_name(option.default)|lower == name else None) }}
|
||||
{{ RangeRow(option_name, option, option.get_option_name(id), name, False, name if option.default == id else None) }}
|
||||
{% else %}
|
||||
{{ RangeRow(option_name, option, option.get_option_name(id), name) }}
|
||||
{% endif %}
|
||||
|
||||
@@ -152,7 +152,7 @@ def get_payload(ctx: ZeldaContext):
|
||||
|
||||
|
||||
def reconcile_shops(ctx: ZeldaContext):
|
||||
checked_location_names = [ctx.location_names.lookup_in_slot(location) for location in ctx.checked_locations]
|
||||
checked_location_names = [ctx.location_names.lookup_in_game(location) for location in ctx.checked_locations]
|
||||
shops = [location for location in checked_location_names if "Shop" in location]
|
||||
left_slots = [shop for shop in shops if "Left" in shop]
|
||||
middle_slots = [shop for shop in shops if "Middle" in shop]
|
||||
@@ -190,7 +190,7 @@ async def parse_locations(locations_array, ctx: ZeldaContext, force: bool, zone=
|
||||
locations_checked = []
|
||||
location = None
|
||||
for location in ctx.missing_locations:
|
||||
location_name = ctx.location_names.lookup_in_slot(location)
|
||||
location_name = ctx.location_names.lookup_in_game(location)
|
||||
|
||||
if location_name in Locations.overworld_locations and zone == "overworld":
|
||||
status = locations_array[Locations.major_location_offsets[location_name]]
|
||||
|
||||
@@ -68,21 +68,21 @@ requires:
|
||||
|
||||
{%- elif option.options -%}
|
||||
{%- for suboption_option_id, sub_option_name in option.name_lookup.items() %}
|
||||
{{ sub_option_name }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %}
|
||||
{{ yaml_dump(sub_option_name) }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %}
|
||||
{%- endfor -%}
|
||||
|
||||
{%- if option.name_lookup[option.default] not in option.options %}
|
||||
{{ option.default }}: 50
|
||||
{{ yaml_dump(option.default) }}: 50
|
||||
{%- endif -%}
|
||||
|
||||
{%- elif option.default is string %}
|
||||
{{ option.default }}: 50
|
||||
{{ yaml_dump(option.default) }}: 50
|
||||
|
||||
{%- elif option.default is iterable and option.default is not mapping %}
|
||||
{{ option.default | list }}
|
||||
|
||||
{%- else %}
|
||||
{{ yaml_dump(option.default) | trim | indent(4, first=false) }}
|
||||
{{ yaml_dump(option.default) | indent(4, first=false) }}
|
||||
{%- endif -%}
|
||||
{{ "\n" }}
|
||||
{%- endfor %}
|
||||
|
||||
@@ -87,9 +87,6 @@
|
||||
# Lingo
|
||||
/worlds/lingo/ @hatkirby
|
||||
|
||||
# Links Awakening DX
|
||||
/worlds/ladx/ @zig-for
|
||||
|
||||
# Lufia II Ancient Cave
|
||||
/worlds/lufia2ac/ @el-u
|
||||
/worlds/lufia2ac/docs/ @wordfcuk @el-u
|
||||
@@ -218,6 +215,8 @@
|
||||
# Final Fantasy (1)
|
||||
# /worlds/ff1/
|
||||
|
||||
# Links Awakening DX
|
||||
# /worlds/ladx/
|
||||
|
||||
## Disabled Unmaintained Worlds
|
||||
|
||||
|
||||
@@ -85,6 +85,50 @@ class ExampleWorld(World):
|
||||
options: ExampleGameOptions
|
||||
```
|
||||
|
||||
### Option Documentation
|
||||
|
||||
Options' [docstrings] are used as their user-facing documentation. They're displayed on the WebHost setup page when a
|
||||
user hovers over the yellow "(?)" icon, and included in the YAML templates generated for each game.
|
||||
|
||||
[docstrings]: /docs/world%20api.md#docstrings
|
||||
|
||||
The WebHost can display Option documentation either as plain text with all whitespace preserved (other than the base
|
||||
indentation), or as HTML generated from the standard Python [reStructuredText] format. Although plain text is the
|
||||
default for backwards compatibility, world authors are encouraged to write their Option documentation as
|
||||
reStructuredText and enable rich text rendering by setting `World.rich_text_options_doc = True`.
|
||||
|
||||
[reStructuredText]: https://docutils.sourceforge.io/rst.html
|
||||
|
||||
```python
|
||||
from worlds.AutoWorld import WebWorld
|
||||
|
||||
|
||||
class ExampleWebWorld(WebWorld):
|
||||
# Render all this world's options as rich text.
|
||||
rich_text_options_doc = True
|
||||
```
|
||||
|
||||
You can set a single option to use rich or plain text by setting
|
||||
`Option.rich_text_doc`.
|
||||
|
||||
```python
|
||||
from Options import Toggle, Range, Choice, PerGameCommonOptions
|
||||
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""Sets overall game difficulty.
|
||||
|
||||
- **Easy:** All enemies die in one hit.
|
||||
- **Normal:** Enemies and the player both have normal health bars.
|
||||
- **Hard:** The player dies in one hit."""
|
||||
display_name = "Difficulty"
|
||||
rich_text_doc = True
|
||||
option_easy = 0
|
||||
option_normal = 1
|
||||
option_hard = 2
|
||||
default = 1
|
||||
```
|
||||
|
||||
### Option Groups
|
||||
Options may be categorized into groups for display on the WebHost. Option groups are displayed in the order specified
|
||||
by your world on the player-options and weighted-options pages. In the generated template files, there will be a comment
|
||||
|
||||
@@ -56,6 +56,12 @@ webhost:
|
||||
|
||||
* `options_page` can be changed to a link instead of an AP-generated options page.
|
||||
|
||||
* `rich_text_options_doc` controls whether [Option documentation] uses plain text (`False`) or rich text (`True`). It
|
||||
defaults to `False`, but world authors are encouraged to set it to `True` for nicer-looking documentation that looks
|
||||
good on both the WebHost and the YAML template.
|
||||
|
||||
[Option documentation]: /docs/options%20api.md#option-documentation
|
||||
|
||||
* `theme` to be used for your game-specific AP pages. Available themes:
|
||||
|
||||
| dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone |
|
||||
@@ -297,6 +303,31 @@ generation (entrance randomization).
|
||||
An access rule is a function that returns `True` or `False` for a `Location` or `Entrance` based on the current `state`
|
||||
(items that have been collected).
|
||||
|
||||
The two possible ways to make a [CollectionRule](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/generic/Rules.py#L10) are:
|
||||
- `def rule(state: CollectionState) -> bool:`
|
||||
- `lambda state: ... boolean expression ...`
|
||||
|
||||
An access rule can be assigned through `set_rule(location, rule)`.
|
||||
|
||||
Access rules usually check for one of two things.
|
||||
- Items that have been collected (e.g. `state.has("Sword", player)`)
|
||||
- Locations, Regions or Entrances that have been reached (e.g. `state.can_reach_region("Boss Room")`)
|
||||
|
||||
Keep in mind that entrances and locations implicitly check for the accessibility of their parent region, so you do not need to check explicitly for it.
|
||||
|
||||
#### An important note on Entrance access rules:
|
||||
When using `state.can_reach` within an entrance access condition, you must also use `multiworld.register_indirect_condition`.
|
||||
|
||||
For efficiency reasons, every time reachable regions are searched, every entrance is only checked once in a somewhat non-deterministic order.
|
||||
This is fine when checking for items using `state.has`, because items do not change during a region sweep.
|
||||
However, `state.can_reach` checks for the very same thing we are updating: Regions.
|
||||
This can lead to non-deterministic behavior and, in the worst case, even generation failures.
|
||||
Even doing `state.can_reach_location` or `state.can_reach_entrance` is problematic, as these functions call `state.can_reach_region` on the respective parent region.
|
||||
|
||||
**Therefore, it is considered unsafe to perform `state.can_reach` from within an access condition for an entrance**, unless you are checking for something that sits in the source region of the entrance.
|
||||
You can use `multiworld.register_indirect_condition(region, entrance)` to explicitly tell the generator that, when a given region becomes accessible, it is necessary to re-check a specific entrance.
|
||||
You **must** use `multiworld.register_indirect_condition` if you perform this kind of `can_reach` from an entrance access rule, unless you have a **very** good technical understanding of the relevant code and can reason why it will never lead to problems in your case.
|
||||
|
||||
### Item Rules
|
||||
|
||||
An item rule is a function that returns `True` or `False` for a `Location` based on a single item. It can be used to
|
||||
@@ -623,7 +654,7 @@ def set_rules(self) -> None:
|
||||
|
||||
Custom methods can be defined for your logic rules. The access rule that ultimately gets assigned to the Location or
|
||||
Entrance should be
|
||||
a [`CollectionRule`](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/generic/Rules.py#L9).
|
||||
a [`CollectionRule`](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/generic/Rules.py#L10).
|
||||
Typically, this is done by defining a lambda expression on demand at the relevant bit, typically calling other
|
||||
functions, but this can also be achieved by defining a method with the appropriate format and assigning it directly.
|
||||
For an example, see [The Messenger](/worlds/messenger/rules.py).
|
||||
|
||||
@@ -26,6 +26,7 @@ def _generate_local_inner(games: Iterable[str],
|
||||
with TemporaryDirectory() as players_dir:
|
||||
with TemporaryDirectory() as output_dir:
|
||||
import Generate
|
||||
import Main
|
||||
|
||||
for n, game in enumerate(games, 1):
|
||||
player_path = Path(players_dir) / f"{n}.yaml"
|
||||
@@ -42,7 +43,7 @@ def _generate_local_inner(games: Iterable[str],
|
||||
sys.argv = [sys.argv[0], "--seed", str(hash(tuple(games))),
|
||||
"--player_files_path", players_dir,
|
||||
"--outputpath", output_dir]
|
||||
Generate.main()
|
||||
Main.main(*Generate.main())
|
||||
output_files = list(Path(output_dir).glob('*.zip'))
|
||||
assert len(output_files) == 1
|
||||
final_file = dest / output_files[0].name
|
||||
|
||||
@@ -9,6 +9,7 @@ from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import Generate
|
||||
import Main
|
||||
|
||||
|
||||
class TestGenerateMain(unittest.TestCase):
|
||||
@@ -58,7 +59,7 @@ class TestGenerateMain(unittest.TestCase):
|
||||
'--player_files_path', str(self.abs_input_dir),
|
||||
'--outputpath', self.output_tempdir.name]
|
||||
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}')
|
||||
Generate.main()
|
||||
Main.main(*Generate.main())
|
||||
|
||||
self.assertOutput(self.output_tempdir.name)
|
||||
|
||||
@@ -67,7 +68,7 @@ class TestGenerateMain(unittest.TestCase):
|
||||
'--player_files_path', str(self.rel_input_dir),
|
||||
'--outputpath', self.output_tempdir.name]
|
||||
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}')
|
||||
Generate.main()
|
||||
Main.main(*Generate.main())
|
||||
|
||||
self.assertOutput(self.output_tempdir.name)
|
||||
|
||||
@@ -86,7 +87,7 @@ class TestGenerateMain(unittest.TestCase):
|
||||
sys.argv = [sys.argv[0], '--seed', '0',
|
||||
'--outputpath', self.output_tempdir.name]
|
||||
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}, player_files_path={self.yaml_input_dir}')
|
||||
Generate.main()
|
||||
Main.main(*Generate.main())
|
||||
finally:
|
||||
user_path.cached_path = user_path_backup
|
||||
|
||||
|
||||
@@ -223,6 +223,21 @@ class WebWorld(metaclass=WebWorldRegister):
|
||||
option_groups: ClassVar[List[OptionGroup]] = []
|
||||
"""Ordered list of option groupings. Any options not set in a group will be placed in a pre-built "Game Options"."""
|
||||
|
||||
rich_text_options_doc = False
|
||||
"""Whether the WebHost should render Options' docstrings as rich text.
|
||||
|
||||
If this is True, Options' docstrings are interpreted as reStructuredText_,
|
||||
the standard Python markup format. In the WebHost, they're rendered to HTML
|
||||
so that lists, emphasis, and other rich text features are displayed
|
||||
properly.
|
||||
|
||||
If this is False, the docstrings are instead interpreted as plain text, and
|
||||
displayed as-is on the WebHost with whitespace preserved. For backwards
|
||||
compatibility, this is the default.
|
||||
|
||||
.. _reStructuredText: https://docutils.sourceforge.io/rst.html
|
||||
"""
|
||||
|
||||
location_descriptions: Dict[str, str] = {}
|
||||
"""An optional map from location names (or location group names) to brief descriptions for users."""
|
||||
|
||||
|
||||
@@ -128,3 +128,4 @@ from .AutoWorld import AutoWorldRegister
|
||||
network_data_package: DataPackage = {
|
||||
"games": {world_name: world.get_data_package_data() for world_name, world in AutoWorldRegister.world_types.items()},
|
||||
}
|
||||
|
||||
|
||||
@@ -339,7 +339,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
|
||||
def new_check(location_id):
|
||||
new_locations.append(location_id)
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
location = ctx.location_names.lookup_in_game(location_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ' +
|
||||
f'({len(ctx.checked_locations) + 1 if ctx.checked_locations else len(ctx.locations_checked)}/' +
|
||||
@@ -552,7 +552,7 @@ class ALTTPSNIClient(SNIClient):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
|
||||
@@ -220,26 +220,7 @@ def get_invalid_bunny_revival_dungeons():
|
||||
yield 'Sanctuary'
|
||||
|
||||
|
||||
def no_logic_rules(world, player):
|
||||
"""
|
||||
Add OWG transitions to no logic player's world
|
||||
"""
|
||||
create_no_logic_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'))
|
||||
create_no_logic_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player))
|
||||
|
||||
# Glitched speed drops.
|
||||
create_no_logic_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'))
|
||||
|
||||
# Mirror clip spots.
|
||||
if world.mode[player] != 'inverted':
|
||||
create_no_logic_connections(player, world, get_mirror_clip_spots_dw())
|
||||
create_no_logic_connections(player, world, get_mirror_offset_spots_dw())
|
||||
else:
|
||||
create_no_logic_connections(player, world, get_mirror_offset_spots_lw(player))
|
||||
|
||||
|
||||
def overworld_glitch_connections(world, player):
|
||||
|
||||
# Boots-accessible locations.
|
||||
create_owg_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'))
|
||||
create_owg_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player))
|
||||
|
||||
@@ -10,7 +10,7 @@ from . import OverworldGlitchRules
|
||||
from .Bosses import GanonDefeatRule
|
||||
from .Items import item_factory, item_name_groups, item_table, progression_items
|
||||
from .Options import small_key_shuffle
|
||||
from .OverworldGlitchRules import no_logic_rules, overworld_glitches_rules
|
||||
from .OverworldGlitchRules import overworld_glitches_rules
|
||||
from .Regions import LTTPRegionType, location_table
|
||||
from .StateHelpers import (can_extend_magic, can_kill_most_things,
|
||||
can_lift_heavy_rocks, can_lift_rocks,
|
||||
@@ -33,7 +33,6 @@ def set_rules(world):
|
||||
'WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
|
||||
|
||||
if world.players == 1:
|
||||
no_logic_rules(world, player)
|
||||
for exit in world.get_region('Menu', player).exits:
|
||||
exit.hide_path = True
|
||||
return
|
||||
|
||||
@@ -146,7 +146,7 @@ class Castlevania64Client(BizHawkClient):
|
||||
text_color = bytearray([0xA2, 0x0B])
|
||||
else:
|
||||
text_color = bytearray([0xA2, 0x02])
|
||||
received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_slot(next_item.item)}\n"
|
||||
received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_game(next_item.item)}\n"
|
||||
f"from {ctx.player_names[next_item.player]}", 96)
|
||||
await bizhawk.guarded_write(ctx.bizhawk_ctx,
|
||||
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
|
||||
|
||||
@@ -86,7 +86,7 @@ class DKC3SNIClient(SNIClient):
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||
@@ -99,7 +99,7 @@ class DKC3SNIClient(SNIClient):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ async def game_watcher(ctx: FactorioContext):
|
||||
if ctx.locations_checked != research_data:
|
||||
bridge_logger.debug(
|
||||
f"New researches done: "
|
||||
f"{[ctx.location_names.lookup_in_slot(rid) for rid in research_data - ctx.locations_checked]}")
|
||||
f"{[ctx.location_names.lookup_in_game(rid) for rid in research_data - ctx.locations_checked]}")
|
||||
ctx.locations_checked = research_data
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
||||
death_link_tick = data.get("death_link_tick", 0)
|
||||
@@ -360,7 +360,7 @@ async def factorio_server_watcher(ctx: FactorioContext):
|
||||
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
|
||||
item_id = transfer_item.item
|
||||
player_name = ctx.player_names[transfer_item.player]
|
||||
item_name = ctx.item_names.lookup_in_slot(item_id)
|
||||
item_name = ctx.item_names.lookup_in_game(item_id)
|
||||
factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.")
|
||||
commands[ctx.send_index] = f"/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}"
|
||||
ctx.send_index += 1
|
||||
|
||||
@@ -330,7 +330,7 @@ class KDL3SNIClient(SNIClient):
|
||||
item = ctx.items_received[recv_amount]
|
||||
recv_amount += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_amount, len(ctx.items_received)))
|
||||
|
||||
@@ -415,7 +415,7 @@ class KDL3SNIClient(SNIClient):
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/'
|
||||
f'{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
|
||||
@@ -7,7 +7,7 @@ import typing
|
||||
import bsdiff4
|
||||
|
||||
import settings
|
||||
from BaseClasses import Entrance, Item, ItemClassification, Location, Tutorial
|
||||
from BaseClasses import Entrance, Item, ItemClassification, Location, Tutorial, MultiWorld
|
||||
from Fill import fill_restrictive
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .Common import *
|
||||
@@ -24,7 +24,7 @@ from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup
|
||||
from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion,
|
||||
create_regions_from_ladxr, get_locations_to_id)
|
||||
from .Options import DungeonItemShuffle, links_awakening_options, ShuffleInstruments
|
||||
from .Rom import LADXDeltaPatch
|
||||
from .Rom import LADXDeltaPatch, get_base_rom_path
|
||||
|
||||
DEVELOPER_MODE = False
|
||||
|
||||
@@ -433,6 +433,12 @@ class LinksAwakeningWorld(World):
|
||||
|
||||
return "TRADING_ITEM_LETTER"
|
||||
|
||||
@classmethod
|
||||
def stage_assert_generate(cls, multiworld: MultiWorld):
|
||||
rom_file = get_base_rom_path()
|
||||
if not os.path.exists(rom_file):
|
||||
raise FileNotFoundError(rom_file)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
# copy items back to locations
|
||||
for r in self.multiworld.get_regions(self.player):
|
||||
|
||||
@@ -16,6 +16,7 @@ from .regions import create_regions
|
||||
|
||||
class LingoWebWorld(WebWorld):
|
||||
option_groups = lingo_option_groups
|
||||
rich_text_options_doc = True
|
||||
theme = "grass"
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
|
||||
@@ -9,8 +9,12 @@ from .items import TRAP_ITEMS
|
||||
|
||||
class ShuffleDoors(Choice):
|
||||
"""If on, opening doors will require their respective "keys".
|
||||
In "simple", doors are sorted into logical groups, which are all opened by receiving an item.
|
||||
In "complex", the items are much more granular, and will usually only open a single door each."""
|
||||
|
||||
- **Simple:** Doors are sorted into logical groups, which are all opened by
|
||||
receiving an item.
|
||||
- **Complex:** The items are much more granular, and will usually only open
|
||||
a single door each.
|
||||
"""
|
||||
display_name = "Shuffle Doors"
|
||||
option_none = 0
|
||||
option_simple = 1
|
||||
@@ -19,24 +23,37 @@ class ShuffleDoors(Choice):
|
||||
|
||||
class ProgressiveOrangeTower(DefaultOnToggle):
|
||||
"""When "Shuffle Doors" is on, this setting governs the manner in which the Orange Tower floors open up.
|
||||
If off, there is an item for each floor of the tower, and each floor's item is the only one needed to access that floor.
|
||||
If on, there are six progressive items, which open up the tower from the bottom floor upward.
|
||||
|
||||
- **Off:** There is an item for each floor of the tower, and each floor's
|
||||
item is the only one needed to access that floor.
|
||||
- **On:** There are six progressive items, which open up the tower from the
|
||||
bottom floor upward.
|
||||
"""
|
||||
display_name = "Progressive Orange Tower"
|
||||
|
||||
|
||||
class ProgressiveColorful(DefaultOnToggle):
|
||||
"""When "Shuffle Doors" is on "complex", this setting governs the manner in which The Colorful opens up.
|
||||
If off, there is an item for each room of The Colorful, meaning that random rooms in the middle of the sequence can open up without giving you access to them.
|
||||
If on, there are ten progressive items, which open up the sequence from White forward."""
|
||||
|
||||
- **Off:** There is an item for each room of The Colorful, meaning that
|
||||
random rooms in the middle of the sequence can open up without giving you
|
||||
access to them.
|
||||
- **On:** There are ten progressive items, which open up the sequence from
|
||||
White forward.
|
||||
"""
|
||||
display_name = "Progressive Colorful"
|
||||
|
||||
|
||||
class LocationChecks(Choice):
|
||||
"""Determines what locations are available.
|
||||
On "normal", there will be a location check for each panel set that would ordinarily open a door, as well as for achievement panels and a small handful of other panels.
|
||||
On "reduced", many of the locations that are associated with opening doors are removed.
|
||||
On "insanity", every individual panel in the game is a location check."""
|
||||
|
||||
- **Normal:** There will be a location check for each panel set that would
|
||||
ordinarily open a door, as well as for achievement panels and a small
|
||||
handful of other panels.
|
||||
- **Reduced:** Many of the locations that are associated with opening doors
|
||||
are removed.
|
||||
- **Insanity:** Every individual panel in the game is a location check.
|
||||
"""
|
||||
display_name = "Location Checks"
|
||||
option_normal = 0
|
||||
option_reduced = 1
|
||||
@@ -44,16 +61,20 @@ class LocationChecks(Choice):
|
||||
|
||||
|
||||
class ShuffleColors(DefaultOnToggle):
|
||||
"""
|
||||
If on, an item is added to the pool for every puzzle color (besides White).
|
||||
You will need to unlock the requisite colors in order to be able to solve puzzles of that color.
|
||||
"""If on, an item is added to the pool for every puzzle color (besides White).
|
||||
|
||||
You will need to unlock the requisite colors in order to be able to solve
|
||||
puzzles of that color.
|
||||
"""
|
||||
display_name = "Shuffle Colors"
|
||||
|
||||
|
||||
class ShufflePanels(Choice):
|
||||
"""If on, the puzzles on each panel are randomized.
|
||||
On "rearrange", the puzzles are the same as the ones in the base game, but are placed in different areas."""
|
||||
|
||||
On "rearrange", the puzzles are the same as the ones in the base game, but
|
||||
are placed in different areas.
|
||||
"""
|
||||
display_name = "Shuffle Panels"
|
||||
option_none = 0
|
||||
option_rearrange = 1
|
||||
@@ -66,22 +87,26 @@ class ShufflePaintings(Toggle):
|
||||
|
||||
class EnablePilgrimage(Toggle):
|
||||
"""Determines how the pilgrimage works.
|
||||
If on, you are required to complete a pilgrimage in order to access the Pilgrim Antechamber.
|
||||
If off, the pilgrimage will be deactivated, and the sun painting will be added to the pool, even if door shuffle is off."""
|
||||
|
||||
- **On:** You are required to complete a pilgrimage in order to access the
|
||||
Pilgrim Antechamber.
|
||||
- **Off:** The pilgrimage will be deactivated, and the sun painting will be
|
||||
added to the pool, even if door shuffle is off.
|
||||
"""
|
||||
display_name = "Enable Pilgrimage"
|
||||
|
||||
|
||||
class PilgrimageAllowsRoofAccess(DefaultOnToggle):
|
||||
"""
|
||||
If on, you may use the Crossroads roof access during a pilgrimage (and you may be expected to do so).
|
||||
"""If on, you may use the Crossroads roof access during a pilgrimage (and you may be expected to do so).
|
||||
|
||||
Otherwise, pilgrimage will be deactivated when going up the stairs.
|
||||
"""
|
||||
display_name = "Allow Roof Access for Pilgrimage"
|
||||
|
||||
|
||||
class PilgrimageAllowsPaintings(DefaultOnToggle):
|
||||
"""
|
||||
If on, you may use paintings during a pilgrimage (and you may be expected to do so).
|
||||
"""If on, you may use paintings during a pilgrimage (and you may be expected to do so).
|
||||
|
||||
Otherwise, pilgrimage will be deactivated when going through a painting.
|
||||
"""
|
||||
display_name = "Allow Paintings for Pilgrimage"
|
||||
@@ -89,11 +114,17 @@ class PilgrimageAllowsPaintings(DefaultOnToggle):
|
||||
|
||||
class SunwarpAccess(Choice):
|
||||
"""Determines how access to sunwarps works.
|
||||
On "normal", all sunwarps are enabled from the start.
|
||||
On "disabled", all sunwarps are disabled. Pilgrimage must be disabled when this is used.
|
||||
On "unlock", sunwarps start off disabled, and all six activate once you receive an item.
|
||||
On "individual", sunwarps start off disabled, and each has a corresponding item that unlocks it.
|
||||
On "progressive", sunwarps start off disabled, and they unlock in order using a progressive item."""
|
||||
|
||||
- **Normal:** All sunwarps are enabled from the start.
|
||||
- **Disabled:** All sunwarps are disabled. Pilgrimage must be disabled when
|
||||
this is used.
|
||||
- **Unlock:** Sunwarps start off disabled, and all six activate once you
|
||||
receive an item.
|
||||
- **Individual:** Sunwarps start off disabled, and each has a corresponding
|
||||
item that unlocks it.
|
||||
- **Progressive:** Sunwarps start off disabled, and they unlock in order
|
||||
using a progressive item.
|
||||
"""
|
||||
display_name = "Sunwarp Access"
|
||||
option_normal = 0
|
||||
option_disabled = 1
|
||||
@@ -109,10 +140,16 @@ class ShuffleSunwarps(Toggle):
|
||||
|
||||
class VictoryCondition(Choice):
|
||||
"""Change the victory condition.
|
||||
On "the_end", the goal is to solve THE END at the top of the tower.
|
||||
On "the_master", the goal is to solve THE MASTER at the top of the tower, after getting the number of achievements specified in the Mastery Achievements option.
|
||||
On "level_2", the goal is to solve LEVEL 2 in the second room, after solving the number of panels specified in the Level 2 Requirement option.
|
||||
On "pilgrimage", the goal is to solve PILGRIM in the Pilgrim Antechamber, typically after performing a Pilgrimage."""
|
||||
|
||||
- **The End:** the goal is to solve THE END at the top of the tower.
|
||||
- **The Master:** The goal is to solve THE MASTER at the top of the tower,
|
||||
after getting the number of achievements specified in the Mastery
|
||||
Achievements option.
|
||||
- **Level 2:** The goal is to solve LEVEL 2 in the second room, after
|
||||
solving the number of panels specified in the Level 2 Requirement option.
|
||||
- **Pilgrimage:** The goal is to solve PILGRIM in the Pilgrim Antechamber,
|
||||
typically after performing a Pilgrimage.
|
||||
"""
|
||||
display_name = "Victory Condition"
|
||||
option_the_end = 0
|
||||
option_the_master = 1
|
||||
@@ -122,9 +159,12 @@ class VictoryCondition(Choice):
|
||||
|
||||
class MasteryAchievements(Range):
|
||||
"""The number of achievements required to unlock THE MASTER.
|
||||
In the base game, 21 achievements are needed.
|
||||
If you include The Scientific and The Unchallenged, which are in the base game but are not counted for mastery, 23 would be required.
|
||||
If you include the custom achievement (The Wanderer), 24 would be required.
|
||||
|
||||
- In the base game, 21 achievements are needed.
|
||||
- If you include The Scientific and The Unchallenged, which are in the base
|
||||
game but are not counted for mastery, 23 would be required.
|
||||
- If you include the custom achievement (The Wanderer), 24 would be
|
||||
required.
|
||||
"""
|
||||
display_name = "Mastery Achievements"
|
||||
range_start = 1
|
||||
@@ -134,9 +174,10 @@ class MasteryAchievements(Range):
|
||||
|
||||
class Level2Requirement(Range):
|
||||
"""The number of panel solves required to unlock LEVEL 2.
|
||||
In the base game, 223 are needed.
|
||||
Note that this count includes ANOTHER TRY.
|
||||
When set to 1, the panel hunt is disabled, and you can access LEVEL 2 for free.
|
||||
|
||||
In the base game, 223 are needed. Note that this count includes ANOTHER TRY.
|
||||
When set to 1, the panel hunt is disabled, and you can access LEVEL 2 for
|
||||
free.
|
||||
"""
|
||||
display_name = "Level 2 Requirement"
|
||||
range_start = 1
|
||||
@@ -145,9 +186,10 @@ class Level2Requirement(Range):
|
||||
|
||||
|
||||
class EarlyColorHallways(Toggle):
|
||||
"""
|
||||
When on, a painting warp to the color hallways area will appear in the starting room.
|
||||
This lets you avoid being trapped in the starting room for long periods of time when door shuffle is on.
|
||||
"""When on, a painting warp to the color hallways area will appear in the starting room.
|
||||
|
||||
This lets you avoid being trapped in the starting room for long periods of
|
||||
time when door shuffle is on.
|
||||
"""
|
||||
display_name = "Early Color Hallways"
|
||||
|
||||
@@ -161,8 +203,8 @@ class TrapPercentage(Range):
|
||||
|
||||
|
||||
class TrapWeights(OptionDict):
|
||||
"""
|
||||
Specify the distribution of traps that should be placed into the pool.
|
||||
"""Specify the distribution of traps that should be placed into the pool.
|
||||
|
||||
If you don't want a specific type of trap, set the weight to zero.
|
||||
"""
|
||||
display_name = "Trap Weights"
|
||||
|
||||
@@ -147,7 +147,7 @@ class L2ACSNIClient(SNIClient):
|
||||
snes_items_received += 1
|
||||
|
||||
snes_logger.info("Received %s from %s (%s) (%d/%d in list)" % (
|
||||
ctx.item_names.lookup_in_slot(item.item),
|
||||
ctx.item_names.lookup_in_game(item.item),
|
||||
ctx.player_names[item.player],
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player),
|
||||
snes_items_received, len(ctx.items_received)))
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Pokemon Emerald
|
||||
|
||||
Version 2.0.0
|
||||
@@ -661,6 +661,9 @@ class PokemonRedBlueWorld(World):
|
||||
"dark_rock_tunnel_logic": self.multiworld.dark_rock_tunnel_logic[self.player].value,
|
||||
"split_card_key": self.multiworld.split_card_key[self.player].value,
|
||||
"all_elevators_locked": self.multiworld.all_elevators_locked[self.player].value,
|
||||
"require_pokedex": self.multiworld.require_pokedex[self.player].value,
|
||||
"area_1_to_1_mapping": self.multiworld.area_1_to_1_mapping[self.player].value,
|
||||
"blind_trainers": self.multiworld.blind_trainers[self.player].value,
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ As we are using BizHawk, this guide is only applicable to Windows and Linux syst
|
||||
|
||||
## Optional Software
|
||||
|
||||
- [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/j-imbo/pkmnrb_jim/releases/latest), for use with [PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
- [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/coveleski/rb_tracker/releases/latest), for use with [PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
|
||||
|
||||
## Configuring BizHawk
|
||||
@@ -109,7 +109,7 @@ server uses password, type in the bottom textfield `/connect <address>:<port> [p
|
||||
|
||||
Pokémon Red and Blue has a fully functional map tracker that supports auto-tracking.
|
||||
|
||||
1. Download [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/j-imbo/pkmnrb_jim/releases/latest) and [PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
1. Download [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/coveleski/rb_tracker/releases/latest) and [PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
2. Open PopTracker, and load the Pokémon Red and Blue pack.
|
||||
3. Click on the "AP" symbol at the top.
|
||||
4. Enter the AP address, slot name and password.
|
||||
|
||||
@@ -17,7 +17,7 @@ Al usar BizHawk, esta guía solo es aplicable en los sistemas de Windows y Linux
|
||||
|
||||
## Software Opcional
|
||||
|
||||
- [Tracker de mapa para Pokémon Red and Blue Archipelago](https://github.com/j-imbo/pkmnrb_jim/releases/latest), para usar con [PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
- [Tracker de mapa para Pokémon Red and Blue Archipelago](https://github.com/coveleski/rb_tracker/releases/latest), para usar con [PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
|
||||
|
||||
## Configurando BizHawk
|
||||
@@ -101,7 +101,7 @@ Ahora ya estás listo para tu aventura en Kanto.
|
||||
|
||||
Pokémon Red and Blue tiene un mapa completamente funcional que soporta seguimiento automático.
|
||||
|
||||
1. Descarga el [Tracker de mapa para Pokémon Red and Blue Archipelago](https://github.com/j-imbo/pkmnrb_jim/releases/latest) y [PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
1. Descarga el [Tracker de mapa para Pokémon Red and Blue Archipelago](https://github.com/coveleski/rb_tracker/releases/latest) y [PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
2. Abre PopTracker, y carga el pack de Pokémon Red and Blue.
|
||||
3. Haz clic en el símbolo "AP" en la parte superior.
|
||||
4. Ingresa la dirección de AP, nombre del slot y contraseña (si es que hay).
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
7. Install protontricks, on the Steam Deck this can be done via the Discover store, on other distros instructions vary, [see its github page](https://github.com/Matoking/protontricks).
|
||||
|
||||
8. Download the [.NET 7 Desktop Runtime for x64 Windows](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-7.0.17-windows-x64-installer}. If this link does not work, the download can be found on [this page](https://dotnet.microsoft.com/en-us/download/dotnet/7.0).
|
||||
8. Download the [.NET 7 Desktop Runtime for x64 Windows](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-7.0.17-windows-x64-installer). If this link does not work, the download can be found on [this page](https://dotnet.microsoft.com/en-us/download/dotnet/7.0).
|
||||
|
||||
9. Right click the .NET 7 Desktop Runtime exe, and assuming protontricks was installed correctly, the option to "Open with Protontricks Launcher" should be available. Click that, and in the popup window that opens, select SAModManager.exe. Follow the prompts after this to install the .NET 7 Desktop Runtime for SAModManager. Once it is done, you should be able to successfully launch SAModManager to steam.
|
||||
|
||||
|
||||
@@ -243,10 +243,10 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
self.formatted_print(f" [u]{faction.name}[/u] ")
|
||||
|
||||
for item_id in categorized_items[faction]:
|
||||
item_name = self.ctx.item_names.lookup_in_slot(item_id)
|
||||
item_name = self.ctx.item_names.lookup_in_game(item_id)
|
||||
received_child_items = items_received_set.intersection(parent_to_child.get(item_id, []))
|
||||
matching_children = [child for child in received_child_items
|
||||
if item_matches_filter(self.ctx.item_names.lookup_in_slot(child))]
|
||||
if item_matches_filter(self.ctx.item_names.lookup_in_game(child))]
|
||||
received_items_of_this_type = items_received.get(item_id, [])
|
||||
item_is_match = item_matches_filter(item_name)
|
||||
if item_is_match or len(matching_children) > 0:
|
||||
@@ -1164,7 +1164,7 @@ def request_unfinished_missions(ctx: SC2Context) -> None:
|
||||
objectives = set(ctx.locations_for_mission(mission))
|
||||
if objectives:
|
||||
remaining_objectives = objectives.difference(ctx.checked_locations)
|
||||
unfinished_locations[mission] = [ctx.location_names.lookup_in_slot(location_id) for location_id in remaining_objectives]
|
||||
unfinished_locations[mission] = [ctx.location_names.lookup_in_game(location_id) for location_id in remaining_objectives]
|
||||
else:
|
||||
unfinished_locations[mission] = []
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ class SC2Manager(GameManager):
|
||||
for loc in self.ctx.locations_for_mission(mission_name):
|
||||
if loc in self.ctx.missing_locations:
|
||||
count += 1
|
||||
locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names.lookup_in_slot(loc))
|
||||
locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names.lookup_in_game(loc))
|
||||
|
||||
plando_locations = []
|
||||
for plando_loc in self.ctx.plando_locations:
|
||||
|
||||
@@ -123,7 +123,7 @@ class SMSNIClient(SNIClient):
|
||||
location_id = locations_start_id + item_index
|
||||
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
location = ctx.location_names.lookup_in_game(location_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||
@@ -151,7 +151,7 @@ class SMSNIClient(SNIClient):
|
||||
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
|
||||
bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
|
||||
|
||||
|
||||
@@ -448,7 +448,7 @@ class SMWSNIClient(SNIClient):
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||
@@ -501,14 +501,14 @@ class SMWSNIClient(SNIClient):
|
||||
recv_index += 1
|
||||
sending_game = ctx.slot_info[item.player].game
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
if self.should_show_message(ctx, item):
|
||||
if item.item != 0xBC0012 and item.item not in trap_rom_data:
|
||||
# Don't send messages for Boss Tokens
|
||||
item_name = ctx.item_names.lookup_in_slot(item.item)
|
||||
item_name = ctx.item_names.lookup_in_game(item.item)
|
||||
player_name = ctx.player_names[item.player]
|
||||
|
||||
receive_message = generate_received_text(item_name, player_name)
|
||||
@@ -516,7 +516,7 @@ class SMWSNIClient(SNIClient):
|
||||
|
||||
snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF]))
|
||||
if item.item in trap_rom_data:
|
||||
item_name = ctx.item_names.lookup_in_slot(item.item)
|
||||
item_name = ctx.item_names.lookup_in_game(item.item)
|
||||
player_name = ctx.player_names[item.player]
|
||||
|
||||
receive_message = generate_received_text(item_name, player_name)
|
||||
@@ -597,7 +597,7 @@ class SMWSNIClient(SNIClient):
|
||||
for loc_id in ctx.checked_locations:
|
||||
if loc_id not in ctx.locations_checked:
|
||||
ctx.locations_checked.add(loc_id)
|
||||
loc_name = ctx.location_names.lookup_in_slot(loc_id)
|
||||
loc_name = ctx.location_names.lookup_in_game(loc_id)
|
||||
|
||||
if loc_name not in location_id_to_level_id:
|
||||
continue
|
||||
|
||||
@@ -109,7 +109,7 @@ class SMZ3SNIClient(SNIClient):
|
||||
location_id = locations_start_id + convertLocSMZ3IDToAPID(item_index)
|
||||
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_names.lookup_in_slot(location_id)
|
||||
location = ctx.location_names.lookup_in_game(location_id)
|
||||
snes_logger.info(f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||
|
||||
@@ -132,7 +132,7 @@ class SMZ3SNIClient(SNIClient):
|
||||
item_out_ptr += 1
|
||||
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + recv_progress_addr_table_offset, bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
@@ -174,6 +174,13 @@ class ShuffleLadders(Toggle):
|
||||
|
||||
|
||||
class TunicPlandoConnections(PlandoConnections):
|
||||
"""
|
||||
Generic connection plando. Format is:
|
||||
- entrance: "Entrance Name"
|
||||
exit: "Exit Name"
|
||||
percentage: 100
|
||||
Percentage is an integer from 0 to 100 which determines whether that connection will be made. Defaults to 100 if omitted.
|
||||
"""
|
||||
entrances = {*(portal.name for portal in portal_mapping), "Shop", "Shop Portal"}
|
||||
exits = {*(portal.name for portal in portal_mapping), "Shop", "Shop Portal"}
|
||||
|
||||
|
||||
@@ -399,6 +399,16 @@ class WitnessPlayerLogic:
|
||||
mnt_lasers = world.options.mountain_lasers
|
||||
chal_lasers = world.options.challenge_lasers
|
||||
|
||||
# Victory Condition
|
||||
if victory == "elevator":
|
||||
self.VICTORY_LOCATION = "0x3D9A9"
|
||||
elif victory == "challenge":
|
||||
self.VICTORY_LOCATION = "0x0356B"
|
||||
elif victory == "mountain_box_short":
|
||||
self.VICTORY_LOCATION = "0x09F7F"
|
||||
elif victory == "mountain_box_long":
|
||||
self.VICTORY_LOCATION = "0xFFF00"
|
||||
|
||||
# Exclude panels from the post-game if shuffle_postgame is false.
|
||||
if not world.options.shuffle_postgame:
|
||||
adjustment_linesets_in_order += self.handle_postgame(world)
|
||||
@@ -418,17 +428,6 @@ class WitnessPlayerLogic:
|
||||
if not victory == "challenge":
|
||||
adjustment_linesets_in_order.append(["Disabled Locations:", "0x0A332"])
|
||||
|
||||
# Victory Condition
|
||||
|
||||
if victory == "elevator":
|
||||
self.VICTORY_LOCATION = "0x3D9A9"
|
||||
elif victory == "challenge":
|
||||
self.VICTORY_LOCATION = "0x0356B"
|
||||
elif victory == "mountain_box_short":
|
||||
self.VICTORY_LOCATION = "0x09F7F"
|
||||
elif victory == "mountain_box_long":
|
||||
self.VICTORY_LOCATION = "0xFFF00"
|
||||
|
||||
# Long box can usually only be solved by opening Mountain Entry. However, if it requires 7 lasers or less
|
||||
# (challenge_lasers <= 7), you can now solve it without opening Mountain Entry first.
|
||||
# Furthermore, if the user sets mountain_lasers > 7, the box is rotated to not require Mountain Entry either.
|
||||
@@ -874,7 +873,7 @@ class WitnessPlayerLogic:
|
||||
self.PRECOMPLETED_LOCATIONS = set()
|
||||
self.EXCLUDED_LOCATIONS = set()
|
||||
self.ADDED_CHECKS = set()
|
||||
self.VICTORY_LOCATION = "0x0356B"
|
||||
self.VICTORY_LOCATION: str
|
||||
|
||||
self.ALWAYS_EVENT_NAMES_BY_HEX = {
|
||||
"0x00509": "+1 Laser (Symmetry Laser)",
|
||||
|
||||
@@ -116,7 +116,7 @@ class YoshisIslandSNIClient(SNIClient):
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_slot(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
total_locations = len(ctx.missing_locations) + len(ctx.checked_locations)
|
||||
snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})")
|
||||
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}])
|
||||
@@ -127,7 +127,7 @@ class YoshisIslandSNIClient(SNIClient):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info("Received %s from %s (%s) (%d/%d in list)" % (
|
||||
color(ctx.item_names.lookup_in_slot(item.item), "red", "bold"),
|
||||
color(ctx.item_names.lookup_in_game(item.item), "red", "bold"),
|
||||
color(ctx.player_names[item.player], "yellow"),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user