2023-11-08 23:35:12 +00:00
|
|
|
"""
|
|
|
|
Archipelago init file for Lingo
|
|
|
|
"""
|
2023-12-03 23:06:11 +00:00
|
|
|
from logging import warning
|
|
|
|
|
2024-07-24 12:34:51 +00:00
|
|
|
from BaseClasses import CollectionState, Item, ItemClassification, Tutorial
|
2024-05-04 06:40:17 +00:00
|
|
|
from Options import OptionError
|
2023-11-08 23:35:12 +00:00
|
|
|
from worlds.AutoWorld import WebWorld, World
|
2024-03-15 08:26:00 +00:00
|
|
|
from .datatypes import Room, RoomEntrance
|
2024-03-22 20:28:41 +00:00
|
|
|
from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, TRAP_ITEMS, LingoItem
|
2024-03-21 15:46:53 +00:00
|
|
|
from .locations import ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP
|
2024-07-25 21:09:37 +00:00
|
|
|
from .options import LingoOptions, lingo_option_groups, SunwarpAccess, VictoryCondition
|
2023-11-08 23:35:12 +00:00
|
|
|
from .player_logic import LingoPlayerLogic
|
|
|
|
from .regions import create_regions
|
|
|
|
|
|
|
|
|
|
|
|
class LingoWebWorld(WebWorld):
|
2024-05-23 00:22:39 +00:00
|
|
|
option_groups = lingo_option_groups
|
2024-06-14 22:53:42 +00:00
|
|
|
rich_text_options_doc = True
|
2023-11-08 23:35:12 +00:00
|
|
|
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()
|
|
|
|
}
|
2024-03-21 15:46:53 +00:00
|
|
|
item_name_groups = ITEMS_BY_GROUP
|
|
|
|
location_name_groups = LOCATIONS_BY_GROUP
|
2023-11-08 23:35:12 +00:00
|
|
|
|
|
|
|
player_logic: LingoPlayerLogic
|
|
|
|
|
|
|
|
def generate_early(self):
|
2024-07-25 21:09:37 +00:00
|
|
|
if not (self.options.shuffle_doors or self.options.shuffle_colors or
|
|
|
|
(self.options.sunwarp_access >= SunwarpAccess.option_unlock and
|
|
|
|
self.options.victory_condition == VictoryCondition.option_pilgrimage)):
|
2023-12-03 23:06:11 +00:00
|
|
|
if self.multiworld.players == 1:
|
2024-07-25 21:09:37 +00:00
|
|
|
warning(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on Door"
|
|
|
|
f" Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage victory condition"
|
|
|
|
f" if that doesn't seem right.")
|
2023-12-03 23:06:11 +00:00
|
|
|
else:
|
2024-07-25 21:09:37 +00:00
|
|
|
raise OptionError(f"{self.player_name}'s Lingo world doesn't have any progression items. Please turn on"
|
|
|
|
f" Door Shuffle or Color Shuffle, or use item-blocked sunwarps with the Pilgrimage"
|
|
|
|
f" victory condition.")
|
2023-12-03 23:06:11 +00:00
|
|
|
|
2023-11-08 23:35:12 +00:00
|
|
|
self.player_logic = LingoPlayerLogic(self)
|
|
|
|
|
|
|
|
def create_regions(self):
|
2024-04-13 22:20:31 +00:00
|
|
|
create_regions(self)
|
2023-11-08 23:35:12 +00:00
|
|
|
|
2024-07-24 12:34:51 +00:00
|
|
|
if not self.options.shuffle_postgame:
|
|
|
|
state = CollectionState(self.multiworld)
|
|
|
|
state.collect(LingoItem("Prevent Victory", ItemClassification.progression, None, self.player), True)
|
|
|
|
|
|
|
|
# Note: relies on the assumption that real_items is a definitive list of real progression items in this
|
|
|
|
# world, and is not modified after being created.
|
|
|
|
for item in self.player_logic.real_items:
|
|
|
|
state.collect(self.create_item(item), True)
|
|
|
|
|
|
|
|
# Exception to the above: a forced good item is not considered a "real item", but needs to be here anyway.
|
|
|
|
if self.player_logic.forced_good_item != "":
|
|
|
|
state.collect(self.create_item(self.player_logic.forced_good_item), True)
|
|
|
|
|
|
|
|
all_locations = self.multiworld.get_locations(self.player)
|
|
|
|
state.sweep_for_events(locations=all_locations)
|
|
|
|
|
|
|
|
unreachable_locations = [location for location in all_locations
|
|
|
|
if not state.can_reach_location(location.name, self.player)]
|
|
|
|
|
|
|
|
for location in unreachable_locations:
|
|
|
|
if location.name in self.player_logic.event_loc_to_item.keys():
|
|
|
|
continue
|
|
|
|
|
|
|
|
self.player_logic.real_locations.remove(location.name)
|
|
|
|
location.parent_region.locations.remove(location)
|
|
|
|
|
|
|
|
if len(self.player_logic.real_items) > len(self.player_logic.real_locations):
|
|
|
|
raise OptionError(f"{self.player_name}'s Lingo world does not have enough locations to fit the number"
|
|
|
|
f" of required items without shuffling the postgame. Either enable postgame"
|
|
|
|
f" shuffling, or choose different options.")
|
|
|
|
|
2023-11-08 23:35:12 +00:00
|
|
|
def create_items(self):
|
2023-11-25 12:09:08 +00:00
|
|
|
pool = [self.create_item(name) for name in self.player_logic.real_items]
|
2023-11-08 23:35:12 +00:00
|
|
|
|
2023-11-25 12:09:08 +00:00
|
|
|
if self.player_logic.forced_good_item != "":
|
|
|
|
new_item = self.create_item(self.player_logic.forced_good_item)
|
2023-11-08 23:35:12 +00:00
|
|
|
location_obj = self.multiworld.get_location("Second Room - Good Luck", self.player)
|
|
|
|
location_obj.place_locked_item(new_item)
|
|
|
|
|
2023-11-25 12:09:08 +00:00
|
|
|
item_difference = len(self.player_logic.real_locations) - len(pool)
|
2023-11-08 23:35:12 +00:00
|
|
|
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):
|
2024-01-31 06:56:35 +00:00
|
|
|
pool.append(self.create_item(self.get_filler_item_name()))
|
2023-11-08 23:35:12 +00:00
|
|
|
|
|
|
|
for i in range(0, skips):
|
|
|
|
pool.append(self.create_item("Puzzle Skip"))
|
|
|
|
|
|
|
|
if traps:
|
2024-03-22 20:28:41 +00:00
|
|
|
total_weight = sum(self.options.trap_weights.values())
|
|
|
|
|
|
|
|
if total_weight == 0:
|
2024-05-04 06:40:17 +00:00
|
|
|
raise OptionError("Sum of trap weights must be at least one.")
|
2024-03-22 20:28:41 +00:00
|
|
|
|
|
|
|
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))
|
2023-11-08 23:35:12 +00:00
|
|
|
|
|
|
|
self.multiworld.itempool += pool
|
|
|
|
|
|
|
|
def create_item(self, name: str) -> Item:
|
|
|
|
item = ALL_ITEM_TABLE[name]
|
2023-11-24 16:30:15 +00:00
|
|
|
|
|
|
|
classification = item.classification
|
2024-03-15 08:26:00 +00:00
|
|
|
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) \
|
Lingo: Fix entrance checking being broken on default settings (#2506)
The most serious issue this PR addresses is that entrances that use doors without items (a small subset of doors when door shuffle is on, but *every* door when door shuffle is off, which is the default) underestimate the requirements needed to use that entrance. The logic would calculate the panels needed to open the door, but would neglect to keep track of the rooms those panels were in, meaning that doors would be considered openable if you had the colors needed to solve a panel that's in a room you have no access to.
Another issue is that, previously, logic would always consider the "ANOTHER TRY" panel accessible for the purposes of the LEVEL 2 panel hunt. This could result in seeds where the player is expected to have exactly the correct number of solves to reach LEVEL 2, but in reality is short by one because ANOTHER TRY itself is not revealed until the panel hunt is complete. This change marks ANOTHER TRY as non-counting, because even though it is technically a counting panel in-game, it can never contribute to the LEVEL 2 panel hunt. This issue could also apply to THE MASTER, since it is the only other counting panel with special access rules, although it is much less likely. This change adds special handling for counting THE MASTER. These issues were possible to manifest whenever the LEVEL 2 panel hunt was enabled, which it is by default.
Smaller logic issues also fixed in this PR:
* The Orange Tower Basement MASTERY panel was marked as requiring the mastery doors to be opened, when it was actually possible to get it without them by using a painting to get into the room.
* The Pilgrim Room painting item was incorrectly being marked as a filler item, despite it being progression.
* There has been another update to the game that adds connections between areas that were previously not connected. These changes were additive, which is why they are not critical.
* The panel stacks in the rhyme room now require both colours on each panel.
2023-12-10 18:15:42 +00:00
|
|
|
and "pilgrim_painting2" not in item.painting_ids:
|
2023-11-24 16:30:15 +00:00
|
|
|
# If this is a "door" that just moves one or more paintings, and painting shuffle is on and those paintings
|
Lingo: Fix entrance checking being broken on default settings (#2506)
The most serious issue this PR addresses is that entrances that use doors without items (a small subset of doors when door shuffle is on, but *every* door when door shuffle is off, which is the default) underestimate the requirements needed to use that entrance. The logic would calculate the panels needed to open the door, but would neglect to keep track of the rooms those panels were in, meaning that doors would be considered openable if you had the colors needed to solve a panel that's in a room you have no access to.
Another issue is that, previously, logic would always consider the "ANOTHER TRY" panel accessible for the purposes of the LEVEL 2 panel hunt. This could result in seeds where the player is expected to have exactly the correct number of solves to reach LEVEL 2, but in reality is short by one because ANOTHER TRY itself is not revealed until the panel hunt is complete. This change marks ANOTHER TRY as non-counting, because even though it is technically a counting panel in-game, it can never contribute to the LEVEL 2 panel hunt. This issue could also apply to THE MASTER, since it is the only other counting panel with special access rules, although it is much less likely. This change adds special handling for counting THE MASTER. These issues were possible to manifest whenever the LEVEL 2 panel hunt was enabled, which it is by default.
Smaller logic issues also fixed in this PR:
* The Orange Tower Basement MASTERY panel was marked as requiring the mastery doors to be opened, when it was actually possible to get it without them by using a painting to get into the room.
* The Pilgrim Room painting item was incorrectly being marked as a filler item, despite it being progression.
* There has been another update to the game that adds connections between areas that were previously not connected. These changes were additive, which is why they are not critical.
* The panel stacks in the rhyme room now require both colours on each panel.
2023-12-10 18:15:42 +00:00
|
|
|
# go nowhere, then this item should not be progression. The Pilgrim Room painting is special and needs to be
|
|
|
|
# excluded from this.
|
2023-11-24 16:30:15 +00:00
|
|
|
classification = ItemClassification.filler
|
|
|
|
|
|
|
|
return LingoItem(name, classification, item.code, self.player)
|
2023-11-08 23:35:12 +00:00
|
|
|
|
|
|
|
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",
|
Lingo: The Pilgrim Update (#2884)
* An option was added to enable or disable the pilgrimage, and it defaults to disabled. When disabled, the client will prevent you from performing a pilgrimage (i.e. the yellow border will not appear when you enter the 1 sunwarp). The sun painting is added to the item pool when pilgrimage is disabled, as otherwise there is no way into the Pilgrim Antechamber. Inversely, the sun painting is no longer in the item pool when pilgrimage is enabled (even if door shuffle is on), requiring you to perform a pilgrimage to get to that room.
* The canonical pilgrimage has been deprecated. Instead, there is logic for determining whether a pilgrimage is possible.
* Two options were added that allow the player to decide whether paintings and/or Crossroads - Roof Access are permitted during the pilgrimage. Both default to disabled. These options apply both to logical expectations in the generator, and are also enforced by the game client.
* An option was added to control how sunwarps are accessed. The default is for them to always be accessible, like in the base game. It is also possible to disable them entirely (which is not possible when pilgrimage is enabled), or lock them behind items similar to door shuffle. It can either be one item that unlocks all sunwarps at the same time, six progressive items that unlock the sunwarps from 1 to 6, or six individual items that unlock the sunwarps in any order. This option is independent from door shuffle.
* An option was added that shuffles sunwarps. This acts similarly to painting shuffle. The 12 sunwarps are re-ordered and re-paired. Sunwarps that were previously entrances or exits do not need to stay entrances or exits. Performing a pilgrimage requires proceeding through the sunwarps in the new order, rather than the original one.
* Pilgrimage was added as a win condition. It requires you to solve the blue PILGRIM panel in the Pilgrim Antechamber.
2024-04-18 16:45:33 +00:00
|
|
|
"enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks",
|
2024-07-26 08:53:11 +00:00
|
|
|
"early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps",
|
|
|
|
"group_doors"
|
2023-11-08 23:35:12 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
slot_data = {
|
|
|
|
"seed": self.random.randint(0, 1000000),
|
|
|
|
**self.options.as_dict(*slot_options),
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.options.shuffle_paintings:
|
2023-11-25 12:09:08 +00:00
|
|
|
slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping
|
2023-11-08 23:35:12 +00:00
|
|
|
|
Lingo: The Pilgrim Update (#2884)
* An option was added to enable or disable the pilgrimage, and it defaults to disabled. When disabled, the client will prevent you from performing a pilgrimage (i.e. the yellow border will not appear when you enter the 1 sunwarp). The sun painting is added to the item pool when pilgrimage is disabled, as otherwise there is no way into the Pilgrim Antechamber. Inversely, the sun painting is no longer in the item pool when pilgrimage is enabled (even if door shuffle is on), requiring you to perform a pilgrimage to get to that room.
* The canonical pilgrimage has been deprecated. Instead, there is logic for determining whether a pilgrimage is possible.
* Two options were added that allow the player to decide whether paintings and/or Crossroads - Roof Access are permitted during the pilgrimage. Both default to disabled. These options apply both to logical expectations in the generator, and are also enforced by the game client.
* An option was added to control how sunwarps are accessed. The default is for them to always be accessible, like in the base game. It is also possible to disable them entirely (which is not possible when pilgrimage is enabled), or lock them behind items similar to door shuffle. It can either be one item that unlocks all sunwarps at the same time, six progressive items that unlock the sunwarps from 1 to 6, or six individual items that unlock the sunwarps in any order. This option is independent from door shuffle.
* An option was added that shuffles sunwarps. This acts similarly to painting shuffle. The 12 sunwarps are re-ordered and re-paired. Sunwarps that were previously entrances or exits do not need to stay entrances or exits. Performing a pilgrimage requires proceeding through the sunwarps in the new order, rather than the original one.
* Pilgrimage was added as a win condition. It requires you to solve the blue PILGRIM panel in the Pilgrim Antechamber.
2024-04-18 16:45:33 +00:00
|
|
|
if self.options.shuffle_sunwarps:
|
|
|
|
slot_data["sunwarp_permutation"] = self.player_logic.sunwarp_mapping
|
|
|
|
|
2023-11-08 23:35:12 +00:00
|
|
|
return slot_data
|
2024-01-31 06:56:35 +00:00
|
|
|
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
|
|
filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
|
|
|
|
return self.random.choice(filler_list)
|