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.gd65
-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.gd22
-rw-r--r--client/Archipelago/gamedata.gd62
-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/manager.gd458
-rw-r--r--client/Archipelago/messages.gd18
-rw-r--r--client/Archipelago/painting.gd28
-rw-r--r--client/Archipelago/panel.gd101
-rw-r--r--client/Archipelago/pauseMenu.gd44
-rw-r--r--client/Archipelago/player.gd326
-rw-r--r--client/Archipelago/saver.gd23
-rw-r--r--client/Archipelago/settings_screen.gd100
-rw-r--r--client/Archipelago/teleport.gd38
-rw-r--r--client/Archipelago/teleportListener.gd49
-rw-r--r--client/Archipelago/textclient.gd86
-rw-r--r--client/Archipelago/victoryListener.gd20
-rw-r--r--client/Archipelago/visibilityListener.gd38
-rw-r--r--client/Archipelago/worldport.gd10
-rw-r--r--client/Archipelago/worldportListener.gd8
26 files changed, 1794 insertions, 108 deletions
diff --git a/client/Archipelago/animationListener.gd b/client/Archipelago/animationListener.gd new file mode 100644 index 0000000..c3b26db --- /dev/null +++ b/client/Archipelago/animationListener.gd
@@ -0,0 +1,38 @@
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 index d394b6c..843647d 100644 --- a/client/Archipelago/client.gd +++ b/client/Archipelago/client.gd
@@ -32,20 +32,26 @@ var _players = []
32var _player_name_by_slot = {} 32var _player_name_by_slot = {}
33var _game_by_player = {} 33var _game_by_player = {}
34var _checked_locations = [] 34var _checked_locations = []
35var _received_items = [] 35var _received_indexes = []
36var _received_items = {}
36var _slot_data = {} 37var _slot_data = {}
37 38
38signal could_not_connect 39signal could_not_connect
39signal connect_status 40signal connect_status
40signal client_connected 41signal client_connected(slot_data)
41signal item_received(item_id, index, player, flags) 42signal item_received(item_id, index, player, flags, amount)
42signal message_received(message) 43signal message_received(message)
44signal location_scout_received(item_id, location_id, player, flags)
43 45
44 46
45func _init(): 47func _init():
48 set_process_mode(Node.PROCESS_MODE_ALWAYS)
49
50 _ws.inbound_buffer_size = 8388608
51
46 global._print("Instantiated APClient") 52 global._print("Instantiated APClient")
47 53
48 # Read AP settings from file, if there are any 54 # Read AP datapackages from file, if there are any
49 if FileAccess.file_exists("user://ap_datapackages"): 55 if FileAccess.file_exists("user://ap_datapackages"):
50 var file = FileAccess.open("user://ap_datapackages", FileAccess.READ) 56 var file = FileAccess.open("user://ap_datapackages", FileAccess.READ)
51 var data = file.get_var(true) 57 var data = file.get_var(true)
@@ -74,6 +80,8 @@ func _reset_state():
74 _authenticated = false 80 _authenticated = false
75 _try_wss = false 81 _try_wss = false
76 _has_connected = false 82 _has_connected = false
83 _received_items = {}
84 _received_indexes = []
77 85
78 86
79func _errored(): 87func _errored():
@@ -183,7 +191,7 @@ func _process(_delta):
183 player["slot"] 191 player["slot"]
184 )]["game"] 192 )]["game"]
185 193
186 emit_signal("client_connected") 194 emit_signal("client_connected", _slot_data)
187 195
188 elif cmd == "ConnectionRefused": 196 elif cmd == "ConnectionRefused":
189 var error_message = "" 197 var error_message = ""
@@ -219,7 +227,7 @@ func _process(_delta):
219 error_message = "Unknown error." 227 error_message = "Unknown error."
220 228
221 _initiated_disconnect = true 229 _initiated_disconnect = true
222 _ws.disconnect_from_host() 230 _ws.close()
223 231
224 emit_signal("could_not_connect", error_message) 232 emit_signal("could_not_connect", error_message)
225 global._print("Connection to AP refused") 233 global._print("Connection to AP refused")
@@ -228,21 +236,40 @@ func _process(_delta):
228 elif cmd == "ReceivedItems": 236 elif cmd == "ReceivedItems":
229 var i = 0 237 var i = 0
230 for item in message["items"]: 238 for item in message["items"]:
231 if not _received_items.has(int(item["item"])): 239 var index = int(message["index"] + i)
232 _received_items.append(int(item["item"])) 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
233 250
234 emit_signal( 251 emit_signal(
235 "item_received", 252 "item_received",
236 int(item["item"]), 253 item_id,
237 int(message["index"]) + i, 254 index,
238 int(item["player"]), 255 int(item["player"]),
239 int(item["flags"]) 256 int(item["flags"]),
257 _received_items[item_id]
240 ) 258 )
241 i += 1
242 259
243 elif cmd == "PrintJSON": 260 elif cmd == "PrintJSON":
244 emit_signal("message_received", message) 261 emit_signal("message_received", message)
245 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
246 elif state == WebSocketPeer.STATE_CLOSED: 273 elif state == WebSocketPeer.STATE_CLOSED:
247 if _has_connected: 274 if _has_connected:
248 _closed() 275 _closed()
@@ -284,7 +311,7 @@ func connectToServer(server, un, pw):
284 % err 311 % err
285 ) 312 )
286 ) 313 )
287 global._print("Could not connect to AP: " + err) 314 global._print("Could not connect to AP: %d" % err)
288 return 315 return
289 _should_process = true 316 _should_process = true
290 317
@@ -353,6 +380,10 @@ func sendLocation(loc_id):
353 sendMessage([{"cmd": "LocationChecks", "locations": [loc_id]}]) 380 sendMessage([{"cmd": "LocationChecks", "locations": [loc_id]}])
354 381
355 382
383func sendLocations(loc_ids):
384 sendMessage([{"cmd": "LocationChecks", "locations": loc_ids}])
385
386
356func setValue(key, value, operation = "replace"): 387func setValue(key, value, operation = "replace"):
357 sendMessage( 388 sendMessage(
358 [ 389 [
@@ -374,5 +405,13 @@ func completedGoal():
374 sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL 405 sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL
375 406
376 407
408func scoutLocations(loc_ids):
409 sendMessage([{"cmd": "LocationScouts", "locations": loc_ids}])
410
411
377func hasItem(item_id): 412func hasItem(item_id):
378 return _received_items.has(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 new file mode 100644 index 0000000..4a17a2a --- /dev/null +++ b/client/Archipelago/collectable.gd
@@ -0,0 +1,16 @@
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 new file mode 100644 index 0000000..c90475a --- /dev/null +++ b/client/Archipelago/compass.gd
@@ -0,0 +1,66 @@
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 new file mode 100644 index 0000000..56e81ff --- /dev/null +++ b/client/Archipelago/compass_overlay.gd
@@ -0,0 +1,17 @@
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 index 731eca4..49f5728 100644 --- a/client/Archipelago/door.gd +++ b/client/Archipelago/door.gd
@@ -1,6 +1,7 @@
1extends "res://scripts/nodes/door.gd" 1extends "res://scripts/nodes/door.gd"
2 2
3var item_id 3var item_id
4var item_amount
4 5
5 6
6func _ready(): 7func _ready():
@@ -8,17 +9,16 @@ func _ready():
8 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() 9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
9 ) 10 )
10 11
11 print("node: %s" % node_path)
12
13 var gamedata = global.get_node("Gamedata") 12 var gamedata = global.get_node("Gamedata")
14 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) 13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
15 if door_id != null: 14 if door_id != null:
16 print("door_id: %d" % door_id)
17
18 var ap = global.get_node("Archipelago") 15 var ap = global.get_node("Archipelago")
19 item_id = ap.get_item_id_for_door(door_id) 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]
20 21
21 if item_id != null:
22 self.senders = [] 22 self.senders = []
23 self.senderGroup = [] 23 self.senderGroup = []
24 self.nested = false 24 self.nested = false
@@ -28,11 +28,19 @@ func _ready():
28 28
29 call_deferred("_readier") 29 call_deferred("_readier")
30 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
31 super._ready() 39 super._ready()
32 40
33 41
34func _readier(): 42func _readier():
35 var ap = global.get_node("Archipelago") 43 var ap = global.get_node("Archipelago")
36 44
37 if ap.has_item(item_id): 45 if ap.client.getItemAmount(item_id) >= item_amount:
38 handleTriggered() 46 handleTriggered()
diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd index 16368a9..41d966a 100644 --- a/client/Archipelago/gamedata.gd +++ b/client/Archipelago/gamedata.gd
@@ -5,13 +5,42 @@ var SCRIPT_proto
5var objects 5var objects
6var door_id_by_map_node_path = {} 6var door_id_by_map_node_path = {}
7var painting_id_by_map_node_path = {} 7var painting_id_by_map_node_path = {}
8var panel_id_by_map_node_path = {}
8var door_id_by_ap_id = {} 9var door_id_by_ap_id = {}
9var map_id_by_name = {} 10var map_id_by_name = {}
11var progressive_id_by_ap_id = {}
12var letter_id_by_ap_id = {}
13var symbol_item_ids = []
14var anti_trap_ids = {}
15
16var kSYMBOL_ITEMS
10 17
11 18
12func _init(proto_script): 19func _init(proto_script):
13 SCRIPT_proto = proto_script 20 SCRIPT_proto = proto_script
14 21
22 kSYMBOL_ITEMS = {
23 SCRIPT_proto.PuzzleSymbol.SUN: "Sun Symbol",
24 SCRIPT_proto.PuzzleSymbol.SPARKLES: "Sparkles Symbol",
25 SCRIPT_proto.PuzzleSymbol.ZERO: "Zero Symbol",
26 SCRIPT_proto.PuzzleSymbol.EXAMPLE: "Example Symbol",
27 SCRIPT_proto.PuzzleSymbol.BOXES: "Boxes Symbol",
28 SCRIPT_proto.PuzzleSymbol.PLANET: "Planet Symbol",
29 SCRIPT_proto.PuzzleSymbol.PYRAMID: "Pyramid Symbol",
30 SCRIPT_proto.PuzzleSymbol.CROSS: "Cross Symbol",
31 SCRIPT_proto.PuzzleSymbol.SWEET: "Sweet Symbol",
32 SCRIPT_proto.PuzzleSymbol.GENDER: "Gender Symbol",
33 SCRIPT_proto.PuzzleSymbol.AGE: "Age Symbol",
34 SCRIPT_proto.PuzzleSymbol.SOUND: "Sound Symbol",
35 SCRIPT_proto.PuzzleSymbol.ANAGRAM: "Anagram Symbol",
36 SCRIPT_proto.PuzzleSymbol.JOB: "Job Symbol",
37 SCRIPT_proto.PuzzleSymbol.STARS: "Stars Symbol",
38 SCRIPT_proto.PuzzleSymbol.NULL: "Null Symbol",
39 SCRIPT_proto.PuzzleSymbol.EVAL: "Eval Symbol",
40 SCRIPT_proto.PuzzleSymbol.LINGO: "Lingo Symbol",
41 SCRIPT_proto.PuzzleSymbol.QUESTION: "Question Symbol",
42 }
43
15 44
16func load(data_bytes): 45func load(data_bytes):
17 objects = SCRIPT_proto.AllObjects.new() 46 objects = SCRIPT_proto.AllObjects.new()
@@ -50,6 +79,31 @@ func load(data_bytes):
50 79
51 var _map_data = painting_id_by_map_node_path[map.get_name()] 80 var _map_data = painting_id_by_map_node_path[map.get_name()]
52 81
82 for progressive in objects.get_progressives():
83 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id()
84
85 for letter in objects.get_letters():
86 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id()
87
88 for panel in objects.get_panels():
89 var room = objects.get_rooms()[panel.get_room_id()]
90 var map = objects.get_maps()[room.get_map_id()]
91
92 if not map.get_name() in panel_id_by_map_node_path:
93 panel_id_by_map_node_path[map.get_name()] = {}
94
95 var map_data = panel_id_by_map_node_path[map.get_name()]
96 map_data[panel.get_path()] = panel.get_id()
97
98 for symbol_name in kSYMBOL_ITEMS.values():
99 symbol_item_ids.append(objects.get_special_ids()[symbol_name])
100
101 for special_name in objects.get_special_ids().keys():
102 if special_name.begins_with("Anti "):
103 anti_trap_ids[objects.get_special_ids()[special_name]] = (
104 special_name.substr(5).to_lower()
105 )
106
53 107
54func get_door_for_map_node_path(map_name, node_path): 108func get_door_for_map_node_path(map_name, node_path):
55 if not door_id_by_map_node_path.has(map_name): 109 if not door_id_by_map_node_path.has(map_name):
@@ -59,6 +113,14 @@ func get_door_for_map_node_path(map_name, node_path):
59 return map_data.get(node_path, null) 113 return map_data.get(node_path, null)
60 114
61 115
116func get_panel_for_map_node_path(map_name, node_path):
117 if not panel_id_by_map_node_path.has(map_name):
118 return null
119
120 var map_data = panel_id_by_map_node_path[map_name]
121 return map_data.get(node_path, null)
122
123
62func get_door_ap_id(door_id): 124func get_door_ap_id(door_id):
63 var door = objects.get_doors()[door_id] 125 var door = objects.get_doors()[door_id]
64 if door.has_ap_id(): 126 if door.has_ap_id():
diff --git a/client/Archipelago/keyHolder.gd b/client/Archipelago/keyHolder.gd new file mode 100644 index 0000000..3c037ff --- /dev/null +++ b/client/Archipelago/keyHolder.gd
@@ -0,0 +1,38 @@
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 new file mode 100644 index 0000000..a75a9e4 --- /dev/null +++ b/client/Archipelago/keyHolderChecker.gd
@@ -0,0 +1,24 @@
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 new file mode 100644 index 0000000..d5300f3 --- /dev/null +++ b/client/Archipelago/keyHolderResetterListener.gd
@@ -0,0 +1,8 @@
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 new file mode 100644 index 0000000..450566d --- /dev/null +++ b/client/Archipelago/keyboard.gd
@@ -0,0 +1,199 @@
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/manager.gd b/client/Archipelago/manager.gd index 99cb47b..218870c 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -1,45 +1,149 @@
1extends Node 1extends Node
2 2
3const my_version = "0.1.0" 3const MOD_VERSION = 7
4 4
5var SCRIPT_client 5var SCRIPT_client
6var SCRIPT_keyboard
6var SCRIPT_locationListener 7var SCRIPT_locationListener
7var SCRIPT_uuid 8var SCRIPT_uuid
9var SCRIPT_victoryListener
8 10
9var ap_server = "" 11var ap_server = ""
10var ap_user = "" 12var ap_user = ""
11var ap_pass = "" 13var ap_pass = ""
12var connection_history = [] 14var connection_history = []
15var show_compass = false
13 16
14var client 17var client
18var keyboard
15 19
16var _received_indexes = [] 20var _localdata_file = ""
17var _last_new_item = -1 21var _last_new_item = -1
22var _batch_locations = false
23var _held_locations = []
24var _held_location_scouts = []
25var _location_scouts = {}
26var _item_locks = {}
27var _inverse_item_locks = {}
28var _held_letters = {}
29var _letters_setup = false
30
31const kSHUFFLE_LETTERS_VANILLA = 0
32const kSHUFFLE_LETTERS_UNLOCKED = 1
33const kSHUFFLE_LETTERS_PROGRESSIVE = 2
34const kSHUFFLE_LETTERS_VANILLA_CYAN = 3
35const kSHUFFLE_LETTERS_ITEM_CYAN = 4
36
37const kLETTER_BEHAVIOR_VANILLA = 0
38const kLETTER_BEHAVIOR_ITEM = 1
39const kLETTER_BEHAVIOR_UNLOCKED = 2
40
41const kCYAN_DOOR_BEHAVIOR_H2 = 0
42const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1
43const kCYAN_DOOR_BEHAVIOR_ITEM = 2
44
45var apworld_version = [0, 0]
46var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
47var daedalus_roof_access = false
48var keyholder_sanity = false
49var shuffle_control_center_colors = false
50var shuffle_doors = false
51var shuffle_gallery_paintings = false
52var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
53var shuffle_symbols = false
54var strict_cyan_ending = false
55var strict_purple_ending = false
56var victory_condition = -1
18 57
19signal could_not_connect 58signal could_not_connect
20signal connect_status 59signal connect_status
21signal ap_connected 60signal ap_connected
22 61
23 62
63func _init():
64 # Read AP settings from file, if there are any
65 if FileAccess.file_exists("user://ap_settings"):
66 var file = FileAccess.open("user://ap_settings", FileAccess.READ)
67 var data = file.get_var(true)
68 file.close()
69
70 if typeof(data) != TYPE_ARRAY:
71 global._print("AP settings file is corrupted")
72 data = []
73
74 if data.size() > 0:
75 ap_server = data[0]
76
77 if data.size() > 1:
78 ap_user = data[1]
79
80 if data.size() > 2:
81 ap_pass = data[2]
82
83 if data.size() > 3:
84 connection_history = data[3]
85
86 if data.size() > 4:
87 show_compass = data[4]
88
89
24func _ready(): 90func _ready():
25 client = SCRIPT_client.new() 91 client = SCRIPT_client.new()
26 client.SCRIPT_uuid = SCRIPT_uuid 92 client.SCRIPT_uuid = SCRIPT_uuid
27 93
28 client.connect("item_received", _process_item) 94 client.connect("item_received", _process_item)
95 client.connect("message_received", _process_message)
96 client.connect("location_scout_received", _process_location_scout)
29 client.connect("could_not_connect", _client_could_not_connect) 97 client.connect("could_not_connect", _client_could_not_connect)
30 client.connect("connect_status", _client_connect_status) 98 client.connect("connect_status", _client_connect_status)
31 client.connect("client_connected", _client_connected) 99 client.connect("client_connected", _client_connected)
32 100
33 add_child(client) 101 add_child(client)
34 102
103 keyboard = SCRIPT_keyboard.new()
104 add_child(keyboard)
105
35 106
36func saveSettings(): 107func saveSettings():
37 pass 108 # Save the AP settings to disk.
109 var path = "user://ap_settings"
110 var file = FileAccess.open(path, FileAccess.WRITE)
111
112 var data = [
113 ap_server,
114 ap_user,
115 ap_pass,
116 connection_history,
117 show_compass,
118 ]
119 file.store_var(data, true)
120 file.close()
121
122
123func saveLocaldata():
124 # Save the MW/slot specific settings to disk.
125 var dir = DirAccess.open("user://")
126 var folder = "archipelago_data"
127 if not dir.dir_exists(folder):
128 dir.make_dir(folder)
129
130 var file = FileAccess.open(_localdata_file, FileAccess.WRITE)
131
132 var data = [
133 _last_new_item,
134 ]
135 file.store_var(data, true)
136 file.close()
38 137
39 138
40func connectToServer(): 139func connectToServer():
41 _received_indexes = []
42 _last_new_item = -1 140 _last_new_item = -1
141 _batch_locations = false
142 _held_locations = []
143 _held_location_scouts = []
144 _location_scouts = {}
145 _letters_setup = false
146 _held_letters = {}
43 147
44 client.connectToServer(ap_server, ap_user, ap_pass) 148 client.connectToServer(ap_server, ap_user, ap_pass)
45 149
@@ -53,65 +157,73 @@ func disconnect_from_ap():
53 157
54 158
55func get_item_id_for_door(door_id): 159func get_item_id_for_door(door_id):
56 var gamedata = global.get_node("Gamedata") 160 return _item_locks.get(door_id, null)
57 var door = gamedata.objects.get_doors()[door_id]
58 if (
59 door.get_type() == gamedata.SCRIPT_proto.DoorType.EVENT
60 or door.get_type() == gamedata.SCRIPT_proto.DoorType.LOCATION_ONLY
61 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
62 ):
63 return null
64 return gamedata.get_door_ap_id(door_id)
65
66
67func has_item(item_id):
68 return client.hasItem(item_id)
69 161
70 162
71func _process_item(item, index, from, flags): 163func _process_item(item, index, from, flags, amount):
72 if index != null:
73 if _received_indexes.has(index):
74 # Do not re-process items.
75 return
76
77 _received_indexes.append(index)
78
79 var item_name = "Unknown" 164 var item_name = "Unknown"
80 if client._item_id_to_name["Lingo 2"].has(item): 165 if client._item_id_to_name["Lingo 2"].has(item):
81 item_name = client._item_id_to_name["Lingo 2"][item] 166 item_name = client._item_id_to_name["Lingo 2"][item]
82 167
83 var gamedata = global.get_node("Gamedata") 168 var gamedata = global.get_node("Gamedata")
84 var door_id = gamedata.door_id_by_ap_id.get(item, null) 169
85 if door_id != null and gamedata.get_door_map_name(door_id) == global.map: 170 var prog_id = null
86 var receivers = gamedata.get_door_receivers(door_id) 171 if _inverse_item_locks.has(item):
87 var scene = get_tree().get_root().get_node_or_null("scene") 172 for lock in _inverse_item_locks.get(item):
88 if scene != null: 173 if lock[1] != amount:
89 for receiver in receivers: 174 continue
90 var rnode = scene.get_node_or_null(receiver) 175
91 if rnode != null: 176 if gamedata.progressive_id_by_ap_id.has(item):
92 rnode.handleTriggered() 177 prog_id = lock[0]
93 for painting_id in gamedata.objects.get_doors()[door_id].get_move_paintings(): 178
94 var painting = gamedata.objects.get_paintings()[painting_id] 179 if gamedata.get_door_map_name(lock[0]) != global.map:
95 var pnode = scene.get_node_or_null(painting.get_path() + "/teleportListener") 180 continue
96 if pnode != null: 181
97 pnode.handleTriggered() 182 var receivers = gamedata.get_door_receivers(lock[0])
183 var scene = get_tree().get_root().get_node_or_null("scene")
184 if scene != null:
185 for receiver in receivers:
186 var rnode = scene.get_node_or_null(receiver)
187 if rnode != null:
188 rnode.handleTriggered()
189
190 var letter_id = gamedata.letter_id_by_ap_id.get(item, null)
191 if letter_id != null:
192 var letter = gamedata.objects.get_letters()[letter_id]
193 if not letter.has_level2() or not letter.get_level2():
194 _process_key_item(letter.get_key(), amount)
195
196 if gamedata.symbol_item_ids.has(item):
197 var player = get_tree().get_root().get_node_or_null("scene/player")
198 if player != null:
199 player.emit_signal("evaluate_solvability")
98 200
99 # Show a message about the item if it's new. 201 # Show a message about the item if it's new.
100 if index != null and index > _last_new_item: 202 if index != null and index > _last_new_item:
101 _last_new_item = index 203 _last_new_item = index
102 #saveLocaldata() 204 saveLocaldata()
103 205
104 var player_name = "Unknown" 206 var player_name = "Unknown"
105 if client._player_name_by_slot.has(from): 207 if client._player_name_by_slot.has(float(from)):
106 player_name = client._player_name_by_slot[from] 208 player_name = client._player_name_by_slot[float(from)]
107 209
108 var item_color = colorForItemType(flags) 210 var item_color = colorForItemType(flags)
109 211
212 var full_item_name = item_name
213 if prog_id != null:
214 var door = gamedata.objects.get_doors()[prog_id]
215 full_item_name = "%s (%s)" % [item_name, door.get_name()]
216
110 var message 217 var message
111 if from == client._slot: 218 if from == client._slot:
112 message = "Found [color=%s]%s[/color]" % [item_color, item_name] 219 message = "Found [color=%s]%s[/color]" % [item_color, full_item_name]
113 else: 220 else:
114 message = "Received [color=%s]%s[/color] from %s" % [item_color, item_name, player_name] 221 message = (
222 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name]
223 )
224
225 if gamedata.anti_trap_ids.has(item):
226 keyboard.block_letter(gamedata.anti_trap_ids[item])
115 227
116 global._print(message) 228 global._print(message)
117 229
@@ -119,6 +231,8 @@ func _process_item(item, index, from, flags):
119 231
120 232
121func _process_message(message): 233func _process_message(message):
234 parse_printjson_for_textclient(message)
235
122 if ( 236 if (
123 !message.has("receiving") 237 !message.has("receiving")
124 or !message.has("item") 238 or !message.has("item")
@@ -128,15 +242,15 @@ func _process_message(message):
128 242
129 var item_name = "Unknown" 243 var item_name = "Unknown"
130 var item_player_game = client._game_by_player[message["receiving"]] 244 var item_player_game = client._game_by_player[message["receiving"]]
131 if client._item_id_to_name[item_player_game].has(message["item"]["item"]): 245 if client._item_id_to_name[item_player_game].has(int(message["item"]["item"])):
132 item_name = client._item_id_to_name[item_player_game][message["item"]["item"]] 246 item_name = client._item_id_to_name[item_player_game][int(message["item"]["item"])]
133 247
134 var location_name = "Unknown" 248 var location_name = "Unknown"
135 var location_player_game = client._game_by_player[message["item"]["player"]] 249 var location_player_game = client._game_by_player[message["item"]["player"]]
136 if client._location_id_to_name[location_player_game].has(message["item"]["location"]): 250 if client._location_id_to_name[location_player_game].has(int(message["item"]["location"])):
137 location_name = ( 251 location_name = (client._location_id_to_name[location_player_game][int(
138 client._location_id_to_name[location_player_game][message["item"]["location"]] 252 message["item"]["location"]
139 ) 253 )])
140 254
141 var player_name = "Unknown" 255 var player_name = "Unknown"
142 if client._player_name_by_slot.has(message["receiving"]): 256 if client._player_name_by_slot.has(message["receiving"]):
@@ -163,20 +277,209 @@ func _process_message(message):
163 global.get_node("Messages").showMessage(sentMsg) 277 global.get_node("Messages").showMessage(sentMsg)
164 278
165 279
166func _client_could_not_connect(): 280func parse_printjson_for_textclient(message):
167 emit_signal("could_not_connect") 281 var parts = []
282 for message_part in message["data"]:
283 if !message_part.has("type") and message_part.has("text"):
284 parts.append(message_part["text"])
285 elif message_part["type"] == "player_id":
286 if int(message_part["text"]) == client._slot:
287 parts.append(
288 "[color=#ee00ee]%s[/color]" % client._player_name_by_slot[client._slot]
289 )
290 else:
291 var from = float(message_part["text"])
292 parts.append("[color=#fafad2]%s[/color]" % client._player_name_by_slot[from])
293 elif message_part["type"] == "item_id":
294 var item_name = "Unknown"
295 var item_player_game = client._game_by_player[message_part["player"]]
296 if client._item_id_to_name[item_player_game].has(int(message_part["text"])):
297 item_name = client._item_id_to_name[item_player_game][int(message_part["text"])]
298
299 parts.append(
300 "[color=%s]%s[/color]" % [colorForItemType(message_part["flags"]), item_name]
301 )
302 elif message_part["type"] == "location_id":
303 var location_name = "Unknown"
304 var location_player_game = client._game_by_player[message_part["player"]]
305 if client._location_id_to_name[location_player_game].has(int(message_part["text"])):
306 location_name = client._location_id_to_name[location_player_game][int(
307 message_part["text"]
308 )]
309
310 parts.append("[color=#00ff7f]%s[/color]" % location_name)
311 elif message_part.has("text"):
312 parts.append(message_part["text"])
313
314 var textclient_node = global.get_node("Textclient")
315 if textclient_node != null:
316 textclient_node.parse_printjson("".join(parts))
317
318
319func _process_location_scout(item_id, location_id, player, flags):
320 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
321
322 if player == client._slot and flags & 4 != 0:
323 # This is a trap for us, so let's not display it.
324 return
325
326 var gamedata = global.get_node("Gamedata")
327 var map_id = gamedata.map_id_by_name.get(global.map)
328
329 var item_name = "Unknown"
330 var item_player_game = client._game_by_player[float(player)]
331 if client._item_id_to_name[item_player_game].has(item_id):
332 item_name = client._item_id_to_name[item_player_game][item_id]
333
334 var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null)
335 if letter_id != null:
336 var letter = gamedata.objects.get_letters()[letter_id]
337 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
338 if room.get_map_id() == map_id:
339 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
340 letter.get_path()
341 )
342 if collectable != null:
343 collectable.setScoutedText(item_name)
344
345
346func _client_could_not_connect(message):
347 emit_signal("could_not_connect", message)
168 348
169 349
170func _client_connect_status(message): 350func _client_connect_status(message):
171 emit_signal("connect_status", message) 351 emit_signal("connect_status", message)
172 352
173 353
174func _client_connected(): 354func _client_connected(slot_data):
355 var gamedata = global.get_node("Gamedata")
356
357 _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot]
358 _last_new_item = -1
359
360 if FileAccess.file_exists(_localdata_file):
361 var ap_file = FileAccess.open(_localdata_file, FileAccess.READ)
362 var localdata = []
363 if ap_file != null:
364 localdata = ap_file.get_var(true)
365 ap_file.close()
366
367 if typeof(localdata) != TYPE_ARRAY:
368 print("AP localdata file is corrupted")
369 localdata = []
370
371 if localdata.size() > 0:
372 _last_new_item = localdata[0]
373
374 # Read slot data.
375 cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0))
376 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
377 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
378 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
379 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
380 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
381 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
382 shuffle_symbols = bool(slot_data.get("shuffle_symbols", false))
383 strict_cyan_ending = bool(slot_data.get("strict_cyan_ending", false))
384 strict_purple_ending = bool(slot_data.get("strict_purple_ending", false))
385 victory_condition = int(slot_data.get("victory_condition", 0))
386
387 if slot_data.has("version"):
388 apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])]
389
390 # Set up item locks.
391 _item_locks = {}
392
393 if shuffle_doors:
394 for door in gamedata.objects.get_doors():
395 if (
396 door.get_type() == gamedata.SCRIPT_proto.DoorType.STANDARD
397 or door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
398 ):
399 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
400
401 for progressive in gamedata.objects.get_progressives():
402 for i in range(0, progressive.get_doors().size()):
403 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]]
404 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1]
405
406 for door_group in gamedata.objects.get_door_groups():
407 if (
408 door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR
409 or door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP
410 ):
411 for door in door_group.get_doors():
412 _item_locks[door] = [door_group.get_ap_id(), 1]
413
414 if shuffle_control_center_colors:
415 for door in gamedata.objects.get_doors():
416 if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR:
417 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
418
419 for door_group in gamedata.objects.get_door_groups():
420 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR:
421 for door in door_group.get_doors():
422 _item_locks[door] = [door_group.get_ap_id(), 1]
423
424 if shuffle_gallery_paintings:
425 for door in gamedata.objects.get_doors():
426 if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING:
427 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
428
429 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM:
430 for door_group in gamedata.objects.get_door_groups():
431 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS:
432 for door in door_group.get_doors():
433 if not _item_locks.has(door):
434 _item_locks[door] = [door_group.get_ap_id(), 1]
435
436 # Create a reverse item locks map for processing items.
437 _inverse_item_locks = {}
438
439 for door_id in _item_locks.keys():
440 var lock = _item_locks.get(door_id)
441
442 if not _inverse_item_locks.has(lock[0]):
443 _inverse_item_locks[lock[0]] = []
444
445 _inverse_item_locks[lock[0]].append([door_id, lock[1]])
446
175 emit_signal("ap_connected") 447 emit_signal("ap_connected")
176 448
177 449
450func start_batching_locations():
451 _batch_locations = true
452
453
178func send_location(loc_id): 454func send_location(loc_id):
179 client.sendLocation(loc_id) 455 if _batch_locations:
456 _held_locations.append(loc_id)
457 else:
458 client.sendLocation(loc_id)
459
460
461func scout_location(loc_id):
462 if _location_scouts.has(loc_id):
463 return _location_scouts.get(loc_id)
464
465 if _batch_locations:
466 _held_location_scouts.append(loc_id)
467 else:
468 client.scoutLocation(loc_id)
469
470 return null
471
472
473func stop_batching_locations():
474 _batch_locations = false
475
476 if not _held_locations.is_empty():
477 client.sendLocations(_held_locations)
478 _held_locations.clear()
479
480 if not _held_location_scouts.is_empty():
481 client.scoutLocations(_held_location_scouts)
482 _held_location_scouts.clear()
180 483
181 484
182func colorForItemType(flags): 485func colorForItemType(flags):
@@ -192,3 +495,50 @@ func colorForItemType(flags):
192 return "#d63a22" 495 return "#d63a22"
193 else: # filler 496 else: # filler
194 return "#14de9e" 497 return "#14de9e"
498
499
500func get_letter_behavior(key, level2):
501 if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED:
502 return kLETTER_BEHAVIOR_UNLOCKED
503
504 if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters):
505 if level2:
506 if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN:
507 return kLETTER_BEHAVIOR_VANILLA
508 else:
509 return kLETTER_BEHAVIOR_ITEM
510 else:
511 return kLETTER_BEHAVIOR_UNLOCKED
512
513 if not level2 and ["h", "i", "n", "t"].has(key):
514 # This differs from the equivalent function in the apworld. Logically it is
515 # the same as UNLOCKED since they are in the starting room, but VANILLA
516 # means the player still has to actually pick up the letters.
517 return kLETTER_BEHAVIOR_VANILLA
518
519 if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE:
520 return kLETTER_BEHAVIOR_ITEM
521
522 return kLETTER_BEHAVIOR_VANILLA
523
524
525func setup_keys():
526 keyboard.load_seed()
527
528 _letters_setup = true
529
530 for k in _held_letters.keys():
531 _process_key_item(k, _held_letters[k])
532
533 _held_letters.clear()
534
535
536func _process_key_item(key, level):
537 if not _letters_setup:
538 _held_letters[key] = max(_held_letters.get(key, 0), level)
539 return
540
541 if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN:
542 level += 1
543
544 keyboard.collect_remote_letter(key, level)
diff --git a/client/Archipelago/messages.gd b/client/Archipelago/messages.gd index 52f38b9..82fdbc4 100644 --- a/client/Archipelago/messages.gd +++ b/client/Archipelago/messages.gd
@@ -48,10 +48,11 @@ func showMessage(text):
48 while !_ordered_labels.is_empty(): 48 while !_ordered_labels.is_empty():
49 await get_tree().create_timer(timeout).timeout 49 await get_tree().create_timer(timeout).timeout
50 50
51 var to_remove = _ordered_labels.pop_front() 51 if !_ordered_labels.is_empty():
52 var to_tween = get_tree().create_tween().bind_node(to_remove) 52 var to_remove = _ordered_labels.pop_front()
53 to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5) 53 var to_tween = get_tree().create_tween().bind_node(to_remove)
54 to_tween.tween_callback(to_remove.queue_free) 54 to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5)
55 to_tween.tween_callback(to_remove.queue_free)
55 56
56 if !_message_queue.is_empty(): 57 if !_message_queue.is_empty():
57 var next_msg = _message_queue.pop_front() 58 var next_msg = _message_queue.pop_front()
@@ -59,3 +60,12 @@ func showMessage(text):
59 60
60 if timeout > 4: 61 if timeout > 4:
61 timeout -= 3 62 timeout -= 3
63
64
65func clear():
66 _message_queue.clear()
67
68 for message_label in _ordered_labels:
69 message_label.queue_free()
70
71 _ordered_labels.clear()
diff --git a/client/Archipelago/painting.gd b/client/Archipelago/painting.gd index 17baeb5..276d4eb 100644 --- a/client/Archipelago/painting.gd +++ b/client/Archipelago/painting.gd
@@ -1,6 +1,7 @@
1extends "res://scripts/nodes/painting.gd" 1extends "res://scripts/nodes/painting.gd"
2 2
3var item_id 3var item_id
4var item_amount
4 5
5 6
6func _ready(): 7func _ready():
@@ -8,24 +9,23 @@ func _ready():
8 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() 9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
9 ) 10 )
10 11
11 print("node: %s" % node_path)
12
13 var gamedata = global.get_node("Gamedata") 12 var gamedata = global.get_node("Gamedata")
14 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) 13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
15 if door_id != null: 14 if door_id != null:
16 print("door_id: %d" % door_id) 15 var ap = global.get_node("Archipelago")
16 var item_lock = ap.get_item_id_for_door(door_id)
17 17
18 self.senders = [] 18 if item_lock != null:
19 self.senderGroup = [] 19 item_id = item_lock[0]
20 self.nested = false 20 item_amount = item_lock[1]
21 self.complete_at = 0
22 self.max_length = 0
23 self.excludeSenders = []
24 21
25 var ap = global.get_node("Archipelago") 22 self.senders = []
26 item_id = ap.get_item_id_for_door(door_id) 23 self.senderGroup = []
24 self.nested = false
25 self.complete_at = 0
26 self.max_length = 0
27 self.excludeSenders = []
27 28
28 if item_id != null:
29 call_deferred("_readier") 29 call_deferred("_readier")
30 30
31 super._ready() 31 super._ready()
@@ -34,5 +34,5 @@ func _ready():
34func _readier(): 34func _readier():
35 var ap = global.get_node("Archipelago") 35 var ap = global.get_node("Archipelago")
36 36
37 if ap.has_item(item_id): 37 if ap.client.getItemAmount(item_id) >= item_amount:
38 $teleportListener.handleTriggered() 38 handleTriggered()
diff --git a/client/Archipelago/panel.gd b/client/Archipelago/panel.gd new file mode 100644 index 0000000..fdaaf0e --- /dev/null +++ b/client/Archipelago/panel.gd
@@ -0,0 +1,101 @@
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 new file mode 100644 index 0000000..cd1813c --- /dev/null +++ b/client/Archipelago/pauseMenu.gd
@@ -0,0 +1,44 @@
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 index a84548a..538830f 100644 --- a/client/Archipelago/player.gd +++ b/client/Archipelago/player.gd
@@ -1,10 +1,38 @@
1extends "res://scripts/nodes/player.gd" 1extends "res://scripts/nodes/player.gd"
2 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
3 23
4func _ready(): 24func _ready():
25 var khl_script = load("res://scripts/nodes/keyHolderListener.gd")
26
5 var ap = global.get_node("Archipelago") 27 var ap = global.get_node("Archipelago")
6 var gamedata = global.get_node("Gamedata") 28 var gamedata = global.get_node("Gamedata")
7 29
30 compass = global.get_node("Compass")
31 compass.visible = ap.show_compass
32
33 ap.start_batching_locations()
34
35 # Set up door locations.
8 var map_id = gamedata.map_id_by_name.get(global.map) 36 var map_id = gamedata.map_id_by_name.get(global.map)
9 for door in gamedata.objects.get_doors(): 37 for door in gamedata.objects.get_doors():
10 if door.get_map_id() != map_id: 38 if door.get_map_id() != map_id:
@@ -13,7 +41,10 @@ func _ready():
13 if not door.has_ap_id(): 41 if not door.has_ap_id():
14 continue 42 continue
15 43
16 if door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY: 44 if (
45 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
46 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
47 ):
17 continue 48 continue
18 49
19 var locationListener = ap.SCRIPT_locationListener.new() 50 var locationListener = ap.SCRIPT_locationListener.new()
@@ -21,12 +52,39 @@ func _ready():
21 locationListener.name = "locationListener_%d" % door.get_ap_id() 52 locationListener.name = "locationListener_%d" % door.get_ap_id()
22 53
23 for panel_ref in door.get_panels(): 54 for panel_ref in door.get_panels():
24 # TODO: specific answers
25 var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()] 55 var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()]
26 locationListener.senders.append(NodePath("/root/scene/" + panel_data.get_path())) 56 var panel_path = panel_data.get_path()
57
58 if panel_ref.has_answer():
59 for proxy in panel_data.get_proxies():
60 if proxy.get_answer() == panel_ref.get_answer():
61 panel_path = proxy.get_path()
62 break
63
64 locationListener.senders.append(NodePath("/root/scene/" + panel_path))
65
66 for keyholder_ref in door.get_keyholders():
67 var keyholder_data = gamedata.objects.get_keyholders()[keyholder_ref.get_keyholder()]
68
69 var khl = khl_script.new()
70 khl.name = (
71 "location_%d_keyholder_%d" % [door.get_ap_id(), keyholder_ref.get_keyholder()]
72 )
73 khl.answer = keyholder_ref.get_key()
74 khl.senders.append(NodePath("/root/scene/" + keyholder_data.get_path()))
75 get_parent().add_child.call_deferred(khl)
76
77 locationListener.senders.append(NodePath("../" + khl.name))
78
79 for sender in door.get_senders():
80 locationListener.senders.append(NodePath("/root/scene/" + sender))
81
82 if door.has_complete_at():
83 locationListener.complete_at = door.get_complete_at()
27 84
28 get_parent().add_child.call_deferred(locationListener) 85 get_parent().add_child.call_deferred(locationListener)
29 86
87 # Set up letter locations.
30 for letter in gamedata.objects.get_letters(): 88 for letter in gamedata.objects.get_letters():
31 var room = gamedata.objects.get_rooms()[letter.get_room_id()] 89 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
32 if room.get_map_id() != map_id: 90 if room.get_map_id() != map_id:
@@ -39,4 +97,266 @@ func _ready():
39 97
40 get_parent().add_child.call_deferred(locationListener) 98 get_parent().add_child.call_deferred(locationListener)
41 99
100 if (
101 ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2())
102 != ap.kLETTER_BEHAVIOR_VANILLA
103 ):
104 var scout = ap.scout_location(letter.get_ap_id())
105 if (
106 scout != null
107 and not (scout["player"] == ap.client._slot and scout["flags"] & 4 != 0)
108 ):
109 var item_name = "Unknown"
110 var item_player_game = ap.client._game_by_player[float(scout["player"])]
111 if ap.client._item_id_to_name[item_player_game].has(scout["item"]):
112 item_name = ap.client._item_id_to_name[item_player_game][scout["item"]]
113
114 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
115 letter.get_path()
116 )
117 if collectable != null:
118 collectable.setScoutedText.call_deferred(item_name)
119
120 # Set up mastery locations.
121 for mastery in gamedata.objects.get_masteries():
122 var room = gamedata.objects.get_rooms()[mastery.get_room_id()]
123 if room.get_map_id() != map_id:
124 continue
125
126 var locationListener = ap.SCRIPT_locationListener.new()
127 locationListener.location_id = mastery.get_ap_id()
128 locationListener.name = "locationListener_%d" % mastery.get_ap_id()
129 locationListener.senders.append(NodePath("/root/scene/" + mastery.get_path()))
130
131 get_parent().add_child.call_deferred(locationListener)
132
133 # Set up ending locations.
134 for ending in gamedata.objects.get_endings():
135 var room = gamedata.objects.get_rooms()[ending.get_room_id()]
136 if room.get_map_id() != map_id:
137 continue
138
139 var locationListener = ap.SCRIPT_locationListener.new()
140 locationListener.location_id = ending.get_ap_id()
141 locationListener.name = "locationListener_%d" % ending.get_ap_id()
142 locationListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
143
144 get_parent().add_child.call_deferred(locationListener)
145
146 if kEndingNameByVictoryValue.get(ap.victory_condition, null) == ending.get_name():
147 var victoryListener = ap.SCRIPT_victoryListener.new()
148 victoryListener.name = "victoryListener"
149 victoryListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
150
151 get_parent().add_child.call_deferred(victoryListener)
152
153 # Set up keyholder locations, in keyholder sanity.
154 if ap.keyholder_sanity:
155 for keyholder in gamedata.objects.get_keyholders():
156 if not keyholder.has_key():
157 continue
158
159 var room = gamedata.objects.get_rooms()[keyholder.get_room_id()]
160 if room.get_map_id() != map_id:
161 continue
162
163 var locationListener = ap.SCRIPT_locationListener.new()
164 locationListener.location_id = keyholder.get_ap_id()
165 locationListener.name = "locationListener_%d" % keyholder.get_ap_id()
166
167 var khl = khl_script.new()
168 khl.name = "location_%d_keyholder" % keyholder.get_ap_id()
169 khl.answer = keyholder.get_key()
170 khl.senders.append(NodePath("/root/scene/" + keyholder.get_path()))
171 get_parent().add_child.call_deferred(khl)
172
173 locationListener.senders.append(NodePath("../" + khl.name))
174
175 get_parent().add_child.call_deferred(locationListener)
176
177 # Block off roof access in Daedalus.
178 if global.map == "daedalus" and not ap.daedalus_roof_access:
179 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49)
180 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1)
181 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10)
182 _set_up_invis_wall(67.5, 11, 17, 16, 10, 1)
183 _set_up_invis_wall(50.5, 11, 14, 10, 10, 1)
184 _set_up_invis_wall(39, 10, 18.5, 1, 10, 22)
185 _set_up_invis_wall(20, 15, 18.5, 1, 10, 16)
186 _set_up_invis_wall(11.5, 15, 3, 32, 10, 1)
187 _set_up_invis_wall(11.5, 16, -20, 14, 20, 1)
188 _set_up_invis_wall(14, 16, -26.5, 1, 20, 4)
189 _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25)
190 _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1)
191 _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1)
192 _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11)
193 _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11)
194
195 var warp_exit_prefab = preload("res://objects/nodes/exit.tscn")
196 var warp_exit = warp_exit_prefab.instantiate()
197 warp_exit.name = "roof_access_blocker_warp_exit"
198 warp_exit.position = Vector3(58, 10, 0)
199 warp_exit.rotation_degrees.y = 90
200 get_parent().add_child.call_deferred(warp_exit)
201
202 var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn")
203 var warp_enter = warp_enter_prefab.instantiate()
204 warp_enter.target = warp_exit
205 warp_enter.position = Vector3(76.5, 30, 1)
206 warp_enter.scale = Vector3(4, 1.5, 1)
207 warp_enter.rotation_degrees.y = 90
208 get_parent().add_child.call_deferred(warp_enter)
209
210 if global.map == "the_entry":
211 # Remove door behind X1.
212 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
213 door_node.handleTriggered()
214
215 # Display win condition.
216 var sign_prefab = preload("res://objects/nodes/sign.tscn")
217 var sign1 = sign_prefab.instantiate()
218 sign1.position = Vector3(-7, 5, -15.01)
219 sign1.text = "victory"
220 get_parent().add_child.call_deferred(sign1)
221
222 var sign2 = sign_prefab.instantiate()
223 sign2.position = Vector3(-7, 4, -15.01)
224 sign2.text = "%s ending" % kEndingNameByVictoryValue.get(ap.victory_condition, "?")
225
226 var sign2_color = kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
227 if sign2_color == "white":
228 sign2_color = "silver"
229
230 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
231 get_parent().add_child.call_deferred(sign2)
232
233 # Add the strict purple ending validation.
234 if global.map == "the_sun_temple" and ap.strict_purple_ending:
235 var panel_prefab = preload("res://objects/nodes/panel.tscn")
236 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
237 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
238
239 var previous_panel = null
240 var next_y = -100
241 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
242 for word in words:
243 var panel = panel_prefab.instantiate()
244 panel.position = Vector3(0, next_y, 0)
245 next_y -= 10
246 panel.clue = word
247 panel.symbol = ""
248 panel.answer = word
249 panel.name = "EndCheck_%s" % word
250
251 var tpl = tpl_prefab.instantiate()
252 tpl.teleport_point = Vector3(0, 1, 0)
253 tpl.teleport_rotate = Vector3(-45, 180, 0)
254 tpl.target_path = panel
255 tpl.name = "Teleport"
256
257 if previous_panel == null:
258 tpl.senders.append(NodePath("/root/scene/Panels/End/panel_24"))
259 else:
260 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
261
262 var reversing = reverse_prefab.instantiate()
263 reversing.senders.append(NodePath(".."))
264 reversing.name = "Reversing"
265 tpl.senders.append(NodePath("../Reversing"))
266
267 panel.add_child.call_deferred(tpl)
268 panel.add_child.call_deferred(reversing)
269 get_parent().get_node("Panels").add_child.call_deferred(panel)
270
271 previous_panel = panel
272
273 # Duplicate the doors that usually wait on EQUINOX. We can't set the senders
274 # here for some reason so we actually set them in the door ready function.
275 var endplat = get_node("/root/scene/Components/Doors/EndPlatform")
276 var endplat2 = endplat.duplicate()
277 endplat2.name = "spe_EndPlatform"
278 endplat.get_parent().add_child.call_deferred(endplat2)
279 endplat.queue_free()
280
281 var entry2 = get_node("/root/scene/Components/Doors/entry_2")
282 var entry22 = entry2.duplicate()
283 entry22.name = "spe_entry_2"
284 entry2.get_parent().add_child.call_deferred(entry22)
285 entry2.queue_free()
286
287 # Add the strict cyan ending validation.
288 if global.map == "the_parthenon" and ap.strict_cyan_ending:
289 var panel_prefab = preload("res://objects/nodes/panel.tscn")
290 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
291 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
292
293 var previous_panel = null
294 var next_y = -100
295 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
296 for word in words:
297 var panel = panel_prefab.instantiate()
298 panel.position = Vector3(0, next_y, 0)
299 next_y -= 10
300 panel.clue = word
301 panel.symbol = "."
302 panel.answer = "%s%s" % [word, word]
303 panel.name = "EndCheck_%s" % word
304
305 var tpl = tpl_prefab.instantiate()
306 tpl.teleport_point = Vector3(0, 1, -11)
307 tpl.teleport_rotate = Vector3(-45, 0, 0)
308 tpl.target_path = panel
309 tpl.name = "Teleport"
310
311 if previous_panel == null:
312 tpl.senderGroup.append(NodePath("/root/scene/Panels/Rulers"))
313 else:
314 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
315
316 var reversing = reverse_prefab.instantiate()
317 reversing.senders.append(NodePath(".."))
318 reversing.name = "Reversing"
319 tpl.senders.append(NodePath("../Reversing"))
320
321 panel.add_child.call_deferred(tpl)
322 panel.add_child.call_deferred(reversing)
323 get_parent().get_node("Panels").add_child.call_deferred(panel)
324
325 previous_panel = panel
326
327 # Duplicate the door that usually waits on the rulers. We can't set the
328 # senders here for some reason so we actually set them in the door ready
329 # function.
330 var entry1 = get_node("/root/scene/Components/Doors/entry_1")
331 var entry12 = entry1.duplicate()
332 entry12.name = "spe_entry_1"
333 entry1.get_parent().add_child.call_deferred(entry12)
334 entry1.queue_free()
335
42 super._ready() 336 super._ready()
337
338 await get_tree().process_frame
339 await get_tree().process_frame
340
341 ap.stop_batching_locations()
342
343
344func _set_up_invis_wall(x, y, z, sx, sy, sz):
345 var prefab = preload("res://objects/nodes/block.tscn")
346 var newwall = prefab.instantiate()
347 newwall.position.x = x
348 newwall.position.y = y
349 newwall.position.z = z
350 newwall.scale.x = sz
351 newwall.scale.y = sy
352 newwall.scale.z = sx
353 newwall.set_surface_override_material(0, preload("res://assets/materials/blackMatte.material"))
354 newwall.visibility_range_end = 3
355 newwall.visibility_range_end_margin = 1
356 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF
357 newwall.skeleton = ".."
358 get_parent().add_child.call_deferred(newwall)
359
360
361func _process(_dt):
362 compass.update_rotation(global_rotation.y)
diff --git a/client/Archipelago/saver.gd b/client/Archipelago/saver.gd new file mode 100644 index 0000000..44bc179 --- /dev/null +++ b/client/Archipelago/saver.gd
@@ -0,0 +1,23 @@
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_screen.gd b/client/Archipelago/settings_screen.gd index a675f8e..b7bfacf 100644 --- a/client/Archipelago/settings_screen.gd +++ b/client/Archipelago/settings_screen.gd
@@ -22,23 +22,33 @@ func _ready():
22 var ap_instance = ap_script.new() 22 var ap_instance = ap_script.new()
23 ap_instance.name = "Archipelago" 23 ap_instance.name = "Archipelago"
24 24
25 #apclient_instance.SCRIPT_doorControl = load("user://maps/Archipelago/doorControl.gd")
26 #apclient_instance.SCRIPT_effects = load("user://maps/Archipelago/effects.gd")
27 #apclient_instance.SCRIPT_location = load("user://maps/Archipelago/location.gd")
28 #apclient_instance.SCRIPT_mypainting = load("user://maps/Archipelago/mypainting.gd")
29 #apclient_instance.SCRIPT_panel = load("user://maps/Archipelago/panel.gd")
30 #apclient_instance.SCRIPT_textclient = load("user://maps/Archipelago/textclient.gd")
31
32 ap_instance.SCRIPT_client = load("user://maps/Archipelago/client.gd") 25 ap_instance.SCRIPT_client = load("user://maps/Archipelago/client.gd")
26 ap_instance.SCRIPT_keyboard = load("user://maps/Archipelago/keyboard.gd")
33 ap_instance.SCRIPT_locationListener = load("user://maps/Archipelago/locationListener.gd") 27 ap_instance.SCRIPT_locationListener = load("user://maps/Archipelago/locationListener.gd")
34 ap_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd") 28 ap_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd")
29 ap_instance.SCRIPT_victoryListener = load("user://maps/Archipelago/victoryListener.gd")
35 30
36 global.add_child(ap_instance) 31 global.add_child(ap_instance)
37 32
38 # Let's also inject any scripts we need to inject now. 33 # Let's also inject any scripts we need to inject now.
34 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/animationListener.gd"))
35 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/collectable.gd"))
39 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/door.gd")) 36 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/door.gd"))
37 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolder.gd"))
38 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolderChecker.gd"))
39 installScriptExtension(
40 ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd")
41 )
40 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd")) 42 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd"))
43 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/panel.gd"))
44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd"))
41 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd")) 45 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd"))
46 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleport.gd"))
48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd"))
49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd"))
50 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldport.gd"))
51 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd"))
42 52
43 var proto_script = load("user://maps/Archipelago/generated/proto.gd") 53 var proto_script = load("user://maps/Archipelago/generated/proto.gd")
44 var gamedata_script = load("user://maps/Archipelago/gamedata.gd") 54 var gamedata_script = load("user://maps/Archipelago/gamedata.gd")
@@ -54,7 +64,19 @@ func _ready():
54 messages_instance.name = "Messages" 64 messages_instance.name = "Messages"
55 global.add_child(messages_instance) 65 global.add_child(messages_instance)
56 66
67 var textclient_script = load("user://maps/Archipelago/textclient.gd")
68 var textclient_instance = textclient_script.new()
69 textclient_instance.name = "Textclient"
70 global.add_child(textclient_instance)
71
72 var compass_overlay_script = load("user://maps/Archipelago/compass_overlay.gd")
73 var compass_overlay_instance = compass_overlay_script.new()
74 compass_overlay_instance.name = "Compass"
75 compass_overlay_instance.SCRIPT_compass = load("user://maps/Archipelago/compass.gd")
76 global.add_child(compass_overlay_instance)
77
57 var ap = global.get_node("Archipelago") 78 var ap = global.get_node("Archipelago")
79 var gamedata = global.get_node("Gamedata")
58 ap.connect("ap_connected", connectionSuccessful) 80 ap.connect("ap_connected", connectionSuccessful)
59 ap.connect("could_not_connect", connectionUnsuccessful) 81 ap.connect("could_not_connect", connectionUnsuccessful)
60 ap.connect("connect_status", connectionStatus) 82 ap.connect("connect_status", connectionStatus)
@@ -78,13 +100,17 @@ func _ready():
78 history_box.get_popup().connect("id_pressed", historySelected) 100 history_box.get_popup().connect("id_pressed", historySelected)
79 101
80 # Show client version. 102 # Show client version.
81 $Panel/title.text = "ARCHIPELAGO (%s)" % ap.my_version 103 $Panel/title.text = "ARCHIPELAGO (%d.%d)" % [gamedata.objects.get_version(), ap.MOD_VERSION]
82 104
83 # Increase font size in text boxes. 105 # Increase font size in text boxes.
84 $Panel/server_box.add_theme_font_size_override("font_size", 36) 106 $Panel/server_box.add_theme_font_size_override("font_size", 36)
85 $Panel/player_box.add_theme_font_size_override("font_size", 36) 107 $Panel/player_box.add_theme_font_size_override("font_size", 36)
86 $Panel/password_box.add_theme_font_size_override("font_size", 36) 108 $Panel/password_box.add_theme_font_size_override("font_size", 36)
87 109
110 # Set up version mismatch dialog.
111 $Panel/VersionMismatch.connect("confirmed", startGame)
112 $Panel/VersionMismatch.get_cancel_button().pressed.connect(versionMismatchDeclined)
113
88 114
89# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd 115# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd
90func installScriptExtension(childScript: Resource): 116func installScriptExtension(childScript: Resource):
@@ -114,6 +140,33 @@ func connectionStatus(message):
114 140
115func connectionSuccessful(): 141func connectionSuccessful():
116 var ap = global.get_node("Archipelago") 142 var ap = global.get_node("Archipelago")
143 var gamedata = global.get_node("Gamedata")
144
145 # Check for major version mismatch.
146 if ap.apworld_version[0] != gamedata.objects.get_version():
147 $Panel/AcceptDialog.exclusive = false
148
149 var popup = self.get_node("Panel/VersionMismatch")
150 popup.title = "Version Mismatch!"
151 popup.dialog_text = (
152 "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."
153 % [
154 ap.apworld_version[0],
155 ap.apworld_version[1],
156 gamedata.objects.get_version(),
157 ap.MOD_VERSION
158 ]
159 )
160 popup.exclusive = true
161 popup.popup_centered()
162
163 return
164
165 startGame()
166
167
168func startGame():
169 var ap = global.get_node("Archipelago")
117 170
118 # Save connection details 171 # Save connection details
119 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass] 172 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass]
@@ -130,16 +183,31 @@ func connectionSuccessful():
130 global.universe = "lingo" 183 global.universe = "lingo"
131 global.map = "the_entry" 184 global.map = "the_entry"
132 185
133 unlocks.resetKeys()
134 unlocks.resetCollectables() 186 unlocks.resetCollectables()
135 unlocks.resetData() 187 unlocks.resetData()
136 unlocks.loadKeys() 188
189 ap.setup_keys()
190
137 unlocks.loadCollectables() 191 unlocks.loadCollectables()
138 unlocks.loadData() 192 unlocks.loadData()
139 unlocks.unlockKey("capslock", 1) 193 unlocks.unlockKey("capslock", 1)
140 194
195 clearResourceCache("res://objects/meshes/gridDoor.tscn")
196 clearResourceCache("res://objects/nodes/collectable.tscn")
141 clearResourceCache("res://objects/nodes/door.tscn") 197 clearResourceCache("res://objects/nodes/door.tscn")
198 clearResourceCache("res://objects/nodes/keyHolder.tscn")
199 clearResourceCache("res://objects/nodes/listeners/animationListener.tscn")
200 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
201 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
202 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
203 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn")
204 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
205 clearResourceCache("res://objects/nodes/panel.tscn")
142 clearResourceCache("res://objects/nodes/player.tscn") 206 clearResourceCache("res://objects/nodes/player.tscn")
207 clearResourceCache("res://objects/nodes/saver.tscn")
208 clearResourceCache("res://objects/nodes/teleport.tscn")
209 clearResourceCache("res://objects/nodes/worldport.tscn")
210 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn")
143 211
144 var paintings_dir = DirAccess.open("res://objects/meshes/paintings") 212 var paintings_dir = DirAccess.open("res://objects/meshes/paintings")
145 if paintings_dir: 213 if paintings_dir:
@@ -150,7 +218,7 @@ func connectionSuccessful():
150 clearResourceCache("res://objects/meshes/paintings/" + file_name) 218 clearResourceCache("res://objects/meshes/paintings/" + file_name)
151 file_name = paintings_dir.get_next() 219 file_name = paintings_dir.get_next()
152 220
153 switcher.switch_map("res://objects/scenes/the_entry.tscn") 221 switcher.switch_map.call_deferred("res://objects/scenes/the_entry.tscn")
154 222
155 223
156func connectionUnsuccessful(error_message): 224func connectionUnsuccessful(error_message):
@@ -163,6 +231,13 @@ func connectionUnsuccessful(error_message):
163 popup.get_ok_button().visible = true 231 popup.get_ok_button().visible = true
164 popup.popup_centered() 232 popup.popup_centered()
165 233
234 $Panel/connect_button.disabled = false
235
236
237func versionMismatchDeclined():
238 $Panel/AcceptDialog.hide()
239 $Panel/connect_button.disabled = false
240
166 241
167func historySelected(index): 242func historySelected(index):
168 var ap = global.get_node("Archipelago") 243 var ap = global.get_node("Archipelago")
@@ -174,5 +249,4 @@ func historySelected(index):
174 249
175 250
176func clearResourceCache(path): 251func clearResourceCache(path):
177 ResourceLoader.load_threaded_request(path, "", false, ResourceLoader.CACHE_MODE_REPLACE) 252 ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)
178 ResourceLoader.load_threaded_get(path)
diff --git a/client/Archipelago/teleport.gd b/client/Archipelago/teleport.gd new file mode 100644 index 0000000..428d50b --- /dev/null +++ b/client/Archipelago/teleport.gd
@@ -0,0 +1,38 @@
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 new file mode 100644 index 0000000..6f363af --- /dev/null +++ b/client/Archipelago/teleportListener.gd
@@ -0,0 +1,49 @@
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 new file mode 100644 index 0000000..85cc6d2 --- /dev/null +++ b/client/Archipelago/textclient.gd
@@ -0,0 +1,86 @@
1extends CanvasLayer
2
3var panel
4var label
5var entry
6var is_open = false
7
8
9func _ready():
10 process_mode = ProcessMode.PROCESS_MODE_ALWAYS
11
12 panel = Panel.new()
13 panel.set_name("Panel")
14 panel.offset_left = 100
15 panel.offset_right = 1820
16 panel.offset_top = 100
17 panel.offset_bottom = 980
18 panel.visible = false
19 add_child(panel)
20
21 label = RichTextLabel.new()
22 label.set_name("Label")
23 label.offset_left = 80
24 label.offset_right = 1640
25 label.offset_top = 80
26 label.offset_bottom = 720
27 label.scroll_following = true
28 label.selection_enabled = true
29 panel.add_child(label)
30
31 label.push_font(load("res://assets/fonts/Lingo2.ttf"))
32 label.push_font_size(36)
33
34 var entry_style = StyleBoxFlat.new()
35 entry_style.bg_color = Color(0.9, 0.9, 0.9, 1)
36
37 entry = LineEdit.new()
38 entry.set_name("Entry")
39 entry.offset_left = 80
40 entry.offset_right = 1640
41 entry.offset_top = 760
42 entry.offset_bottom = 840
43 entry.add_theme_font_override("font", load("res://assets/fonts/Lingo2.ttf"))
44 entry.add_theme_font_size_override("font_size", 36)
45 entry.add_theme_color_override("font_color", Color(0, 0, 0, 1))
46 entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1))
47 entry.add_theme_stylebox_override("focus", entry_style)
48 panel.add_child(entry)
49 entry.connect("text_submitted", text_entered)
50
51
52func _input(event):
53 if global.loaded and event is InputEventKey and event.pressed:
54 if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT):
55 if !get_tree().paused:
56 is_open = true
57 get_tree().paused = true
58 Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
59 panel.visible = true
60 entry.grab_focus()
61 get_viewport().set_input_as_handled()
62 else:
63 dismiss()
64 elif event.keycode == KEY_ESCAPE:
65 if is_open:
66 dismiss()
67 get_viewport().set_input_as_handled()
68
69
70func dismiss():
71 if is_open:
72 get_tree().paused = false
73 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
74 panel.visible = false
75 is_open = false
76
77
78func parse_printjson(text):
79 label.append_text("[p]" + text + "[/p]")
80
81
82func text_entered(text):
83 var ap = global.get_node("Archipelago")
84 var cmd = text.trim_suffix("\n")
85 ap.client.say(cmd)
86 entry.text = ""
diff --git a/client/Archipelago/victoryListener.gd b/client/Archipelago/victoryListener.gd new file mode 100644 index 0000000..e9089d7 --- /dev/null +++ b/client/Archipelago/victoryListener.gd
@@ -0,0 +1,20 @@
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 new file mode 100644 index 0000000..5ea17a0 --- /dev/null +++ b/client/Archipelago/visibilityListener.gd
@@ -0,0 +1,38 @@
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 new file mode 100644 index 0000000..d0fb6c9 --- /dev/null +++ b/client/Archipelago/worldport.gd
@@ -0,0 +1,10 @@
1extends "res://scripts/nodes/worldport.gd"
2
3
4func _ready():
5 if global.map == "icarus" and exit == "daedalus":
6 var ap = global.get_node("Archipelago")
7 if not ap.daedalus_roof_access:
8 entry_point = Vector3(58, 10, 0)
9
10 super._ready()
diff --git a/client/Archipelago/worldportListener.gd b/client/Archipelago/worldportListener.gd new file mode 100644 index 0000000..5c2faff --- /dev/null +++ b/client/Archipelago/worldportListener.gd
@@ -0,0 +1,8 @@
1extends "res://scripts/nodes/listeners/worldportListener.gd"
2
3
4func handleTriggered():
5 if exit == "menus/credits":
6 return
7
8 super.handleTriggered()