diff --git a/Options.py b/Options.py
index cec6b054..f8c19edd 100644
--- a/Options.py
+++ b/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
diff --git a/WebHostLib/options.py b/WebHostLib/options.py
index 53c3a615..33339daa 100644
--- a/WebHostLib/options.py
+++ b/WebHostLib/options.py
@@ -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)
diff --git a/WebHostLib/static/styles/tooltip.css b/WebHostLib/static/styles/tooltip.css
index 02992b18..dc9026ce 100644
--- a/WebHostLib/static/styles/tooltip.css
+++ b/WebHostLib/static/styles/tooltip.css
@@ -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;
+}
diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html
index 76cf0769..415739b8 100644
--- a/WebHostLib/templates/playerOptions/macros.html
+++ b/WebHostLib/templates/playerOptions/macros.html
@@ -111,7 +111,7 @@
{% endmacro %}
-{% macro ItemDict(option_name, option, world) %}
+{% macro ItemDict(option_name, option) %}
{{ OptionTitle(option_name, option) }}
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %}
@@ -135,7 +135,7 @@
{% for group_name in world.item_name_groups.keys()|sort %}
@@ -196,7 +196,18 @@
{% macro OptionTitle(option_name, option) %}
{% endmacro %}
diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html
index 2506cf96..aeb6e864 100644
--- a/WebHostLib/templates/playerOptions/playerOptions.html
+++ b/WebHostLib/templates/playerOptions/playerOptions.html
@@ -1,5 +1,5 @@
{% extends 'pageWrapper.html' %}
-{% import 'playerOptions/macros.html' as inputs %}
+{% import 'playerOptions/macros.html' as inputs with context %}
{% block head %}
{{ world_name }} Options
@@ -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) }}
diff --git a/docs/options api.md b/docs/options api.md
index cba38323..7e479809 100644
--- a/docs/options api.md
+++ b/docs/options api.md
@@ -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
diff --git a/docs/world api.md b/docs/world api.md
index 37638c3c..756ef3f3 100644
--- a/docs/world api.md
+++ b/docs/world api.md
@@ -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 |
diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py
index bed375cf..b5f0fd17 100644
--- a/worlds/AutoWorld.py
+++ b/worlds/AutoWorld.py
@@ -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."""
diff --git a/worlds/__init__.py b/worlds/__init__.py
index 8d784a5b..bb2fe866 100644
--- a/worlds/__init__.py
+++ b/worlds/__init__.py
@@ -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()},
}
+
diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py
index 302e7e1d..8d6a7fc4 100644
--- a/worlds/lingo/__init__.py
+++ b/worlds/lingo/__init__.py
@@ -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",
diff --git a/worlds/lingo/options.py b/worlds/lingo/options.py
index 1c1f645b..333b3e1e 100644
--- a/worlds/lingo/options.py
+++ b/worlds/lingo/options.py
@@ -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"