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/animationListener.gd38
-rw-r--r--client/Archipelago/client.gd415
-rw-r--r--client/Archipelago/collectable.gd16
-rw-r--r--client/Archipelago/door.gd38
-rw-r--r--client/Archipelago/gamedata.gd133
-rw-r--r--client/Archipelago/keyHolder.gd38
-rw-r--r--client/Archipelago/keyHolderChecker.gd24
-rw-r--r--client/Archipelago/keyHolderResetterListener.gd8
-rw-r--r--client/Archipelago/keyboard.gd178
-rw-r--r--client/Archipelago/locationListener.gd20
-rw-r--r--client/Archipelago/manager.gd514
-rw-r--r--client/Archipelago/messages.gd61
-rw-r--r--client/Archipelago/painting.gd38
-rw-r--r--client/Archipelago/panel.gd101
-rw-r--r--client/Archipelago/pauseMenu.gd12
-rw-r--r--client/Archipelago/player.gd238
-rw-r--r--client/Archipelago/saver.gd9
-rw-r--r--client/Archipelago/settings_buttons.gd24
-rw-r--r--client/Archipelago/settings_screen.gd204
-rw-r--r--client/Archipelago/teleportListener.gd49
-rw-r--r--client/Archipelago/textclient.gd86
-rw-r--r--client/Archipelago/vendor/LICENSE21
-rw-r--r--client/Archipelago/vendor/uuid.gd195
-rw-r--r--client/Archipelago/victoryListener.gd20
-rw-r--r--client/Archipelago/visibilityListener.gd38
-rw-r--r--client/Archipelago/worldportListener.gd8
26 files changed, 0 insertions, 2526 deletions
diff --git a/client/Archipelago/animationListener.gd b/client/Archipelago/animationListener.gd deleted file mode 100644 index c3b26db..0000000 --- a/client/Archipelago/animationListener.gd +++ /dev/null
@@ -1,38 +0,0 @@
1extends "res://scripts/nodes/listeners/animationListener.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/client.gd b/client/Archipelago/client.gd deleted file mode 100644 index 2e080fd..0000000 --- a/client/Archipelago/client.gd +++ /dev/null
@@ -1,415 +0,0 @@
1extends Node
2
3const ap_version = {"major": 0, "minor": 6, "build": 3, "class": "Version"}
4
5var SCRIPT_uuid
6
7var _ws = WebSocketPeer.new()
8var _should_process = false
9var _initiated_disconnect = false
10var _try_wss = false
11var _has_connected = false
12
13var _datapackages = {}
14var _pending_packages = []
15var _item_id_to_name = {} # All games
16var _location_id_to_name = {} # All games
17var _item_name_to_id = {} # Lingo 2 only
18var _location_name_to_id = {} # Lingo 2 only
19
20var _remote_version = {"major": 0, "minor": 0, "build": 0}
21var _gen_version = {"major": 0, "minor": 0, "build": 0}
22
23var ap_server = ""
24var ap_user = ""
25var ap_pass = ""
26
27var _authenticated = false
28var _seed = ""
29var _team = 0
30var _slot = 0
31var _players = []
32var _player_name_by_slot = {}
33var _game_by_player = {}
34var _checked_locations = []
35var _received_indexes = []
36var _received_items = {}
37var _slot_data = {}
38
39signal could_not_connect
40signal connect_status
41signal client_connected(slot_data)
42signal item_received(item_id, index, player, flags, amount)
43signal message_received(message)
44signal location_scout_received(item_id, location_id, player, flags)
45
46
47func _init():
48 set_process_mode(Node.PROCESS_MODE_ALWAYS)
49
50 global._print("Instantiated APClient")
51
52 # Read AP datapackages from file, if there are any
53 if FileAccess.file_exists("user://ap_datapackages"):
54 var file = FileAccess.open("user://ap_datapackages", FileAccess.READ)
55 var data = file.get_var(true)
56 file.close()
57
58 if typeof(data) != TYPE_DICTIONARY:
59 global._print("AP datapackages file is corrupted")
60 data = {}
61
62 _datapackages = data
63
64 processDatapackages()
65
66
67func _ready():
68 pass
69 #_ws.connect("connection_closed", _closed)
70 #_ws.connect("connection_failed", _closed)
71 #_ws.connect("server_disconnected", _closed)
72 #_ws.connect("connection_error", _errored)
73 #_ws.connect("connection_established", _connected)
74
75
76func _reset_state():
77 _should_process = false
78 _authenticated = false
79 _try_wss = false
80 _has_connected = false
81 _received_items = {}
82 _received_indexes = []
83
84
85func _errored():
86 if _try_wss:
87 global._print("Could not connect to AP with ws://, now trying wss://")
88 connectToServer(ap_server, ap_user, ap_pass)
89 else:
90 global._print("AP connection failed")
91 _reset_state()
92
93 emit_signal(
94 "could_not_connect",
95 "Could not connect to Archipelago. Check that your server and port are correct. See the error log for more information."
96 )
97
98
99func _closed(_was_clean = true):
100 global._print("Connection closed")
101 _reset_state()
102
103 if not _initiated_disconnect:
104 emit_signal("could_not_connect", "Disconnected from Archipelago")
105
106 _initiated_disconnect = false
107
108
109func _connected(_proto = ""):
110 global._print("Connected!")
111 _try_wss = false
112
113
114func disconnect_from_ap():
115 _initiated_disconnect = true
116 _ws.close()
117
118
119func _process(_delta):
120 if _should_process:
121 _ws.poll()
122
123 var state = _ws.get_ready_state()
124 if state == WebSocketPeer.STATE_OPEN:
125 if not _has_connected:
126 _has_connected = true
127
128 _connected()
129
130 while _ws.get_available_packet_count():
131 var packet = _ws.get_packet()
132 global._print("Got data from server: " + packet.get_string_from_utf8())
133 var json = JSON.new()
134 var jserror = json.parse(packet.get_string_from_utf8())
135 if jserror != OK:
136 global._print("Error parsing packet from AP: " + jserror.error_string)
137 return
138
139 for message in json.data:
140 var cmd = message["cmd"]
141 global._print("Received command: " + cmd)
142
143 if cmd == "RoomInfo":
144 _seed = message["seed_name"]
145 _remote_version = message["version"]
146 _gen_version = message["generator_version"]
147
148 var needed_games = []
149 for game in message["datapackage_checksums"].keys():
150 if (
151 !_datapackages.has(game)
152 or (
153 _datapackages[game]["checksum"]
154 != message["datapackage_checksums"][game]
155 )
156 ):
157 needed_games.append(game)
158
159 if !needed_games.is_empty():
160 _pending_packages = needed_games
161 var cur_needed = _pending_packages.pop_front()
162 requestDatapackages([cur_needed])
163 else:
164 connectToRoom()
165
166 elif cmd == "DataPackage":
167 for game in message["data"]["games"].keys():
168 _datapackages[game] = message["data"]["games"][game]
169 saveDatapackages()
170
171 if !_pending_packages.is_empty():
172 var cur_needed = _pending_packages.pop_front()
173 requestDatapackages([cur_needed])
174 else:
175 processDatapackages()
176 connectToRoom()
177
178 elif cmd == "Connected":
179 _authenticated = true
180 _team = message["team"]
181 _slot = message["slot"]
182 _players = message["players"]
183 _checked_locations = message["checked_locations"]
184 _slot_data = message["slot_data"]
185
186 for player in _players:
187 _player_name_by_slot[player["slot"]] = player["alias"]
188 _game_by_player[player["slot"]] = message["slot_info"][str(
189 player["slot"]
190 )]["game"]
191
192 emit_signal("client_connected", _slot_data)
193
194 elif cmd == "ConnectionRefused":
195 var error_message = ""
196 for error in message["errors"]:
197 var submsg = ""
198 if error == "InvalidSlot":
199 submsg = "Invalid player name."
200 elif error == "InvalidGame":
201 submsg = "The specified player is not playing Lingo."
202 elif error == "IncompatibleVersion":
203 submsg = (
204 "The Archipelago server is not the correct version for this client. Expected v%d.%d.%d. Found v%d.%d.%d."
205 % [
206 ap_version["major"],
207 ap_version["minor"],
208 ap_version["build"],
209 _remote_version["major"],
210 _remote_version["minor"],
211 _remote_version["build"]
212 ]
213 )
214 elif error == "InvalidPassword":
215 submsg = "Incorrect password."
216 elif error == "InvalidItemsHandling":
217 submsg = "Invalid item handling flag. This is a bug with the client."
218
219 if submsg != "":
220 if error_message != "":
221 error_message += " "
222 error_message += submsg
223
224 if error_message == "":
225 error_message = "Unknown error."
226
227 _initiated_disconnect = true
228 _ws.disconnect_from_host()
229
230 emit_signal("could_not_connect", error_message)
231 global._print("Connection to AP refused")
232 global._print(message)
233
234 elif cmd == "ReceivedItems":
235 var i = 0
236 for item in message["items"]:
237 var index = int(message["index"] + i)
238 i += 1
239
240 if _received_indexes.has(index):
241 # Do not re-process items.
242 continue
243
244 _received_indexes.append(index)
245
246 var item_id = int(item["item"])
247 _received_items[item_id] = _received_items.get(item_id, 0) + 1
248
249 emit_signal(
250 "item_received",
251 item_id,
252 index,
253 int(item["player"]),
254 int(item["flags"]),
255 _received_items[item_id]
256 )
257
258 elif cmd == "PrintJSON":
259 emit_signal("message_received", message)
260
261 elif cmd == "LocationInfo":
262 for loc in message["locations"]:
263 emit_signal(
264 "location_scout_received",
265 int(loc["item"]),
266 int(loc["location"]),
267 int(loc["player"]),
268 int(loc["flags"])
269 )
270
271 elif state == WebSocketPeer.STATE_CLOSED:
272 if _has_connected:
273 _closed()
274 else:
275 _errored()
276
277
278func saveDatapackages():
279 # Save the AP datapackages to disk.
280 var file = FileAccess.open("user://ap_datapackages", FileAccess.WRITE)
281 file.store_var(_datapackages, true)
282 file.close()
283
284
285func connectToServer(server, un, pw):
286 ap_server = server
287 ap_user = un
288 ap_pass = pw
289
290 _initiated_disconnect = false
291
292 var url = ""
293 if ap_server.begins_with("ws://") or ap_server.begins_with("wss://"):
294 url = ap_server
295 _try_wss = false
296 elif _try_wss:
297 url = "wss://" + ap_server
298 _try_wss = false
299 else:
300 url = "ws://" + ap_server
301 _try_wss = true
302
303 var err = _ws.connect_to_url(url)
304 if err != OK:
305 emit_signal(
306 "could_not_connect",
307 (
308 "Could not connect to Archipelago. Check that your server and port are correct. See the error log for more information. Error code: %d."
309 % err
310 )
311 )
312 global._print("Could not connect to AP: " + err)
313 return
314 _should_process = true
315
316 emit_signal("connect_status", "Connecting...")
317
318
319func sendMessage(msg):
320 var payload = JSON.stringify(msg)
321 _ws.send_text(payload)
322
323
324func requestDatapackages(games):
325 emit_signal("connect_status", "Downloading %s data package..." % games[0])
326
327 sendMessage([{"cmd": "GetDataPackage", "games": games}])
328
329
330func processDatapackages():
331 _item_id_to_name = {}
332 _location_id_to_name = {}
333 for game in _datapackages.keys():
334 var package = _datapackages[game]
335
336 _item_id_to_name[game] = {}
337 for item_name in package["item_name_to_id"].keys():
338 _item_id_to_name[game][int(package["item_name_to_id"][item_name])] = item_name
339
340 _location_id_to_name[game] = {}
341 for location_name in package["location_name_to_id"].keys():
342 _location_id_to_name[game][int(package["location_name_to_id"][location_name])] = location_name
343
344 if _datapackages.has("Lingo 2"):
345 _item_name_to_id = _datapackages["Lingo 2"]["item_name_to_id"]
346 _location_name_to_id = _datapackages["Lingo 2"]["location_name_to_id"]
347
348
349func connectToRoom():
350 emit_signal("connect_status", "Authenticating...")
351
352 sendMessage(
353 [
354 {
355 "cmd": "Connect",
356 "password": ap_pass,
357 "game": "Lingo 2",
358 "name": ap_user,
359 "uuid": SCRIPT_uuid.v4(),
360 "version": ap_version,
361 "items_handling": 0b111, # always receive our items
362 "tags": [],
363 "slot_data": true
364 }
365 ]
366 )
367
368
369func sendConnectUpdate(tags):
370 sendMessage([{"cmd": "ConnectUpdate", "tags": tags}])
371
372
373func requestSync():
374 sendMessage([{"cmd": "Sync"}])
375
376
377func sendLocation(loc_id):
378 sendMessage([{"cmd": "LocationChecks", "locations": [loc_id]}])
379
380
381func sendLocations(loc_ids):
382 sendMessage([{"cmd": "LocationChecks", "locations": loc_ids}])
383
384
385func setValue(key, value, operation = "replace"):
386 sendMessage(
387 [
388 {
389 "cmd": "Set",
390 "key": "Lingo2_%d_%s" % [_slot, key],
391 "want_reply": false,
392 "operations": [{"operation": operation, "value": value}]
393 }
394 ]
395 )
396
397
398func say(textdata):
399 sendMessage([{"cmd": "Say", "text": textdata}])
400
401
402func completedGoal():
403 sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL
404
405
406func scoutLocations(loc_ids):
407 sendMessage([{"cmd": "LocationScouts", "locations": loc_ids}])
408
409
410func hasItem(item_id):
411 return _received_items.has(item_id)
412
413
414func getItemAmount(item_id):
415 return _received_items.get(item_id, 0)
diff --git a/client/Archipelago/collectable.gd b/client/Archipelago/collectable.gd deleted file mode 100644 index 4a17a2a..0000000 --- a/client/Archipelago/collectable.gd +++ /dev/null
@@ -1,16 +0,0 @@
1extends "res://scripts/nodes/collectable.gd"
2
3
4func pickedUp():
5 if unlock_type == "key":
6 var ap = global.get_node("Archipelago")
7 if ap.get_letter_behavior(unlock_key, level == 2) == ap.kLETTER_BEHAVIOR_VANILLA:
8 ap.keyboard.collect_local_letter(unlock_key, level)
9 else:
10 ap.keyboard.update_unlocks()
11
12 super.pickedUp()
13
14
15func setScoutedText(text):
16 get_node("MeshInstance3D").mesh.text = text.replace(" ", "\n")
diff --git a/client/Archipelago/door.gd b/client/Archipelago/door.gd deleted file mode 100644 index fead818..0000000 --- a/client/Archipelago/door.gd +++ /dev/null
@@ -1,38 +0,0 @@
1extends "res://scripts/nodes/door.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/gamedata.gd b/client/Archipelago/gamedata.gd deleted file mode 100644 index d8d16ed..0000000 --- a/client/Archipelago/gamedata.gd +++ /dev/null
@@ -1,133 +0,0 @@
1extends Node
2
3var SCRIPT_proto
4
5var objects
6var door_id_by_map_node_path = {}
7var painting_id_by_map_node_path = {}
8var panel_id_by_map_node_path = {}
9var door_id_by_ap_id = {}
10var map_id_by_name = {}
11var progressive_id_by_ap_id = {}
12var letter_id_by_ap_id = {}
13var symbol_item_ids = []
14
15var kSYMBOL_ITEMS
16
17
18func _init(proto_script):
19 SCRIPT_proto = proto_script
20
21 kSYMBOL_ITEMS = {
22 SCRIPT_proto.PuzzleSymbol.SUN: "Sun Symbol",
23 SCRIPT_proto.PuzzleSymbol.SPARKLES: "Sparkles Symbol",
24 SCRIPT_proto.PuzzleSymbol.ZERO: "Zero Symbol",
25 SCRIPT_proto.PuzzleSymbol.EXAMPLE: "Example Symbol",
26 SCRIPT_proto.PuzzleSymbol.BOXES: "Boxes Symbol",
27 SCRIPT_proto.PuzzleSymbol.PLANET: "Planet Symbol",
28 SCRIPT_proto.PuzzleSymbol.PYRAMID: "Pyramid Symbol",
29 SCRIPT_proto.PuzzleSymbol.CROSS: "Cross Symbol",
30 SCRIPT_proto.PuzzleSymbol.SWEET: "Sweet Symbol",
31 SCRIPT_proto.PuzzleSymbol.GENDER: "Gender Symbol",
32 SCRIPT_proto.PuzzleSymbol.AGE: "Age Symbol",
33 SCRIPT_proto.PuzzleSymbol.SOUND: "Sound Symbol",
34 SCRIPT_proto.PuzzleSymbol.ANAGRAM: "Anagram Symbol",
35 SCRIPT_proto.PuzzleSymbol.JOB: "Job Symbol",
36 SCRIPT_proto.PuzzleSymbol.STARS: "Stars Symbol",
37 SCRIPT_proto.PuzzleSymbol.NULL: "Null Symbol",
38 SCRIPT_proto.PuzzleSymbol.EVAL: "Eval Symbol",
39 SCRIPT_proto.PuzzleSymbol.LINGO: "Lingo Symbol",
40 SCRIPT_proto.PuzzleSymbol.QUESTION: "Question Symbol",
41 }
42
43
44func load(data_bytes):
45 objects = SCRIPT_proto.AllObjects.new()
46
47 var result_code = objects.from_bytes(data_bytes)
48 if result_code != SCRIPT_proto.PB_ERR.NO_ERRORS:
49 print("Could not load generated data: %d" % result_code)
50 return
51
52 for map in objects.get_maps():
53 map_id_by_name[map.get_name()] = map.get_id()
54
55 for door in objects.get_doors():
56 var map = objects.get_maps()[door.get_map_id()]
57
58 if not map.get_name() in door_id_by_map_node_path:
59 door_id_by_map_node_path[map.get_name()] = {}
60
61 var map_data = door_id_by_map_node_path[map.get_name()]
62 for receiver in door.get_receivers():
63 map_data[receiver] = door.get_id()
64
65 for painting_id in door.get_move_paintings():
66 var painting = objects.get_paintings()[painting_id]
67 map_data[painting.get_path()] = door.get_id()
68
69 if door.has_ap_id():
70 door_id_by_ap_id[door.get_ap_id()] = door.get_id()
71
72 for painting in objects.get_paintings():
73 var room = objects.get_rooms()[painting.get_room_id()]
74 var map = objects.get_maps()[room.get_map_id()]
75
76 if not map.get_name() in painting_id_by_map_node_path:
77 painting_id_by_map_node_path[map.get_name()] = {}
78
79 var _map_data = painting_id_by_map_node_path[map.get_name()]
80
81 for progressive in objects.get_progressives():
82 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id()
83
84 for letter in objects.get_letters():
85 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id()
86
87 for panel in objects.get_panels():
88 var room = objects.get_rooms()[panel.get_room_id()]
89 var map = objects.get_maps()[room.get_map_id()]
90
91 if not map.get_name() in panel_id_by_map_node_path:
92 panel_id_by_map_node_path[map.get_name()] = {}
93
94 var map_data = panel_id_by_map_node_path[map.get_name()]
95 map_data[panel.get_path()] = panel.get_id()
96
97 for symbol_name in kSYMBOL_ITEMS.values():
98 symbol_item_ids.append(objects.get_special_ids()[symbol_name])
99
100
101func get_door_for_map_node_path(map_name, node_path):
102 if not door_id_by_map_node_path.has(map_name):
103 return null
104
105 var map_data = door_id_by_map_node_path[map_name]
106 return map_data.get(node_path, null)
107
108
109func get_panel_for_map_node_path(map_name, node_path):
110 if not panel_id_by_map_node_path.has(map_name):
111 return null
112
113 var map_data = panel_id_by_map_node_path[map_name]
114 return map_data.get(node_path, null)
115
116
117func get_door_ap_id(door_id):
118 var door = objects.get_doors()[door_id]
119 if door.has_ap_id():
120 return door.get_ap_id()
121 else:
122 return null
123
124
125func get_door_receivers(door_id):
126 var door = objects.get_doors()[door_id]
127 return door.get_receivers()
128
129
130func get_door_map_name(door_id):
131 var door = objects.get_doors()[door_id]
132 var map = objects.get_maps()[door.get_map_id()]
133 return map.get_name()
diff --git a/client/Archipelago/keyHolder.gd b/client/Archipelago/keyHolder.gd deleted file mode 100644 index 3c037ff..0000000 --- a/client/Archipelago/keyHolder.gd +++ /dev/null
@@ -1,38 +0,0 @@
1extends "res://scripts/nodes/keyHolder.gd"
2
3
4func setFromAp(key, level):
5 if level > 0:
6 has_key = true
7 is_complete = "%s%d" % [key, level]
8 held_key = key
9 held_level = level
10 get_node("Hinge/Letter").mesh.text = held_key
11 get_node("Hinge/Letter2").mesh.text = held_key
12 setMaterial()
13 emit_signal("trigger")
14 else:
15 has_key = false
16 held_key = ""
17 held_level = 0
18 setMaterial()
19 get_node("Hinge/Letter").mesh.text = "-"
20 get_node("Hinge/Letter2").mesh.text = "-"
21 is_complete = ""
22 emit_signal("untrigger")
23
24
25func addKey(key):
26 var node_path = String(
27 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
28 )
29 var ap = global.get_node("Archipelago")
30 ap.keyboard.put_in_keyholder(key, global.map, node_path)
31
32
33func removeKey():
34 var node_path = String(
35 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
36 )
37 var ap = global.get_node("Archipelago")
38 ap.keyboard.remove_from_keyholder(held_key, global.map, node_path)
diff --git a/client/Archipelago/keyHolderChecker.gd b/client/Archipelago/keyHolderChecker.gd deleted file mode 100644 index a75a9e4..0000000 --- a/client/Archipelago/keyHolderChecker.gd +++ /dev/null
@@ -1,24 +0,0 @@
1extends "res://scripts/nodes/listeners/keyHolderChecker.gd"
2
3
4func check():
5 var ap = global.get_node("Archipelago")
6 var matches = []
7 for map in ap.keyboard.keyholder_state.keys():
8 var nodes = ap.keyboard.keyholder_state[map]
9 for node in nodes.keys():
10 matches.append([nodes[node], 1, map, "/root/scene/%s" % node])
11
12 var count = 0
13 for key_match in matches:
14 var active = (
15 key_match[2] + String(key_match[3]).replace("/root/scene/Components/KeyHolders/", ".")
16 )
17 if map[active] == key_match[0]:
18 emit_signal("trigger_letter", key_match[0], true)
19 count += 1
20 else:
21 emit_signal("trigger_letter", key_match[0], false)
22
23 if count > 25:
24 emit_signal("trigger")
diff --git a/client/Archipelago/keyHolderResetterListener.gd b/client/Archipelago/keyHolderResetterListener.gd deleted file mode 100644 index d5300f3..0000000 --- a/client/Archipelago/keyHolderResetterListener.gd +++ /dev/null
@@ -1,8 +0,0 @@
1extends "res://scripts/nodes/listeners/keyHolderResetterListener.gd"
2
3
4func reset():
5 var ap = global.get_node("Archipelago")
6 var was_removed = ap.keyboard.reset_keyholders()
7 if was_removed:
8 sfxPlayer.sfx_play("pickup")
diff --git a/client/Archipelago/keyboard.gd b/client/Archipelago/keyboard.gd deleted file mode 100644 index 600a047..0000000 --- a/client/Archipelago/keyboard.gd +++ /dev/null
@@ -1,178 +0,0 @@
1extends Node
2
3const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz"
4
5var letters_saved = {}
6var letters_in_keyholders = []
7var letters_dynamic = {}
8var keyholder_state = {}
9
10var filename = ""
11
12
13func _init():
14 reset()
15
16
17func reset():
18 letters_saved.clear()
19 letters_in_keyholders.clear()
20 letters_dynamic.clear()
21 keyholder_state.clear()
22
23
24func load_seed():
25 var ap = global.get_node("Archipelago")
26
27 reset()
28
29 filename = "user://archipelago_keys/%s_%d" % [ap.client._seed, ap.client._slot]
30
31 if FileAccess.file_exists(filename):
32 var ap_file = FileAccess.open(filename, FileAccess.READ)
33 var localdata = []
34 if ap_file != null:
35 localdata = ap_file.get_var(true)
36 ap_file.close()
37
38 if typeof(localdata) != TYPE_ARRAY:
39 print("AP keyboard file is corrupted")
40 localdata = []
41
42 if localdata.size() > 0:
43 letters_saved = localdata[0]
44 if localdata.size() > 1:
45 letters_in_keyholders = localdata[1]
46 if localdata.size() > 2:
47 keyholder_state = localdata[2]
48
49 for k in kALL_LETTERS:
50 var level = 0
51
52 if ap.get_letter_behavior(k, false) == ap.kLETTER_BEHAVIOR_UNLOCKED:
53 level += 1
54 if ap.get_letter_behavior(k, true) == ap.kLETTER_BEHAVIOR_UNLOCKED:
55 level += 1
56
57 letters_dynamic[k] = level
58
59 update_unlocks()
60
61
62func save():
63 var dir = DirAccess.open("user://")
64 var folder = "archipelago_keys"
65 if not dir.dir_exists(folder):
66 dir.make_dir(folder)
67
68 var file = FileAccess.open(filename, FileAccess.WRITE)
69
70 var data = [
71 letters_saved,
72 letters_in_keyholders,
73 keyholder_state,
74 ]
75 file.store_var(data, true)
76 file.close()
77
78
79func update_unlocks():
80 unlocks.resetKeys()
81
82 var has_doubles = false
83
84 for k in kALL_LETTERS:
85 var level = 0
86
87 if not letters_in_keyholders.has(k):
88 level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0)
89
90 if level >= 2:
91 level = 2
92 has_doubles = true
93
94 unlocks.unlockKey(k, level)
95
96 if has_doubles and unlocks.data["double_letters"] != "unlocked":
97 var ap = global.get_node("Archipelago")
98 if ap.cyan_door_behavior == ap.kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER:
99 unlocks.setData("double_letters", "unlocked")
100
101
102func collect_local_letter(key, level):
103 if level < 0 or level > 2 or level < letters_saved.get(key, 0):
104 return
105
106 letters_saved[key] = level
107
108 update_unlocks()
109 save()
110
111
112func collect_remote_letter(key, level):
113 if level < 0 or level > 2 or level < letters_dynamic.get(key, 0):
114 return
115
116 letters_dynamic[key] = level
117
118 update_unlocks()
119 save()
120
121
122func put_in_keyholder(key, map, kh_path):
123 if not keyholder_state.has(map):
124 keyholder_state[map] = {}
125
126 keyholder_state[map][kh_path] = key
127 letters_in_keyholders.append(key)
128
129 get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(
130 key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
131 )
132
133 update_unlocks()
134 save()
135
136
137func remove_from_keyholder(key, map, kh_path):
138 if not keyholder_state.has(map):
139 # This... shouldn't happen.
140 keyholder_state[map] = {}
141
142 keyholder_state[map].erase(kh_path)
143 letters_in_keyholders.erase(key)
144
145 get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(key, 0)
146
147 update_unlocks()
148 save()
149
150
151func load_keyholders(map):
152 if keyholder_state.has(map):
153 var khs = keyholder_state[map]
154
155 for path in khs.keys():
156 var key = khs[path]
157 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
158 key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
159 )
160
161
162func reset_keyholders():
163 if letters_in_keyholders.is_empty():
164 return false
165
166 if keyholder_state.has(global.map):
167 for path in keyholder_state[global.map]:
168 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
169 keyholder_state[global.map][path], 0
170 )
171
172 keyholder_state.clear()
173 letters_in_keyholders.clear()
174
175 update_unlocks()
176 save()
177
178 return true
diff --git a/client/Archipelago/locationListener.gd b/client/Archipelago/locationListener.gd deleted file mode 100644 index 71792ed..0000000 --- a/client/Archipelago/locationListener.gd +++ /dev/null
@@ -1,20 +0,0 @@
1extends Receiver
2
3var location_id
4
5
6func _ready():
7 super._ready()
8
9
10func handleTriggered():
11 triggered += 1
12 if triggered >= total:
13 var ap = global.get_node("Archipelago")
14 ap.send_location(location_id)
15
16
17func handleUntriggered():
18 triggered -= 1
19 if triggered < total:
20 pass
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)
diff --git a/client/Archipelago/messages.gd b/client/Archipelago/messages.gd deleted file mode 100644 index 52f38b9..0000000 --- a/client/Archipelago/messages.gd +++ /dev/null
@@ -1,61 +0,0 @@
1extends CanvasLayer
2
3var _message_queue = []
4var _font
5var _container
6var _ordered_labels = []
7
8
9func _ready():
10 _container = VBoxContainer.new()
11 _container.set_name("Container")
12 _container.anchor_bottom = 1
13 _container.offset_left = 20.0
14 _container.offset_right = 1920.0
15 _container.offset_top = 0.0
16 _container.offset_bottom = -20.0
17 _container.alignment = BoxContainer.ALIGNMENT_END
18 _container.mouse_filter = Control.MOUSE_FILTER_IGNORE
19 self.add_child(_container)
20
21 _font = load("res://assets/fonts/Lingo2.ttf")
22
23
24func _add_message(text):
25 var new_label = RichTextLabel.new()
26 new_label.push_font(_font)
27 new_label.push_font_size(36)
28 new_label.push_outline_color(Color(0, 0, 0, 1))
29 new_label.push_outline_size(2)
30 new_label.append_text(text)
31 new_label.fit_content = true
32
33 _container.add_child(new_label)
34 _ordered_labels.push_back(new_label)
35
36
37func showMessage(text):
38 if _ordered_labels.size() >= 9:
39 _message_queue.append(text)
40 return
41
42 _add_message(text)
43
44 if _ordered_labels.size() > 1:
45 return
46
47 var timeout = 10.0
48 while !_ordered_labels.is_empty():
49 await get_tree().create_timer(timeout).timeout
50
51 var to_remove = _ordered_labels.pop_front()
52 var to_tween = get_tree().create_tween().bind_node(to_remove)
53 to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5)
54 to_tween.tween_callback(to_remove.queue_free)
55
56 if !_message_queue.is_empty():
57 var next_msg = _message_queue.pop_front()
58 _add_message(next_msg)
59
60 if timeout > 4:
61 timeout -= 3
diff --git a/client/Archipelago/painting.gd b/client/Archipelago/painting.gd deleted file mode 100644 index 276d4eb..0000000 --- a/client/Archipelago/painting.gd +++ /dev/null
@@ -1,38 +0,0 @@
1extends "res://scripts/nodes/painting.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/panel.gd b/client/Archipelago/panel.gd deleted file mode 100644 index fdaaf0e..0000000 --- a/client/Archipelago/panel.gd +++ /dev/null
@@ -1,101 +0,0 @@
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 deleted file mode 100644 index 5da114a..0000000 --- a/client/Archipelago/pauseMenu.gd +++ /dev/null
@@ -1,12 +0,0 @@
1extends "res://scripts/ui/pauseMenu.gd"
2
3
4func _pause_game():
5 global.get_node("Textclient").dismiss()
6 super._pause_game()
7
8
9func _main_menu():
10 global.loaded = false
11 global.get_node("Archipelago").disconnect_from_ap()
12 super._main_menu()
diff --git a/client/Archipelago/player.gd b/client/Archipelago/player.gd deleted file mode 100644 index 9de3e07..0000000 --- a/client/Archipelago/player.gd +++ /dev/null
@@ -1,238 +0,0 @@
1extends "res://scripts/nodes/player.gd"
2
3const kEndingNameByVictoryValue = {
4 0: "GRAY",
5 1: "PURPLE",
6 2: "MINT",
7 3: "BLACK",
8 4: "BLUE",
9 5: "CYAN",
10 6: "RED",
11 7: "PLUM",
12 8: "ORANGE",
13 9: "GOLD",
14 10: "YELLOW",
15 11: "GREEN",
16 12: "WHITE",
17}
18
19signal evaluate_solvability
20
21
22func _ready():
23 var khl_script = load("res://scripts/nodes/keyHolderListener.gd")
24
25 var ap = global.get_node("Archipelago")
26 var gamedata = global.get_node("Gamedata")
27
28 ap.start_batching_locations()
29
30 # Set up door locations.
31 var map_id = gamedata.map_id_by_name.get(global.map)
32 for door in gamedata.objects.get_doors():
33 if door.get_map_id() != map_id:
34 continue
35
36 if not door.has_ap_id():
37 continue
38
39 if door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY:
40 continue
41
42 var locationListener = ap.SCRIPT_locationListener.new()
43 locationListener.location_id = door.get_ap_id()
44 locationListener.name = "locationListener_%d" % door.get_ap_id()
45
46 for panel_ref in door.get_panels():
47 var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()]
48 var panel_path = panel_data.get_path()
49
50 if panel_ref.has_answer():
51 for proxy in panel_data.get_proxies():
52 if proxy.get_answer() == panel_ref.get_answer():
53 panel_path = proxy.get_path()
54 break
55
56 locationListener.senders.append(NodePath("/root/scene/" + panel_path))
57
58 for keyholder_ref in door.get_keyholders():
59 var keyholder_data = gamedata.objects.get_keyholders()[keyholder_ref.get_keyholder()]
60
61 var khl = khl_script.new()
62 khl.name = (
63 "location_%d_keyholder_%d" % [door.get_ap_id(), keyholder_ref.get_keyholder()]
64 )
65 khl.answer = keyholder_ref.get_key()
66 khl.senders.append(NodePath("/root/scene/" + keyholder_data.get_path()))
67 get_parent().add_child.call_deferred(khl)
68
69 locationListener.senders.append(NodePath("../" + khl.name))
70
71 get_parent().add_child.call_deferred(locationListener)
72
73 # Set up letter locations.
74 for letter in gamedata.objects.get_letters():
75 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
76 if room.get_map_id() != map_id:
77 continue
78
79 var locationListener = ap.SCRIPT_locationListener.new()
80 locationListener.location_id = letter.get_ap_id()
81 locationListener.name = "locationListener_%d" % letter.get_ap_id()
82 locationListener.senders.append(NodePath("/root/scene/" + letter.get_path()))
83
84 get_parent().add_child.call_deferred(locationListener)
85
86 if (
87 ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2())
88 != ap.kLETTER_BEHAVIOR_VANILLA
89 ):
90 var scout = ap.scout_location(letter.get_ap_id())
91 if scout != null:
92 var item_name = "Unknown"
93 var item_player_game = ap.client._game_by_player[float(scout["player"])]
94 if ap.client._item_id_to_name[item_player_game].has(scout["item"]):
95 item_name = ap.client._item_id_to_name[item_player_game][scout["item"]]
96
97 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
98 letter.get_path()
99 )
100 if collectable != null:
101 collectable.setScoutedText.call_deferred(item_name)
102
103 # Set up mastery locations.
104 for mastery in gamedata.objects.get_masteries():
105 var room = gamedata.objects.get_rooms()[mastery.get_room_id()]
106 if room.get_map_id() != map_id:
107 continue
108
109 var locationListener = ap.SCRIPT_locationListener.new()
110 locationListener.location_id = mastery.get_ap_id()
111 locationListener.name = "locationListener_%d" % mastery.get_ap_id()
112 locationListener.senders.append(NodePath("/root/scene/" + mastery.get_path()))
113
114 get_parent().add_child.call_deferred(locationListener)
115
116 # Set up ending locations.
117 for ending in gamedata.objects.get_endings():
118 var room = gamedata.objects.get_rooms()[ending.get_room_id()]
119 if room.get_map_id() != map_id:
120 continue
121
122 var locationListener = ap.SCRIPT_locationListener.new()
123 locationListener.location_id = ending.get_ap_id()
124 locationListener.name = "locationListener_%d" % ending.get_ap_id()
125 locationListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
126
127 get_parent().add_child.call_deferred(locationListener)
128
129 if kEndingNameByVictoryValue.get(ap.victory_condition, null) == ending.get_name():
130 var victoryListener = ap.SCRIPT_victoryListener.new()
131 victoryListener.name = "victoryListener"
132 victoryListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
133
134 get_parent().add_child.call_deferred(victoryListener)
135
136 # Set up keyholder locations, in keyholder sanity.
137 if ap.keyholder_sanity:
138 for keyholder in gamedata.objects.get_keyholders():
139 if not keyholder.has_key():
140 continue
141
142 var room = gamedata.objects.get_rooms()[keyholder.get_room_id()]
143 if room.get_map_id() != map_id:
144 continue
145
146 var locationListener = ap.SCRIPT_locationListener.new()
147 locationListener.location_id = keyholder.get_ap_id()
148 locationListener.name = "locationListener_%d" % keyholder.get_ap_id()
149
150 var khl = khl_script.new()
151 khl.name = "location_%d_keyholder" % keyholder.get_ap_id()
152 khl.answer = keyholder.get_key()
153 khl.senders.append(NodePath("/root/scene/" + keyholder.get_path()))
154 get_parent().add_child.call_deferred(khl)
155
156 locationListener.senders.append(NodePath("../" + khl.name))
157
158 get_parent().add_child.call_deferred(locationListener)
159
160 # Block off roof access in Daedalus.
161 if global.map == "daedalus" and not ap.daedalus_roof_access:
162 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49)
163 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1)
164 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10)
165 _set_up_invis_wall(67.5, 11, 17, 16, 10, 1)
166 _set_up_invis_wall(50.5, 11, 14, 10, 10, 1)
167 _set_up_invis_wall(39, 10, 18.5, 1, 10, 22)
168 _set_up_invis_wall(20, 15, 18.5, 1, 10, 16)
169 _set_up_invis_wall(11.5, 15, 3, 32, 10, 1)
170 _set_up_invis_wall(11.5, 16, -20, 14, 20, 1)
171 _set_up_invis_wall(14, 16, -26.5, 1, 20, 4)
172 _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25)
173 _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1)
174 _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1)
175 _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11)
176 _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11)
177
178 var warp_exit_prefab = preload("res://objects/nodes/exit.tscn")
179 var warp_exit = warp_exit_prefab.instantiate()
180 warp_exit.name = "roof_access_blocker_warp_exit"
181 warp_exit.position = Vector3(58, 10, 0)
182 warp_exit.rotation_degrees.y = 90
183 get_parent().add_child.call_deferred(warp_exit)
184
185 var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn")
186 var warp_enter = warp_enter_prefab.instantiate()
187 warp_enter.target = warp_exit
188 warp_enter.position = Vector3(76.5, 30, 1)
189 warp_enter.scale = Vector3(4, 1.5, 1)
190 warp_enter.rotation_degrees.y = 90
191 get_parent().add_child.call_deferred(warp_enter)
192
193 if global.map == "the_entry":
194 # Remove door behind X1.
195 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
196 door_node.handleTriggered()
197
198 # Display win condition.
199 var sign_prefab = preload("res://objects/nodes/sign.tscn")
200 var sign1 = sign_prefab.instantiate()
201 sign1.position = Vector3(-7, 5, -15.01)
202 sign1.text = "victory"
203 get_parent().add_child.call_deferred(sign1)
204
205 var sign2 = sign_prefab.instantiate()
206 sign2.position = Vector3(-7, 4, -15.01)
207 sign2.text = "%s ending" % kEndingNameByVictoryValue.get(ap.victory_condition, "?")
208
209 var sign2_color = kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
210 if sign2_color == "white":
211 sign2_color = "silver"
212
213 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
214 get_parent().add_child.call_deferred(sign2)
215
216 super._ready()
217
218 await get_tree().process_frame
219 await get_tree().process_frame
220
221 ap.stop_batching_locations()
222
223
224func _set_up_invis_wall(x, y, z, sx, sy, sz):
225 var prefab = preload("res://objects/nodes/block.tscn")
226 var newwall = prefab.instantiate()
227 newwall.position.x = x
228 newwall.position.y = y
229 newwall.position.z = z
230 newwall.scale.x = sz
231 newwall.scale.y = sy
232 newwall.scale.z = sx
233 newwall.set_surface_override_material(0, preload("res://assets/materials/blackMatte.material"))
234 newwall.visibility_range_end = 3
235 newwall.visibility_range_end_margin = 1
236 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF
237 newwall.skeleton = ".."
238 get_parent().add_child.call_deferred(newwall)
diff --git a/client/Archipelago/saver.gd b/client/Archipelago/saver.gd deleted file mode 100644 index 0fba9e7..0000000 --- a/client/Archipelago/saver.gd +++ /dev/null
@@ -1,9 +0,0 @@
1extends "res://scripts/nodes/saver.gd"
2
3
4func levelLoaded():
5 if type == "keyholders":
6 var ap = global.get_node("Archipelago")
7 ap.keyboard.load_keyholders.call_deferred(global.map)
8 else:
9 reload.call_deferred()
diff --git a/client/Archipelago/settings_buttons.gd b/client/Archipelago/settings_buttons.gd deleted file mode 100644 index 9e61cb0..0000000 --- a/client/Archipelago/settings_buttons.gd +++ /dev/null
@@ -1,24 +0,0 @@
1extends Button
2
3
4func _ready():
5 pass
6
7
8func _connect_pressed():
9 self.disabled = true
10
11 var ap = global.get_node("Archipelago")
12 ap.ap_server = self.get_parent().get_node("server_box").text
13 ap.ap_user = self.get_parent().get_node("player_box").text
14 ap.ap_pass = self.get_parent().get_node("password_box").text
15 ap.saveSettings()
16
17 ap.connectToServer()
18
19
20func _back_pressed():
21 var ap = global.get_node("Archipelago")
22 ap.disconnect_from_ap()
23
24 get_tree().change_scene_to_file("res://objects/scenes/menus/main_menu.tscn")
diff --git a/client/Archipelago/settings_screen.gd b/client/Archipelago/settings_screen.gd deleted file mode 100644 index ff6f9df..0000000 --- a/client/Archipelago/settings_screen.gd +++ /dev/null
@@ -1,204 +0,0 @@
1extends Node2D
2
3
4func _ready():
5 # Some helpful logging.
6 if Steam.isSubscribed():
7 global._print("Provisioning successful! Build ID: %d" % Steam.getAppBuildId())
8 else:
9 global._print("Provisioning failed.")
10
11 # Undo the load screen removing our cursor
12 get_tree().get_root().set_disable_input(false)
13 Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
14
15 # Increase the WebSocket input buffer size so that we can download large
16 # data packages.
17 ProjectSettings.set_setting("network/limits/websocket_client/max_in_buffer_kb", 8192)
18
19 # Create the global AP manager, if it doesn't already exist.
20 if not global.has_node("Archipelago"):
21 var ap_script = ResourceLoader.load("user://maps/Archipelago/manager.gd")
22 var ap_instance = ap_script.new()
23 ap_instance.name = "Archipelago"
24
25 ap_instance.SCRIPT_client = load("user://maps/Archipelago/client.gd")
26 ap_instance.SCRIPT_keyboard = load("user://maps/Archipelago/keyboard.gd")
27 ap_instance.SCRIPT_locationListener = load("user://maps/Archipelago/locationListener.gd")
28 ap_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd")
29 ap_instance.SCRIPT_victoryListener = load("user://maps/Archipelago/victoryListener.gd")
30
31 global.add_child(ap_instance)
32
33 # Let's also inject any scripts we need to inject now.
34 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/animationListener.gd"))
35 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/collectable.gd"))
36 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/door.gd"))
37 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolder.gd"))
38 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolderChecker.gd"))
39 installScriptExtension(
40 ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd")
41 )
42 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd"))
43 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/panel.gd"))
44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd"))
45 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd"))
46 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd"))
48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd"))
49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd"))
50
51 var proto_script = load("user://maps/Archipelago/generated/proto.gd")
52 var gamedata_script = load("user://maps/Archipelago/gamedata.gd")
53 var gamedata_instance = gamedata_script.new(proto_script)
54 gamedata_instance.load(
55 FileAccess.get_file_as_bytes("user://maps/Archipelago/generated/data.binpb")
56 )
57 gamedata_instance.name = "Gamedata"
58 global.add_child(gamedata_instance)
59
60 var messages_script = load("user://maps/Archipelago/messages.gd")
61 var messages_instance = messages_script.new()
62 messages_instance.name = "Messages"
63 global.add_child(messages_instance)
64
65 var textclient_script = load("user://maps/Archipelago/textclient.gd")
66 var textclient_instance = textclient_script.new()
67 textclient_instance.name = "Textclient"
68 global.add_child(textclient_instance)
69
70 var ap = global.get_node("Archipelago")
71 var gamedata = global.get_node("Gamedata")
72 ap.connect("ap_connected", connectionSuccessful)
73 ap.connect("could_not_connect", connectionUnsuccessful)
74 ap.connect("connect_status", connectionStatus)
75
76 # Populate textboxes with AP settings.
77 $Panel/server_box.text = ap.ap_server
78 $Panel/player_box.text = ap.ap_user
79 $Panel/password_box.text = ap.ap_pass
80
81 var history_box = $Panel/connection_history
82 if ap.connection_history.is_empty():
83 history_box.disabled = true
84 else:
85 history_box.disabled = false
86
87 var i = 0
88 for details in ap.connection_history:
89 history_box.get_popup().add_item("%s (%s)" % [details[1], details[0]], i)
90 i += 1
91
92 history_box.get_popup().connect("id_pressed", historySelected)
93
94 # Show client version.
95 $Panel/title.text = "ARCHIPELAGO (%d.%d)" % [gamedata.objects.get_version(), ap.MOD_VERSION]
96
97 # Increase font size in text boxes.
98 $Panel/server_box.add_theme_font_size_override("font_size", 36)
99 $Panel/player_box.add_theme_font_size_override("font_size", 36)
100 $Panel/password_box.add_theme_font_size_override("font_size", 36)
101
102
103# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd
104func installScriptExtension(childScript: Resource):
105 # Force Godot to compile the script now.
106 # We need to do this here to ensure that the inheritance chain is
107 # properly set up, and multiple mods can chain-extend the same
108 # class multiple times.
109 # This is also needed to make Godot instantiate the extended class
110 # when creating singletons.
111 # The actual instance is thrown away.
112 childScript.new()
113
114 var parentScript = childScript.get_base_script()
115 var parentScriptPath = parentScript.resource_path
116 global._print("ModLoader: Installing script extension over %s" % parentScriptPath)
117 childScript.take_over_path(parentScriptPath)
118
119
120func connectionStatus(message):
121 var popup = self.get_node("Panel/AcceptDialog")
122 popup.title = "Connecting to Archipelago"
123 popup.dialog_text = message
124 popup.exclusive = true
125 popup.get_ok_button().visible = false
126 popup.popup_centered()
127
128
129func connectionSuccessful():
130 var ap = global.get_node("Archipelago")
131
132 # Save connection details
133 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass]
134 if ap.connection_history.has(connection_details):
135 ap.connection_history.erase(connection_details)
136 ap.connection_history.push_front(connection_details)
137 if ap.connection_history.size() > 10:
138 ap.connection_history.resize(10)
139 ap.saveSettings()
140
141 # Switch to the_entry
142 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
143 global.user = ap.getSaveFileName()
144 global.universe = "lingo"
145 global.map = "the_entry"
146
147 unlocks.resetCollectables()
148 unlocks.resetData()
149
150 ap.setup_keys()
151
152 unlocks.loadCollectables()
153 unlocks.loadData()
154 unlocks.unlockKey("capslock", 1)
155
156 clearResourceCache("res://objects/meshes/gridDoor.tscn")
157 clearResourceCache("res://objects/nodes/collectable.tscn")
158 clearResourceCache("res://objects/nodes/door.tscn")
159 clearResourceCache("res://objects/nodes/keyHolder.tscn")
160 clearResourceCache("res://objects/nodes/listeners/animationListener.tscn")
161 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
162 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
163 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
164 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn")
165 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
166 clearResourceCache("res://objects/nodes/panel.tscn")
167 clearResourceCache("res://objects/nodes/player.tscn")
168 clearResourceCache("res://objects/nodes/saver.tscn")
169 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn")
170
171 var paintings_dir = DirAccess.open("res://objects/meshes/paintings")
172 if paintings_dir:
173 paintings_dir.list_dir_begin()
174 var file_name = paintings_dir.get_next()
175 while file_name != "":
176 if not paintings_dir.current_is_dir() and file_name.ends_with(".tscn"):
177 clearResourceCache("res://objects/meshes/paintings/" + file_name)
178 file_name = paintings_dir.get_next()
179
180 switcher.switch_map.call_deferred("res://objects/scenes/the_entry.tscn")
181
182
183func connectionUnsuccessful(error_message):
184 $Panel/connect_button.disabled = false
185
186 var popup = $Panel/AcceptDialog
187 popup.title = "Could not connect to Archipelago"
188 popup.dialog_text = error_message
189 popup.exclusive = true
190 popup.get_ok_button().visible = true
191 popup.popup_centered()
192
193
194func historySelected(index):
195 var ap = global.get_node("Archipelago")
196 var details = ap.connection_history[index]
197
198 $Panel/server_box.text = details[0]
199 $Panel/player_box.text = details[1]
200 $Panel/password_box.text = details[2]
201
202
203func clearResourceCache(path):
204 ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)
diff --git a/client/Archipelago/teleportListener.gd b/client/Archipelago/teleportListener.gd deleted file mode 100644 index 6f363af..0000000 --- a/client/Archipelago/teleportListener.gd +++ /dev/null
@@ -1,49 +0,0 @@
1extends "res://scripts/nodes/listeners/teleportListener.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 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
23 var gamedata = global.get_node("Gamedata")
24 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
25 if door_id != null:
26 var ap = global.get_node("Archipelago")
27 var item_lock = ap.get_item_id_for_door(door_id)
28
29 if item_lock != null:
30 item_id = item_lock[0]
31 item_amount = item_lock[1]
32
33 self.senders = []
34 self.senderGroup = []
35 self.nested = false
36 self.complete_at = 0
37 self.max_length = 0
38 self.excludeSenders = []
39
40 call_deferred("_readier")
41
42 super._ready()
43
44
45func _readier():
46 var ap = global.get_node("Archipelago")
47
48 if ap.client.getItemAmount(item_id) >= item_amount:
49 handleTriggered()
diff --git a/client/Archipelago/textclient.gd b/client/Archipelago/textclient.gd deleted file mode 100644 index 85cc6d2..0000000 --- a/client/Archipelago/textclient.gd +++ /dev/null
@@ -1,86 +0,0 @@
1extends CanvasLayer
2
3var panel
4var label
5var entry
6var is_open = false
7
8
9func _ready():
10 process_mode = ProcessMode.PROCESS_MODE_ALWAYS
11
12 panel = Panel.new()
13 panel.set_name("Panel")
14 panel.offset_left = 100
15 panel.offset_right = 1820
16 panel.offset_top = 100
17 panel.offset_bottom = 980
18 panel.visible = false
19 add_child(panel)
20
21 label = RichTextLabel.new()
22 label.set_name("Label")
23 label.offset_left = 80
24 label.offset_right = 1640
25 label.offset_top = 80
26 label.offset_bottom = 720
27 label.scroll_following = true
28 label.selection_enabled = true
29 panel.add_child(label)
30
31 label.push_font(load("res://assets/fonts/Lingo2.ttf"))
32 label.push_font_size(36)
33
34 var entry_style = StyleBoxFlat.new()
35 entry_style.bg_color = Color(0.9, 0.9, 0.9, 1)
36
37 entry = LineEdit.new()
38 entry.set_name("Entry")
39 entry.offset_left = 80
40 entry.offset_right = 1640
41 entry.offset_top = 760
42 entry.offset_bottom = 840
43 entry.add_theme_font_override("font", load("res://assets/fonts/Lingo2.ttf"))
44 entry.add_theme_font_size_override("font_size", 36)
45 entry.add_theme_color_override("font_color", Color(0, 0, 0, 1))
46 entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1))
47 entry.add_theme_stylebox_override("focus", entry_style)
48 panel.add_child(entry)
49 entry.connect("text_submitted", text_entered)
50
51
52func _input(event):
53 if global.loaded and event is InputEventKey and event.pressed:
54 if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT):
55 if !get_tree().paused:
56 is_open = true
57 get_tree().paused = true
58 Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
59 panel.visible = true
60 entry.grab_focus()
61 get_viewport().set_input_as_handled()
62 else:
63 dismiss()
64 elif event.keycode == KEY_ESCAPE:
65 if is_open:
66 dismiss()
67 get_viewport().set_input_as_handled()
68
69
70func dismiss():
71 if is_open:
72 get_tree().paused = false
73 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
74 panel.visible = false
75 is_open = false
76
77
78func parse_printjson(text):
79 label.append_text("[p]" + text + "[/p]")
80
81
82func text_entered(text):
83 var ap = global.get_node("Archipelago")
84 var cmd = text.trim_suffix("\n")
85 ap.client.say(cmd)
86 entry.text = ""
diff --git a/client/Archipelago/vendor/LICENSE b/client/Archipelago/vendor/LICENSE deleted file mode 100644 index 115ba15..0000000 --- a/client/Archipelago/vendor/LICENSE +++ /dev/null
@@ -1,21 +0,0 @@
1MIT License
2
3Copyright (c) 2023 Xavier Sellier
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE. \ No newline at end of file
diff --git a/client/Archipelago/vendor/uuid.gd b/client/Archipelago/vendor/uuid.gd deleted file mode 100644 index b63fa04..0000000 --- a/client/Archipelago/vendor/uuid.gd +++ /dev/null
@@ -1,195 +0,0 @@
1# Note: The code might not be as pretty it could be, since it's written
2# in a way that maximizes performance. Methods are inlined and loops are avoided.
3extends Node
4
5const BYTE_MASK: int = 0b11111111
6
7
8static func uuidbin():
9 randomize()
10 # 16 random bytes with the bytes on index 6 and 8 modified
11 return [
12 randi() & BYTE_MASK,
13 randi() & BYTE_MASK,
14 randi() & BYTE_MASK,
15 randi() & BYTE_MASK,
16 randi() & BYTE_MASK,
17 randi() & BYTE_MASK,
18 ((randi() & BYTE_MASK) & 0x0f) | 0x40,
19 randi() & BYTE_MASK,
20 ((randi() & BYTE_MASK) & 0x3f) | 0x80,
21 randi() & BYTE_MASK,
22 randi() & BYTE_MASK,
23 randi() & BYTE_MASK,
24 randi() & BYTE_MASK,
25 randi() & BYTE_MASK,
26 randi() & BYTE_MASK,
27 randi() & BYTE_MASK,
28 ]
29
30
31static func uuidbinrng(rng: RandomNumberGenerator):
32 rng.randomize()
33 return [
34 rng.randi() & BYTE_MASK,
35 rng.randi() & BYTE_MASK,
36 rng.randi() & BYTE_MASK,
37 rng.randi() & BYTE_MASK,
38 rng.randi() & BYTE_MASK,
39 rng.randi() & BYTE_MASK,
40 ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40,
41 rng.randi() & BYTE_MASK,
42 ((rng.randi() & BYTE_MASK) & 0x3f) | 0x80,
43 rng.randi() & BYTE_MASK,
44 rng.randi() & BYTE_MASK,
45 rng.randi() & BYTE_MASK,
46 rng.randi() & BYTE_MASK,
47 rng.randi() & BYTE_MASK,
48 rng.randi() & BYTE_MASK,
49 rng.randi() & BYTE_MASK,
50 ]
51
52
53static func v4():
54 # 16 random bytes with the bytes on index 6 and 8 modified
55 var b = uuidbin()
56
57 return (
58 "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
59 % [
60 # low
61 b[0],
62 b[1],
63 b[2],
64 b[3],
65 # mid
66 b[4],
67 b[5],
68 # hi
69 b[6],
70 b[7],
71 # clock
72 b[8],
73 b[9],
74 # clock
75 b[10],
76 b[11],
77 b[12],
78 b[13],
79 b[14],
80 b[15]
81 ]
82 )
83
84
85static func v4_rng(rng: RandomNumberGenerator):
86 # 16 random bytes with the bytes on index 6 and 8 modified
87 var b = uuidbinrng(rng)
88
89 return (
90 "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
91 % [
92 # low
93 b[0],
94 b[1],
95 b[2],
96 b[3],
97 # mid
98 b[4],
99 b[5],
100 # hi
101 b[6],
102 b[7],
103 # clock
104 b[8],
105 b[9],
106 # clock
107 b[10],
108 b[11],
109 b[12],
110 b[13],
111 b[14],
112 b[15]
113 ]
114 )
115
116
117var _uuid: Array
118
119
120func _init(rng := RandomNumberGenerator.new()) -> void:
121 _uuid = uuidbinrng(rng)
122
123
124func as_array() -> Array:
125 return _uuid.duplicate()
126
127
128func as_dict(big_endian := true) -> Dictionary:
129 if big_endian:
130 return {
131 "low": (_uuid[0] << 24) + (_uuid[1] << 16) + (_uuid[2] << 8) + _uuid[3],
132 "mid": (_uuid[4] << 8) + _uuid[5],
133 "hi": (_uuid[6] << 8) + _uuid[7],
134 "clock": (_uuid[8] << 8) + _uuid[9],
135 "node":
136 (
137 (_uuid[10] << 40)
138 + (_uuid[11] << 32)
139 + (_uuid[12] << 24)
140 + (_uuid[13] << 16)
141 + (_uuid[14] << 8)
142 + _uuid[15]
143 )
144 }
145 else:
146 return {
147 "low": _uuid[0] + (_uuid[1] << 8) + (_uuid[2] << 16) + (_uuid[3] << 24),
148 "mid": _uuid[4] + (_uuid[5] << 8),
149 "hi": _uuid[6] + (_uuid[7] << 8),
150 "clock": _uuid[8] + (_uuid[9] << 8),
151 "node":
152 (
153 _uuid[10]
154 + (_uuid[11] << 8)
155 + (_uuid[12] << 16)
156 + (_uuid[13] << 24)
157 + (_uuid[14] << 32)
158 + (_uuid[15] << 40)
159 )
160 }
161
162
163func as_string() -> String:
164 return (
165 "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
166 % [
167 # low
168 _uuid[0],
169 _uuid[1],
170 _uuid[2],
171 _uuid[3],
172 # mid
173 _uuid[4],
174 _uuid[5],
175 # hi
176 _uuid[6],
177 _uuid[7],
178 # clock
179 _uuid[8],
180 _uuid[9],
181 # node
182 _uuid[10],
183 _uuid[11],
184 _uuid[12],
185 _uuid[13],
186 _uuid[14],
187 _uuid[15]
188 ]
189 )
190
191
192func is_equal(other) -> bool:
193 # Godot Engine compares Array recursively
194 # There's no need for custom comparison here.
195 return _uuid == other._uuid
diff --git a/client/Archipelago/victoryListener.gd b/client/Archipelago/victoryListener.gd deleted file mode 100644 index e9089d7..0000000 --- a/client/Archipelago/victoryListener.gd +++ /dev/null
@@ -1,20 +0,0 @@
1extends Receiver
2
3
4func _ready():
5 super._ready()
6
7
8func handleTriggered():
9 triggered += 1
10 if triggered >= total:
11 var ap = global.get_node("Archipelago")
12 ap.client.completedGoal()
13
14 global.get_node("Messages").showMessage("You have completed your goal!")
15
16
17func handleUntriggered():
18 triggered -= 1
19 if triggered < total:
20 pass
diff --git a/client/Archipelago/visibilityListener.gd b/client/Archipelago/visibilityListener.gd deleted file mode 100644 index 5ea17a0..0000000 --- a/client/Archipelago/visibilityListener.gd +++ /dev/null
@@ -1,38 +0,0 @@
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/worldportListener.gd b/client/Archipelago/worldportListener.gd deleted file mode 100644 index 5c2faff..0000000 --- a/client/Archipelago/worldportListener.gd +++ /dev/null
@@ -1,8 +0,0 @@
1extends "res://scripts/nodes/listeners/worldportListener.gd"
2
3
4func handleTriggered():
5 if exit == "menus/credits":
6 return
7
8 super.handleTriggered()