about summary refs log tree commit diff stats
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/Archipelago/client.gd6
-rw-r--r--client/Archipelago/compass.gd66
-rw-r--r--client/Archipelago/compass_overlay.gd17
-rw-r--r--client/Archipelago/door.gd8
-rw-r--r--client/Archipelago/gamedata.gd7
-rw-r--r--client/Archipelago/keyboard.gd25
-rw-r--r--client/Archipelago/manager.gd36
-rw-r--r--client/Archipelago/messages.gd18
-rw-r--r--client/Archipelago/pauseMenu.gd32
-rw-r--r--client/Archipelago/player.gd148
-rw-r--r--client/Archipelago/saver.gd14
-rw-r--r--client/Archipelago/settings_screen.gd48
-rw-r--r--client/Archipelago/teleport.gd38
-rw-r--r--client/Archipelago/teleportListener.gd11
-rw-r--r--client/Archipelago/worldport.gd10
-rw-r--r--client/Archipelago/worldportListener.gd4
-rw-r--r--client/CHANGELOG.md59
-rw-r--r--client/README.md7
-rw-r--r--client/archipelago.tscn5
19 files changed, 536 insertions, 23 deletions
diff --git a/client/Archipelago/client.gd b/client/Archipelago/client.gd index 2e080fd..843647d 100644 --- a/client/Archipelago/client.gd +++ b/client/Archipelago/client.gd
@@ -47,6 +47,8 @@ signal location_scout_received(item_id, location_id, player, flags)
47func _init(): 47func _init():
48 set_process_mode(Node.PROCESS_MODE_ALWAYS) 48 set_process_mode(Node.PROCESS_MODE_ALWAYS)
49 49
50 _ws.inbound_buffer_size = 8388608
51
50 global._print("Instantiated APClient") 52 global._print("Instantiated APClient")
51 53
52 # Read AP datapackages from file, if there are any 54 # Read AP datapackages from file, if there are any
@@ -225,7 +227,7 @@ func _process(_delta):
225 error_message = "Unknown error." 227 error_message = "Unknown error."
226 228
227 _initiated_disconnect = true 229 _initiated_disconnect = true
228 _ws.disconnect_from_host() 230 _ws.close()
229 231
230 emit_signal("could_not_connect", error_message) 232 emit_signal("could_not_connect", error_message)
231 global._print("Connection to AP refused") 233 global._print("Connection to AP refused")
@@ -309,7 +311,7 @@ func connectToServer(server, un, pw):
309 % err 311 % err
310 ) 312 )
311 ) 313 )
312 global._print("Could not connect to AP: " + err) 314 global._print("Could not connect to AP: %d" % err)
313 return 315 return
314 _should_process = true 316 _should_process = true
315 317
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 fead818..49f5728 100644 --- a/client/Archipelago/door.gd +++ b/client/Archipelago/door.gd
@@ -28,6 +28,14 @@ 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
diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd index d8d16ed..41d966a 100644 --- a/client/Archipelago/gamedata.gd +++ b/client/Archipelago/gamedata.gd
@@ -11,6 +11,7 @@ var map_id_by_name = {}
11var progressive_id_by_ap_id = {} 11var progressive_id_by_ap_id = {}
12var letter_id_by_ap_id = {} 12var letter_id_by_ap_id = {}
13var symbol_item_ids = [] 13var symbol_item_ids = []
14var anti_trap_ids = {}
14 15
15var kSYMBOL_ITEMS 16var kSYMBOL_ITEMS
16 17
@@ -97,6 +98,12 @@ func load(data_bytes):
97 for symbol_name in kSYMBOL_ITEMS.values(): 98 for symbol_name in kSYMBOL_ITEMS.values():
98 symbol_item_ids.append(objects.get_special_ids()[symbol_name]) 99 symbol_item_ids.append(objects.get_special_ids()[symbol_name])
99 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
100 107
101func get_door_for_map_node_path(map_name, node_path): 108func get_door_for_map_node_path(map_name, node_path):
102 if not door_id_by_map_node_path.has(map_name): 109 if not door_id_by_map_node_path.has(map_name):
diff --git a/client/Archipelago/keyboard.gd b/client/Archipelago/keyboard.gd index 600a047..450566d 100644 --- a/client/Archipelago/keyboard.gd +++ b/client/Archipelago/keyboard.gd
@@ -4,6 +4,7 @@ const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz"
4 4
5var letters_saved = {} 5var letters_saved = {}
6var letters_in_keyholders = [] 6var letters_in_keyholders = []
7var letters_blocked = []
7var letters_dynamic = {} 8var letters_dynamic = {}
8var keyholder_state = {} 9var keyholder_state = {}
9 10
@@ -17,6 +18,7 @@ func _init():
17func reset(): 18func reset():
18 letters_saved.clear() 19 letters_saved.clear()
19 letters_in_keyholders.clear() 20 letters_in_keyholders.clear()
21 letters_blocked.clear()
20 letters_dynamic.clear() 22 letters_dynamic.clear()
21 keyholder_state.clear() 23 keyholder_state.clear()
22 24
@@ -91,6 +93,9 @@ func update_unlocks():
91 level = 2 93 level = 2
92 has_doubles = true 94 has_doubles = true
93 95
96 if letters_blocked.has(k):
97 level = 0
98
94 unlocks.unlockKey(k, level) 99 unlocks.unlockKey(k, level)
95 100
96 if has_doubles and unlocks.data["double_letters"] != "unlocked": 101 if has_doubles and unlocks.data["double_letters"] != "unlocked":
@@ -105,6 +110,9 @@ func collect_local_letter(key, level):
105 110
106 letters_saved[key] = level 111 letters_saved[key] = level
107 112
113 if letters_blocked.has(key):
114 letters_blocked.erase(key)
115
108 update_unlocks() 116 update_unlocks()
109 save() 117 save()
110 118
@@ -115,6 +123,9 @@ func collect_remote_letter(key, level):
115 123
116 letters_dynamic[key] = level 124 letters_dynamic[key] = level
117 125
126 if letters_blocked.has(key):
127 letters_blocked.erase(key)
128
118 update_unlocks() 129 update_unlocks()
119 save() 130 save()
120 131
@@ -148,6 +159,13 @@ func remove_from_keyholder(key, map, kh_path):
148 save() 159 save()
149 160
150 161
162func block_letter(key):
163 if not letters_blocked.has(key):
164 letters_blocked.append(key)
165
166 update_unlocks()
167
168
151func load_keyholders(map): 169func load_keyholders(map):
152 if keyholder_state.has(map): 170 if keyholder_state.has(map):
153 var khs = keyholder_state[map] 171 var khs = keyholder_state[map]
@@ -160,9 +178,11 @@ func load_keyholders(map):
160 178
161 179
162func reset_keyholders(): 180func reset_keyholders():
163 if letters_in_keyholders.is_empty(): 181 if letters_in_keyholders.is_empty() and letters_blocked.is_empty():
164 return false 182 return false
165 183
184 var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty()
185
166 if keyholder_state.has(global.map): 186 if keyholder_state.has(global.map):
167 for path in keyholder_state[global.map]: 187 for path in keyholder_state[global.map]:
168 get_tree().get_root().get_node("scene").get_node(path).setFromAp( 188 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
@@ -171,8 +191,9 @@ func reset_keyholders():
171 191
172 keyholder_state.clear() 192 keyholder_state.clear()
173 letters_in_keyholders.clear() 193 letters_in_keyholders.clear()
194 letters_blocked.clear()
174 195
175 update_unlocks() 196 update_unlocks()
176 save() 197 save()
177 198
178 return true 199 return cleared_anything
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index 6eea2bd..218870c 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -1,6 +1,6 @@
1extends Node 1extends Node
2 2
3const MOD_VERSION = 0 3const MOD_VERSION = 7
4 4
5var SCRIPT_client 5var SCRIPT_client
6var SCRIPT_keyboard 6var SCRIPT_keyboard
@@ -12,6 +12,7 @@ var ap_server = ""
12var ap_user = "" 12var ap_user = ""
13var ap_pass = "" 13var ap_pass = ""
14var connection_history = [] 14var connection_history = []
15var show_compass = false
15 16
16var client 17var client
17var keyboard 18var keyboard
@@ -41,13 +42,17 @@ const kCYAN_DOOR_BEHAVIOR_H2 = 0
41const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1 42const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1
42const kCYAN_DOOR_BEHAVIOR_ITEM = 2 43const kCYAN_DOOR_BEHAVIOR_ITEM = 2
43 44
45var apworld_version = [0, 0]
44var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 46var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
45var daedalus_roof_access = false 47var daedalus_roof_access = false
46var keyholder_sanity = false 48var keyholder_sanity = false
47var shuffle_control_center_colors = false 49var shuffle_control_center_colors = false
48var shuffle_doors = false 50var shuffle_doors = false
51var shuffle_gallery_paintings = false
49var shuffle_letters = kSHUFFLE_LETTERS_VANILLA 52var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
50var shuffle_symbols = false 53var shuffle_symbols = false
54var strict_cyan_ending = false
55var strict_purple_ending = false
51var victory_condition = -1 56var victory_condition = -1
52 57
53signal could_not_connect 58signal could_not_connect
@@ -78,6 +83,9 @@ func _init():
78 if data.size() > 3: 83 if data.size() > 3:
79 connection_history = data[3] 84 connection_history = data[3]
80 85
86 if data.size() > 4:
87 show_compass = data[4]
88
81 89
82func _ready(): 90func _ready():
83 client = SCRIPT_client.new() 91 client = SCRIPT_client.new()
@@ -106,6 +114,7 @@ func saveSettings():
106 ap_user, 114 ap_user,
107 ap_pass, 115 ap_pass,
108 connection_history, 116 connection_history,
117 show_compass,
109 ] 118 ]
110 file.store_var(data, true) 119 file.store_var(data, true)
111 file.close() 120 file.close()
@@ -213,6 +222,9 @@ func _process_item(item, index, from, flags, amount):
213 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name] 222 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name]
214 ) 223 )
215 224
225 if gamedata.anti_trap_ids.has(item):
226 keyboard.block_letter(gamedata.anti_trap_ids[item])
227
216 global._print(message) 228 global._print(message)
217 229
218 global.get_node("Messages").showMessage(message) 230 global.get_node("Messages").showMessage(message)
@@ -307,6 +319,10 @@ func parse_printjson_for_textclient(message):
307func _process_location_scout(item_id, location_id, player, flags): 319func _process_location_scout(item_id, location_id, player, flags):
308 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags} 320 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
309 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
310 var gamedata = global.get_node("Gamedata") 326 var gamedata = global.get_node("Gamedata")
311 var map_id = gamedata.map_id_by_name.get(global.map) 327 var map_id = gamedata.map_id_by_name.get(global.map)
312 328
@@ -327,8 +343,8 @@ func _process_location_scout(item_id, location_id, player, flags):
327 collectable.setScoutedText(item_name) 343 collectable.setScoutedText(item_name)
328 344
329 345
330func _client_could_not_connect(): 346func _client_could_not_connect(message):
331 emit_signal("could_not_connect") 347 emit_signal("could_not_connect", message)
332 348
333 349
334func _client_connect_status(message): 350func _client_connect_status(message):
@@ -361,10 +377,16 @@ func _client_connected(slot_data):
361 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) 377 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
362 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) 378 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
363 shuffle_doors = bool(slot_data.get("shuffle_doors", false)) 379 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
380 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
364 shuffle_letters = int(slot_data.get("shuffle_letters", 0)) 381 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
365 shuffle_symbols = bool(slot_data.get("shuffle_symbols", false)) 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))
366 victory_condition = int(slot_data.get("victory_condition", 0)) 385 victory_condition = int(slot_data.get("victory_condition", 0))
367 386
387 if slot_data.has("version"):
388 apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])]
389
368 # Set up item locks. 390 # Set up item locks.
369 _item_locks = {} 391 _item_locks = {}
370 392
@@ -399,6 +421,11 @@ func _client_connected(slot_data):
399 for door in door_group.get_doors(): 421 for door in door_group.get_doors():
400 _item_locks[door] = [door_group.get_ap_id(), 1] 422 _item_locks[door] = [door_group.get_ap_id(), 1]
401 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
402 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM: 429 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM:
403 for door_group in gamedata.objects.get_door_groups(): 430 for door_group in gamedata.objects.get_door_groups():
404 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS: 431 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS:
@@ -511,4 +538,7 @@ func _process_key_item(key, level):
511 _held_letters[key] = max(_held_letters.get(key, 0), level) 538 _held_letters[key] = max(_held_letters.get(key, 0), level)
512 return 539 return
513 540
541 if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN:
542 level += 1
543
514 keyboard.collect_remote_letter(key, level) 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/pauseMenu.gd b/client/Archipelago/pauseMenu.gd index 5da114a..cd1813c 100644 --- a/client/Archipelago/pauseMenu.gd +++ b/client/Archipelago/pauseMenu.gd
@@ -1,5 +1,26 @@
1extends "res://scripts/ui/pauseMenu.gd" 1extends "res://scripts/ui/pauseMenu.gd"
2 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
3 24
4func _pause_game(): 25func _pause_game():
5 global.get_node("Textclient").dismiss() 26 global.get_node("Textclient").dismiss()
@@ -9,4 +30,15 @@ func _pause_game():
9func _main_menu(): 30func _main_menu():
10 global.loaded = false 31 global.loaded = false
11 global.get_node("Archipelago").disconnect_from_ap() 32 global.get_node("Archipelago").disconnect_from_ap()
33 global.get_node("Messages").clear()
34 global.get_node("Compass").visible = false
12 super._main_menu() 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 4b995bc..538830f 100644 --- a/client/Archipelago/player.gd +++ b/client/Archipelago/player.gd
@@ -18,6 +18,8 @@ const kEndingNameByVictoryValue = {
18 18
19signal evaluate_solvability 19signal evaluate_solvability
20 20
21var compass
22
21 23
22func _ready(): 24func _ready():
23 var khl_script = load("res://scripts/nodes/keyHolderListener.gd") 25 var khl_script = load("res://scripts/nodes/keyHolderListener.gd")
@@ -25,6 +27,9 @@ func _ready():
25 var ap = global.get_node("Archipelago") 27 var ap = global.get_node("Archipelago")
26 var gamedata = global.get_node("Gamedata") 28 var gamedata = global.get_node("Gamedata")
27 29
30 compass = global.get_node("Compass")
31 compass.visible = ap.show_compass
32
28 ap.start_batching_locations() 33 ap.start_batching_locations()
29 34
30 # Set up door locations. 35 # Set up door locations.
@@ -36,7 +41,10 @@ func _ready():
36 if not door.has_ap_id(): 41 if not door.has_ap_id():
37 continue 42 continue
38 43
39 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 ):
40 continue 48 continue
41 49
42 var locationListener = ap.SCRIPT_locationListener.new() 50 var locationListener = ap.SCRIPT_locationListener.new()
@@ -68,6 +76,12 @@ func _ready():
68 76
69 locationListener.senders.append(NodePath("../" + khl.name)) 77 locationListener.senders.append(NodePath("../" + khl.name))
70 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()
84
71 get_parent().add_child.call_deferred(locationListener) 85 get_parent().add_child.call_deferred(locationListener)
72 86
73 # Set up letter locations. 87 # Set up letter locations.
@@ -88,7 +102,10 @@ func _ready():
88 != ap.kLETTER_BEHAVIOR_VANILLA 102 != ap.kLETTER_BEHAVIOR_VANILLA
89 ): 103 ):
90 var scout = ap.scout_location(letter.get_ap_id()) 104 var scout = ap.scout_location(letter.get_ap_id())
91 if scout != null: 105 if (
106 scout != null
107 and not (scout["player"] == ap.client._slot and scout["flags"] & 4 != 0)
108 ):
92 var item_name = "Unknown" 109 var item_name = "Unknown"
93 var item_player_game = ap.client._game_by_player[float(scout["player"])] 110 var item_player_game = ap.client._game_by_player[float(scout["player"])]
94 if ap.client._item_id_to_name[item_player_game].has(scout["item"]): 111 if ap.client._item_id_to_name[item_player_game].has(scout["item"]):
@@ -190,11 +207,132 @@ func _ready():
190 warp_enter.rotation_degrees.y = 90 207 warp_enter.rotation_degrees.y = 90
191 get_parent().add_child.call_deferred(warp_enter) 208 get_parent().add_child.call_deferred(warp_enter)
192 209
193 # Remove door behind X1.
194 if global.map == "the_entry": 210 if global.map == "the_entry":
211 # Remove door behind X1.
195 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1") 212 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
196 door_node.handleTriggered() 213 door_node.handleTriggered()
197 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
198 super._ready() 336 super._ready()
199 337
200 await get_tree().process_frame 338 await get_tree().process_frame
@@ -218,3 +356,7 @@ func _set_up_invis_wall(x, y, z, sx, sy, sz):
218 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF 356 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF
219 newwall.skeleton = ".." 357 newwall.skeleton = ".."
220 get_parent().add_child.call_deferred(newwall) 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 index 0fba9e7..44bc179 100644 --- a/client/Archipelago/saver.gd +++ b/client/Archipelago/saver.gd
@@ -7,3 +7,17 @@ func levelLoaded():
7 ap.keyboard.load_keyholders.call_deferred(global.map) 7 ap.keyboard.load_keyholders.call_deferred(global.map)
8 else: 8 else:
9 reload.call_deferred() 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 ff6f9df..b7bfacf 100644 --- a/client/Archipelago/settings_screen.gd +++ b/client/Archipelago/settings_screen.gd
@@ -44,8 +44,10 @@ func _ready():
44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd")) 44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd"))
45 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")) 46 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleport.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd")) 48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd"))
48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd")) 49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd"))
50 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldport.gd"))
49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd")) 51 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd"))
50 52
51 var proto_script = load("user://maps/Archipelago/generated/proto.gd") 53 var proto_script = load("user://maps/Archipelago/generated/proto.gd")
@@ -67,6 +69,12 @@ func _ready():
67 textclient_instance.name = "Textclient" 69 textclient_instance.name = "Textclient"
68 global.add_child(textclient_instance) 70 global.add_child(textclient_instance)
69 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
70 var ap = global.get_node("Archipelago") 78 var ap = global.get_node("Archipelago")
71 var gamedata = global.get_node("Gamedata") 79 var gamedata = global.get_node("Gamedata")
72 ap.connect("ap_connected", connectionSuccessful) 80 ap.connect("ap_connected", connectionSuccessful)
@@ -99,6 +107,10 @@ func _ready():
99 $Panel/player_box.add_theme_font_size_override("font_size", 36) 107 $Panel/player_box.add_theme_font_size_override("font_size", 36)
100 $Panel/password_box.add_theme_font_size_override("font_size", 36) 108 $Panel/password_box.add_theme_font_size_override("font_size", 36)
101 109
110 # Set up version mismatch dialog.
111 $Panel/VersionMismatch.connect("confirmed", startGame)
112 $Panel/VersionMismatch.get_cancel_button().pressed.connect(versionMismatchDeclined)
113
102 114
103# 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
104func installScriptExtension(childScript: Resource): 116func installScriptExtension(childScript: Resource):
@@ -128,6 +140,33 @@ func connectionStatus(message):
128 140
129func connectionSuccessful(): 141func connectionSuccessful():
130 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")
131 170
132 # Save connection details 171 # Save connection details
133 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]
@@ -166,6 +205,8 @@ func connectionSuccessful():
166 clearResourceCache("res://objects/nodes/panel.tscn") 205 clearResourceCache("res://objects/nodes/panel.tscn")
167 clearResourceCache("res://objects/nodes/player.tscn") 206 clearResourceCache("res://objects/nodes/player.tscn")
168 clearResourceCache("res://objects/nodes/saver.tscn") 207 clearResourceCache("res://objects/nodes/saver.tscn")
208 clearResourceCache("res://objects/nodes/teleport.tscn")
209 clearResourceCache("res://objects/nodes/worldport.tscn")
169 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn") 210 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn")
170 211
171 var paintings_dir = DirAccess.open("res://objects/meshes/paintings") 212 var paintings_dir = DirAccess.open("res://objects/meshes/paintings")
@@ -190,6 +231,13 @@ func connectionUnsuccessful(error_message):
190 popup.get_ok_button().visible = true 231 popup.get_ok_button().visible = true
191 popup.popup_centered() 232 popup.popup_centered()
192 233
234 $Panel/connect_button.disabled = false
235
236
237func versionMismatchDeclined():
238 $Panel/AcceptDialog.hide()
239 $Panel/connect_button.disabled = false
240
193 241
194func historySelected(index): 242func historySelected(index):
195 var ap = global.get_node("Archipelago") 243 var ap = global.get_node("Archipelago")
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 index 4a7deec..6f363af 100644 --- a/client/Archipelago/teleportListener.gd +++ b/client/Archipelago/teleportListener.gd
@@ -9,6 +9,17 @@ func _ready():
9 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()
10 ) 10 )
11 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
12 var gamedata = global.get_node("Gamedata") 23 var gamedata = global.get_node("Gamedata")
13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) 24 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
14 if door_id != null: 25 if door_id != null:
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 index c31c825..5c2faff 100644 --- a/client/Archipelago/worldportListener.gd +++ b/client/Archipelago/worldportListener.gd
@@ -1,8 +1,8 @@
1extends "res://scripts/nodes/listeners/worldportListener.gd" 1extends "res://scripts/nodes/listeners/worldportListener.gd"
2 2
3 3
4func changeScene(): 4func handleTriggered():
5 if exit == "menus/credits": 5 if exit == "menus/credits":
6 return 6 return
7 7
8 super.changeScene() 8 super.handleTriggered()
diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md new file mode 100644 index 0000000..a0a538b --- /dev/null +++ b/client/CHANGELOG.md
@@ -0,0 +1,59 @@
1# lingo2-archipelago Client Releases
2
3## v5.6 - 2025-09-17
4
5- Letter locations will no longer reappear after being collected.
6- This also prevents a potential scenario in which it is impossible to access
7 the location "The Congruent - Obverse Yellow Puzzles" when door shuffle is
8 disabled.
9
10Download:
11[lingo2-archipelago-client-v5.6.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v5.6.zip)<br/>
12Source:
13[v5.6](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v5.6)
14
15## v5.5 - 2025-09-16
16
17- Compatability update for v5.5 of the apworld.
18
19Download:
20[lingo2-archipelago-client-v5.5.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v5.5.zip)<br/>
21Source:
22[v5.5](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v5.5)
23
24## v4.4 - 2025-09-13
25
26- Added support for anti-collectable trap items.
27- Fixed entrance to The Jubilant not opening properly when using control center
28 color shuffle.
29- Fixed the location "The Entry (Colored Doors Area) - OPEN" not sending.
30- Fixed level 2 letters not activating properly when letter shuffle is set to
31 Item Cyan.
32- Messages are now cleared out when returning to the main menu.
33- The player is prevented from accidentally breaking roof access logic when
34 returning to Daedalus from Icarus.
35
36Download:
37[lingo2-archipelago-client-v4.4.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v4.4.zip)<br/>
38Source:
39[v4.4](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v4.4)
40
41## v3.3 - 2025-09-12
42
43- Fixed issue downloading large datapackages (such as TUNIC's).
44- Connection failures now show error messages.
45
46Download:
47[lingo2-archipelago-client-v3.3.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v3.3.zip)<br/>
48Source:
49[v3.3](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v3.3)
50
51## v3.2 - 2025-09-12
52
53- Initial release for testing. Features include door shuffle, letter shuffle,
54 and symbol shuffle.
55
56Download:
57[lingo2-archipelago-client-v3.2.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v3.2.zip)<br/>
58Source:
59[v3.2](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v3.2)
diff --git a/client/README.md b/client/README.md index 1e94bdb..99589c5 100644 --- a/client/README.md +++ b/client/README.md
@@ -88,10 +88,3 @@ not recommended to load these save files outside of the randomizer.
88 88
89A connection to Archipelago is required to resume playing a multiworld. This is 89A connection to Archipelago is required to resume playing a multiworld. This is
90because the set of items you have received is not stored locally. 90because the set of items you have received is not stored locally.
91
92### What about wall snipes?
93
94"Wall sniping" refers to the fact that you are able to solve puzzles on the
95other side of opaque walls. The player is never expected to or required to do
96this in normal gameplay. This randomizer does not change how wall snipes work,
97but it will likewise never require the use of them.
diff --git a/client/archipelago.tscn b/client/archipelago.tscn index 40dd46f..da83b23 100644 --- a/client/archipelago.tscn +++ b/client/archipelago.tscn
@@ -40,6 +40,7 @@ offset_right = 1920.0
40offset_bottom = 225.0 40offset_bottom = 225.0
41text = "ARCHIPELAGO" 41text = "ARCHIPELAGO"
42valign = 1 42valign = 1
43horizontal_alignment = 1
43theme = ExtResource("2_g4bvn") 44theme = ExtResource("2_g4bvn")
44 45
45[node name="credit" parent="Panel" type="Label"] 46[node name="credit" parent="Panel" type="Label"]
@@ -150,6 +151,10 @@ caret_blink = true
150offset_right = 83.0 151offset_right = 83.0
151offset_bottom = 58.0 152offset_bottom = 58.0
152 153
154[node name="VersionMismatch" type="ConfirmationDialog" parent="Panel"]
155offset_right = 83.0
156offset_bottom = 58.0
157
153[node name="connection_history" type="MenuButton" parent="Panel"] 158[node name="connection_history" type="MenuButton" parent="Panel"]
154offset_left = 1239.0 159offset_left = 1239.0
155offset_top = 276.0 160offset_top = 276.0