summary refs log tree commit diff stats
path: root/client
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2025-09-06 09:20:28 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2025-09-06 09:20:28 -0400
commitbb4b16ca06c70cf263424955713c91117f2f1813 (patch)
tree1f234246ad48a2472904e92a6ff48fc7f13e1c49 /client
parentebda0b634c2396338b86b45128bf507c967e88a7 (diff)
downloadlingo2-archipelago-bb4b16ca06c70cf263424955713c91117f2f1813.tar.gz
lingo2-archipelago-bb4b16ca06c70cf263424955713c91117f2f1813.tar.bz2
lingo2-archipelago-bb4b16ca06c70cf263424955713c91117f2f1813.zip
[Client] Handle letter shuffle
Cyan doors are not handled properly yet.
Diffstat (limited to 'client')
-rw-r--r--client/Archipelago/collectable.gd12
-rw-r--r--client/Archipelago/gamedata.gd5
-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.gd170
-rw-r--r--client/Archipelago/manager.gd74
-rw-r--r--client/Archipelago/saver.gd6
-rw-r--r--client/Archipelago/settings_screen.gd23
9 files changed, 345 insertions, 15 deletions
diff --git a/client/Archipelago/collectable.gd b/client/Archipelago/collectable.gd new file mode 100644 index 0000000..7bbdd8b --- /dev/null +++ b/client/Archipelago/collectable.gd
@@ -0,0 +1,12 @@
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()
diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd index 669ad3d..11f4981 100644 --- a/client/Archipelago/gamedata.gd +++ b/client/Archipelago/gamedata.gd
@@ -8,6 +8,7 @@ var painting_id_by_map_node_path = {}
8var door_id_by_ap_id = {} 8var door_id_by_ap_id = {}
9var map_id_by_name = {} 9var map_id_by_name = {}
10var progressive_id_by_ap_id = {} 10var progressive_id_by_ap_id = {}
11var letter_key_by_ap_id = {}
11 12
12 13
13func _init(proto_script): 14func _init(proto_script):
@@ -54,6 +55,10 @@ func load(data_bytes):
54 for progressive in objects.get_progressives(): 55 for progressive in objects.get_progressives():
55 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id() 56 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id()
56 57
58 for letter in objects.get_letters():
59 if not letter.has_level2() or not letter.get_level2():
60 letter_key_by_ap_id[letter.get_ap_id()] = letter.get_key()
61
57 62
58func get_door_for_map_node_path(map_name, node_path): 63func get_door_for_map_node_path(map_name, node_path):
59 if not door_id_by_map_node_path.has(map_name): 64 if not door_id_by_map_node_path.has(map_name):
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..e43ec9f --- /dev/null +++ b/client/Archipelago/keyboard.gd
@@ -0,0 +1,170 @@
1extends Node
2
3const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz"
4
5var letters_saved = {}
6var letters_in_keyholders = []
7var letters_dynamic = {}
8var keyholder_state = {}
9
10var filename = ""
11
12
13func _init():
14 reset()
15
16
17func reset():
18 letters_saved.clear()
19 letters_in_keyholders.clear()
20 letters_dynamic.clear()
21 keyholder_state.clear()
22
23
24func load_seed():
25 var ap = global.get_node("Archipelago")
26
27 reset()
28
29 filename = "user://archipelago_keys/%s_%d" % [ap.client._seed, ap.client._slot]
30
31 if FileAccess.file_exists(filename):
32 var ap_file = FileAccess.open(filename, FileAccess.READ)
33 var localdata = []
34 if ap_file != null:
35 localdata = ap_file.get_var(true)
36 ap_file.close()
37
38 if typeof(localdata) != TYPE_ARRAY:
39 print("AP keyboard file is corrupted")
40 localdata = []
41
42 if localdata.size() > 0:
43 letters_saved = localdata[0]
44 if localdata.size() > 1:
45 letters_in_keyholders = localdata[1]
46 if localdata.size() > 2:
47 keyholder_state = localdata[2]
48
49 for k in kALL_LETTERS:
50 var level = 0
51
52 if ap.get_letter_behavior(k, false) == ap.kLETTER_BEHAVIOR_UNLOCKED:
53 level += 1
54 if ap.get_letter_behavior(k, true) == ap.kLETTER_BEHAVIOR_UNLOCKED:
55 level += 1
56
57 letters_dynamic[k] = level
58
59 update_unlocks()
60
61
62func save():
63 var dir = DirAccess.open("user://")
64 var folder = "archipelago_keys"
65 if not dir.dir_exists(folder):
66 dir.make_dir(folder)
67
68 var file = FileAccess.open(filename, FileAccess.WRITE)
69
70 var data = [
71 letters_saved,
72 letters_in_keyholders,
73 keyholder_state,
74 ]
75 file.store_var(data, true)
76 file.close()
77
78
79func update_unlocks():
80 unlocks.resetKeys()
81
82 for k in kALL_LETTERS:
83 var level = 0
84
85 if not letters_in_keyholders.has(k):
86 level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0)
87
88 if level > 2:
89 level = 2
90
91 unlocks.unlockKey(k, level)
92
93
94func collect_local_letter(key, level):
95 if level < 0 or level > 2 or level < letters_saved.get(key, 0):
96 return
97
98 letters_saved[key] = level
99
100 update_unlocks()
101 save()
102
103
104func collect_remote_letter(key, level):
105 if level < 0 or level > 2 or level < letters_dynamic.get(key, 0):
106 return
107
108 letters_dynamic[key] = level
109
110 update_unlocks()
111 save()
112
113
114func put_in_keyholder(key, map, kh_path):
115 if not keyholder_state.has(map):
116 keyholder_state[map] = {}
117
118 keyholder_state[map][kh_path] = key
119 letters_in_keyholders.append(key)
120
121 get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(
122 key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
123 )
124
125 update_unlocks()
126 save()
127
128
129func remove_from_keyholder(key, map, kh_path):
130 if not keyholder_state.has(map):
131 # This... shouldn't happen.
132 keyholder_state[map] = {}
133
134 keyholder_state[map].erase(kh_path)
135 letters_in_keyholders.erase(key)
136
137 get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(key, 0)
138
139 update_unlocks()
140 save()
141
142
143func load_keyholders(map):
144 if keyholder_state.has(map):
145 var khs = keyholder_state[map]
146
147 for path in khs.keys():
148 var key = khs[path]
149 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
150 key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
151 )
152
153
154func reset_keyholders():
155 if letters_in_keyholders.is_empty():
156 return false
157
158 if keyholder_state.has(global.map):
159 for path in keyholder_state[global.map]:
160 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
161 keyholder_state[global.map][path], 0
162 )
163
164 keyholder_state.clear()
165 letters_in_keyholders.clear()
166
167 update_unlocks()
168 save()
169
170 return true
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index 609e645..07d28a4 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -3,6 +3,7 @@ extends Node
3const my_version = "0.1.0" 3const my_version = "0.1.0"
4 4
5var SCRIPT_client 5var SCRIPT_client
6var SCRIPT_keyboard
6var SCRIPT_locationListener 7var SCRIPT_locationListener
7var SCRIPT_uuid 8var SCRIPT_uuid
8var SCRIPT_victoryListener 9var SCRIPT_victoryListener
@@ -13,16 +14,30 @@ var ap_pass = ""
13var connection_history = [] 14var connection_history = []
14 15
15var client 16var client
17var keyboard
16 18
17var _localdata_file = "" 19var _localdata_file = ""
18var _last_new_item = -1 20var _last_new_item = -1
19var _batch_locations = false 21var _batch_locations = false
20var _held_locations = [] 22var _held_locations = []
21var _item_locks = {} 23var _item_locks = {}
24var _held_letters = {}
25var _letters_setup = false
26
27const kSHUFFLE_LETTERS_VANILLA = 0
28const kSHUFFLE_LETTERS_UNLOCKED = 1
29const kSHUFFLE_LETTERS_PROGRESSIVE = 2
30const kSHUFFLE_LETTERS_VANILLA_CYAN = 3
31const kSHUFFLE_LETTERS_ITEM_CYAN = 4
32
33const kLETTER_BEHAVIOR_VANILLA = 0
34const kLETTER_BEHAVIOR_ITEM = 1
35const kLETTER_BEHAVIOR_UNLOCKED = 2
22 36
23var daedalus_roof_access = false 37var daedalus_roof_access = false
24var keyholder_sanity = false 38var keyholder_sanity = false
25var shuffle_doors = false 39var shuffle_doors = false
40var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
26var victory_condition = -1 41var victory_condition = -1
27 42
28signal could_not_connect 43signal could_not_connect
@@ -66,6 +81,9 @@ func _ready():
66 81
67 add_child(client) 82 add_child(client)
68 83
84 keyboard = SCRIPT_keyboard.new()
85 add_child(keyboard)
86
69 87
70func saveSettings(): 88func saveSettings():
71 # Save the AP settings to disk. 89 # Save the AP settings to disk.
@@ -100,6 +118,8 @@ func saveLocaldata():
100 118
101func connectToServer(): 119func connectToServer():
102 _last_new_item = -1 120 _last_new_item = -1
121 _letters_setup = false
122 _held_letters = {}
103 123
104 client.connectToServer(ap_server, ap_user, ap_pass) 124 client.connectToServer(ap_server, ap_user, ap_pass)
105 125
@@ -140,11 +160,10 @@ func _process_item(item, index, from, flags, amount):
140 var rnode = scene.get_node_or_null(receiver) 160 var rnode = scene.get_node_or_null(receiver)
141 if rnode != null: 161 if rnode != null:
142 rnode.handleTriggered() 162 rnode.handleTriggered()
143 #for painting_id in gamedata.objects.get_doors()[door_id].get_move_paintings(): 163
144 # var painting = gamedata.objects.get_paintings()[painting_id] 164 var letter_key = gamedata.letter_key_by_ap_id.get(item, null)
145 # var pnode = scene.get_node_or_null(painting.get_path() + "/teleportListener") 165 if letter_key != null:
146 # if pnode != null: 166 _process_key_item(letter_key, amount)
147 # pnode.handleTriggered()
148 167
149 # Show a message about the item if it's new. 168 # Show a message about the item if it's new.
150 if index != null and index > _last_new_item: 169 if index != null and index > _last_new_item:
@@ -293,6 +312,7 @@ func _client_connected(slot_data):
293 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) 312 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
294 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) 313 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
295 shuffle_doors = bool(slot_data.get("shuffle_doors", false)) 314 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
315 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
296 victory_condition = int(slot_data.get("victory_condition", 0)) 316 victory_condition = int(slot_data.get("victory_condition", 0))
297 317
298 # Set up item locks. 318 # Set up item locks.
@@ -344,3 +364,47 @@ func colorForItemType(flags):
344 return "#d63a22" 364 return "#d63a22"
345 else: # filler 365 else: # filler
346 return "#14de9e" 366 return "#14de9e"
367
368
369func get_letter_behavior(key, level2):
370 if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED:
371 return kLETTER_BEHAVIOR_UNLOCKED
372
373 if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters):
374 if level2:
375 if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN:
376 return kLETTER_BEHAVIOR_VANILLA
377 else:
378 return kLETTER_BEHAVIOR_ITEM
379 else:
380 return kLETTER_BEHAVIOR_UNLOCKED
381
382 if not level2 and ["h", "i", "n", "t"].has(key):
383 # This differs from the equivalent function in the apworld. Logically it is
384 # the same as UNLOCKED since they are in the starting room, but VANILLA
385 # means the player still has to actually pick up the letters.
386 return kLETTER_BEHAVIOR_VANILLA
387
388 if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE:
389 return kLETTER_BEHAVIOR_ITEM
390
391 return kLETTER_BEHAVIOR_VANILLA
392
393
394func setup_keys():
395 keyboard.load_seed()
396
397 _letters_setup = true
398
399 for k in _held_letters.keys():
400 _process_key_item(k, _held_letters[k])
401
402 _held_letters.clear()
403
404
405func _process_key_item(key, level):
406 if not _letters_setup:
407 _held_letters[key] = max(_held_letters.get(key, 0), level)
408 return
409
410 keyboard.collect_remote_letter(key, level)
diff --git a/client/Archipelago/saver.gd b/client/Archipelago/saver.gd index 7e788a8..0fba9e7 100644 --- a/client/Archipelago/saver.gd +++ b/client/Archipelago/saver.gd
@@ -2,4 +2,8 @@ extends "res://scripts/nodes/saver.gd"
2 2
3 3
4func levelLoaded(): 4func levelLoaded():
5 reload.call_deferred() 5 if type == "keyholders":
6 var ap = global.get_node("Archipelago")
7 ap.keyboard.load_keyholders.call_deferred(global.map)
8 else:
9 reload.call_deferred()
diff --git a/client/Archipelago/settings_screen.gd b/client/Archipelago/settings_screen.gd index ed9571d..aaaf72a 100644 --- a/client/Archipelago/settings_screen.gd +++ b/client/Archipelago/settings_screen.gd
@@ -22,14 +22,8 @@ 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")
35 ap_instance.SCRIPT_victoryListener = load("user://maps/Archipelago/victoryListener.gd") 29 ap_instance.SCRIPT_victoryListener = load("user://maps/Archipelago/victoryListener.gd")
@@ -38,7 +32,13 @@ func _ready():
38 32
39 # Let's also inject any scripts we need to inject now. 33 # Let's also inject any scripts we need to inject now.
40 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/animationListener.gd")) 34 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/animationListener.gd"))
35 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/collectable.gd"))
41 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 )
42 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd")) 42 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd"))
43 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd")) 43 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd"))
44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd")) 44 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd"))
@@ -141,17 +141,22 @@ func connectionSuccessful():
141 global.universe = "lingo" 141 global.universe = "lingo"
142 global.map = "the_entry" 142 global.map = "the_entry"
143 143
144 unlocks.resetKeys()
145 unlocks.resetCollectables() 144 unlocks.resetCollectables()
146 unlocks.resetData() 145 unlocks.resetData()
147 unlocks.loadKeys() 146
147 ap.setup_keys()
148
148 unlocks.loadCollectables() 149 unlocks.loadCollectables()
149 unlocks.loadData() 150 unlocks.loadData()
150 unlocks.unlockKey("capslock", 1) 151 unlocks.unlockKey("capslock", 1)
151 152
152 clearResourceCache("res://objects/meshes/gridDoor.tscn") 153 clearResourceCache("res://objects/meshes/gridDoor.tscn")
154 clearResourceCache("res://objects/nodes/collectable.tscn")
153 clearResourceCache("res://objects/nodes/door.tscn") 155 clearResourceCache("res://objects/nodes/door.tscn")
156 clearResourceCache("res://objects/nodes/keyHolder.tscn")
154 clearResourceCache("res://objects/nodes/listeners/animationListener.tscn") 157 clearResourceCache("res://objects/nodes/listeners/animationListener.tscn")
158 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
159 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
155 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") 160 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
156 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") 161 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
157 clearResourceCache("res://objects/nodes/player.tscn") 162 clearResourceCache("res://objects/nodes/player.tscn")