about summary refs log tree commit diff stats
path: root/client/Archipelago/manager.gd
diff options
context:
space:
mode:
Diffstat (limited to 'client/Archipelago/manager.gd')
-rw-r--r--client/Archipelago/manager.gd458
1 files changed, 404 insertions, 54 deletions
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index 99cb47b..218870c 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -1,45 +1,149 @@
1extends Node 1extends Node
2 2
3const my_version = "0.1.0" 3const MOD_VERSION = 7
4 4
5var SCRIPT_client 5var SCRIPT_client
6var SCRIPT_keyboard
6var SCRIPT_locationListener 7var SCRIPT_locationListener
7var SCRIPT_uuid 8var SCRIPT_uuid
9var SCRIPT_victoryListener
8 10
9var ap_server = "" 11var ap_server = ""
10var ap_user = "" 12var ap_user = ""
11var ap_pass = "" 13var ap_pass = ""
12var connection_history = [] 14var connection_history = []
15var show_compass = false
13 16
14var client 17var client
18var keyboard
15 19
16var _received_indexes = [] 20var _localdata_file = ""
17var _last_new_item = -1 21var _last_new_item = -1
22var _batch_locations = false
23var _held_locations = []
24var _held_location_scouts = []
25var _location_scouts = {}
26var _item_locks = {}
27var _inverse_item_locks = {}
28var _held_letters = {}
29var _letters_setup = false
30
31const kSHUFFLE_LETTERS_VANILLA = 0
32const kSHUFFLE_LETTERS_UNLOCKED = 1
33const kSHUFFLE_LETTERS_PROGRESSIVE = 2
34const kSHUFFLE_LETTERS_VANILLA_CYAN = 3
35const kSHUFFLE_LETTERS_ITEM_CYAN = 4
36
37const kLETTER_BEHAVIOR_VANILLA = 0
38const kLETTER_BEHAVIOR_ITEM = 1
39const kLETTER_BEHAVIOR_UNLOCKED = 2
40
41const kCYAN_DOOR_BEHAVIOR_H2 = 0
42const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1
43const kCYAN_DOOR_BEHAVIOR_ITEM = 2
44
45var apworld_version = [0, 0]
46var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
47var daedalus_roof_access = false
48var keyholder_sanity = false
49var shuffle_control_center_colors = false
50var shuffle_doors = false
51var shuffle_gallery_paintings = false
52var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
53var shuffle_symbols = false
54var strict_cyan_ending = false
55var strict_purple_ending = false
56var victory_condition = -1
18 57
19signal could_not_connect 58signal could_not_connect
20signal connect_status 59signal connect_status
21signal ap_connected 60signal ap_connected
22 61
23 62
63func _init():
64 # Read AP settings from file, if there are any
65 if FileAccess.file_exists("user://ap_settings"):
66 var file = FileAccess.open("user://ap_settings", FileAccess.READ)
67 var data = file.get_var(true)
68 file.close()
69
70 if typeof(data) != TYPE_ARRAY:
71 global._print("AP settings file is corrupted")
72 data = []
73
74 if data.size() > 0:
75 ap_server = data[0]
76
77 if data.size() > 1:
78 ap_user = data[1]
79
80 if data.size() > 2:
81 ap_pass = data[2]
82
83 if data.size() > 3:
84 connection_history = data[3]
85
86 if data.size() > 4:
87 show_compass = data[4]
88
89
24func _ready(): 90func _ready():
25 client = SCRIPT_client.new() 91 client = SCRIPT_client.new()
26 client.SCRIPT_uuid = SCRIPT_uuid 92 client.SCRIPT_uuid = SCRIPT_uuid
27 93
28 client.connect("item_received", _process_item) 94 client.connect("item_received", _process_item)
95 client.connect("message_received", _process_message)
96 client.connect("location_scout_received", _process_location_scout)
29 client.connect("could_not_connect", _client_could_not_connect) 97 client.connect("could_not_connect", _client_could_not_connect)
30 client.connect("connect_status", _client_connect_status) 98 client.connect("connect_status", _client_connect_status)
31 client.connect("client_connected", _client_connected) 99 client.connect("client_connected", _client_connected)
32 100
33 add_child(client) 101 add_child(client)
34 102
103 keyboard = SCRIPT_keyboard.new()
104 add_child(keyboard)
105
35 106
36func saveSettings(): 107func saveSettings():
37 pass 108 # Save the AP settings to disk.
109 var path = "user://ap_settings"
110 var file = FileAccess.open(path, FileAccess.WRITE)
111
112 var data = [
113 ap_server,
114 ap_user,
115 ap_pass,
116 connection_history,
117 show_compass,
118 ]
119 file.store_var(data, true)
120 file.close()
121
122
123func saveLocaldata():
124 # Save the MW/slot specific settings to disk.
125 var dir = DirAccess.open("user://")
126 var folder = "archipelago_data"
127 if not dir.dir_exists(folder):
128 dir.make_dir(folder)
129
130 var file = FileAccess.open(_localdata_file, FileAccess.WRITE)
131
132 var data = [
133 _last_new_item,
134 ]
135 file.store_var(data, true)
136 file.close()
38 137
39 138
40func connectToServer(): 139func connectToServer():
41 _received_indexes = []
42 _last_new_item = -1 140 _last_new_item = -1
141 _batch_locations = false
142 _held_locations = []
143 _held_location_scouts = []
144 _location_scouts = {}
145 _letters_setup = false
146 _held_letters = {}
43 147
44 client.connectToServer(ap_server, ap_user, ap_pass) 148 client.connectToServer(ap_server, ap_user, ap_pass)
45 149
@@ -53,65 +157,73 @@ func disconnect_from_ap():
53 157
54 158
55func get_item_id_for_door(door_id): 159func get_item_id_for_door(door_id):
56 var gamedata = global.get_node("Gamedata") 160 return _item_locks.get(door_id, null)
57 var door = gamedata.objects.get_doors()[door_id]
58 if (
59 door.get_type() == gamedata.SCRIPT_proto.DoorType.EVENT
60 or door.get_type() == gamedata.SCRIPT_proto.DoorType.LOCATION_ONLY
61 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
62 ):
63 return null
64 return gamedata.get_door_ap_id(door_id)
65
66
67func has_item(item_id):
68 return client.hasItem(item_id)
69 161
70 162
71func _process_item(item, index, from, flags): 163func _process_item(item, index, from, flags, amount):
72 if index != null:
73 if _received_indexes.has(index):
74 # Do not re-process items.
75 return
76
77 _received_indexes.append(index)
78
79 var item_name = "Unknown" 164 var item_name = "Unknown"
80 if client._item_id_to_name["Lingo 2"].has(item): 165 if client._item_id_to_name["Lingo 2"].has(item):
81 item_name = client._item_id_to_name["Lingo 2"][item] 166 item_name = client._item_id_to_name["Lingo 2"][item]
82 167
83 var gamedata = global.get_node("Gamedata") 168 var gamedata = global.get_node("Gamedata")
84 var door_id = gamedata.door_id_by_ap_id.get(item, null) 169
85 if door_id != null and gamedata.get_door_map_name(door_id) == global.map: 170 var prog_id = null
86 var receivers = gamedata.get_door_receivers(door_id) 171 if _inverse_item_locks.has(item):
87 var scene = get_tree().get_root().get_node_or_null("scene") 172 for lock in _inverse_item_locks.get(item):
88 if scene != null: 173 if lock[1] != amount:
89 for receiver in receivers: 174 continue
90 var rnode = scene.get_node_or_null(receiver) 175
91 if rnode != null: 176 if gamedata.progressive_id_by_ap_id.has(item):
92 rnode.handleTriggered() 177 prog_id = lock[0]
93 for painting_id in gamedata.objects.get_doors()[door_id].get_move_paintings(): 178
94 var painting = gamedata.objects.get_paintings()[painting_id] 179 if gamedata.get_door_map_name(lock[0]) != global.map:
95 var pnode = scene.get_node_or_null(painting.get_path() + "/teleportListener") 180 continue
96 if pnode != null: 181
97 pnode.handleTriggered() 182 var receivers = gamedata.get_door_receivers(lock[0])
183 var scene = get_tree().get_root().get_node_or_null("scene")
184 if scene != null:
185 for receiver in receivers:
186 var rnode = scene.get_node_or_null(receiver)
187 if rnode != null:
188 rnode.handleTriggered()
189
190 var letter_id = gamedata.letter_id_by_ap_id.get(item, null)
191 if letter_id != null:
192 var letter = gamedata.objects.get_letters()[letter_id]
193 if not letter.has_level2() or not letter.get_level2():
194 _process_key_item(letter.get_key(), amount)
195
196 if gamedata.symbol_item_ids.has(item):
197 var player = get_tree().get_root().get_node_or_null("scene/player")
198 if player != null:
199 player.emit_signal("evaluate_solvability")
98 200
99 # Show a message about the item if it's new. 201 # Show a message about the item if it's new.
100 if index != null and index > _last_new_item: 202 if index != null and index > _last_new_item:
101 _last_new_item = index 203 _last_new_item = index
102 #saveLocaldata() 204 saveLocaldata()
103 205
104 var player_name = "Unknown" 206 var player_name = "Unknown"
105 if client._player_name_by_slot.has(from): 207 if client._player_name_by_slot.has(float(from)):
106 player_name = client._player_name_by_slot[from] 208 player_name = client._player_name_by_slot[float(from)]
107 209
108 var item_color = colorForItemType(flags) 210 var item_color = colorForItemType(flags)
109 211
212 var full_item_name = item_name
213 if prog_id != null:
214 var door = gamedata.objects.get_doors()[prog_id]
215 full_item_name = "%s (%s)" % [item_name, door.get_name()]
216
110 var message 217 var message
111 if from == client._slot: 218 if from == client._slot:
112 message = "Found [color=%s]%s[/color]" % [item_color, item_name] 219 message = "Found [color=%s]%s[/color]" % [item_color, full_item_name]
113 else: 220 else:
114 message = "Received [color=%s]%s[/color] from %s" % [item_color, item_name, player_name] 221 message = (
222 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name]
223 )
224
225 if gamedata.anti_trap_ids.has(item):
226 keyboard.block_letter(gamedata.anti_trap_ids[item])
115 227
116 global._print(message) 228 global._print(message)
117 229
@@ -119,6 +231,8 @@ func _process_item(item, index, from, flags):
119 231
120 232
121func _process_message(message): 233func _process_message(message):
234 parse_printjson_for_textclient(message)
235
122 if ( 236 if (
123 !message.has("receiving") 237 !message.has("receiving")
124 or !message.has("item") 238 or !message.has("item")
@@ -128,15 +242,15 @@ func _process_message(message):
128 242
129 var item_name = "Unknown" 243 var item_name = "Unknown"
130 var item_player_game = client._game_by_player[message["receiving"]] 244 var item_player_game = client._game_by_player[message["receiving"]]
131 if client._item_id_to_name[item_player_game].has(message["item"]["item"]): 245 if client._item_id_to_name[item_player_game].has(int(message["item"]["item"])):
132 item_name = client._item_id_to_name[item_player_game][message["item"]["item"]] 246 item_name = client._item_id_to_name[item_player_game][int(message["item"]["item"])]
133 247
134 var location_name = "Unknown" 248 var location_name = "Unknown"
135 var location_player_game = client._game_by_player[message["item"]["player"]] 249 var location_player_game = client._game_by_player[message["item"]["player"]]
136 if client._location_id_to_name[location_player_game].has(message["item"]["location"]): 250 if client._location_id_to_name[location_player_game].has(int(message["item"]["location"])):
137 location_name = ( 251 location_name = (client._location_id_to_name[location_player_game][int(
138 client._location_id_to_name[location_player_game][message["item"]["location"]] 252 message["item"]["location"]
139 ) 253 )])
140 254
141 var player_name = "Unknown" 255 var player_name = "Unknown"
142 if client._player_name_by_slot.has(message["receiving"]): 256 if client._player_name_by_slot.has(message["receiving"]):
@@ -163,20 +277,209 @@ func _process_message(message):
163 global.get_node("Messages").showMessage(sentMsg) 277 global.get_node("Messages").showMessage(sentMsg)
164 278
165 279
166func _client_could_not_connect(): 280func parse_printjson_for_textclient(message):
167 emit_signal("could_not_connect") 281 var parts = []
282 for message_part in message["data"]:
283 if !message_part.has("type") and message_part.has("text"):
284 parts.append(message_part["text"])
285 elif message_part["type"] == "player_id":
286 if int(message_part["text"]) == client._slot:
287 parts.append(
288 "[color=#ee00ee]%s[/color]" % client._player_name_by_slot[client._slot]
289 )
290 else:
291 var from = float(message_part["text"])
292 parts.append("[color=#fafad2]%s[/color]" % client._player_name_by_slot[from])
293 elif message_part["type"] == "item_id":
294 var item_name = "Unknown"
295 var item_player_game = client._game_by_player[message_part["player"]]
296 if client._item_id_to_name[item_player_game].has(int(message_part["text"])):
297 item_name = client._item_id_to_name[item_player_game][int(message_part["text"])]
298
299 parts.append(
300 "[color=%s]%s[/color]" % [colorForItemType(message_part["flags"]), item_name]
301 )
302 elif message_part["type"] == "location_id":
303 var location_name = "Unknown"
304 var location_player_game = client._game_by_player[message_part["player"]]
305 if client._location_id_to_name[location_player_game].has(int(message_part["text"])):
306 location_name = client._location_id_to_name[location_player_game][int(
307 message_part["text"]
308 )]
309
310 parts.append("[color=#00ff7f]%s[/color]" % location_name)
311 elif message_part.has("text"):
312 parts.append(message_part["text"])
313
314 var textclient_node = global.get_node("Textclient")
315 if textclient_node != null:
316 textclient_node.parse_printjson("".join(parts))
317
318
319func _process_location_scout(item_id, location_id, player, flags):
320 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
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
326 var gamedata = global.get_node("Gamedata")
327 var map_id = gamedata.map_id_by_name.get(global.map)
328
329 var item_name = "Unknown"
330 var item_player_game = client._game_by_player[float(player)]
331 if client._item_id_to_name[item_player_game].has(item_id):
332 item_name = client._item_id_to_name[item_player_game][item_id]
333
334 var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null)
335 if letter_id != null:
336 var letter = gamedata.objects.get_letters()[letter_id]
337 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
338 if room.get_map_id() == map_id:
339 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
340 letter.get_path()
341 )
342 if collectable != null:
343 collectable.setScoutedText(item_name)
344
345
346func _client_could_not_connect(message):
347 emit_signal("could_not_connect", message)
168 348
169 349
170func _client_connect_status(message): 350func _client_connect_status(message):
171 emit_signal("connect_status", message) 351 emit_signal("connect_status", message)
172 352
173 353
174func _client_connected(): 354func _client_connected(slot_data):
355 var gamedata = global.get_node("Gamedata")
356
357 _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot]
358 _last_new_item = -1
359
360 if FileAccess.file_exists(_localdata_file):
361 var ap_file = FileAccess.open(_localdata_file, FileAccess.READ)
362 var localdata = []
363 if ap_file != null:
364 localdata = ap_file.get_var(true)
365 ap_file.close()
366
367 if typeof(localdata) != TYPE_ARRAY:
368 print("AP localdata file is corrupted")
369 localdata = []
370
371 if localdata.size() > 0:
372 _last_new_item = localdata[0]
373
374 # Read slot data.
375 cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0))
376 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
377 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
378 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
379 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
380 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
381 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
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))
385 victory_condition = int(slot_data.get("victory_condition", 0))
386
387 if slot_data.has("version"):
388 apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])]
389
390 # Set up item locks.
391 _item_locks = {}
392
393 if shuffle_doors:
394 for door in gamedata.objects.get_doors():
395 if (
396 door.get_type() == gamedata.SCRIPT_proto.DoorType.STANDARD
397 or door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
398 ):
399 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
400
401 for progressive in gamedata.objects.get_progressives():
402 for i in range(0, progressive.get_doors().size()):
403 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]]
404 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1]
405
406 for door_group in gamedata.objects.get_door_groups():
407 if (
408 door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR
409 or door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP
410 ):
411 for door in door_group.get_doors():
412 _item_locks[door] = [door_group.get_ap_id(), 1]
413
414 if shuffle_control_center_colors:
415 for door in gamedata.objects.get_doors():
416 if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR:
417 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
418
419 for door_group in gamedata.objects.get_door_groups():
420 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR:
421 for door in door_group.get_doors():
422 _item_locks[door] = [door_group.get_ap_id(), 1]
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
429 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM:
430 for door_group in gamedata.objects.get_door_groups():
431 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS:
432 for door in door_group.get_doors():
433 if not _item_locks.has(door):
434 _item_locks[door] = [door_group.get_ap_id(), 1]
435
436 # Create a reverse item locks map for processing items.
437 _inverse_item_locks = {}
438
439 for door_id in _item_locks.keys():
440 var lock = _item_locks.get(door_id)
441
442 if not _inverse_item_locks.has(lock[0]):
443 _inverse_item_locks[lock[0]] = []
444
445 _inverse_item_locks[lock[0]].append([door_id, lock[1]])
446
175 emit_signal("ap_connected") 447 emit_signal("ap_connected")
176 448
177 449
450func start_batching_locations():
451 _batch_locations = true
452
453
178func send_location(loc_id): 454func send_location(loc_id):
179 client.sendLocation(loc_id) 455 if _batch_locations:
456 _held_locations.append(loc_id)
457 else:
458 client.sendLocation(loc_id)
459
460
461func scout_location(loc_id):
462 if _location_scouts.has(loc_id):
463 return _location_scouts.get(loc_id)
464
465 if _batch_locations:
466 _held_location_scouts.append(loc_id)
467 else:
468 client.scoutLocation(loc_id)
469
470 return null
471
472
473func stop_batching_locations():
474 _batch_locations = false
475
476 if not _held_locations.is_empty():
477 client.sendLocations(_held_locations)
478 _held_locations.clear()
479
480 if not _held_location_scouts.is_empty():
481 client.scoutLocations(_held_location_scouts)
482 _held_location_scouts.clear()
180 483
181 484
182func colorForItemType(flags): 485func colorForItemType(flags):
@@ -192,3 +495,50 @@ func colorForItemType(flags):
192 return "#d63a22" 495 return "#d63a22"
193 else: # filler 496 else: # filler
194 return "#14de9e" 497 return "#14de9e"
498
499
500func get_letter_behavior(key, level2):
501 if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED:
502 return kLETTER_BEHAVIOR_UNLOCKED
503
504 if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters):
505 if level2:
506 if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN:
507 return kLETTER_BEHAVIOR_VANILLA
508 else:
509 return kLETTER_BEHAVIOR_ITEM
510 else:
511 return kLETTER_BEHAVIOR_UNLOCKED
512
513 if not level2 and ["h", "i", "n", "t"].has(key):
514 # This differs from the equivalent function in the apworld. Logically it is
515 # the same as UNLOCKED since they are in the starting room, but VANILLA
516 # means the player still has to actually pick up the letters.
517 return kLETTER_BEHAVIOR_VANILLA
518
519 if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE:
520 return kLETTER_BEHAVIOR_ITEM
521
522 return kLETTER_BEHAVIOR_VANILLA
523
524
525func setup_keys():
526 keyboard.load_seed()
527
528 _letters_setup = true
529
530 for k in _held_letters.keys():
531 _process_key_item(k, _held_letters[k])
532
533 _held_letters.clear()
534
535
536func _process_key_item(key, level):
537 if not _letters_setup:
538 _held_letters[key] = max(_held_letters.get(key, 0), level)
539 return
540
541 if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN:
542 level += 1
543
544 keyboard.collect_remote_letter(key, level)