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