mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-18 21:38:13 -07: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: |
|
run: |
|
||||||
Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/${Env:ENEMIZER_VERSION}/win-x64.zip -OutFile enemizer.zip
|
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
|
Expand-Archive -Path enemizer.zip -DestinationPath EnemizerCLI -Force
|
||||||
|
choco install innosetup --version=6.2.2 --allow-downgrade
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class AdventureContext(CommonContext):
|
|||||||
if ': !' not in msg:
|
if ': !' not in msg:
|
||||||
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||||
elif cmd == "ReceivedItems":
|
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)
|
self._set_message(msg, SYSTEM_MESSAGE_ID)
|
||||||
elif cmd == "Retrieved":
|
elif cmd == "Retrieved":
|
||||||
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:
|
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:
|
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
|
"""Returns the name for an item/location id in the context of a specific slot or own slot if `slot` is
|
||||||
omitted.
|
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:
|
if slot is None:
|
||||||
slot = self.ctx.slot
|
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)
|
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.
|
# __name__ == "__main__" check so unittests that already imported worlds don't trip this.
|
||||||
if __name__ == "__main__" and "worlds" in sys.modules:
|
if __name__ == "__main__" and "worlds" in sys.modules:
|
||||||
raise Exception("Worlds system should not be loaded before logging init.")
|
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:
|
with open(os.path.join(args.outputpath if args.outputpath else ".", f"generate_{seed_name}.yaml"), "wt") as f:
|
||||||
yaml.dump(important, f)
|
yaml.dump(important, f)
|
||||||
|
|
||||||
from Main import main as ERmain
|
return erargs, seed
|
||||||
return ERmain(erargs, seed)
|
|
||||||
|
|
||||||
|
|
||||||
def read_weights_yamls(path) -> Tuple[Any, ...]:
|
def read_weights_yamls(path) -> Tuple[Any, ...]:
|
||||||
@@ -547,7 +546,9 @@ def roll_alttp_settings(ret: argparse.Namespace, weights):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import atexit
|
import atexit
|
||||||
confirmation = atexit.register(input, "Press enter to close.")
|
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__:
|
if __debug__:
|
||||||
import gc
|
import gc
|
||||||
import sys
|
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()})
|
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
|
||||||
options.update(new_options)
|
options.update(new_options)
|
||||||
# apply aliases, without name_lookup
|
# apply aliases, without name_lookup
|
||||||
aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if
|
aliases = attrs["aliases"] = {name[6:].lower(): option_id for name, option_id in attrs.items() if
|
||||||
name.startswith("alias_")}
|
name.startswith("alias_")}
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
name in {"Option", "VerifyKeys"} or # base abstract classes don't need default
|
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
|
# can be weighted between selections
|
||||||
supports_weighting = True
|
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:
|
# filled by AssembleOptions:
|
||||||
name_lookup: typing.ClassVar[typing.Dict[T, str]] # type: ignore
|
name_lookup: typing.ClassVar[typing.Dict[T, str]] # type: ignore
|
||||||
# https://github.com/python/typing/discussions/1460 the reason for this type: ignore
|
# https://github.com/python/typing/discussions/1460 the reason for this type: ignore
|
||||||
options: typing.ClassVar[typing.Dict[str, int]]
|
options: typing.ClassVar[typing.Dict[str, int]]
|
||||||
|
aliases: typing.ClassVar[typing.Dict[str, int]]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.__class__.__name__}({self.current_option_name})"
|
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():
|
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__} " +
|
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}")
|
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
|
self.value = value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1121,10 +1145,13 @@ class PlandoConnections(Option[typing.List[PlandoConnection]], metaclass=Connect
|
|||||||
|
|
||||||
class Accessibility(Choice):
|
class Accessibility(Choice):
|
||||||
"""Set rules for reachability of your items/locations.
|
"""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.
|
- **Locations:** ensure everything can be reached and acquired.
|
||||||
Minimal: ensure what is needed to reach your goal can be 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"
|
display_name = "Accessibility"
|
||||||
|
rich_text_doc = True
|
||||||
option_locations = 0
|
option_locations = 0
|
||||||
option_items = 1
|
option_items = 1
|
||||||
option_minimal = 2
|
option_minimal = 2
|
||||||
@@ -1133,14 +1160,15 @@ class Accessibility(Choice):
|
|||||||
|
|
||||||
|
|
||||||
class ProgressionBalancing(NamedRange):
|
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.
|
A lower setting means more getting stuck. A higher setting means less getting stuck.
|
||||||
"""
|
"""
|
||||||
default = 50
|
default = 50
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 99
|
range_end = 99
|
||||||
display_name = "Progression Balancing"
|
display_name = "Progression Balancing"
|
||||||
|
rich_text_doc = True
|
||||||
special_range_names = {
|
special_range_names = {
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"normal": 50,
|
"normal": 50,
|
||||||
@@ -1205,29 +1233,36 @@ class CommonOptions(metaclass=OptionsMetaProperty):
|
|||||||
class LocalItems(ItemSet):
|
class LocalItems(ItemSet):
|
||||||
"""Forces these items to be in their native world."""
|
"""Forces these items to be in their native world."""
|
||||||
display_name = "Local Items"
|
display_name = "Local Items"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class NonLocalItems(ItemSet):
|
class NonLocalItems(ItemSet):
|
||||||
"""Forces these items to be outside their native world."""
|
"""Forces these items to be outside their native world."""
|
||||||
display_name = "Non-local Items"
|
display_name = "Non-local Items"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class StartInventory(ItemDict):
|
class StartInventory(ItemDict):
|
||||||
"""Start with these items."""
|
"""Start with these items."""
|
||||||
verify_item_name = True
|
verify_item_name = True
|
||||||
display_name = "Start Inventory"
|
display_name = "Start Inventory"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class StartInventoryPool(StartInventory):
|
class StartInventoryPool(StartInventory):
|
||||||
"""Start with these items and don't place them in the world.
|
"""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
|
verify_item_name = True
|
||||||
display_name = "Start Inventory from Pool"
|
display_name = "Start Inventory from Pool"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class StartHints(ItemSet):
|
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"
|
display_name = "Start Hints"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class LocationSet(OptionSet):
|
class LocationSet(OptionSet):
|
||||||
@@ -1236,28 +1271,33 @@ class LocationSet(OptionSet):
|
|||||||
|
|
||||||
|
|
||||||
class StartLocationHints(LocationSet):
|
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"
|
display_name = "Start Location Hints"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class ExcludeLocations(LocationSet):
|
class ExcludeLocations(LocationSet):
|
||||||
"""Prevent these locations from having an important item"""
|
"""Prevent these locations from having an important item."""
|
||||||
display_name = "Excluded Locations"
|
display_name = "Excluded Locations"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class PriorityLocations(LocationSet):
|
class PriorityLocations(LocationSet):
|
||||||
"""Prevent these locations from having an unimportant item"""
|
"""Prevent these locations from having an unimportant item."""
|
||||||
display_name = "Priority Locations"
|
display_name = "Priority Locations"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class DeathLink(Toggle):
|
class DeathLink(Toggle):
|
||||||
"""When you die, everyone dies. Of course the reverse is true too."""
|
"""When you die, everyone dies. Of course the reverse is true too."""
|
||||||
display_name = "Death Link"
|
display_name = "Death Link"
|
||||||
|
rich_text_doc = True
|
||||||
|
|
||||||
|
|
||||||
class ItemLinks(OptionList):
|
class ItemLinks(OptionList):
|
||||||
"""Share part of your item pool with other players."""
|
"""Share part of your item pool with other players."""
|
||||||
display_name = "Item Links"
|
display_name = "Item Links"
|
||||||
|
rich_text_doc = True
|
||||||
default = []
|
default = []
|
||||||
schema = Schema([
|
schema = Schema([
|
||||||
{
|
{
|
||||||
@@ -1324,6 +1364,7 @@ class ItemLinks(OptionList):
|
|||||||
|
|
||||||
class Removed(FreeText):
|
class Removed(FreeText):
|
||||||
"""This Option has been Removed."""
|
"""This Option has been Removed."""
|
||||||
|
rich_text_doc = True
|
||||||
default = ""
|
default = ""
|
||||||
visibility = Visibility.none
|
visibility = Visibility.none
|
||||||
|
|
||||||
@@ -1426,14 +1467,18 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
|||||||
|
|
||||||
return data, notes
|
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():
|
for game_name, world in AutoWorldRegister.world_types.items():
|
||||||
if not world.hidden or generate_hidden:
|
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:
|
with open(local_path("data", "options.yaml")) as f:
|
||||||
file_data = f.read()
|
file_data = f.read()
|
||||||
res = Template(file_data).render(
|
res = Template(file_data).render(
|
||||||
option_groups=grouped_options,
|
option_groups=option_groups,
|
||||||
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
|
__version__=__version__, game=game_name, yaml_dump=yaml_dump_scalar,
|
||||||
dictify_range=dictify_range,
|
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:
|
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
|
||||||
toDraw = ""
|
toDraw = ""
|
||||||
for i in range(20):
|
for i in range(20):
|
||||||
if i < len(str(ctx.item_names.lookup_in_slot(l.item))):
|
if i < len(str(ctx.item_names.lookup_in_game(l.item))):
|
||||||
toDraw += str(ctx.item_names.lookup_in_slot(l.item))[i]
|
toDraw += str(ctx.item_names.lookup_in_game(l.item))[i]
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
f.write(toDraw)
|
f.write(toDraw)
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ class WargrooveContext(CommonContext):
|
|||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
open(path, 'w').close()
|
open(path, 'w').close()
|
||||||
# Announcing commander unlocks
|
# 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():
|
if item_name in faction_table.keys():
|
||||||
for commander in faction_table[item_name]:
|
for commander in faction_table[item_name]:
|
||||||
logger.info(f"{commander.name} has been unlocked!")
|
logger.info(f"{commander.name} has been unlocked!")
|
||||||
@@ -197,7 +197,7 @@ class WargrooveContext(CommonContext):
|
|||||||
open(print_path, 'w').close()
|
open(print_path, 'w').close()
|
||||||
with open(print_path, 'w') as f:
|
with open(print_path, 'w') as f:
|
||||||
f.write("Received " +
|
f.write("Received " +
|
||||||
self.item_names.lookup_in_slot(network_item.item) +
|
self.item_names.lookup_in_game(network_item.item) +
|
||||||
" from " +
|
" from " +
|
||||||
self.player_names[network_item.player])
|
self.player_names[network_item.player])
|
||||||
f.close()
|
f.close()
|
||||||
@@ -342,7 +342,7 @@ class WargrooveContext(CommonContext):
|
|||||||
faction_items = 0
|
faction_items = 0
|
||||||
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
|
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
|
||||||
for network_item in self.items_received:
|
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
|
faction_items += 1
|
||||||
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
|
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
|
||||||
# Must be an integer larger than 0
|
# Must be an integer larger than 0
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Dict, Union
|
from typing import Dict, Union
|
||||||
|
from docutils.core import publish_parts
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from flask import redirect, render_template, request, Response
|
from flask import redirect, render_template, request, Response
|
||||||
@@ -66,6 +67,22 @@ def filter_dedent(text: str) -> str:
|
|||||||
return dedent(text).strip("\n ")
|
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")
|
@app.template_test("ordered")
|
||||||
def test_ordered(obj):
|
def test_ordered(obj):
|
||||||
return isinstance(obj, collections.abc.Sequence)
|
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 */
|
/* Base styles for the element that has a tooltip */
|
||||||
[data-tooltip], .tooltip {
|
[data-tooltip], .tooltip-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Base styles for the entire tooltip */
|
/* 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;
|
position: absolute;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -39,14 +39,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
|||||||
pointer-events: none;
|
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;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Directional arrow styles */
|
/** Directional arrow styles */
|
||||||
.tooltip:before, [data-tooltip]:before {
|
[data-tooltip]:before, .tooltip-container:before {
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
border: 6px solid transparent;
|
border: 6px solid transparent;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -54,7 +55,7 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Content styles */
|
/** Content styles */
|
||||||
.tooltip:after, [data-tooltip]:after {
|
[data-tooltip]:after, .tooltip {
|
||||||
width: 260px;
|
width: 260px;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
padding: 8px;
|
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);
|
background-color: hsla(0, 0%, 20%, 0.9);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
content: attr(data-tooltip);
|
content: attr(data-tooltip);
|
||||||
white-space: pre-wrap;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.2;
|
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;
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after,
|
[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
|
||||||
.tooltip-top:before, .tooltip-top:after {
|
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-tooltip]:before, .tooltip:before, .tooltip-top:before {
|
[data-tooltip]:before, .tooltip-container:before {
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
margin-bottom: -12px;
|
margin-bottom: -12px;
|
||||||
border-top-color: #000;
|
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 */
|
/** Horizontally align tooltips on the top and bottom */
|
||||||
[data-tooltip]:after, .tooltip:after, .tooltip-top:after {
|
[data-tooltip]:after, .tooltip {
|
||||||
margin-left: -80px;
|
margin-left: -80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[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-top:hover:before, .tooltip-top:hover:after {
|
.tooltip-container:hover .tooltip {
|
||||||
-webkit-transform: translateY(-12px);
|
-webkit-transform: translateY(-12px);
|
||||||
-moz-transform: translateY(-12px);
|
-moz-transform: translateY(-12px);
|
||||||
transform: translateY(-12px);
|
transform: translateY(-12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tooltips on the left */
|
/** Tooltips on the left */
|
||||||
.tooltip-left:before, .tooltip-left:after {
|
.tooltip-left:before, [data-tooltip].tooltip-left:after, .tooltip-left .tooltip {
|
||||||
right: 100%;
|
right: 100%;
|
||||||
bottom: 50%;
|
bottom: 50%;
|
||||||
left: auto;
|
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);
|
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);
|
-webkit-transform: translateX(-12px);
|
||||||
-moz-transform: translateX(-12px);
|
-moz-transform: translateX(-12px);
|
||||||
transform: translateX(-12px);
|
transform: translateX(-12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tooltips on the bottom */
|
/** Tooltips on the bottom */
|
||||||
.tooltip-bottom:before, .tooltip-bottom:after {
|
.tooltip-bottom:before, [data-tooltip].tooltip-bottom:after, .tooltip-bottom .tooltip {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
left: 50%;
|
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);
|
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);
|
-webkit-transform: translateY(12px);
|
||||||
-moz-transform: translateY(12px);
|
-moz-transform: translateY(12px);
|
||||||
transform: translateY(12px);
|
transform: translateY(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tooltips on the right */
|
/** Tooltips on the right */
|
||||||
.tooltip-right:before, .tooltip-right:after {
|
.tooltip-right:before, [data-tooltip].tooltip-right:after, .tooltip-right .tooltip {
|
||||||
bottom: 50%;
|
bottom: 50%;
|
||||||
left: 100%;
|
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);
|
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);
|
-webkit-transform: translateX(12px);
|
||||||
-moz-transform: translateX(12px);
|
-moz-transform: translateX(12px);
|
||||||
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 */
|
/** 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-left: 0;
|
||||||
margin-bottom: -16px;
|
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" }}>
|
<select id="{{ option_name }}-select" data-option-name="{{ option_name }}" {{ "disabled" if option.default == "random" }}>
|
||||||
{% for key, val in option.special_range_names.items() %}
|
{% for key, val in option.special_range_names.items() %}
|
||||||
{% if option.default == val %}
|
{% if option.default == val %}
|
||||||
<option value="{{ val }}" selected>{{ key }} ({{ val }})</option>
|
<option value="{{ val }}" selected>{{ key|replace("_", " ")|title }} ({{ val }})</option>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{ val }}">{{ key }} ({{ val }})</option>
|
<option value="{{ val }}">{{ key|replace("_", " ")|title }} ({{ val }})</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<option value="custom" hidden>Custom</option>
|
<option value="custom" hidden>Custom</option>
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro ItemDict(option_name, option, world) %}
|
{% macro ItemDict(option_name, option) %}
|
||||||
{{ OptionTitle(option_name, option) }}
|
{{ OptionTitle(option_name, option) }}
|
||||||
<div class="option-container">
|
<div class="option-container">
|
||||||
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %}
|
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %}
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro LocationSet(option_name, option, world) %}
|
{% macro LocationSet(option_name, option) %}
|
||||||
{{ OptionTitle(option_name, option) }}
|
{{ OptionTitle(option_name, option) }}
|
||||||
<div class="option-container">
|
<div class="option-container">
|
||||||
{% for group_name in world.location_name_groups.keys()|sort %}
|
{% for group_name in world.location_name_groups.keys()|sort %}
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro ItemSet(option_name, option, world) %}
|
{% macro ItemSet(option_name, option) %}
|
||||||
{{ OptionTitle(option_name, option) }}
|
{{ OptionTitle(option_name, option) }}
|
||||||
<div class="option-container">
|
<div class="option-container">
|
||||||
{% for group_name in world.item_name_groups.keys()|sort %}
|
{% for group_name in world.item_name_groups.keys()|sort %}
|
||||||
@@ -196,7 +196,18 @@
|
|||||||
{% macro OptionTitle(option_name, option) %}
|
{% macro OptionTitle(option_name, option) %}
|
||||||
<label for="{{ option_name }}">
|
<label for="{{ option_name }}">
|
||||||
{{ option.display_name|default(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>
|
</label>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% extends 'pageWrapper.html' %}
|
{% extends 'pageWrapper.html' %}
|
||||||
{% import 'playerOptions/macros.html' as inputs %}
|
{% import 'playerOptions/macros.html' as inputs with context %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<title>{{ world_name }} Options</title>
|
<title>{{ world_name }} Options</title>
|
||||||
@@ -94,16 +94,16 @@
|
|||||||
{{ inputs.FreeText(option_name, option) }}
|
{{ inputs.FreeText(option_name, option) }}
|
||||||
|
|
||||||
{% elif issubclass(option, Options.ItemDict) and option.verify_item_name %}
|
{% 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 %}
|
{% elif issubclass(option, Options.OptionList) and option.valid_keys %}
|
||||||
{{ inputs.OptionList(option_name, option) }}
|
{{ inputs.OptionList(option_name, option) }}
|
||||||
|
|
||||||
{% elif issubclass(option, Options.LocationSet) and option.verify_location_name %}
|
{% 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 %}
|
{% 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 %}
|
{% elif issubclass(option, Options.OptionSet) and option.valid_keys %}
|
||||||
{{ inputs.OptionSet(option_name, option) }}
|
{{ inputs.OptionSet(option_name, option) }}
|
||||||
@@ -134,16 +134,16 @@
|
|||||||
{{ inputs.FreeText(option_name, option) }}
|
{{ inputs.FreeText(option_name, option) }}
|
||||||
|
|
||||||
{% elif issubclass(option, Options.ItemDict) and option.verify_item_name %}
|
{% 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 %}
|
{% elif issubclass(option, Options.OptionList) and option.valid_keys %}
|
||||||
{{ inputs.OptionList(option_name, option) }}
|
{{ inputs.OptionList(option_name, option) }}
|
||||||
|
|
||||||
{% elif issubclass(option, Options.LocationSet) and option.verify_location_name %}
|
{% 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 %}
|
{% 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 %}
|
{% elif issubclass(option, Options.OptionSet) and option.valid_keys %}
|
||||||
{{ inputs.OptionSet(option_name, option) }}
|
{{ inputs.OptionSet(option_name, option) }}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
{% for id, name in option.name_lookup.items() %}
|
{% for id, name in option.name_lookup.items() %}
|
||||||
{% if name != 'random' %}
|
{% if name != 'random' %}
|
||||||
{% if option.default != '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 %}
|
{% else %}
|
||||||
{{ RangeRow(option_name, option, option.get_option_name(id), name) }}
|
{{ RangeRow(option_name, option, option.get_option_name(id), name) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -41,13 +41,13 @@
|
|||||||
The following values have special meanings, and may fall outside the normal range.
|
The following values have special meanings, and may fall outside the normal range.
|
||||||
<ul>
|
<ul>
|
||||||
{% for name, value in option.special_range_names.items() %}
|
{% for name, value in option.special_range_names.items() %}
|
||||||
<li>{{ value }}: {{ name }}</li>
|
<li>{{ value }}: {{ name|replace("_", " ")|title }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="add-option-div">
|
<div class="add-option-div">
|
||||||
<input type="number" class="range-option-value" data-option="{{ option_name }}" />
|
<input type="number" class="range-option-value" data-option="{{ option_name }}" />
|
||||||
<button class="add-range-option-button" data-option="{{ option_name }}">Add</button>
|
<button type="button" class="add-range-option-button" data-option="{{ option_name }}">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="range-rows" data-option="{{ option_name }}">
|
<table class="range-rows" data-option="{{ option_name }}">
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
This option allows custom values only. Please enter your desired values below.
|
This option allows custom values only. Please enter your desired values below.
|
||||||
<div class="custom-value-wrapper">
|
<div class="custom-value-wrapper">
|
||||||
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||||
<button data-option="{{ option_name }}">Add</button>
|
<button type="button" data-option="{{ option_name }}">Add</button>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
Custom values are also allowed for this option. To create one, enter it into the input box below.
|
Custom values are also allowed for this option. To create one, enter it into the input box below.
|
||||||
<div class="custom-value-wrapper">
|
<div class="custom-value-wrapper">
|
||||||
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||||
<button data-option="{{ option_name }}">Add</button>
|
<button type="button" data-option="{{ option_name }}">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
{% for id, name in option.name_lookup.items() %}
|
{% for id, name in option.name_lookup.items() %}
|
||||||
{% if name != 'random' %}
|
{% if name != 'random' %}
|
||||||
{% if option.default != '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 %}
|
{% else %}
|
||||||
{{ RangeRow(option_name, option, option.get_option_name(id), name) }}
|
{{ RangeRow(option_name, option, option.get_option_name(id), name) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ def get_payload(ctx: ZeldaContext):
|
|||||||
|
|
||||||
|
|
||||||
def reconcile_shops(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]
|
shops = [location for location in checked_location_names if "Shop" in location]
|
||||||
left_slots = [shop for shop in shops if "Left" in shop]
|
left_slots = [shop for shop in shops if "Left" in shop]
|
||||||
middle_slots = [shop for shop in shops if "Middle" 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 = []
|
locations_checked = []
|
||||||
location = None
|
location = None
|
||||||
for location in ctx.missing_locations:
|
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":
|
if location_name in Locations.overworld_locations and zone == "overworld":
|
||||||
status = locations_array[Locations.major_location_offsets[location_name]]
|
status = locations_array[Locations.major_location_offsets[location_name]]
|
||||||
|
|||||||
@@ -68,21 +68,21 @@ requires:
|
|||||||
|
|
||||||
{%- elif option.options -%}
|
{%- elif option.options -%}
|
||||||
{%- for suboption_option_id, sub_option_name in option.name_lookup.items() %}
|
{%- 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 -%}
|
{%- endfor -%}
|
||||||
|
|
||||||
{%- if option.name_lookup[option.default] not in option.options %}
|
{%- if option.name_lookup[option.default] not in option.options %}
|
||||||
{{ option.default }}: 50
|
{{ yaml_dump(option.default) }}: 50
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
{%- elif option.default is string %}
|
{%- elif option.default is string %}
|
||||||
{{ option.default }}: 50
|
{{ yaml_dump(option.default) }}: 50
|
||||||
|
|
||||||
{%- elif option.default is iterable and option.default is not mapping %}
|
{%- elif option.default is iterable and option.default is not mapping %}
|
||||||
{{ option.default | list }}
|
{{ option.default | list }}
|
||||||
|
|
||||||
{%- else %}
|
{%- else %}
|
||||||
{{ yaml_dump(option.default) | trim | indent(4, first=false) }}
|
{{ yaml_dump(option.default) | indent(4, first=false) }}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{{ "\n" }}
|
{{ "\n" }}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
|||||||
@@ -87,9 +87,6 @@
|
|||||||
# Lingo
|
# Lingo
|
||||||
/worlds/lingo/ @hatkirby
|
/worlds/lingo/ @hatkirby
|
||||||
|
|
||||||
# Links Awakening DX
|
|
||||||
/worlds/ladx/ @zig-for
|
|
||||||
|
|
||||||
# Lufia II Ancient Cave
|
# Lufia II Ancient Cave
|
||||||
/worlds/lufia2ac/ @el-u
|
/worlds/lufia2ac/ @el-u
|
||||||
/worlds/lufia2ac/docs/ @wordfcuk @el-u
|
/worlds/lufia2ac/docs/ @wordfcuk @el-u
|
||||||
@@ -218,6 +215,8 @@
|
|||||||
# Final Fantasy (1)
|
# Final Fantasy (1)
|
||||||
# /worlds/ff1/
|
# /worlds/ff1/
|
||||||
|
|
||||||
|
# Links Awakening DX
|
||||||
|
# /worlds/ladx/
|
||||||
|
|
||||||
## Disabled Unmaintained Worlds
|
## Disabled Unmaintained Worlds
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,50 @@ class ExampleWorld(World):
|
|||||||
options: ExampleGameOptions
|
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
|
### Option Groups
|
||||||
Options may be categorized into groups for display on the WebHost. Option groups are displayed in the order specified
|
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
|
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.
|
* `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:
|
* `theme` to be used for your game-specific AP pages. Available themes:
|
||||||
|
|
||||||
| dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone |
|
| 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`
|
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).
|
(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
|
### 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
|
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
|
Custom methods can be defined for your logic rules. The access rule that ultimately gets assigned to the Location or
|
||||||
Entrance should be
|
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
|
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.
|
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).
|
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 players_dir:
|
||||||
with TemporaryDirectory() as output_dir:
|
with TemporaryDirectory() as output_dir:
|
||||||
import Generate
|
import Generate
|
||||||
|
import Main
|
||||||
|
|
||||||
for n, game in enumerate(games, 1):
|
for n, game in enumerate(games, 1):
|
||||||
player_path = Path(players_dir) / f"{n}.yaml"
|
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))),
|
sys.argv = [sys.argv[0], "--seed", str(hash(tuple(games))),
|
||||||
"--player_files_path", players_dir,
|
"--player_files_path", players_dir,
|
||||||
"--outputpath", output_dir]
|
"--outputpath", output_dir]
|
||||||
Generate.main()
|
Main.main(*Generate.main())
|
||||||
output_files = list(Path(output_dir).glob('*.zip'))
|
output_files = list(Path(output_dir).glob('*.zip'))
|
||||||
assert len(output_files) == 1
|
assert len(output_files) == 1
|
||||||
final_file = dest / output_files[0].name
|
final_file = dest / output_files[0].name
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from pathlib import Path
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
import Generate
|
import Generate
|
||||||
|
import Main
|
||||||
|
|
||||||
|
|
||||||
class TestGenerateMain(unittest.TestCase):
|
class TestGenerateMain(unittest.TestCase):
|
||||||
@@ -58,7 +59,7 @@ class TestGenerateMain(unittest.TestCase):
|
|||||||
'--player_files_path', str(self.abs_input_dir),
|
'--player_files_path', str(self.abs_input_dir),
|
||||||
'--outputpath', self.output_tempdir.name]
|
'--outputpath', self.output_tempdir.name]
|
||||||
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}')
|
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}')
|
||||||
Generate.main()
|
Main.main(*Generate.main())
|
||||||
|
|
||||||
self.assertOutput(self.output_tempdir.name)
|
self.assertOutput(self.output_tempdir.name)
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ class TestGenerateMain(unittest.TestCase):
|
|||||||
'--player_files_path', str(self.rel_input_dir),
|
'--player_files_path', str(self.rel_input_dir),
|
||||||
'--outputpath', self.output_tempdir.name]
|
'--outputpath', self.output_tempdir.name]
|
||||||
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}')
|
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}')
|
||||||
Generate.main()
|
Main.main(*Generate.main())
|
||||||
|
|
||||||
self.assertOutput(self.output_tempdir.name)
|
self.assertOutput(self.output_tempdir.name)
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ class TestGenerateMain(unittest.TestCase):
|
|||||||
sys.argv = [sys.argv[0], '--seed', '0',
|
sys.argv = [sys.argv[0], '--seed', '0',
|
||||||
'--outputpath', self.output_tempdir.name]
|
'--outputpath', self.output_tempdir.name]
|
||||||
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}, player_files_path={self.yaml_input_dir}')
|
print(f'Testing Generate.py {sys.argv} in {os.getcwd()}, player_files_path={self.yaml_input_dir}')
|
||||||
Generate.main()
|
Main.main(*Generate.main())
|
||||||
finally:
|
finally:
|
||||||
user_path.cached_path = user_path_backup
|
user_path.cached_path = user_path_backup
|
||||||
|
|
||||||
|
|||||||
@@ -223,6 +223,21 @@ class WebWorld(metaclass=WebWorldRegister):
|
|||||||
option_groups: ClassVar[List[OptionGroup]] = []
|
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"."""
|
"""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] = {}
|
location_descriptions: Dict[str, str] = {}
|
||||||
"""An optional map from location names (or location group names) to brief descriptions for users."""
|
"""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 = {
|
network_data_package: DataPackage = {
|
||||||
"games": {world_name: world.get_data_package_data() for world_name, world in AutoWorldRegister.world_types.items()},
|
"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):
|
def new_check(location_id):
|
||||||
new_locations.append(location_id)
|
new_locations.append(location_id)
|
||||||
ctx.locations_checked.add(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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ' +
|
f'New Check: {location} ' +
|
||||||
f'({len(ctx.checked_locations) + 1 if ctx.checked_locations else len(ctx.locations_checked)}/' +
|
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]
|
item = ctx.items_received[recv_index]
|
||||||
recv_index += 1
|
recv_index += 1
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
color(ctx.player_names[item.player], 'yellow'),
|
||||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
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'
|
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):
|
def overworld_glitch_connections(world, player):
|
||||||
|
|
||||||
# Boots-accessible locations.
|
# 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_lw(world.mode[player] == 'inverted'))
|
||||||
create_owg_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player))
|
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 .Bosses import GanonDefeatRule
|
||||||
from .Items import item_factory, item_name_groups, item_table, progression_items
|
from .Items import item_factory, item_name_groups, item_table, progression_items
|
||||||
from .Options import small_key_shuffle
|
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 .Regions import LTTPRegionType, location_table
|
||||||
from .StateHelpers import (can_extend_magic, can_kill_most_things,
|
from .StateHelpers import (can_extend_magic, can_kill_most_things,
|
||||||
can_lift_heavy_rocks, can_lift_rocks,
|
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!')
|
'WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
|
||||||
|
|
||||||
if world.players == 1:
|
if world.players == 1:
|
||||||
no_logic_rules(world, player)
|
|
||||||
for exit in world.get_region('Menu', player).exits:
|
for exit in world.get_region('Menu', player).exits:
|
||||||
exit.hide_path = True
|
exit.hide_path = True
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class Castlevania64Client(BizHawkClient):
|
|||||||
text_color = bytearray([0xA2, 0x0B])
|
text_color = bytearray([0xA2, 0x0B])
|
||||||
else:
|
else:
|
||||||
text_color = bytearray([0xA2, 0x02])
|
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)
|
f"from {ctx.player_names[next_item.player]}", 96)
|
||||||
await bizhawk.guarded_write(ctx.bizhawk_ctx,
|
await bizhawk.guarded_write(ctx.bizhawk_ctx,
|
||||||
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
|
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class DKC3SNIClient(SNIClient):
|
|||||||
|
|
||||||
for new_check_id in new_checks:
|
for new_check_id in new_checks:
|
||||||
ctx.locations_checked.add(new_check_id)
|
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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
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]}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||||
@@ -99,7 +99,7 @@ class DKC3SNIClient(SNIClient):
|
|||||||
item = ctx.items_received[recv_index]
|
item = ctx.items_received[recv_index]
|
||||||
recv_index += 1
|
recv_index += 1
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
color(ctx.player_names[item.player], 'yellow'),
|
||||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
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:
|
if ctx.locations_checked != research_data:
|
||||||
bridge_logger.debug(
|
bridge_logger.debug(
|
||||||
f"New researches done: "
|
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
|
ctx.locations_checked = research_data
|
||||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
|
||||||
death_link_tick = data.get("death_link_tick", 0)
|
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]
|
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
|
||||||
item_id = transfer_item.item
|
item_id = transfer_item.item
|
||||||
player_name = ctx.player_names[transfer_item.player]
|
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}.")
|
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}"
|
commands[ctx.send_index] = f"/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}"
|
||||||
ctx.send_index += 1
|
ctx.send_index += 1
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ class KDL3SNIClient(SNIClient):
|
|||||||
item = ctx.items_received[recv_amount]
|
item = ctx.items_received[recv_amount]
|
||||||
recv_amount += 1
|
recv_amount += 1
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
color(ctx.player_names[item.player], 'yellow'),
|
||||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_amount, len(ctx.items_received)))
|
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:
|
for new_check_id in new_checks:
|
||||||
ctx.locations_checked.add(new_check_id)
|
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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ({len(ctx.locations_checked)}/'
|
f'New Check: {location} ({len(ctx.locations_checked)}/'
|
||||||
f'{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
f'{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import typing
|
|||||||
import bsdiff4
|
import bsdiff4
|
||||||
|
|
||||||
import settings
|
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 Fill import fill_restrictive
|
||||||
from worlds.AutoWorld import WebWorld, World
|
from worlds.AutoWorld import WebWorld, World
|
||||||
from .Common import *
|
from .Common import *
|
||||||
@@ -24,7 +24,7 @@ from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup
|
|||||||
from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion,
|
from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion,
|
||||||
create_regions_from_ladxr, get_locations_to_id)
|
create_regions_from_ladxr, get_locations_to_id)
|
||||||
from .Options import DungeonItemShuffle, links_awakening_options, ShuffleInstruments
|
from .Options import DungeonItemShuffle, links_awakening_options, ShuffleInstruments
|
||||||
from .Rom import LADXDeltaPatch
|
from .Rom import LADXDeltaPatch, get_base_rom_path
|
||||||
|
|
||||||
DEVELOPER_MODE = False
|
DEVELOPER_MODE = False
|
||||||
|
|
||||||
@@ -433,6 +433,12 @@ class LinksAwakeningWorld(World):
|
|||||||
|
|
||||||
return "TRADING_ITEM_LETTER"
|
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):
|
def generate_output(self, output_directory: str):
|
||||||
# copy items back to locations
|
# copy items back to locations
|
||||||
for r in self.multiworld.get_regions(self.player):
|
for r in self.multiworld.get_regions(self.player):
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from .regions import create_regions
|
|||||||
|
|
||||||
class LingoWebWorld(WebWorld):
|
class LingoWebWorld(WebWorld):
|
||||||
option_groups = lingo_option_groups
|
option_groups = lingo_option_groups
|
||||||
|
rich_text_options_doc = True
|
||||||
theme = "grass"
|
theme = "grass"
|
||||||
tutorials = [Tutorial(
|
tutorials = [Tutorial(
|
||||||
"Multiworld Setup Guide",
|
"Multiworld Setup Guide",
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ from .items import TRAP_ITEMS
|
|||||||
|
|
||||||
class ShuffleDoors(Choice):
|
class ShuffleDoors(Choice):
|
||||||
"""If on, opening doors will require their respective "keys".
|
"""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"
|
display_name = "Shuffle Doors"
|
||||||
option_none = 0
|
option_none = 0
|
||||||
option_simple = 1
|
option_simple = 1
|
||||||
@@ -19,24 +23,37 @@ class ShuffleDoors(Choice):
|
|||||||
|
|
||||||
class ProgressiveOrangeTower(DefaultOnToggle):
|
class ProgressiveOrangeTower(DefaultOnToggle):
|
||||||
"""When "Shuffle Doors" is on, this setting governs the manner in which the Orange Tower floors open up.
|
"""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"
|
display_name = "Progressive Orange Tower"
|
||||||
|
|
||||||
|
|
||||||
class ProgressiveColorful(DefaultOnToggle):
|
class ProgressiveColorful(DefaultOnToggle):
|
||||||
"""When "Shuffle Doors" is on "complex", this setting governs the manner in which The Colorful opens up.
|
"""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"
|
display_name = "Progressive Colorful"
|
||||||
|
|
||||||
|
|
||||||
class LocationChecks(Choice):
|
class LocationChecks(Choice):
|
||||||
"""Determines what locations are available.
|
"""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.
|
- **Normal:** There will be a location check for each panel set that would
|
||||||
On "insanity", every individual panel in the game is a location check."""
|
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"
|
display_name = "Location Checks"
|
||||||
option_normal = 0
|
option_normal = 0
|
||||||
option_reduced = 1
|
option_reduced = 1
|
||||||
@@ -44,16 +61,20 @@ class LocationChecks(Choice):
|
|||||||
|
|
||||||
|
|
||||||
class ShuffleColors(DefaultOnToggle):
|
class ShuffleColors(DefaultOnToggle):
|
||||||
"""
|
"""If on, an item is added to the pool for every puzzle color (besides White).
|
||||||
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.
|
You will need to unlock the requisite colors in order to be able to solve
|
||||||
|
puzzles of that color.
|
||||||
"""
|
"""
|
||||||
display_name = "Shuffle Colors"
|
display_name = "Shuffle Colors"
|
||||||
|
|
||||||
|
|
||||||
class ShufflePanels(Choice):
|
class ShufflePanels(Choice):
|
||||||
"""If on, the puzzles on each panel are randomized.
|
"""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"
|
display_name = "Shuffle Panels"
|
||||||
option_none = 0
|
option_none = 0
|
||||||
option_rearrange = 1
|
option_rearrange = 1
|
||||||
@@ -66,22 +87,26 @@ class ShufflePaintings(Toggle):
|
|||||||
|
|
||||||
class EnablePilgrimage(Toggle):
|
class EnablePilgrimage(Toggle):
|
||||||
"""Determines how the pilgrimage works.
|
"""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"
|
display_name = "Enable Pilgrimage"
|
||||||
|
|
||||||
|
|
||||||
class PilgrimageAllowsRoofAccess(DefaultOnToggle):
|
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.
|
Otherwise, pilgrimage will be deactivated when going up the stairs.
|
||||||
"""
|
"""
|
||||||
display_name = "Allow Roof Access for Pilgrimage"
|
display_name = "Allow Roof Access for Pilgrimage"
|
||||||
|
|
||||||
|
|
||||||
class PilgrimageAllowsPaintings(DefaultOnToggle):
|
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.
|
Otherwise, pilgrimage will be deactivated when going through a painting.
|
||||||
"""
|
"""
|
||||||
display_name = "Allow Paintings for Pilgrimage"
|
display_name = "Allow Paintings for Pilgrimage"
|
||||||
@@ -89,11 +114,17 @@ class PilgrimageAllowsPaintings(DefaultOnToggle):
|
|||||||
|
|
||||||
class SunwarpAccess(Choice):
|
class SunwarpAccess(Choice):
|
||||||
"""Determines how access to sunwarps works.
|
"""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.
|
- **Normal:** All sunwarps are enabled from the start.
|
||||||
On "unlock", sunwarps start off disabled, and all six activate once you receive an item.
|
- **Disabled:** All sunwarps are disabled. Pilgrimage must be disabled when
|
||||||
On "individual", sunwarps start off disabled, and each has a corresponding item that unlocks it.
|
this is used.
|
||||||
On "progressive", sunwarps start off disabled, and they unlock in order using a progressive item."""
|
- **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"
|
display_name = "Sunwarp Access"
|
||||||
option_normal = 0
|
option_normal = 0
|
||||||
option_disabled = 1
|
option_disabled = 1
|
||||||
@@ -109,10 +140,16 @@ class ShuffleSunwarps(Toggle):
|
|||||||
|
|
||||||
class VictoryCondition(Choice):
|
class VictoryCondition(Choice):
|
||||||
"""Change the victory condition.
|
"""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.
|
- **The End:** the goal is to solve THE END at the top of the tower.
|
||||||
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.
|
- **The Master:** The goal is to solve THE MASTER at the top of the tower,
|
||||||
On "pilgrimage", the goal is to solve PILGRIM in the Pilgrim Antechamber, typically after performing a Pilgrimage."""
|
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"
|
display_name = "Victory Condition"
|
||||||
option_the_end = 0
|
option_the_end = 0
|
||||||
option_the_master = 1
|
option_the_master = 1
|
||||||
@@ -122,9 +159,12 @@ class VictoryCondition(Choice):
|
|||||||
|
|
||||||
class MasteryAchievements(Range):
|
class MasteryAchievements(Range):
|
||||||
"""The number of achievements required to unlock THE MASTER.
|
"""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.
|
- In the base game, 21 achievements are needed.
|
||||||
If you include the custom achievement (The Wanderer), 24 would be required.
|
- 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"
|
display_name = "Mastery Achievements"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
@@ -134,9 +174,10 @@ class MasteryAchievements(Range):
|
|||||||
|
|
||||||
class Level2Requirement(Range):
|
class Level2Requirement(Range):
|
||||||
"""The number of panel solves required to unlock LEVEL 2.
|
"""The number of panel solves required to unlock LEVEL 2.
|
||||||
In the base game, 223 are needed.
|
|
||||||
Note that this count includes ANOTHER TRY.
|
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.
|
When set to 1, the panel hunt is disabled, and you can access LEVEL 2 for
|
||||||
|
free.
|
||||||
"""
|
"""
|
||||||
display_name = "Level 2 Requirement"
|
display_name = "Level 2 Requirement"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
@@ -145,9 +186,10 @@ class Level2Requirement(Range):
|
|||||||
|
|
||||||
|
|
||||||
class EarlyColorHallways(Toggle):
|
class EarlyColorHallways(Toggle):
|
||||||
"""
|
"""When on, a painting warp to the color hallways area will appear in the starting room.
|
||||||
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.
|
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"
|
display_name = "Early Color Hallways"
|
||||||
|
|
||||||
@@ -161,8 +203,8 @@ class TrapPercentage(Range):
|
|||||||
|
|
||||||
|
|
||||||
class TrapWeights(OptionDict):
|
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.
|
If you don't want a specific type of trap, set the weight to zero.
|
||||||
"""
|
"""
|
||||||
display_name = "Trap Weights"
|
display_name = "Trap Weights"
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class L2ACSNIClient(SNIClient):
|
|||||||
snes_items_received += 1
|
snes_items_received += 1
|
||||||
|
|
||||||
snes_logger.info("Received %s from %s (%s) (%d/%d in list)" % (
|
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.player_names[item.player],
|
||||||
ctx.location_names.lookup_in_slot(item.location, item.player),
|
ctx.location_names.lookup_in_slot(item.location, item.player),
|
||||||
snes_items_received, len(ctx.items_received)))
|
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,
|
"dark_rock_tunnel_logic": self.multiworld.dark_rock_tunnel_logic[self.player].value,
|
||||||
"split_card_key": self.multiworld.split_card_key[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,
|
"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
|
## 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
|
## 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.
|
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.
|
2. Open PopTracker, and load the Pokémon Red and Blue pack.
|
||||||
3. Click on the "AP" symbol at the top.
|
3. Click on the "AP" symbol at the top.
|
||||||
4. Enter the AP address, slot name and password.
|
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
|
## 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
|
## 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.
|
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.
|
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.
|
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).
|
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).
|
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.
|
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] ")
|
self.formatted_print(f" [u]{faction.name}[/u] ")
|
||||||
|
|
||||||
for item_id in categorized_items[faction]:
|
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, []))
|
received_child_items = items_received_set.intersection(parent_to_child.get(item_id, []))
|
||||||
matching_children = [child for child in received_child_items
|
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, [])
|
received_items_of_this_type = items_received.get(item_id, [])
|
||||||
item_is_match = item_matches_filter(item_name)
|
item_is_match = item_matches_filter(item_name)
|
||||||
if item_is_match or len(matching_children) > 0:
|
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))
|
objectives = set(ctx.locations_for_mission(mission))
|
||||||
if objectives:
|
if objectives:
|
||||||
remaining_objectives = objectives.difference(ctx.checked_locations)
|
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:
|
else:
|
||||||
unfinished_locations[mission] = []
|
unfinished_locations[mission] = []
|
||||||
|
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ class SC2Manager(GameManager):
|
|||||||
for loc in self.ctx.locations_for_mission(mission_name):
|
for loc in self.ctx.locations_for_mission(mission_name):
|
||||||
if loc in self.ctx.missing_locations:
|
if loc in self.ctx.missing_locations:
|
||||||
count += 1
|
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 = []
|
plando_locations = []
|
||||||
for plando_loc in self.ctx.plando_locations:
|
for plando_loc in self.ctx.plando_locations:
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class SMSNIClient(SNIClient):
|
|||||||
location_id = locations_start_id + item_index
|
location_id = locations_start_id + item_index
|
||||||
|
|
||||||
ctx.locations_checked.add(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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
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]}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||||
@@ -151,7 +151,7 @@ class SMSNIClient(SNIClient):
|
|||||||
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
|
snes_buffered_write(ctx, SM_RECV_QUEUE_WCOUNT,
|
||||||
bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
bytes([item_out_ptr & 0xFF, (item_out_ptr >> 8) & 0xFF]))
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
color(ctx.player_names[item.player], 'yellow'),
|
||||||
ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
|
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:
|
for new_check_id in new_checks:
|
||||||
ctx.locations_checked.add(new_check_id)
|
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(
|
snes_logger.info(
|
||||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
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]}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||||
@@ -501,14 +501,14 @@ class SMWSNIClient(SNIClient):
|
|||||||
recv_index += 1
|
recv_index += 1
|
||||||
sending_game = ctx.slot_info[item.player].game
|
sending_game = ctx.slot_info[item.player].game
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
color(ctx.player_names[item.player], 'yellow'),
|
||||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||||
|
|
||||||
if self.should_show_message(ctx, item):
|
if self.should_show_message(ctx, item):
|
||||||
if item.item != 0xBC0012 and item.item not in trap_rom_data:
|
if item.item != 0xBC0012 and item.item not in trap_rom_data:
|
||||||
# Don't send messages for Boss Tokens
|
# 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]
|
player_name = ctx.player_names[item.player]
|
||||||
|
|
||||||
receive_message = generate_received_text(item_name, player_name)
|
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]))
|
snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF]))
|
||||||
if item.item in trap_rom_data:
|
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]
|
player_name = ctx.player_names[item.player]
|
||||||
|
|
||||||
receive_message = generate_received_text(item_name, player_name)
|
receive_message = generate_received_text(item_name, player_name)
|
||||||
@@ -597,7 +597,7 @@ class SMWSNIClient(SNIClient):
|
|||||||
for loc_id in ctx.checked_locations:
|
for loc_id in ctx.checked_locations:
|
||||||
if loc_id not in ctx.locations_checked:
|
if loc_id not in ctx.locations_checked:
|
||||||
ctx.locations_checked.add(loc_id)
|
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:
|
if loc_name not in location_id_to_level_id:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class SMZ3SNIClient(SNIClient):
|
|||||||
location_id = locations_start_id + convertLocSMZ3IDToAPID(item_index)
|
location_id = locations_start_id + convertLocSMZ3IDToAPID(item_index)
|
||||||
|
|
||||||
ctx.locations_checked.add(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} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
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]}])
|
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ class SMZ3SNIClient(SNIClient):
|
|||||||
item_out_ptr += 1
|
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]))
|
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)' % (
|
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)))
|
ctx.location_names.lookup_in_slot(item.location, item.player), item_out_ptr, len(ctx.items_received)))
|
||||||
|
|
||||||
await snes_flush_writes(ctx)
|
await snes_flush_writes(ctx)
|
||||||
|
|||||||
@@ -174,6 +174,13 @@ class ShuffleLadders(Toggle):
|
|||||||
|
|
||||||
|
|
||||||
class TunicPlandoConnections(PlandoConnections):
|
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"}
|
entrances = {*(portal.name for portal in portal_mapping), "Shop", "Shop Portal"}
|
||||||
exits = {*(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
|
mnt_lasers = world.options.mountain_lasers
|
||||||
chal_lasers = world.options.challenge_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.
|
# Exclude panels from the post-game if shuffle_postgame is false.
|
||||||
if not world.options.shuffle_postgame:
|
if not world.options.shuffle_postgame:
|
||||||
adjustment_linesets_in_order += self.handle_postgame(world)
|
adjustment_linesets_in_order += self.handle_postgame(world)
|
||||||
@@ -418,17 +428,6 @@ class WitnessPlayerLogic:
|
|||||||
if not victory == "challenge":
|
if not victory == "challenge":
|
||||||
adjustment_linesets_in_order.append(["Disabled Locations:", "0x0A332"])
|
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
|
# 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.
|
# (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.
|
# 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.PRECOMPLETED_LOCATIONS = set()
|
||||||
self.EXCLUDED_LOCATIONS = set()
|
self.EXCLUDED_LOCATIONS = set()
|
||||||
self.ADDED_CHECKS = set()
|
self.ADDED_CHECKS = set()
|
||||||
self.VICTORY_LOCATION = "0x0356B"
|
self.VICTORY_LOCATION: str
|
||||||
|
|
||||||
self.ALWAYS_EVENT_NAMES_BY_HEX = {
|
self.ALWAYS_EVENT_NAMES_BY_HEX = {
|
||||||
"0x00509": "+1 Laser (Symmetry Laser)",
|
"0x00509": "+1 Laser (Symmetry Laser)",
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class YoshisIslandSNIClient(SNIClient):
|
|||||||
|
|
||||||
for new_check_id in new_checks:
|
for new_check_id in new_checks:
|
||||||
ctx.locations_checked.add(new_check_id)
|
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)
|
total_locations = len(ctx.missing_locations) + len(ctx.checked_locations)
|
||||||
snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})")
|
snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})")
|
||||||
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}])
|
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}])
|
||||||
@@ -127,7 +127,7 @@ class YoshisIslandSNIClient(SNIClient):
|
|||||||
item = ctx.items_received[recv_index]
|
item = ctx.items_received[recv_index]
|
||||||
recv_index += 1
|
recv_index += 1
|
||||||
logging.info("Received %s from %s (%s) (%d/%d in list)" % (
|
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"),
|
color(ctx.player_names[item.player], "yellow"),
|
||||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user