summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--apworld/__init__.py1
-rw-r--r--apworld/options.py9
-rw-r--r--apworld/player_logic.py35
-rw-r--r--apworld/static_logic.py3
-rw-r--r--client/Archipelago/client.gd15
-rw-r--r--client/Archipelago/collectable.gd4
-rw-r--r--client/Archipelago/gamedata.gd5
-rw-r--r--client/Archipelago/manager.gd73
-rw-r--r--client/Archipelago/player.gd17
-rw-r--r--data/door_groups.txtpb84
-rw-r--r--data/ids.yaml12
-rw-r--r--data/maps/daedalus/doors.txtpb7
-rw-r--r--data/maps/the_entry/connections.txtpb2
-rw-r--r--data/maps/the_entry/doors.txtpb2
-rw-r--r--data/maps/the_great/doors.txtpb1
-rw-r--r--data/maps/the_linear/doors.txtpb1
-rw-r--r--data/maps/the_repetitive/connections.txtpb2
-rw-r--r--data/maps/the_repetitive/doors.txtpb2
-rw-r--r--data/maps/the_three_doors/doors.txtpb1
-rw-r--r--data/maps/the_tree/doors.txtpb1
-rw-r--r--proto/data.proto23
-rw-r--r--proto/human.proto11
-rw-r--r--tools/assign_ids/main.cpp20
-rw-r--r--tools/datapacker/container.cpp17
-rw-r--r--tools/datapacker/container.h3
-rw-r--r--tools/datapacker/main.cpp31
-rw-r--r--tools/util/ids_yaml_format.cpp12
-rw-r--r--tools/validator/human_processor.cpp33
-rw-r--r--tools/validator/structs.h9
-rw-r--r--tools/validator/validator.cpp28
30 files changed, 440 insertions, 24 deletions
diff --git a/apworld/__init__.py b/apworld/__init__.py index 8e3066d..d6a3acb 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py
@@ -68,6 +68,7 @@ class Lingo2World(World):
68 slot_options = [ 68 slot_options = [
69 "daedalus_roof_access", 69 "daedalus_roof_access",
70 "keyholder_sanity", 70 "keyholder_sanity",
71 "shuffle_control_center_colors",
71 "shuffle_doors", 72 "shuffle_doors",
72 "shuffle_letters", 73 "shuffle_letters",
73 "victory_condition", 74 "victory_condition",
diff --git a/apworld/options.py b/apworld/options.py index f7dc5bd..dbf09e7 100644 --- a/apworld/options.py +++ b/apworld/options.py
@@ -8,6 +8,14 @@ class ShuffleDoors(Toggle):
8 display_name = "Shuffle Doors" 8 display_name = "Shuffle Doors"
9 9
10 10
11class ShuffleControlCenterColors(Toggle):
12 """
13 Some doors open after solving the COLOR panel in the Control Center. If this option is enabled, these doors will
14 instead open upon receiving an item.
15 """
16 display_name = "Shuffle Control Center Colors"
17
18
11class ShuffleLetters(Choice): 19class ShuffleLetters(Choice):
12 """ 20 """
13 Controls how letter unlocks are handled. Note that H1, I1, N1, and T1 will always be present at their vanilla 21 Controls how letter unlocks are handled. Note that H1, I1, N1, and T1 will always be present at their vanilla
@@ -71,6 +79,7 @@ class VictoryCondition(Choice):
71@dataclass 79@dataclass
72class Lingo2Options(PerGameCommonOptions): 80class Lingo2Options(PerGameCommonOptions):
73 shuffle_doors: ShuffleDoors 81 shuffle_doors: ShuffleDoors
82 shuffle_control_center_colors: ShuffleControlCenterColors
74 shuffle_letters: ShuffleLetters 83 shuffle_letters: ShuffleLetters
75 keyholder_sanity: KeyholderSanity 84 keyholder_sanity: KeyholderSanity
76 daedalus_roof_access: DaedalusRoofAccess 85 daedalus_roof_access: DaedalusRoofAccess
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 5cb9011..ce9a4e5 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -123,16 +123,39 @@ class Lingo2PlayerLogic:
123 self.item_by_door[progressive.doors[i]] = (progressive.name, i + 1) 123 self.item_by_door[progressive.doors[i]] = (progressive.name, i + 1)
124 self.real_items.append(progressive.name) 124 self.real_items.append(progressive.name)
125 125
126 for door_group in world.static_logic.objects.door_groups:
127 if door_group.type == data_pb2.DoorGroupType.CONNECTOR and not self.world.options.shuffle_doors:
128 continue
129
130 if (door_group.type == data_pb2.DoorGroupType.COLOR_CONNECTOR and
131 not self.world.options.shuffle_control_center_colors):
132 continue
133
134 for door in door_group.doors:
135 self.item_by_door[door] = (door_group.name, 1)
136
137 self.real_items.append(door_group.name)
138
126 # We iterate through the doors in two parts because it is essential that we determine which doors are shuffled 139 # We iterate through the doors in two parts because it is essential that we determine which doors are shuffled
127 # before we calculate any access requirements. 140 # before we calculate any access requirements.
128 for door in world.static_logic.objects.doors: 141 for door in world.static_logic.objects.doors:
129 if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors: 142 if door.type in [data_pb2.DoorType.EVENT, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]:
130 if door.id in self.item_by_door: 143 continue
131 continue 144
145 if door.id in self.item_by_door:
146 continue
147
148 if (door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and
149 not self.world.options.shuffle_doors):
150 continue
151
152 if (door.type == data_pb2.DoorType.CONTROL_CENTER_COLOR and
153 not self.world.options.shuffle_control_center_colors):
154 continue
132 155
133 door_item_name = self.world.static_logic.get_door_item_name(door) 156 door_item_name = self.world.static_logic.get_door_item_name(door)
134 self.item_by_door[door.id] = (door_item_name, 1) 157 self.item_by_door[door.id] = (door_item_name, 1)
135 self.real_items.append(door_item_name) 158 self.real_items.append(door_item_name)
136 159
137 for door in world.static_logic.objects.doors: 160 for door in world.static_logic.objects.doors:
138 if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]: 161 if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]:
diff --git a/apworld/static_logic.py b/apworld/static_logic.py index b699d59..3f6cdea 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py
@@ -44,6 +44,9 @@ class Lingo2StaticLogic:
44 for progressive in self.objects.progressives: 44 for progressive in self.objects.progressives:
45 self.item_id_to_name[progressive.ap_id] = progressive.name 45 self.item_id_to_name[progressive.ap_id] = progressive.name
46 46
47 for door_group in self.objects.door_groups:
48 self.item_id_to_name[door_group.ap_id] = door_group.name
49
47 for keyholder in self.objects.keyholders: 50 for keyholder in self.objects.keyholders:
48 if keyholder.HasField("key"): 51 if keyholder.HasField("key"):
49 location_name = f"{self.get_room_object_location_prefix(keyholder)} - {keyholder.key.upper()} Keyholder" 52 location_name = f"{self.get_room_object_location_prefix(keyholder)} - {keyholder.key.upper()} Keyholder"
diff --git a/client/Archipelago/client.gd b/client/Archipelago/client.gd index 428560e..2e080fd 100644 --- a/client/Archipelago/client.gd +++ b/client/Archipelago/client.gd
@@ -41,6 +41,7 @@ signal connect_status
41signal client_connected(slot_data) 41signal client_connected(slot_data)
42signal item_received(item_id, index, player, flags, amount) 42signal item_received(item_id, index, player, flags, amount)
43signal message_received(message) 43signal message_received(message)
44signal location_scout_received(item_id, location_id, player, flags)
44 45
45 46
46func _init(): 47func _init():
@@ -257,6 +258,16 @@ func _process(_delta):
257 elif cmd == "PrintJSON": 258 elif cmd == "PrintJSON":
258 emit_signal("message_received", message) 259 emit_signal("message_received", message)
259 260
261 elif cmd == "LocationInfo":
262 for loc in message["locations"]:
263 emit_signal(
264 "location_scout_received",
265 int(loc["item"]),
266 int(loc["location"]),
267 int(loc["player"]),
268 int(loc["flags"])
269 )
270
260 elif state == WebSocketPeer.STATE_CLOSED: 271 elif state == WebSocketPeer.STATE_CLOSED:
261 if _has_connected: 272 if _has_connected:
262 _closed() 273 _closed()
@@ -392,6 +403,10 @@ func completedGoal():
392 sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL 403 sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL
393 404
394 405
406func scoutLocations(loc_ids):
407 sendMessage([{"cmd": "LocationScouts", "locations": loc_ids}])
408
409
395func hasItem(item_id): 410func hasItem(item_id):
396 return _received_items.has(item_id) 411 return _received_items.has(item_id)
397 412
diff --git a/client/Archipelago/collectable.gd b/client/Archipelago/collectable.gd index 7bbdd8b..4a17a2a 100644 --- a/client/Archipelago/collectable.gd +++ b/client/Archipelago/collectable.gd
@@ -10,3 +10,7 @@ func pickedUp():
10 ap.keyboard.update_unlocks() 10 ap.keyboard.update_unlocks()
11 11
12 super.pickedUp() 12 super.pickedUp()
13
14
15func setScoutedText(text):
16 get_node("MeshInstance3D").mesh.text = text.replace(" ", "\n")
diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd index 11f4981..f7a5d90 100644 --- a/client/Archipelago/gamedata.gd +++ b/client/Archipelago/gamedata.gd
@@ -8,7 +8,7 @@ var painting_id_by_map_node_path = {}
8var door_id_by_ap_id = {} 8var door_id_by_ap_id = {}
9var map_id_by_name = {} 9var map_id_by_name = {}
10var progressive_id_by_ap_id = {} 10var progressive_id_by_ap_id = {}
11var letter_key_by_ap_id = {} 11var letter_id_by_ap_id = {}
12 12
13 13
14func _init(proto_script): 14func _init(proto_script):
@@ -56,8 +56,7 @@ func load(data_bytes):
56 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id() 56 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id()
57 57
58 for letter in objects.get_letters(): 58 for letter in objects.get_letters():
59 if not letter.has_level2() or not letter.get_level2(): 59 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id()
60 letter_key_by_ap_id[letter.get_ap_id()] = letter.get_key()
61 60
62 61
63func get_door_for_map_node_path(map_name, node_path): 62func get_door_for_map_node_path(map_name, node_path):
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index 07d28a4..72abf34 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -20,6 +20,8 @@ var _localdata_file = ""
20var _last_new_item = -1 20var _last_new_item = -1
21var _batch_locations = false 21var _batch_locations = false
22var _held_locations = [] 22var _held_locations = []
23var _held_location_scouts = []
24var _location_scouts = {}
23var _item_locks = {} 25var _item_locks = {}
24var _held_letters = {} 26var _held_letters = {}
25var _letters_setup = false 27var _letters_setup = false
@@ -36,6 +38,7 @@ const kLETTER_BEHAVIOR_UNLOCKED = 2
36 38
37var daedalus_roof_access = false 39var daedalus_roof_access = false
38var keyholder_sanity = false 40var keyholder_sanity = false
41var shuffle_control_center_colors = false
39var shuffle_doors = false 42var shuffle_doors = false
40var shuffle_letters = kSHUFFLE_LETTERS_VANILLA 43var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
41var victory_condition = -1 44var victory_condition = -1
@@ -75,6 +78,7 @@ func _ready():
75 78
76 client.connect("item_received", _process_item) 79 client.connect("item_received", _process_item)
77 client.connect("message_received", _process_message) 80 client.connect("message_received", _process_message)
81 client.connect("location_scout_received", _process_location_scout)
78 client.connect("could_not_connect", _client_could_not_connect) 82 client.connect("could_not_connect", _client_could_not_connect)
79 client.connect("connect_status", _client_connect_status) 83 client.connect("connect_status", _client_connect_status)
80 client.connect("client_connected", _client_connected) 84 client.connect("client_connected", _client_connected)
@@ -161,9 +165,11 @@ func _process_item(item, index, from, flags, amount):
161 if rnode != null: 165 if rnode != null:
162 rnode.handleTriggered() 166 rnode.handleTriggered()
163 167
164 var letter_key = gamedata.letter_key_by_ap_id.get(item, null) 168 var letter_id = gamedata.letter_id_by_ap_id.get(item, null)
165 if letter_key != null: 169 if letter_id != null:
166 _process_key_item(letter_key, amount) 170 var letter = gamedata.objects.get_letters()[letter_id]
171 if not letter.has_level2() or not letter.get_level2():
172 _process_key_item(letter.get_key(), amount)
167 173
168 # Show a message about the item if it's new. 174 # Show a message about the item if it's new.
169 if index != null and index > _last_new_item: 175 if index != null and index > _last_new_item:
@@ -280,6 +286,29 @@ func parse_printjson_for_textclient(message):
280 textclient_node.parse_printjson("".join(parts)) 286 textclient_node.parse_printjson("".join(parts))
281 287
282 288
289func _process_location_scout(item_id, location_id, player, flags):
290 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
291
292 var gamedata = global.get_node("Gamedata")
293 var map_id = gamedata.map_id_by_name.get(global.map)
294
295 var item_name = "Unknown"
296 var item_player_game = client._game_by_player[float(player)]
297 if client._item_id_to_name[item_player_game].has(item_id):
298 item_name = client._item_id_to_name[item_player_game][item_id]
299
300 var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null)
301 if letter_id != null:
302 var letter = gamedata.objects.get_letters()[letter_id]
303 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
304 if room.get_map_id() == map_id:
305 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
306 letter.get_path()
307 )
308 if collectable != null:
309 collectable.setScoutedText(item_name)
310
311
283func _client_could_not_connect(): 312func _client_could_not_connect():
284 emit_signal("could_not_connect") 313 emit_signal("could_not_connect")
285 314
@@ -311,6 +340,7 @@ func _client_connected(slot_data):
311 # Read slot data. 340 # Read slot data.
312 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) 341 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
313 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) 342 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
343 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
314 shuffle_doors = bool(slot_data.get("shuffle_doors", false)) 344 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
315 shuffle_letters = int(slot_data.get("shuffle_letters", 0)) 345 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
316 victory_condition = int(slot_data.get("victory_condition", 0)) 346 victory_condition = int(slot_data.get("victory_condition", 0))
@@ -331,6 +361,21 @@ func _client_connected(slot_data):
331 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]] 361 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]]
332 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1] 362 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1]
333 363
364 for door_group in gamedata.objects.get_door_groups():
365 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR:
366 for door in door_group.get_doors():
367 _item_locks[door] = [door_group.get_ap_id(), 1]
368
369 if shuffle_control_center_colors:
370 for door in gamedata.objects.get_doors():
371 if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR:
372 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
373
374 for door_group in gamedata.objects.get_door_groups():
375 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR:
376 for door in door_group.get_doors():
377 _item_locks[door] = [door_group.get_ap_id(), 1]
378
334 emit_signal("ap_connected") 379 emit_signal("ap_connected")
335 380
336 381
@@ -345,10 +390,28 @@ func send_location(loc_id):
345 client.sendLocation(loc_id) 390 client.sendLocation(loc_id)
346 391
347 392
393func scout_location(loc_id):
394 if _location_scouts.has(loc_id):
395 return _location_scouts.get(loc_id)
396
397 if _batch_locations:
398 _held_location_scouts.append(loc_id)
399 else:
400 client.scoutLocation(loc_id)
401
402 return null
403
404
348func stop_batching_locations(): 405func stop_batching_locations():
349 _batch_locations = false 406 _batch_locations = false
350 client.sendLocations(_held_locations) 407
351 _held_locations.clear() 408 if not _held_locations.is_empty():
409 client.sendLocations(_held_locations)
410 _held_locations.clear()
411
412 if not _held_location_scouts.is_empty():
413 client.scoutLocations(_held_location_scouts)
414 _held_location_scouts.clear()
352 415
353 416
354func colorForItemType(flags): 417func colorForItemType(flags):
diff --git a/client/Archipelago/player.gd b/client/Archipelago/player.gd index 4569af5..dd6aa2b 100644 --- a/client/Archipelago/player.gd +++ b/client/Archipelago/player.gd
@@ -81,6 +81,23 @@ func _ready():
81 81
82 get_parent().add_child.call_deferred(locationListener) 82 get_parent().add_child.call_deferred(locationListener)
83 83
84 if (
85 ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2())
86 != ap.kLETTER_BEHAVIOR_VANILLA
87 ):
88 var scout = ap.scout_location(letter.get_ap_id())
89 if scout != null:
90 var item_name = "Unknown"
91 var item_player_game = ap.client._game_by_player[float(scout["player"])]
92 if ap.client._item_id_to_name[item_player_game].has(scout["item"]):
93 item_name = ap.client._item_id_to_name[item_player_game][scout["item"]]
94
95 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
96 letter.get_path()
97 )
98 if collectable != null:
99 collectable.setScoutedText.call_deferred(item_name)
100
84 # Set up mastery locations. 101 # Set up mastery locations.
85 for mastery in gamedata.objects.get_masteries(): 102 for mastery in gamedata.objects.get_masteries():
86 var room = gamedata.objects.get_rooms()[mastery.get_room_id()] 103 var room = gamedata.objects.get_rooms()[mastery.get_room_id()]
diff --git a/data/door_groups.txtpb b/data/door_groups.txtpb new file mode 100644 index 0000000..ca8ce54 --- /dev/null +++ b/data/door_groups.txtpb
@@ -0,0 +1,84 @@
1door_groups {
2 name: "The Entry - Repetitive Entrance"
3 type: CONNECTOR
4 doors {
5 map: "the_entry"
6 name: "Starting Room West Wall North Door"
7 }
8 doors {
9 map: "the_repetitive"
10 name: "Entry Entrance"
11 }
12}
13door_groups {
14 name: "The Repetitive - Plaza Entrance"
15 type: CONNECTOR
16 doors {
17 map: "the_repetitive"
18 name: "Black Hallway"
19 }
20 doors {
21 map: "the_plaza"
22 name: "Repetitive Entrance"
23 }
24}
25door_groups {
26 name: "Control Center White Doors"
27 type: COLOR_CONNECTOR
28 doors {
29 map: "daedalus"
30 name: "White Hallway From Entry"
31 }
32 doors {
33 map: "the_entry"
34 name: "Control Center White Door"
35 }
36}
37door_groups {
38 name: "Control Center Purple Doors"
39 type: COLOR_CONNECTOR
40 doors {
41 map: "daedalus"
42 name: "Purple Hallway From Great"
43 }
44 doors {
45 map: "the_great"
46 name: "Control Center Purple Door"
47 }
48}
49door_groups {
50 name: "Control Center Orange Doors"
51 type: COLOR_CONNECTOR
52 doors {
53 map: "daedalus"
54 name: "Control Center Orange Door"
55 }
56 doors {
57 map: "the_unkempt"
58 name: "Control Center Orange Door"
59 }
60}
61door_groups {
62 name: "Control Center Brown Doors"
63 type: COLOR_CONNECTOR
64 doors {
65 map: "the_bearer"
66 name: "Control Center Brown Door"
67 }
68 doors {
69 map: "the_tree"
70 name: "Control Center Brown Door"
71 }
72}
73door_groups {
74 name: "Control Center Blue Doors"
75 type: COLOR_CONNECTOR
76 doors {
77 map: "the_digital"
78 name: "Control Center Blue Door"
79 }
80 doors {
81 map: "the_unyielding"
82 name: "Digital Entrance"
83 }
84}
diff --git a/data/ids.yaml b/data/ids.yaml index 6498619..bd6cbc1 100644 --- a/data/ids.yaml +++ b/data/ids.yaml
@@ -1743,13 +1743,13 @@ maps:
1743 Red Blue Area Left Door: 302 1743 Red Blue Area Left Door: 302
1744 Red Blue Area Right Door: 303 1744 Red Blue Area Right Door: 303
1745 Red Room Painting: 323 1745 Red Room Painting: 323
1746 Repetitive Entrance: 312
1747 Revitalized Entrance: 306 1746 Revitalized Entrance: 306
1748 Right Eye Entrance: 301 1747 Right Eye Entrance: 301
1749 Scarf Door: 296 1748 Scarf Door: 296
1750 Second Room Left Door: 298 1749 Second Room Left Door: 298
1751 Second Room Right Door: 290 1750 Second Room Right Door: 290
1752 Shop Entrance: 313 1751 Shop Entrance: 313
1752 Starting Room West Wall North Door: 2781
1753 Third Eye Painting: 324 1753 Third Eye Painting: 324
1754 Trick Door: 287 1754 Trick Door: 287
1755 Trick To Shop Door: 289 1755 Trick To Shop Door: 289
@@ -2760,6 +2760,7 @@ maps:
2760 ZEROING: 1118 2760 ZEROING: 1118
2761 doors: 2761 doors:
2762 Anti-Collectable Room: 1025 2762 Anti-Collectable Room: 1025
2763 Black Hallway: 2780
2763 Cyan Door: 1028 2764 Cyan Door: 1028
2764 Cyan Puzzles: 1032 2765 Cyan Puzzles: 1032
2765 Dot Area Entrance: 1026 2766 Dot Area Entrance: 1026
@@ -2768,7 +2769,6 @@ maps:
2768 Lime Puzzles: 1031 2769 Lime Puzzles: 1031
2769 Magenta Door: 1029 2770 Magenta Door: 1029
2770 Magenta Puzzles: 1033 2771 Magenta Puzzles: 1033
2771 Plaza Entrance: 1024
2772 Yellow Door: 1030 2772 Yellow Door: 1030
2773 Yellow Puzzles: 1034 2773 Yellow Puzzles: 1034
2774 the_revitalized: 2774 the_revitalized:
@@ -3840,3 +3840,11 @@ special:
3840 A Job Well Done: 1160 3840 A Job Well Done: 1160
3841progressives: 3841progressives:
3842 Progressive Gold Ending: 2753 3842 Progressive Gold Ending: 2753
3843door_groups:
3844 Control Center Blue Doors: 2788
3845 Control Center Brown Doors: 2787
3846 Control Center Orange Doors: 2786
3847 Control Center Purple Doors: 2785
3848 Control Center White Doors: 2784
3849 The Entry - Repetitive Entrance: 2782
3850 The Repetitive - Plaza Entrance: 2783
diff --git a/data/maps/daedalus/doors.txtpb b/data/maps/daedalus/doors.txtpb index 2d8ba0b..d6c33f4 100644 --- a/data/maps/daedalus/doors.txtpb +++ b/data/maps/daedalus/doors.txtpb
@@ -892,16 +892,12 @@ doors {
892} 892}
893doors { 893doors {
894 name: "White Hallway From Entry" 894 name: "White Hallway From Entry"
895 # TODO: This should be combined with the corresponding door in the_entry, at
896 # least when connections are not shuffled.
897 type: CONTROL_CENTER_COLOR 895 type: CONTROL_CENTER_COLOR
898 receivers: "Components/Doors/Halls/froom_6" 896 receivers: "Components/Doors/Halls/froom_6"
899 control_center_color: "white" 897 control_center_color: "white"
900} 898}
901doors { 899doors {
902 name: "Purple Hallway From Great" 900 name: "Purple Hallway From Great"
903 # TODO: This should be combined with the corresponding door in the_great, at
904 # least when connections are not shuffled.
905 type: CONTROL_CENTER_COLOR 901 type: CONTROL_CENTER_COLOR
906 receivers: "Components/Doors/Halls/froom_7" 902 receivers: "Components/Doors/Halls/froom_7"
907 control_center_color: "purple" 903 control_center_color: "purple"
@@ -1080,6 +1076,7 @@ doors {
1080 panels { room: "Outside Snake Room" name: "SONG (South)" } 1076 panels { room: "Outside Snake Room" name: "SONG (South)" }
1081 panels { room: "West Castle Area" name: "SONG (2)" } 1077 panels { room: "West Castle Area" name: "SONG (2)" }
1082 location_room: "West Castle Area" 1078 location_room: "West Castle Area"
1079 location_name: "South SONGs"
1083} 1080}
1084doors { 1081doors {
1085 name: "Amber North Door" 1082 name: "Amber North Door"
@@ -1088,6 +1085,7 @@ doors {
1088 panels { room: "Outside Snake Room" name: "SONG (North)" } 1085 panels { room: "Outside Snake Room" name: "SONG (North)" }
1089 panels { room: "Amber North 2" name: "SONG" } 1086 panels { room: "Amber North 2" name: "SONG" }
1090 location_room: "Amber North 2" 1087 location_room: "Amber North 2"
1088 location_name: "North SONGs"
1091} 1089}
1092doors { 1090doors {
1093 name: "Amber East Doors" 1091 name: "Amber East Doors"
@@ -1921,6 +1919,7 @@ doors {
1921 type: LOCATION_ONLY 1919 type: LOCATION_ONLY
1922 panels { room: "Dark Light Exit" name: "GASKET" } 1920 panels { room: "Dark Light Exit" name: "GASKET" }
1923 location_room: "Dark Light Exit" 1921 location_room: "Dark Light Exit"
1922 location_name: "GASKET"
1924} 1923}
1925doors { 1924doors {
1926 name: "Dark Light Room Divider" 1925 name: "Dark Light Room Divider"
diff --git a/data/maps/the_entry/connections.txtpb b/data/maps/the_entry/connections.txtpb index 6f847da..a2e325a 100644 --- a/data/maps/the_entry/connections.txtpb +++ b/data/maps/the_entry/connections.txtpb
@@ -192,7 +192,7 @@ connections {
192connections { 192connections {
193 from_room: "Starting Room" 193 from_room: "Starting Room"
194 to_room: "Repetitive Entrance" 194 to_room: "Repetitive Entrance"
195 door { name: "Repetitive Entrance" } 195 door { name: "Starting Room West Wall North Door" }
196} 196}
197connections { 197connections {
198 from_room: "Lime Room" 198 from_room: "Lime Room"
diff --git a/data/maps/the_entry/doors.txtpb b/data/maps/the_entry/doors.txtpb index 78ffa52..6bef160 100644 --- a/data/maps/the_entry/doors.txtpb +++ b/data/maps/the_entry/doors.txtpb
@@ -193,7 +193,7 @@ doors {
193 location_room: "Starting Room" 193 location_room: "Starting Room"
194} 194}
195doors { 195doors {
196 name: "Repetitive Entrance" 196 name: "Starting Room West Wall North Door"
197 type: ITEM_ONLY 197 type: ITEM_ONLY
198 receivers: "Components/Doors/Entry/entry_proxied_9" 198 receivers: "Components/Doors/Entry/entry_proxied_9"
199 double_letters: true 199 double_letters: true
diff --git a/data/maps/the_great/doors.txtpb b/data/maps/the_great/doors.txtpb index dc25128..992b4fa 100644 --- a/data/maps/the_great/doors.txtpb +++ b/data/maps/the_great/doors.txtpb
@@ -49,6 +49,7 @@ doors {
49 type: LOCATION_ONLY 49 type: LOCATION_ONLY
50 panels { room: "West Side" name: "ERASE" } 50 panels { room: "West Side" name: "ERASE" }
51 location_room: "West Side" 51 location_room: "West Side"
52 location_name: "ERASE"
52} 53}
53doors { 54doors {
54 name: "Control Center Purple Door" 55 name: "Control Center Purple Door"
diff --git a/data/maps/the_linear/doors.txtpb b/data/maps/the_linear/doors.txtpb index 63d8ae8..9a57158 100644 --- a/data/maps/the_linear/doors.txtpb +++ b/data/maps/the_linear/doors.txtpb
@@ -10,4 +10,5 @@ doors {
10 panels { room: "Room" name: "INTO" } 10 panels { room: "Room" name: "INTO" }
11 panels { room: "Room" name: "NOR" } 11 panels { room: "Room" name: "NOR" }
12 location_room: "Room" 12 location_room: "Room"
13 location_name: "Gravestone"
13} 14}
diff --git a/data/maps/the_repetitive/connections.txtpb b/data/maps/the_repetitive/connections.txtpb index 2b115a9..0afe72d 100644 --- a/data/maps/the_repetitive/connections.txtpb +++ b/data/maps/the_repetitive/connections.txtpb
@@ -6,7 +6,7 @@ connections {
6connections { 6connections {
7 from_room: "Main Room" 7 from_room: "Main Room"
8 to_room: "Plaza Connector" 8 to_room: "Plaza Connector"
9 door { name: "Plaza Entrance" } 9 door { name: "Black Hallway" }
10 oneway: true 10 oneway: true
11} 11}
12connections { 12connections {
diff --git a/data/maps/the_repetitive/doors.txtpb b/data/maps/the_repetitive/doors.txtpb index 9e63c1d..8171dc4 100644 --- a/data/maps/the_repetitive/doors.txtpb +++ b/data/maps/the_repetitive/doors.txtpb
@@ -6,7 +6,7 @@ doors {
6 location_room: "Main Room" 6 location_room: "Main Room"
7} 7}
8doors { 8doors {
9 name: "Plaza Entrance" 9 name: "Black Hallway"
10 type: STANDARD 10 type: STANDARD
11 receivers: "Components/Doors/Door12" 11 receivers: "Components/Doors/Door12"
12 panels { room: "Main Room" name: "I" } 12 panels { room: "Main Room" name: "I" }
diff --git a/data/maps/the_three_doors/doors.txtpb b/data/maps/the_three_doors/doors.txtpb index 99fbcee..5ae9d90 100644 --- a/data/maps/the_three_doors/doors.txtpb +++ b/data/maps/the_three_doors/doors.txtpb
@@ -50,4 +50,5 @@ doors {
50 panels { room: "Dead End Room" name: "DEAD" } 50 panels { room: "Dead End Room" name: "DEAD" }
51 panels { room: "Dead End Room" name: "END" } 51 panels { room: "Dead End Room" name: "END" }
52 location_room: "Loose Strings Room" 52 location_room: "Loose Strings Room"
53 location_name: "Gravestone"
53} 54}
diff --git a/data/maps/the_tree/doors.txtpb b/data/maps/the_tree/doors.txtpb index b62a881..6cb4086 100644 --- a/data/maps/the_tree/doors.txtpb +++ b/data/maps/the_tree/doors.txtpb
@@ -38,4 +38,5 @@ doors {
38 panels { room: "Main Area" name: "SMALL (3)" } 38 panels { room: "Main Area" name: "SMALL (3)" }
39 panels { room: "Main Area" name: "SPRINKLE" } 39 panels { room: "Main Area" name: "SPRINKLE" }
40 location_room: "Main Area" 40 location_room: "Main Area"
41 location_name: "Gravestone"
41} 42}
diff --git a/proto/data.proto b/proto/data.proto index 855c28c..84d14ef 100644 --- a/proto/data.proto +++ b/proto/data.proto
@@ -29,6 +29,20 @@ enum DoorType {
29 GRAVESTONE = 6; 29 GRAVESTONE = 6;
30} 30}
31 31
32enum DoorGroupType {
33 DOOR_GROUP_TYPE_UNKNOWN = 0;
34
35 // These doors border a worldport. They should be grouped when connections are
36 // not shuffled.
37 CONNECTOR = 1;
38
39 // Similar to CONNECTOR, but these doors are also ordinarily opened by solving
40 // the COLOR panel in the Control Center. These should be grouped when
41 // connections are not shuffled, but are not items at all when control center
42 // colors are not shuffled.
43 COLOR_CONNECTOR = 2;
44}
45
32enum AxisDirection { 46enum AxisDirection {
33 AXIS_DIRECTION_UNKNOWN = 0; 47 AXIS_DIRECTION_UNKNOWN = 0;
34 48
@@ -232,6 +246,14 @@ message Progressive {
232 repeated uint64 doors = 4; 246 repeated uint64 doors = 4;
233} 247}
234 248
249message DoorGroup {
250 optional uint64 id = 1;
251 optional string name = 2;
252 optional uint64 ap_id = 3;
253 optional DoorGroupType type = 4;
254 repeated uint64 doors = 5;
255}
256
235message AllObjects { 257message AllObjects {
236 repeated Map maps = 7; 258 repeated Map maps = 7;
237 repeated Room rooms = 1; 259 repeated Room rooms = 1;
@@ -245,5 +267,6 @@ message AllObjects {
245 repeated Ending endings = 12; 267 repeated Ending endings = 12;
246 repeated Connection connections = 6; 268 repeated Connection connections = 6;
247 repeated Progressive progressives = 13; 269 repeated Progressive progressives = 13;
270 repeated DoorGroup door_groups = 14;
248 map<string, uint64> special_ids = 8; 271 map<string, uint64> special_ids = 8;
249} 272}
diff --git a/proto/human.proto b/proto/human.proto index 205b867..89fd076 100644 --- a/proto/human.proto +++ b/proto/human.proto
@@ -203,6 +203,16 @@ message HumanProgressives {
203 repeated HumanProgressive progressives = 1; 203 repeated HumanProgressive progressives = 1;
204} 204}
205 205
206message HumanDoorGroup {
207 optional string name = 1;
208 optional DoorGroupType type = 2;
209 repeated DoorIdentifier doors = 3;
210}
211
212message HumanDoorGroups {
213 repeated HumanDoorGroup door_groups = 1;
214}
215
206message IdMappings { 216message IdMappings {
207 message RoomIds { 217 message RoomIds {
208 map<string, uint64> panels = 1; 218 map<string, uint64> panels = 1;
@@ -220,4 +230,5 @@ message IdMappings {
220 map<string, uint64> letters = 3; 230 map<string, uint64> letters = 3;
221 map<string, uint64> endings = 4; 231 map<string, uint64> endings = 4;
222 map<string, uint64> progressives = 5; 232 map<string, uint64> progressives = 5;
233 map<string, uint64> door_groups = 6;
223} 234}
diff --git a/tools/assign_ids/main.cpp b/tools/assign_ids/main.cpp index e3add66..d212767 100644 --- a/tools/assign_ids/main.cpp +++ b/tools/assign_ids/main.cpp
@@ -42,6 +42,7 @@ class AssignIds {
42 ProcessMaps(datadir_path); 42 ProcessMaps(datadir_path);
43 ProcessSpecialIds(); 43 ProcessSpecialIds();
44 ProcessProgressivesFile(datadir_path / "progressives.txtpb"); 44 ProcessProgressivesFile(datadir_path / "progressives.txtpb");
45 ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb");
45 46
46 WriteIds(ids_path); 47 WriteIds(ids_path);
47 48
@@ -61,6 +62,7 @@ class AssignIds {
61 for (const auto& [_, room] : map.rooms()) { 62 for (const auto& [_, room] : map.rooms()) {
62 UpdateNextId(room.panels()); 63 UpdateNextId(room.panels());
63 UpdateNextId(room.masteries()); 64 UpdateNextId(room.masteries());
65 UpdateNextId(room.keyholders());
64 } 66 }
65 } 67 }
66 68
@@ -68,6 +70,7 @@ class AssignIds {
68 UpdateNextId(id_mappings_.letters()); 70 UpdateNextId(id_mappings_.letters());
69 UpdateNextId(id_mappings_.endings()); 71 UpdateNextId(id_mappings_.endings());
70 UpdateNextId(id_mappings_.progressives()); 72 UpdateNextId(id_mappings_.progressives());
73 UpdateNextId(id_mappings_.door_groups());
71 74
72 next_id_++; 75 next_id_++;
73 } 76 }
@@ -267,6 +270,23 @@ class AssignIds {
267 } 270 }
268 } 271 }
269 272
273 void ProcessDoorGroupsFile(std::filesystem::path path) {
274 if (!std::filesystem::exists(path)) {
275 return;
276 }
277
278 auto h_groups = ReadMessageFromFile<HumanDoorGroups>(path.string());
279 auto& groups = *output_.mutable_door_groups();
280
281 for (const HumanDoorGroup& h_group : h_groups.door_groups()) {
282 if (!id_mappings_.door_groups().contains(h_group.name())) {
283 groups[h_group.name()] = next_id_++;
284 } else {
285 groups[h_group.name()] = id_mappings_.door_groups().at(h_group.name());
286 }
287 }
288 }
289
270 private: 290 private:
271 void UpdateNextId(const google::protobuf::Map<std::string, uint64_t>& ids) { 291 void UpdateNextId(const google::protobuf::Map<std::string, uint64_t>& ids) {
272 for (const auto& [_, id] : ids) { 292 for (const auto& [_, id] : ids) {
diff --git a/tools/datapacker/container.cpp b/tools/datapacker/container.cpp index 624caf8..4a656b3 100644 --- a/tools/datapacker/container.cpp +++ b/tools/datapacker/container.cpp
@@ -348,6 +348,23 @@ uint64_t Container::FindOrAddProgressive(std::string prog_name) {
348 } 348 }
349} 349}
350 350
351uint64_t Container::FindOrAddDoorGroup(std::string group_name) {
352 auto it = door_group_id_by_name_.find(group_name);
353
354 if (it == door_group_id_by_name_.end()) {
355 uint64_t new_id = all_objects_.door_groups_size();
356 DoorGroup* door_group = all_objects_.add_door_groups();
357 door_group->set_id(new_id);
358 door_group->set_name(group_name);
359
360 door_group_id_by_name_[group_name] = new_id;
361
362 return new_id;
363 } else {
364 return it->second;
365 }
366}
367
351void Container::AddConnection(const Connection& connection) { 368void Container::AddConnection(const Connection& connection) {
352 *all_objects_.add_connections() = connection; 369 *all_objects_.add_connections() = connection;
353} 370}
diff --git a/tools/datapacker/container.h b/tools/datapacker/container.h index 8cec560..bc02ba4 100644 --- a/tools/datapacker/container.h +++ b/tools/datapacker/container.h
@@ -62,6 +62,8 @@ class Container {
62 62
63 uint64_t FindOrAddProgressive(std::string prog_name); 63 uint64_t FindOrAddProgressive(std::string prog_name);
64 64
65 uint64_t FindOrAddDoorGroup(std::string group_name);
66
65 AllObjects& all_objects() { return all_objects_; } 67 AllObjects& all_objects() { return all_objects_; }
66 68
67 private: 69 private:
@@ -85,6 +87,7 @@ class Container {
85 door_id_by_map_door_names_; 87 door_id_by_map_door_names_;
86 std::map<std::string, uint64_t> ending_id_by_name_; 88 std::map<std::string, uint64_t> ending_id_by_name_;
87 std::map<std::string, uint64_t> progressive_id_by_name_; 89 std::map<std::string, uint64_t> progressive_id_by_name_;
90 std::map<std::string, uint64_t> door_group_id_by_name_;
88}; 91};
89 92
90} // namespace com::fourisland::lingo2_archipelago 93} // namespace com::fourisland::lingo2_archipelago
diff --git a/tools/datapacker/main.cpp b/tools/datapacker/main.cpp index 595647d..c72462d 100644 --- a/tools/datapacker/main.cpp +++ b/tools/datapacker/main.cpp
@@ -44,6 +44,7 @@ class DataPacker {
44 ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt); 44 ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt);
45 ProcessMaps(datadir_path); 45 ProcessMaps(datadir_path);
46 ProcessProgressivesFile(datadir_path / "progressives.txtpb"); 46 ProcessProgressivesFile(datadir_path / "progressives.txtpb");
47 ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb");
47 ProcessIdsFile(datadir_path / "ids.yaml"); 48 ProcessIdsFile(datadir_path / "ids.yaml");
48 49
49 { 50 {
@@ -577,6 +578,31 @@ class DataPacker {
577 } 578 }
578 } 579 }
579 580
581 void ProcessDoorGroupsFile(std::filesystem::path path) {
582 if (!std::filesystem::exists(path)) {
583 return;
584 }
585
586 auto h_groups = ReadMessageFromFile<HumanDoorGroups>(path.string());
587
588 for (const HumanDoorGroup& h_group : h_groups.door_groups()) {
589 ProcessDoorGroup(h_group);
590 }
591 }
592
593 void ProcessDoorGroup(const HumanDoorGroup& h_group) {
594 uint64_t group_id = container_.FindOrAddDoorGroup(h_group.name());
595 DoorGroup& group = *container_.all_objects().mutable_door_groups(group_id);
596
597 group.set_type(h_group.type());
598
599 for (const DoorIdentifier& di : h_group.doors()) {
600 uint64_t door_id =
601 container_.FindOrAddDoor(di.map(), di.name(), std::nullopt);
602 group.add_doors(door_id);
603 }
604 }
605
580 void ProcessIdsFile(std::filesystem::path path) { 606 void ProcessIdsFile(std::filesystem::path path) {
581 auto ids = ReadIdsFromYaml(path.string()); 607 auto ids = ReadIdsFromYaml(path.string());
582 608
@@ -631,6 +657,11 @@ class DataPacker {
631 uint64_t prog_id = container_.FindOrAddProgressive(prog_name); 657 uint64_t prog_id = container_.FindOrAddProgressive(prog_name);
632 container_.all_objects().mutable_progressives(prog_id)->set_ap_id(ap_id); 658 container_.all_objects().mutable_progressives(prog_id)->set_ap_id(ap_id);
633 } 659 }
660
661 for (const auto& [group_name, ap_id] : ids.door_groups()) {
662 uint64_t group_id = container_.FindOrAddDoorGroup(group_name);
663 container_.all_objects().mutable_door_groups(group_id)->set_ap_id(ap_id);
664 }
634 } 665 }
635 666
636 std::string mapdir_; 667 std::string mapdir_;
diff --git a/tools/util/ids_yaml_format.cpp b/tools/util/ids_yaml_format.cpp index 67c21d6..71bfd63 100644 --- a/tools/util/ids_yaml_format.cpp +++ b/tools/util/ids_yaml_format.cpp
@@ -104,6 +104,13 @@ IdMappings ReadIdsFromYaml(const std::string& filename) {
104 } 104 }
105 } 105 }
106 106
107 if (document["door_groups"]) {
108 for (const auto& group_it : document["door_groups"]) {
109 (*result.mutable_door_groups())[group_it.first.as<std::string>()] =
110 group_it.second.as<uint64_t>();
111 }
112 }
113
107 return result; 114 return result;
108} 115}
109 116
@@ -171,6 +178,11 @@ void WriteIdsAsYaml(const IdMappings& ids, const std::string& filename) {
171 result["progressives"][prog_name] = prog_id; 178 result["progressives"][prog_name] = prog_id;
172 }); 179 });
173 180
181 OperateOnSortedMap(ids.door_groups(), [&result](const std::string& group_name,
182 uint64_t group_id) {
183 result["door_groups"][group_name] = group_id;
184 });
185
174 std::ofstream output_stream(filename); 186 std::ofstream output_stream(filename);
175 output_stream << result << std::endl; 187 output_stream << result << std::endl;
176} 188}
diff --git a/tools/validator/human_processor.cpp b/tools/validator/human_processor.cpp index 5720ba9..561225e 100644 --- a/tools/validator/human_processor.cpp +++ b/tools/validator/human_processor.cpp
@@ -43,6 +43,7 @@ class HumanProcessor {
43 ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt); 43 ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt);
44 ProcessMaps(datadir_path); 44 ProcessMaps(datadir_path);
45 ProcessProgressivesFile(datadir_path / "progressives.txtpb"); 45 ProcessProgressivesFile(datadir_path / "progressives.txtpb");
46 ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb");
46 ProcessIdsFile(datadir_path / "ids.yaml"); 47 ProcessIdsFile(datadir_path / "ids.yaml");
47 } 48 }
48 49
@@ -510,6 +511,33 @@ class HumanProcessor {
510 } 511 }
511 } 512 }
512 513
514 void ProcessDoorGroupsFile(std::filesystem::path path) {
515 if (!std::filesystem::exists(path)) {
516 return;
517 }
518
519 auto h_groups = ReadMessageFromFile<HumanDoorGroups>(path.string());
520
521 for (const HumanDoorGroup& h_group : h_groups.door_groups()) {
522 ProcessDoorGroup(h_group);
523 }
524 }
525
526 void ProcessDoorGroup(const HumanDoorGroup& h_group) {
527 DoorGroupInfo& group_info = info_.door_groups[h_group.name()];
528 group_info.definitions.push_back(h_group);
529
530 for (const DoorIdentifier& di : h_group.doors()) {
531 if (!di.has_map()) {
532 group_info.malformed_doors.push_back(di);
533 continue;
534 }
535
536 DoorInfo& door_info = info_.doors[di];
537 door_info.door_groups_referenced_by.push_back(h_group.name());
538 }
539 }
540
513 void ProcessIdsFile(std::filesystem::path path) { 541 void ProcessIdsFile(std::filesystem::path path) {
514 auto ids = ReadIdsFromYaml(path.string()); 542 auto ids = ReadIdsFromYaml(path.string());
515 543
@@ -573,6 +601,11 @@ class HumanProcessor {
573 ProgressiveInfo& prog_info = info_.progressives[prog_name]; 601 ProgressiveInfo& prog_info = info_.progressives[prog_name];
574 prog_info.has_id = true; 602 prog_info.has_id = true;
575 } 603 }
604
605 for (const auto& [group_name, ap_id] : ids.door_groups()) {
606 DoorGroupInfo& group_info = info_.door_groups[group_name];
607 group_info.has_id = true;
608 }
576 } 609 }
577 610
578 std::string mapdir_; 611 std::string mapdir_;
diff --git a/tools/validator/structs.h b/tools/validator/structs.h index e24ed3d..17ed33a 100644 --- a/tools/validator/structs.h +++ b/tools/validator/structs.h
@@ -47,6 +47,7 @@ struct DoorInfo {
47 std::vector<PaintingIdentifier> paintings_referenced_by; 47 std::vector<PaintingIdentifier> paintings_referenced_by;
48 std::vector<PortIdentifier> ports_referenced_by; 48 std::vector<PortIdentifier> ports_referenced_by;
49 std::vector<std::string> progressives_referenced_by; 49 std::vector<std::string> progressives_referenced_by;
50 std::vector<std::string> door_groups_referenced_by;
50 51
51 MalformedIdentifiers malformed_identifiers; 52 MalformedIdentifiers malformed_identifiers;
52}; 53};
@@ -115,6 +116,13 @@ struct ProgressiveInfo {
115 std::vector<DoorIdentifier> malformed_doors; 116 std::vector<DoorIdentifier> malformed_doors;
116}; 117};
117 118
119struct DoorGroupInfo {
120 std::vector<HumanDoorGroup> definitions;
121 bool has_id = false;
122
123 std::vector<DoorIdentifier> malformed_doors;
124};
125
118struct CollectedInfo { 126struct CollectedInfo {
119 std::map<std::string, MapInfo> maps; 127 std::map<std::string, MapInfo> maps;
120 std::map<RoomIdentifier, RoomInfo, RoomIdentifierLess> rooms; 128 std::map<RoomIdentifier, RoomInfo, RoomIdentifierLess> rooms;
@@ -128,6 +136,7 @@ struct CollectedInfo {
128 std::map<std::string, EndingInfo> endings; 136 std::map<std::string, EndingInfo> endings;
129 std::map<std::string, PanelNameInfo> panel_names; 137 std::map<std::string, PanelNameInfo> panel_names;
130 std::map<std::string, ProgressiveInfo> progressives; 138 std::map<std::string, ProgressiveInfo> progressives;
139 std::map<std::string, DoorGroupInfo> door_groups;
131}; 140};
132 141
133} // namespace com::fourisland::lingo2_archipelago 142} // namespace com::fourisland::lingo2_archipelago
diff --git a/tools/validator/validator.cpp b/tools/validator/validator.cpp index ab1612e..4149caa 100644 --- a/tools/validator/validator.cpp +++ b/tools/validator/validator.cpp
@@ -48,6 +48,9 @@ class Validator {
48 for (const auto& [prog_name, prog_info] : info_.progressives) { 48 for (const auto& [prog_name, prog_info] : info_.progressives) {
49 ValidateProgressive(prog_name, prog_info); 49 ValidateProgressive(prog_name, prog_info);
50 } 50 }
51 for (const auto& [group_name, group_info] : info_.door_groups) {
52 ValidateDoorGroup(group_name, group_info);
53 }
51 } 54 }
52 55
53 private: 56 private:
@@ -173,6 +176,11 @@ class Validator {
173 std::cout << " PROGRESSIVE " << prog_name << std::endl; 176 std::cout << " PROGRESSIVE " << prog_name << std::endl;
174 } 177 }
175 178
179 for (const std::string& group_name :
180 door_info.door_groups_referenced_by) {
181 std::cout << " DOOR GROUP " << group_name << std::endl;
182 }
183
176 if (door_info.has_id) { 184 if (door_info.has_id) {
177 std::cout << " An AP ID is present." << std::endl; 185 std::cout << " An AP ID is present." << std::endl;
178 } 186 }
@@ -460,6 +468,26 @@ class Validator {
460 } 468 }
461 } 469 }
462 470
471 void ValidateDoorGroup(const std::string& group_name,
472 const DoorGroupInfo& group_info) const {
473 if (group_info.definitions.empty()) {
474 std::cout << "Door group \"" << group_name
475 << "\" has no definition, but was referenced:" << std::endl;
476
477 if (group_info.has_id) {
478 std::cout << " An AP ID is present." << std::endl;
479 }
480 } else if (group_info.definitions.size() > 1) {
481 std::cout << "Door group \"" << group_name
482 << "\" has multiple definitions." << std::endl;
483 }
484
485 if (!group_info.has_id) {
486 std::cout << "Door group \"" << group_name << "\" is missing an AP ID."
487 << std::endl;
488 }
489 }
490
463 const CollectedInfo& info_; 491 const CollectedInfo& info_;
464}; 492};
465 493