diff options
Diffstat (limited to 'apworld/client/manager.gd')
-rw-r--r-- | apworld/client/manager.gd | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd new file mode 100644 index 0000000..dac09b2 --- /dev/null +++ b/apworld/client/manager.gd | |||
@@ -0,0 +1,651 @@ | |||
1 | extends Node | ||
2 | |||
3 | var SCRIPT_client | ||
4 | var SCRIPT_keyboard | ||
5 | var SCRIPT_locationListener | ||
6 | var SCRIPT_minimap | ||
7 | var SCRIPT_victoryListener | ||
8 | var SCRIPT_websocketserver | ||
9 | |||
10 | var ap_server = "" | ||
11 | var ap_user = "" | ||
12 | var ap_pass = "" | ||
13 | var connection_history = [] | ||
14 | var show_compass = false | ||
15 | var show_locations = false | ||
16 | var show_minimap = false | ||
17 | |||
18 | var client | ||
19 | var keyboard | ||
20 | |||
21 | var _localdata_file = "" | ||
22 | var _last_new_item = -1 | ||
23 | var _batch_locations = false | ||
24 | var _held_locations = [] | ||
25 | var _held_location_scouts = [] | ||
26 | var _location_scouts = {} | ||
27 | var _item_locks = {} | ||
28 | var _inverse_item_locks = {} | ||
29 | var _held_letters = {} | ||
30 | var _letters_setup = false | ||
31 | var _already_connected = false | ||
32 | |||
33 | const kSHUFFLE_LETTERS_VANILLA = 0 | ||
34 | const kSHUFFLE_LETTERS_UNLOCKED = 1 | ||
35 | const kSHUFFLE_LETTERS_PROGRESSIVE = 2 | ||
36 | const kSHUFFLE_LETTERS_VANILLA_CYAN = 3 | ||
37 | const kSHUFFLE_LETTERS_ITEM_CYAN = 4 | ||
38 | |||
39 | const kLETTER_BEHAVIOR_VANILLA = 0 | ||
40 | const kLETTER_BEHAVIOR_ITEM = 1 | ||
41 | const kLETTER_BEHAVIOR_UNLOCKED = 2 | ||
42 | |||
43 | const kCYAN_DOOR_BEHAVIOR_H2 = 0 | ||
44 | const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1 | ||
45 | const kCYAN_DOOR_BEHAVIOR_ITEM = 2 | ||
46 | |||
47 | const kEndingNameByVictoryValue = { | ||
48 | 0: "GRAY", | ||
49 | 1: "PURPLE", | ||
50 | 2: "MINT", | ||
51 | 3: "BLACK", | ||
52 | 4: "BLUE", | ||
53 | 5: "CYAN", | ||
54 | 6: "RED", | ||
55 | 7: "PLUM", | ||
56 | 8: "ORANGE", | ||
57 | 9: "GOLD", | ||
58 | 10: "YELLOW", | ||
59 | 11: "GREEN", | ||
60 | 12: "WHITE", | ||
61 | } | ||
62 | |||
63 | var apworld_version = [0, 0, 0] | ||
64 | var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 | ||
65 | var daedalus_roof_access = false | ||
66 | var keyholder_sanity = false | ||
67 | var port_pairings = {} | ||
68 | var shuffle_control_center_colors = false | ||
69 | var shuffle_doors = false | ||
70 | var shuffle_gallery_paintings = false | ||
71 | var shuffle_letters = kSHUFFLE_LETTERS_VANILLA | ||
72 | var shuffle_symbols = false | ||
73 | var shuffle_worldports = false | ||
74 | var strict_cyan_ending = false | ||
75 | var strict_purple_ending = false | ||
76 | var victory_condition = -1 | ||
77 | |||
78 | var color_by_material_path = {} | ||
79 | |||
80 | signal could_not_connect | ||
81 | signal connect_status | ||
82 | signal ap_connected | ||
83 | |||
84 | |||
85 | func _init(): | ||
86 | # Read AP settings from file, if there are any | ||
87 | if FileAccess.file_exists("user://ap_settings"): | ||
88 | var file = FileAccess.open("user://ap_settings", FileAccess.READ) | ||
89 | var data = file.get_var(true) | ||
90 | file.close() | ||
91 | |||
92 | if typeof(data) != TYPE_ARRAY: | ||
93 | global._print("AP settings file is corrupted") | ||
94 | data = [] | ||
95 | |||
96 | if data.size() > 0: | ||
97 | ap_server = data[0] | ||
98 | |||
99 | if data.size() > 1: | ||
100 | ap_user = data[1] | ||
101 | |||
102 | if data.size() > 2: | ||
103 | ap_pass = data[2] | ||
104 | |||
105 | if data.size() > 3: | ||
106 | connection_history = data[3] | ||
107 | |||
108 | if data.size() > 4: | ||
109 | show_compass = data[4] | ||
110 | |||
111 | if data.size() > 5: | ||
112 | show_locations = data[5] | ||
113 | |||
114 | if data.size() > 6: | ||
115 | show_minimap = data[6] | ||
116 | |||
117 | # We need to create a mapping from material paths to the original colors of | ||
118 | # those materials. We force reload the materials, overwriting any custom | ||
119 | # textures, and create the mapping. We then reload the textures in case the | ||
120 | # player had a custom one enabled. | ||
121 | var directory = DirAccess.open("res://assets/materials") | ||
122 | for material_name in directory.get_files(): | ||
123 | var material = ResourceLoader.load( | ||
124 | "res://assets/materials/" + material_name, "", ResourceLoader.CACHE_MODE_REPLACE | ||
125 | ) | ||
126 | |||
127 | color_by_material_path[material.resource_path] = Color(material.albedo_color) | ||
128 | |||
129 | settings.load_user_textures() | ||
130 | |||
131 | |||
132 | func _ready(): | ||
133 | client = SCRIPT_client.new() | ||
134 | client.SCRIPT_websocketserver = SCRIPT_websocketserver | ||
135 | |||
136 | client.item_received.connect(_process_item) | ||
137 | client.location_scout_received.connect(_process_location_scout) | ||
138 | client.text_message_received.connect(_process_text_message) | ||
139 | client.item_sent_notification.connect(_process_item_sent_notification) | ||
140 | client.hint_received.connect(_process_hint_received) | ||
141 | client.accessible_locations_updated.connect(_on_accessible_locations_updated) | ||
142 | client.checked_locations_updated.connect(_on_checked_locations_updated) | ||
143 | client.checked_worldports_updated.connect(_on_checked_worldports_updated) | ||
144 | |||
145 | client.could_not_connect.connect(_client_could_not_connect) | ||
146 | client.connect_status.connect(_client_connect_status) | ||
147 | client.client_connected.connect(_client_connected) | ||
148 | |||
149 | add_child(client) | ||
150 | |||
151 | keyboard = SCRIPT_keyboard.new() | ||
152 | add_child(keyboard) | ||
153 | client.keyboard_update_received.connect(keyboard.remote_keyboard_updated) | ||
154 | |||
155 | |||
156 | func saveSettings(): | ||
157 | # Save the AP settings to disk. | ||
158 | var path = "user://ap_settings" | ||
159 | var file = FileAccess.open(path, FileAccess.WRITE) | ||
160 | |||
161 | var data = [ | ||
162 | ap_server, | ||
163 | ap_user, | ||
164 | ap_pass, | ||
165 | connection_history, | ||
166 | show_compass, | ||
167 | show_locations, | ||
168 | show_minimap, | ||
169 | ] | ||
170 | file.store_var(data, true) | ||
171 | file.close() | ||
172 | |||
173 | |||
174 | func saveLocaldata(): | ||
175 | # Save the MW/slot specific settings to disk. | ||
176 | var dir = DirAccess.open("user://") | ||
177 | var folder = "archipelago_data" | ||
178 | if not dir.dir_exists(folder): | ||
179 | dir.make_dir(folder) | ||
180 | |||
181 | var file = FileAccess.open(_localdata_file, FileAccess.WRITE) | ||
182 | |||
183 | var data = [ | ||
184 | _last_new_item, | ||
185 | ] | ||
186 | file.store_var(data, true) | ||
187 | file.close() | ||
188 | |||
189 | |||
190 | func connectToServer(): | ||
191 | _last_new_item = -1 | ||
192 | _batch_locations = false | ||
193 | _held_locations = [] | ||
194 | _held_location_scouts = [] | ||
195 | _location_scouts = {} | ||
196 | _letters_setup = false | ||
197 | _held_letters = {} | ||
198 | _already_connected = false | ||
199 | |||
200 | client.connectToServer(ap_server, ap_user, ap_pass) | ||
201 | |||
202 | |||
203 | func getSaveFileName(): | ||
204 | return "zzAP_%s_%d" % [client._seed, client._slot] | ||
205 | |||
206 | |||
207 | func disconnect_from_ap(): | ||
208 | _already_connected = false | ||
209 | |||
210 | var effects = global.get_node("Effects") | ||
211 | effects.set_connection_lost(false) | ||
212 | |||
213 | client.disconnect_from_ap() | ||
214 | |||
215 | |||
216 | func get_item_id_for_door(door_id): | ||
217 | return _item_locks.get(door_id, null) | ||
218 | |||
219 | |||
220 | func _process_item(item, amount): | ||
221 | var gamedata = global.get_node("Gamedata") | ||
222 | |||
223 | var item_id = int(item["id"]) | ||
224 | var prog_id = null | ||
225 | if _inverse_item_locks.has(item_id): | ||
226 | for lock in _inverse_item_locks.get(item_id): | ||
227 | if lock[1] != amount: | ||
228 | continue | ||
229 | |||
230 | if gamedata.progressive_id_by_ap_id.has(item_id): | ||
231 | prog_id = lock[0] | ||
232 | |||
233 | if gamedata.get_door_map_name(lock[0]) != global.map: | ||
234 | continue | ||
235 | |||
236 | # TODO: fix doors opening from door groups | ||
237 | var receivers = gamedata.get_door_receivers(lock[0]) | ||
238 | var scene = get_tree().get_root().get_node_or_null("scene") | ||
239 | if scene != null: | ||
240 | for receiver in receivers: | ||
241 | var rnode = scene.get_node_or_null(receiver) | ||
242 | if rnode != null: | ||
243 | rnode.handleTriggered() | ||
244 | |||
245 | var letter_id = gamedata.letter_id_by_ap_id.get(item_id, null) | ||
246 | if letter_id != null: | ||
247 | var letter = gamedata.objects.get_letters()[letter_id] | ||
248 | if not letter.has_level2() or not letter.get_level2(): | ||
249 | _process_key_item(letter.get_key(), amount) | ||
250 | |||
251 | if gamedata.symbol_item_ids.has(item_id): | ||
252 | var player = get_tree().get_root().get_node_or_null("scene/player") | ||
253 | if player != null: | ||
254 | player.evaluate_solvability.emit() | ||
255 | |||
256 | if item_id == gamedata.objects.get_special_ids()["A Job Well Done"]: | ||
257 | update_job_well_done_sign() | ||
258 | |||
259 | # Show a message about the item if it's new. | ||
260 | if int(item["index"]) > _last_new_item: | ||
261 | _last_new_item = int(item["index"]) | ||
262 | saveLocaldata() | ||
263 | |||
264 | var full_item_name = item["text"] | ||
265 | if prog_id != null: | ||
266 | var door = gamedata.objects.get_doors()[prog_id] | ||
267 | full_item_name = "%s (%s)" % [full_item_name, door.get_name()] | ||
268 | |||
269 | var message | ||
270 | if "sender" in item: | ||
271 | message = ( | ||
272 | "Received %s from %s" | ||
273 | % [wrapInItemColorTags(full_item_name, item["flags"]), item["sender"]] | ||
274 | ) | ||
275 | else: | ||
276 | message = "Found %s" % wrapInItemColorTags(full_item_name, item["flags"]) | ||
277 | |||
278 | if gamedata.anti_trap_ids.has(item): | ||
279 | keyboard.block_letter(gamedata.anti_trap_ids[item]) | ||
280 | |||
281 | global._print(message) | ||
282 | |||
283 | global.get_node("Messages").showMessage(message) | ||
284 | |||
285 | |||
286 | func _process_item_sent_notification(message): | ||
287 | var sentMsg = ( | ||
288 | "Sent %s to %s" | ||
289 | % [ | ||
290 | wrapInItemColorTags(message["item_name"], message["item_flags"]), | ||
291 | message["receiver_name"] | ||
292 | ] | ||
293 | ) | ||
294 | #if _hinted_locations.has(message["item"]["location"]): | ||
295 | # sentMsg += " ([color=#fafad2]Hinted![/color])" | ||
296 | global.get_node("Messages").showMessage(sentMsg) | ||
297 | |||
298 | |||
299 | func _process_hint_received(message): | ||
300 | var is_for = "" | ||
301 | if message["self"] == 0: | ||
302 | is_for = " for %s" % message["receiver_name"] | ||
303 | |||
304 | global.get_node("Messages").showMessage( | ||
305 | ( | ||
306 | "Hint: %s%s is on %s" | ||
307 | % [ | ||
308 | wrapInItemColorTags(message["item_name"], message["item_flags"]), | ||
309 | is_for, | ||
310 | message["location_name"] | ||
311 | ] | ||
312 | ) | ||
313 | ) | ||
314 | |||
315 | |||
316 | func _process_text_message(message): | ||
317 | var parts = [] | ||
318 | for message_part in message: | ||
319 | if message_part["type"] == "text": | ||
320 | parts.append(message_part["text"]) | ||
321 | elif message_part["type"] == "player": | ||
322 | if message_part["self"] == 1: | ||
323 | parts.append("[color=#ee00ee]%s[/color]" % message_part["text"]) | ||
324 | else: | ||
325 | parts.append("[color=#fafad2]%s[/color]" % message_part["text"]) | ||
326 | elif message_part["type"] == "item": | ||
327 | parts.append(wrapInItemColorTags(message_part["text"], int(message_part["flags"]))) | ||
328 | elif message_part["type"] == "location": | ||
329 | parts.append("[color=#00ff7f]%s[/color]" % message_part["text"]) | ||
330 | |||
331 | var textclient_node = global.get_node("Textclient") | ||
332 | if textclient_node != null: | ||
333 | textclient_node.parse_printjson("".join(parts)) | ||
334 | |||
335 | |||
336 | func _process_location_scout(location_id, item_name, player_name, flags, for_self): | ||
337 | _location_scouts[location_id] = { | ||
338 | "item": item_name, "player": player_name, "flags": flags, "for_self": for_self | ||
339 | } | ||
340 | |||
341 | if for_self and flags & 4 != 0: | ||
342 | # This is a trap for us, so let's not display it. | ||
343 | return | ||
344 | |||
345 | var gamedata = global.get_node("Gamedata") | ||
346 | var map_id = gamedata.map_id_by_name.get(global.map) | ||
347 | |||
348 | var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null) | ||
349 | if letter_id != null: | ||
350 | var letter = gamedata.objects.get_letters()[letter_id] | ||
351 | var room = gamedata.objects.get_rooms()[letter.get_room_id()] | ||
352 | if room.get_map_id() == map_id: | ||
353 | var collectable = get_tree().get_root().get_node("scene").get_node_or_null( | ||
354 | letter.get_path() | ||
355 | ) | ||
356 | if collectable != null: | ||
357 | collectable.setScoutedText(item_name) | ||
358 | |||
359 | |||
360 | func _on_accessible_locations_updated(): | ||
361 | var textclient_node = global.get_node("Textclient") | ||
362 | if textclient_node != null: | ||
363 | textclient_node.update_locations() | ||
364 | |||
365 | |||
366 | func _on_checked_locations_updated(): | ||
367 | var textclient_node = global.get_node("Textclient") | ||
368 | if textclient_node != null: | ||
369 | textclient_node.update_locations(false) | ||
370 | |||
371 | |||
372 | func _on_checked_worldports_updated(): | ||
373 | var textclient_node = global.get_node("Textclient") | ||
374 | if textclient_node != null: | ||
375 | textclient_node.update_locations() | ||
376 | textclient_node.update_worldports() | ||
377 | |||
378 | |||
379 | func _client_could_not_connect(message): | ||
380 | could_not_connect.emit(message) | ||
381 | |||
382 | if global.loaded: | ||
383 | var effects = global.get_node("Effects") | ||
384 | effects.set_connection_lost(true) | ||
385 | |||
386 | var messages = global.get_node("Messages") | ||
387 | messages.showMessage("Connection to multiworld lost.") | ||
388 | |||
389 | |||
390 | func _client_connect_status(message): | ||
391 | connect_status.emit(message) | ||
392 | |||
393 | |||
394 | func _client_connected(slot_data): | ||
395 | var effects = global.get_node("Effects") | ||
396 | effects.set_connection_lost(false) | ||
397 | |||
398 | if _already_connected: | ||
399 | var messages = global.get_node("Messages") | ||
400 | messages.showMessage("Reconnected to multiworld!") | ||
401 | return | ||
402 | |||
403 | _already_connected = true | ||
404 | |||
405 | var gamedata = global.get_node("Gamedata") | ||
406 | |||
407 | _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot] | ||
408 | _last_new_item = -1 | ||
409 | |||
410 | if FileAccess.file_exists(_localdata_file): | ||
411 | var ap_file = FileAccess.open(_localdata_file, FileAccess.READ) | ||
412 | var localdata = [] | ||
413 | if ap_file != null: | ||
414 | localdata = ap_file.get_var(true) | ||
415 | ap_file.close() | ||
416 | |||
417 | if typeof(localdata) != TYPE_ARRAY: | ||
418 | print("AP localdata file is corrupted") | ||
419 | localdata = [] | ||
420 | |||
421 | if localdata.size() > 0: | ||
422 | _last_new_item = localdata[0] | ||
423 | |||
424 | # Read slot data. | ||
425 | cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0)) | ||
426 | daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) | ||
427 | keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) | ||
428 | shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) | ||
429 | shuffle_doors = bool(slot_data.get("shuffle_doors", false)) | ||
430 | shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false)) | ||
431 | shuffle_letters = int(slot_data.get("shuffle_letters", 0)) | ||
432 | shuffle_symbols = bool(slot_data.get("shuffle_symbols", false)) | ||
433 | shuffle_worldports = bool(slot_data.get("shuffle_worldports", false)) | ||
434 | strict_cyan_ending = bool(slot_data.get("strict_cyan_ending", false)) | ||
435 | strict_purple_ending = bool(slot_data.get("strict_purple_ending", false)) | ||
436 | victory_condition = int(slot_data.get("victory_condition", 0)) | ||
437 | |||
438 | if slot_data.has("version"): | ||
439 | var version_msg = slot_data["version"] | ||
440 | apworld_version = [int(version_msg[0]), int(version_msg[1]), 0] | ||
441 | if version_msg.size() > 2: | ||
442 | apworld_version[2] = int(version_msg[2]) | ||
443 | |||
444 | port_pairings.clear() | ||
445 | if slot_data.has("port_pairings"): | ||
446 | var raw_pp = slot_data.get("port_pairings") | ||
447 | |||
448 | for p1 in raw_pp.keys(): | ||
449 | port_pairings[int(p1)] = int(raw_pp[p1]) | ||
450 | |||
451 | # Set up item locks. | ||
452 | _item_locks = {} | ||
453 | |||
454 | if shuffle_doors: | ||
455 | for door in gamedata.objects.get_doors(): | ||
456 | if ( | ||
457 | door.get_type() == gamedata.SCRIPT_proto.DoorType.STANDARD | ||
458 | or door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY | ||
459 | ): | ||
460 | _item_locks[door.get_id()] = [door.get_ap_id(), 1] | ||
461 | |||
462 | for progressive in gamedata.objects.get_progressives(): | ||
463 | for i in range(0, progressive.get_doors().size()): | ||
464 | var door = gamedata.objects.get_doors()[progressive.get_doors()[i]] | ||
465 | _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1] | ||
466 | |||
467 | for door_group in gamedata.objects.get_door_groups(): | ||
468 | if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR: | ||
469 | if shuffle_worldports: | ||
470 | continue | ||
471 | elif door_group.get_type() != gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP: | ||
472 | continue | ||
473 | |||
474 | for door in door_group.get_doors(): | ||
475 | _item_locks[door] = [door_group.get_ap_id(), 1] | ||
476 | |||
477 | if shuffle_control_center_colors: | ||
478 | for door in gamedata.objects.get_doors(): | ||
479 | if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR: | ||
480 | _item_locks[door.get_id()] = [door.get_ap_id(), 1] | ||
481 | |||
482 | for door_group in gamedata.objects.get_door_groups(): | ||
483 | if ( | ||
484 | door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR | ||
485 | and not shuffle_worldports | ||
486 | ): | ||
487 | for door in door_group.get_doors(): | ||
488 | _item_locks[door] = [door_group.get_ap_id(), 1] | ||
489 | |||
490 | if shuffle_gallery_paintings: | ||
491 | for door in gamedata.objects.get_doors(): | ||
492 | if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING: | ||
493 | _item_locks[door.get_id()] = [door.get_ap_id(), 1] | ||
494 | |||
495 | if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM: | ||
496 | for door_group in gamedata.objects.get_door_groups(): | ||
497 | if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS: | ||
498 | for door in door_group.get_doors(): | ||
499 | if not _item_locks.has(door): | ||
500 | _item_locks[door] = [door_group.get_ap_id(), 1] | ||
501 | |||
502 | # Create a reverse item locks map for processing items. | ||
503 | _inverse_item_locks = {} | ||
504 | |||
505 | for door_id in _item_locks.keys(): | ||
506 | var lock = _item_locks.get(door_id) | ||
507 | |||
508 | if not _inverse_item_locks.has(lock[0]): | ||
509 | _inverse_item_locks[lock[0]] = [] | ||
510 | |||
511 | _inverse_item_locks[lock[0]].append([door_id, lock[1]]) | ||
512 | |||
513 | if shuffle_worldports: | ||
514 | var textclient = global.get_node("Textclient") | ||
515 | textclient.setup_worldports() | ||
516 | |||
517 | ap_connected.emit() | ||
518 | |||
519 | |||
520 | func start_batching_locations(): | ||
521 | _batch_locations = true | ||
522 | |||
523 | |||
524 | func send_location(loc_id): | ||
525 | if client._checked_locations.has(loc_id): | ||
526 | return | ||
527 | |||
528 | if _batch_locations: | ||
529 | _held_locations.append(loc_id) | ||
530 | else: | ||
531 | client.sendLocation(loc_id) | ||
532 | |||
533 | |||
534 | func scout_location(loc_id): | ||
535 | if _location_scouts.has(loc_id): | ||
536 | return _location_scouts.get(loc_id) | ||
537 | |||
538 | if _batch_locations: | ||
539 | _held_location_scouts.append(loc_id) | ||
540 | else: | ||
541 | client.scoutLocation(loc_id) | ||
542 | |||
543 | return null | ||
544 | |||
545 | |||
546 | func stop_batching_locations(): | ||
547 | _batch_locations = false | ||
548 | |||
549 | if not _held_locations.is_empty(): | ||
550 | client.sendLocations(_held_locations) | ||
551 | _held_locations.clear() | ||
552 | |||
553 | if not _held_location_scouts.is_empty(): | ||
554 | client.scoutLocations(_held_location_scouts) | ||
555 | _held_location_scouts.clear() | ||
556 | |||
557 | |||
558 | func colorForItemType(flags): | ||
559 | var int_flags = int(flags) | ||
560 | if int_flags & 1: # progression | ||
561 | if int_flags & 2: # proguseful | ||
562 | return "#f0d200" | ||
563 | else: | ||
564 | return "#bc51e0" | ||
565 | elif int_flags & 2: # useful | ||
566 | return "#2b67ff" | ||
567 | elif int_flags & 4: # trap | ||
568 | return "#d63a22" | ||
569 | else: # filler | ||
570 | return "#14de9e" | ||
571 | |||
572 | |||
573 | func wrapInItemColorTags(text, flags): | ||
574 | var int_flags = int(flags) | ||
575 | if int_flags & 1 and int_flags & 2: # proguseful | ||
576 | return "[rainbow]%s[/rainbow]" % text | ||
577 | else: | ||
578 | return "[color=%s]%s[/color]" % [colorForItemType(flags), text] | ||
579 | |||
580 | |||
581 | func get_letter_behavior(key, level2): | ||
582 | if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED: | ||
583 | return kLETTER_BEHAVIOR_UNLOCKED | ||
584 | |||
585 | if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters): | ||
586 | if level2: | ||
587 | if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN: | ||
588 | return kLETTER_BEHAVIOR_VANILLA | ||
589 | else: | ||
590 | return kLETTER_BEHAVIOR_ITEM | ||
591 | else: | ||
592 | return kLETTER_BEHAVIOR_UNLOCKED | ||
593 | |||
594 | if not level2 and ["h", "i", "n", "t"].has(key): | ||
595 | # This differs from the equivalent function in the apworld. Logically it is | ||
596 | # the same as UNLOCKED since they are in the starting room, but VANILLA | ||
597 | # means the player still has to actually pick up the letters. | ||
598 | return kLETTER_BEHAVIOR_VANILLA | ||
599 | |||
600 | if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE: | ||
601 | return kLETTER_BEHAVIOR_ITEM | ||
602 | |||
603 | return kLETTER_BEHAVIOR_VANILLA | ||
604 | |||
605 | |||
606 | func setup_keys(): | ||
607 | keyboard.load_seed() | ||
608 | |||
609 | _letters_setup = true | ||
610 | |||
611 | for k in _held_letters.keys(): | ||
612 | _process_key_item(k, _held_letters[k]) | ||
613 | |||
614 | _held_letters.clear() | ||
615 | |||
616 | |||
617 | func _process_key_item(key, level): | ||
618 | if not _letters_setup: | ||
619 | _held_letters[key] = max(_held_letters.get(key, 0), level) | ||
620 | return | ||
621 | |||
622 | if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN: | ||
623 | level += 1 | ||
624 | |||
625 | keyboard.collect_remote_letter(key, level) | ||
626 | |||
627 | |||
628 | func update_job_well_done_sign(): | ||
629 | if global.map != "daedalus": | ||
630 | return | ||
631 | |||
632 | var gamedata = global.get_node("Gamedata") | ||
633 | var job_item = gamedata.objects.get_special_ids()["A Job Well Done"] | ||
634 | var jobs_done = client.getItemAmount(job_item) | ||
635 | |||
636 | var sign2 = get_tree().get_root().get_node_or_null("scene/Meshes/Miscellaneous/sign2") | ||
637 | var sign3 = get_tree().get_root().get_node_or_null("scene/Meshes/Miscellaneous/sign3") | ||
638 | |||
639 | if sign2 != null and sign3 != null: | ||
640 | if jobs_done == 0: | ||
641 | sign2.text = "what are you doing" | ||
642 | sign3.text = "?" | ||
643 | elif jobs_done == 1: | ||
644 | sign2.text = "a job well done" | ||
645 | sign3.text = "is its own reward" | ||
646 | else: | ||
647 | sign2.text = "%d jobs well done" % jobs_done | ||
648 | sign3.text = "are their own reward" | ||
649 | |||
650 | sign2.get_node("MeshInstance3D").mesh.text = sign2.text | ||
651 | sign3.get_node("MeshInstance3D").mesh.text = sign3.text | ||