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.gd417
-rw-r--r--client/Archipelago/collectable.gd16
-rw-r--r--client/Archipelago/compass.gd66
-rw-r--r--client/Archipelago/compass_overlay.gd17
-rw-r--r--client/Archipelago/door.gd46
-rw-r--r--client/Archipelago/gamedata.gd159
-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.gd199
-rw-r--r--client/Archipelago/locationListener.gd20
-rw-r--r--client/Archipelago/manager.gd571
-rw-r--r--client/Archipelago/messages.gd74
-rw-r--r--client/Archipelago/minimap.gd175
-rw-r--r--client/Archipelago/painting.gd38
-rw-r--r--client/Archipelago/panel.gd101
-rw-r--r--client/Archipelago/pauseMenu.gd44
-rw-r--r--client/Archipelago/player.gd369
-rw-r--r--client/Archipelago/rainbowText.gd10
-rw-r--r--client/Archipelago/saver.gd23
-rw-r--r--client/Archipelago/settings_buttons.gd24
-rw-r--r--client/Archipelago/settings_screen.gd261
-rw-r--r--client/Archipelago/teleport.gd38
-rw-r--r--client/Archipelago/teleportListener.gd49
-rw-r--r--client/Archipelago/textclient.gd95
-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/worldport.gd53
-rw-r--r--client/Archipelago/worldportListener.gd8
32 files changed, 0 insertions, 3255 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 843647d..0000000 --- a/client/Archipelago/client.gd +++ /dev/null
@@ -1,417 +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 _ws.inbound_buffer_size = 8388608
51
52 global._print("Instantiated APClient")
53
54 # Read AP datapackages from file, if there are any
55 if FileAccess.file_exists("user://ap_datapackages"):
56 var file = FileAccess.open("user://ap_datapackages", FileAccess.READ)
57 var data = file.get_var(true)
58 file.close()
59
60 if typeof(data) != TYPE_DICTIONARY:
61 global._print("AP datapackages file is corrupted")
62 data = {}
63
64 _datapackages = data
65
66 processDatapackages()
67
68
69func _ready():
70 pass
71 #_ws.connect("connection_closed", _closed)
72 #_ws.connect("connection_failed", _closed)
73 #_ws.connect("server_disconnected", _closed)
74 #_ws.connect("connection_error", _errored)
75 #_ws.connect("connection_established", _connected)
76
77
78func _reset_state():
79 _should_process = false
80 _authenticated = false
81 _try_wss = false
82 _has_connected = false
83 _received_items = {}
84 _received_indexes = []
85
86
87func _errored():
88 if _try_wss:
89 global._print("Could not connect to AP with ws://, now trying wss://")
90 connectToServer(ap_server, ap_user, ap_pass)
91 else:
92 global._print("AP connection failed")
93 _reset_state()
94
95 emit_signal(
96 "could_not_connect",
97 "Could not connect to Archipelago. Check that your server and port are correct. See the error log for more information."
98 )
99
100
101func _closed(_was_clean = true):
102 global._print("Connection closed")
103 _reset_state()
104
105 if not _initiated_disconnect:
106 emit_signal("could_not_connect", "Disconnected from Archipelago")
107
108 _initiated_disconnect = false
109
110
111func _connected(_proto = ""):
112 global._print("Connected!")
113 _try_wss = false
114
115
116func disconnect_from_ap():
117 _initiated_disconnect = true
118 _ws.close()
119
120
121func _process(_delta):
122 if _should_process:
123 _ws.poll()
124
125 var state = _ws.get_ready_state()
126 if state == WebSocketPeer.STATE_OPEN:
127 if not _has_connected:
128 _has_connected = true
129
130 _connected()
131
132 while _ws.get_available_packet_count():
133 var packet = _ws.get_packet()
134 global._print("Got data from server: " + packet.get_string_from_utf8())
135 var json = JSON.new()
136 var jserror = json.parse(packet.get_string_from_utf8())
137 if jserror != OK:
138 global._print("Error parsing packet from AP: " + jserror.error_string)
139 return
140
141 for message in json.data:
142 var cmd = message["cmd"]
143 global._print("Received command: " + cmd)
144
145 if cmd == "RoomInfo":
146 _seed = message["seed_name"]
147 _remote_version = message["version"]
148 _gen_version = message["generator_version"]
149
150 var needed_games = []
151 for game in message["datapackage_checksums"].keys():
152 if (
153 !_datapackages.has(game)
154 or (
155 _datapackages[game]["checksum"]
156 != message["datapackage_checksums"][game]
157 )
158 ):
159 needed_games.append(game)
160
161 if !needed_games.is_empty():
162 _pending_packages = needed_games
163 var cur_needed = _pending_packages.pop_front()
164 requestDatapackages([cur_needed])
165 else:
166 connectToRoom()
167
168 elif cmd == "DataPackage":
169 for game in message["data"]["games"].keys():
170 _datapackages[game] = message["data"]["games"][game]
171 saveDatapackages()
172
173 if !_pending_packages.is_empty():
174 var cur_needed = _pending_packages.pop_front()
175 requestDatapackages([cur_needed])
176 else:
177 processDatapackages()
178 connectToRoom()
179
180 elif cmd == "Connected":
181 _authenticated = true
182 _team = message["team"]
183 _slot = message["slot"]
184 _players = message["players"]
185 _checked_locations = message["checked_locations"]
186 _slot_data = message["slot_data"]
187
188 for player in _players:
189 _player_name_by_slot[player["slot"]] = player["alias"]
190 _game_by_player[player["slot"]] = message["slot_info"][str(
191 player["slot"]
192 )]["game"]
193
194 emit_signal("client_connected", _slot_data)
195
196 elif cmd == "ConnectionRefused":
197 var error_message = ""
198 for error in message["errors"]:
199 var submsg = ""
200 if error == "InvalidSlot":
201 submsg = "Invalid player name."
202 elif error == "InvalidGame":
203 submsg = "The specified player is not playing Lingo."
204 elif error == "IncompatibleVersion":
205 submsg = (
206 "The Archipelago server is not the correct version for this client. Expected v%d.%d.%d. Found v%d.%d.%d."
207 % [
208 ap_version["major"],
209 ap_version["minor"],
210 ap_version["build"],
211 _remote_version["major"],
212 _remote_version["minor"],
213 _remote_version["build"]
214 ]
215 )
216 elif error == "InvalidPassword":
217 submsg = "Incorrect password."
218 elif error == "InvalidItemsHandling":
219 submsg = "Invalid item handling flag. This is a bug with the client."
220
221 if submsg != "":
222 if error_message != "":
223 error_message += " "
224 error_message += submsg
225
226 if error_message == "":
227 error_message = "Unknown error."
228
229 _initiated_disconnect = true
230 _ws.close()
231
232 emit_signal("could_not_connect", error_message)
233 global._print("Connection to AP refused")
234 global._print(message)
235
236 elif cmd == "ReceivedItems":
237 var i = 0
238 for item in message["items"]:
239 var index = int(message["index"] + i)
240 i += 1
241
242 if _received_indexes.has(index):
243 # Do not re-process items.
244 continue
245
246 _received_indexes.append(index)
247
248 var item_id = int(item["item"])
249 _received_items[item_id] = _received_items.get(item_id, 0) + 1
250
251 emit_signal(
252 "item_received",
253 item_id,
254 index,
255 int(item["player"]),
256 int(item["flags"]),
257 _received_items[item_id]
258 )
259
260 elif cmd == "PrintJSON":
261 emit_signal("message_received", message)
262
263 elif cmd == "LocationInfo":
264 for loc in message["locations"]:
265 emit_signal(
266 "location_scout_received",
267 int(loc["item"]),
268 int(loc["location"]),
269 int(loc["player"]),
270 int(loc["flags"])
271 )
272
273 elif state == WebSocketPeer.STATE_CLOSED:
274 if _has_connected:
275 _closed()
276 else:
277 _errored()
278
279
280func saveDatapackages():
281 # Save the AP datapackages to disk.
282 var file = FileAccess.open("user://ap_datapackages", FileAccess.WRITE)
283 file.store_var(_datapackages, true)
284 file.close()
285
286
287func connectToServer(server, un, pw):
288 ap_server = server
289 ap_user = un
290 ap_pass = pw
291
292 _initiated_disconnect = false
293
294 var url = ""
295 if ap_server.begins_with("ws://") or ap_server.begins_with("wss://"):
296 url = ap_server
297 _try_wss = false
298 elif _try_wss:
299 url = "wss://" + ap_server
300 _try_wss = false
301 else:
302 url = "ws://" + ap_server
303 _try_wss = true
304
305 var err = _ws.connect_to_url(url)
306 if err != OK:
307 emit_signal(
308 "could_not_connect",
309 (
310 "Could not connect to Archipelago. Check that your server and port are correct. See the error log for more information. Error code: %d."
311 % err
312 )
313 )
314 global._print("Could not connect to AP: %d" % err)
315 return
316 _should_process = true
317
318 emit_signal("connect_status", "Connecting...")
319
320
321func sendMessage(msg):
322 var payload = JSON.stringify(msg)
323 _ws.send_text(payload)
324
325
326func requestDatapackages(games):
327 emit_signal("connect_status", "Downloading %s data package..." % games[0])
328
329 sendMessage([{"cmd": "GetDataPackage", "games": games}])
330
331
332func processDatapackages():
333 _item_id_to_name = {}
334 _location_id_to_name = {}
335 for game in _datapackages.keys():
336 var package = _datapackages[game]
337
338 _item_id_to_name[game] = {}
339 for item_name in package["item_name_to_id"].keys():
340 _item_id_to_name[game][int(package["item_name_to_id"][item_name])] = item_name
341
342 _location_id_to_name[game] = {}
343 for location_name in package["location_name_to_id"].keys():
344 _location_id_to_name[game][int(package["location_name_to_id"][location_name])] = location_name
345
346 if _datapackages.has("Lingo 2"):
347 _item_name_to_id = _datapackages["Lingo 2"]["item_name_to_id"]
348 _location_name_to_id = _datapackages["Lingo 2"]["location_name_to_id"]
349
350
351func connectToRoom():
352 emit_signal("connect_status", "Authenticating...")
353
354 sendMessage(
355 [
356 {
357 "cmd": "Connect",
358 "password": ap_pass,
359 "game": "Lingo 2",
360 "name": ap_user,
361 "uuid": SCRIPT_uuid.v4(),
362 "version": ap_version,
363 "items_handling": 0b111, # always receive our items
364 "tags": [],
365 "slot_data": true
366 }
367 ]
368 )
369
370
371func sendConnectUpdate(tags):
372 sendMessage([{"cmd": "ConnectUpdate", "tags": tags}])
373
374
375func requestSync():
376 sendMessage([{"cmd": "Sync"}])
377
378
379func sendLocation(loc_id):
380 sendMessage([{"cmd": "LocationChecks", "locations": [loc_id]}])
381
382
383func sendLocations(loc_ids):
384 sendMessage([{"cmd": "LocationChecks", "locations": loc_ids}])
385
386
387func setValue(key, value, operation = "replace"):
388 sendMessage(
389 [
390 {
391 "cmd": "Set",
392 "key": "Lingo2_%d_%s" % [_slot, key],
393 "want_reply": false,
394 "operations": [{"operation": operation, "value": value}]
395 }
396 ]
397 )
398
399
400func say(textdata):
401 sendMessage([{"cmd": "Say", "text": textdata}])
402
403
404func completedGoal():
405 sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL
406
407
408func scoutLocations(loc_ids):
409 sendMessage([{"cmd": "LocationScouts", "locations": loc_ids}])
410
411
412func hasItem(item_id):
413 return _received_items.has(item_id)
414
415
416func getItemAmount(item_id):
417 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/compass.gd b/client/Archipelago/compass.gd deleted file mode 100644 index c90475a..0000000 --- a/client/Archipelago/compass.gd +++ /dev/null
@@ -1,66 +0,0 @@
1extends Node2D
2
3const RADIUS = 48
4
5var _font
6
7
8func _ready():
9 _font = load("res://assets/fonts/Lingo2.ttf")
10
11
12func _draw():
13 draw_circle(Vector2.ZERO, RADIUS, Color(1.0, 1.0, 1.0, 0.8), true)
14 draw_circle(Vector2.ZERO, RADIUS, Color.BLACK, false)
15 draw_string(
16 _font,
17 Vector2(-4, -RADIUS * 3.0 / 4.0),
18 "N",
19 HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT,
20 -1,
21 16,
22 Color.BLACK
23 )
24 draw_set_transform(Vector2.ZERO, PI / 2)
25 draw_string(
26 _font,
27 Vector2(-4, -RADIUS * 3.0 / 4.0),
28 "E",
29 HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT,
30 -1,
31 16,
32 Color.BLACK
33 )
34 draw_set_transform(Vector2.ZERO, PI)
35 draw_string(
36 _font,
37 Vector2(-4, -RADIUS * 3.0 / 4.0),
38 "S",
39 HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT,
40 -1,
41 16,
42 Color.BLACK
43 )
44 draw_set_transform(Vector2.ZERO, PI * 3.0 / 2.0)
45 draw_string(
46 _font,
47 Vector2(-4, -RADIUS * 3.0 / 4.0),
48 "W",
49 HorizontalAlignment.HORIZONTAL_ALIGNMENT_LEFT,
50 -1,
51 16,
52 Color.BLACK
53 )
54 draw_set_transform(Vector2.ZERO)
55 draw_colored_polygon(
56 PackedVector2Array(
57 [Vector2(0, -RADIUS * 5.0 / 8.0), Vector2(-RADIUS / 6.0, 0), Vector2(RADIUS / 6.0, 0)]
58 ),
59 Color.RED
60 )
61 draw_colored_polygon(
62 PackedVector2Array(
63 [Vector2(0, RADIUS * 5.0 / 8.0), Vector2(-RADIUS / 6.0, 0), Vector2(RADIUS / 6.0, 0)]
64 ),
65 Color.GRAY
66 )
diff --git a/client/Archipelago/compass_overlay.gd b/client/Archipelago/compass_overlay.gd deleted file mode 100644 index 56e81ff..0000000 --- a/client/Archipelago/compass_overlay.gd +++ /dev/null
@@ -1,17 +0,0 @@
1extends CanvasLayer
2
3var SCRIPT_compass
4
5var compass
6
7
8func _ready():
9 compass = SCRIPT_compass.new()
10 compass.position = Vector2(1840, 80)
11 add_child(compass)
12
13 visible = false
14
15
16func update_rotation(ry):
17 compass.rotation = ry
diff --git a/client/Archipelago/door.gd b/client/Archipelago/door.gd deleted file mode 100644 index 49f5728..0000000 --- a/client/Archipelago/door.gd +++ /dev/null
@@ -1,46 +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 if global.map == "the_sun_temple":
32 if name == "spe_EndPlatform" or name == "spe_entry_2":
33 senders = [NodePath("/root/scene/Panels/EndCheck_dog")]
34
35 if global.map == "the_parthenon":
36 if name == "spe_entry_1":
37 senders = [NodePath("/root/scene/Panels/EndCheck_dog")]
38
39 super._ready()
40
41
42func _readier():
43 var ap = global.get_node("Archipelago")
44
45 if ap.client.getItemAmount(item_id) >= item_amount:
46 handleTriggered()
diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd deleted file mode 100644 index 9eeec3b..0000000 --- a/client/Archipelago/gamedata.gd +++ /dev/null
@@ -1,159 +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 port_id_by_map_node_path = {}
10var door_id_by_ap_id = {}
11var map_id_by_name = {}
12var progressive_id_by_ap_id = {}
13var letter_id_by_ap_id = {}
14var symbol_item_ids = []
15var anti_trap_ids = {}
16
17var kSYMBOL_ITEMS
18
19
20func _init(proto_script):
21 SCRIPT_proto = proto_script
22
23 kSYMBOL_ITEMS = {
24 SCRIPT_proto.PuzzleSymbol.SUN: "Sun Symbol",
25 SCRIPT_proto.PuzzleSymbol.SPARKLES: "Sparkles Symbol",
26 SCRIPT_proto.PuzzleSymbol.ZERO: "Zero Symbol",
27 SCRIPT_proto.PuzzleSymbol.EXAMPLE: "Example Symbol",
28 SCRIPT_proto.PuzzleSymbol.BOXES: "Boxes Symbol",
29 SCRIPT_proto.PuzzleSymbol.PLANET: "Planet Symbol",
30 SCRIPT_proto.PuzzleSymbol.PYRAMID: "Pyramid Symbol",
31 SCRIPT_proto.PuzzleSymbol.CROSS: "Cross Symbol",
32 SCRIPT_proto.PuzzleSymbol.SWEET: "Sweet Symbol",
33 SCRIPT_proto.PuzzleSymbol.GENDER: "Gender Symbol",
34 SCRIPT_proto.PuzzleSymbol.AGE: "Age Symbol",
35 SCRIPT_proto.PuzzleSymbol.SOUND: "Sound Symbol",
36 SCRIPT_proto.PuzzleSymbol.ANAGRAM: "Anagram Symbol",
37 SCRIPT_proto.PuzzleSymbol.JOB: "Job Symbol",
38 SCRIPT_proto.PuzzleSymbol.STARS: "Stars Symbol",
39 SCRIPT_proto.PuzzleSymbol.NULL: "Null Symbol",
40 SCRIPT_proto.PuzzleSymbol.EVAL: "Eval Symbol",
41 SCRIPT_proto.PuzzleSymbol.LINGO: "Lingo Symbol",
42 SCRIPT_proto.PuzzleSymbol.QUESTION: "Question Symbol",
43 }
44
45
46func load(data_bytes):
47 objects = SCRIPT_proto.AllObjects.new()
48
49 var result_code = objects.from_bytes(data_bytes)
50 if result_code != SCRIPT_proto.PB_ERR.NO_ERRORS:
51 print("Could not load generated data: %d" % result_code)
52 return
53
54 for map in objects.get_maps():
55 map_id_by_name[map.get_name()] = map.get_id()
56
57 for door in objects.get_doors():
58 var map = objects.get_maps()[door.get_map_id()]
59
60 if not map.get_name() in door_id_by_map_node_path:
61 door_id_by_map_node_path[map.get_name()] = {}
62
63 var map_data = door_id_by_map_node_path[map.get_name()]
64 for receiver in door.get_receivers():
65 map_data[receiver] = door.get_id()
66
67 for painting_id in door.get_move_paintings():
68 var painting = objects.get_paintings()[painting_id]
69 map_data[painting.get_path()] = door.get_id()
70
71 if door.has_ap_id():
72 door_id_by_ap_id[door.get_ap_id()] = door.get_id()
73
74 for painting in objects.get_paintings():
75 var room = objects.get_rooms()[painting.get_room_id()]
76 var map = objects.get_maps()[room.get_map_id()]
77
78 if not map.get_name() in painting_id_by_map_node_path:
79 painting_id_by_map_node_path[map.get_name()] = {}
80
81 var _map_data = painting_id_by_map_node_path[map.get_name()]
82
83 for port in objects.get_ports():
84 var room = objects.get_rooms()[port.get_room_id()]
85 var map = objects.get_maps()[room.get_map_id()]
86
87 if not map.get_name() in port_id_by_map_node_path:
88 port_id_by_map_node_path[map.get_name()] = {}
89
90 var map_data = port_id_by_map_node_path[map.get_name()]
91 map_data[port.get_path()] = port.get_id()
92
93 for progressive in objects.get_progressives():
94 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id()
95
96 for letter in objects.get_letters():
97 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id()
98
99 for panel in objects.get_panels():
100 var room = objects.get_rooms()[panel.get_room_id()]
101 var map = objects.get_maps()[room.get_map_id()]
102
103 if not map.get_name() in panel_id_by_map_node_path:
104 panel_id_by_map_node_path[map.get_name()] = {}
105
106 var map_data = panel_id_by_map_node_path[map.get_name()]
107 map_data[panel.get_path()] = panel.get_id()
108
109 for symbol_name in kSYMBOL_ITEMS.values():
110 symbol_item_ids.append(objects.get_special_ids()[symbol_name])
111
112 for special_name in objects.get_special_ids().keys():
113 if special_name.begins_with("Anti "):
114 anti_trap_ids[objects.get_special_ids()[special_name]] = (
115 special_name.substr(5).to_lower()
116 )
117
118
119func get_door_for_map_node_path(map_name, node_path):
120 if not door_id_by_map_node_path.has(map_name):
121 return null
122
123 var map_data = door_id_by_map_node_path[map_name]
124 return map_data.get(node_path, null)
125
126
127func get_panel_for_map_node_path(map_name, node_path):
128 if not panel_id_by_map_node_path.has(map_name):
129 return null
130
131 var map_data = panel_id_by_map_node_path[map_name]
132 return map_data.get(node_path, null)
133
134
135func get_port_for_map_node_path(map_name, node_path):
136 if not port_id_by_map_node_path.has(map_name):
137 return null
138
139 var map_data = port_id_by_map_node_path[map_name]
140 return map_data.get(node_path, null)
141
142
143func get_door_ap_id(door_id):
144 var door = objects.get_doors()[door_id]
145 if door.has_ap_id():
146 return door.get_ap_id()
147 else:
148 return null
149
150
151func get_door_receivers(door_id):
152 var door = objects.get_doors()[door_id]
153 return door.get_receivers()
154
155
156func get_door_map_name(door_id):
157 var door = objects.get_doors()[door_id]
158 var map = objects.get_maps()[door.get_map_id()]
159 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 450566d..0000000 --- a/client/Archipelago/keyboard.gd +++ /dev/null
@@ -1,199 +0,0 @@
1extends Node
2
3const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz"
4
5var letters_saved = {}
6var letters_in_keyholders = []
7var letters_blocked = []
8var letters_dynamic = {}
9var keyholder_state = {}
10
11var filename = ""
12
13
14func _init():
15 reset()
16
17
18func reset():
19 letters_saved.clear()
20 letters_in_keyholders.clear()
21 letters_blocked.clear()
22 letters_dynamic.clear()
23 keyholder_state.clear()
24
25
26func load_seed():
27 var ap = global.get_node("Archipelago")
28
29 reset()
30
31 filename = "user://archipelago_keys/%s_%d" % [ap.client._seed, ap.client._slot]
32
33 if FileAccess.file_exists(filename):
34 var ap_file = FileAccess.open(filename, FileAccess.READ)
35 var localdata = []
36 if ap_file != null:
37 localdata = ap_file.get_var(true)
38 ap_file.close()
39
40 if typeof(localdata) != TYPE_ARRAY:
41 print("AP keyboard file is corrupted")
42 localdata = []
43
44 if localdata.size() > 0:
45 letters_saved = localdata[0]
46 if localdata.size() > 1:
47 letters_in_keyholders = localdata[1]
48 if localdata.size() > 2:
49 keyholder_state = localdata[2]
50
51 for k in kALL_LETTERS:
52 var level = 0
53
54 if ap.get_letter_behavior(k, false) == ap.kLETTER_BEHAVIOR_UNLOCKED:
55 level += 1
56 if ap.get_letter_behavior(k, true) == ap.kLETTER_BEHAVIOR_UNLOCKED:
57 level += 1
58
59 letters_dynamic[k] = level
60
61 update_unlocks()
62
63
64func save():
65 var dir = DirAccess.open("user://")
66 var folder = "archipelago_keys"
67 if not dir.dir_exists(folder):
68 dir.make_dir(folder)
69
70 var file = FileAccess.open(filename, FileAccess.WRITE)
71
72 var data = [
73 letters_saved,
74 letters_in_keyholders,
75 keyholder_state,
76 ]
77 file.store_var(data, true)
78 file.close()
79
80
81func update_unlocks():
82 unlocks.resetKeys()
83
84 var has_doubles = false
85
86 for k in kALL_LETTERS:
87 var level = 0
88
89 if not letters_in_keyholders.has(k):
90 level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0)
91
92 if level >= 2:
93 level = 2
94 has_doubles = true
95
96 if letters_blocked.has(k):
97 level = 0
98
99 unlocks.unlockKey(k, level)
100
101 if has_doubles and unlocks.data["double_letters"] != "unlocked":
102 var ap = global.get_node("Archipelago")
103 if ap.cyan_door_behavior == ap.kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER:
104 unlocks.setData("double_letters", "unlocked")
105
106
107func collect_local_letter(key, level):
108 if level < 0 or level > 2 or level < letters_saved.get(key, 0):
109 return
110
111 letters_saved[key] = level
112
113 if letters_blocked.has(key):
114 letters_blocked.erase(key)
115
116 update_unlocks()
117 save()
118
119
120func collect_remote_letter(key, level):
121 if level < 0 or level > 2 or level < letters_dynamic.get(key, 0):
122 return
123
124 letters_dynamic[key] = level
125
126 if letters_blocked.has(key):
127 letters_blocked.erase(key)
128
129 update_unlocks()
130 save()
131
132
133func put_in_keyholder(key, map, kh_path):
134 if not keyholder_state.has(map):
135 keyholder_state[map] = {}
136
137 keyholder_state[map][kh_path] = key
138 letters_in_keyholders.append(key)
139
140 get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(
141 key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
142 )
143
144 update_unlocks()
145 save()
146
147
148func remove_from_keyholder(key, map, kh_path):
149 if not keyholder_state.has(map):
150 # This... shouldn't happen.
151 keyholder_state[map] = {}
152
153 keyholder_state[map].erase(kh_path)
154 letters_in_keyholders.erase(key)
155
156 get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(key, 0)
157
158 update_unlocks()
159 save()
160
161
162func block_letter(key):
163 if not letters_blocked.has(key):
164 letters_blocked.append(key)
165
166 update_unlocks()
167
168
169func load_keyholders(map):
170 if keyholder_state.has(map):
171 var khs = keyholder_state[map]
172
173 for path in khs.keys():
174 var key = khs[path]
175 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
176 key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
177 )
178
179
180func reset_keyholders():
181 if letters_in_keyholders.is_empty() and letters_blocked.is_empty():
182 return false
183
184 var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty()
185
186 if keyholder_state.has(global.map):
187 for path in keyholder_state[global.map]:
188 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
189 keyholder_state[global.map][path], 0
190 )
191
192 keyholder_state.clear()
193 letters_in_keyholders.clear()
194 letters_blocked.clear()
195
196 update_unlocks()
197 save()
198
199 return cleared_anything
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 b170c77..0000000 --- a/client/Archipelago/manager.gd +++ /dev/null
@@ -1,571 +0,0 @@
1extends Node
2
3const MOD_VERSION = 7
4
5var SCRIPT_client
6var SCRIPT_keyboard
7var SCRIPT_locationListener
8var SCRIPT_minimap
9var SCRIPT_uuid
10var SCRIPT_victoryListener
11
12var ap_server = ""
13var ap_user = ""
14var ap_pass = ""
15var connection_history = []
16var show_compass = false
17
18var client
19var keyboard
20
21var _localdata_file = ""
22var _last_new_item = -1
23var _batch_locations = false
24var _held_locations = []
25var _held_location_scouts = []
26var _location_scouts = {}
27var _item_locks = {}
28var _inverse_item_locks = {}
29var _held_letters = {}
30var _letters_setup = false
31
32const kSHUFFLE_LETTERS_VANILLA = 0
33const kSHUFFLE_LETTERS_UNLOCKED = 1
34const kSHUFFLE_LETTERS_PROGRESSIVE = 2
35const kSHUFFLE_LETTERS_VANILLA_CYAN = 3
36const kSHUFFLE_LETTERS_ITEM_CYAN = 4
37
38const kLETTER_BEHAVIOR_VANILLA = 0
39const kLETTER_BEHAVIOR_ITEM = 1
40const kLETTER_BEHAVIOR_UNLOCKED = 2
41
42const kCYAN_DOOR_BEHAVIOR_H2 = 0
43const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1
44const kCYAN_DOOR_BEHAVIOR_ITEM = 2
45
46var apworld_version = [0, 0]
47var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
48var daedalus_roof_access = false
49var keyholder_sanity = false
50var port_pairings = {}
51var shuffle_control_center_colors = false
52var shuffle_doors = false
53var shuffle_gallery_paintings = false
54var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
55var shuffle_symbols = false
56var shuffle_worldports = false
57var strict_cyan_ending = false
58var strict_purple_ending = false
59var victory_condition = -1
60
61signal could_not_connect
62signal connect_status
63signal ap_connected
64
65
66func _init():
67 # Read AP settings from file, if there are any
68 if FileAccess.file_exists("user://ap_settings"):
69 var file = FileAccess.open("user://ap_settings", FileAccess.READ)
70 var data = file.get_var(true)
71 file.close()
72
73 if typeof(data) != TYPE_ARRAY:
74 global._print("AP settings file is corrupted")
75 data = []
76
77 if data.size() > 0:
78 ap_server = data[0]
79
80 if data.size() > 1:
81 ap_user = data[1]
82
83 if data.size() > 2:
84 ap_pass = data[2]
85
86 if data.size() > 3:
87 connection_history = data[3]
88
89 if data.size() > 4:
90 show_compass = data[4]
91
92
93func _ready():
94 client = SCRIPT_client.new()
95 client.SCRIPT_uuid = SCRIPT_uuid
96
97 client.connect("item_received", _process_item)
98 client.connect("message_received", _process_message)
99 client.connect("location_scout_received", _process_location_scout)
100 client.connect("could_not_connect", _client_could_not_connect)
101 client.connect("connect_status", _client_connect_status)
102 client.connect("client_connected", _client_connected)
103
104 add_child(client)
105
106 keyboard = SCRIPT_keyboard.new()
107 add_child(keyboard)
108
109
110func saveSettings():
111 # Save the AP settings to disk.
112 var path = "user://ap_settings"
113 var file = FileAccess.open(path, FileAccess.WRITE)
114
115 var data = [
116 ap_server,
117 ap_user,
118 ap_pass,
119 connection_history,
120 show_compass,
121 ]
122 file.store_var(data, true)
123 file.close()
124
125
126func saveLocaldata():
127 # Save the MW/slot specific settings to disk.
128 var dir = DirAccess.open("user://")
129 var folder = "archipelago_data"
130 if not dir.dir_exists(folder):
131 dir.make_dir(folder)
132
133 var file = FileAccess.open(_localdata_file, FileAccess.WRITE)
134
135 var data = [
136 _last_new_item,
137 ]
138 file.store_var(data, true)
139 file.close()
140
141
142func connectToServer():
143 _last_new_item = -1
144 _batch_locations = false
145 _held_locations = []
146 _held_location_scouts = []
147 _location_scouts = {}
148 _letters_setup = false
149 _held_letters = {}
150
151 client.connectToServer(ap_server, ap_user, ap_pass)
152
153
154func getSaveFileName():
155 return "zzAP_%s_%d" % [client._seed, client._slot]
156
157
158func disconnect_from_ap():
159 client.disconnect_from_ap()
160
161
162func get_item_id_for_door(door_id):
163 return _item_locks.get(door_id, null)
164
165
166func _process_item(item, index, from, flags, amount):
167 var item_name = "Unknown"
168 if client._item_id_to_name["Lingo 2"].has(item):
169 item_name = client._item_id_to_name["Lingo 2"][item]
170
171 var gamedata = global.get_node("Gamedata")
172
173 var prog_id = null
174 if _inverse_item_locks.has(item):
175 for lock in _inverse_item_locks.get(item):
176 if lock[1] != amount:
177 continue
178
179 if gamedata.progressive_id_by_ap_id.has(item):
180 prog_id = lock[0]
181
182 if gamedata.get_door_map_name(lock[0]) != global.map:
183 continue
184
185 var receivers = gamedata.get_door_receivers(lock[0])
186 var scene = get_tree().get_root().get_node_or_null("scene")
187 if scene != null:
188 for receiver in receivers:
189 var rnode = scene.get_node_or_null(receiver)
190 if rnode != null:
191 rnode.handleTriggered()
192
193 var letter_id = gamedata.letter_id_by_ap_id.get(item, null)
194 if letter_id != null:
195 var letter = gamedata.objects.get_letters()[letter_id]
196 if not letter.has_level2() or not letter.get_level2():
197 _process_key_item(letter.get_key(), amount)
198
199 if gamedata.symbol_item_ids.has(item):
200 var player = get_tree().get_root().get_node_or_null("scene/player")
201 if player != null:
202 player.emit_signal("evaluate_solvability")
203
204 # Show a message about the item if it's new.
205 if index != null and index > _last_new_item:
206 _last_new_item = index
207 saveLocaldata()
208
209 var player_name = "Unknown"
210 if client._player_name_by_slot.has(float(from)):
211 player_name = client._player_name_by_slot[float(from)]
212
213 var full_item_name = item_name
214 if prog_id != null:
215 var door = gamedata.objects.get_doors()[prog_id]
216 full_item_name = "%s (%s)" % [item_name, door.get_name()]
217
218 var message
219 if from == client._slot:
220 message = "Found %s" % wrapInItemColorTags(full_item_name, flags)
221 else:
222 message = (
223 "Received %s from %s" % [wrapInItemColorTags(full_item_name, flags), player_name]
224 )
225
226 if gamedata.anti_trap_ids.has(item):
227 keyboard.block_letter(gamedata.anti_trap_ids[item])
228
229 global._print(message)
230
231 global.get_node("Messages").showMessage(message)
232
233
234func _process_message(message):
235 parse_printjson_for_textclient(message)
236
237 if (
238 !message.has("receiving")
239 or !message.has("item")
240 or message["item"]["player"] != client._slot
241 ):
242 return
243
244 var item_name = "Unknown"
245 var item_player_game = client._game_by_player[message["receiving"]]
246 if client._item_id_to_name[item_player_game].has(int(message["item"]["item"])):
247 item_name = client._item_id_to_name[item_player_game][int(message["item"]["item"])]
248
249 var location_name = "Unknown"
250 var location_player_game = client._game_by_player[message["item"]["player"]]
251 if client._location_id_to_name[location_player_game].has(int(message["item"]["location"])):
252 location_name = (client._location_id_to_name[location_player_game][int(
253 message["item"]["location"]
254 )])
255
256 var player_name = "Unknown"
257 if client._player_name_by_slot.has(message["receiving"]):
258 player_name = client._player_name_by_slot[message["receiving"]]
259
260 var item_color = colorForItemType(message["item"]["flags"])
261
262 if message["type"] == "Hint":
263 var is_for = ""
264 if message["receiving"] != client._slot:
265 is_for = " for %s" % player_name
266 if !message.has("found") || !message["found"]:
267 global.get_node("Messages").showMessage(
268 (
269 "Hint: %s%s is on %s"
270 % [
271 wrapInItemColorTags(item_name, message["item"]["flags"]),
272 is_for,
273 location_name
274 ]
275 )
276 )
277 else:
278 if message["receiving"] != client._slot:
279 var sentMsg = (
280 "Sent %s to %s"
281 % [wrapInItemColorTags(item_name, message["item"]["flags"]), player_name]
282 )
283 #if _hinted_locations.has(message["item"]["location"]):
284 # sentMsg += " ([color=#fafad2]Hinted![/color])"
285 global.get_node("Messages").showMessage(sentMsg)
286
287
288func parse_printjson_for_textclient(message):
289 var parts = []
290 for message_part in message["data"]:
291 if !message_part.has("type") and message_part.has("text"):
292 parts.append(message_part["text"])
293 elif message_part["type"] == "player_id":
294 if int(message_part["text"]) == client._slot:
295 parts.append(
296 "[color=#ee00ee]%s[/color]" % client._player_name_by_slot[client._slot]
297 )
298 else:
299 var from = float(message_part["text"])
300 parts.append("[color=#fafad2]%s[/color]" % client._player_name_by_slot[from])
301 elif message_part["type"] == "item_id":
302 var item_name = "Unknown"
303 var item_player_game = client._game_by_player[message_part["player"]]
304 if client._item_id_to_name[item_player_game].has(int(message_part["text"])):
305 item_name = client._item_id_to_name[item_player_game][int(message_part["text"])]
306
307 parts.append(wrapInItemColorTags(item_name, message_part["flags"]))
308 elif message_part["type"] == "location_id":
309 var location_name = "Unknown"
310 var location_player_game = client._game_by_player[message_part["player"]]
311 if client._location_id_to_name[location_player_game].has(int(message_part["text"])):
312 location_name = client._location_id_to_name[location_player_game][int(
313 message_part["text"]
314 )]
315
316 parts.append("[color=#00ff7f]%s[/color]" % location_name)
317 elif message_part.has("text"):
318 parts.append(message_part["text"])
319
320 var textclient_node = global.get_node("Textclient")
321 if textclient_node != null:
322 textclient_node.parse_printjson("".join(parts))
323
324
325func _process_location_scout(item_id, location_id, player, flags):
326 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
327
328 if player == client._slot and flags & 4 != 0:
329 # This is a trap for us, so let's not display it.
330 return
331
332 var gamedata = global.get_node("Gamedata")
333 var map_id = gamedata.map_id_by_name.get(global.map)
334
335 var item_name = "Unknown"
336 var item_player_game = client._game_by_player[float(player)]
337 if client._item_id_to_name[item_player_game].has(item_id):
338 item_name = client._item_id_to_name[item_player_game][item_id]
339
340 var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null)
341 if letter_id != null:
342 var letter = gamedata.objects.get_letters()[letter_id]
343 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
344 if room.get_map_id() == map_id:
345 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
346 letter.get_path()
347 )
348 if collectable != null:
349 collectable.setScoutedText(item_name)
350
351
352func _client_could_not_connect(message):
353 emit_signal("could_not_connect", message)
354
355
356func _client_connect_status(message):
357 emit_signal("connect_status", message)
358
359
360func _client_connected(slot_data):
361 var gamedata = global.get_node("Gamedata")
362
363 _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot]
364 _last_new_item = -1
365
366 if FileAccess.file_exists(_localdata_file):
367 var ap_file = FileAccess.open(_localdata_file, FileAccess.READ)
368 var localdata = []
369 if ap_file != null:
370 localdata = ap_file.get_var(true)
371 ap_file.close()
372
373 if typeof(localdata) != TYPE_ARRAY:
374 print("AP localdata file is corrupted")
375 localdata = []
376
377 if localdata.size() > 0:
378 _last_new_item = localdata[0]
379
380 # Read slot data.
381 cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0))
382 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
383 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
384 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
385 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
386 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
387 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
388 shuffle_symbols = bool(slot_data.get("shuffle_symbols", false))
389 shuffle_worldports = bool(slot_data.get("shuffle_worldports", false))
390 strict_cyan_ending = bool(slot_data.get("strict_cyan_ending", false))
391 strict_purple_ending = bool(slot_data.get("strict_purple_ending", false))
392 victory_condition = int(slot_data.get("victory_condition", 0))
393
394 if slot_data.has("version"):
395 apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])]
396
397 port_pairings.clear()
398 if slot_data.has("port_pairings"):
399 var raw_pp = slot_data.get("port_pairings")
400
401 for p1 in raw_pp.keys():
402 port_pairings[int(p1)] = int(raw_pp[p1])
403
404 # Set up item locks.
405 _item_locks = {}
406
407 if shuffle_doors:
408 for door in gamedata.objects.get_doors():
409 if (
410 door.get_type() == gamedata.SCRIPT_proto.DoorType.STANDARD
411 or door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
412 ):
413 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
414
415 for progressive in gamedata.objects.get_progressives():
416 for i in range(0, progressive.get_doors().size()):
417 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]]
418 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1]
419
420 for door_group in gamedata.objects.get_door_groups():
421 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR:
422 if shuffle_worldports:
423 continue
424 elif door_group.get_type() != gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP:
425 continue
426
427 for door in door_group.get_doors():
428 _item_locks[door] = [door_group.get_ap_id(), 1]
429
430 if shuffle_control_center_colors:
431 for door in gamedata.objects.get_doors():
432 if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR:
433 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
434
435 for door_group in gamedata.objects.get_door_groups():
436 if (
437 door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR
438 and not shuffle_worldports
439 ):
440 for door in door_group.get_doors():
441 _item_locks[door] = [door_group.get_ap_id(), 1]
442
443 if shuffle_gallery_paintings:
444 for door in gamedata.objects.get_doors():
445 if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING:
446 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
447
448 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM:
449 for door_group in gamedata.objects.get_door_groups():
450 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS:
451 for door in door_group.get_doors():
452 if not _item_locks.has(door):
453 _item_locks[door] = [door_group.get_ap_id(), 1]
454
455 # Create a reverse item locks map for processing items.
456 _inverse_item_locks = {}
457
458 for door_id in _item_locks.keys():
459 var lock = _item_locks.get(door_id)
460
461 if not _inverse_item_locks.has(lock[0]):
462 _inverse_item_locks[lock[0]] = []
463
464 _inverse_item_locks[lock[0]].append([door_id, lock[1]])
465
466 emit_signal("ap_connected")
467
468
469func start_batching_locations():
470 _batch_locations = true
471
472
473func send_location(loc_id):
474 if _batch_locations:
475 _held_locations.append(loc_id)
476 else:
477 client.sendLocation(loc_id)
478
479
480func scout_location(loc_id):
481 if _location_scouts.has(loc_id):
482 return _location_scouts.get(loc_id)
483
484 if _batch_locations:
485 _held_location_scouts.append(loc_id)
486 else:
487 client.scoutLocation(loc_id)
488
489 return null
490
491
492func stop_batching_locations():
493 _batch_locations = false
494
495 if not _held_locations.is_empty():
496 client.sendLocations(_held_locations)
497 _held_locations.clear()
498
499 if not _held_location_scouts.is_empty():
500 client.scoutLocations(_held_location_scouts)
501 _held_location_scouts.clear()
502
503
504func colorForItemType(flags):
505 var int_flags = int(flags)
506 if int_flags & 1: # progression
507 if int_flags & 2: # proguseful
508 return "#f0d200"
509 else:
510 return "#bc51e0"
511 elif int_flags & 2: # useful
512 return "#2b67ff"
513 elif int_flags & 4: # trap
514 return "#d63a22"
515 else: # filler
516 return "#14de9e"
517
518
519func wrapInItemColorTags(text, flags):
520 var int_flags = int(flags)
521 if int_flags & 1 and int_flags & 2: # proguseful
522 return "[rainbow]%s[/rainbow]" % text
523 else:
524 return "[color=%s]%s[/color]" % [colorForItemType(flags), text]
525
526
527func get_letter_behavior(key, level2):
528 if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED:
529 return kLETTER_BEHAVIOR_UNLOCKED
530
531 if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters):
532 if level2:
533 if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN:
534 return kLETTER_BEHAVIOR_VANILLA
535 else:
536 return kLETTER_BEHAVIOR_ITEM
537 else:
538 return kLETTER_BEHAVIOR_UNLOCKED
539
540 if not level2 and ["h", "i", "n", "t"].has(key):
541 # This differs from the equivalent function in the apworld. Logically it is
542 # the same as UNLOCKED since they are in the starting room, but VANILLA
543 # means the player still has to actually pick up the letters.
544 return kLETTER_BEHAVIOR_VANILLA
545
546 if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE:
547 return kLETTER_BEHAVIOR_ITEM
548
549 return kLETTER_BEHAVIOR_VANILLA
550
551
552func setup_keys():
553 keyboard.load_seed()
554
555 _letters_setup = true
556
557 for k in _held_letters.keys():
558 _process_key_item(k, _held_letters[k])
559
560 _held_letters.clear()
561
562
563func _process_key_item(key, level):
564 if not _letters_setup:
565 _held_letters[key] = max(_held_letters.get(key, 0), level)
566 return
567
568 if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN:
569 level += 1
570
571 keyboard.collect_remote_letter(key, level)
diff --git a/client/Archipelago/messages.gd b/client/Archipelago/messages.gd deleted file mode 100644 index ab4f071..0000000 --- a/client/Archipelago/messages.gd +++ /dev/null
@@ -1,74 +0,0 @@
1extends CanvasLayer
2
3var SCRIPT_rainbowText
4
5var _message_queue = []
6var _font
7var _container
8var _ordered_labels = []
9
10
11func _ready():
12 _container = VBoxContainer.new()
13 _container.set_name("Container")
14 _container.anchor_bottom = 1
15 _container.offset_left = 20.0
16 _container.offset_right = 1920.0
17 _container.offset_top = 0.0
18 _container.offset_bottom = -20.0
19 _container.alignment = BoxContainer.ALIGNMENT_END
20 _container.mouse_filter = Control.MOUSE_FILTER_IGNORE
21 self.add_child(_container)
22
23 _font = load("res://assets/fonts/Lingo2.ttf")
24
25
26func _add_message(text):
27 var new_label = RichTextLabel.new()
28 new_label.install_effect(SCRIPT_rainbowText.new())
29 new_label.push_font(_font)
30 new_label.push_font_size(36)
31 new_label.push_outline_color(Color(0, 0, 0, 1))
32 new_label.push_outline_size(2)
33 new_label.append_text(text)
34 new_label.fit_content = true
35
36 _container.add_child(new_label)
37 _ordered_labels.push_back(new_label)
38
39
40func showMessage(text):
41 if _ordered_labels.size() >= 9:
42 _message_queue.append(text)
43 return
44
45 _add_message(text)
46
47 if _ordered_labels.size() > 1:
48 return
49
50 var timeout = 10.0
51 while !_ordered_labels.is_empty():
52 await get_tree().create_timer(timeout).timeout
53
54 if !_ordered_labels.is_empty():
55 var to_remove = _ordered_labels.pop_front()
56 var to_tween = get_tree().create_tween().bind_node(to_remove)
57 to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5)
58 to_tween.tween_callback(to_remove.queue_free)
59
60 if !_message_queue.is_empty():
61 var next_msg = _message_queue.pop_front()
62 _add_message(next_msg)
63
64 if timeout > 4:
65 timeout -= 3
66
67
68func clear():
69 _message_queue.clear()
70
71 for message_label in _ordered_labels:
72 message_label.queue_free()
73
74 _ordered_labels.clear()
diff --git a/client/Archipelago/minimap.gd b/client/Archipelago/minimap.gd deleted file mode 100644 index 5640716..0000000 --- a/client/Archipelago/minimap.gd +++ /dev/null
@@ -1,175 +0,0 @@
1extends CanvasLayer
2
3var player
4var drawer
5var sprite
6var label
7
8var cell_left
9var cell_top
10var cell_right
11var cell_bottom
12var cell_width
13var cell_height
14var center_x_min
15var center_x_max
16var center_y_min
17var center_y_max
18
19
20func _ready():
21 player = get_tree().get_root().get_node("scene/player")
22
23 var svc = PanelContainer.new()
24 svc.anchor_left = 1.0
25 svc.anchor_top = 1.0
26 svc.anchor_right = 1.0
27 svc.anchor_bottom = 1.0
28 svc.offset_left = -320.0
29 svc.offset_top = -320.0
30 svc.offset_right = -64.0
31 svc.offset_bottom = -64.0
32 svc.clip_contents = true
33 add_child(svc)
34
35 var background_color = Color.WHITE
36
37 var world_env = get_tree().get_root().get_node("scene/WorldEnvironment")
38 if world_env != null and world_env.environment != null:
39 if world_env.environment.background_mode == Environment.BG_COLOR:
40 background_color = world_env.environment.background_color
41 elif (
42 world_env.environment.background_mode == Environment.BG_SKY
43 and world_env.environment.sky != null
44 and world_env.environment.sky.sky_material != null
45 ):
46 var sky = world_env.environment.sky.sky_material
47 if sky is PhysicalSkyMaterial:
48 background_color = sky.ground_color
49 elif sky is ProceduralSkyMaterial:
50 background_color = sky.sky_top_color
51
52 var stylebox = StyleBoxFlat.new()
53 stylebox.bg_color = Color(background_color, 0.6)
54 svc.add_theme_stylebox_override("panel", stylebox)
55
56 drawer = Node2D.new()
57 svc.add_child(drawer)
58
59 var gridmap = get_tree().get_root().get_node("scene/GridMap")
60 if gridmap == null:
61 visible = false
62 return
63
64 cell_left = 0
65 cell_top = 0
66 cell_right = 0
67 cell_bottom = 0
68
69 for pos in gridmap.get_used_cells():
70 if pos.x < cell_left:
71 cell_left = pos.x
72 if pos.x > cell_right:
73 cell_right = pos.x
74 if pos.z < cell_top:
75 cell_top = pos.z
76 if pos.z > cell_bottom:
77 cell_bottom = pos.z
78
79 cell_width = cell_right - cell_left + 1
80 cell_height = cell_bottom - cell_top + 1
81
82 var rendered = _renderMap(gridmap)
83
84 var image_texture = ImageTexture.create_from_image(rendered)
85 sprite = Sprite2D.new()
86 sprite.texture = image_texture
87 sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
88 sprite.scale = Vector2(2, 2)
89 sprite.centered = false
90 drawer.add_child(sprite)
91
92 label = Label.new()
93 label.theme = preload("res://assets/themes/baseUI.tres")
94 label.add_theme_font_size_override("font_size", 32)
95 label.text = "@"
96 drawer.add_child(label)
97
98 #var local_tl = gridmap.map_to_local(Vector3i(cell_left, 0, cell_top))
99 #var global_tl = gridmap.to_global(local_tl)
100 #var local_br = gridmap.map_to_local(Vector3i(cell_right, 0, cell_bottom))
101 #var global_br = gridmap.to_global(local_br)
102
103 center_x_min = 0
104 center_x_max = cell_width - 128
105 center_y_min = 0
106 center_y_max = cell_height - 128
107
108 if center_x_max < center_x_min:
109 center_x_min = (center_x_min + center_x_max) / 2
110 center_x_max = center_x_min
111
112 if center_y_max < center_y_min:
113 center_y_min = (center_y_min + center_y_max) / 2
114 center_y_max = center_y_min
115
116
117func _process(_delta):
118 if visible == false:
119 return
120
121 drawer.position.x = clamp(player.position.x - cell_left - 64, center_x_min, center_x_max) * -2
122 drawer.position.y = clamp(player.position.z - cell_top - 64, center_y_min, center_y_max) * -2
123
124 label.position.x = (player.position.x - cell_left) * 2 - 16
125 label.position.y = (player.position.z - cell_top) * 2 - 16
126
127
128func _renderMap(gridmap):
129 var heights = {}
130
131 var rendered = Image.create_empty(cell_width, cell_height, false, Image.FORMAT_RGBA8)
132 rendered.fill(Color.TRANSPARENT)
133
134 var meshes_node = get_tree().get_root().get_node("scene/Meshes")
135 if meshes_node != null:
136 _renderMeshNode(gridmap, meshes_node, rendered)
137
138 for pos in gridmap.get_used_cells():
139 var in_plane = Vector2i(pos.x, pos.z)
140
141 if in_plane in heights and heights[in_plane] > pos.y:
142 continue
143
144 heights[in_plane] = pos.y
145
146 var cell_item = gridmap.get_cell_item(pos)
147 var mesh = gridmap.mesh_library.get_item_mesh(cell_item)
148 var material = mesh.surface_get_material(0)
149 var color = material.albedo_color
150
151 rendered.set_pixel(pos.x - cell_left, pos.z - cell_top, color)
152
153 return rendered
154
155
156func _renderMeshNode(gridmap, mesh, rendered):
157 if mesh is MeshInstance3D:
158 var local_tl = gridmap.map_to_local(Vector3i(cell_left, 0, cell_top))
159 var global_tl = gridmap.to_global(local_tl)
160 var mesh_material = mesh.get_surface_override_material(0)
161 if mesh_material != null:
162 var mesh_color = mesh_material.albedo_color
163
164 for y in range(
165 max(mesh.position.z - mesh.scale.z / 2 - global_tl.z, 0),
166 min(mesh.position.z + mesh.scale.z / 2 - global_tl.z, cell_height)
167 ):
168 for x in range(
169 max(mesh.position.x - mesh.scale.x / 2 - global_tl.x, 0),
170 min(mesh.position.x + mesh.scale.x / 2 - global_tl.x, cell_width)
171 ):
172 rendered.set_pixel(x, y, mesh_color)
173
174 for child in mesh.get_children():
175 _renderMeshNode(gridmap, child, rendered)
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 cd1813c..0000000 --- a/client/Archipelago/pauseMenu.gd +++ /dev/null
@@ -1,44 +0,0 @@
1extends "res://scripts/ui/pauseMenu.gd"
2
3var compass_button
4
5
6func _ready():
7 var ap_panel = Panel.new()
8 ap_panel.name = "Archipelago"
9 get_node("menu/settings/settingsInner/TabContainer").add_child(ap_panel)
10
11 var ap = global.get_node("Archipelago")
12
13 compass_button = CheckBox.new()
14 compass_button.text = "show compass"
15 compass_button.button_pressed = ap.show_compass
16 compass_button.position = Vector2(65, 100)
17 compass_button.theme = preload("res://assets/themes/baseUI.tres")
18 compass_button.add_theme_font_size_override("font_size", 60)
19 compass_button.pressed.connect(_toggle_compass)
20 ap_panel.add_child(compass_button)
21
22 super._ready()
23
24
25func _pause_game():
26 global.get_node("Textclient").dismiss()
27 super._pause_game()
28
29
30func _main_menu():
31 global.loaded = false
32 global.get_node("Archipelago").disconnect_from_ap()
33 global.get_node("Messages").clear()
34 global.get_node("Compass").visible = false
35 super._main_menu()
36
37
38func _toggle_compass():
39 var ap = global.get_node("Archipelago")
40 ap.show_compass = compass_button.button_pressed
41 ap.saveSettings()
42
43 var compass = global.get_node("Compass")
44 compass.visible = compass_button.button_pressed
diff --git a/client/Archipelago/player.gd b/client/Archipelago/player.gd deleted file mode 100644 index e58f1bc..0000000 --- a/client/Archipelago/player.gd +++ /dev/null
@@ -1,369 +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
21var compass
22
23
24func _ready():
25 var khl_script = load("res://scripts/nodes/keyHolderListener.gd")
26
27 var pause_menu = get_node("pause_menu")
28 pause_menu.layer = 3
29
30 var ap = global.get_node("Archipelago")
31 var gamedata = global.get_node("Gamedata")
32
33 compass = global.get_node("Compass")
34 compass.visible = ap.show_compass
35
36 ap.start_batching_locations()
37
38 # Set up door locations.
39 var map_id = gamedata.map_id_by_name.get(global.map)
40 for door in gamedata.objects.get_doors():
41 if door.get_map_id() != map_id:
42 continue
43
44 if not door.has_ap_id():
45 continue
46
47 if (
48 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
49 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
50 ):
51 continue
52
53 var locationListener = ap.SCRIPT_locationListener.new()
54 locationListener.location_id = door.get_ap_id()
55 locationListener.name = "locationListener_%d" % door.get_ap_id()
56
57 for panel_ref in door.get_panels():
58 var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()]
59 var panel_path = panel_data.get_path()
60
61 if panel_ref.has_answer():
62 for proxy in panel_data.get_proxies():
63 if proxy.get_answer() == panel_ref.get_answer():
64 panel_path = proxy.get_path()
65 break
66
67 locationListener.senders.append(NodePath("/root/scene/" + panel_path))
68
69 for keyholder_ref in door.get_keyholders():
70 var keyholder_data = gamedata.objects.get_keyholders()[keyholder_ref.get_keyholder()]
71
72 var khl = khl_script.new()
73 khl.name = (
74 "location_%d_keyholder_%d" % [door.get_ap_id(), keyholder_ref.get_keyholder()]
75 )
76 khl.answer = keyholder_ref.get_key()
77 khl.senders.append(NodePath("/root/scene/" + keyholder_data.get_path()))
78 get_parent().add_child.call_deferred(khl)
79
80 locationListener.senders.append(NodePath("../" + khl.name))
81
82 for sender in door.get_senders():
83 locationListener.senders.append(NodePath("/root/scene/" + sender))
84
85 if door.has_complete_at():
86 locationListener.complete_at = door.get_complete_at()
87
88 get_parent().add_child.call_deferred(locationListener)
89
90 # Set up letter locations.
91 for letter in gamedata.objects.get_letters():
92 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
93 if room.get_map_id() != map_id:
94 continue
95
96 var locationListener = ap.SCRIPT_locationListener.new()
97 locationListener.location_id = letter.get_ap_id()
98 locationListener.name = "locationListener_%d" % letter.get_ap_id()
99 locationListener.senders.append(NodePath("/root/scene/" + letter.get_path()))
100
101 get_parent().add_child.call_deferred(locationListener)
102
103 if (
104 ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2())
105 != ap.kLETTER_BEHAVIOR_VANILLA
106 ):
107 var scout = ap.scout_location(letter.get_ap_id())
108 if (
109 scout != null
110 and not (scout["player"] == ap.client._slot and scout["flags"] & 4 != 0)
111 ):
112 var item_name = "Unknown"
113 var item_player_game = ap.client._game_by_player[float(scout["player"])]
114 if ap.client._item_id_to_name[item_player_game].has(scout["item"]):
115 item_name = ap.client._item_id_to_name[item_player_game][scout["item"]]
116
117 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
118 letter.get_path()
119 )
120 if collectable != null:
121 collectable.setScoutedText.call_deferred(item_name)
122
123 # Set up mastery locations.
124 for mastery in gamedata.objects.get_masteries():
125 var room = gamedata.objects.get_rooms()[mastery.get_room_id()]
126 if room.get_map_id() != map_id:
127 continue
128
129 var locationListener = ap.SCRIPT_locationListener.new()
130 locationListener.location_id = mastery.get_ap_id()
131 locationListener.name = "locationListener_%d" % mastery.get_ap_id()
132 locationListener.senders.append(NodePath("/root/scene/" + mastery.get_path()))
133
134 get_parent().add_child.call_deferred(locationListener)
135
136 # Set up ending locations.
137 for ending in gamedata.objects.get_endings():
138 var room = gamedata.objects.get_rooms()[ending.get_room_id()]
139 if room.get_map_id() != map_id:
140 continue
141
142 var locationListener = ap.SCRIPT_locationListener.new()
143 locationListener.location_id = ending.get_ap_id()
144 locationListener.name = "locationListener_%d" % ending.get_ap_id()
145 locationListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
146
147 get_parent().add_child.call_deferred(locationListener)
148
149 if kEndingNameByVictoryValue.get(ap.victory_condition, null) == ending.get_name():
150 var victoryListener = ap.SCRIPT_victoryListener.new()
151 victoryListener.name = "victoryListener"
152 victoryListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
153
154 get_parent().add_child.call_deferred(victoryListener)
155
156 # Set up keyholder locations, in keyholder sanity.
157 if ap.keyholder_sanity:
158 for keyholder in gamedata.objects.get_keyholders():
159 if not keyholder.has_key():
160 continue
161
162 var room = gamedata.objects.get_rooms()[keyholder.get_room_id()]
163 if room.get_map_id() != map_id:
164 continue
165
166 var locationListener = ap.SCRIPT_locationListener.new()
167 locationListener.location_id = keyholder.get_ap_id()
168 locationListener.name = "locationListener_%d" % keyholder.get_ap_id()
169
170 var khl = khl_script.new()
171 khl.name = "location_%d_keyholder" % keyholder.get_ap_id()
172 khl.answer = keyholder.get_key()
173 khl.senders.append(NodePath("/root/scene/" + keyholder.get_path()))
174 get_parent().add_child.call_deferred(khl)
175
176 locationListener.senders.append(NodePath("../" + khl.name))
177
178 get_parent().add_child.call_deferred(locationListener)
179
180 # Block off roof access in Daedalus.
181 if global.map == "daedalus" and not ap.daedalus_roof_access:
182 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49)
183 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1)
184 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10)
185 _set_up_invis_wall(67.5, 11, 17, 16, 10, 1)
186 _set_up_invis_wall(50.5, 11, 14, 10, 10, 1)
187 _set_up_invis_wall(39, 10, 18.5, 1, 10, 22)
188 _set_up_invis_wall(20, 15, 18.5, 1, 10, 16)
189 _set_up_invis_wall(11.5, 15, 3, 32, 10, 1)
190 _set_up_invis_wall(11.5, 16, -20, 14, 20, 1)
191 _set_up_invis_wall(14, 16, -26.5, 1, 20, 4)
192 _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25)
193 _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1)
194 _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1)
195 _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11)
196 _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11)
197
198 var warp_exit_prefab = preload("res://objects/nodes/exit.tscn")
199 var warp_exit = warp_exit_prefab.instantiate()
200 warp_exit.name = "roof_access_blocker_warp_exit"
201 warp_exit.position = Vector3(58, 10, 0)
202 warp_exit.rotation_degrees.y = 90
203 get_parent().add_child.call_deferred(warp_exit)
204
205 var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn")
206 var warp_enter = warp_enter_prefab.instantiate()
207 warp_enter.target = warp_exit
208 warp_enter.position = Vector3(76.5, 30, 1)
209 warp_enter.scale = Vector3(4, 1.5, 1)
210 warp_enter.rotation_degrees.y = 90
211 get_parent().add_child.call_deferred(warp_enter)
212
213 if global.map == "the_entry":
214 # Remove door behind X1.
215 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
216 door_node.handleTriggered()
217
218 # Display win condition.
219 var sign_prefab = preload("res://objects/nodes/sign.tscn")
220 var sign1 = sign_prefab.instantiate()
221 sign1.position = Vector3(-7, 5, -15.01)
222 sign1.text = "victory"
223 get_parent().add_child.call_deferred(sign1)
224
225 var sign2 = sign_prefab.instantiate()
226 sign2.position = Vector3(-7, 4, -15.01)
227 sign2.text = "%s ending" % kEndingNameByVictoryValue.get(ap.victory_condition, "?")
228
229 var sign2_color = kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
230 if sign2_color == "white":
231 sign2_color = "silver"
232
233 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
234 get_parent().add_child.call_deferred(sign2)
235
236 # Add the strict purple ending validation.
237 if global.map == "the_sun_temple" and ap.strict_purple_ending:
238 var panel_prefab = preload("res://objects/nodes/panel.tscn")
239 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
240 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
241
242 var previous_panel = null
243 var next_y = -100
244 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
245 for word in words:
246 var panel = panel_prefab.instantiate()
247 panel.position = Vector3(0, next_y, 0)
248 next_y -= 10
249 panel.clue = word
250 panel.symbol = ""
251 panel.answer = word
252 panel.name = "EndCheck_%s" % word
253
254 var tpl = tpl_prefab.instantiate()
255 tpl.teleport_point = Vector3(0, 1, 0)
256 tpl.teleport_rotate = Vector3(-45, 180, 0)
257 tpl.target_path = panel
258 tpl.name = "Teleport"
259
260 if previous_panel == null:
261 tpl.senders.append(NodePath("/root/scene/Panels/End/panel_24"))
262 else:
263 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
264
265 var reversing = reverse_prefab.instantiate()
266 reversing.senders.append(NodePath(".."))
267 reversing.name = "Reversing"
268 tpl.senders.append(NodePath("../Reversing"))
269
270 panel.add_child.call_deferred(tpl)
271 panel.add_child.call_deferred(reversing)
272 get_parent().get_node("Panels").add_child.call_deferred(panel)
273
274 previous_panel = panel
275
276 # Duplicate the doors that usually wait on EQUINOX. We can't set the senders
277 # here for some reason so we actually set them in the door ready function.
278 var endplat = get_node("/root/scene/Components/Doors/EndPlatform")
279 var endplat2 = endplat.duplicate()
280 endplat2.name = "spe_EndPlatform"
281 endplat.get_parent().add_child.call_deferred(endplat2)
282 endplat.queue_free()
283
284 var entry2 = get_node("/root/scene/Components/Doors/entry_2")
285 var entry22 = entry2.duplicate()
286 entry22.name = "spe_entry_2"
287 entry2.get_parent().add_child.call_deferred(entry22)
288 entry2.queue_free()
289
290 # Add the strict cyan ending validation.
291 if global.map == "the_parthenon" and ap.strict_cyan_ending:
292 var panel_prefab = preload("res://objects/nodes/panel.tscn")
293 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
294 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
295
296 var previous_panel = null
297 var next_y = -100
298 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
299 for word in words:
300 var panel = panel_prefab.instantiate()
301 panel.position = Vector3(0, next_y, 0)
302 next_y -= 10
303 panel.clue = word
304 panel.symbol = "."
305 panel.answer = "%s%s" % [word, word]
306 panel.name = "EndCheck_%s" % word
307
308 var tpl = tpl_prefab.instantiate()
309 tpl.teleport_point = Vector3(0, 1, -11)
310 tpl.teleport_rotate = Vector3(-45, 0, 0)
311 tpl.target_path = panel
312 tpl.name = "Teleport"
313
314 if previous_panel == null:
315 tpl.senderGroup.append(NodePath("/root/scene/Panels/Rulers"))
316 else:
317 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
318
319 var reversing = reverse_prefab.instantiate()
320 reversing.senders.append(NodePath(".."))
321 reversing.name = "Reversing"
322 tpl.senders.append(NodePath("../Reversing"))
323
324 panel.add_child.call_deferred(tpl)
325 panel.add_child.call_deferred(reversing)
326 get_parent().get_node("Panels").add_child.call_deferred(panel)
327
328 previous_panel = panel
329
330 # Duplicate the door that usually waits on the rulers. We can't set the
331 # senders here for some reason so we actually set them in the door ready
332 # function.
333 var entry1 = get_node("/root/scene/Components/Doors/entry_1")
334 var entry12 = entry1.duplicate()
335 entry12.name = "spe_entry_1"
336 entry1.get_parent().add_child.call_deferred(entry12)
337 entry1.queue_free()
338
339 var minimap = ap.SCRIPT_minimap.new()
340 minimap.name = "Minimap"
341 get_parent().add_child.call_deferred(minimap)
342
343 super._ready()
344
345 await get_tree().process_frame
346 await get_tree().process_frame
347
348 ap.stop_batching_locations()
349
350
351func _set_up_invis_wall(x, y, z, sx, sy, sz):
352 var prefab = preload("res://objects/nodes/block.tscn")
353 var newwall = prefab.instantiate()
354 newwall.position.x = x
355 newwall.position.y = y
356 newwall.position.z = z
357 newwall.scale.x = sz
358 newwall.scale.y = sy
359 newwall.scale.z = sx
360 newwall.set_surface_override_material(0, preload("res://assets/materials/blackMatte.material"))
361 newwall.visibility_range_end = 3
362 newwall.visibility_range_end_margin = 1
363 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF
364 newwall.skeleton = ".."
365 get_parent().add_child.call_deferred(newwall)
366
367
368func _process(_dt):
369 compass.update_rotation(global_rotation.y)
diff --git a/client/Archipelago/rainbowText.gd b/client/Archipelago/rainbowText.gd deleted file mode 100644 index 9a4c1d0..0000000 --- a/client/Archipelago/rainbowText.gd +++ /dev/null
@@ -1,10 +0,0 @@
1extends RichTextEffect
2
3var bbcode = "rainbow"
4
5
6func _process_custom_fx(char_fx: CharFXTransform):
7 char_fx.color = Color.from_hsv(
8 char_fx.elapsed_time - floor(char_fx.elapsed_time), 1.0, 1.0, 1.0
9 )
10 return true
diff --git a/client/Archipelago/saver.gd b/client/Archipelago/saver.gd deleted file mode 100644 index 44bc179..0000000 --- a/client/Archipelago/saver.gd +++ /dev/null
@@ -1,23 +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()
10
11
12func reload():
13 # Just rewriting this whole thing so I can remove Chris's safeguard.
14 var file = FileAccess.open(path + type + ".save", FileAccess.READ)
15 if file:
16 var data = file.get_var(true)
17 file.close()
18 for datum in data:
19 var saveable = get_node_or_null(datum[0])
20 if saveable != null:
21 saveable.is_complete = datum[1]
22 if saveable.is_complete:
23 saveable.loadData(saveable.is_complete)
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 d5aa747..0000000 --- a/client/Archipelago/settings_screen.gd +++ /dev/null
@@ -1,261 +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 switcher.layer = 4
20
21 # Create the global AP manager, if it doesn't already exist.
22 if not global.has_node("Archipelago"):
23 var ap_script = ResourceLoader.load("user://maps/Archipelago/manager.gd")
24 var ap_instance = ap_script.new()
25 ap_instance.name = "Archipelago"
26
27 ap_instance.SCRIPT_client = load("user://maps/Archipelago/client.gd")
28 ap_instance.SCRIPT_keyboard = load("user://maps/Archipelago/keyboard.gd")
29 ap_instance.SCRIPT_locationListener = load("user://maps/Archipelago/locationListener.gd")
30 ap_instance.SCRIPT_minimap = load("user://maps/Archipelago/minimap.gd")
31 ap_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd")
32 ap_instance.SCRIPT_victoryListener = load("user://maps/Archipelago/victoryListener.gd")
33
34 global.add_child(ap_instance)
35
36 # Let's also inject any scripts we need to inject now.
37 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/animationListener.gd"))
38 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/collectable.gd"))
39 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/door.gd"))
40 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolder.gd"))
41 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolderChecker.gd"))
42 installScriptExtension(
43 ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd")
44 )
45 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd"))
46 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/panel.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd"))
48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd"))
49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd"))
50 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleport.gd"))
51 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd"))
52 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd"))
53 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldport.gd"))
54 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd"))
55
56 var proto_script = load("user://maps/Archipelago/generated/proto.gd")
57 var gamedata_script = load("user://maps/Archipelago/gamedata.gd")
58 var gamedata_instance = gamedata_script.new(proto_script)
59 gamedata_instance.load(
60 FileAccess.get_file_as_bytes("user://maps/Archipelago/generated/data.binpb")
61 )
62 gamedata_instance.name = "Gamedata"
63 global.add_child(gamedata_instance)
64
65 var messages_script = load("user://maps/Archipelago/messages.gd")
66 var messages_instance = messages_script.new()
67 messages_instance.name = "Messages"
68 messages_instance.SCRIPT_rainbowText = load("user://maps/Archipelago/rainbowText.gd")
69 global.add_child(messages_instance)
70
71 var textclient_script = load("user://maps/Archipelago/textclient.gd")
72 var textclient_instance = textclient_script.new()
73 textclient_instance.name = "Textclient"
74 global.add_child(textclient_instance)
75
76 var compass_overlay_script = load("user://maps/Archipelago/compass_overlay.gd")
77 var compass_overlay_instance = compass_overlay_script.new()
78 compass_overlay_instance.name = "Compass"
79 compass_overlay_instance.SCRIPT_compass = load("user://maps/Archipelago/compass.gd")
80 global.add_child(compass_overlay_instance)
81
82 var ap = global.get_node("Archipelago")
83 var gamedata = global.get_node("Gamedata")
84 ap.connect("ap_connected", connectionSuccessful)
85 ap.connect("could_not_connect", connectionUnsuccessful)
86 ap.connect("connect_status", connectionStatus)
87
88 # Populate textboxes with AP settings.
89 $Panel/server_box.text = ap.ap_server
90 $Panel/player_box.text = ap.ap_user
91 $Panel/password_box.text = ap.ap_pass
92
93 var history_box = $Panel/connection_history
94 if ap.connection_history.is_empty():
95 history_box.disabled = true
96 else:
97 history_box.disabled = false
98
99 var i = 0
100 for details in ap.connection_history:
101 history_box.get_popup().add_item("%s (%s)" % [details[1], details[0]], i)
102 i += 1
103
104 history_box.get_popup().connect("id_pressed", historySelected)
105
106 # Show client version.
107 $Panel/title.text = "ARCHIPELAGO (%d.%d)" % [gamedata.objects.get_version(), ap.MOD_VERSION]
108
109 # Increase font size in text boxes.
110 $Panel/server_box.add_theme_font_size_override("font_size", 36)
111 $Panel/player_box.add_theme_font_size_override("font_size", 36)
112 $Panel/password_box.add_theme_font_size_override("font_size", 36)
113
114 # Set up version mismatch dialog.
115 $Panel/VersionMismatch.connect("confirmed", startGame)
116 $Panel/VersionMismatch.get_cancel_button().pressed.connect(versionMismatchDeclined)
117
118
119# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd
120func installScriptExtension(childScript: Resource):
121 # Force Godot to compile the script now.
122 # We need to do this here to ensure that the inheritance chain is
123 # properly set up, and multiple mods can chain-extend the same
124 # class multiple times.
125 # This is also needed to make Godot instantiate the extended class
126 # when creating singletons.
127 # The actual instance is thrown away.
128 childScript.new()
129
130 var parentScript = childScript.get_base_script()
131 var parentScriptPath = parentScript.resource_path
132 global._print("ModLoader: Installing script extension over %s" % parentScriptPath)
133 childScript.take_over_path(parentScriptPath)
134
135
136func connectionStatus(message):
137 var popup = self.get_node("Panel/AcceptDialog")
138 popup.title = "Connecting to Archipelago"
139 popup.dialog_text = message
140 popup.exclusive = true
141 popup.get_ok_button().visible = false
142 popup.popup_centered()
143
144
145func connectionSuccessful():
146 var ap = global.get_node("Archipelago")
147 var gamedata = global.get_node("Gamedata")
148
149 # Check for major version mismatch.
150 if ap.apworld_version[0] != gamedata.objects.get_version():
151 $Panel/AcceptDialog.exclusive = false
152
153 var popup = self.get_node("Panel/VersionMismatch")
154 popup.title = "Version Mismatch!"
155 popup.dialog_text = (
156 "This slot was generated using v%d.%d of the Lingo 2 apworld,\nwhich has a different major version than this client (v%d.%d).\nIt is highly recommended to play using the correct version of the client.\nYou may experience bugs or logic issues if you continue."
157 % [
158 ap.apworld_version[0],
159 ap.apworld_version[1],
160 gamedata.objects.get_version(),
161 ap.MOD_VERSION
162 ]
163 )
164 popup.exclusive = true
165 popup.popup_centered()
166
167 return
168
169 startGame()
170
171
172func startGame():
173 var ap = global.get_node("Archipelago")
174
175 # Save connection details
176 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass]
177 if ap.connection_history.has(connection_details):
178 ap.connection_history.erase(connection_details)
179 ap.connection_history.push_front(connection_details)
180 if ap.connection_history.size() > 10:
181 ap.connection_history.resize(10)
182 ap.saveSettings()
183
184 # Switch to the_entry
185 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
186 global.user = ap.getSaveFileName()
187 global.universe = "lingo"
188 global.map = "the_entry"
189
190 unlocks.resetCollectables()
191 unlocks.resetData()
192
193 ap.setup_keys()
194
195 unlocks.loadCollectables()
196 unlocks.loadData()
197 unlocks.unlockKey("capslock", 1)
198
199 if ap.shuffle_worldports:
200 settings.worldport_fades = "default"
201 else:
202 settings.worldport_fades = "never"
203
204 clearResourceCache("res://objects/meshes/gridDoor.tscn")
205 clearResourceCache("res://objects/nodes/collectable.tscn")
206 clearResourceCache("res://objects/nodes/door.tscn")
207 clearResourceCache("res://objects/nodes/keyHolder.tscn")
208 clearResourceCache("res://objects/nodes/listeners/animationListener.tscn")
209 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
210 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
211 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
212 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn")
213 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
214 clearResourceCache("res://objects/nodes/panel.tscn")
215 clearResourceCache("res://objects/nodes/player.tscn")
216 clearResourceCache("res://objects/nodes/saver.tscn")
217 clearResourceCache("res://objects/nodes/teleport.tscn")
218 clearResourceCache("res://objects/nodes/worldport.tscn")
219 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn")
220
221 var paintings_dir = DirAccess.open("res://objects/meshes/paintings")
222 if paintings_dir:
223 paintings_dir.list_dir_begin()
224 var file_name = paintings_dir.get_next()
225 while file_name != "":
226 if not paintings_dir.current_is_dir() and file_name.ends_with(".tscn"):
227 clearResourceCache("res://objects/meshes/paintings/" + file_name)
228 file_name = paintings_dir.get_next()
229
230 switcher.switch_map.call_deferred("res://objects/scenes/the_entry.tscn")
231
232
233func connectionUnsuccessful(error_message):
234 $Panel/connect_button.disabled = false
235
236 var popup = $Panel/AcceptDialog
237 popup.title = "Could not connect to Archipelago"
238 popup.dialog_text = error_message
239 popup.exclusive = true
240 popup.get_ok_button().visible = true
241 popup.popup_centered()
242
243 $Panel/connect_button.disabled = false
244
245
246func versionMismatchDeclined():
247 $Panel/AcceptDialog.hide()
248 $Panel/connect_button.disabled = false
249
250
251func historySelected(index):
252 var ap = global.get_node("Archipelago")
253 var details = ap.connection_history[index]
254
255 $Panel/server_box.text = details[0]
256 $Panel/player_box.text = details[1]
257 $Panel/password_box.text = details[2]
258
259
260func clearResourceCache(path):
261 ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)
diff --git a/client/Archipelago/teleport.gd b/client/Archipelago/teleport.gd deleted file mode 100644 index 428d50b..0000000 --- a/client/Archipelago/teleport.gd +++ /dev/null
@@ -1,38 +0,0 @@
1extends "res://scripts/nodes/teleport.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/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 26831b4..0000000 --- a/client/Archipelago/textclient.gd +++ /dev/null
@@ -1,95 +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 layer = 2
12
13 panel = Panel.new()
14 panel.set_name("Panel")
15 panel.offset_left = 100
16 panel.offset_right = 1820
17 panel.offset_top = 100
18 panel.offset_bottom = 980
19 panel.visible = false
20 add_child(panel)
21
22 label = RichTextLabel.new()
23 label.set_name("Label")
24 label.offset_left = 80
25 label.offset_right = 1640
26 label.offset_top = 80
27 label.offset_bottom = 720
28 label.scroll_following = true
29 label.selection_enabled = true
30 panel.add_child(label)
31
32 label.push_font(load("res://assets/fonts/Lingo2.ttf"))
33 label.push_font_size(36)
34
35 var entry_style = StyleBoxFlat.new()
36 entry_style.bg_color = Color(0.9, 0.9, 0.9, 1)
37
38 entry = LineEdit.new()
39 entry.set_name("Entry")
40 entry.offset_left = 80
41 entry.offset_right = 1640
42 entry.offset_top = 760
43 entry.offset_bottom = 840
44 entry.add_theme_font_override("font", load("res://assets/fonts/Lingo2.ttf"))
45 entry.add_theme_font_size_override("font_size", 36)
46 entry.add_theme_color_override("font_color", Color(0, 0, 0, 1))
47 entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1))
48 entry.add_theme_stylebox_override("focus", entry_style)
49 panel.add_child(entry)
50 entry.connect("text_submitted", text_entered)
51
52
53func _input(event):
54 if global.loaded and event is InputEventKey and event.pressed:
55 if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT):
56 if !get_tree().paused:
57 is_open = true
58 get_tree().paused = true
59 Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
60 panel.visible = true
61 entry.grab_focus()
62 get_viewport().set_input_as_handled()
63 else:
64 dismiss()
65 elif event.keycode == KEY_ESCAPE:
66 if is_open:
67 dismiss()
68 get_viewport().set_input_as_handled()
69
70
71func dismiss():
72 if is_open:
73 get_tree().paused = false
74 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
75 panel.visible = false
76 is_open = false
77
78
79func parse_printjson(text):
80 label.append_text("[p]" + text + "[/p]")
81
82
83func text_entered(text):
84 var ap = global.get_node("Archipelago")
85 var cmd = text.trim_suffix("\n")
86 entry.text = ""
87 if OS.is_debug_build():
88 if cmd.begins_with("/tp_map "):
89 var new_map = cmd.substr(8)
90 global.map = new_map
91 global.sets_entry_point = false
92 switcher.switch_map("res://objects/scenes/%s.tscn" % new_map)
93 return
94
95 ap.client.say(cmd)
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/worldport.gd b/client/Archipelago/worldport.gd deleted file mode 100644 index cdca248..0000000 --- a/client/Archipelago/worldport.gd +++ /dev/null
@@ -1,53 +0,0 @@
1extends "res://scripts/nodes/worldport.gd"
2
3var absolute_rotation = false
4var target_rotation = 0
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 ap = global.get_node("Archipelago")
13
14 if ap.shuffle_worldports:
15 var gamedata = global.get_node("Gamedata")
16 var port_id = gamedata.get_port_for_map_node_path(global.map, node_path)
17 if port_id != null:
18 if port_id in ap.port_pairings:
19 var target_port = gamedata.objects.get_ports()[ap.port_pairings[port_id]]
20 var target_room = gamedata.objects.get_rooms()[target_port.get_room_id()]
21 var target_map = gamedata.objects.get_maps()[target_room.get_map_id()]
22
23 exit = target_map.get_name()
24 entry_point.x = target_port.get_destination().get_x()
25 entry_point.y = target_port.get_destination().get_y()
26 entry_point.z = target_port.get_destination().get_z()
27 absolute_rotation = true
28 target_rotation = target_port.get_rotation()
29 sets_entry_point = true
30 invisible = false
31 fades = true
32
33 if global.map == "icarus" and exit == "daedalus":
34 if not ap.daedalus_roof_access:
35 entry_point = Vector3(58, 10, 0)
36
37 super._ready()
38
39
40func bodyEntered(body):
41 if body.is_in_group("player"):
42 if absolute_rotation:
43 entry_rotate.y = target_rotation - body.rotation_degrees.y
44
45 super.bodyEntered(body)
46
47
48func changeScene():
49 var player = get_tree().get_root().get_node("scene/player")
50 if player != null:
51 player.playable = false
52
53 super.changeScene()
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()