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