about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README.md88
-rw-r--r--apworld/CHANGELOG.md54
-rw-r--r--apworld/__init__.py20
-rw-r--r--apworld/items.py2
-rw-r--r--apworld/options.py55
-rw-r--r--apworld/player_logic.py127
-rw-r--r--apworld/regions.py47
-rw-r--r--apworld/rules.py35
-rw-r--r--apworld/static_logic.py16
-rw-r--r--apworld/version.py2
-rw-r--r--client/Archipelago/client.gd6
-rw-r--r--client/Archipelago/compass.gd66
-rw-r--r--client/Archipelago/compass_overlay.gd17
-rw-r--r--client/Archipelago/door.gd8
-rw-r--r--client/Archipelago/gamedata.gd7
-rw-r--r--client/Archipelago/keyboard.gd25
-rw-r--r--client/Archipelago/manager.gd36
-rw-r--r--client/Archipelago/messages.gd18
-rw-r--r--client/Archipelago/pauseMenu.gd32
-rw-r--r--client/Archipelago/player.gd148
-rw-r--r--client/Archipelago/saver.gd14
-rw-r--r--client/Archipelago/settings_screen.gd48
-rw-r--r--client/Archipelago/teleport.gd38
-rw-r--r--client/Archipelago/teleportListener.gd11
-rw-r--r--client/Archipelago/worldport.gd10
-rw-r--r--client/Archipelago/worldportListener.gd4
-rw-r--r--client/CHANGELOG.md59
-rw-r--r--client/README.md7
-rw-r--r--client/archipelago.tscn5
-rw-r--r--data/connections.txtpb54
-rw-r--r--data/ids.yaml84
-rw-r--r--data/maps/daedalus/connections.txtpb45
-rw-r--r--data/maps/daedalus/doors.txtpb109
-rw-r--r--data/maps/daedalus/rooms/C Keyholder.txtpb2
-rw-r--r--data/maps/daedalus/rooms/Red Color Door.txtpb2
-rw-r--r--data/maps/daedalus/rooms/Wonderland.txtpb1
-rw-r--r--data/maps/the_ancient/rooms/Outside.txtpb1
-rw-r--r--data/maps/the_bearer/connections.txtpb5
-rw-r--r--data/maps/the_bearer/rooms/Back Area.txtpb6
-rw-r--r--data/maps/the_bearer/rooms/Tree Entrance.txtpb6
-rw-r--r--data/maps/the_darkroom/connections.txtpb15
-rw-r--r--data/maps/the_darkroom/rooms/Congruent Entrance.txtpb7
-rw-r--r--data/maps/the_darkroom/rooms/Cyan Hallway.txtpb7
-rw-r--r--data/maps/the_darkroom/rooms/Double Sided Entrance.txtpb7
-rw-r--r--data/maps/the_darkroom/rooms/First Room.txtpb12
-rw-r--r--data/maps/the_darkroom/rooms/Second Room.txtpb6
-rw-r--r--data/maps/the_entry/connections.txtpb21
-rw-r--r--data/maps/the_entry/doors.txtpb4
-rw-r--r--data/maps/the_entry/rooms/Flipped Second Room.txtpb7
-rw-r--r--data/maps/the_entry/rooms/Four Rooms Entrance.txtpb7
-rw-r--r--data/maps/the_entry/rooms/Liberated Entrance.txtpb6
-rw-r--r--data/maps/the_entry/rooms/Link Area.txtpb12
-rw-r--r--data/maps/the_entry/rooms/Literate Entrance.txtpb6
-rw-r--r--data/maps/the_entry/rooms/Shop Entrance.txtpb2
-rw-r--r--data/maps/the_gallery/doors.txtpb34
-rw-r--r--data/maps/the_great/doors.txtpb2
-rw-r--r--data/maps/the_nuanced/doors.txtpb4
-rw-r--r--data/maps/the_parthenon/connections.txtpb1
-rw-r--r--data/maps/the_plaza/connections.txtpb8
-rw-r--r--data/maps/the_plaza/doors.txtpb16
-rw-r--r--data/maps/the_repetitive/doors.txtpb6
-rw-r--r--data/maps/the_repetitive/metadata.txtpb4
-rw-r--r--data/maps/the_repetitive/rooms/Anti Room.txtpb11
-rw-r--r--data/maps/the_sun_temple/connections.txtpb1
-rw-r--r--data/maps/the_unkempt/connections.txtpb2
-rw-r--r--data/maps/the_unkempt/doors.txtpb2
-rw-r--r--data/metadata.txtpb29
-rw-r--r--proto/data.proto6
-rw-r--r--proto/human.proto16
-rw-r--r--tools/datapacker/main.cpp13
-rw-r--r--tools/validator/human_processor.cpp26
-rw-r--r--tools/validator/structs.h3
-rw-r--r--tools/validator/validator.cpp51
73 files changed, 1419 insertions, 255 deletions
diff --git a/README.md b/README.md index 60dc1b3..1da3c2b 100644 --- a/README.md +++ b/README.md
@@ -9,6 +9,94 @@ This is a project that modifies the game
9[Lingo 2](https://www.lingothegame.com/lingo2.html) so that it can be played as 9[Lingo 2](https://www.lingothegame.com/lingo2.html) so that it can be played as
10part of an Archipelago multiworld game. 10part of an Archipelago multiworld game.
11 11
12## How To Play
13
14Here are the components needed in order to play:
15
16- **Apworld**: This is used by Archipelago to generate randomized Lingo 2
17 worlds.
18 - [Installation & FAQ](https://code.fourisland.com/lingo2-archipelago/about/apworld/README.md)
19 - [Downloads](https://code.fourisland.com/lingo2-archipelago/about/apworld/CHANGELOG.md)
20- **Client**: This is how Lingo 2 connects to an Archipelago multiworld.
21 - [Installation & FAQ](https://code.fourisland.com/lingo2-archipelago/about/client/README.md)
22 - [Downloads](https://code.fourisland.com/lingo2-archipelago/about/client/CHANGELOG.md)
23
24## Frequently Asked Questions
25
26### Why aren't the starting room letters shuffled?
27
28The letter requirements for solving puzzles are very restrictive, especially in
29the early game. It is possible for the generator to find some subset of letters
30and doors to place in the starting room such that you are not trapped, but this
31places a lot of strain on generation and leads to significantly more generation
32failures.
33
34As a result, the starting room letters (H1, I1, N1, and T1) are always present
35in the starting room, even when remote letter shuffle is enabled. These letters
36will _also_ count as clearing a check, so you will send out another item at the
37same time as collecting the letter.
38
39### What areas are randomized?
40
41Almost all maps that you can access from the base game are randomized. The
42exceptions are:
43
44- Icarus (this will be randomized at some point, although it will be optional)
45- Demo
46- The Hinterlands (this will probably be repurposed)
47- The beta tester gift maps
48
49### What about wall snipes?
50
51"Wall sniping" refers to the fact that you are able to solve puzzles on the
52other side of opaque walls. The player is never expected to or required to do
53this in normal gameplay. This randomizer does not change how wall snipes work,
54but it will likewise never require the use of them.
55
56### How do cyan doors work?
57
58In the base game, there are a number of cyan-colored doors that ordinarily open
59once you collect H2 in The Repetitive. There are also a handful of panels that
60only appear upon getting H2 as well, which the apworld treats the same as the
61cyan doors.
62
63There is an option that lets you choose how these doors and panels behave. By
64default, they act the same as in the base game: they only open or appear after
65collecting H2. Note that this means the actual H2 collectable in The Repetitive.
66Receiving H2 via remote letter shuffle does not count for this requirement.
67However, you can also make cyan doors activate upon collecting or receiving your
68first double letter, regardless of what it is or if it's remote. Finally, you
69can lock cyan doors behind an item called "Cyan Doors".
70
71It is important to note, however, that the Cyan Door Behavior option only
72applies to cyan doors that are not already affected by another type of
73shuffling. When door shuffle is on, the following cyan doors are activated by
74individual items and are not impacted by your choice of Cyan Door Behavior:
75
76- The entrance to The Tower from The Great (The Great - Tower Entrance)
77- The entrance to The Butterfly from The Bearer (The Bearer - Butterfly
78 Entrance)
79- The entrance to The Repetitive from The Entry (The Entry - Repetitive
80 Entrance)
81- The eye painting near the yellow color hallway in Daedalus (Daedalus - Eye
82 Painting)
83- The Red I room in The Repetitive (The Repetitive - Anti Collectable Room)
84
85Additionally, when control center color shuffle is enabled, the orange door in
86The Unkempt (which ordinarily doubles as a cyan door) opens upon receiving the
87Control Center Orange Doors item, instead of following the Cyan Door Behavior
88option.
89
90### Help! I lost C/G in The Congruent!
91
92If you place C or G into the relevant keyholders in The Congruent, the keyholder
93disappears. You can retrieve your letter immediately by pressing C or G again
94before leaving solve mode, as the keyholder will still be considered to be
95"focused", even though it has moved. If you have already moved, though, there is
96another way to get your letters back: just use the Key Return in The Entry.
97
98## Project Details
99
12There are multiple parts of this project: 100There are multiple parts of this project:
13 101
14- [Client](https://code.fourisland.com/lingo2-archipelago/about/client/README.md) 102- [Client](https://code.fourisland.com/lingo2-archipelago/about/client/README.md)
diff --git a/apworld/CHANGELOG.md b/apworld/CHANGELOG.md new file mode 100644 index 0000000..af45992 --- /dev/null +++ b/apworld/CHANGELOG.md
@@ -0,0 +1,54 @@
1# lingo2-archipelago Apworld Releases
2
3## v5.5 - 2025-09-16
4
5- Fixed a panel in The Ancient that was missing a symbol.
6- Fixed an issue where you could be expected to get S1 in The Darkroom without
7 having U.
8- Renamed a few locations.
9
10Download:
11[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v5.5/lingo2.apworld)<br/>
12Template YAML:
13[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v5.5/Lingo%202.yaml)<br/>
14Source:
15[v5.5](https://code.fourisland.com/lingo2-archipelago/tag/?h=apworld-v5.5)
16
17## v4.4 - 2025-09-14
18
19- Fixed panel set location names.
20
21Download:
22[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v4.4/lingo2.apworld)<br/>
23Template YAML:
24[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v4.4/Lingo%202.yaml)<br/>
25Source:
26[v4.4](https://code.fourisland.com/lingo2-archipelago/tag/?h=apworld-v4.4)
27
28## v4.3 - 2025-09-13
29
30- Added a location for the anti-collectable in The Repetitive.
31- Added trap items. These remove letters from your keyboard until you use the
32 Key Return in The Entry, similar to the anti-collectable in The Repetitive.
33 This can be controlled using the `trap_percentage` option, which defaults to
34 zero.
35- Fixed crash on load when using Python 3.11.
36
37Download:
38[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v4.3/lingo2.apworld)<br/>
39Template YAML:
40[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v4.3/Lingo%202.yaml)<br/>
41Source:
42[v4.3](https://code.fourisland.com/lingo2-archipelago/tag/?h=apworld-v4.3)
43
44## v3.2 - 2025-09-12
45
46- Initial release for testing. Features include door shuffle, letter shuffle,
47 and symbol shuffle.
48
49Download:
50[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v3.2/lingo2.apworld)<br/>
51Template YAML:
52[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v3.2/Lingo%202.yaml)<br/>
53Source:
54[v3.2](https://code.fourisland.com/lingo2-archipelago/tag/?h=apworld-v3.2)
diff --git a/apworld/__init__.py b/apworld/__init__.py index 4044d76..f1de503 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py
@@ -3,7 +3,7 @@ Archipelago init file for Lingo 2
3""" 3"""
4from BaseClasses import ItemClassification, Item, Tutorial 4from BaseClasses import ItemClassification, Item, Tutorial
5from worlds.AutoWorld import WebWorld, World 5from worlds.AutoWorld import WebWorld, World
6from .items import Lingo2Item 6from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS
7from .options import Lingo2Options 7from .options import Lingo2Options
8from .player_logic import Lingo2PlayerLogic 8from .player_logic import Lingo2PlayerLogic
9from .regions import create_regions 9from .regions import create_regions
@@ -62,6 +62,20 @@ class Lingo2World(World):
62 total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values()) 62 total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values())
63 63
64 item_difference = total_locations - len(pool) 64 item_difference = total_locations - len(pool)
65
66 if self.options.trap_percentage > 0:
67 num_traps = int(item_difference * self.options.trap_percentage / 100)
68 item_difference = item_difference - num_traps
69
70 trap_names = []
71 trap_weights = []
72 for letter_name, weight in self.static_logic.letter_weights.items():
73 trap_names.append(f"Anti {letter_name}")
74 trap_weights.append(weight)
75
76 bad_letters = self.random.choices(trap_names, weights=trap_weights, k=num_traps)
77 pool += [self.create_item(trap_name) for trap_name in bad_letters]
78
65 for i in range(0, item_difference): 79 for i in range(0, item_difference):
66 pool.append(self.create_item(self.get_filler_item_name())) 80 pool.append(self.create_item(self.get_filler_item_name()))
67 81
@@ -69,6 +83,7 @@ class Lingo2World(World):
69 83
70 def create_item(self, name: str) -> Item: 84 def create_item(self, name: str) -> Item:
71 return Lingo2Item(name, ItemClassification.filler if name == self.get_filler_item_name() else 85 return Lingo2Item(name, ItemClassification.filler if name == self.get_filler_item_name() else
86 ItemClassification.trap if name in ANTI_COLLECTABLE_TRAPS else
72 ItemClassification.progression, 87 ItemClassification.progression,
73 self.item_name_to_id.get(name), self.player) 88 self.item_name_to_id.get(name), self.player)
74 89
@@ -82,8 +97,11 @@ class Lingo2World(World):
82 "keyholder_sanity", 97 "keyholder_sanity",
83 "shuffle_control_center_colors", 98 "shuffle_control_center_colors",
84 "shuffle_doors", 99 "shuffle_doors",
100 "shuffle_gallery_paintings",
85 "shuffle_letters", 101 "shuffle_letters",
86 "shuffle_symbols", 102 "shuffle_symbols",
103 "strict_cyan_ending",
104 "strict_purple_ending",
87 "victory_condition", 105 "victory_condition",
88 ] 106 ]
89 107
diff --git a/apworld/items.py b/apworld/items.py index 32568a3..28158c3 100644 --- a/apworld/items.py +++ b/apworld/items.py
@@ -27,3 +27,5 @@ SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = {
27 data_pb2.PuzzleSymbol.LINGO: "Lingo Symbol", 27 data_pb2.PuzzleSymbol.LINGO: "Lingo Symbol",
28 data_pb2.PuzzleSymbol.QUESTION: "Question Symbol", 28 data_pb2.PuzzleSymbol.QUESTION: "Question Symbol",
29} 29}
30
31ANTI_COLLECTABLE_TRAPS: list[str] = [f"Anti {letter}" for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"]
diff --git a/apworld/options.py b/apworld/options.py index f72e826..3646eea 100644 --- a/apworld/options.py +++ b/apworld/options.py
@@ -1,9 +1,9 @@
1from dataclasses import dataclass 1from dataclasses import dataclass
2 2
3from Options import PerGameCommonOptions, Toggle, Choice 3from Options import PerGameCommonOptions, Toggle, Choice, DefaultOnToggle, Range
4 4
5 5
6class ShuffleDoors(Toggle): 6class ShuffleDoors(DefaultOnToggle):
7 """If enabled, most doors will open from receiving an item rather than fulfilling the in-game requirements.""" 7 """If enabled, most doors will open from receiving an item rather than fulfilling the in-game requirements."""
8 display_name = "Shuffle Doors" 8 display_name = "Shuffle Doors"
9 9
@@ -16,6 +16,11 @@ class ShuffleControlCenterColors(Toggle):
16 display_name = "Shuffle Control Center Colors" 16 display_name = "Shuffle Control Center Colors"
17 17
18 18
19class ShuffleGalleryPaintings(Toggle):
20 """If enabled, gallery paintings will appear from receiving an item rather than by triggering them normally."""
21 display_name = "Shuffle Gallery Paintings"
22
23
19class ShuffleLetters(Choice): 24class ShuffleLetters(Choice):
20 """ 25 """
21 Controls how letter unlocks are handled. Note that H1, I1, N1, and T1 will always be present at their vanilla 26 Controls how letter unlocks are handled. Note that H1, I1, N1, and T1 will always be present at their vanilla
@@ -87,8 +92,40 @@ class DaedalusRoofAccess(Toggle):
87 display_name = "Allow Daedalus Roof Access" 92 display_name = "Allow Daedalus Roof Access"
88 93
89 94
95class StrictPurpleEnding(DefaultOnToggle):
96 """
97 If enabled, the player will be required to have all purple (level 1) letters in order to get Purple Ending.
98 Otherwise, some of the letters may be skippable depending on the other options.
99 """
100 display_name = "Strict Purple Ending"
101
102
103class StrictCyanEnding(DefaultOnToggle):
104 """
105 If enabled, the player will be required to have all cyan (level 2) letters in order to get Cyan Ending. Otherwise,
106 at least J2, Q2, and V2 are skippable. Others may also be skippable depending on the options chosen.
107 """
108 display_name = "Strict Cyan Ending"
109
110
90class VictoryCondition(Choice): 111class VictoryCondition(Choice):
91 """Victory condition.""" 112 """
113 This option determines what your goal is.
114
115 - **Gray Ending** (The Colorful)
116 - **Purple Ending** (The Sun Temple). This ordinarily requires all level 1 (purple) letters.
117 - **Mint Ending** (typing EXIT into the keyholders in Control Center)
118 - **Black Ending** (The Graveyard)
119 - **Blue Ending** (The Words)
120 - **Cyan Ending** (The Parthenon). This ordinarily requires almost all level 2 (cyan) letters.
121 - **Red Ending** (The Tower)
122 - **Plum Ending** (The Wondrous / The Door)
123 - **Orange Ending** (the castle in Daedalus)
124 - **Gold Ending** (The Gold). This involves going through the color rooms in Daedalus.
125 - **Yellow Ending** (The Gallery). This requires unlocking all gallery paintings.
126 - **Green Ending** (The Ancient). This requires filling all keyholders with specific letters.
127 - **White Ending** (Control Center). This combines every other ending.
128 """
92 display_name = "Victory Condition" 129 display_name = "Victory Condition"
93 option_gray_ending = 0 130 option_gray_ending = 0
94 option_purple_ending = 1 131 option_purple_ending = 1
@@ -105,13 +142,25 @@ class VictoryCondition(Choice):
105 option_white_ending = 12 142 option_white_ending = 12
106 143
107 144
145class TrapPercentage(Range):
146 """Replaces junk items with traps, at the specified rate."""
147 display_name = "Trap Percentage"
148 range_start = 0
149 range_end = 100
150 default = 0
151
152
108@dataclass 153@dataclass
109class Lingo2Options(PerGameCommonOptions): 154class Lingo2Options(PerGameCommonOptions):
110 shuffle_doors: ShuffleDoors 155 shuffle_doors: ShuffleDoors
111 shuffle_control_center_colors: ShuffleControlCenterColors 156 shuffle_control_center_colors: ShuffleControlCenterColors
157 shuffle_gallery_paintings: ShuffleGalleryPaintings
112 shuffle_letters: ShuffleLetters 158 shuffle_letters: ShuffleLetters
113 shuffle_symbols: ShuffleSymbols 159 shuffle_symbols: ShuffleSymbols
114 keyholder_sanity: KeyholderSanity 160 keyholder_sanity: KeyholderSanity
115 cyan_door_behavior: CyanDoorBehavior 161 cyan_door_behavior: CyanDoorBehavior
116 daedalus_roof_access: DaedalusRoofAccess 162 daedalus_roof_access: DaedalusRoofAccess
163 strict_purple_ending: StrictPurpleEnding
164 strict_cyan_ending: StrictCyanEnding
117 victory_condition: VictoryCondition 165 victory_condition: VictoryCondition
166 trap_percentage: TrapPercentage
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 42b36e6..4aa481d 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -30,6 +30,11 @@ class AccessRequirements:
30 # This is an AND of ORs. 30 # This is an AND of ORs.
31 or_logic: list[list["AccessRequirements"]] 31 or_logic: list[list["AccessRequirements"]]
32 32
33 # When complete_at is set, at least that many of the requirements in possibilities must be accessible. This should
34 # only be used for doors with complete_at > 1, as or_logic is more efficient for complete_at == 1.
35 complete_at: int | None
36 possibilities: list["AccessRequirements"]
37
33 def __init__(self): 38 def __init__(self):
34 self.items = set() 39 self.items = set()
35 self.progressives = dict() 40 self.progressives = dict()
@@ -37,6 +42,20 @@ class AccessRequirements:
37 self.letters = dict() 42 self.letters = dict()
38 self.cyans = False 43 self.cyans = False
39 self.or_logic = list() 44 self.or_logic = list()
45 self.complete_at = None
46 self.possibilities = list()
47
48 def copy(self) -> "AccessRequirements":
49 reqs = AccessRequirements()
50 reqs.items = self.items.copy()
51 reqs.progressives = self.progressives.copy()
52 reqs.rooms = self.rooms.copy()
53 reqs.letters = self.letters.copy()
54 reqs.cyans = self.cyans
55 reqs.or_logic = [[other_req.copy() for other_req in disjunction] for disjunction in self.or_logic]
56 reqs.complete_at = self.complete_at
57 reqs.possibilities = self.possibilities.copy()
58 return reqs
40 59
41 def merge(self, other: "AccessRequirements"): 60 def merge(self, other: "AccessRequirements"):
42 for item in other.items: 61 for item in other.items:
@@ -56,9 +75,97 @@ class AccessRequirements:
56 for disjunction in other.or_logic: 75 for disjunction in other.or_logic:
57 self.or_logic.append(disjunction) 76 self.or_logic.append(disjunction)
58 77
78 if other.complete_at is not None:
79 # Merging multiple requirements that use complete_at sucks, and is part of why we want to minimize use of
80 # it. If both requirements use complete_at, we will cheat by using the or_logic field, which supports
81 # conjunctions of requirements.
82 if self.complete_at is not None:
83 print("Merging requirements with complete_at > 1. This is messy and should be avoided!")
84
85 left_req = AccessRequirements()
86 left_req.complete_at = self.complete_at
87 left_req.possibilities = self.possibilities
88 self.or_logic.append([left_req])
89
90 self.complete_at = None
91 self.possibilities = list()
92
93 right_req = AccessRequirements()
94 right_req.complete_at = other.complete_at
95 right_req.possibilities = other.possibilities
96 self.or_logic.append([right_req])
97 else:
98 self.complete_at = other.complete_at
99 self.possibilities = other.possibilities
100
59 def is_empty(self) -> bool: 101 def is_empty(self) -> bool:
60 return (len(self.items) == 0 and len(self.progressives) == 0 and len(self.rooms) == 0 and len(self.letters) == 0 102 return (len(self.items) == 0 and len(self.progressives) == 0 and len(self.rooms) == 0 and len(self.letters) == 0
61 and not self.cyans and len(self.or_logic) == 0) 103 and not self.cyans and len(self.or_logic) == 0 and self.complete_at is None)
104
105 def __eq__(self, other: "AccessRequirements"):
106 return (self.items == other.items and self.progressives == other.progressives and self.rooms == other.rooms and
107 self.letters == other.letters and self.cyans == other.cyans and self.or_logic == other.or_logic and
108 self.complete_at == other.complete_at and self.possibilities == other.possibilities)
109
110 def simplify(self):
111 resimplify = False
112
113 if len(self.or_logic) > 0:
114 old_or_logic = self.or_logic
115
116 def remove_redundant(sub_reqs: "AccessRequirements"):
117 new_reqs = sub_reqs.copy()
118 new_reqs.letters = {l: v for l, v in new_reqs.letters.items() if self.letters.get(l, 0) < v}
119 if new_reqs != sub_reqs:
120 return new_reqs
121 else:
122 return sub_reqs
123
124 self.or_logic = []
125 for disjunction in old_or_logic:
126 new_disjunction = []
127 for ssr in disjunction:
128 new_ssr = remove_redundant(ssr)
129 if not new_ssr.is_empty():
130 new_disjunction.append(new_ssr)
131 else:
132 new_disjunction.clear()
133 break
134 if len(new_disjunction) == 1:
135 self.merge(new_disjunction[0])
136 resimplify = True
137 elif len(new_disjunction) > 1:
138 if all(cjr == new_disjunction[0] for cjr in new_disjunction):
139 self.merge(new_disjunction[0])
140 resimplify = True
141 else:
142 self.or_logic.append(new_disjunction)
143
144 if resimplify:
145 self.simplify()
146
147 def get_referenced_rooms(self):
148 result = set(self.rooms)
149
150 for disjunction in self.or_logic:
151 for sub_req in disjunction:
152 result = result.union(sub_req.get_referenced_rooms())
153
154 for sub_req in self.possibilities:
155 result = result.union(sub_req.get_referenced_rooms())
156
157 return result
158
159 def remove_room(self, room: str):
160 if room in self.rooms:
161 self.rooms.remove(room)
162
163 for disjunction in self.or_logic:
164 for sub_req in disjunction:
165 sub_req.remove_room(room)
166
167 for sub_req in self.possibilities:
168 sub_req.remove_room(room)
62 169
63 def __repr__(self): 170 def __repr__(self):
64 parts = [] 171 parts = []
@@ -74,7 +181,11 @@ class AccessRequirements:
74 parts.append(f"cyans=True") 181 parts.append(f"cyans=True")
75 if len(self.or_logic) > 0: 182 if len(self.or_logic) > 0:
76 parts.append(f"or_logic={self.or_logic}") 183 parts.append(f"or_logic={self.or_logic}")
77 return f"AccessRequirements({", ".join(parts)})" 184 if self.complete_at is not None:
185 parts.append(f"complete_at={self.complete_at}")
186 if len(self.possibilities) > 0:
187 parts.append(f"possibilities={self.possibilities}")
188 return "AccessRequirements(" + ", ".join(parts) + ")"
78 189
79 190
80class PlayerLocation(NamedTuple): 191class PlayerLocation(NamedTuple):
@@ -156,6 +267,9 @@ class Lingo2PlayerLogic:
156 not self.world.options.shuffle_control_center_colors): 267 not self.world.options.shuffle_control_center_colors):
157 continue 268 continue
158 269
270 if door.type == data_pb2.DoorType.GALLERY_PAINTING and not self.world.options.shuffle_gallery_paintings:
271 continue
272
159 door_item_name = self.world.static_logic.get_door_item_name(door) 273 door_item_name = self.world.static_logic.get_door_item_name(door)
160 self.item_by_door[door.id] = (door_item_name, 1) 274 self.item_by_door[door.id] = (door_item_name, 1)
161 self.real_items.append(door_item_name) 275 self.real_items.append(door_item_name)
@@ -306,7 +420,6 @@ class Lingo2PlayerLogic:
306 door = self.world.static_logic.objects.doors[door_id] 420 door = self.world.static_logic.objects.doors[door_id]
307 reqs = AccessRequirements() 421 reqs = AccessRequirements()
308 422
309 # TODO: lavender_cubes, endings
310 if not door.HasField("complete_at") or door.complete_at == 0: 423 if not door.HasField("complete_at") or door.complete_at == 0:
311 for proxy in door.panels: 424 for proxy in door.panels:
312 panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) 425 panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None)
@@ -323,8 +436,10 @@ class Lingo2PlayerLogic:
323 if len(disjunction) > 0: 436 if len(disjunction) > 0:
324 reqs.or_logic.append(disjunction) 437 reqs.or_logic.append(disjunction)
325 else: 438 else:
326 # TODO: Handle complete_at > 1 439 reqs.complete_at = door.complete_at
327 pass 440 for proxy in door.panels:
441 panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None)
442 reqs.possibilities.append(panel_reqs)
328 443
329 if door.HasField("control_center_color"): 444 if door.HasField("control_center_color"):
330 # TODO: Logic for ensuring two CC states aren't needed at once. 445 # TODO: Logic for ensuring two CC states aren't needed at once.
@@ -365,6 +480,8 @@ class Lingo2PlayerLogic:
365 sub_reqs = self.get_door_open_reqs(sub_door_id) 480 sub_reqs = self.get_door_open_reqs(sub_door_id)
366 reqs.merge(sub_reqs) 481 reqs.merge(sub_reqs)
367 482
483 reqs.simplify()
484
368 return reqs 485 return reqs
369 486
370 # This gets the requirements to open a door within the world. When a door is shuffled, this means having the item 487 # This gets the requirements to open a door within the world. When a door is shuffled, this means having the item
diff --git a/apworld/regions.py b/apworld/regions.py index e30493c..993eec8 100644 --- a/apworld/regions.py +++ b/apworld/regions.py
@@ -11,12 +11,17 @@ if TYPE_CHECKING:
11 11
12 12
13def create_region(room, world: "Lingo2World") -> Region: 13def create_region(room, world: "Lingo2World") -> Region:
14 new_region = Region(world.static_logic.get_room_region_name(room.id), world.player, world.multiworld) 14 return Region(world.static_logic.get_room_region_name(room.id), world.player, world.multiworld)
15 15
16
17def create_locations(room, new_region: Region, world: "Lingo2World", regions: dict[str, Region]):
16 for location in world.player_logic.locations_by_room.get(room.id, {}): 18 for location in world.player_logic.locations_by_room.get(room.id, {}):
19 reqs = location.reqs.copy()
20 reqs.remove_room(new_region.name)
21
17 new_location = Lingo2Location(world.player, world.static_logic.location_id_to_name[location.code], 22 new_location = Lingo2Location(world.player, world.static_logic.location_id_to_name[location.code],
18 location.code, new_region) 23 location.code, new_region)
19 new_location.access_rule = make_location_lambda(location.reqs, world) 24 new_location.access_rule = make_location_lambda(reqs, world, regions)
20 new_region.locations.append(new_location) 25 new_region.locations.append(new_location)
21 26
22 for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items(): 27 for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items():
@@ -25,17 +30,23 @@ def create_region(room, world: "Lingo2World") -> Region:
25 new_location.place_locked_item(event_item) 30 new_location.place_locked_item(event_item)
26 new_region.locations.append(new_location) 31 new_region.locations.append(new_location)
27 32
28 return new_region
29
30
31def create_regions(world: "Lingo2World"): 33def create_regions(world: "Lingo2World"):
32 regions = { 34 regions = {
33 "Menu": Region("Menu", world.player, world.multiworld) 35 "Menu": Region("Menu", world.player, world.multiworld)
34 } 36 }
35 37
38 region_and_room = []
39
40 # Create the regions in two stages. First, make the actual region objects and memoize them. Then, add all of the
41 # locations. This allows us to reference the actual region objects in the access rules for the locations, which is
42 # faster than having to look them up during access checking.
36 for room in world.static_logic.objects.rooms: 43 for room in world.static_logic.objects.rooms:
37 region = create_region(room, world) 44 region = create_region(room, world)
38 regions[region.name] = region 45 regions[region.name] = region
46 region_and_room.append((region, room))
47
48 for (region, room) in region_and_room:
49 create_locations(room, region, world, regions)
39 50
40 regions["Menu"].connect(regions["The Entry - Starting Room"], "Start Game") 51 regions["Menu"].connect(regions["The Entry - Starting Room"], "Start Game")
41 52
@@ -46,6 +57,10 @@ def create_regions(world: "Lingo2World"):
46 57
47 from_region = world.static_logic.get_room_region_name(connection.from_room) 58 from_region = world.static_logic.get_room_region_name(connection.from_room)
48 to_region = world.static_logic.get_room_region_name(connection.to_room) 59 to_region = world.static_logic.get_room_region_name(connection.to_room)
60
61 if from_region not in regions or to_region not in regions:
62 continue
63
49 connection_name = f"{from_region} -> {to_region}" 64 connection_name = f"{from_region} -> {to_region}"
50 65
51 reqs = AccessRequirements() 66 reqs = AccessRequirements()
@@ -82,14 +97,22 @@ def create_regions(world: "Lingo2World"):
82 else: 97 else:
83 connection_name = f"{connection_name} (via panel {panel.name})" 98 connection_name = f"{connection_name} (via panel {panel.name})"
84 99
85 if from_region in regions and to_region in regions: 100 if connection.HasField("purple_ending") and connection.purple_ending and world.options.strict_purple_ending:
86 connection = Entrance(world.player, connection_name, regions[from_region]) 101 world.player_logic.add_solution_reqs(reqs, "abcdefghijklmnopqrstuvwxyz")
87 connection.access_rule = make_location_lambda(reqs, world) 102
103 if connection.HasField("cyan_ending") and connection.cyan_ending and world.options.strict_cyan_ending:
104 world.player_logic.add_solution_reqs(reqs, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")
105
106 reqs.simplify()
107 reqs.remove_room(from_region)
108
109 connection = Entrance(world.player, connection_name, regions[from_region])
110 connection.access_rule = make_location_lambda(reqs, world, regions)
88 111
89 regions[from_region].exits.append(connection) 112 regions[from_region].exits.append(connection)
90 connection.connect(regions[to_region]) 113 connection.connect(regions[to_region])
91 114
92 for region in reqs.rooms: 115 for region in reqs.get_referenced_rooms():
93 world.multiworld.register_indirect_condition(regions[region], connection) 116 world.multiworld.register_indirect_condition(regions[region], connection)
94 117
95 world.multiworld.regions += regions.values() 118 world.multiworld.regions += regions.values()
diff --git a/apworld/rules.py b/apworld/rules.py index 0bff056..c077858 100644 --- a/apworld/rules.py +++ b/apworld/rules.py
@@ -1,14 +1,15 @@
1from collections.abc import Callable 1from collections.abc import Callable
2from typing import TYPE_CHECKING 2from typing import TYPE_CHECKING
3 3
4from BaseClasses import CollectionState 4from BaseClasses import CollectionState, Region
5from .player_logic import AccessRequirements 5from .player_logic import AccessRequirements
6 6
7if TYPE_CHECKING: 7if TYPE_CHECKING:
8 from . import Lingo2World 8 from . import Lingo2World
9 9
10 10
11def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirements, world: "Lingo2World") -> bool: 11def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirements, regions: list[Region],
12 world: "Lingo2World") -> bool:
12 if not all(state.has(item, world.player) for item in reqs.items): 13 if not all(state.has(item, world.player) for item in reqs.items):
13 return False 14 return False
14 15
@@ -18,6 +19,9 @@ def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirem
18 if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms): 19 if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms):
19 return False 20 return False
20 21
22 if not all(state.can_reach(region) for region in regions):
23 return False
24
21 for letter_key, letter_level in reqs.letters.items(): 25 for letter_key, letter_level in reqs.letters.items():
22 if not state.has(letter_key, world.player, letter_level): 26 if not state.has(letter_key, world.player, letter_level):
23 return False 27 return False
@@ -28,11 +32,32 @@ def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirem
28 return False 32 return False
29 33
30 if len(reqs.or_logic) > 0: 34 if len(reqs.or_logic) > 0:
31 if not all(any(lingo2_can_satisfy_requirements(state, sub_reqs, world) for sub_reqs in subjunction) 35 if not all(any(lingo2_can_satisfy_requirements(state, sub_reqs, [], world) for sub_reqs in subjunction)
32 for subjunction in reqs.or_logic): 36 for subjunction in reqs.or_logic):
33 return False 37 return False
34 38
39 if reqs.complete_at is not None:
40 completed = 0
41 checked = 0
42 for possibility in reqs.possibilities:
43 checked += 1
44 if lingo2_can_satisfy_requirements(state, possibility, [], world):
45 completed += 1
46 if completed >= reqs.complete_at:
47 break
48 elif len(reqs.possibilities) - checked + completed < reqs.complete_at:
49 # There aren't enough remaining possibilities for the check to pass.
50 return False
51 if completed < reqs.complete_at:
52 return False
53
35 return True 54 return True
36 55
37def make_location_lambda(reqs: AccessRequirements, world: "Lingo2World") -> Callable[[CollectionState], bool]: 56def make_location_lambda(reqs: AccessRequirements, world: "Lingo2World",
38 return lambda state: lingo2_can_satisfy_requirements(state, reqs, world) 57 regions: dict[str, Region]) -> Callable[[CollectionState], bool]:
58 # Replace required rooms with regions for the top level requirement, which saves looking up the regions during rule
59 # checking.
60 required_regions = [regions[room_name] for room_name in reqs.rooms]
61 new_reqs = reqs.copy()
62 new_reqs.rooms.clear()
63 return lambda state: lingo2_can_satisfy_requirements(state, new_reqs, required_regions, world)
diff --git a/apworld/static_logic.py b/apworld/static_logic.py index 07800f8..e4d7d49 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py
@@ -1,5 +1,5 @@
1from .generated import data_pb2 as data_pb2 1from .generated import data_pb2 as data_pb2
2from .items import SYMBOL_ITEMS 2from .items import SYMBOL_ITEMS, ANTI_COLLECTABLE_TRAPS
3import pkgutil 3import pkgutil
4 4
5class Lingo2StaticLogic: 5class Lingo2StaticLogic:
@@ -12,11 +12,14 @@ class Lingo2StaticLogic:
12 item_name_groups: dict[str, list[str]] 12 item_name_groups: dict[str, list[str]]
13 location_name_groups: dict[str, list[str]] 13 location_name_groups: dict[str, list[str]]
14 14
15 letter_weights: dict[str, int]
16
15 def __init__(self): 17 def __init__(self):
16 self.item_id_to_name = {} 18 self.item_id_to_name = {}
17 self.location_id_to_name = {} 19 self.location_id_to_name = {}
18 self.item_name_groups = {} 20 self.item_name_groups = {}
19 self.location_name_groups = {} 21 self.location_name_groups = {}
22 self.letter_weights = {}
20 23
21 file = pkgutil.get_data(__name__, "generated/data.binpb") 24 file = pkgutil.get_data(__name__, "generated/data.binpb")
22 self.objects = data_pb2.AllObjects() 25 self.objects = data_pb2.AllObjects()
@@ -68,9 +71,16 @@ class Lingo2StaticLogic:
68 for symbol_name in SYMBOL_ITEMS.values(): 71 for symbol_name in SYMBOL_ITEMS.values():
69 self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name 72 self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name
70 73
74 for trap_name in ANTI_COLLECTABLE_TRAPS:
75 self.item_id_to_name[self.objects.special_ids[trap_name]] = trap_name
76
71 self.item_name_to_id = {name: ap_id for ap_id, name in self.item_id_to_name.items()} 77 self.item_name_to_id = {name: ap_id for ap_id, name in self.item_id_to_name.items()}
72 self.location_name_to_id = {name: ap_id for ap_id, name in self.location_id_to_name.items()} 78 self.location_name_to_id = {name: ap_id for ap_id, name in self.location_id_to_name.items()}
73 79
80 for panel in self.objects.panels:
81 for letter in panel.answer.upper():
82 self.letter_weights[letter] = self.letter_weights.get(letter, 0) + 1
83
74 def get_door_item_name(self, door: data_pb2.Door) -> str: 84 def get_door_item_name(self, door: data_pb2.Door) -> str:
75 return f"{self.get_map_object_map_name(door)} - {door.name}" 85 return f"{self.get_map_object_map_name(door)} - {door.name}"
76 86
@@ -94,7 +104,7 @@ class Lingo2StaticLogic:
94 if door.type != data_pb2.DoorType.STANDARD: 104 if door.type != data_pb2.DoorType.STANDARD:
95 return None 105 return None
96 106
97 if len(door.keyholders) > 0 or len(door.endings) > 0: 107 if len(door.keyholders) > 0 or len(door.endings) > 0 or door.HasField("complete_at"):
98 return None 108 return None
99 109
100 if len(door.panels) > 4: 110 if len(door.panels) > 4:
@@ -130,7 +140,7 @@ class Lingo2StaticLogic:
130 for panel_id in door.panels] 140 for panel_id in door.panels]
131 panel_names.sort() 141 panel_names.sort()
132 142
133 return f"{map_part} - {", ".join(panel_names)}" 143 return map_part + " - " + ", ".join(panel_names)
134 144
135 def get_door_location_name_by_id(self, door_id: int) -> str: 145 def get_door_location_name_by_id(self, door_id: int) -> str:
136 door = self.objects.doors[door_id] 146 door = self.objects.doors[door_id]
diff --git a/apworld/version.py b/apworld/version.py index 645cce6..ac799cd 100644 --- a/apworld/version.py +++ b/apworld/version.py
@@ -1 +1 @@
APWORLD_VERSION = 0 APWORLD_VERSION = 6
diff --git a/client/Archipelago/client.gd b/client/Archipelago/client.gd index 2e080fd..843647d 100644 --- a/client/Archipelago/client.gd +++ b/client/Archipelago/client.gd
@@ -47,6 +47,8 @@ signal location_scout_received(item_id, location_id, player, flags)
47func _init(): 47func _init():
48 set_process_mode(Node.PROCESS_MODE_ALWAYS) 48 set_process_mode(Node.PROCESS_MODE_ALWAYS)
49 49
50 _ws.inbound_buffer_size = 8388608
51
50 global._print("Instantiated APClient") 52 global._print("Instantiated APClient")
51 53
52 # Read AP datapackages from file, if there are any 54 # Read AP datapackages from file, if there are any
@@ -225,7 +227,7 @@ func _process(_delta):
225 error_message = "Unknown error." 227 error_message = "Unknown error."
226 228
227 _initiated_disconnect = true 229 _initiated_disconnect = true
228 _ws.disconnect_from_host() 230 _ws.close()
229 231
230 emit_signal("could_not_connect", error_message) 232 emit_signal("could_not_connect", error_message)
231 global._print("Connection to AP refused") 233 global._print("Connection to AP refused")
@@ -309,7 +311,7 @@ func connectToServer(server, un, pw):
309 % err 311 % err
310 ) 312 )
311 ) 313 )
312 global._print("Could not connect to AP: " + err) 314 global._print("Could not connect to AP: %d" % err)
313 return 315 return
314 _should_process = true 316 _should_process = true
315 317
diff --git a/client/Archipelago/compass.gd b/client/Archipelago/compass.gd new file mode 100644 index 0000000..c90475a --- /dev/null +++ b/client/Archipelago/compass.gd
@@ -0,0 +1,66 @@
1extends Node2D
2
3const RADIUS = 48
4
5var _font
6
7
8func _ready():
9 _font = load("res://assets/fonts/Lingo2.ttf")
10
11
12func _draw():
13 draw_circle(Vector2.ZERO, RADIUS, Color(1.0, 1.0, 1.0, 0.8), true)
14 draw_circle(Vector2.ZERO, RADIUS, Color.BLACK, false)
15 draw_string(
16 _font,
17 Vector2(-4, -RADIUS * 3.0 / 4.0),
18 "N",
19 HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT,
20 -1,
21 16,
22 Color.BLACK
23 )
24 draw_set_transform(Vector2.ZERO, PI / 2)
25 draw_string(
26 _font,
27 Vector2(-4, -RADIUS * 3.0 / 4.0),
28 "E",
29 HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT,
30 -1,
31 16,
32 Color.BLACK
33 )
34 draw_set_transform(Vector2.ZERO, PI)
35 draw_string(
36 _font,
37 Vector2(-4, -RADIUS * 3.0 / 4.0),
38 "S",
39 HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT,
40 -1,
41 16,
42 Color.BLACK
43 )
44 draw_set_transform(Vector2.ZERO, PI * 3.0 / 2.0)
45 draw_string(
46 _font,
47 Vector2(-4, -RADIUS * 3.0 / 4.0),
48 "W",
49 HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT,
50 -1,
51 16,
52 Color.BLACK
53 )
54 draw_set_transform(Vector2.ZERO)
55 draw_colored_polygon(
56 PackedVector2Array(
57 [Vector2(0, -RADIUS * 5.0 / 8.0), Vector2(-RADIUS / 6.0, 0), Vector2(RADIUS / 6.0, 0)]
58 ),
59 Color.RED
60 )
61 draw_colored_polygon(
62 PackedVector2Array(
63 [Vector2(0, RADIUS * 5.0 / 8.0), Vector2(-RADIUS / 6.0, 0), Vector2(RADIUS / 6.0, 0)]
64 ),
65 Color.GRAY
66 )
diff --git a/client/Archipelago/compass_overlay.gd b/client/Archipelago/compass_overlay.gd new file mode 100644 index 0000000..56e81ff --- /dev/null +++ b/client/Archipelago/compass_overlay.gd
@@ -0,0 +1,17 @@
1extends CanvasLayer
2
3var SCRIPT_compass
4
5var compass
6
7
8func _ready():
9 compass = SCRIPT_compass.new()
10 compass.position = Vector2(1840, 80)
11 add_child(compass)
12
13 visible = false
14
15
16func update_rotation(ry):
17 compass.rotation = ry
diff --git a/client/Archipelago/door.gd b/client/Archipelago/door.gd index fead818..49f5728 100644 --- a/client/Archipelago/door.gd +++ b/client/Archipelago/door.gd
@@ -28,6 +28,14 @@ func _ready():
28 28
29 call_deferred("_readier") 29 call_deferred("_readier")
30 30
31 if global.map == "the_sun_temple":
32 if name == "spe_EndPlatform" or name == "spe_entry_2":
33 senders = [NodePath("/root/scene/Panels/EndCheck_dog")]
34
35 if global.map == "the_parthenon":
36 if name == "spe_entry_1":
37 senders = [NodePath("/root/scene/Panels/EndCheck_dog")]
38
31 super._ready() 39 super._ready()
32 40
33 41
diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd index d8d16ed..41d966a 100644 --- a/client/Archipelago/gamedata.gd +++ b/client/Archipelago/gamedata.gd
@@ -11,6 +11,7 @@ var map_id_by_name = {}
11var progressive_id_by_ap_id = {} 11var progressive_id_by_ap_id = {}
12var letter_id_by_ap_id = {} 12var letter_id_by_ap_id = {}
13var symbol_item_ids = [] 13var symbol_item_ids = []
14var anti_trap_ids = {}
14 15
15var kSYMBOL_ITEMS 16var kSYMBOL_ITEMS
16 17
@@ -97,6 +98,12 @@ func load(data_bytes):
97 for symbol_name in kSYMBOL_ITEMS.values(): 98 for symbol_name in kSYMBOL_ITEMS.values():
98 symbol_item_ids.append(objects.get_special_ids()[symbol_name]) 99 symbol_item_ids.append(objects.get_special_ids()[symbol_name])
99 100
101 for special_name in objects.get_special_ids().keys():
102 if special_name.begins_with("Anti "):
103 anti_trap_ids[objects.get_special_ids()[special_name]] = (
104 special_name.substr(5).to_lower()
105 )
106
100 107
101func get_door_for_map_node_path(map_name, node_path): 108func get_door_for_map_node_path(map_name, node_path):
102 if not door_id_by_map_node_path.has(map_name): 109 if not door_id_by_map_node_path.has(map_name):
diff --git a/client/Archipelago/keyboard.gd b/client/Archipelago/keyboard.gd index 600a047..450566d 100644 --- a/client/Archipelago/keyboard.gd +++ b/client/Archipelago/keyboard.gd
@@ -4,6 +4,7 @@ const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz"
4 4
5var letters_saved = {} 5var letters_saved = {}
6var letters_in_keyholders = [] 6var letters_in_keyholders = []
7var letters_blocked = []
7var letters_dynamic = {} 8var letters_dynamic = {}
8var keyholder_state = {} 9var keyholder_state = {}
9 10
@@ -17,6 +18,7 @@ func _init():
17func reset(): 18func reset():
18 letters_saved.clear() 19 letters_saved.clear()
19 letters_in_keyholders.clear() 20 letters_in_keyholders.clear()
21 letters_blocked.clear()
20 letters_dynamic.clear() 22 letters_dynamic.clear()
21 keyholder_state.clear() 23 keyholder_state.clear()
22 24
@@ -91,6 +93,9 @@ func update_unlocks():
91 level = 2 93 level = 2
92 has_doubles = true 94 has_doubles = true
93 95
96 if letters_blocked.has(k):
97 level = 0
98
94 unlocks.unlockKey(k, level) 99 unlocks.unlockKey(k, level)
95 100
96 if has_doubles and unlocks.data["double_letters"] != "unlocked": 101 if has_doubles and unlocks.data["double_letters"] != "unlocked":
@@ -105,6 +110,9 @@ func collect_local_letter(key, level):
105 110
106 letters_saved[key] = level 111 letters_saved[key] = level
107 112
113 if letters_blocked.has(key):
114 letters_blocked.erase(key)
115
108 update_unlocks() 116 update_unlocks()
109 save() 117 save()
110 118
@@ -115,6 +123,9 @@ func collect_remote_letter(key, level):
115 123
116 letters_dynamic[key] = level 124 letters_dynamic[key] = level
117 125
126 if letters_blocked.has(key):
127 letters_blocked.erase(key)
128
118 update_unlocks() 129 update_unlocks()
119 save() 130 save()
120 131
@@ -148,6 +159,13 @@ func remove_from_keyholder(key, map, kh_path):
148 save() 159 save()
149 160
150 161
162func block_letter(key):
163 if not letters_blocked.has(key):
164 letters_blocked.append(key)
165
166 update_unlocks()
167
168
151func load_keyholders(map): 169func load_keyholders(map):
152 if keyholder_state.has(map): 170 if keyholder_state.has(map):
153 var khs = keyholder_state[map] 171 var khs = keyholder_state[map]
@@ -160,9 +178,11 @@ func load_keyholders(map):
160 178
161 179
162func reset_keyholders(): 180func reset_keyholders():
163 if letters_in_keyholders.is_empty(): 181 if letters_in_keyholders.is_empty() and letters_blocked.is_empty():
164 return false 182 return false
165 183
184 var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty()
185
166 if keyholder_state.has(global.map): 186 if keyholder_state.has(global.map):
167 for path in keyholder_state[global.map]: 187 for path in keyholder_state[global.map]:
168 get_tree().get_root().get_node("scene").get_node(path).setFromAp( 188 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
@@ -171,8 +191,9 @@ func reset_keyholders():
171 191
172 keyholder_state.clear() 192 keyholder_state.clear()
173 letters_in_keyholders.clear() 193 letters_in_keyholders.clear()
194 letters_blocked.clear()
174 195
175 update_unlocks() 196 update_unlocks()
176 save() 197 save()
177 198
178 return true 199 return cleared_anything
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index 6eea2bd..218870c 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -1,6 +1,6 @@
1extends Node 1extends Node
2 2
3const MOD_VERSION = 0 3const MOD_VERSION = 7
4 4
5var SCRIPT_client 5var SCRIPT_client
6var SCRIPT_keyboard 6var SCRIPT_keyboard
@@ -12,6 +12,7 @@ var ap_server = ""
12var ap_user = "" 12var ap_user = ""
13var ap_pass = "" 13var ap_pass = ""
14var connection_history = [] 14var connection_history = []
15var show_compass = false
15 16
16var client 17var client
17var keyboard 18var keyboard
@@ -41,13 +42,17 @@ const kCYAN_DOOR_BEHAVIOR_H2 = 0
41const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1 42const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1
42const kCYAN_DOOR_BEHAVIOR_ITEM = 2 43const kCYAN_DOOR_BEHAVIOR_ITEM = 2
43 44
45var apworld_version = [0, 0]
44var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 46var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
45var daedalus_roof_access = false 47var daedalus_roof_access = false
46var keyholder_sanity = false 48var keyholder_sanity = false
47var shuffle_control_center_colors = false 49var shuffle_control_center_colors = false
48var shuffle_doors = false 50var shuffle_doors = false
51var shuffle_gallery_paintings = false
49var shuffle_letters = kSHUFFLE_LETTERS_VANILLA 52var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
50var shuffle_symbols = false 53var shuffle_symbols = false
54var strict_cyan_ending = false
55var strict_purple_ending = false
51var victory_condition = -1 56var victory_condition = -1
52 57
53signal could_not_connect 58signal could_not_connect
@@ -78,6 +83,9 @@ func _init():
78 if data.size() > 3: 83 if data.size() > 3:
79 connection_history = data[3] 84 connection_history = data[3]
80 85
86 if data.size() > 4:
87 show_compass = data[4]
88
81 89
82func _ready(): 90func _ready():
83 client = SCRIPT_client.new() 91 client = SCRIPT_client.new()
@@ -106,6 +114,7 @@ func saveSettings():
106 ap_user, 114 ap_user,
107 ap_pass, 115 ap_pass,
108 connection_history, 116 connection_history,
117 show_compass,
109 ] 118 ]
110 file.store_var(data, true) 119 file.store_var(data, true)
111 file.close() 120 file.close()
@@ -213,6 +222,9 @@ func _process_item(item, index, from, flags, amount):
213 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name] 222 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name]
214 ) 223 )
215 224
225 if gamedata.anti_trap_ids.has(item):
226 keyboard.block_letter(gamedata.anti_trap_ids[item])
227
216 global._print(message) 228 global._print(message)
217 229
218 global.get_node("Messages").showMessage(message) 230 global.get_node("Messages").showMessage(message)
@@ -307,6 +319,10 @@ func parse_printjson_for_textclient(message):
307func _process_location_scout(item_id, location_id, player, flags): 319func _process_location_scout(item_id, location_id, player, flags):
308 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags} 320 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
309 321
322 if player == client._slot and flags & 4 != 0:
323 # This is a trap for us, so let's not display it.
324 return
325
310 var gamedata = global.get_node("Gamedata") 326 var gamedata = global.get_node("Gamedata")
311 var map_id = gamedata.map_id_by_name.get(global.map) 327 var map_id = gamedata.map_id_by_name.get(global.map)
312 328
@@ -327,8 +343,8 @@ func _process_location_scout(item_id, location_id, player, flags):
327 collectable.setScoutedText(item_name) 343 collectable.setScoutedText(item_name)
328 344
329 345
330func _client_could_not_connect(): 346func _client_could_not_connect(message):
331 emit_signal("could_not_connect") 347 emit_signal("could_not_connect", message)
332 348
333 349
334func _client_connect_status(message): 350func _client_connect_status(message):
@@ -361,10 +377,16 @@ func _client_connected(slot_data):
361 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) 377 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
362 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) 378 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
363 shuffle_doors = bool(slot_data.get("shuffle_doors", false)) 379 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
380 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
364 shuffle_letters = int(slot_data.get("shuffle_letters", 0)) 381 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
365 shuffle_symbols = bool(slot_data.get("shuffle_symbols", false)) 382 shuffle_symbols = bool(slot_data.get("shuffle_symbols", false))
383 strict_cyan_ending = bool(slot_data.get("strict_cyan_ending", false))
384 strict_purple_ending = bool(slot_data.get("strict_purple_ending", false))
366 victory_condition = int(slot_data.get("victory_condition", 0)) 385 victory_condition = int(slot_data.get("victory_condition", 0))
367 386
387 if slot_data.has("version"):
388 apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])]
389
368 # Set up item locks. 390 # Set up item locks.
369 _item_locks = {} 391 _item_locks = {}
370 392
@@ -399,6 +421,11 @@ func _client_connected(slot_data):
399 for door in door_group.get_doors(): 421 for door in door_group.get_doors():
400 _item_locks[door] = [door_group.get_ap_id(), 1] 422 _item_locks[door] = [door_group.get_ap_id(), 1]
401 423
424 if shuffle_gallery_paintings:
425 for door in gamedata.objects.get_doors():
426 if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING:
427 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
428
402 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM: 429 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM:
403 for door_group in gamedata.objects.get_door_groups(): 430 for door_group in gamedata.objects.get_door_groups():
404 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS: 431 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS:
@@ -511,4 +538,7 @@ func _process_key_item(key, level):
511 _held_letters[key] = max(_held_letters.get(key, 0), level) 538 _held_letters[key] = max(_held_letters.get(key, 0), level)
512 return 539 return
513 540
541 if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN:
542 level += 1
543
514 keyboard.collect_remote_letter(key, level) 544 keyboard.collect_remote_letter(key, level)
diff --git a/client/Archipelago/messages.gd b/client/Archipelago/messages.gd index 52f38b9..82fdbc4 100644 --- a/client/Archipelago/messages.gd +++ b/client/Archipelago/messages.gd
@@ -48,10 +48,11 @@ func showMessage(text):
48 while !_ordered_labels.is_empty(): 48 while !_ordered_labels.is_empty():
49 await get_tree().create_timer(timeout).timeout 49 await get_tree().create_timer(timeout).timeout
50 50
51 var to_remove = _ordered_labels.pop_front() 51 if !_ordered_labels.is_empty():
52 var to_tween = get_tree().create_tween().bind_node(to_remove) 52 var to_remove = _ordered_labels.pop_front()
53 to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5) 53 var to_tween = get_tree().create_tween().bind_node(to_remove)
54 to_tween.tween_callback(to_remove.queue_free) 54 to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5)
55 to_tween.tween_callback(to_remove.queue_free)
55 56
56 if !_message_queue.is_empty(): 57 if !_message_queue.is_empty():
57 var next_msg = _message_queue.pop_front() 58 var next_msg = _message_queue.pop_front()
@@ -59,3 +60,12 @@ func showMessage(text):
59 60
60 if timeout > 4: 61 if timeout > 4:
61 timeout -= 3 62 timeout -= 3
63
64
65func clear():
66 _message_queue.clear()
67
68 for message_label in _ordered_labels:
69 message_label.queue_free()
70
71 _ordered_labels.clear()
diff --git a/client/Archipelago/pauseMenu.gd b/client/Archipelago/pauseMenu.gd index 5da114a..cd1813c 100644 --- a/client/Archipelago/pauseMenu.gd +++ b/client/Archipelago/pauseMenu.gd
@@ -1,5 +1,26 @@
1extends "res://scripts/ui/pauseMenu.gd" 1extends "res://scripts/ui/pauseMenu.gd"
2 2
3var compass_button
4
5
6func _ready():
7 var ap_panel = Panel.new()
8 ap_panel.name = "Archipelago"
9 get_node("menu/settings/settingsInner/TabContainer").add_child(ap_panel)
10
11 var ap = global.get_node("Archipelago")
12
13 compass_button = CheckBox.new()
14 compass_button.text = "show compass"
15 compass_button.button_pressed = ap.show_compass
16 compass_button.position = Vector2(65, 100)
17 compass_button.theme = preload("res://assets/themes/baseUI.tres")
18 compass_button.add_theme_font_size_override("font_size", 60)
19 compass_button.pressed.connect(_toggle_compass)
20 ap_panel.add_child(compass_button)
21
22 super._ready()
23
3 24
4func _pause_game(): 25func _pause_game():
5 global.get_node("Textclient").dismiss() 26 global.get_node("Textclient").dismiss()
@@ -9,4 +30,15 @@ func _pause_game():
9func _main_menu(): 30func _main_menu():
10 global.loaded = false 31 global.loaded = false
11 global.get_node("Archipelago").disconnect_from_ap() 32 global.get_node("Archipelago").disconnect_from_ap()
33 global.get_node("Messages").clear()
34 global.get_node("Compass").visible = false
12 super._main_menu() 35 super._main_menu()
36
37
38func _toggle_compass():
39 var ap = global.get_node("Archipelago")
40 ap.show_compass = compass_button.button_pressed
41 ap.saveSettings()
42
43 var compass = global.get_node("Compass")
44 compass.visible = compass_button.button_pressed
diff --git a/client/Archipelago/player.gd b/client/Archipelago/player.gd index 4b995bc..538830f 100644 --- a/client/Archipelago/player.gd +++ b/client/Archipelago/player.gd
@@ -18,6 +18,8 @@ const kEndingNameByVictoryValue = {
18 18
19signal evaluate_solvability 19signal evaluate_solvability
20 20
21var compass
22
21 23
22func _ready(): 24func _ready():
23 var khl_script = load("res://scripts/nodes/keyHolderListener.gd") 25 var khl_script = load("res://scripts/nodes/keyHolderListener.gd")
@@ -25,6 +27,9 @@ func _ready():
25 var ap = global.get_node("Archipelago") 27 var ap = global.get_node("Archipelago")
26 var gamedata = global.get_node("Gamedata") 28 var gamedata = global.get_node("Gamedata")
27 29
30 compass = global.get_node("Compass")
31 compass.visible = ap.show_compass
32
28 ap.start_batching_locations() 33 ap.start_batching_locations()
29 34
30 # Set up door locations. 35 # Set up door locations.
@@ -36,7 +41,10 @@ func _ready():
36 if not door.has_ap_id(): 41 if not door.has_ap_id():
37 continue 42 continue
38 43
39 if door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY: 44 if (
45 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
46 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
47 ):
40 continue 48 continue
41 49
42 var locationListener = ap.SCRIPT_locationListener.new() 50 var locationListener = ap.SCRIPT_locationListener.new()
@@ -68,6 +76,12 @@ func _ready():
68 76
69 locationListener.senders.append(NodePath("../" + khl.name)) 77 locationListener.senders.append(NodePath("../" + khl.name))
70 78
79 for sender in door.get_senders():
80 locationListener.senders.append(NodePath("/root/scene/" + sender))
81
82 if door.has_complete_at():
83 locationListener.complete_at = door.get_complete_at()
84
71 get_parent().add_child.call_deferred(locationListener) 85 get_parent().add_child.call_deferred(locationListener)
72 86
73 # Set up letter locations. 87 # Set up letter locations.
@@ -88,7 +102,10 @@ func _ready():
88 != ap.kLETTER_BEHAVIOR_VANILLA 102 != ap.kLETTER_BEHAVIOR_VANILLA
89 ): 103 ):
90 var scout = ap.scout_location(letter.get_ap_id()) 104 var scout = ap.scout_location(letter.get_ap_id())
91 if scout != null: 105 if (
106 scout != null
107 and not (scout["player"] == ap.client._slot and scout["flags"] & 4 != 0)
108 ):
92 var item_name = "Unknown" 109 var item_name = "Unknown"
93 var item_player_game = ap.client._game_by_player[float(scout["player"])] 110 var item_player_game = ap.client._game_by_player[float(scout["player"])]
94 if ap.client._item_id_to_name[item_player_game].has(scout["item"]): 111 if ap.client._item_id_to_name[item_player_game].has(scout["item"]):
@@ -190,11 +207,132 @@ func _ready():
190 warp_enter.rotation_degrees.y = 90 207 warp_enter.rotation_degrees.y = 90
191 get_parent().add_child.call_deferred(warp_enter) 208 get_parent().add_child.call_deferred(warp_enter)
192 209
193 # Remove door behind X1.
194 if global.map == "the_entry": 210 if global.map == "the_entry":
211 # Remove door behind X1.
195 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1") 212 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
196 door_node.handleTriggered() 213 door_node.handleTriggered()
197 214
215 # Display win condition.
216 var sign_prefab = preload("res://objects/nodes/sign.tscn")
217 var sign1 = sign_prefab.instantiate()
218 sign1.position = Vector3(-7, 5, -15.01)
219 sign1.text = "victory"
220 get_parent().add_child.call_deferred(sign1)
221
222 var sign2 = sign_prefab.instantiate()
223 sign2.position = Vector3(-7, 4, -15.01)
224 sign2.text = "%s ending" % kEndingNameByVictoryValue.get(ap.victory_condition, "?")
225
226 var sign2_color = kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
227 if sign2_color == "white":
228 sign2_color = "silver"
229
230 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
231 get_parent().add_child.call_deferred(sign2)
232
233 # Add the strict purple ending validation.
234 if global.map == "the_sun_temple" and ap.strict_purple_ending:
235 var panel_prefab = preload("res://objects/nodes/panel.tscn")
236 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
237 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
238
239 var previous_panel = null
240 var next_y = -100
241 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
242 for word in words:
243 var panel = panel_prefab.instantiate()
244 panel.position = Vector3(0, next_y, 0)
245 next_y -= 10
246 panel.clue = word
247 panel.symbol = ""
248 panel.answer = word
249 panel.name = "EndCheck_%s" % word
250
251 var tpl = tpl_prefab.instantiate()
252 tpl.teleport_point = Vector3(0, 1, 0)
253 tpl.teleport_rotate = Vector3(-45, 180, 0)
254 tpl.target_path = panel
255 tpl.name = "Teleport"
256
257 if previous_panel == null:
258 tpl.senders.append(NodePath("/root/scene/Panels/End/panel_24"))
259 else:
260 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
261
262 var reversing = reverse_prefab.instantiate()
263 reversing.senders.append(NodePath(".."))
264 reversing.name = "Reversing"
265 tpl.senders.append(NodePath("../Reversing"))
266
267 panel.add_child.call_deferred(tpl)
268 panel.add_child.call_deferred(reversing)
269 get_parent().get_node("Panels").add_child.call_deferred(panel)
270
271 previous_panel = panel
272
273 # Duplicate the doors that usually wait on EQUINOX. We can't set the senders
274 # here for some reason so we actually set them in the door ready function.
275 var endplat = get_node("/root/scene/Components/Doors/EndPlatform")
276 var endplat2 = endplat.duplicate()
277 endplat2.name = "spe_EndPlatform"
278 endplat.get_parent().add_child.call_deferred(endplat2)
279 endplat.queue_free()
280
281 var entry2 = get_node("/root/scene/Components/Doors/entry_2")
282 var entry22 = entry2.duplicate()
283 entry22.name = "spe_entry_2"
284 entry2.get_parent().add_child.call_deferred(entry22)
285 entry2.queue_free()
286
287 # Add the strict cyan ending validation.
288 if global.map == "the_parthenon" and ap.strict_cyan_ending:
289 var panel_prefab = preload("res://objects/nodes/panel.tscn")
290 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
291 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
292
293 var previous_panel = null
294 var next_y = -100
295 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
296 for word in words:
297 var panel = panel_prefab.instantiate()
298 panel.position = Vector3(0, next_y, 0)
299 next_y -= 10
300 panel.clue = word
301 panel.symbol = "."
302 panel.answer = "%s%s" % [word, word]
303 panel.name = "EndCheck_%s" % word
304
305 var tpl = tpl_prefab.instantiate()
306 tpl.teleport_point = Vector3(0, 1, -11)
307 tpl.teleport_rotate = Vector3(-45, 0, 0)
308 tpl.target_path = panel
309 tpl.name = "Teleport"
310
311 if previous_panel == null:
312 tpl.senderGroup.append(NodePath("/root/scene/Panels/Rulers"))
313 else:
314 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
315
316 var reversing = reverse_prefab.instantiate()
317 reversing.senders.append(NodePath(".."))
318 reversing.name = "Reversing"
319 tpl.senders.append(NodePath("../Reversing"))
320
321 panel.add_child.call_deferred(tpl)
322 panel.add_child.call_deferred(reversing)
323 get_parent().get_node("Panels").add_child.call_deferred(panel)
324
325 previous_panel = panel
326
327 # Duplicate the door that usually waits on the rulers. We can't set the
328 # senders here for some reason so we actually set them in the door ready
329 # function.
330 var entry1 = get_node("/root/scene/Components/Doors/entry_1")
331 var entry12 = entry1.duplicate()
332 entry12.name = "spe_entry_1"
333 entry1.get_parent().add_child.call_deferred(entry12)
334 entry1.queue_free()
335
198 super._ready() 336 super._ready()
199 337
200 await get_tree().process_frame 338 await get_tree().process_frame
@@ -218,3 +356,7 @@ func _set_up_invis_wall(x, y, z, sx, sy, sz):
218 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF 356 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF
219 newwall.skeleton = ".." 357 newwall.skeleton = ".."
220 get_parent().add_child.call_deferred(newwall) 358 get_parent().add_child.call_deferred(newwall)
359
360
361func _process(_dt):
362 compass.update_rotation(global_rotation.y)
diff --git a/client/Archipelago/saver.gd b/client/Archipelago/saver.gd index 0fba9e7..44bc179 100644 --- a/client/Archipelago/saver.gd +++ b/client/Archipelago/saver.gd
@@ -7,3 +7,17 @@ func levelLoaded():
7 ap.keyboard.load_keyholders.call_deferred(global.map) 7 ap.keyboard.load_keyholders.call_deferred(global.map)
8 else: 8 else:
9 reload.call_deferred() 9 reload.call_deferred()
10
11
12func reload():
13 # Just rewriting this whole thing so I can remove Chris's safeguard.
14 var file = FileAccess.open(path + type + ".save", FileAccess.READ)
15 if file:
16 var data = file.get_var(true)
17 file.close()
18 for datum in data:
19 var saveable = get_node_or_null(datum[0])
20 if saveable != null:
21 saveable.is_complete = datum[1]
22 if saveable.is_complete:
23 saveable.loadData(saveable.is_complete)
diff --git a/client/Archipelago/settings_screen.gd b/client/Archipelago/settings_screen.gd index ff6f9df..b7bfacf 100644 --- a/client/Archipelago/settings_screen.gd +++ b/client/Archipelago/settings_screen.gd
@@ -44,8 +44,10 @@ func _ready():
44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd")) 44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd"))
45 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd")) 45 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd"))
46 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd")) 46 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleport.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd")) 48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd"))
48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd")) 49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd"))
50 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldport.gd"))
49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd")) 51 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd"))
50 52
51 var proto_script = load("user://maps/Archipelago/generated/proto.gd") 53 var proto_script = load("user://maps/Archipelago/generated/proto.gd")
@@ -67,6 +69,12 @@ func _ready():
67 textclient_instance.name = "Textclient" 69 textclient_instance.name = "Textclient"
68 global.add_child(textclient_instance) 70 global.add_child(textclient_instance)
69 71
72 var compass_overlay_script = load("user://maps/Archipelago/compass_overlay.gd")
73 var compass_overlay_instance = compass_overlay_script.new()
74 compass_overlay_instance.name = "Compass"
75 compass_overlay_instance.SCRIPT_compass = load("user://maps/Archipelago/compass.gd")
76 global.add_child(compass_overlay_instance)
77
70 var ap = global.get_node("Archipelago") 78 var ap = global.get_node("Archipelago")
71 var gamedata = global.get_node("Gamedata") 79 var gamedata = global.get_node("Gamedata")
72 ap.connect("ap_connected", connectionSuccessful) 80 ap.connect("ap_connected", connectionSuccessful)
@@ -99,6 +107,10 @@ func _ready():
99 $Panel/player_box.add_theme_font_size_override("font_size", 36) 107 $Panel/player_box.add_theme_font_size_override("font_size", 36)
100 $Panel/password_box.add_theme_font_size_override("font_size", 36) 108 $Panel/password_box.add_theme_font_size_override("font_size", 36)
101 109
110 # Set up version mismatch dialog.
111 $Panel/VersionMismatch.connect("confirmed", startGame)
112 $Panel/VersionMismatch.get_cancel_button().pressed.connect(versionMismatchDeclined)
113
102 114
103# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd 115# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd
104func installScriptExtension(childScript: Resource): 116func installScriptExtension(childScript: Resource):
@@ -128,6 +140,33 @@ func connectionStatus(message):
128 140
129func connectionSuccessful(): 141func connectionSuccessful():
130 var ap = global.get_node("Archipelago") 142 var ap = global.get_node("Archipelago")
143 var gamedata = global.get_node("Gamedata")
144
145 # Check for major version mismatch.
146 if ap.apworld_version[0] != gamedata.objects.get_version():
147 $Panel/AcceptDialog.exclusive = false
148
149 var popup = self.get_node("Panel/VersionMismatch")
150 popup.title = "Version Mismatch!"
151 popup.dialog_text = (
152 "This slot was generated using v%d.%d of the Lingo 2 apworld,\nwhich has a different major version than this client (v%d.%d).\nIt is highly recommended to play using the correct version of the client.\nYou may experience bugs or logic issues if you continue."
153 % [
154 ap.apworld_version[0],
155 ap.apworld_version[1],
156 gamedata.objects.get_version(),
157 ap.MOD_VERSION
158 ]
159 )
160 popup.exclusive = true
161 popup.popup_centered()
162
163 return
164
165 startGame()
166
167
168func startGame():
169 var ap = global.get_node("Archipelago")
131 170
132 # Save connection details 171 # Save connection details
133 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass] 172 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass]
@@ -166,6 +205,8 @@ func connectionSuccessful():
166 clearResourceCache("res://objects/nodes/panel.tscn") 205 clearResourceCache("res://objects/nodes/panel.tscn")
167 clearResourceCache("res://objects/nodes/player.tscn") 206 clearResourceCache("res://objects/nodes/player.tscn")
168 clearResourceCache("res://objects/nodes/saver.tscn") 207 clearResourceCache("res://objects/nodes/saver.tscn")
208 clearResourceCache("res://objects/nodes/teleport.tscn")
209 clearResourceCache("res://objects/nodes/worldport.tscn")
169 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn") 210 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn")
170 211
171 var paintings_dir = DirAccess.open("res://objects/meshes/paintings") 212 var paintings_dir = DirAccess.open("res://objects/meshes/paintings")
@@ -190,6 +231,13 @@ func connectionUnsuccessful(error_message):
190 popup.get_ok_button().visible = true 231 popup.get_ok_button().visible = true
191 popup.popup_centered() 232 popup.popup_centered()
192 233
234 $Panel/connect_button.disabled = false
235
236
237func versionMismatchDeclined():
238 $Panel/AcceptDialog.hide()
239 $Panel/connect_button.disabled = false
240
193 241
194func historySelected(index): 242func historySelected(index):
195 var ap = global.get_node("Archipelago") 243 var ap = global.get_node("Archipelago")
diff --git a/client/Archipelago/teleport.gd b/client/Archipelago/teleport.gd new file mode 100644 index 0000000..428d50b --- /dev/null +++ b/client/Archipelago/teleport.gd
@@ -0,0 +1,38 @@
1extends "res://scripts/nodes/teleport.gd"
2
3var item_id
4var item_amount
5
6
7func _ready():
8 var node_path = String(
9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
10 )
11
12 var gamedata = global.get_node("Gamedata")
13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
14 if door_id != null:
15 var ap = global.get_node("Archipelago")
16 var item_lock = ap.get_item_id_for_door(door_id)
17
18 if item_lock != null:
19 item_id = item_lock[0]
20 item_amount = item_lock[1]
21
22 self.senders = []
23 self.senderGroup = []
24 self.nested = false
25 self.complete_at = 0
26 self.max_length = 0
27 self.excludeSenders = []
28
29 call_deferred("_readier")
30
31 super._ready()
32
33
34func _readier():
35 var ap = global.get_node("Archipelago")
36
37 if ap.client.getItemAmount(item_id) >= item_amount:
38 handleTriggered()
diff --git a/client/Archipelago/teleportListener.gd b/client/Archipelago/teleportListener.gd index 4a7deec..6f363af 100644 --- a/client/Archipelago/teleportListener.gd +++ b/client/Archipelago/teleportListener.gd
@@ -9,6 +9,17 @@ func _ready():
9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() 9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
10 ) 10 )
11 11
12 if (
13 global.map == "daedalus"
14 and (
15 node_path == "Components/Triggers/teleportListenerConnections"
16 or node_path == "Components/Triggers/teleportListenerConnections2"
17 )
18 ):
19 # Effectively disable these.
20 teleport_point = target_path.position
21 return
22
12 var gamedata = global.get_node("Gamedata") 23 var gamedata = global.get_node("Gamedata")
13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) 24 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
14 if door_id != null: 25 if door_id != null:
diff --git a/client/Archipelago/worldport.gd b/client/Archipelago/worldport.gd new file mode 100644 index 0000000..d0fb6c9 --- /dev/null +++ b/client/Archipelago/worldport.gd
@@ -0,0 +1,10 @@
1extends "res://scripts/nodes/worldport.gd"
2
3
4func _ready():
5 if global.map == "icarus" and exit == "daedalus":
6 var ap = global.get_node("Archipelago")
7 if not ap.daedalus_roof_access:
8 entry_point = Vector3(58, 10, 0)
9
10 super._ready()
diff --git a/client/Archipelago/worldportListener.gd b/client/Archipelago/worldportListener.gd index c31c825..5c2faff 100644 --- a/client/Archipelago/worldportListener.gd +++ b/client/Archipelago/worldportListener.gd
@@ -1,8 +1,8 @@
1extends "res://scripts/nodes/listeners/worldportListener.gd" 1extends "res://scripts/nodes/listeners/worldportListener.gd"
2 2
3 3
4func changeScene(): 4func handleTriggered():
5 if exit == "menus/credits": 5 if exit == "menus/credits":
6 return 6 return
7 7
8 super.changeScene() 8 super.handleTriggered()
diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md new file mode 100644 index 0000000..a0a538b --- /dev/null +++ b/client/CHANGELOG.md
@@ -0,0 +1,59 @@
1# lingo2-archipelago Client Releases
2
3## v5.6 - 2025-09-17
4
5- Letter locations will no longer reappear after being collected.
6- This also prevents a potential scenario in which it is impossible to access
7 the location "The Congruent - Obverse Yellow Puzzles" when door shuffle is
8 disabled.
9
10Download:
11[lingo2-archipelago-client-v5.6.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v5.6.zip)<br/>
12Source:
13[v5.6](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v5.6)
14
15## v5.5 - 2025-09-16
16
17- Compatability update for v5.5 of the apworld.
18
19Download:
20[lingo2-archipelago-client-v5.5.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v5.5.zip)<br/>
21Source:
22[v5.5](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v5.5)
23
24## v4.4 - 2025-09-13
25
26- Added support for anti-collectable trap items.
27- Fixed entrance to The Jubilant not opening properly when using control center
28 color shuffle.
29- Fixed the location "The Entry (Colored Doors Area) - OPEN" not sending.
30- Fixed level 2 letters not activating properly when letter shuffle is set to
31 Item Cyan.
32- Messages are now cleared out when returning to the main menu.
33- The player is prevented from accidentally breaking roof access logic when
34 returning to Daedalus from Icarus.
35
36Download:
37[lingo2-archipelago-client-v4.4.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v4.4.zip)<br/>
38Source:
39[v4.4](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v4.4)
40
41## v3.3 - 2025-09-12
42
43- Fixed issue downloading large datapackages (such as TUNIC's).
44- Connection failures now show error messages.
45
46Download:
47[lingo2-archipelago-client-v3.3.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v3.3.zip)<br/>
48Source:
49[v3.3](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v3.3)
50
51## v3.2 - 2025-09-12
52
53- Initial release for testing. Features include door shuffle, letter shuffle,
54 and symbol shuffle.
55
56Download:
57[lingo2-archipelago-client-v3.2.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v3.2.zip)<br/>
58Source:
59[v3.2](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v3.2)
diff --git a/client/README.md b/client/README.md index 1e94bdb..99589c5 100644 --- a/client/README.md +++ b/client/README.md
@@ -88,10 +88,3 @@ not recommended to load these save files outside of the randomizer.
88 88
89A connection to Archipelago is required to resume playing a multiworld. This is 89A connection to Archipelago is required to resume playing a multiworld. This is
90because the set of items you have received is not stored locally. 90because the set of items you have received is not stored locally.
91
92### What about wall snipes?
93
94"Wall sniping" refers to the fact that you are able to solve puzzles on the
95other side of opaque walls. The player is never expected to or required to do
96this in normal gameplay. This randomizer does not change how wall snipes work,
97but it will likewise never require the use of them.
diff --git a/client/archipelago.tscn b/client/archipelago.tscn index 40dd46f..da83b23 100644 --- a/client/archipelago.tscn +++ b/client/archipelago.tscn
@@ -40,6 +40,7 @@ offset_right = 1920.0
40offset_bottom = 225.0 40offset_bottom = 225.0
41text = "ARCHIPELAGO" 41text = "ARCHIPELAGO"
42valign = 1 42valign = 1
43horizontal_alignment = 1
43theme = ExtResource("2_g4bvn") 44theme = ExtResource("2_g4bvn")
44 45
45[node name="credit" parent="Panel" type="Label"] 46[node name="credit" parent="Panel" type="Label"]
@@ -150,6 +151,10 @@ caret_blink = true
150offset_right = 83.0 151offset_right = 83.0
151offset_bottom = 58.0 152offset_bottom = 58.0
152 153
154[node name="VersionMismatch" type="ConfirmationDialog" parent="Panel"]
155offset_right = 83.0
156offset_bottom = 58.0
157
153[node name="connection_history" type="MenuButton" parent="Panel"] 158[node name="connection_history" type="MenuButton" parent="Panel"]
154offset_left = 1239.0 159offset_left = 1239.0
155offset_top = 276.0 160offset_top = 276.0
diff --git a/data/connections.txtpb b/data/connections.txtpb index d718c96..17f71a3 100644 --- a/data/connections.txtpb +++ b/data/connections.txtpb
@@ -20,7 +20,7 @@ connections {
20 from { 20 from {
21 port { 21 port {
22 map: "the_entry" 22 map: "the_entry"
23 room: "Flipped Second Room" 23 room: "Four Rooms Entrance"
24 name: "FOUR" 24 name: "FOUR"
25 } 25 }
26 } 26 }
@@ -141,7 +141,7 @@ connections {
141 to { 141 to {
142 port { 142 port {
143 map: "the_darkroom" 143 map: "the_darkroom"
144 room: "First Room" 144 room: "Cyan Hallway"
145 name: "COLORFUL" 145 name: "COLORFUL"
146 } 146 }
147 } 147 }
@@ -157,7 +157,7 @@ connections {
157 to { 157 to {
158 port { 158 port {
159 map: "the_darkroom" 159 map: "the_darkroom"
160 room: "Second Room" 160 room: "Congruent Entrance"
161 name: "CONGRUENT" 161 name: "CONGRUENT"
162 } 162 }
163 } 163 }
@@ -233,7 +233,7 @@ connections {
233 from { 233 from {
234 port { 234 port {
235 map: "the_darkroom" 235 map: "the_darkroom"
236 room: "First Room" 236 room: "Double Sided Entrance"
237 name: "DOUBLESIDED" 237 name: "DOUBLESIDED"
238 } 238 }
239 } 239 }
@@ -308,6 +308,23 @@ connections {
308 name: "GALLERY" 308 name: "GALLERY"
309 } 309 }
310 } 310 }
311 oneway: true
312}
313connections {
314 from {
315 port {
316 map: "the_butterfly"
317 room: "Main Area"
318 name: "GALLERY"
319 }
320 }
321 to {
322 room {
323 map: "the_gallery"
324 name: "Main Area"
325 }
326 }
327 oneway: true
311} 328}
312connections { 329connections {
313 from { 330 from {
@@ -618,7 +635,7 @@ connections {
618 from { 635 from {
619 port { 636 port {
620 map: "the_entry" 637 map: "the_entry"
621 room: "Link Area" 638 room: "Liberated Entrance"
622 name: "BLUE" 639 name: "BLUE"
623 } 640 }
624 } 641 }
@@ -666,7 +683,7 @@ connections {
666 from { 683 from {
667 port { 684 port {
668 map: "the_entry" 685 map: "the_entry"
669 room: "Link Area" 686 room: "Literate Entrance"
670 name: "BROWN" 687 name: "BROWN"
671 } 688 }
672 } 689 }
@@ -876,6 +893,7 @@ connections {
876 } 893 }
877 } 894 }
878 oneway: true 895 oneway: true
896 bypass_target_door: true
879} 897}
880connections { 898connections {
881 from { 899 from {
@@ -1455,7 +1473,6 @@ connections {
1455 name: "GREAT" 1473 name: "GREAT"
1456 } 1474 }
1457 } 1475 }
1458 door { map: "the_great" name: "Daedalus Entrance" }
1459 oneway: true 1476 oneway: true
1460} 1477}
1461connections { 1478connections {
@@ -1474,6 +1491,7 @@ connections {
1474 } 1491 }
1475 } 1492 }
1476 oneway: true 1493 oneway: true
1494 bypass_target_door: true
1477} 1495}
1478connections { 1496connections {
1479 from { 1497 from {
@@ -1769,12 +1787,13 @@ connections {
1769 } 1787 }
1770 } 1788 }
1771 oneway: true 1789 oneway: true
1790 bypass_target_door: true
1772} 1791}
1773connections { 1792connections {
1774 from { 1793 from {
1775 port { 1794 port {
1776 map: "the_bearer" 1795 map: "the_bearer"
1777 room: "Back Area" 1796 room: "Tree Entrance"
1778 name: "TREE" 1797 name: "TREE"
1779 } 1798 }
1780 } 1799 }
@@ -1851,7 +1870,6 @@ connections {
1851 } 1870 }
1852} 1871}
1853connections { 1872connections {
1854 # Two one-way connections because the door only blocks one direction.
1855 from { 1873 from {
1856 port { 1874 port {
1857 map: "the_great" 1875 map: "the_great"
@@ -1868,6 +1886,7 @@ connections {
1868 } 1886 }
1869} 1887}
1870connections { 1888connections {
1889 # Two one-way connections because the door only blocks one direction.
1871 from { 1890 from {
1872 port { 1891 port {
1873 map: "the_unkempt" 1892 map: "the_unkempt"
@@ -1900,6 +1919,7 @@ connections {
1900 } 1919 }
1901 } 1920 }
1902 oneway: true 1921 oneway: true
1922 bypass_target_door: true
1903} 1923}
1904connections { 1924connections {
1905 from { 1925 from {
@@ -2425,3 +2445,19 @@ connections {
2425 } 2445 }
2426 } 2446 }
2427} 2447}
2448connections {
2449 from {
2450 painting {
2451 map: "the_entry"
2452 room: "Eye Room"
2453 name: "GALLERY"
2454 }
2455 }
2456 to {
2457 room {
2458 map: "the_gallery"
2459 name: "Main Area"
2460 }
2461 }
2462 oneway: true
2463}
diff --git a/data/ids.yaml b/data/ids.yaml index 30a400b..80ace1a 100644 --- a/data/ids.yaml +++ b/data/ids.yaml
@@ -748,7 +748,7 @@ maps:
748 Back (2): 2090 748 Back (2): 2090
749 Colors: 2097 749 Colors: 2097
750 FIR: 2095 750 FIR: 2095
751 Left: 2088 751 Near Obscured Puzzles: 2088
752 OAK: 2093 752 OAK: 2093
753 PINE: 2094 753 PINE: 2094
754 WALK BACK: 2091 754 WALK BACK: 2091
@@ -987,7 +987,6 @@ maps:
987 Blue Rainbow Room: 1538 987 Blue Rainbow Room: 1538
988 Blue Room: 1477 988 Blue Room: 1477
989 Blue Room Entrance: 1476 989 Blue Room Entrance: 1476
990 Blue Smiley Entrance: 1478
991 Blue Smiley Exit To Red: 1547 990 Blue Smiley Exit To Red: 1547
992 Book Room Entrance: 1588 991 Book Room Entrance: 1588
993 Book Room Exit: 1592 992 Book Room Exit: 1592
@@ -1018,10 +1017,10 @@ maps:
1018 Eye Painting: 2751 1017 Eye Painting: 2751
1019 Eye Painting Exit: 1446 1018 Eye Painting Exit: 1446
1020 F Keyholder Door: 1551 1019 F Keyholder Door: 1551
1021 F2 Room Back Left Door: 1491 1020 F2 Room Northwest Door: 1491
1022 F2 Room Back Middle Door: 1492 1021 F2 Room Southeast Door: 1487
1023 F2 Room Back Right Door: 1490 1022 F2 Room Southwest Door: 1490
1024 F2 Room Entrance: 1487 1023 F2 Room West Door: 1492
1025 Flip Painting Blocker: 1552 1024 Flip Painting Blocker: 1552
1026 Globe Room East Door: 1589 1025 Globe Room East Door: 1589
1027 Globe Room South Door: 1591 1026 Globe Room South Door: 1591
@@ -1044,6 +1043,7 @@ maps:
1044 House Entrance: 1495 1043 House Entrance: 1495
1045 House Side Door: 1566 1044 House Side Door: 1566
1046 Intense Room Entrance: 1522 1045 Intense Room Entrance: 1522
1046 Lime Hexes: 2810
1047 Magenta Hexes: 2272 1047 Magenta Hexes: 2272
1048 Magic Room Entrance: 1500 1048 Magic Room Entrance: 1500
1049 Magic Room Panels: 1499 1049 Magic Room Panels: 1499
@@ -1056,7 +1056,8 @@ maps:
1056 Near Sweet Brown Door: 1561 1056 Near Sweet Brown Door: 1561
1057 Near Yellow Room Door: 1565 1057 Near Yellow Room Door: 1565
1058 North Castle Panel: 2742 1058 North Castle Panel: 2742
1059 O2 Room Back Door: 1485 1059 O2 Room Northeast Door: 1485
1060 O2 Room Southeast Door: 1478
1060 Orange Rainbow Panel: 2267 1061 Orange Rainbow Panel: 2267
1061 Orange Rainbow Room: 1535 1062 Orange Rainbow Room: 1535
1062 Orange Room: 1507 1063 Orange Room: 1507
@@ -1110,8 +1111,8 @@ maps:
1110 South Castle Area Entrance: 1575 1111 South Castle Area Entrance: 1575
1111 South Castle Panel: 2744 1112 South Castle Panel: 2744
1112 Southwest Area Intersection: 1475 1113 Southwest Area Intersection: 1475
1113 Splintering Exit Left Door: 1449 1114 Splintering Exit North Door: 1449
1114 Splintering Exit Right Door: 1450 1115 Splintering Exit South Door: 1450
1115 Starting Room East Wall Center Door: 1439 1116 Starting Room East Wall Center Door: 1439
1116 Starting Room East Wall North Door: 1440 1117 Starting Room East Wall North Door: 1440
1117 Starting Room North Wall Center Door: 1432 1118 Starting Room North Wall Center Door: 1432
@@ -1124,16 +1125,17 @@ maps:
1124 Starting Room West Wall South Door: 1433 1125 Starting Room West Wall South Door: 1433
1125 Sticks And Stones Door: 1593 1126 Sticks And Stones Door: 1593
1126 Temple of the Eyes Entrance: 1444 1127 Temple of the Eyes Entrance: 1444
1127 U2 Room Back Door: 1497 1128 Theo Panels: 2811
1128 U2 Room Back Right Door: 1496 1129 U2 Room East Door: 1498
1129 U2 Room Entrance: 1498 1130 U2 Room Southeast Door: 1493
1130 U2 Room Shortcut: 1493 1131 U2 Room Southwest Door: 1496
1132 U2 Room West Door: 1497
1131 Welcome Back Door: 1435 1133 Welcome Back Door: 1435
1132 Welcome Back Secret Door: 1434 1134 Welcome Back Secret Door: 1434
1133 West Castle Panel: 2743 1135 West Castle Panel: 2743
1134 White Hallway From Entry: 1488 1136 White Hallway From Entry: 1488
1135 Wonderland Left Door: 1520 1137 Wonderland North Door: 1520
1136 Wonderland Right Door: 1504 1138 Wonderland South Door: 1504
1137 Yellow Rainbow Panel: 2268 1139 Yellow Rainbow Panel: 2268
1138 Yellow Rainbow Room: 1536 1140 Yellow Rainbow Room: 1536
1139 Yellow Room: 1568 1141 Yellow Room: 1568
@@ -2325,9 +2327,9 @@ maps:
2325 keyholders: 2327 keyholders:
2326 S: 2767 2328 S: 2767
2327 doors: 2329 doors:
2328 Left Room Puzzles: 763 2330 Blue Side Puzzles: 763
2331 Green Side Puzzles: 764
2329 Main Room Door: 2750 2332 Main Room Door: 2750
2330 Right Room Puzzles: 764
2331 the_orb: 2333 the_orb:
2332 rooms: 2334 rooms:
2333 Main Area: 2335 Main Area:
@@ -2557,17 +2559,17 @@ maps:
2557 TYPIST BEAR RIGHT WING: 968 2559 TYPIST BEAR RIGHT WING: 968
2558 WING: 950 2560 WING: 950
2559 doors: 2561 doors:
2560 Bottom Left Door: 894 2562 Northeast Door: 893
2561 Bottom Left Puzzles: 898 2563 Northeast Puzzles: 897
2562 Bottom Right Door: 895 2564 Northwest Door: 892
2563 Bottom Right Puzzles: 899 2565 Northwest Puzzles: 896
2564 Repetitive Entrance: 888 2566 Repetitive Entrance: 888
2565 Sirenic Entrance: 890 2567 Sirenic Entrance: 890
2568 Southeast Door: 895
2569 Southeast Puzzles: 899
2570 Southwest Door: 894
2571 Southwest Puzzles: 898
2566 Symbolic Entrance: 889 2572 Symbolic Entrance: 889
2567 Top Left Door: 892
2568 Top Left Puzzles: 896
2569 Top Right Door: 893
2570 Top Right Puzzles: 897
2571 Turtle Entrance: 891 2573 Turtle Entrance: 891
2572 the_quiet: 2574 the_quiet:
2573 rooms: 2575 rooms:
@@ -2650,7 +2652,8 @@ maps:
2650 rooms: 2652 rooms:
2651 Anti Room: 2653 Anti Room:
2652 panels: 2654 panels:
2653 EYE: 1041 2655 EYE (1): 1041
2656 EYE (2): 2813
2654 HA (1): 1035 2657 HA (1): 1035
2655 HA (2): 1036 2658 HA (2): 1036
2656 HA (3): 1037 2659 HA (3): 1037
@@ -2757,6 +2760,7 @@ maps:
2757 W: 1117 2760 W: 1117
2758 ZEROING: 1118 2761 ZEROING: 1118
2759 doors: 2762 doors:
2763 Anti-Collectable: 2812
2760 Anti-Collectable Room: 1025 2764 Anti-Collectable Room: 1025
2761 Black Hallway: 2780 2765 Black Hallway: 2780
2762 Cyan Door: 1028 2766 Cyan Door: 1028
@@ -3448,9 +3452,9 @@ maps:
3448 doors: 3452 doors:
3449 Cog Rhino Hug Rug: 2586 3453 Cog Rhino Hug Rug: 2586
3450 Control Center Orange Door: 2582 3454 Control Center Orange Door: 2582
3455 East Door: 2580
3451 Honor Our Hint: 2585 3456 Honor Our Hint: 2585
3452 Let Untrue Tie: 2583 3457 Let Untrue Tie: 2583
3453 Right Door: 2580
3454 Routine Out Chute: 2584 3458 Routine Out Chute: 2584
3455 W2 Room Door: 2581 3459 W2 Room Door: 2581
3456 the_unyielding: 3460 the_unyielding:
@@ -3838,6 +3842,32 @@ special:
3838 A Job Well Done: 1160 3842 A Job Well Done: 1160
3839 Age Symbol: 2791 3843 Age Symbol: 2791
3840 Anagram Symbol: 2792 3844 Anagram Symbol: 2792
3845 Anti A: 2814
3846 Anti B: 2815
3847 Anti C: 2816
3848 Anti D: 2817
3849 Anti E: 2818
3850 Anti F: 2819
3851 Anti G: 2820
3852 Anti H: 2821
3853 Anti I: 2822
3854 Anti J: 2823
3855 Anti K: 2824
3856 Anti L: 2825
3857 Anti M: 2826
3858 Anti N: 2827
3859 Anti O: 2828
3860 Anti P: 2829
3861 Anti Q: 2830
3862 Anti R: 2831
3863 Anti S: 2832
3864 Anti T: 2833
3865 Anti U: 2834
3866 Anti V: 2835
3867 Anti W: 2836
3868 Anti X: 2837
3869 Anti Y: 2838
3870 Anti Z: 2839
3841 Boxes Symbol: 2793 3871 Boxes Symbol: 2793
3842 Cross Symbol: 2794 3872 Cross Symbol: 2794
3843 Eval Symbol: 2795 3873 Eval Symbol: 2795
diff --git a/data/maps/daedalus/connections.txtpb b/data/maps/daedalus/connections.txtpb index 09613ae..cb27c38 100644 --- a/data/maps/daedalus/connections.txtpb +++ b/data/maps/daedalus/connections.txtpb
@@ -100,6 +100,11 @@ connections {
100 oneway: true 100 oneway: true
101} 101}
102connections { 102connections {
103 from_room: "Outside House"
104 to_room: "Blue Hallway Tall Side"
105 door { name: "House Side Door" }
106}
107connections {
103 from_room: "Purple SE Vestibule" 108 from_room: "Purple SE Vestibule"
104 to_room: "Welcome Back Area" 109 to_room: "Welcome Back Area"
105 oneway: true 110 oneway: true
@@ -222,12 +227,12 @@ connections {
222connections { 227connections {
223 from_room: "West Castle Area" 228 from_room: "West Castle Area"
224 to_room: "Post Orange Smiley Three Way" 229 to_room: "Post Orange Smiley Three Way"
225 door { name: "Splintering Exit Left Door" } 230 door { name: "Splintering Exit North Door" }
226} 231}
227connections { 232connections {
228 from_room: "West Castle Area" 233 from_room: "West Castle Area"
229 to_room: "Amber North 2" 234 to_room: "Amber North 2"
230 door { name: "Splintering Exit Right Door" } 235 door { name: "Splintering Exit South Door" }
231} 236}
232connections { 237connections {
233 from_room: "Z2 Room" 238 from_room: "Z2 Room"
@@ -378,7 +383,7 @@ connections {
378connections { 383connections {
379 from_room: "O2 Room" 384 from_room: "O2 Room"
380 to_room: "Blue Smiley" 385 to_room: "Blue Smiley"
381 door { name: "Blue Smiley Entrance" } 386 door { name: "O2 Room Southeast Door" }
382} 387}
383connections { 388connections {
384 from_room: "O2 Room" 389 from_room: "O2 Room"
@@ -408,7 +413,7 @@ connections {
408connections { 413connections {
409 from_room: "O2 Room" 414 from_room: "O2 Room"
410 to_room: "Blue Hallway" 415 to_room: "Blue Hallway"
411 door { name: "O2 Room Back Door" } 416 door { name: "O2 Room Northeast Door" }
412} 417}
413connections { 418connections {
414 from_room: "O2 Room" 419 from_room: "O2 Room"
@@ -423,7 +428,7 @@ connections {
423connections { 428connections {
424 from_room: "Sweet Foyer" 429 from_room: "Sweet Foyer"
425 to_room: "F2 Room" 430 to_room: "F2 Room"
426 door { name: "F2 Room Entrance" } 431 door { name: "F2 Room Southeast Door" }
427} 432}
428connections { 433connections {
429 from_room: "Globe Room" 434 from_room: "Globe Room"
@@ -438,17 +443,17 @@ connections {
438connections { 443connections {
439 from_room: "F2 Room" 444 from_room: "F2 Room"
440 to_room: "Blue Hallway" 445 to_room: "Blue Hallway"
441 door { name: "F2 Room Back Right Door" } 446 door { name: "F2 Room Southwest Door" }
442} 447}
443connections { 448connections {
444 from_room: "F2 Room" 449 from_room: "F2 Room"
445 to_room: "Outside Salt Room" 450 to_room: "Outside Salt Room"
446 door { name: "F2 Room Back Left Door" } 451 door { name: "F2 Room Northwest Door" }
447} 452}
448connections { 453connections {
449 from_room: "F2 Room" 454 from_room: "F2 Room"
450 to_room: "Red Color Door" 455 to_room: "Red Color Door"
451 door { name: "F2 Room Back Middle Door" } 456 door { name: "F2 Room West Door" }
452 oneway: true 457 oneway: true
453 # This is the red backside, which has nothing in it. Maybe could be its own 458 # This is the red backside, which has nothing in it. Maybe could be its own
454 # region at some point. 459 # region at some point.
@@ -461,7 +466,7 @@ connections {
461connections { 466connections {
462 from_room: "U2 Room" 467 from_room: "U2 Room"
463 to_room: "Maze Paintings Area" 468 to_room: "Maze Paintings Area"
464 door { name: "U2 Room Shortcut" } 469 door { name: "U2 Room Southeast Door" }
465} 470}
466connections { 471connections {
467 from_room: "Maze Paintings Area" 472 from_room: "Maze Paintings Area"
@@ -476,17 +481,17 @@ connections {
476connections { 481connections {
477 from_room: "U2 Room" 482 from_room: "U2 Room"
478 to_room: "Purple SE Vestibule" 483 to_room: "Purple SE Vestibule"
479 door { name: "U2 Room Back Right Door" } 484 door { name: "U2 Room Southwest Door" }
480} 485}
481connections { 486connections {
482 from_room: "U2 Room" 487 from_room: "U2 Room"
483 to_room: "Purple Room East" 488 to_room: "Purple Room East"
484 door { name: "U2 Room Back Door" } 489 door { name: "U2 Room West Door" }
485} 490}
486connections { 491connections {
487 from_room: "Maze" 492 from_room: "Maze"
488 to_room: "U2 Room" 493 to_room: "U2 Room"
489 door { name: "U2 Room Entrance" } 494 door { name: "U2 Room East Door" }
490} 495}
491connections { 496connections {
492 from_room: "Outside Magic Room" 497 from_room: "Outside Magic Room"
@@ -511,7 +516,7 @@ connections {
511connections { 516connections {
512 from_room: "Wonderland" 517 from_room: "Wonderland"
513 to_room: "Black Hex" 518 to_room: "Black Hex"
514 door { name: "Wonderland Right Door" } 519 door { name: "Wonderland South Door" }
515} 520}
516connections { 521connections {
517 from_room: "Outside Pyramid" 522 from_room: "Outside Pyramid"
@@ -601,7 +606,7 @@ connections {
601connections { 606connections {
602 from_room: "Wonderland" 607 from_room: "Wonderland"
603 to_room: "Number Paintings Area" 608 to_room: "Number Paintings Area"
604 door { name: "Wonderland Left Door" } 609 door { name: "Wonderland North Door" }
605} 610}
606connections { 611connections {
607 from_room: "Outside House" 612 from_room: "Outside House"
@@ -1857,3 +1862,15 @@ connections {
1857 oneway: true 1862 oneway: true
1858 roof_access: true 1863 roof_access: true
1859} 1864}
1865connections {
1866 from_room: "Roof"
1867 to_room: "F Keyholder"
1868 oneway: true
1869 roof_access: true
1870}
1871connections {
1872 from_room: "Roof"
1873 to_room: "Yellow Color Backside"
1874 oneway: true
1875 roof_access: true
1876}
diff --git a/data/maps/daedalus/doors.txtpb b/data/maps/daedalus/doors.txtpb index ccbf3f0..95a3537 100644 --- a/data/maps/daedalus/doors.txtpb +++ b/data/maps/daedalus/doors.txtpb
@@ -195,8 +195,8 @@ doors {
195} 195}
196doors { 196doors {
197 name: "Welcome Back Door" 197 name: "Welcome Back Door"
198 type: STANDARD 198 type: LOCATION_ONLY
199 receivers: "Components/Doors/Entry/entry_14" 199 #receivers: "Components/Doors/Entry/entry_14"
200 panels { room: "Welcome Back Area" name: "GREETINGS OLD FRIEND" } 200 panels { room: "Welcome Back Area" name: "GREETINGS OLD FRIEND" }
201 location_room: "Welcome Back Area" 201 location_room: "Welcome Back Area"
202} 202}
@@ -298,7 +298,7 @@ doors {
298 location_name: "Black Hex" 298 location_name: "Black Hex"
299} 299}
300doors { 300doors {
301 name: "Splintering Exit Left Door" 301 name: "Splintering Exit North Door"
302 type: STANDARD 302 type: STANDARD
303 receivers: "Components/Doors/Entry/gate_4" 303 receivers: "Components/Doors/Entry/gate_4"
304 panels { room: "West Castle Area" name: "EVER" } 304 panels { room: "West Castle Area" name: "EVER" }
@@ -308,7 +308,7 @@ doors {
308 location_room: "West Castle Area" 308 location_room: "West Castle Area"
309} 309}
310doors { 310doors {
311 name: "Splintering Exit Right Door" 311 name: "Splintering Exit South Door"
312 type: ITEM_ONLY 312 type: ITEM_ONLY
313 receivers: "Components/Doors/Entry/gate_5" 313 receivers: "Components/Doors/Entry/gate_5"
314 panels { room: "West Castle Area" name: "EVER" } 314 panels { room: "West Castle Area" name: "EVER" }
@@ -493,7 +493,6 @@ doors {
493 panels { room: "Outside House" name: "WALLS" } 493 panels { room: "Outside House" name: "WALLS" }
494 panels { room: "Outside House" name: "LOCK" } 494 panels { room: "Outside House" name: "LOCK" }
495 location_room: "Outside House" 495 location_room: "Outside House"
496 location_name: "North Purple Vestibules"
497} 496}
498doors { 497doors {
499 name: "Purple NW Vestibule" 498 name: "Purple NW Vestibule"
@@ -716,7 +715,7 @@ doors {
716 panels { room: "O2 Room" name: "UNBLOCKED" } 715 panels { room: "O2 Room" name: "UNBLOCKED" }
717} 716}
718doors { 717doors {
719 name: "Blue Smiley Entrance" 718 name: "O2 Room Southeast Door"
720 type: STANDARD 719 type: STANDARD
721 receivers: "Components/Doors/Halls/oroom_2" 720 receivers: "Components/Doors/Halls/oroom_2"
722 panels { room: "O2 Room" name: "HONEST" } 721 panels { room: "O2 Room" name: "HONEST" }
@@ -822,7 +821,7 @@ doors {
822 name: "Composite Room NW Entrance" 821 name: "Composite Room NW Entrance"
823 type: STANDARD 822 type: STANDARD
824 receivers: "Components/Doors/Halls/oroom_10" 823 receivers: "Components/Doors/Halls/oroom_10"
825 panels { room: "Red Color Door" name: "Left" } 824 panels { room: "Red Color Door" name: "Near Obscured Puzzles" }
826 location_room: "Red Color Door" 825 location_room: "Red Color Door"
827} 826}
828doors { 827doors {
@@ -857,7 +856,7 @@ doors {
857 location_name: "South Rooms" 856 location_name: "South Rooms"
858} 857}
859doors { 858doors {
860 name: "O2 Room Back Door" 859 name: "O2 Room Northeast Door"
861 type: STANDARD 860 type: STANDARD
862 receivers: "Components/Doors/Halls/oroom_4" 861 receivers: "Components/Doors/Halls/oroom_4"
863 panels { room: "O2 Room" name: "UNBLOCKED" } 862 panels { room: "O2 Room" name: "UNBLOCKED" }
@@ -884,7 +883,7 @@ doors {
884 panels { room: "F2 Room" name: "SHAPE" } 883 panels { room: "F2 Room" name: "SHAPE" }
885} 884}
886doors { 885doors {
887 name: "F2 Room Entrance" 886 name: "F2 Room Southeast Door"
888 type: STANDARD 887 type: STANDARD
889 receivers: "Components/Doors/Halls/froom_2" 888 receivers: "Components/Doors/Halls/froom_2"
890 panels { room: "Sweet Foyer" name: "RENT (1)" } 889 panels { room: "Sweet Foyer" name: "RENT (1)" }
@@ -903,21 +902,21 @@ doors {
903 control_center_color: "purple" 902 control_center_color: "purple"
904} 903}
905doors { 904doors {
906 name: "F2 Room Back Right Door" 905 name: "F2 Room Southwest Door"
907 type: STANDARD 906 type: STANDARD
908 receivers: "Components/Doors/Halls/froom_3" 907 receivers: "Components/Doors/Halls/froom_3"
909 panels { room: "F2 Room" name: "RISKY" } 908 panels { room: "F2 Room" name: "RISKY" }
910 location_room: "F2 Room" 909 location_room: "F2 Room"
911} 910}
912doors { 911doors {
913 name: "F2 Room Back Left Door" 912 name: "F2 Room Northwest Door"
914 type: STANDARD 913 type: STANDARD
915 receivers: "Components/Doors/Halls/froom_4" 914 receivers: "Components/Doors/Halls/froom_4"
916 panels { room: "F2 Room" name: "SHAPE" } 915 panels { room: "F2 Room" name: "SHAPE" }
917 location_room: "F2 Room" 916 location_room: "F2 Room"
918} 917}
919doors { 918doors {
920 name: "F2 Room Back Middle Door" 919 name: "F2 Room West Door"
921 type: STANDARD 920 type: STANDARD
922 receivers: "Components/Doors/Halls/froom_5" 921 receivers: "Components/Doors/Halls/froom_5"
923 panels { room: "F2 Room" name: "DIRT" } 922 panels { room: "F2 Room" name: "DIRT" }
@@ -938,7 +937,7 @@ doors {
938 panels { room: "U2 Room" name: "HEAVEN" } 937 panels { room: "U2 Room" name: "HEAVEN" }
939} 938}
940doors { 939doors {
941 name: "U2 Room Shortcut" 940 name: "U2 Room Southeast Door"
942 type: STANDARD 941 type: STANDARD
943 receivers: "Components/Doors/Halls/uroom_2" 942 receivers: "Components/Doors/Halls/uroom_2"
944 panels { room: "U2 Room" name: "WICKED" } 943 panels { room: "U2 Room" name: "WICKED" }
@@ -959,21 +958,21 @@ doors {
959 location_room: "House Entrance" 958 location_room: "House Entrance"
960} 959}
961doors { 960doors {
962 name: "U2 Room Back Right Door" 961 name: "U2 Room Southwest Door"
963 type: STANDARD 962 type: STANDARD
964 receivers: "Components/Doors/Halls/uroom_3" 963 receivers: "Components/Doors/Halls/uroom_3"
965 panels { room: "U2 Room" name: "HEAVEN" } 964 panels { room: "U2 Room" name: "HEAVEN" }
966 location_room: "U2 Room" 965 location_room: "U2 Room"
967} 966}
968doors { 967doors {
969 name: "U2 Room Back Door" 968 name: "U2 Room West Door"
970 type: ITEM_ONLY 969 type: ITEM_ONLY
971 receivers: "Components/Doors/Halls/uroom_5" 970 receivers: "Components/Doors/Halls/uroom_5"
972 panels { room: "Purple Room South" name: "ANY" } 971 panels { room: "Purple Room South" name: "ANY" }
973 panels { room: "Outside House" name: "A" } 972 panels { room: "Outside House" name: "A" }
974} 973}
975doors { 974doors {
976 name: "U2 Room Entrance" 975 name: "U2 Room East Door"
977 type: ITEM_ONLY 976 type: ITEM_ONLY
978 receivers: "Components/Doors/Halls/uroom_4" 977 receivers: "Components/Doors/Halls/uroom_4"
979 panels { room: "Outside Magic Room" name: "WIZARD" } 978 panels { room: "Outside Magic Room" name: "WIZARD" }
@@ -1017,7 +1016,7 @@ doors {
1017 panels { room: "Outside Magic Room" name: "WIZARD" } 1016 panels { room: "Outside Magic Room" name: "WIZARD" }
1018} 1017}
1019doors { 1018doors {
1020 name: "Wonderland Right Door" 1019 name: "Wonderland South Door"
1021 type: STANDARD 1020 type: STANDARD
1022 receivers: "Components/Doors/Halls/wonderland_1" 1021 receivers: "Components/Doors/Halls/wonderland_1"
1023 panels { room: "Wonderland" name: "APRIL" } 1022 panels { room: "Wonderland" name: "APRIL" }
@@ -1208,70 +1207,37 @@ doors {
1208 type: ITEM_ONLY 1207 type: ITEM_ONLY
1209 receivers: "Components/Doors/Halls/connections_1" 1208 receivers: "Components/Doors/Halls/connections_1"
1210 receivers: "Components/Doors/Halls/connections_3" 1209 receivers: "Components/Doors/Halls/connections_3"
1210 # These have the same effect as the above, but including them here prevents
1211 # them from opening in door shuffle when the J2 door opens.
1212 receivers: "Components/Triggers/teleportListenerConnections3"
1213 receivers: "Components/Triggers/teleportListenerConnections4"
1214 # This door can open from either solving all panels, or just the smiley ones,
1215 # and the latter is obviously a subset of the former so let's just check for
1216 # that.
1211 panels { room: "Hotel" name: "PARKA" } 1217 panels { room: "Hotel" name: "PARKA" }
1212 panels { room: "Hotel" name: "MARLIN" }
1213 panels { room: "Hotel" name: "WHO" }
1214 panels { room: "Hotel" name: "CLOAK" } 1218 panels { room: "Hotel" name: "CLOAK" }
1215 panels { room: "Hotel" name: "MANE" }
1216 panels { room: "Hotel" name: "WHAT" }
1217 panels { room: "Hotel" name: "BLAZER" }
1218 panels { room: "Hotel" name: "WHERE" }
1219 panels { room: "Hotel" name: "DOROTHY" } 1219 panels { room: "Hotel" name: "DOROTHY" }
1220 panels { room: "Hotel" name: "JACKET" }
1221 panels { room: "Hotel" name: "TAIL" }
1222 panels { room: "Hotel" name: "JAWS" } 1220 panels { room: "Hotel" name: "JAWS" }
1223 panels { room: "Hotel" name: "FLOUNDER" }
1224 panels { room: "Hotel" name: "WHEN" } 1221 panels { room: "Hotel" name: "WHEN" }
1225 panels { room: "Hotel" name: "CLAWS" } 1222 panels { room: "Hotel" name: "CLAWS" }
1226 panels { room: "Hotel" name: "BRUCE" }
1227 panels { room: "Hotel" name: "POTATO" } 1223 panels { room: "Hotel" name: "POTATO" }
1228 panels { room: "Hotel" name: "SALAD" }
1229 panels { room: "Hotel" name: "BATHING" }
1230 panels { room: "Hotel" name: "MICRO" } 1224 panels { room: "Hotel" name: "MICRO" }
1231 panels { room: "Hotel" name: "BUSINESS" }
1232 panels { room: "Hotel" name: "WEDDING" }
1233 panels { room: "Hotel" name: "TREE" }
1234 panels { room: "Hotel" name: "RIVER" }
1235 panels { room: "Hotel" name: "TUNING" } 1225 panels { room: "Hotel" name: "TUNING" }
1236 panels { room: "Hotel" name: "BOXING" }
1237 panels { room: "Hotel" name: "TELEPHONE" }
1238 panels { room: "Hotel" name: "LAW" } 1226 panels { room: "Hotel" name: "LAW" }
1239 panels { room: "Hotel" name: "POKER" }
1240 panels { room: "Hotel" name: "CARD" } 1227 panels { room: "Hotel" name: "CARD" }
1241 panels { room: "Hotel" name: "ROAD" } 1228 panels { room: "Hotel" name: "ROAD" }
1242 panels { room: "Hotel" name: "CHOCOLATE" }
1243 panels { room: "Hotel" name: "DEPART" } 1229 panels { room: "Hotel" name: "DEPART" }
1244 panels { room: "Hotel" name: "WITHDRAW" }
1245 panels { room: "Hotel" name: "QUIT" }
1246 panels { room: "Hotel" name: "LEAVE" } 1230 panels { room: "Hotel" name: "LEAVE" }
1247 panels { room: "Hotel" name: "PALE" }
1248 panels { room: "Hotel" name: "JUST" }
1249 panels { room: "Hotel" name: "NEW" }
1250 panels { room: "Hotel" name: "UNTALENTED" }
1251 panels { room: "Hotel" name: "SERVICE" } 1231 panels { room: "Hotel" name: "SERVICE" }
1252 panels { room: "Hotel" name: "FULL" }
1253 panels { room: "Hotel" name: "EVIL" }
1254 panels { room: "Hotel" name: "HONEY" } 1232 panels { room: "Hotel" name: "HONEY" }
1255 panels { room: "Hotel" name: "CRESCENT" }
1256 panels { room: "Hotel" name: "INVALID" } 1233 panels { room: "Hotel" name: "INVALID" }
1257 panels { room: "Hotel" name: "FESTIVAL" } 1234 panels { room: "Hotel" name: "FESTIVAL" }
1258 panels { room: "Hotel" name: "BEAUTIFUL" }
1259 panels { room: "Hotel" name: "WILTED" } 1235 panels { room: "Hotel" name: "WILTED" }
1260 panels { room: "Hotel" name: "DROOPED" }
1261 panels { room: "Hotel" name: "FADED" }
1262 panels { room: "Hotel" name: "WANED" } 1236 panels { room: "Hotel" name: "WANED" }
1263 panels { room: "Hotel" name: "TALL" }
1264 panels { room: "Hotel" name: "CANVAS" }
1265 panels { room: "Hotel" name: "LEVER" }
1266 panels { room: "Hotel" name: "SCULPTURE" }
1267 panels { room: "Hotel" name: "RAGE" } 1237 panels { room: "Hotel" name: "RAGE" }
1268 panels { room: "Hotel" name: "BALL" }
1269 panels { room: "Hotel" name: "FOOL" }
1270 panels { room: "Hotel" name: "VERGE" } 1238 panels { room: "Hotel" name: "VERGE" }
1271 panels { room: "Hotel" name: "ART" }
1272 panels { room: "Hotel" name: "EVER" } 1239 panels { room: "Hotel" name: "EVER" }
1273 panels { room: "Hotel" name: "PAIN" } 1240 panels { room: "Hotel" name: "PAIN" }
1274 panels { room: "Hotel" name: "FOOT" }
1275} 1241}
1276doors { 1242doors {
1277 name: "J2 Door 1" 1243 name: "J2 Door 1"
@@ -1307,7 +1273,7 @@ doors {
1307 panels { room: "J2 Vestibule" name: "COLORFUL" } 1273 panels { room: "J2 Vestibule" name: "COLORFUL" }
1308} 1274}
1309doors { 1275doors {
1310 name: "Wonderland Left Door" 1276 name: "Wonderland North Door"
1311 type: ITEM_ONLY 1277 type: ITEM_ONLY
1312 receivers: "Components/Doors/Halls/wonderland_2" 1278 receivers: "Components/Doors/Halls/wonderland_2"
1313 panels { room: "Wonderland" name: "APRIL" } 1279 panels { room: "Wonderland" name: "APRIL" }
@@ -1648,6 +1614,7 @@ doors {
1648 # Components/Doors/Smileys/blue_1 1614 # Components/Doors/Smileys/blue_1
1649 panels { room: "Blue Smiley" name: "SMILE" } 1615 panels { room: "Blue Smiley" name: "SMILE" }
1650 location_room: "Blue Smiley" 1616 location_room: "Blue Smiley"
1617 location_name: "Blue SMILE"
1651} 1618}
1652doors { 1619doors {
1653 name: "Blue Smiley Annex" 1620 name: "Blue Smiley Annex"
@@ -1679,6 +1646,7 @@ doors {
1679 receivers: "Components/Doors/Smileys/yellow_2" 1646 receivers: "Components/Doors/Smileys/yellow_2"
1680 panels { room: "Hedges" name: "SMILE" } 1647 panels { room: "Hedges" name: "SMILE" }
1681 location_room: "Hedges" 1648 location_room: "Hedges"
1649 location_name: "Yellow SMILE"
1682} 1650}
1683doors { 1651doors {
1684 name: "Green Smiley" 1652 name: "Green Smiley"
@@ -1687,6 +1655,7 @@ doors {
1687 receivers: "Components/Doors/Smileys/green_2" 1655 receivers: "Components/Doors/Smileys/green_2"
1688 panels { room: "Green Smiley" name: "SMILE" } 1656 panels { room: "Green Smiley" name: "SMILE" }
1689 location_room: "Green Smiley" 1657 location_room: "Green Smiley"
1658 location_name: "Green SMILE"
1690} 1659}
1691doors { 1660doors {
1692 name: "Orange Smiley Exit" 1661 name: "Orange Smiley Exit"
@@ -1694,6 +1663,7 @@ doors {
1694 receivers: "Components/Doors/Smileys/orange_1" 1663 receivers: "Components/Doors/Smileys/orange_1"
1695 panels { room: "Outside Orange Room" name: "SMILE" } 1664 panels { room: "Outside Orange Room" name: "SMILE" }
1696 location_room: "Outside Orange Room" 1665 location_room: "Outside Orange Room"
1666 location_name: "Orange SMILE"
1697} 1667}
1698doors { 1668doors {
1699 name: "F Keyholder Door" 1669 name: "F Keyholder Door"
@@ -1718,6 +1688,7 @@ doors {
1718 type: LOCATION_ONLY 1688 type: LOCATION_ONLY
1719 panels { room: "Red Smiley" name: "SMILE" } 1689 panels { room: "Red Smiley" name: "SMILE" }
1720 location_room: "Red Smiley" 1690 location_room: "Red Smiley"
1691 location_name: "Red SMILE"
1721} 1692}
1722doors { 1693doors {
1723 name: "Pink Hallway" 1694 name: "Pink Hallway"
@@ -2187,6 +2158,7 @@ doors {
2187 receivers: "Components/Doors/Unincorporated/temple_foyer_6" 2158 receivers: "Components/Doors/Unincorporated/temple_foyer_6"
2188 panels { room: "Globe Room" name: "WORD" } 2159 panels { room: "Globe Room" name: "WORD" }
2189 location_room: "Globe Room" 2160 location_room: "Globe Room"
2161 location_name: "Sticks and Stones"
2190} 2162}
2191doors { 2163doors {
2192 name: "Castle Numbers Puzzle" 2164 name: "Castle Numbers Puzzle"
@@ -2306,3 +2278,24 @@ doors {
2306 receivers: "Components/Paintings/Temple of the Eyes/eyeRedStart/teleportListener" 2278 receivers: "Components/Paintings/Temple of the Eyes/eyeRedStart/teleportListener"
2307 double_letters: true 2279 double_letters: true
2308} 2280}
2281doors {
2282 name: "Lime Hexes"
2283 type: LOCATION_ONLY
2284 panels { room: "Tree Entrance" name: "RAT" }
2285 panels { room: "Tree Entrance" name: "DIFFERENCE" }
2286 panels { room: "Tree Entrance" name: "LEANS" }
2287 panels { room: "Tree Entrance" name: "QUESTION" }
2288 panels { room: "Tree Entrance" name: "WHERE" }
2289 panels { room: "Tree Entrance" name: "SUNDER" }
2290 location_room: "Tree Entrance"
2291}
2292doors {
2293 name: "Theo Panels"
2294 type: LOCATION_ONLY
2295 panels { room: "House" name: "GOAT" }
2296 panels { room: "House" name: "AMAZE" }
2297 panels { room: "House" name: "SKINNYHIM" }
2298 panels { room: "House" name: "THEO" }
2299 location_room: "House"
2300 location_name: "All Puzzles"
2301}
diff --git a/data/maps/daedalus/rooms/C Keyholder.txtpb b/data/maps/daedalus/rooms/C Keyholder.txtpb index ef10a90..28793b2 100644 --- a/data/maps/daedalus/rooms/C Keyholder.txtpb +++ b/data/maps/daedalus/rooms/C Keyholder.txtpb
@@ -1,5 +1,5 @@
1name: "C Keyholder" 1name: "C Keyholder"
2panel_display_name: "North Area" 2panel_display_name: "East Area"
3keyholders { 3keyholders {
4 name: "C" 4 name: "C"
5 path: "Components/KeyHolders/keyHolderC" 5 path: "Components/KeyHolders/keyHolderC"
diff --git a/data/maps/daedalus/rooms/Red Color Door.txtpb b/data/maps/daedalus/rooms/Red Color Door.txtpb index f7eab21..344193e 100644 --- a/data/maps/daedalus/rooms/Red Color Door.txtpb +++ b/data/maps/daedalus/rooms/Red Color Door.txtpb
@@ -1,7 +1,7 @@
1name: "Red Color Door" 1name: "Red Color Door"
2panel_display_name: "Southwest Area" 2panel_display_name: "Southwest Area"
3panels { 3panels {
4 name: "Left" 4 name: "Near Obscured Puzzles"
5 path: "Panels/Halls/wb_1" 5 path: "Panels/Halls/wb_1"
6 clue: "" 6 clue: ""
7 answer: "sidewalk" 7 answer: "sidewalk"
diff --git a/data/maps/daedalus/rooms/Wonderland.txtpb b/data/maps/daedalus/rooms/Wonderland.txtpb index 4b69e99..ae9b3f1 100644 --- a/data/maps/daedalus/rooms/Wonderland.txtpb +++ b/data/maps/daedalus/rooms/Wonderland.txtpb
@@ -1,6 +1,5 @@
1name: "Wonderland" 1name: "Wonderland"
2panel_display_name: "Northwest Area" 2panel_display_name: "Northwest Area"
3# TODO: There's a warp from The Entry into here.
4panels { 3panels {
5 name: "APRIL" 4 name: "APRIL"
6 path: "Panels/Wonderland/wonderland_1" 5 path: "Panels/Wonderland/wonderland_1"
diff --git a/data/maps/the_ancient/rooms/Outside.txtpb b/data/maps/the_ancient/rooms/Outside.txtpb index a3372af..1458357 100644 --- a/data/maps/the_ancient/rooms/Outside.txtpb +++ b/data/maps/the_ancient/rooms/Outside.txtpb
@@ -4,4 +4,5 @@ panels {
4 path: "Panels/panel_1" 4 path: "Panels/panel_1"
5 clue: "this" 5 clue: "this"
6 answer: "sphinx" 6 answer: "sphinx"
7 symbols: QUESTION
7} 8}
diff --git a/data/maps/the_bearer/connections.txtpb b/data/maps/the_bearer/connections.txtpb index 23410f0..ba14d83 100644 --- a/data/maps/the_bearer/connections.txtpb +++ b/data/maps/the_bearer/connections.txtpb
@@ -263,3 +263,8 @@ connections {
263 to_room: "Butterfly Room" 263 to_room: "Butterfly Room"
264 door { name: "Butterfly Entrance" } 264 door { name: "Butterfly Entrance" }
265} 265}
266connections {
267 from_room: "Back Area"
268 to_room: "Tree Entrance"
269 door { name: "Control Center Brown Door" }
270}
diff --git a/data/maps/the_bearer/rooms/Back Area.txtpb b/data/maps/the_bearer/rooms/Back Area.txtpb index 27e175c..b1860de 100644 --- a/data/maps/the_bearer/rooms/Back Area.txtpb +++ b/data/maps/the_bearer/rooms/Back Area.txtpb
@@ -7,12 +7,6 @@ panels {
7 symbols: EXAMPLE 7 symbols: EXAMPLE
8} 8}
9ports { 9ports {
10 name: "TREE"
11 path: "Components/Warps/worldport3"
12 orientation: "north"
13 required_door { name: "Control Center Brown Door" }
14}
15ports {
16 name: "DAEDALUS" 10 name: "DAEDALUS"
17 path: "Components/Warps/worldport2" 11 path: "Components/Warps/worldport2"
18 orientation: "north" 12 orientation: "north"
diff --git a/data/maps/the_bearer/rooms/Tree Entrance.txtpb b/data/maps/the_bearer/rooms/Tree Entrance.txtpb new file mode 100644 index 0000000..97a07da --- /dev/null +++ b/data/maps/the_bearer/rooms/Tree Entrance.txtpb
@@ -0,0 +1,6 @@
1name: "Tree Entrance"
2ports {
3 name: "TREE"
4 path: "Components/Warps/worldport3"
5 orientation: "north"
6}
diff --git a/data/maps/the_darkroom/connections.txtpb b/data/maps/the_darkroom/connections.txtpb index 4093585..1b7ad05 100644 --- a/data/maps/the_darkroom/connections.txtpb +++ b/data/maps/the_darkroom/connections.txtpb
@@ -33,3 +33,18 @@ connections {
33 to_room: "S Room" 33 to_room: "S Room"
34 door { name: "S1 Door" } 34 door { name: "S1 Door" }
35} 35}
36connections {
37 from_room: "First Room"
38 to_room: "Cyan Hallway"
39 door { name: "Colorful Entrance" }
40}
41connections {
42 from_room: "Second Room"
43 to_room: "Congruent Entrance"
44 door { name: "Congruent Entrance" }
45}
46connections {
47 from_room: "First Room"
48 to_room: "Double Sided Entrance"
49 door { name: "Double Sided Entrance" }
50}
diff --git a/data/maps/the_darkroom/rooms/Congruent Entrance.txtpb b/data/maps/the_darkroom/rooms/Congruent Entrance.txtpb new file mode 100644 index 0000000..7ea1286 --- /dev/null +++ b/data/maps/the_darkroom/rooms/Congruent Entrance.txtpb
@@ -0,0 +1,7 @@
1name: "Congruent Entrance"
2panel_display_name: "Second Room"
3ports {
4 name: "CONGRUENT"
5 path: "Components/Warps/worldport7"
6 orientation: "east"
7}
diff --git a/data/maps/the_darkroom/rooms/Cyan Hallway.txtpb b/data/maps/the_darkroom/rooms/Cyan Hallway.txtpb new file mode 100644 index 0000000..308efb1 --- /dev/null +++ b/data/maps/the_darkroom/rooms/Cyan Hallway.txtpb
@@ -0,0 +1,7 @@
1name: "Cyan Hallway"
2panel_display_name: "First Room"
3ports {
4 name: "COLORFUL"
5 path: "Components/Warps/worldport8"
6 orientation: "north"
7}
diff --git a/data/maps/the_darkroom/rooms/Double Sided Entrance.txtpb b/data/maps/the_darkroom/rooms/Double Sided Entrance.txtpb new file mode 100644 index 0000000..9d25108 --- /dev/null +++ b/data/maps/the_darkroom/rooms/Double Sided Entrance.txtpb
@@ -0,0 +1,7 @@
1name: "Double Sided Entrance"
2panel_display_name: "First Room"
3ports {
4 name: "DOUBLESIDED"
5 path: "Components/Warps/worldport6"
6 orientation: "east"
7}
diff --git a/data/maps/the_darkroom/rooms/First Room.txtpb b/data/maps/the_darkroom/rooms/First Room.txtpb index c93f5b4..c635757 100644 --- a/data/maps/the_darkroom/rooms/First Room.txtpb +++ b/data/maps/the_darkroom/rooms/First Room.txtpb
@@ -42,15 +42,3 @@ ports {
42 orientation: "north" 42 orientation: "north"
43 required_door { name: "Second Room Entrance" } 43 required_door { name: "Second Room Entrance" }
44} 44}
45ports {
46 name: "COLORFUL"
47 path: "Components/Warps/worldport8"
48 orientation: "north"
49 required_door { name: "Colorful Entrance" }
50}
51ports {
52 name: "DOUBLESIDED"
53 path: "Components/Warps/worldport6"
54 orientation: "east"
55 required_door { name: "Double Sided Entrance" }
56}
diff --git a/data/maps/the_darkroom/rooms/Second Room.txtpb b/data/maps/the_darkroom/rooms/Second Room.txtpb index baeea12..a3964ea 100644 --- a/data/maps/the_darkroom/rooms/Second Room.txtpb +++ b/data/maps/the_darkroom/rooms/Second Room.txtpb
@@ -47,9 +47,3 @@ ports {
47 orientation: "north" 47 orientation: "north"
48 required_door { name: "Third Room Entrance" } 48 required_door { name: "Third Room Entrance" }
49} 49}
50ports {
51 name: "CONGRUENT"
52 path: "Components/Warps/worldport7"
53 orientation: "east"
54 required_door { name: "Congruent Entrance" }
55}
diff --git a/data/maps/the_entry/connections.txtpb b/data/maps/the_entry/connections.txtpb index a2e325a..ca0207e 100644 --- a/data/maps/the_entry/connections.txtpb +++ b/data/maps/the_entry/connections.txtpb
@@ -97,6 +97,12 @@ connections {
97 from_room: "Red Blue Halls" 97 from_room: "Red Blue Halls"
98 to_room: "Wrath Room" 98 to_room: "Wrath Room"
99 door { name: "Noon Door" } 99 door { name: "Noon Door" }
100 oneway: true
101}
102connections {
103 from_room: "Wrath Room"
104 to_room: "Least Blue Last"
105 oneway: true
100} 106}
101connections { 107connections {
102 from_room: "Red Blue Halls" 108 from_room: "Red Blue Halls"
@@ -199,3 +205,18 @@ connections {
199 to_room: "White Hallway To Daedalus" 205 to_room: "White Hallway To Daedalus"
200 door { name: "Control Center White Door" } 206 door { name: "Control Center White Door" }
201} 207}
208connections {
209 from_room: "Flipped Second Room"
210 to_room: "Four Rooms Entrance"
211 door { name: "Flipped Second Room Right Door" }
212}
213connections {
214 from_room: "Link Area"
215 to_room: "Liberated Entrance"
216 door { name: "Liberated Entrance" }
217}
218connections {
219 from_room: "Link Area"
220 to_room: "Literate Entrance"
221 door { name: "Literate Entrance" }
222}
diff --git a/data/maps/the_entry/doors.txtpb b/data/maps/the_entry/doors.txtpb index 6bef160..466f5ce 100644 --- a/data/maps/the_entry/doors.txtpb +++ b/data/maps/the_entry/doors.txtpb
@@ -137,8 +137,10 @@ doors {
137 type: STANDARD 137 type: STANDARD
138 receivers: "Components/Doors/back_left_2" 138 receivers: "Components/Doors/back_left_2"
139 panels { room: "Colored Doors Area" name: "OPEN" answer: "orange" } 139 panels { room: "Colored Doors Area" name: "OPEN" answer: "orange" }
140 # "wall" is supposed to also work. idk man 140 panels { room: "Colored Doors Area" name: "OPEN" answer: "wall" }
141 complete_at: 1
141 location_room: "Colored Doors Area" 142 location_room: "Colored Doors Area"
143 location_name: "OPEN"
142} 144}
143doors { 145doors {
144 name: "Lime Room Entrance" 146 name: "Lime Room Entrance"
diff --git a/data/maps/the_entry/rooms/Flipped Second Room.txtpb b/data/maps/the_entry/rooms/Flipped Second Room.txtpb index 5841ca1..0d518bb 100644 --- a/data/maps/the_entry/rooms/Flipped Second Room.txtpb +++ b/data/maps/the_entry/rooms/Flipped Second Room.txtpb
@@ -21,10 +21,3 @@ paintings {
21 gravity: Y_PLUS 21 gravity: Y_PLUS
22 display_name: "Eye Painting" 22 display_name: "Eye Painting"
23} 23}
24ports {
25 name: "FOUR"
26 path: "Components/Warps/worldport9"
27 orientation: "south"
28 gravity: Y_PLUS
29 required_door { name: "Flipped Second Room Right Door" }
30} \ No newline at end of file
diff --git a/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb b/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb new file mode 100644 index 0000000..689d23e --- /dev/null +++ b/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb
@@ -0,0 +1,7 @@
1name: "Four Rooms Entrance"
2ports {
3 name: "FOUR"
4 path: "Components/Warps/worldport9"
5 orientation: "south"
6 gravity: Y_PLUS
7}
diff --git a/data/maps/the_entry/rooms/Liberated Entrance.txtpb b/data/maps/the_entry/rooms/Liberated Entrance.txtpb new file mode 100644 index 0000000..f0176a0 --- /dev/null +++ b/data/maps/the_entry/rooms/Liberated Entrance.txtpb
@@ -0,0 +1,6 @@
1name: "Liberated Entrance"
2ports {
3 name: "BLUE"
4 path: "worldport8"
5 orientation: "west"
6}
diff --git a/data/maps/the_entry/rooms/Link Area.txtpb b/data/maps/the_entry/rooms/Link Area.txtpb index 689f57a..5b68279 100644 --- a/data/maps/the_entry/rooms/Link Area.txtpb +++ b/data/maps/the_entry/rooms/Link Area.txtpb
@@ -26,15 +26,3 @@ paintings {
26 orientation: "south" 26 orientation: "south"
27 display_name: "Center Painting" 27 display_name: "Center Painting"
28} 28}
29ports {
30 name: "BLUE"
31 path: "worldport8"
32 orientation: "west"
33 required_door { name: "Liberated Entrance" }
34}
35ports {
36 name: "BROWN"
37 path: "worldport9"
38 orientation: "east"
39 required_door { name: "Literate Entrance" }
40} \ No newline at end of file
diff --git a/data/maps/the_entry/rooms/Literate Entrance.txtpb b/data/maps/the_entry/rooms/Literate Entrance.txtpb new file mode 100644 index 0000000..4ec402f --- /dev/null +++ b/data/maps/the_entry/rooms/Literate Entrance.txtpb
@@ -0,0 +1,6 @@
1name: "Literate Entrance"
2ports {
3 name: "BROWN"
4 path: "worldport9"
5 orientation: "east"
6}
diff --git a/data/maps/the_entry/rooms/Shop Entrance.txtpb b/data/maps/the_entry/rooms/Shop Entrance.txtpb index f793da3..67aa6de 100644 --- a/data/maps/the_entry/rooms/Shop Entrance.txtpb +++ b/data/maps/the_entry/rooms/Shop Entrance.txtpb
@@ -1,5 +1,5 @@
1name: "Shop Entrance" 1name: "Shop Entrance"
2panel_display_name: "Starting Room" 2panel_display_name: "Shop Entrance"
3panels { 3panels {
4 name: "TURN" 4 name: "TURN"
5 path: "Panels/Entry/l_opener_2" 5 path: "Panels/Entry/l_opener_2"
diff --git a/data/maps/the_gallery/doors.txtpb b/data/maps/the_gallery/doors.txtpb index a7a5d85..adbc766 100644 --- a/data/maps/the_gallery/doors.txtpb +++ b/data/maps/the_gallery/doors.txtpb
@@ -1,7 +1,7 @@
1# The Gallery is interesting because there's so many cross-map requirements. 1# The Gallery is interesting because there's so many cross-map requirements.
2doors { 2doors {
3 name: "Darkroom Painting" 3 name: "Darkroom Painting"
4 type: ITEM_ONLY 4 type: GALLERY_PAINTING
5 #move_paintings { room: "Main Area" name: "DARKROOM" } 5 #move_paintings { room: "Main Area" name: "DARKROOM" }
6 receivers: "Components/Paintings/darkroom/teleportListener" 6 receivers: "Components/Paintings/darkroom/teleportListener"
7 panels { map: "the_darkroom" room: "First Room" name: "BISON" } 7 panels { map: "the_darkroom" room: "First Room" name: "BISON" }
@@ -27,14 +27,14 @@ doors {
27} 27}
28doors { 28doors {
29 name: "Butterfly Painting" 29 name: "Butterfly Painting"
30 type: ITEM_ONLY 30 type: GALLERY_PAINTING
31 #move_paintings { room: "Main Area" name: "BUTTERFLY" } 31 #move_paintings { room: "Main Area" name: "BUTTERFLY" }
32 receivers: "Components/Paintings/butterfly/teleportListener" 32 receivers: "Components/Paintings/butterfly/teleportListener"
33 rooms { map: "the_butterfly" name: "Main Area" } 33 rooms { map: "the_butterfly" name: "Main Area" }
34} 34}
35doors { 35doors {
36 name: "Between Painting" 36 name: "Between Painting"
37 type: ITEM_ONLY 37 type: GALLERY_PAINTING
38 #move_paintings { room: "Main Area" name: "BETWEEN" } 38 #move_paintings { room: "Main Area" name: "BETWEEN" }
39 receivers: "Components/Paintings/between/teleportListener" 39 receivers: "Components/Paintings/between/teleportListener"
40 panels { map: "the_between" room: "Main Area" name: "SUN" } 40 panels { map: "the_between" room: "Main Area" name: "SUN" }
@@ -70,14 +70,14 @@ doors {
70} 70}
71doors { 71doors {
72 name: "Entry Painting" 72 name: "Entry Painting"
73 type: ITEM_ONLY 73 type: GALLERY_PAINTING
74 #move_paintings { room: "Main Area" name: "ENTRY" } 74 #move_paintings { room: "Main Area" name: "ENTRY" }
75 receivers: "Components/Paintings/eyes/teleportListener" 75 receivers: "Components/Paintings/eyes/teleportListener"
76 panels { map: "the_entry" room: "Eye Room" name: "I" } 76 panels { map: "the_entry" room: "Eye Room" name: "I" }
77} 77}
78doors { 78doors {
79 name: "Wise Painting" 79 name: "Wise Painting"
80 type: ITEM_ONLY 80 type: GALLERY_PAINTING
81 #move_paintings { room: "Main Area" name: "WISE" } 81 #move_paintings { room: "Main Area" name: "WISE" }
82 receivers: "Components/Paintings/triangle/teleportListener" 82 receivers: "Components/Paintings/triangle/teleportListener"
83 panels { map: "the_wise" room: "Entry" name: "INK" } 83 panels { map: "the_wise" room: "Entry" name: "INK" }
@@ -105,7 +105,7 @@ doors {
105} 105}
106doors { 106doors {
107 name: "Tree Painting" 107 name: "Tree Painting"
108 type: ITEM_ONLY 108 type: GALLERY_PAINTING
109 #move_paintings { room: "Main Area" name: "TREE" } 109 #move_paintings { room: "Main Area" name: "TREE" }
110 receivers: "Components/Paintings/Clue Maps/tree/teleportListener" 110 receivers: "Components/Paintings/Clue Maps/tree/teleportListener"
111 panels { map: "the_tree" room: "Main Area" name: "COLOR" } 111 panels { map: "the_tree" room: "Main Area" name: "COLOR" }
@@ -142,35 +142,35 @@ doors {
142} 142}
143doors { 143doors {
144 name: "Unyielding Painting" 144 name: "Unyielding Painting"
145 type: ITEM_ONLY 145 type: GALLERY_PAINTING
146 #move_paintings { room: "Main Area" name: "UNYIELDING" } 146 #move_paintings { room: "Main Area" name: "UNYIELDING" }
147 receivers: "Components/Paintings/Clue Maps/unyielding/teleportListener" 147 receivers: "Components/Paintings/Clue Maps/unyielding/teleportListener"
148 rooms { map: "the_unyielding" name: "Digital Entrance" } 148 rooms { map: "the_unyielding" name: "Digital Entrance" }
149} 149}
150doors { 150doors {
151 name: "Graveyard Painting" 151 name: "Graveyard Painting"
152 type: ITEM_ONLY 152 type: GALLERY_PAINTING
153 #move_paintings { room: "Main Area" name: "GRAVEYARD" } 153 #move_paintings { room: "Main Area" name: "GRAVEYARD" }
154 receivers: "Components/Paintings/Endings/grave/teleportListener" 154 receivers: "Components/Paintings/Endings/grave/teleportListener"
155 rooms { map: "the_graveyard" name: "Outside" } 155 rooms { map: "the_graveyard" name: "Outside" }
156} 156}
157doors { 157doors {
158 name: "Control Center Painting" 158 name: "Control Center Painting"
159 type: ITEM_ONLY 159 type: GALLERY_PAINTING
160 #move_paintings { room: "Main Area" name: "CC" } 160 #move_paintings { room: "Main Area" name: "CC" }
161 receivers: "Components/Paintings/Endings/desert/teleportListener" 161 receivers: "Components/Paintings/Endings/desert/teleportListener"
162 rooms { map: "the_impressive" name: "M2 Room" } 162 rooms { map: "the_impressive" name: "M2 Room" }
163} 163}
164doors { 164doors {
165 name: "Tower Painting" 165 name: "Tower Painting"
166 type: ITEM_ONLY 166 type: GALLERY_PAINTING
167 #move_paintings { room: "Main Area" name: "TOWER" } 167 #move_paintings { room: "Main Area" name: "TOWER" }
168 receivers: "Components/Paintings/Endings/red/teleportListener" 168 receivers: "Components/Paintings/Endings/red/teleportListener"
169 rooms { map: "the_tower" name: "First Floor" } 169 rooms { map: "the_tower" name: "First Floor" }
170} 170}
171doors { 171doors {
172 name: "Wondrous Painting" 172 name: "Wondrous Painting"
173 type: ITEM_ONLY 173 type: GALLERY_PAINTING
174 #move_paintings { room: "Main Area" name: "WONDROUS" } 174 #move_paintings { room: "Main Area" name: "WONDROUS" }
175 receivers: "Components/Paintings/Endings/window/teleportListener" 175 receivers: "Components/Paintings/Endings/window/teleportListener"
176 panels { map: "the_wondrous" room: "Entry" name: "WONDER" } 176 panels { map: "the_wondrous" room: "Entry" name: "WONDER" }
@@ -187,42 +187,42 @@ doors {
187} 187}
188doors { 188doors {
189 name: "Rainbow Painting" 189 name: "Rainbow Painting"
190 type: ITEM_ONLY 190 type: GALLERY_PAINTING
191 #move_paintings { room: "Main Area" name: "RAINBOW" } 191 #move_paintings { room: "Main Area" name: "RAINBOW" }
192 receivers: "Components/Paintings/Endings/rainbow/teleportListener" 192 receivers: "Components/Paintings/Endings/rainbow/teleportListener"
193 rooms { map: "daedalus" name: "Rainbow Start" } 193 rooms { map: "daedalus" name: "Rainbow Start" }
194} 194}
195doors { 195doors {
196 name: "Words Painting" 196 name: "Words Painting"
197 type: ITEM_ONLY 197 type: GALLERY_PAINTING
198 #move_paintings { room: "Main Area" name: "WORDS" } 198 #move_paintings { room: "Main Area" name: "WORDS" }
199 receivers: "Components/Paintings/Endings/words/teleportListener" 199 receivers: "Components/Paintings/Endings/words/teleportListener"
200 rooms { map: "the_words" name: "Main Area" } 200 rooms { map: "the_words" name: "Main Area" }
201} 201}
202doors { 202doors {
203 name: "Colorful Painting" 203 name: "Colorful Painting"
204 type: ITEM_ONLY 204 type: GALLERY_PAINTING
205 #move_paintings { room: "Main Area" name: "COLORFUL" } 205 #move_paintings { room: "Main Area" name: "COLORFUL" }
206 receivers: "Components/Paintings/Endings/colorful/teleportListener" 206 receivers: "Components/Paintings/Endings/colorful/teleportListener"
207 rooms { map: "the_colorful" name: "White Room" } 207 rooms { map: "the_colorful" name: "White Room" }
208} 208}
209doors { 209doors {
210 name: "Castle Painting" 210 name: "Castle Painting"
211 type: ITEM_ONLY 211 type: GALLERY_PAINTING
212 #move_paintings { room: "Main Area" name: "CASTLE" } 212 #move_paintings { room: "Main Area" name: "CASTLE" }
213 receivers: "Components/Paintings/Endings/castle/teleportListener" 213 receivers: "Components/Paintings/Endings/castle/teleportListener"
214 rooms { map: "daedalus" name: "Castle" } 214 rooms { map: "daedalus" name: "Castle" }
215} 215}
216doors { 216doors {
217 name: "Sun Temple Painting" 217 name: "Sun Temple Painting"
218 type: ITEM_ONLY 218 type: GALLERY_PAINTING
219 #move_paintings { room: "Main Area" name: "SUNTEMPLE" } 219 #move_paintings { room: "Main Area" name: "SUNTEMPLE" }
220 receivers: "Components/Paintings/Endings/temple/teleportListener" 220 receivers: "Components/Paintings/Endings/temple/teleportListener"
221 rooms { map: "the_sun_temple" name: "Entrance" } 221 rooms { map: "the_sun_temple" name: "Entrance" }
222} 222}
223doors { 223doors {
224 name: "Ancient Painting" 224 name: "Ancient Painting"
225 type: ITEM_ONLY 225 type: GALLERY_PAINTING
226 #move_paintings { room: "Main Area" name: "ANCIENT" } 226 #move_paintings { room: "Main Area" name: "ANCIENT" }
227 receivers: "Components/Paintings/Endings/cubes/teleportListener" 227 receivers: "Components/Paintings/Endings/cubes/teleportListener"
228 rooms { map: "the_ancient" name: "Outside" } 228 rooms { map: "the_ancient" name: "Outside" }
diff --git a/data/maps/the_great/doors.txtpb b/data/maps/the_great/doors.txtpb index 5d0e90d..abbbc11 100644 --- a/data/maps/the_great/doors.txtpb +++ b/data/maps/the_great/doors.txtpb
@@ -66,7 +66,7 @@ doors {
66doors { 66doors {
67 name: "Control Center Red Door" 67 name: "Control Center Red Door"
68 type: CONTROL_CENTER_COLOR 68 type: CONTROL_CENTER_COLOR
69 receivers: "Components/Doors/Gates/Gate" 69 receivers: "Components/Doors/entry_18"
70 control_center_color: "red" 70 control_center_color: "red"
71} 71}
72doors { 72doors {
diff --git a/data/maps/the_nuanced/doors.txtpb b/data/maps/the_nuanced/doors.txtpb index 9b58001..cd29766 100644 --- a/data/maps/the_nuanced/doors.txtpb +++ b/data/maps/the_nuanced/doors.txtpb
@@ -1,5 +1,5 @@
1doors { 1doors {
2 name: "Left Room Puzzles" 2 name: "Blue Side Puzzles"
3 type: LOCATION_ONLY 3 type: LOCATION_ONLY
4 panels { room: "Main Room" name: "HOARSE" } 4 panels { room: "Main Room" name: "HOARSE" }
5 panels { room: "Main Room" name: "NAY" } 5 panels { room: "Main Room" name: "NAY" }
@@ -11,7 +11,7 @@ doors {
11 location_room: "Main Room" 11 location_room: "Main Room"
12} 12}
13doors { 13doors {
14 name: "Right Room Puzzles" 14 name: "Green Side Puzzles"
15 type: LOCATION_ONLY 15 type: LOCATION_ONLY
16 panels { room: "Main Room" name: "HOSE" } 16 panels { room: "Main Room" name: "HOSE" }
17 panels { room: "Main Room" name: "NIGH" } 17 panels { room: "Main Room" name: "NIGH" }
diff --git a/data/maps/the_parthenon/connections.txtpb b/data/maps/the_parthenon/connections.txtpb index a07d858..331ac66 100644 --- a/data/maps/the_parthenon/connections.txtpb +++ b/data/maps/the_parthenon/connections.txtpb
@@ -7,6 +7,7 @@ connections {
7 from_room: "Main Area" 7 from_room: "Main Area"
8 to_room: "Ending" 8 to_room: "Ending"
9 door { name: "Ending Door" } 9 door { name: "Ending Door" }
10 cyan_ending: true
10} 11}
11connections { 12connections {
12 from_room: "Main Area" 13 from_room: "Main Area"
diff --git a/data/maps/the_plaza/connections.txtpb b/data/maps/the_plaza/connections.txtpb index 44586be..6da201c 100644 --- a/data/maps/the_plaza/connections.txtpb +++ b/data/maps/the_plaza/connections.txtpb
@@ -21,22 +21,22 @@ connections {
21connections { 21connections {
22 from_room: "Center Room" 22 from_room: "Center Room"
23 to_room: "Top Left Room" 23 to_room: "Top Left Room"
24 door { name: "Top Left Door" } 24 door { name: "Northwest Door" }
25} 25}
26connections { 26connections {
27 from_room: "Center Room" 27 from_room: "Center Room"
28 to_room: "Top Right Room" 28 to_room: "Top Right Room"
29 door { name: "Top Right Door" } 29 door { name: "Northeast Door" }
30} 30}
31connections { 31connections {
32 from_room: "Center Room" 32 from_room: "Center Room"
33 to_room: "Bottom Left Room" 33 to_room: "Bottom Left Room"
34 door { name: "Bottom Left Door" } 34 door { name: "Southwest Door" }
35} 35}
36connections { 36connections {
37 from_room: "Center Room" 37 from_room: "Center Room"
38 to_room: "Bottom Right Room" 38 to_room: "Bottom Right Room"
39 door { name: "Bottom Right Door" } 39 door { name: "Southeast Door" }
40} 40}
41connections { 41connections {
42 from_room: "Center Room" 42 from_room: "Center Room"
diff --git a/data/maps/the_plaza/doors.txtpb b/data/maps/the_plaza/doors.txtpb index 322fe39..d95273c 100644 --- a/data/maps/the_plaza/doors.txtpb +++ b/data/maps/the_plaza/doors.txtpb
@@ -31,7 +31,7 @@ doors {
31 location_room: "Main Area" 31 location_room: "Main Area"
32} 32}
33doors { 33doors {
34 name: "Top Left Door" 34 name: "Northwest Door"
35 type: STANDARD 35 type: STANDARD
36 receivers: "Components/Doors/entry_6" 36 receivers: "Components/Doors/entry_6"
37 panels { room: "Center Room" name: "REPORTER" } 37 panels { room: "Center Room" name: "REPORTER" }
@@ -44,7 +44,7 @@ doors {
44 location_name: "First Room" 44 location_name: "First Room"
45} 45}
46doors { 46doors {
47 name: "Top Right Door" 47 name: "Northeast Door"
48 type: ITEM_ONLY 48 type: ITEM_ONLY
49 receivers: "Components/Doors/entry_7" 49 receivers: "Components/Doors/entry_7"
50 panels { room: "Center Room" name: "REPORTER" } 50 panels { room: "Center Room" name: "REPORTER" }
@@ -55,7 +55,7 @@ doors {
55 panels { room: "Center Room" name: "SQUIRREL" } 55 panels { room: "Center Room" name: "SQUIRREL" }
56} 56}
57doors { 57doors {
58 name: "Bottom Left Door" 58 name: "Southwest Door"
59 type: ITEM_ONLY 59 type: ITEM_ONLY
60 receivers: "Components/Doors/entry_5" 60 receivers: "Components/Doors/entry_5"
61 panels { room: "Center Room" name: "REPORTER" } 61 panels { room: "Center Room" name: "REPORTER" }
@@ -66,7 +66,7 @@ doors {
66 panels { room: "Center Room" name: "SQUIRREL" } 66 panels { room: "Center Room" name: "SQUIRREL" }
67} 67}
68doors { 68doors {
69 name: "Bottom Right Door" 69 name: "Southeast Door"
70 type: ITEM_ONLY 70 type: ITEM_ONLY
71 receivers: "Components/Doors/entry_4" 71 receivers: "Components/Doors/entry_4"
72 panels { room: "Center Room" name: "REPORTER" } 72 panels { room: "Center Room" name: "REPORTER" }
@@ -77,7 +77,7 @@ doors {
77 panels { room: "Center Room" name: "SQUIRREL" } 77 panels { room: "Center Room" name: "SQUIRREL" }
78} 78}
79doors { 79doors {
80 name: "Top Left Puzzles" 80 name: "Northwest Puzzles"
81 type: LOCATION_ONLY 81 type: LOCATION_ONLY
82 panels { room: "Top Left Room" name: "BARE SOD" } 82 panels { room: "Top Left Room" name: "BARE SOD" }
83 panels { room: "Top Left Room" name: "SOD" } 83 panels { room: "Top Left Room" name: "SOD" }
@@ -104,7 +104,7 @@ doors {
104 location_room: "Top Left Room" 104 location_room: "Top Left Room"
105} 105}
106doors { 106doors {
107 name: "Top Right Puzzles" 107 name: "Northeast Puzzles"
108 type: LOCATION_ONLY 108 type: LOCATION_ONLY
109 panels { room: "Top Right Room" name: "RIGHT WING" } 109 panels { room: "Top Right Room" name: "RIGHT WING" }
110 panels { room: "Top Right Room" name: "WING" } 110 panels { room: "Top Right Room" name: "WING" }
@@ -130,7 +130,7 @@ doors {
130 location_room: "Top Right Room" 130 location_room: "Top Right Room"
131} 131}
132doors { 132doors {
133 name: "Bottom Left Puzzles" 133 name: "Southwest Puzzles"
134 type: LOCATION_ONLY 134 type: LOCATION_ONLY
135 panels { room: "Bottom Left Room" name: "SHELL (1)" } 135 panels { room: "Bottom Left Room" name: "SHELL (1)" }
136 panels { room: "Bottom Left Room" name: "SHELL (2)" } 136 panels { room: "Bottom Left Room" name: "SHELL (2)" }
@@ -141,7 +141,7 @@ doors {
141 location_room: "Bottom Left Room" 141 location_room: "Bottom Left Room"
142} 142}
143doors { 143doors {
144 name: "Bottom Right Puzzles" 144 name: "Southeast Puzzles"
145 type: LOCATION_ONLY 145 type: LOCATION_ONLY
146 panels { room: "Bottom Right Room" name: "FLY" } 146 panels { room: "Bottom Right Room" name: "FLY" }
147 panels { room: "Bottom Right Room" name: "DECLOG" } 147 panels { room: "Bottom Right Room" name: "DECLOG" }
diff --git a/data/maps/the_repetitive/doors.txtpb b/data/maps/the_repetitive/doors.txtpb index 8171dc4..d964928 100644 --- a/data/maps/the_repetitive/doors.txtpb +++ b/data/maps/the_repetitive/doors.txtpb
@@ -194,3 +194,9 @@ doors {
194 panels { room: "Yellow Room" name: "ASSESSES" } 194 panels { room: "Yellow Room" name: "ASSESSES" }
195 panels { room: "Yellow Room" name: "TINTING" } 195 panels { room: "Yellow Room" name: "TINTING" }
196} 196}
197doors {
198 name: "Anti-Collectable"
199 type: LOCATION_ONLY
200 senders: "Components/Collectables/anticollectable"
201 location_room: "Anti Room"
202}
diff --git a/data/maps/the_repetitive/metadata.txtpb b/data/maps/the_repetitive/metadata.txtpb index 6f5c459..76a0f50 100644 --- a/data/maps/the_repetitive/metadata.txtpb +++ b/data/maps/the_repetitive/metadata.txtpb
@@ -1,10 +1,6 @@
1display_name: "The Repetitive" 1display_name: "The Repetitive"
2# The anti-collectable doesn't fit into our system right now so let's ignore it.
3excluded_nodes: "Components/Collectables/anticollectable"
4# These paintings are directly above/behind panels and thus can't be entered. 2# These paintings are directly above/behind panels and thus can't be entered.
5excluded_nodes: "Meshes/eyeRed3" 3excluded_nodes: "Meshes/eyeRed3"
6excluded_nodes: "Meshes/eyeRed4" 4excluded_nodes: "Meshes/eyeRed4"
7# I do not know what this is.
8excluded_nodes: "Components/Doors/Door3/Hinge/panel_i"
9# This has something to do with the magenta room entrance proxy panel. 5# This has something to do with the magenta room entrance proxy panel.
10excluded_nodes: "Panels/Eval/panel_26_proxyied_fake" 6excluded_nodes: "Panels/Eval/panel_26_proxyied_fake"
diff --git a/data/maps/the_repetitive/rooms/Anti Room.txtpb b/data/maps/the_repetitive/rooms/Anti Room.txtpb index 641fede..65a99ff 100644 --- a/data/maps/the_repetitive/rooms/Anti Room.txtpb +++ b/data/maps/the_repetitive/rooms/Anti Room.txtpb
@@ -1,5 +1,4 @@
1name: "Anti Room" 1name: "Anti Room"
2# Ignore the collectible. The mod should remove it and the back wall too.
3panels { 2panels {
4 name: "HA (1)" 3 name: "HA (1)"
5 path: "Panels/Entry/panel_7" 4 path: "Panels/Entry/panel_7"
@@ -38,9 +37,17 @@ panels {
38 symbols: EXAMPLE 37 symbols: EXAMPLE
39} 38}
40panels { 39panels {
41 name: "EYE" 40 name: "EYE (1)"
42 path: "Panels/Entry/panel4" 41 path: "Panels/Entry/panel4"
43 clue: "eye" 42 clue: "eye"
44 answer: "iris" 43 answer: "iris"
45 symbols: BOXES 44 symbols: BOXES
46} 45}
46panels {
47 # This appears after grabbing the anti-collectable.
48 name: "EYE (2)"
49 path: "Components/Doors/Door3/Hinge/panel_i"
50 clue: "eye"
51 answer: "i"
52 symbols: ZERO
53}
diff --git a/data/maps/the_sun_temple/connections.txtpb b/data/maps/the_sun_temple/connections.txtpb index b0b3a0a..ffe4d5d 100644 --- a/data/maps/the_sun_temple/connections.txtpb +++ b/data/maps/the_sun_temple/connections.txtpb
@@ -7,6 +7,7 @@ connections {
7 from_room: "Temple" 7 from_room: "Temple"
8 to_room: "Ending" 8 to_room: "Ending"
9 door { name: "Ending" } 9 door { name: "Ending" }
10 purple_ending: true
10} 11}
11connections { 12connections {
12 from_room: "Temple" 13 from_room: "Temple"
diff --git a/data/maps/the_unkempt/connections.txtpb b/data/maps/the_unkempt/connections.txtpb index a9e30db..d4a046c 100644 --- a/data/maps/the_unkempt/connections.txtpb +++ b/data/maps/the_unkempt/connections.txtpb
@@ -1,7 +1,7 @@
1connections { 1connections {
2 from_room: "Main Area" 2 from_room: "Main Area"
3 to_room: "Right Area" 3 to_room: "Right Area"
4 door { name: "Right Door" } 4 door { name: "East Door" }
5} 5}
6connections { 6connections {
7 from_room: "Middle Room" 7 from_room: "Middle Room"
diff --git a/data/maps/the_unkempt/doors.txtpb b/data/maps/the_unkempt/doors.txtpb index 2349913..29065ec 100644 --- a/data/maps/the_unkempt/doors.txtpb +++ b/data/maps/the_unkempt/doors.txtpb
@@ -1,5 +1,5 @@
1doors { 1doors {
2 name: "Right Door" 2 name: "East Door"
3 type: STANDARD 3 type: STANDARD
4 receivers: "Components/Doors/entry_2" 4 receivers: "Components/Doors/entry_2"
5 panels { room: "Main Area" name: "EYE" } 5 panels { room: "Main Area" name: "EYE" }
diff --git a/data/metadata.txtpb b/data/metadata.txtpb index d0b3229..b7ba807 100644 --- a/data/metadata.txtpb +++ b/data/metadata.txtpb
@@ -1,4 +1,4 @@
1version: 1 1version: 6
2# Filler item. 2# Filler item.
3special_names: "A Job Well Done" 3special_names: "A Job Well Done"
4# Symbol items. 4# Symbol items.
@@ -21,3 +21,30 @@ special_names: "Stars Symbol"
21special_names: "Sun Symbol" 21special_names: "Sun Symbol"
22special_names: "Sweet Symbol" 22special_names: "Sweet Symbol"
23special_names: "Zero Symbol" 23special_names: "Zero Symbol"
24# Anti collectable traps
25special_names: "Anti A"
26special_names: "Anti B"
27special_names: "Anti C"
28special_names: "Anti D"
29special_names: "Anti E"
30special_names: "Anti F"
31special_names: "Anti G"
32special_names: "Anti H"
33special_names: "Anti I"
34special_names: "Anti J"
35special_names: "Anti K"
36special_names: "Anti L"
37special_names: "Anti M"
38special_names: "Anti N"
39special_names: "Anti O"
40special_names: "Anti P"
41special_names: "Anti Q"
42special_names: "Anti R"
43special_names: "Anti S"
44special_names: "Anti T"
45special_names: "Anti U"
46special_names: "Anti V"
47special_names: "Anti W"
48special_names: "Anti X"
49special_names: "Anti Y"
50special_names: "Anti Z"
diff --git a/proto/data.proto b/proto/data.proto index 827e639..64e3ddc 100644 --- a/proto/data.proto +++ b/proto/data.proto
@@ -27,6 +27,9 @@ enum DoorType {
27 27
28 // This door is an item if gravestone shuffle is enabled, and is a location as long as panelsanity is not on. 28 // This door is an item if gravestone shuffle is enabled, and is a location as long as panelsanity is not on.
29 GRAVESTONE = 6; 29 GRAVESTONE = 6;
30
31 // This door is never a location, and is an item as long as gallery painting shuffle is on.
32 GALLERY_PAINTING = 7;
30} 33}
31 34
32enum DoorGroupType { 35enum DoorGroupType {
@@ -106,6 +109,8 @@ message Connection {
106 } 109 }
107 110
108 optional bool roof_access = 7; 111 optional bool roof_access = 7;
112 optional bool purple_ending = 8;
113 optional bool cyan_ending = 9;
109} 114}
110 115
111message Door { 116message Door {
@@ -127,6 +132,7 @@ message Door {
127 repeated uint64 doors = 15; 132 repeated uint64 doors = 15;
128 repeated uint64 endings = 16; 133 repeated uint64 endings = 16;
129 optional bool double_letters = 18; 134 optional bool double_letters = 18;
135 repeated string senders = 19;
130 136
131 optional DoorType type = 8; 137 optional DoorType type = 8;
132 138
diff --git a/proto/human.proto b/proto/human.proto index e5335e7..c247edf 100644 --- a/proto/human.proto +++ b/proto/human.proto
@@ -66,6 +66,18 @@ message HumanConnection {
66 // If true, this connection will only be logically allowed if the Daedalus 66 // If true, this connection will only be logically allowed if the Daedalus
67 // Roof Access option is enabled. 67 // Roof Access option is enabled.
68 optional bool roof_access = 7; 68 optional bool roof_access = 7;
69
70 // This means that the connection intentionally skips the target object's
71 // required door.
72 optional bool bypass_target_door = 8;
73
74 // This means that the connection should additionally require all purple
75 // letters when the Strict Purple Ending option is on.
76 optional bool purple_ending = 9;
77
78 // This means that the connection should additionally require all cyan letters
79 // when the Strict Cyan Ending option is on.
80 optional bool cyan_ending = 10;
69} 81}
70 82
71message HumanConnections { 83message HumanConnections {
@@ -92,6 +104,10 @@ message HumanDoor {
92 repeated string endings = 13; 104 repeated string endings = 13;
93 optional bool double_letters = 15; 105 optional bool double_letters = 15;
94 106
107 // Sender nodes to be added to the list of requirements for triggering the
108 // location. Only for senders that have no logic requirements.
109 repeated string senders = 16;
110
95 optional DoorType type = 4; 111 optional DoorType type = 4;
96 optional string location_room = 5; 112 optional string location_room = 5;
97 optional string location_name = 14; 113 optional string location_name = 14;
diff --git a/tools/datapacker/main.cpp b/tools/datapacker/main.cpp index 6bbb461..596259b 100644 --- a/tools/datapacker/main.cpp +++ b/tools/datapacker/main.cpp
@@ -345,6 +345,9 @@ class DataPacker {
345 std::copy( 345 std::copy(
346 h_door.receivers().begin(), h_door.receivers().end(), 346 h_door.receivers().begin(), h_door.receivers().end(),
347 google::protobuf::RepeatedFieldBackInserter(door.mutable_receivers())); 347 google::protobuf::RepeatedFieldBackInserter(door.mutable_receivers()));
348 std::copy(
349 h_door.senders().begin(), h_door.senders().end(),
350 google::protobuf::RepeatedFieldBackInserter(door.mutable_senders()));
348 351
349 for (const PaintingIdentifier& pi : h_door.move_paintings()) { 352 for (const PaintingIdentifier& pi : h_door.move_paintings()) {
350 std::optional<std::string> map_name = 353 std::optional<std::string> map_name =
@@ -470,6 +473,16 @@ class DataPacker {
470 r_connection.set_roof_access(human_connection.roof_access()); 473 r_connection.set_roof_access(human_connection.roof_access());
471 } 474 }
472 475
476 if (human_connection.has_purple_ending()) {
477 f_connection.set_purple_ending(human_connection.purple_ending());
478 r_connection.set_purple_ending(human_connection.purple_ending());
479 }
480
481 if (human_connection.has_cyan_ending()) {
482 f_connection.set_cyan_ending(human_connection.cyan_ending());
483 r_connection.set_cyan_ending(human_connection.cyan_ending());
484 }
485
473 container_.AddConnection(f_connection); 486 container_.AddConnection(f_connection);
474 if (!human_connection.oneway()) { 487 if (!human_connection.oneway()) {
475 container_.AddConnection(r_connection); 488 container_.AddConnection(r_connection);
diff --git a/tools/validator/human_processor.cpp b/tools/validator/human_processor.cpp index 561225e..2c978bf 100644 --- a/tools/validator/human_processor.cpp +++ b/tools/validator/human_processor.cpp
@@ -394,7 +394,9 @@ class HumanProcessor {
394 } 394 }
395 } else if (human_connection.has_from()) { 395 } else if (human_connection.has_from()) {
396 ProcessSingleConnection(human_connection, human_connection.from(), 396 ProcessSingleConnection(human_connection, human_connection.from(),
397 current_map_name); 397 current_map_name,
398 /*is_target=*/!human_connection.oneway() &&
399 !human_connection.bypass_target_door());
398 } 400 }
399 401
400 if (human_connection.has_to_room()) { 402 if (human_connection.has_to_room()) {
@@ -410,8 +412,9 @@ class HumanProcessor {
410 std::cout << "A global connection used to_room." << std::endl; 412 std::cout << "A global connection used to_room." << std::endl;
411 } 413 }
412 } else if (human_connection.has_to()) { 414 } else if (human_connection.has_to()) {
413 ProcessSingleConnection(human_connection, human_connection.to(), 415 ProcessSingleConnection(
414 current_map_name); 416 human_connection, human_connection.to(), current_map_name,
417 /*is_target=*/!human_connection.bypass_target_door());
415 } 418 }
416 419
417 if (human_connection.has_door()) { 420 if (human_connection.has_door()) {
@@ -432,7 +435,7 @@ class HumanProcessor {
432 void ProcessSingleConnection( 435 void ProcessSingleConnection(
433 const HumanConnection& human_connection, 436 const HumanConnection& human_connection,
434 const HumanConnection::Endpoint& endpoint, 437 const HumanConnection::Endpoint& endpoint,
435 const std::optional<std::string>& current_map_name) { 438 const std::optional<std::string>& current_map_name, bool is_target) {
436 if (endpoint.has_room()) { 439 if (endpoint.has_room()) {
437 auto room_identifier = 440 auto room_identifier =
438 GetCompleteRoomIdentifier(endpoint.room(), current_map_name); 441 GetCompleteRoomIdentifier(endpoint.room(), current_map_name);
@@ -451,6 +454,11 @@ class HumanProcessor {
451 if (painting_identifier) { 454 if (painting_identifier) {
452 PaintingInfo& painting_info = info_.paintings[*painting_identifier]; 455 PaintingInfo& painting_info = info_.paintings[*painting_identifier];
453 painting_info.connections_referenced_by.push_back(human_connection); 456 painting_info.connections_referenced_by.push_back(human_connection);
457
458 if (is_target) {
459 painting_info.target_connections_referenced_by.push_back(
460 human_connection);
461 }
454 } else { 462 } else {
455 // Not sure where else to store this right now. 463 // Not sure where else to store this right now.
456 std::cout 464 std::cout
@@ -463,6 +471,11 @@ class HumanProcessor {
463 if (port_identifier) { 471 if (port_identifier) {
464 PortInfo& port_info = info_.ports[*port_identifier]; 472 PortInfo& port_info = info_.ports[*port_identifier];
465 port_info.connections_referenced_by.push_back(human_connection); 473 port_info.connections_referenced_by.push_back(human_connection);
474
475 if (is_target) {
476 port_info.target_connections_referenced_by.push_back(
477 human_connection);
478 }
466 } else { 479 } else {
467 // Not sure where else to store this right now. 480 // Not sure where else to store this right now.
468 std::cout 481 std::cout
@@ -480,6 +493,11 @@ class HumanProcessor {
480 panel_info.proxies[endpoint.panel().answer()] 493 panel_info.proxies[endpoint.panel().answer()]
481 .connections_referenced_by.push_back(human_connection); 494 .connections_referenced_by.push_back(human_connection);
482 } 495 }
496
497 if (is_target) {
498 panel_info.target_connections_referenced_by.push_back(
499 human_connection);
500 }
483 } 501 }
484 } 502 }
485 } 503 }
diff --git a/tools/validator/structs.h b/tools/validator/structs.h index 17ed33a..d1d45f2 100644 --- a/tools/validator/structs.h +++ b/tools/validator/structs.h
@@ -56,12 +56,14 @@ struct PortInfo {
56 std::vector<HumanPort> definitions; 56 std::vector<HumanPort> definitions;
57 57
58 std::vector<HumanConnection> connections_referenced_by; 58 std::vector<HumanConnection> connections_referenced_by;
59 std::vector<HumanConnection> target_connections_referenced_by;
59}; 60};
60 61
61struct PaintingInfo { 62struct PaintingInfo {
62 std::vector<HumanPainting> definitions; 63 std::vector<HumanPainting> definitions;
63 64
64 std::vector<HumanConnection> connections_referenced_by; 65 std::vector<HumanConnection> connections_referenced_by;
66 std::vector<HumanConnection> target_connections_referenced_by;
65 std::vector<DoorIdentifier> doors_referenced_by; 67 std::vector<DoorIdentifier> doors_referenced_by;
66}; 68};
67 69
@@ -79,6 +81,7 @@ struct PanelInfo {
79 std::string map_area_name; 81 std::string map_area_name;
80 82
81 std::vector<HumanConnection> connections_referenced_by; 83 std::vector<HumanConnection> connections_referenced_by;
84 std::vector<HumanConnection> target_connections_referenced_by;
82 std::vector<DoorIdentifier> doors_referenced_by; 85 std::vector<DoorIdentifier> doors_referenced_by;
83 86
84 std::map<std::string, ProxyInfo> proxies; 87 std::map<std::string, ProxyInfo> proxies;
diff --git a/tools/validator/validator.cpp b/tools/validator/validator.cpp index 4149caa..dd41f5c 100644 --- a/tools/validator/validator.cpp +++ b/tools/validator/validator.cpp
@@ -106,7 +106,8 @@ class Validator {
106 return false; 106 return false;
107 } 107 }
108 108
109 if (h_door.keyholders_size() > 0 || h_door.endings_size() > 0) { 109 if (h_door.keyholders_size() > 0 || h_door.endings_size() > 0 ||
110 h_door.complete_at() > 0) {
110 return true; 111 return true;
111 } 112 }
112 113
@@ -256,6 +257,22 @@ class Validator {
256 std::cout << "Port " << port_identifier.ShortDebugString() 257 std::cout << "Port " << port_identifier.ShortDebugString()
257 << " was defined multiple times." << std::endl; 258 << " was defined multiple times." << std::endl;
258 } 259 }
260
261 if (!port_info.target_connections_referenced_by.empty()) {
262 for (const HumanPort& port : port_info.definitions) {
263 if (port.has_required_door()) {
264 std::cout << "Port " << port_identifier.ShortDebugString()
265 << " has a required door but is the target of a connection:"
266 << std::endl;
267
268 for (const HumanConnection& connection :
269 port_info.target_connections_referenced_by) {
270 std::cout << " CONNECTION " << connection.ShortDebugString()
271 << std::endl;
272 }
273 }
274 }
275 }
259 } 276 }
260 277
261 void ValidatePainting(const PaintingIdentifier& painting_identifier, 278 void ValidatePainting(const PaintingIdentifier& painting_identifier,
@@ -279,6 +296,22 @@ class Validator {
279 std::cout << "Painting " << painting_identifier.ShortDebugString() 296 std::cout << "Painting " << painting_identifier.ShortDebugString()
280 << " was defined multiple times." << std::endl; 297 << " was defined multiple times." << std::endl;
281 } 298 }
299
300 if (!painting_info.target_connections_referenced_by.empty()) {
301 for (const HumanPainting& painting : painting_info.definitions) {
302 if (painting.has_required_door()) {
303 std::cout << "Painting " << painting_identifier.ShortDebugString()
304 << " has a required door but is the target of a connection:"
305 << std::endl;
306
307 for (const HumanConnection& connection :
308 painting_info.target_connections_referenced_by) {
309 std::cout << " CONNECTION " << connection.ShortDebugString()
310 << std::endl;
311 }
312 }
313 }
314 }
282 } 315 }
283 316
284 void ValidatePanel(const PanelIdentifier& panel_identifier, 317 void ValidatePanel(const PanelIdentifier& panel_identifier,
@@ -340,6 +373,22 @@ class Validator {
340 std::cout << "Panel " << panel_identifier.ShortDebugString() 373 std::cout << "Panel " << panel_identifier.ShortDebugString()
341 << " is missing an AP ID." << std::endl; 374 << " is missing an AP ID." << std::endl;
342 } 375 }
376
377 if (!panel_info.target_connections_referenced_by.empty()) {
378 for (const HumanPanel& panel : panel_info.definitions) {
379 if (panel.has_required_door()) {
380 std::cout << "Panel " << panel_identifier.ShortDebugString()
381 << " has a required door but is the target of a connection:"
382 << std::endl;
383
384 for (const HumanConnection& connection :
385 panel_info.target_connections_referenced_by) {
386 std::cout << " CONNECTION " << connection.ShortDebugString()
387 << std::endl;
388 }
389 }
390 }
391 }
343 } 392 }
344 393
345 void ValidateKeyholder(const KeyholderIdentifier& keyholder_identifier, 394 void ValidateKeyholder(const KeyholderIdentifier& keyholder_identifier,