mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-10 11:38:14 -07:00
Merge branch 'main' into main
This commit is contained in:
64
Options.py
64
Options.py
@@ -126,6 +126,23 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
|
||||
# can be weighted between selections
|
||||
supports_weighting = True
|
||||
|
||||
rich_text_doc: typing.Optional[bool] = None
|
||||
"""Whether the WebHost should render the Option's docstring as rich text.
|
||||
|
||||
If this is True, the Option's docstring is interpreted as reStructuredText_,
|
||||
the standard Python markup format. In the WebHost, it's rendered to HTML so
|
||||
that lists, emphasis, and other rich text features are displayed properly.
|
||||
|
||||
If this is False, the docstring is instead interpreted as plain text, and
|
||||
displayed as-is on the WebHost with whitespace preserved.
|
||||
|
||||
If this is None, it inherits the value of `World.rich_text_options_doc`. For
|
||||
backwards compatibility, this defaults to False, but worlds are encouraged to
|
||||
set it to True and use reStructuredText for their Option documentation.
|
||||
|
||||
.. _reStructuredText: https://docutils.sourceforge.io/rst.html
|
||||
"""
|
||||
|
||||
# filled by AssembleOptions:
|
||||
name_lookup: typing.ClassVar[typing.Dict[T, str]] # type: ignore
|
||||
# https://github.com/python/typing/discussions/1460 the reason for this type: ignore
|
||||
@@ -1127,10 +1144,13 @@ class PlandoConnections(Option[typing.List[PlandoConnection]], metaclass=Connect
|
||||
|
||||
class Accessibility(Choice):
|
||||
"""Set rules for reachability of your items/locations.
|
||||
Locations: ensure everything can be reached and acquired.
|
||||
Items: ensure all logically relevant items can be acquired.
|
||||
Minimal: ensure what is needed to reach your goal can be acquired."""
|
||||
|
||||
- **Locations:** ensure everything can be reached and acquired.
|
||||
- **Items:** ensure all logically relevant items can be acquired.
|
||||
- **Minimal:** ensure what is needed to reach your goal can be acquired.
|
||||
"""
|
||||
display_name = "Accessibility"
|
||||
rich_text_doc = True
|
||||
option_locations = 0
|
||||
option_items = 1
|
||||
option_minimal = 2
|
||||
@@ -1139,14 +1159,15 @@ class Accessibility(Choice):
|
||||
|
||||
|
||||
class ProgressionBalancing(NamedRange):
|
||||
"""
|
||||
A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
|
||||
"""A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
|
||||
|
||||
A lower setting means more getting stuck. A higher setting means less getting stuck.
|
||||
"""
|
||||
default = 50
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
display_name = "Progression Balancing"
|
||||
rich_text_doc = True
|
||||
special_range_names = {
|
||||
"disabled": 0,
|
||||
"normal": 50,
|
||||
@@ -1211,29 +1232,36 @@ class CommonOptions(metaclass=OptionsMetaProperty):
|
||||
class LocalItems(ItemSet):
|
||||
"""Forces these items to be in their native world."""
|
||||
display_name = "Local Items"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class NonLocalItems(ItemSet):
|
||||
"""Forces these items to be outside their native world."""
|
||||
display_name = "Non-local Items"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class StartInventory(ItemDict):
|
||||
"""Start with these items."""
|
||||
verify_item_name = True
|
||||
display_name = "Start Inventory"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class StartInventoryPool(StartInventory):
|
||||
"""Start with these items and don't place them in the world.
|
||||
The game decides what the replacement items will be."""
|
||||
|
||||
The game decides what the replacement items will be.
|
||||
"""
|
||||
verify_item_name = True
|
||||
display_name = "Start Inventory from Pool"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class StartHints(ItemSet):
|
||||
"""Start with these item's locations prefilled into the !hint command."""
|
||||
"""Start with these item's locations prefilled into the ``!hint`` command."""
|
||||
display_name = "Start Hints"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class LocationSet(OptionSet):
|
||||
@@ -1242,28 +1270,33 @@ class LocationSet(OptionSet):
|
||||
|
||||
|
||||
class StartLocationHints(LocationSet):
|
||||
"""Start with these locations and their item prefilled into the !hint command"""
|
||||
"""Start with these locations and their item prefilled into the ``!hint`` command."""
|
||||
display_name = "Start Location Hints"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class ExcludeLocations(LocationSet):
|
||||
"""Prevent these locations from having an important item"""
|
||||
"""Prevent these locations from having an important item."""
|
||||
display_name = "Excluded Locations"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class PriorityLocations(LocationSet):
|
||||
"""Prevent these locations from having an unimportant item"""
|
||||
"""Prevent these locations from having an unimportant item."""
|
||||
display_name = "Priority Locations"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class DeathLink(Toggle):
|
||||
"""When you die, everyone dies. Of course the reverse is true too."""
|
||||
display_name = "Death Link"
|
||||
rich_text_doc = True
|
||||
|
||||
|
||||
class ItemLinks(OptionList):
|
||||
"""Share part of your item pool with other players."""
|
||||
display_name = "Item Links"
|
||||
rich_text_doc = True
|
||||
default = []
|
||||
schema = Schema([
|
||||
{
|
||||
@@ -1330,6 +1363,7 @@ class ItemLinks(OptionList):
|
||||
|
||||
class Removed(FreeText):
|
||||
"""This Option has been Removed."""
|
||||
rich_text_doc = True
|
||||
default = ""
|
||||
visibility = Visibility.none
|
||||
|
||||
@@ -1432,14 +1466,18 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||
|
||||
return data, notes
|
||||
|
||||
def yaml_dump_scalar(scalar) -> str:
|
||||
# yaml dump may add end of document marker and newlines.
|
||||
return yaml.dump(scalar).replace("...\n", "").strip()
|
||||
|
||||
for game_name, world in AutoWorldRegister.world_types.items():
|
||||
if not world.hidden or generate_hidden:
|
||||
grouped_options = get_option_groups(world)
|
||||
option_groups = get_option_groups(world)
|
||||
with open(local_path("data", "options.yaml")) as f:
|
||||
file_data = f.read()
|
||||
res = Template(file_data).render(
|
||||
option_groups=grouped_options,
|
||||
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
|
||||
option_groups=option_groups,
|
||||
__version__=__version__, game=game_name, yaml_dump=yaml_dump_scalar,
|
||||
dictify_range=dictify_range,
|
||||
)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
from textwrap import dedent
|
||||
from typing import Dict, Union
|
||||
from docutils.core import publish_parts
|
||||
|
||||
import yaml
|
||||
from flask import redirect, render_template, request, Response
|
||||
@@ -66,6 +67,22 @@ def filter_dedent(text: str) -> str:
|
||||
return dedent(text).strip("\n ")
|
||||
|
||||
|
||||
@app.template_filter("rst_to_html")
|
||||
def filter_rst_to_html(text: str) -> str:
|
||||
"""Converts reStructuredText (such as a Python docstring) to HTML."""
|
||||
if text.startswith(" ") or text.startswith("\t"):
|
||||
text = dedent(text)
|
||||
elif "\n" in text:
|
||||
lines = text.splitlines()
|
||||
text = lines[0] + "\n" + dedent("\n".join(lines[1:]))
|
||||
|
||||
return publish_parts(text, writer_name='html', settings=None, settings_overrides={
|
||||
'raw_enable': False,
|
||||
'file_insertion_enabled': False,
|
||||
'output_encoding': 'unicode'
|
||||
})['body']
|
||||
|
||||
|
||||
@app.template_test("ordered")
|
||||
def test_ordered(obj):
|
||||
return isinstance(obj, collections.abc.Sequence)
|
||||
|
||||
@@ -12,12 +12,12 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
*/
|
||||
|
||||
/* Base styles for the element that has a tooltip */
|
||||
[data-tooltip], .tooltip {
|
||||
[data-tooltip], .tooltip-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Base styles for the entire tooltip */
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after {
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
@@ -39,14 +39,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip:hover:before, .tooltip:hover:after{
|
||||
[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip-container:hover:before,
|
||||
.tooltip-container:hover .tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/** Directional arrow styles */
|
||||
.tooltip:before, [data-tooltip]:before {
|
||||
[data-tooltip]:before, .tooltip-container:before {
|
||||
z-index: 10000;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
@@ -54,7 +55,7 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
}
|
||||
|
||||
/** Content styles */
|
||||
.tooltip:after, [data-tooltip]:after {
|
||||
[data-tooltip]:after, .tooltip {
|
||||
width: 260px;
|
||||
z-index: 10000;
|
||||
padding: 8px;
|
||||
@@ -63,24 +64,26 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
background-color: hsla(0, 0%, 20%, 0.9);
|
||||
color: #fff;
|
||||
content: attr(data-tooltip);
|
||||
white-space: pre-wrap;
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
[data-tooltip]:before, [data-tooltip]:after{
|
||||
[data-tooltip]:after {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip:before, .tooltip:after,
|
||||
.tooltip-top:before, .tooltip-top:after {
|
||||
[data-tooltip]:before, [data-tooltip]:after, .tooltip-container:before, .tooltip {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
[data-tooltip]:before, .tooltip:before, .tooltip-top:before {
|
||||
[data-tooltip]:before, .tooltip-container:before {
|
||||
margin-left: -6px;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #000;
|
||||
@@ -88,19 +91,19 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
}
|
||||
|
||||
/** Horizontally align tooltips on the top and bottom */
|
||||
[data-tooltip]:after, .tooltip:after, .tooltip-top:after {
|
||||
[data-tooltip]:after, .tooltip {
|
||||
margin-left: -80px;
|
||||
}
|
||||
|
||||
[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip:hover:before, .tooltip:hover:after,
|
||||
.tooltip-top:hover:before, .tooltip-top:hover:after {
|
||||
[data-tooltip]:hover:before, [data-tooltip]:hover:after, .tooltip-container:hover:before,
|
||||
.tooltip-container:hover .tooltip {
|
||||
-webkit-transform: translateY(-12px);
|
||||
-moz-transform: translateY(-12px);
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
|
||||
/** Tooltips on the left */
|
||||
.tooltip-left:before, .tooltip-left:after {
|
||||
.tooltip-left:before, [data-tooltip].tooltip-left:after, .tooltip-left .tooltip {
|
||||
right: 100%;
|
||||
bottom: 50%;
|
||||
left: auto;
|
||||
@@ -115,14 +118,14 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
border-left-color: hsla(0, 0%, 20%, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-left:hover:before, .tooltip-left:hover:after {
|
||||
.tooltip-left:hover:before, [data-tooltip].tooltip-left:hover:after, .tooltip-left:hover .tooltip {
|
||||
-webkit-transform: translateX(-12px);
|
||||
-moz-transform: translateX(-12px);
|
||||
transform: translateX(-12px);
|
||||
}
|
||||
|
||||
/** Tooltips on the bottom */
|
||||
.tooltip-bottom:before, .tooltip-bottom:after {
|
||||
.tooltip-bottom:before, [data-tooltip].tooltip-bottom:after, .tooltip-bottom .tooltip {
|
||||
top: 100%;
|
||||
bottom: auto;
|
||||
left: 50%;
|
||||
@@ -136,14 +139,15 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
border-bottom-color: hsla(0, 0%, 20%, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-bottom:hover:before, .tooltip-bottom:hover:after {
|
||||
.tooltip-bottom:hover:before, [data-tooltip].tooltip-bottom:hover:after,
|
||||
.tooltip-bottom:hover .tooltip {
|
||||
-webkit-transform: translateY(12px);
|
||||
-moz-transform: translateY(12px);
|
||||
transform: translateY(12px);
|
||||
}
|
||||
|
||||
/** Tooltips on the right */
|
||||
.tooltip-right:before, .tooltip-right:after {
|
||||
.tooltip-right:before, [data-tooltip].tooltip-right:after, .tooltip-right .tooltip {
|
||||
bottom: 50%;
|
||||
left: 100%;
|
||||
}
|
||||
@@ -156,7 +160,8 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
border-right-color: hsla(0, 0%, 20%, 0.9);
|
||||
}
|
||||
|
||||
.tooltip-right:hover:before, .tooltip-right:hover:after {
|
||||
.tooltip-right:hover:before, [data-tooltip].tooltip-right:hover:after,
|
||||
.tooltip-right:hover .tooltip {
|
||||
-webkit-transform: translateX(12px);
|
||||
-moz-transform: translateX(12px);
|
||||
transform: translateX(12px);
|
||||
@@ -168,7 +173,16 @@ give it one of the following classes: tooltip-left, tooltip-right, tooltip-top,
|
||||
}
|
||||
|
||||
/** Center content vertically for tooltips ont he left and right */
|
||||
.tooltip-left:after, .tooltip-right:after {
|
||||
[data-tooltip].tooltip-left:after, [data-tooltip].tooltip-right:after,
|
||||
.tooltip-left .tooltip, .tooltip-right .tooltip {
|
||||
margin-left: 0;
|
||||
margin-bottom: -16px;
|
||||
}
|
||||
|
||||
.tooltip ul, .tooltip ol {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.tooltip :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro ItemDict(option_name, option, world) %}
|
||||
{% macro ItemDict(option_name, option) %}
|
||||
{{ OptionTitle(option_name, option) }}
|
||||
<div class="option-container">
|
||||
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %}
|
||||
@@ -135,7 +135,7 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro LocationSet(option_name, option, world) %}
|
||||
{% macro LocationSet(option_name, option) %}
|
||||
{{ OptionTitle(option_name, option) }}
|
||||
<div class="option-container">
|
||||
{% for group_name in world.location_name_groups.keys()|sort %}
|
||||
@@ -158,7 +158,7 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro ItemSet(option_name, option, world) %}
|
||||
{% macro ItemSet(option_name, option) %}
|
||||
{{ OptionTitle(option_name, option) }}
|
||||
<div class="option-container">
|
||||
{% for group_name in world.item_name_groups.keys()|sort %}
|
||||
@@ -196,7 +196,18 @@
|
||||
{% macro OptionTitle(option_name, option) %}
|
||||
<label for="{{ option_name }}">
|
||||
{{ option.display_name|default(option_name) }}:
|
||||
<span class="interactive" data-tooltip="{{(option.__doc__ | default("Please document me!"))|replace('\n ', '\n')|escape|trim}}">(?)</span>
|
||||
<span
|
||||
class="interactive tooltip-container"
|
||||
{% if not (option.rich_text_doc | default(world.web.rich_text_options_doc, true)) %}
|
||||
data-tooltip="{{(option.__doc__ | default("Please document me!"))|replace('\n ', '\n')|escape|trim}}"
|
||||
{% endif %}>
|
||||
(?)
|
||||
{% if option.rich_text_doc | default(world.web.rich_text_options_doc, true) %}
|
||||
<div class="tooltip">
|
||||
{{ option.__doc__ | default("**Please document me!**") | rst_to_html | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</span>
|
||||
</label>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'pageWrapper.html' %}
|
||||
{% import 'playerOptions/macros.html' as inputs %}
|
||||
{% import 'playerOptions/macros.html' as inputs with context %}
|
||||
|
||||
{% block head %}
|
||||
<title>{{ world_name }} Options</title>
|
||||
@@ -94,16 +94,16 @@
|
||||
{{ inputs.FreeText(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.ItemDict) and option.verify_item_name %}
|
||||
{{ inputs.ItemDict(option_name, option, world) }}
|
||||
{{ inputs.ItemDict(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.OptionList) and option.valid_keys %}
|
||||
{{ inputs.OptionList(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.LocationSet) and option.verify_location_name %}
|
||||
{{ inputs.LocationSet(option_name, option, world) }}
|
||||
{{ inputs.LocationSet(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.ItemSet) and option.verify_item_name %}
|
||||
{{ inputs.ItemSet(option_name, option, world) }}
|
||||
{{ inputs.ItemSet(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.OptionSet) and option.valid_keys %}
|
||||
{{ inputs.OptionSet(option_name, option) }}
|
||||
@@ -134,16 +134,16 @@
|
||||
{{ inputs.FreeText(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.ItemDict) and option.verify_item_name %}
|
||||
{{ inputs.ItemDict(option_name, option, world) }}
|
||||
{{ inputs.ItemDict(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.OptionList) and option.valid_keys %}
|
||||
{{ inputs.OptionList(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.LocationSet) and option.verify_location_name %}
|
||||
{{ inputs.LocationSet(option_name, option, world) }}
|
||||
{{ inputs.LocationSet(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.ItemSet) and option.verify_item_name %}
|
||||
{{ inputs.ItemSet(option_name, option, world) }}
|
||||
{{ inputs.ItemSet(option_name, option) }}
|
||||
|
||||
{% elif issubclass(option, Options.OptionSet) and option.valid_keys %}
|
||||
{{ inputs.OptionSet(option_name, option) }}
|
||||
|
||||
@@ -68,21 +68,21 @@ requires:
|
||||
|
||||
{%- elif option.options -%}
|
||||
{%- for suboption_option_id, sub_option_name in option.name_lookup.items() %}
|
||||
{{ sub_option_name }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %}
|
||||
{{ yaml_dump(sub_option_name) }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %}
|
||||
{%- endfor -%}
|
||||
|
||||
{%- if option.name_lookup[option.default] not in option.options %}
|
||||
{{ option.default }}: 50
|
||||
{{ yaml_dump(option.default) }}: 50
|
||||
{%- endif -%}
|
||||
|
||||
{%- elif option.default is string %}
|
||||
{{ option.default }}: 50
|
||||
{{ yaml_dump(option.default) }}: 50
|
||||
|
||||
{%- elif option.default is iterable and option.default is not mapping %}
|
||||
{{ option.default | list }}
|
||||
|
||||
{%- else %}
|
||||
{{ yaml_dump(option.default) | trim | indent(4, first=false) }}
|
||||
{{ yaml_dump(option.default) | indent(4, first=false) }}
|
||||
{%- endif -%}
|
||||
{{ "\n" }}
|
||||
{%- endfor %}
|
||||
|
||||
@@ -85,6 +85,50 @@ class ExampleWorld(World):
|
||||
options: ExampleGameOptions
|
||||
```
|
||||
|
||||
### Option Documentation
|
||||
|
||||
Options' [docstrings] are used as their user-facing documentation. They're displayed on the WebHost setup page when a
|
||||
user hovers over the yellow "(?)" icon, and included in the YAML templates generated for each game.
|
||||
|
||||
[docstrings]: /docs/world%20api.md#docstrings
|
||||
|
||||
The WebHost can display Option documentation either as plain text with all whitespace preserved (other than the base
|
||||
indentation), or as HTML generated from the standard Python [reStructuredText] format. Although plain text is the
|
||||
default for backwards compatibility, world authors are encouraged to write their Option documentation as
|
||||
reStructuredText and enable rich text rendering by setting `World.rich_text_options_doc = True`.
|
||||
|
||||
[reStructuredText]: https://docutils.sourceforge.io/rst.html
|
||||
|
||||
```python
|
||||
from worlds.AutoWorld import WebWorld
|
||||
|
||||
|
||||
class ExampleWebWorld(WebWorld):
|
||||
# Render all this world's options as rich text.
|
||||
rich_text_options_doc = True
|
||||
```
|
||||
|
||||
You can set a single option to use rich or plain text by setting
|
||||
`Option.rich_text_doc`.
|
||||
|
||||
```python
|
||||
from Options import Toggle, Range, Choice, PerGameCommonOptions
|
||||
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""Sets overall game difficulty.
|
||||
|
||||
- **Easy:** All enemies die in one hit.
|
||||
- **Normal:** Enemies and the player both have normal health bars.
|
||||
- **Hard:** The player dies in one hit."""
|
||||
display_name = "Difficulty"
|
||||
rich_text_doc = True
|
||||
option_easy = 0
|
||||
option_normal = 1
|
||||
option_hard = 2
|
||||
default = 1
|
||||
```
|
||||
|
||||
### Option Groups
|
||||
Options may be categorized into groups for display on the WebHost. Option groups are displayed in the order specified
|
||||
by your world on the player-options and weighted-options pages. In the generated template files, there will be a comment
|
||||
|
||||
@@ -56,6 +56,12 @@ webhost:
|
||||
|
||||
* `options_page` can be changed to a link instead of an AP-generated options page.
|
||||
|
||||
* `rich_text_options_doc` controls whether [Option documentation] uses plain text (`False`) or rich text (`True`). It
|
||||
defaults to `False`, but world authors are encouraged to set it to `True` for nicer-looking documentation that looks
|
||||
good on both the WebHost and the YAML template.
|
||||
|
||||
[Option documentation]: /docs/options%20api.md#option-documentation
|
||||
|
||||
* `theme` to be used for your game-specific AP pages. Available themes:
|
||||
|
||||
| dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone |
|
||||
|
||||
@@ -223,6 +223,21 @@ class WebWorld(metaclass=WebWorldRegister):
|
||||
option_groups: ClassVar[List[OptionGroup]] = []
|
||||
"""Ordered list of option groupings. Any options not set in a group will be placed in a pre-built "Game Options"."""
|
||||
|
||||
rich_text_options_doc = False
|
||||
"""Whether the WebHost should render Options' docstrings as rich text.
|
||||
|
||||
If this is True, Options' docstrings are interpreted as reStructuredText_,
|
||||
the standard Python markup format. In the WebHost, they're rendered to HTML
|
||||
so that lists, emphasis, and other rich text features are displayed
|
||||
properly.
|
||||
|
||||
If this is False, the docstrings are instead interpreted as plain text, and
|
||||
displayed as-is on the WebHost with whitespace preserved. For backwards
|
||||
compatibility, this is the default.
|
||||
|
||||
.. _reStructuredText: https://docutils.sourceforge.io/rst.html
|
||||
"""
|
||||
|
||||
location_descriptions: Dict[str, str] = {}
|
||||
"""An optional map from location names (or location group names) to brief descriptions for users."""
|
||||
|
||||
|
||||
@@ -128,3 +128,4 @@ from .AutoWorld import AutoWorldRegister
|
||||
network_data_package: DataPackage = {
|
||||
"games": {world_name: world.get_data_package_data() for world_name, world in AutoWorldRegister.world_types.items()},
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from .regions import create_regions
|
||||
|
||||
class LingoWebWorld(WebWorld):
|
||||
option_groups = lingo_option_groups
|
||||
rich_text_options_doc = True
|
||||
theme = "grass"
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
|
||||
@@ -9,8 +9,12 @@ from .items import TRAP_ITEMS
|
||||
|
||||
class ShuffleDoors(Choice):
|
||||
"""If on, opening doors will require their respective "keys".
|
||||
In "simple", doors are sorted into logical groups, which are all opened by receiving an item.
|
||||
In "complex", the items are much more granular, and will usually only open a single door each."""
|
||||
|
||||
- **Simple:** Doors are sorted into logical groups, which are all opened by
|
||||
receiving an item.
|
||||
- **Complex:** The items are much more granular, and will usually only open
|
||||
a single door each.
|
||||
"""
|
||||
display_name = "Shuffle Doors"
|
||||
option_none = 0
|
||||
option_simple = 1
|
||||
@@ -19,24 +23,37 @@ class ShuffleDoors(Choice):
|
||||
|
||||
class ProgressiveOrangeTower(DefaultOnToggle):
|
||||
"""When "Shuffle Doors" is on, this setting governs the manner in which the Orange Tower floors open up.
|
||||
If off, there is an item for each floor of the tower, and each floor's item is the only one needed to access that floor.
|
||||
If on, there are six progressive items, which open up the tower from the bottom floor upward.
|
||||
|
||||
- **Off:** There is an item for each floor of the tower, and each floor's
|
||||
item is the only one needed to access that floor.
|
||||
- **On:** There are six progressive items, which open up the tower from the
|
||||
bottom floor upward.
|
||||
"""
|
||||
display_name = "Progressive Orange Tower"
|
||||
|
||||
|
||||
class ProgressiveColorful(DefaultOnToggle):
|
||||
"""When "Shuffle Doors" is on "complex", this setting governs the manner in which The Colorful opens up.
|
||||
If off, there is an item for each room of The Colorful, meaning that random rooms in the middle of the sequence can open up without giving you access to them.
|
||||
If on, there are ten progressive items, which open up the sequence from White forward."""
|
||||
|
||||
- **Off:** There is an item for each room of The Colorful, meaning that
|
||||
random rooms in the middle of the sequence can open up without giving you
|
||||
access to them.
|
||||
- **On:** There are ten progressive items, which open up the sequence from
|
||||
White forward.
|
||||
"""
|
||||
display_name = "Progressive Colorful"
|
||||
|
||||
|
||||
class LocationChecks(Choice):
|
||||
"""Determines what locations are available.
|
||||
On "normal", there will be a location check for each panel set that would ordinarily open a door, as well as for achievement panels and a small handful of other panels.
|
||||
On "reduced", many of the locations that are associated with opening doors are removed.
|
||||
On "insanity", every individual panel in the game is a location check."""
|
||||
|
||||
- **Normal:** There will be a location check for each panel set that would
|
||||
ordinarily open a door, as well as for achievement panels and a small
|
||||
handful of other panels.
|
||||
- **Reduced:** Many of the locations that are associated with opening doors
|
||||
are removed.
|
||||
- **Insanity:** Every individual panel in the game is a location check.
|
||||
"""
|
||||
display_name = "Location Checks"
|
||||
option_normal = 0
|
||||
option_reduced = 1
|
||||
@@ -44,16 +61,20 @@ class LocationChecks(Choice):
|
||||
|
||||
|
||||
class ShuffleColors(DefaultOnToggle):
|
||||
"""
|
||||
If on, an item is added to the pool for every puzzle color (besides White).
|
||||
You will need to unlock the requisite colors in order to be able to solve puzzles of that color.
|
||||
"""If on, an item is added to the pool for every puzzle color (besides White).
|
||||
|
||||
You will need to unlock the requisite colors in order to be able to solve
|
||||
puzzles of that color.
|
||||
"""
|
||||
display_name = "Shuffle Colors"
|
||||
|
||||
|
||||
class ShufflePanels(Choice):
|
||||
"""If on, the puzzles on each panel are randomized.
|
||||
On "rearrange", the puzzles are the same as the ones in the base game, but are placed in different areas."""
|
||||
|
||||
On "rearrange", the puzzles are the same as the ones in the base game, but
|
||||
are placed in different areas.
|
||||
"""
|
||||
display_name = "Shuffle Panels"
|
||||
option_none = 0
|
||||
option_rearrange = 1
|
||||
@@ -66,22 +87,26 @@ class ShufflePaintings(Toggle):
|
||||
|
||||
class EnablePilgrimage(Toggle):
|
||||
"""Determines how the pilgrimage works.
|
||||
If on, you are required to complete a pilgrimage in order to access the Pilgrim Antechamber.
|
||||
If off, the pilgrimage will be deactivated, and the sun painting will be added to the pool, even if door shuffle is off."""
|
||||
|
||||
- **On:** You are required to complete a pilgrimage in order to access the
|
||||
Pilgrim Antechamber.
|
||||
- **Off:** The pilgrimage will be deactivated, and the sun painting will be
|
||||
added to the pool, even if door shuffle is off.
|
||||
"""
|
||||
display_name = "Enable Pilgrimage"
|
||||
|
||||
|
||||
class PilgrimageAllowsRoofAccess(DefaultOnToggle):
|
||||
"""
|
||||
If on, you may use the Crossroads roof access during a pilgrimage (and you may be expected to do so).
|
||||
"""If on, you may use the Crossroads roof access during a pilgrimage (and you may be expected to do so).
|
||||
|
||||
Otherwise, pilgrimage will be deactivated when going up the stairs.
|
||||
"""
|
||||
display_name = "Allow Roof Access for Pilgrimage"
|
||||
|
||||
|
||||
class PilgrimageAllowsPaintings(DefaultOnToggle):
|
||||
"""
|
||||
If on, you may use paintings during a pilgrimage (and you may be expected to do so).
|
||||
"""If on, you may use paintings during a pilgrimage (and you may be expected to do so).
|
||||
|
||||
Otherwise, pilgrimage will be deactivated when going through a painting.
|
||||
"""
|
||||
display_name = "Allow Paintings for Pilgrimage"
|
||||
@@ -89,11 +114,17 @@ class PilgrimageAllowsPaintings(DefaultOnToggle):
|
||||
|
||||
class SunwarpAccess(Choice):
|
||||
"""Determines how access to sunwarps works.
|
||||
On "normal", all sunwarps are enabled from the start.
|
||||
On "disabled", all sunwarps are disabled. Pilgrimage must be disabled when this is used.
|
||||
On "unlock", sunwarps start off disabled, and all six activate once you receive an item.
|
||||
On "individual", sunwarps start off disabled, and each has a corresponding item that unlocks it.
|
||||
On "progressive", sunwarps start off disabled, and they unlock in order using a progressive item."""
|
||||
|
||||
- **Normal:** All sunwarps are enabled from the start.
|
||||
- **Disabled:** All sunwarps are disabled. Pilgrimage must be disabled when
|
||||
this is used.
|
||||
- **Unlock:** Sunwarps start off disabled, and all six activate once you
|
||||
receive an item.
|
||||
- **Individual:** Sunwarps start off disabled, and each has a corresponding
|
||||
item that unlocks it.
|
||||
- **Progressive:** Sunwarps start off disabled, and they unlock in order
|
||||
using a progressive item.
|
||||
"""
|
||||
display_name = "Sunwarp Access"
|
||||
option_normal = 0
|
||||
option_disabled = 1
|
||||
@@ -109,10 +140,16 @@ class ShuffleSunwarps(Toggle):
|
||||
|
||||
class VictoryCondition(Choice):
|
||||
"""Change the victory condition.
|
||||
On "the_end", the goal is to solve THE END at the top of the tower.
|
||||
On "the_master", the goal is to solve THE MASTER at the top of the tower, after getting the number of achievements specified in the Mastery Achievements option.
|
||||
On "level_2", the goal is to solve LEVEL 2 in the second room, after solving the number of panels specified in the Level 2 Requirement option.
|
||||
On "pilgrimage", the goal is to solve PILGRIM in the Pilgrim Antechamber, typically after performing a Pilgrimage."""
|
||||
|
||||
- **The End:** the goal is to solve THE END at the top of the tower.
|
||||
- **The Master:** The goal is to solve THE MASTER at the top of the tower,
|
||||
after getting the number of achievements specified in the Mastery
|
||||
Achievements option.
|
||||
- **Level 2:** The goal is to solve LEVEL 2 in the second room, after
|
||||
solving the number of panels specified in the Level 2 Requirement option.
|
||||
- **Pilgrimage:** The goal is to solve PILGRIM in the Pilgrim Antechamber,
|
||||
typically after performing a Pilgrimage.
|
||||
"""
|
||||
display_name = "Victory Condition"
|
||||
option_the_end = 0
|
||||
option_the_master = 1
|
||||
@@ -122,9 +159,12 @@ class VictoryCondition(Choice):
|
||||
|
||||
class MasteryAchievements(Range):
|
||||
"""The number of achievements required to unlock THE MASTER.
|
||||
In the base game, 21 achievements are needed.
|
||||
If you include The Scientific and The Unchallenged, which are in the base game but are not counted for mastery, 23 would be required.
|
||||
If you include the custom achievement (The Wanderer), 24 would be required.
|
||||
|
||||
- In the base game, 21 achievements are needed.
|
||||
- If you include The Scientific and The Unchallenged, which are in the base
|
||||
game but are not counted for mastery, 23 would be required.
|
||||
- If you include the custom achievement (The Wanderer), 24 would be
|
||||
required.
|
||||
"""
|
||||
display_name = "Mastery Achievements"
|
||||
range_start = 1
|
||||
@@ -134,9 +174,10 @@ class MasteryAchievements(Range):
|
||||
|
||||
class Level2Requirement(Range):
|
||||
"""The number of panel solves required to unlock LEVEL 2.
|
||||
In the base game, 223 are needed.
|
||||
Note that this count includes ANOTHER TRY.
|
||||
When set to 1, the panel hunt is disabled, and you can access LEVEL 2 for free.
|
||||
|
||||
In the base game, 223 are needed. Note that this count includes ANOTHER TRY.
|
||||
When set to 1, the panel hunt is disabled, and you can access LEVEL 2 for
|
||||
free.
|
||||
"""
|
||||
display_name = "Level 2 Requirement"
|
||||
range_start = 1
|
||||
@@ -145,9 +186,10 @@ class Level2Requirement(Range):
|
||||
|
||||
|
||||
class EarlyColorHallways(Toggle):
|
||||
"""
|
||||
When on, a painting warp to the color hallways area will appear in the starting room.
|
||||
This lets you avoid being trapped in the starting room for long periods of time when door shuffle is on.
|
||||
"""When on, a painting warp to the color hallways area will appear in the starting room.
|
||||
|
||||
This lets you avoid being trapped in the starting room for long periods of
|
||||
time when door shuffle is on.
|
||||
"""
|
||||
display_name = "Early Color Hallways"
|
||||
|
||||
@@ -161,8 +203,8 @@ class TrapPercentage(Range):
|
||||
|
||||
|
||||
class TrapWeights(OptionDict):
|
||||
"""
|
||||
Specify the distribution of traps that should be placed into the pool.
|
||||
"""Specify the distribution of traps that should be placed into the pool.
|
||||
|
||||
If you don't want a specific type of trap, set the weight to zero.
|
||||
"""
|
||||
display_name = "Trap Weights"
|
||||
|
||||
Reference in New Issue
Block a user