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.gd326
1 files changed, 274 insertions, 52 deletions
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index 10c4096..8a15728 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -1,10 +1,12 @@
1extends Node 1extends Node
2 2
3const my_version = "0.1.0" 3const MOD_VERSION = 2
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 = ""
@@ -12,10 +14,43 @@ var ap_pass = ""
12var connection_history = [] 14var connection_history = []
13 15
14var client 16var client
17var keyboard
15 18
16var _localdata_file = "" 19var _localdata_file = ""
17var _received_indexes = []
18var _last_new_item = -1 20var _last_new_item = -1
21var _batch_locations = false
22var _held_locations = []
23var _held_location_scouts = []
24var _location_scouts = {}
25var _item_locks = {}
26var _inverse_item_locks = {}
27var _held_letters = {}
28var _letters_setup = false
29
30const kSHUFFLE_LETTERS_VANILLA = 0
31const kSHUFFLE_LETTERS_UNLOCKED = 1
32const kSHUFFLE_LETTERS_PROGRESSIVE = 2
33const kSHUFFLE_LETTERS_VANILLA_CYAN = 3
34const kSHUFFLE_LETTERS_ITEM_CYAN = 4
35
36const kLETTER_BEHAVIOR_VANILLA = 0
37const kLETTER_BEHAVIOR_ITEM = 1
38const kLETTER_BEHAVIOR_UNLOCKED = 2
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
46var daedalus_roof_access = false
47var keyholder_sanity = false
48var shuffle_control_center_colors = false
49var shuffle_doors = false
50var shuffle_gallery_paintings = false
51var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
52var shuffle_symbols = false
53var victory_condition = -1
19 54
20signal could_not_connect 55signal could_not_connect
21signal connect_status 56signal connect_status
@@ -52,12 +87,16 @@ func _ready():
52 87
53 client.connect("item_received", _process_item) 88 client.connect("item_received", _process_item)
54 client.connect("message_received", _process_message) 89 client.connect("message_received", _process_message)
90 client.connect("location_scout_received", _process_location_scout)
55 client.connect("could_not_connect", _client_could_not_connect) 91 client.connect("could_not_connect", _client_could_not_connect)
56 client.connect("connect_status", _client_connect_status) 92 client.connect("connect_status", _client_connect_status)
57 client.connect("client_connected", _client_connected) 93 client.connect("client_connected", _client_connected)
58 94
59 add_child(client) 95 add_child(client)
60 96
97 keyboard = SCRIPT_keyboard.new()
98 add_child(keyboard)
99
61 100
62func saveSettings(): 101func saveSettings():
63 # Save the AP settings to disk. 102 # Save the AP settings to disk.
@@ -91,8 +130,13 @@ func saveLocaldata():
91 130
92 131
93func connectToServer(): 132func connectToServer():
94 _received_indexes = []
95 _last_new_item = -1 133 _last_new_item = -1
134 _batch_locations = false
135 _held_locations = []
136 _held_location_scouts = []
137 _location_scouts = {}
138 _letters_setup = false
139 _held_letters = {}
96 140
97 client.connectToServer(ap_server, ap_user, ap_pass) 141 client.connectToServer(ap_server, ap_user, ap_pass)
98 142
@@ -106,48 +150,46 @@ func disconnect_from_ap():
106 150
107 151
108func get_item_id_for_door(door_id): 152func get_item_id_for_door(door_id):
109 var gamedata = global.get_node("Gamedata") 153 return _item_locks.get(door_id, null)
110 var door = gamedata.objects.get_doors()[door_id]
111 if (
112 door.get_type() == gamedata.SCRIPT_proto.DoorType.EVENT
113 or door.get_type() == gamedata.SCRIPT_proto.DoorType.LOCATION_ONLY
114 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
115 ):
116 return null
117 return gamedata.get_door_ap_id(door_id)
118
119
120func has_item(item_id):
121 return client.hasItem(item_id)
122
123 154
124func _process_item(item, index, from, flags):
125 if index != null:
126 if _received_indexes.has(index):
127 # Do not re-process items.
128 return
129
130 _received_indexes.append(index)
131 155
156func _process_item(item, index, from, flags, amount):
132 var item_name = "Unknown" 157 var item_name = "Unknown"
133 if client._item_id_to_name["Lingo 2"].has(item): 158 if client._item_id_to_name["Lingo 2"].has(item):
134 item_name = client._item_id_to_name["Lingo 2"][item] 159 item_name = client._item_id_to_name["Lingo 2"][item]
135 160
136 var gamedata = global.get_node("Gamedata") 161 var gamedata = global.get_node("Gamedata")
137 var door_id = gamedata.door_id_by_ap_id.get(item, null) 162
138 if door_id != null and gamedata.get_door_map_name(door_id) == global.map: 163 var prog_id = null
139 var receivers = gamedata.get_door_receivers(door_id) 164 if _inverse_item_locks.has(item):
140 var scene = get_tree().get_root().get_node_or_null("scene") 165 for lock in _inverse_item_locks.get(item):
141 if scene != null: 166 if lock[1] != amount:
142 for receiver in receivers: 167 continue
143 var rnode = scene.get_node_or_null(receiver) 168
144 if rnode != null: 169 if gamedata.progressive_id_by_ap_id.has(item):
145 rnode.handleTriggered() 170 prog_id = lock[0]
146 #for painting_id in gamedata.objects.get_doors()[door_id].get_move_paintings(): 171
147 # var painting = gamedata.objects.get_paintings()[painting_id] 172 if gamedata.get_door_map_name(lock[0]) != global.map:
148 # var pnode = scene.get_node_or_null(painting.get_path() + "/teleportListener") 173 continue
149 # if pnode != null: 174
150 # pnode.handleTriggered() 175 var receivers = gamedata.get_door_receivers(lock[0])
176 var scene = get_tree().get_root().get_node_or_null("scene")
177 if scene != null:
178 for receiver in receivers:
179 var rnode = scene.get_node_or_null(receiver)
180 if rnode != null:
181 rnode.handleTriggered()
182
183 var letter_id = gamedata.letter_id_by_ap_id.get(item, null)
184 if letter_id != null:
185 var letter = gamedata.objects.get_letters()[letter_id]
186 if not letter.has_level2() or not letter.get_level2():
187 _process_key_item(letter.get_key(), amount)
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")
151 193
152 # Show a message about the item if it's new. 194 # Show a message about the item if it's new.
153 if index != null and index > _last_new_item: 195 if index != null and index > _last_new_item:
@@ -155,16 +197,23 @@ func _process_item(item, index, from, flags):
155 saveLocaldata() 197 saveLocaldata()
156 198
157 var player_name = "Unknown" 199 var player_name = "Unknown"
158 if client._player_name_by_slot.has(from): 200 if client._player_name_by_slot.has(float(from)):
159 player_name = client._player_name_by_slot[from] 201 player_name = client._player_name_by_slot[float(from)]
160 202
161 var item_color = colorForItemType(flags) 203 var item_color = colorForItemType(flags)
162 204
205 var full_item_name = item_name
206 if prog_id != null:
207 var door = gamedata.objects.get_doors()[prog_id]
208 full_item_name = "%s (%s)" % [item_name, door.get_name()]
209
163 var message 210 var message
164 if from == client._slot: 211 if from == client._slot:
165 message = "Found [color=%s]%s[/color]" % [item_color, item_name] 212 message = "Found [color=%s]%s[/color]" % [item_color, full_item_name]
166 else: 213 else:
167 message = "Received [color=%s]%s[/color] from %s" % [item_color, item_name, player_name] 214 message = (
215 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name]
216 )
168 217
169 global._print(message) 218 global._print(message)
170 219
@@ -183,15 +232,15 @@ func _process_message(message):
183 232
184 var item_name = "Unknown" 233 var item_name = "Unknown"
185 var item_player_game = client._game_by_player[message["receiving"]] 234 var item_player_game = client._game_by_player[message["receiving"]]
186 if client._item_id_to_name[item_player_game].has(message["item"]["item"]): 235 if client._item_id_to_name[item_player_game].has(int(message["item"]["item"])):
187 item_name = client._item_id_to_name[item_player_game][message["item"]["item"]] 236 item_name = client._item_id_to_name[item_player_game][int(message["item"]["item"])]
188 237
189 var location_name = "Unknown" 238 var location_name = "Unknown"
190 var location_player_game = client._game_by_player[message["item"]["player"]] 239 var location_player_game = client._game_by_player[message["item"]["player"]]
191 if client._location_id_to_name[location_player_game].has(message["item"]["location"]): 240 if client._location_id_to_name[location_player_game].has(int(message["item"]["location"])):
192 location_name = ( 241 location_name = (client._location_id_to_name[location_player_game][int(
193 client._location_id_to_name[location_player_game][message["item"]["location"]] 242 message["item"]["location"]
194 ) 243 )])
195 244
196 var player_name = "Unknown" 245 var player_name = "Unknown"
197 if client._player_name_by_slot.has(message["receiving"]): 246 if client._player_name_by_slot.has(message["receiving"]):
@@ -257,6 +306,29 @@ func parse_printjson_for_textclient(message):
257 textclient_node.parse_printjson("".join(parts)) 306 textclient_node.parse_printjson("".join(parts))
258 307
259 308
309func _process_location_scout(item_id, location_id, player, flags):
310 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
311
312 var gamedata = global.get_node("Gamedata")
313 var map_id = gamedata.map_id_by_name.get(global.map)
314
315 var item_name = "Unknown"
316 var item_player_game = client._game_by_player[float(player)]
317 if client._item_id_to_name[item_player_game].has(item_id):
318 item_name = client._item_id_to_name[item_player_game][item_id]
319
320 var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null)
321 if letter_id != null:
322 var letter = gamedata.objects.get_letters()[letter_id]
323 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
324 if room.get_map_id() == map_id:
325 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
326 letter.get_path()
327 )
328 if collectable != null:
329 collectable.setScoutedText(item_name)
330
331
260func _client_could_not_connect(): 332func _client_could_not_connect():
261 emit_signal("could_not_connect") 333 emit_signal("could_not_connect")
262 334
@@ -265,14 +337,18 @@ func _client_connect_status(message):
265 emit_signal("connect_status", message) 337 emit_signal("connect_status", message)
266 338
267 339
268func _client_connected(): 340func _client_connected(slot_data):
341 var gamedata = global.get_node("Gamedata")
342
269 _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot] 343 _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot]
270 _last_new_item = -1 344 _last_new_item = -1
271 345
272 if FileAccess.file_exists(_localdata_file): 346 if FileAccess.file_exists(_localdata_file):
273 var ap_file = FileAccess.open(_localdata_file, FileAccess.READ) 347 var ap_file = FileAccess.open(_localdata_file, FileAccess.READ)
274 var localdata = ap_file.get_var(true) 348 var localdata = []
275 ap_file.close() 349 if ap_file != null:
350 localdata = ap_file.get_var(true)
351 ap_file.close()
276 352
277 if typeof(localdata) != TYPE_ARRAY: 353 if typeof(localdata) != TYPE_ARRAY:
278 print("AP localdata file is corrupted") 354 print("AP localdata file is corrupted")
@@ -281,11 +357,113 @@ func _client_connected():
281 if localdata.size() > 0: 357 if localdata.size() > 0:
282 _last_new_item = localdata[0] 358 _last_new_item = localdata[0]
283 359
360 # Read slot data.
361 cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0))
362 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
363 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
364 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
365 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
366 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
367 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
368 shuffle_symbols = bool(slot_data.get("shuffle_symbols", false))
369 victory_condition = int(slot_data.get("victory_condition", 0))
370
371 if slot_data.has("version"):
372 apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])]
373
374 # Set up item locks.
375 _item_locks = {}
376
377 if shuffle_doors:
378 for door in gamedata.objects.get_doors():
379 if (
380 door.get_type() == gamedata.SCRIPT_proto.DoorType.STANDARD
381 or door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
382 ):
383 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
384
385 for progressive in gamedata.objects.get_progressives():
386 for i in range(0, progressive.get_doors().size()):
387 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]]
388 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1]
389
390 for door_group in gamedata.objects.get_door_groups():
391 if (
392 door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR
393 or door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP
394 ):
395 for door in door_group.get_doors():
396 _item_locks[door] = [door_group.get_ap_id(), 1]
397
398 if shuffle_control_center_colors:
399 for door in gamedata.objects.get_doors():
400 if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR:
401 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
402
403 for door_group in gamedata.objects.get_door_groups():
404 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR:
405 for door in door_group.get_doors():
406 _item_locks[door] = [door_group.get_ap_id(), 1]
407
408 if shuffle_gallery_paintings:
409 for door in gamedata.objects.get_doors():
410 if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING:
411 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
412
413 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM:
414 for door_group in gamedata.objects.get_door_groups():
415 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS:
416 for door in door_group.get_doors():
417 if not _item_locks.has(door):
418 _item_locks[door] = [door_group.get_ap_id(), 1]
419
420 # Create a reverse item locks map for processing items.
421 _inverse_item_locks = {}
422
423 for door_id in _item_locks.keys():
424 var lock = _item_locks.get(door_id)
425
426 if not _inverse_item_locks.has(lock[0]):
427 _inverse_item_locks[lock[0]] = []
428
429 _inverse_item_locks[lock[0]].append([door_id, lock[1]])
430
284 emit_signal("ap_connected") 431 emit_signal("ap_connected")
285 432
286 433
434func start_batching_locations():
435 _batch_locations = true
436
437
287func send_location(loc_id): 438func send_location(loc_id):
288 client.sendLocation(loc_id) 439 if _batch_locations:
440 _held_locations.append(loc_id)
441 else:
442 client.sendLocation(loc_id)
443
444
445func scout_location(loc_id):
446 if _location_scouts.has(loc_id):
447 return _location_scouts.get(loc_id)
448
449 if _batch_locations:
450 _held_location_scouts.append(loc_id)
451 else:
452 client.scoutLocation(loc_id)
453
454 return null
455
456
457func stop_batching_locations():
458 _batch_locations = false
459
460 if not _held_locations.is_empty():
461 client.sendLocations(_held_locations)
462 _held_locations.clear()
463
464 if not _held_location_scouts.is_empty():
465 client.scoutLocations(_held_location_scouts)
466 _held_location_scouts.clear()
289 467
290 468
291func colorForItemType(flags): 469func colorForItemType(flags):
@@ -301,3 +479,47 @@ func colorForItemType(flags):
301 return "#d63a22" 479 return "#d63a22"
302 else: # filler 480 else: # filler
303 return "#14de9e" 481 return "#14de9e"
482
483
484func get_letter_behavior(key, level2):
485 if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED:
486 return kLETTER_BEHAVIOR_UNLOCKED
487
488 if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters):
489 if level2:
490 if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN:
491 return kLETTER_BEHAVIOR_VANILLA
492 else:
493 return kLETTER_BEHAVIOR_ITEM
494 else:
495 return kLETTER_BEHAVIOR_UNLOCKED
496
497 if not level2 and ["h", "i", "n", "t"].has(key):
498 # This differs from the equivalent function in the apworld. Logically it is
499 # the same as UNLOCKED since they are in the starting room, but VANILLA
500 # means the player still has to actually pick up the letters.
501 return kLETTER_BEHAVIOR_VANILLA
502
503 if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE:
504 return kLETTER_BEHAVIOR_ITEM
505
506 return kLETTER_BEHAVIOR_VANILLA
507
508
509func setup_keys():
510 keyboard.load_seed()
511
512 _letters_setup = true
513
514 for k in _held_letters.keys():
515 _process_key_item(k, _held_letters[k])
516
517 _held_letters.clear()
518
519
520func _process_key_item(key, level):
521 if not _letters_setup:
522 _held_letters[key] = max(_held_letters.get(key, 0), level)
523 return
524
525 keyboard.collect_remote_letter(key, level)