about summary refs log tree commit diff stats
path: root/client/Archipelago
diff options
context:
space:
mode:
Diffstat (limited to 'client/Archipelago')
-rw-r--r--client/Archipelago/client.gd6
-rw-r--r--client/Archipelago/gamedata.gd54
-rw-r--r--client/Archipelago/keyboard.gd35
-rw-r--r--client/Archipelago/manager.gd121
-rw-r--r--client/Archipelago/messages.gd18
-rw-r--r--client/Archipelago/panel.gd101
-rw-r--r--client/Archipelago/pauseMenu.gd7
-rw-r--r--client/Archipelago/player.gd41
-rw-r--r--client/Archipelago/saver.gd14
-rw-r--r--client/Archipelago/settings_screen.gd49
-rw-r--r--client/Archipelago/teleport.gd38
-rw-r--r--client/Archipelago/teleportListener.gd11
-rw-r--r--client/Archipelago/textclient.gd2
-rw-r--r--client/Archipelago/visibilityListener.gd38
-rw-r--r--client/Archipelago/worldport.gd10
-rw-r--r--client/Archipelago/worldportListener.gd4
16 files changed, 513 insertions, 36 deletions
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/gamedata.gd b/client/Archipelago/gamedata.gd index f7a5d90..41d966a 100644 --- a/client/Archipelago/gamedata.gd +++ b/client/Archipelago/gamedata.gd
@@ -5,15 +5,42 @@ var SCRIPT_proto
5var objects 5var objects
6var door_id_by_map_node_path = {} 6var door_id_by_map_node_path = {}
7var painting_id_by_map_node_path = {} 7var painting_id_by_map_node_path = {}
8var panel_id_by_map_node_path = {}
8var door_id_by_ap_id = {} 9var door_id_by_ap_id = {}
9var map_id_by_name = {} 10var map_id_by_name = {}
10var progressive_id_by_ap_id = {} 11var progressive_id_by_ap_id = {}
11var letter_id_by_ap_id = {} 12var letter_id_by_ap_id = {}
13var symbol_item_ids = []
14var anti_trap_ids = {}
15
16var kSYMBOL_ITEMS
12 17
13 18
14func _init(proto_script): 19func _init(proto_script):
15 SCRIPT_proto = proto_script 20 SCRIPT_proto = proto_script
16 21
22 kSYMBOL_ITEMS = {
23 SCRIPT_proto.PuzzleSymbol.SUN: "Sun Symbol",
24 SCRIPT_proto.PuzzleSymbol.SPARKLES: "Sparkles Symbol",
25 SCRIPT_proto.PuzzleSymbol.ZERO: "Zero Symbol",
26 SCRIPT_proto.PuzzleSymbol.EXAMPLE: "Example Symbol",
27 SCRIPT_proto.PuzzleSymbol.BOXES: "Boxes Symbol",
28 SCRIPT_proto.PuzzleSymbol.PLANET: "Planet Symbol",
29 SCRIPT_proto.PuzzleSymbol.PYRAMID: "Pyramid Symbol",
30 SCRIPT_proto.PuzzleSymbol.CROSS: "Cross Symbol",
31 SCRIPT_proto.PuzzleSymbol.SWEET: "Sweet Symbol",
32 SCRIPT_proto.PuzzleSymbol.GENDER: "Gender Symbol",
33 SCRIPT_proto.PuzzleSymbol.AGE: "Age Symbol",
34 SCRIPT_proto.PuzzleSymbol.SOUND: "Sound Symbol",
35 SCRIPT_proto.PuzzleSymbol.ANAGRAM: "Anagram Symbol",
36 SCRIPT_proto.PuzzleSymbol.JOB: "Job Symbol",
37 SCRIPT_proto.PuzzleSymbol.STARS: "Stars Symbol",
38 SCRIPT_proto.PuzzleSymbol.NULL: "Null Symbol",
39 SCRIPT_proto.PuzzleSymbol.EVAL: "Eval Symbol",
40 SCRIPT_proto.PuzzleSymbol.LINGO: "Lingo Symbol",
41 SCRIPT_proto.PuzzleSymbol.QUESTION: "Question Symbol",
42 }
43
17 44
18func load(data_bytes): 45func load(data_bytes):
19 objects = SCRIPT_proto.AllObjects.new() 46 objects = SCRIPT_proto.AllObjects.new()
@@ -58,6 +85,25 @@ func load(data_bytes):
58 for letter in objects.get_letters(): 85 for letter in objects.get_letters():
59 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id() 86 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id()
60 87
88 for panel in objects.get_panels():
89 var room = objects.get_rooms()[panel.get_room_id()]
90 var map = objects.get_maps()[room.get_map_id()]
91
92 if not map.get_name() in panel_id_by_map_node_path:
93 panel_id_by_map_node_path[map.get_name()] = {}
94
95 var map_data = panel_id_by_map_node_path[map.get_name()]
96 map_data[panel.get_path()] = panel.get_id()
97
98 for symbol_name in kSYMBOL_ITEMS.values():
99 symbol_item_ids.append(objects.get_special_ids()[symbol_name])
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
61 107
62func get_door_for_map_node_path(map_name, node_path): 108func get_door_for_map_node_path(map_name, node_path):
63 if not door_id_by_map_node_path.has(map_name): 109 if not door_id_by_map_node_path.has(map_name):
@@ -67,6 +113,14 @@ func get_door_for_map_node_path(map_name, node_path):
67 return map_data.get(node_path, null) 113 return map_data.get(node_path, null)
68 114
69 115
116func get_panel_for_map_node_path(map_name, node_path):
117 if not panel_id_by_map_node_path.has(map_name):
118 return null
119
120 var map_data = panel_id_by_map_node_path[map_name]
121 return map_data.get(node_path, null)
122
123
70func get_door_ap_id(door_id): 124func get_door_ap_id(door_id):
71 var door = objects.get_doors()[door_id] 125 var door = objects.get_doors()[door_id]
72 if door.has_ap_id(): 126 if door.has_ap_id():
diff --git a/client/Archipelago/keyboard.gd b/client/Archipelago/keyboard.gd index e43ec9f..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
@@ -79,17 +81,28 @@ func save():
79func update_unlocks(): 81func update_unlocks():
80 unlocks.resetKeys() 82 unlocks.resetKeys()
81 83
84 var has_doubles = false
85
82 for k in kALL_LETTERS: 86 for k in kALL_LETTERS:
83 var level = 0 87 var level = 0
84 88
85 if not letters_in_keyholders.has(k): 89 if not letters_in_keyholders.has(k):
86 level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0) 90 level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0)
87 91
88 if level > 2: 92 if level >= 2:
89 level = 2 93 level = 2
94 has_doubles = true
95
96 if letters_blocked.has(k):
97 level = 0
90 98
91 unlocks.unlockKey(k, level) 99 unlocks.unlockKey(k, level)
92 100
101 if has_doubles and unlocks.data["double_letters"] != "unlocked":
102 var ap = global.get_node("Archipelago")
103 if ap.cyan_door_behavior == ap.kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER:
104 unlocks.setData("double_letters", "unlocked")
105
93 106
94func collect_local_letter(key, level): 107func collect_local_letter(key, level):
95 if level < 0 or level > 2 or level < letters_saved.get(key, 0): 108 if level < 0 or level > 2 or level < letters_saved.get(key, 0):
@@ -97,6 +110,9 @@ func collect_local_letter(key, level):
97 110
98 letters_saved[key] = level 111 letters_saved[key] = level
99 112
113 if letters_blocked.has(key):
114 letters_blocked.erase(key)
115
100 update_unlocks() 116 update_unlocks()
101 save() 117 save()
102 118
@@ -107,6 +123,9 @@ func collect_remote_letter(key, level):
107 123
108 letters_dynamic[key] = level 124 letters_dynamic[key] = level
109 125
126 if letters_blocked.has(key):
127 letters_blocked.erase(key)
128
110 update_unlocks() 129 update_unlocks()
111 save() 130 save()
112 131
@@ -140,6 +159,13 @@ func remove_from_keyholder(key, map, kh_path):
140 save() 159 save()
141 160
142 161
162func block_letter(key):
163 if not letters_blocked.has(key):
164 letters_blocked.append(key)
165
166 update_unlocks()
167
168
143func load_keyholders(map): 169func load_keyholders(map):
144 if keyholder_state.has(map): 170 if keyholder_state.has(map):
145 var khs = keyholder_state[map] 171 var khs = keyholder_state[map]
@@ -152,9 +178,11 @@ func load_keyholders(map):
152 178
153 179
154func reset_keyholders(): 180func reset_keyholders():
155 if letters_in_keyholders.is_empty(): 181 if letters_in_keyholders.is_empty() and letters_blocked.is_empty():
156 return false 182 return false
157 183
184 var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty()
185
158 if keyholder_state.has(global.map): 186 if keyholder_state.has(global.map):
159 for path in keyholder_state[global.map]: 187 for path in keyholder_state[global.map]:
160 get_tree().get_root().get_node("scene").get_node(path).setFromAp( 188 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
@@ -163,8 +191,9 @@ func reset_keyholders():
163 191
164 keyholder_state.clear() 192 keyholder_state.clear()
165 letters_in_keyholders.clear() 193 letters_in_keyholders.clear()
194 letters_blocked.clear()
166 195
167 update_unlocks() 196 update_unlocks()
168 save() 197 save()
169 198
170 return true 199 return cleared_anything
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index bcb21e7..06ae7d9 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -1,6 +1,6 @@
1extends Node 1extends Node
2 2
3const my_version = "0.1.0" 3const MOD_VERSION = 6
4 4
5var SCRIPT_client 5var SCRIPT_client
6var SCRIPT_keyboard 6var SCRIPT_keyboard
@@ -23,6 +23,7 @@ var _held_locations = []
23var _held_location_scouts = [] 23var _held_location_scouts = []
24var _location_scouts = {} 24var _location_scouts = {}
25var _item_locks = {} 25var _item_locks = {}
26var _inverse_item_locks = {}
26var _held_letters = {} 27var _held_letters = {}
27var _letters_setup = false 28var _letters_setup = false
28 29
@@ -36,10 +37,19 @@ const kLETTER_BEHAVIOR_VANILLA = 0
36const kLETTER_BEHAVIOR_ITEM = 1 37const kLETTER_BEHAVIOR_ITEM = 1
37const kLETTER_BEHAVIOR_UNLOCKED = 2 38const kLETTER_BEHAVIOR_UNLOCKED = 2
38 39
40const kCYAN_DOOR_BEHAVIOR_H2 = 0
41const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1
42const kCYAN_DOOR_BEHAVIOR_ITEM = 2
43
44var apworld_version = [0, 0]
45var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
39var daedalus_roof_access = false 46var daedalus_roof_access = false
40var keyholder_sanity = false 47var keyholder_sanity = false
48var shuffle_control_center_colors = false
41var shuffle_doors = false 49var shuffle_doors = false
50var shuffle_gallery_paintings = false
42var shuffle_letters = kSHUFFLE_LETTERS_VANILLA 51var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
52var shuffle_symbols = false
43var victory_condition = -1 53var victory_condition = -1
44 54
45signal could_not_connect 55signal could_not_connect
@@ -121,6 +131,10 @@ func saveLocaldata():
121 131
122func connectToServer(): 132func connectToServer():
123 _last_new_item = -1 133 _last_new_item = -1
134 _batch_locations = false
135 _held_locations = []
136 _held_location_scouts = []
137 _location_scouts = {}
124 _letters_setup = false 138 _letters_setup = false
125 _held_letters = {} 139 _held_letters = {}
126 140
@@ -145,24 +159,26 @@ func _process_item(item, index, from, flags, amount):
145 item_name = client._item_id_to_name["Lingo 2"][item] 159 item_name = client._item_id_to_name["Lingo 2"][item]
146 160
147 var gamedata = global.get_node("Gamedata") 161 var gamedata = global.get_node("Gamedata")
148 var door_id = gamedata.door_id_by_ap_id.get(item, null) 162
149 var prog_id = null 163 var prog_id = null
164 if _inverse_item_locks.has(item):
165 for lock in _inverse_item_locks.get(item):
166 if lock[1] != amount:
167 continue
150 168
151 if door_id == null: 169 if gamedata.progressive_id_by_ap_id.has(item):
152 prog_id = gamedata.progressive_id_by_ap_id.get(item, null) 170 prog_id = lock[0]
153 if prog_id != null: 171
154 var progressive = gamedata.objects.get_progressives()[prog_id] 172 if gamedata.get_door_map_name(lock[0]) != global.map:
155 if progressive.get_doors().size() >= amount: 173 continue
156 door_id = progressive.get_doors()[amount - 1] 174
157 175 var receivers = gamedata.get_door_receivers(lock[0])
158 if door_id != null and gamedata.get_door_map_name(door_id) == global.map: 176 var scene = get_tree().get_root().get_node_or_null("scene")
159 var receivers = gamedata.get_door_receivers(door_id) 177 if scene != null:
160 var scene = get_tree().get_root().get_node_or_null("scene") 178 for receiver in receivers:
161 if scene != null: 179 var rnode = scene.get_node_or_null(receiver)
162 for receiver in receivers: 180 if rnode != null:
163 var rnode = scene.get_node_or_null(receiver) 181 rnode.handleTriggered()
164 if rnode != null:
165 rnode.handleTriggered()
166 182
167 var letter_id = gamedata.letter_id_by_ap_id.get(item, null) 183 var letter_id = gamedata.letter_id_by_ap_id.get(item, null)
168 if letter_id != null: 184 if letter_id != null:
@@ -170,6 +186,11 @@ func _process_item(item, index, from, flags, amount):
170 if not letter.has_level2() or not letter.get_level2(): 186 if not letter.has_level2() or not letter.get_level2():
171 _process_key_item(letter.get_key(), amount) 187 _process_key_item(letter.get_key(), amount)
172 188
189 if gamedata.symbol_item_ids.has(item):
190 var player = get_tree().get_root().get_node_or_null("scene/player")
191 if player != null:
192 player.emit_signal("evaluate_solvability")
193
173 # Show a message about the item if it's new. 194 # Show a message about the item if it's new.
174 if index != null and index > _last_new_item: 195 if index != null and index > _last_new_item:
175 _last_new_item = index 196 _last_new_item = index
@@ -182,8 +203,8 @@ func _process_item(item, index, from, flags, amount):
182 var item_color = colorForItemType(flags) 203 var item_color = colorForItemType(flags)
183 204
184 var full_item_name = item_name 205 var full_item_name = item_name
185 if prog_id != null and door_id != null: 206 if prog_id != null:
186 var door = gamedata.objects.get_doors()[door_id] 207 var door = gamedata.objects.get_doors()[prog_id]
187 full_item_name = "%s (%s)" % [item_name, door.get_name()] 208 full_item_name = "%s (%s)" % [item_name, door.get_name()]
188 209
189 var message 210 var message
@@ -194,6 +215,9 @@ func _process_item(item, index, from, flags, amount):
194 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name] 215 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name]
195 ) 216 )
196 217
218 if gamedata.anti_trap_ids.has(item):
219 keyboard.block_letter(gamedata.anti_trap_ids[item])
220
197 global._print(message) 221 global._print(message)
198 222
199 global.get_node("Messages").showMessage(message) 223 global.get_node("Messages").showMessage(message)
@@ -288,6 +312,10 @@ func parse_printjson_for_textclient(message):
288func _process_location_scout(item_id, location_id, player, flags): 312func _process_location_scout(item_id, location_id, player, flags):
289 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags} 313 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
290 314
315 if player == client._slot and flags & 4 != 0:
316 # This is a trap for us, so let's not display it.
317 return
318
291 var gamedata = global.get_node("Gamedata") 319 var gamedata = global.get_node("Gamedata")
292 var map_id = gamedata.map_id_by_name.get(global.map) 320 var map_id = gamedata.map_id_by_name.get(global.map)
293 321
@@ -308,8 +336,8 @@ func _process_location_scout(item_id, location_id, player, flags):
308 collectable.setScoutedText(item_name) 336 collectable.setScoutedText(item_name)
309 337
310 338
311func _client_could_not_connect(): 339func _client_could_not_connect(message):
312 emit_signal("could_not_connect") 340 emit_signal("could_not_connect", message)
313 341
314 342
315func _client_connect_status(message): 343func _client_connect_status(message):
@@ -337,12 +365,19 @@ func _client_connected(slot_data):
337 _last_new_item = localdata[0] 365 _last_new_item = localdata[0]
338 366
339 # Read slot data. 367 # Read slot data.
368 cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0))
340 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) 369 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
341 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) 370 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
371 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
342 shuffle_doors = bool(slot_data.get("shuffle_doors", false)) 372 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
373 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
343 shuffle_letters = int(slot_data.get("shuffle_letters", 0)) 374 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
375 shuffle_symbols = bool(slot_data.get("shuffle_symbols", false))
344 victory_condition = int(slot_data.get("victory_condition", 0)) 376 victory_condition = int(slot_data.get("victory_condition", 0))
345 377
378 if slot_data.has("version"):
379 apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])]
380
346 # Set up item locks. 381 # Set up item locks.
347 _item_locks = {} 382 _item_locks = {}
348 383
@@ -359,6 +394,47 @@ func _client_connected(slot_data):
359 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]] 394 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]]
360 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1] 395 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1]
361 396
397 for door_group in gamedata.objects.get_door_groups():
398 if (
399 door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR
400 or door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP
401 ):
402 for door in door_group.get_doors():
403 _item_locks[door] = [door_group.get_ap_id(), 1]
404
405 if shuffle_control_center_colors:
406 for door in gamedata.objects.get_doors():
407 if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR:
408 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
409
410 for door_group in gamedata.objects.get_door_groups():
411 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR:
412 for door in door_group.get_doors():
413 _item_locks[door] = [door_group.get_ap_id(), 1]
414
415 if shuffle_gallery_paintings:
416 for door in gamedata.objects.get_doors():
417 if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING:
418 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
419
420 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM:
421 for door_group in gamedata.objects.get_door_groups():
422 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS:
423 for door in door_group.get_doors():
424 if not _item_locks.has(door):
425 _item_locks[door] = [door_group.get_ap_id(), 1]
426
427 # Create a reverse item locks map for processing items.
428 _inverse_item_locks = {}
429
430 for door_id in _item_locks.keys():
431 var lock = _item_locks.get(door_id)
432
433 if not _inverse_item_locks.has(lock[0]):
434 _inverse_item_locks[lock[0]] = []
435
436 _inverse_item_locks[lock[0]].append([door_id, lock[1]])
437
362 emit_signal("ap_connected") 438 emit_signal("ap_connected")
363 439
364 440
@@ -453,4 +529,7 @@ func _process_key_item(key, level):
453 _held_letters[key] = max(_held_letters.get(key, 0), level) 529 _held_letters[key] = max(_held_letters.get(key, 0), level)
454 return 530 return
455 531
532 if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN:
533 level += 1
534
456 keyboard.collect_remote_letter(key, level) 535 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/panel.gd b/client/Archipelago/panel.gd new file mode 100644 index 0000000..fdaaf0e --- /dev/null +++ b/client/Archipelago/panel.gd
@@ -0,0 +1,101 @@
1extends "res://scripts/nodes/panel.gd"
2
3var panel_logic = null
4var symbol_solvable = true
5
6var black = load("res://assets/materials/black.material")
7
8
9func _ready():
10 super._ready()
11
12 var node_path = String(
13 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
14 )
15
16 var gamedata = global.get_node("Gamedata")
17 var panel_id = gamedata.get_panel_for_map_node_path(global.map, node_path)
18 if panel_id != null:
19 var ap = global.get_node("Archipelago")
20 if ap.shuffle_symbols:
21 if global.map == "the_entry" and node_path == "Panels/Entry/front_1":
22 clue = "i"
23 symbol = ""
24
25 setField("clue", clue)
26 setField("symbol", symbol)
27
28 panel_logic = gamedata.objects.get_panels()[panel_id]
29 checkSymbolSolvable()
30
31 if not symbol_solvable:
32 get_tree().get_root().get_node("scene/player").connect(
33 "evaluate_solvability", evaluateSolvability
34 )
35
36
37func checkSymbolSolvable():
38 var old_solvable = symbol_solvable
39 symbol_solvable = true
40
41 if panel_logic == null:
42 # There's no logic for this panel.
43 return
44
45 var ap = global.get_node("Archipelago")
46 if not ap.shuffle_symbols:
47 # Symbols aren't item-locked.
48 return
49
50 var gamedata = global.get_node("Gamedata")
51 for symbol in panel_logic.get_symbols():
52 var item_name = gamedata.kSYMBOL_ITEMS.get(symbol)
53 var item_id = gamedata.objects.get_special_ids()[item_name]
54 if ap.client.getItemAmount(item_id) < 1:
55 symbol_solvable = false
56 break
57
58 if symbol_solvable != old_solvable:
59 if symbol_solvable:
60 setField("clue", clue)
61 setField("symbol", symbol)
62 setField("answer", answer)
63 else:
64 quad_mesh.surface_set_material(0, black)
65 get_node("Hinge/clue").text = "missing"
66 get_node("Hinge/answer").text = "symbols"
67
68
69func checkSolvable(key):
70 checkSymbolSolvable()
71 if not symbol_solvable:
72 return false
73
74 return super.checkSolvable(key)
75
76
77func evaluateSolvability():
78 checkSolvable("")
79
80
81func passedInput(key, skip_focus_check = false):
82 if not symbol_solvable:
83 return
84
85 super.passedInput(key, skip_focus_check)
86
87
88func focus():
89 if not symbol_solvable:
90 has_focus = false
91 return
92
93 super.focus()
94
95
96func unfocus():
97 if not symbol_solvable:
98 has_focus = false
99 return
100
101 super.unfocus()
diff --git a/client/Archipelago/pauseMenu.gd b/client/Archipelago/pauseMenu.gd index 6c013a5..df4bfd1 100644 --- a/client/Archipelago/pauseMenu.gd +++ b/client/Archipelago/pauseMenu.gd
@@ -4,3 +4,10 @@ extends "res://scripts/ui/pauseMenu.gd"
4func _pause_game(): 4func _pause_game():
5 global.get_node("Textclient").dismiss() 5 global.get_node("Textclient").dismiss()
6 super._pause_game() 6 super._pause_game()
7
8
9func _main_menu():
10 global.loaded = false
11 global.get_node("Archipelago").disconnect_from_ap()
12 global.get_node("Messages").clear()
13 super._main_menu()
diff --git a/client/Archipelago/player.gd b/client/Archipelago/player.gd index dd6aa2b..f0b214f 100644 --- a/client/Archipelago/player.gd +++ b/client/Archipelago/player.gd
@@ -16,6 +16,8 @@ const kEndingNameByVictoryValue = {
16 12: "WHITE", 16 12: "WHITE",
17} 17}
18 18
19signal evaluate_solvability
20
19 21
20func _ready(): 22func _ready():
21 var khl_script = load("res://scripts/nodes/keyHolderListener.gd") 23 var khl_script = load("res://scripts/nodes/keyHolderListener.gd")
@@ -34,7 +36,10 @@ func _ready():
34 if not door.has_ap_id(): 36 if not door.has_ap_id():
35 continue 37 continue
36 38
37 if door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY: 39 if (
40 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
41 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
42 ):
38 continue 43 continue
39 44
40 var locationListener = ap.SCRIPT_locationListener.new() 45 var locationListener = ap.SCRIPT_locationListener.new()
@@ -66,6 +71,12 @@ func _ready():
66 71
67 locationListener.senders.append(NodePath("../" + khl.name)) 72 locationListener.senders.append(NodePath("../" + khl.name))
68 73
74 for sender in door.get_senders():
75 locationListener.senders.append(NodePath("/root/scene/" + sender))
76
77 if door.has_complete_at():
78 locationListener.complete_at = door.get_complete_at()
79
69 get_parent().add_child.call_deferred(locationListener) 80 get_parent().add_child.call_deferred(locationListener)
70 81
71 # Set up letter locations. 82 # Set up letter locations.
@@ -86,7 +97,10 @@ func _ready():
86 != ap.kLETTER_BEHAVIOR_VANILLA 97 != ap.kLETTER_BEHAVIOR_VANILLA
87 ): 98 ):
88 var scout = ap.scout_location(letter.get_ap_id()) 99 var scout = ap.scout_location(letter.get_ap_id())
89 if scout != null: 100 if (
101 scout != null
102 and not (scout["player"] == ap.client._slot and scout["flags"] & 4 != 0)
103 ):
90 var item_name = "Unknown" 104 var item_name = "Unknown"
91 var item_player_game = ap.client._game_by_player[float(scout["player"])] 105 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"]): 106 if ap.client._item_id_to_name[item_player_game].has(scout["item"]):
@@ -188,6 +202,29 @@ func _ready():
188 warp_enter.rotation_degrees.y = 90 202 warp_enter.rotation_degrees.y = 90
189 get_parent().add_child.call_deferred(warp_enter) 203 get_parent().add_child.call_deferred(warp_enter)
190 204
205 if global.map == "the_entry":
206 # Remove door behind X1.
207 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
208 door_node.handleTriggered()
209
210 # Display win condition.
211 var sign_prefab = preload("res://objects/nodes/sign.tscn")
212 var sign1 = sign_prefab.instantiate()
213 sign1.position = Vector3(-7, 5, -15.01)
214 sign1.text = "victory"
215 get_parent().add_child.call_deferred(sign1)
216
217 var sign2 = sign_prefab.instantiate()
218 sign2.position = Vector3(-7, 4, -15.01)
219 sign2.text = "%s ending" % kEndingNameByVictoryValue.get(ap.victory_condition, "?")
220
221 var sign2_color = kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
222 if sign2_color == "white":
223 sign2_color = "silver"
224
225 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
226 get_parent().add_child.call_deferred(sign2)
227
191 super._ready() 228 super._ready()
192 229
193 await get_tree().process_frame 230 await get_tree().process_frame
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 aaaf72a..140b4f4 100644 --- a/client/Archipelago/settings_screen.gd +++ b/client/Archipelago/settings_screen.gd
@@ -40,10 +40,14 @@ func _ready():
40 ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd") 40 ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd")
41 ) 41 )
42 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd")) 42 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd"))
43 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/panel.gd"))
43 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd")) 44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd"))
44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd")) 45 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd"))
45 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"))
46 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd")) 48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd"))
49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd"))
50 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldport.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd")) 51 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd"))
48 52
49 var proto_script = load("user://maps/Archipelago/generated/proto.gd") 53 var proto_script = load("user://maps/Archipelago/generated/proto.gd")
@@ -66,6 +70,7 @@ func _ready():
66 global.add_child(textclient_instance) 70 global.add_child(textclient_instance)
67 71
68 var ap = global.get_node("Archipelago") 72 var ap = global.get_node("Archipelago")
73 var gamedata = global.get_node("Gamedata")
69 ap.connect("ap_connected", connectionSuccessful) 74 ap.connect("ap_connected", connectionSuccessful)
70 ap.connect("could_not_connect", connectionUnsuccessful) 75 ap.connect("could_not_connect", connectionUnsuccessful)
71 ap.connect("connect_status", connectionStatus) 76 ap.connect("connect_status", connectionStatus)
@@ -89,13 +94,17 @@ func _ready():
89 history_box.get_popup().connect("id_pressed", historySelected) 94 history_box.get_popup().connect("id_pressed", historySelected)
90 95
91 # Show client version. 96 # Show client version.
92 $Panel/title.text = "ARCHIPELAGO (%s)" % ap.my_version 97 $Panel/title.text = "ARCHIPELAGO (%d.%d)" % [gamedata.objects.get_version(), ap.MOD_VERSION]
93 98
94 # Increase font size in text boxes. 99 # Increase font size in text boxes.
95 $Panel/server_box.add_theme_font_size_override("font_size", 36) 100 $Panel/server_box.add_theme_font_size_override("font_size", 36)
96 $Panel/player_box.add_theme_font_size_override("font_size", 36) 101 $Panel/player_box.add_theme_font_size_override("font_size", 36)
97 $Panel/password_box.add_theme_font_size_override("font_size", 36) 102 $Panel/password_box.add_theme_font_size_override("font_size", 36)
98 103
104 # Set up version mismatch dialog.
105 $Panel/VersionMismatch.connect("confirmed", startGame)
106 $Panel/VersionMismatch.get_cancel_button().pressed.connect(versionMismatchDeclined)
107
99 108
100# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd 109# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd
101func installScriptExtension(childScript: Resource): 110func installScriptExtension(childScript: Resource):
@@ -125,6 +134,33 @@ func connectionStatus(message):
125 134
126func connectionSuccessful(): 135func connectionSuccessful():
127 var ap = global.get_node("Archipelago") 136 var ap = global.get_node("Archipelago")
137 var gamedata = global.get_node("Gamedata")
138
139 # Check for major version mismatch.
140 if ap.apworld_version[0] != gamedata.objects.get_version():
141 $Panel/AcceptDialog.exclusive = false
142
143 var popup = self.get_node("Panel/VersionMismatch")
144 popup.title = "Version Mismatch!"
145 popup.dialog_text = (
146 "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."
147 % [
148 ap.apworld_version[0],
149 ap.apworld_version[1],
150 gamedata.objects.get_version(),
151 ap.MOD_VERSION
152 ]
153 )
154 popup.exclusive = true
155 popup.popup_centered()
156
157 return
158
159 startGame()
160
161
162func startGame():
163 var ap = global.get_node("Archipelago")
128 164
129 # Save connection details 165 # Save connection details
130 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass] 166 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass]
@@ -158,9 +194,13 @@ func connectionSuccessful():
158 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn") 194 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
159 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn") 195 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
160 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") 196 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
197 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn")
161 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") 198 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
199 clearResourceCache("res://objects/nodes/panel.tscn")
162 clearResourceCache("res://objects/nodes/player.tscn") 200 clearResourceCache("res://objects/nodes/player.tscn")
163 clearResourceCache("res://objects/nodes/saver.tscn") 201 clearResourceCache("res://objects/nodes/saver.tscn")
202 clearResourceCache("res://objects/nodes/teleport.tscn")
203 clearResourceCache("res://objects/nodes/worldport.tscn")
164 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn") 204 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn")
165 205
166 var paintings_dir = DirAccess.open("res://objects/meshes/paintings") 206 var paintings_dir = DirAccess.open("res://objects/meshes/paintings")
@@ -185,6 +225,13 @@ func connectionUnsuccessful(error_message):
185 popup.get_ok_button().visible = true 225 popup.get_ok_button().visible = true
186 popup.popup_centered() 226 popup.popup_centered()
187 227
228 $Panel/connect_button.disabled = false
229
230
231func versionMismatchDeclined():
232 $Panel/AcceptDialog.hide()
233 $Panel/connect_button.disabled = false
234
188 235
189func historySelected(index): 236func historySelected(index):
190 var ap = global.get_node("Archipelago") 237 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/textclient.gd b/client/Archipelago/textclient.gd index 4b03151..85cc6d2 100644 --- a/client/Archipelago/textclient.gd +++ b/client/Archipelago/textclient.gd
@@ -50,7 +50,7 @@ func _ready():
50 50
51 51
52func _input(event): 52func _input(event):
53 if event is InputEventKey and event.pressed: 53 if global.loaded and event is InputEventKey and event.pressed:
54 if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT): 54 if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT):
55 if !get_tree().paused: 55 if !get_tree().paused:
56 is_open = true 56 is_open = true
diff --git a/client/Archipelago/visibilityListener.gd b/client/Archipelago/visibilityListener.gd new file mode 100644 index 0000000..5ea17a0 --- /dev/null +++ b/client/Archipelago/visibilityListener.gd
@@ -0,0 +1,38 @@
1extends "res://scripts/nodes/listeners/visibilityListener.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/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()