mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00

* Render option documentation as reStructuredText in the WebView This means that options can use the standard Python documentation format, while producing much nicer-looking documentation in the WebView with things like emphasis, lists, and so on. * Opt existing worlds out of rich option docs This avoids breaking the rendering of existing option docs which were written with the old plain text rendering in mind, while also allowing new options to default to the rich text rendering instead. * Use reStructuredText formatting for Lingo Options docstrings * Disable raw and file insertion RST directives * Update doc comments per code review * Make rich text docs opt-in * Put rich_text_options_doc on WebWorld * Document rich text API * Code review * Update docs/options api.md Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> * Update Options.py Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> --------- Co-authored-by: Chris Wilson <chris@legendserver.info> Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
158 lines
6.3 KiB
Python
158 lines
6.3 KiB
Python
"""
|
|
Archipelago init file for Lingo
|
|
"""
|
|
from logging import warning
|
|
|
|
from BaseClasses import Item, ItemClassification, Tutorial
|
|
from Options import OptionError
|
|
from worlds.AutoWorld import WebWorld, World
|
|
from .datatypes import Room, RoomEntrance
|
|
from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, TRAP_ITEMS, LingoItem
|
|
from .locations import ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP
|
|
from .options import LingoOptions, lingo_option_groups
|
|
from .player_logic import LingoPlayerLogic
|
|
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",
|
|
"A guide to playing Lingo with Archipelago.",
|
|
"English",
|
|
"setup_en.md",
|
|
"setup/en",
|
|
["hatkirby"]
|
|
)]
|
|
|
|
|
|
class LingoWorld(World):
|
|
"""
|
|
Lingo is a first person indie puzzle game in the vein of The Witness. You find yourself in a mazelike, non-Euclidean
|
|
world filled with 800 word puzzles that use a variety of different mechanics.
|
|
"""
|
|
game = "Lingo"
|
|
web = LingoWebWorld()
|
|
|
|
base_id = 444400
|
|
topology_present = True
|
|
|
|
options_dataclass = LingoOptions
|
|
options: LingoOptions
|
|
|
|
item_name_to_id = {
|
|
name: data.code for name, data in ALL_ITEM_TABLE.items()
|
|
}
|
|
location_name_to_id = {
|
|
name: data.code for name, data in ALL_LOCATION_TABLE.items()
|
|
}
|
|
item_name_groups = ITEMS_BY_GROUP
|
|
location_name_groups = LOCATIONS_BY_GROUP
|
|
|
|
player_logic: LingoPlayerLogic
|
|
|
|
def generate_early(self):
|
|
if not (self.options.shuffle_doors or self.options.shuffle_colors or self.options.shuffle_sunwarps):
|
|
if self.multiworld.players == 1:
|
|
warning(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any progression"
|
|
f" items. Please turn on Door Shuffle, Color Shuffle, or Sunwarp Shuffle if that doesn't seem"
|
|
f" right.")
|
|
else:
|
|
raise OptionError(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any"
|
|
f" progression items. Please turn on Door Shuffle, Color Shuffle or Sunwarp Shuffle.")
|
|
|
|
self.player_logic = LingoPlayerLogic(self)
|
|
|
|
def create_regions(self):
|
|
create_regions(self)
|
|
|
|
def create_items(self):
|
|
pool = [self.create_item(name) for name in self.player_logic.real_items]
|
|
|
|
if self.player_logic.forced_good_item != "":
|
|
new_item = self.create_item(self.player_logic.forced_good_item)
|
|
location_obj = self.multiworld.get_location("Second Room - Good Luck", self.player)
|
|
location_obj.place_locked_item(new_item)
|
|
|
|
item_difference = len(self.player_logic.real_locations) - len(pool)
|
|
if item_difference:
|
|
trap_percentage = self.options.trap_percentage
|
|
traps = int(item_difference * trap_percentage / 100.0)
|
|
non_traps = item_difference - traps
|
|
|
|
if non_traps:
|
|
skip_percentage = self.options.puzzle_skip_percentage
|
|
skips = int(non_traps * skip_percentage / 100.0)
|
|
non_skips = non_traps - skips
|
|
|
|
for i in range(0, non_skips):
|
|
pool.append(self.create_item(self.get_filler_item_name()))
|
|
|
|
for i in range(0, skips):
|
|
pool.append(self.create_item("Puzzle Skip"))
|
|
|
|
if traps:
|
|
total_weight = sum(self.options.trap_weights.values())
|
|
|
|
if total_weight == 0:
|
|
raise OptionError("Sum of trap weights must be at least one.")
|
|
|
|
trap_counts = {name: int(weight * traps / total_weight)
|
|
for name, weight in self.options.trap_weights.items()}
|
|
|
|
trap_difference = traps - sum(trap_counts.values())
|
|
if trap_difference > 0:
|
|
allowed_traps = [name for name in TRAP_ITEMS if self.options.trap_weights[name] > 0]
|
|
for i in range(0, trap_difference):
|
|
trap_counts[allowed_traps[i % len(allowed_traps)]] += 1
|
|
|
|
for name, count in trap_counts.items():
|
|
for i in range(0, count):
|
|
pool.append(self.create_item(name))
|
|
|
|
self.multiworld.itempool += pool
|
|
|
|
def create_item(self, name: str) -> Item:
|
|
item = ALL_ITEM_TABLE[name]
|
|
|
|
classification = item.classification
|
|
if hasattr(self, "options") and self.options.shuffle_paintings and len(item.painting_ids) > 0 \
|
|
and not item.has_doors and all(painting_id not in self.player_logic.painting_mapping
|
|
for painting_id in item.painting_ids) \
|
|
and "pilgrim_painting2" not in item.painting_ids:
|
|
# If this is a "door" that just moves one or more paintings, and painting shuffle is on and those paintings
|
|
# go nowhere, then this item should not be progression. The Pilgrim Room painting is special and needs to be
|
|
# excluded from this.
|
|
classification = ItemClassification.filler
|
|
|
|
return LingoItem(name, classification, item.code, self.player)
|
|
|
|
def set_rules(self):
|
|
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
|
|
|
def fill_slot_data(self):
|
|
slot_options = [
|
|
"death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels",
|
|
"enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks",
|
|
"early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps"
|
|
]
|
|
|
|
slot_data = {
|
|
"seed": self.random.randint(0, 1000000),
|
|
**self.options.as_dict(*slot_options),
|
|
}
|
|
|
|
if self.options.shuffle_paintings:
|
|
slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping
|
|
|
|
if self.options.shuffle_sunwarps:
|
|
slot_data["sunwarp_permutation"] = self.player_logic.sunwarp_mapping
|
|
|
|
return slot_data
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
|
|
return self.random.choice(filler_list)
|