#include "connection_dialog.h"
#include "tracker_config.h"
ConnectionDialog::ConnectionDialog()
: wxDialog(nullptr, wxID_ANY, "Connect to Archipelago") {
server_box_ = new wxTextCtrl(
this, -1,
wxString::FromUTF8(GetTrackerConfig().connection_details.ap_server),
wxDefaultPosition, FromDIP(wxSize{300, -1}));
player_box_ = new wxTextCtrl(
this, -1,
wxString::FromUTF8pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */from .generated import data_pb2 as data_pb2
from typing import TYPE_CHECKING, NamedTuple
from .options import VictoryCondition
if TYPE_CHECKING:
from . import Lingo2World
def calculate_letter_histogram(solution: str) -> dict[str, int]:
histogram = dict()
for l in solution:
if l.isalpha():
real_l = l.upper()
histogram[real_l] = min(histogram.get(real_l, 0) + 1, 2)
for free_letter in "HINT":
if histogram.get(free_letter, 0) == 1:
del histogram[free_letter]
return histogram
class AccessRequirements:
items: set[str]
progressives: dict[str, int]
rooms: set[str]
symbols: set[str]
letters: dict[str, int]
# This is an AND of ORs.
or_logic: list[list["AccessRequirements"]]
def __init__(self):
self.items = set()
self.progressives = dict()
self.rooms = set()
self.symbols = set()
self.letters = dict()
self.or_logic = list()
def add_solution(self, solution: str):
histogram = calculate_letter_histogram(solution)
for l, a in histogram.items():
self.letters[l] = max(self.letters.get(l, 0), histogram.get(l))
def merge(self, other: "AccessRequirements"):
for item in other.items:
self.items.add(item)
for item, amount in other.progressives.items():
self.progressives[item] = max(amount, self.progressives.get(item, 0))
for room in other.rooms:
self.rooms.add(room)
for symbol in other.symbols:
self.symbols.add(symbol)
for letter, level in other.letters.items():
self.letters[letter] = max(self.letters.get(letter, 0), level)
for disjunction in other.or_logic:
self.or_logic.append(disjunction)
def __repr__(self):
parts = []
if len(self.items) > 0:
parts.append(f"items={self.items}")
if len(self.progressives) > 0:
parts.append(f"progressives={self.progressives}")
if len(self.rooms) > 0:
parts.append(f"rooms={self.rooms}")
if len(self.symbols) > 0:
parts.append(f"symbols={self.symbols}")
if len(self.letters) > 0:
parts.append(f"letters={self.letters}")
if len(self.or_logic) > 0:
parts.append(f"or_logic={self.or_logic}")
return f"AccessRequirements({", ".join(parts)})"
class PlayerLocation(NamedTuple):
code: int | None
reqs: AccessRequirements
class Lingo2PlayerLogic:
world: "Lingo2World"
locations_by_room: dict[int, list[PlayerLocation]]
event_loc_item_by_room: dict[int, dict[str, str]]
item_by_door: dict[int, tuple[str, int]]
panel_reqs: dict[int, AccessRequirements]
proxy_reqs: dict[int, dict[str, AccessRequirements]]
door_reqs: dict[int, AccessRequirements]
real_items: list[str]
def __init__(self, world: "Lingo2World"):
self.world = world
self.locations_by_room = {}
self.event_loc_item_by_room = {}
self.item_by_door = {}
self.panel_reqs = dict()
self.proxy_reqs = dict()
self.door_reqs = dict()
self.real_items = list()
if self.world.options.shuffle_doors:
for progressive in world.static_logic.objects.progressives:
for i in range(0, len(progressive.doors)):
self.item_by_door[progressive.doors[i]] = (progressive.name, i + 1)
self.real_items.append(progressive.name)
# We iterate through the doors in two parts because it is essential that we determine which doors are shuffled
# before we calculate any access requirements.
for door in world.static_logic.objects.doors:
if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors:
if door.id in self.item_by_door:
continue
door_item_name = self.world.static_logic.get_door_item_name(door)
self.item_by_door[door.id] = (door_item_name, 1)
self.real_items.append(door_item_name)
for door in world.static_logic.objects.doors:
if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]:
self.locations_by_room.setdefault(door.room_id, []).append(PlayerLocation(door.ap_id,
self.get_door_reqs(door.id)))
for letter in world.static_logic.objects.letters:
self.locations_by_room.setdefault(letter.room_id, []).append(PlayerLocation(letter.ap_id,
AccessRequirements()))
letter_name = f"{letter.key.upper()}{'2' if letter.level2 else '1'}"
event_name = f"{letter_name} (Collected)"
self.event_loc_item_by_room.setdefault(letter.room_id, {})[event_name] = letter.key.upper()
if letter.level2:
event_name = f"{letter_name} (Double Collected)"
self.event_loc_item_by_room.setdefault(letter.room_id, {})[event_name] = letter.key.upper()
for mastery in world.static_logic.objects.masteries:
self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id,
AccessRequirements()))
for ending in world.static_logic.objects.endings:
# Don't ever create a location for White Ending. Don't even make an event for it if it's not the victory
# condition, since it is necessarily going to be in the postgame.
if ending.name == "WHITE":
if self.world.options.victory_condition != VictoryCondition.option_white_ending:
continue
else:
self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id,
AccessRequirements()))
event_name = f"{ending.name.capitalize()} Ending (Achieved)"
item_name = event_name
if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name:
item_name = "Victory"
self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name
if self.world.options.keyholder_sanity:
for keyholder in world.static_logic.objects.keyholders:
if keyholder.HasField("key"):
reqs = AccessRequirements()
reqs.letters[keyholder.key.upper()] = 1
self.locations_by_room.setdefault(keyholder.room_id, []).append(PlayerLocation(keyholder.ap_id,
reqs))
def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements:
if answer is None:
if panel_id not in self.panel_reqs:
self.panel_reqs[panel_id] = self.calculate_panel_reqs(panel_id, answer)
return self.panel_reqs.get(panel_id)
else:
if panel_id not in self.proxy_reqs or answer not in self.proxy_reqs.get(panel_id):
self.proxy_reqs.setdefault(panel_id, {})[answer] = self.calculate_panel_reqs(panel_id, answer)
return self.proxy_reqs.get(panel_id).get(answer)
def calculate_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements:
panel = self.world.static_logic.objects.panels[panel_id]
reqs = AccessRequirements()
reqs.rooms.add(self.world.static_logic.get_room_region_name(panel.room_id))
if answer is not None:
reqs.add_solution(answer)
elif len(panel.proxies) > 0:
possibilities = []
for proxy in panel.proxies:
proxy_reqs = AccessRequirements()
proxy_reqs.add_solution(proxy.answer)
possibilities.append(proxy_reqs)
if not any(proxy.answer == panel.answer for proxy in panel.proxies):
proxy_reqs = AccessRequirements()
proxy_reqs.add_solution(panel.answer)
possibilities.append(proxy_reqs)
reqs.or_logic.append(possibilities)
else:
reqs.add_solution(panel.answer)
for symbol in panel.symbols:
reqs.symbols.add(symbol)
if panel.HasField("required_door"):
door_reqs = self.get_door_open_reqs(panel.required_door)
reqs.merge(door_reqs)
if panel.HasField("required_room"):
reqs.rooms.add(self.world.static_logic.get_room_region_name(panel.required_room))
return reqs
# This gets/calculates the requirements described by the door object. This is most notably used as the requirements
# for clearing a location, or opening a door when the door is not shuffled.
def get_door_reqs(self, door_id: int) -> AccessRequirements:
if door_id not in self.door_reqs:
self.door_reqs[door_id] = self.calculate_door_reqs(door_id)
return self.door_reqs.get(door_id)
def calculate_door_reqs(self, door_id: int) -> AccessRequirements:
door = self.world.static_logic.objects.doors[door_id]
reqs = AccessRequirements()
# TODO: lavender_cubes, endings
if not door.HasField("complete_at") or door.complete_at == 0:
for proxy in door.panels:
panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None)
reqs.merge(panel_reqs)
elif door.complete_at == 1:
reqs.or_logic.append([self.get_panel_reqs(proxy.panel,
proxy.answer if proxy.HasField("answer") else None)
for proxy in door.panels])
else:
# TODO: Handle complete_at > 1
pass
if door.HasField("control_center_color"):
# TODO: Logic for ensuring two CC states aren't needed at once.
reqs.rooms.add("Control Center - Main Area")
reqs.add_solution(door.control_center_color)
if door.double_letters:
# TODO: When letter shuffle is on, change this to require any double letter instead.
reqs.rooms.add("The Repetitive - Main Room")
for keyholder_uses in door.keyholders:
key_name = keyholder_uses.key.upper()
if key_name not in reqs.letters:
reqs.letters[key_name] = 1
keyholder = self.world.static_logic.objects.keyholders[keyholder_uses.keyholder]
reqs.rooms.add(self.world.static_logic.get_room_region_name(keyholder.room_id))
for room in door.rooms:
reqs.rooms.add(self.world.static_logic.get_room_region_name(room))
for ending_id in door.endings:
ending = self.world.static_logic.objects.endings[ending_id]
reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)")
for sub_door_id in door.doors:
sub_reqs = self.get_door_open_reqs(sub_door_id)
reqs.merge(sub_reqs)
return reqs
# This gets the requirements to open a door within the world. When a door is shuffled, this means having the item
# that acts as the door's key.
def get_door_open_reqs(self, door_id: int) -> AccessRequirements:
if door_id in self.item_by_door:
reqs = AccessRequirements()
item_name, amount = self.item_by_door.get(door_id)
if amount == 1:
reqs.items.add(item_name)
else:
reqs.progressives[item_name] = amount
return reqs
else:
return self.get_door_reqs(door_id)